Skip to content

Commit 251def5

Browse files
committed
1 parent 83f5034 commit 251def5

25 files changed

Lines changed: 747 additions & 52 deletions

packages/fontkit/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ The following properties describe the general metrics of the font. See [here](ht
102102
* `underlinePosition` - the offset from the normal underline position that should be used
103103
* `underlineThickness` - the weight of the underline that should be used
104104
* `italicAngle` - if this is an italic font, the angle the cursor should be drawn at to match the font design
105+
* `lineHeight` - is the vertical space between adjacent lines (their baselines) of text, also known as leading. See [here](https://en.wikipedia.org/wiki/Leading) for more details.
105106
* `capHeight` - the height of capital letters above the baseline. See [here](http://en.wikipedia.org/wiki/Cap_height) for more details.
106107
* `xHeight`- the height of lower case letters. See [here](http://en.wikipedia.org/wiki/X-height) for more details.
107108
* `bbox` - the font’s bounding box, i.e. the box that encloses all glyphs in the font
@@ -205,7 +206,14 @@ You do not create glyph objects directly. They are created by various methods on
205206
* `path` - a vector Path object representing the glyph
206207
* `bbox` - the glyph’s bounding box, i.e. the rectangle that encloses the glyph outline as tightly as possible.
207208
* `cbox` - the glyph’s control box. This is often the same as the bounding box, but is faster to compute. Because of the way bezier curves are defined, some of the control points can be outside of the bounding box. Where `bbox` takes this into account, `cbox` does not. Thus, `cbox` is less accurate, but faster to compute. See [here](http://www.freetype.org/freetype2/docs/glyphs/glyphs-6.html#section-2) for a more detailed description.
209+
* `width` - the glyph’s width.
210+
* `height` - the glyph’s height.
208211
* `advanceWidth` - the glyph’s advance width.
212+
* `advanceHeight` - the glyph’s advance height.
213+
* `leftBearing` - the glyph’s left side bearing.
214+
* `topBearing` - the glyph’s top side bearing.
215+
* `rightBearing` - the glyph’s right side bearing.
216+
* `bottomBearing` - the glyph’s bottom side bearing.
209217

210218
### `glyph.render(ctx, size)`
211219

packages/fontkit/src/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ interface FontInstance {
4646
variationAxes: Record<string, unknown>;
4747
namedVariations: Record<string, VariationSettings>;
4848
cff: unknown;
49-
"OS/2": { sFamilyClass: number };
49+
"OS/2"?: { sFamilyClass: number };
5050
head: { macStyle: { italic: boolean } };
5151
post: { isFixedPitch: boolean };
5252
hasGlyphForCodePoint(codePoint: number): boolean;

packages/fontkit/src/cmap-processor.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ export default class CmapProcessor {
299299

300300
getVariationSelector(codepoint: number, variationSelector: number): number {
301301
if (!this.#uvs) {
302+
// This font file does not contain information about variation forms.
302303
return 0;
303304
}
304305

@@ -307,8 +308,8 @@ export default class CmapProcessor {
307308
selectors,
308309
(record: VariationSelectorRecord) => variationSelector - record.varSelector,
309310
);
310-
311311
if (selectorIndex === -1) {
312+
// There is no information about the specified variation form.
312313
return 0;
313314
}
314315

@@ -324,16 +325,21 @@ export default class CmapProcessor {
324325
? +1
325326
: 0,
326327
);
328+
if (matchIndex !== -1) {
329+
// Since this variation is the default form of this character,
330+
// the base character’s glyph should be searched for as usual.
331+
return 0;
332+
}
327333
}
328334

329-
if (matchIndex !== -1 && sel.nonDefaultUVS) {
335+
if (sel.nonDefaultUVS) {
330336
const nonDefaultRecords = materialize(sel.nonDefaultUVS);
331337
const nonDefaultIndex = binarySearch(
332338
nonDefaultRecords,
333339
(record: UVSMapping) => codepoint - record.unicodeValue,
334340
);
335-
336341
if (nonDefaultIndex !== -1) {
342+
// The glyph for this variation form has been identified.
337343
return nonDefaultRecords[nonDefaultIndex].glyphID;
338344
}
339345
}
@@ -419,7 +425,7 @@ export default class CmapProcessor {
419425
}
420426
}
421427

422-
if (g === gid) {
428+
if ((g & 0xffff) === gid) {
423429
res.push(c);
424430
}
425431
}

packages/fontkit/src/glyph/cff-glyph.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { StandardEncoding } from "../cff/cff-encodings.js";
2+
import CFFStandardStrings from "../cff/cff-standard-strings.js";
13
import Glyph from "./glyph.js";
24
import Path from "./path.js";
35

@@ -88,12 +90,23 @@ export default class CFFGlyph extends Glyph {
8890
let vsindex = privateDict.vsindex;
8991
const variationProcessor = this._font._variationProcessor;
9092

93+
let encodingVector;
94+
const font = this._font;
95+
9196
const checkWidth = (): void => {
9297
if (width == null) {
9398
width = stack.shift() + privateDict.nominalWidthX;
9499
}
95100
};
96101

102+
function glyphForName(name) {
103+
if (!encodingVector) {
104+
encodingVector = cff.topDict.charset.glyphs.map((g) => CFFStandardStrings[g]);
105+
}
106+
const glyphId = Math.max(0, encodingVector.indexOf(name) + 1); // .notdef is not included, hence + 1
107+
return font.getGlyph(glyphId);
108+
}
109+
97110
const parseStems = (): void => {
98111
if (stack.length % 2 !== 0) {
99112
checkWidth();
@@ -218,7 +231,23 @@ export default class CFFGlyph extends Glyph {
218231
break;
219232
}
220233

221-
if (stack.length > 0) {
234+
if (stack.length >= 4) {
235+
// Type 2 Charstring Format Appendix C
236+
// treat like Type 1 seac command (standard encoding accented character)
237+
const acharName = StandardEncoding?.[stack.pop()];
238+
const bcharName = StandardEncoding?.[stack.pop()];
239+
const ady = stack.pop();
240+
const adx = stack.pop();
241+
// const asb = stack.pop(); // ignored for Type 2
242+
243+
const achar = glyphForName(acharName);
244+
const bchar = glyphForName(bcharName);
245+
246+
const aPathShifted = achar.path.translate(adx, ady);
247+
path.commands = [...bchar.path.commands, ...aPathShifted.commands];
248+
249+
open = false;
250+
} else if (stack.length > 0) {
222251
checkWidth();
223252
}
224253

packages/fontkit/src/glyph/glyph.ts

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ type GlyphMetrics = {
7272
advanceHeight: number;
7373
leftBearing: number;
7474
topBearing: number;
75+
width: number;
76+
height: number;
77+
rightBearing: number;
78+
bottomBearing: number;
7579
};
7680

7781
export type CanvasContextLike = {
@@ -152,44 +156,49 @@ export default class Glyph {
152156
return res;
153157
}
154158

155-
_getMetrics(cbox?: { maxY: number } | null): GlyphMetrics {
159+
_getMetrics(cbox?: { maxY: number; width: number; height: number } | null): GlyphMetrics {
156160
if (this._metrics) {
157161
return this._metrics;
158162
}
163+
if (typeof cbox === "undefined" || cbox === null) {
164+
({ cbox } = this);
165+
}
159166

160167
const { advance: advanceWidthRaw, bearing: leftBearing } = this._getTableMetrics(
161168
this._font.hmtx,
162169
);
163170
let advanceWidth = advanceWidthRaw;
164171

165-
// For vertical metrics, use vmtx if available, or fall back to global data from OS/2 or hhea
172+
// For vertical metrics, use vmtx if available, or fall back to global data
166173
if (this._font.vmtx) {
167174
var { advance: advanceHeight, bearing: topBearing } = this._getTableMetrics(this._font.vmtx);
168175
} else {
169-
let os2;
170176
if (typeof cbox === "undefined" || cbox === null) {
171177
({ cbox } = this);
172178
}
173179

174-
if ((os2 = this._font["OS/2"]) && os2.version > 0) {
175-
var advanceHeight = Math.abs(os2.typoAscender - os2.typoDescender);
176-
var topBearing = os2.typoAscender - cbox.maxY;
177-
} else {
178-
let { hhea } = this._font;
179-
var advanceHeight = Math.abs(hhea.ascent - hhea.descent);
180-
var topBearing = hhea.ascent - cbox.maxY;
181-
}
180+
var advanceHeight = Math.abs(this._font.ascent - this._font.descent);
181+
var topBearing = this._font.ascent - cbox.maxY;
182182
}
183183

184184
if (this._font._variationProcessor && this._font.HVAR) {
185185
advanceWidth += this._font._variationProcessor.getAdvanceAdjustment(this.id, this._font.HVAR);
186186
}
187187

188+
let width = cbox.width;
189+
let height = cbox.height;
190+
let rightBearing = advanceWidth - leftBearing - width;
191+
let bottomBearing = advanceHeight - topBearing - height;
192+
188193
return (this._metrics = {
194+
width,
195+
height,
189196
advanceWidth,
190197
advanceHeight,
191198
leftBearing,
192199
topBearing,
200+
rightBearing,
201+
bottomBearing,
193202
});
194203
}
195204

@@ -249,6 +258,24 @@ export default class Glyph {
249258
return this._getMetrics().advanceWidth;
250259
}
251260

261+
/**
262+
* The glyph's width.
263+
* @type {number}
264+
*/
265+
@cache
266+
get width() {
267+
return this._getMetrics().width;
268+
}
269+
270+
/**
271+
* The glyph's height.
272+
* @type {number}
273+
*/
274+
@cache
275+
get height() {
276+
return this._getMetrics().height;
277+
}
278+
252279
/**
253280
* The glyph's advance height.
254281
* @type {number}
@@ -258,6 +285,42 @@ export default class Glyph {
258285
return this._getMetrics().advanceHeight;
259286
}
260287

288+
/**
289+
* The glyph's left side bearing.
290+
* @type {number}
291+
*/
292+
@cache
293+
get leftBearing() {
294+
return this._getMetrics().leftBearing;
295+
}
296+
297+
/**
298+
* The glyph's top side bearing.
299+
* @type {number}
300+
*/
301+
@cache
302+
get topBearing() {
303+
return this._getMetrics().topBearing;
304+
}
305+
306+
/**
307+
* The glyph's right side bearing.
308+
* @type {number}
309+
*/
310+
@cache
311+
get rightBearing() {
312+
return this._getMetrics().rightBearing;
313+
}
314+
315+
/**
316+
* The glyph's bottom side bearing.
317+
* @type {number}
318+
*/
319+
@cache
320+
get bottomBearing() {
321+
return this._getMetrics().bottomBearing;
322+
}
323+
261324
get ligatureCaretPositions(): number[] | null {
262325
return null;
263326
}

packages/fontkit/src/glyph/path.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ export default class Path {
8383
}
8484
}
8585

86+
if (this.commands.length === 0) {
87+
// No content, put 0 instead of Infinity
88+
cbox.minX = 0;
89+
cbox.minY = 0;
90+
cbox.maxX = 0;
91+
cbox.maxY = 0;
92+
}
93+
8694
this.#cbox = Object.freeze(cbox);
8795
}
8896

@@ -198,6 +206,14 @@ export default class Path {
198206
}
199207
}
200208

209+
if (this.commands.length === 0) {
210+
// No content, put 0 instead of Infinity
211+
bbox.minX = 0;
212+
bbox.minY = 0;
213+
bbox.maxX = 0;
214+
bbox.maxY = 0;
215+
}
216+
201217
this.#bbox = Object.freeze(bbox);
202218
return this.#bbox;
203219
}

packages/fontkit/src/glyph/ttf-glyph.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,17 @@ export default class TTFGlyph extends Glyph {
8787
return this.path.cbox;
8888
}
8989

90+
let glyfPos = this._font.loca.offsets[this.id];
91+
let nextPos = this._font.loca.offsets[this.id + 1];
92+
93+
// No data for this glyph (space?)
94+
if (glyfPos === nextPos) {
95+
return super._getCBox();
96+
}
97+
9098
let stream = this._font._getTableStream("glyf");
91-
stream.pos += this._font.loca.offsets[this.id];
99+
stream.pos += glyfPos;
100+
92101
let glyph = GlyfHeader.decode(stream);
93102

94103
let cbox = new BBox(glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax);

packages/fontkit/src/opentype/gpos-processor.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,9 @@ export default class GPOSProcessor extends OTProcessor {
391391
}
392392

393393
getAnchor(anchor: any): { x: number; y: number } {
394+
if (anchor == null) {
395+
return { x: 0, y: 0 };
396+
}
394397
// TODO: contour point, device tables
395398
let x = anchor.xCoordinate;
396399
let y = anchor.yCoordinate;

packages/fontkit/src/subset/subset.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ export default abstract class Subset<TFont extends FontLike = FontLike> {
88
protected font: TFont;
99
protected glyphs: number[];
1010
protected mapping: Record<number, number>;
11+
protected tables: string[];
1112

1213
constructor(font: TFont) {
1314
this.font = font;
1415
this.glyphs = [];
1516
this.mapping = {};
17+
this.tables = [];
1618

1719
// always include the missing glyph
1820
this.includeGlyph(0);
@@ -29,6 +31,12 @@ export default abstract class Subset<TFont extends FontLike = FontLike> {
2931
return this.mapping[glyphId];
3032
}
3133

34+
includeTable(table: unknown) {
35+
if (typeof table === "string" && table.length === 4) {
36+
this.tables.push(table);
37+
}
38+
}
39+
3240
/**
3341
* Encode the subset to a Uint8Array buffer.
3442
*/

0 commit comments

Comments
 (0)