-
Notifications
You must be signed in to change notification settings - Fork 501
feat: Support for Mark to Base Attachment Positioning (incl. GPOS 4, 9) #557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 5 commits
09ba12f
5580fb6
6e07277
d1c3fd3
30ef17c
951b9f9
0c78812
a48ceb6
89c8b46
c262bc9
ca8e020
5485849
d8fd81d
d224142
6c68a11
0a8f62f
78847cd
0de1b3f
14d9ad6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './kern'; | ||
export * from './mark'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* Apply kerning positioning advance glyphs advance | ||
*/ | ||
|
||
function kern(lookupTable, glyphs) { | ||
const coords = []; | ||
for (let i = 0; i < glyphs.length; i += 1) { | ||
const glyph = glyphs[i]; | ||
coords[i] = { xAdvance: 0, yAdvance: 0 }; | ||
if (i > 0) { | ||
coords[i] = { | ||
xAdvance: this.position.getKerningValue([lookupTable], glyphs[i - 1].index, glyph.index), | ||
yAdvance: 0 | ||
}; | ||
} | ||
} | ||
return coords; | ||
} | ||
|
||
export { kern }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* Apply MarkToBase positioning advance glyphs advance | ||
*/ | ||
|
||
function mark(lookupTable, glyphs) { | ||
const coords = []; | ||
for (let i = 0; i < glyphs.length; i += 1) { | ||
const glyph = glyphs[i]; | ||
coords[i] = { xAdvance: 0, yAdvance: 0 }; | ||
if (i > 0) { | ||
const coordinatedPair = this.position.getMarkToBaseAttachment([lookupTable], glyphs[i - 1].index, glyph.index); | ||
if (coordinatedPair) { | ||
const { attachmentMarkPoint, baseMarkPoint } = coordinatedPair; | ||
// Base mark's advanceWidth must be ignored to have a proper positiong for the attachment mark | ||
coords[i] = { | ||
xAdvance: baseMarkPoint.xCoordinate - attachmentMarkPoint.xCoordinate - glyphs[i - 1].advanceWidth, | ||
yAdvance: baseMarkPoint.yCoordinate - attachmentMarkPoint.yCoordinate | ||
}; | ||
} | ||
} | ||
} | ||
return coords; | ||
} | ||
|
||
export { mark }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,9 +64,10 @@ function searchRange(ranges, value) { | |
* @exports opentype.Layout | ||
* @class | ||
*/ | ||
function Layout(font, tableName) { | ||
function Layout(font, tableName, supportedFeatures) { | ||
this.font = font; | ||
this.tableName = tableName; | ||
this.supportedFeatures = supportedFeatures || []; | ||
} | ||
|
||
Layout.prototype = { | ||
|
@@ -194,6 +195,61 @@ Layout.prototype = { | |
} | ||
}, | ||
|
||
/** | ||
* Returns an ordered, union lookup tables for all requested features. | ||
* This follows an ordered processing requirements (specs): | ||
* > During text processing, it processes the lookups referenced by that feature in their lookup list order. | ||
* > Note that an application may process lookups for multiple features simultaneously. In this case: | ||
* > the list of lookups is the union of lookups referenced by all of those features, and these are all processed in their lookup list order. | ||
* | ||
* https://learn.microsoft.com/en-us/typography/opentype/otspec191alpha/chapter2#lookup-list-table | ||
* | ||
* @param {string[]} requestedFeatures | ||
* @param {string} [script='DFLT'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dflt should be lower case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. according to the spec it's uppercase: https://learn.microsoft.com/en-us/typography/opentype/spec/scripttags |
||
* @param {string} [language='dlft'] | ||
* @return {Object[]} an ordered lookup list of requested features | ||
*/ | ||
getFeaturesLookups: function(requestedFeatures, script, language) { | ||
if (!this.font.tables[this.tableName] || !requestedFeatures) { | ||
return []; | ||
} | ||
|
||
// Fitler out only supported by layout table features | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just y small typo, Fitler => Filter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
requestedFeatures = this.supportedFeatures.filter(f => requestedFeatures.includes(f.featureName)); | ||
|
||
const lookupUnionList = {}; | ||
const allLookups = this.font.tables[this.tableName].lookups; | ||
requestedFeatures.forEach(feature => { | ||
const { featureName, supportedLookups } = feature; | ||
let featureTable = this.getFeatureTable(script, language, featureName); | ||
if (featureTable && supportedLookups.length) { | ||
let lookupTable; | ||
const lookupListIndexes = featureTable.lookupListIndexes; | ||
for (let i = 0; i < lookupListIndexes.length; i++) { | ||
const idx = `idx${lookupListIndexes[i]}`; | ||
if (lookupUnionList.hasOwnProperty(idx)) continue; // Skips a lookup table that is already on the processing list | ||
lookupTable = allLookups[lookupListIndexes[i]]; | ||
if (!lookupTable) continue; | ||
let validLookupType = supportedLookups.indexOf(lookupTable.lookupType) !== -1; | ||
// Extension lookup table support | ||
if (lookupTable.subtables.length === 1) { | ||
const { extensionLookupType, extension } = lookupTable.subtables[0]; | ||
if (extensionLookupType && extension && supportedLookups.indexOf(extensionLookupType) !== -1) { | ||
lookupTable.lookupType = extensionLookupType; | ||
lookupTable.subtables = [extension]; | ||
validLookupType = true; | ||
} | ||
} | ||
if (validLookupType) { | ||
lookupTable.feature = featureName; | ||
lookupUnionList[idx] = lookupTable; | ||
} | ||
} | ||
} | ||
}); | ||
return Object.values(lookupUnionList); | ||
}, | ||
|
||
/** | ||
* Get a specific feature table. | ||
* @instance | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -477,6 +477,74 @@ Parser.prototype.parseCoverage = function() { | |
throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); | ||
}; | ||
|
||
/** | ||
* Parse a BaseArray Table in GPOS table | ||
* https://learn.microsoft.com/en-us/typography/opentype/otspec191alpha/gpos#lookup-type-4-mark-to-base-attachment-positioning-subtable | ||
* | ||
* @param {Number} marksClassCount | ||
* @returns {Array} | ||
*/ | ||
Parser.prototype.parseBaseArray = function(marksClassCount) { | ||
const count = this.parseUShort(); | ||
return this.parseList(count, Parser.list( | ||
marksClassCount, | ||
Parser.pointer(Parser.anchor) | ||
)); | ||
}; | ||
|
||
/** | ||
* Parse a MarkArray Table in GPOS table | ||
* https://learn.microsoft.com/en-us/typography/opentype/otspec191alpha/gpos_delta#mark-array-table | ||
* | ||
* @returns {Array} | ||
*/ | ||
Parser.prototype.parseMarkArray = function() { | ||
const count = this.parseUShort(); | ||
return this.parseRecordList(count, { | ||
class: Parser.uShort, | ||
attachmentPoint: Parser.pointer(Parser.anchor) | ||
}); | ||
}; | ||
|
||
/** | ||
* Parse a an anchor definition Table in GPOS table | ||
* https://learn.microsoft.com/en-us/typography/opentype/otspec191alpha/gpos_delta#anchor-tables | ||
* | ||
* @returns {Object} Anchor object representing format type | ||
*/ | ||
Parser.prototype.parseAnchorPoint = function() { | ||
const startOffset = this.offset + this.relativeOffset; | ||
const format = this.parseUShort(); | ||
switch (format) { | ||
case 1: | ||
return { | ||
format, | ||
xCoordinate: this.parseShort(), | ||
yCoordinate: this.parseShort() | ||
}; | ||
case 2: | ||
return { | ||
format, | ||
xCoordinate: this.parseShort(), | ||
yCoordinate: this.parseShort(), | ||
anchorPoint: this.parseUShort() | ||
}; | ||
|
||
// TODO: Add a support Device offsets | ||
Connum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// https://learn.microsoft.com/en-us/typography/opentype/otspec191alpha/gpos_delta#anchor-table-format-3-design-units-plus-device-or-variationindex-tables | ||
case 3: | ||
return { | ||
format, | ||
xCoordinate: this.parseShort(), | ||
yCoordinate: this.parseShort(), | ||
xDevice: 0x00, | ||
yDevice: 0x00, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when parsing these values, we have to call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! I will fix shortly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. Pointer is moving now |
||
}; | ||
} | ||
|
||
throw new Error('0x' + startOffset.toString(16) + ': Anchor format must be 1, 2 or 3.'); | ||
}; | ||
|
||
// Parse a Class Definition Table in a GSUB, GPOS or GDEF table. | ||
// https://www.microsoft.com/typography/OTSPEC/chapter2.htm | ||
Parser.prototype.parseClassDef = function() { | ||
|
@@ -548,6 +616,7 @@ Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; | |
Parser.uLongList = Parser.prototype.parseULongList; | ||
Parser.struct = Parser.prototype.parseStruct; | ||
Parser.coverage = Parser.prototype.parseCoverage; | ||
Parser.anchor = Parser.prototype.parseAnchorPoint; | ||
Parser.classDef = Parser.prototype.parseClassDef; | ||
|
||
///// Script, Feature, Lookup lists /////////////////////////////////////////////// | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to microsoft documentation dflt is lowercase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ILOVEPIE I could not find this. I believe you might have referred to the langSysTable (tag) and this is a script tag? Moreover:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://learn.microsoft.com/en-us/typography/opentype/spec/scripttags it's lowercase for all other tags, but DFLT is uppercase in the docs