Skip to content
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

Respect useTypoMetrics #306

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 50 additions & 3 deletions src/TTFFont.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -166,23 +204,23 @@ export default class TTFFont {
* @type {number}
*/
get ascent() {
return this.hhea.ascent;
return this._getMetrics().ascent;
}

/**
* The font’s [descender](https://en.wikipedia.org/wiki/Descender)
* @type {number}
*/
get descent() {
return this.hhea.descent;
return this._getMetrics().descent;
}

/**
* The amount of space that should be included between lines
* @type {number}
*/
get lineGap() {
return this.hhea.lineGap;
return this._getMetrics().lineGap;
}

/**
Expand All @@ -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.
Expand Down
17 changes: 3 additions & 14 deletions src/glyph/Glyph.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
16 changes: 16 additions & 0 deletions src/glyph/Path.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
10 changes: 9 additions & 1 deletion src/glyph/TTFGlyph.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/tables/maxp.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/tables/post.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/tables/vhea.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as r from 'restructure';
import { version16Dot16 } from '../utils';

// Vertical Header Table
export default new r.Struct({
version: r.uint16, // 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
Expand Down
36 changes: 36 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DecodeStream, EncodeStream } from 'restructure';

export function binarySearch(arr, cmp) {
let min = 0;
let max = arr.length - 1;
Expand Down Expand Up @@ -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();