132 lines
3.7 KiB
JavaScript
132 lines
3.7 KiB
JavaScript
// Font class to generate tables
|
|
'use strict';
|
|
|
|
const u = require('../utils');
|
|
const debug = require('debug')('font');
|
|
const Head = require('./table_head');
|
|
const Cmap = require('./table_cmap');
|
|
const Glyf = require('./table_glyf');
|
|
const Loca = require('./table_loca');
|
|
const Kern = require('./table_kern');
|
|
|
|
class Font {
|
|
constructor(fontData, options) {
|
|
this.src = fontData;
|
|
|
|
this.opts = options;
|
|
|
|
// Map chars to IDs (zero is reserved)
|
|
this.glyph_id = { 0: 0 };
|
|
|
|
this.last_id = 1;
|
|
this.createIDs();
|
|
debug(`last_id: ${this.last_id}`);
|
|
|
|
this.init_tables();
|
|
|
|
this.minY = Math.min(...this.src.glyphs.map(g => g.bbox.y));
|
|
debug(`minY: ${this.minY}`);
|
|
this.maxY = Math.max(...this.src.glyphs.map(g => g.bbox.y + g.bbox.height));
|
|
debug(`maxY: ${this.maxY}`);
|
|
|
|
// 0 => 1 byte, 1 => 2 bytes
|
|
this.glyphIdFormat = Math.max(...Object.values(this.glyph_id)) > 255 ? 1 : 0;
|
|
debug(`glyphIdFormat: ${this.glyphIdFormat}`);
|
|
|
|
// 1.0 by default, will be stored in font as FP12.4
|
|
this.kerningScale = 1.0;
|
|
let kerningMax = Math.max(...this.src.glyphs.map(g => Object.values(g.kerning).map(Math.abs)).flat());
|
|
if (kerningMax >= 7.5) this.kerningScale = Math.ceil(kerningMax / 7.5 * 16) / 16;
|
|
debug(`kerningScale: ${this.kerningScale}`);
|
|
|
|
// 0 => int, 1 => FP4
|
|
this.advanceWidthFormat = this.hasKerning() ? 1 : 0;
|
|
debug(`advanceWidthFormat: ${this.advanceWidthFormat}`);
|
|
|
|
this.xy_bits = Math.max(...this.src.glyphs.map(g => Math.max(
|
|
u.signed_bits(g.bbox.x), u.signed_bits(g.bbox.y)
|
|
)));
|
|
debug(`xy_bits: ${this.xy_bits}`);
|
|
|
|
this.wh_bits = Math.max(...this.src.glyphs.map(g => Math.max(
|
|
u.unsigned_bits(g.bbox.width), u.unsigned_bits(g.bbox.height)
|
|
)));
|
|
debug(`wh_bits: ${this.wh_bits}`);
|
|
|
|
this.advanceWidthBits = Math.max(...this.src.glyphs.map(
|
|
g => u.signed_bits(this.widthToInt(g.advanceWidth))
|
|
));
|
|
debug(`advanceWidthBits: ${this.advanceWidthBits}`);
|
|
|
|
let glyphs = this.src.glyphs;
|
|
|
|
this.monospaced = glyphs.every((v, i, arr) => v.advanceWidth === arr[0].advanceWidth);
|
|
debug(`monospaced: ${this.monospaced}`);
|
|
|
|
// This should stay in the end, because depends on previous variables
|
|
// 0 => 2 bytes, 1 => 4 bytes
|
|
this.indexToLocFormat = this.glyf.getSize() > 65535 ? 1 : 0;
|
|
debug(`indexToLocFormat: ${this.indexToLocFormat}`);
|
|
|
|
this.subpixels_mode = options.lcd ? 1 : (options.lcd_v ? 2 : 0);
|
|
debug(`subpixels_mode: ${this.subpixels_mode}`);
|
|
}
|
|
|
|
init_tables() {
|
|
this.head = new Head(this);
|
|
this.glyf = new Glyf(this);
|
|
this.cmap = new Cmap(this);
|
|
this.loca = new Loca(this);
|
|
this.kern = new Kern(this);
|
|
}
|
|
|
|
createIDs() {
|
|
// Simplified, don't check dupes
|
|
this.last_id = 1;
|
|
|
|
for (let i = 0; i < this.src.glyphs.length; i++) {
|
|
// reserve zero for special cases
|
|
this.glyph_id[this.src.glyphs[i].code] = this.last_id;
|
|
this.last_id++;
|
|
}
|
|
}
|
|
|
|
hasKerning() {
|
|
if (this.opts.no_kerning) return false;
|
|
|
|
for (let glyph of this.src.glyphs) {
|
|
if (glyph.kerning && Object.keys(glyph.kerning).length) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns integer width, depending on format
|
|
widthToInt(val) {
|
|
if (this.advanceWidthFormat === 0) return Math.round(val);
|
|
|
|
return Math.round(val * 16);
|
|
}
|
|
|
|
// Convert kerning to FP4.4, useable for writer. Apply `kerningScale`.
|
|
kernToFP(val) {
|
|
return Math.round(val / this.kerningScale * 16);
|
|
}
|
|
|
|
toBin() {
|
|
const result = Buffer.concat([
|
|
this.head.toBin(),
|
|
this.cmap.toBin(),
|
|
this.loca.toBin(),
|
|
this.glyf.toBin(),
|
|
this.kern.toBin()
|
|
]);
|
|
|
|
debug(`font size: ${result.length}`);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
module.exports = Font;
|