Add mem free printing (I need it to verify a suspicion relating to a bug)
This commit is contained in:
9
node_modules/lv_font_conv/lib/app_error.js
generated
vendored
Normal file
9
node_modules/lv_font_conv/lib/app_error.js
generated
vendored
Normal 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
318
node_modules/lv_font_conv/lib/cli.js
generated
vendored
Normal 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
173
node_modules/lv_font_conv/lib/collect_font_data.js
generated
vendored
Normal 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
30
node_modules/lv_font_conv/lib/convert.js
generated
vendored
Normal 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);
|
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;
|
317
node_modules/lv_font_conv/lib/freetype/index.js
generated
vendored
Normal file
317
node_modules/lv_font_conv/lib/freetype/index.js
generated
vendored
Normal 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
83
node_modules/lv_font_conv/lib/freetype/render.c
generated
vendored
Normal 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
51
node_modules/lv_font_conv/lib/ranger.js
generated
vendored
Normal 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
131
node_modules/lv_font_conv/lib/utils.js
generated
vendored
Normal 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
17
node_modules/lv_font_conv/lib/writers/bin.js
generated
vendored
Normal 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
68
node_modules/lv_font_conv/lib/writers/dump.js
generated
vendored
Normal 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
17
node_modules/lv_font_conv/lib/writers/lvgl/index.js
generated
vendored
Normal 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
98
node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js
generated
vendored
Normal 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;
|
125
node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js
generated
vendored
Normal file
125
node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js
generated
vendored
Normal 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;
|
121
node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js
generated
vendored
Normal file
121
node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js
generated
vendored
Normal 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;
|
99
node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js
generated
vendored
Normal file
99
node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js
generated
vendored
Normal 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;
|
121
node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js
generated
vendored
Normal file
121
node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js
generated
vendored
Normal 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;
|
Reference in New Issue
Block a user