@overpunch/vf-clamp 2.1.4 → 2.2.0

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/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const N=require("node:module");var g=typeof document<"u"?document.currentScript:null;const O=N.createRequire(typeof document>"u"?require("url").pathToFileURL(__filename).href:g&&g.tagName.toUpperCase()==="SCRIPT"&&g.src||new URL("index.cjs",document.baseURI).href),{preparePyodide:l,PyodideFile:u}=O("@web-alchemy/fonttools/src/pyodide.js");let h=null;async function j(){return h||(h=l().then(e=>e.runPythonAsync(`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const $=require("node:module");var T=typeof document<"u"?document.currentScript:null;const k=$.createRequire(typeof document>"u"?require("url").pathToFileURL(__filename).href:T&&T.tagName.toUpperCase()==="SCRIPT"&&T.src||new URL("index.cjs",document.baseURI).href),{preparePyodide:d,PyodideFile:x}=k("@web-alchemy/fonttools/src/pyodide.js");let b=null;async function C(){return b||(b=d().then(t=>t.runPythonAsync(`
2
2
  from fontTools.ttLib import TTFont
3
3
 
4
4
  def vf_convert_flavor(file_options):
@@ -7,7 +7,7 @@ def vf_convert_flavor(file_options):
7
7
  font.save(file_options['output-file'])
8
8
 
9
9
  vf_convert_flavor
10
- `))),h}async function V(e,o){const a=await l(),t=new u({pyodide:a}),n=new u({pyodide:a});try{await t.upload(e);const i=new Map([["input-file",t.filename],["output-file",n.filename],["flavor",o]]);return(await j())(i),n.download()}finally{try{t.delete()}catch{}try{n.delete()}catch{}}}async function I(e){return V(e,"woff")}async function D(e){return V(e,"woff2")}let w=null;async function L(){return w||(w=l().then(e=>e.runPythonAsync(`
10
+ `))),b}async function P(t,o){const a=await d(),e=new x({pyodide:a}),n=new x({pyodide:a});try{await e.upload(t);const i=new Map([["input-file",e.filename],["output-file",n.filename],["flavor",o]]);return(await C())(i),n.download()}finally{try{e.delete()}catch{}try{n.delete()}catch{}}}async function j(t){return P(t,"woff")}async function M(t){return P(t,"woff2")}let F=null;async function E(){return F||(F=d().then(t=>t.runPythonAsync(`
11
11
  import json
12
12
  from fontTools.ttLib import TTFont
13
13
 
@@ -42,7 +42,7 @@ def get_instances_fn(input_file):
42
42
  return json.dumps({'axes': axes, 'instances': instances})
43
43
 
44
44
  get_instances_fn
45
- `))),w}async function R(e){const o=await l(),a=e instanceof ArrayBuffer?new Uint8Array(e):e,t=new u({pyodide:o});try{await t.upload(a);const i=(await L())(t.filename);return JSON.parse(i)}finally{try{t.delete()}catch{}}}let v=null,y=null,A=null,T=null,b=null;async function M(){return v||(v=l().then(e=>e.runPythonAsync(`
45
+ `))),F}async function L(t){const o=await d(),a=t instanceof ArrayBuffer?new Uint8Array(t):t,e=new x({pyodide:o});try{await e.upload(a);const i=(await E())(e.filename);return JSON.parse(i)}finally{try{e.delete()}catch{}}}let S=null,I=null,V=null,D=null,O=null;async function z(){return S||(S=d().then(t=>t.runPythonAsync(`
46
46
  from fontTools import ttLib
47
47
 
48
48
  def patch_font_names_fn(file_options):
@@ -95,7 +95,7 @@ def patch_font_names_fn(file_options):
95
95
  font.save(file_options['output-file'])
96
96
 
97
97
  patch_font_names_fn
98
- `))),v}async function U(){return y||(y=l().then(e=>e.runPythonAsync(`
98
+ `))),S}async function W(){return I||(I=d().then(t=>t.runPythonAsync(`
99
99
  from fontTools.ttLib import TTFont
100
100
  from fontTools.varLib import instancer
101
101
  import json
@@ -124,7 +124,7 @@ def vf_clamp_instantiate(file_options, axes_json):
124
124
  partial.save(file_options['output-file'])
125
125
 
126
126
  vf_clamp_instantiate
127
- `))),y}async function k(){return A||(A=l().then(e=>e.runPythonAsync(`
127
+ `))),I}async function q(){return V||(V=d().then(t=>t.runPythonAsync(`
128
128
  from fontTools.ttLib import TTFont
129
129
 
130
130
  def vf_clamp_normalize_wght(file_options, new_min_str):
@@ -181,7 +181,7 @@ def vf_clamp_normalize_wght(file_options, new_min_str):
181
181
  font.save(file_options['output-file'])
182
182
 
183
183
  vf_clamp_normalize_wght
184
- `))),A}async function C(){return T||(T=l().then(e=>e.runPythonAsync(`
184
+ `))),V}async function B(){return D||(D=d().then(t=>t.runPythonAsync(`
185
185
  from fontTools.ttLib import TTFont
186
186
 
187
187
  def vf_clamp_prune_stat(file_options):
@@ -239,7 +239,7 @@ def vf_clamp_prune_stat(file_options):
239
239
  font.save(file_options['output-file'])
240
240
 
241
241
  vf_clamp_prune_stat
242
- `))),T}async function $(){return b||(b=l().then(e=>e.runPythonAsync(`
242
+ `))),D}async function G(){return O||(O=d().then(t=>t.runPythonAsync(`
243
243
  from fontTools.ttLib import TTFont
244
244
 
245
245
  def vf_clamp_update_os2(file_options):
@@ -284,4 +284,4 @@ def vf_clamp_update_os2(file_options):
284
284
  font.save(file_options['output-file'])
285
285
 
286
286
  vf_clamp_update_os2
287
- `))),b}function z(e){return e.replace(/[^A-Za-z0-9 -]/g,"").trim().split(/\s+/).join("-").replace(/^-+|-+$/g,"").slice(0,63)}async function W(e,o){if(!o)return e;const a=await l(),t=new u({pyodide:a}),n=new u({pyodide:a});try{const i=z(o);await t.upload(e);const r=new Map([["input-file",t.filename],["output-file",n.filename],["family-name",o],["postscript-name",i]]);return(await M())(r),n.download()}catch(i){return console.warn(`vf-clamp: name table patching failed for "${o}" — returning unpatched buffer`,i),e}finally{try{t.delete()}catch{}try{n.delete()}catch{}}}function q(e){return typeof e=="number"?e:e===null?null:[e.min,e.max]}async function B(e,o){const a=await l(),t=new u({pyodide:a}),n=new u({pyodide:a});try{await t.upload(e);const i=new Map([["input-file",t.filename],["output-file",n.filename]]);return(await U())(i,JSON.stringify(o)),n.download()}finally{try{t.delete()}catch{}try{n.delete()}catch{}}}async function E(e){const o=await l(),a=new u({pyodide:o}),t=new u({pyodide:o});try{await a.upload(e);const n=new Map([["input-file",a.filename],["output-file",t.filename]]);return(await C())(n),t.download()}catch(n){return console.warn("vf-clamp: STAT prune failed — returning unpruned buffer",n),e}finally{try{a.delete()}catch{}try{t.delete()}catch{}}}async function G(e){const o=await l(),a=new u({pyodide:o}),t=new u({pyodide:o});try{await a.upload(e);const n=new Map([["input-file",a.filename],["output-file",t.filename]]);return(await $())(n),t.download()}catch(n){return console.warn("vf-clamp: OS/2 + macStyle update failed — returning original buffer",n),e}finally{try{a.delete()}catch{}try{t.delete()}catch{}}}async function J(e,o){const a=await l(),t=new u({pyodide:a}),n=new u({pyodide:a});try{await t.upload(e);const i=new Map([["input-file",t.filename],["output-file",n.filename]]);return(await k())(i,String(o)),n.download()}catch(i){return console.warn("vf-clamp: wght normalisation failed — returning unnormalised buffer",i),e}finally{try{t.delete()}catch{}try{n.delete()}catch{}}}function H(e,o){const a=new Map(o.map(i=>[i.name,i])),t={};for(const i of e){const r=a.get(i);if(!r)throw new Error(`Named instance "${i}" not found in font`);for(const[s,m]of Object.entries(r.coordinates))t[s]?(t[s].min=Math.min(t[s].min,m),t[s].max=Math.max(t[s].max,m)):t[s]={min:m,max:m}}const n={};for(const[i,{min:r,max:s}]of Object.entries(t))n[i]=r===s?r:{min:r,max:s};return n}async function Z(e,o){var s,m;if(o.outputs.length===0)return[];const a=e instanceof ArrayBuffer?new Uint8Array(e):e,t=o.format??"ttf";let n=[],i=[];if(o.outputs.some(f=>{var p;return(p=f.instances)==null?void 0:p.length})){const f=await R(a);n=f.instances,i=f.axes}const r=[];for(const f of o.outputs){let p={};(s=f.instances)!=null&&s.length&&(p=H(f.instances,n)),f.axes&&(p={...p,...f.axes});for(const d of i){const x=p[d.tag];if(x!=null&&typeof x=="object"){const _=x;if(d.default<_.min||d.default>_.max){const P=Math.max(_.min,Math.min(_.max,d.default));console.warn(`vf-clamp: axis "${d.tag}" default (${d.default}) is outside restricted range [${_.min}, ${_.max}] — will be clamped to ${P}`)}}}const F={};for(const[d,x]of Object.entries(p))x!==null&&(F[d]=q(x));const S=f.name??((m=f.instances)!=null&&m.length?f.instances.length===1?f.instances[0]:`${f.instances[0]}-${f.instances[f.instances.length-1]}`:"output");let c=await B(a,F);c=await E(c),o.normalizeWeightAxis&&(c=await J(c,100)),c=await G(c),c=await W(c,S),t==="woff2"?c=await D(c):t==="woff"&&(c=await I(c)),r.push({name:S,buffer:c,format:t})}return r}function K(e,o){if(e===o)return e;const a=e.split(" "),t=o.split(" ");let n=0;for(;n<a.length&&n<t.length&&a[n]===t[n];)n++;let i=0;for(;i<a.length-n&&i<t.length-n&&a[a.length-1-i]===t[t.length-1-i];)i++;const r=a.slice(0,n).join(" "),s=a.slice(n,a.length-(i||0)).join(" "),m=t.slice(n,t.length-(i||0)).join(" "),f=i>0?a.slice(a.length-i).join(" "):"",p=s&&m?`${s}-${m}`:s||m;return[r,p,f].filter(Boolean).join(" ")}exports.clampFont=Z;exports.compactName=K;exports.convertToWoff=I;exports.convertToWoff2=D;exports.getInstances=R;
287
+ `))),O}function H(t){return t.replace(/[^A-Za-z0-9 -]/g,"").trim().split(/\s+/).join("-").replace(/^-+|-+$/g,"").slice(0,63)}async function J(t,o){if(!o)return t;const a=await d(),e=new x({pyodide:a}),n=new x({pyodide:a});try{const i=H(o);await e.upload(t);const s=new Map([["input-file",e.filename],["output-file",n.filename],["family-name",o],["postscript-name",i]]);return(await z())(s),n.download()}catch(i){throw Object.assign(new Error(`vf-clamp: name table patching failed for "${o}"`),{cause:i})}finally{try{e.delete()}catch{}try{n.delete()}catch{}}}function Z(t){return typeof t=="number"?t:t===null?null:[t.min,t.max]}async function K(t,o){const a=await d(),e=new x({pyodide:a}),n=new x({pyodide:a});try{await e.upload(t);const i=new Map([["input-file",e.filename],["output-file",n.filename]]);return(await W())(i,JSON.stringify(o)),n.download()}finally{try{e.delete()}catch{}try{n.delete()}catch{}}}async function Q(t){const o=await d(),a=new x({pyodide:o}),e=new x({pyodide:o});try{await a.upload(t);const n=new Map([["input-file",a.filename],["output-file",e.filename]]);return(await B())(n),e.download()}catch(n){throw Object.assign(new Error("vf-clamp: STAT pruning failed"),{cause:n})}finally{try{a.delete()}catch{}try{e.delete()}catch{}}}async function X(t){const o=await d(),a=new x({pyodide:o}),e=new x({pyodide:o});try{await a.upload(t);const n=new Map([["input-file",a.filename],["output-file",e.filename]]);return(await G())(n),e.download()}catch(n){throw Object.assign(new Error("vf-clamp: OS/2 and macStyle update failed"),{cause:n})}finally{try{a.delete()}catch{}try{e.delete()}catch{}}}async function Y(t,o){const a=await d(),e=new x({pyodide:a}),n=new x({pyodide:a});try{await e.upload(t);const i=new Map([["input-file",e.filename],["output-file",n.filename]]);return(await q())(i,String(o)),n.download()}catch(i){throw Object.assign(new Error("vf-clamp: wght normalisation failed"),{cause:i})}finally{try{e.delete()}catch{}try{n.delete()}catch{}}}function tt(t,o){const a=new Map(o.map(i=>[i.name,i])),e={};for(const i of t){const s=a.get(i);if(!s)throw new Error(`Named instance "${i}" not found in font`);for(const[f,c]of Object.entries(s.coordinates))e[f]?(e[f].min=Math.min(e[f].min,c),e[f].max=Math.max(e[f].max,c)):e[f]={min:c,max:c}}const n={};for(const[i,{min:s,max:f}]of Object.entries(e))n[i]=s===f?s:{min:s,max:f};return n}function et(t){return t.length>=4&&t[0]===79&&t[1]===84&&t[2]===84&&t[3]===79}async function nt(t,o){var f,c,w;if(o.outputs.length===0)return[];const a=t instanceof ArrayBuffer?new Uint8Array(t):t,e=o.format??"ttf",{instances:n,axes:i}=await L(a),s=[];for(const m of o.outputs){let _={};if((f=m.instances)!=null&&f.length&&(_=tt(m.instances,n)),m.axes&&(_={..._,...m.axes}),o.strict&&((c=m.instances)!=null&&c.length)){const p=new Set(m.instances),g=n.filter(h=>!p.has(h.name)&&i.every(y=>{const A=h.coordinates[y.tag]??y.default,v=_[y.tag];return v==null?!0:typeof v=="number"?A===v:A>=v.min&&A<=v.max})).map(h=>h.name);if(g.length)throw new Error(`vf-clamp: output "${m.name||m.instances.join(", ")}" would include unselected instances (${g.join(", ")}); use planOutputs() to split the selection`)}for(const p of i){const g=_[p.tag];if(g!=null&&typeof g=="object"){const h=g;if(p.default<h.min||p.default>h.max){const y=Math.max(h.min,Math.min(h.max,p.default));console.warn(`vf-clamp: axis "${p.tag}" default (${p.default}) is outside restricted range [${h.min}, ${h.max}] — will be clamped to ${y}`)}}}const r={};for(const[p,g]of Object.entries(_))g!==null&&(r[p]=Z(g));const u=m.name||((w=m.instances)!=null&&w.length?m.instances.length===1?m.instances[0]:`${m.instances[0]}-${m.instances[m.instances.length-1]}`:"output");let l=await K(a,r);if(e==="otf"&&!et(l))throw new Error("vf-clamp: format 'otf' needs a CFF/CFF2 source font; this font has TrueType outlines — use 'ttf'");l=await Q(l),o.normalizeWeightAxis&&(l=await Y(l,100)),l=await X(l),l=await J(l,u),e==="woff2"?l=await M(l):e==="woff"&&(l=await j(l)),s.push({name:u,buffer:l,format:e})}return s}function U(t,o){if(t===o)return t;const a=t.split(" "),e=o.split(" ");let n=0;for(;n<a.length&&n<e.length&&a[n]===e[n];)n++;let i=0;for(;i<a.length-n&&i<e.length-n&&a[a.length-1-i]===e[e.length-1-i];)i++;const s=a.slice(0,n).join(" "),f=a.slice(n,a.length-(i||0)).join(" "),c=e.slice(n,e.length-(i||0)).join(" "),w=i>0?a.slice(a.length-i).join(" "):"",m=f&&c?`${f}-${c}`:f||c;return[s,m,w].filter(Boolean).join(" ")}function R(t,o){const a={};for(const e of o){const n=t.map(i=>i.coordinates[e.tag]??e.default);a[e.tag]={min:Math.min(...n),max:Math.max(...n)}}return a}function N(t,o,a,e){return o.filter(n=>a.has(n.name)?!1:e.every(i=>{const s=n.coordinates[i.tag]??i.default,f=t[i.tag];return!f||s>=f.min&&s<=f.max}))}function at(t){return t.reduce((o,a)=>!o||a.maximum-a.minimum>o.maximum-o.minimum?a:o,void 0)}function it(t,o,a){const{axes:e,instances:n}=t,i=new Map(n.map(r=>[r.name,r])),s=[];for(const r of new Set(o)){const u=i.get(r);if(!u)throw new Error(`Named instance "${r}" not found in font`);s.push(u)}if(!s.length)return[];const f=new Set(s.map(r=>r.name));let c;if(!N(R(s,e),n,f,e).length)c=[s];else{c=s.map(u=>[u]);let r=!0;for(;r;){r=!1;t:for(let u=0;u<c.length;u++)for(let l=u+1;l<c.length;l++){const p=[...c[u],...c[l]];if(!N(R(p,e),n,f,e).length){c[u]=p,c.splice(l,1),r=!0;break t}}}}const w=at(e),m=w?[w,...e.filter(r=>r.tag!==w.tag)]:e,_=(r,u)=>{for(const l of m){const p=(r.coordinates[l.tag]??l.default)-(u.coordinates[l.tag]??l.default);if(p)return p}return 0};return c=c.map(r=>[...r].sort(_)).sort((r,u)=>_(r[0],u[0])),c.map(r=>{const u=r[0],l=r[r.length-1],p=r.length===1?u.name:U(u.name,l.name);return{name:a?`${a} ${p}`:p,instances:r.map(g=>g.name)}})}function ot(t,o){const a=new Map(t.instances.map(n=>[n.name,n])),e=o.map(n=>a.get(n)).filter(n=>!!n);return e.length?N(R(e,t.axes),t.instances,new Set(o),t.axes).map(n=>n.name):[]}exports.clampFont=nt;exports.compactName=U;exports.convertToWoff=j;exports.convertToWoff2=M;exports.getInstances=L;exports.planOutputs=it;exports.unboughtInstances=ot;
package/dist/index.d.ts CHANGED
@@ -52,9 +52,15 @@ export declare interface ClampOptions {
52
52
  /**
53
53
  * Output format — defaults to 'ttf'.
54
54
  * 'woff' and 'woff2' transcode the result to a web-compressed flavour.
55
- * 'otf' is a passthrough — the instancer preserves the input outline format.
55
+ * 'otf' is a label, not a conversion — it requires a CFF/CFF2 source and throws for TrueType outlines.
56
56
  */
57
57
  format?: OutputFormat;
58
+ /**
59
+ * Refuse any instances-based output whose range would include a named instance that was not listed
60
+ * (e.g. Regular + Bold would also hand over Medium and SemiBold). Throws instead of clamping.
61
+ * Use planOutputs() to split a customer's selection into safe outputs. Defaults to false.
62
+ */
63
+ strict?: boolean;
58
64
  /**
59
65
  * Remap the wght axis so its minimum becomes 100, making CSS font-weight values
60
66
  * (100–900) work as expected. Instance and STAT axis value coordinates are
@@ -147,14 +153,40 @@ export declare interface OutputConfig {
147
153
  /**
148
154
  * Output format for clampFont() — defaults to 'ttf'.
149
155
  * 'woff' and 'woff2' transcode to web-compressed flavours.
150
- * 'otf' is a passthrough (no outline conversion); the instancer preserves the input flavour.
156
+ * 'otf' requires a CFF/CFF2 source (no outline conversion is done) and throws for TrueType outlines.
151
157
  */
152
158
  export declare type OutputFormat = 'ttf' | 'otf' | 'woff' | 'woff2';
153
159
 
154
160
  /** Pin an axis at a single value — the axis is locked and removed from the output font's design space */
155
161
  export declare type PinnedAxis = number;
156
162
 
163
+ /**
164
+ * Groups a customer's selected named instances into clampFont outputs so that no output's range
165
+ * contains a named instance the customer did not select.
166
+ *
167
+ * Selections merge into one output only when their combined range holds no unselected named
168
+ * instance: Regular + Medium + SemiBold + Bold → one output; Regular + Bold alone → two outputs
169
+ * (each pinned to its own instance). Output names come from compactName(), optionally prefixed
170
+ * with `family`.
171
+ *
172
+ * @param font - The font's axes and named instances, from getInstances()
173
+ * @param selected - Names of the instances the customer bought (must match exactly)
174
+ * @param family - Optional family name to prefix each output name with, e.g. "Encode Sans"
175
+ * @returns OutputConfig entries ready for clampFont(), sorted along the primary axis
176
+ * @throws If a selected name is not a named instance of the font
177
+ */
178
+ export declare function planOutputs(font: FontInstancesResult, selected: string[], family?: string): OutputConfig[];
179
+
157
180
  /** @deprecated Use OutputConfig instead */
158
181
  export declare type SubfamilyConfig = OutputConfig;
159
182
 
183
+ /**
184
+ * Lists the named instances that a single output built from `names` would include without being selected,
185
+ * e.g. ['Medium', 'SemiBold'] for Regular + Bold. An empty array means the output is purchase-safe.
186
+ *
187
+ * @param font - The font's axes and named instances, from getInstances()
188
+ * @param names - The instance names one output would hull
189
+ */
190
+ export declare function unboughtInstances(font: FontInstancesResult, names: string[]): string[];
191
+
160
192
  export { }
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { createRequire as D } from "node:module";
2
- const I = D(import.meta.url), { preparePyodide: l, PyodideFile: u } = I("@web-alchemy/fonttools/src/pyodide.js");
3
- let g = null;
4
- async function P() {
5
- return g || (g = l().then(
1
+ import { createRequire as j } from "node:module";
2
+ const R = j(import.meta.url), { preparePyodide: d, PyodideFile: x } = R("@web-alchemy/fonttools/src/pyodide.js");
3
+ let T = null;
4
+ async function M() {
5
+ return T || (T = d().then(
6
6
  (t) => t.runPythonAsync(`
7
7
  from fontTools.ttLib import TTFont
8
8
 
@@ -13,10 +13,10 @@ def vf_convert_flavor(file_options):
13
13
 
14
14
  vf_convert_flavor
15
15
  `)
16
- )), g;
16
+ )), T;
17
17
  }
18
- async function V(t, o) {
19
- const a = await l(), e = new u({ pyodide: a }), n = new u({ pyodide: a });
18
+ async function P(t, o) {
19
+ const a = await d(), e = new x({ pyodide: a }), n = new x({ pyodide: a });
20
20
  try {
21
21
  await e.upload(t);
22
22
  const i = /* @__PURE__ */ new Map([
@@ -24,7 +24,7 @@ async function V(t, o) {
24
24
  ["output-file", n.filename],
25
25
  ["flavor", o]
26
26
  ]);
27
- return (await P())(i), n.download();
27
+ return (await M())(i), n.download();
28
28
  } finally {
29
29
  try {
30
30
  e.delete();
@@ -36,15 +36,15 @@ async function V(t, o) {
36
36
  }
37
37
  }
38
38
  }
39
- async function R(t) {
40
- return V(t, "woff");
39
+ async function L(t) {
40
+ return P(t, "woff");
41
41
  }
42
- async function N(t) {
43
- return V(t, "woff2");
42
+ async function $(t) {
43
+ return P(t, "woff2");
44
44
  }
45
- let h = null;
46
- async function O() {
47
- return h || (h = l().then(
45
+ let b = null;
46
+ async function k() {
47
+ return b || (b = d().then(
48
48
  (t) => t.runPythonAsync(`
49
49
  import json
50
50
  from fontTools.ttLib import TTFont
@@ -81,13 +81,13 @@ def get_instances_fn(input_file):
81
81
 
82
82
  get_instances_fn
83
83
  `)
84
- )), h;
84
+ )), b;
85
85
  }
86
- async function j(t) {
87
- const o = await l(), a = t instanceof ArrayBuffer ? new Uint8Array(t) : t, e = new u({ pyodide: o });
86
+ async function E(t) {
87
+ const o = await d(), a = t instanceof ArrayBuffer ? new Uint8Array(t) : t, e = new x({ pyodide: o });
88
88
  try {
89
89
  await e.upload(a);
90
- const i = (await O())(e.filename);
90
+ const i = (await k())(e.filename);
91
91
  return JSON.parse(i);
92
92
  } finally {
93
93
  try {
@@ -96,9 +96,9 @@ async function j(t) {
96
96
  }
97
97
  }
98
98
  }
99
- let w = null, v = null, y = null, A = null, T = null;
100
- async function L() {
101
- return w || (w = l().then(
99
+ let F = null, S = null, V = null, I = null, D = null;
100
+ async function U() {
101
+ return F || (F = d().then(
102
102
  (t) => t.runPythonAsync(`
103
103
  from fontTools import ttLib
104
104
 
@@ -153,10 +153,10 @@ def patch_font_names_fn(file_options):
153
153
 
154
154
  patch_font_names_fn
155
155
  `)
156
- )), w;
156
+ )), F;
157
157
  }
158
- async function M() {
159
- return v || (v = l().then(
158
+ async function C() {
159
+ return S || (S = d().then(
160
160
  (t) => t.runPythonAsync(`
161
161
  from fontTools.ttLib import TTFont
162
162
  from fontTools.varLib import instancer
@@ -187,10 +187,10 @@ def vf_clamp_instantiate(file_options, axes_json):
187
187
 
188
188
  vf_clamp_instantiate
189
189
  `)
190
- )), v;
190
+ )), S;
191
191
  }
192
- async function U() {
193
- return y || (y = l().then(
192
+ async function z() {
193
+ return V || (V = d().then(
194
194
  (t) => t.runPythonAsync(`
195
195
  from fontTools.ttLib import TTFont
196
196
 
@@ -249,10 +249,10 @@ def vf_clamp_normalize_wght(file_options, new_min_str):
249
249
 
250
250
  vf_clamp_normalize_wght
251
251
  `)
252
- )), y;
252
+ )), V;
253
253
  }
254
- async function k() {
255
- return A || (A = l().then(
254
+ async function B() {
255
+ return I || (I = d().then(
256
256
  (t) => t.runPythonAsync(`
257
257
  from fontTools.ttLib import TTFont
258
258
 
@@ -312,10 +312,10 @@ def vf_clamp_prune_stat(file_options):
312
312
 
313
313
  vf_clamp_prune_stat
314
314
  `)
315
- )), A;
315
+ )), I;
316
316
  }
317
- async function $() {
318
- return T || (T = l().then(
317
+ async function W() {
318
+ return D || (D = d().then(
319
319
  (t) => t.runPythonAsync(`
320
320
  from fontTools.ttLib import TTFont
321
321
 
@@ -362,26 +362,26 @@ def vf_clamp_update_os2(file_options):
362
362
 
363
363
  vf_clamp_update_os2
364
364
  `)
365
- )), T;
365
+ )), D;
366
366
  }
367
- function C(t) {
367
+ function q(t) {
368
368
  return t.replace(/[^A-Za-z0-9 -]/g, "").trim().split(/\s+/).join("-").replace(/^-+|-+$/g, "").slice(0, 63);
369
369
  }
370
- async function z(t, o) {
370
+ async function G(t, o) {
371
371
  if (!o) return t;
372
- const a = await l(), e = new u({ pyodide: a }), n = new u({ pyodide: a });
372
+ const a = await d(), e = new x({ pyodide: a }), n = new x({ pyodide: a });
373
373
  try {
374
- const i = C(o);
374
+ const i = q(o);
375
375
  await e.upload(t);
376
- const r = /* @__PURE__ */ new Map([
376
+ const s = /* @__PURE__ */ new Map([
377
377
  ["input-file", e.filename],
378
378
  ["output-file", n.filename],
379
379
  ["family-name", o],
380
380
  ["postscript-name", i]
381
381
  ]);
382
- return (await L())(r), n.download();
382
+ return (await U())(s), n.download();
383
383
  } catch (i) {
384
- return console.warn(`vf-clamp: name table patching failed for "${o}" — returning unpatched buffer`, i), t;
384
+ throw Object.assign(new Error(`vf-clamp: name table patching failed for "${o}"`), { cause: i });
385
385
  } finally {
386
386
  try {
387
387
  e.delete();
@@ -393,18 +393,18 @@ async function z(t, o) {
393
393
  }
394
394
  }
395
395
  }
396
- function B(t) {
396
+ function H(t) {
397
397
  return typeof t == "number" ? t : t === null ? null : [t.min, t.max];
398
398
  }
399
- async function E(t, o) {
400
- const a = await l(), e = new u({ pyodide: a }), n = new u({ pyodide: a });
399
+ async function J(t, o) {
400
+ const a = await d(), e = new x({ pyodide: a }), n = new x({ pyodide: a });
401
401
  try {
402
402
  await e.upload(t);
403
403
  const i = /* @__PURE__ */ new Map([
404
404
  ["input-file", e.filename],
405
405
  ["output-file", n.filename]
406
406
  ]);
407
- return (await M())(i, JSON.stringify(o)), n.download();
407
+ return (await C())(i, JSON.stringify(o)), n.download();
408
408
  } finally {
409
409
  try {
410
410
  e.delete();
@@ -416,17 +416,17 @@ async function E(t, o) {
416
416
  }
417
417
  }
418
418
  }
419
- async function W(t) {
420
- const o = await l(), a = new u({ pyodide: o }), e = new u({ pyodide: o });
419
+ async function Z(t) {
420
+ const o = await d(), a = new x({ pyodide: o }), e = new x({ pyodide: o });
421
421
  try {
422
422
  await a.upload(t);
423
423
  const n = /* @__PURE__ */ new Map([
424
424
  ["input-file", a.filename],
425
425
  ["output-file", e.filename]
426
426
  ]);
427
- return (await k())(n), e.download();
427
+ return (await B())(n), e.download();
428
428
  } catch (n) {
429
- return console.warn("vf-clamp: STAT prune failed — returning unpruned buffer", n), t;
429
+ throw Object.assign(new Error("vf-clamp: STAT pruning failed"), { cause: n });
430
430
  } finally {
431
431
  try {
432
432
  a.delete();
@@ -438,17 +438,17 @@ async function W(t) {
438
438
  }
439
439
  }
440
440
  }
441
- async function q(t) {
442
- const o = await l(), a = new u({ pyodide: o }), e = new u({ pyodide: o });
441
+ async function K(t) {
442
+ const o = await d(), a = new x({ pyodide: o }), e = new x({ pyodide: o });
443
443
  try {
444
444
  await a.upload(t);
445
445
  const n = /* @__PURE__ */ new Map([
446
446
  ["input-file", a.filename],
447
447
  ["output-file", e.filename]
448
448
  ]);
449
- return (await $())(n), e.download();
449
+ return (await W())(n), e.download();
450
450
  } catch (n) {
451
- return console.warn("vf-clamp: OS/2 + macStyle update failed — returning original buffer", n), t;
451
+ throw Object.assign(new Error("vf-clamp: OS/2 and macStyle update failed"), { cause: n });
452
452
  } finally {
453
453
  try {
454
454
  a.delete();
@@ -460,17 +460,17 @@ async function q(t) {
460
460
  }
461
461
  }
462
462
  }
463
- async function G(t, o) {
464
- const a = await l(), e = new u({ pyodide: a }), n = new u({ pyodide: a });
463
+ async function Q(t, o) {
464
+ const a = await d(), e = new x({ pyodide: a }), n = new x({ pyodide: a });
465
465
  try {
466
466
  await e.upload(t);
467
467
  const i = /* @__PURE__ */ new Map([
468
468
  ["input-file", e.filename],
469
469
  ["output-file", n.filename]
470
470
  ]);
471
- return (await U())(i, String(o)), n.download();
471
+ return (await z())(i, String(o)), n.download();
472
472
  } catch (i) {
473
- return console.warn("vf-clamp: wght normalisation failed — returning unnormalised buffer", i), t;
473
+ throw Object.assign(new Error("vf-clamp: wght normalisation failed"), { cause: i });
474
474
  } finally {
475
475
  try {
476
476
  e.delete();
@@ -482,57 +482,60 @@ async function G(t, o) {
482
482
  }
483
483
  }
484
484
  }
485
- function J(t, o) {
485
+ function X(t, o) {
486
486
  const a = new Map(o.map((i) => [i.name, i])), e = {};
487
487
  for (const i of t) {
488
- const r = a.get(i);
489
- if (!r) throw new Error(`Named instance "${i}" not found in font`);
490
- for (const [s, m] of Object.entries(r.coordinates))
491
- e[s] ? (e[s].min = Math.min(e[s].min, m), e[s].max = Math.max(e[s].max, m)) : e[s] = { min: m, max: m };
488
+ const s = a.get(i);
489
+ if (!s) throw new Error(`Named instance "${i}" not found in font`);
490
+ for (const [f, c] of Object.entries(s.coordinates))
491
+ e[f] ? (e[f].min = Math.min(e[f].min, c), e[f].max = Math.max(e[f].max, c)) : e[f] = { min: c, max: c };
492
492
  }
493
493
  const n = {};
494
- for (const [i, { min: r, max: s }] of Object.entries(e))
495
- n[i] = r === s ? r : { min: r, max: s };
494
+ for (const [i, { min: s, max: f }] of Object.entries(e))
495
+ n[i] = s === f ? s : { min: s, max: f };
496
496
  return n;
497
497
  }
498
- async function Z(t, o) {
499
- var s, m;
498
+ function Y(t) {
499
+ return t.length >= 4 && t[0] === 79 && t[1] === 84 && t[2] === 84 && t[3] === 79;
500
+ }
501
+ async function at(t, o) {
502
+ var f, c, w;
500
503
  if (o.outputs.length === 0) return [];
501
- const a = t instanceof ArrayBuffer ? new Uint8Array(t) : t, e = o.format ?? "ttf";
502
- let n = [], i = [];
503
- if (o.outputs.some((f) => {
504
- var p;
505
- return (p = f.instances) == null ? void 0 : p.length;
506
- })) {
507
- const f = await j(a);
508
- n = f.instances, i = f.axes;
509
- }
510
- const r = [];
511
- for (const f of o.outputs) {
512
- let p = {};
513
- (s = f.instances) != null && s.length && (p = J(f.instances, n)), f.axes && (p = { ...p, ...f.axes });
514
- for (const d of i) {
515
- const x = p[d.tag];
516
- if (x != null && typeof x == "object") {
517
- const _ = x;
518
- if (d.default < _.min || d.default > _.max) {
519
- const S = Math.max(_.min, Math.min(_.max, d.default));
504
+ const a = t instanceof ArrayBuffer ? new Uint8Array(t) : t, e = o.format ?? "ttf", { instances: n, axes: i } = await E(a), s = [];
505
+ for (const m of o.outputs) {
506
+ let _ = {};
507
+ if ((f = m.instances) != null && f.length && (_ = X(m.instances, n)), m.axes && (_ = { ..._, ...m.axes }), o.strict && ((c = m.instances) != null && c.length)) {
508
+ const p = new Set(m.instances), g = n.filter((h) => !p.has(h.name) && i.every((y) => {
509
+ const A = h.coordinates[y.tag] ?? y.default, v = _[y.tag];
510
+ return v == null ? !0 : typeof v == "number" ? A === v : A >= v.min && A <= v.max;
511
+ })).map((h) => h.name);
512
+ if (g.length)
513
+ throw new Error(`vf-clamp: output "${m.name || m.instances.join(", ")}" would include unselected instances (${g.join(", ")}); use planOutputs() to split the selection`);
514
+ }
515
+ for (const p of i) {
516
+ const g = _[p.tag];
517
+ if (g != null && typeof g == "object") {
518
+ const h = g;
519
+ if (p.default < h.min || p.default > h.max) {
520
+ const y = Math.max(h.min, Math.min(h.max, p.default));
520
521
  console.warn(
521
- `vf-clamp: axis "${d.tag}" default (${d.default}) is outside restricted range [${_.min}, ${_.max}] — will be clamped to ${S}`
522
+ `vf-clamp: axis "${p.tag}" default (${p.default}) is outside restricted range [${h.min}, ${h.max}] — will be clamped to ${y}`
522
523
  );
523
524
  }
524
525
  }
525
526
  }
526
- const b = {};
527
- for (const [d, x] of Object.entries(p))
528
- x !== null && (b[d] = B(x));
529
- const F = f.name ?? ((m = f.instances) != null && m.length ? f.instances.length === 1 ? f.instances[0] : `${f.instances[0]}-${f.instances[f.instances.length - 1]}` : "output");
530
- let c = await E(a, b);
531
- c = await W(c), o.normalizeWeightAxis && (c = await G(c, 100)), c = await q(c), c = await z(c, F), e === "woff2" ? c = await N(c) : e === "woff" && (c = await R(c)), r.push({ name: F, buffer: c, format: e });
527
+ const r = {};
528
+ for (const [p, g] of Object.entries(_))
529
+ g !== null && (r[p] = H(g));
530
+ const u = m.name || ((w = m.instances) != null && w.length ? m.instances.length === 1 ? m.instances[0] : `${m.instances[0]}-${m.instances[m.instances.length - 1]}` : "output");
531
+ let l = await J(a, r);
532
+ if (e === "otf" && !Y(l))
533
+ throw new Error("vf-clamp: format 'otf' needs a CFF/CFF2 source font; this font has TrueType outlines — use 'ttf'");
534
+ l = await Z(l), o.normalizeWeightAxis && (l = await Q(l, 100)), l = await K(l), l = await G(l, u), e === "woff2" ? l = await $(l) : e === "woff" && (l = await L(l)), s.push({ name: u, buffer: l, format: e });
532
535
  }
533
- return r;
536
+ return s;
534
537
  }
535
- function K(t, o) {
538
+ function tt(t, o) {
536
539
  if (t === o) return t;
537
540
  const a = t.split(" "), e = o.split(" ");
538
541
  let n = 0;
@@ -541,13 +544,75 @@ function K(t, o) {
541
544
  let i = 0;
542
545
  for (; i < a.length - n && i < e.length - n && a[a.length - 1 - i] === e[e.length - 1 - i]; )
543
546
  i++;
544
- const r = a.slice(0, n).join(" "), s = a.slice(n, a.length - (i || 0)).join(" "), m = e.slice(n, e.length - (i || 0)).join(" "), f = i > 0 ? a.slice(a.length - i).join(" ") : "", p = s && m ? `${s}-${m}` : s || m;
545
- return [r, p, f].filter(Boolean).join(" ");
547
+ const s = a.slice(0, n).join(" "), f = a.slice(n, a.length - (i || 0)).join(" "), c = e.slice(n, e.length - (i || 0)).join(" "), w = i > 0 ? a.slice(a.length - i).join(" ") : "", m = f && c ? `${f}-${c}` : f || c;
548
+ return [s, m, w].filter(Boolean).join(" ");
549
+ }
550
+ function O(t, o) {
551
+ const a = {};
552
+ for (const e of o) {
553
+ const n = t.map((i) => i.coordinates[e.tag] ?? e.default);
554
+ a[e.tag] = { min: Math.min(...n), max: Math.max(...n) };
555
+ }
556
+ return a;
557
+ }
558
+ function N(t, o, a, e) {
559
+ return o.filter((n) => a.has(n.name) ? !1 : e.every((i) => {
560
+ const s = n.coordinates[i.tag] ?? i.default, f = t[i.tag];
561
+ return !f || s >= f.min && s <= f.max;
562
+ }));
563
+ }
564
+ function et(t) {
565
+ return t.reduce((o, a) => !o || a.maximum - a.minimum > o.maximum - o.minimum ? a : o, void 0);
566
+ }
567
+ function it(t, o, a) {
568
+ const { axes: e, instances: n } = t, i = new Map(n.map((r) => [r.name, r])), s = [];
569
+ for (const r of new Set(o)) {
570
+ const u = i.get(r);
571
+ if (!u) throw new Error(`Named instance "${r}" not found in font`);
572
+ s.push(u);
573
+ }
574
+ if (!s.length) return [];
575
+ const f = new Set(s.map((r) => r.name));
576
+ let c;
577
+ if (!N(O(s, e), n, f, e).length)
578
+ c = [s];
579
+ else {
580
+ c = s.map((u) => [u]);
581
+ let r = !0;
582
+ for (; r; ) {
583
+ r = !1;
584
+ t: for (let u = 0; u < c.length; u++)
585
+ for (let l = u + 1; l < c.length; l++) {
586
+ const p = [...c[u], ...c[l]];
587
+ if (!N(O(p, e), n, f, e).length) {
588
+ c[u] = p, c.splice(l, 1), r = !0;
589
+ break t;
590
+ }
591
+ }
592
+ }
593
+ }
594
+ const w = et(e), m = w ? [w, ...e.filter((r) => r.tag !== w.tag)] : e, _ = (r, u) => {
595
+ for (const l of m) {
596
+ const p = (r.coordinates[l.tag] ?? l.default) - (u.coordinates[l.tag] ?? l.default);
597
+ if (p) return p;
598
+ }
599
+ return 0;
600
+ };
601
+ return c = c.map((r) => [...r].sort(_)).sort((r, u) => _(r[0], u[0])), c.map((r) => {
602
+ const u = r[0], l = r[r.length - 1], p = r.length === 1 ? u.name : tt(u.name, l.name);
603
+ return { name: a ? `${a} ${p}` : p, instances: r.map((g) => g.name) };
604
+ });
605
+ }
606
+ function ot(t, o) {
607
+ const a = new Map(t.instances.map((n) => [n.name, n])), e = o.map((n) => a.get(n)).filter((n) => !!n);
608
+ return e.length ? N(O(e, t.axes), t.instances, new Set(o), t.axes).map((n) => n.name) : [];
546
609
  }
547
610
  export {
548
- Z as clampFont,
549
- K as compactName,
550
- R as convertToWoff,
551
- N as convertToWoff2,
552
- j as getInstances
611
+ at as clampFont,
612
+ tt as compactName,
613
+ L as convertToWoff,
614
+ $ as convertToWoff2,
615
+ E as getInstances,
616
+ it as planOutputs,
617
+ ot as unboughtInstances
553
618
  };