Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/troika-3d-text/src/facade/Text3DFacade.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const TEXT_MESH_PROPS = [
'textAlign',
'textIndent',
'whiteSpace',
'enableLigatures',
'material',
'color',
'colorRanges',
Expand Down
3 changes: 3 additions & 0 deletions packages/troika-examples/text/TextExample.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class TextExample extends React.Component {
textScale: 1,
lineHeight: 1.15,
letterSpacing: 0,
enableLigatures: true,
maxWidth: 2, //2m
textAlign: 'justify',
textIndent: 0,
Expand Down Expand Up @@ -217,6 +218,7 @@ class TextExample extends React.Component {
textIndent: state.textIndent,
lineHeight: state.lineHeight,
letterSpacing: state.letterSpacing,
enableLigatures: state.enableLigatures,
anchorX: state.anchorX,
anchorY: state.anchorY,
selectable: state.selectable,
Expand Down Expand Up @@ -342,6 +344,7 @@ class TextExample extends React.Component {
{type: 'number', path: "maxWidth", min: 1, max: 5, step: 0.01},
{type: 'number', path: "lineHeight", min: 1, max: 2, step: 0.01},
{type: 'number', path: "letterSpacing", min: -0.1, max: 0.5, step: 0.01},
{type: 'boolean', path: "enableLigatures", label: "Ligatures"},
{type: 'number', path: "fillOpacity", min: 0, max: 1, step: 0.0001},
{type: 'number', path: "curveRadius", min: -5, max: 5, step: 0.001},

Expand Down
41 changes: 23 additions & 18 deletions packages/troika-three-text/src/FontParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { defineWorkerModule } from 'troika-worker-utils'
* @property {number} descender
* @property {number} xHeight
* @property {(number) => boolean} supportsCodePoint
* @property {(text:string, fontSize:number, letterSpacing:number, callback) => number} forEachGlyph
* @property {(text:string, fontSize:number, letterSpacing:number, enableLigatures:boolean, callback) => number} forEachGlyph
* @property {number} lineGap
* @property {number} capHeight
* @property {number} unitsPerEm
Expand Down Expand Up @@ -122,34 +122,39 @@ function parserFactory(Typr, woff2otf) {
return joiningForms
}

function stringToGlyphs (font, str) {
function stringToGlyphs(font, str, letterSpacing, enableLigatures) {
const glyphIds = []
for (let i = 0; i < str.length; i++) {
const cc = str.codePointAt(i)
if (cc > 0xffff) i++
glyphIds.push(Typr.U.codeToGlyph(font, cc))
}

if (!enableLigatures) return glyphIds

const gsub = font['GSUB']
if (gsub) {
const {lookupList, featureList} = gsub
const { lookupList, featureList } = gsub
let joiningForms
const supportedFeatures = /^(rlig|liga|mset|isol|init|fina|medi|half|pres|blws|ccmp)$/
// These features are ignored when non-zero letter spacing is applied
const ignoreLetterSpacingFeatures = /^(liga|dlig|clig)$/
const usedLookups = []
featureList.forEach(feature => {
if (supportedFeatures.test(feature.tag)) {
for (let ti = 0; ti < feature.tab.length; ti++) {
if (usedLookups[feature.tab[ti]]) continue
usedLookups[feature.tab[ti]] = true
const tab = lookupList[feature.tab[ti]]
const isJoiningFeature = /^(isol|init|fina|medi)$/.test(feature.tag)
if (isJoiningFeature && !joiningForms) { //lazy
joiningForms = detectJoiningForms(str)
}
for (let ci = 0; ci < glyphIds.length; ci++) {
if (!joiningForms || !isJoiningFeature || formsToFeatures[joiningForms[ci]] === feature.tag) {
Typr.U._applySubs(glyphIds, ci, tab, lookupList)
}
if (!supportedFeatures.test(feature.tag)) return
if (letterSpacing !== 0 && ignoreLetterSpacingFeatures.test(feature.tag)) return

for (let ti = 0; ti < feature.tab.length; ti++) {
if (usedLookups[feature.tab[ti]]) continue
usedLookups[feature.tab[ti]] = true
const tab = lookupList[feature.tab[ti]]
const isJoiningFeature = /^(isol|init|fina|medi)$/.test(feature.tag)
if (isJoiningFeature && !joiningForms) { //lazy
joiningForms = detectJoiningForms(str)
}
for (let ci = 0; ci < glyphIds.length; ci++) {
if (!joiningForms || !isJoiningFeature || formsToFeatures[joiningForms[ci]] === feature.tag) {
Typr.U._applySubs(glyphIds, ci, tab, lookupList)
}
}
}
Expand Down Expand Up @@ -321,11 +326,11 @@ function parserFactory(Typr, woff2otf) {
supportsCodePoint(code) {
return Typr.U.codeToGlyph(typrFont, code) > 0
},
forEachGlyph(text, fontSize, letterSpacing, callback) {
forEachGlyph(text, fontSize, letterSpacing, enableLigatures, callback) {
let penX = 0
const fontScale = 1 / fontObj.unitsPerEm * fontSize

const glyphIds = stringToGlyphs(typrFont, text)
const glyphIds = stringToGlyphs(typrFont, text, letterSpacing, enableLigatures)
let charIndex = 0
const positions = calcGlyphPositions(typrFont, glyphIds)

Expand Down
9 changes: 9 additions & 0 deletions packages/troika-three-text/src/Text.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const SYNCABLE_PROPS = [
'textAlign',
'textIndent',
'whiteSpace',
'enableLigatures',
'anchorX',
'anchorY',
'colorRanges',
Expand Down Expand Up @@ -222,6 +223,13 @@ class Text extends Mesh {
*/
this.whiteSpace = 'normal'

/**
* @member {boolean} enableLigatures
* Toggles ligature substitution.
* Defaults to true.
*/
this.enableLigatures = true


// === Presentation properties: === //

Expand Down Expand Up @@ -432,6 +440,7 @@ class Text extends Mesh {
textAlign: this.textAlign,
textIndent: this.textIndent,
whiteSpace: this.whiteSpace,
enableLigatures: this.enableLigatures,
overflowWrap: this.overflowWrap,
anchorX: this.anchorX,
anchorY: this.anchorY,
Expand Down
6 changes: 4 additions & 2 deletions packages/troika-three-text/src/Typesetter.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* @property {string} [textAlign='left']
* @property {number} [textIndent=0]
* @property {'normal'|'nowrap'} [whiteSpace='normal']
* @property {boolean} [enableLigatures=true]
* @property {'normal'|'break-word'} [overflowWrap='normal']
* @property {AnchorXValue} [anchorX=0]
* @property {AnchorYValue} [anchorY=0]
Expand Down Expand Up @@ -156,6 +157,7 @@ export function createTypesetter(resolveFonts, bidi) {
textAlign='left',
textIndent=0,
whiteSpace='normal',
enableLigatures=true,
overflowWrap='normal',
anchorX = 0,
anchorY = 0,
Expand Down Expand Up @@ -256,7 +258,7 @@ export function createTypesetter(resolveFonts, bidi) {

const runText = text.slice(run.start, run.end + 1)
let prevGlyphX, prevGlyphObj
fontObj.forEachGlyph(runText, fontSize, letterSpacing, (glyphObj, glyphX, glyphY, charIndex) => {
fontObj.forEachGlyph(runText, fontSize, letterSpacing, enableLigatures, (glyphObj, glyphX, glyphY, charIndex) => {
glyphX += prevRunEndX
charIndex += run.start
prevGlyphX = glyphX
Expand Down Expand Up @@ -503,7 +505,7 @@ export function createTypesetter(resolveFonts, bidi) {
if (rtl) {
const mirrored = bidi.getMirroredCharacter(text[glyphInfo.charIndex])
if (mirrored) {
glyphInfo.fontData.fontObj.forEachGlyph(mirrored, 0, 0, setGlyphObj)
glyphInfo.fontData.fontObj.forEachGlyph(mirrored, 0, 0, enableLigatures, setGlyphObj)
}
}

Expand Down