Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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); }
Comment thread
StLyn4 marked this conversation as resolved.

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();