@takumi-rs/helpers 2.0.0-rc.5 → 2.0.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fonts-CrmMlf6O.mjs +1 -0
- package/dist/fonts-gcSpolCe.cjs +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/renderer.cjs +1 -0
- package/dist/renderer.d.cts +133 -0
- package/dist/renderer.d.mts +133 -0
- package/dist/renderer.mjs +1 -0
- package/package.json +11 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=/url\(\s*(['"]?)(.*?)\1\s*\)/g;async function t(e,t={}){let n=t.fetch??globalThis.fetch,r=t.timeout===void 0?void 0:AbortSignal.timeout(t.timeout),i=[t.signal,t.init?.signal,r].filter(e=>e!==void 0),a=i.length?AbortSignal.any(i):void 0,o=await n(e,{...t.init,signal:a});if(!o.ok)throw Error(`HTTP ${o.status} ${o.statusText} fetching ${e}`);return o}function n(e){return e.startsWith(`https://`)||e.startsWith(`http://`)}function r(t,i){if(typeof t==`string`)for(let r of t.matchAll(e)){let e=r[2]?.trim();e&&n(e)&&i.add(e)}else if(Array.isArray(t))for(let e of t)r(e,i)}function i(e){let t=new Set,i=e=>{let a=e=>{e&&(r(e.backgroundImage,t),r(e.maskImage,t))};if(a(e.style),a(e.preset),r(e.tw,t),e.type===`image`){typeof e.src==`string`&&n(e.src)&&t.add(e.src);return}if(e.type===`container`)for(let t of e.children??[])i(t)};return i(e),[...t]}function a(e,n,r){let i=r?.get(e);if(i)return i;let a=t(e,n).then(e=>e.arrayBuffer()).catch(t=>{throw r?.delete(e),t});return r?.set(e,a),a}async function o({node:e,sources:t=[],fetchCache:n,fetch:r,timeout:o=5e3,signal:s,throwOnError:c=!0}){let l=Array.isArray(e)?e:[e],u=new Map;for(let e of t)u.set(e.src,e);let d=[...new Set(l.flatMap(i))].filter(e=>!u.has(e)),f={fetch:r,timeout:o,signal:s},p=d.map(async e=>({src:e,data:await a(e,f,n)})),m=c?await Promise.all(p):(await Promise.allSettled(p)).filter(e=>e.status===`fulfilled`).map(e=>e.value);return[...u.values(),...m]}function s(e,t,n,r){let i=new Map(r),a=n.includes(`italic`),o=a?n.includes(`normal`)?[0,1]:[1]:[void 0],s=[...a?[`ital`]:[],...i.keys(),`wght`].sort(),c=o.flatMap(e=>t.map(t=>s.map(n=>n===`ital`?String(e):n===`wght`?t:i.get(n)??``).join(`,`))).sort();return`${e}:${s.join(`,`)}@${c.join(`;`)}`}function c(e){let t=new URL(e.baseUrl??`https://fonts.googleapis.com/css2`);for(let n of e.families){if(typeof n==`string`){t.searchParams.append(`family`,s(n,[`400`],[`normal`],[]));continue}let e=n.weight??400,r=(Array.isArray(e)?[...e].sort((e,t)=>e-t):[e]).map(String),i=n.style??`normal`,a=(Array.isArray(i)?i:[i]).map(String),o=Object.entries(n.axes??{}).filter(([e])=>e!==`ital`&&e!==`wght`).map(([e,t])=>[e,String(t)]);t.searchParams.append(`family`,s(n.name,r,a,o))}return e.display&&t.searchParams.set(`display`,e.display),t.toString()}function l(e,n){return t(e,{...n,init:{headers:{"User-Agent":`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36`}}}).then(e=>e.text())}async function u(e,t){let n=t.cache?.get(e);if(n!==void 0)return n;let r=await l(e,t);return t.cache?.set(e,r),r}const d=/(?:\/\*\s*([^*]+?)\s*\*\/\s*)?@font-face\s*\{([^}]*)\}/g;function f(e,n){let r=`${e.family} ${e.subset}`,i=e.ranges.map(([e,t])=>`${e}-${t}`).join(`,`);return{name:r,subsetOf:e.family,key:`${r}:${e.weight??``}:${e.style??``}:${i}`,weight:e.weight,style:e.style,ranges:e.ranges,data:()=>t(e.url,n).then(e=>e.arrayBuffer())}}function p(e){let t=[];for(let n of e.split(`,`)){let e=n.trim().replace(/^U\+/i,``);if(e)if(e.includes(`-`)){let[n,r]=e.split(`-`);n&&r&&t.push([parseInt(n,16),parseInt(r,16)])}else if(e.includes(`?`))t.push([parseInt(e.replace(/\?/g,`0`),16),parseInt(e.replace(/\?/g,`F`),16)]);else{let n=parseInt(e,16);t.push([n,n])}}return t}function m(e){let t=[],n=0;for(let r of e.matchAll(d)){let e=r[2];if(!e)continue;let i=e.match(/src:\s*url\(([^)]+)\)/)?.[1]?.replace(/['"]/g,``).trim(),a=e.match(/font-family:\s*['"]?([^'";]+)['"]?/)?.[1]?.trim();if(!i||!a)continue;let o=e.match(/unicode-range:\s*([^;]+)/)?.[1],s=e.match(/font-weight:\s*(\d+)(?:\s+(\d+))?/);t.push({family:a,subset:r[1]?.trim()||`subset-${n}`,url:i,weight:s&&!s[2]?Number(s[1]):void 0,style:e.match(/font-style:\s*([a-z]+)/i)?.[1],ranges:o?p(o):[]}),n+=1}return t}function h(e){let t=new Map;for(let n of e){let e=t.get(n.url)??new Set;e.add(n.weight),t.set(n.url,e)}let n=new Set,r=[];for(let i of e){if(n.has(i.url))continue;n.add(i.url);let e=(t.get(i.url)?.size??0)>1;r.push(e?{...i,weight:void 0}:i)}return r}function g(e){let t=new Set,n=e=>{for(let n of e)t.add(n.codePointAt(0))},r=e=>{e.type===`text`?n(e.text):e.type===`container`&&e.children?.forEach(r)};return typeof e==`string`?n(e):Array.isArray(e)?e.forEach(r):r(e),t}function _(e,t){if(e.length===0)return!0;for(let n of t)for(let[t,r]of e)if(n>=t&&n<=r)return!0;return!1}function v({fonts:e,source:t}){let n=g(t);return e.filter(e=>_(e.ranges??[],n))}function y(e){return{key:e,data:()=>t(e).then(e=>e.arrayBuffer())}}async function b(e){return e.families.length===0?[]:h(m(await u(c(e),e))).map(t=>f(t,e))}export{t as a,v as i,y as n,o,b as r,g as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=/url\(\s*(['"]?)(.*?)\1\s*\)/g;async function t(e,t={}){let n=t.fetch??globalThis.fetch,r=t.timeout===void 0?void 0:AbortSignal.timeout(t.timeout),i=[t.signal,t.init?.signal,r].filter(e=>e!==void 0),a=i.length?AbortSignal.any(i):void 0,o=await n(e,{...t.init,signal:a});if(!o.ok)throw Error(`HTTP ${o.status} ${o.statusText} fetching ${e}`);return o}function n(e){return e.startsWith(`https://`)||e.startsWith(`http://`)}function r(t,i){if(typeof t==`string`)for(let r of t.matchAll(e)){let e=r[2]?.trim();e&&n(e)&&i.add(e)}else if(Array.isArray(t))for(let e of t)r(e,i)}function i(e){let t=new Set,i=e=>{let a=e=>{e&&(r(e.backgroundImage,t),r(e.maskImage,t))};if(a(e.style),a(e.preset),r(e.tw,t),e.type===`image`){typeof e.src==`string`&&n(e.src)&&t.add(e.src);return}if(e.type===`container`)for(let t of e.children??[])i(t)};return i(e),[...t]}function a(e,n,r){let i=r?.get(e);if(i)return i;let a=t(e,n).then(e=>e.arrayBuffer()).catch(t=>{throw r?.delete(e),t});return r?.set(e,a),a}async function o({node:e,sources:t=[],fetchCache:n,fetch:r,timeout:o=5e3,signal:s,throwOnError:c=!0}){let l=Array.isArray(e)?e:[e],u=new Map;for(let e of t)u.set(e.src,e);let d=[...new Set(l.flatMap(i))].filter(e=>!u.has(e)),f={fetch:r,timeout:o,signal:s},p=d.map(async e=>({src:e,data:await a(e,f,n)})),m=c?await Promise.all(p):(await Promise.allSettled(p)).filter(e=>e.status===`fulfilled`).map(e=>e.value);return[...u.values(),...m]}function s(e,t,n,r){let i=new Map(r),a=n.includes(`italic`),o=a?n.includes(`normal`)?[0,1]:[1]:[void 0],s=[...a?[`ital`]:[],...i.keys(),`wght`].sort(),c=o.flatMap(e=>t.map(t=>s.map(n=>n===`ital`?String(e):n===`wght`?t:i.get(n)??``).join(`,`))).sort();return`${e}:${s.join(`,`)}@${c.join(`;`)}`}function c(e){let t=new URL(e.baseUrl??`https://fonts.googleapis.com/css2`);for(let n of e.families){if(typeof n==`string`){t.searchParams.append(`family`,s(n,[`400`],[`normal`],[]));continue}let e=n.weight??400,r=(Array.isArray(e)?[...e].sort((e,t)=>e-t):[e]).map(String),i=n.style??`normal`,a=(Array.isArray(i)?i:[i]).map(String),o=Object.entries(n.axes??{}).filter(([e])=>e!==`ital`&&e!==`wght`).map(([e,t])=>[e,String(t)]);t.searchParams.append(`family`,s(n.name,r,a,o))}return e.display&&t.searchParams.set(`display`,e.display),t.toString()}function l(e,n){return t(e,{...n,init:{headers:{"User-Agent":`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36`}}}).then(e=>e.text())}async function u(e,t){let n=t.cache?.get(e);if(n!==void 0)return n;let r=await l(e,t);return t.cache?.set(e,r),r}const d=/(?:\/\*\s*([^*]+?)\s*\*\/\s*)?@font-face\s*\{([^}]*)\}/g;function f(e,n){let r=`${e.family} ${e.subset}`,i=e.ranges.map(([e,t])=>`${e}-${t}`).join(`,`);return{name:r,subsetOf:e.family,key:`${r}:${e.weight??``}:${e.style??``}:${i}`,weight:e.weight,style:e.style,ranges:e.ranges,data:()=>t(e.url,n).then(e=>e.arrayBuffer())}}function p(e){let t=[];for(let n of e.split(`,`)){let e=n.trim().replace(/^U\+/i,``);if(e)if(e.includes(`-`)){let[n,r]=e.split(`-`);n&&r&&t.push([parseInt(n,16),parseInt(r,16)])}else if(e.includes(`?`))t.push([parseInt(e.replace(/\?/g,`0`),16),parseInt(e.replace(/\?/g,`F`),16)]);else{let n=parseInt(e,16);t.push([n,n])}}return t}function m(e){let t=[],n=0;for(let r of e.matchAll(d)){let e=r[2];if(!e)continue;let i=e.match(/src:\s*url\(([^)]+)\)/)?.[1]?.replace(/['"]/g,``).trim(),a=e.match(/font-family:\s*['"]?([^'";]+)['"]?/)?.[1]?.trim();if(!i||!a)continue;let o=e.match(/unicode-range:\s*([^;]+)/)?.[1],s=e.match(/font-weight:\s*(\d+)(?:\s+(\d+))?/);t.push({family:a,subset:r[1]?.trim()||`subset-${n}`,url:i,weight:s&&!s[2]?Number(s[1]):void 0,style:e.match(/font-style:\s*([a-z]+)/i)?.[1],ranges:o?p(o):[]}),n+=1}return t}function h(e){let t=new Map;for(let n of e){let e=t.get(n.url)??new Set;e.add(n.weight),t.set(n.url,e)}let n=new Set,r=[];for(let i of e){if(n.has(i.url))continue;n.add(i.url);let e=(t.get(i.url)?.size??0)>1;r.push(e?{...i,weight:void 0}:i)}return r}function g(e){let t=new Set,n=e=>{for(let n of e)t.add(n.codePointAt(0))},r=e=>{e.type===`text`?n(e.text):e.type===`container`&&e.children?.forEach(r)};return typeof e==`string`?n(e):Array.isArray(e)?e.forEach(r):r(e),t}function _(e,t){if(e.length===0)return!0;for(let n of t)for(let[t,r]of e)if(n>=t&&n<=r)return!0;return!1}function v({fonts:e,source:t}){let n=g(t);return e.filter(e=>_(e.ranges??[],n))}function y(e){return{key:e,data:()=>t(e).then(e=>e.arrayBuffer())}}async function b(e){return e.families.length===0?[]:h(m(await u(c(e),e))).map(t=>f(t,e))}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return g}});
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./fonts-gcSpolCe.cjs"),t=require("./helpers-4kGcvc-Q.cjs");exports.collectCodepoints=e.t,exports.container=t.t,exports.em=t.n,exports.fetchOk=e.a,exports.fontFromUrl=e.n,exports.fr=t.r,exports.googleFonts=e.r,exports.image=t.i,exports.percentage=t.a,exports.prepareImages=e.o,exports.rem=t.o,exports.rgba=t.s,exports.style=t.c,exports.subsetFonts=e.i,exports.text=t.l,exports.vh=t.u,exports.vw=t.d;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,
|
|
1
|
+
import{a as e,i as t,n,o as r,r as i,t as a}from"./fonts-CrmMlf6O.mjs";import{a as o,c as s,d as c,i as l,l as u,n as d,o as f,r as p,s as m,t as h,u as g}from"./helpers-CVQCT1Rh.mjs";export{a as collectCodepoints,h as container,d as em,e as fetchOk,n as fontFromUrl,p as fr,i as googleFonts,l as image,o as percentage,r as prepareImages,f as rem,m as rgba,s as style,t as subsetFonts,u as text,g as vh,c as vw};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./fonts-gcSpolCe.cjs");function t(e){return e instanceof Uint8Array||e instanceof ArrayBuffer||typeof Buffer<`u`&&Buffer.isBuffer(e)}function n(e){return t(e)?e:typeof e.data==`function`?e.data():e.data}function r(e){return t(e)?e:`key`in e&&e.key?e.key:typeof e.data==`function`?`${e.name??``}:${e.weight??``}:${e.style??``}`:e.data}async function i(e){let{sources:t=[],cache:n}=Array.isArray(e)?{sources:e}:e,r=new Map;for(let e of t)r.set(e.src,e);return Promise.all([...r.values()].map(async({src:e,data:t,cache:r})=>({src:e,data:typeof t==`function`?await t():t,cache:r??n})))}var a=class{registerInner;byKey=new Map;byData=new WeakMap;constructor(e){this.registerInner=e}getFont(e){return typeof e==`string`?this.byKey.get(e):this.byData.get(e)}setFont(e,t){typeof e==`string`?this.byKey.set(e,t):this.byData.set(e,t)}deleteFont(e){typeof e==`string`?this.byKey.delete(e):this.byData.delete(e)}async register(i){let a=typeof i==`string`?e.n(i):i,o=r(a),s=this.getFont(o);if(s)return s;let c=n(a),l=Promise.resolve(c).then(e=>this.registerInner(t(a)?e:{...a,data:e})).catch(e=>{throw this.deleteFont(o),e});return this.setFont(o,l),l}async prepareFonts(e){if(!e)return;let t=await Promise.all(e.map(e=>this.register(e)));return[...new Set(t.flat().map(e=>e.name))]}async resolveResources(e,t,n){let r=await this.prepareFonts(e);return{images:t?await i(t):void 0,fontFamilies:n??r}}};exports.FontRegistry=a,exports.resolveImageLoaders=i;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//#region src/renderer.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared wrapper primitives for the `@takumi-rs/core` (napi) and `@takumi-rs/wasm` renderer
|
|
4
|
+
* bindings. Both backends compose their public `Renderer` from these so the two surfaces cannot
|
|
5
|
+
* drift: the font/image loader shapes, the output-format options, and the font-registration state
|
|
6
|
+
* machine live here once. The concrete `Font`/`ImageSource`/`ByteBuf`/`RegisteredFamily` shapes
|
|
7
|
+
* mirror each backend's generated types field-for-field; the register callback wiring in each
|
|
8
|
+
* backend fails to compile if they diverge.
|
|
9
|
+
*/
|
|
10
|
+
type Awaitable<T> = T | Promise<T>;
|
|
11
|
+
/** Font byte payloads accepted across environments. */
|
|
12
|
+
type ByteBuf = Uint8Array | ArrayBuffer | Buffer;
|
|
13
|
+
/** A font descriptor, matching each backend's generated `FontDetails`. */
|
|
14
|
+
type FontDetails = {
|
|
15
|
+
name?: string;
|
|
16
|
+
data: ByteBuf;
|
|
17
|
+
weight?: number;
|
|
18
|
+
style?: "normal" | "italic" | "oblique" | `oblique ${number}deg` | (string & {});
|
|
19
|
+
/**
|
|
20
|
+
* Logical family this font is a coverage subset of. Subsets sharing a `subsetOf` are
|
|
21
|
+
* kept as distinct families and `font-family: {subsetOf}` expands to all of them, so each
|
|
22
|
+
* script routes to the subset that covers it.
|
|
23
|
+
*/
|
|
24
|
+
subsetOf?: string;
|
|
25
|
+
};
|
|
26
|
+
/** A registered font, either detailed or raw bytes. */
|
|
27
|
+
type Font = FontDetails | ByteBuf;
|
|
28
|
+
/** Cache policy for a decoded image. Defaults to `"auto"`. */
|
|
29
|
+
type ImageCacheMode = "auto" | "none";
|
|
30
|
+
/** An image source with its URL and raw bytes, matching each backend's generated `ImageSource`. */
|
|
31
|
+
type ImageSource = {
|
|
32
|
+
src: string;
|
|
33
|
+
data: ByteBuf;
|
|
34
|
+
cache?: ImageCacheMode;
|
|
35
|
+
};
|
|
36
|
+
/** A font family produced by `registerFont`. Only `name` is read here; backends keep the full type. */
|
|
37
|
+
type RegisteredFamilyLike = {
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A font to register. Either a URL string (fetched on demand, with name/weight/style read from the
|
|
42
|
+
* file), raw bytes, or a descriptor with a lazy `data()` loader.
|
|
43
|
+
*/
|
|
44
|
+
type FontLoader = string | Font | (Omit<FontDetails, "data"> & {
|
|
45
|
+
data: () => Promise<FontDetails["data"]> | FontDetails["data"]; /** Inclusive codepoint ranges this face covers; lets `render` skip it when unused. */
|
|
46
|
+
ranges?: [number, number][];
|
|
47
|
+
} & ({
|
|
48
|
+
key: string;
|
|
49
|
+
} | {
|
|
50
|
+
name: string;
|
|
51
|
+
}));
|
|
52
|
+
type ImageLoader = Omit<ImageSource, "data"> & {
|
|
53
|
+
data: ImageSource["data"] | (() => ImageSource["data"] | Promise<ImageSource["data"]>);
|
|
54
|
+
};
|
|
55
|
+
/** Images for a render: pre-fetched entries, or a group with a decode-cache default. */
|
|
56
|
+
type ImagesInput = ImageLoader[] | {
|
|
57
|
+
/** Pre-fetched entries, same as the array form. */sources?: ImageLoader[]; /** Decode-cache default for every image this render; a source's own `cache` wins. */
|
|
58
|
+
cache?: NonNullable<ImageLoader["cache"]>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Output format. Format-specific options live on the variant that supports them, so `quality`
|
|
62
|
+
* cannot be paired with PNG/ICO/raw, and `lossless` is WebP-only. On native (`@takumi-rs/core`),
|
|
63
|
+
* WebP honors both — `lossless` takes precedence over `quality`, and omitting both encodes
|
|
64
|
+
* losslessly. On wasm (`@takumi-rs/wasm`) WebP is always lossless: `lossless` is effectively always
|
|
65
|
+
* on and `quality` is ignored for WebP (lossy WebP is native-only).
|
|
66
|
+
*/
|
|
67
|
+
type OutputFormatOptions = {
|
|
68
|
+
format?: "png";
|
|
69
|
+
} | {
|
|
70
|
+
format: "jpeg";
|
|
71
|
+
quality?: number;
|
|
72
|
+
} | {
|
|
73
|
+
format: "webp";
|
|
74
|
+
quality?: number;
|
|
75
|
+
lossless?: boolean;
|
|
76
|
+
} | {
|
|
77
|
+
format: "ico";
|
|
78
|
+
} | {
|
|
79
|
+
format: "raw";
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Animation output format. `quality` and `lossless` are WebP-only. On native, `lossless` takes
|
|
83
|
+
* precedence over `quality` and omitting both encodes losslessly; on wasm animated WebP is always
|
|
84
|
+
* lossless and `quality` is ignored.
|
|
85
|
+
*/
|
|
86
|
+
type AnimationOutputFormatOptions = {
|
|
87
|
+
format?: "webp";
|
|
88
|
+
quality?: number;
|
|
89
|
+
lossless?: boolean;
|
|
90
|
+
} | {
|
|
91
|
+
format: "apng";
|
|
92
|
+
} | {
|
|
93
|
+
format: "gif";
|
|
94
|
+
};
|
|
95
|
+
/** The wrapper-managed extras every render entry point accepts on top of the binding options. */
|
|
96
|
+
type RenderExtras = {
|
|
97
|
+
fonts?: FontLoader[];
|
|
98
|
+
signal?: AbortSignal;
|
|
99
|
+
images?: ImagesInput;
|
|
100
|
+
};
|
|
101
|
+
/** Normalize an {@link ImagesInput} into concrete, deduped {@link ImageSource} entries. */
|
|
102
|
+
declare function resolveImageLoaders(images: ImagesInput): Promise<ImageSource[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Font-registration state machine shared by both backends: dedupes registrations by a stable key
|
|
105
|
+
* (or by buffer identity), resolves lazy `data()` loaders once, and turns a set of {@link FontLoader}s
|
|
106
|
+
* plus an {@link ImagesInput} into the `fontFamilies`/`images` the binding expects.
|
|
107
|
+
*
|
|
108
|
+
* `registerInner` is the backend's raw `registerFont`; its precise `RegisteredFamily` type flows
|
|
109
|
+
* back out through {@link TFamily}.
|
|
110
|
+
*/
|
|
111
|
+
declare class FontRegistry<TFamily extends RegisteredFamilyLike> {
|
|
112
|
+
private readonly registerInner;
|
|
113
|
+
private readonly byKey;
|
|
114
|
+
private readonly byData;
|
|
115
|
+
constructor(registerInner: (font: Font) => Awaitable<TFamily[]>);
|
|
116
|
+
private getFont;
|
|
117
|
+
private setFont;
|
|
118
|
+
private deleteFont;
|
|
119
|
+
/** Registers one font, deduped against earlier registrations. */
|
|
120
|
+
register(font: FontLoader): Promise<TFamily[]>;
|
|
121
|
+
/** Registers every font and returns the distinct family names produced. */
|
|
122
|
+
prepareFonts(fonts: FontLoader[] | undefined): Promise<string[] | undefined>;
|
|
123
|
+
/**
|
|
124
|
+
* Registers `fonts` and resolves lazy `images`, yielding the `images`/`fontFamilies` the binding
|
|
125
|
+
* expects. Explicit `fontFamilies` wins over the registered set.
|
|
126
|
+
*/
|
|
127
|
+
resolveResources(fonts: FontLoader[] | undefined, images: ImagesInput | undefined, fontFamilies: string[] | undefined): Promise<{
|
|
128
|
+
images?: ImageSource[];
|
|
129
|
+
fontFamilies?: string[];
|
|
130
|
+
}>;
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { AnimationOutputFormatOptions, ByteBuf, Font, FontDetails, FontLoader, FontRegistry, ImageCacheMode, ImageLoader, ImageSource, ImagesInput, OutputFormatOptions, RegisteredFamilyLike, RenderExtras, resolveImageLoaders };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//#region src/renderer.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared wrapper primitives for the `@takumi-rs/core` (napi) and `@takumi-rs/wasm` renderer
|
|
4
|
+
* bindings. Both backends compose their public `Renderer` from these so the two surfaces cannot
|
|
5
|
+
* drift: the font/image loader shapes, the output-format options, and the font-registration state
|
|
6
|
+
* machine live here once. The concrete `Font`/`ImageSource`/`ByteBuf`/`RegisteredFamily` shapes
|
|
7
|
+
* mirror each backend's generated types field-for-field; the register callback wiring in each
|
|
8
|
+
* backend fails to compile if they diverge.
|
|
9
|
+
*/
|
|
10
|
+
type Awaitable<T> = T | Promise<T>;
|
|
11
|
+
/** Font byte payloads accepted across environments. */
|
|
12
|
+
type ByteBuf = Uint8Array | ArrayBuffer | Buffer;
|
|
13
|
+
/** A font descriptor, matching each backend's generated `FontDetails`. */
|
|
14
|
+
type FontDetails = {
|
|
15
|
+
name?: string;
|
|
16
|
+
data: ByteBuf;
|
|
17
|
+
weight?: number;
|
|
18
|
+
style?: "normal" | "italic" | "oblique" | `oblique ${number}deg` | (string & {});
|
|
19
|
+
/**
|
|
20
|
+
* Logical family this font is a coverage subset of. Subsets sharing a `subsetOf` are
|
|
21
|
+
* kept as distinct families and `font-family: {subsetOf}` expands to all of them, so each
|
|
22
|
+
* script routes to the subset that covers it.
|
|
23
|
+
*/
|
|
24
|
+
subsetOf?: string;
|
|
25
|
+
};
|
|
26
|
+
/** A registered font, either detailed or raw bytes. */
|
|
27
|
+
type Font = FontDetails | ByteBuf;
|
|
28
|
+
/** Cache policy for a decoded image. Defaults to `"auto"`. */
|
|
29
|
+
type ImageCacheMode = "auto" | "none";
|
|
30
|
+
/** An image source with its URL and raw bytes, matching each backend's generated `ImageSource`. */
|
|
31
|
+
type ImageSource = {
|
|
32
|
+
src: string;
|
|
33
|
+
data: ByteBuf;
|
|
34
|
+
cache?: ImageCacheMode;
|
|
35
|
+
};
|
|
36
|
+
/** A font family produced by `registerFont`. Only `name` is read here; backends keep the full type. */
|
|
37
|
+
type RegisteredFamilyLike = {
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A font to register. Either a URL string (fetched on demand, with name/weight/style read from the
|
|
42
|
+
* file), raw bytes, or a descriptor with a lazy `data()` loader.
|
|
43
|
+
*/
|
|
44
|
+
type FontLoader = string | Font | (Omit<FontDetails, "data"> & {
|
|
45
|
+
data: () => Promise<FontDetails["data"]> | FontDetails["data"]; /** Inclusive codepoint ranges this face covers; lets `render` skip it when unused. */
|
|
46
|
+
ranges?: [number, number][];
|
|
47
|
+
} & ({
|
|
48
|
+
key: string;
|
|
49
|
+
} | {
|
|
50
|
+
name: string;
|
|
51
|
+
}));
|
|
52
|
+
type ImageLoader = Omit<ImageSource, "data"> & {
|
|
53
|
+
data: ImageSource["data"] | (() => ImageSource["data"] | Promise<ImageSource["data"]>);
|
|
54
|
+
};
|
|
55
|
+
/** Images for a render: pre-fetched entries, or a group with a decode-cache default. */
|
|
56
|
+
type ImagesInput = ImageLoader[] | {
|
|
57
|
+
/** Pre-fetched entries, same as the array form. */sources?: ImageLoader[]; /** Decode-cache default for every image this render; a source's own `cache` wins. */
|
|
58
|
+
cache?: NonNullable<ImageLoader["cache"]>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Output format. Format-specific options live on the variant that supports them, so `quality`
|
|
62
|
+
* cannot be paired with PNG/ICO/raw, and `lossless` is WebP-only. On native (`@takumi-rs/core`),
|
|
63
|
+
* WebP honors both — `lossless` takes precedence over `quality`, and omitting both encodes
|
|
64
|
+
* losslessly. On wasm (`@takumi-rs/wasm`) WebP is always lossless: `lossless` is effectively always
|
|
65
|
+
* on and `quality` is ignored for WebP (lossy WebP is native-only).
|
|
66
|
+
*/
|
|
67
|
+
type OutputFormatOptions = {
|
|
68
|
+
format?: "png";
|
|
69
|
+
} | {
|
|
70
|
+
format: "jpeg";
|
|
71
|
+
quality?: number;
|
|
72
|
+
} | {
|
|
73
|
+
format: "webp";
|
|
74
|
+
quality?: number;
|
|
75
|
+
lossless?: boolean;
|
|
76
|
+
} | {
|
|
77
|
+
format: "ico";
|
|
78
|
+
} | {
|
|
79
|
+
format: "raw";
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Animation output format. `quality` and `lossless` are WebP-only. On native, `lossless` takes
|
|
83
|
+
* precedence over `quality` and omitting both encodes losslessly; on wasm animated WebP is always
|
|
84
|
+
* lossless and `quality` is ignored.
|
|
85
|
+
*/
|
|
86
|
+
type AnimationOutputFormatOptions = {
|
|
87
|
+
format?: "webp";
|
|
88
|
+
quality?: number;
|
|
89
|
+
lossless?: boolean;
|
|
90
|
+
} | {
|
|
91
|
+
format: "apng";
|
|
92
|
+
} | {
|
|
93
|
+
format: "gif";
|
|
94
|
+
};
|
|
95
|
+
/** The wrapper-managed extras every render entry point accepts on top of the binding options. */
|
|
96
|
+
type RenderExtras = {
|
|
97
|
+
fonts?: FontLoader[];
|
|
98
|
+
signal?: AbortSignal;
|
|
99
|
+
images?: ImagesInput;
|
|
100
|
+
};
|
|
101
|
+
/** Normalize an {@link ImagesInput} into concrete, deduped {@link ImageSource} entries. */
|
|
102
|
+
declare function resolveImageLoaders(images: ImagesInput): Promise<ImageSource[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Font-registration state machine shared by both backends: dedupes registrations by a stable key
|
|
105
|
+
* (or by buffer identity), resolves lazy `data()` loaders once, and turns a set of {@link FontLoader}s
|
|
106
|
+
* plus an {@link ImagesInput} into the `fontFamilies`/`images` the binding expects.
|
|
107
|
+
*
|
|
108
|
+
* `registerInner` is the backend's raw `registerFont`; its precise `RegisteredFamily` type flows
|
|
109
|
+
* back out through {@link TFamily}.
|
|
110
|
+
*/
|
|
111
|
+
declare class FontRegistry<TFamily extends RegisteredFamilyLike> {
|
|
112
|
+
private readonly registerInner;
|
|
113
|
+
private readonly byKey;
|
|
114
|
+
private readonly byData;
|
|
115
|
+
constructor(registerInner: (font: Font) => Awaitable<TFamily[]>);
|
|
116
|
+
private getFont;
|
|
117
|
+
private setFont;
|
|
118
|
+
private deleteFont;
|
|
119
|
+
/** Registers one font, deduped against earlier registrations. */
|
|
120
|
+
register(font: FontLoader): Promise<TFamily[]>;
|
|
121
|
+
/** Registers every font and returns the distinct family names produced. */
|
|
122
|
+
prepareFonts(fonts: FontLoader[] | undefined): Promise<string[] | undefined>;
|
|
123
|
+
/**
|
|
124
|
+
* Registers `fonts` and resolves lazy `images`, yielding the `images`/`fontFamilies` the binding
|
|
125
|
+
* expects. Explicit `fontFamilies` wins over the registered set.
|
|
126
|
+
*/
|
|
127
|
+
resolveResources(fonts: FontLoader[] | undefined, images: ImagesInput | undefined, fontFamilies: string[] | undefined): Promise<{
|
|
128
|
+
images?: ImageSource[];
|
|
129
|
+
fontFamilies?: string[];
|
|
130
|
+
}>;
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { AnimationOutputFormatOptions, ByteBuf, Font, FontDetails, FontLoader, FontRegistry, ImageCacheMode, ImageLoader, ImageSource, ImagesInput, OutputFormatOptions, RegisteredFamilyLike, RenderExtras, resolveImageLoaders };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e}from"./fonts-CrmMlf6O.mjs";function t(e){return e instanceof Uint8Array||e instanceof ArrayBuffer||typeof Buffer<`u`&&Buffer.isBuffer(e)}function n(e){return t(e)?e:typeof e.data==`function`?e.data():e.data}function r(e){return t(e)?e:`key`in e&&e.key?e.key:typeof e.data==`function`?`${e.name??``}:${e.weight??``}:${e.style??``}`:e.data}async function i(e){let{sources:t=[],cache:n}=Array.isArray(e)?{sources:e}:e,r=new Map;for(let e of t)r.set(e.src,e);return Promise.all([...r.values()].map(async({src:e,data:t,cache:r})=>({src:e,data:typeof t==`function`?await t():t,cache:r??n})))}var a=class{registerInner;byKey=new Map;byData=new WeakMap;constructor(e){this.registerInner=e}getFont(e){return typeof e==`string`?this.byKey.get(e):this.byData.get(e)}setFont(e,t){typeof e==`string`?this.byKey.set(e,t):this.byData.set(e,t)}deleteFont(e){typeof e==`string`?this.byKey.delete(e):this.byData.delete(e)}async register(i){let a=typeof i==`string`?e(i):i,o=r(a),s=this.getFont(o);if(s)return s;let c=n(a),l=Promise.resolve(c).then(e=>this.registerInner(t(a)?e:{...a,data:e})).catch(e=>{throw this.deleteFont(o),e});return this.setFont(o,l),l}async prepareFonts(e){if(!e)return;let t=await Promise.all(e.map(e=>this.register(e)));return[...new Set(t.flat().map(e=>e.name))]}async resolveResources(e,t,n){let r=await this.prepareFonts(e);return{images:t?await i(t):void 0,fontFamilies:n??r}}};export{a as FontRegistry,i as resolveImageLoaders};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takumi-rs/helpers",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.6",
|
|
4
4
|
"description": "Utility helpers for converting JSX/HTML to Takumi node trees, loading fonts, and processing emoji.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -74,6 +74,16 @@
|
|
|
74
74
|
"types": "./dist/emoji.d.cts",
|
|
75
75
|
"default": "./dist/emoji.cjs"
|
|
76
76
|
}
|
|
77
|
+
},
|
|
78
|
+
"./renderer": {
|
|
79
|
+
"import": {
|
|
80
|
+
"types": "./dist/renderer.d.mts",
|
|
81
|
+
"default": "./dist/renderer.mjs"
|
|
82
|
+
},
|
|
83
|
+
"require": {
|
|
84
|
+
"types": "./dist/renderer.d.cts",
|
|
85
|
+
"default": "./dist/renderer.cjs"
|
|
86
|
+
}
|
|
77
87
|
}
|
|
78
88
|
},
|
|
79
89
|
"publishConfig": {
|