From 0a6f4b5c78f887754d383285c2083c34604ca636 Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:44:35 +0800 Subject: [PATCH 01/16] feat: add CAD color mode integration helpers --- packages/renderers/cad/src/colorMode.ts | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 packages/renderers/cad/src/colorMode.ts diff --git a/packages/renderers/cad/src/colorMode.ts b/packages/renderers/cad/src/colorMode.ts new file mode 100644 index 00000000..521c93f7 --- /dev/null +++ b/packages/renderers/cad/src/colorMode.ts @@ -0,0 +1,30 @@ +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 = '#0f172a'): 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 applyCadViewerColorMode( + viewer: unknown, + mode: FileViewerCadColorMode, + monochromeColor: string +): boolean { + if (!supportsCadViewerColorMode(viewer)) return false; + viewer.setColorMode?.(mode, monochromeColor); + return true; +} From d8c34ec0c974a30c56e8ddd18a03bdc37855302f Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:45:58 +0800 Subject: [PATCH 02/16] chore: apply deterministic issue 213 integration --- .github/workflows/apply-issue-213.yml | 203 ++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 .github/workflows/apply-issue-213.yml diff --git a/.github/workflows/apply-issue-213.yml b/.github/workflows/apply-issue-213.yml new file mode 100644 index 00000000..1a28f2ca --- /dev/null +++ b/.github/workflows/apply-issue-213.yml @@ -0,0 +1,203 @@ +name: Apply issue 213 CAD integration + +on: + push: + branches: [feature/issue-213-monochrome-cad] + +permissions: + contents: write + +jobs: + codemod: + if: ${{ !contains(github.event.head_commit.message, '[issue-213-applied]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: feature/issue-213-monochrome-cad + - name: Apply deterministic source changes + run: | + python <<'PY' + from pathlib import Path + + def replace_once(path: str, old: str, new: str) -> None: + file = Path(path) + text = file.read_text(encoding='utf-8') + if new in text: + return + count = text.count(old) + if count != 1: + raise RuntimeError(f'{path}: expected one match, found {count}: {old[:80]!r}') + file.write_text(text.replace(old, new, 1), encoding='utf-8') + + def append_once(path: str, marker: str, content: str) -> None: + file = Path(path) + text = file.read_text(encoding='utf-8') + if marker in text: + return + file.write_text(text.rstrip() + '\n\n' + content.strip() + '\n', encoding='utf-8') + + # Core public contracts and localized toolbar labels. + replace_once( + 'packages/core/src/contracts/types.ts', + " | 'cad.toolbar.zoomIn'\n | 'cad.layers.title'", + " | 'cad.toolbar.zoomIn'\n | 'cad.toolbar.colorSource'\n | 'cad.toolbar.monochrome'\n | 'cad.layers.title'", + ) + replace_once( + 'packages/core/src/contracts/types.ts', + "export type FileViewerCadRenderer = 'auto' | 'webgl' | 'canvas2d';\nexport type FileViewerCadDwfLineWeightMode = 'adaptive' | 'physical' | 'hairline';\nexport type FileViewerCadFitMode = 'best' | 'native';", + "export type FileViewerCadRenderer = 'auto' | 'webgl' | 'canvas2d';\nexport type FileViewerCadDwfLineWeightMode = 'adaptive' | 'physical' | 'hairline';\nexport type FileViewerCadColorMode = 'source' | 'monochrome';\nexport type FileViewerCadFitMode = 'best' | 'native';", + ) + replace_once( + 'packages/core/src/contracts/types.ts', + " renderer?: FileViewerCadRenderer;\n preferDwgWasm?: boolean;", + " renderer?: FileViewerCadRenderer;\n /** Initial CAD color policy. `monochrome` applies one plot color without mutating source data. */\n colorMode?: FileViewerCadColorMode;\n /** CSS color used by monochrome mode. Defaults to the CAD foreground color. */\n monochromeColor?: string;\n /** Shows the CAD toolbar color-mode toggle. Defaults to true. */\n showColorModeToggle?: boolean;\n preferDwgWasm?: boolean;", + ) + + replace_once( + 'packages/core/src/i18n/messages.ts', + " 'cad.toolbar.zoomOut': '缩小',\n 'cad.toolbar.zoomIn': '放大',\n 'cad.layers.title': '图层',", + " 'cad.toolbar.zoomOut': '缩小',\n 'cad.toolbar.zoomIn': '放大',\n 'cad.toolbar.colorSource': '彩色',\n 'cad.toolbar.monochrome': '黑白',\n 'cad.layers.title': '图层',", + ) + replace_once( + 'packages/core/src/i18n/messages.ts', + " 'cad.toolbar.zoomOut': 'Zoom out',\n 'cad.toolbar.zoomIn': 'Zoom in',\n 'cad.layers.title': 'Layers',", + " 'cad.toolbar.zoomOut': 'Zoom out',\n 'cad.toolbar.zoomIn': 'Zoom in',\n 'cad.toolbar.colorSource': 'Source colors',\n 'cad.toolbar.monochrome': 'Monochrome',\n 'cad.layers.title': 'Layers',", + ) + replace_once( + 'packages/core/src/i18n/messages.ja.ts', + " 'cad.toolbar.zoomOut': '縮小',\n 'cad.toolbar.zoomIn': '拡大',\n 'cad.layers.title': 'レイヤー',", + " 'cad.toolbar.zoomOut': '縮小',\n 'cad.toolbar.zoomIn': '拡大',\n 'cad.toolbar.colorSource': '元の色',\n 'cad.toolbar.monochrome': 'モノクロ',\n 'cad.layers.title': 'レイヤー',", + ) + replace_once( + 'packages/core/src/i18n/messages.de.ts', + ' "cad.toolbar.zoomOut": "Herauszoomen",\n "cad.toolbar.zoomIn": "Vergrößern",\n "cad.layers.title": "Schichten",', + ' "cad.toolbar.zoomOut": "Herauszoomen",\n "cad.toolbar.zoomIn": "Vergrößern",\n "cad.toolbar.colorSource": "Originalfarben",\n "cad.toolbar.monochrome": "Monochrom",\n "cad.layers.title": "Schichten",', + ) + + for path in [ + 'packages/core/src/index.ts', + 'packages/core/src/headless.ts', + 'packages/components/vue3/src/package/index.ts', + ]: + replace_once( + path, + ' FileViewerCadDwfLineWeightMode,\n FileViewerCadOptions,', + ' FileViewerCadColorMode,\n FileViewerCadDwfLineWeightMode,\n FileViewerCadOptions,', + ) + + replace_once( + 'packages/components/vue3/src/package/common/type.ts', + ' FileViewerCadDwfLineWeightMode as CoreFileViewerCadDwfLineWeightMode,\n FileViewerCadOptions as CoreFileViewerCadOptions,', + ' FileViewerCadColorMode as CoreFileViewerCadColorMode,\n FileViewerCadDwfLineWeightMode as CoreFileViewerCadDwfLineWeightMode,\n FileViewerCadOptions as CoreFileViewerCadOptions,', + ) + replace_once( + 'packages/components/vue3/src/package/common/type.ts', + 'export type FileViewerCadRenderer = CoreFileViewerCadRenderer;\nexport type FileViewerCadDwfLineWeightMode = CoreFileViewerCadDwfLineWeightMode;', + 'export type FileViewerCadRenderer = CoreFileViewerCadRenderer;\nexport type FileViewerCadColorMode = CoreFileViewerCadColorMode;\nexport type FileViewerCadDwfLineWeightMode = CoreFileViewerCadDwfLineWeightMode;', + ) + + cad = 'packages/renderers/cad/src/cad.ts' + replace_once( + cad, + ' type FileViewerCadOptions,\n type FileViewerFitRequest,', + ' type FileViewerCadColorMode,\n type FileViewerCadOptions,\n type FileViewerFitRequest,', + ) + replace_once( + cad, + "} from '@file-viewer/core';\n\ntype CadStatus = 'loading' | 'ready' | 'error';", + "} from '@file-viewer/core';\nimport {\n applyCadViewerColorMode,\n normalizeFileViewerCadColorMode,\n resolveFileViewerCadMonochromeColor,\n supportsCadViewerColorMode,\n} from './colorMode';\n\ntype CadStatus = 'loading' | 'ready' | 'error';", + ) + replace_once( + cad, + ".cad-tools button:hover{background:rgba(31,150,110,.14);color:#0f8f62}\n.cad-zoom,.cad-meta span{color:#64748b;font-size:12px;font-weight:800;letter-spacing:0}", + ".cad-tools button:hover{background:rgba(31,150,110,.14);color:#0f8f62}\n.cad-tools button.cad-color-mode{min-width:52px;padding:0 10px;font-size:12px}\n.cad-tools button.cad-color-mode[aria-pressed='true']{background:#0f172a;color:#fff}\n.cad-tools button.cad-color-mode:disabled{cursor:not-allowed;opacity:.46}\n.cad-zoom,.cad-meta span{color:#64748b;font-size:12px;font-weight:800;letter-spacing:0}", + ) + replace_once( + cad, + " const t = createFileViewerTranslator(context?.options);\n const locale = resolveFileViewerLocale(context?.options);\n let status: CadStatus = 'loading';", + " const t = createFileViewerTranslator(context?.options);\n const locale = resolveFileViewerLocale(context?.options);\n const monochromeColor = resolveFileViewerCadMonochromeColor(options.monochromeColor);\n let colorMode = normalizeFileViewerCadColorMode(options.colorMode);\n let status: CadStatus = 'loading';", + ) + replace_once( + cad, + " const zoomText = createElement('span', 'cad-zoom', '100%');\n const zoomInButton = createElement('button', undefined, '+') as HTMLButtonElement;\n const meta = createElement('div', 'cad-meta');", + " const zoomText = createElement('span', 'cad-zoom', '100%');\n const zoomInButton = createElement('button', undefined, '+') as HTMLButtonElement;\n const colorModeButton = createElement('button', 'cad-color-mode') as HTMLButtonElement;\n colorModeButton.hidden = options.showColorModeToggle === false;\n const meta = createElement('div', 'cad-meta');", + ) + replace_once( + cad, + " [fitButton, zoomOutButton, zoomInButton].forEach(button => {\n button.type = 'button';\n });\n fitButton.title = t('cad.toolbar.fit');\n zoomOutButton.title = t('cad.toolbar.zoomOut');\n zoomInButton.title = t('cad.toolbar.zoomIn');\n tools.append(fitButton, zoomOutButton, zoomText, zoomInButton);", + " [fitButton, zoomOutButton, zoomInButton, colorModeButton].forEach(button => {\n button.type = 'button';\n });\n fitButton.title = t('cad.toolbar.fit');\n zoomOutButton.title = t('cad.toolbar.zoomOut');\n zoomInButton.title = t('cad.toolbar.zoomIn');\n tools.append(fitButton, zoomOutButton, zoomText, zoomInButton, colorModeButton);", + ) + replace_once( + cad, + " backend: renderStats?.backend,\n layerCount: layers.length,", + " backend: renderStats?.backend,\n colorMode,\n monochromeColor: colorMode === 'monochrome' ? monochromeColor : undefined,\n layerCount: layers.length,", + ) + replace_once( + cad, + " const color = createElement('span', 'cad-layer-color');\n if (typeof layer.color === 'string') {\n color.style.background = layer.color;\n }", + " const color = createElement('span', 'cad-layer-color');\n if (colorMode === 'monochrome') {\n color.style.background = monochromeColor;\n } else if (typeof layer.color === 'string') {\n color.style.background = layer.color;\n }", + ) + replace_once( + cad, + " const syncState = () => {\n zoomText.textContent = `${getZoomPercent()}%`;\n backendMeta.textContent = (renderStats?.backend || 'auto').toUpperCase();", + " const syncState = () => {\n zoomText.textContent = `${getZoomPercent()}%`;\n backendMeta.textContent = (renderStats?.backend || 'auto').toUpperCase();\n const colorModeSupported = supportsCadViewerColorMode(viewer);\n colorModeButton.disabled = status !== 'ready' || !colorModeSupported;\n colorModeButton.setAttribute('aria-pressed', String(colorMode === 'monochrome'));\n colorModeButton.textContent = t(colorMode === 'monochrome'\n ? 'cad.toolbar.monochrome'\n : 'cad.toolbar.colorSource');\n colorModeButton.title = t(colorMode === 'monochrome'\n ? 'cad.toolbar.colorSource'\n : 'cad.toolbar.monochrome');", + ) + replace_once( + cad, + " fitButton.addEventListener('click', () => fitToView());\n zoomInButton.addEventListener('click', zoomIn);\n zoomOutButton.addEventListener('click', zoomOut);", + " fitButton.addEventListener('click', () => fitToView());\n zoomInButton.addEventListener('click', zoomIn);\n zoomOutButton.addEventListener('click', zoomOut);\n colorModeButton.addEventListener('click', () => {\n const previousMode = colorMode;\n colorMode = colorMode === 'monochrome' ? 'source' : 'monochrome';\n if (!applyCadViewerColorMode(viewer, colorMode, monochromeColor)) {\n colorMode = previousMode;\n syncState();\n return;\n }\n syncUi();\n emitCadViewStateChange('cad-view-change', 'user');\n });", + ) + replace_once( + cad, + " dwfMinTextCssPx: options.dwfMinTextCssPx,\n dwfMinFilledAreaCssPx: options.dwfMinFilledAreaCssPx,\n autoFit: true,", + " dwfMinTextCssPx: options.dwfMinTextCssPx,\n dwfMinFilledAreaCssPx: options.dwfMinFilledAreaCssPx,\n ...({ colorMode, monochromeColor } as Record),\n autoFit: true,", + ) + + append_once( + 'packages/renderers/cad/README.md', + '## 黑白出图模式', + ''' + ## 黑白出图模式 + + `cad.colorMode` 可设为 `source` 或 `monochrome`。黑白模式通过 CAD 引擎的实体颜色策略实现,不使用 CSS 滤镜,因此 Canvas、WebGL、文字覆盖层及原生 DWF 渲染可保持一致,同时保留透明度、线型、线宽和源文件数据。 + + ```ts + { + cad: { + colorMode: 'monochrome', + monochromeColor: '#000000', + showColorModeToggle: true, + }, + } + ``` + ''', + ) + append_once( + 'packages/renderers/cad/README.en.md', + '## Monochrome plot mode', + ''' + ## 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, + }, + } + ``` + ''', + ) + PY + + rm .github/workflows/apply-issue-213.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --check + git commit -m "feat: integrate monochrome CAD mode [issue-213-applied]" + git push origin HEAD:feature/issue-213-monochrome-cad From a16baa21197f5a7886b5212cd91a377be44174c5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 06:46:12 +0000 Subject: [PATCH 03/16] feat: integrate monochrome CAD mode [issue-213-applied] --- .github/workflows/apply-issue-213.yml | 203 ------------------ .../vue3/src/package/common/type.ts | 2 + packages/components/vue3/src/package/index.ts | 1 + packages/core/src/contracts/types.ts | 9 + packages/core/src/headless.ts | 1 + packages/core/src/i18n/messages.de.ts | 2 + packages/core/src/i18n/messages.ja.ts | 2 + packages/core/src/i18n/messages.ts | 4 + packages/core/src/index.ts | 1 + packages/renderers/cad/README.en.md | 14 ++ packages/renderers/cad/README.md | 14 ++ packages/renderers/cad/src/cad.ts | 45 +++- 12 files changed, 92 insertions(+), 206 deletions(-) delete mode 100644 .github/workflows/apply-issue-213.yml diff --git a/.github/workflows/apply-issue-213.yml b/.github/workflows/apply-issue-213.yml deleted file mode 100644 index 1a28f2ca..00000000 --- a/.github/workflows/apply-issue-213.yml +++ /dev/null @@ -1,203 +0,0 @@ -name: Apply issue 213 CAD integration - -on: - push: - branches: [feature/issue-213-monochrome-cad] - -permissions: - contents: write - -jobs: - codemod: - if: ${{ !contains(github.event.head_commit.message, '[issue-213-applied]') }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: feature/issue-213-monochrome-cad - - name: Apply deterministic source changes - run: | - python <<'PY' - from pathlib import Path - - def replace_once(path: str, old: str, new: str) -> None: - file = Path(path) - text = file.read_text(encoding='utf-8') - if new in text: - return - count = text.count(old) - if count != 1: - raise RuntimeError(f'{path}: expected one match, found {count}: {old[:80]!r}') - file.write_text(text.replace(old, new, 1), encoding='utf-8') - - def append_once(path: str, marker: str, content: str) -> None: - file = Path(path) - text = file.read_text(encoding='utf-8') - if marker in text: - return - file.write_text(text.rstrip() + '\n\n' + content.strip() + '\n', encoding='utf-8') - - # Core public contracts and localized toolbar labels. - replace_once( - 'packages/core/src/contracts/types.ts', - " | 'cad.toolbar.zoomIn'\n | 'cad.layers.title'", - " | 'cad.toolbar.zoomIn'\n | 'cad.toolbar.colorSource'\n | 'cad.toolbar.monochrome'\n | 'cad.layers.title'", - ) - replace_once( - 'packages/core/src/contracts/types.ts', - "export type FileViewerCadRenderer = 'auto' | 'webgl' | 'canvas2d';\nexport type FileViewerCadDwfLineWeightMode = 'adaptive' | 'physical' | 'hairline';\nexport type FileViewerCadFitMode = 'best' | 'native';", - "export type FileViewerCadRenderer = 'auto' | 'webgl' | 'canvas2d';\nexport type FileViewerCadDwfLineWeightMode = 'adaptive' | 'physical' | 'hairline';\nexport type FileViewerCadColorMode = 'source' | 'monochrome';\nexport type FileViewerCadFitMode = 'best' | 'native';", - ) - replace_once( - 'packages/core/src/contracts/types.ts', - " renderer?: FileViewerCadRenderer;\n preferDwgWasm?: boolean;", - " renderer?: FileViewerCadRenderer;\n /** Initial CAD color policy. `monochrome` applies one plot color without mutating source data. */\n colorMode?: FileViewerCadColorMode;\n /** CSS color used by monochrome mode. Defaults to the CAD foreground color. */\n monochromeColor?: string;\n /** Shows the CAD toolbar color-mode toggle. Defaults to true. */\n showColorModeToggle?: boolean;\n preferDwgWasm?: boolean;", - ) - - replace_once( - 'packages/core/src/i18n/messages.ts', - " 'cad.toolbar.zoomOut': '缩小',\n 'cad.toolbar.zoomIn': '放大',\n 'cad.layers.title': '图层',", - " 'cad.toolbar.zoomOut': '缩小',\n 'cad.toolbar.zoomIn': '放大',\n 'cad.toolbar.colorSource': '彩色',\n 'cad.toolbar.monochrome': '黑白',\n 'cad.layers.title': '图层',", - ) - replace_once( - 'packages/core/src/i18n/messages.ts', - " 'cad.toolbar.zoomOut': 'Zoom out',\n 'cad.toolbar.zoomIn': 'Zoom in',\n 'cad.layers.title': 'Layers',", - " 'cad.toolbar.zoomOut': 'Zoom out',\n 'cad.toolbar.zoomIn': 'Zoom in',\n 'cad.toolbar.colorSource': 'Source colors',\n 'cad.toolbar.monochrome': 'Monochrome',\n 'cad.layers.title': 'Layers',", - ) - replace_once( - 'packages/core/src/i18n/messages.ja.ts', - " 'cad.toolbar.zoomOut': '縮小',\n 'cad.toolbar.zoomIn': '拡大',\n 'cad.layers.title': 'レイヤー',", - " 'cad.toolbar.zoomOut': '縮小',\n 'cad.toolbar.zoomIn': '拡大',\n 'cad.toolbar.colorSource': '元の色',\n 'cad.toolbar.monochrome': 'モノクロ',\n 'cad.layers.title': 'レイヤー',", - ) - replace_once( - 'packages/core/src/i18n/messages.de.ts', - ' "cad.toolbar.zoomOut": "Herauszoomen",\n "cad.toolbar.zoomIn": "Vergrößern",\n "cad.layers.title": "Schichten",', - ' "cad.toolbar.zoomOut": "Herauszoomen",\n "cad.toolbar.zoomIn": "Vergrößern",\n "cad.toolbar.colorSource": "Originalfarben",\n "cad.toolbar.monochrome": "Monochrom",\n "cad.layers.title": "Schichten",', - ) - - for path in [ - 'packages/core/src/index.ts', - 'packages/core/src/headless.ts', - 'packages/components/vue3/src/package/index.ts', - ]: - replace_once( - path, - ' FileViewerCadDwfLineWeightMode,\n FileViewerCadOptions,', - ' FileViewerCadColorMode,\n FileViewerCadDwfLineWeightMode,\n FileViewerCadOptions,', - ) - - replace_once( - 'packages/components/vue3/src/package/common/type.ts', - ' FileViewerCadDwfLineWeightMode as CoreFileViewerCadDwfLineWeightMode,\n FileViewerCadOptions as CoreFileViewerCadOptions,', - ' FileViewerCadColorMode as CoreFileViewerCadColorMode,\n FileViewerCadDwfLineWeightMode as CoreFileViewerCadDwfLineWeightMode,\n FileViewerCadOptions as CoreFileViewerCadOptions,', - ) - replace_once( - 'packages/components/vue3/src/package/common/type.ts', - 'export type FileViewerCadRenderer = CoreFileViewerCadRenderer;\nexport type FileViewerCadDwfLineWeightMode = CoreFileViewerCadDwfLineWeightMode;', - 'export type FileViewerCadRenderer = CoreFileViewerCadRenderer;\nexport type FileViewerCadColorMode = CoreFileViewerCadColorMode;\nexport type FileViewerCadDwfLineWeightMode = CoreFileViewerCadDwfLineWeightMode;', - ) - - cad = 'packages/renderers/cad/src/cad.ts' - replace_once( - cad, - ' type FileViewerCadOptions,\n type FileViewerFitRequest,', - ' type FileViewerCadColorMode,\n type FileViewerCadOptions,\n type FileViewerFitRequest,', - ) - replace_once( - cad, - "} from '@file-viewer/core';\n\ntype CadStatus = 'loading' | 'ready' | 'error';", - "} from '@file-viewer/core';\nimport {\n applyCadViewerColorMode,\n normalizeFileViewerCadColorMode,\n resolveFileViewerCadMonochromeColor,\n supportsCadViewerColorMode,\n} from './colorMode';\n\ntype CadStatus = 'loading' | 'ready' | 'error';", - ) - replace_once( - cad, - ".cad-tools button:hover{background:rgba(31,150,110,.14);color:#0f8f62}\n.cad-zoom,.cad-meta span{color:#64748b;font-size:12px;font-weight:800;letter-spacing:0}", - ".cad-tools button:hover{background:rgba(31,150,110,.14);color:#0f8f62}\n.cad-tools button.cad-color-mode{min-width:52px;padding:0 10px;font-size:12px}\n.cad-tools button.cad-color-mode[aria-pressed='true']{background:#0f172a;color:#fff}\n.cad-tools button.cad-color-mode:disabled{cursor:not-allowed;opacity:.46}\n.cad-zoom,.cad-meta span{color:#64748b;font-size:12px;font-weight:800;letter-spacing:0}", - ) - replace_once( - cad, - " const t = createFileViewerTranslator(context?.options);\n const locale = resolveFileViewerLocale(context?.options);\n let status: CadStatus = 'loading';", - " const t = createFileViewerTranslator(context?.options);\n const locale = resolveFileViewerLocale(context?.options);\n const monochromeColor = resolveFileViewerCadMonochromeColor(options.monochromeColor);\n let colorMode = normalizeFileViewerCadColorMode(options.colorMode);\n let status: CadStatus = 'loading';", - ) - replace_once( - cad, - " const zoomText = createElement('span', 'cad-zoom', '100%');\n const zoomInButton = createElement('button', undefined, '+') as HTMLButtonElement;\n const meta = createElement('div', 'cad-meta');", - " const zoomText = createElement('span', 'cad-zoom', '100%');\n const zoomInButton = createElement('button', undefined, '+') as HTMLButtonElement;\n const colorModeButton = createElement('button', 'cad-color-mode') as HTMLButtonElement;\n colorModeButton.hidden = options.showColorModeToggle === false;\n const meta = createElement('div', 'cad-meta');", - ) - replace_once( - cad, - " [fitButton, zoomOutButton, zoomInButton].forEach(button => {\n button.type = 'button';\n });\n fitButton.title = t('cad.toolbar.fit');\n zoomOutButton.title = t('cad.toolbar.zoomOut');\n zoomInButton.title = t('cad.toolbar.zoomIn');\n tools.append(fitButton, zoomOutButton, zoomText, zoomInButton);", - " [fitButton, zoomOutButton, zoomInButton, colorModeButton].forEach(button => {\n button.type = 'button';\n });\n fitButton.title = t('cad.toolbar.fit');\n zoomOutButton.title = t('cad.toolbar.zoomOut');\n zoomInButton.title = t('cad.toolbar.zoomIn');\n tools.append(fitButton, zoomOutButton, zoomText, zoomInButton, colorModeButton);", - ) - replace_once( - cad, - " backend: renderStats?.backend,\n layerCount: layers.length,", - " backend: renderStats?.backend,\n colorMode,\n monochromeColor: colorMode === 'monochrome' ? monochromeColor : undefined,\n layerCount: layers.length,", - ) - replace_once( - cad, - " const color = createElement('span', 'cad-layer-color');\n if (typeof layer.color === 'string') {\n color.style.background = layer.color;\n }", - " const color = createElement('span', 'cad-layer-color');\n if (colorMode === 'monochrome') {\n color.style.background = monochromeColor;\n } else if (typeof layer.color === 'string') {\n color.style.background = layer.color;\n }", - ) - replace_once( - cad, - " const syncState = () => {\n zoomText.textContent = `${getZoomPercent()}%`;\n backendMeta.textContent = (renderStats?.backend || 'auto').toUpperCase();", - " const syncState = () => {\n zoomText.textContent = `${getZoomPercent()}%`;\n backendMeta.textContent = (renderStats?.backend || 'auto').toUpperCase();\n const colorModeSupported = supportsCadViewerColorMode(viewer);\n colorModeButton.disabled = status !== 'ready' || !colorModeSupported;\n colorModeButton.setAttribute('aria-pressed', String(colorMode === 'monochrome'));\n colorModeButton.textContent = t(colorMode === 'monochrome'\n ? 'cad.toolbar.monochrome'\n : 'cad.toolbar.colorSource');\n colorModeButton.title = t(colorMode === 'monochrome'\n ? 'cad.toolbar.colorSource'\n : 'cad.toolbar.monochrome');", - ) - replace_once( - cad, - " fitButton.addEventListener('click', () => fitToView());\n zoomInButton.addEventListener('click', zoomIn);\n zoomOutButton.addEventListener('click', zoomOut);", - " fitButton.addEventListener('click', () => fitToView());\n zoomInButton.addEventListener('click', zoomIn);\n zoomOutButton.addEventListener('click', zoomOut);\n colorModeButton.addEventListener('click', () => {\n const previousMode = colorMode;\n colorMode = colorMode === 'monochrome' ? 'source' : 'monochrome';\n if (!applyCadViewerColorMode(viewer, colorMode, monochromeColor)) {\n colorMode = previousMode;\n syncState();\n return;\n }\n syncUi();\n emitCadViewStateChange('cad-view-change', 'user');\n });", - ) - replace_once( - cad, - " dwfMinTextCssPx: options.dwfMinTextCssPx,\n dwfMinFilledAreaCssPx: options.dwfMinFilledAreaCssPx,\n autoFit: true,", - " dwfMinTextCssPx: options.dwfMinTextCssPx,\n dwfMinFilledAreaCssPx: options.dwfMinFilledAreaCssPx,\n ...({ colorMode, monochromeColor } as Record),\n autoFit: true,", - ) - - append_once( - 'packages/renderers/cad/README.md', - '## 黑白出图模式', - ''' - ## 黑白出图模式 - - `cad.colorMode` 可设为 `source` 或 `monochrome`。黑白模式通过 CAD 引擎的实体颜色策略实现,不使用 CSS 滤镜,因此 Canvas、WebGL、文字覆盖层及原生 DWF 渲染可保持一致,同时保留透明度、线型、线宽和源文件数据。 - - ```ts - { - cad: { - colorMode: 'monochrome', - monochromeColor: '#000000', - showColorModeToggle: true, - }, - } - ``` - ''', - ) - append_once( - 'packages/renderers/cad/README.en.md', - '## Monochrome plot mode', - ''' - ## 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, - }, - } - ``` - ''', - ) - PY - - rm .github/workflows/apply-issue-213.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --check - git commit -m "feat: integrate monochrome CAD mode [issue-213-applied]" - git push origin HEAD:feature/issue-213-monochrome-cad diff --git a/packages/components/vue3/src/package/common/type.ts b/packages/components/vue3/src/package/common/type.ts index e7334088..ce5b37e6 100644 --- a/packages/components/vue3/src/package/common/type.ts +++ b/packages/components/vue3/src/package/common/type.ts @@ -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, @@ -110,6 +111,7 @@ export type FileViewerSpreadsheetOptions = CoreFileViewerSpreadsheetOptions; export type FileViewerTypstOptions = CoreFileViewerTypstOptions; export type FileViewerCadRenderer = CoreFileViewerCadRenderer; +export type FileViewerCadColorMode = CoreFileViewerCadColorMode; export type FileViewerCadDwfLineWeightMode = CoreFileViewerCadDwfLineWeightMode; /** diff --git a/packages/components/vue3/src/package/index.ts b/packages/components/vue3/src/package/index.ts index dca994c4..27acc31c 100644 --- a/packages/components/vue3/src/package/index.ts +++ b/packages/components/vue3/src/package/index.ts @@ -107,6 +107,7 @@ export type { FileViewerApplyViewStateOptions, FileViewerArchiveOptions, FileViewerBeforeOperation, + FileViewerCadColorMode, FileViewerCadDwfLineWeightMode, FileViewerCadOptions, FileViewerCadRenderer, diff --git a/packages/core/src/contracts/types.ts b/packages/core/src/contracts/types.ts index 1aae40d9..1c105723 100644 --- a/packages/core/src/contracts/types.ts +++ b/packages/core/src/contracts/types.ts @@ -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' @@ -1108,6 +1110,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 { @@ -1118,6 +1121,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; diff --git a/packages/core/src/headless.ts b/packages/core/src/headless.ts index 2e63f6e2..f26c708c 100644 --- a/packages/core/src/headless.ts +++ b/packages/core/src/headless.ts @@ -450,6 +450,7 @@ export type { FileViewerArchiveOptions, FileViewerApplyViewStateOptions, FileViewerBeforeOperation, + FileViewerCadColorMode, FileViewerCadDwfLineWeightMode, FileViewerCadOptions, FileViewerCadRenderer, diff --git a/packages/core/src/i18n/messages.de.ts b/packages/core/src/i18n/messages.de.ts index f74a5ca1..3baf1d27 100644 --- a/packages/core/src/i18n/messages.de.ts +++ b/packages/core/src/i18n/messages.de.ts @@ -348,6 +348,8 @@ export const DE_DE_MESSAGES: Record = { "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)", diff --git a/packages/core/src/i18n/messages.ja.ts b/packages/core/src/i18n/messages.ja.ts index be9197af..5cbf7e2d 100644 --- a/packages/core/src/i18n/messages.ja.ts +++ b/packages/core/src/i18n/messages.ja.ts @@ -349,6 +349,8 @@ export const JA_JP_MESSAGES: Record = { '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} 件を統合)', diff --git a/packages/core/src/i18n/messages.ts b/packages/core/src/i18n/messages.ts index fccd9f0f..01a1658d 100644 --- a/packages/core/src/i18n/messages.ts +++ b/packages/core/src/i18n/messages.ts @@ -367,6 +367,8 @@ const ZH_CN_MESSAGES: Record = { '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} 个同名图层)', @@ -822,6 +824,8 @@ const EN_US_MESSAGES: Record = { '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)', diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 3e294c56..c35f6f5e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1105,6 +1105,7 @@ export type { FileViewerArchiveOptions, FileViewerApplyViewStateOptions, FileViewerBeforeOperation, + FileViewerCadColorMode, FileViewerCadDwfLineWeightMode, FileViewerCadOptions, FileViewerCadRenderer, diff --git a/packages/renderers/cad/README.en.md b/packages/renderers/cad/README.en.md index 7a89141a..727c6bed 100644 --- a/packages/renderers/cad/README.en.md +++ b/packages/renderers/cad/README.en.md @@ -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, + }, + } + ``` diff --git a/packages/renderers/cad/README.md b/packages/renderers/cad/README.md index f60b6d10..b90cd3a7 100644 --- a/packages/renderers/cad/README.md +++ b/packages/renderers/cad/README.md @@ -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, + }, + } + ``` diff --git a/packages/renderers/cad/src/cad.ts b/packages/renderers/cad/src/cad.ts index 072e21c6..0b97b4e4 100644 --- a/packages/renderers/cad/src/cad.ts +++ b/packages/renderers/cad/src/cad.ts @@ -31,6 +31,7 @@ import { unregisterFileViewerZoomProvider, type FileViewerApplyViewStateOptions, type FileRenderContext, + type FileViewerCadColorMode, type FileViewerCadOptions, type FileViewerFitRequest, type FileViewerFitResult, @@ -40,6 +41,12 @@ import { type FileViewerViewStateChangeSource, type FileViewerZoomState, } from '@file-viewer/core'; +import { + applyCadViewerColorMode, + normalizeFileViewerCadColorMode, + resolveFileViewerCadMonochromeColor, + supportsCadViewerColorMode, +} from './colorMode'; type CadStatus = 'loading' | 'ready' | 'error'; @@ -63,6 +70,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} @@ -791,6 +801,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 = ''; @@ -817,16 +829,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); @@ -905,6 +919,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) @@ -1051,7 +1067,9 @@ 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)); @@ -1085,6 +1103,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') { @@ -1240,6 +1267,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( @@ -1284,6 +1322,7 @@ export default async function renderCad( dwfMaxOverviewStrokeCssPx: options.dwfMaxOverviewStrokeCssPx, dwfMinTextCssPx: options.dwfMinTextCssPx, dwfMinFilledAreaCssPx: options.dwfMinFilledAreaCssPx, + ...({ colorMode, monochromeColor } as Record), autoFit: true, canvasOptions, onLoadProgress: updateProgress, From 29401330f719511f8bdcb35b652c15ac6401e0f4 Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:50:47 +0800 Subject: [PATCH 04/16] test: verify File Viewer CAD color mode integration --- .../cad/scripts/verify-color-mode.mjs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/renderers/cad/scripts/verify-color-mode.mjs diff --git a/packages/renderers/cad/scripts/verify-color-mode.mjs b/packages/renderers/cad/scripts/verify-color-mode.mjs new file mode 100644 index 00000000..0d4a76dd --- /dev/null +++ b/packages/renderers/cad/scripts/verify-color-mode.mjs @@ -0,0 +1,28 @@ +import assert from 'node:assert/strict'; + +import { + applyCadViewerColorMode, + normalizeFileViewerCadColorMode, + 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), '#0f172a'); + +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); + +console.log('CAD color mode integration checks passed.'); From f1a88b86b0b1b232b46054614987a81582fedc97 Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:50:59 +0800 Subject: [PATCH 05/16] feat: export CAD color mode integration helpers --- packages/renderers/cad/src/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/renderers/cad/src/index.ts b/packages/renderers/cad/src/index.ts index c2a29279..48783ddf 100644 --- a/packages/renderers/cad/src/index.ts +++ b/packages/renderers/cad/src/index.ts @@ -6,6 +6,14 @@ import { type RendererDefinition, } from '@file-viewer/core'; +export { + applyCadViewerColorMode, + normalizeFileViewerCadColorMode, + resolveFileViewerCadMonochromeColor, + supportsCadViewerColorMode, +} from './colorMode'; +export type { CadViewerColorModeAdapter } from './colorMode'; + const cadDefinition = DEFAULT_RENDERER_DEFINITIONS.find( definition => definition.id === 'cad' ) as RendererDefinition | undefined; From e0e11d36d4114c838214b4cd3127a523f6c6ea52 Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:51:12 +0800 Subject: [PATCH 06/16] test: expose CAD color mode verification command --- packages/renderers/cad/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/renderers/cad/package.json b/packages/renderers/cad/package.json index b90e2104..3cf8b721 100644 --- a/packages/renderers/cad/package.json +++ b/packages/renderers/cad/package.json @@ -19,7 +19,7 @@ ], "publishConfig": { "access": "public", - "registry": "https://registry.npmjs.org/" + "registry": "https://registry.npmjs.org" }, "author": { "name": "Yu Wang", @@ -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.0", From 9501e69656f45bd1fb16e759684aa7d68f08615f Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:51:29 +0800 Subject: [PATCH 07/16] ci: verify CAD monochrome integration --- .github/workflows/public-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/public-ci.yml b/.github/workflows/public-ci.yml index c1a9ec67..c3eb87ee 100644 --- a/.github/workflows/public-ci.yml +++ b/.github/workflows/public-ci.yml @@ -51,6 +51,9 @@ jobs: - name: Build source, components and demo run: pnpm build + - name: Verify CAD color mode integration + run: pnpm --filter @file-viewer/renderer-cad verify:color-mode + - name: Verify PPTX generated styles run: pnpm exec vitest run test/pptx-generated-style-regression.spec.ts From 13c7fd896acdc83460fbe28b84f18d40172dcf8b Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:52:41 +0800 Subject: [PATCH 08/16] docs: fix monochrome example formatting --- packages/renderers/cad/README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/renderers/cad/README.md b/packages/renderers/cad/README.md index b90cd3a7..d6373bdf 100644 --- a/packages/renderers/cad/README.md +++ b/packages/renderers/cad/README.md @@ -52,14 +52,14 @@ CAD 预览已经从 `@file-viewer/core` 中彻底移出,core 只保留资产 m ## 黑白出图模式 - `cad.colorMode` 可设为 `source` 或 `monochrome`。黑白模式通过 CAD 引擎的实体颜色策略实现,不使用 CSS 滤镜,因此 Canvas、WebGL、文字覆盖层及原生 DWF 渲染可保持一致,同时保留透明度、线型、线宽和源文件数据。 - - ```ts - { - cad: { - colorMode: 'monochrome', - monochromeColor: '#000000', - showColorModeToggle: true, - }, - } - ``` +`cad.colorMode` 可设为 `source` 或 `monochrome`。黑白模式通过 CAD 引擎的实体颜色策略实现,不使用 CSS 滤镜,因此 Canvas、WebGL、文字覆盖层及原生 DWF 渲染可保持一致,同时保留透明度、线型、线宽和源文件数据。 + +```ts +{ + cad: { + colorMode: 'monochrome', + monochromeColor: '#000000', + showColorModeToggle: true, + }, +} +``` From de69884172d8a3f8e72fb84b139fcf3281905479 Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:52:55 +0800 Subject: [PATCH 09/16] docs: fix English monochrome example formatting --- packages/renderers/cad/README.en.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/renderers/cad/README.en.md b/packages/renderers/cad/README.en.md index 727c6bed..bb7c2859 100644 --- a/packages/renderers/cad/README.en.md +++ b/packages/renderers/cad/README.en.md @@ -52,14 +52,14 @@ CAD preview has moved completely out of `@file-viewer/core`. Core now only keeps ## 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, - }, - } - ``` +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, + }, +} +``` From daf630424c0ed7490ed2e398207559e7b1517fc8 Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:53:09 +0800 Subject: [PATCH 10/16] chore: preserve the existing npm registry URL --- packages/renderers/cad/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/renderers/cad/package.json b/packages/renderers/cad/package.json index 3cf8b721..5501ffa0 100644 --- a/packages/renderers/cad/package.json +++ b/packages/renderers/cad/package.json @@ -19,7 +19,7 @@ ], "publishConfig": { "access": "public", - "registry": "https://registry.npmjs.org" + "registry": "https://registry.npmjs.org/" }, "author": { "name": "Yu Wang", From 733f6a934abecfe3a3dc1243ba11221a6130086f Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:58:12 +0800 Subject: [PATCH 11/16] chore: apply CAD source-document integration fix --- .../apply-issue-213-source-document-fix.yml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/apply-issue-213-source-document-fix.yml diff --git a/.github/workflows/apply-issue-213-source-document-fix.yml b/.github/workflows/apply-issue-213-source-document-fix.yml new file mode 100644 index 00000000..85167f5c --- /dev/null +++ b/.github/workflows/apply-issue-213-source-document-fix.yml @@ -0,0 +1,65 @@ +name: Apply issue 213 source-document fix + +on: + push: + branches: [feature/issue-213-monochrome-cad] + +permissions: + contents: write + +jobs: + patch: + if: ${{ !contains(github.event.head_commit.message, '[source-document-fix-applied]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: feature/issue-213-monochrome-cad + - name: Apply deterministic fix + run: | + python <<'PY' + from pathlib import Path + + helper = Path('packages/renderers/cad/src/colorMode.ts') + text = helper.read_text(encoding='utf-8') + marker = 'export function resolveCadViewerSourceDocument' + if marker not in text: + insertion = ''' + + export function resolveCadViewerSourceDocument(viewer: unknown): unknown { + const candidate = viewer as { + getSourceDocument?: () => unknown; + getDocument?: () => unknown; + } | null | undefined; + return candidate?.getSourceDocument?.() ?? candidate?.getDocument?.(); + } + ''' + anchor = '\nexport function applyCadViewerColorMode(' + if text.count(anchor) != 1: + raise RuntimeError('Unable to locate color-mode helper insertion point') + helper.write_text(text.replace(anchor, insertion + anchor, 1), encoding='utf-8') + + cad = Path('packages/renderers/cad/src/cad.ts') + text = cad.read_text(encoding='utf-8') + old_import = ' resolveFileViewerCadMonochromeColor,\n supportsCadViewerColorMode,' + new_import = ' resolveFileViewerCadMonochromeColor,\n resolveCadViewerSourceDocument,\n supportsCadViewerColorMode,' + if new_import not in text: + if text.count(old_import) != 1: + raise RuntimeError('Unable to locate CAD color-mode import list') + text = text.replace(old_import, new_import, 1) + old = ' const document = viewer?.getDocument();' + new = ' const document = resolveCadViewerSourceDocument(viewer) as CadDocument | undefined;' + if new not in text: + if text.count(old) != 1: + raise RuntimeError(f'Expected one layer source-document lookup, found {text.count(old)}') + text = text.replace(old, new, 1) + cad.write_text(text, encoding='utf-8') + PY + + rm .github/workflows/apply-issue-213-source-document-fix.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --check + git commit -m "fix: preserve parser-owned CAD source on layer toggles [source-document-fix-applied]" + git push origin HEAD:feature/issue-213-monochrome-cad From 2c714cc3dc986ae755796ad7445222481337ba52 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 06:58:29 +0000 Subject: [PATCH 12/16] fix: preserve parser-owned CAD source on layer toggles [source-document-fix-applied] --- .../apply-issue-213-source-document-fix.yml | 65 ------------------- packages/renderers/cad/src/cad.ts | 3 +- packages/renderers/cad/src/colorMode.ts | 9 +++ 3 files changed, 11 insertions(+), 66 deletions(-) delete mode 100644 .github/workflows/apply-issue-213-source-document-fix.yml diff --git a/.github/workflows/apply-issue-213-source-document-fix.yml b/.github/workflows/apply-issue-213-source-document-fix.yml deleted file mode 100644 index 85167f5c..00000000 --- a/.github/workflows/apply-issue-213-source-document-fix.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Apply issue 213 source-document fix - -on: - push: - branches: [feature/issue-213-monochrome-cad] - -permissions: - contents: write - -jobs: - patch: - if: ${{ !contains(github.event.head_commit.message, '[source-document-fix-applied]') }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: feature/issue-213-monochrome-cad - - name: Apply deterministic fix - run: | - python <<'PY' - from pathlib import Path - - helper = Path('packages/renderers/cad/src/colorMode.ts') - text = helper.read_text(encoding='utf-8') - marker = 'export function resolveCadViewerSourceDocument' - if marker not in text: - insertion = ''' - - export function resolveCadViewerSourceDocument(viewer: unknown): unknown { - const candidate = viewer as { - getSourceDocument?: () => unknown; - getDocument?: () => unknown; - } | null | undefined; - return candidate?.getSourceDocument?.() ?? candidate?.getDocument?.(); - } - ''' - anchor = '\nexport function applyCadViewerColorMode(' - if text.count(anchor) != 1: - raise RuntimeError('Unable to locate color-mode helper insertion point') - helper.write_text(text.replace(anchor, insertion + anchor, 1), encoding='utf-8') - - cad = Path('packages/renderers/cad/src/cad.ts') - text = cad.read_text(encoding='utf-8') - old_import = ' resolveFileViewerCadMonochromeColor,\n supportsCadViewerColorMode,' - new_import = ' resolveFileViewerCadMonochromeColor,\n resolveCadViewerSourceDocument,\n supportsCadViewerColorMode,' - if new_import not in text: - if text.count(old_import) != 1: - raise RuntimeError('Unable to locate CAD color-mode import list') - text = text.replace(old_import, new_import, 1) - old = ' const document = viewer?.getDocument();' - new = ' const document = resolveCadViewerSourceDocument(viewer) as CadDocument | undefined;' - if new not in text: - if text.count(old) != 1: - raise RuntimeError(f'Expected one layer source-document lookup, found {text.count(old)}') - text = text.replace(old, new, 1) - cad.write_text(text, encoding='utf-8') - PY - - rm .github/workflows/apply-issue-213-source-document-fix.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --check - git commit -m "fix: preserve parser-owned CAD source on layer toggles [source-document-fix-applied]" - git push origin HEAD:feature/issue-213-monochrome-cad diff --git a/packages/renderers/cad/src/cad.ts b/packages/renderers/cad/src/cad.ts index 0b97b4e4..bbae4fe6 100644 --- a/packages/renderers/cad/src/cad.ts +++ b/packages/renderers/cad/src/cad.ts @@ -45,6 +45,7 @@ import { applyCadViewerColorMode, normalizeFileViewerCadColorMode, resolveFileViewerCadMonochromeColor, + resolveCadViewerSourceDocument, supportsCadViewerColorMode, } from './colorMode'; @@ -1074,7 +1075,7 @@ export default async function renderCad( } 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; } diff --git a/packages/renderers/cad/src/colorMode.ts b/packages/renderers/cad/src/colorMode.ts index 521c93f7..1fb9bc70 100644 --- a/packages/renderers/cad/src/colorMode.ts +++ b/packages/renderers/cad/src/colorMode.ts @@ -19,6 +19,15 @@ export function supportsCadViewerColorMode(value: unknown): value is CadViewerCo 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, From 0ced292a44ea6ca9e8079751df8dd3664b2b3ea3 Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:59:43 +0800 Subject: [PATCH 13/16] test: preserve parser-owned CAD document during layer updates --- packages/renderers/cad/scripts/verify-color-mode.mjs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/renderers/cad/scripts/verify-color-mode.mjs b/packages/renderers/cad/scripts/verify-color-mode.mjs index 0d4a76dd..c8b00be6 100644 --- a/packages/renderers/cad/scripts/verify-color-mode.mjs +++ b/packages/renderers/cad/scripts/verify-color-mode.mjs @@ -3,6 +3,7 @@ import assert from 'node:assert/strict'; import { applyCadViewerColorMode, normalizeFileViewerCadColorMode, + resolveCadViewerSourceDocument, resolveFileViewerCadMonochromeColor, supportsCadViewerColorMode, } from '../dist/colorMode.js'; @@ -25,4 +26,15 @@ 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.'); From aebb49ce11509cef7d0e59944402e7415ed93ffe Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:00:02 +0800 Subject: [PATCH 14/16] feat: export parser-owned CAD document resolver --- packages/renderers/cad/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/renderers/cad/src/index.ts b/packages/renderers/cad/src/index.ts index 48783ddf..1b03e5f8 100644 --- a/packages/renderers/cad/src/index.ts +++ b/packages/renderers/cad/src/index.ts @@ -9,6 +9,7 @@ import { export { applyCadViewerColorMode, normalizeFileViewerCadColorMode, + resolveCadViewerSourceDocument, resolveFileViewerCadMonochromeColor, supportsCadViewerColorMode, } from './colorMode'; From 485d9ac6cc3462c562a851345cb246330470f938 Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:02:32 +0800 Subject: [PATCH 15/16] fix: default File Viewer monochrome output to black --- packages/renderers/cad/src/colorMode.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/renderers/cad/src/colorMode.ts b/packages/renderers/cad/src/colorMode.ts index 1fb9bc70..7511ecd5 100644 --- a/packages/renderers/cad/src/colorMode.ts +++ b/packages/renderers/cad/src/colorMode.ts @@ -10,7 +10,7 @@ export function normalizeFileViewerCadColorMode(value: unknown): FileViewerCadCo return value === 'monochrome' ? 'monochrome' : 'source'; } -export function resolveFileViewerCadMonochromeColor(value: unknown, fallback = '#0f172a'): string { +export function resolveFileViewerCadMonochromeColor(value: unknown, fallback = '#000000'): string { if (typeof value === 'string' && value.trim()) return value.trim(); return fallback; } @@ -19,7 +19,6 @@ export function supportsCadViewerColorMode(value: unknown): value is CadViewerCo return Boolean(value && typeof (value as CadViewerColorModeAdapter).setColorMode === 'function'); } - export function resolveCadViewerSourceDocument(viewer: unknown): unknown { const candidate = viewer as { getSourceDocument?: () => unknown; From 0192fa7ab20f3525db64f2e1eecd30b035dca0e4 Mon Sep 17 00:00:00 2001 From: Brownie Woom <161313394+brownie-cake@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:02:43 +0800 Subject: [PATCH 16/16] test: assert black monochrome default --- packages/renderers/cad/scripts/verify-color-mode.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/renderers/cad/scripts/verify-color-mode.mjs b/packages/renderers/cad/scripts/verify-color-mode.mjs index c8b00be6..9aaccd77 100644 --- a/packages/renderers/cad/scripts/verify-color-mode.mjs +++ b/packages/renderers/cad/scripts/verify-color-mode.mjs @@ -12,7 +12,7 @@ assert.equal(normalizeFileViewerCadColorMode('monochrome'), 'monochrome'); assert.equal(normalizeFileViewerCadColorMode('source'), 'source'); assert.equal(normalizeFileViewerCadColorMode('unexpected'), 'source'); assert.equal(resolveFileViewerCadMonochromeColor(' #101820 '), '#101820'); -assert.equal(resolveFileViewerCadMonochromeColor(undefined), '#0f172a'); +assert.equal(resolveFileViewerCadMonochromeColor(undefined), '#000000'); const calls = []; const viewer = {