Files
zooqueen b6d88c8e40 cut a static font out of the variable one, in the tab
A variable font is what a browser wants and what a font menu, a design tool and
a terminal often cannot use — so Zen shipped as one file people could not always
install. zen::instance walks the outlines at a place on the axes, writes them
into a plain glyf, and returns a font that needs no variation settings to look
right. Same source native and wasm, so the crate the CLI uses is the crate the
page runs.

Width and tracking are baked rather than left to CSS. transform:scaleX stretches
what is painted and leaves the layout box where it was; a font that is really
wider carries the width in its advances too, or its letters overlap. Tracking is
space a layout engine adds, and here it is space the glyph owns. A cut taken at
.zen-wide is therefore that preset, with no stylesheet.

Stylistic sets are applied by DRAWING the alternate: a glyph the feature
substitutes is written with the substitute's outline and advance under the
original's id, so the character map never moves and the file stays a drop-in
replacement carrying the letterforms you chose. Only single substitution — a
ligature depends on what is beside it and cannot be baked into a glyph.

The axis is named rather than assumed, so the pixel element cuts the same way
the weight does.

Tests measure the cut against the variable font it came from: same points at
four weights, same advances, no variation tables, ss01 reaches the a and no
other letter, width and tracking land where they should, and the element behaves
like any other axis.

skrifa 0.22 and write-fonts 0.28 do not build on current rustc; both move up.
@hanzo/font 1.8.10 carries the wasm at ./wasm.
2026-08-24 14:37:12 -07:00

383 lines
13 KiB
JavaScript

/**
* Cut a static font out of the variable one, at the settings on the page.
*
* `at` names the place on the axes, `wght=497` or `ELSH=40`, comma separated.
* `features` are stylistic set tags (`ss01`, `ss04`) to bake in, comma
* separated too. `width` and `track` are baked into the outlines and advances,
* so the file needs no CSS to carry the setting. The bytes come back for the page to hand to a download — nothing
* is uploaded and no server sees the font or the settings.
* @param {Uint8Array} font
* @param {string} at
* @param {number} width
* @param {number} track
* @param {string} features
* @param {string} family
* @param {string} style
* @param {string} notice
* @returns {Uint8Array}
*/
export function cut(font, at, width, track, features, family, style, notice) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passArray8ToWasm0(font, wasm.__wbindgen_export);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(at, wasm.__wbindgen_export, wasm.__wbindgen_export2);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passStringToWasm0(features, wasm.__wbindgen_export, wasm.__wbindgen_export2);
const len2 = WASM_VECTOR_LEN;
const ptr3 = passStringToWasm0(family, wasm.__wbindgen_export, wasm.__wbindgen_export2);
const len3 = WASM_VECTOR_LEN;
const ptr4 = passStringToWasm0(style, wasm.__wbindgen_export, wasm.__wbindgen_export2);
const len4 = WASM_VECTOR_LEN;
const ptr5 = passStringToWasm0(notice, wasm.__wbindgen_export, wasm.__wbindgen_export2);
const len5 = WASM_VECTOR_LEN;
wasm.cut(retptr, ptr0, len0, ptr1, len1, width, track, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5);
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
if (r3) {
throw takeObject(r2);
}
var v7 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_export3(r0, r1 * 1, 1);
return v7;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* Search a candidate's parameter space for the closest match to a target face.
*
* Both fonts come in as bytes. Nothing is uploaded — this runs in the tab.
* @param {Uint8Array} target
* @param {Uint8Array} candidate
* @param {string} text
* @returns {any}
*/
export function fit_to(target, candidate, text) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passArray8ToWasm0(target, wasm.__wbindgen_export);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passArray8ToWasm0(candidate, wasm.__wbindgen_export);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passStringToWasm0(text, wasm.__wbindgen_export, wasm.__wbindgen_export2);
const len2 = WASM_VECTOR_LEN;
wasm.fit_to(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
if (r2) {
throw takeObject(r1);
}
return takeObject(r0);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* Per-pair optical corrections, in em, for a string.
*
* `target` is the white to aim each join at. Pass 0 to use the string's own
* median, which is the right default for a wordmark: the pairs are judged against
* each other rather than against a number from somewhere else.
* @param {Uint8Array} font
* @param {string} text
* @param {number} wght
* @param {number} target
* @returns {any}
*/
export function kern_text(font, text, wght, target) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passArray8ToWasm0(font, wasm.__wbindgen_export);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(text, wasm.__wbindgen_export, wasm.__wbindgen_export2);
const len1 = WASM_VECTOR_LEN;
wasm.kern_text(retptr, ptr0, len0, ptr1, len1, wght, target);
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
if (r2) {
throw takeObject(r1);
}
return takeObject(r0);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* Metrics and the proportions that decide whether one face can stand in for
* another: stem and bar as fractions of cap, and their ratio.
* @param {Uint8Array} font
* @param {number} wght
* @returns {any}
*/
export function measure(font, wght) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passArray8ToWasm0(font, wasm.__wbindgen_export);
const len0 = WASM_VECTOR_LEN;
wasm.measure(retptr, ptr0, len0, wght);
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
if (r2) {
throw takeObject(r1);
}
return takeObject(r0);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* The presets, so a page renders the same five names the CSS does.
* @returns {any}
*/
export function presets() {
const ret = wasm.presets();
return takeObject(ret);
}
function __wbg_get_imports() {
const import0 = {
__proto__: null,
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
// Cast intrinsic for `Ref(String) -> Externref`.
const ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
},
};
return {
__proto__: null,
"./zen_bg.js": import0,
};
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
function dropObject(idx) {
if (idx < 1028) return;
heap[idx] = heap_next;
heap_next = idx;
}
function getArrayU8FromWasm0(ptr, len) {
ptr = ptr >>> 0;
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
}
let cachedDataViewMemory0 = null;
function getDataViewMemory0() {
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
}
return cachedDataViewMemory0;
}
function getStringFromWasm0(ptr, len) {
return decodeText(ptr >>> 0, len);
}
let cachedUint8ArrayMemory0 = null;
function getUint8ArrayMemory0() {
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8ArrayMemory0;
}
function getObject(idx) { return heap[idx]; }
let heap = new Array(1024).fill(undefined);
heap.push(undefined, null, true, false);
let heap_next = heap.length;
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1, 1) >>> 0;
getUint8ArrayMemory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8ArrayMemory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = cachedTextEncoder.encodeInto(arg, view);
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
const MAX_SAFARI_DECODE_BYTES = 2146435072;
let numBytesDecoded = 0;
function decodeText(ptr, len) {
numBytesDecoded += len;
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
numBytesDecoded = len;
}
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
}
const cachedTextEncoder = new TextEncoder();
if (!('encodeInto' in cachedTextEncoder)) {
cachedTextEncoder.encodeInto = function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
};
}
let WASM_VECTOR_LEN = 0;
let wasmModule, wasmInstance, wasm;
function __wbg_finalize_init(instance, module) {
wasmInstance = instance;
wasm = instance.exports;
wasmModule = module;
cachedDataViewMemory0 = null;
cachedUint8ArrayMemory0 = null;
return wasm;
}
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (!module.ok) {
throw new Error(`failed to fetch Wasm: ${module.status} ${module.statusText} fetching '${module.url}'`);
}
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
const validResponse = expectedResponseType(module.type);
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else { throw e; }
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
function expectedResponseType(type) {
switch (type) {
case 'basic': case 'cors': case 'default': return true;
}
return false;
}
}
function initSync(module) {
if (wasm !== undefined) return wasm;
if (module !== undefined) {
if (Object.getPrototypeOf(module) === Object.prototype) {
({module} = module)
} else {
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
}
}
const imports = __wbg_get_imports();
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return __wbg_finalize_init(instance, module);
}
async function __wbg_init(module_or_path) {
if (wasm !== undefined) return wasm;
if (module_or_path !== undefined) {
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
({module_or_path} = module_or_path)
} else {
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
}
}
if (module_or_path === undefined) {
module_or_path = new URL('zen_bg.wasm', import.meta.url);
}
const imports = __wbg_get_imports();
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
module_or_path = fetch(module_or_path);
}
const { instance, module } = await __wbg_load(await module_or_path, imports);
return __wbg_finalize_init(instance, module);
}
export { initSync, __wbg_init as default };