Add mem free printing (I need it to verify a suspicion relating to a bug)
This commit is contained in:
105
node_modules/lv_font_conv/lib/font/cmap_build_subtables.js
generated
vendored
Normal file
105
node_modules/lv_font_conv/lib/font/cmap_build_subtables.js
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Find an optimal configuration of cmap tables representing set of codepoints,
|
||||
// using simple breadth-first algorithm
|
||||
//
|
||||
// Assume that:
|
||||
// - codepoints have one-to-one correspondence to glyph ids
|
||||
// - glyph ids are always bigger for bigger codepoints
|
||||
// - glyph ids are always consecutive (1..N without gaps)
|
||||
//
|
||||
// This way we can omit glyph ids from all calculations entirely: if codepoints
|
||||
// fit in format0, then glyph ids also will.
|
||||
//
|
||||
// format6 is not considered, because if glyph ids can be delta-coded,
|
||||
// multiple format0 tables are guaranteed to be smaller than a single format6.
|
||||
//
|
||||
// sparse format is not used because as long as glyph ids are consecutive,
|
||||
// sparse_tiny will always be preferred.
|
||||
//
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
function estimate_format0_tiny_size(/*start_code, end_code*/) {
|
||||
return 16;
|
||||
}
|
||||
|
||||
function estimate_format0_size(start_code, end_code) {
|
||||
return 16 + (end_code - start_code + 1);
|
||||
}
|
||||
|
||||
//function estimate_sparse_size(count) {
|
||||
// return 16 + count * 4;
|
||||
//}
|
||||
|
||||
function estimate_sparse_tiny_size(count) {
|
||||
return 16 + count * 2;
|
||||
}
|
||||
|
||||
module.exports = function cmap_split(all_codepoints) {
|
||||
all_codepoints = all_codepoints.sort((a, b) => a - b);
|
||||
|
||||
let min_paths = [];
|
||||
|
||||
for (let i = 0; i < all_codepoints.length; i++) {
|
||||
let min = { dist: Infinity };
|
||||
|
||||
for (let j = 0; j <= i; j++) {
|
||||
let prev_dist = (j - 1 >= 0) ? min_paths[j - 1].dist : 0;
|
||||
let s;
|
||||
|
||||
if (all_codepoints[i] - all_codepoints[j] < 256) {
|
||||
s = estimate_format0_size(all_codepoints[j], all_codepoints[i]);
|
||||
|
||||
/* eslint-disable max-depth */
|
||||
if (prev_dist + s < min.dist) {
|
||||
min = {
|
||||
dist: prev_dist + s,
|
||||
start: j,
|
||||
end: i,
|
||||
format: 'format0'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (all_codepoints[i] - all_codepoints[j] < 256 && all_codepoints[i] - i === all_codepoints[j] - j) {
|
||||
s = estimate_format0_tiny_size(all_codepoints[j], all_codepoints[i]);
|
||||
|
||||
/* eslint-disable max-depth */
|
||||
if (prev_dist + s < min.dist) {
|
||||
min = {
|
||||
dist: prev_dist + s,
|
||||
start: j,
|
||||
end: i,
|
||||
format: 'format0_tiny'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// tiny sparse will always be preferred over full sparse because glyph ids are consecutive
|
||||
if (all_codepoints[i] - all_codepoints[j] < 65536) {
|
||||
s = estimate_sparse_tiny_size(i - j + 1);
|
||||
|
||||
if (prev_dist + s < min.dist) {
|
||||
min = {
|
||||
dist: prev_dist + s,
|
||||
start: j,
|
||||
end: i,
|
||||
format: 'sparse_tiny'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
min_paths[i] = min;
|
||||
}
|
||||
|
||||
let result = [];
|
||||
|
||||
for (let i = all_codepoints.length - 1; i >= 0;) {
|
||||
let path = min_paths[i];
|
||||
result.unshift([ path.format, all_codepoints.slice(path.start, path.end + 1) ]);
|
||||
i = path.start - 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
107
node_modules/lv_font_conv/lib/font/compress.js
generated
vendored
Normal file
107
node_modules/lv_font_conv/lib/font/compress.js
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
'use strict';
|
||||
|
||||
//const debug = require('debug')('compress');
|
||||
|
||||
function count_same(arr, offset) {
|
||||
let same = 1;
|
||||
let val = arr[offset];
|
||||
|
||||
for (let i = offset + 1; i < arr.length; i++) {
|
||||
if (arr[i] !== val) break;
|
||||
same++;
|
||||
}
|
||||
|
||||
return same;
|
||||
}
|
||||
|
||||
//
|
||||
// Compress pixels with RLE-like algorythm (modified I3BN)
|
||||
//
|
||||
// 1. Require minimal repeat count (1) to enter I3BN mode
|
||||
// 2. Increased 1-bit-replaced repeat limit (2 => 10)
|
||||
// 3. Length of direct repetition counter reduced (8 => 6 bits).
|
||||
//
|
||||
// pixels - flat array of pixels (one per entry)
|
||||
// options.bpp - bits per pixels
|
||||
//
|
||||
module.exports = function compress(bitStream, pixels, options) {
|
||||
const opts = Object.assign({}, { repeat: 1 }, options);
|
||||
|
||||
// Minimal repetitions count to enable RLE mode.
|
||||
const RLE_SKIP_COUNT = 1;
|
||||
// Number of repeats, when `1` used to replace data
|
||||
// If more - write as number
|
||||
const RLE_BIT_COLLAPSED_COUNT = 10;
|
||||
|
||||
const RLE_COUNTER_BITS = 6; // (2^bits - 1) - max value
|
||||
const RLE_COUNTER_MAX = (1 << RLE_COUNTER_BITS) - 1;
|
||||
// Force flush if counter dencity exceeded.
|
||||
const RLE_MAX_REPEATS = RLE_COUNTER_MAX + RLE_BIT_COLLAPSED_COUNT + 1;
|
||||
|
||||
//let bits_start_offset = bitStream.index;
|
||||
|
||||
let offset = 0;
|
||||
|
||||
while (offset < pixels.length) {
|
||||
const p = pixels[offset];
|
||||
|
||||
let same = count_same(pixels, offset);
|
||||
|
||||
// Clamp value because RLE counter density is limited
|
||||
if (same > RLE_MAX_REPEATS + RLE_SKIP_COUNT) {
|
||||
same = RLE_MAX_REPEATS + RLE_SKIP_COUNT;
|
||||
}
|
||||
|
||||
//debug(`offset: ${offset}, count: ${same}, pixel: ${p}`);
|
||||
|
||||
offset += same;
|
||||
|
||||
// If not enough for RLE - write as is.
|
||||
if (same <= RLE_SKIP_COUNT) {
|
||||
for (let i = 0; i < same; i++) {
|
||||
bitStream.writeBits(p, opts.bpp);
|
||||
//debug(`==> ${opts.bpp} bits`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// First, write "skipped" head as is.
|
||||
for (let i = 0; i < RLE_SKIP_COUNT; i++) {
|
||||
bitStream.writeBits(p, opts.bpp);
|
||||
//debug(`==> ${opts.bpp} bits`);
|
||||
}
|
||||
|
||||
same -= RLE_SKIP_COUNT;
|
||||
|
||||
// Not reached state to use counter => dump bit-extended
|
||||
if (same <= RLE_BIT_COLLAPSED_COUNT) {
|
||||
bitStream.writeBits(p, opts.bpp);
|
||||
//debug(`==> ${opts.bpp} bits (val)`);
|
||||
for (let i = 0; i < same; i++) {
|
||||
/*eslint-disable max-depth*/
|
||||
if (i < same - 1) {
|
||||
bitStream.writeBits(1, 1);
|
||||
//debug('==> 1 bit (rle repeat)');
|
||||
} else {
|
||||
bitStream.writeBits(0, 1);
|
||||
//debug('==> 1 bit (rle repeat last)');
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
same -= RLE_BIT_COLLAPSED_COUNT + 1;
|
||||
|
||||
bitStream.writeBits(p, opts.bpp);
|
||||
//debug(`==> ${opts.bpp} bits (val)`);
|
||||
|
||||
for (let i = 0; i < RLE_BIT_COLLAPSED_COUNT + 1; i++) {
|
||||
bitStream.writeBits(1, 1);
|
||||
//debug('==> 1 bit (rle repeat)');
|
||||
}
|
||||
bitStream.writeBits(same, RLE_COUNTER_BITS);
|
||||
//debug(`==> 4 bits (rle repeat count ${same})`);
|
||||
}
|
||||
|
||||
//debug(`output bits: ${bitStream.index - bits_start_offset}`);
|
||||
};
|
131
node_modules/lv_font_conv/lib/font/font.js
generated
vendored
Normal file
131
node_modules/lv_font_conv/lib/font/font.js
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
// 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;
|
201
node_modules/lv_font_conv/lib/font/table_cmap.js
generated
vendored
Normal file
201
node_modules/lv_font_conv/lib/font/table_cmap.js
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
const build_subtables = require('./cmap_build_subtables');
|
||||
const u = require('../utils');
|
||||
const debug = require('debug')('font.table.cmap');
|
||||
|
||||
|
||||
const O_SIZE = 0;
|
||||
const O_LABEL = O_SIZE + 4;
|
||||
const O_COUNT = O_LABEL + 4;
|
||||
|
||||
const HEAD_LENGTH = O_COUNT + 4;
|
||||
|
||||
const SUB_FORMAT_0 = 0;
|
||||
const SUB_FORMAT_0_TINY = 2;
|
||||
const SUB_FORMAT_SPARSE = 1;
|
||||
const SUB_FORMAT_SPARSE_TINY = 3;
|
||||
|
||||
|
||||
class Cmap {
|
||||
constructor(font) {
|
||||
this.font = font;
|
||||
this.label = 'cmap';
|
||||
|
||||
this.sub_heads = [];
|
||||
this.sub_data = [];
|
||||
|
||||
this.compiled = false;
|
||||
}
|
||||
|
||||
compile() {
|
||||
if (this.compiled) return;
|
||||
this.compiled = true;
|
||||
|
||||
const f = this.font;
|
||||
|
||||
let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code));
|
||||
|
||||
const count_format0 = subtables_plan.filter(s => s[0] === 'format0').length;
|
||||
const count_sparse = subtables_plan.length - count_format0;
|
||||
debug(`${subtables_plan.length} subtable(s): ${count_format0} "format 0", ${count_sparse} "sparse"`);
|
||||
|
||||
for (let [ format, codepoints ] of subtables_plan) {
|
||||
let g = this.glyphByCode(codepoints[0]);
|
||||
let start_glyph_id = f.glyph_id[g.code];
|
||||
let min_code = codepoints[0];
|
||||
let max_code = codepoints[codepoints.length - 1];
|
||||
let entries_count = max_code - min_code + 1;
|
||||
let format_code = 0;
|
||||
|
||||
if (format === 'format0_tiny') {
|
||||
format_code = SUB_FORMAT_0_TINY;
|
||||
this.sub_data.push(Buffer.alloc(0));
|
||||
} else if (format === 'format0') {
|
||||
format_code = SUB_FORMAT_0;
|
||||
this.sub_data.push(this.create_format0_data(min_code, max_code, start_glyph_id));
|
||||
} else if (format === 'sparse_tiny') {
|
||||
entries_count = codepoints.length;
|
||||
format_code = SUB_FORMAT_SPARSE_TINY;
|
||||
this.sub_data.push(this.create_sparse_tiny_data(codepoints, start_glyph_id));
|
||||
} else { // assume format === 'sparse'
|
||||
entries_count = codepoints.length;
|
||||
format_code = SUB_FORMAT_SPARSE;
|
||||
this.sub_data.push(this.create_sparse_data(codepoints, start_glyph_id));
|
||||
}
|
||||
|
||||
this.sub_heads.push(this.createSubHeader(
|
||||
min_code,
|
||||
max_code - min_code + 1,
|
||||
start_glyph_id,
|
||||
entries_count,
|
||||
format_code
|
||||
));
|
||||
}
|
||||
|
||||
this.subHeaderUpdateAllOffsets();
|
||||
}
|
||||
|
||||
createSubHeader(rangeStart, rangeLen, glyphIdOffset, total, type) {
|
||||
const buf = Buffer.alloc(16);
|
||||
|
||||
// buf.writeUInt32LE(offset, 0); offset unknown at this moment
|
||||
buf.writeUInt32LE(rangeStart, 4);
|
||||
buf.writeUInt16LE(rangeLen, 8);
|
||||
buf.writeUInt16LE(glyphIdOffset, 10);
|
||||
buf.writeUInt16LE(total, 12);
|
||||
buf.writeUInt8(type, 14);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
subHeaderUpdateOffset(header, val) {
|
||||
header.writeUInt32LE(val, 0);
|
||||
}
|
||||
|
||||
subHeaderUpdateAllOffsets() {
|
||||
for (let i = 0; i < this.sub_heads.length; i++) {
|
||||
const offset = HEAD_LENGTH +
|
||||
u.sum(this.sub_heads.map(h => h.length)) +
|
||||
u.sum(this.sub_data.slice(0, i).map(d => d.length));
|
||||
|
||||
this.subHeaderUpdateOffset(this.sub_heads[i], offset);
|
||||
}
|
||||
}
|
||||
|
||||
glyphByCode(code) {
|
||||
for (let g of this.font.src.glyphs) {
|
||||
if (g.code === code) return g;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
collect_format0_data(min_code, max_code, start_glyph_id) {
|
||||
let data = [];
|
||||
|
||||
for (let i = min_code; i <= max_code; i++) {
|
||||
const g = this.glyphByCode(i);
|
||||
|
||||
if (!g) {
|
||||
data.push(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
const id_delta = this.font.glyph_id[g.code] - start_glyph_id;
|
||||
|
||||
if (id_delta < 0 || id_delta > 255) throw new Error('Glyph ID delta out of Format 0 range');
|
||||
|
||||
data.push(id_delta);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
create_format0_data(min_code, max_code, start_glyph_id) {
|
||||
const data = this.collect_format0_data(min_code, max_code, start_glyph_id);
|
||||
|
||||
return u.balign4(Buffer.from(data));
|
||||
}
|
||||
|
||||
collect_sparse_data(codepoints, start_glyph_id) {
|
||||
let codepoints_list = [];
|
||||
let ids_list = [];
|
||||
|
||||
for (let code of codepoints) {
|
||||
let g = this.glyphByCode(code);
|
||||
let id = this.font.glyph_id[g.code];
|
||||
|
||||
let code_delta = code - codepoints[0];
|
||||
let id_delta = id - start_glyph_id;
|
||||
|
||||
if (code_delta < 0 || code_delta > 65535) throw new Error('Codepoint delta out of range');
|
||||
if (id_delta < 0 || id_delta > 65535) throw new Error('Glyph ID delta out of range');
|
||||
|
||||
codepoints_list.push(code_delta);
|
||||
ids_list.push(id_delta);
|
||||
}
|
||||
|
||||
return {
|
||||
codes: codepoints_list,
|
||||
ids: ids_list
|
||||
};
|
||||
}
|
||||
|
||||
create_sparse_data(codepoints, start_glyph_id) {
|
||||
const data = this.collect_sparse_data(codepoints, start_glyph_id);
|
||||
|
||||
return u.balign4(Buffer.concat([
|
||||
u.bFromA16(data.codes),
|
||||
u.bFromA16(data.ids)
|
||||
]));
|
||||
}
|
||||
|
||||
create_sparse_tiny_data(codepoints, start_glyph_id) {
|
||||
const data = this.collect_sparse_data(codepoints, start_glyph_id);
|
||||
|
||||
return u.balign4(u.bFromA16(data.codes));
|
||||
}
|
||||
|
||||
toBin() {
|
||||
if (!this.compiled) this.compile();
|
||||
|
||||
const buf = Buffer.concat([
|
||||
Buffer.alloc(HEAD_LENGTH),
|
||||
Buffer.concat(this.sub_heads),
|
||||
Buffer.concat(this.sub_data)
|
||||
]);
|
||||
debug(`table size = ${buf.length}`);
|
||||
|
||||
buf.writeUInt32LE(buf.length, O_SIZE);
|
||||
buf.write(this.label, O_LABEL);
|
||||
buf.writeUInt32LE(this.sub_heads.length, O_COUNT);
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = Cmap;
|
147
node_modules/lv_font_conv/lib/font/table_glyf.js
generated
vendored
Normal file
147
node_modules/lv_font_conv/lib/font/table_glyf.js
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
'use strict';
|
||||
|
||||
const u = require('../utils');
|
||||
const { BitStream } = require('bit-buffer');
|
||||
const debug = require('debug')('font.table.glyf');
|
||||
const compress = require('./compress');
|
||||
|
||||
|
||||
const O_SIZE = 0;
|
||||
const O_LABEL = O_SIZE + 4;
|
||||
|
||||
const HEAD_LENGTH = O_LABEL + 4;
|
||||
|
||||
|
||||
class Glyf {
|
||||
constructor(font) {
|
||||
this.font = font;
|
||||
this.label = 'glyf';
|
||||
|
||||
this.compiled = false;
|
||||
|
||||
this.binData = [];
|
||||
}
|
||||
|
||||
// convert 8-bit opacity to bpp-bit
|
||||
pixelsToBpp(pixels) {
|
||||
const bpp = this.font.opts.bpp;
|
||||
return pixels.map(line => line.map(p => (p >>> (8 - bpp))));
|
||||
}
|
||||
|
||||
// Returns "binary stream" (Buffer) of compiled glyph data
|
||||
compileGlyph(glyph) {
|
||||
// Allocate memory, enough for eny storage formats
|
||||
const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4);
|
||||
const bs = new BitStream(buf);
|
||||
bs.bigEndian = true;
|
||||
const f = this.font;
|
||||
|
||||
// Store Width
|
||||
if (!f.monospaced) {
|
||||
let w = f.widthToInt(glyph.advanceWidth);
|
||||
bs.writeBits(w, f.advanceWidthBits);
|
||||
}
|
||||
|
||||
// Store X, Y
|
||||
bs.writeBits(glyph.bbox.x, f.xy_bits);
|
||||
bs.writeBits(glyph.bbox.y, f.xy_bits);
|
||||
bs.writeBits(glyph.bbox.width, f.wh_bits);
|
||||
bs.writeBits(glyph.bbox.height, f.wh_bits);
|
||||
|
||||
const pixels = this.pixelsToBpp(glyph.pixels);
|
||||
|
||||
this.storePixels(bs, pixels);
|
||||
|
||||
// Shrink size
|
||||
const result = Buffer.alloc(bs.byteIndex);
|
||||
buf.copy(result, 0, 0, bs.byteIndex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
storePixels(bitStream, pixels) {
|
||||
if (this.getCompressionCode() === 0) this.storePixelsRaw(bitStream, pixels);
|
||||
else this.storePixelsCompressed(bitStream, pixels);
|
||||
}
|
||||
|
||||
storePixelsRaw(bitStream, pixels) {
|
||||
const bpp = this.font.opts.bpp;
|
||||
|
||||
for (let y = 0; y < pixels.length; y++) {
|
||||
const line = pixels[y];
|
||||
for (let x = 0; x < line.length; x++) {
|
||||
bitStream.writeBits(line[x], bpp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storePixelsCompressed(bitStream, pixels) {
|
||||
let p;
|
||||
|
||||
if (this.font.opts.no_prefilter) p = pixels.flat();
|
||||
else p = u.prefilter(pixels).flat();
|
||||
|
||||
compress(bitStream, p, this.font.opts);
|
||||
}
|
||||
|
||||
// Create internal struct with binary data for each glyph
|
||||
// Needed to calculate offsets & build final result
|
||||
compile() {
|
||||
this.compiled = true;
|
||||
|
||||
this.binData = [
|
||||
Buffer.alloc(0) // Reserve id 0
|
||||
];
|
||||
|
||||
const f = this.font;
|
||||
|
||||
f.src.glyphs.forEach(g => {
|
||||
const id = f.glyph_id[g.code];
|
||||
|
||||
this.binData[id] = this.compileGlyph(g);
|
||||
});
|
||||
}
|
||||
|
||||
toBin() {
|
||||
if (!this.compiled) this.compile();
|
||||
|
||||
const buf = u.balign4(Buffer.concat([
|
||||
Buffer.alloc(HEAD_LENGTH),
|
||||
Buffer.concat(this.binData)
|
||||
]));
|
||||
|
||||
buf.writeUInt32LE(buf.length, O_SIZE);
|
||||
buf.write(this.label, O_LABEL);
|
||||
|
||||
debug(`table size = ${buf.length}`);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
getSize() {
|
||||
if (!this.compiled) this.compile();
|
||||
|
||||
return u.align4(HEAD_LENGTH + u.sum(this.binData.map(b => b.length)));
|
||||
}
|
||||
|
||||
getOffset(id) {
|
||||
if (!this.compiled) this.compile();
|
||||
|
||||
let offset = HEAD_LENGTH;
|
||||
|
||||
for (let i = 0; i < id; i++) offset += this.binData[i].length;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
getCompressionCode() {
|
||||
if (this.font.opts.no_compress) return 0;
|
||||
if (this.font.opts.bpp === 1) return 0;
|
||||
|
||||
if (this.font.opts.no_prefilter) return 2;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = Glyf;
|
99
node_modules/lv_font_conv/lib/font/table_head.js
generated
vendored
Normal file
99
node_modules/lv_font_conv/lib/font/table_head.js
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
const u = require('../utils');
|
||||
const debug = require('debug')('font.table.head');
|
||||
|
||||
const O_SIZE = 0;
|
||||
const O_LABEL = O_SIZE + 4;
|
||||
const O_VERSION = O_LABEL + 4;
|
||||
const O_TABLES = O_VERSION + 4;
|
||||
const O_FONT_SIZE = O_TABLES + 2;
|
||||
const O_ASCENT = O_FONT_SIZE + 2;
|
||||
const O_DESCENT = O_ASCENT + 2;
|
||||
const O_TYPO_ASCENT = O_DESCENT + 2;
|
||||
const O_TYPO_DESCENT = O_TYPO_ASCENT + 2;
|
||||
const O_TYPO_LINE_GAP = O_TYPO_DESCENT + 2;
|
||||
const O_MIN_Y = O_TYPO_LINE_GAP + 2;
|
||||
const O_MAX_Y = O_MIN_Y + 2;
|
||||
const O_DEF_ADVANCE_WIDTH = O_MAX_Y + 2;
|
||||
const O_KERNING_SCALE = O_DEF_ADVANCE_WIDTH + 2;
|
||||
const O_INDEX_TO_LOC_FORMAT = O_KERNING_SCALE + 2;
|
||||
const O_GLYPH_ID_FORMAT = O_INDEX_TO_LOC_FORMAT + 1;
|
||||
const O_ADVANCE_WIDTH_FORMAT = O_GLYPH_ID_FORMAT + 1;
|
||||
const O_BITS_PER_PIXEL = O_ADVANCE_WIDTH_FORMAT + 1;
|
||||
const O_XY_BITS = O_BITS_PER_PIXEL + 1;
|
||||
const O_WH_BITS = O_XY_BITS + 1;
|
||||
const O_ADVANCE_WIDTH_BITS = O_WH_BITS + 1;
|
||||
const O_COMPRESSION_ID = O_ADVANCE_WIDTH_BITS + 1;
|
||||
const O_SUBPIXELS_MODE = O_COMPRESSION_ID + 1;
|
||||
const O_TMP_RESERVED1 = O_SUBPIXELS_MODE + 1;
|
||||
const O_UNDERLINE_POSITION = O_TMP_RESERVED1 + 1;
|
||||
const O_UNDERLINE_THICKNESS = O_UNDERLINE_POSITION + 2;
|
||||
const HEAD_LENGTH = u.align4(O_UNDERLINE_THICKNESS + 2);
|
||||
|
||||
|
||||
class Head {
|
||||
constructor(font) {
|
||||
this.font = font;
|
||||
this.label = 'head';
|
||||
this.version = 1;
|
||||
}
|
||||
|
||||
toBin() {
|
||||
const buf = Buffer.alloc(HEAD_LENGTH);
|
||||
debug(`table size = ${buf.length}`);
|
||||
|
||||
buf.writeUInt32LE(HEAD_LENGTH, O_SIZE);
|
||||
buf.write(this.label, O_LABEL);
|
||||
buf.writeUInt32LE(this.version, O_VERSION);
|
||||
|
||||
const f = this.font;
|
||||
|
||||
const tables_count = f.hasKerning() ? 4 : 3;
|
||||
|
||||
buf.writeUInt16LE(tables_count, O_TABLES);
|
||||
|
||||
buf.writeUInt16LE(f.src.size, O_FONT_SIZE);
|
||||
buf.writeUInt16LE(f.src.ascent, O_ASCENT);
|
||||
buf.writeInt16LE(f.src.descent, O_DESCENT);
|
||||
|
||||
buf.writeUInt16LE(f.src.typoAscent, O_TYPO_ASCENT);
|
||||
buf.writeInt16LE(f.src.typoDescent, O_TYPO_DESCENT);
|
||||
buf.writeUInt16LE(f.src.typoLineGap, O_TYPO_LINE_GAP);
|
||||
|
||||
buf.writeInt16LE(f.minY, O_MIN_Y);
|
||||
buf.writeInt16LE(f.maxY, O_MAX_Y);
|
||||
|
||||
if (f.monospaced) {
|
||||
buf.writeUInt16LE(f.widthToInt(f.src.glyphs[0].advanceWidth), O_DEF_ADVANCE_WIDTH);
|
||||
} else {
|
||||
buf.writeUInt16LE(0, O_DEF_ADVANCE_WIDTH);
|
||||
}
|
||||
|
||||
buf.writeUInt16LE(Math.round(f.kerningScale * 16), O_KERNING_SCALE); // FP12.4
|
||||
|
||||
buf.writeUInt8(f.indexToLocFormat, O_INDEX_TO_LOC_FORMAT);
|
||||
buf.writeUInt8(f.glyphIdFormat, O_GLYPH_ID_FORMAT);
|
||||
buf.writeUInt8(f.advanceWidthFormat, O_ADVANCE_WIDTH_FORMAT);
|
||||
|
||||
buf.writeUInt8(f.opts.bpp, O_BITS_PER_PIXEL);
|
||||
buf.writeUInt8(f.xy_bits, O_XY_BITS);
|
||||
buf.writeUInt8(f.wh_bits, O_WH_BITS);
|
||||
|
||||
if (f.monospaced) buf.writeUInt8(0, O_ADVANCE_WIDTH_BITS);
|
||||
else buf.writeUInt8(f.advanceWidthBits, O_ADVANCE_WIDTH_BITS);
|
||||
|
||||
buf.writeUInt8(f.glyf.getCompressionCode(), O_COMPRESSION_ID);
|
||||
|
||||
buf.writeUInt8(f.subpixels_mode, O_SUBPIXELS_MODE);
|
||||
|
||||
buf.writeInt16LE(f.src.underlinePosition, O_UNDERLINE_POSITION);
|
||||
buf.writeUInt16LE(f.src.underlineThickness, O_UNDERLINE_POSITION);
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = Head;
|
256
node_modules/lv_font_conv/lib/font/table_kern.js
generated
vendored
Normal file
256
node_modules/lv_font_conv/lib/font/table_kern.js
generated
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
'use strict';
|
||||
|
||||
const u = require('../utils');
|
||||
const debug = require('debug')('font.table.kern');
|
||||
|
||||
|
||||
const O_SIZE = 0;
|
||||
const O_LABEL = O_SIZE + 4;
|
||||
const O_FORMAT = O_LABEL + 4;
|
||||
|
||||
const HEAD_LENGTH = u.align4(O_FORMAT + 1);
|
||||
|
||||
|
||||
class Kern {
|
||||
constructor(font) {
|
||||
this.font = font;
|
||||
this.label = 'kern';
|
||||
this.format3_forced = false;
|
||||
}
|
||||
|
||||
collect_format0_data() {
|
||||
const f = this.font;
|
||||
const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]);
|
||||
const kernSorted = [];
|
||||
|
||||
for (let g of glyphs) {
|
||||
if (!g.kerning || !Object.keys(g.kerning).length) continue;
|
||||
|
||||
const glyph_id = f.glyph_id[g.code];
|
||||
const paired = u.sort_by(Object.keys(g.kerning), code => f.glyph_id[code]);
|
||||
|
||||
for (let code of paired) {
|
||||
const glyph_id2 = f.glyph_id[code];
|
||||
kernSorted.push([ glyph_id, glyph_id2, g.kerning[code] ]);
|
||||
}
|
||||
}
|
||||
|
||||
return kernSorted;
|
||||
}
|
||||
|
||||
create_format0_data() {
|
||||
const f = this.font;
|
||||
const glyphs = this.font.src.glyphs;
|
||||
const kernSorted = this.collect_format0_data();
|
||||
|
||||
const count = kernSorted.length;
|
||||
|
||||
const kerned_glyphs = glyphs.filter(g => Object.keys(g.kerning).length).length;
|
||||
const kerning_list_max = Math.max(...glyphs.map(g => Object.keys(g.kerning).length));
|
||||
debug(`${kerned_glyphs} kerned glyphs of ${glyphs.length}, ${kerning_list_max} max list, ${count} total pairs`);
|
||||
|
||||
const subheader = Buffer.alloc(4);
|
||||
|
||||
subheader.writeUInt32LE(count, 0);
|
||||
|
||||
const pairs_buf = Buffer.alloc((f.glyphIdFormat ? 4 : 2) * count);
|
||||
|
||||
// Write kerning pairs
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (f.glyphIdFormat === 0) {
|
||||
pairs_buf.writeUInt8(kernSorted[i][0], 2 * i);
|
||||
pairs_buf.writeUInt8(kernSorted[i][1], 2 * i + 1);
|
||||
} else {
|
||||
pairs_buf.writeUInt16LE(kernSorted[i][0], 4 * i);
|
||||
pairs_buf.writeUInt16LE(kernSorted[i][1], 4 * i + 2);
|
||||
}
|
||||
}
|
||||
|
||||
const values_buf = Buffer.alloc(count);
|
||||
|
||||
// Write kerning values
|
||||
for (let i = 0; i < count; i++) {
|
||||
values_buf.writeInt8(f.kernToFP(kernSorted[i][2]), i); // FP4.4
|
||||
}
|
||||
|
||||
let buf = Buffer.concat([
|
||||
subheader,
|
||||
pairs_buf,
|
||||
values_buf
|
||||
]);
|
||||
|
||||
let buf_aligned = u.balign4(buf);
|
||||
|
||||
debug(`table format0 size = ${buf_aligned.length}`);
|
||||
return buf_aligned;
|
||||
}
|
||||
|
||||
collect_format3_data() {
|
||||
const f = this.font;
|
||||
const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]);
|
||||
|
||||
// extract kerning pairs for each character;
|
||||
// left kernings are kerning values based on left char (already there),
|
||||
// right kernings are kerning values based on right char (extracted from left)
|
||||
const left_kernings = {};
|
||||
const right_kernings = {};
|
||||
|
||||
for (let g of glyphs) {
|
||||
if (!g.kerning || !Object.keys(g.kerning).length) continue;
|
||||
|
||||
const paired = Object.keys(g.kerning);
|
||||
|
||||
left_kernings[g.code] = g.kerning;
|
||||
|
||||
for (let code of paired) {
|
||||
right_kernings[code] = right_kernings[code] || {};
|
||||
right_kernings[code][g.code] = g.kerning[code];
|
||||
}
|
||||
}
|
||||
|
||||
// input:
|
||||
// - kernings, char => { hash: String, [char1]: Number, [char2]: Number, ... }
|
||||
//
|
||||
// returns:
|
||||
// - array of [ char1, char2, ... ]
|
||||
//
|
||||
function build_classes(kernings) {
|
||||
const classes = [];
|
||||
|
||||
for (let code of Object.keys(kernings)) {
|
||||
// for each kerning table calculate unique value representing it;
|
||||
// keys needs to be sorted for this (but we're using numeric keys, so
|
||||
// sorting happens automatically and can't be changed)
|
||||
const hash = JSON.stringify(kernings[code]);
|
||||
|
||||
classes[hash] = classes[hash] || [];
|
||||
classes[hash].push(Number(code));
|
||||
}
|
||||
|
||||
return Object.values(classes);
|
||||
}
|
||||
|
||||
const left_classes = build_classes(left_kernings);
|
||||
debug(`unique left classes: ${left_classes.length}`);
|
||||
|
||||
const right_classes = build_classes(right_kernings);
|
||||
debug(`unique right classes: ${right_classes.length}`);
|
||||
|
||||
if (left_classes.length >= 255 || right_classes.length >= 255) {
|
||||
debug('too many classes for format3 subtable');
|
||||
return null;
|
||||
}
|
||||
|
||||
function kern_class_mapping(classes) {
|
||||
const arr = Array(f.last_id).fill(0);
|
||||
|
||||
classes.forEach((members, idx) => {
|
||||
for (let code of members) {
|
||||
arr[f.glyph_id[code]] = idx + 1;
|
||||
}
|
||||
});
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function kern_class_values() {
|
||||
const arr = [];
|
||||
|
||||
for (let left_class of left_classes) {
|
||||
for (let right_class of right_classes) {
|
||||
let code1 = left_class[0];
|
||||
let code2 = right_class[0];
|
||||
arr.push(left_kernings[code1][code2] || 0);
|
||||
}
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
return {
|
||||
left_classes: left_classes.length,
|
||||
right_classes: right_classes.length,
|
||||
left_mapping: kern_class_mapping(left_classes),
|
||||
right_mapping: kern_class_mapping(right_classes),
|
||||
values: kern_class_values()
|
||||
};
|
||||
}
|
||||
|
||||
create_format3_data() {
|
||||
const f = this.font;
|
||||
const {
|
||||
left_classes,
|
||||
right_classes,
|
||||
left_mapping,
|
||||
right_mapping,
|
||||
values
|
||||
} = this.collect_format3_data();
|
||||
|
||||
const subheader = Buffer.alloc(4);
|
||||
subheader.writeUInt16LE(f.last_id);
|
||||
subheader.writeUInt8(left_classes, 2);
|
||||
subheader.writeUInt8(right_classes, 3);
|
||||
|
||||
let buf = Buffer.concat([
|
||||
subheader,
|
||||
Buffer.from(left_mapping),
|
||||
Buffer.from(right_mapping),
|
||||
Buffer.from(values.map(v => f.kernToFP(v)))
|
||||
]);
|
||||
|
||||
let buf_aligned = u.balign4(buf);
|
||||
|
||||
debug(`table format3 size = ${buf_aligned.length}`);
|
||||
return buf_aligned;
|
||||
}
|
||||
|
||||
should_use_format3() {
|
||||
if (!this.font.hasKerning()) return false;
|
||||
|
||||
const format0_data = this.create_format0_data();
|
||||
const format3_data = this.create_format3_data();
|
||||
|
||||
if (format3_data && format3_data.length <= format0_data.length) return true;
|
||||
|
||||
if (this.font.opts.fast_kerning && format3_data) {
|
||||
this.format3_forced = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
toBin() {
|
||||
if (!this.font.hasKerning()) return Buffer.alloc(0);
|
||||
|
||||
const format0_data = this.create_format0_data();
|
||||
const format3_data = this.create_format3_data();
|
||||
|
||||
let header = Buffer.alloc(HEAD_LENGTH);
|
||||
|
||||
let data = format0_data;
|
||||
header.writeUInt8(0, O_FORMAT);
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
if (this.should_use_format3()) {
|
||||
data = format3_data;
|
||||
header.writeUInt8(3, O_FORMAT);
|
||||
|
||||
if (this.format3_forced) {
|
||||
let diff = format3_data.length - format0_data.length;
|
||||
console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`);
|
||||
}
|
||||
} else if (this.font.opts.fast_kerning) {
|
||||
console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.');
|
||||
}
|
||||
|
||||
header.writeUInt32LE(header.length + data.length, O_SIZE);
|
||||
header.write(this.label, O_LABEL);
|
||||
|
||||
return Buffer.concat([ header, data ]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = Kern;
|
42
node_modules/lv_font_conv/lib/font/table_loca.js
generated
vendored
Normal file
42
node_modules/lv_font_conv/lib/font/table_loca.js
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
const u = require('../utils');
|
||||
const debug = require('debug')('font.table.loca');
|
||||
|
||||
|
||||
const O_SIZE = 0;
|
||||
const O_LABEL = O_SIZE + 4;
|
||||
const O_COUNT = O_LABEL + 4;
|
||||
|
||||
const HEAD_LENGTH = O_COUNT + 4;
|
||||
|
||||
|
||||
class Loca {
|
||||
constructor(font) {
|
||||
this.font = font;
|
||||
this.label = 'loca';
|
||||
}
|
||||
|
||||
toBin() {
|
||||
const f = this.font;
|
||||
|
||||
const offsets = [ ...Array(f.last_id).keys() ].map(i => f.glyf.getOffset(i));
|
||||
|
||||
const buf = u.balign4(Buffer.concat([
|
||||
Buffer.alloc(HEAD_LENGTH),
|
||||
f.indexToLocFormat ? u.bFromA32(offsets) : u.bFromA16(offsets)
|
||||
]));
|
||||
|
||||
buf.writeUInt32LE(buf.length, O_SIZE);
|
||||
buf.write(this.label, O_LABEL);
|
||||
buf.writeUInt32LE(f.last_id, O_COUNT);
|
||||
|
||||
debug(`table size = ${buf.length}`);
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = Loca;
|
Reference in New Issue
Block a user