From c4a882018a80038e556e3bb4b7674f829856c0ea Mon Sep 17 00:00:00 2001 From: Vsevolod Volkov Date: Sun, 26 Mar 2023 20:50:41 +0300 Subject: [PATCH 1/6] Fix `VHEA` table structure Signed-off-by: Vsevolod Volkov --- src/tables/vhea.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tables/vhea.js b/src/tables/vhea.js index 004cdf93..c0f4ddd4 100644 --- a/src/tables/vhea.js +++ b/src/tables/vhea.js @@ -2,7 +2,8 @@ import * as r from 'restructure'; // Vertical Header Table export default new r.Struct({ - version: r.uint16, // Version number of the Vertical Header Table + majorVersion: r.uint16, // Major version number of the Vertical Header Table + minorVersion: r.uint16, // Minor version number of the Vertical Header Table ascent: r.int16, // The vertical typographic ascender for this font descent: r.int16, // The vertical typographic descender for this font lineGap: r.int16, // The vertical typographic line gap for this font From 7c7e6ac889056bb09e39b46e2950a906df3f9fc7 Mon Sep 17 00:00:00 2001 From: Vsevolod Volkov Date: Fri, 7 Apr 2023 18:34:02 +0300 Subject: [PATCH 2/6] Fix loading non-existent data from `glyf` table Signed-off-by: Vsevolod Volkov --- src/glyph/TTFGlyph.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/glyph/TTFGlyph.js b/src/glyph/TTFGlyph.js index 8dc1c75b..4a001be7 100644 --- a/src/glyph/TTFGlyph.js +++ b/src/glyph/TTFGlyph.js @@ -74,8 +74,16 @@ export default class TTFGlyph extends Glyph { return this.path.cbox; } + let glyfPos = this._font.loca.offsets[this.id]; + let nextPos = this._font.loca.offsets[this.id + 1]; + + // No data for this glyph (space?) + if (glyfPos === nextPos) { + return super._getCBox(); + } + let stream = this._font._getTableStream('glyf'); - stream.pos += this._font.loca.offsets[this.id]; + stream.pos += glyfPos; let glyph = GlyfHeader.decode(stream); let cbox = new BBox(glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax); From 6b9538a158af66758590c5d028b9d9a6669d5730 Mon Sep 17 00:00:00 2001 From: Vsevolod Volkov Date: Fri, 7 Apr 2023 18:35:25 +0300 Subject: [PATCH 3/6] Setting zero coordinates for empty glyphs (space) Signed-off-by: Vsevolod Volkov --- src/glyph/Path.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/glyph/Path.js b/src/glyph/Path.js index a119fb28..5396b088 100644 --- a/src/glyph/Path.js +++ b/src/glyph/Path.js @@ -63,6 +63,14 @@ export default class Path { } } + if (this.commands.length === 0) { + // No content, put 0 instead of Infinity + cbox.minX = 0; + cbox.minY = 0; + cbox.maxX = 0; + cbox.maxY = 0; + } + this._cbox = Object.freeze(cbox); } @@ -172,6 +180,14 @@ export default class Path { } } + if (this.commands.length === 0) { + // No content, put 0 instead of Infinity + bbox.minX = 0; + bbox.minY = 0; + bbox.maxX = 0; + bbox.maxY = 0; + } + return this._bbox = Object.freeze(bbox); } From 30acdf8424eb3b1dfc659f7a9f4bb130ddfc94dd Mon Sep 17 00:00:00 2001 From: Vsevolod Volkov Date: Mon, 10 Apr 2023 16:32:22 +0300 Subject: [PATCH 4/6] Handling `maxp`, `post, `vhea` versions as Version16Dot16 Signed-off-by: Vsevolod Volkov --- src/tables/maxp.js | 3 ++- src/tables/post.js | 3 ++- src/tables/vhea.js | 4 ++-- src/utils.js | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/tables/maxp.js b/src/tables/maxp.js index 3e211388..b85f17aa 100644 --- a/src/tables/maxp.js +++ b/src/tables/maxp.js @@ -1,8 +1,9 @@ import * as r from 'restructure'; +import { version16Dot16 } from '../utils'; // maxiumum profile export default new r.Struct({ - version: r.int32, + version: version16Dot16, numGlyphs: r.uint16, // The number of glyphs in the font maxPoints: r.uint16, // Maximum points in a non-composite glyph maxContours: r.uint16, // Maximum contours in a non-composite glyph diff --git a/src/tables/post.js b/src/tables/post.js index 04fdeabe..d03da1db 100644 --- a/src/tables/post.js +++ b/src/tables/post.js @@ -1,7 +1,8 @@ import * as r from 'restructure'; +import { version16Dot16 } from '../utils'; // PostScript information -export default new r.VersionedStruct(r.fixed32, { +export default new r.VersionedStruct(version16Dot16, { header: { // these fields exist at the top of all versions italicAngle: r.fixed32, // Italic angle in counter-clockwise degrees from the vertical. underlinePosition: r.int16, // Suggested distance of the top of the underline from the baseline diff --git a/src/tables/vhea.js b/src/tables/vhea.js index c0f4ddd4..ba6c7428 100644 --- a/src/tables/vhea.js +++ b/src/tables/vhea.js @@ -1,9 +1,9 @@ import * as r from 'restructure'; +import { version16Dot16 } from '../utils'; // Vertical Header Table export default new r.Struct({ - majorVersion: r.uint16, // Major version number of the Vertical Header Table - minorVersion: r.uint16, // Minor version number of the Vertical Header Table + version: version16Dot16, ascent: r.int16, // The vertical typographic ascender for this font descent: r.int16, // The vertical typographic descender for this font lineGap: r.int16, // The vertical typographic line gap for this font diff --git a/src/utils.js b/src/utils.js index 72db5760..2389c9d2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,3 +1,5 @@ +import { DecodeStream, EncodeStream } from 'restructure'; + export function binarySearch(arr, cmp) { let min = 0; let max = arr.length - 1; @@ -60,3 +62,37 @@ export function decodeBase64(base64) { return bytes; } + +export class Version16Dot16 { + fromBuffer(buffer) { + let stream = new DecodeStream(buffer); + return this.decode(stream); + } + + toBuffer(value) { + let size = this.size(value); + let buffer = new Uint8Array(size); + let stream = new EncodeStream(buffer); + this.encode(stream, value); + return buffer; + } + + size() { + return 4; + } + + decode(stream) { + let major = stream.readUInt16BE(); + let minor = stream.readUInt16BE() >> 12; + return major + minor / 10; + } + + encode(stream, val) { + let major = Math.trunc(val); + let minor = (val - major) * 10; + stream.writeUInt16BE(major); + return stream.writeUInt16BE(minor << 12); + } +} + +export const version16Dot16 = new Version16Dot16(); From dad5cca73caa7931424da933d6914aac07ab55ea Mon Sep 17 00:00:00 2001 From: Vsevolod Volkov Date: Fri, 7 Apr 2023 00:33:03 +0300 Subject: [PATCH 5/6] Added respect for the "useTypoMetrics" flag Signed-off-by: Vsevolod Volkov --- README.md | 1 + src/TTFFont.js | 53 +++++++++++++++++++++++++++++++++++++++++++--- src/glyph/Glyph.js | 17 +++------------ 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 027b61ce..248bf3e5 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ The following properties describe the general metrics of the font. See [here](ht * `underlinePosition` - the offset from the normal underline position that should be used * `underlineThickness` - the weight of the underline that should be used * `italicAngle` - if this is an italic font, the angle the cursor should be drawn at to match the font design +* `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. * `capHeight` - the height of capital letters above the baseline. See [here](http://en.wikipedia.org/wiki/Cap_height) for more details. * `xHeight`- the height of lower case letters. See [here](http://en.wikipedia.org/wiki/X-height) for more details. * `bbox` - the font’s bounding box, i.e. the box that encloses all glyphs in the font diff --git a/src/TTFFont.js b/src/TTFFont.js index 6aa0937a..08483b4f 100644 --- a/src/TTFFont.js +++ b/src/TTFFont.js @@ -91,6 +91,44 @@ export default class TTFFont { return result; } + _getMetrics() { + if (this._metrics) { return this._metrics; } + + let { 'OS/2': os2, hhea } = this; + let useTypoMetrics = os2.fsSelection.useTypoMetrics; + let ascent, descent, lineGap, lineHeight; + + // Use the same approach as FreeType + // https://gitlab.freedesktop.org/freetype/freetype/-/blob/4d8db130ea4342317581bab65fc96365ce806b77/src/sfnt/sfobjs.c#L1310 + + if (useTypoMetrics) { + ascent = os2.typoAscender; + descent = os2.typoDescender; + lineGap = os2.typoLineGap; + lineHeight = ascent - descent + lineGap; + } else { + ascent = hhea.ascent; + descent = hhea.descent; + lineGap = hhea.lineGap; + lineHeight = ascent - descent + lineGap; + } + + if (!ascent || !descent) { + if (os2.typoAscender || os2.typoDescender) { + ascent = os2.typoAscender; + descent = os2.typoDescender; + lineGap = os2.typoLineGap; + lineHeight = ascent - descent + lineGap; + } else { + ascent = os2.winAscent; + descent = -os2.winDescent; + lineHeight = ascent - descent; + } + } + + return this._metrics = {ascent, descent, lineGap, lineHeight}; + } + /** * Gets a string from the font's `name` table * `lang` is a BCP-47 language code. @@ -166,7 +204,7 @@ export default class TTFFont { * @type {number} */ get ascent() { - return this.hhea.ascent; + return this._getMetrics().ascent; } /** @@ -174,7 +212,7 @@ export default class TTFFont { * @type {number} */ get descent() { - return this.hhea.descent; + return this._getMetrics().descent; } /** @@ -182,7 +220,7 @@ export default class TTFFont { * @type {number} */ get lineGap() { - return this.hhea.lineGap; + return this._getMetrics().lineGap; } /** @@ -209,6 +247,15 @@ export default class TTFFont { return this.post.italicAngle; } + /** + * The vertical space between adjacent lines (their baselines) of text. + * See [here](https://en.wikipedia.org/wiki/Leading) for more details. + * @type {number} + */ + get lineHeight() { + return this._getMetrics().lineHeight; + } + /** * The height of capital letters above the baseline. * See [here](https://en.wikipedia.org/wiki/Cap_height) for more details. diff --git a/src/glyph/Glyph.js b/src/glyph/Glyph.js index 3592150f..15e26c0e 100644 --- a/src/glyph/Glyph.js +++ b/src/glyph/Glyph.js @@ -64,23 +64,12 @@ export default class Glyph { let {advance:advanceWidth, bearing:leftBearing} = this._getTableMetrics(this._font.hmtx); - // For vertical metrics, use vmtx if available, or fall back to global data from OS/2 or hhea + // For vertical metrics, use vmtx if available, or fall back to global data if (this._font.vmtx) { var {advance:advanceHeight, bearing:topBearing} = this._getTableMetrics(this._font.vmtx); - } else { - let os2; - if (typeof cbox === 'undefined' || cbox === null) { ({ cbox } = this); } - - if ((os2 = this._font['OS/2']) && os2.version > 0) { - var advanceHeight = Math.abs(os2.typoAscender - os2.typoDescender); - var topBearing = os2.typoAscender - cbox.maxY; - - } else { - let { hhea } = this._font; - var advanceHeight = Math.abs(hhea.ascent - hhea.descent); - var topBearing = hhea.ascent - cbox.maxY; - } + var advanceHeight = Math.abs(this._font.ascent - this._font.descent); + var topBearing = this._font.ascent - cbox.maxY; } if (this._font._variationProcessor && this._font.HVAR) { From 8ff3117bb20e620bdbd39085ec44cbc157c09b80 Mon Sep 17 00:00:00 2001 From: Vsevolod Volkov Date: Tue, 19 Nov 2024 17:26:01 +0200 Subject: [PATCH 6/6] Fix undefined `cbox` in `_getMetrics` Signed-off-by: Vsevolod Volkov --- src/glyph/Glyph.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/glyph/Glyph.js b/src/glyph/Glyph.js index 15e26c0e..3ed72c88 100644 --- a/src/glyph/Glyph.js +++ b/src/glyph/Glyph.js @@ -68,6 +68,7 @@ export default class Glyph { if (this._font.vmtx) { var {advance:advanceHeight, bearing:topBearing} = this._getTableMetrics(this._font.vmtx); } else { + if (typeof cbox === 'undefined' || cbox === null) { ({ cbox } = this); } var advanceHeight = Math.abs(this._font.ascent - this._font.descent); var topBearing = this._font.ascent - cbox.maxY; }