Add mem free printing (I need it to verify a suspicion relating to a bug)

This commit is contained in:
2024-04-22 13:28:24 -05:00
parent 20cd951cde
commit c60251359c
120 changed files with 31903 additions and 2 deletions

9
node_modules/lv_font_conv/lib/app_error.js generated vendored Normal file
View File

@@ -0,0 +1,9 @@
// Custom Error type to simplify error messaging
//
'use strict';
//const ExtendableError = require('es6-error');
//module.exports = class AppError extends ExtendableError {};
module.exports = require('make-error')('AppError');

318
node_modules/lv_font_conv/lib/cli.js generated vendored Normal file
View File

@@ -0,0 +1,318 @@
// Parse input arguments and execute convertor
'use strict';
const argparse = require('argparse');
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const convert = require('./convert');
class ActionFontAdd extends argparse.Action {
call(parser, namespace, value/*, option_string*/) {
let items = (namespace[this.dest] || []).slice();
items.push({ source_path: value, ranges: [] });
namespace[this.dest] = items;
}
}
// add range or symbols to font;
// need to merge them into one array here so overrides work correctly
class ActionFontRangeAdd extends argparse.Action {
call(parser, namespace, value, option_string) {
let fonts = namespace.font || [];
if (fonts.length === 0) {
parser.error(`argument ${option_string}: Only allowed after --font`);
}
let lastFont = fonts[fonts.length - 1];
// { symbols: 'ABC' }, or { range: [ 65, 67, 65 ] }
lastFont.ranges.push({ [this.dest]: value });
}
}
// add hinting option to font;
class ActionFontStoreTrue extends argparse.Action {
constructor(options) {
options = options || {};
options.const = true;
options.default = options.default !== null ? options.default : false;
options.nargs = 0;
super(options);
}
call(parser, namespace, value, option_string) {
let fonts = namespace.font || [];
if (fonts.length === 0) {
parser.error(`argument ${option_string}: Only allowed after --font`);
}
let lastFont = fonts[fonts.length - 1];
lastFont[this.dest] = this.const;
}
}
// Formatter with support of `\n` in Help texts.
class RawTextHelpFormatter2 extends argparse.RawDescriptionHelpFormatter {
// executes parent _split_lines for each line of the help, then flattens the result
_split_lines(text, width) {
return [].concat(...text.split('\n').map(line => super._split_lines(line, width)));
}
}
// parse decimal or hex code in unicode range
function unicode_point(str) {
let m = /^(?:(?:0x([0-9a-f]+))|([0-9]+))$/i.exec(str.trim());
if (!m) throw new TypeError(`${str} is not a number`);
let [ , hex, dec ] = m;
let value = hex ? parseInt(hex, 16) : parseInt(dec, 10);
if (value > 0x10FFFF) throw new TypeError(`${str} is out of unicode range`);
return value;
}
// parse range
function range(str) {
let result = [];
for (let s of str.split(',')) {
let m = /^(.+?)(?:-(.+?))?(?:=>(.+?))?$/i.exec(s);
let [ , start, end, mapped_start ] = m;
if (!end) end = start;
if (!mapped_start) mapped_start = start;
start = unicode_point(start);
end = unicode_point(end);
if (start > end) throw new TypeError(`Invalid range: ${s}`);
mapped_start = unicode_point(mapped_start);
result.push(start, end, mapped_start);
}
return result;
}
// exclude negative numbers and non-numbers
function positive_int(str) {
if (!/^\d+$/.test(str)) throw new TypeError(`${str} is not a valid number`);
let n = parseInt(str, 10);
if (n <= 0) throw new TypeError(`${str} is not a valid number`);
return n;
}
module.exports.run = async function (argv, debug = false) {
//
// Configure CLI
//
let parser = new argparse.ArgumentParser({
add_help: true,
formatter_class: RawTextHelpFormatter2
});
if (debug) {
parser.exit = function (status, message) {
throw new Error(message);
};
}
parser.add_argument('-v', '--version', {
action: 'version',
version: require('../package.json').version
});
parser.add_argument('--size', {
metavar: 'PIXELS',
type: positive_int,
required: true,
help: 'Output font size, pixels.'
});
parser.add_argument('-o', '--output', {
metavar: '<path>',
help: 'Output path.'
});
parser.add_argument('--bpp', {
choices: [ 1, 2, 3, 4, 8 ],
type: positive_int,
required: true,
help: 'Bits per pixel, for antialiasing.'
});
let lcd_group = parser.add_mutually_exclusive_group();
lcd_group.add_argument('--lcd', {
action: 'store_true',
default: false,
help: 'Enable subpixel rendering (horizontal pixel layout).'
});
lcd_group.add_argument('--lcd-v', {
action: 'store_true',
default: false,
help: 'Enable subpixel rendering (vertical pixel layout).'
});
parser.add_argument('--use-color-info', {
dest: 'use_color_info',
action: 'store_true',
default: false,
help: 'Try to use glyph color info from font to create grayscale icons. ' +
'Since gray tones are emulated via transparency, result will be good on contrast background only.'
});
parser.add_argument('--format', {
choices: convert.formats,
required: true,
help: 'Output format.'
});
parser.add_argument('--font', {
metavar: '<path>',
action: ActionFontAdd,
required: true,
help: 'Source font path. Can be used multiple times to merge glyphs from different fonts.'
});
parser.add_argument('-r', '--range', {
type: range,
action: ActionFontRangeAdd,
help: `
Range of glyphs to copy. Can be used multiple times, belongs to previously declared "--font". Examples:
-r 0x1F450
-r 0x20-0x7F
-r 32-127
-r 32-127,0x1F450
-r '0x1F450=>0xF005'
-r '0x1F450-0x1F470=>0xF005'
`
});
parser.add_argument('--symbols', {
action: ActionFontRangeAdd,
help: `
List of characters to copy, belongs to previously declared "--font". Examples:
--symbols ,.0123456789
--symbols abcdefghigklmnopqrstuvwxyz
`
});
parser.add_argument('--autohint-off', {
type: range,
action: ActionFontStoreTrue,
help: 'Disable autohinting for previously declared "--font"'
});
parser.add_argument('--autohint-strong', {
type: range,
action: ActionFontStoreTrue,
help: 'Use more strong autohinting for previously declared "--font" (will break kerning)'
});
parser.add_argument('--force-fast-kern-format', {
dest: 'fast_kerning',
action: 'store_true',
default: false,
help: 'Always use kern classes instead of pairs (might be larger but faster).'
});
parser.add_argument('--no-compress', {
dest: 'no_compress',
action: 'store_true',
default: false,
help: 'Disable built-in RLE compression.'
});
parser.add_argument('--no-prefilter', {
dest: 'no_prefilter',
action: 'store_true',
default: false,
help: 'Disable bitmap lines filter (XOR), used to improve compression ratio.'
});
parser.add_argument('--no-kerning', {
dest: 'no_kerning',
action: 'store_true',
default: false,
help: 'Drop kerning info to reduce size (not recommended).'
});
parser.add_argument('--lv-include', {
metavar: '<path>',
help: 'Set alternate "lvgl.h" path (for --format lvgl).'
});
parser.add_argument('--full-info', {
dest: 'full_info',
action: 'store_true',
default: false,
help: 'Don\'t shorten "font_info.json" (include pixels data).'
});
//
// Process CLI options
//
let args = parser.parse_args(argv.length ? argv : [ '-h' ]);
for (let font of args.font) {
if (font.ranges.length === 0) {
parser.error(`You need to specify either "--range" or "--symbols" for font "${font.source_path}"`);
}
try {
font.source_bin = fs.readFileSync(font.source_path);
} catch (err) {
parser.error(`Cannot read file "${font.source_path}": ${err.message}`);
}
}
//
// Convert
//
let files = await convert(args);
//
// Store files
//
for (let [ filename, data ] of Object.entries(files)) {
let dir = path.dirname(filename);
mkdirp.sync(dir);
fs.writeFileSync(filename, data);
}
};
// export for tests
module.exports._range = range;

173
node_modules/lv_font_conv/lib/collect_font_data.js generated vendored Normal file
View File

@@ -0,0 +1,173 @@
// Read fonts
'use strict';
const opentype = require('opentype.js');
const ft_render = require('./freetype');
const AppError = require('./app_error');
const Ranger = require('./ranger');
module.exports = async function collect_font_data(args) {
await ft_render.init();
// Duplicate font options as k/v for quick access
let fonts_options = {};
args.font.forEach(f => { fonts_options[f.source_path] = f; });
// read fonts
let fonts_opentype = {};
let fonts_freetype = {};
for (let { source_path, source_bin } of args.font) {
// don't load font again if it's specified multiple times in args
if (fonts_opentype[source_path]) continue;
try {
let b = source_bin;
if (Buffer.isBuffer(b)) {
// node.js Buffer -> ArrayBuffer
b = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
}
fonts_opentype[source_path] = opentype.parse(b);
} catch (err) {
throw new AppError(`Cannot load font "${source_path}": ${err.message}`);
}
fonts_freetype[source_path] = ft_render.fontface_create(source_bin, args.size);
}
// merge all ranges
let ranger = new Ranger();
for (let { source_path, ranges } of args.font) {
let font = fonts_freetype[source_path];
for (let item of ranges) {
/* eslint-disable max-depth */
if (item.range) {
for (let i = 0; i < item.range.length; i += 3) {
let range = item.range.slice(i, i + 3);
let chars = ranger.add_range(source_path, ...range);
let is_empty = true;
for (let code of chars) {
if (ft_render.glyph_exists(font, code)) {
is_empty = false;
break;
}
}
if (is_empty) {
let a = '0x' + range[0].toString(16);
let b = '0x' + range[1].toString(16);
throw new AppError(`Font "${source_path}" doesn't have any characters included in range ${a}-${b}`);
}
}
}
if (item.symbols) {
let chars = ranger.add_symbols(source_path, item.symbols);
let is_empty = true;
for (let code of chars) {
if (ft_render.glyph_exists(font, code)) {
is_empty = false;
break;
}
}
if (is_empty) {
throw new AppError(`Font "${source_path}" doesn't have any characters included in "${item.symbols}"`);
}
}
}
}
let mapping = ranger.get();
let glyphs = [];
let all_dst_charcodes = Object.keys(mapping).sort((a, b) => a - b).map(Number);
for (let dst_code of all_dst_charcodes) {
let src_code = mapping[dst_code].code;
let src_font = mapping[dst_code].font;
if (!ft_render.glyph_exists(fonts_freetype[src_font], src_code)) continue;
let ft_result = ft_render.glyph_render(
fonts_freetype[src_font],
src_code,
{
autohint_off: fonts_options[src_font].autohint_off,
autohint_strong: fonts_options[src_font].autohint_strong,
lcd: args.lcd,
lcd_v: args.lcd_v,
mono: !args.lcd && !args.lcd_v && args.bpp === 1,
use_color_info: args.use_color_info
}
);
glyphs.push({
code: dst_code,
advanceWidth: ft_result.advance_x,
bbox: {
x: ft_result.x,
y: ft_result.y - ft_result.height,
width: ft_result.width,
height: ft_result.height
},
kerning: {},
freetype: ft_result.freetype,
pixels: ft_result.pixels
});
}
if (!args.no_kerning) {
let existing_dst_charcodes = glyphs.map(g => g.code);
for (let { code, kerning } of glyphs) {
let src_code = mapping[code].code;
let src_font = mapping[code].font;
let font = fonts_opentype[src_font];
let glyph = font.charToGlyph(String.fromCodePoint(src_code));
for (let dst_code2 of existing_dst_charcodes) {
// can't merge kerning values from 2 different fonts
if (mapping[dst_code2].font !== src_font) continue;
let src_code2 = mapping[dst_code2].code;
let glyph2 = font.charToGlyph(String.fromCodePoint(src_code2));
let krn_value = font.getKerningValue(glyph, glyph2);
if (krn_value) kerning[dst_code2] = krn_value * args.size / font.unitsPerEm;
//let krn_value = ft_render.get_kerning(font, src_code, src_code2).x;
//if (krn_value) kerning[dst_code2] = krn_value;
}
}
}
let first_font = fonts_freetype[args.font[0].source_path];
let first_font_scale = args.size / first_font.units_per_em;
let os2_metrics = ft_render.fontface_os2_table(first_font);
let post_table = fonts_opentype[args.font[0].source_path].tables.post;
for (let font of Object.values(fonts_freetype)) ft_render.fontface_destroy(font);
ft_render.destroy();
return {
ascent: Math.max(...glyphs.map(g => g.bbox.y + g.bbox.height)),
descent: Math.min(...glyphs.map(g => g.bbox.y)),
typoAscent: Math.round(os2_metrics.typoAscent * first_font_scale),
typoDescent: Math.round(os2_metrics.typoDescent * first_font_scale),
typoLineGap: Math.round(os2_metrics.typoLineGap * first_font_scale),
size: args.size,
glyphs,
underlinePosition: Math.round(post_table.underlinePosition * first_font_scale),
underlineThickness: Math.round(post_table.underlineThickness * first_font_scale)
};
};

30
node_modules/lv_font_conv/lib/convert.js generated vendored Normal file
View File

@@ -0,0 +1,30 @@
// Internal API to convert input data into output font data
// Used by both CLI and Web wrappers.
'use strict';
const collect_font_data = require('./collect_font_data');
let writers = {
dump: require('./writers/dump'),
bin: require('./writers/bin'),
lvgl: require('./writers/lvgl')
};
//
// Input:
// - args like from CLI (optionally extended with binary content of files)
//
// Output:
// - { name1: bin_data1, name2: bin_data2, ... }
//
// returns hash with files to write
//
module.exports = async function convert(args) {
let font_data = await collect_font_data(args);
let files = writers[args.format](args, font_data);
return files;
};
module.exports.formats = Object.keys(writers);

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;

317
node_modules/lv_font_conv/lib/freetype/index.js generated vendored Normal file
View File

@@ -0,0 +1,317 @@
'use strict';
const ft_render_fabric = require('./build/ft_render');
let m = null; // compiled module instance
let library = 0; // pointer to library struct in wasm memory
// workaround because of bug in emscripten:
// https://github.com/emscripten-core/emscripten/issues/5820
const runtime_initialized = new Promise(resolve => {
ft_render_fabric().then(module_instance => {
m = module_instance;
resolve();
});
});
function from_16_16(fixed_point) {
return fixed_point / (1 << 16);
}
function from_26_6(fixed_point) {
return fixed_point / (1 << 6);
}
function int8_to_uint8(value) {
return value >= 0 ? value : value + 0x100;
}
let FT_New_Memory_Face,
FT_Set_Char_Size,
FT_Set_Pixel_Sizes,
FT_Get_Char_Index,
FT_Load_Glyph,
FT_Get_Sfnt_Table,
FT_Get_Kerning,
FT_Done_Face;
module.exports.init = async function () {
await runtime_initialized;
m._init_constants();
FT_New_Memory_Face = module.exports.FT_New_Memory_Face =
m.cwrap('FT_New_Memory_Face', 'number', [ 'number', 'number', 'number', 'number', 'number' ]);
FT_Set_Char_Size = module.exports.FT_Set_Char_Size =
m.cwrap('FT_Set_Char_Size', 'number', [ 'number', 'number', 'number', 'number', 'number' ]);
FT_Set_Pixel_Sizes = module.exports.FT_Set_Pixel_Sizes =
m.cwrap('FT_Set_Pixel_Sizes', 'number', [ 'number', 'number', 'number' ]);
FT_Get_Char_Index = module.exports.FT_Get_Char_Index =
m.cwrap('FT_Get_Char_Index', 'number', [ 'number', 'number' ]);
FT_Load_Glyph = module.exports.FT_Load_Glyph =
m.cwrap('FT_Load_Glyph', 'number', [ 'number', 'number', 'number' ]);
FT_Get_Sfnt_Table = module.exports.FT_Get_Sfnt_Table =
m.cwrap('FT_Get_Sfnt_Table', 'number', [ 'number', 'number' ]);
FT_Get_Kerning = module.exports.FT_Get_Kerning =
m.cwrap('FT_Get_Kerning', 'number', [ 'number', 'number', 'number', 'number', 'number' ]);
FT_Done_Face = module.exports.FT_Done_Face =
m.cwrap('FT_Done_Face', 'number', [ 'number' ]);
if (!library) {
let ptr = m._malloc(4);
try {
let error = m.ccall('FT_Init_FreeType', 'number', [ 'number' ], [ ptr ]);
if (error) throw new Error(`error in FT_Init_FreeType: ${error}`);
library = m.getValue(ptr, 'i32');
} finally {
m._free(ptr);
}
}
};
module.exports.fontface_create = function (source, size) {
let error;
let face = {
ptr: 0,
font: m._malloc(source.length)
};
m.writeArrayToMemory(source, face.font);
let ptr = m._malloc(4);
try {
error = FT_New_Memory_Face(library, face.font, source.length, 0, ptr);
if (error) throw new Error(`error in FT_New_Memory_Face: ${error}`);
face.ptr = m.getValue(ptr, 'i32');
} finally {
m._free(ptr);
}
error = FT_Set_Char_Size(face.ptr, 0, size * 64, 300, 300);
if (error) throw new Error(`error in FT_Set_Char_Size: ${error}`);
error = FT_Set_Pixel_Sizes(face.ptr, 0, size);
if (error) throw new Error(`error in FT_Set_Pixel_Sizes: ${error}`);
let units_per_em = m.getValue(face.ptr + m.OFFSET_FACE_UNITS_PER_EM, 'i16');
let ascender = m.getValue(face.ptr + m.OFFSET_FACE_ASCENDER, 'i16');
let descender = m.getValue(face.ptr + m.OFFSET_FACE_DESCENDER, 'i16');
let height = m.getValue(face.ptr + m.OFFSET_FACE_HEIGHT, 'i16');
return Object.assign(face, {
units_per_em,
ascender,
descender,
height
});
};
module.exports.fontface_os2_table = function (face) {
let sfnt_ptr = FT_Get_Sfnt_Table(face.ptr, m.FT_SFNT_OS2);
if (!sfnt_ptr) throw new Error('os/2 table not found for this font');
let typoAscent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_ASCENDER, 'i16');
let typoDescent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_DESCENDER, 'i16');
let typoLineGap = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_LINEGAP, 'i16');
return {
typoAscent,
typoDescent,
typoLineGap
};
};
module.exports.get_kerning = function (face, code1, code2) {
let glyph1 = FT_Get_Char_Index(face.ptr, code1);
let glyph2 = FT_Get_Char_Index(face.ptr, code2);
let ptr = m._malloc(4 * 2);
try {
let error = FT_Get_Kerning(face.ptr, glyph1, glyph2, m.FT_KERNING_DEFAULT, ptr);
if (error) throw new Error(`error in FT_Get_Kerning: ${error}`);
} finally {
m._free(ptr);
}
return {
x: from_26_6(m.getValue(ptr, 'i32')),
y: from_26_6(m.getValue(ptr + 4, 'i32'))
};
};
module.exports.glyph_exists = function (face, code) {
let glyph_index = FT_Get_Char_Index(face.ptr, code);
return glyph_index !== 0;
};
module.exports.glyph_render = function (face, code, opts = {}) {
let glyph_index = FT_Get_Char_Index(face.ptr, code);
if (glyph_index === 0) throw new Error(`glyph does not exist for codepoint ${code}`);
let load_flags = m.FT_LOAD_RENDER;
if (opts.mono) {
load_flags |= m.FT_LOAD_TARGET_MONO;
} else if (opts.lcd) {
load_flags |= m.FT_LOAD_TARGET_LCD;
} else if (opts.lcd_v) {
load_flags |= m.FT_LOAD_TARGET_LCD_V;
} else {
/* eslint-disable no-lonely-if */
// Use "light" by default, it changes horizontal lines only.
// "normal" is more strong (with vertical lines), but will break kerning, if
// no additional care taken. More advanced rendering requires upper level
// layout support (via Harfbuzz, for example).
if (!opts.autohint_strong) load_flags |= m.FT_LOAD_TARGET_LIGHT;
else load_flags |= m.FT_LOAD_TARGET_NORMAL;
}
if (opts.autohint_off) load_flags |= m.FT_LOAD_NO_AUTOHINT;
else load_flags |= m.FT_LOAD_FORCE_AUTOHINT;
if (opts.use_color_info) load_flags |= m.FT_LOAD_COLOR;
let error = FT_Load_Glyph(face.ptr, glyph_index, load_flags);
if (error) throw new Error(`error in FT_Load_Glyph: ${error}`);
let glyph = m.getValue(face.ptr + m.OFFSET_FACE_GLYPH, 'i32');
let glyph_data = {
glyph_index: m.getValue(glyph + m.OFFSET_GLYPH_INDEX, 'i32'),
metrics: {
width: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_WIDTH, 'i32')),
height: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HEIGHT, 'i32')),
horiBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_X, 'i32')),
horiBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_Y, 'i32')),
horiAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_ADVANCE, 'i32')),
vertBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_X, 'i32')),
vertBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_Y, 'i32')),
vertAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_ADVANCE, 'i32'))
},
linearHoriAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_HORI_ADVANCE, 'i32')),
linearVertAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_VERT_ADVANCE, 'i32')),
advance: {
x: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_X, 'i32')),
y: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_Y, 'i32'))
},
bitmap: {
width: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_WIDTH, 'i32'),
rows: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_ROWS, 'i32'),
pitch: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PITCH, 'i32'),
num_grays: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_NUM_GRAYS, 'i16'),
pixel_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PIXEL_MODE, 'i8'),
palette_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PALETTE_MODE, 'i8')
},
bitmap_left: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_LEFT, 'i32'),
bitmap_top: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_TOP, 'i32'),
lsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_LSB_DELTA, 'i32')),
rsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_RSB_DELTA, 'i32'))
};
let g_w = glyph_data.bitmap.width;
let g_h = glyph_data.bitmap.rows;
let g_x = glyph_data.bitmap_left;
let g_y = glyph_data.bitmap_top;
let buffer = m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_BUFFER, 'i32');
let pitch = Math.abs(glyph_data.bitmap.pitch);
let advance_x = glyph_data.linearHoriAdvance;
let advance_y = glyph_data.linearVertAdvance;
let pixel_mode = glyph_data.bitmap.pixel_mode;
let output = [];
for (let y = 0; y < g_h; y++) {
let row_start = buffer + y * pitch;
let line = [];
for (let x = 0; x < g_w; x++) {
if (pixel_mode === m.FT_PIXEL_MODE_MONO) {
let value = m.getValue(row_start + ~~(x / 8), 'i8');
line.push(value & (1 << (7 - (x % 8))) ? 255 : 0);
} else if (pixel_mode === m.FT_PIXEL_MODE_BGRA) {
let blue = int8_to_uint8(m.getValue(row_start + (x * 4) + 0, 'i8'));
let green = int8_to_uint8(m.getValue(row_start + (x * 4) + 1, 'i8'));
let red = int8_to_uint8(m.getValue(row_start + (x * 4) + 2, 'i8'));
let alpha = int8_to_uint8(m.getValue(row_start + (x * 4) + 3, 'i8'));
// convert RGBA to grayscale
let grayscale = Math.round(0.299 * red + 0.587 * green + 0.114 * blue);
if (grayscale > 255) grayscale = 255;
// meld grayscale into alpha channel
alpha = ((255 - grayscale) * alpha) / 255;
line.push(alpha);
} else {
let value = m.getValue(row_start + x, 'i8');
line.push(int8_to_uint8(value));
}
}
output.push(line);
}
return {
x: g_x,
y: g_y,
width: g_w,
height: g_h,
advance_x,
advance_y,
pixels: output,
freetype: glyph_data
};
};
module.exports.fontface_destroy = function (face) {
let error = FT_Done_Face(face.ptr);
if (error) throw new Error(`error in FT_Done_Face: ${error}`);
m._free(face.font);
face.ptr = 0;
face.font = 0;
};
module.exports.destroy = function () {
let error = m.ccall('FT_Done_FreeType', 'number', [ 'number' ], [ library ]);
if (error) throw new Error(`error in FT_Done_FreeType: ${error}`);
library = 0;
// don't unload wasm - slows down tests too much
//m = null;
};

83
node_modules/lv_font_conv/lib/freetype/render.c generated vendored Normal file
View File

@@ -0,0 +1,83 @@
#include <emscripten.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_TRUETYPE_TABLES_H
static void set_js_variable(char* name, int value) {
char buffer[strlen(name) + 32];
sprintf(buffer, "Module.%s = %d;", name, value);
emscripten_run_script(buffer);
}
// Expose constants, used in calls from js
void init_constants()
{
set_js_variable("FT_LOAD_DEFAULT", FT_LOAD_DEFAULT);
set_js_variable("FT_LOAD_NO_HINTING", FT_LOAD_NO_HINTING);
set_js_variable("FT_LOAD_RENDER", FT_LOAD_RENDER);
set_js_variable("FT_LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT);
set_js_variable("FT_LOAD_PEDANTIC", FT_LOAD_PEDANTIC);
set_js_variable("FT_LOAD_MONOCHROME", FT_LOAD_MONOCHROME);
set_js_variable("FT_LOAD_NO_AUTOHINT", FT_LOAD_NO_AUTOHINT);
set_js_variable("FT_LOAD_COLOR", FT_LOAD_COLOR);
set_js_variable("FT_LOAD_TARGET_NORMAL", FT_LOAD_TARGET_NORMAL);
set_js_variable("FT_LOAD_TARGET_LIGHT", FT_LOAD_TARGET_LIGHT);
set_js_variable("FT_LOAD_TARGET_MONO", FT_LOAD_TARGET_MONO);
set_js_variable("FT_LOAD_TARGET_LCD", FT_LOAD_TARGET_LCD);
set_js_variable("FT_LOAD_TARGET_LCD_V", FT_LOAD_TARGET_LCD_V);
set_js_variable("FT_RENDER_MODE_NORMAL", FT_RENDER_MODE_NORMAL);
set_js_variable("FT_RENDER_MODE_MONO", FT_RENDER_MODE_MONO);
set_js_variable("FT_RENDER_MODE_LCD", FT_RENDER_MODE_LCD);
set_js_variable("FT_RENDER_MODE_LCD_V", FT_RENDER_MODE_LCD_V);
set_js_variable("FT_KERNING_DEFAULT", FT_KERNING_DEFAULT);
set_js_variable("FT_KERNING_UNFITTED", FT_KERNING_UNFITTED);
set_js_variable("FT_KERNING_UNSCALED", FT_KERNING_UNSCALED);
set_js_variable("FT_SFNT_OS2", FT_SFNT_OS2);
set_js_variable("FT_FACE_FLAG_COLOR", FT_FACE_FLAG_COLOR);
set_js_variable("FT_PIXEL_MODE_MONO", FT_PIXEL_MODE_MONO);
set_js_variable("FT_PIXEL_MODE_BGRA", FT_PIXEL_MODE_BGRA);
set_js_variable("OFFSET_FACE_GLYPH", offsetof(FT_FaceRec, glyph));
set_js_variable("OFFSET_FACE_UNITS_PER_EM", offsetof(FT_FaceRec, units_per_EM));
set_js_variable("OFFSET_FACE_ASCENDER", offsetof(FT_FaceRec, ascender));
set_js_variable("OFFSET_FACE_DESCENDER", offsetof(FT_FaceRec, descender));
set_js_variable("OFFSET_FACE_HEIGHT", offsetof(FT_FaceRec, height));
set_js_variable("OFFSET_FACE_FACE_FLAGS", offsetof(FT_FaceRec, face_flags));
set_js_variable("OFFSET_GLYPH_BITMAP_WIDTH", offsetof(FT_GlyphSlotRec, bitmap.width));
set_js_variable("OFFSET_GLYPH_BITMAP_ROWS", offsetof(FT_GlyphSlotRec, bitmap.rows));
set_js_variable("OFFSET_GLYPH_BITMAP_PITCH", offsetof(FT_GlyphSlotRec, bitmap.pitch));
set_js_variable("OFFSET_GLYPH_BITMAP_BUFFER", offsetof(FT_GlyphSlotRec, bitmap.buffer));
set_js_variable("OFFSET_GLYPH_BITMAP_NUM_GRAYS", offsetof(FT_GlyphSlotRec, bitmap.num_grays));
set_js_variable("OFFSET_GLYPH_BITMAP_PIXEL_MODE", offsetof(FT_GlyphSlotRec, bitmap.pixel_mode));
set_js_variable("OFFSET_GLYPH_BITMAP_PALETTE_MODE", offsetof(FT_GlyphSlotRec, bitmap.palette_mode));
set_js_variable("OFFSET_GLYPH_METRICS_WIDTH", offsetof(FT_GlyphSlotRec, metrics.width));
set_js_variable("OFFSET_GLYPH_METRICS_HEIGHT", offsetof(FT_GlyphSlotRec, metrics.height));
set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.horiBearingX));
set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.horiBearingY));
set_js_variable("OFFSET_GLYPH_METRICS_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.horiAdvance));
set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.vertBearingX));
set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.vertBearingY));
set_js_variable("OFFSET_GLYPH_METRICS_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.vertAdvance));
set_js_variable("OFFSET_GLYPH_BITMAP_LEFT", offsetof(FT_GlyphSlotRec, bitmap_left));
set_js_variable("OFFSET_GLYPH_BITMAP_TOP", offsetof(FT_GlyphSlotRec, bitmap_top));
set_js_variable("OFFSET_GLYPH_INDEX", offsetof(FT_GlyphSlotRec, glyph_index));
set_js_variable("OFFSET_GLYPH_LINEAR_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, linearHoriAdvance));
set_js_variable("OFFSET_GLYPH_LINEAR_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, linearVertAdvance));
set_js_variable("OFFSET_GLYPH_ADVANCE_X", offsetof(FT_GlyphSlotRec, advance.x));
set_js_variable("OFFSET_GLYPH_ADVANCE_Y", offsetof(FT_GlyphSlotRec, advance.y));
set_js_variable("OFFSET_GLYPH_LSB_DELTA", offsetof(FT_GlyphSlotRec, lsb_delta));
set_js_variable("OFFSET_GLYPH_RSB_DELTA", offsetof(FT_GlyphSlotRec, rsb_delta));
set_js_variable("OFFSET_TT_OS2_ASCENDER", offsetof(TT_OS2, sTypoAscender));
set_js_variable("OFFSET_TT_OS2_DESCENDER", offsetof(TT_OS2, sTypoDescender));
set_js_variable("OFFSET_TT_OS2_LINEGAP", offsetof(TT_OS2, sTypoLineGap));
}

51
node_modules/lv_font_conv/lib/ranger.js generated vendored Normal file
View File

@@ -0,0 +1,51 @@
// Merge ranges into single object
'use strict';
class Ranger {
constructor() {
this.data = {};
}
// input:
// -r 0x1F450 - single value, dec or hex format
// -r 0x1F450-0x1F470 - range
// -r 0x1F450=>0xF005 - single glyph with mapping
// -r 0x1F450-0x1F470=>0xF005 - range with mapping
add_range(font, start, end, mapped_start) {
let offset = mapped_start - start;
let output = [];
for (let i = start; i <= end; i++) {
this._set_char(font, i, i + offset);
output.push(i);
}
return output;
}
// input: characters to copy, e.g. '1234567890abcdef'
add_symbols(font, str) {
let output = [];
for (let chr of str) {
let code = chr.codePointAt(0);
this._set_char(font, code, code);
output.push(code);
}
return output;
}
_set_char(font, code, mapped_to) {
this.data[mapped_to] = { font, code };
}
get() {
return this.data;
}
}
module.exports = Ranger;

131
node_modules/lv_font_conv/lib/utils.js generated vendored Normal file
View File

@@ -0,0 +1,131 @@
'use strict';
function set_byte_depth(depth) {
return function (byte) {
// calculate significant bits, e.g. for depth=2 it's 0, 1, 2 or 3
let value = ~~(byte / (256 >> depth));
// spread those bits around 0..255 range, e.g. for depth=2 it's 0, 85, 170 or 255
let scale = (2 << (depth - 1)) - 1;
return (value * 0xFFFF / scale) >> 8;
};
}
module.exports.set_depth = function set_depth(glyph, depth) {
let pixels = [];
let fn = set_byte_depth(depth);
for (let y = 0; y < glyph.bbox.height; y++) {
pixels.push(glyph.pixels[y].map(fn));
}
return Object.assign({}, glyph, { pixels });
};
function count_bits(val) {
let count = 0;
val = ~~val;
while (val) {
count++;
val >>= 1;
}
return count;
}
// Minimal number of bits to store unsigned value
module.exports.unsigned_bits = count_bits;
// Minimal number of bits to store signed value
module.exports.signed_bits = function signed_bits(val) {
if (val >= 0) return count_bits(val) + 1;
return count_bits(Math.abs(val) - 1) + 1;
};
// Align value to 4x - useful to create word-aligned arrays
function align4(size) {
if (size % 4 === 0) return size;
return size + 4 - (size % 4);
}
module.exports.align4 = align4;
// Align buffer length to 4x (returns copy with zero-filled tail)
module.exports.balign4 = function balign4(buf) {
let buf_aligned = Buffer.alloc(align4(buf.length));
buf.copy(buf_aligned);
return buf_aligned;
};
// Pre-filter image to improve compression ratio
// In this case - XOR lines, because it's very effective
// in decompressor and does not depend on bpp.
module.exports.prefilter = function prefilter(pixels) {
return pixels.map((line, l_idx, arr) => {
if (l_idx === 0) return line.slice();
return line.map((p, idx) => p ^ arr[l_idx - 1][idx]);
});
};
// Convert array with uint16 data to buffer
module.exports.bFromA16 = function bFromA16(arr) {
const buf = Buffer.alloc(arr.length * 2);
for (let i = 0; i < arr.length; i++) buf.writeUInt16LE(arr[i], i * 2);
return buf;
};
// Convert array with uint32 data to buffer
module.exports.bFromA32 = function bFromA32(arr) {
const buf = Buffer.alloc(arr.length * 4);
for (let i = 0; i < arr.length; i++) buf.writeUInt32LE(arr[i], i * 4);
return buf;
};
function chunk(arr, size) {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
// Dump long array to multiline format with X columns and Y indent
module.exports.long_dump = function long_dump(arr, options = {}) {
const defaults = {
col: 8,
indent: 4,
hex: false
};
let opts = Object.assign({}, defaults, options);
let indent = ' '.repeat(opts.indent);
return chunk(Array.from(arr), opts.col)
.map(l => l.map(v => (opts.hex ? `0x${v.toString(16)}` : v.toString())))
.map(l => `${indent}${l.join(', ')}`)
.join(',\n');
};
// stable sort by pick() result
module.exports.sort_by = function sort_by(arr, pick) {
return arr
.map((el, idx) => ({ el, idx }))
.sort((a, b) => (pick(a.el) - pick(b.el)) || (a.idx - b.idx))
.map(({ el }) => el);
};
module.exports.sum = function sum(arr) {
return arr.reduce((a, v) => a + v, 0);
};

17
node_modules/lv_font_conv/lib/writers/bin.js generated vendored Normal file
View File

@@ -0,0 +1,17 @@
// Write font in binary format
'use strict';
const AppError = require('../app_error');
const Font = require('../font/font');
module.exports = function write_images(args, fontData) {
if (!args.output) throw new AppError('Output is required for "bin" writer');
const font = new Font(fontData, args);
return {
[args.output]: font.toBin()
};
};

68
node_modules/lv_font_conv/lib/writers/dump.js generated vendored Normal file
View File

@@ -0,0 +1,68 @@
// Write font data into png images
'use strict';
const path = require('path');
const { PNG } = require('pngjs');
const AppError = require('../app_error');
const utils = require('../utils');
const normal_color = [ 255, 255, 255 ];
const outside_color = [ 255, 127, 184 ];
module.exports = function write_images(args, font) {
if (!args.output) throw new AppError('Output is required for "dump" writer');
let files = {};
let glyphs = font.glyphs.map(glyph => utils.set_depth(glyph, args.bpp));
for (let glyph of glyphs) {
let { code, advanceWidth, bbox, pixels } = glyph;
advanceWidth = Math.round(advanceWidth);
let minX = bbox.x;
let maxX = Math.max(bbox.x + bbox.width - 1, bbox.x);
let minY = Math.min(bbox.y, font.typoDescent);
let maxY = Math.max(bbox.y + bbox.height - 1, font.typoAscent);
let png = new PNG({ width: maxX - minX + 1, height: maxY - minY + 1 });
/* eslint-disable max-depth */
for (let pos = 0, y = maxY; y >= minY; y--) {
for (let x = minX; x <= maxX; x++) {
let value = 0;
if (x >= bbox.x && x < bbox.x + bbox.width && y >= bbox.y && y < bbox.y + bbox.height) {
value = pixels[bbox.height - (y - bbox.y) - 1][x - bbox.x];
}
let r, g, b;
if (x < 0 || x >= advanceWidth || y < font.typoDescent || y > font.typoAscent) {
[ r, g, b ] = outside_color;
} else {
[ r, g, b ] = normal_color;
}
png.data[pos++] = (255 - value) * r / 255;
png.data[pos++] = (255 - value) * g / 255;
png.data[pos++] = (255 - value) * b / 255;
png.data[pos++] = 255;
}
}
files[path.join(args.output, `${code.toString(16)}.png`)] = PNG.sync.write(png);
}
files[path.join(args.output, 'font_info.json')] = JSON.stringify(
font,
(k, v) => (k === 'pixels' && !args.full_info ? undefined : v),
2);
return files;
};

17
node_modules/lv_font_conv/lib/writers/lvgl/index.js generated vendored Normal file
View File

@@ -0,0 +1,17 @@
// Write font in lvgl format
'use strict';
const AppError = require('../../app_error');
const Font = require('./lv_font');
module.exports = function write_images(args, fontData) {
if (!args.output) throw new AppError('Output is required for "lvgl" writer');
const font = new Font(fontData, args);
return {
[args.output]: font.toLVGL()
};
};

98
node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js generated vendored Normal file
View File

@@ -0,0 +1,98 @@
'use strict';
const path = require('path');
const Font = require('../../font/font');
const Head = require('./lv_table_head');
const Cmap = require('./lv_table_cmap');
const Glyf = require('./lv_table_glyf');
const Kern = require('./lv_table_kern');
const AppError = require('../../app_error');
class LvFont extends Font {
constructor(fontData, options) {
super(fontData, options);
const ext = path.extname(options.output);
this.font_name = path.basename(options.output, ext);
if (options.bpp === 3 & options.no_compress) {
throw new AppError('LittlevGL supports "--bpp 3" with compression only');
}
}
init_tables() {
this.head = new Head(this);
this.glyf = new Glyf(this);
this.cmap = new Cmap(this);
this.kern = new Kern(this);
}
large_format_guard() {
let guard_required = false;
let glyphs_bin_size = 0;
this.glyf.lv_data.forEach(d => {
glyphs_bin_size += d.bin.length;
if (d.glyph.bbox.width > 255 ||
d.glyph.bbox.height > 255 ||
Math.abs(d.glyph.bbox.x) > 127 ||
Math.abs(d.glyph.bbox.y) > 127 ||
Math.round(d.glyph.advanceWidth * 16) > 4096) {
guard_required = true;
}
});
if (glyphs_bin_size > 1024 * 1024) guard_required = true;
if (!guard_required) return '';
return `
#if (LV_FONT_FMT_TXT_LARGE == 0)
# error "Too large font or glyphs in ${this.font_name.toUpperCase()}. Enable LV_FONT_FMT_TXT_LARGE in lv_conf.h")
#endif
`.trimLeft();
}
toLVGL() {
let guard_name = this.font_name.toUpperCase();
return `/*******************************************************************************
* Size: ${this.src.size} px
* Bpp: ${this.opts.bpp}
* Opts: ${process.argv.slice(2).join(' ')}
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "${this.opts.lv_include || 'lvgl/lvgl.h'}"
#endif
#ifndef ${guard_name}
#define ${guard_name} 1
#endif
#if ${guard_name}
${this.glyf.toLVGL()}
${this.cmap.toLVGL()}
${this.kern.toLVGL()}
${this.head.toLVGL()}
${this.large_format_guard()}
#endif /*#if ${guard_name}*/
`;
}
}
module.exports = LvFont;

View File

@@ -0,0 +1,125 @@
'use strict';
const u = require('../../utils');
const build_subtables = require('../../font/cmap_build_subtables');
const Cmap = require('../../font/table_cmap');
class LvCmap extends Cmap {
constructor(font) {
super(font);
this.lv_compiled = false;
this.lv_subtables = [];
}
lv_format2enum(name) {
switch (name) {
case 'format0_tiny': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY';
case 'format0': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL';
case 'sparse_tiny': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_TINY';
case 'sparse': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_FULL';
default: throw new Error('Unknown subtable format');
}
}
lv_compile() {
if (this.lv_compiled) return;
this.lv_compiled = true;
const f = this.font;
let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code));
let idx = 0;
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 has_charcodes = false;
let has_ids = false;
let defs = '';
let entries_count = 0;
if (format === 'format0_tiny') {
// use default empty values
} else if (format === 'format0') {
has_ids = true;
let d = this.collect_format0_data(min_code, max_code, start_glyph_id);
entries_count = d.length;
defs = `
static const uint8_t glyph_id_ofs_list_${idx}[] = {
${u.long_dump(d)}
};
`.trim();
} else if (format === 'sparse_tiny') {
has_charcodes = true;
let d = this.collect_sparse_data(codepoints, start_glyph_id);
entries_count = d.codes.length;
defs = `
static const uint16_t unicode_list_${idx}[] = {
${u.long_dump(d.codes, { hex: true })}
};
`.trim();
} else { // assume format === 'sparse'
has_charcodes = true;
has_ids = true;
let d = this.collect_sparse_data(codepoints, start_glyph_id);
entries_count = d.codes.length;
defs = `
static const uint16_t unicode_list_${idx}[] = {
${u.long_dump(d.codes, { hex: true })}
};
static const uint16_t glyph_id_ofs_list_${idx}[] = {
${u.long_dump(d.ids)}
};
`.trim();
}
const u_list = has_charcodes ? `unicode_list_${idx}` : 'NULL';
const id_list = has_ids ? `glyph_id_ofs_list_${idx}` : 'NULL';
/* eslint-disable max-len */
const head = ` {
.range_start = ${min_code}, .range_length = ${max_code - min_code + 1}, .glyph_id_start = ${start_glyph_id},
.unicode_list = ${u_list}, .glyph_id_ofs_list = ${id_list}, .list_length = ${entries_count}, .type = ${this.lv_format2enum(format)}
}`;
this.lv_subtables.push({
defs,
head
});
idx++;
}
}
toLVGL() {
this.lv_compile();
return `
/*---------------------
* CHARACTER MAPPING
*--------------------*/
${this.lv_subtables.map(d => d.defs).filter(Boolean).join('\n\n')}
/*Collect the unicode lists and glyph_id offsets*/
static const lv_font_fmt_txt_cmap_t cmaps[] =
{
${this.lv_subtables.map(d => d.head).join(',\n')}
};
`.trim();
}
}
module.exports = LvCmap;

View File

@@ -0,0 +1,121 @@
'use strict';
const { BitStream } = require('bit-buffer');
const u = require('../../utils');
const Glyf = require('../../font/table_glyf');
class LvGlyf extends Glyf {
constructor(font) {
super(font);
this.lv_data = [];
this.lv_compiled = false;
}
lv_bitmap(glyph) {
const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4);
const bs = new BitStream(buf);
bs.bigEndian = true;
const pixels = this.font.glyf.pixelsToBpp(glyph.pixels);
this.font.glyf.storePixels(bs, pixels);
const glyph_bitmap = Buffer.alloc(bs.byteIndex);
buf.copy(glyph_bitmap, 0, 0, bs.byteIndex);
return glyph_bitmap;
}
lv_compile() {
if (this.lv_compiled) return;
this.lv_compiled = true;
const f = this.font;
this.lv_data = [];
let offset = 0;
f.src.glyphs.forEach(g => {
const id = f.glyph_id[g.code];
const bin = this.lv_bitmap(g);
this.lv_data[id] = {
bin,
offset,
glyph: g
};
offset += bin.length;
});
}
to_lv_bitmaps() {
this.lv_compile();
let result = [];
this.lv_data.forEach((d, idx) => {
if (idx === 0) return;
const code_hex = d.glyph.code.toString(16).toUpperCase();
const code_str = JSON.stringify(String.fromCodePoint(d.glyph.code));
let txt = ` /* U+${code_hex.padStart(4, '0')} ${code_str} */
${u.long_dump(d.bin, { hex: true })}`;
if (idx < this.lv_data.length - 1) {
// skip comma for zero data
txt += d.bin.length ? ',\n\n' : '\n';
}
result.push(txt);
});
return result.join('');
}
to_lv_glyph_dsc() {
this.lv_compile();
/* eslint-disable max-len */
let result = [ ' {.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */' ];
this.lv_data.forEach(d => {
const idx = d.offset,
adv_w = Math.round(d.glyph.advanceWidth * 16),
h = d.glyph.bbox.height,
w = d.glyph.bbox.width,
x = d.glyph.bbox.x,
y = d.glyph.bbox.y;
result.push(` {.bitmap_index = ${idx}, .adv_w = ${adv_w}, .box_w = ${w}, .box_h = ${h}, .ofs_x = ${x}, .ofs_y = ${y}}`);
});
return result.join(',\n');
}
toLVGL() {
return `
/*-----------------
* BITMAPS
*----------------*/
/*Store the image of the glyphs*/
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
${this.to_lv_bitmaps()}
};
/*---------------------
* GLYPH DESCRIPTION
*--------------------*/
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
${this.to_lv_glyph_dsc()}
};
`.trim();
}
}
module.exports = LvGlyf;

View File

@@ -0,0 +1,99 @@
'use strict';
const Head = require('../../font/table_head');
class LvHead extends Head {
constructor(font) {
super(font);
}
kern_ref() {
const f = this.font;
if (!f.hasKerning()) {
return {
scale: '0',
dsc: 'NULL',
classes: '0'
};
}
if (!f.kern.should_use_format3()) {
return {
scale: `${Math.round(f.kerningScale * 16)}`,
dsc: '&kern_pairs',
classes: '0'
};
}
return {
scale: `${Math.round(f.kerningScale * 16)}`,
dsc: '&kern_classes',
classes: '1'
};
}
toLVGL() {
const f = this.font;
const kern = this.kern_ref();
const subpixels = (f.subpixels_mode === 0) ? 'LV_FONT_SUBPX_NONE' :
(f.subpixels_mode === 1) ? 'LV_FONT_SUBPX_HOR' : 'LV_FONT_SUBPX_VER';
return `
/*--------------------
* ALL CUSTOM DATA
*--------------------*/
#if LV_VERSION_CHECK(8, 0, 0)
/*Store all the custom data of the font*/
static lv_font_fmt_txt_glyph_cache_t cache;
static const lv_font_fmt_txt_dsc_t font_dsc = {
#else
static lv_font_fmt_txt_dsc_t font_dsc = {
#endif
.glyph_bitmap = glyph_bitmap,
.glyph_dsc = glyph_dsc,
.cmaps = cmaps,
.kern_dsc = ${kern.dsc},
.kern_scale = ${kern.scale},
.cmap_num = ${f.cmap.toBin().readUInt32LE(8)},
.bpp = ${f.opts.bpp},
.kern_classes = ${kern.classes},
.bitmap_format = ${f.glyf.getCompressionCode()},
#if LV_VERSION_CHECK(8, 0, 0)
.cache = &cache
#endif
};
/*-----------------
* PUBLIC FONT
*----------------*/
/*Initialize a public general font descriptor*/
#if LV_VERSION_CHECK(8, 0, 0)
const lv_font_t ${f.font_name} = {
#else
lv_font_t ${f.font_name} = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
.line_height = ${f.src.ascent - f.src.descent}, /*The maximum line height required by the font*/
.base_line = ${-f.src.descent}, /*Baseline measured from the bottom of the line*/
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
.subpx = ${subpixels},
#endif
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
.underline_position = ${f.src.underlinePosition},
.underline_thickness = ${f.src.underlineThickness},
#endif
.dsc = &font_dsc /*The custom font data. Will be accessed by \`get_glyph_bitmap/dsc\` */
};
`.trim();
}
}
module.exports = LvHead;

View File

@@ -0,0 +1,121 @@
'use strict';
const u = require('../../utils');
const Kern = require('../../font/table_kern');
class LvKern extends Kern {
constructor(font) {
super(font);
}
to_lv_format0() {
const f = this.font;
let kern_pairs = this.collect_format0_data();
return `
/*-----------------
* KERNING
*----------------*/
/*Pair left and right glyphs for kerning*/
static const ${f.glyphIdFormat ? 'uint16_t' : 'uint8_t'} kern_pair_glyph_ids[] =
{
${kern_pairs.map(pair => ` ${pair[0]}, ${pair[1]}`).join(',\n')}
};
/* Kerning between the respective left and right glyphs
* 4.4 format which needs to scaled with \`kern_scale\`*/
static const int8_t kern_pair_values[] =
{
${u.long_dump(kern_pairs.map(pair => f.kernToFP(pair[2])))}
};
/*Collect the kern pair's data in one place*/
static const lv_font_fmt_txt_kern_pair_t kern_pairs =
{
.glyph_ids = kern_pair_glyph_ids,
.values = kern_pair_values,
.pair_cnt = ${kern_pairs.length},
.glyph_ids_size = ${f.glyphIdFormat}
};
`.trim();
}
to_lv_format3() {
const f = this.font;
const {
left_classes,
right_classes,
left_mapping,
right_mapping,
values
} = this.collect_format3_data();
return `
/*-----------------
* KERNING
*----------------*/
/*Map glyph_ids to kern left classes*/
static const uint8_t kern_left_class_mapping[] =
{
${u.long_dump(left_mapping)}
};
/*Map glyph_ids to kern right classes*/
static const uint8_t kern_right_class_mapping[] =
{
${u.long_dump(right_mapping)}
};
/*Kern values between classes*/
static const int8_t kern_class_values[] =
{
${u.long_dump(values.map(v => f.kernToFP(v)))}
};
/*Collect the kern class' data in one place*/
static const lv_font_fmt_txt_kern_classes_t kern_classes =
{
.class_pair_values = kern_class_values,
.left_class_mapping = kern_left_class_mapping,
.right_class_mapping = kern_right_class_mapping,
.left_class_cnt = ${left_classes},
.right_class_cnt = ${right_classes},
};
`.trim();
}
toLVGL() {
const f = this.font;
if (!f.hasKerning()) return '';
/* eslint-disable no-console */
if (f.kern.should_use_format3()) {
if (f.kern.format3_forced) {
let diff = this.create_format3_data().length - this.create_format0_data().length;
console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`);
}
return this.to_lv_format3();
}
if (this.font.opts.fast_kerning) {
console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.');
}
return this.to_lv_format0();
}
}
module.exports = LvKern;