Skip to content

flyfish-dev/cad-viewer

Repository files navigation

@flyfish-dev/cad-viewer

A professional, lightweight, extensible frontend CAD viewer for modern browsers.

License: AGPL-3.0-only npm

Live demo: cad-viewer-iys.pages.dev
Source: github.com/flyfish-dev/cad-viewer

The project provides a clean loader architecture for DWG, DXF, DWF, DWFx and XPS. DWG/DXF are normalized into a common CadDocument and rendered through retained WebGL with a lightweight Canvas overlay; DWF/DWFx/XPS are delegated to the native dwf-viewer renderer for WebGL-accelerated W2D and XPS/DWFx vectors, W3D/HSF eModel geometry, embedded XPS fonts and optional WASM fallback. Files are read locally in the browser; the viewer does not upload drawings to a backend.

DWG support uses @mlightcad/libredwg-web / LibreDWG WebAssembly in a worker. DXF support uses JavaScript parsing plus a built-in fallback parser. DWF, DWFx and XPS support is powered by dwf-viewer 0.6.x, including DWF 6+ ZIP containers, WHIP/W2D 2D sheets, W3D/HSF 3D eModel geometry, DWFx/OPC/XPS pages, adaptive CAD line weights and an optional raster WASM fallback.

What changed in 0.6.2

  • Updated the native DWF path to dwf-viewer 0.6.4 for upstream DWF/DWFx bug fixes while keeping this package on its own SemVer line.
  • Verified the exposed CadViewer DWF/XPS options still match the current dwf-viewer API.

What changed in 0.6.1

  • Updated the native DWF path to dwf-viewer 0.6.1.
  • Adopted the current renderer behavior for WebGL-accelerated XPS/DWFx and W2D 2D vector rendering, W3D/HSF 3D rendering, embedded XPS font loading and optional WASM raster fallback.
  • Exposed DWF/XPS overview tuning through dwfLineWeightMode, dwfMinStrokeCssPx, dwfMaxOverviewStrokeCssPx, dwfMinTextCssPx and dwfMinFilledAreaCssPx.
  • Refreshed README, format notes, architecture notes and package metadata for the latest DWF support.

What changed in 0.6.0

  • Replaced the previous DWFx/XPS subset parser with the published dwf-viewer native renderer.
  • DWF/DWFx/XPS files now mount a dedicated native viewer path instead of being forced into the DWG/DXF CadDocument renderer.
  • Added support for DWF 6+ ZIP packages, WHIP/W2D 2D sheets, W3D/HSF 3D eModel pages, DWFx/OPC/XPS pages and dwf-viewer's WebGL/WASM render backends.
  • Added CadNativeRenderableLoader, allowing loaders to mount an optimized DOM/WebGL viewer when a format needs more than the normalized 2D scene model.
  • Added dwfWasmUrl, dwfPreferWebgl, dwfPreferWasm, dwfMaxCanvasPixels, dwfMaxGpuCacheBytes and related production options.
  • Runtime asset scripts now copy and validate both libredwg-web.wasm and dwfv-render.wasm.
  • Removed the old classic-DWF warning path and the fflate dependency from this package.
  • Updated package licensing to AGPL-3.0-only to match the integrated DWF renderer.

What changed in 0.5.3

  • Fixed DWG color fidelity for layer-indexed drawings. Indexed LibreDWG layers now resolve from ACI instead of the converter placeholder 0xffffff, preventing monochrome-white output.
  • Preserved DWG true-color values even when the RGB integer is in the ACI range, such as 0x0000ff.
  • Added BYBLOCK color inheritance for expanded INSERT/block entities.

What changed in 0.5.2

  • Fixed the noisy Vite build output where @mlightcad/libredwg-web printed a multi-megabyte data:application/wasm;base64,... warning.
  • DWG worker builds now use the lean LibreDWG ESM wrapper and load /wasm/libredwg-web.js + /wasm/libredwg-web.wasm at runtime, avoiding duplicated inline wasm assets.
  • build:lib now creates dist/index.js as a compatibility re-export for integrations that still request /dist/index.js.
  • Vite dev mode now serves a small /dist/index.js compatibility shim that forwards stale demo pages to /demo/main.ts.
  • npm run preview now builds the demo first, so clean checkouts do not fail because dist-demo is missing.

What changed in 0.5

  • The default rendering backend is now a retained WebGL renderer. CAD primitives are flattened once and uploaded to GPU buffers; pan/zoom updates only view uniforms.
  • Added spatial indexing and viewport culling. Line, triangle and point batches are bucketed across the drawing bounds, so zoomed-in views submit only visible batches.
  • Added large-drawing memory controls: coordinates are stored relative to the drawing center in Float32Array, colors are stored in Uint8Array, and temporary CPU arrays are released after upload.
  • Text and images render in a separate overlay with minimum screen-height and maximum visible-label limits.
  • CadViewer now supports renderer: 'auto' | 'webgl' | 'canvas2d'; auto prefers WebGL and falls back to Canvas2D.
  • Demo now reports renderer backend, visible primitives and estimated GPU memory.

What changed in 0.4

  • DWG parsing now runs in a dedicated Web Worker by default, so LibreDWG WASM initialization and binary decoding no longer block pan/zoom/UI interactions.
  • The worker keeps the LibreDWG WASM instance warm and reuses it across DWG loads.
  • Added cancellable loading through AbortSignal, worker timeout support, progress events, and explicit worker asset configuration.
  • Reduced DWG memory pressure by stripping raw parser objects from worker payloads unless keepRaw: true is explicitly enabled.
  • Demo now includes a loading overlay, progress bar, cancel action and loader mode indicator.
  • viewer.destroy() disposes canvas listeners and terminates owned DWG workers, which is safe for SPA route changes.
  • Library exports supportsDwgWorker and DwgWorkerClient for advanced integrations.

Features

  • Pure frontend viewer component: new CadViewer({ container }) or new CadViewer({ canvas }).
  • Loader registry: DWG, DXF and DWF loaders are independent and replaceable; native-renderable loaders can mount their own optimized viewer.
  • DWG preview: browser-local parsing through LibreDWG WebAssembly, executed in a Web Worker by default.
  • DXF preview: JavaScript parser path with fallback support for common ASCII DXF ENTITIES.
  • DWF/DWFx/XPS preview: powered by dwf-viewer for DWF ZIP packages, WebGL-accelerated W2D and XPS/DWFx 2D vectors, W3D/HSF eModel geometry, embedded XPS fonts, adaptive CAD line weights and raster fallback.
  • CAD color handling: ACI, BYLAYER, BYBLOCK inheritance, DWG layer colors, true color, fill color, opacity and adaptive contrast.
  • High-performance WebGL viewport controls: retained GPU buffers, spatial culling, zoom, pan, fit-to-view, cursor world coordinates and zoom percentage.
  • Professional demo UI: drag-and-drop, compact toolbar, status strip, parse/render timing, entity summary and warnings.
  • Library + demo builds: publishable npm package and Cloudflare Pages demo.

Install

npm install @flyfish-dev/cad-viewer

For local development from this repository:

npm install
npm run dev

The DWG and DWF render paths need runtime WASM assets in a public directory. This repository copies and validates them for the demo:

npm run copy:wasm
npm run check:wasm

The demo resolves wasmPath to an absolute URL before sending it to the DWG worker and uses the same directory for dwfv-render.wasm. In your own app, prefer an absolute path or URL, for example /wasm or new URL('wasm/', document.baseURI).href. Avoid passing a worker-relative path such as ./wasm unless it is resolved on the UI thread first.

When publishing the npm package, build:lib copies these files into dist/wasm and exposes them as package subpaths. Applications still need to serve the .wasm files from a public URL and pass that directory as wasmPath, or pass dwfWasmUrl explicitly.

Demo startup notes

Use Vite for the source demo:

npm install
npm run dev

For a production preview, use:

npm run preview

Do not serve the source directory with a plain static server and expect TypeScript entries to run. If a stale page requests /dist/index.js, run npm run build:lib to create the compatibility entry, or use the Vite dev server above.

Basic usage

import { CadViewer } from '@flyfish-dev/cad-viewer';
import '@flyfish-dev/cad-viewer/style.css';

const viewer = new CadViewer({
  container: document.querySelector('#viewer')!,
  renderer: 'auto',       // WebGL first, Canvas2D fallback
  wasmPath: new URL('wasm/', document.baseURI).href,
  dwfWasmUrl: new URL('wasm/dwfv-render.wasm', document.baseURI).href,
  canvasOptions: {
    background: '#05070d',
    foreground: '#f8fafc',
    contrastMode: 'adaptive',
    minColorContrast: 2.45
  }
});

const input = document.querySelector<HTMLInputElement>('input[type=file]')!;
input.addEventListener('change', async () => {
  const file = input.files?.[0];
  if (!file) return;
  await viewer.loadFile(file);
});

WebGL rendering and performance model

The default renderer: 'auto' path attempts to create CadWebGLRenderer first. Instead of traversing every entity and rebuilding Canvas2D paths on every zoom, the renderer builds a retained scene once in setDocument():

CadDocument
  ↓
flatten blocks / curves / fills
  ↓
Float32Array positions + Uint8Array colors
  ↓
spatial GPU batches
  ↓
WebGL drawArrays with viewport culling

Tunable options:

new CadViewer({
  container,
  renderer: 'auto', // force 'webgl' or 'canvas2d' when needed
  canvasOptions: {
    enableSpatialIndex: true,
    spatialIndexCellCount: 96,
    maxVerticesPerBatch: 32768,
    maxCurveSegments: 72,
    textMinPixelHeight: 4,
    maxVisibleTextLabels: 2400,
    powerPreference: 'high-performance',
    preserveDrawingBuffer: false
  },
  onRenderStats(stats) {
    console.log(stats.backend, stats.visiblePrimitiveCount, stats.gpuMemoryBytes);
  }
});

For very large drawings, lower maxCurveSegments, increase spatialIndexCellCount, and cap maxVisibleTextLabels.

Worker-backed DWG parsing

DwgLoader uses a module Web Worker by default in browsers. The worker imports @mlightcad/libredwg-web, initializes LibreDWG WASM inside the worker thread, caches that WASM instance, decodes DWG bytes, normalizes the result into a structured-clone-safe CadDocument, and sends only the normalized scene back to the UI thread. Canvas rendering remains on the main thread.

const controller = new AbortController();

const viewer = new CadViewer({
  container,
  wasmPath: new URL('wasm/', document.baseURI).href,
  dwfWasmUrl: new URL('wasm/dwfv-render.wasm', document.baseURI).href,
  useWorker: true,
  workerTimeoutMs: 120_000,
  onLoadProgress(progress) {
    console.log(progress.phase, progress.message, progress.percent);
  }
});

await viewer.preloadDwg(); // optional: warm the worker before the first file
await viewer.loadFile(file, { signal: controller.signal });

// cancel a large DWG load
controller.abort();

Advanced deployments can override the worker constructor when the bundler or CDN has a custom asset layout:

new CadViewer({
  container,
  wasmPath: new URL('wasm/', document.baseURI).href,
  dwfWasmUrl: new URL('wasm/dwfv-render.wasm', document.baseURI).href,
  workerUrl: new URL('/assets/dwg-worker.js', window.location.origin)
});

The default package is worker-first. For non-browser runtimes, register a custom DWG loader instead of disabling workers.

Component API

const viewer = new CadViewer({
  container,             // HTMLElement; creates a canvas inside
  canvas,                // optional existing HTMLCanvasElement
  renderer: 'auto',      // 'auto' | 'webgl' | 'canvas2d'
  wasmPath: '/wasm',     // directory containing libredwg-web.wasm and dwfv-render.wasm
  dwfWasmUrl: '/wasm/dwfv-render.wasm',
  autoFit: true,
  canvasOptions: {
    background: '#05070d',
    foreground: '#ffffff',
    contrastMode: 'adaptive',       // 'adaptive' | 'preserve'
    minColorContrast: 2.45,
    showPageBounds: true,
    showUnsupportedMarkers: false,
    trueColorByteOrder: 'rgb',
    enableSpatialIndex: true,
    spatialIndexCellCount: 96,
    maxVerticesPerBatch: 32768,
    maxCurveSegments: 72,
    textMinPixelHeight: 4,
    maxVisibleTextLabels: 2400
  },
  useWorker: true,                 // default for DWG
  workerTimeoutMs: 0,              // 0 = disabled
  dwfPreferWebgl: true,
  dwfPreferWasm: true,
  dwfMaxCanvasPixels: 16_777_216,
  dwfLineWeightMode: 'adaptive',  // 'adaptive' | 'physical' | 'hairline'
  dwfMinStrokeCssPx: 0.42,
  dwfMinTextCssPx: 1.05,
  onLoadProgress(progress) {},
  onLoad(result) {},
  onError(error) {},
  onRenderStats(stats) {},
  onViewChange(event) {}
});

await viewer.loadFile(file);
await viewer.loadBuffer(arrayBuffer, 'drawing.dxf');
viewer.fit();
viewer.zoomIn();
viewer.zoomOut();
await viewer.preloadDwg();       // optional DWG worker/WASM warmup
viewer.setCanvasOptions({ background: '#f7f8fb', foreground: '#111827' });
viewer.clear();
viewer.destroy();

Loader architecture

File / ArrayBuffer
  ↓
CadLoaderRegistry
  ↓
DwgLoader | DxfLoader | DwfLoader | custom loader
  ↓
DWG/DXF: CadDocument → CadWebGLRenderer | CadCanvasRenderer fallback
DWF/DWFx/XPS: DwfLoader.mount() → dwf-viewer native WebGL vectors / 3D / WASM fallback

Each loader returns a normalized CadDocument:

interface CadDocument {
  format: 'dwg' | 'dxf' | 'dwf' | 'dwfx' | 'xps' | 'unknown';
  layers: Record<string, CadLayer>;
  blocks: Record<string, CadBlock>;
  entities: CadEntity[];
  pages?: CadPage[];
  warnings: string[];
  raw?: unknown;
}

Register a custom loader:

viewer.registerLoader({
  id: 'my-cad-format',
  label: 'My CAD Format',
  formats: ['unknown'],
  accepts(input) {
    return input.fileName?.endsWith('.cad') ?? false;
  },
  async load(input) {
    return {
      document: {
        format: 'unknown',
        layers: {},
        blocks: {},
        entities: [],
        metadata: {},
        warnings: []
      },
      bytes: input.buffer instanceof Uint8Array ? input.buffer.byteLength : 0,
      elapsedMs: 0,
      format: 'unknown',
      warnings: []
    };
  }
});

Format support

Format Loader Coverage
DWG DwgLoader Uses LibreDWG WebAssembly. Rendering coverage depends on the entities exposed by LibreDWG conversion.
DXF DxfLoader Uses dxf-parser plus fallback parsing. Supports core entities, blocks/inserts, colors/layers, polylines, text, hatch boundaries and splines as preview polylines.
DWF DwfLoader + dwf-viewer DWF 6+ ZIP packages, WHIP/W2D 2D sheets, W3D/HSF 3D eModel pages, model tree metadata, WebGL rendering and optional WASM fallback.
DWFx / XPS DwfLoader + dwf-viewer DWFx/OPC/XPS pages with WebGL-accelerated vector paths, embedded fonts, text, images, package resources and adaptive overview line weights through the native DWF renderer.

Color handling

The color resolver follows CAD semantics instead of treating all numbers as RGB:

  1. explicit CSS color or true color object,
  2. explicit DWG true-color integer, including low RGB values such as 0x0000ff,
  3. entity ACI (colorIndex, colorNumber, color in 1..255),
  4. BYBLOCK inheritance (0) when expanding INSERT/block geometry,
  5. BYLAYER lookup (256 / unset),
  6. viewer foreground fallback.

Layer colors prefer a valid ACI index when a converter also exposes a placeholder RGB value. ACI 7 is foreground-dependent: it renders light on dark canvas and dark on light canvas. With contrastMode: 'adaptive', colors too close to the current canvas background are adjusted just enough to stay readable. Use contrastMode: 'preserve' when exact plotted colors are more important than screen readability.

If a particular converter exposes true-color integers in BGR order:

new CadViewer({ canvasOptions: { trueColorByteOrder: 'bgr' } });

Development

npm install
npm run dev          # run the demo
npm run typecheck    # TypeScript validation
npm run build        # library + demo
npm run preview      # preview built demo

npm publishing

  1. Build and inspect the package:
npm run build:lib
npm run pack:dry
  1. Publish:
npm login
npm publish --access public --auth-type=web

The package also exposes:

npm run release:npm

Cloudflare Pages publishing

Direct upload with Wrangler. The repository includes public/_headers, so Cloudflare Pages serves .wasm as application/wasm and caches it long-term:

npm install
npm run build:demo
npx wrangler pages deploy dist-demo --project-name cad-viewer

Or use the included script:

npm run deploy:pages

For GitHub Actions, configure repository secrets:

CLOUDFLARE_API_TOKEN
CLOUDFLARE_ACCOUNT_ID

The workflow is included at .github/workflows/pages.yml.

Repository layout

src/
  core/          shared format detection, colors, geometry, transforms, normalized types
  loaders/       DwgLoader, DxfLoader, DwfLoader and CadLoaderRegistry
  viewer/        CadViewer component and Canvas renderer
demo/            professional Vite demo UI
docs/            English and Chinese architecture / format notes
scripts/         clean and runtime WASM copy/validation helpers
public/wasm/     demo WASM asset output directory

License

AGPL-3.0-only. The default DWG loader integrates @mlightcad/libredwg-web / LibreDWG, and the DWF renderer integrates dwf-viewer. Keep attribution and license notices when redistributing, modifying, embedding or using this package as part of another application. For closed-source commercial products, review all dependency licenses and replace loaders where your licensing model requires it.

About

Production-oriented browser CAD viewer for DWG, DXF, DWF, DWFx and XPS with workers, LibreDWG WASM, native dwf-viewer, WebGL 2D/3D rendering and embedded XPS fonts.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors