@cantoo/fontkit 2.0.4 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-module.mjs +4448 -1142
- package/dist/browser-module.mjs.map +1 -1
- package/dist/browser.cjs +4449 -1142
- package/dist/browser.cjs.map +1 -1
- package/dist/main.cjs +4449 -1142
- package/dist/main.cjs.map +1 -1
- package/dist/module.mjs +4448 -1142
- package/dist/module.mjs.map +1 -1
- package/package.json +4 -1
- package/src/CmapProcessor.js +60 -21
- package/src/DFont.js +114 -14
- package/src/TTFFont.js +260 -62
- package/src/TrueTypeCollection.js +59 -15
- package/src/WOFF2Font.js +128 -22
- package/src/WOFFFont.js +45 -8
- package/src/aat/AATFeatureMap.js +83 -40
- package/src/aat/AATLayoutEngine.js +34 -2
- package/src/aat/AATLookupTable.js +24 -6
- package/src/aat/AATMorxProcessor.js +202 -65
- package/src/aat/AATStateMachine.js +35 -3
- package/src/base.js +20 -1
- package/src/cff/CFFCharsets.js +3 -0
- package/src/cff/CFFDict.js +81 -11
- package/src/cff/CFFEncodings.js +2 -0
- package/src/cff/CFFFont.js +120 -24
- package/src/cff/CFFIndex.js +66 -9
- package/src/cff/CFFOperand.js +53 -28
- package/src/cff/CFFPointer.js +41 -2
- package/src/cff/CFFPrivateDict.js +34 -24
- package/src/cff/CFFStandardStrings.js +68 -67
- package/src/cff/CFFTop.js +161 -68
- package/src/decorators.js +34 -15
- package/src/encodings.js +133 -116
- package/src/fs.js +10 -0
- package/src/glyph/BBox.js +14 -0
- package/src/glyph/CFFGlyph.js +293 -152
- package/src/glyph/COLRGlyph.js +40 -5
- package/src/glyph/Glyph.js +72 -13
- package/src/glyph/GlyphVariationProcessor.js +158 -62
- package/src/glyph/Path.js +119 -32
- package/src/glyph/SBIXGlyph.js +57 -8
- package/src/glyph/StandardNames.js +1 -0
- package/src/glyph/TTFGlyph.js +203 -63
- package/src/glyph/TTFGlyphEncoder.js +58 -26
- package/src/glyph/WOFF2Glyph.js +25 -6
- package/src/layout/GlyphPosition.js +6 -0
- package/src/layout/GlyphRun.js +28 -10
- package/src/layout/KernProcessor.js +63 -4
- package/src/layout/LayoutEngine.js +99 -26
- package/src/layout/Script.js +52 -26
- package/src/layout/UnicodeLayoutEngine.js +34 -10
- package/src/opentype/GPOSProcessor.js +156 -45
- package/src/opentype/GSUBProcessor.js +92 -33
- package/src/opentype/GlyphInfo.js +53 -5
- package/src/opentype/GlyphIterator.js +51 -2
- package/src/opentype/OTLayoutEngine.js +66 -8
- package/src/opentype/OTProcessor.js +284 -71
- package/src/opentype/ShapingPlan.js +52 -5
- package/src/opentype/shapers/ArabicShaper.js +27 -8
- package/src/opentype/shapers/DefaultShaper.js +54 -10
- package/src/opentype/shapers/HangulShaper.js +120 -42
- package/src/opentype/shapers/HebrewShaper.js +50 -16
- package/src/opentype/shapers/IndicShaper.js +159 -44
- package/src/opentype/shapers/ThaiShaper.js +50 -19
- package/src/opentype/shapers/UniversalShaper.js +83 -17
- package/src/opentype/shapers/index.js +48 -35
- package/src/opentype/shapers/indic-data.js +2 -2
- package/src/packages/unicode-properties/data-trie.js +2 -1
- package/src/packages/unicode-properties/data.js +13 -1
- package/src/packages/unicode-properties/dist/index.cjs +23 -10
- package/src/packages/unicode-properties/dist/index.cjs.map +2 -2
- package/src/packages/unicode-properties/dist/index.mjs +22 -10
- package/src/packages/unicode-properties/dist/index.mjs.map +2 -2
- package/src/packages/unicode-properties/generate.js +3 -4
- package/src/packages/unicode-properties/index.js +95 -30
- package/src/packages/unicode-properties/package.json +1 -1
- package/src/packages/unicode-trie/builder.js +146 -29
- package/src/packages/unicode-trie/dist/builder.cjs +135 -25
- package/src/packages/unicode-trie/dist/builder.cjs.map +2 -2
- package/src/packages/unicode-trie/dist/builder.mjs +134 -25
- package/src/packages/unicode-trie/dist/builder.mjs.map +2 -2
- package/src/packages/unicode-trie/dist/index.cjs +22 -9
- package/src/packages/unicode-trie/dist/index.cjs.map +2 -2
- package/src/packages/unicode-trie/dist/index.mjs +21 -9
- package/src/packages/unicode-trie/dist/index.mjs.map +2 -2
- package/src/packages/unicode-trie/index.js +63 -12
- package/src/packages/unicode-trie/package.json +1 -1
- package/src/packages/unicode-trie/swap.js +15 -2
- package/src/subset/CFFSubset.js +112 -22
- package/src/subset/Subset.js +23 -3
- package/src/subset/TTFSubset.js +84 -41
- package/src/tables/BASE.js +28 -27
- package/src/tables/COLR.js +10 -9
- package/src/tables/CPAL.js +2 -1
- package/src/tables/DSIG.js +8 -7
- package/src/tables/EBDT.js +10 -4
- package/src/tables/EBLC.js +70 -10
- package/src/tables/GDEF.js +18 -17
- package/src/tables/GPOS.js +153 -90
- package/src/tables/GSUB.js +50 -39
- package/src/tables/HVAR.js +51 -5
- package/src/tables/JSTF.js +17 -16
- package/src/tables/LTSH.js +4 -3
- package/src/tables/OS2.js +50 -49
- package/src/tables/PCLT.js +16 -15
- package/src/tables/STAT.js +46 -6
- package/src/tables/VDMX.js +18 -17
- package/src/tables/VORG.js +8 -7
- package/src/tables/WOFF2Directory.js +57 -9
- package/src/tables/WOFFDirectory.js +32 -20
- package/src/tables/aat.js +160 -35
- package/src/tables/avar.js +1 -0
- package/src/tables/bsln.js +3 -2
- package/src/tables/cmap.js +83 -63
- package/src/tables/cvt.js +1 -0
- package/src/tables/directory.js +56 -28
- package/src/tables/feat.js +24 -2
- package/src/tables/fpgm.js +1 -0
- package/src/tables/fvar.js +32 -4
- package/src/tables/gasp.js +6 -5
- package/src/tables/glyf.js +2 -1
- package/src/tables/gvar.js +29 -2
- package/src/tables/hdmx.js +18 -7
- package/src/tables/head.js +17 -16
- package/src/tables/hhea.js +15 -14
- package/src/tables/hmtx.js +18 -2
- package/src/tables/index.js +1 -5
- package/src/tables/just.js +15 -4
- package/src/tables/kern.js +84 -47
- package/src/tables/loca.js +23 -10
- package/src/tables/maxp.js +16 -15
- package/src/tables/morx.js +27 -20
- package/src/tables/name.js +120 -39
- package/src/tables/opbd.js +2 -1
- package/src/tables/opentype.js +126 -91
- package/src/tables/post.js +18 -11
- package/src/tables/prep.js +1 -0
- package/src/tables/sbix.js +12 -1
- package/src/tables/variations.js +29 -9
- package/src/tables/vhea.js +15 -14
- package/src/tables/vmtx.js +20 -4
- package/src/utils.js +15 -0
package/src/glyph/WOFF2Glyph.js
CHANGED
|
@@ -1,46 +1,61 @@
|
|
|
1
1
|
import TTFGlyph, { Point } from './TTFGlyph';
|
|
2
2
|
import BBox from './BBox';
|
|
3
3
|
|
|
4
|
+
/** @typedef {import('../../types/fontkit').DecodedGlyf} DecodedGlyf */
|
|
5
|
+
/** @typedef {import('../../types/fontkit').GlyphComponent} GlyphComponent */
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* Represents a TrueType glyph in the WOFF2 format, which compresses glyphs differently.
|
|
6
9
|
*/
|
|
7
10
|
export default class WOFF2Glyph extends TTFGlyph {
|
|
8
11
|
type = 'WOFF2';
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @returns {DecodedGlyf | null}
|
|
15
|
+
*/
|
|
10
16
|
_decode() {
|
|
11
|
-
let cached = this._font._transformedGlyphs[this.id];
|
|
17
|
+
let cached = this._font._transformedGlyphs?.[this.id];
|
|
12
18
|
if (!cached) {
|
|
13
19
|
return null;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
if (!this._font._variationProcessor) {
|
|
17
23
|
// Non-variable path: return the pre-decoded glyph without copying.
|
|
18
|
-
return cached;
|
|
24
|
+
return /** @type {DecodedGlyf} */ (cached);
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
// Clone so per-instance deltas do not mutate the shared transformed cache.
|
|
22
28
|
// Transformed glyf has no per-glyph bbox; zeros keep phantom-point math
|
|
23
29
|
// aligned with hmtx (same approach as a missing bbox bitmap entry).
|
|
30
|
+
/** @type {DecodedGlyf} */
|
|
24
31
|
let glyph = {
|
|
25
32
|
numberOfContours: cached.numberOfContours,
|
|
26
33
|
xMin: 0,
|
|
27
34
|
yMin: 0,
|
|
28
35
|
xMax: 0,
|
|
29
|
-
yMax: 0
|
|
36
|
+
yMax: 0
|
|
30
37
|
};
|
|
31
38
|
|
|
32
39
|
if (cached.points) {
|
|
33
|
-
glyph.points = cached.points.map(p =>
|
|
40
|
+
glyph.points = cached.points.map((p) => {
|
|
41
|
+
if (typeof p.copy === 'function') {
|
|
42
|
+
return p.copy();
|
|
43
|
+
}
|
|
44
|
+
return new Point(p.onCurve, p.endContour, p.x, p.y);
|
|
45
|
+
});
|
|
34
46
|
let points = glyph.points.concat(this._getPhantomPoints(glyph));
|
|
35
47
|
this._font._variationProcessor.transformPoints(this.id, points);
|
|
36
48
|
glyph.phantomPoints = points.slice(-4);
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
if (cached.components) {
|
|
40
|
-
glyph.components = cached.components.map(c => {
|
|
41
|
-
|
|
52
|
+
glyph.components = cached.components.map((c) => {
|
|
53
|
+
/** @type {GlyphComponent} */
|
|
54
|
+
let clone = Object.assign(Object.create(Object.getPrototypeOf(c)), c);
|
|
55
|
+
return clone;
|
|
42
56
|
});
|
|
43
57
|
|
|
58
|
+
/** @type {Point[]} */
|
|
44
59
|
let points = [];
|
|
45
60
|
for (let component of glyph.components) {
|
|
46
61
|
points.push(new Point(true, true, component.dx, component.dy));
|
|
@@ -59,6 +74,10 @@ export default class WOFF2Glyph extends TTFGlyph {
|
|
|
59
74
|
return glyph;
|
|
60
75
|
}
|
|
61
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @param {boolean} [internal]
|
|
79
|
+
* @returns {BBox}
|
|
80
|
+
*/
|
|
62
81
|
_getCBox(internal) {
|
|
63
82
|
// Avoid path recursion while building phantom points during variation decode.
|
|
64
83
|
if (internal) {
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* Represents positioning information for a glyph in a GlyphRun.
|
|
3
3
|
*/
|
|
4
4
|
export default class GlyphPosition {
|
|
5
|
+
/**
|
|
6
|
+
* @param {number} [xAdvance]
|
|
7
|
+
* @param {number} [yAdvance]
|
|
8
|
+
* @param {number} [xOffset]
|
|
9
|
+
* @param {number} [yOffset]
|
|
10
|
+
*/
|
|
5
11
|
constructor(xAdvance = 0, yAdvance = 0, xOffset = 0, yOffset = 0) {
|
|
6
12
|
/**
|
|
7
13
|
* The amount to move the virtual pen in the X direction after rendering this glyph.
|
package/src/layout/GlyphRun.js
CHANGED
|
@@ -6,43 +6,50 @@ import * as Script from '../layout/Script';
|
|
|
6
6
|
* Returned by the font layout method.
|
|
7
7
|
*/
|
|
8
8
|
export default class GlyphRun {
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('../glyph/Glyph').default[]} glyphs
|
|
11
|
+
* @param {import('../../types/fontkit').FeatureInput | null | undefined} features
|
|
12
|
+
* @param {import('../../types/fontkit').ScriptTag | string[] | null | undefined} script
|
|
13
|
+
* @param {import('../../types/fontkit').LanguageTag | null | undefined} language
|
|
14
|
+
* @param {import('../../types/fontkit').TextDirection | null | undefined} direction
|
|
15
|
+
*/
|
|
9
16
|
constructor(glyphs, features, script, language, direction) {
|
|
10
17
|
/**
|
|
11
18
|
* An array of Glyph objects in the run
|
|
12
|
-
* @type {Glyph[]}
|
|
19
|
+
* @type {import('../glyph/Glyph').default[]}
|
|
13
20
|
*/
|
|
14
21
|
this.glyphs = glyphs;
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* An array of GlyphPosition objects for each glyph in the run
|
|
18
|
-
* @type {GlyphPosition[]}
|
|
25
|
+
* @type {import('./GlyphPosition').default[] | null}
|
|
19
26
|
*/
|
|
20
27
|
this.positions = null;
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
30
|
* The script that was requested for shaping. This was either passed in or detected automatically.
|
|
24
|
-
* @type {string}
|
|
31
|
+
* @type {import('../../types/fontkit').ScriptTag | string[] | null | undefined}
|
|
25
32
|
*/
|
|
26
33
|
this.script = script;
|
|
27
34
|
|
|
28
35
|
/**
|
|
29
36
|
* The language requested for shaping, as passed in. If `null`, the default language for the
|
|
30
37
|
* script was used.
|
|
31
|
-
* @type {
|
|
38
|
+
* @type {import('../../types/fontkit').LanguageTag | null}
|
|
32
39
|
*/
|
|
33
40
|
this.language = language || null;
|
|
34
41
|
|
|
35
42
|
/**
|
|
36
43
|
* The direction requested for shaping, as passed in (either ltr or rtl).
|
|
37
44
|
* If `null`, the default direction of the script is used.
|
|
38
|
-
* @type {
|
|
45
|
+
* @type {import('../../types/fontkit').TextDirection}
|
|
39
46
|
*/
|
|
40
47
|
this.direction = direction || Script.direction(script);
|
|
41
48
|
|
|
42
49
|
/**
|
|
43
50
|
* The features requested during shaping. This is a combination of user
|
|
44
51
|
* specified features and features chosen by the shaper.
|
|
45
|
-
* @type {
|
|
52
|
+
* @type {import('../../types/fontkit').FeatureMap}
|
|
46
53
|
*/
|
|
47
54
|
this.features = {};
|
|
48
55
|
|
|
@@ -51,7 +58,7 @@ export default class GlyphRun {
|
|
|
51
58
|
for (let tag of features) {
|
|
52
59
|
this.features[tag] = true;
|
|
53
60
|
}
|
|
54
|
-
} else if (typeof features === 'object') {
|
|
61
|
+
} else if (typeof features === 'object' && features !== null) {
|
|
55
62
|
this.features = features;
|
|
56
63
|
}
|
|
57
64
|
}
|
|
@@ -62,6 +69,10 @@ export default class GlyphRun {
|
|
|
62
69
|
*/
|
|
63
70
|
get advanceWidth() {
|
|
64
71
|
let width = 0;
|
|
72
|
+
if (!this.positions) {
|
|
73
|
+
return width;
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
for (let position of this.positions) {
|
|
66
77
|
width += position.xAdvance;
|
|
67
78
|
}
|
|
@@ -69,12 +80,16 @@ export default class GlyphRun {
|
|
|
69
80
|
return width;
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
|
|
83
|
+
/**
|
|
73
84
|
* The total advance height of the run.
|
|
74
85
|
* @type {number}
|
|
75
86
|
*/
|
|
76
87
|
get advanceHeight() {
|
|
77
88
|
let height = 0;
|
|
89
|
+
if (!this.positions) {
|
|
90
|
+
return height;
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
for (let position of this.positions) {
|
|
79
94
|
height += position.yAdvance;
|
|
80
95
|
}
|
|
@@ -82,12 +97,15 @@ export default class GlyphRun {
|
|
|
82
97
|
return height;
|
|
83
98
|
}
|
|
84
99
|
|
|
85
|
-
|
|
100
|
+
/**
|
|
86
101
|
* The bounding box containing all glyphs in the run.
|
|
87
102
|
* @type {BBox}
|
|
88
103
|
*/
|
|
89
104
|
get bbox() {
|
|
90
|
-
let bbox = new BBox;
|
|
105
|
+
let bbox = new BBox();
|
|
106
|
+
if (!this.positions) {
|
|
107
|
+
return bbox;
|
|
108
|
+
}
|
|
91
109
|
|
|
92
110
|
let x = 0;
|
|
93
111
|
let y = 0;
|
|
@@ -1,10 +1,54 @@
|
|
|
1
|
-
import {binarySearch} from '../utils';
|
|
1
|
+
import { binarySearch } from '../utils';
|
|
2
|
+
|
|
3
|
+
/** @typedef {import('../../types/fontkit').LayoutFont} LayoutFont */
|
|
4
|
+
/** @typedef {import('../../types/fontkit').KernTable} KernTable */
|
|
5
|
+
/** @typedef {import('../../types/fontkit').KernPair} KernPair */
|
|
6
|
+
/** @typedef {import('../../types/fontkit').KernFormat0} KernFormat0 */
|
|
7
|
+
/** @typedef {import('../../types/fontkit').KernFormat2} KernFormat2 */
|
|
8
|
+
/** @typedef {import('../../types/fontkit').KernFormat3} KernFormat3 */
|
|
9
|
+
/** @typedef {import('../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
|
|
10
|
+
/** @typedef {import('../../types/fontkit').LayoutGlyph} LayoutGlyph */
|
|
11
|
+
/** @typedef {import('../../types/fontkit').GlyphPositionLike} GlyphPositionLike */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {unknown} s
|
|
15
|
+
* @returns {s is KernFormat0}
|
|
16
|
+
*/
|
|
17
|
+
function isKernFormat0(s) {
|
|
18
|
+
return typeof s === 'object' && s !== null && Array.isArray(/** @type {{ pairs?: unknown }} */ (s).pairs);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {unknown} s
|
|
23
|
+
* @returns {s is KernFormat2}
|
|
24
|
+
*/
|
|
25
|
+
function isKernFormat2(s) {
|
|
26
|
+
return typeof s === 'object' && s !== null
|
|
27
|
+
&& 'leftTable' in s && 'rightTable' in s && 'array' in s;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {unknown} s
|
|
32
|
+
* @returns {s is KernFormat3}
|
|
33
|
+
*/
|
|
34
|
+
function isKernFormat3(s) {
|
|
35
|
+
return typeof s === 'object' && s !== null
|
|
36
|
+
&& 'glyphCount' in s && 'kernValue' in s && 'kernIndex' in s;
|
|
37
|
+
}
|
|
2
38
|
|
|
3
39
|
export default class KernProcessor {
|
|
40
|
+
/**
|
|
41
|
+
* @param {LayoutFont} font
|
|
42
|
+
*/
|
|
4
43
|
constructor(font) {
|
|
5
|
-
|
|
44
|
+
/** @type {KernTable} */
|
|
45
|
+
this.kern = /** @type {KernTable} */ (font.kern);
|
|
6
46
|
}
|
|
7
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @param {Array<LayoutGlyph | GlyphInfoLike>} glyphs
|
|
50
|
+
* @param {GlyphPositionLike[]} positions
|
|
51
|
+
*/
|
|
8
52
|
process(glyphs, positions) {
|
|
9
53
|
for (let glyphIndex = 0; glyphIndex < glyphs.length - 1; glyphIndex++) {
|
|
10
54
|
let left = glyphs[glyphIndex].id;
|
|
@@ -13,6 +57,11 @@ export default class KernProcessor {
|
|
|
13
57
|
}
|
|
14
58
|
}
|
|
15
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @param {number} left
|
|
62
|
+
* @param {number} right
|
|
63
|
+
* @returns {number}
|
|
64
|
+
*/
|
|
16
65
|
getKerning(left, right) {
|
|
17
66
|
let res = 0;
|
|
18
67
|
|
|
@@ -42,7 +91,10 @@ export default class KernProcessor {
|
|
|
42
91
|
let s = table.subtable;
|
|
43
92
|
switch (table.format) {
|
|
44
93
|
case 0: {
|
|
45
|
-
|
|
94
|
+
if (!isKernFormat0(s)) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
let pairIdx = binarySearch(s.pairs, /** @param {KernPair} pair */ (pair) => {
|
|
46
98
|
return (left - pair.left) || (right - pair.right);
|
|
47
99
|
});
|
|
48
100
|
|
|
@@ -54,7 +106,11 @@ export default class KernProcessor {
|
|
|
54
106
|
}
|
|
55
107
|
|
|
56
108
|
case 2: {
|
|
57
|
-
|
|
109
|
+
if (!isKernFormat2(s)) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
let leftOffset = 0;
|
|
113
|
+
let rightOffset = 0;
|
|
58
114
|
if (left >= s.leftTable.firstGlyph && left < s.leftTable.firstGlyph + s.leftTable.nGlyphs) {
|
|
59
115
|
leftOffset = s.leftTable.offsets[left - s.leftTable.firstGlyph];
|
|
60
116
|
} else {
|
|
@@ -71,6 +127,9 @@ export default class KernProcessor {
|
|
|
71
127
|
}
|
|
72
128
|
|
|
73
129
|
case 3: {
|
|
130
|
+
if (!isKernFormat3(s)) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
74
133
|
if (left >= s.glyphCount || right >= s.glyphCount) {
|
|
75
134
|
return 0;
|
|
76
135
|
}
|
|
@@ -6,32 +6,60 @@ import * as Script from './Script';
|
|
|
6
6
|
import AATLayoutEngine from '../aat/AATLayoutEngine';
|
|
7
7
|
import OTLayoutEngine from '../opentype/OTLayoutEngine';
|
|
8
8
|
|
|
9
|
+
/** @typedef {import('../../types/fontkit').LayoutFont} LayoutFont */
|
|
10
|
+
/** @typedef {import('../../types/fontkit').LayoutGlyph} LayoutGlyph */
|
|
11
|
+
/** @typedef {import('../../types/fontkit').FeatureInput} FeatureInput */
|
|
12
|
+
/** @typedef {import('../../types/fontkit').FeatureMap} FeatureMap */
|
|
13
|
+
/** @typedef {import('../../types/fontkit').ScriptTag} ScriptTag */
|
|
14
|
+
/** @typedef {import('../../types/fontkit').LanguageTag} LanguageTag */
|
|
15
|
+
/** @typedef {import('../../types/fontkit').TextDirection} TextDirection */
|
|
16
|
+
/** @typedef {import('../../types/fontkit').AdvancedLayoutEngine} AdvancedLayoutEngine */
|
|
17
|
+
/** @typedef {import('../../types/fontkit').GlyphRunLike} GlyphRunLike */
|
|
18
|
+
/** @typedef {import('../../types/fontkit').GlyphPositionLike} GlyphPositionLike */
|
|
19
|
+
/** @typedef {import('./GlyphRun').default} GlyphRunInstance */
|
|
20
|
+
|
|
9
21
|
export default class LayoutEngine {
|
|
22
|
+
/**
|
|
23
|
+
* @param {LayoutFont} font
|
|
24
|
+
*/
|
|
10
25
|
constructor(font) {
|
|
26
|
+
/** @type {LayoutFont} */
|
|
11
27
|
this.font = font;
|
|
28
|
+
/** @type {UnicodeLayoutEngine | null} */
|
|
12
29
|
this.unicodeLayoutEngine = null;
|
|
30
|
+
/** @type {KernProcessor | null} */
|
|
13
31
|
this.kernProcessor = null;
|
|
32
|
+
/** @type {AdvancedLayoutEngine | undefined} */
|
|
33
|
+
this.engine = undefined;
|
|
14
34
|
|
|
15
35
|
// Choose an advanced layout engine. We try the AAT morx table first since more
|
|
16
36
|
// scripts are currently supported because the shaping logic is built into the font.
|
|
17
37
|
if (this.font.morx) {
|
|
18
|
-
this.engine = new AATLayoutEngine(this.font);
|
|
19
|
-
|
|
38
|
+
this.engine = /** @type {AdvancedLayoutEngine} */ (new AATLayoutEngine(this.font));
|
|
20
39
|
} else if (this.font.GSUB || this.font.GPOS) {
|
|
21
|
-
this.engine = new OTLayoutEngine(this.font);
|
|
40
|
+
this.engine = /** @type {AdvancedLayoutEngine} */ (new OTLayoutEngine(this.font));
|
|
22
41
|
}
|
|
23
42
|
}
|
|
24
43
|
|
|
44
|
+
/**
|
|
45
|
+
* @param {string | LayoutGlyph[]} string
|
|
46
|
+
* @param {FeatureInput | ScriptTag | string[] | null | undefined} [features]
|
|
47
|
+
* @param {ScriptTag | string[] | LanguageTag | null | undefined} [script]
|
|
48
|
+
* @param {LanguageTag | TextDirection | null | undefined} [language]
|
|
49
|
+
* @param {TextDirection | null | undefined} [direction]
|
|
50
|
+
* @returns {GlyphRunInstance}
|
|
51
|
+
*/
|
|
25
52
|
layout(string, features, script, language, direction) {
|
|
26
53
|
// Make the features parameter optional
|
|
27
54
|
if (typeof features === 'string') {
|
|
28
|
-
direction = language;
|
|
29
|
-
language = script;
|
|
55
|
+
direction = /** @type {TextDirection | null | undefined} */ (language);
|
|
56
|
+
language = /** @type {LanguageTag | null | undefined} */ (script);
|
|
30
57
|
script = features;
|
|
31
58
|
features = [];
|
|
32
59
|
}
|
|
33
60
|
|
|
34
61
|
// Map string to glyphs if needed
|
|
62
|
+
/** @type {LayoutGlyph[]} */
|
|
35
63
|
let glyphs;
|
|
36
64
|
if (typeof string === 'string') {
|
|
37
65
|
// Attempt to detect the script from the string if not provided.
|
|
@@ -43,6 +71,7 @@ export default class LayoutEngine {
|
|
|
43
71
|
} else {
|
|
44
72
|
// Attempt to detect the script from the glyph code points if not provided.
|
|
45
73
|
if (script == null) {
|
|
74
|
+
/** @type {number[]} */
|
|
46
75
|
let codePoints = [];
|
|
47
76
|
for (let glyph of string) {
|
|
48
77
|
codePoints.push(...glyph.codePoints);
|
|
@@ -54,7 +83,13 @@ export default class LayoutEngine {
|
|
|
54
83
|
glyphs = string;
|
|
55
84
|
}
|
|
56
85
|
|
|
57
|
-
let glyphRun = new GlyphRun(
|
|
86
|
+
let glyphRun = new GlyphRun(
|
|
87
|
+
/** @type {import('../glyph/Glyph').default[]} */ (glyphs),
|
|
88
|
+
/** @type {FeatureInput | null | undefined} */ (features),
|
|
89
|
+
script,
|
|
90
|
+
/** @type {LanguageTag | null | undefined} */ (language),
|
|
91
|
+
/** @type {TextDirection | null | undefined} */ (direction)
|
|
92
|
+
);
|
|
58
93
|
|
|
59
94
|
// Return early if there are no glyphs
|
|
60
95
|
if (glyphs.length === 0) {
|
|
@@ -64,14 +99,16 @@ export default class LayoutEngine {
|
|
|
64
99
|
|
|
65
100
|
// Setup the advanced layout engine
|
|
66
101
|
if (this.engine && this.engine.setup) {
|
|
67
|
-
this.engine.setup(glyphRun);
|
|
102
|
+
this.engine.setup(/** @type {GlyphRunLike} */ (glyphRun));
|
|
68
103
|
}
|
|
69
104
|
|
|
70
105
|
// Substitute and position the glyphs
|
|
71
106
|
this.substitute(glyphRun);
|
|
72
107
|
this.position(glyphRun);
|
|
73
108
|
|
|
74
|
-
|
|
109
|
+
if (glyphRun.positions) {
|
|
110
|
+
this.hideDefaultIgnorables(/** @type {LayoutGlyph[]} */ (glyphRun.glyphs), glyphRun.positions);
|
|
111
|
+
}
|
|
75
112
|
|
|
76
113
|
// Let the layout engine clean up any state it might have
|
|
77
114
|
if (this.engine && this.engine.cleanup) {
|
|
@@ -81,21 +118,30 @@ export default class LayoutEngine {
|
|
|
81
118
|
return glyphRun;
|
|
82
119
|
}
|
|
83
120
|
|
|
121
|
+
/**
|
|
122
|
+
* @param {GlyphRunInstance} glyphRun
|
|
123
|
+
*/
|
|
84
124
|
substitute(glyphRun) {
|
|
85
125
|
// Call the advanced layout engine to make substitutions
|
|
86
126
|
if (this.engine && this.engine.substitute) {
|
|
87
|
-
this.engine.substitute(glyphRun);
|
|
127
|
+
this.engine.substitute(/** @type {GlyphRunLike} */ (glyphRun));
|
|
88
128
|
}
|
|
89
129
|
}
|
|
90
130
|
|
|
131
|
+
/**
|
|
132
|
+
* @param {GlyphRunInstance} glyphRun
|
|
133
|
+
*/
|
|
91
134
|
position(glyphRun) {
|
|
92
135
|
// Get initial glyph positions
|
|
93
|
-
glyphRun.positions = glyphRun.glyphs.map(
|
|
136
|
+
glyphRun.positions = /** @type {LayoutGlyph[]} */ (glyphRun.glyphs).map(
|
|
137
|
+
/** @param {LayoutGlyph} glyph */ glyph => new GlyphPosition(glyph.advanceWidth)
|
|
138
|
+
);
|
|
139
|
+
/** @type {FeatureMap | Record<string, import('../../types/fontkit').OTFeature> | null | undefined | false} */
|
|
94
140
|
let positioned = null;
|
|
95
141
|
|
|
96
142
|
// Call the advanced layout engine. Returns the features applied.
|
|
97
143
|
if (this.engine && this.engine.position) {
|
|
98
|
-
positioned = this.engine.position(glyphRun);
|
|
144
|
+
positioned = this.engine.position(/** @type {GlyphRunLike} */ (glyphRun));
|
|
99
145
|
}
|
|
100
146
|
|
|
101
147
|
// if there is no GPOS table, use unicode properties to position marks.
|
|
@@ -104,7 +150,10 @@ export default class LayoutEngine {
|
|
|
104
150
|
this.unicodeLayoutEngine = new UnicodeLayoutEngine(this.font);
|
|
105
151
|
}
|
|
106
152
|
|
|
107
|
-
this.unicodeLayoutEngine.positionGlyphs(
|
|
153
|
+
this.unicodeLayoutEngine.positionGlyphs(
|
|
154
|
+
/** @type {LayoutGlyph[]} */ (glyphRun.glyphs),
|
|
155
|
+
/** @type {GlyphPositionLike[]} */ (glyphRun.positions)
|
|
156
|
+
);
|
|
108
157
|
}
|
|
109
158
|
|
|
110
159
|
// if kerning is not supported by GPOS, do kerning with the TrueType/AAT kern table
|
|
@@ -113,13 +162,23 @@ export default class LayoutEngine {
|
|
|
113
162
|
this.kernProcessor = new KernProcessor(this.font);
|
|
114
163
|
}
|
|
115
164
|
|
|
116
|
-
this.kernProcessor.process(
|
|
165
|
+
this.kernProcessor.process(
|
|
166
|
+
/** @type {LayoutGlyph[]} */ (glyphRun.glyphs),
|
|
167
|
+
/** @type {GlyphPositionLike[]} */ (glyphRun.positions)
|
|
168
|
+
);
|
|
117
169
|
glyphRun.features.kern = true;
|
|
118
170
|
}
|
|
119
171
|
}
|
|
120
172
|
|
|
173
|
+
/**
|
|
174
|
+
* @param {LayoutGlyph[]} glyphs
|
|
175
|
+
* @param {GlyphPositionLike[]} positions
|
|
176
|
+
*/
|
|
121
177
|
hideDefaultIgnorables(glyphs, positions) {
|
|
122
178
|
let space = this.font.glyphForCodePoint(0x20);
|
|
179
|
+
if (!space) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
123
182
|
for (let i = 0; i < glyphs.length; i++) {
|
|
124
183
|
if (this.isDefaultIgnorable(glyphs[i].codePoints[0])) {
|
|
125
184
|
glyphs[i] = space;
|
|
@@ -129,6 +188,10 @@ export default class LayoutEngine {
|
|
|
129
188
|
}
|
|
130
189
|
}
|
|
131
190
|
|
|
191
|
+
/**
|
|
192
|
+
* @param {number} ch
|
|
193
|
+
* @returns {boolean}
|
|
194
|
+
*/
|
|
132
195
|
isDefaultIgnorable(ch) {
|
|
133
196
|
// From DerivedCoreProperties.txt in the Unicode database,
|
|
134
197
|
// minus U+115F, U+1160, U+3164 and U+FFA0, which is what
|
|
@@ -137,27 +200,33 @@ export default class LayoutEngine {
|
|
|
137
200
|
if (plane === 0) {
|
|
138
201
|
// BMP
|
|
139
202
|
switch (ch >> 8) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
203
|
+
case 0x00: return ch === 0x00AD;
|
|
204
|
+
case 0x03: return ch === 0x034F;
|
|
205
|
+
case 0x06: return ch === 0x061C;
|
|
206
|
+
case 0x17: return 0x17B4 <= ch && ch <= 0x17B5;
|
|
207
|
+
case 0x18: return 0x180B <= ch && ch <= 0x180E;
|
|
208
|
+
case 0x20: return (0x200B <= ch && ch <= 0x200F) || (0x202A <= ch && ch <= 0x202E) || (0x2060 <= ch && ch <= 0x206F);
|
|
209
|
+
case 0xFE: return (0xFE00 <= ch && ch <= 0xFE0F) || ch === 0xFEFF;
|
|
210
|
+
case 0xFF: return 0xFFF0 <= ch && ch <= 0xFFF8;
|
|
211
|
+
default: return false;
|
|
149
212
|
}
|
|
150
213
|
} else {
|
|
151
214
|
// Other planes
|
|
152
215
|
switch (plane) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
216
|
+
case 0x01: return (0x1BCA0 <= ch && ch <= 0x1BCA3) || (0x1D173 <= ch && ch <= 0x1D17A);
|
|
217
|
+
case 0x0E: return 0xE0000 <= ch && ch <= 0xE0FFF;
|
|
218
|
+
default: return false;
|
|
156
219
|
}
|
|
157
220
|
}
|
|
158
221
|
}
|
|
159
222
|
|
|
223
|
+
/**
|
|
224
|
+
* @param {ScriptTag | string[] | null | undefined} [script]
|
|
225
|
+
* @param {LanguageTag | null | undefined} [language]
|
|
226
|
+
* @returns {string[]}
|
|
227
|
+
*/
|
|
160
228
|
getAvailableFeatures(script, language) {
|
|
229
|
+
/** @type {string[]} */
|
|
161
230
|
let features = [];
|
|
162
231
|
|
|
163
232
|
if (this.engine) {
|
|
@@ -171,8 +240,12 @@ export default class LayoutEngine {
|
|
|
171
240
|
return features;
|
|
172
241
|
}
|
|
173
242
|
|
|
243
|
+
/**
|
|
244
|
+
* @param {number} gid
|
|
245
|
+
* @returns {string[]}
|
|
246
|
+
*/
|
|
174
247
|
stringsForGlyph(gid) {
|
|
175
|
-
let result = new Set;
|
|
248
|
+
let result = new Set();
|
|
176
249
|
|
|
177
250
|
let codePoints = this.font._cmapProcessor.codePointsForGlyph(gid);
|
|
178
251
|
for (let codePoint of codePoints) {
|
package/src/layout/Script.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {getScript} from '../packages/unicode-properties/index.js';
|
|
1
|
+
import { getScript } from '../packages/unicode-properties/index.js';
|
|
2
|
+
|
|
3
|
+
/** @typedef {import('../../types/fontkit').TextDirection} TextDirection */
|
|
2
4
|
|
|
3
5
|
// This maps the Unicode Script property to an OpenType script tag
|
|
4
6
|
// Data from http://www.microsoft.com/typography/otspec/scripttags.htm
|
|
5
7
|
// and http://www.unicode.org/Public/UNIDATA/PropertyValueAliases.txt.
|
|
8
|
+
/** @type {Record<string, string | string[]>} */
|
|
6
9
|
const UNICODE_SCRIPTS = {
|
|
7
10
|
Caucasian_Albanian: 'aghb',
|
|
8
11
|
Arabic: 'arab',
|
|
@@ -133,6 +136,7 @@ const UNICODE_SCRIPTS = {
|
|
|
133
136
|
Unknown: 'zzzz'
|
|
134
137
|
};
|
|
135
138
|
|
|
139
|
+
/** @type {Record<string, string>} */
|
|
136
140
|
const OPENTYPE_SCRIPTS = {};
|
|
137
141
|
for (let script in UNICODE_SCRIPTS) {
|
|
138
142
|
let tag = UNICODE_SCRIPTS[script];
|
|
@@ -145,14 +149,26 @@ for (let script in UNICODE_SCRIPTS) {
|
|
|
145
149
|
}
|
|
146
150
|
}
|
|
147
151
|
|
|
152
|
+
/**
|
|
153
|
+
* @param {string} script
|
|
154
|
+
* @returns {string | string[] | undefined}
|
|
155
|
+
*/
|
|
148
156
|
export function fromUnicode(script) {
|
|
149
157
|
return UNICODE_SCRIPTS[script];
|
|
150
158
|
}
|
|
151
159
|
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} tag
|
|
162
|
+
* @returns {string | undefined}
|
|
163
|
+
*/
|
|
152
164
|
export function fromOpenType(tag) {
|
|
153
165
|
return OPENTYPE_SCRIPTS[tag];
|
|
154
166
|
}
|
|
155
167
|
|
|
168
|
+
/**
|
|
169
|
+
* @param {string} string
|
|
170
|
+
* @returns {string | string[]}
|
|
171
|
+
*/
|
|
156
172
|
export function forString(string) {
|
|
157
173
|
let len = string.length;
|
|
158
174
|
let idx = 0;
|
|
@@ -179,6 +195,10 @@ export function forString(string) {
|
|
|
179
195
|
return UNICODE_SCRIPTS.Unknown;
|
|
180
196
|
}
|
|
181
197
|
|
|
198
|
+
/**
|
|
199
|
+
* @param {number[]} codePoints
|
|
200
|
+
* @returns {string | string[]}
|
|
201
|
+
*/
|
|
182
202
|
export function forCodePoints(codePoints) {
|
|
183
203
|
for (let i = 0; i < codePoints.length; i++) {
|
|
184
204
|
let codePoint = codePoints[i];
|
|
@@ -192,38 +212,44 @@ export function forCodePoints(codePoints) {
|
|
|
192
212
|
}
|
|
193
213
|
|
|
194
214
|
// The scripts in this map are written from right to left
|
|
215
|
+
/** @type {Record<string, boolean>} */
|
|
195
216
|
const RTL = {
|
|
196
|
-
arab: true,
|
|
197
|
-
hebr: true,
|
|
198
|
-
syrc: true,
|
|
199
|
-
thaa: true,
|
|
200
|
-
cprt: true,
|
|
201
|
-
khar: true,
|
|
202
|
-
phnx: true,
|
|
217
|
+
arab: true, // Arabic
|
|
218
|
+
hebr: true, // Hebrew
|
|
219
|
+
syrc: true, // Syriac
|
|
220
|
+
thaa: true, // Thaana
|
|
221
|
+
cprt: true, // Cypriot Syllabary
|
|
222
|
+
khar: true, // Kharosthi
|
|
223
|
+
phnx: true, // Phoenician
|
|
203
224
|
'nko ': true, // N'Ko
|
|
204
|
-
lydi: true,
|
|
205
|
-
avst: true,
|
|
206
|
-
armi: true,
|
|
207
|
-
phli: true,
|
|
208
|
-
prti: true,
|
|
209
|
-
sarb: true,
|
|
210
|
-
orkh: true,
|
|
211
|
-
samr: true,
|
|
212
|
-
mand: true,
|
|
213
|
-
merc: true,
|
|
214
|
-
mero: true,
|
|
225
|
+
lydi: true, // Lydian
|
|
226
|
+
avst: true, // Avestan
|
|
227
|
+
armi: true, // Imperial Aramaic
|
|
228
|
+
phli: true, // Inscriptional Pahlavi
|
|
229
|
+
prti: true, // Inscriptional Parthian
|
|
230
|
+
sarb: true, // Old South Arabian
|
|
231
|
+
orkh: true, // Old Turkic, Orkhon Runic
|
|
232
|
+
samr: true, // Samaritan
|
|
233
|
+
mand: true, // Mandaic, Mandaean
|
|
234
|
+
merc: true, // Meroitic Cursive
|
|
235
|
+
mero: true, // Meroitic Hieroglyphs
|
|
215
236
|
|
|
216
237
|
// Unicode 7.0 (not listed on http://www.microsoft.com/typography/otspec/scripttags.htm)
|
|
217
|
-
mani: true,
|
|
218
|
-
mend: true,
|
|
219
|
-
nbat: true,
|
|
220
|
-
narb: true,
|
|
221
|
-
palm: true,
|
|
222
|
-
phlp: true
|
|
238
|
+
mani: true, // Manichaean
|
|
239
|
+
mend: true, // Mende Kikakui
|
|
240
|
+
nbat: true, // Nabataean
|
|
241
|
+
narb: true, // Old North Arabian
|
|
242
|
+
palm: true, // Palmyrene
|
|
243
|
+
phlp: true // Psalter Pahlavi
|
|
223
244
|
};
|
|
224
245
|
|
|
246
|
+
/**
|
|
247
|
+
* @param {string | string[] | null | undefined} script
|
|
248
|
+
* @returns {TextDirection}
|
|
249
|
+
*/
|
|
225
250
|
export function direction(script) {
|
|
226
|
-
|
|
251
|
+
let tag = Array.isArray(script) ? script[0] : script;
|
|
252
|
+
if (tag && RTL[tag]) {
|
|
227
253
|
return 'rtl';
|
|
228
254
|
}
|
|
229
255
|
|