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

main
Caleb Fontenot 2024-04-22 13:28:24 +07:00
parent 20cd951cde
commit c60251359c
120 changed files with 31903 additions and 2 deletions

113
node_modules/.package-lock.json generated vendored

@ -0,0 +1,113 @@
{
"name": "InfiniTime",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/lv_font_conv": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz",
"integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==",
"bundleDependencies": [
"argparse",
"bit-buffer",
"debug",
"make-error",
"mkdirp",
"opentype.js",
"pngjs"
],
"dependencies": {
"argparse": "^2.0.0",
"bit-buffer": "^0.2.5",
"debug": "^4.1.1",
"make-error": "^1.3.5",
"mkdirp": "^1.0.4",
"opentype.js": "^1.1.0",
"pngjs": "^6.0.0"
},
"bin": {
"lv_font_conv": "lv_font_conv.js"
}
},
"node_modules/lv_font_conv/node_modules/argparse": {
"version": "2.0.1",
"inBundle": true,
"license": "Python-2.0"
},
"node_modules/lv_font_conv/node_modules/bit-buffer": {
"version": "0.2.5",
"inBundle": true,
"license": "MIT"
},
"node_modules/lv_font_conv/node_modules/debug": {
"version": "4.3.1",
"inBundle": true,
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/lv_font_conv/node_modules/make-error": {
"version": "1.3.6",
"inBundle": true,
"license": "ISC"
},
"node_modules/lv_font_conv/node_modules/mkdirp": {
"version": "1.0.4",
"inBundle": true,
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/lv_font_conv/node_modules/ms": {
"version": "2.1.2",
"inBundle": true,
"license": "MIT"
},
"node_modules/lv_font_conv/node_modules/opentype.js": {
"version": "1.3.3",
"inBundle": true,
"license": "MIT",
"dependencies": {
"string.prototype.codepointat": "^0.2.1",
"tiny-inflate": "^1.0.3"
},
"bin": {
"ot": "bin/ot"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/lv_font_conv/node_modules/pngjs": {
"version": "6.0.0",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">=12.13.0"
}
},
"node_modules/lv_font_conv/node_modules/string.prototype.codepointat": {
"version": "0.2.1",
"inBundle": true,
"license": "MIT"
},
"node_modules/lv_font_conv/node_modules/tiny-inflate": {
"version": "1.0.3",
"inBundle": true,
"license": "MIT"
}
}
}

@ -0,0 +1,164 @@
1.5.2 / 2021-07-18
------------------
- Fixed lvgl version check for v8+, #64.
1.5.1 / 2021-04-06
------------------
- Fixed fail of CMAP generation for edge cases, #62.
- Dev deps bump.
1.5.0 / 2021-03-08
------------------
- More `const` in generated font (for v8+), #59.
1.4.1 / 2021-01-26
------------------
- Fix charcodes padding in comments, #54.
1.4.0 / 2021-01-03
------------------
- Added OTF fonts support.
- Added `--use-color-info` for limited multi-tone glyphs support.
1.3.1 / 2020-12-28
------------------
- Unify `lvgl.h` include.
- Updated repo refs (littlevgl => lvgl).
- Deps bump.
- Moved CI to github actions.
1.3.0 / 2020-10-25
------------------
- Drop `lodash` use.
- Deps bump.
1.2.1 / 2020-10-24
------------------
- Reduced npm package size (drop unneeded files before publish).
1.2.0 / 2020-10-24
------------------
- Bump FreeType to 2.10.4.
- Bundle dependencies to npm package.
1.1.3 / 2020-09-22
------------------
- lvgl: added `LV_FONT_FMT_TXT_LARGE` check or very large fonts.
1.1.2 / 2020-08-23
------------------
- Fix: skip `glyph.advanceWidth` for monospace fonts, #43.
- Spec fix: version size should be 4 bytes, #44.
- Spec fix: bbox x/y bits => unsigned, #45.
- Bump argparse.
- Cleanup help formatter.
1.1.1 / 2020-08-01
------------------
- `--version` should show number from `package.json`.
1.1.0 / 2020-07-27
------------------
- Added `post.underlinePosition` & `post.underlineThickness` info to font header.
1.0.0 / 2020-06-26
------------------
- Maintenance release.
- Set package version 1.x, to label package as stable.
- Deps bump.
0.4.3 / 2020-03-05
------------------
- Enabled `--bpp 8` mode.
0.4.2 / 2020-01-05
------------------
- Added `--lv_include` option to set alternate `lvgl.h` path.
- Added guards to hide `.subpx` property for lvgl 6.0 (supported from 6.1 only), #32.
- Dev deps bump
0.4.1 / 2019-12-09
------------------
- Allow memory growth for FreeType build, #29.
- Dev deps bump.
- Web build update.
0.4.0 / 2019-11-29
------------------
- Note, this release is for lvgl 6.1 and has potentially breaking changes
(see below). If you have compatibility issues with lvgl 6.0 - use previous
versions or update your code.
- Spec change: added subpixels info field to font header (header size increased).
- Updated `bin` & `lvgl` writers to match new spec.
- lvgl: fixed data type for kerning values (needs appropriate update
in LittlevGL 6.1+).
- Fix errors display (disable emscripten error catcher).
0.3.1 / 2019-10-24
------------------
- Fixed "out of range" error for big `--size`.
0.3.0 / 2019-10-12
------------------
- Added beta options `--lcd` & `--lcd-v` for subpixel rendering (still need
header info update).
- Added FreeType data properties to dump info.
- Fixed glyph width (missed fractional part after switch to FreeType).
- Fixed missed sigh for negative X/Y bitmap offsets.
- Deps bump.
0.2.0 / 2019-09-26
------------------
- Use FreeType renderer. Should solve all regressions, reported in 0.1.0.
- Enforced light autohinting (horizontal lines only).
- Use special hinter for monochrome output (improve quality).
- API changed to async.
- Fix: added missed `.bitmap_format` field to lvgl writer.
- Fix: changed struct fields init order to match declaration, #25.
0.1.0 / 2019-09-03
------------------
- First release.

22
node_modules/lv_font_conv/LICENSE generated vendored

@ -0,0 +1,22 @@
Copyright (c) 2018 authors
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

150
node_modules/lv_font_conv/README.md generated vendored

@ -0,0 +1,150 @@
lv_font_conv - font convertor to compact bitmap format
======================================================
[![CI](https://github.com/lvgl/lv_font_conv/workflows/CI/badge.svg?branch=master)](https://github.com/lvgl/lv_font_conv/actions)
[![NPM version](https://img.shields.io/npm/v/lv_font_conv.svg?style=flat)](https://www.npmjs.org/package/lv_font_conv)
Converts TTF/WOFF/OTF fonts to __[compact format](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)__, suitable for small embedded systems. Main features are:
- Allows bitonal and anti-aliased glyphs (1-4 bits per pixel).
- Preserves kerning info.
- Compression.
- Users can select required glyphs only (subsetting).
- Multiple font sources can be merged.
- Simple CLI interface, easy to integrate into external build systems.
## Install the script
[node.js](https://nodejs.org/en/download/) v10+ required.
Global install of the last version, execute as "lv_font_conv"
```sh
# install release from npm registry
npm i lv_font_conv -g
# install from github's repo, master branch
npm i lvgl/lv_font_conv -g
```
**run via [npx](https://www.npmjs.com/package/npx) without install**
```sh
# run from npm registry
npx lv_font_conv -h
# run from github master
npx github:lvgl/lv_font_conv -h
```
Note, runing via `npx` may take some time until modules installed, be patient.
## CLI params
Common:
- `--bpp` - bits per pixel (antialiasing).
- `--size` - output font size (pixels).
- `-o`, `--output` - output path (file or directory, depends on format).
- `--format` - output format.
- `--format dump` - dump glyph images and font info, useful for debug.
- `--format bin` - dump font in binary form (as described in [spec](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)).
- `--format lvgl` - dump font in [LittlevGL](https://github.com/lvgl/lvgl) format.
- `--force-fast-kern-format` - always use more fast kering storage format,
at cost of some size. If size difference appears, it will be displayed.
- `--lcd` - generate bitmaps with 3x horizontal resolution, for subpixel
smoothing.
- `--lcd-v` - generate bitmaps with 3x vertical resolution, for subpixel
smoothing.
- `--use-color-info` - 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.
- `--lv-include` - only with `--format lvgl`, set alternate path for `lvgl.h`.
Per font:
- `--font` - path to font file (ttf/woff/woff2/otf). May be used multiple time for
merge.
- `-r`, `--range` - single glyph or range + optional mapping, belongs to
previously declared `--font`. Can be used multiple times. Examples:
- `-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.
- `-r 0x1F450 -r 0x1F451-0x1F470` - 2 ranges.
- `-r 0x1F450,0x1F451-0x1F470` - the same as above, but defined with single `-r`.
- `--symbols` - list of characters to copy (instead of numeric format in `-r`).
- `--symbols 0123456789.,` - extract chars to display numbers.
- `--autohint-off` - do not force autohinting ("light" is on by default).
- `--autohint-strong` - use more strong autohinting (will break kerning).
Additional debug options:
- `--no-compress` - disable built-in RLE compression.
- `--no-prefilter` - disable bitmap lines filter (XOR), used to improve
compression ratio.
- `--no-kerning` - drop kerning info to reduce size (not recommended).
- `--full-info` - don't shorten 'font_info.json' (include pixels data).
## Examples
Merge english from Roboto Regular and icons from Font Awesome, and show debug
info:
`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --font FontAwesome.ttf -r 0xFE00=>0x81 --size 16 --format bin --bpp 3 --no-compress -o output.font`
Merge english & russian from Roboto Regular, and show debug info:
`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F -r 0x401,0x410-0x44F,0x451 --size 16 --format bin --bpp 3 --no-compress -o output.font`
Dump all Roboto glyphs to inspect icons and font details:
`lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --size 16 --format dump --bpp 3 -o ./dump`
**Note**. Option `--no-compress` exists temporary, to avoid confusion until LVGL
adds compression support.
## Technical notes
### Supported output formats
1. **bin** - universal binary format, as described in https://github.com/lvgl/lv_font_conv/tree/master/doc.
2. **lvgl** - format for LittlevGL, C file. Has minor limitations and a bit
bigger size, because C does not allow to effectively define relative offsets
in data blocks.
3. **dump** - create folder with each glyph in separate image, and other font
data as `json`. Useful for debug.
### Merged font metrics
When multiple fonts merged into one, sources can have different metrics. Result
will follow principles below:
1. No scaling. Glyphs will have exactly the same size, as intended by font authors.
2. The same baseline.
3. `OS/2` metrics (`sTypoAscender`, `sTypoDescender`, `sTypoLineGap`) will be
used from the first font in list.
4. `hhea` metrics (`ascender`, `descender`), defined as max/min point of all
font glyphs, are recalculated, according to new glyphs set.
## Development
Current package includes WebAssembly build of FreeType with some helper
functions. Everything is wrapped into Docker and requires zero knowledge about
additional tools install. See `package.json` for additional commands. You may
need those if decide to upgrade FreeType or update helpers.
This builds image with emscripten & freetype, usually should be done only once:
```
npm run build:dockerimage
```
This compiles helpers and creates WebAssembly files:
```
npm run build:freetype
```

@ -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');

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

@ -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)
};
};

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

@ -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;
};

@ -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}`);
};

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

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

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

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

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

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

@ -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;
};

@ -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));
}

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

@ -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);
};

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

@ -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;
};

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

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

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

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

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

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

@ -0,0 +1,17 @@
#!/usr/bin/env node
'use strict';
const AppError = require('./lib/app_error');
require('./lib/cli').run(process.argv.slice(2)).catch(err => {
/*eslint-disable no-console*/
if (err instanceof AppError) {
// Try to beautify normal errors
console.error(err.message.trim());
} else {
// Print crashes
console.error(err.stack);
}
process.exit(1);
});

@ -0,0 +1,216 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.1] - 2020-08-29
### Fixed
- Fix issue with `process.argv` when used with interpreters (`coffee`, `ts-node`, etc.), #150.
## [2.0.0] - 2020-08-14
### Changed
- Full rewrite. Now port from python 3.9.0 & more precise following.
See [doc](./doc) for difference and migration info.
- node.js 10+ required
- Removed most of local docs in favour of original ones.
## [1.0.10] - 2018-02-15
### Fixed
- Use .concat instead of + for arrays, #122.
## [1.0.9] - 2016-09-29
### Changed
- Rerelease after 1.0.8 - deps cleanup.
## [1.0.8] - 2016-09-29
### Changed
- Maintenance (deps bump, fix node 6.5+ tests, coverage report).
## [1.0.7] - 2016-03-17
### Changed
- Teach `addArgument` to accept string arg names. #97, @tomxtobin.
## [1.0.6] - 2016-02-06
### Changed
- Maintenance: moved to eslint & updated CS.
## [1.0.5] - 2016-02-05
### Changed
- Removed lodash dependency to significantly reduce install size.
Thanks to @mourner.
## [1.0.4] - 2016-01-17
### Changed
- Maintenance: lodash update to 4.0.0.
## [1.0.3] - 2015-10-27
### Fixed
- Fix parse `=` in args: `--examplepath="C:\myfolder\env=x64"`. #84, @CatWithApple.
## [1.0.2] - 2015-03-22
### Changed
- Relaxed lodash version dependency.
## [1.0.1] - 2015-02-20
### Changed
- Changed dependencies to be compatible with ancient nodejs.
## [1.0.0] - 2015-02-19
### Changed
- Maintenance release.
- Replaced `underscore` with `lodash`.
- Bumped version to 1.0.0 to better reflect semver meaning.
- HISTORY.md -> CHANGELOG.md
## [0.1.16] - 2013-12-01
### Changed
- Maintenance release. Updated dependencies and docs.
## [0.1.15] - 2013-05-13
### Fixed
- Fixed #55, @trebor89
## [0.1.14] - 2013-05-12
### Fixed
- Fixed #62, @maxtaco
## [0.1.13] - 2013-04-08
### Changed
- Added `.npmignore` to reduce package size
## [0.1.12] - 2013-02-10
### Fixed
- Fixed conflictHandler (#46), @hpaulj
## [0.1.11] - 2013-02-07
### Added
- Added 70+ tests (ported from python), @hpaulj
- Added conflictHandler, @applepicke
- Added fromfilePrefixChar, @hpaulj
### Fixed
- Multiple bugfixes, @hpaulj
## [0.1.10] - 2012-12-30
### Added
- Added [mutual exclusion](http://docs.python.org/dev/library/argparse.html#mutual-exclusion)
support, thanks to @hpaulj
### Fixed
- Fixed options check for `storeConst` & `appendConst` actions, thanks to @hpaulj
## [0.1.9] - 2012-12-27
### Fixed
- Fixed option dest interferens with other options (issue #23), thanks to @hpaulj
- Fixed default value behavior with `*` positionals, thanks to @hpaulj
- Improve `getDefault()` behavior, thanks to @hpaulj
- Improve negative argument parsing, thanks to @hpaulj
## [0.1.8] - 2012-12-01
### Fixed
- Fixed parser parents (issue #19), thanks to @hpaulj
- Fixed negative argument parse (issue #20), thanks to @hpaulj
## [0.1.7] - 2012-10-14
### Fixed
- Fixed 'choices' argument parse (issue #16)
- Fixed stderr output (issue #15)
## [0.1.6] - 2012-09-09
### Fixed
- Fixed check for conflict of options (thanks to @tomxtobin)
## [0.1.5] - 2012-09-03
### Fixed
- Fix parser #setDefaults method (thanks to @tomxtobin)
## [0.1.4] - 2012-07-30
### Fixed
- Fixed pseudo-argument support (thanks to @CGamesPlay)
- Fixed addHelp default (should be true), if not set (thanks to @benblank)
## [0.1.3] - 2012-06-27
### Fixed
- Fixed formatter api name: Formatter -> HelpFormatter
## [0.1.2] - 2012-05-29
### Fixed
- Removed excess whitespace in help
- Fixed error reporting, when parcer with subcommands
called with empty arguments
### Added
- Added basic tests
## [0.1.1] - 2012-05-23
### Fixed
- Fixed line wrapping in help formatter
- Added better error reporting on invalid arguments
## [0.1.0] - 2012-05-16
### Added
- First release.
[2.0.1]: https://github.com/nodeca/argparse/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/nodeca/argparse/compare/1.0.10...2.0.0
[1.0.10]: https://github.com/nodeca/argparse/compare/1.0.9...1.0.10
[1.0.9]: https://github.com/nodeca/argparse/compare/1.0.8...1.0.9
[1.0.8]: https://github.com/nodeca/argparse/compare/1.0.7...1.0.8
[1.0.7]: https://github.com/nodeca/argparse/compare/1.0.6...1.0.7
[1.0.6]: https://github.com/nodeca/argparse/compare/1.0.5...1.0.6
[1.0.5]: https://github.com/nodeca/argparse/compare/1.0.4...1.0.5
[1.0.4]: https://github.com/nodeca/argparse/compare/1.0.3...1.0.4
[1.0.3]: https://github.com/nodeca/argparse/compare/1.0.2...1.0.3
[1.0.2]: https://github.com/nodeca/argparse/compare/1.0.1...1.0.2
[1.0.1]: https://github.com/nodeca/argparse/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/nodeca/argparse/compare/0.1.16...1.0.0
[0.1.16]: https://github.com/nodeca/argparse/compare/0.1.15...0.1.16
[0.1.15]: https://github.com/nodeca/argparse/compare/0.1.14...0.1.15
[0.1.14]: https://github.com/nodeca/argparse/compare/0.1.13...0.1.14
[0.1.13]: https://github.com/nodeca/argparse/compare/0.1.12...0.1.13
[0.1.12]: https://github.com/nodeca/argparse/compare/0.1.11...0.1.12
[0.1.11]: https://github.com/nodeca/argparse/compare/0.1.10...0.1.11
[0.1.10]: https://github.com/nodeca/argparse/compare/0.1.9...0.1.10
[0.1.9]: https://github.com/nodeca/argparse/compare/0.1.8...0.1.9
[0.1.8]: https://github.com/nodeca/argparse/compare/0.1.7...0.1.8
[0.1.7]: https://github.com/nodeca/argparse/compare/0.1.6...0.1.7
[0.1.6]: https://github.com/nodeca/argparse/compare/0.1.5...0.1.6
[0.1.5]: https://github.com/nodeca/argparse/compare/0.1.4...0.1.5
[0.1.4]: https://github.com/nodeca/argparse/compare/0.1.3...0.1.4
[0.1.3]: https://github.com/nodeca/argparse/compare/0.1.2...0.1.3
[0.1.2]: https://github.com/nodeca/argparse/compare/0.1.1...0.1.2
[0.1.1]: https://github.com/nodeca/argparse/compare/0.1.0...0.1.1
[0.1.0]: https://github.com/nodeca/argparse/releases/tag/0.1.0

@ -0,0 +1,254 @@
A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
year, the PythonLabs team moved to Digital Creations, which became
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
https://www.python.org/psf/) was formed, a non-profit organization
created specifically to own Python-related Intellectual Property.
Zope Corporation was a sponsoring member of the PSF.
All Python releases are Open Source (see http://www.opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
Release Derived Year Owner GPL-
from compatible? (1)
0.9.0 thru 1.2 1991-1995 CWI yes
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
1.6 1.5.2 2000 CNRI no
2.0 1.6 2000 BeOpen.com no
1.6.1 1.6 2001 CNRI yes (2)
2.1 2.0+1.6.1 2001 PSF no
2.0.1 2.0+1.6.1 2001 PSF yes
2.1.1 2.1+2.0.1 2001 PSF yes
2.1.2 2.1.1 2002 PSF yes
2.1.3 2.1.2 2002 PSF yes
2.2 and above 2.1.1 2001-now PSF yes
Footnotes:
(1) GPL-compatible doesn't mean that we're distributing Python under
the GPL. All Python licenses, unlike the GPL, let you distribute
a modified version without making your changes open source. The
GPL-compatible licenses make it possible to combine Python with
other software that is released under the GPL; the others don't.
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
because its license has a choice of law clause. According to
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
is "not incompatible" with the GPL.
Thanks to the many outside volunteers who have worked under Guido's
direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
-------------------------------------------
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
Individual or Organization ("Licensee") accessing and otherwise using
this software in source or binary form and its associated
documentation ("the Software").
2. Subject to the terms and conditions of this BeOpen Python License
Agreement, BeOpen hereby grants Licensee a non-exclusive,
royalty-free, world-wide license to reproduce, analyze, test, perform
and/or display publicly, prepare derivative works, distribute, and
otherwise use the Software alone or in any derivative version,
provided, however, that the BeOpen Python License is retained in the
Software, alone or in any derivative version prepared by Licensee.
3. BeOpen is making the Software available to Licensee on an "AS IS"
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
5. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
6. This License Agreement shall be governed by and interpreted in all
respects by the law of the State of California, excluding conflict of
law provisions. Nothing in this License Agreement shall be deemed to
create any relationship of agency, partnership, or joint venture
between BeOpen and Licensee. This License Agreement does not grant
permission to use BeOpen trademarks or trade names in a trademark
sense to endorse or promote products or services of Licensee, or any
third party. As an exception, the "BeOpen Python" logos available at
http://www.pythonlabs.com/logos.html may be used according to the
permissions granted on that web page.
7. By copying, installing or otherwise using the software, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
---------------------------------------
1. This LICENSE AGREEMENT is between the Corporation for National
Research Initiatives, having an office at 1895 Preston White Drive,
Reston, VA 20191 ("CNRI"), and the Individual or Organization
("Licensee") accessing and otherwise using Python 1.6.1 software in
source or binary form and its associated documentation.
2. Subject to the terms and conditions of this License Agreement, CNRI
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 1.6.1
alone or in any derivative version, provided, however, that CNRI's
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
1995-2001 Corporation for National Research Initiatives; All Rights
Reserved" are retained in Python 1.6.1 alone or in any derivative
version prepared by Licensee. Alternately, in lieu of CNRI's License
Agreement, Licensee may substitute the following text (omitting the
quotes): "Python 1.6.1 is made available subject to the terms and
conditions in CNRI's License Agreement. This Agreement together with
Python 1.6.1 may be located on the Internet using the following
unique, persistent identifier (known as a handle): 1895.22/1013. This
Agreement may also be obtained from a proxy server on the Internet
using the following URL: http://hdl.handle.net/1895.22/1013".
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 1.6.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 1.6.1.
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. This License Agreement shall be governed by the federal
intellectual property law of the United States, including without
limitation the federal copyright law, and, to the extent such
U.S. federal law does not apply, by the law of the Commonwealth of
Virginia, excluding Virginia's conflict of law provisions.
Notwithstanding the foregoing, with regard to derivative works based
on Python 1.6.1 that incorporate non-separable material that was
previously distributed under the GNU General Public License (GPL), the
law of the Commonwealth of Virginia shall govern this License
Agreement only as to issues arising under or with respect to
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
License Agreement shall be deemed to create any relationship of
agency, partnership, or joint venture between CNRI and Licensee. This
License Agreement does not grant permission to use CNRI trademarks or
trade name in a trademark sense to endorse or promote products or
services of Licensee, or any third party.
8. By clicking on the "ACCEPT" button where indicated, or by copying,
installing or otherwise using Python 1.6.1, Licensee agrees to be
bound by the terms and conditions of this License Agreement.
ACCEPT
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
--------------------------------------------------
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
The Netherlands. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

@ -0,0 +1,84 @@
argparse
========
[![Build Status](https://secure.travis-ci.org/nodeca/argparse.svg?branch=master)](http://travis-ci.org/nodeca/argparse)
[![NPM version](https://img.shields.io/npm/v/argparse.svg)](https://www.npmjs.org/package/argparse)
CLI arguments parser for node.js, with [sub-commands](https://docs.python.org/3.9/library/argparse.html#sub-commands) support. Port of python's [argparse](http://docs.python.org/dev/library/argparse.html) (version [3.9.0](https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py)).
**Difference with original.**
- JS has no keyword arguments support.
- Pass options instead: `new ArgumentParser({ description: 'example', add_help: true })`.
- JS has no python's types `int`, `float`, ...
- Use string-typed names: `.add_argument('-b', { type: 'int', help: 'help' })`.
- `%r` format specifier uses `require('util').inspect()`.
More details in [doc](./doc).
Example
-------
`test.js` file:
```javascript
#!/usr/bin/env node
'use strict';
const { ArgumentParser } = require('argparse');
const { version } = require('./package.json');
const parser = new ArgumentParser({
description: 'Argparse example'
});
parser.add_argument('-v', '--version', { action: 'version', version });
parser.add_argument('-f', '--foo', { help: 'foo bar' });
parser.add_argument('-b', '--bar', { help: 'bar foo' });
parser.add_argument('--baz', { help: 'baz bar' });
console.dir(parser.parse_args());
```
Display help:
```
$ ./test.js -h
usage: test.js [-h] [-v] [-f FOO] [-b BAR] [--baz BAZ]
Argparse example
optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-f FOO, --foo FOO foo bar
-b BAR, --bar BAR bar foo
--baz BAZ baz bar
```
Parse arguments:
```
$ ./test.js -f=3 --bar=4 --baz 5
{ foo: '3', bar: '4', baz: '5' }
```
API docs
--------
Since this is a port with minimal divergence, there's no separate documentation.
Use original one instead, with notes about difference.
1. [Original doc](https://docs.python.org/3.9/library/argparse.html).
2. [Original tutorial](https://docs.python.org/3.9/howto/argparse.html).
3. [Difference with python](./doc).
argparse for enterprise
-----------------------
Available as part of the Tidelift Subscription
The maintainers of argparse and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-argparse?utm_source=npm-argparse&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,67 @@
// Limited implementation of python % string operator, supports only %s and %r for now
// (other formats are not used here, but may appear in custom templates)
'use strict'
const { inspect } = require('util')
module.exports = function sub(pattern, ...values) {
let regex = /%(?:(%)|(-)?(\*)?(?:\((\w+)\))?([A-Za-z]))/g
let result = pattern.replace(regex, function (_, is_literal, is_left_align, is_padded, name, format) {
if (is_literal) return '%'
let padded_count = 0
if (is_padded) {
if (values.length === 0) throw new TypeError('not enough arguments for format string')
padded_count = values.shift()
if (!Number.isInteger(padded_count)) throw new TypeError('* wants int')
}
let str
if (name !== undefined) {
let dict = values[0]
if (typeof dict !== 'object' || dict === null) throw new TypeError('format requires a mapping')
if (!(name in dict)) throw new TypeError(`no such key: '${name}'`)
str = dict[name]
} else {
if (values.length === 0) throw new TypeError('not enough arguments for format string')
str = values.shift()
}
switch (format) {
case 's':
str = String(str)
break
case 'r':
str = inspect(str)
break
case 'd':
case 'i':
if (typeof str !== 'number') {
throw new TypeError(`%${format} format: a number is required, not ${typeof str}`)
}
str = String(str.toFixed(0))
break
default:
throw new TypeError(`unsupported format character '${format}'`)
}
if (padded_count > 0) {
return is_left_align ? str.padEnd(padded_count) : str.padStart(padded_count)
} else {
return str
}
})
if (values.length) {
if (values.length === 1 && typeof values[0] === 'object' && values[0] !== null) {
// mapping
} else {
throw new TypeError('not all arguments converted during string formatting')
}
}
return result
}

@ -0,0 +1,440 @@
// Partial port of python's argparse module, version 3.9.0 (only wrap and fill functions):
// https://github.com/python/cpython/blob/v3.9.0b4/Lib/textwrap.py
'use strict'
/*
* Text wrapping and filling.
*/
// Copyright (C) 1999-2001 Gregory P. Ward.
// Copyright (C) 2002, 2003 Python Software Foundation.
// Copyright (C) 2020 argparse.js authors
// Originally written by Greg Ward <gward@python.net>
// Hardcode the recognized whitespace characters to the US-ASCII
// whitespace characters. The main reason for doing this is that
// some Unicode spaces (like \u00a0) are non-breaking whitespaces.
//
// This less funky little regex just split on recognized spaces. E.g.
// "Hello there -- you goof-ball, use the -b option!"
// splits into
// Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/
const wordsep_simple_re = /([\t\n\x0b\x0c\r ]+)/
class TextWrapper {
/*
* Object for wrapping/filling text. The public interface consists of
* the wrap() and fill() methods; the other methods are just there for
* subclasses to override in order to tweak the default behaviour.
* If you want to completely replace the main wrapping algorithm,
* you'll probably have to override _wrap_chunks().
*
* Several instance attributes control various aspects of wrapping:
* width (default: 70)
* the maximum width of wrapped lines (unless break_long_words
* is false)
* initial_indent (default: "")
* string that will be prepended to the first line of wrapped
* output. Counts towards the line's width.
* subsequent_indent (default: "")
* string that will be prepended to all lines save the first
* of wrapped output; also counts towards each line's width.
* expand_tabs (default: true)
* Expand tabs in input text to spaces before further processing.
* Each tab will become 0 .. 'tabsize' spaces, depending on its position
* in its line. If false, each tab is treated as a single character.
* tabsize (default: 8)
* Expand tabs in input text to 0 .. 'tabsize' spaces, unless
* 'expand_tabs' is false.
* replace_whitespace (default: true)
* Replace all whitespace characters in the input text by spaces
* after tab expansion. Note that if expand_tabs is false and
* replace_whitespace is true, every tab will be converted to a
* single space!
* fix_sentence_endings (default: false)
* Ensure that sentence-ending punctuation is always followed
* by two spaces. Off by default because the algorithm is
* (unavoidably) imperfect.
* break_long_words (default: true)
* Break words longer than 'width'. If false, those words will not
* be broken, and some lines might be longer than 'width'.
* break_on_hyphens (default: true)
* Allow breaking hyphenated words. If true, wrapping will occur
* preferably on whitespaces and right after hyphens part of
* compound words.
* drop_whitespace (default: true)
* Drop leading and trailing whitespace from lines.
* max_lines (default: None)
* Truncate wrapped lines.
* placeholder (default: ' [...]')
* Append to the last line of truncated text.
*/
constructor(options = {}) {
let {
width = 70,
initial_indent = '',
subsequent_indent = '',
expand_tabs = true,
replace_whitespace = true,
fix_sentence_endings = false,
break_long_words = true,
drop_whitespace = true,
break_on_hyphens = true,
tabsize = 8,
max_lines = undefined,
placeholder=' [...]'
} = options
this.width = width
this.initial_indent = initial_indent
this.subsequent_indent = subsequent_indent
this.expand_tabs = expand_tabs
this.replace_whitespace = replace_whitespace
this.fix_sentence_endings = fix_sentence_endings
this.break_long_words = break_long_words
this.drop_whitespace = drop_whitespace
this.break_on_hyphens = break_on_hyphens
this.tabsize = tabsize
this.max_lines = max_lines
this.placeholder = placeholder
}
// -- Private methods -----------------------------------------------
// (possibly useful for subclasses to override)
_munge_whitespace(text) {
/*
* _munge_whitespace(text : string) -> string
*
* Munge whitespace in text: expand tabs and convert all other
* whitespace characters to spaces. Eg. " foo\\tbar\\n\\nbaz"
* becomes " foo bar baz".
*/
if (this.expand_tabs) {
text = text.replace(/\t/g, ' '.repeat(this.tabsize)) // not strictly correct in js
}
if (this.replace_whitespace) {
text = text.replace(/[\t\n\x0b\x0c\r]/g, ' ')
}
return text
}
_split(text) {
/*
* _split(text : string) -> [string]
*
* Split the text to wrap into indivisible chunks. Chunks are
* not quite the same as words; see _wrap_chunks() for full
* details. As an example, the text
* Look, goof-ball -- use the -b option!
* breaks into the following chunks:
* 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ',
* 'use', ' ', 'the', ' ', '-b', ' ', 'option!'
* if break_on_hyphens is True, or in:
* 'Look,', ' ', 'goof-ball', ' ', '--', ' ',
* 'use', ' ', 'the', ' ', '-b', ' ', option!'
* otherwise.
*/
let chunks = text.split(wordsep_simple_re)
chunks = chunks.filter(Boolean)
return chunks
}
_handle_long_word(reversed_chunks, cur_line, cur_len, width) {
/*
* _handle_long_word(chunks : [string],
* cur_line : [string],
* cur_len : int, width : int)
*
* Handle a chunk of text (most likely a word, not whitespace) that
* is too long to fit in any line.
*/
// Figure out when indent is larger than the specified width, and make
// sure at least one character is stripped off on every pass
let space_left
if (width < 1) {
space_left = 1
} else {
space_left = width - cur_len
}
// If we're allowed to break long words, then do so: put as much
// of the next chunk onto the current line as will fit.
if (this.break_long_words) {
cur_line.push(reversed_chunks[reversed_chunks.length - 1].slice(0, space_left))
reversed_chunks[reversed_chunks.length - 1] = reversed_chunks[reversed_chunks.length - 1].slice(space_left)
// Otherwise, we have to preserve the long word intact. Only add
// it to the current line if there's nothing already there --
// that minimizes how much we violate the width constraint.
} else if (!cur_line) {
cur_line.push(...reversed_chunks.pop())
}
// If we're not allowed to break long words, and there's already
// text on the current line, do nothing. Next time through the
// main loop of _wrap_chunks(), we'll wind up here again, but
// cur_len will be zero, so the next line will be entirely
// devoted to the long word that we can't handle right now.
}
_wrap_chunks(chunks) {
/*
* _wrap_chunks(chunks : [string]) -> [string]
*
* Wrap a sequence of text chunks and return a list of lines of
* length 'self.width' or less. (If 'break_long_words' is false,
* some lines may be longer than this.) Chunks correspond roughly
* to words and the whitespace between them: each chunk is
* indivisible (modulo 'break_long_words'), but a line break can
* come between any two chunks. Chunks should not have internal
* whitespace; ie. a chunk is either all whitespace or a "word".
* Whitespace chunks will be removed from the beginning and end of
* lines, but apart from that whitespace is preserved.
*/
let lines = []
let indent
if (this.width <= 0) {
throw Error(`invalid width ${this.width} (must be > 0)`)
}
if (this.max_lines !== undefined) {
if (this.max_lines > 1) {
indent = this.subsequent_indent
} else {
indent = this.initial_indent
}
if (indent.length + this.placeholder.trimStart().length > this.width) {
throw Error('placeholder too large for max width')
}
}
// Arrange in reverse order so items can be efficiently popped
// from a stack of chucks.
chunks = chunks.reverse()
while (chunks.length > 0) {
// Start the list of chunks that will make up the current line.
// cur_len is just the length of all the chunks in cur_line.
let cur_line = []
let cur_len = 0
// Figure out which static string will prefix this line.
let indent
if (lines) {
indent = this.subsequent_indent
} else {
indent = this.initial_indent
}
// Maximum width for this line.
let width = this.width - indent.length
// First chunk on line is whitespace -- drop it, unless this
// is the very beginning of the text (ie. no lines started yet).
if (this.drop_whitespace && chunks[chunks.length - 1].trim() === '' && lines.length > 0) {
chunks.pop()
}
while (chunks.length > 0) {
let l = chunks[chunks.length - 1].length
// Can at least squeeze this chunk onto the current line.
if (cur_len + l <= width) {
cur_line.push(chunks.pop())
cur_len += l
// Nope, this line is full.
} else {
break
}
}
// The current line is full, and the next chunk is too big to
// fit on *any* line (not just this one).
if (chunks.length && chunks[chunks.length - 1].length > width) {
this._handle_long_word(chunks, cur_line, cur_len, width)
cur_len = cur_line.map(l => l.length).reduce((a, b) => a + b, 0)
}
// If the last chunk on this line is all whitespace, drop it.
if (this.drop_whitespace && cur_line.length > 0 && cur_line[cur_line.length - 1].trim() === '') {
cur_len -= cur_line[cur_line.length - 1].length
cur_line.pop()
}
if (cur_line) {
if (this.max_lines === undefined ||
lines.length + 1 < this.max_lines ||
(chunks.length === 0 ||
this.drop_whitespace &&
chunks.length === 1 &&
!chunks[0].trim()) && cur_len <= width) {
// Convert current line back to a string and store it in
// list of all lines (return value).
lines.push(indent + cur_line.join(''))
} else {
let had_break = false
while (cur_line) {
if (cur_line[cur_line.length - 1].trim() &&
cur_len + this.placeholder.length <= width) {
cur_line.push(this.placeholder)
lines.push(indent + cur_line.join(''))
had_break = true
break
}
cur_len -= cur_line[-1].length
cur_line.pop()
}
if (!had_break) {
if (lines) {
let prev_line = lines[lines.length - 1].trimEnd()
if (prev_line.length + this.placeholder.length <=
this.width) {
lines[lines.length - 1] = prev_line + this.placeholder
break
}
}
lines.push(indent + this.placeholder.lstrip())
}
break
}
}
}
return lines
}
_split_chunks(text) {
text = this._munge_whitespace(text)
return this._split(text)
}
// -- Public interface ----------------------------------------------
wrap(text) {
/*
* wrap(text : string) -> [string]
*
* Reformat the single paragraph in 'text' so it fits in lines of
* no more than 'self.width' columns, and return a list of wrapped
* lines. Tabs in 'text' are expanded with string.expandtabs(),
* and all other whitespace characters (including newline) are
* converted to space.
*/
let chunks = this._split_chunks(text)
// not implemented in js
//if (this.fix_sentence_endings) {
// this._fix_sentence_endings(chunks)
//}
return this._wrap_chunks(chunks)
}
fill(text) {
/*
* fill(text : string) -> string
*
* Reformat the single paragraph in 'text' to fit in lines of no
* more than 'self.width' columns, and return a new string
* containing the entire wrapped paragraph.
*/
return this.wrap(text).join('\n')
}
}
// -- Convenience interface ---------------------------------------------
function wrap(text, options = {}) {
/*
* Wrap a single paragraph of text, returning a list of wrapped lines.
*
* Reformat the single paragraph in 'text' so it fits in lines of no
* more than 'width' columns, and return a list of wrapped lines. By
* default, tabs in 'text' are expanded with string.expandtabs(), and
* all other whitespace characters (including newline) are converted to
* space. See TextWrapper class for available keyword args to customize
* wrapping behaviour.
*/
let { width = 70, ...kwargs } = options
let w = new TextWrapper(Object.assign({ width }, kwargs))
return w.wrap(text)
}
function fill(text, options = {}) {
/*
* Fill a single paragraph of text, returning a new string.
*
* Reformat the single paragraph in 'text' to fit in lines of no more
* than 'width' columns, and return a new string containing the entire
* wrapped paragraph. As with wrap(), tabs are expanded and other
* whitespace characters converted to space. See TextWrapper class for
* available keyword args to customize wrapping behaviour.
*/
let { width = 70, ...kwargs } = options
let w = new TextWrapper(Object.assign({ width }, kwargs))
return w.fill(text)
}
// -- Loosely related functionality -------------------------------------
let _whitespace_only_re = /^[ \t]+$/mg
let _leading_whitespace_re = /(^[ \t]*)(?:[^ \t\n])/mg
function dedent(text) {
/*
* Remove any common leading whitespace from every line in `text`.
*
* This can be used to make triple-quoted strings line up with the left
* edge of the display, while still presenting them in the source code
* in indented form.
*
* Note that tabs and spaces are both treated as whitespace, but they
* are not equal: the lines " hello" and "\\thello" are
* considered to have no common leading whitespace.
*
* Entirely blank lines are normalized to a newline character.
*/
// Look for the longest leading string of spaces and tabs common to
// all lines.
let margin = undefined
text = text.replace(_whitespace_only_re, '')
let indents = text.match(_leading_whitespace_re) || []
for (let indent of indents) {
indent = indent.slice(0, -1)
if (margin === undefined) {
margin = indent
// Current line more deeply indented than previous winner:
// no change (previous winner is still on top).
} else if (indent.startsWith(margin)) {
// pass
// Current line consistent with and no deeper than previous winner:
// it's the new winner.
} else if (margin.startsWith(indent)) {
margin = indent
// Find the largest common whitespace between current line and previous
// winner.
} else {
for (let i = 0; i < margin.length && i < indent.length; i++) {
if (margin[i] !== indent[i]) {
margin = margin.slice(0, i)
break
}
}
}
}
if (margin) {
text = text.replace(new RegExp('^' + margin, 'mg'), '')
}
return text
}
module.exports = { wrap, fill, dedent }

@ -0,0 +1,67 @@
{
"_args": [
[
"argparse@2.0.1",
"/home/vitaly/Dropbox/Coding/lv_font_conv"
]
],
"_from": "argparse@2.0.1",
"_id": "argparse@2.0.1",
"_inBundle": false,
"_integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"_location": "/argparse",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "argparse@2.0.1",
"name": "argparse",
"escapedName": "argparse",
"rawSpec": "2.0.1",
"saveSpec": null,
"fetchSpec": "2.0.1"
},
"_requiredBy": [
"/",
"/mocha/js-yaml"
],
"_resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"_spec": "2.0.1",
"_where": "/home/vitaly/Dropbox/Coding/lv_font_conv",
"bugs": {
"url": "https://github.com/nodeca/argparse/issues"
},
"description": "CLI arguments parser. Native port of python's argparse.",
"devDependencies": {
"@babel/eslint-parser": "^7.11.0",
"@babel/plugin-syntax-class-properties": "^7.10.4",
"eslint": "^7.5.0",
"mocha": "^8.0.1",
"nyc": "^15.1.0"
},
"files": [
"argparse.js",
"lib/"
],
"homepage": "https://github.com/nodeca/argparse#readme",
"keywords": [
"cli",
"parser",
"argparse",
"option",
"args"
],
"license": "Python-2.0",
"main": "argparse.js",
"name": "argparse",
"repository": {
"type": "git",
"url": "git+https://github.com/nodeca/argparse.git"
},
"scripts": {
"coverage": "npm run test && nyc report --reporter html",
"lint": "eslint .",
"test": "npm run lint && nyc mocha"
},
"version": "2.0.1"
}

@ -0,0 +1,36 @@
name: Node.js CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 15.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
lint:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 15.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run lint

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 bit-buffer developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,148 @@
# BitBuffer
![Node.js CI](https://github.com/inolen/bit-buffer/workflows/Node.js%20CI/badge.svg)
BitBuffer provides two objects, `BitView` and `BitStream`. `BitView` is a wrapper for ArrayBuffers, similar to JavaScript's [DataView](https://developer.mozilla.org/en-US/docs/JavaScript/Typed_arrays/DataView), but with support for bit-level reads and writes. `BitStream` is a wrapper for a `BitView` used to help maintain your current buffer position, as well as to provide higher-level read / write operations such as for ASCII strings.
## BitView
### Attributes
```javascript
bb.buffer // Underlying ArrayBuffer.
```
```javascript
bb.bigEndian = true; // Switch to big endian (default is little)
```
### Methods
#### BitView(buffer, optional byteOffset, optional byteLength)
Default constructor, takes in a single argument of an ArrayBuffer. Optional are the `byteOffset` and `byteLength` arguments to offset and truncate the view's representation of the buffer.
### getBits(offset, bits, signed)
Reads `bits` number of bits starting at `offset`, twiddling the bits appropriately to return a proper 32-bit signed or unsigned value. NOTE: While JavaScript numbers are 64-bit floating-point values, we don't bother with anything other than the first 32 bits.
### getInt8, getUint8, getInt16, getUint16, getInt32, getUint32(offset)
Shortcuts for getBits, setting the correct `bits` / `signed` values.
### getFloat32(offset)
Gets 32 bits from `offset`, and coerces and returns as a proper float32 value.
### getFloat64(offset)
Gets 64 bits from `offset`, and coerces and returns as a proper float64 value.
### setBits(offset, value, bits)
Sets `bits` number of bits at `offset`.
### setInt8, setUint8, setInt16, setUint16, setInt32, setUint32(offset)
Shortcuts for setBits, setting the correct `bits` count.
### setFloat32(offset)
Coerces a float32 to uint32 and sets at `offset`.
### setFloat64(offset)
Coerces a float64 to two uint32s and sets at `offset`.
## BitStream
### Attributes
```javascript
bb.byteIndex; // Get current index in bytes.
bb.byteIndex = 0; // Set current index in bytes.
```
```javascript
bb.view; // Underlying BitView
```
```javascript
bb.length; // Get the length of the stream in bits
```
```javascript
bb.bitsLeft; // The number of bits left in the stream
```
```javascript
bb.index; // Get the current index in bits
bb.index = 0// Set the current index in bits
```
```javascript
bb.bigEndian = true; // Switch to big endian (default is little)
```
### Methods
#### BitStream(view)
Default constructor, takes in a single argument of a `BitView`, `ArrayBuffer` or node `Buffer`.
#### BitSteam(buffer, optional byteOffset, optional byteLength)
Shortcut constructor that initializes a new `BitView(buffer, byteOffset, byteLength)` for the stream to use.
#### readBits(bits, signed)
Returns `bits` numbers of bits from the view at the current index, updating the index.
#### writeBits(value, bits)
Sets `bits` numbers of bits from `value` in the view at the current index, updating the index.
#### readUint8(), readUint16(), readUint32(), readInt8(), readInt16(), readInt32()
Read a 8, 16 or 32 bits (unsigned) integer at the current index, updating the index.
#### writeUint8(value), writeUint16(value), writeUint32(value), writeInt8(value), writeInt16(value), writeInt32(value)
Write 8, 16 or 32 bits from `value` as (unsigned) integer at the current index, updating the index.
#### readFloat32(), readFloat64()
Read a 32 or 64 bit floating point number at the current index, updating the index.
#### writeFloat32(value), writeFloat64()
Set 32 or 64 bits from `value` as floating point value at the current index, updating the index.
#### readBoolean()
Read a single bit from the view at the current index, updating the index.
#### writeBoolean(value)
Write a single bit to the view at the current index, updating the index.
#### readASCIIString(optional bytes), readUTF8String(optional bytes)
Reads bytes from the underlying view at the current index until either `bytes` count is reached or a 0x00 terminator is reached.
#### writeASCIIString(string, optional bytes), writeUTF8String(string, optional bytes)
Writes a string followed by a NULL character to the underlying view starting at the current index. If the string is longer than `bytes` it will be truncated, and if it is shorter 0x00 will be written in its place.
#### readBitStream(length)
Create a new `BitStream` from the underlying view starting the the current index and a length of `length` bits. Updating the index of the existing `BitStream`
#### readArrayBuffer(byteLength)
Read `byteLength` bytes of data from the underlying view as `ArrayBuffer`, updating the index.
## license
MIT

@ -0,0 +1,115 @@
declare module 'bit-buffer' {
import {Buffer} from 'buffer';
export class BitView {
constructor(buffer: ArrayBuffer | Buffer, byteLength?: number);
readonly buffer: Buffer;
readonly byteLength: number;
bigEndian: boolean;
getBits(offset: number, bits: number, signed?: boolean): number;
getInt8(offset: number): number;
getInt16(offset: number): number;
getInt32(offset: number): number;
getUint8(offset: number): number;
getUint16(offset: number): number;
getUint32(offset: number): number;
getFloat32(offset: number): number;
getFloat64(offset: number): number;
setBits(offset: number, value: number, bits: number);
setInt8(offset: number);
setInt16(offset: number);
setInt32(offset: number);
setUint8(offset: number);
setUint16(offset: number);
setUint32(offset: number);
setFloat32(offset: number, value: number);
setFloat64(offset: number, value: number);
}
export class BitStream {
constructor(source: ArrayBuffer | Buffer | BitView, byteOffset?: number, byteLength?: number)
readonly length: number;
readonly bitsLeft: number;
readonly buffer: Buffer;
readonly view: BitView;
byteIndex: number;
index: number;
bigEndian: boolean;
readBits(bits: number, signed?: boolean): number;
writeBits(value: number, bits: number);
readBoolean(): boolean;
readInt8(): number;
readUint8(): number;
readInt16(): number;
readUint16(): number;
readInt32(): number;
readUint32(): number;
readFloat32(): number;
readFloat64(): number;
writeBoolean(value: boolean);
writeInt8(value: number);
writeUint8(value: number);
writeInt16(value: number);
writeUint16(value: number);
writeInt32(value: number);
writeUint32(value: number);
writeFloat32(value: number);
writeFloat64(value: number);
readASCIIString(length?: number): string;
readUTF8String(length?: number): string;
writeASCIIString(data: string, length?: number);
writeUTF8String(data: string, length?: number);
readBitStream(length: number): BitStream;
readArrayBuffer(byteLength: number): Uint8Array;
writeBitStream(stream: BitStream, length?: number);
writeArrayBuffer(buffer: BitStream, length?: number);
}
}

@ -0,0 +1,502 @@
(function (root) {
/**********************************************************
*
* BitView
*
* BitView provides a similar interface to the standard
* DataView, but with support for bit-level reads / writes.
*
**********************************************************/
var BitView = function (source, byteOffset, byteLength) {
var isBuffer = source instanceof ArrayBuffer ||
(typeof Buffer !== 'undefined' && source instanceof Buffer);
if (!isBuffer) {
throw new Error('Must specify a valid ArrayBuffer or Buffer.');
}
byteOffset = byteOffset || 0;
byteLength = byteLength || source.byteLength /* ArrayBuffer */ || source.length /* Buffer */;
this._view = new Uint8Array(source.buffer || source, byteOffset, byteLength);
this.bigEndian = false;
};
// Used to massage fp values so we can operate on them
// at the bit level.
BitView._scratch = new DataView(new ArrayBuffer(8));
Object.defineProperty(BitView.prototype, 'buffer', {
get: function () { return typeof Buffer !== 'undefined' ? Buffer.from(this._view.buffer) : this._view.buffer; },
enumerable: true,
configurable: false
});
Object.defineProperty(BitView.prototype, 'byteLength', {
get: function () { return this._view.length; },
enumerable: true,
configurable: false
});
BitView.prototype._setBit = function (offset, on) {
if (on) {
this._view[offset >> 3] |= 1 << (offset & 7);
} else {
this._view[offset >> 3] &= ~(1 << (offset & 7));
}
};
BitView.prototype.getBits = function (offset, bits, signed) {
var available = (this._view.length * 8 - offset);
if (bits > available) {
throw new Error('Cannot get ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available');
}
var value = 0;
for (var i = 0; i < bits;) {
var remaining = bits - i;
var bitOffset = offset & 7;
var currentByte = this._view[offset >> 3];
// the max number of bits we can read from the current byte
var read = Math.min(remaining, 8 - bitOffset);
var mask, readBits;
if (this.bigEndian) {
// create a mask with the correct bit width
mask = ~(0xFF << read);
// shift the bits we want to the start of the byte and mask of the rest
readBits = (currentByte >> (8 - read - bitOffset)) & mask;
value <<= read;
value |= readBits;
} else {
// create a mask with the correct bit width
mask = ~(0xFF << read);
// shift the bits we want to the start of the byte and mask off the rest
readBits = (currentByte >> bitOffset) & mask;
value |= readBits << i;
}
offset += read;
i += read;
}
if (signed) {
// If we're not working with a full 32 bits, check the
// imaginary MSB for this bit count and convert to a
// valid 32-bit signed value if set.
if (bits !== 32 && value & (1 << (bits - 1))) {
value |= -1 ^ ((1 << bits) - 1);
}
return value;
}
return value >>> 0;
};
BitView.prototype.setBits = function (offset, value, bits) {
var available = (this._view.length * 8 - offset);
if (bits > available) {
throw new Error('Cannot set ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available');
}
for (var i = 0; i < bits;) {
var remaining = bits - i;
var bitOffset = offset & 7;
var byteOffset = offset >> 3;
var wrote = Math.min(remaining, 8 - bitOffset);
var mask, writeBits, destMask;
if (this.bigEndian) {
// create a mask with the correct bit width
mask = ~(~0 << wrote);
// shift the bits we want to the start of the byte and mask of the rest
writeBits = (value >> (bits - i - wrote)) & mask;
var destShift = 8 - bitOffset - wrote;
// destination mask to zero all the bits we're changing first
destMask = ~(mask << destShift);
this._view[byteOffset] =
(this._view[byteOffset] & destMask)
| (writeBits << destShift);
} else {
// create a mask with the correct bit width
mask = ~(0xFF << wrote);
// shift the bits we want to the start of the byte and mask of the rest
writeBits = value & mask;
value >>= wrote;
// destination mask to zero all the bits we're changing first
destMask = ~(mask << bitOffset);
this._view[byteOffset] =
(this._view[byteOffset] & destMask)
| (writeBits << bitOffset);
}
offset += wrote;
i += wrote;
}
};
BitView.prototype.getBoolean = function (offset) {
return this.getBits(offset, 1, false) !== 0;
};
BitView.prototype.getInt8 = function (offset) {
return this.getBits(offset, 8, true);
};
BitView.prototype.getUint8 = function (offset) {
return this.getBits(offset, 8, false);
};
BitView.prototype.getInt16 = function (offset) {
return this.getBits(offset, 16, true);
};
BitView.prototype.getUint16 = function (offset) {
return this.getBits(offset, 16, false);
};
BitView.prototype.getInt32 = function (offset) {
return this.getBits(offset, 32, true);
};
BitView.prototype.getUint32 = function (offset) {
return this.getBits(offset, 32, false);
};
BitView.prototype.getFloat32 = function (offset) {
BitView._scratch.setUint32(0, this.getUint32(offset));
return BitView._scratch.getFloat32(0);
};
BitView.prototype.getFloat64 = function (offset) {
BitView._scratch.setUint32(0, this.getUint32(offset));
// DataView offset is in bytes.
BitView._scratch.setUint32(4, this.getUint32(offset+32));
return BitView._scratch.getFloat64(0);
};
BitView.prototype.setBoolean = function (offset, value) {
this.setBits(offset, value ? 1 : 0, 1);
};
BitView.prototype.setInt8 =
BitView.prototype.setUint8 = function (offset, value) {
this.setBits(offset, value, 8);
};
BitView.prototype.setInt16 =
BitView.prototype.setUint16 = function (offset, value) {
this.setBits(offset, value, 16);
};
BitView.prototype.setInt32 =
BitView.prototype.setUint32 = function (offset, value) {
this.setBits(offset, value, 32);
};
BitView.prototype.setFloat32 = function (offset, value) {
BitView._scratch.setFloat32(0, value);
this.setBits(offset, BitView._scratch.getUint32(0), 32);
};
BitView.prototype.setFloat64 = function (offset, value) {
BitView._scratch.setFloat64(0, value);
this.setBits(offset, BitView._scratch.getUint32(0), 32);
this.setBits(offset+32, BitView._scratch.getUint32(4), 32);
};
BitView.prototype.getArrayBuffer = function (offset, byteLength) {
var buffer = new Uint8Array(byteLength);
for (var i = 0; i < byteLength; i++) {
buffer[i] = this.getUint8(offset + (i * 8));
}
return buffer;
};
/**********************************************************
*
* BitStream
*
* Small wrapper for a BitView to maintain your position,
* as well as to handle reading / writing of string data
* to the underlying buffer.
*
**********************************************************/
var reader = function (name, size) {
return function () {
if (this._index + size > this._length) {
throw new Error('Trying to read past the end of the stream');
}
var val = this._view[name](this._index);
this._index += size;
return val;
};
};
var writer = function (name, size) {
return function (value) {
this._view[name](this._index, value);
this._index += size;
};
};
function readASCIIString(stream, bytes) {
return readString(stream, bytes, false);
}
function readUTF8String(stream, bytes) {
return readString(stream, bytes, true);
}
function readString(stream, bytes, utf8) {
if (bytes === 0) {
return '';
}
var i = 0;
var chars = [];
var append = true;
var fixedLength = !!bytes;
if (!bytes) {
bytes = Math.floor((stream._length - stream._index) / 8);
}
// Read while we still have space available, or until we've
// hit the fixed byte length passed in.
while (i < bytes) {
var c = stream.readUint8();
// Stop appending chars once we hit 0x00
if (c === 0x00) {
append = false;
// If we don't have a fixed length to read, break out now.
if (!fixedLength) {
break;
}
}
if (append) {
chars.push(c);
}
i++;
}
var string = String.fromCharCode.apply(null, chars);
if (utf8) {
try {
return decodeURIComponent(escape(string)); // https://stackoverflow.com/a/17192845
} catch (e) {
return string;
}
} else {
return string;
}
}
function writeASCIIString(stream, string, bytes) {
var length = bytes || string.length + 1; // + 1 for NULL
for (var i = 0; i < length; i++) {
stream.writeUint8(i < string.length ? string.charCodeAt(i) : 0x00);
}
}
function writeUTF8String(stream, string, bytes) {
var byteArray = stringToByteArray(string);
var length = bytes || byteArray.length + 1; // + 1 for NULL
for (var i = 0; i < length; i++) {
stream.writeUint8(i < byteArray.length ? byteArray[i] : 0x00);
}
}
function stringToByteArray(str) { // https://gist.github.com/volodymyr-mykhailyk/2923227
var b = [], i, unicode;
for (i = 0; i < str.length; i++) {
unicode = str.charCodeAt(i);
// 0x00000000 - 0x0000007f -> 0xxxxxxx
if (unicode <= 0x7f) {
b.push(unicode);
// 0x00000080 - 0x000007ff -> 110xxxxx 10xxxxxx
} else if (unicode <= 0x7ff) {
b.push((unicode >> 6) | 0xc0);
b.push((unicode & 0x3F) | 0x80);
// 0x00000800 - 0x0000ffff -> 1110xxxx 10xxxxxx 10xxxxxx
} else if (unicode <= 0xffff) {
b.push((unicode >> 12) | 0xe0);
b.push(((unicode >> 6) & 0x3f) | 0x80);
b.push((unicode & 0x3f) | 0x80);
// 0x00010000 - 0x001fffff -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
} else {
b.push((unicode >> 18) | 0xf0);
b.push(((unicode >> 12) & 0x3f) | 0x80);
b.push(((unicode >> 6) & 0x3f) | 0x80);
b.push((unicode & 0x3f) | 0x80);
}
}
return b;
}
var BitStream = function (source, byteOffset, byteLength) {
var isBuffer = source instanceof ArrayBuffer ||
(typeof Buffer !== 'undefined' && source instanceof Buffer);
if (!(source instanceof BitView) && !isBuffer) {
throw new Error('Must specify a valid BitView, ArrayBuffer or Buffer');
}
if (isBuffer) {
this._view = new BitView(source, byteOffset, byteLength);
} else {
this._view = source;
}
this._index = 0;
this._startIndex = 0;
this._length = this._view.byteLength * 8;
};
Object.defineProperty(BitStream.prototype, 'index', {
get: function () { return this._index - this._startIndex; },
set: function (val) { this._index = val + this._startIndex; },
enumerable: true,
configurable: true
});
Object.defineProperty(BitStream.prototype, 'length', {
get: function () { return this._length - this._startIndex; },
set: function (val) { this._length = val + this._startIndex; },
enumerable : true,
configurable: true
});
Object.defineProperty(BitStream.prototype, 'bitsLeft', {
get: function () { return this._length - this._index; },
enumerable : true,
configurable: true
});
Object.defineProperty(BitStream.prototype, 'byteIndex', {
// Ceil the returned value, over compensating for the amount of
// bits written to the stream.
get: function () { return Math.ceil(this._index / 8); },
set: function (val) { this._index = val * 8; },
enumerable: true,
configurable: true
});
Object.defineProperty(BitStream.prototype, 'buffer', {
get: function () { return this._view.buffer; },
enumerable: true,
configurable: false
});
Object.defineProperty(BitStream.prototype, 'view', {
get: function () { return this._view; },
enumerable: true,
configurable: false
});
Object.defineProperty(BitStream.prototype, 'bigEndian', {
get: function () { return this._view.bigEndian; },
set: function (val) { this._view.bigEndian = val; },
enumerable: true,
configurable: false
});
BitStream.prototype.readBits = function (bits, signed) {
var val = this._view.getBits(this._index, bits, signed);
this._index += bits;
return val;
};
BitStream.prototype.writeBits = function (value, bits) {
this._view.setBits(this._index, value, bits);
this._index += bits;
};
BitStream.prototype.readBoolean = reader('getBoolean', 1);
BitStream.prototype.readInt8 = reader('getInt8', 8);
BitStream.prototype.readUint8 = reader('getUint8', 8);
BitStream.prototype.readInt16 = reader('getInt16', 16);
BitStream.prototype.readUint16 = reader('getUint16', 16);
BitStream.prototype.readInt32 = reader('getInt32', 32);
BitStream.prototype.readUint32 = reader('getUint32', 32);
BitStream.prototype.readFloat32 = reader('getFloat32', 32);
BitStream.prototype.readFloat64 = reader('getFloat64', 64);
BitStream.prototype.writeBoolean = writer('setBoolean', 1);
BitStream.prototype.writeInt8 = writer('setInt8', 8);
BitStream.prototype.writeUint8 = writer('setUint8', 8);
BitStream.prototype.writeInt16 = writer('setInt16', 16);
BitStream.prototype.writeUint16 = writer('setUint16', 16);
BitStream.prototype.writeInt32 = writer('setInt32', 32);
BitStream.prototype.writeUint32 = writer('setUint32', 32);
BitStream.prototype.writeFloat32 = writer('setFloat32', 32);
BitStream.prototype.writeFloat64 = writer('setFloat64', 64);
BitStream.prototype.readASCIIString = function (bytes) {
return readASCIIString(this, bytes);
};
BitStream.prototype.readUTF8String = function (bytes) {
return readUTF8String(this, bytes);
};
BitStream.prototype.writeASCIIString = function (string, bytes) {
writeASCIIString(this, string, bytes);
};
BitStream.prototype.writeUTF8String = function (string, bytes) {
writeUTF8String(this, string, bytes);
};
BitStream.prototype.readBitStream = function(bitLength) {
var slice = new BitStream(this._view);
slice._startIndex = this._index;
slice._index = this._index;
slice.length = bitLength;
this._index += bitLength;
return slice;
};
BitStream.prototype.writeBitStream = function(stream, length) {
if (!length) {
length = stream.bitsLeft;
}
var bitsToWrite;
while (length > 0) {
bitsToWrite = Math.min(length, 32);
this.writeBits(stream.readBits(bitsToWrite), bitsToWrite);
length -= bitsToWrite;
}
};
BitStream.prototype.readArrayBuffer = function(byteLength) {
var buffer = this._view.getArrayBuffer(this._index, byteLength);
this._index += (byteLength * 8);
return buffer;
};
BitStream.prototype.writeArrayBuffer = function(buffer, byteLength) {
this.writeBitStream(new BitStream(buffer), byteLength * 8);
};
// AMD / RequireJS
if (typeof define !== 'undefined' && define.amd) {
define(function () {
return {
BitView: BitView,
BitStream: BitStream
};
});
}
// Node.js
else if (typeof module !== 'undefined' && module.exports) {
module.exports = {
BitView: BitView,
BitStream: BitStream
};
}
}(this));

@ -0,0 +1,71 @@
{
"_args": [
[
"bit-buffer@0.2.5",
"/home/vitaly/Dropbox/Coding/lv_font_conv"
]
],
"_from": "bit-buffer@0.2.5",
"_id": "bit-buffer@0.2.5",
"_inBundle": false,
"_integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==",
"_location": "/bit-buffer",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "bit-buffer@0.2.5",
"name": "bit-buffer",
"escapedName": "bit-buffer",
"rawSpec": "0.2.5",
"saveSpec": null,
"fetchSpec": "0.2.5"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz",
"_spec": "0.2.5",
"_where": "/home/vitaly/Dropbox/Coding/lv_font_conv",
"author": {
"name": "Anthony Pesch"
},
"bugs": {
"url": "https://github.com/inolen/bit-buffer/issues"
},
"contributors": [
{
"name": "Robin Appelman"
}
],
"description": "Bit-level reads and writes for ArrayBuffers",
"devDependencies": {
"@types/node": "^14.14.22",
"jshint": "^2.12.0",
"mocha": "^8.2.1"
},
"directories": {
"test": "test"
},
"gitHead": "cd4417237bed1f22dd5adfd8a6b961ea7234d9c9",
"homepage": "https://github.com/inolen/bit-buffer#readme",
"keywords": [
"dataview",
"arraybuffer",
"bit",
"bits"
],
"license": "MIT",
"main": "bit-buffer.js",
"name": "bit-buffer",
"repository": {
"type": "git",
"url": "git://github.com/inolen/bit-buffer.git"
},
"scripts": {
"lint": "jshint bit-buffer.js",
"test": "mocha --ui tdd"
},
"types": "./bit-buffer.d.ts",
"version": "0.2.5"
}

@ -0,0 +1,628 @@
var assert = require('assert'),
BitView = require('./bit-buffer').BitView,
BitStream = require('./bit-buffer').BitStream;
suite('BitBuffer', function () {
var array, bv, bsw, bsr;
setup(function () {
array = new ArrayBuffer(64);
bv = new BitView(array);
bsw = new BitStream(bv);
// Test initializing straight from the array.
bsr = new BitStream(array);
});
test('Min / max signed 5 bits', function () {
var signed_max = (1 << 4) - 1;
bsw.writeBits(signed_max, 5);
bsw.writeBits(-signed_max - 1, 5);
assert(bsr.readBits(5, true) === signed_max);
assert(bsr.readBits(5, true) === -signed_max - 1);
});
test('Min / max unsigned 5 bits', function () {
var unsigned_max = (1 << 5) - 1;
bsw.writeBits(unsigned_max, 5);
bsw.writeBits(-unsigned_max, 5);
assert.equal(bsr.readBits(5), unsigned_max);
assert.equal(bsr.readBits(5), 1);
});
test('Min / max int8', function () {
var signed_max = 0x7F;
bsw.writeInt8(signed_max);
bsw.writeInt8(-signed_max - 1);
assert.equal(bsr.readInt8(), signed_max);
assert.equal(bsr.readInt8(), -signed_max - 1);
});
test('Min / max uint8', function () {
var unsigned_max = 0xFF;
bsw.writeUint8(unsigned_max);
bsw.writeUint8(-unsigned_max);
assert.equal(bsr.readUint8(), unsigned_max);
assert.equal(bsr.readUint8(), 1);
});
test('Min / max int16', function () {
var signed_max = 0x7FFF;
bsw.writeInt16(signed_max);
bsw.writeInt16(-signed_max - 1);
assert.equal(bsr.readInt16(), signed_max);
assert.equal(bsr.readInt16(), -signed_max - 1);
});
test('Min / max uint16', function () {
var unsigned_max = 0xFFFF;
bsw.writeUint16(unsigned_max);
bsw.writeUint16(-unsigned_max);
assert.equal(bsr.readUint16(), unsigned_max);
assert.equal(bsr.readUint16(), 1);
});
test('Min / max int32', function () {
var signed_max = 0x7FFFFFFF;
bsw.writeInt32(signed_max);
bsw.writeInt32(-signed_max - 1);
assert.equal(bsr.readInt32(), signed_max);
assert.equal(bsr.readInt32(), -signed_max - 1);
});
test('Min / max uint32', function () {
var unsigned_max = 0xFFFFFFFF;
bsw.writeUint32(unsigned_max);
bsw.writeUint32(-unsigned_max);
assert.equal(bsr.readUint32(), unsigned_max);
assert.equal(bsr.readUint32(), 1);
});
test('Unaligned reads', function () {
bsw.writeBits(13, 5);
bsw.writeUint8(0xFF);
bsw.writeBits(14, 5);
assert.equal(bsr.readBits(5), 13);
assert.equal(bsr.readUint8(), 0xFF);
assert.equal(bsr.readBits(5), 14);
});
test('Min / max float32 (normal values)', function () {
var scratch = new DataView(new ArrayBuffer(8));
scratch.setUint32(0, 0x00800000);
scratch.setUint32(4, 0x7f7fffff);
var min = scratch.getFloat32(0);
var max = scratch.getFloat32(4);
bsw.writeFloat32(min);
bsw.writeFloat32(max);
assert.equal(bsr.readFloat32(), min);
assert.equal(bsr.readFloat32(), max);
});
test('Min / max float64 (normal values)', function () {
var scratch = new DataView(new ArrayBuffer(16));
scratch.setUint32(0, 0x00100000);
scratch.setUint32(4, 0x00000000);
scratch.setUint32(8, 0x7fefffff);
scratch.setUint32(12, 0xffffffff);
var min = scratch.getFloat64(0);
var max = scratch.getFloat64(8);
bsw.writeFloat64(min);
bsw.writeFloat64(max);
assert.equal(bsr.readFloat64(), min);
assert.equal(bsr.readFloat64(), max);
});
test('Overwrite previous value with 0', function () {
bv.setUint8(0, 13);
bv.setUint8(0, 0);
assert.equal(bv.getUint8(0), 0);
});
test('Read / write ASCII string, fixed length', function () {
var str = 'foobar';
var len = 16;
bsw.writeASCIIString(str, len);
assert.equal(bsw.byteIndex, len);
assert.equal(bsr.readASCIIString(len), str);
assert.equal(bsr.byteIndex, len);
});
test('Read / write ASCII string, unknown length', function () {
var str = 'foobar';
bsw.writeASCIIString(str);
assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00
assert.equal(bsr.readASCIIString(), str);
assert.equal(bsr.byteIndex, str.length + 1);
});
test('Read ASCII string, 0 length', function () {
var str = 'foobar';
bsw.writeASCIIString(str);
assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00
assert.equal(bsr.readASCIIString(0), '');
assert.equal(bsr.byteIndex, 0);
});
test('Read overflow', function () {
var exception = false;
try {
bsr.readASCIIString(128);
} catch (e) {
exception = true;
}
assert(exception);
});
test('Write overflow', function () {
var exception = false;
try {
bsw.writeASCIIString('foobar', 128);
} catch (e) {
exception = true;
}
assert(exception);
});
test('Get boolean', function () {
bv.setUint8(0, 1);
assert(bv.getBoolean(0));
bv.setUint8(0, 0);
assert(!bv.getBoolean(0));
});
test('Set boolean', function () {
bv.setBoolean(0, true);
assert(bv.getBoolean(0));
bv.setBoolean(0, false);
assert(!bv.getBoolean(0));
});
test('Read boolean', function () {
bv.setBits(0, 1, 1);
bv.setBits(1, 0, 1);
assert(bsr.readBoolean());
assert(!bsr.readBoolean());
});
test('Write boolean', function () {
bsr.writeBoolean(true);
assert.equal(bv.getBits(0, 1, false), 1);
bsr.writeBoolean(false);
assert.equal(bv.getBits(1, 1, false), 0);
});
test('Read / write UTF8 string, only ASCII characters', function () {
var str = 'foobar';
bsw.writeUTF8String(str);
assert(bsw.byteIndex === str.length + 1); // +1 for 0x00
assert.equal(bsr.readUTF8String(), str);
assert.equal(bsr.byteIndex, str.length + 1);
});
test('Read / write UTF8 string, non ASCII characters', function () {
var str = '日本語';
var bytes = [
0xE6,
0x97,
0xA5,
0xE6,
0x9C,
0xAC,
0xE8,
0xAA,
0x9E
];
bsw.writeUTF8String(str);
for (var i = 0; i < bytes.length; i++) {
assert.equal(bytes[i], bv.getBits(i * 8, 8));
}
assert.equal(bsw.byteIndex, bytes.length + 1); // +1 for 0x00
assert.equal(str, bsr.readUTF8String());
assert.equal(bsr.byteIndex, bytes.length + 1);
});
test('readBitStream', function () {
bsw.writeBits(0xF0, 8); //0b11110000
bsw.writeBits(0xF1, 8); //0b11110001
bsr.readBits(3); //offset
var slice = bsr.readBitStream(8);
assert.equal(slice.readBits(6), 0x3E); //0b111110
assert.equal(9, slice._index);
assert.equal(6, slice.index);
assert.equal(8, slice.length);
assert.equal(2, slice.bitsLeft);
assert.equal(bsr._index, 11);
assert.equal((64 * 8) - 11, bsr.bitsLeft);
});
test('readBitStream overflow', function () {
bsw.writeBits(0xF0, 8); //0b11110000
bsw.writeBits(0xF1, 8); //0b11110001
bsr.readBits(3); //offset
var slice = bsr.readBitStream(4);
var exception = false;
try {
slice.readUint8();
} catch (e) {
exception = true;
}
assert(exception);
});
test('writeBitStream', function () {
var buf = new ArrayBuffer(64);
var sourceStream = new BitStream(buf);
sourceStream.writeBits(0xF0, 8); //0b11110000
sourceStream.writeBits(0xF1, 8); //0b11110001
sourceStream.index = 0;
sourceStream.readBits(3); //offset
bsr.writeBitStream(sourceStream, 8);
assert.equal(8, bsr.index);
bsr.index = 0;
assert.equal(bsr.readBits(6), 0x3E); //0b00111110
assert.equal(11, sourceStream.index);
var bin = new Uint8Array(buf);
assert.equal(bin[0], 0xF0);
assert.equal(bin[1], 0xF1);
});
test('writeBitStream Buffer', function () {
var buf = Buffer.alloc(64);
var sourceStream = new BitStream(buf);
sourceStream.writeBits(0xF0, 8); //0b11110000
sourceStream.writeBits(0xF1, 8); //0b11110001
sourceStream.index = 0;
sourceStream.readBits(3); //offset
bsr.writeBitStream(sourceStream, 8);
assert.equal(8, bsr.index);
bsr.index = 0;
assert.equal(bsr.readBits(6), 0x3E); //0b00111110
assert.equal(11, sourceStream.index);
var bin = new Uint8Array(buf.buffer);
assert.equal(bin[0], 0xF0);
assert.equal(bin[1], 0xF1);
});
test('writeBitStream long', function () {
var sourceStream = new BitStream(new ArrayBuffer(64));
sourceStream.writeBits(0xF0, 8);
sourceStream.writeBits(0xF1, 8);
sourceStream.writeBits(0xF1, 8);
sourceStream.writeBits(0xF1, 8);
sourceStream.writeBits(0xF1, 8);
sourceStream.index = 0;
sourceStream.readBits(3); //offset
bsr.index = 3;
bsr.writeBitStream(sourceStream, 35);
assert.equal(38, bsr.index);
bsr.index = 3;
assert.equal(bsr.readBits(35), 1044266558);
assert.equal(38, sourceStream.index);
});
test('readArrayBuffer', function () {
bsw.writeBits(0xF0, 8); //0b11110000
bsw.writeBits(0xF1, 8); //0b11110001
bsw.writeBits(0xF0, 8); //0b11110000
bsr.readBits(3); //offset
var buffer = bsr.readArrayBuffer(2);
assert.equal(0x3E, buffer[0]); //0b00111110
assert.equal(0x1E, buffer[1]); //0b00011110
assert.equal(3 + (2 * 8), bsr._index);
});
test('writeArrayBuffer', function () {
var source = new Uint8Array(4);
source[0] = 0xF0;
source[1] = 0xF1;
source[2] = 0xF1;
bsr.readBits(3); //offset
bsr.writeArrayBuffer(source.buffer, 2);
assert.equal(19, bsr.index);
bsr.index = 0;
assert.equal(bsr.readBits(8), 128);
});
test('Get buffer from view', function () {
bv.setBits(0, 0xFFFFFFFF, 32);
var buffer = bv.buffer;
assert.equal(64, buffer.length);
assert.equal(0xFFFF, buffer.readUInt16LE(0));
});
test('Get buffer from stream', function () {
bsw.writeBits(0xFFFFFFFF, 32);
var buffer = bsr.buffer;
assert.equal(64, buffer.length);
assert.equal(0xFFFF, buffer.readUInt16LE(0));
});
});
suite('Reading big/little endian', function () {
var array, u8, bv, bsw, bsr;
setup(function () {
array = new ArrayBuffer(64);
u8 = new Uint8Array(array);
u8[0] = 0x01;
u8[1] = 0x02;
// Test initializing straight from the array.
bsr = new BitStream(array);
});
test('4b, little-endian', function () {
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(4));
result.push(bsr.readBits(4));
result.push(bsr.readBits(4));
result.push(bsr.readBits(4));
// 0000 0001 0000 0010 [01 02]
// [#2] [#1] [#4] [#3]
assert.deepEqual(result, [1, 0, 2, 0]);
});
test('8b, little-endian', function () {
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(8));
result.push(bsr.readBits(8));
// 0000 0001 0000 0010 [01 02]
// [ #1] [ #2]
assert.deepEqual(result, [1, 2]);
});
test('10b, little-endian', function () {
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(10));
// 0000 0001 0000 0010 [01 02]
// ... #1] [ #2][#1...
assert.deepEqual(result, [513]);
});
test('16b, little-endian', function () {
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(16));
// 0000 0001 0000 0010 [01 02]
// [ #1]
assert.deepEqual(result, [0x201]);
});
test('24b, little-endian', function () {
u8[2] = 0x03;
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(24));
// 0000 0001 0000 0010 0000 0011 [01 02 03]
// [ #1]
assert.deepEqual(result, [0x30201]);
});
test('4b, big-endian', function () {
bsr.bigEndian = true;
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(4));
result.push(bsr.readBits(4));
result.push(bsr.readBits(4));
result.push(bsr.readBits(4));
// 0000 0001 0000 0010 [01 02]
// [#1] [#2] [#3] [#4]
assert.deepEqual(result, [0, 1, 0, 2]);
});
test('8b, big-endian', function () {
bsr.bigEndian = true;
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(8));
result.push(bsr.readBits(8));
// 0000 0001 0000 0010 [01 02]
// [ #1] [ #2]
assert.deepEqual(result, [1, 2]);
});
test('10b, big-endian', function () {
bsr.bigEndian = true;
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(10));
result.push(bsr.readBits(6));
// 0000 0001 0000 0010 [01 02]
// [ #1][ #2]
assert.deepEqual(result, [4, 2]);
});
test('16b, big-endian', function () {
bsr.bigEndian = true;
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(16));
// 0000 0001 0000 0010 [01 02]
// [ #1]
assert.deepEqual(result, [0x102]);
});
test('24b, big-endian', function () {
u8[2] = 0x03;
bsr.bigEndian = true;
assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0');
var result = [];
result.push(bsr.readBits(24));
// 0000 0001 0000 0010 0000 0011 [01 02 03]
// [ #1]
assert.deepEqual(result, [0x10203]);
});
});
suite('Writing big/little endian', function () {
var array, u8, bv, bsw, bsr;
setup(function () {
array = new ArrayBuffer(2);
u8 = new Uint8Array(array);
bv = new BitView(array);
bsw = new BitStream(bv);
});
test('4b, little-endian', function () {
// 0000 0001 0000 0010 [01 02]
// [#2] [#1] [#4] [#3]
bsw.writeBits(1, 4);
bsw.writeBits(0, 4);
bsw.writeBits(2, 4);
bsw.writeBits(0, 4);
assert.deepEqual(u8, new Uint8Array([0x01, 0x02]));
});
test('8b, little-endian', function () {
// 0000 0001 0000 0010 [01 02]
// [ #1] [ #2]
bsw.writeBits(1, 8);
bsw.writeBits(2, 8);
assert.deepEqual(u8, new Uint8Array([0x01, 0x02]));
});
test('10b, little-endian', function () {
// 0000 0001 0000 0010 [01 02]
// ... #1] [ #2][#1...
bsw.writeBits(513, 10);
assert.deepEqual(u8, new Uint8Array([0x01, 0x02]));
});
test('16b, little-endian', function () {
// 0000 0001 0000 0010 [01 02]
// [ #1]
bsw.writeBits(0x201, 16);
assert.deepEqual(u8, new Uint8Array([0x01, 0x02]));
});
test('4b, big-endian', function () {
bsw.bigEndian = true;
// 0000 0001 0000 0010 [01 02]
// [#1] [#2] [#3] [#4]
bsw.writeBits(0, 4);
bsw.writeBits(1, 4);
bsw.writeBits(0, 4);
bsw.writeBits(2, 4);
assert.deepEqual(u8, new Uint8Array([0x01, 0x02]));
});
test('8b, big-endian', function () {
bsw.bigEndian = true;
// 0000 0001 0000 0010 [01 02]
// [ #1] [ #2]
bsw.writeBits(1, 8);
bsw.writeBits(2, 8);
assert.deepEqual(u8, new Uint8Array([0x01, 0x02]));
});
test('10b, big-endian', function () {
bsw.bigEndian = true;
// 0000 0001 0000 0010 [01 02]
// [ #1][ #2]
bsw.writeBits(4, 10);
bsw.writeBits(2, 6);
assert.deepEqual(u8, new Uint8Array([0x01, 0x02]));
});
test('16b, big-endian', function () {
bsw.bigEndian = true;
// 0000 0001 0000 0010 [01 02]
// [ #1]
bsw.writeBits(0x102, 16);
assert.deepEqual(u8, new Uint8Array([0x01, 0x02]));
});
});

@ -0,0 +1,19 @@
(The MIT License)
Copyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the 'Software'), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,455 @@
# debug
[![Build Status](https://travis-ci.org/visionmedia/debug.svg?branch=master)](https://travis-ci.org/visionmedia/debug) [![Coverage Status](https://coveralls.io/repos/github/visionmedia/debug/badge.svg?branch=master)](https://coveralls.io/github/visionmedia/debug?branch=master) [![Slack](https://visionmedia-community-slackin.now.sh/badge.svg)](https://visionmedia-community-slackin.now.sh/) [![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/debug/sponsors/badge.svg)](#sponsors)
<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
A tiny JavaScript debugging utility modelled after Node.js core's debugging
technique. Works in Node.js and web browsers.
## Installation
```bash
$ npm install debug
```
## Usage
`debug` exposes a function; simply pass this function the name of your module, and it will return a decorated version of `console.error` for you to pass debug statements to. This will allow you to toggle the debug output for different parts of your module as well as the module as a whole.
Example [_app.js_](./examples/node/app.js):
```js
var debug = require('debug')('http')
, http = require('http')
, name = 'My App';
// fake app
debug('booting %o', name);
http.createServer(function(req, res){
debug(req.method + ' ' + req.url);
res.end('hello\n');
}).listen(3000, function(){
debug('listening');
});
// fake worker of some kind
require('./worker');
```
Example [_worker.js_](./examples/node/worker.js):
```js
var a = require('debug')('worker:a')
, b = require('debug')('worker:b');
function work() {
a('doing lots of uninteresting work');
setTimeout(work, Math.random() * 1000);
}
work();
function workb() {
b('doing some work');
setTimeout(workb, Math.random() * 2000);
}
workb();
```
The `DEBUG` environment variable is then used to enable these based on space or
comma-delimited names.
Here are some examples:
<img width="647" alt="screen shot 2017-08-08 at 12 53 04 pm" src="https://user-images.githubusercontent.com/71256/29091703-a6302cdc-7c38-11e7-8304-7c0b3bc600cd.png">
<img width="647" alt="screen shot 2017-08-08 at 12 53 38 pm" src="https://user-images.githubusercontent.com/71256/29091700-a62a6888-7c38-11e7-800b-db911291ca2b.png">
<img width="647" alt="screen shot 2017-08-08 at 12 53 25 pm" src="https://user-images.githubusercontent.com/71256/29091701-a62ea114-7c38-11e7-826a-2692bedca740.png">
#### Windows command prompt notes
##### CMD
On Windows the environment variable is set using the `set` command.
```cmd
set DEBUG=*,-not_this
```
Example:
```cmd
set DEBUG=* & node app.js
```
##### PowerShell (VS Code default)
PowerShell uses different syntax to set environment variables.
```cmd
$env:DEBUG = "*,-not_this"
```
Example:
```cmd
$env:DEBUG='app';node app.js
```
Then, run the program to be debugged as usual.
npm script example:
```js
"windowsDebug": "@powershell -Command $env:DEBUG='*';node app.js",
```
## Namespace Colors
Every debug instance has a color generated for it based on its namespace name.
This helps when visually parsing the debug output to identify which debug instance
a debug line belongs to.
#### Node.js
In Node.js, colors are enabled when stderr is a TTY. You also _should_ install
the [`supports-color`](https://npmjs.org/supports-color) module alongside debug,
otherwise debug will only use a small handful of basic colors.
<img width="521" src="https://user-images.githubusercontent.com/71256/29092181-47f6a9e6-7c3a-11e7-9a14-1928d8a711cd.png">
#### Web Browser
Colors are also enabled on "Web Inspectors" that understand the `%c` formatting
option. These are WebKit web inspectors, Firefox ([since version
31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/))
and the Firebug plugin for Firefox (any version).
<img width="524" src="https://user-images.githubusercontent.com/71256/29092033-b65f9f2e-7c39-11e7-8e32-f6f0d8e865c1.png">
## Millisecond diff
When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls.
<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
When stdout is not a TTY, `Date#toISOString()` is used, making it more useful for logging the debug information as shown below:
<img width="647" src="https://user-images.githubusercontent.com/71256/29091956-6bd78372-7c39-11e7-8c55-c948396d6edd.png">
## Conventions
If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". If you append a "*" to the end of your name, it will always be enabled regardless of the setting of the DEBUG environment variable. You can then use it for normal output as well as debug output.
## Wildcards
The `*` character may be used as a wildcard. Suppose for example your library has
debuggers named "connect:bodyParser", "connect:compress", "connect:session",
instead of listing all three with
`DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do
`DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.
You can also exclude specific debuggers by prefixing them with a "-" character.
For example, `DEBUG=*,-connect:*` would include all debuggers except those
starting with "connect:".
## Environment Variables
When running through Node.js, you can set a few environment variables that will
change the behavior of the debug logging:
| Name | Purpose |
|-----------|-------------------------------------------------|
| `DEBUG` | Enables/disables specific debugging namespaces. |
| `DEBUG_HIDE_DATE` | Hide date from debug output (non-TTY). |
| `DEBUG_COLORS`| Whether or not to use colors in the debug output. |
| `DEBUG_DEPTH` | Object inspection depth. |
| `DEBUG_SHOW_HIDDEN` | Shows hidden properties on inspected objects. |
__Note:__ The environment variables beginning with `DEBUG_` end up being
converted into an Options object that gets used with `%o`/`%O` formatters.
See the Node.js documentation for
[`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options)
for the complete list.
## Formatters
Debug uses [printf-style](https://wikipedia.org/wiki/Printf_format_string) formatting.
Below are the officially supported formatters:
| Formatter | Representation |
|-----------|----------------|
| `%O` | Pretty-print an Object on multiple lines. |
| `%o` | Pretty-print an Object all on a single line. |
| `%s` | String. |
| `%d` | Number (both integer and float). |
| `%j` | JSON. Replaced with the string '[Circular]' if the argument contains circular references. |
| `%%` | Single percent sign ('%'). This does not consume an argument. |
### Custom formatters
You can add custom formatters by extending the `debug.formatters` object.
For example, if you wanted to add support for rendering a Buffer as hex with
`%h`, you could do something like:
```js
const createDebug = require('debug')
createDebug.formatters.h = (v) => {
return v.toString('hex')
}
// …elsewhere
const debug = createDebug('foo')
debug('this is hex: %h', new Buffer('hello world'))
// foo this is hex: 68656c6c6f20776f726c6421 +0ms
```
## Browser Support
You can build a browser-ready script using [browserify](https://github.com/substack/node-browserify),
or just use the [browserify-as-a-service](https://wzrd.in/) [build](https://wzrd.in/standalone/debug@latest),
if you don't want to build it yourself.
Debug's enable state is currently persisted by `localStorage`.
Consider the situation shown below where you have `worker:a` and `worker:b`,
and wish to debug both. You can enable this using `localStorage.debug`:
```js
localStorage.debug = 'worker:*'
```
And then refresh the page.
```js
a = debug('worker:a');
b = debug('worker:b');
setInterval(function(){
a('doing some work');
}, 1000);
setInterval(function(){
b('doing some work');
}, 1200);
```
## Output streams
By default `debug` will log to stderr, however this can be configured per-namespace by overriding the `log` method:
Example [_stdout.js_](./examples/node/stdout.js):
```js
var debug = require('debug');
var error = debug('app:error');
// by default stderr is used
error('goes to stderr!');
var log = debug('app:log');
// set this namespace to log via console.log
log.log = console.log.bind(console); // don't forget to bind to console!
log('goes to stdout');
error('still goes to stderr!');
// set all output to go via console.info
// overrides all per-namespace log settings
debug.log = console.info.bind(console);
error('now goes to stdout via console.info');
log('still goes to stdout, but via console.info now');
```
## Extend
You can simply extend debugger
```js
const log = require('debug')('auth');
//creates new debug instance with extended namespace
const logSign = log.extend('sign');
const logLogin = log.extend('login');
log('hello'); // auth hello
logSign('hello'); //auth:sign hello
logLogin('hello'); //auth:login hello
```
## Set dynamically
You can also enable debug dynamically by calling the `enable()` method :
```js
let debug = require('debug');
console.log(1, debug.enabled('test'));
debug.enable('test');
console.log(2, debug.enabled('test'));
debug.disable();
console.log(3, debug.enabled('test'));
```
print :
```
1 false
2 true
3 false
```
Usage :
`enable(namespaces)`
`namespaces` can include modes separated by a colon and wildcards.
Note that calling `enable()` completely overrides previously set DEBUG variable :
```
$ DEBUG=foo node -e 'var dbg = require("debug"); dbg.enable("bar"); console.log(dbg.enabled("foo"))'
=> false
```
`disable()`
Will disable all namespaces. The functions returns the namespaces currently
enabled (and skipped). This can be useful if you want to disable debugging
temporarily without knowing what was enabled to begin with.
For example:
```js
let debug = require('debug');
debug.enable('foo:*,-foo:bar');
let namespaces = debug.disable();
debug.enable(namespaces);
```
Note: There is no guarantee that the string will be identical to the initial
enable string, but semantically they will be identical.
## Checking whether a debug target is enabled
After you've created a debug instance, you can determine whether or not it is
enabled by checking the `enabled` property:
```javascript
const debug = require('debug')('http');
if (debug.enabled) {
// do stuff...
}
```
You can also manually toggle this property to force the debug instance to be
enabled or disabled.
## Authors
- TJ Holowaychuk
- Nathan Rajlich
- Andrew Rhyne
## Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/debug#backer)]
<a href="https://opencollective.com/debug/backer/0/website" target="_blank"><img src="https://opencollective.com/debug/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/1/website" target="_blank"><img src="https://opencollective.com/debug/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/2/website" target="_blank"><img src="https://opencollective.com/debug/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/3/website" target="_blank"><img src="https://opencollective.com/debug/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/4/website" target="_blank"><img src="https://opencollective.com/debug/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/5/website" target="_blank"><img src="https://opencollective.com/debug/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/6/website" target="_blank"><img src="https://opencollective.com/debug/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/7/website" target="_blank"><img src="https://opencollective.com/debug/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/8/website" target="_blank"><img src="https://opencollective.com/debug/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/9/website" target="_blank"><img src="https://opencollective.com/debug/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/10/website" target="_blank"><img src="https://opencollective.com/debug/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/11/website" target="_blank"><img src="https://opencollective.com/debug/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/12/website" target="_blank"><img src="https://opencollective.com/debug/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/13/website" target="_blank"><img src="https://opencollective.com/debug/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/14/website" target="_blank"><img src="https://opencollective.com/debug/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/15/website" target="_blank"><img src="https://opencollective.com/debug/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/16/website" target="_blank"><img src="https://opencollective.com/debug/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/17/website" target="_blank"><img src="https://opencollective.com/debug/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/18/website" target="_blank"><img src="https://opencollective.com/debug/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/19/website" target="_blank"><img src="https://opencollective.com/debug/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/20/website" target="_blank"><img src="https://opencollective.com/debug/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/21/website" target="_blank"><img src="https://opencollective.com/debug/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/22/website" target="_blank"><img src="https://opencollective.com/debug/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/23/website" target="_blank"><img src="https://opencollective.com/debug/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/24/website" target="_blank"><img src="https://opencollective.com/debug/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/25/website" target="_blank"><img src="https://opencollective.com/debug/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/26/website" target="_blank"><img src="https://opencollective.com/debug/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/27/website" target="_blank"><img src="https://opencollective.com/debug/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/28/website" target="_blank"><img src="https://opencollective.com/debug/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/29/website" target="_blank"><img src="https://opencollective.com/debug/backer/29/avatar.svg"></a>
## Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/debug#sponsor)]
<a href="https://opencollective.com/debug/sponsor/0/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/1/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/2/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/3/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/4/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/5/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/6/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/7/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/8/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/9/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/10/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/11/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/12/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/13/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/14/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/15/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/16/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/17/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/18/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/19/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/20/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/21/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/22/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/23/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/24/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/25/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/26/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/27/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/28/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/29/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/29/avatar.svg"></a>
## License
(The MIT License)
Copyright (c) 2014-2017 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,111 @@
{
"_args": [
[
"debug@4.3.1",
"/home/vitaly/Dropbox/Coding/lv_font_conv"
]
],
"_from": "debug@4.3.1",
"_id": "debug@4.3.1",
"_inBundle": false,
"_integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"_location": "/debug",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "debug@4.3.1",
"name": "debug",
"escapedName": "debug",
"rawSpec": "4.3.1",
"saveSpec": null,
"fetchSpec": "4.3.1"
},
"_requiredBy": [
"/",
"/@babel/core",
"/@babel/helper-define-polyfill-provider",
"/@babel/traverse",
"/@eslint/eslintrc",
"/eslint",
"/istanbul-lib-source-maps",
"/mocha"
],
"_resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"_spec": "4.3.1",
"_where": "/home/vitaly/Dropbox/Coding/lv_font_conv",
"author": {
"name": "TJ Holowaychuk",
"email": "tj@vision-media.ca"
},
"browser": "./src/browser.js",
"bugs": {
"url": "https://github.com/visionmedia/debug/issues"
},
"contributors": [
{
"name": "Nathan Rajlich",
"email": "nathan@tootallnate.net",
"url": "http://n8.io"
},
{
"name": "Andrew Rhyne",
"email": "rhyneandrew@gmail.com"
},
{
"name": "Josh Junon",
"email": "josh@junon.me"
}
],
"dependencies": {
"ms": "2.1.2"
},
"description": "small debugging utility",
"devDependencies": {
"brfs": "^2.0.1",
"browserify": "^16.2.3",
"coveralls": "^3.0.2",
"istanbul": "^0.4.5",
"karma": "^3.1.4",
"karma-browserify": "^6.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-mocha": "^1.3.0",
"mocha": "^5.2.0",
"mocha-lcov-reporter": "^1.2.0",
"xo": "^0.23.0"
},
"engines": {
"node": ">=6.0"
},
"files": [
"src",
"LICENSE",
"README.md"
],
"homepage": "https://github.com/visionmedia/debug#readme",
"keywords": [
"debug",
"log",
"debugger"
],
"license": "MIT",
"main": "./src/index.js",
"name": "debug",
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
},
"repository": {
"type": "git",
"url": "git://github.com/visionmedia/debug.git"
},
"scripts": {
"lint": "xo",
"test": "npm run test:node && npm run test:browser && npm run lint",
"test:browser": "karma start --single-run",
"test:coverage": "cat ./coverage/lcov.info | coveralls",
"test:node": "istanbul cover _mocha -- test.js"
},
"version": "4.3.1"
}

@ -0,0 +1,269 @@
/* eslint-env browser */
/**
* This is the web browser implementation of `debug()`.
*/
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = localstorage();
exports.destroy = (() => {
let warned = false;
return () => {
if (!warned) {
warned = true;
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
};
})();
/**
* Colors.
*/
exports.colors = [
'#0000CC',
'#0000FF',
'#0033CC',
'#0033FF',
'#0066CC',
'#0066FF',
'#0099CC',
'#0099FF',
'#00CC00',
'#00CC33',
'#00CC66',
'#00CC99',
'#00CCCC',
'#00CCFF',
'#3300CC',
'#3300FF',
'#3333CC',
'#3333FF',
'#3366CC',
'#3366FF',
'#3399CC',
'#3399FF',
'#33CC00',
'#33CC33',
'#33CC66',
'#33CC99',
'#33CCCC',
'#33CCFF',
'#6600CC',
'#6600FF',
'#6633CC',
'#6633FF',
'#66CC00',
'#66CC33',
'#9900CC',
'#9900FF',
'#9933CC',
'#9933FF',
'#99CC00',
'#99CC33',
'#CC0000',
'#CC0033',
'#CC0066',
'#CC0099',
'#CC00CC',
'#CC00FF',
'#CC3300',
'#CC3333',
'#CC3366',
'#CC3399',
'#CC33CC',
'#CC33FF',
'#CC6600',
'#CC6633',
'#CC9900',
'#CC9933',
'#CCCC00',
'#CCCC33',
'#FF0000',
'#FF0033',
'#FF0066',
'#FF0099',
'#FF00CC',
'#FF00FF',
'#FF3300',
'#FF3333',
'#FF3366',
'#FF3399',
'#FF33CC',
'#FF33FF',
'#FF6600',
'#FF6633',
'#FF9900',
'#FF9933',
'#FFCC00',
'#FFCC33'
];
/**
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
* and the Firebug extension (any Firefox version) are known
* to support "%c" CSS customizations.
*
* TODO: add a `localStorage` variable to explicitly enable/disable colors
*/
// eslint-disable-next-line complexity
function useColors() {
// NB: In an Electron preload script, document will be defined but not fully
// initialized. Since we know we're in Chrome, we'll just detect this case
// explicitly
if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
return true;
}
// Internet Explorer and Edge do not support colors.
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
return false;
}
// Is webkit? http://stackoverflow.com/a/16459606/376773
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
// Is firebug? http://stackoverflow.com/a/398120/376773
(typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
// Is firefox >= v31?
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
// Double check webkit in userAgent just in case we are in a worker
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
}
/**
* Colorize log arguments if enabled.
*
* @api public
*/
function formatArgs(args) {
args[0] = (this.useColors ? '%c' : '') +
this.namespace +
(this.useColors ? ' %c' : ' ') +
args[0] +
(this.useColors ? '%c ' : ' ') +
'+' + module.exports.humanize(this.diff);
if (!this.useColors) {
return;
}
const c = 'color: ' + this.color;
args.splice(1, 0, c, 'color: inherit');
// The final "%c" is somewhat tricky, because there could be other
// arguments passed either before or after the %c, so we need to
// figure out the correct index to insert the CSS into
let index = 0;
let lastC = 0;
args[0].replace(/%[a-zA-Z%]/g, match => {
if (match === '%%') {
return;
}
index++;
if (match === '%c') {
// We only are interested in the *last* %c
// (the user may have provided their own)
lastC = index;
}
});
args.splice(lastC, 0, c);
}
/**
* Invokes `console.debug()` when available.
* No-op when `console.debug` is not a "function".
* If `console.debug` is not available, falls back
* to `console.log`.
*
* @api public
*/
exports.log = console.debug || console.log || (() => {});
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
try {
if (namespaces) {
exports.storage.setItem('debug', namespaces);
} else {
exports.storage.removeItem('debug');
}
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
let r;
try {
r = exports.storage.getItem('debug');
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
if (!r && typeof process !== 'undefined' && 'env' in process) {
r = process.env.DEBUG;
}
return r;
}
/**
* Localstorage attempts to return the localstorage.
*
* This is necessary because safari throws
* when a user disables cookies/localstorage
* and you attempt to access it.
*
* @return {LocalStorage}
* @api private
*/
function localstorage() {
try {
// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
// The Browser also has localStorage in the global context.
return localStorage;
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
module.exports = require('./common')(exports);
const {formatters} = module.exports;
/**
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
*/
formatters.j = function (v) {
try {
return JSON.stringify(v);
} catch (error) {
return '[UnexpectedJSONParseError]: ' + error.message;
}
};

@ -0,0 +1,261 @@
/**
* This is the common logic for both the Node.js and web browser
* implementations of `debug()`.
*/
function setup(env) {
createDebug.debug = createDebug;
createDebug.default = createDebug;
createDebug.coerce = coerce;
createDebug.disable = disable;
createDebug.enable = enable;
createDebug.enabled = enabled;
createDebug.humanize = require('ms');
createDebug.destroy = destroy;
Object.keys(env).forEach(key => {
createDebug[key] = env[key];
});
/**
* The currently active debug mode names, and names to skip.
*/
createDebug.names = [];
createDebug.skips = [];
/**
* Map of special "%n" handling functions, for the debug "format" argument.
*
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
*/
createDebug.formatters = {};
/**
* Selects a color for a debug namespace
* @param {String} namespace The namespace string for the for the debug instance to be colored
* @return {Number|String} An ANSI color code for the given namespace
* @api private
*/
function selectColor(namespace) {
let hash = 0;
for (let i = 0; i < namespace.length; i++) {
hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
}
createDebug.selectColor = selectColor;
/**
* Create a debugger with the given `namespace`.
*
* @param {String} namespace
* @return {Function}
* @api public
*/
function createDebug(namespace) {
let prevTime;
let enableOverride = null;
function debug(...args) {
// Disabled?
if (!debug.enabled) {
return;
}
const self = debug;
// Set `diff` timestamp
const curr = Number(new Date());
const ms = curr - (prevTime || curr);
self.diff = ms;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
args[0] = createDebug.coerce(args[0]);
if (typeof args[0] !== 'string') {
// Anything else let's inspect with %O
args.unshift('%O');
}
// Apply any `formatters` transformations
let index = 0;
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
// If we encounter an escaped % then don't increase the array index
if (match === '%%') {
return '%';
}
index++;
const formatter = createDebug.formatters[format];
if (typeof formatter === 'function') {
const val = args[index];
match = formatter.call(self, val);
// Now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
}
return match;
});
// Apply env-specific formatting (colors, etc.)
createDebug.formatArgs.call(self, args);
const logFn = self.log || createDebug.log;
logFn.apply(self, args);
}
debug.namespace = namespace;
debug.useColors = createDebug.useColors();
debug.color = createDebug.selectColor(namespace);
debug.extend = extend;
debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.
Object.defineProperty(debug, 'enabled', {
enumerable: true,
configurable: false,
get: () => enableOverride === null ? createDebug.enabled(namespace) : enableOverride,
set: v => {
enableOverride = v;
}
});
// Env-specific initialization logic for debug instances
if (typeof createDebug.init === 'function') {
createDebug.init(debug);
}
return debug;
}
function extend(namespace, delimiter) {
const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
newDebug.log = this.log;
return newDebug;
}
/**
* Enables a debug mode by namespaces. This can include modes
* separated by a colon and wildcards.
*
* @param {String} namespaces
* @api public
*/
function enable(namespaces) {
createDebug.save(namespaces);
createDebug.names = [];
createDebug.skips = [];
let i;
const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
const len = split.length;
for (i = 0; i < len; i++) {
if (!split[i]) {
// ignore empty strings
continue;
}
namespaces = split[i].replace(/\*/g, '.*?');
if (namespaces[0] === '-') {
createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
} else {
createDebug.names.push(new RegExp('^' + namespaces + '$'));
}
}
}
/**
* Disable debug output.
*
* @return {String} namespaces
* @api public
*/
function disable() {
const namespaces = [
...createDebug.names.map(toNamespace),
...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace)
].join(',');
createDebug.enable('');
return namespaces;
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*
* @param {String} name
* @return {Boolean}
* @api public
*/
function enabled(name) {
if (name[name.length - 1] === '*') {
return true;
}
let i;
let len;
for (i = 0, len = createDebug.skips.length; i < len; i++) {
if (createDebug.skips[i].test(name)) {
return false;
}
}
for (i = 0, len = createDebug.names.length; i < len; i++) {
if (createDebug.names[i].test(name)) {
return true;
}
}
return false;
}
/**
* Convert regexp to namespace
*
* @param {RegExp} regxep
* @return {String} namespace
* @api private
*/
function toNamespace(regexp) {
return regexp.toString()
.substring(2, regexp.toString().length - 2)
.replace(/\.\*\?$/, '*');
}
/**
* Coerce `val`.
*
* @param {Mixed} val
* @return {Mixed}
* @api private
*/
function coerce(val) {
if (val instanceof Error) {
return val.stack || val.message;
}
return val;
}
/**
* XXX DO NOT USE. This is a temporary stub function.
* XXX It WILL be removed in the next major release.
*/
function destroy() {
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
createDebug.enable(createDebug.load());
return createDebug;
}
module.exports = setup;

@ -0,0 +1,10 @@
/**
* Detect Electron renderer / nwjs process, which is node, but we should
* treat as a browser.
*/
if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) {
module.exports = require('./browser.js');
} else {
module.exports = require('./node.js');
}

@ -0,0 +1,263 @@
/**
* Module dependencies.
*/
const tty = require('tty');
const util = require('util');
/**
* This is the Node.js implementation of `debug()`.
*/
exports.init = init;
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.destroy = util.deprecate(
() => {},
'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'
);
/**
* Colors.
*/
exports.colors = [6, 2, 3, 4, 5, 1];
try {
// Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
// eslint-disable-next-line import/no-extraneous-dependencies
const supportsColor = require('supports-color');
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
exports.colors = [
20,
21,
26,
27,
32,
33,
38,
39,
40,
41,
42,
43,
44,
45,
56,
57,
62,
63,
68,
69,
74,
75,
76,
77,
78,
79,
80,
81,
92,
93,
98,
99,
112,
113,
128,
129,
134,
135,
148,
149,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
178,
179,
184,
185,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
214,
215,
220,
221
];
}
} catch (error) {
// Swallow - we only care if `supports-color` is available; it doesn't have to be.
}
/**
* Build up the default `inspectOpts` object from the environment variables.
*
* $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
*/
exports.inspectOpts = Object.keys(process.env).filter(key => {
return /^debug_/i.test(key);
}).reduce((obj, key) => {
// Camel-case
const prop = key
.substring(6)
.toLowerCase()
.replace(/_([a-z])/g, (_, k) => {
return k.toUpperCase();
});
// Coerce string value into JS value
let val = process.env[key];
if (/^(yes|on|true|enabled)$/i.test(val)) {
val = true;
} else if (/^(no|off|false|disabled)$/i.test(val)) {
val = false;
} else if (val === 'null') {
val = null;
} else {
val = Number(val);
}
obj[prop] = val;
return obj;
}, {});
/**
* Is stdout a TTY? Colored output is enabled when `true`.
*/
function useColors() {
return 'colors' in exports.inspectOpts ?
Boolean(exports.inspectOpts.colors) :
tty.isatty(process.stderr.fd);
}
/**
* Adds ANSI color escape codes if enabled.
*
* @api public
*/
function formatArgs(args) {
const {namespace: name, useColors} = this;
if (useColors) {
const c = this.color;
const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c);
const prefix = ` ${colorCode};1m${name} \u001B[0m`;
args[0] = prefix + args[0].split('\n').join('\n' + prefix);
args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m');
} else {
args[0] = getDate() + name + ' ' + args[0];
}
}
function getDate() {
if (exports.inspectOpts.hideDate) {
return '';
}
return new Date().toISOString() + ' ';
}
/**
* Invokes `util.format()` with the specified arguments and writes to stderr.
*/
function log(...args) {
return process.stderr.write(util.format(...args) + '\n');
}
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
if (namespaces) {
process.env.DEBUG = namespaces;
} else {
// If you set a process.env field to null or undefined, it gets cast to the
// string 'null' or 'undefined'. Just delete instead.
delete process.env.DEBUG;
}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
return process.env.DEBUG;
}
/**
* Init logic for `debug` instances.
*
* Create a new `inspectOpts` object in case `useColors` is set
* differently for a particular `debug` instance.
*/
function init(debug) {
debug.inspectOpts = {};
const keys = Object.keys(exports.inspectOpts);
for (let i = 0; i < keys.length; i++) {
debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
}
}
module.exports = require('./common')(exports);
const {formatters} = module.exports;
/**
* Map %o to `util.inspect()`, all on a single line.
*/
formatters.o = function (v) {
this.inspectOpts.colors = this.useColors;
return util.inspect(v, this.inspectOpts)
.split('\n')
.map(str => str.trim())
.join(' ');
};
/**
* Map %O to `util.inspect()`, allowing multiple lines if needed.
*/
formatters.O = function (v) {
this.inspectOpts.colors = this.useColors;
return util.inspect(v, this.inspectOpts);
};

@ -0,0 +1,5 @@
Copyright 2014 Julien Fontanet
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

@ -0,0 +1,112 @@
# make-error
[![Package Version](https://badgen.net/npm/v/make-error)](https://npmjs.org/package/make-error) [![Build Status](https://travis-ci.org/JsCommunity/make-error.png?branch=master)](https://travis-ci.org/JsCommunity/make-error) [![PackagePhobia](https://badgen.net/packagephobia/install/make-error)](https://packagephobia.now.sh/result?p=make-error) [![Latest Commit](https://badgen.net/github/last-commit/JsCommunity/make-error)](https://github.com/JsCommunity/make-error/commits/master)
> Make your own error types!
## Features
- Compatible Node & browsers
- `instanceof` support
- `error.name` & `error.stack` support
- compatible with [CSP](https://en.wikipedia.org/wiki/Content_Security_Policy) (i.e. no `eval()`)
## Installation
### Node & [Browserify](http://browserify.org/)/[Webpack](https://webpack.js.org/)
Installation of the [npm package](https://npmjs.org/package/make-error):
```
> npm install --save make-error
```
Then require the package:
```javascript
var makeError = require("make-error");
```
### Browser
You can directly use the build provided at [unpkg.com](https://unpkg.com):
```html
<script src="https://unpkg.com/make-error@1/dist/make-error.js"></script>
```
## Usage
### Basic named error
```javascript
var CustomError = makeError("CustomError");
// Parameters are forwarded to the super class (here Error).
throw new CustomError("a message");
```
### Advanced error class
```javascript
function CustomError(customValue) {
CustomError.super.call(this, "custom error message");
this.customValue = customValue;
}
makeError(CustomError);
// Feel free to extend the prototype.
CustomError.prototype.myMethod = function CustomError$myMethod() {
console.log("CustomError.myMethod (%s, %s)", this.code, this.message);
};
//-----
try {
throw new CustomError(42);
} catch (error) {
error.myMethod();
}
```
### Specialized error
```javascript
var SpecializedError = makeError("SpecializedError", CustomError);
throw new SpecializedError(42);
```
### Inheritance
> Best for ES2015+.
```javascript
import { BaseError } from "make-error";
class CustomError extends BaseError {
constructor() {
super("custom error message");
}
}
```
## Related
- [make-error-cause](https://www.npmjs.com/package/make-error-cause): Make your own error types, with a cause!
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/JsCommunity/make-error/issues)
you've encountered;
- fork and create a pull request.
## License
ISC © [Julien Fontanet](http://julien.isonoe.net)

@ -0,0 +1 @@
!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).makeError=f()}}(function(){return function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){return o(e[i][1][r]||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}({1:[function(require,module,exports){"use strict";var construct="undefined"!=typeof Reflect?Reflect.construct:void 0,defineProperty=Object.defineProperty,captureStackTrace=Error.captureStackTrace;function BaseError(message){void 0!==message&&defineProperty(this,"message",{configurable:!0,value:message,writable:!0});var cname=this.constructor.name;void 0!==cname&&cname!==this.name&&defineProperty(this,"name",{configurable:!0,value:cname,writable:!0}),captureStackTrace(this,this.constructor)}void 0===captureStackTrace&&(captureStackTrace=function(error){var container=new Error;defineProperty(error,"stack",{configurable:!0,get:function(){var stack=container.stack;return defineProperty(this,"stack",{configurable:!0,value:stack,writable:!0}),stack},set:function(stack){defineProperty(error,"stack",{configurable:!0,value:stack,writable:!0})}})}),BaseError.prototype=Object.create(Error.prototype,{constructor:{configurable:!0,value:BaseError,writable:!0}});var setFunctionName=function(){function setFunctionName(fn,name){return defineProperty(fn,"name",{configurable:!0,value:name})}try{var f=function(){};if(setFunctionName(f,"foo"),"foo"===f.name)return setFunctionName}catch(_){}}();(module.exports=function(constructor,super_){if(null==super_||super_===Error)super_=BaseError;else if("function"!=typeof super_)throw new TypeError("super_ should be a function");var name;if("string"==typeof constructor)name=constructor,constructor=void 0!==construct?function(){return construct(super_,arguments,this.constructor)}:function(){super_.apply(this,arguments)},void 0!==setFunctionName&&(setFunctionName(constructor,name),name=void 0);else if("function"!=typeof constructor)throw new TypeError("constructor should be either a string or a function");constructor.super_=constructor.super=super_;var properties={constructor:{configurable:!0,value:constructor,writable:!0}};return void 0!==name&&(properties.name={configurable:!0,value:name,writable:!0}),constructor.prototype=Object.create(super_.prototype,properties),constructor}).BaseError=BaseError},{}]},{},[1])(1)});

@ -0,0 +1,47 @@
/**
* Create a new error constructor instance.
*/
declare function makeError(
name: string
): makeError.Constructor<makeError.BaseError>;
/**
* Set the constructor prototype to `BaseError`.
*/
declare function makeError<T extends Error>(super_: {
new (...args: any[]): T;
}): makeError.Constructor<T & makeError.BaseError>;
/**
* Create a specialized error instance.
*/
declare function makeError<T extends Error, K>(
name: string | Function,
super_: K
): K & makeError.SpecializedConstructor<T>;
declare namespace makeError {
/**
* Use with ES2015+ inheritance.
*/
export class BaseError extends Error {
message: string;
name: string;
stack: string;
constructor(message?: string);
}
export interface Constructor<T> {
new (message?: string): T;
super_: any;
prototype: T;
}
export interface SpecializedConstructor<T> {
super_: any;
prototype: T;
}
}
export = makeError;

@ -0,0 +1,151 @@
// ISC @ Julien Fontanet
"use strict";
// ===================================================================
var construct = typeof Reflect !== "undefined" ? Reflect.construct : undefined;
var defineProperty = Object.defineProperty;
// -------------------------------------------------------------------
var captureStackTrace = Error.captureStackTrace;
if (captureStackTrace === undefined) {
captureStackTrace = function captureStackTrace(error) {
var container = new Error();
defineProperty(error, "stack", {
configurable: true,
get: function getStack() {
var stack = container.stack;
// Replace property with value for faster future accesses.
defineProperty(this, "stack", {
configurable: true,
value: stack,
writable: true,
});
return stack;
},
set: function setStack(stack) {
defineProperty(error, "stack", {
configurable: true,
value: stack,
writable: true,
});
},
});
};
}
// -------------------------------------------------------------------
function BaseError(message) {
if (message !== undefined) {
defineProperty(this, "message", {
configurable: true,
value: message,
writable: true,
});
}
var cname = this.constructor.name;
if (cname !== undefined && cname !== this.name) {
defineProperty(this, "name", {
configurable: true,
value: cname,
writable: true,
});
}
captureStackTrace(this, this.constructor);
}
BaseError.prototype = Object.create(Error.prototype, {
// See: https://github.com/JsCommunity/make-error/issues/4
constructor: {
configurable: true,
value: BaseError,
writable: true,
},
});
// -------------------------------------------------------------------
// Sets the name of a function if possible (depends of the JS engine).
var setFunctionName = (function() {
function setFunctionName(fn, name) {
return defineProperty(fn, "name", {
configurable: true,
value: name,
});
}
try {
var f = function() {};
setFunctionName(f, "foo");
if (f.name === "foo") {
return setFunctionName;
}
} catch (_) {}
})();
// -------------------------------------------------------------------
function makeError(constructor, super_) {
if (super_ == null || super_ === Error) {
super_ = BaseError;
} else if (typeof super_ !== "function") {
throw new TypeError("super_ should be a function");
}
var name;
if (typeof constructor === "string") {
name = constructor;
constructor =
construct !== undefined
? function() {
return construct(super_, arguments, this.constructor);
}
: function() {
super_.apply(this, arguments);
};
// If the name can be set, do it once and for all.
if (setFunctionName !== undefined) {
setFunctionName(constructor, name);
name = undefined;
}
} else if (typeof constructor !== "function") {
throw new TypeError("constructor should be either a string or a function");
}
// Also register the super constructor also as `constructor.super_` just
// like Node's `util.inherits()`.
//
// eslint-disable-next-line dot-notation
constructor.super_ = constructor["super"] = super_;
var properties = {
constructor: {
configurable: true,
value: constructor,
writable: true,
},
};
// If the name could not be set on the constructor, set it on the
// prototype.
if (name !== undefined) {
properties.name = {
configurable: true,
value: name,
writable: true,
};
}
constructor.prototype = Object.create(super_.prototype, properties);
return constructor;
}
exports = module.exports = makeError;
exports.BaseError = BaseError;

@ -0,0 +1,95 @@
{
"_args": [
[
"make-error@1.3.6",
"/home/vitaly/Dropbox/Coding/lv_font_conv"
]
],
"_from": "make-error@1.3.6",
"_id": "make-error@1.3.6",
"_inBundle": false,
"_integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"_location": "/make-error",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "make-error@1.3.6",
"name": "make-error",
"escapedName": "make-error",
"rawSpec": "1.3.6",
"saveSpec": null,
"fetchSpec": "1.3.6"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"_spec": "1.3.6",
"_where": "/home/vitaly/Dropbox/Coding/lv_font_conv",
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@isonoe.net"
},
"bugs": {
"url": "https://github.com/JsCommunity/make-error/issues"
},
"description": "Make your own error types!",
"devDependencies": {
"browserify": "^16.2.3",
"eslint": "^6.5.1",
"eslint-config-prettier": "^6.4.0",
"eslint-config-standard": "^14.1.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"husky": "^3.0.9",
"jest": "^24",
"prettier": "^1.14.3",
"uglify-js": "^3.3.2"
},
"files": [
"dist/",
"index.js",
"index.d.ts"
],
"homepage": "https://github.com/JsCommunity/make-error",
"husky": {
"hooks": {
"commit-msg": "npm run test"
}
},
"jest": {
"testEnvironment": "node"
},
"keywords": [
"create",
"custom",
"derive",
"error",
"errors",
"extend",
"extending",
"extension",
"factory",
"inherit",
"make",
"subclass"
],
"license": "ISC",
"main": "index.js",
"name": "make-error",
"repository": {
"type": "git",
"url": "git://github.com/JsCommunity/make-error.git"
},
"scripts": {
"dev-test": "jest --watch",
"format": "prettier --write '**'",
"prepublishOnly": "mkdir -p dist && browserify -s makeError index.js | uglifyjs -c > dist/make-error.js",
"pretest": "eslint --ignore-path .gitignore .",
"test": "jest"
},
"version": "1.3.6"
}

@ -0,0 +1,15 @@
# Changers Lorgs!
## 1.0
Full rewrite. Essentially a brand new module.
- Return a promise instead of taking a callback.
- Use native `fs.mkdir(path, { recursive: true })` when available.
- Drop support for outdated Node.js versions. (Technically still works on
Node.js v8, but only 10 and above are officially supported.)
## 0.x
Original and most widely used recursive directory creation implementation
in JavaScript, dating back to 2010.

@ -0,0 +1,21 @@
Copyright James Halliday (mail@substack.net) and Isaac Z. Schlueter (i@izs.me)
This project is free software released under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,68 @@
#!/usr/bin/env node
const usage = () => `
usage: mkdirp [DIR1,DIR2..] {OPTIONS}
Create each supplied directory including any necessary parent directories
that don't yet exist.
If the directory already exists, do nothing.
OPTIONS are:
-m<mode> If a directory needs to be created, set the mode as an octal
--mode=<mode> permission string.
-v --version Print the mkdirp version number
-h --help Print this helpful banner
-p --print Print the first directories created for each path provided
--manual Use manual implementation, even if native is available
`
const dirs = []
const opts = {}
let print = false
let dashdash = false
let manual = false
for (const arg of process.argv.slice(2)) {
if (dashdash)
dirs.push(arg)
else if (arg === '--')
dashdash = true
else if (arg === '--manual')
manual = true
else if (/^-h/.test(arg) || /^--help/.test(arg)) {
console.log(usage())
process.exit(0)
} else if (arg === '-v' || arg === '--version') {
console.log(require('../package.json').version)
process.exit(0)
} else if (arg === '-p' || arg === '--print') {
print = true
} else if (/^-m/.test(arg) || /^--mode=/.test(arg)) {
const mode = parseInt(arg.replace(/^(-m|--mode=)/, ''), 8)
if (isNaN(mode)) {
console.error(`invalid mode argument: ${arg}\nMust be an octal number.`)
process.exit(1)
}
opts.mode = mode
} else
dirs.push(arg)
}
const mkdirp = require('../')
const impl = manual ? mkdirp.manual : mkdirp
if (dirs.length === 0)
console.error(usage())
Promise.all(dirs.map(dir => impl(dir, opts)))
.then(made => print ? made.forEach(m => m && console.log(m)) : null)
.catch(er => {
console.error(er.message)
if (er.code)
console.error(' code: ' + er.code)
process.exit(1)
})

@ -0,0 +1,31 @@
const optsArg = require('./lib/opts-arg.js')
const pathArg = require('./lib/path-arg.js')
const {mkdirpNative, mkdirpNativeSync} = require('./lib/mkdirp-native.js')
const {mkdirpManual, mkdirpManualSync} = require('./lib/mkdirp-manual.js')
const {useNative, useNativeSync} = require('./lib/use-native.js')
const mkdirp = (path, opts) => {
path = pathArg(path)
opts = optsArg(opts)
return useNative(opts)
? mkdirpNative(path, opts)
: mkdirpManual(path, opts)
}
const mkdirpSync = (path, opts) => {
path = pathArg(path)
opts = optsArg(opts)
return useNativeSync(opts)
? mkdirpNativeSync(path, opts)
: mkdirpManualSync(path, opts)
}
mkdirp.sync = mkdirpSync
mkdirp.native = (path, opts) => mkdirpNative(pathArg(path), optsArg(opts))
mkdirp.manual = (path, opts) => mkdirpManual(pathArg(path), optsArg(opts))
mkdirp.nativeSync = (path, opts) => mkdirpNativeSync(pathArg(path), optsArg(opts))
mkdirp.manualSync = (path, opts) => mkdirpManualSync(pathArg(path), optsArg(opts))
module.exports = mkdirp

@ -0,0 +1,29 @@
const {dirname} = require('path')
const findMade = (opts, parent, path = undefined) => {
// we never want the 'made' return value to be a root directory
if (path === parent)
return Promise.resolve()
return opts.statAsync(parent).then(
st => st.isDirectory() ? path : undefined, // will fail later
er => er.code === 'ENOENT'
? findMade(opts, dirname(parent), parent)
: undefined
)
}
const findMadeSync = (opts, parent, path = undefined) => {
if (path === parent)
return undefined
try {
return opts.statSync(parent).isDirectory() ? path : undefined
} catch (er) {
return er.code === 'ENOENT'
? findMadeSync(opts, dirname(parent), parent)
: undefined
}
}
module.exports = {findMade, findMadeSync}

@ -0,0 +1,64 @@
const {dirname} = require('path')
const mkdirpManual = (path, opts, made) => {
opts.recursive = false
const parent = dirname(path)
if (parent === path) {
return opts.mkdirAsync(path, opts).catch(er => {
// swallowed by recursive implementation on posix systems
// any other error is a failure
if (er.code !== 'EISDIR')
throw er
})
}
return opts.mkdirAsync(path, opts).then(() => made || path, er => {
if (er.code === 'ENOENT')
return mkdirpManual(parent, opts)
.then(made => mkdirpManual(path, opts, made))
if (er.code !== 'EEXIST' && er.code !== 'EROFS')
throw er
return opts.statAsync(path).then(st => {
if (st.isDirectory())
return made
else
throw er
}, () => { throw er })
})
}
const mkdirpManualSync = (path, opts, made) => {
const parent = dirname(path)
opts.recursive = false
if (parent === path) {
try {
return opts.mkdirSync(path, opts)
} catch (er) {
// swallowed by recursive implementation on posix systems
// any other error is a failure
if (er.code !== 'EISDIR')
throw er
else
return
}
}
try {
opts.mkdirSync(path, opts)
return made || path
} catch (er) {
if (er.code === 'ENOENT')
return mkdirpManualSync(path, opts, mkdirpManualSync(parent, opts, made))
if (er.code !== 'EEXIST' && er.code !== 'EROFS')
throw er
try {
if (!opts.statSync(path).isDirectory())
throw er
} catch (_) {
throw er
}
}
}
module.exports = {mkdirpManual, mkdirpManualSync}

@ -0,0 +1,39 @@
const {dirname} = require('path')
const {findMade, findMadeSync} = require('./find-made.js')
const {mkdirpManual, mkdirpManualSync} = require('./mkdirp-manual.js')
const mkdirpNative = (path, opts) => {
opts.recursive = true
const parent = dirname(path)
if (parent === path)
return opts.mkdirAsync(path, opts)
return findMade(opts, path).then(made =>
opts.mkdirAsync(path, opts).then(() => made)
.catch(er => {
if (er.code === 'ENOENT')
return mkdirpManual(path, opts)
else
throw er
}))
}
const mkdirpNativeSync = (path, opts) => {
opts.recursive = true
const parent = dirname(path)
if (parent === path)
return opts.mkdirSync(path, opts)
const made = findMadeSync(opts, path)
try {
opts.mkdirSync(path, opts)
return made
} catch (er) {
if (er.code === 'ENOENT')
return mkdirpManualSync(path, opts)
else
throw er
}
}
module.exports = {mkdirpNative, mkdirpNativeSync}

@ -0,0 +1,23 @@
const { promisify } = require('util')
const fs = require('fs')
const optsArg = opts => {
if (!opts)
opts = { mode: 0o777, fs }
else if (typeof opts === 'object')
opts = { mode: 0o777, fs, ...opts }
else if (typeof opts === 'number')
opts = { mode: opts, fs }
else if (typeof opts === 'string')
opts = { mode: parseInt(opts, 8), fs }
else
throw new TypeError('invalid options argument')
opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir
opts.mkdirAsync = promisify(opts.mkdir)
opts.stat = opts.stat || opts.fs.stat || fs.stat
opts.statAsync = promisify(opts.stat)
opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync
opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync
return opts
}
module.exports = optsArg

@ -0,0 +1,29 @@
const platform = process.env.__TESTING_MKDIRP_PLATFORM__ || process.platform
const { resolve, parse } = require('path')
const pathArg = path => {
if (/\0/.test(path)) {
// simulate same failure that node raises
throw Object.assign(
new TypeError('path must be a string without null bytes'),
{
path,
code: 'ERR_INVALID_ARG_VALUE',
}
)
}
path = resolve(path)
if (platform === 'win32') {
const badWinChars = /[*|"<>?:]/
const {root} = parse(path)
if (badWinChars.test(path.substr(root.length))) {
throw Object.assign(new Error('Illegal characters in path.'), {
path,
code: 'EINVAL',
})
}
}
return path
}
module.exports = pathArg

@ -0,0 +1,10 @@
const fs = require('fs')
const version = process.env.__TESTING_MKDIRP_NODE_VERSION__ || process.version
const versArr = version.replace(/^v/, '').split('.')
const hasNative = +versArr[0] > 10 || +versArr[0] === 10 && +versArr[1] >= 12
const useNative = !hasNative ? () => false : opts => opts.mkdir === fs.mkdir
const useNativeSync = !hasNative ? () => false : opts => opts.mkdirSync === fs.mkdirSync
module.exports = {useNative, useNativeSync}

@ -0,0 +1,78 @@
{
"_args": [
[
"mkdirp@1.0.4",
"/home/vitaly/Dropbox/Coding/lv_font_conv"
]
],
"_from": "mkdirp@1.0.4",
"_id": "mkdirp@1.0.4",
"_inBundle": false,
"_integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"_location": "/mkdirp",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "mkdirp@1.0.4",
"name": "mkdirp",
"escapedName": "mkdirp",
"rawSpec": "1.0.4",
"saveSpec": null,
"fetchSpec": "1.0.4"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"_spec": "1.0.4",
"_where": "/home/vitaly/Dropbox/Coding/lv_font_conv",
"bin": {
"mkdirp": "bin/cmd.js"
},
"bugs": {
"url": "https://github.com/isaacs/node-mkdirp/issues"
},
"description": "Recursively mkdir, like `mkdir -p`",
"devDependencies": {
"require-inject": "^1.4.4",
"tap": "^14.10.7"
},
"engines": {
"node": ">=10"
},
"files": [
"bin",
"lib",
"index.js"
],
"homepage": "https://github.com/isaacs/node-mkdirp#readme",
"keywords": [
"mkdir",
"directory",
"make dir",
"make",
"dir",
"recursive",
"native"
],
"license": "MIT",
"main": "index.js",
"name": "mkdirp",
"repository": {
"type": "git",
"url": "git+https://github.com/isaacs/node-mkdirp.git"
},
"scripts": {
"postpublish": "git push origin --follow-tags",
"postversion": "npm publish",
"preversion": "npm test",
"snap": "tap",
"test": "tap"
},
"tap": {
"check-coverage": true,
"coverage-map": "map.js"
},
"version": "1.0.4"
}

@ -0,0 +1,266 @@
# mkdirp
Like `mkdir -p`, but in Node.js!
Now with a modern API and no\* bugs!
<small>\* may contain some bugs</small>
# example
## pow.js
```js
const mkdirp = require('mkdirp')
// return value is a Promise resolving to the first directory created
mkdirp('/tmp/foo/bar/baz').then(made =>
console.log(`made directories, starting with ${made}`))
```
Output (where `/tmp/foo` already exists)
```
made directories, starting with /tmp/foo/bar
```
Or, if you don't have time to wait around for promises:
```js
const mkdirp = require('mkdirp')
// return value is the first directory created
const made = mkdirp.sync('/tmp/foo/bar/baz')
console.log(`made directories, starting with ${made}`)
```
And now /tmp/foo/bar/baz exists, huzzah!
# methods
```js
const mkdirp = require('mkdirp')
```
## mkdirp(dir, [opts]) -> Promise<String | undefined>
Create a new directory and any necessary subdirectories at `dir` with octal
permission string `opts.mode`. If `opts` is a string or number, it will be
treated as the `opts.mode`.
If `opts.mode` isn't specified, it defaults to `0o777 &
(~process.umask())`.
Promise resolves to first directory `made` that had to be created, or
`undefined` if everything already exists. Promise rejects if any errors
are encountered. Note that, in the case of promise rejection, some
directories _may_ have been created, as recursive directory creation is not
an atomic operation.
You can optionally pass in an alternate `fs` implementation by passing in
`opts.fs`. Your implementation should have `opts.fs.mkdir(path, opts, cb)`
and `opts.fs.stat(path, cb)`.
You can also override just one or the other of `mkdir` and `stat` by
passing in `opts.stat` or `opts.mkdir`, or providing an `fs` option that
only overrides one of these.
## mkdirp.sync(dir, opts) -> String|null
Synchronously create a new directory and any necessary subdirectories at
`dir` with octal permission string `opts.mode`. If `opts` is a string or
number, it will be treated as the `opts.mode`.
If `opts.mode` isn't specified, it defaults to `0o777 &
(~process.umask())`.
Returns the first directory that had to be created, or undefined if
everything already exists.
You can optionally pass in an alternate `fs` implementation by passing in
`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)`
and `opts.fs.statSync(path)`.
You can also override just one or the other of `mkdirSync` and `statSync`
by passing in `opts.statSync` or `opts.mkdirSync`, or providing an `fs`
option that only overrides one of these.
## mkdirp.manual, mkdirp.manualSync
Use the manual implementation (not the native one). This is the default
when the native implementation is not available or the stat/mkdir
implementation is overridden.
## mkdirp.native, mkdirp.nativeSync
Use the native implementation (not the manual one). This is the default
when the native implementation is available and stat/mkdir are not
overridden.
# implementation
On Node.js v10.12.0 and above, use the native `fs.mkdir(p,
{recursive:true})` option, unless `fs.mkdir`/`fs.mkdirSync` has been
overridden by an option.
## native implementation
- If the path is a root directory, then pass it to the underlying
implementation and return the result/error. (In this case, it'll either
succeed or fail, but we aren't actually creating any dirs.)
- Walk up the path statting each directory, to find the first path that
will be created, `made`.
- Call `fs.mkdir(path, { recursive: true })` (or `fs.mkdirSync`)
- If error, raise it to the caller.
- Return `made`.
## manual implementation
- Call underlying `fs.mkdir` implementation, with `recursive: false`
- If error:
- If path is a root directory, raise to the caller and do not handle it
- If ENOENT, mkdirp parent dir, store result as `made`
- stat(path)
- If error, raise original `mkdir` error
- If directory, return `made`
- Else, raise original `mkdir` error
- else
- return `undefined` if a root dir, or `made` if set, or `path`
## windows vs unix caveat
On Windows file systems, attempts to create a root directory (ie, a drive
letter or root UNC path) will fail. If the root directory exists, then it
will fail with `EPERM`. If the root directory does not exist, then it will
fail with `ENOENT`.
On posix file systems, attempts to create a root directory (in recursive
mode) will succeed silently, as it is treated like just another directory
that already exists. (In non-recursive mode, of course, it fails with
`EEXIST`.)
In order to preserve this system-specific behavior (and because it's not as
if we can create the parent of a root directory anyway), attempts to create
a root directory are passed directly to the `fs` implementation, and any
errors encountered are not handled.
## native error caveat
The native implementation (as of at least Node.js v13.4.0) does not provide
appropriate errors in some cases (see
[nodejs/node#31481](https://github.com/nodejs/node/issues/31481) and
[nodejs/node#28015](https://github.com/nodejs/node/issues/28015)).
In order to work around this issue, the native implementation will fall
back to the manual implementation if an `ENOENT` error is encountered.
# choosing a recursive mkdir implementation
There are a few to choose from! Use the one that suits your needs best :D
## use `fs.mkdir(path, {recursive: true}, cb)` if:
- You wish to optimize performance even at the expense of other factors.
- You don't need to know the first dir created.
- You are ok with getting `ENOENT` as the error when some other problem is
the actual cause.
- You can limit your platforms to Node.js v10.12 and above.
- You're ok with using callbacks instead of promises.
- You don't need/want a CLI.
- You don't need to override the `fs` methods in use.
## use this module (mkdirp 1.x) if:
- You need to know the first directory that was created.
- You wish to use the native implementation if available, but fall back
when it's not.
- You prefer promise-returning APIs to callback-taking APIs.
- You want more useful error messages than the native recursive mkdir
provides (at least as of Node.js v13.4), and are ok with re-trying on
`ENOENT` to achieve this.
- You need (or at least, are ok with) a CLI.
- You need to override the `fs` methods in use.
## use [`make-dir`](http://npm.im/make-dir) if:
- You do not need to know the first dir created (and wish to save a few
`stat` calls when using the native implementation for this reason).
- You wish to use the native implementation if available, but fall back
when it's not.
- You prefer promise-returning APIs to callback-taking APIs.
- You are ok with occasionally getting `ENOENT` errors for failures that
are actually related to something other than a missing file system entry.
- You don't need/want a CLI.
- You need to override the `fs` methods in use.
## use mkdirp 0.x if:
- You need to know the first directory that was created.
- You need (or at least, are ok with) a CLI.
- You need to override the `fs` methods in use.
- You're ok with using callbacks instead of promises.
- You are not running on Windows, where the root-level ENOENT errors can
lead to infinite regress.
- You think vinyl just sounds warmer and richer for some weird reason.
- You are supporting truly ancient Node.js versions, before even the advent
of a `Promise` language primitive. (Please don't. You deserve better.)
# cli
This package also ships with a `mkdirp` command.
```
$ mkdirp -h
usage: mkdirp [DIR1,DIR2..] {OPTIONS}
Create each supplied directory including any necessary parent directories
that don't yet exist.
If the directory already exists, do nothing.
OPTIONS are:
-m<mode> If a directory needs to be created, set the mode as an octal
--mode=<mode> permission string.
-v --version Print the mkdirp version number
-h --help Print this helpful banner
-p --print Print the first directories created for each path provided
--manual Use manual implementation, even if native is available
```
# install
With [npm](http://npmjs.org) do:
```
npm install mkdirp
```
to get the library locally, or
```
npm install -g mkdirp
```
to get the command everywhere, or
```
npx mkdirp ...
```
to run the command without installing it globally.
# platform support
This module works on node v8, but only v10 and above are officially
supported, as Node v8 reached its LTS end of life 2020-01-01, which is in
the past, as of this writing.
# license
MIT

@ -0,0 +1,162 @@
/**
* Helpers.
*/
var s = 1000;
var m = s * 60;
var h = m * 60;
var d = h * 24;
var w = d * 7;
var y = d * 365.25;
/**
* Parse or format the given `val`.
*
* Options:
*
* - `long` verbose formatting [false]
*
* @param {String|Number} val
* @param {Object} [options]
* @throws {Error} throw an error if val is not a non-empty string or a number
* @return {String|Number}
* @api public
*/
module.exports = function(val, options) {
options = options || {};
var type = typeof val;
if (type === 'string' && val.length > 0) {
return parse(val);
} else if (type === 'number' && isFinite(val)) {
return options.long ? fmtLong(val) : fmtShort(val);
}
throw new Error(
'val is not a non-empty string or a valid number. val=' +
JSON.stringify(val)
);
};
/**
* Parse the given `str` and return milliseconds.
*
* @param {String} str
* @return {Number}
* @api private
*/
function parse(str) {
str = String(str);
if (str.length > 100) {
return;
}
var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
str
);
if (!match) {
return;
}
var n = parseFloat(match[1]);
var type = (match[2] || 'ms').toLowerCase();
switch (type) {
case 'years':
case 'year':
case 'yrs':
case 'yr':
case 'y':
return n * y;
case 'weeks':
case 'week':
case 'w':
return n * w;
case 'days':
case 'day':
case 'd':
return n * d;
case 'hours':
case 'hour':
case 'hrs':
case 'hr':
case 'h':
return n * h;
case 'minutes':
case 'minute':
case 'mins':
case 'min':
case 'm':
return n * m;
case 'seconds':
case 'second':
case 'secs':
case 'sec':
case 's':
return n * s;
case 'milliseconds':
case 'millisecond':
case 'msecs':
case 'msec':
case 'ms':
return n;
default:
return undefined;
}
}
/**
* Short format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function fmtShort(ms) {
var msAbs = Math.abs(ms);
if (msAbs >= d) {
return Math.round(ms / d) + 'd';
}
if (msAbs >= h) {
return Math.round(ms / h) + 'h';
}
if (msAbs >= m) {
return Math.round(ms / m) + 'm';
}
if (msAbs >= s) {
return Math.round(ms / s) + 's';
}
return ms + 'ms';
}
/**
* Long format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function fmtLong(ms) {
var msAbs = Math.abs(ms);
if (msAbs >= d) {
return plural(ms, msAbs, d, 'day');
}
if (msAbs >= h) {
return plural(ms, msAbs, h, 'hour');
}
if (msAbs >= m) {
return plural(ms, msAbs, m, 'minute');
}
if (msAbs >= s) {
return plural(ms, msAbs, s, 'second');
}
return ms + ' ms';
}
/**
* Pluralization helper.
*/
function plural(ms, msAbs, n, name) {
var isPlural = msAbs >= n * 1.5;
return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
}

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Zeit, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,72 @@
{
"_args": [
[
"ms@2.1.2",
"/home/vitaly/Dropbox/Coding/lv_font_conv"
]
],
"_from": "ms@2.1.2",
"_id": "ms@2.1.2",
"_inBundle": false,
"_integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"_location": "/ms",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "ms@2.1.2",
"name": "ms",
"escapedName": "ms",
"rawSpec": "2.1.2",
"saveSpec": null,
"fetchSpec": "2.1.2"
},
"_requiredBy": [
"/debug"
],
"_resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"_spec": "2.1.2",
"_where": "/home/vitaly/Dropbox/Coding/lv_font_conv",
"bugs": {
"url": "https://github.com/zeit/ms/issues"
},
"description": "Tiny millisecond conversion utility",
"devDependencies": {
"eslint": "4.12.1",
"expect.js": "0.3.1",
"husky": "0.14.3",
"lint-staged": "5.0.0",
"mocha": "4.0.1"
},
"eslintConfig": {
"extends": "eslint:recommended",
"env": {
"node": true,
"es6": true
}
},
"files": [
"index.js"
],
"homepage": "https://github.com/zeit/ms#readme",
"license": "MIT",
"lint-staged": {
"*.js": [
"npm run lint",
"prettier --single-quote --write",
"git add"
]
},
"main": "./index",
"name": "ms",
"repository": {
"type": "git",
"url": "git+https://github.com/zeit/ms.git"
},
"scripts": {
"lint": "eslint lib/* bin/*",
"precommit": "lint-staged",
"test": "mocha tests.js"
},
"version": "2.1.2"
}

@ -0,0 +1,60 @@
# ms
[![Build Status](https://travis-ci.org/zeit/ms.svg?branch=master)](https://travis-ci.org/zeit/ms)
[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit)
Use this package to easily convert various time formats to milliseconds.
## Examples
```js
ms('2 days') // 172800000
ms('1d') // 86400000
ms('10h') // 36000000
ms('2.5 hrs') // 9000000
ms('2h') // 7200000
ms('1m') // 60000
ms('5s') // 5000
ms('1y') // 31557600000
ms('100') // 100
ms('-3 days') // -259200000
ms('-1h') // -3600000
ms('-200') // -200
```
### Convert from Milliseconds
```js
ms(60000) // "1m"
ms(2 * 60000) // "2m"
ms(-3 * 60000) // "-3m"
ms(ms('10 hours')) // "10h"
```
### Time Format Written-Out
```js
ms(60000, { long: true }) // "1 minute"
ms(2 * 60000, { long: true }) // "2 minutes"
ms(-3 * 60000, { long: true }) // "-3 minutes"
ms(ms('10 hours'), { long: true }) // "10 hours"
```
## Features
- Works both in [Node.js](https://nodejs.org) and in the browser
- If a number is supplied to `ms`, a string with a unit is returned
- If a string that contains the number is supplied, it returns it as a number (e.g.: it returns `100` for `'100'`)
- If you pass a string with a number and a valid unit, the number of equivalent milliseconds is returned
## Related Packages
- [ms.macro](https://github.com/knpwrs/ms.macro) - Run `ms` as a macro at build-time.
## Caught a Bug?
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
2. Link the package to the global module directory: `npm link`
3. Within the module you want to test your local development instance of ms, just link it to the dependencies: `npm link ms`. Instead of the default one from npm, Node.js will now use your clone of ms!
As always, you can run the tests using: `npm test`

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 Frederik De Bleser
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,313 @@
# opentype.js &middot; [![Build Status](https://img.shields.io/travis/opentypejs/opentype.js.svg?style=flat-square)](https://travis-ci.org/opentypejs/opentype.js) [![npm](https://img.shields.io/npm/v/opentype.js.svg?style=flat-square)](https://www.npmjs.com/package/opentype.js) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/opentypejs/opentype.js/blob/master/LICENSE) [![david-dm](https://david-dm.org/opentypejs/opentype.js.svg)](https://david-dm.org/opentypejs/opentype.js) [![Gitter](https://badges.gitter.im/opentypejs/opentype.js.svg)](https://gitter.im/opentypejs/opentype.js)
opentype.js is a JavaScript parser and writer for TrueType and OpenType fonts.
It gives you access to the <strong>letterforms</strong> of text from the browser or Node.js.
See [https://opentype.js.org/](https://opentype.js.org/) for a live demo.
Features
========
* Create a bézier path out of a piece of text.
* Support for composite glyphs (accented letters).
* Support for WOFF, OTF, TTF (both with TrueType `glyf` and PostScript `cff` outlines)
* Support for kerning (Using GPOS or the kern table).
* Support for ligatures.
* Support for TrueType font hinting.
* Support arabic text rendering (See issue #364 & PR #359 #361)
* A low memory mode is available as an option (see #329)
* Runs in the browser and Node.js.
Installation
============
### Using [npm](http://npmjs.org/) package manager
npm install opentype.js
const opentype = require('opentype.js');
import opentype from 'opentype.js'
import { load } from 'opentype.js'
Using TypeScript? [See this example](examples/typescript)
Note: OpenType.js uses ES6-style imports, so if you want to edit it and debug it in Node.js run `npm run build` first and use `npm run watch` to automatically rebuild when files change.
### Directly
[Download the latest ZIP](https://github.com/opentypejs/opentype.js/archive/master.zip) and grab the files in the `dist`
folder. These are compiled.
### Using via a CDN
To use via a CDN, include the following code in your html:
<script src="https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js"></script>
### Using Bower (Deprecated [see official post](https://bower.io/blog/2017/how-to-migrate-away-from-bower/))
To install using [Bower](https://bower.io/), enter the following command in your project directory:
bower install opentype.js
You can then include them in your scripts using:
<script src="/bower_components/opentype.js/dist/opentype.js"></script>
API
===
### Loading a font
![OpenType.js example Hello World](https://raw.github.com/opentypejs/opentype.js/master/g/hello-world.png)
Use `opentype.load(url, callback)` to load a font from a URL. Since this method goes out the network, it is asynchronous.
The callback gets `(err, font)` where `font` is a `Font` object. Check if the `err` is null before using the font.
```javascript
opentype.load('fonts/Roboto-Black.ttf', function(err, font) {
if (err) {
alert('Font could not be loaded: ' + err);
} else {
// Now let's display it on a canvas with id "canvas"
const ctx = document.getElementById('canvas').getContext('2d');
// Construct a Path object containing the letter shapes of the given text.
// The other parameters are x, y and fontSize.
// Note that y is the position of the baseline.
const path = font.getPath('Hello, World!', 0, 150, 72);
// If you just want to draw the text you can also use font.draw(ctx, text, x, y, fontSize).
path.draw(ctx);
}
});
```
You can also use `es6 async/await` syntax to load your fonts
```javascript
async function make(){
const font = await opentype.load('fonts/Roboto-Black.ttf');
const path = font.getPath('Hello, World!', 0, 150, 72);
console.log(path);
}
```
If you already have an `ArrayBuffer`, you can use `opentype.parse(buffer)` to parse the buffer. This method always
returns a Font, but check `font.supported` to see if the font is in a supported format. (Fonts can be marked unsupported
if they have encoding tables we can't read).
const font = opentype.parse(myBuffer);
### Loading a font synchronously (Node.js)
Use `opentype.loadSync(url)` to load a font from a file and return a `Font` object.
Throws an error if the font could not be parsed. This only works in Node.js.
const font = opentype.loadSync('fonts/Roboto-Black.ttf');
### Writing a font
Once you have a `Font` object (either by using `opentype.load` or by creating a new one from scratch) you can write it
back out as a binary file.
In the browser, you can use `Font.download()` to instruct the browser to download a binary .OTF file. The name is based
on the font name.
```javascript
// Create the bézier paths for each of the glyphs.
// Note that the .notdef glyph is required.
const notdefGlyph = new opentype.Glyph({
name: '.notdef',
unicode: 0,
advanceWidth: 650,
path: new opentype.Path()
});
const aPath = new opentype.Path();
aPath.moveTo(100, 0);
aPath.lineTo(100, 700);
// more drawing instructions...
const aGlyph = new opentype.Glyph({
name: 'A',
unicode: 65,
advanceWidth: 650,
path: aPath
});
const glyphs = [notdefGlyph, aGlyph];
const font = new opentype.Font({
familyName: 'OpenTypeSans',
styleName: 'Medium',
unitsPerEm: 1000,
ascender: 800,
descender: -200,
glyphs: glyphs});
font.download();
```
If you want to inspect the font, use `font.toTables()` to generate an object showing the data structures that map
directly to binary values. If you want to get an `ArrayBuffer`, use `font.toArrayBuffer()`.
### The Font object
A Font represents a loaded OpenType font file. It contains a set of glyphs and methods to draw text on a drawing context, or to get a path representing the text.
* `glyphs`: an indexed list of Glyph objects.
* `unitsPerEm`: X/Y coordinates in fonts are stored as integers. This value determines the size of the grid. Common values are 2048 and 4096.
* `ascender`: Distance from baseline of highest ascender. In font units, not pixels.
* `descender`: Distance from baseline of lowest descender. In font units, not pixels.
#### `Font.getPath(text, x, y, fontSize, options)`
Create a Path that represents the given text.
* `x`: Horizontal position of the beginning of the text. (default: 0)
* `y`: Vertical position of the *baseline* of the text. (default: 0)
* `fontSize`: Size of the text in pixels (default: 72).
Options is an optional object containing:
* `kerning`: if true takes kerning information into account (default: true)
* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature.
Currently only ligature features "liga" and "rlig" are supported (default: true).
* `hinting`: if true uses TrueType font hinting if available (default: false).
_Note: there is also `Font.getPaths` with the same arguments which returns a list of Paths._
#### `Font.draw(ctx, text, x, y, fontSize, options)`
Create a Path that represents the given text.
* `ctx`: A 2D drawing context, like Canvas.
* `x`: Horizontal position of the beginning of the text. (default: 0)
* `y`: Vertical position of the *baseline* of the text. (default: 0)
* `fontSize`: Size of the text in pixels (default: 72).
Options is an optional object containing:
* `kerning`: if true takes kerning information into account (default: true)
* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature.
Currently only ligature features "liga" and "rlig" are supported (default: true).
* `hinting`: if true uses TrueType font hinting if available (default: false).
#### `Font.drawPoints(ctx, text, x, y, fontSize, options)`
Draw the points of all glyphs in the text. On-curve points will be drawn in blue, off-curve points will be drawn in red. The arguments are the same as `Font.draw`.
#### `Font.drawMetrics(ctx, text, x, y, fontSize, options)`
Draw lines indicating important font measurements for all glyphs in the text.
Black lines indicate the origin of the coordinate system (point 0,0).
Blue lines indicate the glyph bounding box.
Green line indicates the advance width of the glyph.
#### `Font.stringToGlyphs(string)`
Convert the string to a list of glyph objects.
Note that there is no strict 1-to-1 correspondence between the string and glyph list due to
possible substitutions such as ligatures. The list of returned glyphs can be larger or smaller than the length of the given string.
#### `Font.charToGlyph(char)`
Convert the character to a `Glyph` object. Returns null if the glyph could not be found. Note that this function assumes that there is a one-to-one mapping between the given character and a glyph; for complex scripts this might not be the case.
#### `Font.getKerningValue(leftGlyph, rightGlyph)`
Retrieve the value of the [kerning pair](https://en.wikipedia.org/wiki/Kerning) between the left glyph (or its index) and the right glyph (or its index). If no kerning pair is found, return 0. The kerning value gets added to the advance width when calculating the spacing between glyphs.
#### `Font.getAdvanceWidth(text, fontSize, options)`
Returns the advance width of a text.
This is something different than Path.getBoundingBox() as for example a
suffixed whitespace increases the advancewidth but not the bounding box
or an overhanging letter like a calligraphic 'f' might have a quite larger
bounding box than its advance width.
This corresponds to canvas2dContext.measureText(text).width
* `fontSize`: Size of the text in pixels (default: 72).
* `options`: See Font.getPath
#### The Glyph object
A Glyph is an individual mark that often corresponds to a character. Some glyphs, such as ligatures, are a combination of many characters. Glyphs are the basic building blocks of a font.
* `font`: A reference to the `Font` object.
* `name`: The glyph name (e.g. "Aring", "five")
* `unicode`: The primary unicode value of this glyph (can be `undefined`).
* `unicodes`: The list of unicode values for this glyph (most of the time this will be 1, can also be empty).
* `index`: The index number of the glyph.
* `advanceWidth`: The width to advance the pen when drawing this glyph.
* `xMin`, `yMin`, `xMax`, `yMax`: The bounding box of the glyph.
* `path`: The raw, unscaled path of the glyph.
##### `Glyph.getPath(x, y, fontSize)`
Get a scaled glyph Path object we can draw on a drawing context.
* `x`: Horizontal position of the glyph. (default: 0)
* `y`: Vertical position of the *baseline* of the glyph. (default: 0)
* `fontSize`: Font size in pixels (default: 72).
##### `Glyph.getBoundingBox()`
Calculate the minimum bounding box for the unscaled path of the given glyph. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2.
If the glyph has no points (e.g. a space character), all coordinates will be zero.
##### `Glyph.draw(ctx, x, y, fontSize)`
Draw the glyph on the given context.
* `ctx`: The drawing context.
* `x`: Horizontal position of the glyph. (default: 0)
* `y`: Vertical position of the *baseline* of the glyph. (default: 0)
* `fontSize`: Font size, in pixels (default: 72).
##### `Glyph.drawPoints(ctx, x, y, fontSize)`
Draw the points of the glyph on the given context.
On-curve points will be drawn in blue, off-curve points will be drawn in red.
The arguments are the same as `Glyph.draw`.
##### `Glyph.drawMetrics(ctx, x, y, fontSize)`
Draw lines indicating important font measurements for all glyphs in the text.
Black lines indicate the origin of the coordinate system (point 0,0).
Blue lines indicate the glyph bounding box.
Green line indicates the advance width of the glyph.
The arguments are the same as `Glyph.draw`.
### The Path object
Once you have a path through `Font.getPath` or `Glyph.getPath`, you can use it.
* `commands`: The path commands. Each command is a dictionary containing a type and coordinates. See below for examples.
* `fill`: The fill color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: 'black')
* `stroke`: The stroke color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: `null`: the path will not be stroked)
* `strokeWidth`: The line thickness of the `Path`. (default: 1, but since the `stroke` is null no stroke will be drawn)
##### `Path.draw(ctx)`
Draw the path on the given 2D context. This uses the `fill`, `stroke` and `strokeWidth` properties of the `Path` object.
* `ctx`: The drawing context.
##### `Path.getBoundingBox()`
Calculate the minimum bounding box for the given path. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2.
If the path is empty (e.g. a space character), all coordinates will be zero.
##### `Path.toPathData(decimalPlaces)`
Convert the Path to a string of path data instructions.
See https://www.w3.org/TR/SVG/paths.html#PathData
* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2)
##### `Path.toSVG(decimalPlaces)`
Convert the path to a SVG &lt;path&gt; element, as a string.
* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2)
#### Path commands
* **Move To**: Move to a new position. This creates a new contour. Example: `{type: 'M', x: 100, y: 200}`
* **Line To**: Draw a line from the previous position to the given coordinate. Example: `{type: 'L', x: 100, y: 200}`
* **Curve To**: Draw a bézier curve from the current position to the given coordinate. Example: `{type: 'C', x1: 0, y1: 50, x2: 100, y2: 200, x: 100, y: 200}`
* **Quad To**: Draw a quadratic bézier curve from the current position to the given coordinate. Example: `{type: 'Q', x1: 0, y1: 50, x: 100, y: 200}`
* **Close**: Close the path. If stroked, this will draw a line from the first to the last point of the contour. Example: `{type: 'Z'}`
## Versioning
We use [SemVer](https://semver.org/) for versioning.
## License
MIT
Thanks
======
I would like to acknowledge the work of others without which opentype.js wouldn't be possible:
* [pdf.js](https://mozilla.github.io/pdf.js/): for an awesome implementation of font parsing in the browser.
* [FreeType](https://www.freetype.org/): for the nitty-gritty details and filling in the gaps when the spec was incomplete.
* [ttf.js](https://ynakajima.github.io/ttf.js/demo/glyflist/): for hints about the TrueType parsing code.
* [CFF-glyphlet-fonts](https://pomax.github.io/CFF-glyphlet-fonts/): for a great explanation/implementation of CFF font writing.
* [tiny-inflate](https://github.com/foliojs/tiny-inflate): for WOFF decompression.
* [Microsoft Typography](https://docs.microsoft.com/en-us/typography/opentype/spec/otff): the go-to reference for all things OpenType.
* [Adobe Compact Font Format spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf) and the [Adobe Type 2 Charstring spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf): explains the data structures and commands for the CFF glyph format.
* All contributing authors mentioned in the [AUTHORS](https://github.com/opentypejs/opentype.js/blob/master/AUTHORS.md) file.

@ -0,0 +1,267 @@
1.3.3 (April 20, 2020)
=====================
* fix GlyphOptions with falsy values (#430)
1.3.2 (April 20, 2020)
=====================
* Re-export named exports with a default export and add a TypeScript import example
* 1.3.1 (April 13, 2020)
=====================
* Revert Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369)
1.3.0 (April 13, 2020)
=====================
* Forward os2 Table attributs during font construction (#422)
* Add default export
1.2.1 (April 13, 2020)
=====================
* Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369)
* Fix use of Promise / async/await in the load function (#427)
* Fix a bug for unsupported SUBSTITUTIONS #403
1.2.0 (April 13, 2020)
=====================
* Fix issue #385, merge default options with user options (#386)
* Adds support for browser Async/Await for .load() (#389)
* Introduce ES6 module build (#391)
* Fix test in featureQuery
* Remove Node 4 from Travis (#392)
* Update dependencies & build dist files
1.1.0 (May 1, 2019)
=====================
* Support reading GSUB Single substitution format 1 (PR #382) (thanks @solomancode!)
1.0.1 (April 19, 2019)
=====================
* Fix error if defaultLangSys is undefined (Issue #378)
1.0.0 (April 17, 2019)
=====================
* Render arabic rtl text properly (PR #361, partial fix of #364) (thanks @solomancode!)
* #361 introduced a breaking change to `Font.prototype.defaultRenderOptions`
Before
```js
Font.prototype.defaultRenderOptions = {
kerning: true,
features: {
liga: true,
rlig: true
}
};
```
Now
```js
Font.prototype.defaultRenderOptions = {
kerning: true,
features: [
/**
* these 4 features are required to render Arabic text properly
* and shouldn't be turned off when rendering arabic text.
*/
{ script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] },
{ script: 'latn', tags: ['liga', 'rlig'] }
]
};
```
Also as this project is now using SemVer, the breaking change required a new major version, 1.0.0!
0.12.0 (April 17, 2019)
=====================
* Fix Glyph.getPath() issue (PR #362, fixes #363) (thanks @solomancode!)
* Add lowMemory mode (PR #329) (thanks @debussy2k!)
* Update README (PR #377) (thanks @jolg42!)
0.11.0 (October 22, 2018)
=====================
* Support Arabic text rendering (PR #359, fixes #46) (thanks @solomancode!)
0.10.0 (August 14, 2018)
=====================
* font.download(): use window.URL instead of window.requestFileSystem, which works on a larger set of browsers : Chrome (32+), Opera (19+), Firefox (26+), Safari (7.1+), and all of Edge.
0.9.0 (June 21, 2018)
=====================
* Update/Migrate rollup, update all dependencies, add package-lock.json and fix circular dependency (thanks @jolg42!)
* Parse cmap table with platform id 0 as well (PR #350, fixes #348) (thanks @moyogo!)
* Prevent auto-generated postScriptName from containing whitespace (#339) (thanks @mqudsi!)
* Support non-Basic-Multilingual-Plane (BMP) characters (#338) (thanks @antonytse!)
* GPOS: display correct error message in some cases of malformed data (#336) (thanks @fpirsch!)
* Restore simple GPOS kerning in font.getKerningValue (#335) (thanks @fpirsch!)
* Fix duplicated lineTo when using `getPath` (#328) (thanks @jolg42!)
* Change example generate-font-node.js to be compatible with any Node.js version (thanks @jolg42!)
0.8.0 (March 6, 2018)
=====================
* Fix loading font file on Android devices (thanks @maoamid!).
* Fix loading fonts from a local source (file://data/... for Android for example (thanks @IntuilabGit!).
* Fixing 2 issues when hinting "mutlu.ttf" (thanks @axkibe!).
* Add some support for OpenType font variations (thanks @taylorb-monotype!).
* Make cmap table format 12 if needed (thanks @Jolg42!).
* Enable uglify's mangle and compress optimizations for a ~30% smaller minified file. (thanks @lojjic & @Jolg42!).
* Better parsing of NULL pointers (thanks @fpirsch!).
* Fix bad path init (empty glyphs) (thanks @fpirsch!).
* Rewrite GPOS parsing (thanks @fpirsch!).
* Roboto-Black.ttf updated (thanks @Jolg42!).
0.7.3 (July 18, 2017)
=====================
* Fix "Object x already has key" error in Safari (thanks @neiltron!).
* Fixed a bug where Font.getPaths() didn't pass options (thanks @keeslinp!).
0.7.2 (June 7, 2017)
====================
* WOFF fonts with cvt tables now parse correctly.
* Migrated to ES6 modules and let/const.
* Use Rollup to bundle the JavaScript.
0.7.1 (Apr 25, 2017)
====================
* Auto-generated glyph IDs (CID-keyed fonts) are now prefixed with "gid", e.g. "gid42".
* Fix ligature substitution for fonts with coverage table format 2.
* Better error messages when no valid cmap is found.
0.7.0 (Apr 25, 2017)
====================
* Add font hinting (thanks @axkibe!)
* Add support for CID-keyed fonts, thanks to @tshinnic.
* TrueType fonts with signature 'true' or 'typ1' are also supported.
* Fixing rounding issues.
* Add GSUB and kern output in font-inspector.
* Add font loading error callback.
* Dev server turns browser caching off.
* Add encoding support for variation adjustment deltas (thanks @brawer!).
0.6.9 (Jan 17, 2017)
====================
* Add ligature rendering (thanks @fpirsch!)
0.6.8 (Jan 9, 2017)
=========================
* Add a `getBoundingBox` method to the `Path` and `Glyph` objects.
0.6.7 (Jan 5, 2017)
=========================
* Add basic support for Mac OS X format kern tables.
0.6.6 (October 25, 2016)
=========================
* Add support for letter-spacing and tracking (thanks @lachmanski!).
* Fixed a bug in the nameToGlyph function.
0.6.5 (September 9, 2016)
=========================
* GSUB reading and writing by @fpirsch. This is still missing a user-friendly API.
* Add support for cmap table format 12, which enables support for Unicode characters outside of the 0x0 - 0xFFFF range.
* Better API documentation using [JSDoc](http://usejsdoc.org/).
* Accessing xMin/... metrics works before path load.
0.6.4 (June 30, 2016)
=========================
* Add X/Y scale options to compute a streched path of a glyph.
* Correct reading/writing of font timestamps.
* examples/generate-font-node.js now generates "full" Latin font.
* Add OS/2 value options for weight, width and fsSelection.
0.6.3 (May 10, 2016)
=========================
* Wrapped parseBuffer in a try/catch so it doesn't throw exceptions. Thanks @rBurgett!
* Fix a leaking global variable. Thanks @cuixiping!
0.6.2 (March 11, 2016)
=========================
* Improve table writing to support nested subtables. Thanks @fpirsch!
0.6.1 (February 20, 2016)
=========================
* Left side bearing is now correctly reported.
* Simplified code for including ascender / descender values.
0.6.0 (December 1, 2015)
========================
* Improvements to font writing: generated fonts now work properly on OS X.
* When creating a new font, ascender and descender are now required.
0.5.1 (October 26, 2015)
========================
* Add `Font.getPaths()` which returns a list of paths.
0.5.0 (October 6, 2015)
=======================
* Read support for WOFF.
0.4.11 (September 27, 2015)
===========================
* Fix issue with loading of TrueType composite glyphs.
* Fix issue with missing hmtx values.
* Sensible getMetrics() values for empty glyphs (e.g. space).
0.4.10 (July 30, 2015)
======================
* Add loadSync method for Node.js.
* Unit tests for basic types and tables.
* Implement MACSTRING codec.
* Support multilingual names.
* Handle names of font variation axes and instances.
0.4.9 (June 23, 2015)
=====================
* Improve memory usage by deferring glyph / path loading. Thanks @Pomax!
* Put examples in the "examples" directory. Use the local web server to see them.
0.4.8 (June 3, 2015)
====================
* Fix an issue with writing out fonts that have an UPM != 1000.
0.4.6 (March 26, 2015)
======================
* Fix issues with exporting/subsetting TrueType fonts.
* Improve validness of exported fonts.
* Empty paths (think: space) no longer contain a single closePath command.
* Fix issues with exporting fonts with TrueType half-point values.
* Expose the internal byte parsing algorithms as opentype._parse.
0.4.5 (March 10, 2015)
======================
* Add support for writing quad curves.
* Add support for CFF flex operators.
* Close CFF subpaths.
0.4.4 (Dec 8, 2014)
===================
* Solve issues with Browserify.
0.4.3 (Nov 26, 2014)
====================
* Un-break node.js support.
0.4.2 (Nov 24, 2014)
====================
* 2x speedup when writing fonts, thanks @louisremi!
0.4.1 (Nov 10, 2014)
====================
* Fix bug that prevented `npm install`.
0.4.0 (Nov 10, 2014)
====================
* Add support for font writing.
0.3.0 (Jun 10, 2014)
====================
* Support for GPOS kerning, which works in both PostScript and OpenType.
* Big performance improvements.
* The font and glyph inspector can visually debug a font.
0.2.0 (Feb 7, 2014)
===================
* Support for reading PostScript fonts.
0.1.0 (Sep 27, 2013)
====================
* Initial release.
* Supports reading TrueType CFF fonts.

@ -0,0 +1,84 @@
#!/usr/bin/env node
/* eslint no-console: off */
import fs from 'fs';
import path from 'path';
import { load } from '../src/opentype';
// Print out information about the font on the console.
function printFontInfo(font) {
console.log(' glyphs:', font.glyphs.length);
console.log(' kerning pairs (kern table):', Object.keys(font.kerningPairs).length);
console.log(' kerning pairs (GPOS table):', (font.getGposKerningValue) ? 'yes' : 'no');
}
// Recursively walk a directory and execute the function for every file.
function walk(dir, fn) {
var files, i, file;
files = fs.readdirSync(dir);
for (i = 0; i < files.length; i += 1) {
file = files[i];
var fullName = path.join(dir, file);
var stat = fs.statSync(fullName);
if (stat.isFile()) {
fn(fullName);
} else if (stat.isDirectory()) {
walk(fullName, fn);
}
}
}
// Print out usage information.
function printUsage() {
console.log('Usage: ot command [dir|file]');
console.log();
console.log('Commands:');
console.log();
console.log(' info Get information of specified font or fonts in the specified directory.');
console.log();
}
function fileInfo(file) {
load(file, function(err, font) {
console.log(path.basename(file));
if (err) {
console.log(' (Error: ' + err + ')');
} else if (!font.supported) {
console.log(' (Unsupported)');
} else {
printFontInfo(font);
}
});
}
function recursiveInfo(fontDirectory) {
walk(fontDirectory, function(file) {
var ext = path.extname(file).toLowerCase();
if (ext === '.ttf' || ext === '.otf') {
fileInfo(file);
}
});
}
if (process.argv.length < 3) {
printUsage();
} else {
var command = process.argv[2];
if (command === 'info') {
var fontpath = process.argv.length === 3 ? '.' : process.argv[3];
if (fs.existsSync(fontpath)) {
var ext = path.extname(fontpath).toLowerCase();
if (fs.statSync(fontpath).isDirectory()) {
recursiveInfo(fontpath);
} else if (ext === '.ttf' || ext === '.otf') {
fileInfo(fontpath);
} else {
printUsage();
}
} else {
console.log('Path not found');
}
} else {
printUsage();
}
}

@ -0,0 +1,64 @@
#!/usr/bin/env node
var fs = require('fs');
var http = require('http');
var path = require('path');
var rollup = require('rollup');
var rollupConfig = require('../rollup.config');
var CONTENT_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.png': 'image/png',
'.js': 'text/javascript',
'.ttf': 'font/otf',
'.otf': 'font/otf',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
};
http.createServer(function(req, res) {
var rewrite = '';
var url = req.url.substring(1);
if (url.length === 0) {
url = 'index.html';
rewrite = ' -> ' + url;
}
console.log('HTTP', req.url, rewrite);
var filePath = './' + url;
fs.readFile(filePath, function(err, data) {
if (err) {
res.writeHead(404, {'Content-Type': 'text/plain'});
res.end('Error: ' + err);
} else {
var contentType = CONTENT_TYPES[path.extname(filePath)] || 'text/plain';
res.writeHead(200, {
'Content-Type': contentType,
'Cache-Control': 'max-age=0'
});
res.end(data);
}
});
}).listen(8080);
console.log('Server running at http://localhost:8080/');
// Watch changes and rebundle
var watcher = rollup.watch(rollupConfig);
watcher.on('event', e => {
// event.code can be one of:
// START — the watcher is (re)starting
// BUNDLE_START — building an individual bundle
// BUNDLE_END — finished building a bundle
// END — finished building all bundles
// ERROR — encountered an error while bundling
// FATAL — encountered an unrecoverable error
if (e.code === 'BUNDLE_START') {
console.log('Bundling...');
} else if (e.code === 'BUNDLE_END') {
console.log('Bundled in ' + e.duration + 'ms.');
} else if (e.code === 'ERROR' || e.code === 'FATAL') {
console.error(e.error);
}
});

@ -0,0 +1,96 @@
#!/usr/bin/env node
// This is a command to test the text rendering compliance of OpenType.js.
// It is designed to operate with https://github.com/unicode-org/text-rendering-tests.
//
// Call it like this:
//
// ./bin/test-render --font=fonts/FiraSansOT-Medium.otf --testcase=TEST-1 --render=BALL
//
// The output will look like this:
//
// <?xml version="1.0" encoding="UTF-8"?>
// <svg version="1.1"
// xmlns="http://www.w3.org/2000/svg"
// xmlns:xlink="http://www.w3.org/1999/xlink"
// viewBox="0 -500 2230 1550">
// <symbol id="TEST-1.B" overflow="visible"><path d="M443 203C443 117 375 99 309 99L217 99L217 315L315 315C389 315 443 289 443 203ZM417 504C417 436 380 407 305 407L217 407L217 594L299 594C375 594 417 568 417 504ZM581 200C581 312 500 352 432 365L432 369C489 382 552 430 552 515C552 651 429 691 295 691L84 691L84 0L307 0C448 0 581 44 581 200Z"/></symbol>
// <symbol id="TEST-1.A" overflow="visible"><path d="M452 0L594 0L377 691L214 691L-5 0L133 0L177 160L408 160ZM291 581L295 581L383 260L202 260Z"/></symbol>
// <symbol id="TEST-1.L" overflow="visible"><path d="M478 0L493 108L217 108L217 691L84 691L84 0Z"/></symbol>
// <use xlink:href="#TEST-1.B" x="0" y="0"/>
// <use xlink:href="#TEST-1.A" x="625" y="0"/>
// <use xlink:href="#TEST-1.L" x="1214" y="0"/>
// <use xlink:href="#TEST-1.L" x="1722" y="0"/>
// </svg>
//
// When viewing the SVG, it will be upside-down (since glyphs are designed Y-up).
var opentype = require('../dist/opentype.js');
const SVG_FOOTER = `</svg>`;
function printUsage() {
console.log('Usage: test-render --font=filename.otf --testcase=TEST_NAME --render=TEXT_TO_RENDER');
console.log('This commands output the text to render as an SVG file.');
console.log();
}
let filename;
let testcase;
let textToRender;
for (let i = 0; i < process.argv.length; i++) {
const arg = process.argv[i];
if (arg.startsWith('--font=')) {
filename = arg.substring('--font='.length);
} else if (arg.startsWith('--testcase=')) {
testcase = arg.substring('--testcase='.length);
} else if (arg.startsWith('--render=')) {
textToRender = arg.substring('--render='.length);
}
}
if (filename === undefined || testcase === undefined || textToRender === undefined) {
printUsage();
process.exit(1);
}
function renderSVG() {
var font = opentype.loadSync(filename);
let svgSymbols = [];
let svgBody = [];
var glyphSet = new Set();
let x = 0;
const glyphs = font.stringToGlyphs(textToRender);
for (let i = 0; i < glyphs.length; i++) {
const glyph = glyphs[i];
const symbolId = testcase + '.' + glyph.name;
if (!glyphSet.has(glyph)) {
glyphSet.add(glyph);
const svgPath = glyph.path.toSVG();
svgSymbols.push(` <symbol id="${symbolId}" overflow="visible">${svgPath}</symbol>`);
}
svgBody.push(` <use xlink:href="#${symbolId}" x="${x}" y="0"/>`);
x += glyph.advanceWidth;
}
let minX = 0;
let minY = Math.round(font.descender);
let width = Math.round(x);
let height = Math.round(font.ascender - font.descender);
let svgHeader = `<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="${minX} ${minY} ${width} ${height}">`;
return svgHeader + svgSymbols.join('\n') + svgBody.join('\n') + SVG_FOOTER;
}
try {
var svg = renderSVG();
console.log(svg);
} catch(e) {
console.error(e.stack);
process.exit(1);
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,102 @@
{
"_args": [
[
"opentype.js@1.3.3",
"/home/vitaly/Dropbox/Coding/lv_font_conv"
]
],
"_from": "opentype.js@1.3.3",
"_id": "opentype.js@1.3.3",
"_inBundle": false,
"_integrity": "sha512-/qIY/+WnKGlPIIPhbeNjynfD2PO15G9lA/xqlX2bDH+4lc3Xz5GCQ68mqxj3DdUv6AJqCeaPvuAoH8mVL0zcuA==",
"_location": "/opentype.js",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "opentype.js@1.3.3",
"name": "opentype.js",
"escapedName": "opentype.js",
"rawSpec": "1.3.3",
"saveSpec": null,
"fetchSpec": "1.3.3"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.3.tgz",
"_spec": "1.3.3",
"_where": "/home/vitaly/Dropbox/Coding/lv_font_conv",
"author": {
"name": "Frederik De Bleser",
"email": "frederik@debleser.be"
},
"bin": {
"ot": "bin/ot"
},
"browser": {
"fs": false
},
"bugs": {
"url": "https://github.com/opentypejs/opentype.js/issues"
},
"dependencies": {
"string.prototype.codepointat": "^0.2.1",
"tiny-inflate": "^1.0.3"
},
"description": "OpenType font parser",
"devDependencies": {
"@babel/preset-env": "^7.9.5",
"buble": "^0.20.0",
"cross-env": "^7.0.2",
"jscs": "^3.0.7",
"jshint": "^2.11.0",
"mocha": "^7.1.1",
"reify": "^0.20.12",
"rollup": "^1.32.1",
"rollup-plugin-buble": "^0.19.8",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-license": "^0.9.0",
"rollup-plugin-node-resolve": "^5.2.0",
"uglify-js": "^3.8.1"
},
"engines": {
"node": ">= 8.0.0"
},
"files": [
"LICENSE",
"RELEASES.md",
"README.md",
"bin",
"dist",
"src"
],
"homepage": "https://github.com/opentypejs/opentype.js#readme",
"keywords": [
"graphics",
"fonts",
"font",
"opentype",
"otf",
"ttf",
"woff",
"type"
],
"license": "MIT",
"main": "dist/opentype.js",
"module": "dist/opentype.module.js",
"name": "opentype.js",
"repository": {
"type": "git",
"url": "git://github.com/opentypejs/opentype.js.git"
},
"scripts": {
"build": "rollup -c",
"dist": "npm run test && npm run build && npm run minify",
"minify": "uglifyjs --source-map \"url='opentype.min.js.map'\" --compress --mangle --output ./dist/opentype.min.js -- ./dist/opentype.js",
"start": "node ./bin/server.js",
"test": "mocha --require reify --recursive && jshint . && jscs .",
"watch": "rollup -c -w"
},
"version": "1.3.3"
}

@ -0,0 +1,20 @@
pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors
pngjs derived work Copyright (c) 2012 Kuba Niegowski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,433 @@
[![Build Status](https://travis-ci.com/lukeapage/pngjs.svg?branch=master)](https://travis-ci.com/lukeapage/pngjs) [![Build status](https://ci.appveyor.com/api/projects/status/qo5x8ayutr028108/branch/master?svg=true)](https://ci.appveyor.com/project/lukeapage/pngjs/branch/master) [![codecov](https://codecov.io/gh/lukeapage/pngjs/branch/master/graph/badge.svg)](https://codecov.io/gh/lukeapage/pngjs) [![npm version](https://badge.fury.io/js/pngjs.svg)](http://badge.fury.io/js/pngjs)
# pngjs
Simple PNG encoder/decoder for Node.js with no dependencies.
Based on the original [pngjs](https://github.com/niegowski/node-pngjs) with the follow enhancements.
- Support for reading 1,2,4 & 16 bit files
- Support for reading interlace files
- Support for reading `tTRNS` transparent colours
- Support for writing colortype 0 (grayscale), colortype 2 (RGB), colortype 4 (grayscale alpha) and colortype 6 (RGBA)
- Sync interface as well as async
- API compatible with pngjs and node-pngjs
Known lack of support for:
- Extended PNG e.g. Animation
- Writing in colortype 3 (indexed color)
# Table of Contents
- [Requirements](#requirements)
- [Comparison Table](#comparison-table)
- [Tests](#tests)
- [Installation](#installation)
- [Browser](#browser)
- [Example](#example)
- [Async API](#async-api)
- [Sync API](#sync-api)
- [Changelog](#changelog)
# Comparison Table
| Name | Forked From | Sync | Async | 16 Bit | 1/2/4 Bit | Interlace | Gamma | Encodes | Tested |
| ------------- | ----------- | ---- | ----- | ------ | --------- | --------- | ------ | ------- | ------ |
| pngjs | | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| node-png | pngjs | No | Yes | No | No | No | Hidden | Yes | Manual |
| png-coder | pngjs | No | Yes | Yes | No | No | Hidden | Yes | Manual |
| pngparse | | No | Yes | No | Yes | No | No | No | Yes |
| pngparse-sync | pngparse | Yes | No | No | Yes | No | No | No | Yes |
| png-async | | No | Yes | No | No | No | No | Yes | Yes |
| png-js | | No | Yes | No | No | No | No | No | No |
Native C++ node decoders:
- png
- png-sync (sync version of above)
- pixel-png
- png-img
# Tests
Tested using [PNG Suite](http://www.schaik.com/pngsuite/). We read every file into pngjs, output it in standard 8bit colour, synchronously and asynchronously, then compare the original with the newly saved images.
To run the tests, fetch the repo (tests are not distributed via npm) and install with `npm i`, run `npm test`.
The only thing not converted is gamma correction - this is because multiple vendors will do gamma correction differently, so the tests will have different results on different browsers.
# Installation
```
$ npm install pngjs --save
```
# Browser
The package has been build with a [Browserify](browserify.org) version (`npm run browserify`) and you can use the browser version by including in your code:
```
import { PNG } from 'pngjs/browser';
```
# Example
```js
var fs = require("fs"),
PNG = require("pngjs").PNG;
fs.createReadStream("in.png")
.pipe(
new PNG({
filterType: 4,
})
)
.on("parsed", function () {
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x) << 2;
// invert color
this.data[idx] = 255 - this.data[idx];
this.data[idx + 1] = 255 - this.data[idx + 1];
this.data[idx + 2] = 255 - this.data[idx + 2];
// and reduce opacity
this.data[idx + 3] = this.data[idx + 3] >> 1;
}
}
this.pack().pipe(fs.createWriteStream("out.png"));
});
```
For more examples see `examples` folder.
# Async API
As input any color type is accepted (grayscale, rgb, palette, grayscale with alpha, rgb with alpha) but 8 bit per sample (channel) is the only supported bit depth. Interlaced mode is not supported.
## Class: PNG
`PNG` is readable and writable `Stream`.
### Options
- `width` - use this with `height` if you want to create png from scratch
- `height` - as above
- `checkCRC` - whether parser should be strict about checksums in source stream (default: `true`)
- `deflateChunkSize` - chunk size used for deflating data chunks, this should be power of 2 and must not be less than 256 and more than 32\*1024 (default: 32 kB)
- `deflateLevel` - compression level for deflate (default: 9)
- `deflateStrategy` - compression strategy for deflate (default: 3)
- `deflateFactory` - deflate stream factory (default: `zlib.createDeflate`)
- `filterType` - png filtering method for scanlines (default: -1 => auto, accepts array of numbers 0-4)
- `colorType` - the output colorType - see constants. 0 = grayscale, no alpha, 2 = color, no alpha, 4 = grayscale & alpha, 6 = color & alpha. Default currently 6, but in the future may calculate best mode.
- `inputColorType` - the input colorType - see constants. Default is 6 (RGBA)
- `bitDepth` - the bitDepth of the output, 8 or 16 bits. Input data is expected to have this bit depth.
16 bit data is expected in the system endianness (Default: 8)
- `inputHasAlpha` - whether the input bitmap has 4 bytes per pixel (rgb and alpha) or 3 (rgb - no alpha).
- `bgColor` - an object containing red, green, and blue values between 0 and 255
that is used when packing a PNG if alpha is not to be included (default: 255,255,255)
### Event "metadata"
`function(metadata) { }`
Image's header has been parsed, metadata contains this information:
- `width` image size in pixels
- `height` image size in pixels
- `palette` image is paletted
- `color` image is not grayscale
- `alpha` image contains alpha channel
- `interlace` image is interlaced
### Event: "parsed"
`function(data) { }`
Input image has been completely parsed, `data` is complete and ready for modification.
### Event: "error"
`function(error) { }`
### png.parse(data, [callback])
Parses PNG file data. Can be `String` or `Buffer`. Alternatively you can stream data to instance of PNG.
Optional `callback` is once called on `error` or `parsed`. The callback gets
two arguments `(err, data)`.
Returns `this` for method chaining.
#### Example
```js
new PNG({ filterType: 4 }).parse(imageData, function (error, data) {
console.log(error, data);
});
```
### png.pack()
Starts converting data to PNG file Stream.
Returns `this` for method chaining.
### png.bitblt(dst, sx, sy, w, h, dx, dy)
Helper for image manipulation, copies a rectangle of pixels from current (i.e. the source) image (`sx`, `sy`, `w`, `h`) to `dst` image (at `dx`, `dy`).
Returns `this` for method chaining.
For example, the following code copies the top-left 100x50 px of `in.png` into dst and writes it to `out.png`:
```js
var dst = new PNG({ width: 100, height: 50 });
fs.createReadStream("in.png")
.pipe(new PNG())
.on("parsed", function () {
this.bitblt(dst, 0, 0, 100, 50, 0, 0);
dst.pack().pipe(fs.createWriteStream("out.png"));
});
```
### Property: adjustGamma()
Helper that takes data and adjusts it to be gamma corrected. Note that it is not 100% reliable with transparent colours because that requires knowing the background colour the bitmap is rendered on to.
In tests against PNG suite it compared 100% with chrome on all 8 bit and below images. On IE there were some differences.
The following example reads a file, adjusts the gamma (which sets the gamma to 0) and writes it out again, effectively removing any gamma correction from the image.
```js
fs.createReadStream("in.png")
.pipe(new PNG())
.on("parsed", function () {
this.adjustGamma();
this.pack().pipe(fs.createWriteStream("out.png"));
});
```
### Property: width
Width of image in pixels
### Property: height
Height of image in pixels
### Property: data
Buffer of image pixel data. Every pixel consists 4 bytes: R, G, B, A (opacity).
### Property: gamma
Gamma of image (0 if not specified)
## Packing a PNG and removing alpha (RGBA to RGB)
When removing the alpha channel from an image, there needs to be a background color to correctly
convert each pixel's transparency to the appropriate RGB value. By default, pngjs will flatten
the image against a white background. You can override this in the options:
```js
var fs = require("fs"),
PNG = require("pngjs").PNG;
fs.createReadStream("in.png")
.pipe(
new PNG({
colorType: 2,
bgColor: {
red: 0,
green: 255,
blue: 0,
},
})
)
.on("parsed", function () {
this.pack().pipe(fs.createWriteStream("out.png"));
});
```
# Sync API
## PNG.sync
### PNG.sync.read(buffer)
Take a buffer and returns a PNG image. The properties on the image include the meta data and `data` as per the async API above.
```
var data = fs.readFileSync('in.png');
var png = PNG.sync.read(data);
```
### PNG.sync.write(png)
Take a PNG image and returns a buffer. The properties on the image include the meta data and `data` as per the async API above.
```
var data = fs.readFileSync('in.png');
var png = PNG.sync.read(data);
var options = { colorType: 6 };
var buffer = PNG.sync.write(png, options);
fs.writeFileSync('out.png', buffer);
```
### PNG.adjustGamma(src)
Adjusts the gamma of a sync image. See the async adjustGamma.
```
var data = fs.readFileSync('in.png');
var png = PNG.sync.read(data);
PNG.adjustGamma(png);
```
# Changelog
### 6.0.0 - 24/10/2020
- BREAKING - Sync version now throws if there is unexpected content at the end of the stream.
- BREAKING - Drop support for node 10 (Though nothing incompatible in this release yet)
- Reduce the number of files included in the package
### 5.1.0 - 13/09/2020
- Add option to skip rescaling
### 5.0.0 - 15/04/2020
- Drop support for Node 8
- Browserified bundle may now contain ES20(15-20) code if the supported node version supports it. Please run the browserified version through babel if you need to support older browsers.
### 4.0.1 - 15/04/2020
- Fix to possible null reference in nextTick of async method
### 4.0.0 - 09/04/2020
- Fix issue in newer nodes with using Buffer
- Fix async issue with some png files
- Drop support for Node 4 & 6
### 3.4.0 - 09/03/2019
- Include whether the png has alpha in the meta data
- emit an error if the image is truncated instead of hanging
- Add a browserified version
- speed up some mapping functions
### 3.3.3 - 19/04/2018
- Real fix for node 9
### 3.3.2 - 16/02/2018
- Fix for node 9
### 3.3.1 - 15/11/2017
- Bugfixes and removal of es6
### 3.3.0
- Add writing 16 bit channels and support for grayscale input
### 3.2.0 - 30/04/2017
- Support for encoding 8-bit grayscale images
### 3.1.0 - 30/04/2017
- Support for pngs with zlib chunks that are malformed after valid data
### 3.0.1 - 16/02/2017
- Fix single pixel pngs
### 3.0.0 - 03/08/2016
- Drop support for node below v4 and iojs. Pin to 2.3.0 to use with old, unsupported or patched node versions.
### 2.3.0 - 22/04/2016
- Support for sync in node 0.10
### 2.2.0 - 04/12/2015
- Add sync write api
- Fix newfile example
- Correct comparison table
### 2.1.0 - 28/10/2015
- rename package to pngjs
- added 'bgColor' option
### 2.0.0 - 08/10/2015
- fixes to readme
- _breaking change_ - bitblt on the png prototype now doesn't take a unused, unnecessary src first argument
### 1.2.0 - 13/09/2015
- support passing colorType to write PNG's and writing bitmaps without alpha information
### 1.1.0 - 07/09/2015
- support passing a deflate factory for controlled compression
### 1.0.2 - 22/08/2015
- Expose all PNG creation info
### 1.0.1 - 21/08/2015
- Fix non square interlaced files
### 1.0.0 - 08/08/2015
- More tests
- source linted
- maintainability refactorings
- async API - exceptions in reading now emit warnings
- documentation improvement - sync api now documented, adjustGamma documented
- breaking change - gamma chunk is now written. previously a read then write would destroy gamma information, now it is persisted.
### 0.0.3 - 03/08/2015
- Error handling fixes
- ignore files for smaller npm footprint
### 0.0.2 - 02/08/2015
- Bugfixes to interlacing, support for transparent colours
### 0.0.1 - 02/08/2015
- Initial release, see pngjs for older changelog.
# License
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,267 @@
"use strict";
let interlaceUtils = require("./interlace");
let pixelBppMapper = [
// 0 - dummy entry
function () {},
// 1 - L
// 0: 0, 1: 0, 2: 0, 3: 0xff
function (pxData, data, pxPos, rawPos) {
if (rawPos === data.length) {
throw new Error("Ran out of data");
}
let pixel = data[rawPos];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = 0xff;
},
// 2 - LA
// 0: 0, 1: 0, 2: 0, 3: 1
function (pxData, data, pxPos, rawPos) {
if (rawPos + 1 >= data.length) {
throw new Error("Ran out of data");
}
let pixel = data[rawPos];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = data[rawPos + 1];
},
// 3 - RGB
// 0: 0, 1: 1, 2: 2, 3: 0xff
function (pxData, data, pxPos, rawPos) {
if (rawPos + 2 >= data.length) {
throw new Error("Ran out of data");
}
pxData[pxPos] = data[rawPos];
pxData[pxPos + 1] = data[rawPos + 1];
pxData[pxPos + 2] = data[rawPos + 2];
pxData[pxPos + 3] = 0xff;
},
// 4 - RGBA
// 0: 0, 1: 1, 2: 2, 3: 3
function (pxData, data, pxPos, rawPos) {
if (rawPos + 3 >= data.length) {
throw new Error("Ran out of data");
}
pxData[pxPos] = data[rawPos];
pxData[pxPos + 1] = data[rawPos + 1];
pxData[pxPos + 2] = data[rawPos + 2];
pxData[pxPos + 3] = data[rawPos + 3];
},
];
let pixelBppCustomMapper = [
// 0 - dummy entry
function () {},
// 1 - L
// 0: 0, 1: 0, 2: 0, 3: 0xff
function (pxData, pixelData, pxPos, maxBit) {
let pixel = pixelData[0];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = maxBit;
},
// 2 - LA
// 0: 0, 1: 0, 2: 0, 3: 1
function (pxData, pixelData, pxPos) {
let pixel = pixelData[0];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = pixelData[1];
},
// 3 - RGB
// 0: 0, 1: 1, 2: 2, 3: 0xff
function (pxData, pixelData, pxPos, maxBit) {
pxData[pxPos] = pixelData[0];
pxData[pxPos + 1] = pixelData[1];
pxData[pxPos + 2] = pixelData[2];
pxData[pxPos + 3] = maxBit;
},
// 4 - RGBA
// 0: 0, 1: 1, 2: 2, 3: 3
function (pxData, pixelData, pxPos) {
pxData[pxPos] = pixelData[0];
pxData[pxPos + 1] = pixelData[1];
pxData[pxPos + 2] = pixelData[2];
pxData[pxPos + 3] = pixelData[3];
},
];
function bitRetriever(data, depth) {
let leftOver = [];
let i = 0;
function split() {
if (i === data.length) {
throw new Error("Ran out of data");
}
let byte = data[i];
i++;
let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1;
switch (depth) {
default:
throw new Error("unrecognised depth");
case 16:
byte2 = data[i];
i++;
leftOver.push((byte << 8) + byte2);
break;
case 4:
byte2 = byte & 0x0f;
byte1 = byte >> 4;
leftOver.push(byte1, byte2);
break;
case 2:
byte4 = byte & 3;
byte3 = (byte >> 2) & 3;
byte2 = (byte >> 4) & 3;
byte1 = (byte >> 6) & 3;
leftOver.push(byte1, byte2, byte3, byte4);
break;
case 1:
byte8 = byte & 1;
byte7 = (byte >> 1) & 1;
byte6 = (byte >> 2) & 1;
byte5 = (byte >> 3) & 1;
byte4 = (byte >> 4) & 1;
byte3 = (byte >> 5) & 1;
byte2 = (byte >> 6) & 1;
byte1 = (byte >> 7) & 1;
leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8);
break;
}
}
return {
get: function (count) {
while (leftOver.length < count) {
split();
}
let returner = leftOver.slice(0, count);
leftOver = leftOver.slice(count);
return returner;
},
resetAfterLine: function () {
leftOver.length = 0;
},
end: function () {
if (i !== data.length) {
throw new Error("extra data found");
}
},
};
}
function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) {
// eslint-disable-line max-params
let imageWidth = image.width;
let imageHeight = image.height;
let imagePass = image.index;
for (let y = 0; y < imageHeight; y++) {
for (let x = 0; x < imageWidth; x++) {
let pxPos = getPxPos(x, y, imagePass);
pixelBppMapper[bpp](pxData, data, pxPos, rawPos);
rawPos += bpp; //eslint-disable-line no-param-reassign
}
}
return rawPos;
}
function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) {
// eslint-disable-line max-params
let imageWidth = image.width;
let imageHeight = image.height;
let imagePass = image.index;
for (let y = 0; y < imageHeight; y++) {
for (let x = 0; x < imageWidth; x++) {
let pixelData = bits.get(bpp);
let pxPos = getPxPos(x, y, imagePass);
pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit);
}
bits.resetAfterLine();
}
}
exports.dataToBitMap = function (data, bitmapInfo) {
let width = bitmapInfo.width;
let height = bitmapInfo.height;
let depth = bitmapInfo.depth;
let bpp = bitmapInfo.bpp;
let interlace = bitmapInfo.interlace;
let bits;
if (depth !== 8) {
bits = bitRetriever(data, depth);
}
let pxData;
if (depth <= 8) {
pxData = Buffer.alloc(width * height * 4);
} else {
pxData = new Uint16Array(width * height * 4);
}
let maxBit = Math.pow(2, depth) - 1;
let rawPos = 0;
let images;
let getPxPos;
if (interlace) {
images = interlaceUtils.getImagePasses(width, height);
getPxPos = interlaceUtils.getInterlaceIterator(width, height);
} else {
let nonInterlacedPxPos = 0;
getPxPos = function () {
let returner = nonInterlacedPxPos;
nonInterlacedPxPos += 4;
return returner;
};
images = [{ width: width, height: height }];
}
for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
if (depth === 8) {
rawPos = mapImage8Bit(
images[imageIndex],
pxData,
getPxPos,
bpp,
data,
rawPos
);
} else {
mapImageCustomBit(
images[imageIndex],
pxData,
getPxPos,
bpp,
bits,
maxBit
);
}
}
if (depth === 8) {
if (rawPos !== data.length) {
throw new Error("extra data found");
}
} else {
bits.end();
}
return pxData;
};

@ -0,0 +1,158 @@
"use strict";
let constants = require("./constants");
module.exports = function (dataIn, width, height, options) {
let outHasAlpha =
[constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf(
options.colorType
) !== -1;
if (options.colorType === options.inputColorType) {
let bigEndian = (function () {
let buffer = new ArrayBuffer(2);
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
// Int16Array uses the platform's endianness.
return new Int16Array(buffer)[0] !== 256;
})();
// If no need to convert to grayscale and alpha is present/absent in both, take a fast route
if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) {
return dataIn;
}
}
// map to a UInt16 array if data is 16bit, fix endianness below
let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer);
let maxValue = 255;
let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType];
if (inBpp === 4 && !options.inputHasAlpha) {
inBpp = 3;
}
let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType];
if (options.bitDepth === 16) {
maxValue = 65535;
outBpp *= 2;
}
let outData = Buffer.alloc(width * height * outBpp);
let inIndex = 0;
let outIndex = 0;
let bgColor = options.bgColor || {};
if (bgColor.red === undefined) {
bgColor.red = maxValue;
}
if (bgColor.green === undefined) {
bgColor.green = maxValue;
}
if (bgColor.blue === undefined) {
bgColor.blue = maxValue;
}
function getRGBA() {
let red;
let green;
let blue;
let alpha = maxValue;
switch (options.inputColorType) {
case constants.COLORTYPE_COLOR_ALPHA:
alpha = data[inIndex + 3];
red = data[inIndex];
green = data[inIndex + 1];
blue = data[inIndex + 2];
break;
case constants.COLORTYPE_COLOR:
red = data[inIndex];
green = data[inIndex + 1];
blue = data[inIndex + 2];
break;
case constants.COLORTYPE_ALPHA:
alpha = data[inIndex + 1];
red = data[inIndex];
green = red;
blue = red;
break;
case constants.COLORTYPE_GRAYSCALE:
red = data[inIndex];
green = red;
blue = red;
break;
default:
throw new Error(
"input color type:" +
options.inputColorType +
" is not supported at present"
);
}
if (options.inputHasAlpha) {
if (!outHasAlpha) {
alpha /= maxValue;
red = Math.min(
Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0),
maxValue
);
green = Math.min(
Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0),
maxValue
);
blue = Math.min(
Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0),
maxValue
);
}
}
return { red: red, green: green, blue: blue, alpha: alpha };
}
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let rgba = getRGBA(data, inIndex);
switch (options.colorType) {
case constants.COLORTYPE_COLOR_ALPHA:
case constants.COLORTYPE_COLOR:
if (options.bitDepth === 8) {
outData[outIndex] = rgba.red;
outData[outIndex + 1] = rgba.green;
outData[outIndex + 2] = rgba.blue;
if (outHasAlpha) {
outData[outIndex + 3] = rgba.alpha;
}
} else {
outData.writeUInt16BE(rgba.red, outIndex);
outData.writeUInt16BE(rgba.green, outIndex + 2);
outData.writeUInt16BE(rgba.blue, outIndex + 4);
if (outHasAlpha) {
outData.writeUInt16BE(rgba.alpha, outIndex + 6);
}
}
break;
case constants.COLORTYPE_ALPHA:
case constants.COLORTYPE_GRAYSCALE: {
// Convert to grayscale and alpha
let grayscale = (rgba.red + rgba.green + rgba.blue) / 3;
if (options.bitDepth === 8) {
outData[outIndex] = grayscale;
if (outHasAlpha) {
outData[outIndex + 1] = rgba.alpha;
}
} else {
outData.writeUInt16BE(grayscale, outIndex);
if (outHasAlpha) {
outData.writeUInt16BE(rgba.alpha, outIndex + 2);
}
}
break;
}
default:
throw new Error("unrecognised color Type " + options.colorType);
}
inIndex += inBpp;
outIndex += outBpp;
}
}
return outData;
};

@ -0,0 +1,189 @@
"use strict";
let util = require("util");
let Stream = require("stream");
let ChunkStream = (module.exports = function () {
Stream.call(this);
this._buffers = [];
this._buffered = 0;
this._reads = [];
this._paused = false;
this._encoding = "utf8";
this.writable = true;
});
util.inherits(ChunkStream, Stream);
ChunkStream.prototype.read = function (length, callback) {
this._reads.push({
length: Math.abs(length), // if length < 0 then at most this length
allowLess: length < 0,
func: callback,
});
process.nextTick(
function () {
this._process();
// its paused and there is not enought data then ask for more
if (this._paused && this._reads && this._reads.length > 0) {
this._paused = false;
this.emit("drain");
}
}.bind(this)
);
};
ChunkStream.prototype.write = function (data, encoding) {
if (!this.writable) {
this.emit("error", new Error("Stream not writable"));
return false;
}
let dataBuffer;
if (Buffer.isBuffer(data)) {
dataBuffer = data;
} else {
dataBuffer = Buffer.from(data, encoding || this._encoding);
}
this._buffers.push(dataBuffer);
this._buffered += dataBuffer.length;
this._process();
// ok if there are no more read requests
if (this._reads && this._reads.length === 0) {
this._paused = true;
}
return this.writable && !this._paused;
};
ChunkStream.prototype.end = function (data, encoding) {
if (data) {
this.write(data, encoding);
}
this.writable = false;
// already destroyed
if (!this._buffers) {
return;
}
// enqueue or handle end
if (this._buffers.length === 0) {
this._end();
} else {
this._buffers.push(null);
this._process();
}
};
ChunkStream.prototype.destroySoon = ChunkStream.prototype.end;
ChunkStream.prototype._end = function () {
if (this._reads.length > 0) {
this.emit("error", new Error("Unexpected end of input"));
}
this.destroy();
};
ChunkStream.prototype.destroy = function () {
if (!this._buffers) {
return;
}
this.writable = false;
this._reads = null;
this._buffers = null;
this.emit("close");
};
ChunkStream.prototype._processReadAllowingLess = function (read) {
// ok there is any data so that we can satisfy this request
this._reads.shift(); // == read
// first we need to peek into first buffer
let smallerBuf = this._buffers[0];
// ok there is more data than we need
if (smallerBuf.length > read.length) {
this._buffered -= read.length;
this._buffers[0] = smallerBuf.slice(read.length);
read.func.call(this, smallerBuf.slice(0, read.length));
} else {
// ok this is less than maximum length so use it all
this._buffered -= smallerBuf.length;
this._buffers.shift(); // == smallerBuf
read.func.call(this, smallerBuf);
}
};
ChunkStream.prototype._processRead = function (read) {
this._reads.shift(); // == read
let pos = 0;
let count = 0;
let data = Buffer.alloc(read.length);
// create buffer for all data
while (pos < read.length) {
let buf = this._buffers[count++];
let len = Math.min(buf.length, read.length - pos);
buf.copy(data, pos, 0, len);
pos += len;
// last buffer wasn't used all so just slice it and leave
if (len !== buf.length) {
this._buffers[--count] = buf.slice(len);
}
}
// remove all used buffers
if (count > 0) {
this._buffers.splice(0, count);
}
this._buffered -= read.length;
read.func.call(this, data);
};
ChunkStream.prototype._process = function () {
try {
// as long as there is any data and read requests
while (this._buffered > 0 && this._reads && this._reads.length > 0) {
let read = this._reads[0];
// read any data (but no more than length)
if (read.allowLess) {
this._processReadAllowingLess(read);
} else if (this._buffered >= read.length) {
// ok we can meet some expectations
this._processRead(read);
} else {
// not enought data to satisfy first request in queue
// so we need to wait for more
break;
}
}
if (this._buffers && !this.writable) {
this._end();
}
} catch (ex) {
this.emit("error", ex);
}
};

@ -0,0 +1,32 @@
"use strict";
module.exports = {
PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
TYPE_IHDR: 0x49484452,
TYPE_IEND: 0x49454e44,
TYPE_IDAT: 0x49444154,
TYPE_PLTE: 0x504c5445,
TYPE_tRNS: 0x74524e53, // eslint-disable-line camelcase
TYPE_gAMA: 0x67414d41, // eslint-disable-line camelcase
// color-type bits
COLORTYPE_GRAYSCALE: 0,
COLORTYPE_PALETTE: 1,
COLORTYPE_COLOR: 2,
COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha
// color-type combinations
COLORTYPE_PALETTE_COLOR: 3,
COLORTYPE_COLOR_ALPHA: 6,
COLORTYPE_TO_BPP_MAP: {
0: 1,
2: 3,
3: 1,
4: 2,
6: 4,
},
GAMMA_DIVISION: 100000,
};

@ -0,0 +1,40 @@
"use strict";
let crcTable = [];
(function () {
for (let i = 0; i < 256; i++) {
let currentCrc = i;
for (let j = 0; j < 8; j++) {
if (currentCrc & 1) {
currentCrc = 0xedb88320 ^ (currentCrc >>> 1);
} else {
currentCrc = currentCrc >>> 1;
}
}
crcTable[i] = currentCrc;
}
})();
let CrcCalculator = (module.exports = function () {
this._crc = -1;
});
CrcCalculator.prototype.write = function (data) {
for (let i = 0; i < data.length; i++) {
this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8);
}
return true;
};
CrcCalculator.prototype.crc32 = function () {
return this._crc ^ -1;
};
CrcCalculator.crc32 = function (buf) {
let crc = -1;
for (let i = 0; i < buf.length; i++) {
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
}
return crc ^ -1;
};

@ -0,0 +1,171 @@
"use strict";
let paethPredictor = require("./paeth-predictor");
function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) {
for (let x = 0; x < byteWidth; x++) {
rawData[rawPos + x] = pxData[pxPos + x];
}
}
function filterSumNone(pxData, pxPos, byteWidth) {
let sum = 0;
let length = pxPos + byteWidth;
for (let i = pxPos; i < length; i++) {
sum += Math.abs(pxData[i]);
}
return sum;
}
function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let val = pxData[pxPos + x] - left;
rawData[rawPos + x] = val;
}
}
function filterSumSub(pxData, pxPos, byteWidth, bpp) {
let sum = 0;
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let val = pxData[pxPos + x] - left;
sum += Math.abs(val);
}
return sum;
}
function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) {
for (let x = 0; x < byteWidth; x++) {
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let val = pxData[pxPos + x] - up;
rawData[rawPos + x] = val;
}
}
function filterSumUp(pxData, pxPos, byteWidth) {
let sum = 0;
let length = pxPos + byteWidth;
for (let x = pxPos; x < length; x++) {
let up = pxPos > 0 ? pxData[x - byteWidth] : 0;
let val = pxData[x] - up;
sum += Math.abs(val);
}
return sum;
}
function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let val = pxData[pxPos + x] - ((left + up) >> 1);
rawData[rawPos + x] = val;
}
}
function filterSumAvg(pxData, pxPos, byteWidth, bpp) {
let sum = 0;
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let val = pxData[pxPos + x] - ((left + up) >> 1);
sum += Math.abs(val);
}
return sum;
}
function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let upleft =
pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
let val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
rawData[rawPos + x] = val;
}
}
function filterSumPaeth(pxData, pxPos, byteWidth, bpp) {
let sum = 0;
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let upleft =
pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
let val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
sum += Math.abs(val);
}
return sum;
}
let filters = {
0: filterNone,
1: filterSub,
2: filterUp,
3: filterAvg,
4: filterPaeth,
};
let filterSums = {
0: filterSumNone,
1: filterSumSub,
2: filterSumUp,
3: filterSumAvg,
4: filterSumPaeth,
};
module.exports = function (pxData, width, height, options, bpp) {
let filterTypes;
if (!("filterType" in options) || options.filterType === -1) {
filterTypes = [0, 1, 2, 3, 4];
} else if (typeof options.filterType === "number") {
filterTypes = [options.filterType];
} else {
throw new Error("unrecognised filter types");
}
if (options.bitDepth === 16) {
bpp *= 2;
}
let byteWidth = width * bpp;
let rawPos = 0;
let pxPos = 0;
let rawData = Buffer.alloc((byteWidth + 1) * height);
let sel = filterTypes[0];
for (let y = 0; y < height; y++) {
if (filterTypes.length > 1) {
// find best filter for this line (with lowest sum of values)
let min = Infinity;
for (let i = 0; i < filterTypes.length; i++) {
let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp);
if (sum < min) {
sel = filterTypes[i];
min = sum;
}
}
}
rawData[rawPos] = sel;
rawPos++;
filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp);
rawPos += byteWidth;
pxPos += byteWidth;
}
return rawData;
};

@ -0,0 +1,24 @@
"use strict";
let util = require("util");
let ChunkStream = require("./chunkstream");
let Filter = require("./filter-parse");
let FilterAsync = (module.exports = function (bitmapInfo) {
ChunkStream.call(this);
let buffers = [];
let that = this;
this._filter = new Filter(bitmapInfo, {
read: this.read.bind(this),
write: function (buffer) {
buffers.push(buffer);
},
complete: function () {
that.emit("complete", Buffer.concat(buffers));
},
});
this._filter.start();
});
util.inherits(FilterAsync, ChunkStream);

@ -0,0 +1,21 @@
"use strict";
let SyncReader = require("./sync-reader");
let Filter = require("./filter-parse");
exports.process = function (inBuffer, bitmapInfo) {
let outBuffers = [];
let reader = new SyncReader(inBuffer);
let filter = new Filter(bitmapInfo, {
read: reader.read.bind(reader),
write: function (bufferPart) {
outBuffers.push(bufferPart);
},
complete: function () {},
});
filter.start();
reader.process();
return Buffer.concat(outBuffers);
};

@ -0,0 +1,177 @@
"use strict";
let interlaceUtils = require("./interlace");
let paethPredictor = require("./paeth-predictor");
function getByteWidth(width, bpp, depth) {
let byteWidth = width * bpp;
if (depth !== 8) {
byteWidth = Math.ceil(byteWidth / (8 / depth));
}
return byteWidth;
}
let Filter = (module.exports = function (bitmapInfo, dependencies) {
let width = bitmapInfo.width;
let height = bitmapInfo.height;
let interlace = bitmapInfo.interlace;
let bpp = bitmapInfo.bpp;
let depth = bitmapInfo.depth;
this.read = dependencies.read;
this.write = dependencies.write;
this.complete = dependencies.complete;
this._imageIndex = 0;
this._images = [];
if (interlace) {
let passes = interlaceUtils.getImagePasses(width, height);
for (let i = 0; i < passes.length; i++) {
this._images.push({
byteWidth: getByteWidth(passes[i].width, bpp, depth),
height: passes[i].height,
lineIndex: 0,
});
}
} else {
this._images.push({
byteWidth: getByteWidth(width, bpp, depth),
height: height,
lineIndex: 0,
});
}
// when filtering the line we look at the pixel to the left
// the spec also says it is done on a byte level regardless of the number of pixels
// so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back
// a pixel rather than just a different byte part. However if we are sub byte, we ignore.
if (depth === 8) {
this._xComparison = bpp;
} else if (depth === 16) {
this._xComparison = bpp * 2;
} else {
this._xComparison = 1;
}
});
Filter.prototype.start = function () {
this.read(
this._images[this._imageIndex].byteWidth + 1,
this._reverseFilterLine.bind(this)
);
};
Filter.prototype._unFilterType1 = function (
rawData,
unfilteredLine,
byteWidth
) {
let xComparison = this._xComparison;
let xBiggerThan = xComparison - 1;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
unfilteredLine[x] = rawByte + f1Left;
}
};
Filter.prototype._unFilterType2 = function (
rawData,
unfilteredLine,
byteWidth
) {
let lastLine = this._lastLine;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f2Up = lastLine ? lastLine[x] : 0;
unfilteredLine[x] = rawByte + f2Up;
}
};
Filter.prototype._unFilterType3 = function (
rawData,
unfilteredLine,
byteWidth
) {
let xComparison = this._xComparison;
let xBiggerThan = xComparison - 1;
let lastLine = this._lastLine;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f3Up = lastLine ? lastLine[x] : 0;
let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
let f3Add = Math.floor((f3Left + f3Up) / 2);
unfilteredLine[x] = rawByte + f3Add;
}
};
Filter.prototype._unFilterType4 = function (
rawData,
unfilteredLine,
byteWidth
) {
let xComparison = this._xComparison;
let xBiggerThan = xComparison - 1;
let lastLine = this._lastLine;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f4Up = lastLine ? lastLine[x] : 0;
let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0;
let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft);
unfilteredLine[x] = rawByte + f4Add;
}
};
Filter.prototype._reverseFilterLine = function (rawData) {
let filter = rawData[0];
let unfilteredLine;
let currentImage = this._images[this._imageIndex];
let byteWidth = currentImage.byteWidth;
if (filter === 0) {
unfilteredLine = rawData.slice(1, byteWidth + 1);
} else {
unfilteredLine = Buffer.alloc(byteWidth);
switch (filter) {
case 1:
this._unFilterType1(rawData, unfilteredLine, byteWidth);
break;
case 2:
this._unFilterType2(rawData, unfilteredLine, byteWidth);
break;
case 3:
this._unFilterType3(rawData, unfilteredLine, byteWidth);
break;
case 4:
this._unFilterType4(rawData, unfilteredLine, byteWidth);
break;
default:
throw new Error("Unrecognised filter type - " + filter);
}
}
this.write(unfilteredLine);
currentImage.lineIndex++;
if (currentImage.lineIndex >= currentImage.height) {
this._lastLine = null;
this._imageIndex++;
currentImage = this._images[this._imageIndex];
} else {
this._lastLine = unfilteredLine;
}
if (currentImage) {
// read, using the byte width that may be from the new current image
this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this));
} else {
this._lastLine = null;
this.complete();
}
};

@ -0,0 +1,93 @@
"use strict";
function dePalette(indata, outdata, width, height, palette) {
let pxPos = 0;
// use values from palette
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let color = palette[indata[pxPos]];
if (!color) {
throw new Error("index " + indata[pxPos] + " not in palette");
}
for (let i = 0; i < 4; i++) {
outdata[pxPos + i] = color[i];
}
pxPos += 4;
}
}
}
function replaceTransparentColor(indata, outdata, width, height, transColor) {
let pxPos = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let makeTrans = false;
if (transColor.length === 1) {
if (transColor[0] === indata[pxPos]) {
makeTrans = true;
}
} else if (
transColor[0] === indata[pxPos] &&
transColor[1] === indata[pxPos + 1] &&
transColor[2] === indata[pxPos + 2]
) {
makeTrans = true;
}
if (makeTrans) {
for (let i = 0; i < 4; i++) {
outdata[pxPos + i] = 0;
}
}
pxPos += 4;
}
}
}
function scaleDepth(indata, outdata, width, height, depth) {
let maxOutSample = 255;
let maxInSample = Math.pow(2, depth) - 1;
let pxPos = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
for (let i = 0; i < 4; i++) {
outdata[pxPos + i] = Math.floor(
(indata[pxPos + i] * maxOutSample) / maxInSample + 0.5
);
}
pxPos += 4;
}
}
}
module.exports = function (indata, imageData, skipRescale = false) {
let depth = imageData.depth;
let width = imageData.width;
let height = imageData.height;
let colorType = imageData.colorType;
let transColor = imageData.transColor;
let palette = imageData.palette;
let outdata = indata; // only different for 16 bits
if (colorType === 3) {
// paletted
dePalette(indata, outdata, width, height, palette);
} else {
if (transColor) {
replaceTransparentColor(indata, outdata, width, height, transColor);
}
// if it needs scaling
if (depth !== 8 && !skipRescale) {
// if we need to change the buffer size
if (depth === 16) {
outdata = Buffer.alloc(width * height * 4);
}
scaleDepth(indata, outdata, width, height, depth);
}
}
return outdata;
};

@ -0,0 +1,95 @@
"use strict";
// Adam 7
// 0 1 2 3 4 5 6 7
// 0 x 6 4 6 x 6 4 6
// 1 7 7 7 7 7 7 7 7
// 2 5 6 5 6 5 6 5 6
// 3 7 7 7 7 7 7 7 7
// 4 3 6 4 6 3 6 4 6
// 5 7 7 7 7 7 7 7 7
// 6 5 6 5 6 5 6 5 6
// 7 7 7 7 7 7 7 7 7
let imagePasses = [
{
// pass 1 - 1px
x: [0],
y: [0],
},
{
// pass 2 - 1px
x: [4],
y: [0],
},
{
// pass 3 - 2px
x: [0, 4],
y: [4],
},
{
// pass 4 - 4px
x: [2, 6],
y: [0, 4],
},
{
// pass 5 - 8px
x: [0, 2, 4, 6],
y: [2, 6],
},
{
// pass 6 - 16px
x: [1, 3, 5, 7],
y: [0, 2, 4, 6],
},
{
// pass 7 - 32px
x: [0, 1, 2, 3, 4, 5, 6, 7],
y: [1, 3, 5, 7],
},
];
exports.getImagePasses = function (width, height) {
let images = [];
let xLeftOver = width % 8;
let yLeftOver = height % 8;
let xRepeats = (width - xLeftOver) / 8;
let yRepeats = (height - yLeftOver) / 8;
for (let i = 0; i < imagePasses.length; i++) {
let pass = imagePasses[i];
let passWidth = xRepeats * pass.x.length;
let passHeight = yRepeats * pass.y.length;
for (let j = 0; j < pass.x.length; j++) {
if (pass.x[j] < xLeftOver) {
passWidth++;
} else {
break;
}
}
for (let j = 0; j < pass.y.length; j++) {
if (pass.y[j] < yLeftOver) {
passHeight++;
} else {
break;
}
}
if (passWidth > 0 && passHeight > 0) {
images.push({ width: passWidth, height: passHeight, index: i });
}
}
return images;
};
exports.getInterlaceIterator = function (width) {
return function (x, y, pass) {
let outerXLeftOver = x % imagePasses[pass].x.length;
let outerX =
((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 +
imagePasses[pass].x[outerXLeftOver];
let outerYLeftOver = y % imagePasses[pass].y.length;
let outerY =
((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 +
imagePasses[pass].y[outerYLeftOver];
return outerX * 4 + outerY * width * 4;
};
};

@ -0,0 +1,50 @@
"use strict";
let util = require("util");
let Stream = require("stream");
let constants = require("./constants");
let Packer = require("./packer");
let PackerAsync = (module.exports = function (opt) {
Stream.call(this);
let options = opt || {};
this._packer = new Packer(options);
this._deflate = this._packer.createDeflate();
this.readable = true;
});
util.inherits(PackerAsync, Stream);
PackerAsync.prototype.pack = function (data, width, height, gamma) {
// Signature
this.emit("data", Buffer.from(constants.PNG_SIGNATURE));
this.emit("data", this._packer.packIHDR(width, height));
if (gamma) {
this.emit("data", this._packer.packGAMA(gamma));
}
let filteredData = this._packer.filterData(data, width, height);
// compress it
this._deflate.on("error", this.emit.bind(this, "error"));
this._deflate.on(
"data",
function (compressedData) {
this.emit("data", this._packer.packIDAT(compressedData));
}.bind(this)
);
this._deflate.on(
"end",
function () {
this.emit("data", this._packer.packIEND());
this.emit("end");
}.bind(this)
);
this._deflate.end(filteredData);
};

@ -0,0 +1,56 @@
"use strict";
let hasSyncZlib = true;
let zlib = require("zlib");
if (!zlib.deflateSync) {
hasSyncZlib = false;
}
let constants = require("./constants");
let Packer = require("./packer");
module.exports = function (metaData, opt) {
if (!hasSyncZlib) {
throw new Error(
"To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0"
);
}
let options = opt || {};
let packer = new Packer(options);
let chunks = [];
// Signature
chunks.push(Buffer.from(constants.PNG_SIGNATURE));
// Header
chunks.push(packer.packIHDR(metaData.width, metaData.height));
if (metaData.gamma) {
chunks.push(packer.packGAMA(metaData.gamma));
}
let filteredData = packer.filterData(
metaData.data,
metaData.width,
metaData.height
);
// compress it
let compressedData = zlib.deflateSync(
filteredData,
packer.getDeflateOptions()
);
filteredData = null;
if (!compressedData || !compressedData.length) {
throw new Error("bad png - invalid compressed data response");
}
chunks.push(packer.packIDAT(compressedData));
// End
chunks.push(packer.packIEND());
return Buffer.concat(chunks);
};

@ -0,0 +1,129 @@
"use strict";
let constants = require("./constants");
let CrcStream = require("./crc");
let bitPacker = require("./bitpacker");
let filter = require("./filter-pack");
let zlib = require("zlib");
let Packer = (module.exports = function (options) {
this._options = options;
options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
options.deflateLevel =
options.deflateLevel != null ? options.deflateLevel : 9;
options.deflateStrategy =
options.deflateStrategy != null ? options.deflateStrategy : 3;
options.inputHasAlpha =
options.inputHasAlpha != null ? options.inputHasAlpha : true;
options.deflateFactory = options.deflateFactory || zlib.createDeflate;
options.bitDepth = options.bitDepth || 8;
// This is outputColorType
options.colorType =
typeof options.colorType === "number"
? options.colorType
: constants.COLORTYPE_COLOR_ALPHA;
options.inputColorType =
typeof options.inputColorType === "number"
? options.inputColorType
: constants.COLORTYPE_COLOR_ALPHA;
if (
[
constants.COLORTYPE_GRAYSCALE,
constants.COLORTYPE_COLOR,
constants.COLORTYPE_COLOR_ALPHA,
constants.COLORTYPE_ALPHA,
].indexOf(options.colorType) === -1
) {
throw new Error(
"option color type:" + options.colorType + " is not supported at present"
);
}
if (
[
constants.COLORTYPE_GRAYSCALE,
constants.COLORTYPE_COLOR,
constants.COLORTYPE_COLOR_ALPHA,
constants.COLORTYPE_ALPHA,
].indexOf(options.inputColorType) === -1
) {
throw new Error(
"option input color type:" +
options.inputColorType +
" is not supported at present"
);
}
if (options.bitDepth !== 8 && options.bitDepth !== 16) {
throw new Error(
"option bit depth:" + options.bitDepth + " is not supported at present"
);
}
});
Packer.prototype.getDeflateOptions = function () {
return {
chunkSize: this._options.deflateChunkSize,
level: this._options.deflateLevel,
strategy: this._options.deflateStrategy,
};
};
Packer.prototype.createDeflate = function () {
return this._options.deflateFactory(this.getDeflateOptions());
};
Packer.prototype.filterData = function (data, width, height) {
// convert to correct format for filtering (e.g. right bpp and bit depth)
let packedData = bitPacker(data, width, height, this._options);
// filter pixel data
let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType];
let filteredData = filter(packedData, width, height, this._options, bpp);
return filteredData;
};
Packer.prototype._packChunk = function (type, data) {
let len = data ? data.length : 0;
let buf = Buffer.alloc(len + 12);
buf.writeUInt32BE(len, 0);
buf.writeUInt32BE(type, 4);
if (data) {
data.copy(buf, 8);
}
buf.writeInt32BE(
CrcStream.crc32(buf.slice(4, buf.length - 4)),
buf.length - 4
);
return buf;
};
Packer.prototype.packGAMA = function (gamma) {
let buf = Buffer.alloc(4);
buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0);
return this._packChunk(constants.TYPE_gAMA, buf);
};
Packer.prototype.packIHDR = function (width, height) {
let buf = Buffer.alloc(13);
buf.writeUInt32BE(width, 0);
buf.writeUInt32BE(height, 4);
buf[8] = this._options.bitDepth; // Bit depth
buf[9] = this._options.colorType; // colorType
buf[10] = 0; // compression
buf[11] = 0; // filter
buf[12] = 0; // interlace
return this._packChunk(constants.TYPE_IHDR, buf);
};
Packer.prototype.packIDAT = function (data) {
return this._packChunk(constants.TYPE_IDAT, data);
};
Packer.prototype.packIEND = function () {
return this._packChunk(constants.TYPE_IEND, null);
};

@ -0,0 +1,16 @@
"use strict";
module.exports = function paethPredictor(left, above, upLeft) {
let paeth = left + above - upLeft;
let pLeft = Math.abs(paeth - left);
let pAbove = Math.abs(paeth - above);
let pUpLeft = Math.abs(paeth - upLeft);
if (pLeft <= pAbove && pLeft <= pUpLeft) {
return left;
}
if (pAbove <= pUpLeft) {
return above;
}
return upLeft;
};

@ -0,0 +1,169 @@
"use strict";
let util = require("util");
let zlib = require("zlib");
let ChunkStream = require("./chunkstream");
let FilterAsync = require("./filter-parse-async");
let Parser = require("./parser");
let bitmapper = require("./bitmapper");
let formatNormaliser = require("./format-normaliser");
let ParserAsync = (module.exports = function (options) {
ChunkStream.call(this);
this._parser = new Parser(options, {
read: this.read.bind(this),
error: this._handleError.bind(this),
metadata: this._handleMetaData.bind(this),
gamma: this.emit.bind(this, "gamma"),
palette: this._handlePalette.bind(this),
transColor: this._handleTransColor.bind(this),
finished: this._finished.bind(this),
inflateData: this._inflateData.bind(this),
simpleTransparency: this._simpleTransparency.bind(this),
headersFinished: this._headersFinished.bind(this),
});
this._options = options;
this.writable = true;
this._parser.start();
});
util.inherits(ParserAsync, ChunkStream);
ParserAsync.prototype._handleError = function (err) {
this.emit("error", err);
this.writable = false;
this.destroy();
if (this._inflate && this._inflate.destroy) {
this._inflate.destroy();
}
if (this._filter) {
this._filter.destroy();
// For backward compatibility with Node 7 and below.
// Suppress errors due to _inflate calling write() even after
// it's destroy()'ed.
this._filter.on("error", function () {});
}
this.errord = true;
};
ParserAsync.prototype._inflateData = function (data) {
if (!this._inflate) {
if (this._bitmapInfo.interlace) {
this._inflate = zlib.createInflate();
this._inflate.on("error", this.emit.bind(this, "error"));
this._filter.on("complete", this._complete.bind(this));
this._inflate.pipe(this._filter);
} else {
let rowSize =
((this._bitmapInfo.width *
this._bitmapInfo.bpp *
this._bitmapInfo.depth +
7) >>
3) +
1;
let imageSize = rowSize * this._bitmapInfo.height;
let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK);
this._inflate = zlib.createInflate({ chunkSize: chunkSize });
let leftToInflate = imageSize;
let emitError = this.emit.bind(this, "error");
this._inflate.on("error", function (err) {
if (!leftToInflate) {
return;
}
emitError(err);
});
this._filter.on("complete", this._complete.bind(this));
let filterWrite = this._filter.write.bind(this._filter);
this._inflate.on("data", function (chunk) {
if (!leftToInflate) {
return;
}
if (chunk.length > leftToInflate) {
chunk = chunk.slice(0, leftToInflate);
}
leftToInflate -= chunk.length;
filterWrite(chunk);
});
this._inflate.on("end", this._filter.end.bind(this._filter));
}
}
this._inflate.write(data);
};
ParserAsync.prototype._handleMetaData = function (metaData) {
this._metaData = metaData;
this._bitmapInfo = Object.create(metaData);
this._filter = new FilterAsync(this._bitmapInfo);
};
ParserAsync.prototype._handleTransColor = function (transColor) {
this._bitmapInfo.transColor = transColor;
};
ParserAsync.prototype._handlePalette = function (palette) {
this._bitmapInfo.palette = palette;
};
ParserAsync.prototype._simpleTransparency = function () {
this._metaData.alpha = true;
};
ParserAsync.prototype._headersFinished = function () {
// Up until this point, we don't know if we have a tRNS chunk (alpha)
// so we can't emit metadata any earlier
this.emit("metadata", this._metaData);
};
ParserAsync.prototype._finished = function () {
if (this.errord) {
return;
}
if (!this._inflate) {
this.emit("error", "No Inflate block");
} else {
// no more data to inflate
this._inflate.end();
}
};
ParserAsync.prototype._complete = function (filteredData) {
if (this.errord) {
return;
}
let normalisedBitmapData;
try {
let bitmapData = bitmapper.dataToBitMap(filteredData, this._bitmapInfo);
normalisedBitmapData = formatNormaliser(
bitmapData,
this._bitmapInfo,
this._options.skipRescale
);
bitmapData = null;
} catch (ex) {
this._handleError(ex);
return;
}
this.emit("parsed", normalisedBitmapData);
};

@ -0,0 +1,112 @@
"use strict";
let hasSyncZlib = true;
let zlib = require("zlib");
let inflateSync = require("./sync-inflate");
if (!zlib.deflateSync) {
hasSyncZlib = false;
}
let SyncReader = require("./sync-reader");
let FilterSync = require("./filter-parse-sync");
let Parser = require("./parser");
let bitmapper = require("./bitmapper");
let formatNormaliser = require("./format-normaliser");
module.exports = function (buffer, options) {
if (!hasSyncZlib) {
throw new Error(
"To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0"
);
}
let err;
function handleError(_err_) {
err = _err_;
}
let metaData;
function handleMetaData(_metaData_) {
metaData = _metaData_;
}
function handleTransColor(transColor) {
metaData.transColor = transColor;
}
function handlePalette(palette) {
metaData.palette = palette;
}
function handleSimpleTransparency() {
metaData.alpha = true;
}
let gamma;
function handleGamma(_gamma_) {
gamma = _gamma_;
}
let inflateDataList = [];
function handleInflateData(inflatedData) {
inflateDataList.push(inflatedData);
}
let reader = new SyncReader(buffer);
let parser = new Parser(options, {
read: reader.read.bind(reader),
error: handleError,
metadata: handleMetaData,
gamma: handleGamma,
palette: handlePalette,
transColor: handleTransColor,
inflateData: handleInflateData,
simpleTransparency: handleSimpleTransparency,
});
parser.start();
reader.process();
if (err) {
throw err;
}
//join together the inflate datas
let inflateData = Buffer.concat(inflateDataList);
inflateDataList.length = 0;
let inflatedData;
if (metaData.interlace) {
inflatedData = zlib.inflateSync(inflateData);
} else {
let rowSize =
((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1;
let imageSize = rowSize * metaData.height;
inflatedData = inflateSync(inflateData, {
chunkSize: imageSize,
maxLength: imageSize,
});
}
inflateData = null;
if (!inflatedData || !inflatedData.length) {
throw new Error("bad png - invalid inflate data response");
}
let unfilteredData = FilterSync.process(inflatedData, metaData);
inflateData = null;
let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData);
unfilteredData = null;
let normalisedBitmapData = formatNormaliser(
bitmapData,
metaData,
options.skipRescale
);
metaData.data = normalisedBitmapData;
metaData.gamma = gamma || 0;
return metaData;
};

@ -0,0 +1,290 @@
"use strict";
let constants = require("./constants");
let CrcCalculator = require("./crc");
let Parser = (module.exports = function (options, dependencies) {
this._options = options;
options.checkCRC = options.checkCRC !== false;
this._hasIHDR = false;
this._hasIEND = false;
this._emittedHeadersFinished = false;
// input flags/metadata
this._palette = [];
this._colorType = 0;
this._chunks = {};
this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);
this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);
this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);
this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);
this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);
this.read = dependencies.read;
this.error = dependencies.error;
this.metadata = dependencies.metadata;
this.gamma = dependencies.gamma;
this.transColor = dependencies.transColor;
this.palette = dependencies.palette;
this.parsed = dependencies.parsed;
this.inflateData = dependencies.inflateData;
this.finished = dependencies.finished;
this.simpleTransparency = dependencies.simpleTransparency;
this.headersFinished = dependencies.headersFinished || function () {};
});
Parser.prototype.start = function () {
this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this));
};
Parser.prototype._parseSignature = function (data) {
let signature = constants.PNG_SIGNATURE;
for (let i = 0; i < signature.length; i++) {
if (data[i] !== signature[i]) {
this.error(new Error("Invalid file signature"));
return;
}
}
this.read(8, this._parseChunkBegin.bind(this));
};
Parser.prototype._parseChunkBegin = function (data) {
// chunk content length
let length = data.readUInt32BE(0);
// chunk type
let type = data.readUInt32BE(4);
let name = "";
for (let i = 4; i < 8; i++) {
name += String.fromCharCode(data[i]);
}
//console.log('chunk ', name, length);
// chunk flags
let ancillary = Boolean(data[4] & 0x20); // or critical
// priv = Boolean(data[5] & 0x20), // or public
// safeToCopy = Boolean(data[7] & 0x20); // or unsafe
if (!this._hasIHDR && type !== constants.TYPE_IHDR) {
this.error(new Error("Expected IHDR on beggining"));
return;
}
this._crc = new CrcCalculator();
this._crc.write(Buffer.from(name));
if (this._chunks[type]) {
return this._chunks[type](length);
}
if (!ancillary) {
this.error(new Error("Unsupported critical chunk type " + name));
return;
}
this.read(length + 4, this._skipChunk.bind(this));
};
Parser.prototype._skipChunk = function (/*data*/) {
this.read(8, this._parseChunkBegin.bind(this));
};
Parser.prototype._handleChunkEnd = function () {
this.read(4, this._parseChunkEnd.bind(this));
};
Parser.prototype._parseChunkEnd = function (data) {
let fileCrc = data.readInt32BE(0);
let calcCrc = this._crc.crc32();
// check CRC
if (this._options.checkCRC && calcCrc !== fileCrc) {
this.error(new Error("Crc error - " + fileCrc + " - " + calcCrc));
return;
}
if (!this._hasIEND) {
this.read(8, this._parseChunkBegin.bind(this));
}
};
Parser.prototype._handleIHDR = function (length) {
this.read(length, this._parseIHDR.bind(this));
};
Parser.prototype._parseIHDR = function (data) {
this._crc.write(data);
let width = data.readUInt32BE(0);
let height = data.readUInt32BE(4);
let depth = data[8];
let colorType = data[9]; // bits: 1 palette, 2 color, 4 alpha
let compr = data[10];
let filter = data[11];
let interlace = data[12];
// console.log(' width', width, 'height', height,
// 'depth', depth, 'colorType', colorType,
// 'compr', compr, 'filter', filter, 'interlace', interlace
// );
if (
depth !== 8 &&
depth !== 4 &&
depth !== 2 &&
depth !== 1 &&
depth !== 16
) {
this.error(new Error("Unsupported bit depth " + depth));
return;
}
if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) {
this.error(new Error("Unsupported color type"));
return;
}
if (compr !== 0) {
this.error(new Error("Unsupported compression method"));
return;
}
if (filter !== 0) {
this.error(new Error("Unsupported filter method"));
return;
}
if (interlace !== 0 && interlace !== 1) {
this.error(new Error("Unsupported interlace method"));
return;
}
this._colorType = colorType;
let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType];
this._hasIHDR = true;
this.metadata({
width: width,
height: height,
depth: depth,
interlace: Boolean(interlace),
palette: Boolean(colorType & constants.COLORTYPE_PALETTE),
color: Boolean(colorType & constants.COLORTYPE_COLOR),
alpha: Boolean(colorType & constants.COLORTYPE_ALPHA),
bpp: bpp,
colorType: colorType,
});
this._handleChunkEnd();
};
Parser.prototype._handlePLTE = function (length) {
this.read(length, this._parsePLTE.bind(this));
};
Parser.prototype._parsePLTE = function (data) {
this._crc.write(data);
let entries = Math.floor(data.length / 3);
// console.log('Palette:', entries);
for (let i = 0; i < entries; i++) {
this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]);
}
this.palette(this._palette);
this._handleChunkEnd();
};
Parser.prototype._handleTRNS = function (length) {
this.simpleTransparency();
this.read(length, this._parseTRNS.bind(this));
};
Parser.prototype._parseTRNS = function (data) {
this._crc.write(data);
// palette
if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) {
if (this._palette.length === 0) {
this.error(new Error("Transparency chunk must be after palette"));
return;
}
if (data.length > this._palette.length) {
this.error(new Error("More transparent colors than palette size"));
return;
}
for (let i = 0; i < data.length; i++) {
this._palette[i][3] = data[i];
}
this.palette(this._palette);
}
// for colorType 0 (grayscale) and 2 (rgb)
// there might be one gray/color defined as transparent
if (this._colorType === constants.COLORTYPE_GRAYSCALE) {
// grey, 2 bytes
this.transColor([data.readUInt16BE(0)]);
}
if (this._colorType === constants.COLORTYPE_COLOR) {
this.transColor([
data.readUInt16BE(0),
data.readUInt16BE(2),
data.readUInt16BE(4),
]);
}
this._handleChunkEnd();
};
Parser.prototype._handleGAMA = function (length) {
this.read(length, this._parseGAMA.bind(this));
};
Parser.prototype._parseGAMA = function (data) {
this._crc.write(data);
this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION);
this._handleChunkEnd();
};
Parser.prototype._handleIDAT = function (length) {
if (!this._emittedHeadersFinished) {
this._emittedHeadersFinished = true;
this.headersFinished();
}
this.read(-length, this._parseIDAT.bind(this, length));
};
Parser.prototype._parseIDAT = function (length, data) {
this._crc.write(data);
if (
this._colorType === constants.COLORTYPE_PALETTE_COLOR &&
this._palette.length === 0
) {
throw new Error("Expected palette not found");
}
this.inflateData(data);
let leftOverLength = length - data.length;
if (leftOverLength > 0) {
this._handleIDAT(leftOverLength);
} else {
this._handleChunkEnd();
}
};
Parser.prototype._handleIEND = function (length) {
this.read(length, this._parseIEND.bind(this));
};
Parser.prototype._parseIEND = function (data) {
this._crc.write(data);
this._hasIEND = true;
this._handleChunkEnd();
if (this.finished) {
this.finished();
}
};

Some files were not shown because too many files have changed in this diff Show More