@mlightcad/mtext-renderer 0.12.12 → 0.13.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.
@@ -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
  */
@@ -87,10 +87,18 @@ export declare class FontManager {
87
87
  /** In-flight {@link requestFont} promises keyed by normalized font name. */
88
88
  private fontRequestInFlight;
89
89
  /**
90
- * Fonts whose latest {@link requestFont} finished without registering the
91
- * 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.
92
94
  */
93
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;
94
102
  /**
95
103
  * Bumped by full {@link release} so in-flight loads that complete after a
96
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
+ * Cached glyphs are baked to position and index buffers, so callers must not
6
+ * rely on `instanceof ShapeGeometry` to distinguish mesh glyphs from SHX
7
+ * stroke geometry.
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 outline),
17
+ * including geometries baked down to a plain {@link THREE.BufferGeometry}.
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.
@@ -44,4 +44,21 @@ export declare class TextGeometryBuilder {
44
44
  * @returns The next write offset after all segments have been written.
45
45
  */
46
46
  private static writeTransformedLineSegments;
47
+ /**
48
+ * Merges indexed mesh glyphs into one indexed triangle geometry, applying each
49
+ * glyph matrix while copying vertices.
50
+ *
51
+ * One write pass replaces per-glyph `clone` + `applyMatrix4` followed by a
52
+ * second copy inside `mergeGeometries`.
53
+ *
54
+ * @param entries Cached glyph geometries paired with their placement matrices.
55
+ * @returns A single indexed mesh geometry. Empty when `entries` is empty.
56
+ */
57
+ static mergeMeshGeometries(entries: TransformedLineGeometryEntry[]): THREE.BufferGeometry;
58
+ /**
59
+ * Writes `position` transformed by `matrix` into `output` at `vertexBase`.
60
+ * Affine placement matrices keep `w` at 1; the perspective term matches
61
+ * {@link THREE.Vector3.applyMatrix4} for any other matrix.
62
+ */
63
+ private static writeTransformedMeshPositions;
47
64
  }
package/lib/index.d.ts CHANGED
@@ -5,4 +5,3 @@ export * from './memory';
5
5
  export * from './shxParser';
6
6
  export * from './renderer';
7
7
  export * from './worker';
8
- export * from '@mlightcad/mtext-parser';
@@ -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.
@@ -47,7 +47,8 @@ export declare class MText extends THREE.Object3D {
47
47
  *
48
48
  * @param mtext - The MText string to analyze for font names
49
49
  * @param removeExtension - Whether to remove font file extensions (e.g., .ttf, .shx) from font names. Defaults to false.
50
- * @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.
51
52
  * @example
52
53
  * ```ts
53
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
  *
@@ -111,12 +121,20 @@ export declare class MTextProcessor {
111
121
  private _processedCharCount;
112
122
  /** Line glyph entries collected for batch geometry merge within a style segment. */
113
123
  private _lineBatchEntries;
124
+ /** Mesh glyph entries collected for a single-pass triangle merge within a style segment. */
125
+ private _meshBatchEntries;
114
126
  /** Current paragraph first-line indent in drawing units. */
115
127
  private _currentIndent;
116
128
  /** Current paragraph left margin in drawing units. */
117
129
  private _currentLeftMargin;
118
130
  /** Current paragraph right margin in drawing units. */
119
131
  private _currentRightMargin;
132
+ /**
133
+ * When true, {@link processChar} will not soft-wrap even if the pen is past the
134
+ * defined width. Used while finishing a short word that AutoCAD keeps on the
135
+ * current line with a slight overhang.
136
+ */
137
+ private _suppressSoftWrap;
120
138
  /**
121
139
  * Construct one instance of this class and initialize some properties with default values.
122
140
  * @param style Input text style
@@ -170,6 +188,11 @@ export declare class MTextProcessor {
170
188
  * Single spacing is {@link LINE_SPACING_SCALE_FACTOR} × text height.
171
189
  */
172
190
  get defaultLineSpaceFactor(): number;
191
+ /**
192
+ * AutoCAD DXF group-73 line spacing style.
193
+ * Omitted/`0` values resolve to At Least (AutoCAD default).
194
+ */
195
+ get defaultLineSpaceStyle(): MTextLineSpacingStyle;
173
196
  /**
174
197
  * Font name of current character
175
198
  */
@@ -195,9 +218,15 @@ export declare class MTextProcessor {
195
218
  /**
196
219
  * Baseline-to-baseline advance for the current line (AutoCAD MTEXT semantics).
197
220
  *
198
- * Single spacing is `5/3` of the drawing-space text height; `lineSpaceFactor`
221
+ * Single spacing is `5/3` of the MTEXT text height; `lineSpaceFactor`
199
222
  * (DXF group 44) scales that distance. Font glyph scale factors must not
200
- * affect this layout metric — use {@link currentLayoutFontSize}.
223
+ * affect this layout metric.
224
+ *
225
+ * Nominal spacing is `lineSpaceFactor × MTEXT height × 5/3`. Exact style
226
+ * always uses that distance. At Least (DXF group 73 = 1, or omitted/`0`)
227
+ * never goes below single spacing of the line's layout height, so a compact
228
+ * factor such as `0.25` does not stack glyphs. Taller characters on the line
229
+ * raise the floor further.
201
230
  */
202
231
  get currentLineHeight(): number;
203
232
  /**
@@ -209,8 +238,8 @@ export declare class MTextProcessor {
209
238
  /**
210
239
  * The current space setting between two characters. The meaning of this value is as follows.
211
240
  * - 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
241
+ * - 1.2: increases spacing by 20% of (text height × width factor / font scale)
242
+ * - 0.8: decreases spacing by 20% of that same base
214
243
  */
215
244
  get currentWordSpace(): number;
216
245
  /**
@@ -219,6 +248,22 @@ export declare class MTextProcessor {
219
248
  get currentWidthFactor(): number;
220
249
  /** Horizontal advance for one space, including tracking and width factor. */
221
250
  get currentBlankAdvance(): number;
251
+ /**
252
+ * Horizontal pen advance for a glyph (or space) width.
253
+ *
254
+ * AutoCAD MTEXT tracking (`\T`) adjusts the space *between* characters: 1.0 is
255
+ * normal. Multiplying the full advance by the tracking factor over-spaces CJK
256
+ * ideographs (full-em cells) and forces early soft wraps. Instead, apply width
257
+ * factor to the glyph advance, then add
258
+ * `(tracking - 1) × textHeight × widthFactor / fontScaleFactor`.
259
+ *
260
+ * Dividing by the TrueType capital-A scale keeps tracking tied to the MTEXT
261
+ * text height rather than the inflated outline size used for glyph advances.
262
+ *
263
+ * @param shapeWidth - Unscaled glyph/space advance from the font.
264
+ * @param obliqueExtraAdvance - Extra advance from oblique shear, if any.
265
+ */
266
+ private penAdvance;
222
267
  /**
223
268
  * All of THREE.js objects in current line. It contains objects in all of sections of this line.
224
269
  */
@@ -272,6 +317,8 @@ export declare class MTextProcessor {
272
317
  * Apply a font face change to the current render context, including
273
318
  * derived bold/italic/oblique settings based on font type.
274
319
  * @param fontFace The font face change data from the parser.
320
+ * @param updateInlineBigFont When false, a comma-less family keeps the big
321
+ * font already restored by the context stack. Used for `{}` snapshots.
275
322
  */
276
323
  private applyFontFaceChange;
277
324
  /**
@@ -370,14 +417,16 @@ export declare class MTextProcessor {
370
417
  /**
371
418
  * Appends one glyph's geometry to the active batch buffers and optional char boxes.
372
419
  *
373
- * Mesh fonts (`ShapeGeometry`) are transformed immediately; line fonts are queued in
420
+ * Mesh fonts (tagged via {@link isMeshGlyphGeometry}) are queued in
421
+ * {@link _meshBatchEntries} and merged once via {@link TextGeometryBuilder.mergeMeshGeometries}.
422
+ * Line fonts are queued in
374
423
  * {@link _lineBatchEntries} for later merge via {@link TextGeometryBuilder.mergeLineGeometries}.
375
424
  *
376
425
  * @param shape Source text shape for the glyph.
377
426
  * @param label Character label stored on geometry and char boxes.
378
427
  * @param canonical Untransformed glyph geometry from the font.
379
428
  * @param matrix World transform to apply to the glyph.
380
- * @param geometries Accumulator for mesh (`ShapeGeometry`) primitives.
429
+ * @param geometries Unused. Mesh glyphs are queued on {@link _meshBatchEntries}.
381
430
  * @param meshCharBoxes Accumulator for mesh-glyph picking boxes.
382
431
  * @param lineCharBoxes Accumulator for line-glyph picking boxes.
383
432
  */
@@ -395,6 +444,10 @@ export declare class MTextProcessor {
395
444
  * @returns A {@link THREE.Group} containing merged line/mesh objects and layout metadata.
396
445
  */
397
446
  processText(tokens: Generator<MTextToken>): THREE.Group<THREE.Object3DEventMap>;
447
+ /**
448
+ * True when the current style segment still has mesh glyphs, line glyphs, or decorations to flush.
449
+ */
450
+ private hasPendingDrawables;
398
451
  /**
399
452
  * Flushes pending geometry and char boxes into a styled THREE.js object on `group`.
400
453
  *
@@ -409,9 +462,20 @@ export declare class MTextProcessor {
409
462
  * @param charBoxType Char-box classification stored on the flushed object.
410
463
  */
411
464
  private processGeometries;
465
+ /**
466
+ * True when every code point is in Basic Latin / Latin-1 (drawing numbers,
467
+ * hyphens, etc.). CJK and other scripts soft-wrap between characters.
468
+ */
469
+ private isLatinAsciiRun;
412
470
  /**
413
471
  * Lays out and renders one parser word token, breaking to a new visual line when needed.
414
472
  *
473
+ * - CJK / non-Latin: no whole-word wrap — {@link processChar} breaks between
474
+ * characters when the next glyph would not fit in the defined width.
475
+ * - Latin/ASCII: keep Western word wrapping, except a short run that only
476
+ * slightly overshoots after CJK (e.g. `秘密-FJP-898E-G`) may stay on the
477
+ * current line with a small overhang (AutoCAD).
478
+ *
415
479
  * @param word Character sequence for a single word token.
416
480
  * @param geometries Mesh geometry accumulator for the active style segment.
417
481
  * @param lineGeometries Line geometry accumulator for the active style segment.
@@ -479,8 +543,10 @@ export declare class MTextProcessor {
479
543
  /**
480
544
  * Renders one character glyph, including decorations and line-break handling.
481
545
  *
482
- * Missing glyphs are treated as spaces. When the pen exceeds {@link maxLineWidth},
483
- * a visual line break is recorded before placement.
546
+ * Missing glyphs are treated as spaces. Soft wrap is decided before placement:
547
+ * if the glyph's advance would not fit in the remaining defined width, break
548
+ * first (CJK may break between any two characters). The first glyph on an
549
+ * empty line is always placed even when wider than the box.
484
550
  *
485
551
  * @param char Character to render.
486
552
  * @param geometries Mesh geometry accumulator.
@@ -604,7 +670,7 @@ export declare class MTextProcessor {
604
670
  /**
605
671
  * Converts pending mesh and line geometries into a styled THREE.js object.
606
672
  *
607
- * @param geometries Mesh (`ShapeGeometry`) primitives for the current style segment.
673
+ * @param geometries Mesh glyph primitives for the current style segment.
608
674
  * @param lineGeometries Line primitives and decorations for the current style segment.
609
675
  * @param meshCharBoxes Mesh char boxes to attach to the created object.
610
676
  * @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.12",
3
+ "version": "0.13.0",
4
4
  "description": "AutoCAD MText renderer based on Three.js",
5
5
  "license": "MIT",
6
6
  "author": "MLight Lee <mlight.lee@outlook.com>",
@@ -43,18 +43,19 @@
43
43
  "3d-text"
44
44
  ],
45
45
  "dependencies": {
46
- "@mlightcad/mtext-parser": "^1.5.1",
47
46
  "@mlightcad/shx-parser": "^1.4.5",
48
47
  "iconv-lite": "^0.7.0",
49
48
  "idb": "^8.0.3",
50
49
  "opentype.js": "^2.0.0"
51
50
  },
52
51
  "devDependencies": {
52
+ "@mlightcad/mtext-parser": "^1.5.2",
53
53
  "@types/opentype.js": "^1.3.10",
54
54
  "@types/three": "^0.172.0",
55
55
  "vitest": "^3.2.4"
56
56
  },
57
57
  "peerDependencies": {
58
+ "@mlightcad/mtext-parser": "^1.5.2",
58
59
  "three": "^0.172.0"
59
60
  },
60
61
  "scripts": {
@@ -64,6 +65,7 @@
64
65
  "clean": "rimraf dist lib tsconfig.tsbuildinfo",
65
66
  "lint": "eslint src/",
66
67
  "lint:fix": "eslint --fix --quiet src/",
67
- "test": "vitest run"
68
+ "test": "vitest run",
69
+ "bench": "vitest run --config vitest.bench.config.ts"
68
70
  }
69
71
  }