@mlightcad/mtext-renderer 0.12.11 → 0.12.13

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.
@@ -14,6 +14,14 @@ export declare abstract class BaseTextShape extends THREE.Shape {
14
14
  * @returns A THREE.js BufferGeometry representing the text shape
15
15
  */
16
16
  abstract toGeometry(): THREE.BufferGeometry;
17
+ /**
18
+ * Uniform XY scale applied to {@link toGeometry} when placing the glyph.
19
+ *
20
+ * Mesh fonts cache unit-size outlines and report the requested font size here
21
+ * so one geometry serves every height. SHX stroke geometries are already
22
+ * sized and leave this at `1`.
23
+ */
24
+ get geometryScale(): number;
17
25
  /**
18
26
  * Whether the shape has drawable stroke or mesh geometry (not advance-only).
19
27
  */
@@ -58,8 +58,10 @@ export declare class FontManager {
58
58
  /**
59
59
  * When true, {@link MText.asyncDraw} / {@link Shape.asyncDraw} wait for fonts
60
60
  * referenced by the content and style to finish loading before building
61
- * geometry. Useful with {@link lazyFontLoading} when callers prefer a single
62
- * draw pass instead of redrawing on {@link events.fontLoaded}.
61
+ * geometry. Default/symbol fallback chains are still requested in the
62
+ * background (not awaited) under {@link lazyFontLoading}, so unused preset
63
+ * faces do not block the draw; redraw on {@link events.fontLoaded} if those
64
+ * faces are needed for the final glyphs.
63
65
  *
64
66
  * Has no effect when {@link lazyFontLoading} is false (draw already awaits).
65
67
  */
@@ -85,10 +87,18 @@ export declare class FontManager {
85
87
  /** In-flight {@link requestFont} promises keyed by normalized font name. */
86
88
  private fontRequestInFlight;
87
89
  /**
88
- * Fonts whose latest {@link requestFont} finished without registering the
89
- * face. Prevents per-glyph retry storms until {@link release} clears state.
90
+ * Fonts whose latest {@link requestFont} failed to fetch or parse the face.
91
+ * Prevents per-glyph retry storms until {@link release} clears state.
92
+ * Catalog misses (`NotFound`) are not sticky — a later `fonts.json` refresh
93
+ * must still be able to resolve the name.
90
94
  */
91
95
  private fontRequestFailed;
96
+ /**
97
+ * Names already confirmed absent from IndexedDB during this session.
98
+ * Repeated open-time MTEXT loads must not re-enter IndexedDB for the same miss.
99
+ * Cleared when the face is registered or {@link release} drops all fonts.
100
+ */
101
+ private fontCacheLookupMisses;
92
102
  /**
93
103
  * Bumped by full {@link release} so in-flight loads that complete after a
94
104
  * release do not re-register fonts into a cleared manager.
@@ -6,3 +6,4 @@ export * from './font';
6
6
  export * from './fontFactory';
7
7
  export * from './fontLoader';
8
8
  export * from './fontManager';
9
+ export * from './meshGlyphGeometry';
@@ -67,7 +67,7 @@ export declare class MeshFont extends BaseFont {
67
67
  readonly data: MeshFontData;
68
68
  /** Internal opentype.js font instance used for on-demand glyph parsing */
69
69
  private readonly opentypeFont;
70
- /** Glyph cache to limit memory usage */
70
+ /** Glyph cache to limit memory usage; eviction removes entries from {@link data.glyphs}. */
71
71
  private readonly glyphCache;
72
72
  /**
73
73
  * Creates a new instance of MeshFont.
@@ -18,27 +18,13 @@ export interface MeshFontScaleSource {
18
18
  charToGlyph: (char: string) => MeshFontScaleGlyph | undefined;
19
19
  charToGlyphIndex: (char: string) => number | null | undefined;
20
20
  }
21
- /**
22
- * Threshold above which a Latin-'A'-based scale is treated as unsafe for fonts
23
- * that also contain full-em CJK ideographs (SimSun / SimFang / etc.).
24
- */
25
- export declare const CJK_LATIN_SCALE_INFLATION_THRESHOLD = 1.15;
26
- /**
27
- * Fraction of the em square at which an ideograph advance is treated as a
28
- * full-cell CJK design glyph.
29
- */
30
- export declare const CJK_FULL_EM_ADVANCE_RATIO = 0.9;
31
21
  /**
32
22
  * Computes the mesh-font scale that maps CAD text height onto glyph outlines.
33
23
  *
34
- * AutoCAD maps TrueType text height to the font design size. For Western faces,
35
- * capital {@code A} height is a good proxy (`unitsPerEm / A.yMax`). For CJK
36
- * faces, ideographs occupy the full em while Latin capitals are much shorter
37
- * using {@code A.yMax} then inflates both glyph size and advance (~1.4× for
38
- * SimFang/SimSun), which falsely wraps MTEXT that AutoCAD keeps on one line.
39
- *
40
- * When the font has full-em ideographs and the Latin-based scale would inflate
41
- * advances past {@link CJK_LATIN_SCALE_INFLATION_THRESHOLD}, return {@code 1}
42
- * so text height maps to the em square (AutoCAD CJK TrueType behavior).
24
+ * AutoCAD maps TrueType text height to the capital-letter height of the face
25
+ * (`unitsPerEm / A.yMax`). That scale applies to CJK faces as well: ideographs
26
+ * occupy most of the em, so their advances become larger than the nominal text
27
+ * height. Using the em square alone (`1`) under-sizes both glyphs and advances,
28
+ * which delays soft wraps and shortens the MTEXT block relative to AutoCAD.
43
29
  */
44
30
  export declare function computeMeshFontScaleFactor(font: MeshFontScaleSource): number;
@@ -0,0 +1,23 @@
1
+ import type * as THREE from 'three';
2
+ /**
3
+ * `userData` flag marking BufferGeometry produced for mesh (TTF/OTF) glyphs.
4
+ *
5
+ * `mergeVertices` returns a plain {@link THREE.BufferGeometry}, so callers must
6
+ * not rely on `instanceof ShapeGeometry` to distinguish mesh glyphs from SHX
7
+ * stroke geometry after the first cache fill.
8
+ */
9
+ export declare const MESH_GLYPH_USER_DATA_KEY = "isMeshGlyph";
10
+ /**
11
+ * Canonical size stored in {@link CharGeometryCache} for mesh glyphs.
12
+ * Placement scales by {@link MeshTextShape.geometryScale} (= requested size).
13
+ */
14
+ export declare const MESH_GLYPH_CACHE_SIZE = 1;
15
+ /**
16
+ * Returns true when `geometry` is a mesh-font glyph (filled ShapeGeometry path),
17
+ * including geometries that were demoted to BufferGeometry by `mergeVertices`.
18
+ */
19
+ export declare function isMeshGlyphGeometry(geometry: THREE.BufferGeometry): boolean;
20
+ /**
21
+ * Marks a geometry as a mesh glyph so downstream batching keeps the filled-mesh path.
22
+ */
23
+ export declare function markMeshGlyphGeometry(geometry: THREE.BufferGeometry): void;
@@ -16,9 +16,14 @@ export declare class MeshTextShape extends BaseTextShape {
16
16
  private readonly font;
17
17
  private readonly fontSize;
18
18
  constructor(char: string, fontSize: number, font: MeshFont);
19
+ /**
20
+ * Scale from the unit-size cached outline to the requested drawing height.
21
+ */
22
+ get geometryScale(): number;
19
23
  /**
20
24
  * Converts the text shape to a THREE.js geometry.
21
- * This is used for 3D rendering of the text.
25
+ * Outlines are cached once at {@link MESH_GLYPH_CACHE_SIZE}; callers must
26
+ * apply {@link geometryScale} when placing the glyph.
22
27
  * @returns A THREE.js BufferGeometry representing the text shape
23
28
  */
24
29
  toGeometry(): THREE.BufferGeometry;
@@ -75,6 +75,10 @@ export declare class ShxFont extends BaseFont {
75
75
  * @returns The shape data for the code, or undefined if not found.
76
76
  */
77
77
  getCodeShape(code: number, size: number): ShxTextShape | undefined;
78
+ /**
79
+ * Snaps near-equal heights so layout/geometry cache keys hit across float noise.
80
+ */
81
+ private static quantizeSize;
78
82
  /**
79
83
  * Gets the shape data for a named SHX shape at the requested size.
80
84
  * @param name - The SHX shape name to look up.
@@ -10,6 +10,12 @@ export declare const LINE_SPACING_SCALE_FACTOR: number;
10
10
  * Default DXF group-44 line spacing factor (AutoCAD single spacing).
11
11
  */
12
12
  export declare const DEFAULT_LINE_SPACE_FACTOR = 1;
13
+ /**
14
+ * Default DXF group-73 line spacing style (AutoCAD "At Least").
15
+ *
16
+ * DXF may omit group 73 or write `0`; both mean At Least.
17
+ */
18
+ export declare const DEFAULT_LINE_SPACE_STYLE = 1;
13
19
  /**
14
20
  * Vertical compensation needed after switching normal glyph placement from
15
21
  * top-anchored to baseline-anchored coordinates.
@@ -7,9 +7,10 @@ import * as THREE from 'three';
7
7
  */
8
8
  export interface MTextDrawOptions {
9
9
  /**
10
- * Wait for fonts referenced by the content/style and, when awaiting, the
11
- * configured default/symbol fallback chains to finish loading before
12
- * building geometry.
10
+ * Wait for fonts referenced by the content/style to finish loading before
11
+ * building geometry. Default/symbol fallback chains are requested in the
12
+ * background under lazy loading (not awaited) so open-time draws are not
13
+ * blocked on unused preset faces.
13
14
  *
14
15
  * Defaults to `true` when {@link FontManager.lazyFontLoading} is false, or
15
16
  * when {@link FontManager.awaitFontsBeforeDraw} is true. Otherwise fonts are
@@ -46,7 +47,8 @@ export declare class MText extends THREE.Object3D {
46
47
  *
47
48
  * @param mtext - The MText string to analyze for font names
48
49
  * @param removeExtension - Whether to remove font file extensions (e.g., .ttf, .shx) from font names. Defaults to false.
49
- * @returns A Set containing all unique font names found in the MText string, converted to lowercase
50
+ * @returns A Set containing all unique font names found in the MText string, converted to lowercase.
51
+ * SHX pairs such as `\Ftssdeng,hztxt|c134;` contribute both the primary face and the big font.
50
52
  * @example
51
53
  * ```ts
52
54
  * const mtext = "\\fArial.ttf|Hello\\fTimes New Roman.otf|World";
@@ -1,7 +1,7 @@
1
1
  import { ChangedProperties, MTextParagraphAlignment, MTextToken } from '@mlightcad/mtext-parser';
2
2
  import { FontManager } from '../font';
3
3
  import { StyleManager } from './styleManager';
4
- import { ColorSettings, LineLayout, MTextFlowDirection, TextStyle } from './types';
4
+ import { ColorSettings, LineLayout, MTextFlowDirection, MTextLineSpacingStyle, TextStyle } from './types';
5
5
  import * as THREE from 'three';
6
6
  /**
7
7
  * Options for formatting MText.
@@ -20,6 +20,11 @@ export interface MTextFormatOptions {
20
20
  * single spacing (`5/3` of text height). Default is `1.0`.
21
21
  */
22
22
  lineSpaceFactor: number;
23
+ /**
24
+ * AutoCAD DXF group-73 line spacing style.
25
+ * `1` / omitted / `0` = At Least, `2` = Exact.
26
+ */
27
+ lineSpaceStyle?: number;
23
28
  /**
24
29
  * The horizontal alignment.
25
30
  */
@@ -84,6 +89,11 @@ export declare class MTextProcessor {
84
89
  private _currentContext;
85
90
  /** Largest font size encountered on the current visual line. */
86
91
  private _maxFontSize;
92
+ /**
93
+ * Maximum drawing-space (layout) font size on the current line.
94
+ * Used by At Least line spacing so font glyph scale factors do not inflate the floor.
95
+ */
96
+ private _maxLayoutFontSize;
87
97
  /**
88
98
  * The current horizontal alignment for the paragraph.
89
99
  *
@@ -117,6 +127,12 @@ export declare class MTextProcessor {
117
127
  private _currentLeftMargin;
118
128
  /** Current paragraph right margin in drawing units. */
119
129
  private _currentRightMargin;
130
+ /**
131
+ * When true, {@link processChar} will not soft-wrap even if the pen is past the
132
+ * defined width. Used while finishing a short word that AutoCAD keeps on the
133
+ * current line with a slight overhang.
134
+ */
135
+ private _suppressSoftWrap;
120
136
  /**
121
137
  * Construct one instance of this class and initialize some properties with default values.
122
138
  * @param style Input text style
@@ -170,6 +186,11 @@ export declare class MTextProcessor {
170
186
  * Single spacing is {@link LINE_SPACING_SCALE_FACTOR} × text height.
171
187
  */
172
188
  get defaultLineSpaceFactor(): number;
189
+ /**
190
+ * AutoCAD DXF group-73 line spacing style.
191
+ * Omitted/`0` values resolve to At Least (AutoCAD default).
192
+ */
193
+ get defaultLineSpaceStyle(): MTextLineSpacingStyle;
173
194
  /**
174
195
  * Font name of current character
175
196
  */
@@ -195,9 +216,15 @@ export declare class MTextProcessor {
195
216
  /**
196
217
  * Baseline-to-baseline advance for the current line (AutoCAD MTEXT semantics).
197
218
  *
198
- * Single spacing is `5/3` of the drawing-space text height; `lineSpaceFactor`
219
+ * Single spacing is `5/3` of the MTEXT text height; `lineSpaceFactor`
199
220
  * (DXF group 44) scales that distance. Font glyph scale factors must not
200
- * affect this layout metric — use {@link currentLayoutFontSize}.
221
+ * affect this layout metric.
222
+ *
223
+ * Nominal spacing is `lineSpaceFactor × MTEXT height × 5/3`. Exact style
224
+ * always uses that distance. At Least (DXF group 73 = 1, or omitted/`0`)
225
+ * never goes below single spacing of the line's layout height, so a compact
226
+ * factor such as `0.25` does not stack glyphs. Taller characters on the line
227
+ * raise the floor further.
201
228
  */
202
229
  get currentLineHeight(): number;
203
230
  /**
@@ -209,8 +236,8 @@ export declare class MTextProcessor {
209
236
  /**
210
237
  * The current space setting between two characters. The meaning of this value is as follows.
211
238
  * - 1: no extra spacing (default tracking)
212
- * - 1.2: increases spacing by 20% of the text height
213
- * - 0.8: decreases spacing by 20% of the text height
239
+ * - 1.2: increases spacing by 20% of (text height × width factor / font scale)
240
+ * - 0.8: decreases spacing by 20% of that same base
214
241
  */
215
242
  get currentWordSpace(): number;
216
243
  /**
@@ -219,6 +246,22 @@ export declare class MTextProcessor {
219
246
  get currentWidthFactor(): number;
220
247
  /** Horizontal advance for one space, including tracking and width factor. */
221
248
  get currentBlankAdvance(): number;
249
+ /**
250
+ * Horizontal pen advance for a glyph (or space) width.
251
+ *
252
+ * AutoCAD MTEXT tracking (`\T`) adjusts the space *between* characters: 1.0 is
253
+ * normal. Multiplying the full advance by the tracking factor over-spaces CJK
254
+ * ideographs (full-em cells) and forces early soft wraps. Instead, apply width
255
+ * factor to the glyph advance, then add
256
+ * `(tracking - 1) × textHeight × widthFactor / fontScaleFactor`.
257
+ *
258
+ * Dividing by the TrueType capital-A scale keeps tracking tied to the MTEXT
259
+ * text height rather than the inflated outline size used for glyph advances.
260
+ *
261
+ * @param shapeWidth - Unscaled glyph/space advance from the font.
262
+ * @param obliqueExtraAdvance - Extra advance from oblique shear, if any.
263
+ */
264
+ private penAdvance;
222
265
  /**
223
266
  * All of THREE.js objects in current line. It contains objects in all of sections of this line.
224
267
  */
@@ -272,6 +315,8 @@ export declare class MTextProcessor {
272
315
  * Apply a font face change to the current render context, including
273
316
  * derived bold/italic/oblique settings based on font type.
274
317
  * @param fontFace The font face change data from the parser.
318
+ * @param updateInlineBigFont When false, a comma-less family keeps the big
319
+ * font already restored by the context stack. Used for `{}` snapshots.
275
320
  */
276
321
  private applyFontFaceChange;
277
322
  /**
@@ -370,14 +415,15 @@ export declare class MTextProcessor {
370
415
  /**
371
416
  * Appends one glyph's geometry to the active batch buffers and optional char boxes.
372
417
  *
373
- * Mesh fonts (`ShapeGeometry`) are transformed immediately; line fonts are queued in
418
+ * Mesh fonts (tagged via {@link isMeshGlyphGeometry}) are transformed immediately;
419
+ * line fonts are queued in
374
420
  * {@link _lineBatchEntries} for later merge via {@link TextGeometryBuilder.mergeLineGeometries}.
375
421
  *
376
422
  * @param shape Source text shape for the glyph.
377
423
  * @param label Character label stored on geometry and char boxes.
378
424
  * @param canonical Untransformed glyph geometry from the font.
379
425
  * @param matrix World transform to apply to the glyph.
380
- * @param geometries Accumulator for mesh (`ShapeGeometry`) primitives.
426
+ * @param geometries Accumulator for mesh glyph primitives.
381
427
  * @param meshCharBoxes Accumulator for mesh-glyph picking boxes.
382
428
  * @param lineCharBoxes Accumulator for line-glyph picking boxes.
383
429
  */
@@ -409,9 +455,20 @@ export declare class MTextProcessor {
409
455
  * @param charBoxType Char-box classification stored on the flushed object.
410
456
  */
411
457
  private processGeometries;
458
+ /**
459
+ * True when every code point is in Basic Latin / Latin-1 (drawing numbers,
460
+ * hyphens, etc.). CJK and other scripts soft-wrap between characters.
461
+ */
462
+ private isLatinAsciiRun;
412
463
  /**
413
464
  * Lays out and renders one parser word token, breaking to a new visual line when needed.
414
465
  *
466
+ * - CJK / non-Latin: no whole-word wrap — {@link processChar} breaks between
467
+ * characters when the next glyph would not fit in the defined width.
468
+ * - Latin/ASCII: keep Western word wrapping, except a short run that only
469
+ * slightly overshoots after CJK (e.g. `秘密-FJP-898E-G`) may stay on the
470
+ * current line with a small overhang (AutoCAD).
471
+ *
415
472
  * @param word Character sequence for a single word token.
416
473
  * @param geometries Mesh geometry accumulator for the active style segment.
417
474
  * @param lineGeometries Line geometry accumulator for the active style segment.
@@ -479,8 +536,10 @@ export declare class MTextProcessor {
479
536
  /**
480
537
  * Renders one character glyph, including decorations and line-break handling.
481
538
  *
482
- * Missing glyphs are treated as spaces. When the pen exceeds {@link maxLineWidth},
483
- * a visual line break is recorded before placement.
539
+ * Missing glyphs are treated as spaces. Soft wrap is decided before placement:
540
+ * if the glyph's advance would not fit in the remaining defined width, break
541
+ * first (CJK may break between any two characters). The first glyph on an
542
+ * empty line is always placed even when wider than the box.
484
543
  *
485
544
  * @param char Character to render.
486
545
  * @param geometries Mesh geometry accumulator.
@@ -604,7 +663,7 @@ export declare class MTextProcessor {
604
663
  /**
605
664
  * Converts pending mesh and line geometries into a styled THREE.js object.
606
665
  *
607
- * @param geometries Mesh (`ShapeGeometry`) primitives for the current style segment.
666
+ * @param geometries Mesh glyph primitives for the current style segment.
608
667
  * @param lineGeometries Line primitives and decorations for the current style segment.
609
668
  * @param meshCharBoxes Mesh char boxes to attach to the created object.
610
669
  * @param lineCharBoxes Line char boxes to attach to the created object.
@@ -71,6 +71,22 @@ export declare enum MTextAttachmentPoint {
71
71
  /** Baseline-right point. */
72
72
  BaselineRight = 12
73
73
  }
74
+ /**
75
+ * AutoCAD MTEXT line spacing style (DXF group 73).
76
+ *
77
+ * - At Least: never below single spacing of the line's layout height; factors
78
+ * above 1.0 raise that floor, and taller characters raise it further
79
+ * - Exact: factor spacing is fixed even if characters overlap
80
+ *
81
+ * Omitted or `0` values are treated as {@link MTextLineSpacingStyle.AtLeast}
82
+ * (AutoCAD's default).
83
+ */
84
+ export declare enum MTextLineSpacingStyle {
85
+ /** At least — floor at single spacing of content; taller glyphs raise further. */
86
+ AtLeast = 1,
87
+ /** Exact — fixed baseline spacing; tall characters may overlap. */
88
+ Exact = 2
89
+ }
74
90
  /**
75
91
  * Logical text token with optional pick box information.
76
92
  *
@@ -176,6 +192,11 @@ export interface MTextData {
176
192
  * Default is `1.0`.
177
193
  */
178
194
  lineSpaceFactor?: number;
195
+ /**
196
+ * AutoCAD DXF group-73 line spacing style.
197
+ * `1` = At Least (default when omitted/`0`), `2` = Exact.
198
+ */
199
+ lineSpaceStyle?: MTextLineSpacingStyle | number;
179
200
  /** The width scaling factor applied to each character. Default is 1.0 */
180
201
  widthFactor?: number;
181
202
  /** Whether to collect per-character bounding boxes for picking. Default is true */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mlightcad/mtext-renderer",
3
- "version": "0.12.11",
3
+ "version": "0.12.13",
4
4
  "description": "AutoCAD MText renderer based on Three.js",
5
5
  "license": "MIT",
6
6
  "author": "MLight Lee <mlight.lee@outlook.com>",
@@ -64,6 +64,7 @@
64
64
  "clean": "rimraf dist lib tsconfig.tsbuildinfo",
65
65
  "lint": "eslint src/",
66
66
  "lint:fix": "eslint --fix --quiet src/",
67
- "test": "vitest run"
67
+ "test": "vitest run",
68
+ "bench": "vitest run --config vitest.bench.config.ts"
68
69
  }
69
70
  }