Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .github/workflows/public-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

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 @@ -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 {
Expand All @@ -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;
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 @@ -450,6 +450,7 @@ export type {
FileViewerArchiveOptions,
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 @@ -1105,6 +1105,7 @@ export type {
FileViewerArchiveOptions,
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.0",
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