@cadview/core 0.2.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -2691,7 +2691,7 @@ function drawMText(ctx, entity, pixelSize) {
2691
2691
 
2692
2692
  // src/renderer/entities/draw-insert.ts
2693
2693
  var MAX_INSERT_DEPTH = 100;
2694
- function drawInsert(ctx, entity, doc, vt, theme, pixelSize, depth = 0) {
2694
+ function drawInsert(ctx, entity, doc, vt, theme, pixelSize, depth = 0, stats) {
2695
2695
  if (depth > MAX_INSERT_DEPTH) return;
2696
2696
  const block = doc.blocks.get(entity.blockName);
2697
2697
  if (!block) return;
@@ -2716,9 +2716,9 @@ function drawInsert(ctx, entity, doc, vt, theme, pixelSize, depth = 0) {
2716
2716
  ctx.fillStyle = color;
2717
2717
  ctx.lineWidth = adjustedPixelSize;
2718
2718
  if (blockEntity.type === "INSERT") {
2719
- drawInsert(ctx, blockEntity, doc, vt, theme, adjustedPixelSize, depth + 1);
2719
+ drawInsert(ctx, blockEntity, doc, vt, theme, adjustedPixelSize, depth + 1, stats);
2720
2720
  } else {
2721
- drawEntity(ctx, blockEntity, doc, vt, theme, adjustedPixelSize);
2721
+ drawEntity(ctx, blockEntity, doc, vt, theme, adjustedPixelSize, stats);
2722
2722
  }
2723
2723
  }
2724
2724
  ctx.restore();
@@ -2727,7 +2727,7 @@ function drawInsert(ctx, entity, doc, vt, theme, pixelSize, depth = 0) {
2727
2727
  }
2728
2728
 
2729
2729
  // src/renderer/entities/draw-dimension.ts
2730
- function drawDimension(ctx, entity, doc, vt, theme, pixelSize) {
2730
+ function drawDimension(ctx, entity, doc, vt, theme, pixelSize, stats) {
2731
2731
  if (entity.blockName) {
2732
2732
  const block = doc.blocks.get(entity.blockName);
2733
2733
  if (block) {
@@ -2736,7 +2736,7 @@ function drawDimension(ctx, entity, doc, vt, theme, pixelSize) {
2736
2736
  ctx.strokeStyle = color;
2737
2737
  ctx.fillStyle = color;
2738
2738
  ctx.lineWidth = pixelSize;
2739
- drawEntity(ctx, blockEntity, doc, vt, theme, pixelSize);
2739
+ drawEntity(ctx, blockEntity, doc, vt, theme, pixelSize, stats);
2740
2740
  }
2741
2741
  return;
2742
2742
  }
@@ -2813,7 +2813,11 @@ function drawPoint(ctx, entity, pixelSize) {
2813
2813
  }
2814
2814
 
2815
2815
  // src/renderer/entities/draw-entity.ts
2816
- function drawEntity(ctx, entity, doc, vt, theme, pixelSize) {
2816
+ function drawEntity(ctx, entity, doc, vt, theme, pixelSize, stats) {
2817
+ if (stats) {
2818
+ stats.drawCalls++;
2819
+ stats.byType[entity.type] = (stats.byType[entity.type] ?? 0) + 1;
2820
+ }
2817
2821
  switch (entity.type) {
2818
2822
  case "LINE":
2819
2823
  drawLine(ctx, entity);
@@ -2843,10 +2847,10 @@ function drawEntity(ctx, entity, doc, vt, theme, pixelSize) {
2843
2847
  drawMText(ctx, entity, pixelSize);
2844
2848
  break;
2845
2849
  case "INSERT":
2846
- drawInsert(ctx, entity, doc, vt, theme, pixelSize);
2850
+ drawInsert(ctx, entity, doc, vt, theme, pixelSize, 0, stats);
2847
2851
  break;
2848
2852
  case "DIMENSION":
2849
- drawDimension(ctx, entity, doc, vt, theme, pixelSize);
2853
+ drawDimension(ctx, entity, doc, vt, theme, pixelSize, stats);
2850
2854
  break;
2851
2855
  case "HATCH":
2852
2856
  drawHatch(ctx, entity);
@@ -2893,6 +2897,12 @@ var CanvasRenderer = class {
2893
2897
  render(doc, vt, theme, visibleLayers, selectedEntityIndex) {
2894
2898
  const ctx = this.ctx;
2895
2899
  const dpr = window.devicePixelRatio || 1;
2900
+ const stats = {
2901
+ entitiesDrawn: 0,
2902
+ entitiesSkipped: 0,
2903
+ drawCalls: 0,
2904
+ byType: {}
2905
+ };
2896
2906
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
2897
2907
  ctx.fillStyle = THEMES[theme].backgroundColor;
2898
2908
  ctx.fillRect(0, 0, this.width, this.height);
@@ -2902,13 +2912,20 @@ var CanvasRenderer = class {
2902
2912
  ctx.lineJoin = "round";
2903
2913
  for (let i = 0; i < doc.entities.length; i++) {
2904
2914
  const entity = doc.entities[i];
2905
- if (!entity.visible) continue;
2906
- if (!visibleLayers.has(entity.layer)) continue;
2915
+ if (!entity.visible) {
2916
+ stats.entitiesSkipped++;
2917
+ continue;
2918
+ }
2919
+ if (!visibleLayers.has(entity.layer)) {
2920
+ stats.entitiesSkipped++;
2921
+ continue;
2922
+ }
2907
2923
  const color = resolveEntityColor(entity, doc.layers, theme);
2908
2924
  ctx.strokeStyle = color;
2909
2925
  ctx.fillStyle = color;
2910
2926
  ctx.lineWidth = pixelSize;
2911
- drawEntity(ctx, entity, doc, vt, theme, pixelSize);
2927
+ stats.entitiesDrawn++;
2928
+ drawEntity(ctx, entity, doc, vt, theme, pixelSize, stats);
2912
2929
  }
2913
2930
  if (selectedEntityIndex >= 0 && selectedEntityIndex < doc.entities.length) {
2914
2931
  const selEntity = doc.entities[selectedEntityIndex];
@@ -2918,6 +2935,7 @@ var CanvasRenderer = class {
2918
2935
  ctx.lineWidth = pixelSize * 3;
2919
2936
  drawEntity(ctx, selEntity, doc, vt, theme, pixelSize);
2920
2937
  }
2938
+ return stats;
2921
2939
  }
2922
2940
  renderEmpty(theme) {
2923
2941
  const ctx = this.ctx;
@@ -2930,6 +2948,136 @@ var CanvasRenderer = class {
2930
2948
  }
2931
2949
  };
2932
2950
 
2951
+ // src/renderer/debug-overlay.ts
2952
+ var DEFAULT_DEBUG_OPTIONS = {
2953
+ showFps: true,
2954
+ showRenderStats: true,
2955
+ showDocumentInfo: true,
2956
+ showTimings: true,
2957
+ showCamera: true,
2958
+ position: "top-left"
2959
+ };
2960
+ function resolveDebugOptions(input) {
2961
+ return { ...DEFAULT_DEBUG_OPTIONS, ...input };
2962
+ }
2963
+ function formatBytes(bytes) {
2964
+ if (bytes === 0) return "0 B";
2965
+ if (bytes < 1024) return `${bytes} B`;
2966
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2967
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2968
+ }
2969
+ function formatZoom(scale) {
2970
+ if (scale >= 1) return `${scale.toFixed(2)}x`;
2971
+ return `1:${(1 / scale).toFixed(1)}`;
2972
+ }
2973
+ var FONT = "11px monospace";
2974
+ var LINE_HEIGHT = 15;
2975
+ var SEPARATOR_HEIGHT = 8;
2976
+ var PADDING = 8;
2977
+ var MARGIN = 10;
2978
+ function renderDebugOverlay(ctx, stats, theme, options, canvasWidth, canvasHeight) {
2979
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
2980
+ const sections = [];
2981
+ if (options.showFps) {
2982
+ sections.push([
2983
+ `FPS: ${stats.fps} Frame: ${stats.frameTime.toFixed(1)}ms`
2984
+ ]);
2985
+ }
2986
+ if (options.showRenderStats) {
2987
+ const total = stats.renderStats.entitiesDrawn + stats.renderStats.entitiesSkipped;
2988
+ const lines = [
2989
+ `Drawn: ${stats.renderStats.entitiesDrawn} / ${total} Calls: ${stats.renderStats.drawCalls}`
2990
+ ];
2991
+ const types = Object.entries(stats.renderStats.byType).sort(([, a], [, b]) => b - a).slice(0, 6).map(([type, count]) => `${type}: ${count}`).join(" ");
2992
+ if (types) lines.push(types);
2993
+ sections.push(lines);
2994
+ }
2995
+ if (options.showDocumentInfo) {
2996
+ const lines = [
2997
+ `Layers: ${stats.visibleLayerCount} / ${stats.layerCount} Blocks: ${stats.blockCount}`
2998
+ ];
2999
+ if (stats.dxfVersion) lines.push(`DXF: ${stats.dxfVersion}`);
3000
+ if (stats.fileName) lines.push(`File: ${stats.fileName}`);
3001
+ if (stats.fileSize > 0) lines.push(`Size: ${formatBytes(stats.fileSize)}`);
3002
+ sections.push(lines);
3003
+ }
3004
+ if (options.showTimings) {
3005
+ const parts = [];
3006
+ if (stats.parseTime > 0) parts.push(`Parse: ${stats.parseTime.toFixed(0)}ms`);
3007
+ if (stats.spatialIndexBuildTime > 0) parts.push(`Index: ${stats.spatialIndexBuildTime.toFixed(0)}ms`);
3008
+ if (stats.totalLoadTime > 0) parts.push(`Load: ${stats.totalLoadTime.toFixed(0)}ms`);
3009
+ if (parts.length > 0) {
3010
+ sections.push([parts.join(" ")]);
3011
+ }
3012
+ }
3013
+ if (options.showCamera) {
3014
+ const b = stats.viewportBounds;
3015
+ sections.push([
3016
+ `Zoom: ${formatZoom(stats.zoom)} Pixel: ${stats.pixelSize.toFixed(2)}`,
3017
+ `View: [${b.minX.toFixed(0)}, ${b.minY.toFixed(0)}] \u2192 [${b.maxX.toFixed(0)}, ${b.maxY.toFixed(0)}]`
3018
+ ]);
3019
+ }
3020
+ if (sections.length === 0) return;
3021
+ ctx.font = FONT;
3022
+ const rows = [];
3023
+ for (let s = 0; s < sections.length; s++) {
3024
+ if (s > 0) rows.push({ text: "", isSeparator: true });
3025
+ for (const line of sections[s]) {
3026
+ rows.push({ text: line, isSeparator: false });
3027
+ }
3028
+ }
3029
+ let maxWidth = 0;
3030
+ for (const row of rows) {
3031
+ if (!row.isSeparator) {
3032
+ const w = ctx.measureText(row.text).width;
3033
+ if (w > maxWidth) maxWidth = w;
3034
+ }
3035
+ }
3036
+ const panelWidth = maxWidth + PADDING * 2;
3037
+ let panelHeight = PADDING * 2;
3038
+ for (const row of rows) {
3039
+ panelHeight += row.isSeparator ? SEPARATOR_HEIGHT : LINE_HEIGHT;
3040
+ }
3041
+ let x;
3042
+ let y;
3043
+ switch (options.position) {
3044
+ case "top-left":
3045
+ x = MARGIN;
3046
+ y = MARGIN;
3047
+ break;
3048
+ case "top-right":
3049
+ x = canvasWidth - panelWidth - MARGIN;
3050
+ y = MARGIN;
3051
+ break;
3052
+ case "bottom-left":
3053
+ x = MARGIN;
3054
+ y = canvasHeight - panelHeight - MARGIN;
3055
+ break;
3056
+ case "bottom-right":
3057
+ x = canvasWidth - panelWidth - MARGIN;
3058
+ y = canvasHeight - panelHeight - MARGIN;
3059
+ break;
3060
+ }
3061
+ const config = THEMES[theme];
3062
+ ctx.fillStyle = theme === "dark" ? "rgba(0, 0, 0, 0.75)" : "rgba(255, 255, 255, 0.85)";
3063
+ ctx.fillRect(x, y, panelWidth, panelHeight);
3064
+ ctx.strokeStyle = theme === "dark" ? "rgba(255, 255, 255, 0.15)" : "rgba(0, 0, 0, 0.15)";
3065
+ ctx.lineWidth = 1;
3066
+ ctx.strokeRect(x + 0.5, y + 0.5, panelWidth - 1, panelHeight - 1);
3067
+ ctx.fillStyle = theme === "dark" ? config.defaultEntityColor : "rgba(0, 0, 0, 0.85)";
3068
+ ctx.textAlign = "left";
3069
+ ctx.textBaseline = "top";
3070
+ let cursorY = y + PADDING;
3071
+ for (const row of rows) {
3072
+ if (row.isSeparator) {
3073
+ cursorY += SEPARATOR_HEIGHT;
3074
+ } else {
3075
+ ctx.fillText(row.text, x + PADDING, cursorY);
3076
+ cursorY += LINE_HEIGHT;
3077
+ }
3078
+ }
3079
+ }
3080
+
2933
3081
  // src/viewer/layers.ts
2934
3082
  var LayerManager = class {
2935
3083
  layers = /* @__PURE__ */ new Map();
@@ -3720,6 +3868,19 @@ var CadViewer = class {
3720
3868
  loadGeneration = 0;
3721
3869
  mouseScreenX = 0;
3722
3870
  mouseScreenY = 0;
3871
+ // Debug mode state
3872
+ debugEnabled = false;
3873
+ debugOptions;
3874
+ lastRenderStats = null;
3875
+ lastDebugStats = null;
3876
+ frameTimestamps = [];
3877
+ lastDoRenderTime = 0;
3878
+ lastFrameTime = 0;
3879
+ parseTime = 0;
3880
+ spatialIndexBuildTime = 0;
3881
+ loadedFileName = null;
3882
+ loadedFileSize = 0;
3883
+ debugRafId = 0;
3723
3884
  constructor(canvas, options) {
3724
3885
  this.canvas = canvas;
3725
3886
  this.options = {
@@ -3732,6 +3893,14 @@ var CadViewer = class {
3732
3893
  initialTool: options?.initialTool ?? "pan"
3733
3894
  };
3734
3895
  this.formatConverters = options?.formatConverters ?? [];
3896
+ if (options?.debug) {
3897
+ this.debugEnabled = true;
3898
+ this.debugOptions = resolveDebugOptions(
3899
+ typeof options.debug === "boolean" ? void 0 : options.debug
3900
+ );
3901
+ } else {
3902
+ this.debugOptions = resolveDebugOptions();
3903
+ }
3735
3904
  this.renderer = new CanvasRenderer(canvas);
3736
3905
  this.camera = new Camera(this.options);
3737
3906
  this.layerManager = new LayerManager();
@@ -3744,6 +3913,9 @@ var CadViewer = class {
3744
3913
  this.resizeObserver.observe(canvas);
3745
3914
  canvas.style.cursor = this.getCursorForTool(this.currentTool);
3746
3915
  this.requestRender();
3916
+ if (this.debugEnabled) {
3917
+ this.startDebugLoop();
3918
+ }
3747
3919
  }
3748
3920
  // === Loading ===
3749
3921
  /**
@@ -3782,11 +3954,15 @@ var CadViewer = class {
3782
3954
  async loadFile(file) {
3783
3955
  this.guardDestroyed();
3784
3956
  const generation = ++this.loadGeneration;
3957
+ this.loadedFileName = file.name;
3958
+ this.loadedFileSize = file.size;
3785
3959
  const buffer = await file.arrayBuffer();
3786
3960
  if (this.destroyed || generation !== this.loadGeneration) return;
3787
3961
  const dxfString = await this.runConverters(buffer);
3788
3962
  if (this.destroyed || generation !== this.loadGeneration) return;
3963
+ const t0 = performance.now();
3789
3964
  this.doc = dxfString != null ? parseDxf(dxfString) : parseDxf(buffer);
3965
+ this.parseTime = performance.now() - t0;
3790
3966
  this.onDocumentLoaded();
3791
3967
  }
3792
3968
  /**
@@ -3797,9 +3973,13 @@ var CadViewer = class {
3797
3973
  async loadBuffer(buffer) {
3798
3974
  this.guardDestroyed();
3799
3975
  const generation = ++this.loadGeneration;
3976
+ this.loadedFileName = null;
3977
+ this.loadedFileSize = buffer.byteLength;
3800
3978
  const dxfString = await this.runConverters(buffer);
3801
3979
  if (this.destroyed || generation !== this.loadGeneration) return;
3980
+ const t0 = performance.now();
3802
3981
  this.doc = dxfString != null ? parseDxf(dxfString) : parseDxf(buffer);
3982
+ this.parseTime = performance.now() - t0;
3803
3983
  this.onDocumentLoaded();
3804
3984
  }
3805
3985
  /**
@@ -3809,6 +3989,9 @@ var CadViewer = class {
3809
3989
  loadDocument(doc) {
3810
3990
  this.guardDestroyed();
3811
3991
  ++this.loadGeneration;
3992
+ this.loadedFileName = null;
3993
+ this.loadedFileSize = 0;
3994
+ this.parseTime = 0;
3812
3995
  if (!doc || !Array.isArray(doc.entities) || !(doc.layers instanceof Map)) {
3813
3996
  throw new Error("CadViewer: invalid DxfDocument \u2014 expected entities array and layers Map.");
3814
3997
  }
@@ -3821,7 +4004,11 @@ var CadViewer = class {
3821
4004
  loadString(dxf) {
3822
4005
  this.guardDestroyed();
3823
4006
  ++this.loadGeneration;
4007
+ this.loadedFileName = null;
4008
+ this.loadedFileSize = dxf.length;
4009
+ const t0 = performance.now();
3824
4010
  this.doc = parseDxf(dxf);
4011
+ this.parseTime = performance.now() - t0;
3825
4012
  this.onDocumentLoaded();
3826
4013
  }
3827
4014
  /**
@@ -3831,7 +4018,11 @@ var CadViewer = class {
3831
4018
  loadArrayBuffer(buffer) {
3832
4019
  this.guardDestroyed();
3833
4020
  ++this.loadGeneration;
4021
+ this.loadedFileName = null;
4022
+ this.loadedFileSize = buffer.byteLength;
4023
+ const t0 = performance.now();
3834
4024
  this.doc = parseDxf(buffer);
4025
+ this.parseTime = performance.now() - t0;
3835
4026
  this.onDocumentLoaded();
3836
4027
  }
3837
4028
  /**
@@ -3850,7 +4041,9 @@ var CadViewer = class {
3850
4041
  onDocumentLoaded() {
3851
4042
  if (!this.doc) return;
3852
4043
  this.layerManager.setLayers(this.doc.layers);
4044
+ const t0 = performance.now();
3853
4045
  this.spatialIndex.build(this.doc.entities);
4046
+ this.spatialIndexBuildTime = performance.now() - t0;
3854
4047
  this.selectedEntityIndex = -1;
3855
4048
  this.measureTool.deactivate();
3856
4049
  if (this.currentTool === "measure") {
@@ -3978,6 +4171,7 @@ var CadViewer = class {
3978
4171
  }
3979
4172
  destroy() {
3980
4173
  this.destroyed = true;
4174
+ this.stopDebugLoop();
3981
4175
  this.inputHandler.destroy();
3982
4176
  this.resizeObserver.disconnect();
3983
4177
  this.renderer.destroy();
@@ -3989,7 +4183,12 @@ var CadViewer = class {
3989
4183
  // === Internal (called by InputHandler) ===
3990
4184
  /** @internal */
3991
4185
  requestRender() {
3992
- if (this.renderPending || this.destroyed) return;
4186
+ if (this.destroyed) return;
4187
+ if (this.debugRafId) {
4188
+ this.doRender();
4189
+ return;
4190
+ }
4191
+ if (this.renderPending) return;
3993
4192
  this.renderPending = true;
3994
4193
  requestAnimationFrame(() => {
3995
4194
  this.renderPending = false;
@@ -4002,13 +4201,24 @@ var CadViewer = class {
4002
4201
  this.renderer.renderEmpty(this.options.theme);
4003
4202
  return;
4004
4203
  }
4005
- this.renderer.render(
4204
+ const renderStart = performance.now();
4205
+ const stats = this.renderer.render(
4006
4206
  this.doc,
4007
4207
  this.camera.getTransform(),
4008
4208
  this.options.theme,
4009
4209
  this.layerManager.getVisibleLayerNames(),
4010
4210
  this.selectedEntityIndex
4011
4211
  );
4212
+ this.lastFrameTime = performance.now() - renderStart;
4213
+ this.lastRenderStats = stats;
4214
+ const now = performance.now();
4215
+ if (now - this.lastDoRenderTime >= 3) {
4216
+ this.frameTimestamps.push(now);
4217
+ }
4218
+ this.lastDoRenderTime = now;
4219
+ while (this.frameTimestamps.length > 0 && this.frameTimestamps[0] < now - 1e3) {
4220
+ this.frameTimestamps.shift();
4221
+ }
4012
4222
  if (this.currentTool === "measure" && this.measureTool.state.phase !== "idle") {
4013
4223
  const ctx = this.renderer.getContext();
4014
4224
  renderMeasureOverlay(
@@ -4020,7 +4230,104 @@ var CadViewer = class {
4020
4230
  this.options.theme
4021
4231
  );
4022
4232
  }
4233
+ if (this.debugEnabled) {
4234
+ const ctx = this.renderer.getContext();
4235
+ const debugStats = this.buildDebugStats();
4236
+ this.lastDebugStats = debugStats;
4237
+ renderDebugOverlay(
4238
+ ctx,
4239
+ debugStats,
4240
+ this.options.theme,
4241
+ this.debugOptions,
4242
+ this.renderer.getWidth(),
4243
+ this.renderer.getHeight()
4244
+ );
4245
+ }
4246
+ }
4247
+ // === Debug Mode ===
4248
+ /**
4249
+ * Enable or disable the debug overlay.
4250
+ * Pass `true` for defaults, `false` to disable, or an object for granular control.
4251
+ */
4252
+ setDebug(debug) {
4253
+ this.guardDestroyed();
4254
+ if (typeof debug === "boolean") {
4255
+ this.debugEnabled = debug;
4256
+ } else {
4257
+ this.debugEnabled = true;
4258
+ this.debugOptions = resolveDebugOptions(debug);
4259
+ }
4260
+ if (this.debugEnabled) {
4261
+ this.startDebugLoop();
4262
+ } else {
4263
+ this.stopDebugLoop();
4264
+ this.requestRender();
4265
+ }
4266
+ }
4267
+ startDebugLoop() {
4268
+ if (this.debugRafId) return;
4269
+ const loop = () => {
4270
+ if (!this.debugEnabled || this.destroyed) {
4271
+ this.debugRafId = 0;
4272
+ return;
4273
+ }
4274
+ this.doRender();
4275
+ this.debugRafId = requestAnimationFrame(loop);
4276
+ };
4277
+ this.debugRafId = requestAnimationFrame(loop);
4278
+ }
4279
+ stopDebugLoop() {
4280
+ if (this.debugRafId) {
4281
+ cancelAnimationFrame(this.debugRafId);
4282
+ this.debugRafId = 0;
4283
+ }
4284
+ }
4285
+ /**
4286
+ * Get the latest debug stats snapshot, or null if debug mode is off.
4287
+ */
4288
+ getDebugStats() {
4289
+ return this.debugEnabled ? this.lastDebugStats : null;
4290
+ }
4291
+ buildDebugStats() {
4292
+ const vt = this.camera.getTransform();
4293
+ const w = this.renderer.getWidth();
4294
+ const h = this.renderer.getHeight();
4295
+ const bounds = this.computeViewportBounds(vt, w, h);
4296
+ return {
4297
+ fps: this.frameTimestamps.length,
4298
+ frameTime: this.lastFrameTime,
4299
+ renderStats: this.lastRenderStats ?? {
4300
+ entitiesDrawn: 0,
4301
+ entitiesSkipped: 0,
4302
+ drawCalls: 0,
4303
+ byType: {}
4304
+ },
4305
+ entityCount: this.doc?.entities.length ?? 0,
4306
+ layerCount: this.doc?.layers.size ?? 0,
4307
+ visibleLayerCount: this.layerManager.getVisibleLayerNames().size,
4308
+ blockCount: this.doc?.blocks.size ?? 0,
4309
+ parseTime: this.parseTime,
4310
+ spatialIndexBuildTime: this.spatialIndexBuildTime,
4311
+ totalLoadTime: this.parseTime + this.spatialIndexBuildTime,
4312
+ zoom: vt.scale,
4313
+ pixelSize: vt.scale > 0 ? 1 / vt.scale : 0,
4314
+ viewportBounds: bounds,
4315
+ fileName: this.loadedFileName,
4316
+ fileSize: this.loadedFileSize,
4317
+ dxfVersion: this.doc?.header.acadVersion ?? null
4318
+ };
4319
+ }
4320
+ computeViewportBounds(vt, w, h) {
4321
+ const [x1, y1] = screenToWorld(vt, 0, h);
4322
+ const [x2, y2] = screenToWorld(vt, w, 0);
4323
+ return {
4324
+ minX: Math.min(x1, x2),
4325
+ minY: Math.min(y1, y2),
4326
+ maxX: Math.max(x1, x2),
4327
+ maxY: Math.max(y1, y2)
4328
+ };
4023
4329
  }
4330
+ // === Internal (called by InputHandler) ===
4024
4331
  /** @internal */
4025
4332
  handlePan(dx, dy) {
4026
4333
  this.camera.pan(dx, dy);
@@ -4104,6 +4411,6 @@ var CadViewer = class {
4104
4411
  }
4105
4412
  };
4106
4413
 
4107
- export { CadViewer, Camera, CanvasRenderer, DxfParseError, EventEmitter, LayerManager, MeasureTool, SpatialIndex, THEMES, aciToDisplayColor, aciToHex, applyTransform, computeEntitiesBounds, computeEntityBBox, drawEntity, findSnaps, fitToView, hitTest, parseDxf, renderMeasureOverlay, resolveEntityColor, screenToWorld, trueColorToHex, worldToScreen, zoomAtPoint };
4414
+ export { CadViewer, Camera, CanvasRenderer, DxfParseError, EventEmitter, LayerManager, MeasureTool, SpatialIndex, THEMES, aciToDisplayColor, aciToHex, applyTransform, computeEntitiesBounds, computeEntityBBox, drawEntity, findSnaps, fitToView, hitTest, parseDxf, renderDebugOverlay, renderMeasureOverlay, resolveEntityColor, screenToWorld, trueColorToHex, worldToScreen, zoomAtPoint };
4108
4415
  //# sourceMappingURL=index.js.map
4109
4416
  //# sourceMappingURL=index.js.map