omni-viewer-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/THIRD_PARTY_NOTICES.md +49 -0
- package/dist/assets/audio-engine/audio_engine.mjs +2 -0
- package/dist/assets/audio-engine/audio_engine.wasm +0 -0
- package/dist/assets/pdfjs/pdf.worker.min.mjs +21 -0
- package/dist/host/index.d.ts +133 -0
- package/dist/host/index.js +10 -0
- package/dist/i18n/catalog.en.d.ts +1 -0
- package/dist/i18n/catalog.en.js +411 -0
- package/dist/i18n/catalog.ko.d.ts +1 -0
- package/dist/i18n/catalog.ko.js +68 -0
- package/dist/i18n/index.d.ts +18 -0
- package/dist/i18n/index.js +41 -0
- package/dist/licenses/emscripten.txt +101 -0
- package/dist/licenses/kissfft.txt +28 -0
- package/dist/licenses/pdfjs-dist.txt +177 -0
- package/dist/parsers/a2l/index.d.ts +1 -0
- package/dist/parsers/a2l/index.js +1 -0
- package/dist/parsers/archive/index.d.ts +22 -0
- package/dist/parsers/archive/index.js +46 -0
- package/dist/parsers/archive/model.d.ts +29 -0
- package/dist/parsers/archive/model.js +1 -0
- package/dist/parsers/archive/worker.d.ts +21 -0
- package/dist/parsers/archive/worker.js +1 -0
- package/dist/parsers/arxml/index.d.ts +1 -0
- package/dist/parsers/arxml/index.js +1 -0
- package/dist/parsers/asc/index.d.ts +1 -0
- package/dist/parsers/asc/index.js +1 -0
- package/dist/parsers/audio/index.d.ts +9 -0
- package/dist/parsers/audio/index.js +46 -0
- package/dist/parsers/automotive/index.d.ts +33 -0
- package/dist/parsers/automotive/index.js +973 -0
- package/dist/parsers/blf/index.d.ts +1 -0
- package/dist/parsers/blf/index.js +1 -0
- package/dist/parsers/csv/delimiter.d.ts +31 -0
- package/dist/parsers/csv/delimiter.js +151 -0
- package/dist/parsers/csv/index.d.ts +30 -0
- package/dist/parsers/csv/index.js +108 -0
- package/dist/parsers/csv/parseText.d.ts +20 -0
- package/dist/parsers/csv/parseText.js +108 -0
- package/dist/parsers/csv/sort.d.ts +26 -0
- package/dist/parsers/csv/sort.js +224 -0
- package/dist/parsers/csv/statistics.d.ts +49 -0
- package/dist/parsers/csv/statistics.js +150 -0
- package/dist/parsers/dbc/index.d.ts +56 -0
- package/dist/parsers/dbc/index.js +94 -0
- package/dist/parsers/doc-binary/index.d.ts +35 -0
- package/dist/parsers/doc-binary/index.js +267 -0
- package/dist/parsers/doc-binary/vscode-parser.d.ts +142 -0
- package/dist/parsers/doc-binary/vscode-parser.js +3747 -0
- package/dist/parsers/excel/index.d.ts +17 -0
- package/dist/parsers/excel/index.js +406 -0
- package/dist/parsers/excel/model.d.ts +80 -0
- package/dist/parsers/excel/model.js +48 -0
- package/dist/parsers/excel/serialize.d.ts +3 -0
- package/dist/parsers/excel/serialize.js +78 -0
- package/dist/parsers/hdf5/index.d.ts +75 -0
- package/dist/parsers/hdf5/index.js +658 -0
- package/dist/parsers/hdf5/node.d.ts +21 -0
- package/dist/parsers/hdf5/node.js +95 -0
- package/dist/parsers/json/index.d.ts +15 -0
- package/dist/parsers/json/index.js +102 -0
- package/dist/parsers/json/model.d.ts +42 -0
- package/dist/parsers/json/model.js +140 -0
- package/dist/parsers/json/parse.d.ts +22 -0
- package/dist/parsers/json/parse.js +279 -0
- package/dist/parsers/json/tokenizer.d.ts +17 -0
- package/dist/parsers/json/tokenizer.js +175 -0
- package/dist/parsers/jsonl/index.d.ts +14 -0
- package/dist/parsers/jsonl/index.js +18 -0
- package/dist/parsers/jsonl/model.d.ts +23 -0
- package/dist/parsers/jsonl/model.js +1 -0
- package/dist/parsers/jsonl/parse.d.ts +7 -0
- package/dist/parsers/jsonl/parse.js +34 -0
- package/dist/parsers/markdown/index.d.ts +37 -0
- package/dist/parsers/markdown/index.js +59 -0
- package/dist/parsers/mat/index.d.ts +31 -0
- package/dist/parsers/mat/index.js +426 -0
- package/dist/parsers/mermaid/index.d.ts +5 -0
- package/dist/parsers/mermaid/index.js +3 -0
- package/dist/parsers/mf4/index.d.ts +1 -0
- package/dist/parsers/mf4/index.js +1 -0
- package/dist/parsers/parquet/index.d.ts +21 -0
- package/dist/parsers/parquet/index.js +68 -0
- package/dist/parsers/pcap/index.d.ts +1 -0
- package/dist/parsers/pcap/index.js +1 -0
- package/dist/parsers/pcapng/index.d.ts +1 -0
- package/dist/parsers/pcapng/index.js +1 -0
- package/dist/parsers/plantuml/index.d.ts +5 -0
- package/dist/parsers/plantuml/index.js +4 -0
- package/dist/parsers/ppt-binary/index.d.ts +5 -0
- package/dist/parsers/ppt-binary/index.js +62 -0
- package/dist/parsers/ppt-binary/pptBinaryBuildContext.d.ts +12 -0
- package/dist/parsers/ppt-binary/pptBinaryBuildContext.js +40 -0
- package/dist/parsers/ppt-binary/pptBinaryContainer.d.ts +4 -0
- package/dist/parsers/ppt-binary/pptBinaryContainer.js +174 -0
- package/dist/parsers/ppt-binary/pptBinaryTypes.d.ts +125 -0
- package/dist/parsers/ppt-binary/pptBinaryTypes.js +1 -0
- package/dist/parsers/ppt-binary/pptSlideLayouts.d.ts +9 -0
- package/dist/parsers/ppt-binary/pptSlideLayouts.js +510 -0
- package/dist/parsers/ppt-binary/vscode-parser.d.ts +116 -0
- package/dist/parsers/ppt-binary/vscode-parser.js +2853 -0
- package/dist/parsers/ppt-binary/vscode.d.ts +4 -0
- package/dist/parsers/ppt-binary/vscode.js +12 -0
- package/dist/parsers/pptx/index.d.ts +15 -0
- package/dist/parsers/pptx/index.js +88 -0
- package/dist/parsers/pptx/vscode-parser.d.ts +167 -0
- package/dist/parsers/pptx/vscode-parser.js +1503 -0
- package/dist/parsers/pptx/vscode.d.ts +4 -0
- package/dist/parsers/pptx/vscode.js +18 -0
- package/dist/parsers/pptx/zip-reader.d.ts +3 -0
- package/dist/parsers/pptx/zip-reader.js +20 -0
- package/dist/parsers/proto/index.d.ts +90 -0
- package/dist/parsers/proto/index.js +197 -0
- package/dist/parsers/psd/index.d.ts +16 -0
- package/dist/parsers/psd/index.js +10 -0
- package/dist/parsers/shapefile/dbf.d.ts +23 -0
- package/dist/parsers/shapefile/dbf.js +112 -0
- package/dist/parsers/shapefile/index.d.ts +44 -0
- package/dist/parsers/shapefile/index.js +119 -0
- package/dist/parsers/shapefile/reproject.d.ts +10 -0
- package/dist/parsers/shapefile/reproject.js +41 -0
- package/dist/parsers/slide-model.d.ts +106 -0
- package/dist/parsers/slide-model.js +1 -0
- package/dist/parsers/toml/index.d.ts +15 -0
- package/dist/parsers/toml/index.js +18 -0
- package/dist/parsers/toml/model.d.ts +15 -0
- package/dist/parsers/toml/model.js +1 -0
- package/dist/parsers/toml/parse.d.ts +16 -0
- package/dist/parsers/toml/parse.js +266 -0
- package/dist/parsers/types.d.ts +103 -0
- package/dist/parsers/types.js +106 -0
- package/dist/parsers/video/index.d.ts +6 -0
- package/dist/parsers/video/index.js +2 -0
- package/dist/parsers/yaml/index.d.ts +16 -0
- package/dist/parsers/yaml/index.js +58 -0
- package/dist/parsers/yaml/model.d.ts +22 -0
- package/dist/parsers/yaml/model.js +1 -0
- package/dist/parsers/yaml/self-loading.d.ts +3 -0
- package/dist/parsers/yaml/self-loading.js +19 -0
- package/dist/parsers/zip-scan.d.ts +8 -0
- package/dist/parsers/zip-scan.js +80 -0
- package/dist/registry/container.d.ts +17 -0
- package/dist/registry/container.js +49 -0
- package/dist/registry/index.d.ts +133 -0
- package/dist/registry/index.js +348 -0
- package/dist/registry/probe.d.ts +3 -0
- package/dist/registry/probe.js +177 -0
- package/dist/registry/sniff.d.ts +17 -0
- package/dist/registry/sniff.js +57 -0
- package/dist/styles/archive.css +10 -0
- package/dist/styles/audio.css +66 -0
- package/dist/styles/automotive.css +1 -0
- package/dist/styles/csv.css +262 -0
- package/dist/styles/excel.css +212 -0
- package/dist/styles/fallback.css +48 -0
- package/dist/styles/hdf5.css +3 -0
- package/dist/styles/hwp.css +12 -0
- package/dist/styles/image.css +115 -0
- package/dist/styles/json.css +342 -0
- package/dist/styles/jsonl.css +1 -0
- package/dist/styles/markdown.css +10 -0
- package/dist/styles/mat.css +2 -0
- package/dist/styles/mermaid.css +1 -0
- package/dist/styles/parquet.css +2 -0
- package/dist/styles/pdf.css +551 -0
- package/dist/styles/plantuml.css +1 -0
- package/dist/styles/ppt.css +1 -0
- package/dist/styles/proto.css +2 -0
- package/dist/styles/psd.css +1 -0
- package/dist/styles/shapefile.css +25 -0
- package/dist/styles/toml.css +1 -0
- package/dist/styles/video.css +74 -0
- package/dist/styles/word.css +685 -0
- package/dist/styles/yaml.css +1 -0
- package/dist/viewers/a2l/index.d.ts +15 -0
- package/dist/viewers/a2l/index.js +5 -0
- package/dist/viewers/archive/archive-preview-decoder.d.ts +4 -0
- package/dist/viewers/archive/archive-preview-decoder.js +223 -0
- package/dist/viewers/archive/controller.d.ts +24 -0
- package/dist/viewers/archive/controller.js +18 -0
- package/dist/viewers/archive/index.d.ts +25 -0
- package/dist/viewers/archive/index.js +230 -0
- package/dist/viewers/archive/preview.d.ts +2 -0
- package/dist/viewers/archive/preview.js +2 -0
- package/dist/viewers/archive/styles.d.ts +1 -0
- package/dist/viewers/archive/styles.js +11 -0
- package/dist/viewers/arxml/index.d.ts +15 -0
- package/dist/viewers/arxml/index.js +5 -0
- package/dist/viewers/asc/index.d.ts +15 -0
- package/dist/viewers/asc/index.js +5 -0
- package/dist/viewers/audio/controller.d.ts +33 -0
- package/dist/viewers/audio/controller.js +49 -0
- package/dist/viewers/audio/engine.d.ts +44 -0
- package/dist/viewers/audio/engine.js +108 -0
- package/dist/viewers/audio/index.d.ts +92 -0
- package/dist/viewers/audio/index.js +448 -0
- package/dist/viewers/audio/self-loading.d.ts +7 -0
- package/dist/viewers/audio/self-loading.js +42 -0
- package/dist/viewers/audio/styles.d.ts +1 -0
- package/dist/viewers/audio/styles.js +73 -0
- package/dist/viewers/automotive/index.d.ts +8 -0
- package/dist/viewers/automotive/index.js +152 -0
- package/dist/viewers/automotive/styles.d.ts +1 -0
- package/dist/viewers/automotive/styles.js +1 -0
- package/dist/viewers/avro/index.d.ts +15 -0
- package/dist/viewers/avro/index.js +5 -0
- package/dist/viewers/bag/index.d.ts +15 -0
- package/dist/viewers/bag/index.js +5 -0
- package/dist/viewers/blf/index.d.ts +15 -0
- package/dist/viewers/blf/index.js +5 -0
- package/dist/viewers/csv/controller.d.ts +126 -0
- package/dist/viewers/csv/controller.js +598 -0
- package/dist/viewers/csv/index.d.ts +48 -0
- package/dist/viewers/csv/index.js +846 -0
- package/dist/viewers/csv/styles.d.ts +1 -0
- package/dist/viewers/csv/styles.js +267 -0
- package/dist/viewers/db3/index.d.ts +15 -0
- package/dist/viewers/db3/index.js +5 -0
- package/dist/viewers/dbc/index.d.ts +15 -0
- package/dist/viewers/dbc/index.js +5 -0
- package/dist/viewers/diagram.d.ts +14 -0
- package/dist/viewers/diagram.js +95 -0
- package/dist/viewers/excel/controller.d.ts +108 -0
- package/dist/viewers/excel/controller.js +548 -0
- package/dist/viewers/excel/grid.d.ts +28 -0
- package/dist/viewers/excel/grid.js +99 -0
- package/dist/viewers/excel/index.d.ts +37 -0
- package/dist/viewers/excel/index.js +620 -0
- package/dist/viewers/excel/self-loading.d.ts +6 -0
- package/dist/viewers/excel/self-loading.js +13 -0
- package/dist/viewers/excel/styles.d.ts +1 -0
- package/dist/viewers/excel/styles.js +216 -0
- package/dist/viewers/fallback/index.d.ts +17 -0
- package/dist/viewers/fallback/index.js +130 -0
- package/dist/viewers/fallback/styles.d.ts +1 -0
- package/dist/viewers/fallback/styles.js +49 -0
- package/dist/viewers/hdf5/index.d.ts +19 -0
- package/dist/viewers/hdf5/index.js +170 -0
- package/dist/viewers/hdf5/styles.d.ts +1 -0
- package/dist/viewers/hdf5/styles.js +4 -0
- package/dist/viewers/hwp/controller.d.ts +19 -0
- package/dist/viewers/hwp/controller.js +22 -0
- package/dist/viewers/hwp/index.d.ts +39 -0
- package/dist/viewers/hwp/index.js +271 -0
- package/dist/viewers/hwp/self-loading.d.ts +2 -0
- package/dist/viewers/hwp/self-loading.js +6 -0
- package/dist/viewers/hwp/styles.d.ts +1 -0
- package/dist/viewers/hwp/styles.js +13 -0
- package/dist/viewers/image/controller.d.ts +210 -0
- package/dist/viewers/image/controller.js +336 -0
- package/dist/viewers/image/decode.d.ts +54 -0
- package/dist/viewers/image/decode.js +150 -0
- package/dist/viewers/image/index.d.ts +38 -0
- package/dist/viewers/image/index.js +749 -0
- package/dist/viewers/image/limits.d.ts +18 -0
- package/dist/viewers/image/limits.js +20 -0
- package/dist/viewers/image/styles.d.ts +1 -0
- package/dist/viewers/image/styles.js +119 -0
- package/dist/viewers/json/controller.d.ts +89 -0
- package/dist/viewers/json/controller.js +411 -0
- package/dist/viewers/json/index.d.ts +25 -0
- package/dist/viewers/json/index.js +503 -0
- package/dist/viewers/json/styles.d.ts +1 -0
- package/dist/viewers/json/styles.js +346 -0
- package/dist/viewers/json/transforms.d.ts +27 -0
- package/dist/viewers/json/transforms.js +253 -0
- package/dist/viewers/jsonl/controller.d.ts +48 -0
- package/dist/viewers/jsonl/controller.js +136 -0
- package/dist/viewers/jsonl/index.d.ts +22 -0
- package/dist/viewers/jsonl/index.js +184 -0
- package/dist/viewers/jsonl/styles.d.ts +1 -0
- package/dist/viewers/jsonl/styles.js +1 -0
- package/dist/viewers/markdown/controller.d.ts +50 -0
- package/dist/viewers/markdown/controller.js +62 -0
- package/dist/viewers/markdown/index.d.ts +64 -0
- package/dist/viewers/markdown/index.js +425 -0
- package/dist/viewers/markdown/math.d.ts +21 -0
- package/dist/viewers/markdown/math.js +100 -0
- package/dist/viewers/markdown/self-loading.d.ts +5 -0
- package/dist/viewers/markdown/self-loading.js +48 -0
- package/dist/viewers/markdown/styles.d.ts +1 -0
- package/dist/viewers/markdown/styles.js +11 -0
- package/dist/viewers/mat/index.d.ts +19 -0
- package/dist/viewers/mat/index.js +113 -0
- package/dist/viewers/mat/styles.d.ts +1 -0
- package/dist/viewers/mat/styles.js +3 -0
- package/dist/viewers/media.d.ts +9 -0
- package/dist/viewers/media.js +77 -0
- package/dist/viewers/mermaid/index.d.ts +17 -0
- package/dist/viewers/mermaid/index.js +6 -0
- package/dist/viewers/mermaid/self-loading.d.ts +2 -0
- package/dist/viewers/mermaid/self-loading.js +2 -0
- package/dist/viewers/mf4/index.d.ts +15 -0
- package/dist/viewers/mf4/index.js +5 -0
- package/dist/viewers/parquet/controller.d.ts +37 -0
- package/dist/viewers/parquet/controller.js +26 -0
- package/dist/viewers/parquet/index.d.ts +23 -0
- package/dist/viewers/parquet/index.js +134 -0
- package/dist/viewers/parquet/styles.d.ts +1 -0
- package/dist/viewers/parquet/styles.js +3 -0
- package/dist/viewers/pcap/index.d.ts +15 -0
- package/dist/viewers/pcap/index.js +5 -0
- package/dist/viewers/pcapng/index.d.ts +15 -0
- package/dist/viewers/pcapng/index.js +5 -0
- package/dist/viewers/pdf/controller.d.ts +120 -0
- package/dist/viewers/pdf/controller.js +191 -0
- package/dist/viewers/pdf/editing.d.ts +26 -0
- package/dist/viewers/pdf/editing.js +265 -0
- package/dist/viewers/pdf/index.d.ts +88 -0
- package/dist/viewers/pdf/index.js +1400 -0
- package/dist/viewers/pdf/self-loading.d.ts +6 -0
- package/dist/viewers/pdf/self-loading.js +14 -0
- package/dist/viewers/pdf/styles.d.ts +1 -0
- package/dist/viewers/pdf/styles.js +555 -0
- package/dist/viewers/plantuml/index.d.ts +17 -0
- package/dist/viewers/plantuml/index.js +6 -0
- package/dist/viewers/plantuml/self-loading.d.ts +2 -0
- package/dist/viewers/plantuml/self-loading.js +2 -0
- package/dist/viewers/ppt/chart.d.ts +8 -0
- package/dist/viewers/ppt/chart.js +83 -0
- package/dist/viewers/ppt/controller.d.ts +34 -0
- package/dist/viewers/ppt/controller.js +27 -0
- package/dist/viewers/ppt/index.d.ts +33 -0
- package/dist/viewers/ppt/index.js +101 -0
- package/dist/viewers/ppt/render.d.ts +5 -0
- package/dist/viewers/ppt/render.js +144 -0
- package/dist/viewers/ppt/self-loading.d.ts +9 -0
- package/dist/viewers/ppt/self-loading.js +4 -0
- package/dist/viewers/ppt/styles.d.ts +1 -0
- package/dist/viewers/ppt/styles.js +1 -0
- package/dist/viewers/proto/index.d.ts +17 -0
- package/dist/viewers/proto/index.js +170 -0
- package/dist/viewers/proto/styles.d.ts +1 -0
- package/dist/viewers/proto/styles.js +2 -0
- package/dist/viewers/psd/index.d.ts +27 -0
- package/dist/viewers/psd/index.js +39 -0
- package/dist/viewers/psd/self-loading.d.ts +10 -0
- package/dist/viewers/psd/self-loading.js +20 -0
- package/dist/viewers/psd/styles.d.ts +1 -0
- package/dist/viewers/psd/styles.js +1 -0
- package/dist/viewers/reqif/index.d.ts +15 -0
- package/dist/viewers/reqif/index.js +5 -0
- package/dist/viewers/shapefile/index.d.ts +35 -0
- package/dist/viewers/shapefile/index.js +336 -0
- package/dist/viewers/shapefile/self-loading.d.ts +7 -0
- package/dist/viewers/shapefile/self-loading.js +32 -0
- package/dist/viewers/shapefile/styles.d.ts +1 -0
- package/dist/viewers/shapefile/styles.js +28 -0
- package/dist/viewers/stp/index.d.ts +15 -0
- package/dist/viewers/stp/index.js +5 -0
- package/dist/viewers/structured-styles.d.ts +1 -0
- package/dist/viewers/structured-styles.js +1 -0
- package/dist/viewers/structured.d.ts +36 -0
- package/dist/viewers/structured.js +138 -0
- package/dist/viewers/toml/controller.d.ts +38 -0
- package/dist/viewers/toml/controller.js +61 -0
- package/dist/viewers/toml/index.d.ts +18 -0
- package/dist/viewers/toml/index.js +34 -0
- package/dist/viewers/types.d.ts +36 -0
- package/dist/viewers/types.js +9 -0
- package/dist/viewers/video/controller.d.ts +29 -0
- package/dist/viewers/video/controller.js +54 -0
- package/dist/viewers/video/index.d.ts +18 -0
- package/dist/viewers/video/index.js +305 -0
- package/dist/viewers/video/styles.d.ts +1 -0
- package/dist/viewers/video/styles.js +79 -0
- package/dist/viewers/word/controller.d.ts +19 -0
- package/dist/viewers/word/controller.js +22 -0
- package/dist/viewers/word/docx-preprocess.d.ts +63 -0
- package/dist/viewers/word/docx-preprocess.js +136 -0
- package/dist/viewers/word/index.d.ts +32 -0
- package/dist/viewers/word/index.js +187 -0
- package/dist/viewers/word/normalize-docx.d.ts +2 -0
- package/dist/viewers/word/normalize-docx.js +50 -0
- package/dist/viewers/word/paginate.d.ts +1 -0
- package/dist/viewers/word/paginate.js +1035 -0
- package/dist/viewers/word/self-loading.d.ts +2 -0
- package/dist/viewers/word/self-loading.js +7 -0
- package/dist/viewers/word/styles.d.ts +1 -0
- package/dist/viewers/word/styles.js +686 -0
- package/dist/viewers/yaml/controller.d.ts +42 -0
- package/dist/viewers/yaml/controller.js +41 -0
- package/dist/viewers/yaml/index.d.ts +21 -0
- package/dist/viewers/yaml/index.js +20 -0
- package/package.json +375 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const HWP_MIN_ZOOM = 0.25;
|
|
2
|
+
export const HWP_MAX_ZOOM = 2.5;
|
|
3
|
+
export const HWP_ZOOM_STEP = 0.1;
|
|
4
|
+
export function createHwpController() {
|
|
5
|
+
let zoom = 1;
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
const snapshot = () => ({ zoom });
|
|
8
|
+
const emit = () => listeners.forEach((listener) => listener(snapshot()));
|
|
9
|
+
return {
|
|
10
|
+
get state() { return snapshot(); },
|
|
11
|
+
dispatch(action) {
|
|
12
|
+
if (action.type === 'zoom-in')
|
|
13
|
+
zoom = Math.min(HWP_MAX_ZOOM, Math.round((zoom + HWP_ZOOM_STEP) * 100) / 100);
|
|
14
|
+
if (action.type === 'zoom-out')
|
|
15
|
+
zoom = Math.max(HWP_MIN_ZOOM, Math.round((zoom - HWP_ZOOM_STEP) * 100) / 100);
|
|
16
|
+
if (action.type === 'reset-zoom')
|
|
17
|
+
zoom = 1;
|
|
18
|
+
emit();
|
|
19
|
+
},
|
|
20
|
+
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { HostContext } from '../../host/index.js';
|
|
2
|
+
import { type MountOptions, type ViewerHandle, type ViewerInput } from '../types.js';
|
|
3
|
+
import { type HwpController } from './controller.js';
|
|
4
|
+
export * from './controller.js';
|
|
5
|
+
export { hwpViewerCss } from './styles.js';
|
|
6
|
+
export declare const HWP_VIEWER_META: {
|
|
7
|
+
id: string;
|
|
8
|
+
displayNameKey: string;
|
|
9
|
+
extensions: string[];
|
|
10
|
+
priority: number;
|
|
11
|
+
requiredServices: readonly [];
|
|
12
|
+
optionalServices: readonly [];
|
|
13
|
+
inputOwnership: "consumes";
|
|
14
|
+
};
|
|
15
|
+
export declare const HWP_MAX_INPUT_BYTES: number;
|
|
16
|
+
export declare const HWP_MAX_RENDERED_PAGES = 100;
|
|
17
|
+
export declare const SUPPORTED_DEPENDENCY_VERSIONS: {
|
|
18
|
+
readonly rhwp: "0.7.3";
|
|
19
|
+
};
|
|
20
|
+
export interface RhwpDocument {
|
|
21
|
+
pageCount(): number;
|
|
22
|
+
renderPageSvg(pageIndex: number): string;
|
|
23
|
+
free?(): void;
|
|
24
|
+
}
|
|
25
|
+
export interface RhwpModule {
|
|
26
|
+
HwpDocument: new (data: Uint8Array) => RhwpDocument;
|
|
27
|
+
}
|
|
28
|
+
export interface HwpViewerDeps {
|
|
29
|
+
loadRhwp(): Promise<RhwpModule>;
|
|
30
|
+
}
|
|
31
|
+
export interface HwpViewerHandle extends ViewerHandle {
|
|
32
|
+
readonly controller: HwpController;
|
|
33
|
+
}
|
|
34
|
+
export declare class HwpViewerError extends Error {
|
|
35
|
+
readonly code: 'missing-dependency' | 'invalid-format' | 'corrupted' | 'limit-exceeded';
|
|
36
|
+
readonly messageKey: string;
|
|
37
|
+
constructor(code: 'missing-dependency' | 'invalid-format' | 'corrupted' | 'limit-exceeded', messageKey: string, options?: ErrorOptions);
|
|
38
|
+
}
|
|
39
|
+
export declare function mountHwpViewer(input: ViewerInput, container: HTMLElement, ctx: HostContext, deps?: Partial<HwpViewerDeps>, options?: MountOptions): Promise<HwpViewerHandle>;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { MountAbortedError, VIEWER_ROOT_CLASS } from '../types.js';
|
|
2
|
+
import { createHwpController } from './controller.js';
|
|
3
|
+
import { hwpViewerCss } from './styles.js';
|
|
4
|
+
export * from './controller.js';
|
|
5
|
+
export { hwpViewerCss } from './styles.js';
|
|
6
|
+
export const HWP_VIEWER_META = { id: 'hwp', displayNameKey: 'hwp.title', extensions: ['hwp', 'hwpx'], priority: 15, requiredServices: [], optionalServices: [], inputOwnership: 'consumes' };
|
|
7
|
+
export const HWP_MAX_INPUT_BYTES = 50 * 1024 * 1024;
|
|
8
|
+
export const HWP_MAX_RENDERED_PAGES = 100;
|
|
9
|
+
export const SUPPORTED_DEPENDENCY_VERSIONS = { rhwp: '0.7.3' };
|
|
10
|
+
export class HwpViewerError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
messageKey;
|
|
13
|
+
constructor(code, messageKey, options) {
|
|
14
|
+
super(messageKey, options);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.messageKey = messageKey;
|
|
17
|
+
this.name = 'HwpViewerError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const el = (tag, cls, text) => {
|
|
21
|
+
const node = document.createElement(tag);
|
|
22
|
+
if (cls)
|
|
23
|
+
node.className = cls;
|
|
24
|
+
if (text !== undefined)
|
|
25
|
+
node.textContent = text;
|
|
26
|
+
return node;
|
|
27
|
+
};
|
|
28
|
+
let activeKeyboardOwner;
|
|
29
|
+
const measureTarget = globalThis;
|
|
30
|
+
let measureUsers = 0;
|
|
31
|
+
let originalMeasure;
|
|
32
|
+
let sharedMeasure;
|
|
33
|
+
function installMeasureTextWidth() {
|
|
34
|
+
if (measureUsers++ > 0)
|
|
35
|
+
return releaseMeasureTextWidth;
|
|
36
|
+
originalMeasure = measureTarget.measureTextWidth;
|
|
37
|
+
const canvas = document.createElement('canvas');
|
|
38
|
+
const context = canvas.getContext('2d');
|
|
39
|
+
sharedMeasure = (font, text) => {
|
|
40
|
+
if (!context)
|
|
41
|
+
return String(text ?? '').length * 10;
|
|
42
|
+
context.font = font || '10px sans-serif';
|
|
43
|
+
return context.measureText(String(text ?? '')).width;
|
|
44
|
+
};
|
|
45
|
+
measureTarget.measureTextWidth = sharedMeasure;
|
|
46
|
+
return releaseMeasureTextWidth;
|
|
47
|
+
}
|
|
48
|
+
function releaseMeasureTextWidth() {
|
|
49
|
+
if (measureUsers === 0 || --measureUsers > 0)
|
|
50
|
+
return;
|
|
51
|
+
if (originalMeasure)
|
|
52
|
+
measureTarget.measureTextWidth = originalMeasure;
|
|
53
|
+
else
|
|
54
|
+
delete measureTarget.measureTextWidth;
|
|
55
|
+
originalMeasure = undefined;
|
|
56
|
+
sharedMeasure = undefined;
|
|
57
|
+
}
|
|
58
|
+
function svgElement(markup) {
|
|
59
|
+
const parsed = new DOMParser().parseFromString(markup, 'image/svg+xml');
|
|
60
|
+
const svg = parsed.documentElement;
|
|
61
|
+
if (svg.localName !== 'svg' || parsed.querySelector('parsererror'))
|
|
62
|
+
throw new HwpViewerError('corrupted', 'diag.hwp.corrupted');
|
|
63
|
+
sanitizeSvg(svg);
|
|
64
|
+
return document.importNode(svg, true);
|
|
65
|
+
}
|
|
66
|
+
const FORBIDDEN_SVG_ELEMENTS = new Set(['script', 'style', 'foreignobject', 'iframe', 'object', 'embed', 'audio', 'video', 'animate', 'animatemotion', 'animatetransform', 'set', 'mpath']);
|
|
67
|
+
const URL_ATTRIBUTES = new Set(['href', 'src', 'action', 'formaction', 'poster']);
|
|
68
|
+
const LOCAL_URL_PATTERN = /^url\(\s*(['"]?)#[A-Za-z_][\w:.-]*\1\s*\)$/i;
|
|
69
|
+
const LOCAL_FRAGMENT_PATTERN = /^#[A-Za-z_][\w:.-]*$/;
|
|
70
|
+
// rhwp 0.7.3 emits embedded raster pictures as data URIs. SVG is deliberately
|
|
71
|
+
// excluded because it can carry active content. TIFF is safe to retain but may
|
|
72
|
+
// remain undecodable on browsers without native TIFF support.
|
|
73
|
+
const SAFE_RASTER_DATA_PATTERN = /^data:image\/(?:png|jpe?g|gif|webp|bmp|x-bmp|x-ms-bmp|tiff);base64,[a-z0-9+/=\s]+$/i;
|
|
74
|
+
/** Fixed core policy for SVG derived from untrusted document bytes (ADR 25). */
|
|
75
|
+
function sanitizeSvg(root) {
|
|
76
|
+
for (const node of [root, ...root.querySelectorAll('*')]) {
|
|
77
|
+
if (FORBIDDEN_SVG_ELEMENTS.has(node.localName.toLowerCase())) {
|
|
78
|
+
node.remove();
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
for (const attribute of [...node.attributes]) {
|
|
82
|
+
const name = attribute.localName.toLowerCase();
|
|
83
|
+
const value = attribute.value.trim();
|
|
84
|
+
if (name.startsWith('on') || name === 'srcdoc') {
|
|
85
|
+
node.removeAttributeNode(attribute);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (name === 'style') {
|
|
89
|
+
if (!isSafeSvgStyle(value))
|
|
90
|
+
node.removeAttributeNode(attribute);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (URL_ATTRIBUTES.has(name) || name === 'xlink:href') {
|
|
94
|
+
const localReference = LOCAL_FRAGMENT_PATTERN.test(value);
|
|
95
|
+
const rasterImage = node.localName.toLowerCase() === 'image' && SAFE_RASTER_DATA_PATTERN.test(value);
|
|
96
|
+
if (!localReference && !rasterImage)
|
|
97
|
+
node.removeAttributeNode(attribute);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (/url\s*\(/i.test(value) && !LOCAL_URL_PATTERN.test(value))
|
|
101
|
+
node.removeAttributeNode(attribute);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function isSafeSvgStyle(style) {
|
|
106
|
+
if (/@import|expression\s*\(|javascript\s*:|data\s*:/i.test(style))
|
|
107
|
+
return false;
|
|
108
|
+
const urls = style.match(/url\([^)]*\)/gi) ?? [];
|
|
109
|
+
return urls.every((url) => LOCAL_URL_PATTERN.test(url));
|
|
110
|
+
}
|
|
111
|
+
export async function mountHwpViewer(input, container, ctx, deps = {}, options = {}) {
|
|
112
|
+
if (options.signal?.aborted)
|
|
113
|
+
throw new MountAbortedError();
|
|
114
|
+
if (input.data.byteLength > HWP_MAX_INPUT_BYTES)
|
|
115
|
+
throw new HwpViewerError('limit-exceeded', 'diag.hwp.limit-exceeded');
|
|
116
|
+
const root = options.styleIsolation !== 'scoped' && typeof container.attachShadow === 'function' ? (container.shadowRoot ?? container.attachShadow({ mode: 'open' })) : container;
|
|
117
|
+
root.replaceChildren();
|
|
118
|
+
let ownedStyle;
|
|
119
|
+
if (root === container)
|
|
120
|
+
container.classList.add(VIEWER_ROOT_CLASS, 'omni-viewer--hwp');
|
|
121
|
+
else {
|
|
122
|
+
ownedStyle = el('style');
|
|
123
|
+
ownedStyle.textContent = hwpViewerCss;
|
|
124
|
+
root.append(ownedStyle);
|
|
125
|
+
}
|
|
126
|
+
const frame = el('section', 'omni-hwp');
|
|
127
|
+
const header = el('header', 'omni-hwp__header');
|
|
128
|
+
const title = el('span', 'omni-hwp__title', `📄 ${input.fileName}`);
|
|
129
|
+
const meta = el('span', 'omni-hwp__meta', `${formatBytes(input.data.byteLength)} · ${input.fileName.toLowerCase().endsWith('.hwpx') ? 'HWPX' : 'HWP'}`);
|
|
130
|
+
header.append(title, meta);
|
|
131
|
+
const toolbar = el('div', 'omni-hwp__toolbar');
|
|
132
|
+
const out = el('button', undefined, '−'), level = el('span', 'omni-hwp__zoom', '100%'), inc = el('button', undefined, '+'), reset = el('button', undefined, ctx.i18n.t('hwp.reset'));
|
|
133
|
+
out.type = inc.type = reset.type = 'button';
|
|
134
|
+
out.setAttribute('aria-label', ctx.i18n.t('hwp.zoomOut'));
|
|
135
|
+
inc.setAttribute('aria-label', ctx.i18n.t('hwp.zoomIn'));
|
|
136
|
+
reset.setAttribute('aria-label', ctx.i18n.t('hwp.reset'));
|
|
137
|
+
toolbar.append(out, level, inc, reset);
|
|
138
|
+
const viewport = el('main', 'omni-hwp__viewport');
|
|
139
|
+
const content = el('div', 'omni-hwp__content');
|
|
140
|
+
content.setAttribute('role', 'document');
|
|
141
|
+
const loading = el('div', 'omni-hwp__status omni-hwp__loading', ctx.i18n.t('hwp.loading'));
|
|
142
|
+
viewport.append(content, loading);
|
|
143
|
+
frame.append(header, toolbar, viewport);
|
|
144
|
+
root.append(frame);
|
|
145
|
+
const controller = createHwpController();
|
|
146
|
+
const disposers = [];
|
|
147
|
+
const restoreMeasure = installMeasureTextWidth();
|
|
148
|
+
let rhwpDocument;
|
|
149
|
+
let cleaned = false;
|
|
150
|
+
let loadedRhwp;
|
|
151
|
+
const keyboardOwner = {};
|
|
152
|
+
activeKeyboardOwner = keyboardOwner;
|
|
153
|
+
const freeDocument = () => { const current = rhwpDocument; rhwpDocument = undefined; current?.free?.(); };
|
|
154
|
+
const cleanup = () => { if (cleaned)
|
|
155
|
+
return; cleaned = true; if (activeKeyboardOwner === keyboardOwner)
|
|
156
|
+
activeKeyboardOwner = undefined; off(); disposers.forEach((dispose) => dispose()); freeDocument(); restoreMeasure(); frame.remove(); ownedStyle?.remove(); if (root === container)
|
|
157
|
+
container.classList.remove(VIEWER_ROOT_CLASS, 'omni-viewer--hwp'); };
|
|
158
|
+
const listen = (target, name, fn, listenerOptions) => { target.addEventListener(name, fn, listenerOptions); disposers.push(() => target.removeEventListener(name, fn, listenerOptions)); };
|
|
159
|
+
const off = controller.subscribe((state) => { content.style.transform = `scale(${state.zoom})`; level.textContent = `${Math.round(state.zoom * 100)}%`; });
|
|
160
|
+
listen(out, 'click', () => controller.dispatch({ type: 'zoom-out' }));
|
|
161
|
+
listen(inc, 'click', () => controller.dispatch({ type: 'zoom-in' }));
|
|
162
|
+
listen(reset, 'click', () => controller.dispatch({ type: 'reset-zoom' }));
|
|
163
|
+
listen(frame, 'pointerdown', () => { activeKeyboardOwner = keyboardOwner; });
|
|
164
|
+
listen(frame, 'focusin', () => { activeKeyboardOwner = keyboardOwner; });
|
|
165
|
+
listen(document, 'keydown', ((event) => { if (activeKeyboardOwner !== keyboardOwner || !(event.ctrlKey || event.metaKey))
|
|
166
|
+
return; if (event.key === '+' || event.key === '=')
|
|
167
|
+
controller.dispatch({ type: 'zoom-in' });
|
|
168
|
+
else if (event.key === '-')
|
|
169
|
+
controller.dispatch({ type: 'zoom-out' });
|
|
170
|
+
else if (event.key === '0')
|
|
171
|
+
controller.dispatch({ type: 'reset-zoom' });
|
|
172
|
+
else
|
|
173
|
+
return; event.preventDefault(); }));
|
|
174
|
+
listen(viewport, 'wheel', ((event) => { if (!(event.ctrlKey || event.metaKey))
|
|
175
|
+
return; event.preventDefault(); controller.dispatch({ type: event.deltaY < 0 ? 'zoom-in' : 'zoom-out' }); }), { passive: false });
|
|
176
|
+
try {
|
|
177
|
+
if (!deps.loadRhwp)
|
|
178
|
+
throw new HwpViewerError('missing-dependency', 'diag.hwp.missing-dependency');
|
|
179
|
+
try {
|
|
180
|
+
loadedRhwp = await deps.loadRhwp();
|
|
181
|
+
}
|
|
182
|
+
catch (cause) {
|
|
183
|
+
throw new HwpViewerError('missing-dependency', 'diag.hwp.missing-dependency', { cause });
|
|
184
|
+
}
|
|
185
|
+
if (options.signal?.aborted)
|
|
186
|
+
throw new MountAbortedError();
|
|
187
|
+
try {
|
|
188
|
+
rhwpDocument = new loadedRhwp.HwpDocument(input.data);
|
|
189
|
+
}
|
|
190
|
+
catch (cause) {
|
|
191
|
+
throw new HwpViewerError('invalid-format', 'diag.hwp.invalid-format', { cause });
|
|
192
|
+
}
|
|
193
|
+
const count = rhwpDocument.pageCount();
|
|
194
|
+
if (!Number.isSafeInteger(count) || count < 0)
|
|
195
|
+
throw new HwpViewerError('corrupted', 'diag.hwp.corrupted');
|
|
196
|
+
let renderedCount = Math.min(count, HWP_MAX_RENDERED_PAGES);
|
|
197
|
+
meta.textContent += ` · ${ctx.i18n.t('hwp.pages', { count })}`;
|
|
198
|
+
try {
|
|
199
|
+
renderPageRange(rhwpDocument, 0, renderedCount, content, ctx, options.signal);
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
freeDocument();
|
|
203
|
+
}
|
|
204
|
+
loading.remove();
|
|
205
|
+
if (count > renderedCount) {
|
|
206
|
+
const status = el('div', 'omni-hwp__status');
|
|
207
|
+
const progress = el('span', undefined, ctx.i18n.t('diag.hwp.page-limit', { rendered: renderedCount, total: count }));
|
|
208
|
+
const more = el('button', 'omni-hwp__load-more', ctx.i18n.t('hwp.loadMorePages'));
|
|
209
|
+
more.type = 'button';
|
|
210
|
+
status.append(progress, more);
|
|
211
|
+
content.append(status);
|
|
212
|
+
listen(more, 'click', (async () => {
|
|
213
|
+
if (cleaned || !loadedRhwp)
|
|
214
|
+
return;
|
|
215
|
+
more.disabled = true;
|
|
216
|
+
let batchDocument;
|
|
217
|
+
const batchStart = renderedCount;
|
|
218
|
+
try {
|
|
219
|
+
batchDocument = new loadedRhwp.HwpDocument(input.data);
|
|
220
|
+
const next = Math.min(count, batchStart + HWP_MAX_RENDERED_PAGES);
|
|
221
|
+
renderPageRange(batchDocument, batchStart, next, content, ctx, options.signal, status);
|
|
222
|
+
renderedCount = next;
|
|
223
|
+
if (renderedCount >= count)
|
|
224
|
+
status.remove();
|
|
225
|
+
else {
|
|
226
|
+
progress.textContent = ctx.i18n.t('diag.hwp.page-limit', { rendered: renderedCount, total: count });
|
|
227
|
+
more.disabled = false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
for (const page of [...content.querySelectorAll('.omni-hwp__page')])
|
|
232
|
+
if (Number(page.getAttribute('data-page-index')) >= batchStart)
|
|
233
|
+
page.remove();
|
|
234
|
+
progress.textContent = ctx.i18n.t('diag.hwp.corrupted');
|
|
235
|
+
more.disabled = false;
|
|
236
|
+
ctx.logger.log('error', `hwp: additional page render failed: ${String(error)}`);
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
batchDocument?.free?.();
|
|
240
|
+
}
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
if (count === 0)
|
|
244
|
+
content.append(el('div', 'omni-hwp__status', ctx.i18n.t('hwp.empty')));
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
cleanup();
|
|
248
|
+
if (error instanceof MountAbortedError || error instanceof HwpViewerError)
|
|
249
|
+
throw error;
|
|
250
|
+
ctx.logger.log('error', `hwp: renderer failed: ${String(error)}`);
|
|
251
|
+
throw new HwpViewerError(deps.loadRhwp ? 'corrupted' : 'missing-dependency', deps.loadRhwp ? 'diag.hwp.corrupted' : 'diag.hwp.missing-dependency', { cause: error });
|
|
252
|
+
}
|
|
253
|
+
return { controller, dispose: cleanup };
|
|
254
|
+
}
|
|
255
|
+
function renderPageRange(documentHandle, start, end, content, ctx, signal, before) {
|
|
256
|
+
for (let pageIndex = start; pageIndex < end; pageIndex++) {
|
|
257
|
+
if (signal?.aborted)
|
|
258
|
+
throw new MountAbortedError();
|
|
259
|
+
const page = el('section', 'omni-hwp__page');
|
|
260
|
+
page.dataset.pageIndex = String(pageIndex);
|
|
261
|
+
page.setAttribute('aria-label', ctx.i18n.t('hwp.pageLabel', { page: pageIndex + 1 }));
|
|
262
|
+
page.append(svgElement(documentHandle.renderPageSvg(pageIndex)));
|
|
263
|
+
if (before)
|
|
264
|
+
content.insertBefore(page, before);
|
|
265
|
+
else
|
|
266
|
+
content.append(page);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function formatBytes(bytes) { if (bytes < 1024)
|
|
270
|
+
return `${bytes} B`; if (bytes < 1024 * 1024)
|
|
271
|
+
return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(1)} MB`; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const hwpViewerCss = "\n:host{display:block;height:100%;color:var(--omni-fg,#d4d4d4);background:var(--omni-bg,#1e1e1e)}\n.omni-hwp{display:flex;flex-direction:column;height:100%;font:13px system-ui,-apple-system,\"Segoe UI\",\"Noto Sans KR\",sans-serif}\n.omni-hwp__header,.omni-hwp__toolbar{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--omni-panel-bg,#252526);border-bottom:1px solid var(--omni-border,#3c3c3c)}\n.omni-hwp__header{justify-content:space-between}.omni-hwp__title{overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.omni-hwp__meta{opacity:.7;font-size:12px;white-space:nowrap}\n.omni-hwp__toolbar{justify-content:center}.omni-hwp__toolbar button{min-width:34px;padding:6px 10px;color:inherit;background:var(--omni-control-bg,#3a3d41);border:1px solid var(--omni-border,#555);border-radius:6px;cursor:pointer}.omni-hwp__zoom{min-width:52px;text-align:center;opacity:.8}\n.omni-hwp__viewport{position:relative;flex:1;overflow:auto;padding:28px 24px 48px;background:radial-gradient(circle at top,#ffffff0d,transparent 28%)}\n.omni-hwp__content{display:grid;width:max-content;min-width:100%;gap:24px;justify-items:center;transform-origin:top center}\n.omni-hwp__page{display:block;overflow:hidden;background:#fff;border-radius:4px;box-shadow:0 18px 40px #00000047}.omni-hwp__page svg{display:block;max-width:100%;height:auto}\n.omni-hwp__status,.omni-hwp__error{margin:auto;padding:24px;text-align:center}.omni-hwp__error{color:var(--omni-danger,#f48771)}\n.omni-hwp__status{display:flex;align-items:center;gap:12px}.omni-hwp__load-more{padding:7px 12px;color:inherit;background:var(--omni-control-bg,#3a3d41);border:1px solid var(--omni-border,#555);border-radius:6px;cursor:pointer}.omni-hwp__load-more:disabled{opacity:.5}\n@media print{.omni-hwp__header,.omni-hwp__toolbar{display:none}.omni-hwp__viewport{overflow:visible;padding:0}.omni-hwp__content{display:block;transform:none!important}.omni-hwp__page{box-shadow:none;break-after:page}}\n";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const hwpViewerCss = `
|
|
2
|
+
:host{display:block;height:100%;color:var(--omni-fg,#d4d4d4);background:var(--omni-bg,#1e1e1e)}
|
|
3
|
+
.omni-hwp{display:flex;flex-direction:column;height:100%;font:13px system-ui,-apple-system,"Segoe UI","Noto Sans KR",sans-serif}
|
|
4
|
+
.omni-hwp__header,.omni-hwp__toolbar{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--omni-panel-bg,#252526);border-bottom:1px solid var(--omni-border,#3c3c3c)}
|
|
5
|
+
.omni-hwp__header{justify-content:space-between}.omni-hwp__title{overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.omni-hwp__meta{opacity:.7;font-size:12px;white-space:nowrap}
|
|
6
|
+
.omni-hwp__toolbar{justify-content:center}.omni-hwp__toolbar button{min-width:34px;padding:6px 10px;color:inherit;background:var(--omni-control-bg,#3a3d41);border:1px solid var(--omni-border,#555);border-radius:6px;cursor:pointer}.omni-hwp__zoom{min-width:52px;text-align:center;opacity:.8}
|
|
7
|
+
.omni-hwp__viewport{position:relative;flex:1;overflow:auto;padding:28px 24px 48px;background:radial-gradient(circle at top,#ffffff0d,transparent 28%)}
|
|
8
|
+
.omni-hwp__content{display:grid;width:max-content;min-width:100%;gap:24px;justify-items:center;transform-origin:top center}
|
|
9
|
+
.omni-hwp__page{display:block;overflow:hidden;background:#fff;border-radius:4px;box-shadow:0 18px 40px #00000047}.omni-hwp__page svg{display:block;max-width:100%;height:auto}
|
|
10
|
+
.omni-hwp__status,.omni-hwp__error{margin:auto;padding:24px;text-align:center}.omni-hwp__error{color:var(--omni-danger,#f48771)}
|
|
11
|
+
.omni-hwp__status{display:flex;align-items:center;gap:12px}.omni-hwp__load-more{padding:7px 12px;color:inherit;background:var(--omni-control-bg,#3a3d41);border:1px solid var(--omni-border,#555);border-radius:6px;cursor:pointer}.omni-hwp__load-more:disabled{opacity:.5}
|
|
12
|
+
@media print{.omni-hwp__header,.omni-hwp__toolbar{display:none}.omni-hwp__viewport{overflow:visible;padding:0}.omni-hwp__content{display:block;transform:none!important}.omni-hwp__page{box-shadow:none;break-after:page}}
|
|
13
|
+
`;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { Diagnostic, ParseFailure } from '../../parsers/types.js';
|
|
2
|
+
/** Zoom range and step (image.md §3): 10 %–500 %, 25 % increments. */
|
|
3
|
+
export declare const MIN_ZOOM = 0.1;
|
|
4
|
+
export declare const MAX_ZOOM = 5;
|
|
5
|
+
export declare const ZOOM_STEP = 0.25;
|
|
6
|
+
/** Undo history depth cap (image.md §4 maxUndoEntries). */
|
|
7
|
+
export declare const MAX_UNDO_ENTRIES = 100;
|
|
8
|
+
export type Rotation = 0 | 90 | 180 | 270;
|
|
9
|
+
export type AnnotationTool = 'select' | 'brush' | 'eraser' | 'line' | 'rectangle' | 'circle' | 'text';
|
|
10
|
+
export interface Point {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
}
|
|
14
|
+
export interface AnnotationStyle {
|
|
15
|
+
color: string;
|
|
16
|
+
fillOpacity: number;
|
|
17
|
+
borderOpacity: number;
|
|
18
|
+
fontSize: number;
|
|
19
|
+
brushSize: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const DEFAULT_STYLE: AnnotationStyle;
|
|
22
|
+
/** Annotation geometry, all in source pixel coordinates. */
|
|
23
|
+
export type Annotation = {
|
|
24
|
+
id: string;
|
|
25
|
+
type: 'rectangle';
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
w: number;
|
|
29
|
+
h: number;
|
|
30
|
+
style: AnnotationStyle;
|
|
31
|
+
} | {
|
|
32
|
+
id: string;
|
|
33
|
+
type: 'circle';
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
w: number;
|
|
37
|
+
h: number;
|
|
38
|
+
style: AnnotationStyle;
|
|
39
|
+
} | {
|
|
40
|
+
id: string;
|
|
41
|
+
type: 'line';
|
|
42
|
+
x1: number;
|
|
43
|
+
y1: number;
|
|
44
|
+
x2: number;
|
|
45
|
+
y2: number;
|
|
46
|
+
style: AnnotationStyle;
|
|
47
|
+
} | {
|
|
48
|
+
id: string;
|
|
49
|
+
type: 'text';
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
text: string;
|
|
53
|
+
style: AnnotationStyle;
|
|
54
|
+
} | {
|
|
55
|
+
id: string;
|
|
56
|
+
type: 'stroke';
|
|
57
|
+
points: Point[];
|
|
58
|
+
erase: boolean;
|
|
59
|
+
style: AnnotationStyle;
|
|
60
|
+
};
|
|
61
|
+
export interface FilterState {
|
|
62
|
+
/** Percent values (image.md §3). brightness/contrast/saturation 0–200, grayscale 0–100. */
|
|
63
|
+
brightness: number;
|
|
64
|
+
contrast: number;
|
|
65
|
+
saturation: number;
|
|
66
|
+
grayscale: number;
|
|
67
|
+
}
|
|
68
|
+
export declare const NEUTRAL_FILTER: FilterState;
|
|
69
|
+
export type FilterPreset = 'original' | 'bright' | 'dark' | 'vintage' | 'bw';
|
|
70
|
+
/**
|
|
71
|
+
* Preset filter values. NOTE (image.md §3, I-preset): the exact numbers must be
|
|
72
|
+
* pinned to the existing VS Code / Obsidian fixtures during migration — these
|
|
73
|
+
* are the working defaults until those goldens land.
|
|
74
|
+
*/
|
|
75
|
+
export declare const FILTER_PRESETS: Record<FilterPreset, FilterState>;
|
|
76
|
+
export type GridMode = 'cell-size' | 'rows-cols';
|
|
77
|
+
export interface GridState {
|
|
78
|
+
visible: boolean;
|
|
79
|
+
/** Preserves both VS Code/Obsidian (source-pixel cell) and Web (rows×cols)
|
|
80
|
+
* meanings without lossy conversion on platform switch (image.md I3). */
|
|
81
|
+
mode: GridMode;
|
|
82
|
+
cellWidth: number;
|
|
83
|
+
cellHeight: number;
|
|
84
|
+
rows: number;
|
|
85
|
+
cols: number;
|
|
86
|
+
}
|
|
87
|
+
export declare const DEFAULT_GRID: GridState;
|
|
88
|
+
/** Image metadata surfaced after decode (mount fills this via `setDocument`). */
|
|
89
|
+
export interface ImageMeta {
|
|
90
|
+
mime: string;
|
|
91
|
+
width: number;
|
|
92
|
+
height: number;
|
|
93
|
+
byteLength: number;
|
|
94
|
+
animated: boolean;
|
|
95
|
+
}
|
|
96
|
+
export interface ImageViewState {
|
|
97
|
+
status: 'ok' | 'partial' | 'failed';
|
|
98
|
+
failure: ParseFailure | null;
|
|
99
|
+
diagnostics: Diagnostic[];
|
|
100
|
+
meta: ImageMeta | null;
|
|
101
|
+
/** Editing/PNG export disabled (canvas limit exceeded → partial, image.md §4). */
|
|
102
|
+
editingEnabled: boolean;
|
|
103
|
+
zoom: number;
|
|
104
|
+
fitMode: boolean;
|
|
105
|
+
rotation: Rotation;
|
|
106
|
+
flipH: boolean;
|
|
107
|
+
flipV: boolean;
|
|
108
|
+
filter: FilterState;
|
|
109
|
+
preset: FilterPreset | null;
|
|
110
|
+
grid: GridState;
|
|
111
|
+
annotations: Annotation[];
|
|
112
|
+
tool: AnnotationTool;
|
|
113
|
+
selectedId: string | null;
|
|
114
|
+
style: AnnotationStyle;
|
|
115
|
+
dirty: boolean;
|
|
116
|
+
canUndo: boolean;
|
|
117
|
+
}
|
|
118
|
+
export type ImageAction = {
|
|
119
|
+
type: 'set-zoom';
|
|
120
|
+
zoom: number;
|
|
121
|
+
} | {
|
|
122
|
+
type: 'zoom-in';
|
|
123
|
+
} | {
|
|
124
|
+
type: 'zoom-out';
|
|
125
|
+
} | {
|
|
126
|
+
type: 'fit';
|
|
127
|
+
} | {
|
|
128
|
+
type: 'actual-size';
|
|
129
|
+
} | {
|
|
130
|
+
type: 'rotate-cw';
|
|
131
|
+
} | {
|
|
132
|
+
type: 'flip-h';
|
|
133
|
+
} | {
|
|
134
|
+
type: 'flip-v';
|
|
135
|
+
} | {
|
|
136
|
+
type: 'reset';
|
|
137
|
+
} | {
|
|
138
|
+
type: 'set-filter';
|
|
139
|
+
filter: Partial<FilterState>;
|
|
140
|
+
} | {
|
|
141
|
+
type: 'set-preset';
|
|
142
|
+
preset: FilterPreset;
|
|
143
|
+
} | {
|
|
144
|
+
type: 'toggle-grid';
|
|
145
|
+
} | {
|
|
146
|
+
type: 'set-grid-mode';
|
|
147
|
+
mode: GridMode;
|
|
148
|
+
} | {
|
|
149
|
+
type: 'set-grid-value';
|
|
150
|
+
key: 'cellWidth' | 'cellHeight' | 'rows' | 'cols';
|
|
151
|
+
value: number;
|
|
152
|
+
} | {
|
|
153
|
+
type: 'set-tool';
|
|
154
|
+
tool: AnnotationTool;
|
|
155
|
+
} | {
|
|
156
|
+
type: 'set-style';
|
|
157
|
+
style: Partial<AnnotationStyle>;
|
|
158
|
+
} | {
|
|
159
|
+
type: 'add-annotation';
|
|
160
|
+
annotation: Annotation;
|
|
161
|
+
} | {
|
|
162
|
+
type: 'select-annotation';
|
|
163
|
+
id: string | null;
|
|
164
|
+
} | {
|
|
165
|
+
type: 'move-annotation';
|
|
166
|
+
dx: number;
|
|
167
|
+
dy: number;
|
|
168
|
+
} | {
|
|
169
|
+
type: 'update-annotation';
|
|
170
|
+
id: string;
|
|
171
|
+
patch: Partial<Extract<Annotation, {
|
|
172
|
+
type: 'text';
|
|
173
|
+
}>>;
|
|
174
|
+
} | {
|
|
175
|
+
type: 'delete-annotation';
|
|
176
|
+
id?: string;
|
|
177
|
+
} | {
|
|
178
|
+
type: 'undo';
|
|
179
|
+
};
|
|
180
|
+
export interface ImageController {
|
|
181
|
+
readonly state: ImageViewState;
|
|
182
|
+
dispatch(action: ImageAction): void;
|
|
183
|
+
subscribe(listener: (state: ImageViewState) => void): () => void;
|
|
184
|
+
/** CSS `filter` string for the current filter values (display + export). */
|
|
185
|
+
filterCss(): string;
|
|
186
|
+
/** Record decode metadata / status; editingEnabled gates the canvas ops. */
|
|
187
|
+
setDocument(input: {
|
|
188
|
+
status: 'ok' | 'partial' | 'failed';
|
|
189
|
+
failure?: ParseFailure | null;
|
|
190
|
+
diagnostics?: Diagnostic[];
|
|
191
|
+
meta?: ImageMeta | null;
|
|
192
|
+
editingEnabled?: boolean;
|
|
193
|
+
}): void;
|
|
194
|
+
}
|
|
195
|
+
/** Snap zoom to the 25 % grid within range (image.md §3). */
|
|
196
|
+
export declare function normalizeZoom(zoom: number): number;
|
|
197
|
+
export declare function filterToCss(filter: FilterState): string;
|
|
198
|
+
export declare function createImageController(): ImageController;
|
|
199
|
+
/** Factory helper for the renderer: build an annotation with a fresh id. */
|
|
200
|
+
export declare function makeAnnotation(spec: Omit<Extract<Annotation, {
|
|
201
|
+
type: 'rectangle';
|
|
202
|
+
}>, 'id'> | Omit<Extract<Annotation, {
|
|
203
|
+
type: 'circle';
|
|
204
|
+
}>, 'id'> | Omit<Extract<Annotation, {
|
|
205
|
+
type: 'line';
|
|
206
|
+
}>, 'id'> | Omit<Extract<Annotation, {
|
|
207
|
+
type: 'text';
|
|
208
|
+
}>, 'id'> | Omit<Extract<Annotation, {
|
|
209
|
+
type: 'stroke';
|
|
210
|
+
}>, 'id'>): Annotation;
|