@unmade/text-renderer 1.2.0 → 1.3.1
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/README.md +47 -0
- package/dist/ce-adapter/index.cjs +1 -1
- package/dist/ce-adapter/index.d.ts +1 -1
- package/dist/ce-adapter/index.js +16 -12
- package/dist/ce-adapter/types.d.ts +1 -0
- package/dist/fitting/index.d.ts +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +535 -408
- package/dist/layout/index.d.ts +77 -0
- package/dist/node/ce-adapter/index.d.ts +1 -1
- package/dist/node/ce-adapter/types.d.ts +1 -0
- package/dist/node/fitting/index.d.ts +3 -3
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +304 -175
- package/dist/node/layout/index.d.ts +77 -0
- package/dist/node/renderText.d.ts +3 -0
- package/dist/renderText.d.ts +3 -0
- package/dist/worker/ce-adapter/index.d.ts +1 -1
- package/dist/worker/ce-adapter/types.d.ts +1 -0
- package/dist/worker/fitting/index.d.ts +3 -3
- package/dist/worker/index.d.ts +1 -0
- package/dist/worker/index.js +1839 -1712
- package/dist/worker/layout/index.d.ts +77 -0
- package/dist/worker/renderText.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -147,6 +147,53 @@ rendered output. Rotating the frame rather than reasoning about rotation inside
|
|
|
147
147
|
solver keeps it working in one coordinate space, and is also what lets a rotated tall
|
|
148
148
|
placement use its long side for the baseline.
|
|
149
149
|
|
|
150
|
+
## Measuring text
|
|
151
|
+
|
|
152
|
+
`renderText` returns artwork; `measureText` returns its dimensions, for the same
|
|
153
|
+
inputs.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { measureText } from '@unmade/text-renderer';
|
|
157
|
+
|
|
158
|
+
const m = measureText({
|
|
159
|
+
font,
|
|
160
|
+
text: 'HELLO',
|
|
161
|
+
boxDimensions,
|
|
162
|
+
physicalSize: [20, 'mm'],
|
|
163
|
+
spacing: { outlineWidth: 0, letterSpacing: 0, letterSpacingOutline: 0 },
|
|
164
|
+
baseline: 'flat',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
m.bbox; // { x, y, width, height } in box pixels
|
|
168
|
+
m.physicalWidth; // in the box's units
|
|
169
|
+
m.physicalHeight;
|
|
170
|
+
m.physicalCapHeight; // see below
|
|
171
|
+
m.lines; // per line, in rendered order
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
It measures the **same positioned glyphs that get drawn**, rather than
|
|
175
|
+
re-deriving dimensions from font metrics. That matters for more than tidiness: a
|
|
176
|
+
curved line's bounds follow the arch, which no flat metrics calculation can know
|
|
177
|
+
about, and any second implementation eventually disagrees with the first.
|
|
178
|
+
`renderText` and `measureText` share one layout pass for exactly that reason.
|
|
179
|
+
|
|
180
|
+
It throws where `renderText` would throw, and for the same reasons — so if
|
|
181
|
+
measuring succeeds, rendering the same options will too.
|
|
182
|
+
|
|
183
|
+
### Cap height is reported, not derived
|
|
184
|
+
|
|
185
|
+
`physicalCapHeight` is the physical height of a standard capital at the chosen
|
|
186
|
+
font size. It is **not** `physicalHeight`, and can't be calculated from it:
|
|
187
|
+
|
|
188
|
+
- `physicalHeight` measures the glyphs actually present, so it grows with
|
|
189
|
+
descenders, accents and punctuation
|
|
190
|
+
- `physicalCapHeight` is a property of the font and size alone, so the same size
|
|
191
|
+
always reports the same cap height regardless of what was typed
|
|
192
|
+
|
|
193
|
+
Both are needed. Cap height is what a fixed size preset targets and what gets
|
|
194
|
+
persisted against a design for manufacturing; the measured height is what the
|
|
195
|
+
artwork actually occupies.
|
|
196
|
+
|
|
150
197
|
## Configuration Engine adapter
|
|
151
198
|
|
|
152
199
|
The `ce-adapter` sub-package bridges CE (Configuration Engine) editor state to renderer options:
|
|
@@ -1 +1 @@
|
|
|
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=
|
|
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=e.outlineColour!=null,a=e.spacing??{letter_spacing:e.font.letterSpacing,letter_spacing_outline:e.font.letterSpacingOutline,outline_width:e.font.outlineWidth},s={outlineWidth:i?a.outline_width:0,letterSpacing:a.letter_spacing,letterSpacingOutline:a.letter_spacing_outline},c=e.fillColour?{hex:e.fillColour.hex,reference:e.fillColour.reference??void 0}:void 0,l=e.outlineColour?{hex:e.outlineColour.hex,reference:e.outlineColour.reference??void 0}:void 0,u;if(e.font.fixedColours){let t={};for(let[n,r]of Object.entries(e.font.fixedColours))t[n]={hex:r.hex,reference:r.reference};u=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:s,verticalAlignment:o(e.verticalAlignment),horizontalAlignment:t,baseline:n,fillColour:c,strokeColour:l,colourMap:u}}exports.extractTextPlacements=a,exports.mapStateToRendererOptions=s;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* renderer options. Intended for use in server-side comparison pipelines
|
|
7
7
|
* and any environment that bridges CE state to the text-renderer.
|
|
8
8
|
*/
|
|
9
|
+
export type { BasePlacementSettings, BasePlacementState, CombinedEditorSettings, ConfigEditor, EditorGarment, EditorPalette, EditorPaletteColour, EditorState, EditorStateVals, EditorVariant, EmbellishmentPosition, Palettes, PlacementLibraryItem, PlacementPositionResult, PlacementsSettings, TextLibraryItem, TextLibraryItemFont, } from './ce-types';
|
|
9
10
|
export { extractTextPlacements } from './extractTextPlacements';
|
|
10
11
|
export { mapStateToRendererOptions } from './mapStateToRendererOptions';
|
|
11
|
-
export type { BasePlacementSettings, BasePlacementState, CombinedEditorSettings, ConfigEditor, EditorGarment, EditorPalette, EditorPaletteColour, EditorState, EditorStateVals, EditorVariant, EmbellishmentPosition, Palettes, PlacementLibraryItem, PlacementPositionResult, PlacementsSettings, TextLibraryItem, TextLibraryItemFont, } from './ce-types';
|
|
12
12
|
export type { LoadedTextPlacement, TextPlacementFont, TextPlacementPosition, TextPlacementSpacing, } from './types';
|
package/dist/ce-adapter/index.js
CHANGED
|
@@ -118,24 +118,28 @@ function s(e) {
|
|
|
118
118
|
type: "custom",
|
|
119
119
|
path: e.position.baselinePath
|
|
120
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
},
|
|
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 = e.outlineColour != null, a = e.spacing ?? {
|
|
122
|
+
letter_spacing: e.font.letterSpacing,
|
|
123
|
+
letter_spacing_outline: e.font.letterSpacingOutline,
|
|
124
|
+
outline_width: e.font.outlineWidth
|
|
125
|
+
}, s = {
|
|
126
|
+
outlineWidth: i ? a.outline_width : 0,
|
|
127
|
+
letterSpacing: a.letter_spacing,
|
|
128
|
+
letterSpacingOutline: a.letter_spacing_outline
|
|
129
|
+
}, c = e.fillColour ? {
|
|
126
130
|
hex: e.fillColour.hex,
|
|
127
131
|
reference: e.fillColour.reference ?? void 0
|
|
128
|
-
} : void 0,
|
|
132
|
+
} : void 0, l = e.outlineColour ? {
|
|
129
133
|
hex: e.outlineColour.hex,
|
|
130
134
|
reference: e.outlineColour.reference ?? void 0
|
|
131
|
-
} : void 0,
|
|
135
|
+
} : void 0, u;
|
|
132
136
|
if (e.font.fixedColours) {
|
|
133
137
|
let t = {};
|
|
134
138
|
for (let [n, r] of Object.entries(e.font.fixedColours)) t[n] = {
|
|
135
139
|
hex: r.hex,
|
|
136
140
|
reference: r.reference
|
|
137
141
|
};
|
|
138
|
-
|
|
142
|
+
u = t;
|
|
139
143
|
}
|
|
140
144
|
return {
|
|
141
145
|
text: e.text,
|
|
@@ -149,13 +153,13 @@ function s(e) {
|
|
|
149
153
|
physicalHeight: e.position.physicalHeight,
|
|
150
154
|
physicalUnits: e.position.physicalUnits
|
|
151
155
|
},
|
|
152
|
-
spacing:
|
|
156
|
+
spacing: s,
|
|
153
157
|
verticalAlignment: o(e.verticalAlignment),
|
|
154
158
|
horizontalAlignment: t,
|
|
155
159
|
baseline: n,
|
|
156
|
-
fillColour:
|
|
157
|
-
strokeColour:
|
|
158
|
-
colourMap:
|
|
160
|
+
fillColour: c,
|
|
161
|
+
strokeColour: l,
|
|
162
|
+
colourMap: u
|
|
159
163
|
};
|
|
160
164
|
}
|
|
161
165
|
//#endregion
|
package/dist/fitting/index.d.ts
CHANGED
|
@@ -67,9 +67,9 @@ export interface FitAndTruncateTextToBoxResult extends FitTextToBoxResult {
|
|
|
67
67
|
/**
|
|
68
68
|
* Calculate the font size at which `text` fills `boxDimensions` as closely
|
|
69
69
|
* as possible without overflowing, for any baseline shape. This replaces
|
|
70
|
-
* binary-searching for a fitting font size:
|
|
71
|
-
*
|
|
72
|
-
* numeric solve (see solveCurvedFit).
|
|
70
|
+
* binary-searching for a fitting font size: flat and custom baselines are
|
|
71
|
+
* solved in one step, from a single measurement, while curved baselines need
|
|
72
|
+
* a bounded numeric solve (see solveCurvedFit).
|
|
73
73
|
*
|
|
74
74
|
* `fits: false` means the text doesn't fit even at minFontSize — the caller
|
|
75
75
|
* (e.g. fitAndTruncateTextToBox) should either shorten the text or accept
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { type GenerateCurvedBaselineOptions, type GenerateFlatBaselineOptions, g
|
|
|
3
3
|
export { DEFAULT_OUTLINE_FACTOR, DEFAULT_SPACING, LINE_HEIGHT, } from './constants';
|
|
4
4
|
export { type FitAndTruncateTextToBoxResult, type FitConstraint, type FitSize, type FitTextToBoxOptions, type FitTextToBoxResult, fitAndTruncateTextToBox, getFontSizeToFitBox, } from './fitting';
|
|
5
5
|
export { getFontColours } from './glyph/svg-glyph-converter';
|
|
6
|
+
export { type LayoutTextOptions, layoutText, type MeasuredLine, measureText, type TextLayout, type TextMeasurement, } from './layout';
|
|
6
7
|
export { getFontCache, type LoadedFont, loadFont } from './loading';
|
|
7
8
|
export { type GetFontSizeFromPhysicalSizeOptions, type GetTextMetricsAtFontSizeOptions, getFontSizeFromPhysicalSize, getTextMetricsAtFontSize, } from './measuring';
|
|
8
9
|
export type { GetTextAsPathDataOptions, SvgGlyphEntry } from './rendering';
|