Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0a6f4b5
feat: add CAD color mode integration helpers
brownie-cake Sep 2, 2026
d8c34ec
chore: apply deterministic issue 213 integration
brownie-cake Sep 2, 2026
a16baa2
feat: integrate monochrome CAD mode [issue-213-applied]
github-actions[bot] Sep 2, 2026
2940133
test: verify File Viewer CAD color mode integration
brownie-cake Sep 2, 2026
f1a88b8
feat: export CAD color mode integration helpers
brownie-cake Sep 2, 2026
e0e11d3
test: expose CAD color mode verification command
brownie-cake Sep 2, 2026
9501e69
ci: verify CAD monochrome integration
brownie-cake Sep 2, 2026
13c7fd8
docs: fix monochrome example formatting
brownie-cake Sep 2, 2026
de69884
docs: fix English monochrome example formatting
brownie-cake Sep 2, 2026
daf6304
chore: preserve the existing npm registry URL
brownie-cake Sep 2, 2026
733f6a9
chore: apply CAD source-document integration fix
brownie-cake Sep 2, 2026
2c714cc
fix: preserve parser-owned CAD source on layer toggles [source-docume…
github-actions[bot] Sep 2, 2026
0ced292
test: preserve parser-owned CAD document during layer updates
brownie-cake Sep 2, 2026
aebb49c
feat: export parser-owned CAD document resolver
brownie-cake Sep 2, 2026
485d9ac
fix: default File Viewer monochrome output to black
brownie-cake Sep 2, 2026
0192fa7
test: assert black monochrome default
brownie-cake Sep 2, 2026
8aff132
Merge origin/main into feature/issue-213-monochrome-cad
wybaby168 Sep 5, 2026
c9df96d
fix(security): clear audited dependency advisories
wybaby168 Sep 5, 2026
0b7b9a7
fix(security): refresh public DICOM dependency ledger
wybaby168 Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/viewer-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@file-viewer/vue3": "workspace:3.0.1",
"@lucide/vue": "^1.37.0",
"diff": "^9.0.0",
"qs": "^6.15.2",
"qs": "^6.16.0",
"vue": "^3.5.42"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions packages/components/vue3/src/package/common/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
FileViewerAiOptions as CoreFileViewerAiOptions,
FileViewerArchiveOptions as CoreFileViewerArchiveOptions,
FileViewerBeforeOperation as CoreFileViewerBeforeOperation,
FileViewerCadColorMode as CoreFileViewerCadColorMode,
FileViewerCadDwfLineWeightMode as CoreFileViewerCadDwfLineWeightMode,
FileViewerCadOptions as CoreFileViewerCadOptions,
FileViewerCadRenderer as CoreFileViewerCadRenderer,
Expand Down Expand Up @@ -110,6 +111,7 @@ export type FileViewerSpreadsheetOptions = CoreFileViewerSpreadsheetOptions;
export type FileViewerTypstOptions = CoreFileViewerTypstOptions;

export type FileViewerCadRenderer = CoreFileViewerCadRenderer;
export type FileViewerCadColorMode = CoreFileViewerCadColorMode;
export type FileViewerCadDwfLineWeightMode = CoreFileViewerCadDwfLineWeightMode;

/**
Expand Down
1 change: 1 addition & 0 deletions packages/components/vue3/src/package/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export type {
FileViewerApplyViewStateOptions,
FileViewerArchiveOptions,
FileViewerBeforeOperation,
FileViewerCadColorMode,
FileViewerCadDwfLineWeightMode,
FileViewerCadOptions,
FileViewerCadRenderer,
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/contracts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ export type FileViewerMessageKey =
| 'cad.toolbar.fit'
| 'cad.toolbar.zoomOut'
| 'cad.toolbar.zoomIn'
| 'cad.toolbar.colorSource'
| 'cad.toolbar.monochrome'
| 'cad.layers.title'
| 'cad.layers.count'
| 'cad.layers.merged'
Expand Down Expand Up @@ -1362,6 +1364,7 @@ export interface FileViewerDrawingOptions {

export type FileViewerCadRenderer = 'auto' | 'webgl' | 'canvas2d'
export type FileViewerCadDwfLineWeightMode = 'adaptive' | 'physical' | 'hairline'
export type FileViewerCadColorMode = 'source' | 'monochrome'
export type FileViewerCadFitMode = 'best' | 'native'

export interface FileViewerCadOptions {
Expand All @@ -1372,6 +1375,12 @@ export interface FileViewerCadOptions {
useWorker?: boolean
workerTimeoutMs?: number
renderer?: FileViewerCadRenderer
/** Initial CAD color policy. `monochrome` applies one plot color without mutating source data. */
colorMode?: FileViewerCadColorMode
/** CSS color used by monochrome mode. Defaults to the CAD foreground color. */
monochromeColor?: string
/** Shows the CAD toolbar color-mode toggle. Defaults to true. */
showColorModeToggle?: boolean
preferDwgWasm?: boolean
includePaperSpace?: boolean
maxInsertDepth?: number
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/headless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ export type {
FileViewerChmOptions,
FileViewerApplyViewStateOptions,
FileViewerBeforeOperation,
FileViewerCadColorMode,
FileViewerCadDwfLineWeightMode,
FileViewerCadOptions,
FileViewerCadRenderer,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/i18n/messages.de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,8 @@ export const DE_DE_MESSAGES: Record<FileViewerMessageKey, string> = {
"cad.toolbar.fit": "Fit",
"cad.toolbar.zoomOut": "Herauszoomen",
"cad.toolbar.zoomIn": "Vergrößern",
"cad.toolbar.colorSource": "Originalfarben",
"cad.toolbar.monochrome": "Monochrom",
"cad.layers.title": "Schichten",
"cad.layers.count": "{count} Artikel",
"cad.layers.merged": "{name} ({count} zusammengeführte Ebenen)",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/i18n/messages.ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ export const JA_JP_MESSAGES: Record<FileViewerMessageKey, string> = {
'cad.toolbar.fit': '全体表示',
'cad.toolbar.zoomOut': '縮小',
'cad.toolbar.zoomIn': '拡大',
'cad.toolbar.colorSource': '元の色',
'cad.toolbar.monochrome': 'モノクロ',
'cad.layers.title': 'レイヤー',
'cad.layers.count': '{count} 件',
'cad.layers.merged': '{name}(同名レイヤー {count} 件を統合)',
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ const ZH_CN_MESSAGES: Record<FileViewerMessageKey, string> = {
'cad.toolbar.fit': '适配',
'cad.toolbar.zoomOut': '缩小',
'cad.toolbar.zoomIn': '放大',
'cad.toolbar.colorSource': '彩色',
'cad.toolbar.monochrome': '黑白',
'cad.layers.title': '图层',
'cad.layers.count': '{count} 项',
'cad.layers.merged': '{name}(已合并 {count} 个同名图层)',
Expand Down Expand Up @@ -822,6 +824,8 @@ const EN_US_MESSAGES: Record<FileViewerMessageKey, string> = {
'cad.toolbar.fit': 'Fit',
'cad.toolbar.zoomOut': 'Zoom out',
'cad.toolbar.zoomIn': 'Zoom in',
'cad.toolbar.colorSource': 'Source colors',
'cad.toolbar.monochrome': 'Monochrome',
'cad.layers.title': 'Layers',
'cad.layers.count': '{count} items',
'cad.layers.merged': '{name} ({count} merged layers)',
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,7 @@ export type {
FileViewerChmOptions,
FileViewerApplyViewStateOptions,
FileViewerBeforeOperation,
FileViewerCadColorMode,
FileViewerCadDwfLineWeightMode,
FileViewerCadOptions,
FileViewerCadRenderer,
Expand Down
14 changes: 14 additions & 0 deletions packages/renderers/cad/README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,17 @@ For private deployments, override them with `options.cad.wasmPath`, `options.cad
## Migration Note

CAD preview has moved completely out of `@file-viewer/core`. Core now only keeps the asset manifest, shared types, and a compatibility error message, and it no longer installs `@flyfish-dev/cad-viewer` by default. Install this package and pass it through `renderers`, or use `@file-viewer/preset-all` for the complete CAD experience.

## Monochrome plot mode

Set `cad.colorMode` to `source` or `monochrome`. Monochrome rendering uses the CAD engine's entity-color policy rather than a CSS filter, keeping Canvas, WebGL, text overlays and native DWF output consistent while preserving alpha, line types, line weights and source data.

```ts
{
cad: {
colorMode: 'monochrome',
monochromeColor: '#000000',
showColorModeToggle: true,
},
}
```
14 changes: 14 additions & 0 deletions packages/renderers/cad/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,17 @@ const options = {
## 迁移说明

CAD 预览已经从 `@file-viewer/core` 中彻底移出,core 只保留资产 manifest、类型和兼容错误提示,不再默认安装 `@flyfish-dev/cad-viewer`。完整 CAD 能力请安装本包并传入 `renderers`,或直接使用 `@file-viewer/preset-all`。

## 黑白出图模式

`cad.colorMode` 可设为 `source` 或 `monochrome`。黑白模式通过 CAD 引擎的实体颜色策略实现,不使用 CSS 滤镜,因此 Canvas、WebGL、文字覆盖层及原生 DWF 渲染可保持一致,同时保留透明度、线型、线宽和源文件数据。

```ts
{
cad: {
colorMode: 'monochrome',
monochromeColor: '#000000',
showColorModeToggle: true,
},
}
```
3 changes: 2 additions & 1 deletion packages/renderers/cad/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
],
"scripts": {
"build": "tsc -b tsconfig.json",
"type-check": "tsc -b tsconfig.json"
"type-check": "tsc -b tsconfig.json",
"verify:color-mode": "node scripts/verify-color-mode.mjs"
},
"dependencies": {
"@file-viewer/core": "workspace:3.0.1",
Expand Down
40 changes: 40 additions & 0 deletions packages/renderers/cad/scripts/verify-color-mode.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import assert from 'node:assert/strict';

import {
applyCadViewerColorMode,
normalizeFileViewerCadColorMode,
resolveCadViewerSourceDocument,
resolveFileViewerCadMonochromeColor,
supportsCadViewerColorMode,
} from '../dist/colorMode.js';

assert.equal(normalizeFileViewerCadColorMode('monochrome'), 'monochrome');
assert.equal(normalizeFileViewerCadColorMode('source'), 'source');
assert.equal(normalizeFileViewerCadColorMode('unexpected'), 'source');
assert.equal(resolveFileViewerCadMonochromeColor(' #101820 '), '#101820');
assert.equal(resolveFileViewerCadMonochromeColor(undefined), '#000000');

const calls = [];
const viewer = {
setColorMode(mode, color) {
calls.push([mode, color]);
},
};

assert.equal(supportsCadViewerColorMode(viewer), true);
assert.equal(applyCadViewerColorMode(viewer, 'monochrome', '#000000'), true);
assert.deepEqual(calls, [['monochrome', '#000000']]);
assert.equal(applyCadViewerColorMode({}, 'source', '#000000'), false);

const parserDocument = { metadata: { parserOwned: true } };
const renderDocument = { metadata: { renderOnly: true } };
assert.equal(resolveCadViewerSourceDocument({
getSourceDocument: () => parserDocument,
getDocument: () => renderDocument,
}), parserDocument);
assert.equal(resolveCadViewerSourceDocument({
getDocument: () => renderDocument,
}), renderDocument);
assert.equal(resolveCadViewerSourceDocument(undefined), undefined);

console.log('CAD color mode integration checks passed.');
48 changes: 44 additions & 4 deletions packages/renderers/cad/src/cad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
unregisterFileViewerZoomProvider,
type FileViewerApplyViewStateOptions,
type FileRenderContext,
type FileViewerCadColorMode,
type FileViewerCadOptions,
type FileViewerFitRequest,
type FileViewerFitResult,
Expand All @@ -40,6 +41,13 @@ import {
type FileViewerViewStateChangeSource,
type FileViewerZoomState,
} from '@file-viewer/core';
import {
applyCadViewerColorMode,
normalizeFileViewerCadColorMode,
resolveFileViewerCadMonochromeColor,
resolveCadViewerSourceDocument,
supportsCadViewerColorMode,
} from './colorMode';

type CadStatus = 'loading' | 'ready' | 'error';

Expand All @@ -63,6 +71,9 @@ const cadStyle = `
.cad-tools,.cad-meta{display:flex;align-items:center;gap:8px}
.cad-tools button{min-width:34px;min-height:30px;border:0;border-radius:8px;background:rgba(15,23,42,.06);color:#25344c;cursor:pointer;font-weight:800;letter-spacing:0;transition:background-color .18s ease,color .18s ease}
.cad-tools button:hover{background:rgba(31,150,110,.14);color:#0f8f62}
.cad-tools button.cad-color-mode{min-width:52px;padding:0 10px;font-size:12px}
.cad-tools button.cad-color-mode[aria-pressed='true']{background:#0f172a;color:#fff}
.cad-tools button.cad-color-mode:disabled{cursor:not-allowed;opacity:.46}
.cad-zoom,.cad-meta span{color:#64748b;font-size:12px;font-weight:800;letter-spacing:0}
.cad-meta span{border-radius:999px;padding:5px 9px;background:rgba(15,23,42,.06)}
.cad-body{display:grid;min-height:0;flex:1;grid-template-columns:minmax(176px,236px) minmax(0,1fr) minmax(142px,176px);background:#eef2f7}
Expand Down Expand Up @@ -791,6 +802,8 @@ export default async function renderCad(
const options: FileViewerCadOptions = context?.options?.cad || {};
const t = createFileViewerTranslator(context?.options);
const locale = resolveFileViewerLocale(context?.options);
const monochromeColor = resolveFileViewerCadMonochromeColor(options.monochromeColor);
let colorMode = normalizeFileViewerCadColorMode(options.colorMode);
let status: CadStatus = 'loading';
let progressMessage = t('cad.state.loadingViewer');
let errorMessage = '';
Expand All @@ -817,16 +830,18 @@ export default async function renderCad(
const zoomOutButton = createElement('button', undefined, '-') as HTMLButtonElement;
const zoomText = createElement('span', 'cad-zoom', '100%');
const zoomInButton = createElement('button', undefined, '+') as HTMLButtonElement;
const colorModeButton = createElement('button', 'cad-color-mode') as HTMLButtonElement;
colorModeButton.hidden = options.showColorModeToggle === false;
const meta = createElement('div', 'cad-meta');
const typeMeta = createElement('span', undefined, normalizedType.toUpperCase());
const backendMeta = createElement('span', undefined, 'AUTO');
[fitButton, zoomOutButton, zoomInButton].forEach(button => {
[fitButton, zoomOutButton, zoomInButton, colorModeButton].forEach(button => {
button.type = 'button';
});
fitButton.title = t('cad.toolbar.fit');
zoomOutButton.title = t('cad.toolbar.zoomOut');
zoomInButton.title = t('cad.toolbar.zoomIn');
tools.append(fitButton, zoomOutButton, zoomText, zoomInButton);
tools.append(fitButton, zoomOutButton, zoomText, zoomInButton, colorModeButton);
meta.append(typeMeta, backendMeta);
toolbar.append(tools, meta);

Expand Down Expand Up @@ -905,6 +920,8 @@ export default async function renderCad(
status,
view: viewState ? { ...viewState } : null,
backend: renderStats?.backend,
colorMode,
monochromeColor: colorMode === 'monochrome' ? monochromeColor : undefined,
layerCount: layers.length,
visibleLayers: layers
.filter(layer => layer.isVisible !== false && !layer.isFrozen)
Expand Down Expand Up @@ -1051,12 +1068,14 @@ export default async function renderCad(
: layer.name;
button.setAttribute('aria-pressed', String(layerVisible));
const color = createElement('span', 'cad-layer-color');
if (typeof layer.color === 'string') {
if (colorMode === 'monochrome') {
color.style.background = monochromeColor;
} else if (typeof layer.color === 'string') {
color.style.background = layer.color;
}
button.append(color, createElement('span', undefined, layer.name));
button.addEventListener('click', () => {
const document = viewer?.getDocument();
const document = resolveCadViewerSourceDocument(viewer) as CadDocument | undefined;
if (!document) {
return;
}
Expand Down Expand Up @@ -1085,6 +1104,15 @@ export default async function renderCad(
const syncState = () => {
zoomText.textContent = `${getZoomPercent()}%`;
backendMeta.textContent = (renderStats?.backend || 'auto').toUpperCase();
const colorModeSupported = supportsCadViewerColorMode(viewer);
colorModeButton.disabled = status !== 'ready' || !colorModeSupported;
colorModeButton.setAttribute('aria-pressed', String(colorMode === 'monochrome'));
colorModeButton.textContent = t(colorMode === 'monochrome'
? 'cad.toolbar.monochrome'
: 'cad.toolbar.colorSource');
colorModeButton.title = t(colorMode === 'monochrome'
? 'cad.toolbar.colorSource'
: 'cad.toolbar.monochrome');
state.hidden = status === 'ready';
state.classList.toggle('error', status === 'error');
if (status === 'loading') {
Expand Down Expand Up @@ -1240,6 +1268,17 @@ export default async function renderCad(
fitButton.addEventListener('click', () => fitToView());
zoomInButton.addEventListener('click', zoomIn);
zoomOutButton.addEventListener('click', zoomOut);
colorModeButton.addEventListener('click', () => {
const previousMode = colorMode;
colorMode = colorMode === 'monochrome' ? 'source' : 'monochrome';
if (!applyCadViewerColorMode(viewer, colorMode, monochromeColor)) {
colorMode = previousMode;
syncState();
return;
}
syncUi();
emitCadViewStateChange('cad-view-change', 'user');
});

const createViewer = () => {
const { wasmPath, workerUrl, dwfWasmUrl } = resolveFileViewerCadAssetUrls(
Expand Down Expand Up @@ -1284,6 +1323,7 @@ export default async function renderCad(
dwfMaxOverviewStrokeCssPx: options.dwfMaxOverviewStrokeCssPx,
dwfMinTextCssPx: options.dwfMinTextCssPx,
dwfMinFilledAreaCssPx: options.dwfMinFilledAreaCssPx,
...({ colorMode, monochromeColor } as Record<string, unknown>),
autoFit: true,
canvasOptions,
onLoadProgress: updateProgress,
Expand Down
38 changes: 38 additions & 0 deletions packages/renderers/cad/src/colorMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { FileViewerCadColorMode } from '@file-viewer/core';

export interface CadViewerColorModeAdapter {
setColorMode?: (mode: FileViewerCadColorMode, monochromeColor?: string) => void;
getColorMode?: () => FileViewerCadColorMode;
getMonochromeColor?: () => string | undefined;
}

export function normalizeFileViewerCadColorMode(value: unknown): FileViewerCadColorMode {
return value === 'monochrome' ? 'monochrome' : 'source';
}

export function resolveFileViewerCadMonochromeColor(value: unknown, fallback = '#000000'): string {
if (typeof value === 'string' && value.trim()) return value.trim();
return fallback;
}

export function supportsCadViewerColorMode(value: unknown): value is CadViewerColorModeAdapter {
return Boolean(value && typeof (value as CadViewerColorModeAdapter).setColorMode === 'function');
}

export function resolveCadViewerSourceDocument(viewer: unknown): unknown {
const candidate = viewer as {
getSourceDocument?: () => unknown;
getDocument?: () => unknown;
} | null | undefined;
return candidate?.getSourceDocument?.() ?? candidate?.getDocument?.();
}

export function applyCadViewerColorMode(
viewer: unknown,
mode: FileViewerCadColorMode,
monochromeColor: string
): boolean {
if (!supportsCadViewerColorMode(viewer)) return false;
viewer.setColorMode?.(mode, monochromeColor);
return true;
}
9 changes: 9 additions & 0 deletions packages/renderers/cad/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import {
type RendererDefinition,
} from '@file-viewer/core';

export {
applyCadViewerColorMode,
normalizeFileViewerCadColorMode,
resolveCadViewerSourceDocument,
resolveFileViewerCadMonochromeColor,
supportsCadViewerColorMode,
} from './colorMode';
export type { CadViewerColorModeAdapter } from './colorMode';

const cadDefinition = DEFAULT_RENDERER_DEFINITIONS.find(
definition => definition.id === 'cad'
) as RendererDefinition | undefined;
Expand Down
Loading
Loading