@mlightcad/cad-simple-viewer 1.7.1 → 1.7.2

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 (89) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +185 -185
  3. package/dist/cad-simple-viewer.js +5710 -5430
  4. package/dist/cad-simple-viewer.umd.cjs +119 -115
  5. package/dist/mtext-renderer-worker.js +7048 -6961
  6. package/icons.ts +7 -7
  7. package/lib/app/AcApDocManager.d.ts +9 -6
  8. package/lib/app/AcApDocManager.d.ts.map +1 -1
  9. package/lib/app/AcApDocManager.js +44 -18
  10. package/lib/app/AcApDocManager.js.map +1 -1
  11. package/lib/app/AcApDocument.d.ts +3 -0
  12. package/lib/app/AcApDocument.d.ts.map +1 -1
  13. package/lib/app/AcApDocument.js +31 -24
  14. package/lib/app/AcApDocument.js.map +1 -1
  15. package/lib/app/AcApOpenDatabaseOptions.d.ts +27 -17
  16. package/lib/app/AcApOpenDatabaseOptions.d.ts.map +1 -1
  17. package/lib/app/AcApOpenFileProgressController.d.ts +12 -5
  18. package/lib/app/AcApOpenFileProgressController.d.ts.map +1 -1
  19. package/lib/app/AcApOpenFileProgressController.js +24 -7
  20. package/lib/app/AcApOpenFileProgressController.js.map +1 -1
  21. package/lib/app/AcApWorkerAssets.d.ts +3 -3
  22. package/lib/app/AcApWorkerAssets.d.ts.map +1 -1
  23. package/lib/app/AcApWorkerAssets.js +3 -3
  24. package/lib/app/AcApWorkerAssets.js.map +1 -1
  25. package/lib/app/notification/AcApNotificationEventBridge.d.ts.map +1 -1
  26. package/lib/app/notification/AcApNotificationEventBridge.js.map +1 -1
  27. package/lib/editor/input/ui/AcEdMTextEditor.d.ts.map +1 -1
  28. package/lib/editor/input/ui/AcEdMTextEditor.js +2 -1
  29. package/lib/editor/input/ui/AcEdMTextEditor.js.map +1 -1
  30. package/lib/i18n/ar/main.d.ts +2 -0
  31. package/lib/i18n/ar/main.d.ts.map +1 -1
  32. package/lib/i18n/ar/main.js +4 -2
  33. package/lib/i18n/ar/main.js.map +1 -1
  34. package/lib/i18n/cs/main.d.ts +2 -0
  35. package/lib/i18n/cs/main.d.ts.map +1 -1
  36. package/lib/i18n/cs/main.js +4 -2
  37. package/lib/i18n/cs/main.js.map +1 -1
  38. package/lib/i18n/en/main.d.ts +2 -0
  39. package/lib/i18n/en/main.d.ts.map +1 -1
  40. package/lib/i18n/en/main.js +4 -2
  41. package/lib/i18n/en/main.js.map +1 -1
  42. package/lib/i18n/tr/main.d.ts +2 -0
  43. package/lib/i18n/tr/main.d.ts.map +1 -1
  44. package/lib/i18n/tr/main.js +4 -2
  45. package/lib/i18n/tr/main.js.map +1 -1
  46. package/lib/i18n/zh/main.d.ts +2 -0
  47. package/lib/i18n/zh/main.d.ts.map +1 -1
  48. package/lib/i18n/zh/main.js +4 -2
  49. package/lib/i18n/zh/main.js.map +1 -1
  50. package/lib/index.d.ts +1 -1
  51. package/lib/index.d.ts.map +1 -1
  52. package/lib/index.js +1 -1
  53. package/lib/index.js.map +1 -1
  54. package/lib/ui/AcUiDefaultNotificationUi.d.ts.map +1 -1
  55. package/lib/ui/AcUiDefaultNotificationUi.js +3 -2
  56. package/lib/ui/AcUiDefaultNotificationUi.js.map +1 -1
  57. package/lib/ui/icons.d.ts +7 -1
  58. package/lib/ui/icons.d.ts.map +1 -1
  59. package/lib/ui/icons.js +10 -2
  60. package/lib/ui/icons.js.map +1 -1
  61. package/lib/util/AcApMessageLink.d.ts +41 -0
  62. package/lib/util/AcApMessageLink.d.ts.map +1 -0
  63. package/lib/util/AcApMessageLink.js +127 -0
  64. package/lib/util/AcApMessageLink.js.map +1 -0
  65. package/lib/util/AcApOpenFileErrorMessage.d.ts +18 -1
  66. package/lib/util/AcApOpenFileErrorMessage.d.ts.map +1 -1
  67. package/lib/util/AcApOpenFileErrorMessage.js +34 -2
  68. package/lib/util/AcApOpenFileErrorMessage.js.map +1 -1
  69. package/lib/util/index.d.ts +1 -0
  70. package/lib/util/index.d.ts.map +1 -1
  71. package/lib/util/index.js +1 -0
  72. package/lib/util/index.js.map +1 -1
  73. package/lib/view/AcTrDirectBatch.d.ts.map +1 -1
  74. package/lib/view/AcTrDirectBatch.js +12 -1
  75. package/lib/view/AcTrDirectBatch.js.map +1 -1
  76. package/lib/view/AcTrProgressiveOpenFitController.d.ts +8 -0
  77. package/lib/view/AcTrProgressiveOpenFitController.d.ts.map +1 -1
  78. package/lib/view/AcTrProgressiveOpenFitController.js +13 -0
  79. package/lib/view/AcTrProgressiveOpenFitController.js.map +1 -1
  80. package/lib/view/AcTrView2d.d.ts +86 -9
  81. package/lib/view/AcTrView2d.d.ts.map +1 -1
  82. package/lib/view/AcTrView2d.js +381 -63
  83. package/lib/view/AcTrView2d.js.map +1 -1
  84. package/lib/view/fontLoadRegen.d.ts +25 -0
  85. package/lib/view/fontLoadRegen.d.ts.map +1 -0
  86. package/lib/view/fontLoadRegen.js +33 -0
  87. package/lib/view/fontLoadRegen.js.map +1 -0
  88. package/package.json +10 -8
  89. package/src/ui/icons.ts +399 -387
@@ -99,7 +99,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
99
99
  import { AcCmUiYieldGate, AcDbAttribute, AcDbBlockReference, acdbHostApplicationServices, AcDbMText, AcDbRasterImage, AcDbRay, AcDbSysVarManager, AcDbViewport, AcDbXline, AcGeBox2d, AcGePoint2d, acgiForegroundColorForBackground, log } from '@mlightcad/data-model';
100
100
  import { AcDbSystemVariables } from '@mlightcad/data-model';
101
101
  import { FontManager } from '@mlightcad/mtext-renderer';
102
- import { AcTrEntity, AcTrGlyphEntity, AcTrGroup, AcTrRenderer, AcTrViewportView } from '@mlightcad/three-renderer';
102
+ import { AcTrEntity, AcTrGlyphEntity, AcTrGroup, AcTrRenderer, AcTrViewportView, hasPendingComplexLineTypeGlyphs } from '@mlightcad/three-renderer';
103
103
  import { AcTrMatrixUtil } from '@mlightcad/three-renderer';
104
104
  import * as THREE from 'three';
105
105
  import Stats from 'three/examples/jsm/libs/stats.module';
@@ -130,6 +130,7 @@ import { AcTrLayoutViewManager } from './AcTrLayoutViewManager';
130
130
  import { sortPickResults } from './AcTrPickResultUtil';
131
131
  import { AcTrProgressiveOpenFitController } from './AcTrProgressiveOpenFitController';
132
132
  import { AcTrScene } from './AcTrScene';
133
+ import { shouldRegenDatabaseAfterFontLoad } from './fontLoadRegen';
133
134
  /**
134
135
  * Default view option values
135
136
  */
@@ -247,6 +248,21 @@ var AcTrView2d = /** @class */ (function (_super) {
247
248
  * wasted conversion work — this set skips re-entry until the current pass ends.
248
249
  */
249
250
  _this._convertingLayers = new Set();
251
+ /**
252
+ * Object ids claimed by an in-flight interactive {@link batchConvert},
253
+ * including deferred glyph commits that have not yet called
254
+ * {@link AcTrScene.addEntity}. Without this, a second convert pass can
255
+ * append another batched slot for the same id (visible as doubled TEXT
256
+ * linetype labels) because {@link hasEntity} is still false.
257
+ */
258
+ _this._claimedConvertObjectIds = new Set();
259
+ /**
260
+ * Per-objectId convert generation. Bumped by {@link updateEntity} so an
261
+ * in-flight progressive {@link batchConvert} / deferred commit for the same
262
+ * id cannot `addEntity` after the scene was cleared for reconversion
263
+ * (ghost RasterImage / pre-replacement pixels).
264
+ */
265
+ _this._entityConvertGeneration = new Map();
250
266
  /**
251
267
  * When true, entity conversion during document open yields cooperatively so
252
268
  * geometry paints incrementally while the open overlay is still visible.
@@ -273,6 +289,13 @@ var AcTrView2d = /** @class */ (function (_super) {
273
289
  _this._progressivePaintCount = 0;
274
290
  /** Cooperative yields taken inside progressive {@link batchConvert}. */
275
291
  _this._progressiveYieldCount = 0;
292
+ /**
293
+ * Saved view was not applied at open. Frame batch bounds once linework
294
+ * convert drains, so the canvas is not blank until deferred glyphs finish.
295
+ * Applied on a timer so it does not run on the entity-parse stack.
296
+ */
297
+ _this._openLineworkFramePending = false;
298
+ _this._openLineworkFrameTimer = null;
276
299
  /**
277
300
  * In-flight + queued glyph/group geometry jobs that await fonts via asyncDraw.
278
301
  * Counted separately so linework convert can continue while text waits.
@@ -292,6 +315,22 @@ var AcTrView2d = /** @class */ (function (_super) {
292
315
  */
293
316
  _this._fontLoadedRedrawTimer = null;
294
317
  _this._fontLoadedRedrawEpoch = 0;
318
+ /**
319
+ * `performance.now()` when convert and deferred glyph jobs last reached
320
+ * idle together. Zero until that first transition. Used to ignore the
321
+ * open-time `fontLoaded` regen that replays the loading spinner.
322
+ */
323
+ _this._entityProcessingIdleAt = 0;
324
+ /**
325
+ * Convert epoch for which text-style font preload was started.
326
+ */
327
+ _this._textStyleFontPreloadEpoch = -1;
328
+ /**
329
+ * In-flight (or resolved) preload of {@link AcDbTextStyleTable.fonts}.
330
+ * Started at conversion stage `STYLE` END so download overlaps LAYER/BLOCK/
331
+ * ENTITY parse and linework convert; glyph jobs await this before draw.
332
+ */
333
+ _this._textStyleFontPreloadPromise = null;
295
334
  /** Transient reading mode forces black linework on a white canvas. */
296
335
  _this._readingMode = new AcApReadingModeState({
297
336
  getCurrentBackgroundColor: function () { return _this._renderer.currentBackgroundColor; },
@@ -784,10 +823,13 @@ var AcTrView2d = /** @class */ (function (_super) {
784
823
  * fully drawable scene (export, scripted zoom) should wait on this (as
785
824
  * {@link waitUntilIdle} / {@link zoomToFitDrawing} do).
786
825
  *
787
- * The open-file progress overlay intentionally uses
788
- * {@link isConvertingEntities} instead so "Rendering drawing ..." can hide
789
- * once linework convert finishes while text geometry continues in the
790
- * deferred pool.
826
+ * The open-file progress overlay uses this when
827
+ * {@link AcApOpenDatabaseOptions.progressiveRendering} is off, so
828
+ * "Rendering drawing ..." stays up until deferred glyph jobs finish.
829
+ * When progressive rendering is on, the overlay uses
830
+ * {@link isConvertingEntities} and may hide while text geometry continues
831
+ * in the deferred pool (pan/zoom enabled). Deprecated `waitForTextGeometry`
832
+ * no longer selects this gate.
791
833
  */
792
834
  get: function () {
793
835
  return this._numOfEntitiesToProcess > 0 || this._pendingGeometryJobs > 0;
@@ -1182,13 +1224,16 @@ var AcTrView2d = /** @class */ (function (_super) {
1182
1224
  threeEntity.visible = entity.visibility !== false;
1183
1225
  if (threeEntity instanceof AcTrGroup &&
1184
1226
  threeEntity.isOnTheSameLayer) {
1185
- // Children authored on layer "0" inherit the INSERT layer for
1186
- // ByLayer traits (color, etc.), same as the primary-document path.
1187
- this._inheritedLayerMaterialMapper.remap(threeEntity.children, '0', threeEntity.layerName);
1227
+ threeEntity.userData.insertLayerName = threeEntity.layerName;
1188
1228
  }
1189
1229
  return [4 /*yield*/, this.finishEntityGeometry(threeEntity, false)];
1190
1230
  case 5:
1191
1231
  _g.sent();
1232
+ if (threeEntity instanceof AcTrGroup &&
1233
+ threeEntity.isOnTheSameLayer) {
1234
+ // Remap after glyph geometry exists — see same-layer commit path.
1235
+ this._inheritedLayerMaterialMapper.remap(threeEntity.children, '0', threeEntity.layerName);
1236
+ }
1192
1237
  layout.addEntity(threeEntity);
1193
1238
  threeEntity.dispose();
1194
1239
  return [3 /*break*/, 7];
@@ -1468,6 +1513,40 @@ var AcTrView2d = /** @class */ (function (_super) {
1468
1513
  this._isDirty = true;
1469
1514
  }
1470
1515
  };
1516
+ /**
1517
+ * When open framing waits on {@link zoomToFitDrawing}, show the linework
1518
+ * bounds as soon as entity convert drains. The final fit still runs after
1519
+ * deferred glyphs so text extents can refine the camera.
1520
+ */
1521
+ AcTrView2d.prototype.requestOpenLineworkFrame = function () {
1522
+ this._openLineworkFramePending = true;
1523
+ this.scheduleOpenLineworkFrame();
1524
+ };
1525
+ AcTrView2d.prototype.cancelOpenLineworkFrame = function () {
1526
+ this._openLineworkFramePending = false;
1527
+ if (this._openLineworkFrameTimer != null) {
1528
+ clearTimeout(this._openLineworkFrameTimer);
1529
+ this._openLineworkFrameTimer = null;
1530
+ }
1531
+ };
1532
+ AcTrView2d.prototype.scheduleOpenLineworkFrame = function () {
1533
+ var _this = this;
1534
+ if (this._openLineworkFrameTimer != null ||
1535
+ !this._openLineworkFramePending) {
1536
+ return;
1537
+ }
1538
+ var delay = this.isConvertingEntities ? 50 : 0;
1539
+ this._openLineworkFrameTimer = setTimeout(function () {
1540
+ _this._openLineworkFrameTimer = null;
1541
+ if (!_this._openLineworkFramePending)
1542
+ return;
1543
+ if (_this.isConvertingEntities) {
1544
+ _this.scheduleOpenLineworkFrame();
1545
+ return;
1546
+ }
1547
+ _this.frameOpenLineworkIfReady();
1548
+ }, delay);
1549
+ };
1471
1550
  /**
1472
1551
  * @inheritdoc
1473
1552
  */
@@ -2031,7 +2110,28 @@ var AcTrView2d = /** @class */ (function (_super) {
2031
2110
  */
2032
2111
  AcTrView2d.prototype.addEntity = function (entity) {
2033
2112
  var _a;
2113
+ var _b;
2034
2114
  var entities = Array.isArray(entity) ? entity : [entity];
2115
+ // Mark each owner layout as loaded as soon as the open-time ENTITY stream
2116
+ // enqueues work. Progressive convert may not have committed scene entities
2117
+ // yet (`entityCount` still 0); without this, `loadLayoutEntitiesIfNeeded`
2118
+ // (from `onAfterOpenDocument` → `setActiveLayout`) re-iterates the BTR,
2119
+ // double-increments `_numOfEntitiesToProcess`, and keeps
2120
+ // "Rendering drawing ..." up long after linework should have finished —
2121
+ // so progressive rendering cannot release pan/zoom on time.
2122
+ for (var i = 0; i < entities.length; i++) {
2123
+ var ownerId = (_b = entities[i]) === null || _b === void 0 ? void 0 : _b.ownerId;
2124
+ if (!ownerId)
2125
+ continue;
2126
+ var layout = this._scene.layouts.get(ownerId);
2127
+ if (!layout) {
2128
+ this._scene.addEmptyLayout(ownerId);
2129
+ layout = this._scene.layouts.get(ownerId);
2130
+ }
2131
+ if (layout && !layout.isLoaded) {
2132
+ layout.isLoaded = true;
2133
+ }
2134
+ }
2035
2135
  this._numOfEntitiesToProcess += entities.length;
2036
2136
  // Always serialize convert through one drain loop. Non-progressive opens
2037
2137
  // used to `void batchConvert(chunk)` per ENTITY flush chunk, which ran
@@ -2102,6 +2202,7 @@ var AcTrView2d = /** @class */ (function (_super) {
2102
2202
  */
2103
2203
  AcTrView2d.prototype.updateEntity = function (entity) {
2104
2204
  var _this = this;
2205
+ var _a, _b;
2105
2206
  var entities = this.resolveSceneUpdateEntities(Array.isArray(entity) ? entity : [entity]);
2106
2207
  if (entities.length === 0)
2107
2208
  return;
@@ -2110,6 +2211,17 @@ var AcTrView2d = /** @class */ (function (_super) {
2110
2211
  .filter(function (objectId) { return _this.selectionSet.has(objectId); });
2111
2212
  for (var i = 0; i < entities.length; ++i) {
2112
2213
  var item = entities[i];
2214
+ var objectId = String((_a = item.objectId) !== null && _a !== void 0 ? _a : '');
2215
+ if (objectId) {
2216
+ // Invalidate in-flight commits for this id before releasing the claim /
2217
+ // clearing the scene, so a stale progressive convert cannot re-add the
2218
+ // pre-update drawable after (or instead of) the reconversion pass.
2219
+ this._entityConvertGeneration.set(objectId, ((_b = this._entityConvertGeneration.get(objectId)) !== null && _b !== void 0 ? _b : 0) + 1);
2220
+ // Allow reconversion: open-time batchConvert claims objectIds to prevent
2221
+ // duplicate progressive slots. updateEntity must release that claim or
2222
+ // batchConvert skips the entity (scene already cleared → blank RasterImage).
2223
+ this._claimedConvertObjectIds.delete(objectId);
2224
+ }
2113
2225
  if (this._scene.hasEntity(item.objectId)) {
2114
2226
  this._scene.removeEntity(item.objectId);
2115
2227
  }
@@ -2129,6 +2241,7 @@ var AcTrView2d = /** @class */ (function (_super) {
2129
2241
  this.highlight(selectedIds);
2130
2242
  }
2131
2243
  this._gripManager.refresh();
2244
+ this._isDirty = true;
2132
2245
  return [2 /*return*/];
2133
2246
  }
2134
2247
  });
@@ -2299,7 +2412,11 @@ var AcTrView2d = /** @class */ (function (_super) {
2299
2412
  this.clearFontLoadedRedrawTimer();
2300
2413
  this._convertQueue.length = 0;
2301
2414
  this._numOfEntitiesToProcess = 0;
2415
+ this._claimedConvertObjectIds.clear();
2416
+ this._entityConvertGeneration.clear();
2417
+ this.cancelOpenLineworkFrame();
2302
2418
  this.resetDeferredGeometryQueue();
2419
+ this._entityProcessingIdleAt = 0;
2303
2420
  this._scene.clear();
2304
2421
  this._isDirty = true;
2305
2422
  this._missedImages.clear();
@@ -2335,9 +2452,13 @@ var AcTrView2d = /** @class */ (function (_super) {
2335
2452
  var _a;
2336
2453
  this._convertEpoch++;
2337
2454
  this.clearFontLoadedRedrawTimer();
2455
+ this.cancelOpenLineworkFrame();
2338
2456
  this._convertQueue.length = 0;
2339
2457
  this._numOfEntitiesToProcess = 0;
2458
+ this._claimedConvertObjectIds.clear();
2459
+ this._entityConvertGeneration.clear();
2340
2460
  this.resetDeferredGeometryQueue();
2461
+ this._entityProcessingIdleAt = 0;
2341
2462
  this._scene = state.scene;
2342
2463
  this._layoutViewManager = state.layoutViewManager;
2343
2464
  this._initializedLayouts = state.initializedLayouts;
@@ -2363,9 +2484,13 @@ var AcTrView2d = /** @class */ (function (_super) {
2363
2484
  var parked = this.captureSessionState();
2364
2485
  this._convertEpoch++;
2365
2486
  this.clearFontLoadedRedrawTimer();
2487
+ this.cancelOpenLineworkFrame();
2366
2488
  this._convertQueue.length = 0;
2367
2489
  this._numOfEntitiesToProcess = 0;
2490
+ this._claimedConvertObjectIds.clear();
2491
+ this._entityConvertGeneration.clear();
2368
2492
  this.resetDeferredGeometryQueue();
2493
+ this._entityProcessingIdleAt = 0;
2369
2494
  this._scene = this.createScene();
2370
2495
  this._layoutViewManager = new AcTrLayoutViewManager();
2371
2496
  this._initializedLayouts = new Set();
@@ -2533,6 +2658,7 @@ var AcTrView2d = /** @class */ (function (_super) {
2533
2658
  (_a = this._disposeCanvasTouchCallout) === null || _a === void 0 ? void 0 : _a.call(this);
2534
2659
  this._disposeCanvasTouchCallout = undefined;
2535
2660
  this.clearFontLoadedRedrawTimer();
2661
+ this.cancelOpenLineworkFrame();
2536
2662
  this.stopAnimationLoop();
2537
2663
  };
2538
2664
  /**
@@ -2637,6 +2763,15 @@ var AcTrView2d = /** @class */ (function (_super) {
2637
2763
  }
2638
2764
  var existingLayout = this._scene.layouts.get(layoutBtrId);
2639
2765
  if (existingLayout && existingLayout.isLoaded) {
2766
+ // Streamed layouts flip `isLoaded` from `addEntity` before scene
2767
+ // commits finish. Still repair the viewport-view race when entities
2768
+ // are already in the scene but AcTrViewportView creation was skipped.
2769
+ var layoutView_1 = this._layoutViewManager.getAt(layoutBtrId);
2770
+ if (existingLayout.entityCount > 0 &&
2771
+ layoutView_1 &&
2772
+ layoutView_1.viewportCount === 0) {
2773
+ this.ensureViewportViews(blockTableRecord, layoutView_1);
2774
+ }
2640
2775
  return;
2641
2776
  }
2642
2777
  if (this._loadingLayouts.has(layoutBtrId)) {
@@ -2680,6 +2815,11 @@ var AcTrView2d = /** @class */ (function (_super) {
2680
2815
  // is only for layouts whose entities were never streamed in
2681
2816
  // (typically non-active paper-space layouts loaded on first user
2682
2817
  // visit).
2818
+ //
2819
+ // Note: `addEntity` now sets `isLoaded` when the open stream
2820
+ // enqueues work, so the common progressive-open race (entityCount
2821
+ // still 0 at `onAfterOpenDocument` while the convert queue is full)
2822
+ // is handled by the `isLoaded` early return above.
2683
2823
  if (existingLayout && existingLayout.entityCount > 0) {
2684
2824
  existingLayout.isLoaded = true;
2685
2825
  return;
@@ -2821,34 +2961,115 @@ var AcTrView2d = /** @class */ (function (_super) {
2821
2961
  AcTrView2d.prototype.drawEntity = function (entity, delay) {
2822
2962
  return entity.worldDraw(this._renderer, delay);
2823
2963
  };
2964
+ /**
2965
+ * Starts loading fonts referenced by the drawing text style table.
2966
+ *
2967
+ * Call when conversion stage `STYLE` ends so the download overlaps later
2968
+ * parse stages and linework convert. Does not block the caller — glyph
2969
+ * finalize awaits {@link awaitTextStyleFontsReady} instead.
2970
+ *
2971
+ * @param database - Database whose text style table has just been filled.
2972
+ */
2973
+ AcTrView2d.prototype.startTextStyleFontPreload = function (database) {
2974
+ var _a;
2975
+ var epoch = this._convertEpoch;
2976
+ if (this._textStyleFontPreloadEpoch === epoch &&
2977
+ this._textStyleFontPreloadPromise) {
2978
+ return;
2979
+ }
2980
+ this._textStyleFontPreloadEpoch = epoch;
2981
+ var names = [];
2982
+ try {
2983
+ names = (_a = database.tables.textStyleTable.fonts) !== null && _a !== void 0 ? _a : [];
2984
+ }
2985
+ catch (_b) {
2986
+ this._textStyleFontPreloadPromise = Promise.resolve();
2987
+ return;
2988
+ }
2989
+ if (names.length === 0) {
2990
+ this._textStyleFontPreloadPromise = Promise.resolve();
2991
+ return;
2992
+ }
2993
+ this._textStyleFontPreloadPromise = FontManager.instance
2994
+ .requestFonts(names)
2995
+ .then(function () { }, function () {
2996
+ // Glyph draw still falls back via FontManager defaults / '?'.
2997
+ });
2998
+ };
2999
+ /**
3000
+ * Waits until {@link startTextStyleFontPreload} has finished (or starts a
3001
+ * fallback preload from this view's bound draw database if STYLE was missed).
3002
+ */
3003
+ AcTrView2d.prototype.awaitTextStyleFontsReady = function () {
3004
+ return __awaiter(this, void 0, void 0, function () {
3005
+ var db;
3006
+ return __generator(this, function (_a) {
3007
+ switch (_a.label) {
3008
+ case 0:
3009
+ if (!this._textStyleFontPreloadPromise) {
3010
+ db = this._renderer.context.database;
3011
+ if (db) {
3012
+ this.startTextStyleFontPreload(db);
3013
+ }
3014
+ else {
3015
+ return [2 /*return*/];
3016
+ }
3017
+ }
3018
+ return [4 /*yield*/, this._textStyleFontPreloadPromise];
3019
+ case 1:
3020
+ _a.sent();
3021
+ return [2 /*return*/];
3022
+ }
3023
+ });
3024
+ });
3025
+ };
2824
3026
  /**
2825
3027
  * Finishes geometry for a converted entity.
2826
3028
  *
2827
- * Glyph entities and block groups use {@link AcTrEntity.asyncDraw} so
2828
- * {@link FontManager.awaitFontsBeforeDraw} can wait for fonts without
2829
- * relying on a full-scene regen. Other entities keep the sync finalize path.
3029
+ * Glyph entities, complex-linetype lines, and block groups use
3030
+ * {@link AcTrEntity.asyncDraw} so {@link FontManager.awaitFontsBeforeDraw}
3031
+ * can wait for fonts without relying on a full-scene regen. Other entities
3032
+ * keep the sync finalize path.
2830
3033
  */
2831
3034
  AcTrView2d.prototype.finishEntityGeometry = function (threeEntity, _progressive) {
2832
3035
  return __awaiter(this, void 0, void 0, function () {
3036
+ var needsGlyphDraw;
2833
3037
  return __generator(this, function (_a) {
2834
3038
  switch (_a.label) {
2835
3039
  case 0:
2836
- if (!(threeEntity instanceof AcTrGroup)) return [3 /*break*/, 2];
3040
+ needsGlyphDraw = (threeEntity instanceof AcTrGroup &&
3041
+ this.groupHasPendingGlyphGeometry(threeEntity)) ||
3042
+ hasPendingComplexLineTypeGlyphs(threeEntity) ||
3043
+ (threeEntity instanceof AcTrGlyphEntity &&
3044
+ !threeEntity.hasDrawableGeometry());
3045
+ if (!needsGlyphDraw) return [3 /*break*/, 2];
3046
+ return [4 /*yield*/, this.awaitTextStyleFontsReady()];
3047
+ case 1:
3048
+ _a.sent();
3049
+ _a.label = 2;
3050
+ case 2:
3051
+ if (!(threeEntity instanceof AcTrGroup)) return [3 /*break*/, 4];
2837
3052
  // Linework-only INSERTs (no empty glyph shells) skip asyncDraw; spatial
2838
3053
  // boxes are refreshed by syncGroupSpatialBoundsForIndexing after commit.
2839
3054
  if (!this.groupHasPendingGlyphGeometry(threeEntity)) {
2840
3055
  return [2 /*return*/];
2841
3056
  }
2842
3057
  return [4 /*yield*/, threeEntity.asyncDraw()];
2843
- case 1:
3058
+ case 3:
2844
3059
  _a.sent();
2845
3060
  return [2 /*return*/];
2846
- case 2:
3061
+ case 4:
3062
+ if (!hasPendingComplexLineTypeGlyphs(threeEntity)) return [3 /*break*/, 6];
3063
+ return [4 /*yield*/, threeEntity.asyncDraw()];
3064
+ case 5:
3065
+ _a.sent();
3066
+ return [2 /*return*/];
3067
+ case 6:
2847
3068
  if (threeEntity.hasDrawableGeometry()) {
2848
3069
  return [2 /*return*/];
2849
3070
  }
2850
3071
  return [4 /*yield*/, threeEntity.asyncDraw()];
2851
- case 3:
3072
+ case 7:
2852
3073
  _a.sent();
2853
3074
  return [2 /*return*/];
2854
3075
  }
@@ -2864,7 +3085,7 @@ var AcTrView2d = /** @class */ (function (_super) {
2864
3085
  if (threeEntity instanceof AcTrGroup) {
2865
3086
  return this.groupHasPendingGlyphGeometry(threeEntity);
2866
3087
  }
2867
- return false;
3088
+ return hasPendingComplexLineTypeGlyphs(threeEntity);
2868
3089
  };
2869
3090
  AcTrView2d.prototype.clearFontLoadedRedrawTimer = function () {
2870
3091
  if (this._fontLoadedRedrawTimer != null) {
@@ -2892,12 +3113,15 @@ var AcTrView2d = /** @class */ (function (_super) {
2892
3113
  *
2893
3114
  * Live {@link AcTrGlyphEntity} shells (rare: mid-convert / not yet batched)
2894
3115
  * are re-drawn in place. Committed text is already flattened into batches and
2895
- * disposed, so on-demand faces (style `malgun` → Noto Sans KR) need a database
2896
- * regen once conversion is idle — otherwise Hangul stays baked as '?'.
3116
+ * disposed. A database regen does not rebuild those batches — `entityAppended`
3117
+ * skips ids the view already has — but it does replay open-file CONVERSION
3118
+ * progress. That replay is skipped while the open has just gone idle
3119
+ * ({@link shouldRegenDatabaseAfterFontLoad}); a face that arrives later can
3120
+ * still regen.
2897
3121
  */
2898
3122
  AcTrView2d.prototype.redrawGlyphEntitiesAfterFontLoad = function (epoch, fontName) {
2899
3123
  return __awaiter(this, void 0, void 0, function () {
2900
- var redrawEpoch, glyphs, _a, _b, layout, glyphs_1, glyphs_1_1, glyph, e_12_1, defaults, normalized_1, db;
3124
+ var redrawEpoch, glyphs, _a, _b, layout, glyphs_1, glyphs_1_1, glyph, e_12_1, defaults, normalized_1, msSinceIdle, db;
2901
3125
  var e_13, _c, e_12, _d;
2902
3126
  var _e, _f;
2903
3127
  return __generator(this, function (_g) {
@@ -2964,8 +3188,10 @@ var AcTrView2d = /** @class */ (function (_super) {
2964
3188
  }
2965
3189
  return [2 /*return*/];
2966
3190
  }
2967
- // Default preset loads during open are covered by awaitFontsBeforeDraw;
2968
- // regenerating for those would thrash after every open.
3191
+ // Default/symbol preset faces are requested in the background under lazy
3192
+ // loading (mtext-renderer awaitFontsBeforeDraw only waits on content/style
3193
+ // fonts). Regenerating for every default face load would thrash after open;
3194
+ // style fonts that finish late still take the regen path below.
2969
3195
  if (fontName) {
2970
3196
  defaults = FontManager.instance.defaultFonts;
2971
3197
  normalized_1 = fontName.toLowerCase();
@@ -2976,8 +3202,16 @@ var AcTrView2d = /** @class */ (function (_super) {
2976
3202
  }
2977
3203
  }
2978
3204
  if (epoch !== this._convertEpoch ||
2979
- redrawEpoch !== this._fontLoadedRedrawEpoch ||
2980
- this.isProcessingEntities) {
3205
+ redrawEpoch !== this._fontLoadedRedrawEpoch) {
3206
+ return [2 /*return*/];
3207
+ }
3208
+ msSinceIdle = this._entityProcessingIdleAt > 0
3209
+ ? performance.now() - this._entityProcessingIdleAt
3210
+ : null;
3211
+ // Open-time text awaits its fonts, then this debounced callback runs.
3212
+ // Regen here only flashes the loading spinner again after
3213
+ // "Rendering drawing ..." has already hidden (progressive rendering on).
3214
+ if (!shouldRegenDatabaseAfterFontLoad(this.isProcessingEntities, 0, msSinceIdle)) {
2981
3215
  return [2 /*return*/];
2982
3216
  }
2983
3217
  db = (_f = (_e = AcApDocManager.instance) === null || _e === void 0 ? void 0 : _e.curDocument) === null || _f === void 0 ? void 0 : _f.database;
@@ -3047,13 +3281,13 @@ var AcTrView2d = /** @class */ (function (_super) {
3047
3281
  if (_this._pendingGeometryJobs === 0) {
3048
3282
  _this._isDirty = true;
3049
3283
  }
3284
+ _this.stampEntityProcessingIdle();
3050
3285
  }
3051
3286
  _this.pumpDeferredGeometryQueue();
3052
3287
  });
3053
3288
  };
3054
3289
  var this_2 = this;
3055
- while (this._deferredGeometryActive <
3056
- AcTrView2d.DEFERRED_GEOMETRY_CONCURRENCY &&
3290
+ while (this._deferredGeometryActive < AcTrView2d.DEFERRED_GEOMETRY_CONCURRENCY &&
3057
3291
  this._deferredGeometryQueue.length > 0) {
3058
3292
  _loop_3();
3059
3293
  }
@@ -3065,6 +3299,8 @@ var AcTrView2d = /** @class */ (function (_super) {
3065
3299
  // slots until their `finally` runs. Zeroing here lets pump over-schedule
3066
3300
  // when those completions decrement the counter afterward.
3067
3301
  this._pendingGeometryJobs = 0;
3302
+ this._textStyleFontPreloadPromise = null;
3303
+ this._textStyleFontPreloadEpoch = -1;
3068
3304
  };
3069
3305
  /**
3070
3306
  * Waits until side-pool glyph/group jobs for the current convert epoch finish.
@@ -3136,13 +3372,23 @@ var AcTrView2d = /** @class */ (function (_super) {
3136
3372
  */
3137
3373
  AcTrView2d.prototype.batchConvert = function (entities_3) {
3138
3374
  return __awaiter(this, arguments, void 0, function (entities, options) {
3139
- var epoch, progressive, yieldGate, yieldToEventLoop, _loop_4, this_3, i;
3375
+ var epoch, db, progressive, yieldGate, yieldToEventLoop, _loop_4, this_3, i;
3140
3376
  var _this = this;
3377
+ var _a, _b, _c;
3141
3378
  if (options === void 0) { options = {}; }
3142
- return __generator(this, function (_a) {
3143
- switch (_a.label) {
3379
+ return __generator(this, function (_d) {
3380
+ switch (_d.label) {
3144
3381
  case 0:
3145
3382
  epoch = this._convertEpoch;
3383
+ // Fallback: if STYLE-stage preload never started (e.g. non-conversion open
3384
+ // paths), kick it off without blocking linework convert. Glyph finalize
3385
+ // awaits the promise via {@link awaitTextStyleFontsReady}.
3386
+ if (!options.forExport && !this._textStyleFontPreloadPromise) {
3387
+ db = this._renderer.context.database;
3388
+ if (db) {
3389
+ this.startTextStyleFontPreload(db);
3390
+ }
3391
+ }
3146
3392
  progressive = this._progressiveRendering && !options.forExport;
3147
3393
  yieldGate = options.forExport
3148
3394
  ? undefined
@@ -3151,14 +3397,14 @@ var AcTrView2d = /** @class */ (function (_super) {
3151
3397
  return new Promise(function (resolve) { return setTimeout(resolve, 0); });
3152
3398
  };
3153
3399
  _loop_4 = function (i) {
3154
- var entity, shouldConvert, directMeta, added, threeEntity_1, isMultiLayerGroup, deferGeometry, isExtendBbox_1, commitEntity, layoutView, viewportView, fileName, error_2, didYield;
3155
- return __generator(this, function (_b) {
3156
- switch (_b.label) {
3400
+ var entity, shouldConvert, objectId_1, convertGen_1, directMeta, added, superseded, threeEntity_1, isMultiLayerGroup, deferGeometry, isExtendBbox_1, commitEntity, layoutView, viewportView, fileName, error_2, didYield;
3401
+ return __generator(this, function (_e) {
3402
+ switch (_e.label) {
3157
3403
  case 0:
3158
3404
  entity = entities[i];
3159
- _b.label = 1;
3405
+ _e.label = 1;
3160
3406
  case 1:
3161
- _b.trys.push([1, 9, 10, 11]);
3407
+ _e.trys.push([1, 9, 10, 11]);
3162
3408
  // Document was cleared / replaced while this batch was draining.
3163
3409
  if (epoch !== this_3._convertEpoch) {
3164
3410
  return [2 /*return*/, "continue"];
@@ -3184,23 +3430,43 @@ var AcTrView2d = /** @class */ (function (_super) {
3184
3430
  if (!shouldConvert) {
3185
3431
  return [2 /*return*/, "continue"];
3186
3432
  }
3433
+ objectId_1 = String((_a = entity.objectId) !== null && _a !== void 0 ? _a : '');
3434
+ convertGen_1 = 0;
3435
+ if (!options.forExport) {
3436
+ if (objectId_1 &&
3437
+ (this_3.hasEntity(objectId_1) ||
3438
+ this_3._claimedConvertObjectIds.has(objectId_1))) {
3439
+ return [2 /*return*/, "continue"];
3440
+ }
3441
+ if (objectId_1) {
3442
+ this_3._claimedConvertObjectIds.add(objectId_1);
3443
+ convertGen_1 = (_b = this_3._entityConvertGeneration.get(objectId_1)) !== null && _b !== void 0 ? _b : 0;
3444
+ }
3445
+ }
3187
3446
  directMeta = tryBuildDirectEntityMeta(entity, this_3._renderer);
3188
3447
  if (directMeta) {
3189
3448
  added = false;
3449
+ superseded = false;
3190
3450
  try {
3191
- added = this_3._scene.addDirectEntity(directMeta, shouldExtendBboxForDirectEntity(entity));
3192
- if (added) {
3193
- this_3.applySessionHiddenObjectState(entity.objectId);
3194
- if (progressive) {
3195
- this_3.markProgressiveDirty();
3196
- this_3._progressiveOpenFit.afterGeometryBatch(function () { return _this.resolveLayoutFitBox(); }, i);
3451
+ if (objectId_1 &&
3452
+ ((_c = this_3._entityConvertGeneration.get(objectId_1)) !== null && _c !== void 0 ? _c : 0) !== convertGen_1) {
3453
+ superseded = true;
3454
+ }
3455
+ else {
3456
+ added = this_3._scene.addDirectEntity(directMeta, shouldExtendBboxForDirectEntity(entity));
3457
+ if (added) {
3458
+ this_3.applySessionHiddenObjectState(entity.objectId);
3459
+ if (progressive) {
3460
+ this_3.markProgressiveDirty();
3461
+ this_3._progressiveOpenFit.afterGeometryBatch(function () { return _this.resolveLayoutFitBox(); }, i);
3462
+ }
3197
3463
  }
3198
3464
  }
3199
3465
  }
3200
3466
  finally {
3201
3467
  directMeta.geometry.dispose();
3202
3468
  }
3203
- if (added) {
3469
+ if (superseded || added) {
3204
3470
  return [2 /*return*/, "continue"];
3205
3471
  }
3206
3472
  // Append refused (e.g. invisible) — fall through to the legacy path.
@@ -3217,9 +3483,10 @@ var AcTrView2d = /** @class */ (function (_super) {
3217
3483
  threeEntity_1.visible = entity.visibility !== false;
3218
3484
  if (threeEntity_1 instanceof AcTrGroup &&
3219
3485
  threeEntity_1.isOnTheSameLayer) {
3220
- // Even when a block expands to a single layer bucket, children authored on
3221
- // layer "0" still inherit the INSERT layer for ByLayer traits (color, etc.).
3222
- this_3._inheritedLayerMaterialMapper.remap(threeEntity_1.children, '0', threeEntity_1.layerName);
3486
+ // Layer-0 inheritance must run AFTER finishEntityGeometry so TEXT/
3487
+ // MTEXT glyph materials exist. Remapping earlier (before asyncDraw)
3488
+ // leaves GM/GB-style labels as ACI-7 white while the block frame
3489
+ // remaps correctly (GAS-Meter / GAS-Box, A517B / A517E).
3223
3490
  threeEntity_1.userData.insertLayerName = threeEntity_1.layerName;
3224
3491
  }
3225
3492
  isMultiLayerGroup = threeEntity_1 instanceof AcTrGroup &&
@@ -3228,29 +3495,39 @@ var AcTrView2d = /** @class */ (function (_super) {
3228
3495
  if (!isMultiLayerGroup) return [3 /*break*/, 5];
3229
3496
  if (!deferGeometry) return [3 /*break*/, 2];
3230
3497
  this_3.enqueueDeferredGeometry(function () {
3231
- return _this.handleGroup(threeEntity_1, progressive, epoch);
3498
+ return _this.handleGroup(threeEntity_1, progressive, epoch, convertGen_1);
3232
3499
  }, epoch);
3233
3500
  return [3 /*break*/, 4];
3234
- case 2: return [4 /*yield*/, this_3.handleGroup(threeEntity_1, progressive, epoch)];
3501
+ case 2: return [4 /*yield*/, this_3.handleGroup(threeEntity_1, progressive, epoch, convertGen_1)];
3235
3502
  case 3:
3236
- _b.sent();
3237
- _b.label = 4;
3503
+ _e.sent();
3504
+ _e.label = 4;
3238
3505
  case 4: return [3 /*break*/, 8];
3239
3506
  case 5:
3240
3507
  isExtendBbox_1 = !(entity instanceof AcDbRay || entity instanceof AcDbXline);
3241
3508
  commitEntity = function () { return __awaiter(_this, void 0, void 0, function () {
3242
3509
  var _this = this;
3243
- return __generator(this, function (_a) {
3244
- switch (_a.label) {
3510
+ var _a;
3511
+ return __generator(this, function (_b) {
3512
+ switch (_b.label) {
3245
3513
  case 0: return [4 /*yield*/, this.finishEntityGeometry(threeEntity_1, progressive)];
3246
3514
  case 1:
3247
- _a.sent();
3515
+ _b.sent();
3248
3516
  if (epoch !== this._convertEpoch) {
3249
3517
  threeEntity_1.dispose();
3250
3518
  return [2 /*return*/];
3251
3519
  }
3520
+ if (objectId_1 &&
3521
+ ((_a = this._entityConvertGeneration.get(objectId_1)) !== null && _a !== void 0 ? _a : 0) !==
3522
+ convertGen_1) {
3523
+ threeEntity_1.dispose();
3524
+ return [2 /*return*/];
3525
+ }
3252
3526
  if (threeEntity_1 instanceof AcTrGroup) {
3253
3527
  this.syncGroupSpatialBoundsForIndexing(threeEntity_1);
3528
+ if (threeEntity_1.isOnTheSameLayer) {
3529
+ this._inheritedLayerMaterialMapper.remap(threeEntity_1.children, '0', threeEntity_1.layerName);
3530
+ }
3254
3531
  }
3255
3532
  this._scene.addEntity(threeEntity_1, isExtendBbox_1);
3256
3533
  this.applySessionHiddenObjectState(entity.objectId);
@@ -3269,8 +3546,8 @@ var AcTrView2d = /** @class */ (function (_super) {
3269
3546
  return [3 /*break*/, 8];
3270
3547
  case 6: return [4 /*yield*/, commitEntity()];
3271
3548
  case 7:
3272
- _b.sent();
3273
- _b.label = 8;
3549
+ _e.sent();
3550
+ _e.label = 8;
3274
3551
  case 8:
3275
3552
  if (epoch !== this_3._convertEpoch) {
3276
3553
  return [2 /*return*/, "continue"];
@@ -3294,10 +3571,13 @@ var AcTrView2d = /** @class */ (function (_super) {
3294
3571
  if (fileName && !entity.image) {
3295
3572
  this_3._missedImages.set(entity.objectId, fileName);
3296
3573
  }
3574
+ else if (entity.image) {
3575
+ this_3._missedImages.delete(entity.objectId);
3576
+ }
3297
3577
  }
3298
3578
  return [3 /*break*/, 11];
3299
3579
  case 9:
3300
- error_2 = _b.sent();
3580
+ error_2 = _e.sent();
3301
3581
  log.error("[AcTrView2d] Failed to convert entity ".concat(entity.objectId, " (").concat(entity.type, "):"), error_2);
3302
3582
  return [3 /*break*/, 11];
3303
3583
  case 10:
@@ -3314,24 +3594,24 @@ var AcTrView2d = /** @class */ (function (_super) {
3314
3594
  if (!yieldGate) return [3 /*break*/, 13];
3315
3595
  return [4 /*yield*/, yieldGate.maybeYield(yieldToEventLoop)];
3316
3596
  case 12:
3317
- didYield = _b.sent();
3597
+ didYield = _e.sent();
3318
3598
  if (didYield) {
3319
3599
  this_3._progressiveYieldCount++;
3320
3600
  }
3321
- _b.label = 13;
3601
+ _e.label = 13;
3322
3602
  case 13: return [2 /*return*/];
3323
3603
  }
3324
3604
  });
3325
3605
  };
3326
3606
  this_3 = this;
3327
3607
  i = 0;
3328
- _a.label = 1;
3608
+ _d.label = 1;
3329
3609
  case 1:
3330
3610
  if (!(i < entities.length)) return [3 /*break*/, 4];
3331
3611
  return [5 /*yield**/, _loop_4(i)];
3332
3612
  case 2:
3333
- _a.sent();
3334
- _a.label = 3;
3613
+ _d.sent();
3614
+ _d.label = 3;
3335
3615
  case 3:
3336
3616
  ++i;
3337
3617
  return [3 /*break*/, 1];
@@ -3361,20 +3641,28 @@ var AcTrView2d = /** @class */ (function (_super) {
3361
3641
  userData.spatialIndexChildBoxes = childBoxes;
3362
3642
  };
3363
3643
  AcTrView2d.prototype.handleGroup = function (group_1, progressive_1) {
3364
- return __awaiter(this, arguments, void 0, function (group, progressive, epoch) {
3365
- var children, objectsGroupByLayer, children_1, children_1_1, child, renderContext, groupObjectId, groupLayerName, groupChildBoxes, aggregateSpatialBbox;
3644
+ return __awaiter(this, arguments, void 0, function (group, progressive, epoch, convertGen) {
3645
+ var objectId, children, objectsGroupByLayer, children_1, children_1_1, child, renderContext, groupObjectId, groupLayerName, groupChildBoxes, aggregateSpatialBbox;
3366
3646
  var e_15, _a;
3367
3647
  var _this = this;
3648
+ var _b, _c;
3368
3649
  if (epoch === void 0) { epoch = this._convertEpoch; }
3369
- return __generator(this, function (_b) {
3370
- switch (_b.label) {
3650
+ if (convertGen === void 0) { convertGen = 0; }
3651
+ return __generator(this, function (_d) {
3652
+ switch (_d.label) {
3371
3653
  case 0: return [4 /*yield*/, this.finishEntityGeometry(group, progressive)];
3372
3654
  case 1:
3373
- _b.sent();
3655
+ _d.sent();
3374
3656
  if (epoch !== this._convertEpoch) {
3375
3657
  group.dispose();
3376
3658
  return [2 /*return*/];
3377
3659
  }
3660
+ objectId = String((_b = group.objectId) !== null && _b !== void 0 ? _b : '');
3661
+ if (objectId &&
3662
+ ((_c = this._entityConvertGeneration.get(objectId)) !== null && _c !== void 0 ? _c : 0) !== convertGen) {
3663
+ group.dispose();
3664
+ return [2 /*return*/];
3665
+ }
3378
3666
  this.syncGroupSpatialBoundsForIndexing(group);
3379
3667
  children = group.children;
3380
3668
  objectsGroupByLayer = new Map();
@@ -3506,6 +3794,36 @@ var AcTrView2d = /** @class */ (function (_super) {
3506
3794
  // the user pans/zooms (animate bails when !_isDirty && !_htmlDirty &&
3507
3795
  // !stillLoading).
3508
3796
  this._isDirty = true;
3797
+ // Missed images (and fonts already tracked) are collected during convert;
3798
+ // notify the Resources / External References palette once the queue is idle.
3799
+ eventBus.emit('missed-data-changed', {});
3800
+ }
3801
+ this.stampEntityProcessingIdle();
3802
+ };
3803
+ /**
3804
+ * Zooms to batch geometry once, after entity convert has drained.
3805
+ */
3806
+ AcTrView2d.prototype.frameOpenLineworkIfReady = function () {
3807
+ if (!this._openLineworkFramePending || this.isConvertingEntities) {
3808
+ return;
3809
+ }
3810
+ var box = this.resolveLayoutFitBox();
3811
+ if (!box || box.isEmpty()) {
3812
+ this._openLineworkFramePending = false;
3813
+ return;
3814
+ }
3815
+ this._openLineworkFramePending = false;
3816
+ // Programmatic so a progressive open does not treat this as a user zoom
3817
+ // and skip the glyph-aware final fit.
3818
+ this._progressiveOpenFit.frameProgrammatically(box);
3819
+ };
3820
+ /**
3821
+ * Records the moment convert and deferred glyph jobs are both idle.
3822
+ * Intermediate completions (linework done, text still queued) do not stamp.
3823
+ */
3824
+ AcTrView2d.prototype.stampEntityProcessingIdle = function () {
3825
+ if (this._numOfEntitiesToProcess === 0 && this._pendingGeometryJobs === 0) {
3826
+ this._entityProcessingIdleAt = performance.now();
3509
3827
  }
3510
3828
  };
3511
3829
  /**