@cantoo/fontkit 2.0.4

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.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -0
  3. package/dist/browser-module.mjs +13660 -0
  4. package/dist/browser-module.mjs.map +1 -0
  5. package/dist/browser.cjs +13689 -0
  6. package/dist/browser.cjs.map +1 -0
  7. package/dist/main.cjs +13712 -0
  8. package/dist/main.cjs.map +1 -0
  9. package/dist/module.mjs +13673 -0
  10. package/dist/module.mjs.map +1 -0
  11. package/package.json +99 -0
  12. package/src/CmapProcessor.js +285 -0
  13. package/src/DFont.js +113 -0
  14. package/src/TTFFont.js +621 -0
  15. package/src/TrueTypeCollection.js +65 -0
  16. package/src/WOFF2Font.js +222 -0
  17. package/src/WOFFFont.js +56 -0
  18. package/src/aat/AATFeatureMap.js +545 -0
  19. package/src/aat/AATLayoutEngine.js +48 -0
  20. package/src/aat/AATLookupTable.js +125 -0
  21. package/src/aat/AATMorxProcessor.js +429 -0
  22. package/src/aat/AATStateMachine.js +96 -0
  23. package/src/base.js +29 -0
  24. package/src/cff/CFFCharsets.js +99 -0
  25. package/src/cff/CFFDict.js +166 -0
  26. package/src/cff/CFFEncodings.js +48 -0
  27. package/src/cff/CFFFont.js +158 -0
  28. package/src/cff/CFFIndex.js +150 -0
  29. package/src/cff/CFFOperand.js +135 -0
  30. package/src/cff/CFFPointer.js +50 -0
  31. package/src/cff/CFFPrivateDict.js +39 -0
  32. package/src/cff/CFFStandardStrings.js +71 -0
  33. package/src/cff/CFFTop.js +236 -0
  34. package/src/decorators.js +36 -0
  35. package/src/encodings.js +219 -0
  36. package/src/fs.js +12 -0
  37. package/src/glyph/BBox.js +72 -0
  38. package/src/glyph/CBDTGlyph.js +9 -0
  39. package/src/glyph/CFFGlyph.js +635 -0
  40. package/src/glyph/COLRGlyph.js +108 -0
  41. package/src/glyph/Glyph.js +270 -0
  42. package/src/glyph/GlyphVariationProcessor.js +487 -0
  43. package/src/glyph/Path.js +257 -0
  44. package/src/glyph/SBIXGlyph.js +54 -0
  45. package/src/glyph/StandardNames.js +27 -0
  46. package/src/glyph/TTFGlyph.js +408 -0
  47. package/src/glyph/TTFGlyphEncoder.js +158 -0
  48. package/src/glyph/WOFF2Glyph.js +70 -0
  49. package/src/index.js +15 -0
  50. package/src/layout/GlyphPosition.js +30 -0
  51. package/src/layout/GlyphRun.js +108 -0
  52. package/src/layout/KernProcessor.js +97 -0
  53. package/src/layout/LayoutEngine.js +190 -0
  54. package/src/layout/Script.js +231 -0
  55. package/src/layout/UnicodeLayoutEngine.js +250 -0
  56. package/src/node.js +16 -0
  57. package/src/opentype/GPOSProcessor.js +311 -0
  58. package/src/opentype/GSUBProcessor.js +208 -0
  59. package/src/opentype/GlyphInfo.js +57 -0
  60. package/src/opentype/GlyphIterator.js +82 -0
  61. package/src/opentype/OTLayoutEngine.js +117 -0
  62. package/src/opentype/OTProcessor.js +472 -0
  63. package/src/opentype/ShapingPlan.js +121 -0
  64. package/src/opentype/shapers/ArabicShaper.js +124 -0
  65. package/src/opentype/shapers/DefaultShaper.js +116 -0
  66. package/src/opentype/shapers/HangulShaper.js +285 -0
  67. package/src/opentype/shapers/HebrewShaper.js +105 -0
  68. package/src/opentype/shapers/IndicShaper.js +914 -0
  69. package/src/opentype/shapers/ThaiShaper.js +238 -0
  70. package/src/opentype/shapers/UniversalShaper.js +186 -0
  71. package/src/opentype/shapers/data.trie +0 -0
  72. package/src/opentype/shapers/gen-indic.js +208 -0
  73. package/src/opentype/shapers/gen-use.js +287 -0
  74. package/src/opentype/shapers/generate-data.js +33 -0
  75. package/src/opentype/shapers/index.js +109 -0
  76. package/src/opentype/shapers/indic-data.js +175 -0
  77. package/src/opentype/shapers/indic.json +1 -0
  78. package/src/opentype/shapers/indic.machine +31 -0
  79. package/src/opentype/shapers/indic.trie +0 -0
  80. package/src/opentype/shapers/use.json +1 -0
  81. package/src/opentype/shapers/use.machine +44 -0
  82. package/src/opentype/shapers/use.trie +0 -0
  83. package/src/packages/unicode-properties/LICENSE +8 -0
  84. package/src/packages/unicode-properties/README.md +12 -0
  85. package/src/packages/unicode-properties/data-trie.js +1 -0
  86. package/src/packages/unicode-properties/data.js +1 -0
  87. package/src/packages/unicode-properties/data.json +1 -0
  88. package/src/packages/unicode-properties/data.trie +0 -0
  89. package/src/packages/unicode-properties/dist/index.cjs +257 -0
  90. package/src/packages/unicode-properties/dist/index.cjs.map +7 -0
  91. package/src/packages/unicode-properties/dist/index.mjs +234 -0
  92. package/src/packages/unicode-properties/dist/index.mjs.map +7 -0
  93. package/src/packages/unicode-properties/generate.js +107 -0
  94. package/src/packages/unicode-properties/index.js +184 -0
  95. package/src/packages/unicode-properties/package.json +50 -0
  96. package/src/packages/unicode-trie/LICENSE +8 -0
  97. package/src/packages/unicode-trie/README.md +13 -0
  98. package/src/packages/unicode-trie/builder.js +965 -0
  99. package/src/packages/unicode-trie/dist/builder.cjs +711 -0
  100. package/src/packages/unicode-trie/dist/builder.cjs.map +7 -0
  101. package/src/packages/unicode-trie/dist/builder.mjs +690 -0
  102. package/src/packages/unicode-trie/dist/builder.mjs.map +7 -0
  103. package/src/packages/unicode-trie/dist/index.cjs +112 -0
  104. package/src/packages/unicode-trie/dist/index.cjs.map +7 -0
  105. package/src/packages/unicode-trie/dist/index.mjs +91 -0
  106. package/src/packages/unicode-trie/dist/index.mjs.map +7 -0
  107. package/src/packages/unicode-trie/index.js +135 -0
  108. package/src/packages/unicode-trie/package.json +49 -0
  109. package/src/packages/unicode-trie/swap.js +21 -0
  110. package/src/subset/CFFSubset.js +176 -0
  111. package/src/subset/Subset.js +63 -0
  112. package/src/subset/TTFSubset.js +130 -0
  113. package/src/tables/BASE.js +78 -0
  114. package/src/tables/COLR.js +25 -0
  115. package/src/tables/CPAL.js +24 -0
  116. package/src/tables/DSIG.js +21 -0
  117. package/src/tables/EBDT.js +91 -0
  118. package/src/tables/EBLC.js +80 -0
  119. package/src/tables/GDEF.js +57 -0
  120. package/src/tables/GPOS.js +209 -0
  121. package/src/tables/GSUB.js +85 -0
  122. package/src/tables/HVAR.js +44 -0
  123. package/src/tables/JSTF.js +43 -0
  124. package/src/tables/LTSH.js +10 -0
  125. package/src/tables/OS2.js +84 -0
  126. package/src/tables/PCLT.js +21 -0
  127. package/src/tables/STAT.js +99 -0
  128. package/src/tables/VDMX.js +33 -0
  129. package/src/tables/VORG.js +14 -0
  130. package/src/tables/WOFF2Directory.js +73 -0
  131. package/src/tables/WOFFDirectory.js +37 -0
  132. package/src/tables/aat.js +157 -0
  133. package/src/tables/avar.js +19 -0
  134. package/src/tables/bsln.js +31 -0
  135. package/src/tables/cmap.js +127 -0
  136. package/src/tables/cvt.js +6 -0
  137. package/src/tables/directory.js +58 -0
  138. package/src/tables/feat.js +28 -0
  139. package/src/tables/fpgm.js +8 -0
  140. package/src/tables/fvar.js +31 -0
  141. package/src/tables/gasp.js +15 -0
  142. package/src/tables/glyf.js +4 -0
  143. package/src/tables/gvar.js +27 -0
  144. package/src/tables/hdmx.js +15 -0
  145. package/src/tables/head.js +25 -0
  146. package/src/tables/hhea.js +19 -0
  147. package/src/tables/hmtx.js +11 -0
  148. package/src/tables/index.js +119 -0
  149. package/src/tables/just.js +81 -0
  150. package/src/tables/kern.js +91 -0
  151. package/src/tables/loca.js +30 -0
  152. package/src/tables/maxp.js +20 -0
  153. package/src/tables/morx.js +79 -0
  154. package/src/tables/name.js +157 -0
  155. package/src/tables/opbd.js +15 -0
  156. package/src/tables/opentype.js +217 -0
  157. package/src/tables/post.js +34 -0
  158. package/src/tables/prep.js +6 -0
  159. package/src/tables/sbix.js +17 -0
  160. package/src/tables/variations.js +80 -0
  161. package/src/tables/vhea.js +19 -0
  162. package/src/tables/vmtx.js +12 -0
  163. package/src/utils.js +62 -0
@@ -0,0 +1,408 @@
1
+ import Glyph from './Glyph';
2
+ import Path from './Path';
3
+ import BBox from './BBox';
4
+ import * as r from 'restructure';
5
+
6
+ // The header for both simple and composite glyphs
7
+ let GlyfHeader = new r.Struct({
8
+ numberOfContours: r.int16, // if negative, this is a composite glyph
9
+ xMin: r.int16,
10
+ yMin: r.int16,
11
+ xMax: r.int16,
12
+ yMax: r.int16
13
+ });
14
+
15
+ // Flags for simple glyphs
16
+ const ON_CURVE = 1 << 0;
17
+ const X_SHORT_VECTOR = 1 << 1;
18
+ const Y_SHORT_VECTOR = 1 << 2;
19
+ const REPEAT = 1 << 3;
20
+ const SAME_X = 1 << 4;
21
+ const SAME_Y = 1 << 5;
22
+
23
+ // Flags for composite glyphs
24
+ const ARG_1_AND_2_ARE_WORDS = 1 << 0;
25
+ const _ARGS_ARE_XY_VALUES = 1 << 1;
26
+ const _ROUND_XY_TO_GRID = 1 << 2;
27
+ const WE_HAVE_A_SCALE = 1 << 3;
28
+ const MORE_COMPONENTS = 1 << 5;
29
+ const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
30
+ const WE_HAVE_A_TWO_BY_TWO = 1 << 7;
31
+ const WE_HAVE_INSTRUCTIONS = 1 << 8;
32
+ const _USE_MY_METRICS = 1 << 9;
33
+ const _OVERLAP_COMPOUND = 1 << 10;
34
+ const _SCALED_COMPONENT_OFFSET = 1 << 11;
35
+ const _UNSCALED_COMPONENT_OFFSET = 1 << 12;
36
+
37
+ // Represents a point in a simple glyph
38
+ export class Point {
39
+ constructor(onCurve, endContour, x = 0, y = 0) {
40
+ this.onCurve = onCurve;
41
+ this.endContour = endContour;
42
+ this.x = x;
43
+ this.y = y;
44
+ }
45
+
46
+ copy() {
47
+ return new Point(this.onCurve, this.endContour, this.x, this.y);
48
+ }
49
+ }
50
+
51
+ // Represents a component in a composite glyph
52
+ class Component {
53
+ constructor(glyphID, dx, dy) {
54
+ this.glyphID = glyphID;
55
+ this.dx = dx;
56
+ this.dy = dy;
57
+ this.pos = 0;
58
+ this.scaleX = this.scaleY = 1;
59
+ this.scale01 = this.scale10 = 0;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Represents a TrueType glyph.
65
+ */
66
+ export default class TTFGlyph extends Glyph {
67
+ type = 'TTF';
68
+
69
+ // Parses just the glyph header and returns the bounding box
70
+ _getCBox(internal) {
71
+ // We need to decode the glyph if variation processing is requested,
72
+ // so it's easier just to recompute the path's cbox after decoding.
73
+ if (this._font._variationProcessor && !internal) {
74
+ return this.path.cbox;
75
+ }
76
+
77
+ let glyfPos = this._font.loca.offsets[this.id];
78
+ // No outline data (e.g. space): avoid decoding the next glyph's header
79
+ if (glyfPos === this._font.loca.offsets[this.id + 1]) {
80
+ return Object.freeze(new BBox(0, 0, 0, 0));
81
+ }
82
+
83
+ let stream = this._font._getTableStream('glyf');
84
+ stream.pos += glyfPos;
85
+ let glyph = GlyfHeader.decode(stream);
86
+ return Object.freeze(new BBox(glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax));
87
+ }
88
+
89
+ // Parses a single glyph coordinate
90
+ _parseGlyphCoord(stream, prev, short, same) {
91
+ let val;
92
+ if (short) {
93
+ val = stream.readUInt8();
94
+ if (!same) {
95
+ val = -val;
96
+ }
97
+
98
+ val += prev;
99
+ } else {
100
+ if (same) {
101
+ val = prev;
102
+ } else {
103
+ val = prev + stream.readInt16BE();
104
+ }
105
+ }
106
+
107
+ return val;
108
+ }
109
+
110
+ // Decodes the glyph data into points for simple glyphs,
111
+ // or components for composite glyphs
112
+ _decode() {
113
+ let glyfPos = this._font.loca.offsets[this.id];
114
+ let nextPos = this._font.loca.offsets[this.id + 1];
115
+
116
+ // Nothing to do if there is no data for this glyph
117
+ if (glyfPos === nextPos) { return null; }
118
+
119
+ let stream = this._font._getTableStream('glyf');
120
+ stream.pos += glyfPos;
121
+ let startPos = stream.pos;
122
+
123
+ let glyph = GlyfHeader.decode(stream);
124
+
125
+ if (glyph.numberOfContours > 0) {
126
+ this._decodeSimple(glyph, stream);
127
+
128
+ } else if (glyph.numberOfContours < 0) {
129
+ this._decodeComposite(glyph, stream, startPos);
130
+ }
131
+
132
+ return glyph;
133
+ }
134
+
135
+ _decodeSimple(glyph, stream) {
136
+ // this is a simple glyph
137
+ glyph.points = [];
138
+
139
+ let endPtsOfContours = new r.Array(r.uint16, glyph.numberOfContours).decode(stream);
140
+ glyph.instructions = new r.Array(r.uint8, r.uint16).decode(stream);
141
+
142
+ let flags = [];
143
+ let numCoords = endPtsOfContours[endPtsOfContours.length - 1] + 1;
144
+
145
+ while (flags.length < numCoords) {
146
+ let flag = stream.readUInt8();
147
+ flags.push(flag);
148
+
149
+ // check for repeat flag
150
+ if (flag & REPEAT) {
151
+ let count = stream.readUInt8();
152
+ for (let j = 0; j < count; j++) {
153
+ flags.push(flag);
154
+ }
155
+ }
156
+ }
157
+
158
+ for (let i = 0; i < flags.length; i++) {
159
+ let flag = flags[i];
160
+ let point = new Point(!!(flag & ON_CURVE), endPtsOfContours.indexOf(i) >= 0, 0, 0);
161
+ glyph.points.push(point);
162
+ }
163
+
164
+ let px = 0;
165
+ for (let i = 0; i < flags.length; i++) {
166
+ let flag = flags[i];
167
+ glyph.points[i].x = px = this._parseGlyphCoord(stream, px, flag & X_SHORT_VECTOR, flag & SAME_X);
168
+ }
169
+
170
+ let py = 0;
171
+ for (let i = 0; i < flags.length; i++) {
172
+ let flag = flags[i];
173
+ glyph.points[i].y = py = this._parseGlyphCoord(stream, py, flag & Y_SHORT_VECTOR, flag & SAME_Y);
174
+ }
175
+
176
+ if (this._font._variationProcessor) {
177
+ let points = glyph.points.slice();
178
+ points.push(...this._getPhantomPoints(glyph));
179
+
180
+ this._font._variationProcessor.transformPoints(this.id, points);
181
+ glyph.phantomPoints = points.slice(-4);
182
+ }
183
+
184
+ return;
185
+ }
186
+
187
+ _decodeComposite(glyph, stream, offset = 0) {
188
+ // this is a composite glyph
189
+ glyph.components = [];
190
+ let haveInstructions = false;
191
+ let flags = MORE_COMPONENTS;
192
+
193
+ while (flags & MORE_COMPONENTS) {
194
+ flags = stream.readUInt16BE();
195
+ let gPos = stream.pos - offset;
196
+ let glyphID = stream.readUInt16BE();
197
+ if (!haveInstructions) {
198
+ haveInstructions = (flags & WE_HAVE_INSTRUCTIONS) !== 0;
199
+ }
200
+
201
+ let dx, dy;
202
+ if (flags & ARG_1_AND_2_ARE_WORDS) {
203
+ dx = stream.readInt16BE();
204
+ dy = stream.readInt16BE();
205
+ } else {
206
+ dx = stream.readInt8();
207
+ dy = stream.readInt8();
208
+ }
209
+
210
+ let component = new Component(glyphID, dx, dy);
211
+ component.pos = gPos;
212
+
213
+ if (flags & WE_HAVE_A_SCALE) {
214
+ // fixed number with 14 bits of fraction
215
+ component.scaleX =
216
+ component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
217
+
218
+ } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
219
+ component.scaleX = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
220
+ component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
221
+
222
+ } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
223
+ component.scaleX = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
224
+ component.scale01 = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
225
+ component.scale10 = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
226
+ component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824;
227
+ }
228
+
229
+ glyph.components.push(component);
230
+ }
231
+
232
+ if (this._font._variationProcessor) {
233
+ let points = [];
234
+ for (let j = 0; j < glyph.components.length; j++) {
235
+ let component = glyph.components[j];
236
+ points.push(new Point(true, true, component.dx, component.dy));
237
+ }
238
+
239
+ points.push(...this._getPhantomPoints(glyph));
240
+
241
+ this._font._variationProcessor.transformPoints(this.id, points);
242
+ glyph.phantomPoints = points.splice(-4, 4);
243
+
244
+ for (let i = 0; i < points.length; i++) {
245
+ let point = points[i];
246
+ glyph.components[i].dx = point.x;
247
+ glyph.components[i].dy = point.y;
248
+ }
249
+ }
250
+
251
+ return haveInstructions;
252
+ }
253
+
254
+ _getPhantomPoints(glyph) {
255
+ let cbox = this._getCBox(true);
256
+ if (this._metrics == null) {
257
+ this._metrics = Glyph.prototype._getMetrics.call(this, cbox);
258
+ }
259
+
260
+ let { advanceWidth, advanceHeight, leftBearing, topBearing } = this._metrics;
261
+
262
+ return [
263
+ new Point(false, true, glyph.xMin - leftBearing, 0),
264
+ new Point(false, true, glyph.xMin - leftBearing + advanceWidth, 0),
265
+ new Point(false, true, 0, glyph.yMax + topBearing),
266
+ new Point(false, true, 0, glyph.yMax + topBearing + advanceHeight)
267
+ ];
268
+ }
269
+
270
+ // Decodes font data, resolves composite glyphs, and returns an array of contours
271
+ _getContours() {
272
+ let glyph = this._decode();
273
+ if (!glyph) {
274
+ return [];
275
+ }
276
+
277
+ let points = [];
278
+
279
+ if (glyph.numberOfContours < 0) {
280
+ // resolve composite glyphs
281
+ for (let component of glyph.components) {
282
+ let componentGlyph = this._font._getBaseGlyph(component.glyphID);
283
+ if (!componentGlyph) {
284
+ continue;
285
+ }
286
+
287
+ let contours = componentGlyph._getContours();
288
+ for (let i = 0; i < contours.length; i++) {
289
+ let contour = contours[i];
290
+ for (let j = 0; j < contour.length; j++) {
291
+ let point = contour[j];
292
+ let x = point.x * component.scaleX + point.y * component.scale01 + component.dx;
293
+ let y = point.y * component.scaleY + point.x * component.scale10 + component.dy;
294
+ points.push(new Point(point.onCurve, point.endContour, x, y));
295
+ }
296
+ }
297
+ }
298
+ } else {
299
+ points = glyph.points || [];
300
+ }
301
+
302
+ // Recompute and cache metrics if we performed variation processing, and don't have an HVAR table
303
+ if (glyph.phantomPoints && !this._font.directory.tables.HVAR) {
304
+ let m = this._metrics;
305
+ m.advanceWidth = glyph.phantomPoints[1].x - glyph.phantomPoints[0].x;
306
+ m.advanceHeight = glyph.phantomPoints[3].y - glyph.phantomPoints[2].y;
307
+ m.leftBearing = glyph.xMin - glyph.phantomPoints[0].x;
308
+ m.topBearing = glyph.phantomPoints[2].y - glyph.yMax;
309
+ m.rightBearing = m.advanceWidth - m.leftBearing - m.width;
310
+ m.bottomBearing = m.advanceHeight - m.topBearing - m.height;
311
+ }
312
+
313
+ let contours = [];
314
+ let cur = [];
315
+ for (let k = 0; k < points.length; k++) {
316
+ var point = points[k];
317
+ cur.push(point);
318
+ if (point.endContour) {
319
+ contours.push(cur);
320
+ cur = [];
321
+ }
322
+ }
323
+
324
+ return contours;
325
+ }
326
+
327
+ _getMetrics() {
328
+ if (this._metrics) {
329
+ return this._metrics;
330
+ }
331
+
332
+ let cbox = this._getCBox(true);
333
+ super._getMetrics(cbox);
334
+
335
+ if (this._font._variationProcessor && !this._font.HVAR) {
336
+ // No HVAR table, decode the glyph. This triggers recomputation of metrics.
337
+ this.path;
338
+ }
339
+
340
+ return this._metrics;
341
+ }
342
+
343
+ // Converts contours to a Path object that can be rendered
344
+ _getPath() {
345
+ let contours = this._getContours();
346
+ let path = new Path;
347
+
348
+ for (let i = 0; i < contours.length; i++) {
349
+ let contour = contours[i];
350
+ let firstPt = contour[0];
351
+ let lastPt = contour[contour.length - 1];
352
+ let start = 0;
353
+ let curvePt;
354
+
355
+ if (firstPt.onCurve) {
356
+ // The first point will be consumed by the moveTo command, so skip in the loop
357
+ curvePt = null;
358
+ start = 1;
359
+ } else {
360
+ if (lastPt.onCurve) {
361
+ // Start at the last point if the first point is off curve and the last point is on curve
362
+ firstPt = lastPt;
363
+ } else {
364
+ // Start at the middle if both the first and last points are off curve
365
+ firstPt = new Point(false, false, (firstPt.x + lastPt.x) / 2, (firstPt.y + lastPt.y) / 2);
366
+ }
367
+
368
+ curvePt = firstPt;
369
+ }
370
+
371
+ path.moveTo(firstPt.x, firstPt.y);
372
+
373
+ for (let j = start; j < contour.length; j++) {
374
+ let pt = contour[j];
375
+ let prevPt = j === 0 ? firstPt : contour[j - 1];
376
+
377
+ if (prevPt.onCurve && pt.onCurve) {
378
+ path.lineTo(pt.x, pt.y);
379
+
380
+ } else if (prevPt.onCurve && !pt.onCurve) {
381
+ curvePt = pt;
382
+
383
+ } else if (!prevPt.onCurve && !pt.onCurve) {
384
+ let midX = (prevPt.x + pt.x) / 2;
385
+ let midY = (prevPt.y + pt.y) / 2;
386
+ path.quadraticCurveTo(prevPt.x, prevPt.y, midX, midY);
387
+ curvePt = pt;
388
+
389
+ } else if (!prevPt.onCurve && pt.onCurve) {
390
+ path.quadraticCurveTo(curvePt.x, curvePt.y, pt.x, pt.y);
391
+ curvePt = null;
392
+
393
+ } else {
394
+ throw new Error("Unknown TTF path state");
395
+ }
396
+ }
397
+
398
+ // Connect the first and last points
399
+ if (curvePt) {
400
+ path.quadraticCurveTo(curvePt.x, curvePt.y, firstPt.x, firstPt.y);
401
+ }
402
+
403
+ path.closePath();
404
+ }
405
+
406
+ return path;
407
+ }
408
+ }
@@ -0,0 +1,158 @@
1
+ import * as r from 'restructure';
2
+
3
+ // Flags for simple glyphs
4
+ const ON_CURVE = 1 << 0;
5
+ const X_SHORT_VECTOR = 1 << 1;
6
+ const Y_SHORT_VECTOR = 1 << 2;
7
+ const REPEAT = 1 << 3;
8
+ const SAME_X = 1 << 4;
9
+ const SAME_Y = 1 << 5;
10
+
11
+ class Point {
12
+ static size(val) {
13
+ return val >= 0 && val <= 255 ? 1 : 2;
14
+ }
15
+
16
+ static encode(stream, value) {
17
+ if (value >= 0 && value <= 255) {
18
+ stream.writeUInt8(value);
19
+ } else {
20
+ stream.writeInt16BE(value);
21
+ }
22
+ }
23
+ }
24
+
25
+ let Glyf = new r.Struct({
26
+ numberOfContours: r.int16, // if negative, this is a composite glyph
27
+ xMin: r.int16,
28
+ yMin: r.int16,
29
+ xMax: r.int16,
30
+ yMax: r.int16,
31
+ endPtsOfContours: new r.Array(r.uint16, 'numberOfContours'),
32
+ instructions: new r.Array(r.uint8, r.uint16),
33
+ flags: new r.Array(r.uint8, 0),
34
+ xPoints: new r.Array(Point, 0),
35
+ yPoints: new r.Array(Point, 0)
36
+ });
37
+
38
+ /**
39
+ * Encodes TrueType glyph outlines
40
+ */
41
+ export default class TTFGlyphEncoder {
42
+ encodeSimple(path, instructions = []) {
43
+ let endPtsOfContours = [];
44
+ let xPoints = [];
45
+ let yPoints = [];
46
+ let flags = [];
47
+ let same = 0;
48
+ let lastX = 0, lastY = 0, lastFlag = 0;
49
+ let pointCount = 0;
50
+
51
+ for (let i = 0; i < path.commands.length; i++) {
52
+ let c = path.commands[i];
53
+
54
+ for (let j = 0; j < c.args.length; j += 2) {
55
+ let x = c.args[j];
56
+ let y = c.args[j + 1];
57
+ let flag = 0;
58
+
59
+ // If the ending point of a quadratic curve is the midpoint
60
+ // between the control point and the control point of the next
61
+ // quadratic curve, we can omit the ending point.
62
+ if (c.command === 'quadraticCurveTo' && j === 2) {
63
+ let next = path.commands[i + 1];
64
+ if (next && next.command === 'quadraticCurveTo') {
65
+ let midX = (lastX + next.args[0]) / 2;
66
+ let midY = (lastY + next.args[1]) / 2;
67
+
68
+ if (x === midX && y === midY) {
69
+ continue;
70
+ }
71
+ }
72
+ }
73
+
74
+ // All points except control points are on curve.
75
+ if (!(c.command === 'quadraticCurveTo' && j === 0)) {
76
+ flag |= ON_CURVE;
77
+ }
78
+
79
+ flag = this._encodePoint(x, lastX, xPoints, flag, X_SHORT_VECTOR, SAME_X);
80
+ flag = this._encodePoint(y, lastY, yPoints, flag, Y_SHORT_VECTOR, SAME_Y);
81
+
82
+ if (flag === lastFlag && same < 255) {
83
+ flags[flags.length - 1] |= REPEAT;
84
+ same++;
85
+ } else {
86
+ if (same > 0) {
87
+ flags.push(same);
88
+ same = 0;
89
+ }
90
+
91
+ flags.push(flag);
92
+ lastFlag = flag;
93
+ }
94
+
95
+ lastX = x;
96
+ lastY = y;
97
+ pointCount++;
98
+ }
99
+
100
+ if (c.command === 'closePath') {
101
+ endPtsOfContours.push(pointCount - 1);
102
+ }
103
+ }
104
+
105
+ // Close the path if the last command didn't already
106
+ if (path.commands.length > 1 && path.commands[path.commands.length - 1].command !== 'closePath') {
107
+ endPtsOfContours.push(pointCount - 1);
108
+ }
109
+
110
+ let bbox = path.bbox;
111
+ let glyf = {
112
+ numberOfContours: endPtsOfContours.length,
113
+ xMin: bbox.minX,
114
+ yMin: bbox.minY,
115
+ xMax: bbox.maxX,
116
+ yMax: bbox.maxY,
117
+ endPtsOfContours: endPtsOfContours,
118
+ instructions: instructions,
119
+ flags: flags,
120
+ xPoints: xPoints,
121
+ yPoints: yPoints
122
+ };
123
+
124
+ let size = Glyf.size(glyf);
125
+ let tail = 4 - (size % 4);
126
+
127
+ let stream = new r.EncodeStream(size + tail);
128
+ Glyf.encode(stream, glyf);
129
+
130
+ // Align to 4-byte length
131
+ if (tail !== 0) {
132
+ stream.fill(0, tail);
133
+ }
134
+
135
+ return stream.buffer;
136
+ }
137
+
138
+ _encodePoint(value, last, points, flag, shortFlag, sameFlag) {
139
+ let diff = value - last;
140
+
141
+ if (value === last) {
142
+ flag |= sameFlag;
143
+ } else {
144
+ if (-255 <= diff && diff <= 255) {
145
+ flag |= shortFlag;
146
+ if (diff < 0) {
147
+ diff = -diff;
148
+ } else {
149
+ flag |= sameFlag;
150
+ }
151
+ }
152
+
153
+ points.push(diff);
154
+ }
155
+
156
+ return flag;
157
+ }
158
+ }
@@ -0,0 +1,70 @@
1
+ import TTFGlyph, { Point } from './TTFGlyph';
2
+ import BBox from './BBox';
3
+
4
+ /**
5
+ * Represents a TrueType glyph in the WOFF2 format, which compresses glyphs differently.
6
+ */
7
+ export default class WOFF2Glyph extends TTFGlyph {
8
+ type = 'WOFF2';
9
+
10
+ _decode() {
11
+ let cached = this._font._transformedGlyphs[this.id];
12
+ if (!cached) {
13
+ return null;
14
+ }
15
+
16
+ if (!this._font._variationProcessor) {
17
+ // Non-variable path: return the pre-decoded glyph without copying.
18
+ return cached;
19
+ }
20
+
21
+ // Clone so per-instance deltas do not mutate the shared transformed cache.
22
+ // Transformed glyf has no per-glyph bbox; zeros keep phantom-point math
23
+ // aligned with hmtx (same approach as a missing bbox bitmap entry).
24
+ let glyph = {
25
+ numberOfContours: cached.numberOfContours,
26
+ xMin: 0,
27
+ yMin: 0,
28
+ xMax: 0,
29
+ yMax: 0,
30
+ };
31
+
32
+ if (cached.points) {
33
+ glyph.points = cached.points.map(p => p.copy());
34
+ let points = glyph.points.concat(this._getPhantomPoints(glyph));
35
+ this._font._variationProcessor.transformPoints(this.id, points);
36
+ glyph.phantomPoints = points.slice(-4);
37
+ }
38
+
39
+ if (cached.components) {
40
+ glyph.components = cached.components.map(c => {
41
+ return Object.assign(Object.create(Object.getPrototypeOf(c)), c);
42
+ });
43
+
44
+ let points = [];
45
+ for (let component of glyph.components) {
46
+ points.push(new Point(true, true, component.dx, component.dy));
47
+ }
48
+ points.push(...this._getPhantomPoints(glyph));
49
+
50
+ this._font._variationProcessor.transformPoints(this.id, points);
51
+ glyph.phantomPoints = points.splice(-4, 4);
52
+
53
+ for (let i = 0; i < points.length; i++) {
54
+ glyph.components[i].dx = points[i].x;
55
+ glyph.components[i].dy = points[i].y;
56
+ }
57
+ }
58
+
59
+ return glyph;
60
+ }
61
+
62
+ _getCBox(internal) {
63
+ // Avoid path recursion while building phantom points during variation decode.
64
+ if (internal) {
65
+ return new BBox(0, 0, 0, 0);
66
+ }
67
+
68
+ return this.path.bbox;
69
+ }
70
+ }
package/src/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import { registerFormat } from './base';
2
+ import TTFFont from './TTFFont';
3
+ import WOFFFont from './WOFFFont';
4
+ import WOFF2Font from './WOFF2Font';
5
+ import TrueTypeCollection from './TrueTypeCollection';
6
+ import DFont from './DFont';
7
+
8
+ // Register font formats
9
+ registerFormat(TTFFont);
10
+ registerFormat(WOFFFont);
11
+ registerFormat(WOFF2Font);
12
+ registerFormat(TrueTypeCollection);
13
+ registerFormat(DFont);
14
+
15
+ export * from './base';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Represents positioning information for a glyph in a GlyphRun.
3
+ */
4
+ export default class GlyphPosition {
5
+ constructor(xAdvance = 0, yAdvance = 0, xOffset = 0, yOffset = 0) {
6
+ /**
7
+ * The amount to move the virtual pen in the X direction after rendering this glyph.
8
+ * @type {number}
9
+ */
10
+ this.xAdvance = xAdvance;
11
+
12
+ /**
13
+ * The amount to move the virtual pen in the Y direction after rendering this glyph.
14
+ * @type {number}
15
+ */
16
+ this.yAdvance = yAdvance;
17
+
18
+ /**
19
+ * The offset from the pen position in the X direction at which to render this glyph.
20
+ * @type {number}
21
+ */
22
+ this.xOffset = xOffset;
23
+
24
+ /**
25
+ * The offset from the pen position in the Y direction at which to render this glyph.
26
+ * @type {number}
27
+ */
28
+ this.yOffset = yOffset;
29
+ }
30
+ }