@unmade/text-renderer 1.1.0 → 1.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.
Files changed (68) hide show
  1. package/README.md +184 -0
  2. package/dist/__vite-browser-external-oLchLuMx.js +7 -0
  3. package/dist/baselines/index.d.ts +59 -0
  4. package/dist/ce-adapter/index.cjs +1 -1
  5. package/dist/ce-adapter/index.js +154 -152
  6. package/dist/constants.d.ts +5 -0
  7. package/dist/fitting/index.d.ts +86 -0
  8. package/dist/glyph/svg-glyph-converter.d.ts +42 -0
  9. package/dist/helpers/bbox.d.ts +34 -0
  10. package/dist/helpers/point.d.ts +17 -0
  11. package/dist/helpers/xml.d.ts +1 -0
  12. package/dist/index.d.ts +17 -249
  13. package/dist/index.js +2221 -2005
  14. package/dist/loading/createLoadedFont.d.ts +53 -0
  15. package/dist/loading/index.d.ts +4 -0
  16. package/dist/measuring/conversion.d.ts +2 -0
  17. package/dist/measuring/index.d.ts +28 -0
  18. package/dist/node/baselines/index.d.ts +59 -0
  19. package/dist/node/ce-adapter/ce-types.d.ts +171 -0
  20. package/dist/node/ce-adapter/extractTextPlacements.d.ts +11 -0
  21. package/dist/node/ce-adapter/index.d.ts +12 -0
  22. package/dist/node/ce-adapter/mapStateToRendererOptions.d.ts +13 -0
  23. package/dist/node/ce-adapter/types.d.ts +56 -0
  24. package/dist/node/constants.d.ts +5 -0
  25. package/dist/node/fitting/index.d.ts +86 -0
  26. package/dist/node/glyph/svg-glyph-converter.d.ts +42 -0
  27. package/dist/node/helpers/bbox.d.ts +34 -0
  28. package/dist/node/helpers/point.d.ts +17 -0
  29. package/dist/node/helpers/xml.d.ts +1 -0
  30. package/dist/node/index.d.ts +17 -249
  31. package/dist/node/index.js +940 -948
  32. package/dist/node/loading/createLoadedFont.d.ts +53 -0
  33. package/dist/node/loading/index.d.ts +4 -0
  34. package/dist/node/measuring/conversion.d.ts +2 -0
  35. package/dist/node/measuring/index.d.ts +28 -0
  36. package/dist/node/renderText.d.ts +5 -0
  37. package/dist/node/rendering/index.d.ts +42 -0
  38. package/dist/node/svg/index.d.ts +11 -0
  39. package/dist/node/types.d.ts +75 -0
  40. package/dist/renderText.d.ts +5 -0
  41. package/dist/rendering/index.d.ts +42 -0
  42. package/dist/rolldown-runtime-DtPi1Y-2.js +13 -0
  43. package/dist/svg/index.d.ts +11 -0
  44. package/dist/types.d.ts +75 -0
  45. package/dist/worker/baselines/index.d.ts +59 -0
  46. package/dist/worker/ce-adapter/ce-types.d.ts +171 -0
  47. package/dist/worker/ce-adapter/extractTextPlacements.d.ts +11 -0
  48. package/dist/worker/ce-adapter/index.d.ts +12 -0
  49. package/dist/worker/ce-adapter/mapStateToRendererOptions.d.ts +13 -0
  50. package/dist/worker/ce-adapter/types.d.ts +56 -0
  51. package/dist/worker/constants.d.ts +5 -0
  52. package/dist/worker/fitting/index.d.ts +86 -0
  53. package/dist/worker/glyph/svg-glyph-converter.d.ts +42 -0
  54. package/dist/worker/helpers/bbox.d.ts +34 -0
  55. package/dist/worker/helpers/point.d.ts +17 -0
  56. package/dist/worker/helpers/xml.d.ts +1 -0
  57. package/dist/worker/index.d.ts +17 -249
  58. package/dist/worker/index.js +8346 -9728
  59. package/dist/worker/loading/createLoadedFont.d.ts +53 -0
  60. package/dist/worker/loading/index.d.ts +4 -0
  61. package/dist/worker/measuring/conversion.d.ts +2 -0
  62. package/dist/worker/measuring/index.d.ts +28 -0
  63. package/dist/worker/renderText.d.ts +5 -0
  64. package/dist/worker/rendering/index.d.ts +42 -0
  65. package/dist/worker/svg/index.d.ts +11 -0
  66. package/dist/worker/types.d.ts +75 -0
  67. package/package.json +19 -11
  68. package/dist/__vite-browser-external-l0sNRNKZ.js +0 -1
package/README.md CHANGED
@@ -46,6 +46,107 @@ const { getRenderedText } = await import('@unmade/text-renderer/node');
46
46
 
47
47
  **CE Lambda integration**: When wiring text-renderer into the Configuration Engine Lambda, use the `/node` ESM entry (`@unmade/text-renderer/node`) from an ESM Lambda handler, or use the `await import()` pattern above from an existing CJS handler. The `ce-adapter` sub-package (`@unmade/text-renderer/ce-adapter`) retains a CJS build and can be `require()`'d normally.
48
48
 
49
+ ## Choosing a font size
50
+
51
+ `getRenderedText` draws text at a size you give it. These work out what that size should be.
52
+
53
+ ```typescript
54
+ import { getFontSizeToFitBox, loadFont } from '@unmade/text-renderer';
55
+
56
+ const font = await loadFont('https://example.com/font.ttf');
57
+
58
+ const { fontSize, fits, boundBy } = getFontSizeToFitBox({
59
+ font,
60
+ text: 'HELLO',
61
+ boxDimensions: {
62
+ width: 500,
63
+ height: 200,
64
+ physicalWidth: 100,
65
+ physicalHeight: 40,
66
+ physicalUnits: 'mm',
67
+ },
68
+ size: { mode: 'fitToBox', minFontSize: 20, maxFontSize: 200 },
69
+ spacing: { outlineWidth: 0, letterSpacing: 0, letterSpacingOutline: 0 },
70
+ baseline: 'flat', // or 'curved', or { type: 'custom', path: '...' }
71
+ verticalAlignment: 'centre', // affects where a curved baseline sits
72
+ });
73
+ ```
74
+
75
+ For flat and custom baselines this is a direct calculation rather than a search: glyph
76
+ widths, outline stroke and letter spacing are all `fontSize * constant`, so measuring
77
+ once at a reference size and solving for a scale factor is exact. Curved baselines are
78
+ bisected instead, against both limits at once: neither the arc's length nor the arch's
79
+ position is proportional to font size, so both are evaluated at each candidate size.
80
+
81
+ The height check asks where the text will actually land, not how tall it is. The arch is
82
+ positioned from a standard capital, so that its shape does not change as characters are
83
+ typed — which means a descender hangs below the arch's ends, outside a box the arch itself
84
+ sits comfortably inside. The check reads the arch's placement from the same function that
85
+ generates the baseline path, so a size it accepts is one the renderer can draw.
86
+
87
+ ### The three ways to decide a size
88
+
89
+ `size` covers each of them, so none is a special case:
90
+
91
+ | `size` | Meaning |
92
+ |---|---|
93
+ | `{ mode: 'fitToBox', minFontSize, maxFontSize }` | Solve for the largest size that fits the box |
94
+ | `{ mode: 'fontSize', fontSize }` | Pin a size in pixels |
95
+ | `{ mode: 'physicalSize', physicalSize: [60, 'mm'] }` | Pin a physical size — what a fixed size preset is |
96
+
97
+ `minFontSize` and `maxFontSize` belong only to `fitToBox`, because they only mean
98
+ anything while solving. A pinned size overrides them by design.
99
+
100
+ Pinning inverts the problem. Instead of the text being fixed and the size free, the
101
+ size is fixed and the **text length** becomes the free variable — hold 60mm, and fit as
102
+ many characters as will go.
103
+
104
+ ### Reading the result
105
+
106
+ ```typescript
107
+ interface FitTextToBoxResult {
108
+ fontSize: number;
109
+ fits: boolean; // false when it won't fit even at the smallest allowed size
110
+ boundBy: 'width' | 'height' | 'requestedSize';
111
+ }
112
+ ```
113
+
114
+ `boundBy` says which limit decided the outcome — when it fits, what capped the size;
115
+ when it doesn't, what couldn't be satisfied. Callers generally need to respond
116
+ differently to each: text too wide for its box and text too tall for its box call for
117
+ different remedies, and in the configuration engine they surface as different messages.
118
+
119
+ `requestedSize` means the box wasn't the limiting factor: either `maxFontSize` was
120
+ reached while solving, or a pinned size was simply granted.
121
+
122
+ ### Shortening text that won't fit
123
+
124
+ ```typescript
125
+ import { fitAndTruncateTextToBox } from '@unmade/text-renderer';
126
+
127
+ const result = fitAndTruncateTextToBox({ font, text, boxDimensions, size, spacing });
128
+
129
+ result.text; // possibly shortened
130
+ result.truncated; // whether anything was removed
131
+ result.linesDropped; // whole lines removed
132
+ result.charactersRemoved; // trailing characters removed
133
+ ```
134
+
135
+ Whole lines are dropped before characters are trimmed. Both counts are reported rather
136
+ than a single mode, because one call can do both — drop a line, then find the remainder
137
+ still needs trimming — and because the caller usually wants to say something different
138
+ about each.
139
+
140
+ Works in every `size` mode. With a pinned size it is the only thing that can give: the
141
+ size stays put and the text shortens.
142
+
143
+ ### Rotation
144
+
145
+ Deliberately absent. Pass the box you want text laid out in and apply rotation to the
146
+ rendered output. Rotating the frame rather than reasoning about rotation inside the
147
+ solver keeps it working in one coordinate space, and is also what lets a rotated tall
148
+ placement use its long side for the baseline.
149
+
49
150
  ## Configuration Engine adapter
50
151
 
51
152
  The `ce-adapter` sub-package bridges CE (Configuration Engine) editor state to renderer options:
@@ -88,6 +189,89 @@ comparisons/{designUrlHash}/{timestamp}/{placementId}/
88
189
  renderer-options.json # GetRenderedTextOptions (mapped TR input)
89
190
  ```
90
191
 
192
+ ## Platform bundle suites
193
+
194
+ The package builds a separate bundle per platform — browser, worker and node —
195
+ each resolving a different variant of `@unmade/platform`:
196
+
197
+ | | `DOMPoint` | `DOMMatrix` | `DOMParser` |
198
+ |---|---|---|---|
199
+ | node | polyfill | `@thednp` polyfill | linkedom |
200
+ | worker | **native** | `@thednp` polyfill | linkedom/worker |
201
+ | browser | native | native | native |
202
+
203
+ `__tests__/platform/` tests those built bundles, each in the host it ships into:
204
+ the node bundle in the test process, the browser bundle on a page, and the worker
205
+ bundle inside a real `DedicatedWorkerGlobalScope`. Nothing else runs them — the
206
+ unit suite tests `src` under node, where neither `DOMPoint` nor `DOMMatrix`
207
+ exists, so it can only ever exercise the polyfills.
208
+
209
+ ```bash
210
+ npm run test:platform
211
+ ```
212
+
213
+ It builds first, because it loads `dist` and a stale bundle would otherwise give
214
+ a green run against code that is no longer there. It needs a browser:
215
+ `npx playwright install chromium`.
216
+
217
+ ### The two suites
218
+
219
+ **`render.test.ts`** draws every scenario in `scenarios.ts` on all three bundles,
220
+ through both public render entry points — `getRenderedText`, which loads its own
221
+ font, and `renderText`, which takes one already loaded. Each render is rasterised
222
+ here, by one resvg call for all three, and checked twice:
223
+
224
+ - **pixel-identical to the node bundle's, with no tolerance.** All three go
225
+ through the same rasteriser in the same process, so nothing legitimate can move
226
+ a pixel. This is the assertion that the differing geometry and DOM
227
+ implementations do not reach the artwork.
228
+ - **within 1% of a committed reference** in `platform/references/`, so a change
229
+ that shifts all three together is caught as well.
230
+
231
+ **`api.test.ts`** covers the rest of the public API, which nothing else would
232
+ call on the browser or worker bundles: constants, baseline generation and
233
+ validation, measurement, font loading and the font cache. Each is called with
234
+ identical inputs on all three bundles and the results compared. It also asserts
235
+ the three export the same names, that every export has a probe, and that each
236
+ bundle ran in the host it is built for — a `typeof DOMPoint` check cannot tell a
237
+ page from a worker, since both have the native globals.
238
+
239
+ ### The report
240
+
241
+ Every run writes `__tests__/platform-output/report.html`: one self-contained file
242
+ with each scenario's committed reference beside what each bundle drew, badged
243
+ with both comparisons, plus the diff images and the browser's user agent. It is
244
+ gitignored and collected as a CI artifact from `platform-test` whether the run
245
+ passed or failed, so a by-eye check is always available. The SVG each bundle
246
+ produced is written alongside it, so a difference the report shows as pixels can
247
+ be diffed as text.
248
+
249
+ ### Fonts
250
+
251
+ Served from the test process, so every platform fetches byte-identical bytes from
252
+ the same URL and the font is never a variable. Three, chosen for what they
253
+ exercise:
254
+
255
+ - **Bangers** — irregular display face: curves, diagonals, varying stroke widths.
256
+ Caps only.
257
+ - **Lora** — high-contrast serif with true lowercase, so descenders feed the
258
+ baseline and line-height calculation.
259
+ - **giants-color** — glyphs stored as SVG documents, the only path that goes
260
+ through a `DOMParser` and back out through an `XMLSerializer`.
261
+
262
+ The two added faces are OFL, with their licences beside them in
263
+ `__tests__/fixtures/`.
264
+
265
+ ### Updating the references
266
+
267
+ ```bash
268
+ UPDATE_FIXTURES=true npm run test:platform
269
+ ```
270
+
271
+ Rewrites every reference from the node bundle's render, then still checks the
272
+ other two against what it just stored. Review the diffs in the report before
273
+ committing.
274
+
91
275
  ## Regression tests
92
276
 
93
277
  Regression tests live in `__tests__/regression.test.ts` and use fixture pairs in `__tests__/fixtures/regression/`:
@@ -0,0 +1,7 @@
1
+ import { t as e } from "./rolldown-runtime-DtPi1Y-2.js";
2
+ //#region __vite-browser-external
3
+ var t = /* @__PURE__ */ e(((e, t) => {
4
+ t.exports = {};
5
+ }));
6
+ //#endregion
7
+ export default t();
@@ -0,0 +1,59 @@
1
+ import { BaselineConfig, BaselineType, BoxDimensions, VerticalTextAlignmentValues } from '../types';
2
+ /**
3
+ * Normalise a baseline option (string shorthand or object) into a BaselineConfig.
4
+ */
5
+ export declare const normaliseBaselineOption: (baseline: BaselineType | BaselineConfig | undefined) => BaselineConfig;
6
+ /**
7
+ * Validate a custom baseline SVG path string.
8
+ */
9
+ export declare const validateCustomBaselinePath: (path: string) => void;
10
+ export interface GenerateFlatBaselineOptions {
11
+ boxDimensions: BoxDimensions;
12
+ totalTextHeight: number;
13
+ baseline: number;
14
+ lineHeight: number;
15
+ verticalAlignment?: VerticalTextAlignmentValues;
16
+ lineNumber?: number;
17
+ }
18
+ /**
19
+ * Generate a path as a baseline for straight text
20
+ * This can be called multiple times for multiline text by incrementing the lineNumber parameter
21
+ *
22
+ * @returns an SVG path command to draw the baseline
23
+ */
24
+ export declare const generateFlatBaseline: ({ boxDimensions, totalTextHeight, baseline, lineHeight, verticalAlignment, lineNumber, }: GenerateFlatBaselineOptions) => string;
25
+ export interface GenerateCurvedBaselineOptions {
26
+ boxDimensions: BoxDimensions;
27
+ baseline: number;
28
+ textHeight: number;
29
+ verticalAlignment: VerticalTextAlignmentValues;
30
+ }
31
+ /** Where the arch sits vertically: its peak, and the baseline at its ends. */
32
+ export interface CurvedBaselineGeometry {
33
+ /** y of the peak — the highest point of the baseline. */
34
+ curveTop: number;
35
+ /** y of the two ends — the lowest points of the baseline. */
36
+ curveBottom: number;
37
+ /** Horizontal gap left at each end so the outer glyphs are not clipped. */
38
+ curveXInset: number;
39
+ }
40
+ /**
41
+ * The arch's placement, on its own so that the path and anything reasoning
42
+ * about where the text will land come from one calculation.
43
+ *
44
+ * Sizing needs this: it has to know where the renderer will actually put the
45
+ * baseline before it can say whether the text riding it clears the box. Working
46
+ * that out separately is what let the fit engine believe a size fitted while
47
+ * the render clipped.
48
+ */
49
+ export declare const curvedBaselineGeometry: ({ boxDimensions, baseline, textHeight, verticalAlignment, }: GenerateCurvedBaselineOptions) => CurvedBaselineGeometry;
50
+ /**
51
+ * Generate a path command to curve text and align that text to the top of the box.
52
+ * Uses two quadratic bezier curves — one from the bottom left to the peak, and a
53
+ * second mirrored from the peak to the end.
54
+ *
55
+ * Curved text is not currently supported for multiline text.
56
+ *
57
+ * @returns an SVG path command to draw the curve
58
+ */
59
+ export declare const generateCurvedBaseline: ({ boxDimensions, baseline, textHeight, verticalAlignment, }: GenerateCurvedBaselineOptions) => string;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function P(t){return t.type==="text"}function w(t){const i=t.getState().garment;return t.settings.garments.find(s=>s.slug===i)}function A(t,l,i){if(!t.embellishment_positions)return;const s=t.embellishment_positions.filter(o=>o.placement!==l?!1:!i||!o.variant?!0:o.variant===i||o.variant.toLowerCase()==="common");if(i&&s.length>1){const o=s.find(a=>a.variant===i);if(o)return o}return s[0]}function U(t,l){const i=t.fonts.find(s=>s.id===l);if(i)return{id:i.id,fontUrl:i.font_url,fontFamily:i.font_family,letterSpacing:i.letter_spacing??0,letterSpacingOutline:i.letter_spacing_outline??0,outlineWidth:i.outline_width??0,fixedColours:i.fixed_colours}}function v(t,l,i,s,o,a){const{library:h}=o,u=[];for(const[r,f]of Object.entries(s)){if(f.type!=="text")continue;const d=f,{meta:e}=d;if(!e?.text)continue;const{libraryItemID:z}=d,g=h[z];if(!g||!P(g))continue;const y=e.font;if(y===void 0)continue;const m=U(g,y);if(!m)continue;const c=A(l,r,a);if(!c)continue;let x=null,b=null;if(e.colours&&g.palette!=null)try{const n=t.palettes.getUsedColours(g.palette,e.colours);x=n[0]??null,b=n[1]??null}catch(n){console.warn(`[extractTextPlacements] Colour resolution failed for ${r}:`,n)}const S=o.byId?.[r],p={vertical:"top",horizontal:"left"};if(S){const n=S;n.vertical_alignment&&(p.vertical=n.vertical_alignment),n.horizontal_alignment&&(p.horizontal=n.horizontal_alignment)}u.push({placementId:r,displayName:r,source:i,text:e.text,font:m,isCurved:e.isCurved??!1,isDistributed:e.isDistributed??!1,verticalAlignment:e.verticalAlignment??p.vertical,horizontalAlignment:e.horizontalAlignment??p.horizontal,rotation:e.rotation??0,spacing:e.spacing??null,fillColour:x,outlineColour:b,position:{width:c.width,height:c.height,physicalWidth:c.physical_width_decimal,physicalHeight:c.physical_height_decimal,physicalUnits:c.physical_size_units,baselinePath:c.baseline_path,rotation:c.rotation},userSetPhysicalSize:e.retainedPhysicalSize??null,fontMetrics:(()=>{const n=e.fontMetrics??{size:0,height:0,baseline:0,width:0},C=n.physicalFontSize||(n.physicalHeight!=null&&n.physicalHeight>0&&n.physicalUnits!=null?[n.physicalHeight,n.physicalUnits]:void 0);return{...n,physicalFontSize:C}})(),manufacturingMethod:e.manufacturing_method??""})}return u}function O(t){const l=t.getState(),i=w(t);if(!i)return console.warn("[extractTextPlacements] No matching garment found"),[];const s=typeof t.getCurrentVariant=="function"?t.getCurrentVariant():void 0,o=[],a=l.vals?.placements,h=t.settings.placements;a&&h&&o.push(...v(t,i,"placements",a,h,s));const u=l.vals?.embellishments,r=t.settings.embellishments;return u&&r&&o.push(...v(t,i,"embellishments",u,r,s)),o.length===0&&console.warn("[extractTextPlacements] No text entries found in either placements or embellishments"),o}function _(t){return t==="center"?"centre":t}function T(t){let l=_(t.horizontalAlignment);t.isDistributed&&(l="distributed");let i;t.position.baselinePath?i={type:"custom",path:t.position.baselinePath}:t.isCurved?i="curved":i="flat";const s=t.userSetPhysicalSize?[t.userSetPhysicalSize[0],t.userSetPhysicalSize[1]]:t.fontMetrics.physicalFontSize!=null?t.fontMetrics.physicalFontSize:[t.position.physicalHeight,t.position.physicalUnits],a={outlineWidth:t.outlineColour!=null?t.font.outlineWidth:0,letterSpacing:t.spacing?.letter_spacing??0,letterSpacingOutline:t.spacing?.letter_spacing_outline??0},h=t.fillColour?{hex:t.fillColour.hex,reference:t.fillColour.reference??void 0}:void 0,u=t.outlineColour?{hex:t.outlineColour.hex,reference:t.outlineColour.reference??void 0}:void 0;let r;if(t.font.fixedColours){const f={};for(const[d,e]of Object.entries(t.font.fixedColours))f[d]={hex:e.hex,reference:e.reference};r=f}return{text:t.text,fontUrl:t.font.fontUrl,physicalSize:s,fontSizeFallback:t.fontMetrics.size,boxDimensions:{width:t.position.width,height:t.position.height,physicalWidth:t.position.physicalWidth,physicalHeight:t.position.physicalHeight,physicalUnits:t.position.physicalUnits},spacing:a,verticalAlignment:_(t.verticalAlignment),horizontalAlignment:l,baseline:i,fillColour:h,strokeColour:u,colourMap:r}}exports.extractTextPlacements=O;exports.mapStateToRendererOptions=T;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return e.type===`text`}function t(e){let t=e.getState().garment;return e.settings.garments.find(e=>e.slug===t)}function n(e,t,n){if(!e.embellishment_positions)return;let r=e.embellishment_positions.filter(e=>e.placement===t?!n||!e.variant||e.variant===n||e.variant.toLowerCase()===`common`:!1);if(n&&r.length>1){let e=r.find(e=>e.variant===n);if(e)return e}return r[0]}function r(e,t){let n=e.fonts.find(e=>e.id===t);if(n)return{id:n.id,fontUrl:n.font_url,fontFamily:n.font_family,letterSpacing:n.letter_spacing??0,letterSpacingOutline:n.letter_spacing_outline??0,outlineWidth:n.outline_width??0,fixedColours:n.fixed_colours}}function i(t,i,a,o,s,c){let{library:l}=s,u=[];for(let[d,f]of Object.entries(o)){if(f.type!==`text`)continue;let o=f,{meta:p}=o;if(!p?.text)continue;let{libraryItemID:m}=o,h=l[m];if(!h||!e(h))continue;let g=p.font;if(g===void 0)continue;let _=r(h,g);if(!_)continue;let v=n(i,d,c);if(!v)continue;let y=null,b=null;if(p.colours&&h.palette!=null)try{let e=t.palettes.getUsedColours(h.palette,p.colours);y=e[0]??null,b=e[1]??null}catch(e){console.warn(`[extractTextPlacements] Colour resolution failed for ${d}:`,e)}let x=s.byId?.[d],S={vertical:`top`,horizontal:`left`};if(x){let e=x;e.vertical_alignment&&(S.vertical=e.vertical_alignment),e.horizontal_alignment&&(S.horizontal=e.horizontal_alignment)}u.push({placementId:d,displayName:d,source:a,text:p.text,font:_,isCurved:p.isCurved??!1,isDistributed:p.isDistributed??!1,verticalAlignment:p.verticalAlignment??S.vertical,horizontalAlignment:p.horizontalAlignment??S.horizontal,rotation:p.rotation??0,spacing:p.spacing??null,fillColour:y,outlineColour:b,position:{width:v.width,height:v.height,physicalWidth:v.physical_width_decimal,physicalHeight:v.physical_height_decimal,physicalUnits:v.physical_size_units,baselinePath:v.baseline_path,rotation:v.rotation},userSetPhysicalSize:p.retainedPhysicalSize??null,fontMetrics:(()=>{let e=p.fontMetrics??{size:0,height:0,baseline:0,width:0},t=e.physicalFontSize||(e.physicalHeight!=null&&e.physicalHeight>0&&e.physicalUnits!=null?[e.physicalHeight,e.physicalUnits]:void 0);return{...e,physicalFontSize:t}})(),manufacturingMethod:p.manufacturing_method??``})}return u}function a(e){let n=e.getState(),r=t(e);if(!r)return console.warn(`[extractTextPlacements] No matching garment found`),[];let a=typeof e.getCurrentVariant==`function`?e.getCurrentVariant():void 0,o=[],s=n.vals?.placements,c=e.settings.placements;s&&c&&o.push(...i(e,r,`placements`,s,c,a));let l=n.vals?.embellishments,u=e.settings.embellishments;return l&&u&&o.push(...i(e,r,`embellishments`,l,u,a)),o.length===0&&console.warn(`[extractTextPlacements] No text entries found in either placements or embellishments`),o}function o(e){return e===`center`?`centre`:e}function s(e){let t=o(e.horizontalAlignment);e.isDistributed&&(t=`distributed`);let n;n=e.position.baselinePath?{type:`custom`,path:e.position.baselinePath}:e.isCurved?`curved`:`flat`;let r=e.userSetPhysicalSize?[e.userSetPhysicalSize[0],e.userSetPhysicalSize[1]]:e.fontMetrics.physicalFontSize==null?[e.position.physicalHeight,e.position.physicalUnits]:e.fontMetrics.physicalFontSize,i={outlineWidth:e.outlineColour==null?0:e.font.outlineWidth,letterSpacing:e.spacing?.letter_spacing??0,letterSpacingOutline:e.spacing?.letter_spacing_outline??0},a=e.fillColour?{hex:e.fillColour.hex,reference:e.fillColour.reference??void 0}:void 0,s=e.outlineColour?{hex:e.outlineColour.hex,reference:e.outlineColour.reference??void 0}:void 0,c;if(e.font.fixedColours){let t={};for(let[n,r]of Object.entries(e.font.fixedColours))t[n]={hex:r.hex,reference:r.reference};c=t}return{text:e.text,fontUrl:e.font.fontUrl,physicalSize:r,fontSizeFallback:e.fontMetrics.size,boxDimensions:{width:e.position.width,height:e.position.height,physicalWidth:e.position.physicalWidth,physicalHeight:e.position.physicalHeight,physicalUnits:e.position.physicalUnits},spacing:i,verticalAlignment:o(e.verticalAlignment),horizontalAlignment:t,baseline:n,fillColour:a,strokeColour:s,colourMap:c}}exports.extractTextPlacements=a,exports.mapStateToRendererOptions=s;
@@ -1,160 +1,162 @@
1
- function w(t) {
2
- return t.type === "text";
1
+ //#region src/ce-adapter/extractTextPlacements.ts
2
+ function e(e) {
3
+ return e.type === "text";
3
4
  }
4
- function P(t) {
5
- const i = t.getState().garment;
6
- return t.settings.garments.find((s) => s.slug === i);
5
+ function t(e) {
6
+ let t = e.getState().garment;
7
+ return e.settings.garments.find((e) => e.slug === t);
7
8
  }
8
- function A(t, l, i) {
9
- if (!t.embellishment_positions) return;
10
- const s = t.embellishment_positions.filter((o) => o.placement !== l ? !1 : !i || !o.variant ? !0 : o.variant === i || o.variant.toLowerCase() === "common");
11
- if (i && s.length > 1) {
12
- const o = s.find((a) => a.variant === i);
13
- if (o) return o;
14
- }
15
- return s[0];
9
+ function n(e, t, n) {
10
+ if (!e.embellishment_positions) return;
11
+ let r = e.embellishment_positions.filter((e) => e.placement === t ? !n || !e.variant || e.variant === n || e.variant.toLowerCase() === "common" : !1);
12
+ if (n && r.length > 1) {
13
+ let e = r.find((e) => e.variant === n);
14
+ if (e) return e;
15
+ }
16
+ return r[0];
16
17
  }
17
- function U(t, l) {
18
- const i = t.fonts.find((s) => s.id === l);
19
- if (i)
20
- return {
21
- id: i.id,
22
- fontUrl: i.font_url,
23
- fontFamily: i.font_family,
24
- letterSpacing: i.letter_spacing ?? 0,
25
- letterSpacingOutline: i.letter_spacing_outline ?? 0,
26
- outlineWidth: i.outline_width ?? 0,
27
- fixedColours: i.fixed_colours
28
- };
18
+ function r(e, t) {
19
+ let n = e.fonts.find((e) => e.id === t);
20
+ if (n) return {
21
+ id: n.id,
22
+ fontUrl: n.font_url,
23
+ fontFamily: n.font_family,
24
+ letterSpacing: n.letter_spacing ?? 0,
25
+ letterSpacingOutline: n.letter_spacing_outline ?? 0,
26
+ outlineWidth: n.outline_width ?? 0,
27
+ fixedColours: n.fixed_colours
28
+ };
29
29
  }
30
- function S(t, l, i, s, o, a) {
31
- const { library: h } = o, u = [];
32
- for (const [r, f] of Object.entries(s)) {
33
- if (f.type !== "text") continue;
34
- const d = f, { meta: e } = d;
35
- if (!e?.text) continue;
36
- const { libraryItemID: z } = d, g = h[z];
37
- if (!g || !w(g)) continue;
38
- const y = e.font;
39
- if (y === void 0) continue;
40
- const m = U(g, y);
41
- if (!m) continue;
42
- const c = A(l, r, a);
43
- if (!c) continue;
44
- let x = null, b = null;
45
- if (e.colours && g.palette != null)
46
- try {
47
- const n = t.palettes.getUsedColours(
48
- g.palette,
49
- e.colours
50
- );
51
- x = n[0] ?? null, b = n[1] ?? null;
52
- } catch (n) {
53
- console.warn(`[extractTextPlacements] Colour resolution failed for ${r}:`, n);
54
- }
55
- const v = o.byId?.[r], p = {
56
- vertical: "top",
57
- horizontal: "left"
58
- };
59
- if (v) {
60
- const n = v;
61
- n.vertical_alignment && (p.vertical = n.vertical_alignment), n.horizontal_alignment && (p.horizontal = n.horizontal_alignment);
62
- }
63
- u.push({
64
- placementId: r,
65
- displayName: r,
66
- source: i,
67
- text: e.text,
68
- font: m,
69
- isCurved: e.isCurved ?? !1,
70
- isDistributed: e.isDistributed ?? !1,
71
- verticalAlignment: e.verticalAlignment ?? p.vertical,
72
- horizontalAlignment: e.horizontalAlignment ?? p.horizontal,
73
- rotation: e.rotation ?? 0,
74
- spacing: e.spacing ?? null,
75
- fillColour: x,
76
- outlineColour: b,
77
- position: {
78
- width: c.width,
79
- height: c.height,
80
- physicalWidth: c.physical_width_decimal,
81
- physicalHeight: c.physical_height_decimal,
82
- physicalUnits: c.physical_size_units,
83
- baselinePath: c.baseline_path,
84
- rotation: c.rotation
85
- },
86
- userSetPhysicalSize: e.retainedPhysicalSize ?? null,
87
- fontMetrics: (() => {
88
- const n = e.fontMetrics ?? { size: 0, height: 0, baseline: 0, width: 0 }, C = n.physicalFontSize || (n.physicalHeight != null && n.physicalHeight > 0 && n.physicalUnits != null ? [n.physicalHeight, n.physicalUnits] : void 0);
89
- return { ...n, physicalFontSize: C };
90
- })(),
91
- manufacturingMethod: e.manufacturing_method ?? ""
92
- });
93
- }
94
- return u;
30
+ function i(t, i, a, o, s, c) {
31
+ let { library: l } = s, u = [];
32
+ for (let [d, f] of Object.entries(o)) {
33
+ if (f.type !== "text") continue;
34
+ let o = f, { meta: p } = o;
35
+ if (!p?.text) continue;
36
+ let { libraryItemID: m } = o, h = l[m];
37
+ if (!h || !e(h)) continue;
38
+ let g = p.font;
39
+ if (g === void 0) continue;
40
+ let _ = r(h, g);
41
+ if (!_) continue;
42
+ let v = n(i, d, c);
43
+ if (!v) continue;
44
+ let y = null, b = null;
45
+ if (p.colours && h.palette != null) try {
46
+ let e = t.palettes.getUsedColours(h.palette, p.colours);
47
+ y = e[0] ?? null, b = e[1] ?? null;
48
+ } catch (e) {
49
+ console.warn(`[extractTextPlacements] Colour resolution failed for ${d}:`, e);
50
+ }
51
+ let x = s.byId?.[d], S = {
52
+ vertical: "top",
53
+ horizontal: "left"
54
+ };
55
+ if (x) {
56
+ let e = x;
57
+ e.vertical_alignment && (S.vertical = e.vertical_alignment), e.horizontal_alignment && (S.horizontal = e.horizontal_alignment);
58
+ }
59
+ u.push({
60
+ placementId: d,
61
+ displayName: d,
62
+ source: a,
63
+ text: p.text,
64
+ font: _,
65
+ isCurved: p.isCurved ?? !1,
66
+ isDistributed: p.isDistributed ?? !1,
67
+ verticalAlignment: p.verticalAlignment ?? S.vertical,
68
+ horizontalAlignment: p.horizontalAlignment ?? S.horizontal,
69
+ rotation: p.rotation ?? 0,
70
+ spacing: p.spacing ?? null,
71
+ fillColour: y,
72
+ outlineColour: b,
73
+ position: {
74
+ width: v.width,
75
+ height: v.height,
76
+ physicalWidth: v.physical_width_decimal,
77
+ physicalHeight: v.physical_height_decimal,
78
+ physicalUnits: v.physical_size_units,
79
+ baselinePath: v.baseline_path,
80
+ rotation: v.rotation
81
+ },
82
+ userSetPhysicalSize: p.retainedPhysicalSize ?? null,
83
+ fontMetrics: (() => {
84
+ let e = p.fontMetrics ?? {
85
+ size: 0,
86
+ height: 0,
87
+ baseline: 0,
88
+ width: 0
89
+ }, t = e.physicalFontSize || (e.physicalHeight != null && e.physicalHeight > 0 && e.physicalUnits != null ? [e.physicalHeight, e.physicalUnits] : void 0);
90
+ return {
91
+ ...e,
92
+ physicalFontSize: t
93
+ };
94
+ })(),
95
+ manufacturingMethod: p.manufacturing_method ?? ""
96
+ });
97
+ }
98
+ return u;
95
99
  }
96
- function F(t) {
97
- const l = t.getState(), i = P(t);
98
- if (!i)
99
- return console.warn("[extractTextPlacements] No matching garment found"), [];
100
- const s = typeof t.getCurrentVariant == "function" ? t.getCurrentVariant() : void 0, o = [], a = l.vals?.placements, h = t.settings.placements;
101
- a && h && o.push(...S(t, i, "placements", a, h, s));
102
- const u = l.vals?.embellishments, r = t.settings.embellishments;
103
- return u && r && o.push(...S(t, i, "embellishments", u, r, s)), o.length === 0 && console.warn("[extractTextPlacements] No text entries found in either placements or embellishments"), o;
100
+ function a(e) {
101
+ let n = e.getState(), r = t(e);
102
+ if (!r) return console.warn("[extractTextPlacements] No matching garment found"), [];
103
+ let a = typeof e.getCurrentVariant == "function" ? e.getCurrentVariant() : void 0, o = [], s = n.vals?.placements, c = e.settings.placements;
104
+ s && c && o.push(...i(e, r, "placements", s, c, a));
105
+ let l = n.vals?.embellishments, u = e.settings.embellishments;
106
+ return l && u && o.push(...i(e, r, "embellishments", l, u, a)), o.length === 0 && console.warn("[extractTextPlacements] No text entries found in either placements or embellishments"), o;
104
107
  }
105
- function _(t) {
106
- return t === "center" ? "centre" : t;
108
+ //#endregion
109
+ //#region src/ce-adapter/mapStateToRendererOptions.ts
110
+ function o(e) {
111
+ return e === "center" ? "centre" : e;
107
112
  }
108
- function H(t) {
109
- let l = _(t.horizontalAlignment);
110
- t.isDistributed && (l = "distributed");
111
- let i;
112
- t.position.baselinePath ? i = { type: "custom", path: t.position.baselinePath } : t.isCurved ? i = "curved" : i = "flat";
113
- const s = t.userSetPhysicalSize ? [
114
- t.userSetPhysicalSize[0],
115
- t.userSetPhysicalSize[1]
116
- ] : t.fontMetrics.physicalFontSize != null ? t.fontMetrics.physicalFontSize : [t.position.physicalHeight, t.position.physicalUnits], a = {
117
- outlineWidth: t.outlineColour != null ? t.font.outlineWidth : 0,
118
- letterSpacing: t.spacing?.letter_spacing ?? 0,
119
- letterSpacingOutline: t.spacing?.letter_spacing_outline ?? 0
120
- }, h = t.fillColour ? {
121
- hex: t.fillColour.hex,
122
- reference: t.fillColour.reference ?? void 0
123
- } : void 0, u = t.outlineColour ? {
124
- hex: t.outlineColour.hex,
125
- reference: t.outlineColour.reference ?? void 0
126
- } : void 0;
127
- let r;
128
- if (t.font.fixedColours) {
129
- const f = {};
130
- for (const [d, e] of Object.entries(t.font.fixedColours))
131
- f[d] = { hex: e.hex, reference: e.reference };
132
- r = f;
133
- }
134
- return {
135
- text: t.text,
136
- fontUrl: t.font.fontUrl,
137
- physicalSize: s,
138
- fontSizeFallback: t.fontMetrics.size,
139
- boxDimensions: {
140
- width: t.position.width,
141
- height: t.position.height,
142
- physicalWidth: t.position.physicalWidth,
143
- physicalHeight: t.position.physicalHeight,
144
- physicalUnits: t.position.physicalUnits
145
- },
146
- spacing: a,
147
- verticalAlignment: _(
148
- t.verticalAlignment
149
- ),
150
- horizontalAlignment: l,
151
- baseline: i,
152
- fillColour: h,
153
- strokeColour: u,
154
- colourMap: r
155
- };
113
+ function s(e) {
114
+ let t = o(e.horizontalAlignment);
115
+ e.isDistributed && (t = "distributed");
116
+ let n;
117
+ n = e.position.baselinePath ? {
118
+ type: "custom",
119
+ path: e.position.baselinePath
120
+ } : e.isCurved ? "curved" : "flat";
121
+ let r = e.userSetPhysicalSize ? [e.userSetPhysicalSize[0], e.userSetPhysicalSize[1]] : e.fontMetrics.physicalFontSize == null ? [e.position.physicalHeight, e.position.physicalUnits] : e.fontMetrics.physicalFontSize, i = {
122
+ outlineWidth: e.outlineColour == null ? 0 : e.font.outlineWidth,
123
+ letterSpacing: e.spacing?.letter_spacing ?? 0,
124
+ letterSpacingOutline: e.spacing?.letter_spacing_outline ?? 0
125
+ }, a = e.fillColour ? {
126
+ hex: e.fillColour.hex,
127
+ reference: e.fillColour.reference ?? void 0
128
+ } : void 0, s = e.outlineColour ? {
129
+ hex: e.outlineColour.hex,
130
+ reference: e.outlineColour.reference ?? void 0
131
+ } : void 0, c;
132
+ if (e.font.fixedColours) {
133
+ let t = {};
134
+ for (let [n, r] of Object.entries(e.font.fixedColours)) t[n] = {
135
+ hex: r.hex,
136
+ reference: r.reference
137
+ };
138
+ c = t;
139
+ }
140
+ return {
141
+ text: e.text,
142
+ fontUrl: e.font.fontUrl,
143
+ physicalSize: r,
144
+ fontSizeFallback: e.fontMetrics.size,
145
+ boxDimensions: {
146
+ width: e.position.width,
147
+ height: e.position.height,
148
+ physicalWidth: e.position.physicalWidth,
149
+ physicalHeight: e.position.physicalHeight,
150
+ physicalUnits: e.position.physicalUnits
151
+ },
152
+ spacing: i,
153
+ verticalAlignment: o(e.verticalAlignment),
154
+ horizontalAlignment: t,
155
+ baseline: n,
156
+ fillColour: a,
157
+ strokeColour: s,
158
+ colourMap: c
159
+ };
156
160
  }
157
- export {
158
- F as extractTextPlacements,
159
- H as mapStateToRendererOptions
160
- };
161
+ //#endregion
162
+ export { a as extractTextPlacements, s as mapStateToRendererOptions };
@@ -0,0 +1,5 @@
1
+ import { TextSpacing } from './types';
2
+ export declare const DEFAULT_OUTLINE_FACTOR = 0.03125;
3
+ export declare const LINE_HEIGHT = 1.1;
4
+ export declare const CURVE_DEPTH = 0.2;
5
+ export declare const DEFAULT_SPACING: TextSpacing;