omni-viewer-core 0.21.0 → 0.23.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.
Files changed (141) hide show
  1. package/README.md +343 -7
  2. package/THIRD_PARTY_NOTICES.md +23 -0
  3. package/dist/i18n/catalog.de.js +68 -0
  4. package/dist/i18n/catalog.en.js +366 -0
  5. package/dist/i18n/catalog.fr.js +68 -0
  6. package/dist/i18n/catalog.it.js +68 -0
  7. package/dist/i18n/catalog.ja.js +366 -0
  8. package/dist/i18n/catalog.ko.js +366 -0
  9. package/dist/i18n/catalog.th.js +68 -0
  10. package/dist/i18n/catalog.zh-cn.js +366 -0
  11. package/dist/i18n/catalog.zh-tw.js +257 -0
  12. package/dist/parsers/arrow/index.d.ts +46 -0
  13. package/dist/parsers/arrow/index.js +384 -0
  14. package/dist/parsers/arrow/lz4.d.ts +6 -0
  15. package/dist/parsers/arrow/lz4.js +115 -0
  16. package/dist/parsers/arrow/self-loading.d.ts +6 -0
  17. package/dist/parsers/arrow/self-loading.js +47 -0
  18. package/dist/parsers/arrow/sniff.d.ts +2 -0
  19. package/dist/parsers/arrow/sniff.js +25 -0
  20. package/dist/parsers/dicom/index.d.ts +6 -0
  21. package/dist/parsers/dicom/index.js +335 -0
  22. package/dist/parsers/dicom/model.d.ts +67 -0
  23. package/dist/parsers/dicom/model.js +1 -0
  24. package/dist/parsers/dicom/pixels.d.ts +9 -0
  25. package/dist/parsers/dicom/pixels.js +82 -0
  26. package/dist/parsers/eml/index.d.ts +37 -0
  27. package/dist/parsers/eml/index.js +278 -0
  28. package/dist/parsers/font/index.d.ts +38 -0
  29. package/dist/parsers/font/index.js +253 -0
  30. package/dist/parsers/geojson/index.d.ts +49 -0
  31. package/dist/parsers/geojson/index.js +167 -0
  32. package/dist/parsers/json/index.d.ts +2 -0
  33. package/dist/parsers/json/index.js +12 -1
  34. package/dist/parsers/json/parse.d.ts +2 -0
  35. package/dist/parsers/json/parse.js +11 -7
  36. package/dist/parsers/junit/index.d.ts +49 -0
  37. package/dist/parsers/junit/index.js +841 -0
  38. package/dist/parsers/junit/model.d.ts +141 -0
  39. package/dist/parsers/junit/model.js +8 -0
  40. package/dist/parsers/junit/sniff.d.ts +7 -0
  41. package/dist/parsers/junit/sniff.js +86 -0
  42. package/dist/parsers/mcap/errors.d.ts +5 -0
  43. package/dist/parsers/mcap/errors.js +5 -0
  44. package/dist/parsers/mcap/index.d.ts +95 -0
  45. package/dist/parsers/mcap/index.js +321 -0
  46. package/dist/parsers/mcap/self-loading.d.ts +6 -0
  47. package/dist/parsers/mcap/self-loading.js +21 -0
  48. package/dist/parsers/mcap/zstd.d.ts +8 -0
  49. package/dist/parsers/mcap/zstd.js +102 -0
  50. package/dist/parsers/mesh/index.d.ts +28 -0
  51. package/dist/parsers/mesh/index.js +351 -0
  52. package/dist/parsers/mesh/validation.d.ts +11 -0
  53. package/dist/parsers/mesh/validation.js +215 -0
  54. package/dist/parsers/openapi/index.d.ts +24 -0
  55. package/dist/parsers/openapi/index.js +319 -0
  56. package/dist/parsers/openapi/model.d.ts +57 -0
  57. package/dist/parsers/openapi/model.js +1 -0
  58. package/dist/parsers/openapi/nodes.d.ts +19 -0
  59. package/dist/parsers/openapi/nodes.js +214 -0
  60. package/dist/parsers/openapi/self-loading.d.ts +5 -0
  61. package/dist/parsers/openapi/self-loading.js +35 -0
  62. package/dist/parsers/openapi/sniff.d.ts +6 -0
  63. package/dist/parsers/openapi/sniff.js +136 -0
  64. package/dist/parsers/text/index.d.ts +17 -0
  65. package/dist/parsers/text/index.js +68 -0
  66. package/dist/parsers/xml/model.d.ts +3 -0
  67. package/dist/parsers/xml/parse.js +5 -1
  68. package/dist/parsers/yaml/index.js +3 -2
  69. package/dist/parsers/yaml/model.d.ts +2 -1
  70. package/dist/parsers/yaml/self-loading.js +2 -2
  71. package/dist/registry/index.d.ts +24 -4
  72. package/dist/registry/index.js +108 -9
  73. package/dist/registry/sniff.d.ts +8 -1
  74. package/dist/registry/sniff.js +52 -1
  75. package/dist/styles/arrow.css +9 -0
  76. package/dist/styles/dicom.css +14 -0
  77. package/dist/styles/eml.css +19 -0
  78. package/dist/styles/font.css +15 -0
  79. package/dist/styles/geojson.css +30 -0
  80. package/dist/styles/junit.css +96 -0
  81. package/dist/styles/mcap.css +25 -0
  82. package/dist/styles/mesh.css +15 -0
  83. package/dist/styles/openapi.css +38 -0
  84. package/dist/styles/text.css +14 -0
  85. package/dist/viewers/arrow/controller.d.ts +38 -0
  86. package/dist/viewers/arrow/controller.js +81 -0
  87. package/dist/viewers/arrow/index.d.ts +27 -0
  88. package/dist/viewers/arrow/index.js +215 -0
  89. package/dist/viewers/arrow/self-loading.d.ts +4 -0
  90. package/dist/viewers/arrow/self-loading.js +31 -0
  91. package/dist/viewers/arrow/styles.d.ts +1 -0
  92. package/dist/viewers/arrow/styles.js +10 -0
  93. package/dist/viewers/dicom/index.d.ts +18 -0
  94. package/dist/viewers/dicom/index.js +222 -0
  95. package/dist/viewers/dicom/styles.d.ts +1 -0
  96. package/dist/viewers/dicom/styles.js +15 -0
  97. package/dist/viewers/eml/index.d.ts +19 -0
  98. package/dist/viewers/eml/index.js +219 -0
  99. package/dist/viewers/eml/styles.d.ts +1 -0
  100. package/dist/viewers/eml/styles.js +20 -0
  101. package/dist/viewers/font/index.d.ts +22 -0
  102. package/dist/viewers/font/index.js +204 -0
  103. package/dist/viewers/font/styles.d.ts +1 -0
  104. package/dist/viewers/font/styles.js +16 -0
  105. package/dist/viewers/geojson/index.d.ts +19 -0
  106. package/dist/viewers/geojson/index.js +388 -0
  107. package/dist/viewers/geojson/styles.d.ts +2 -0
  108. package/dist/viewers/geojson/styles.js +32 -0
  109. package/dist/viewers/html/index.d.ts +1 -1
  110. package/dist/viewers/html/index.js +7 -2
  111. package/dist/viewers/junit/index.d.ts +31 -0
  112. package/dist/viewers/junit/index.js +840 -0
  113. package/dist/viewers/junit/styles.d.ts +1 -0
  114. package/dist/viewers/junit/styles.js +101 -0
  115. package/dist/viewers/mcap/index.d.ts +30 -0
  116. package/dist/viewers/mcap/index.js +281 -0
  117. package/dist/viewers/mcap/self-loading.d.ts +4 -0
  118. package/dist/viewers/mcap/self-loading.js +16 -0
  119. package/dist/viewers/mcap/styles.d.ts +1 -0
  120. package/dist/viewers/mcap/styles.js +26 -0
  121. package/dist/viewers/mesh/async.d.ts +2 -0
  122. package/dist/viewers/mesh/async.js +19 -0
  123. package/dist/viewers/mesh/index.d.ts +39 -0
  124. package/dist/viewers/mesh/index.js +462 -0
  125. package/dist/viewers/mesh/self-loading.d.ts +6 -0
  126. package/dist/viewers/mesh/self-loading.js +28 -0
  127. package/dist/viewers/mesh/styles.d.ts +2 -0
  128. package/dist/viewers/mesh/styles.js +17 -0
  129. package/dist/viewers/openapi/controller.d.ts +20 -0
  130. package/dist/viewers/openapi/controller.js +74 -0
  131. package/dist/viewers/openapi/index.d.ts +25 -0
  132. package/dist/viewers/openapi/index.js +556 -0
  133. package/dist/viewers/openapi/self-loading.d.ts +5 -0
  134. package/dist/viewers/openapi/self-loading.js +13 -0
  135. package/dist/viewers/openapi/styles.d.ts +2 -0
  136. package/dist/viewers/openapi/styles.js +40 -0
  137. package/dist/viewers/text/index.d.ts +19 -0
  138. package/dist/viewers/text/index.js +91 -0
  139. package/dist/viewers/text/styles.d.ts +1 -0
  140. package/dist/viewers/text/styles.js +15 -0
  141. package/package.json +142 -4
package/README.md CHANGED
@@ -12,20 +12,25 @@ models, viewers mount into a DOM element, and everything host-specific
12
12
 
13
13
  ## Supported formats
14
14
 
15
- - **Documents** — PDF, Word (DOCX and legacy DOC), HWP, PowerPoint (PPTX and
15
+ - **Documents** — Plain text (.txt), PDF, Word (DOCX and legacy DOC), HWP, PowerPoint (PPTX and
16
16
  legacy PPT), HTML, Markdown, Jupyter Notebook (.ipynb), EPUB e-books (2 and 3),
17
17
  LaTeX (structure and math preview, not typesetting)
18
18
  - **Data & spreadsheets** — Excel, CSV/TSV, JSON, JSONL/NDJSON, YAML, TOML, XML,
19
- Parquet, Avro, HDF5, MATLAB MAT, NumPy (NPY/NPZ), Safetensors, GGUF, ONNX, TFLite/LiteRT,
19
+ Parquet, Arrow IPC / Feather V2 (.arrow/.feather), Avro, HDF5, MATLAB MAT, NumPy (NPY/NPZ), Safetensors, GGUF, ONNX, TFLite/LiteRT,
20
20
  Keras (.keras and legacy .h5), Core ML (.mlmodel and .mlpackage),
21
21
  OpenVINO IR (.xml + .bin), PyTorch Export (.pt2), ExecuTorch (.pte), Protocol
22
22
  Buffers, ReqIF, SQLite
23
- - **Web & network** — HAR (HTTP Archive) request logs
23
+ - **Email, web & network** — EML email messages, HAR (HTTP Archive) request logs,
24
+ OpenAPI / Swagger API documents (JSON/YAML)
25
+ - **Developer tooling** — JUnit XML test reports (Gradle, Maven Surefire,
26
+ pytest, vitest/Jest, Jenkins and other JUnit-format writers)
27
+ - **Medical imaging** — DICOM (.dcm/.dicom), native grayscale/RGB image preview,
28
+ multi-frame navigation, windowing and metadata inspection
24
29
  - **Media & graphics** — audio (waveform/spectrogram), video, images,
25
- Photoshop PSD
30
+ Photoshop PSD, fonts (TTF/OTF/WOFF), 3D meshes (GLB/glTF/STL/OBJ)
26
31
  - **Engineering & automotive** — CAN DBC, AUTOSAR ARXML, ASAM A2L, Vector
27
- ASC/BLF logs, ASAM MDF 4 (MF4), PCAP/PCAPNG, ROS bag, STEP (STP)
28
- - **Diagrams & GIS** — Mermaid, PlantUML, ESRI Shapefile
32
+ ASC/BLF logs, ASAM MDF 4 (MF4), PCAP/PCAPNG, ROS bag, MCAP (.mcap), STEP (STP)
33
+ - **Diagrams & GIS** — Mermaid, PlantUML, ESRI Shapefile, GeoJSON
29
34
  - **Archives** — ZIP-family listing and safe entry preview
30
35
 
31
36
  The HTML viewer handles `.html` and `.htm` files with preview and source tabs.
@@ -34,6 +39,50 @@ navigation removed or blocked; inline styles and embedded data images remain.
34
39
  Files over 8 MiB show a bounded source preview. Very dense markup opens in
35
40
  source view to keep parsing responsive.
36
41
 
42
+ ### Plain text (.txt)
43
+
44
+ `omni-viewer-core/viewers/text` provides a dependency-free, read-only text
45
+ viewer. Whitespace, tabs and line endings are preserved; HTML and Markdown
46
+ remain literal text. Soft line wrapping can be toggled without changing the
47
+ content. UTF-8 is the default; UTF-8 and UTF-16 LE/BE BOMs are recognized.
48
+ For BOM-less UTF-16, pass `parse: { encoding: 'utf-16le' }` (or `'utf-16be'`).
49
+ Legacy encodings such as CP949 are not automatically detected. Malformed
50
+ Unicode bytes are displayed as replacement characters with a visible warning;
51
+ UTF-32 BOMs are rejected.
52
+
53
+ ```ts
54
+ import { mountTextViewer } from 'omni-viewer-core/viewers/text';
55
+
56
+ const handle = await mountTextViewer(
57
+ { fileName: 'notes.txt', data: bytes }, container, hostContext
58
+ );
59
+ // On file switch or tab close:
60
+ handle.dispose();
61
+ ```
62
+
63
+ The DOM-free parser is available at `omni-viewer-core/parsers/text` as
64
+ `parseText(bytes, options)`. The default input limit is 256 MiB and the decoded
65
+ preview budget is 8 MiB (excluding the BOM); larger previews are visibly marked
66
+ as partial. Override them through `limits.maxInputBytes` / `maxPreviewBytes`
67
+ (under `parse.limits` for the viewer). Incomplete characters at a preview
68
+ boundary are omitted without an encoding warning. Decoding limits are
69
+ cooperative; hosts still own file loading and hard CPU/memory bounds.
70
+ Scoped hosts load `omni-viewer-core/styles/text.css`; shadow mode injects it.
71
+
72
+ The registry returns `viewerId: 'text'` for `.txt` (including uppercase `.TXT`).
73
+ Hosts must register that extension and explicitly map `text` to
74
+ `mountTextViewer`; the registry does not load viewers. Other extensions and
75
+ extensionless text retain their existing detection behavior.
76
+
77
+ ### DICOM (.dcm/.dicom)
78
+
79
+ `omni-viewer-core/viewers/dicom` provides a dependency-free native 8/16-bit DICOM
80
+ preview with frame navigation, window center/width, inversion, zoom, and a
81
+ searchable metadata table. Implicit/explicit VR little endian and explicit VR
82
+ big endian are supported. Compressed images keep metadata available and report
83
+ that preview is unavailable. See [DICOM support and limits](examples/dicom/SUPPORT.md)
84
+ and the [offline synthetic example](examples/dicom/README.md).
85
+
37
86
  ## Install
38
87
 
39
88
  ```sh
@@ -74,6 +123,181 @@ Viewers follow the same pattern — a `mount*Viewer(input, container, ctx,
74
123
  deps)` entry per format, plus `self-loading` variants that dynamically import
75
124
  their own dependencies for hosts that don't want to wire them manually.
76
125
 
126
+ ### OpenAPI / Swagger (JSON / YAML)
127
+
128
+ Open Swagger 2.0 and OpenAPI 3.0–3.2 specifications with searchable API
129
+ documentation, HTTP method/tag filters, parameter and request/response schemas,
130
+ examples, authentication definitions and an original-source tab.
131
+
132
+ ```ts
133
+ import { mountSelfLoadingOpenApiViewer } from 'omni-viewer-core/viewers/openapi/self-loading';
134
+
135
+ const handle = await mountSelfLoadingOpenApiViewer(
136
+ { fileName: 'openapi.yaml', data: bytes }, container, hostContext
137
+ );
138
+ handle.dispose();
139
+ ```
140
+
141
+ JSON needs no external library. YAML uses the optional `yaml` 2.8.3 peer;
142
+ base parser/viewer entries accept injected `yamlDeps`, and the opt-in
143
+ `/self-loading` entries load it for YAML. The registry refines generic
144
+ JSON/YAML to OpenAPI when file content matches; ordinary files retain their
145
+ existing viewers. Pass complete text plus `{ yamlDeps }` as `detectViewer`'s
146
+ sixth argument for strict YAML detection. Without it, conservative block YAML
147
+ signatures are recognized and validated when mounted.
148
+
149
+ Local references, Swagger definitions and parameter inheritance are supported.
150
+ The viewer runs offline and renders descriptions as text. Exports include
151
+ `parsers/openapi`, `viewers/openapi`, their `/self-loading` entries and
152
+ `styles/openapi.css`. See the [viewer contract](docs/viewers/openapi.md) and
153
+ [runnable examples](examples/openapi/README.md) for integration and limits.
154
+
155
+ ### 3D meshes (GLB / glTF / STL / OBJ)
156
+
157
+ Install `three` (`^0.180.0`) and, for TypeScript projects, `@types/three`
158
+ (`^0.180.0`). The viewer provides orbit/pan/zoom, fit to view, wireframe,
159
+ grid/axes, mesh statistics, glTF materials and animation playback.
160
+
161
+ ```ts
162
+ import { mountSelfLoadingMeshViewer } from 'omni-viewer-core/viewers/mesh/self-loading';
163
+
164
+ const handle = await mountSelfLoadingMeshViewer(
165
+ { fileName: 'model.glb', data: bytes }, container, hostContext
166
+ );
167
+ // On file switch or tab close:
168
+ handle.dispose();
169
+ ```
170
+
171
+ For injected runtimes, use `mountMeshViewer(input, container, ctx, deps, options)`
172
+ from `viewers/mesh`; `loadMeshViewerDeps()` supplies the standard Three.js deps.
173
+ `parsers/mesh` offers DOM-free `parseMesh(bytes, { fileName, limits })`
174
+ preflight without loading Three.js. It validates structure, coordinates, resource
175
+ paths and budgets; scene decoding happens when mounted.
176
+
177
+ Self-contained GLB and embedded glTF need no asset service. For `.gltf` with
178
+ external `.bin` or textures, provide `ctx.documentAssets.resolve(relativePath)`;
179
+ each returned asset's `dispose()` is called when the viewer closes. Input URLs,
180
+ absolute paths and parent traversal are rejected; the core never resolves them
181
+ against the page URL. Hosts must cap external asset sizes as well as the main
182
+ input. OBJ geometry, UVs and normals are supported; MTL libraries use default
183
+ materials with a warning. OBJ line and point primitives are omitted with a
184
+ warning. Compressed glTF requires host-provided decoders via
185
+ `deps.configureGltfLoader`; the core never downloads them. Rendering requires
186
+ WebGL 2. Scoped hosts should load `styles/mesh.css`; shadow mode injects it.
187
+
188
+ glTF preflight also budgets generated accessors, animation arrays, skeletons,
189
+ instancing and morph storage. Generated GPU storage uses conservative estimates
190
+ to cover device-dependent padding before rendering.
191
+
192
+ Run `npm run build`, serve the repo root, and open `examples/mesh/` for a
193
+ standalone example with sample files in all four formats and local file loading.
194
+
195
+ ### MCAP (.mcap)
196
+
197
+ Inspect topics/channels, schemas, sampled messages, metadata, attachments and
198
+ chunks in MCAP v0 recordings. The viewer includes topic filtering, search,
199
+ pagination, text/hex payload inspection and JSON copy. JSON payloads are
200
+ formatted when complete; CDR, Protobuf and other binary payloads are shown as
201
+ raw bytes. Attachments are listed without extracting or executing their data.
202
+
203
+ ```ts
204
+ import { mountSelfLoadingMcapViewer } from 'omni-viewer-core/viewers/mcap/self-loading';
205
+
206
+ const handle = await mountSelfLoadingMcapViewer(
207
+ { fileName: 'robot.mcap', data: bytes }, container, ctx,
208
+ { parse: { previewMessages: 5_000 } }
209
+ );
210
+ // On close: handle.dispose();
211
+ ```
212
+
213
+ Install the existing optional peers `lz4js` and `fzstd` to read compressed
214
+ chunks with the self-loading entry. Uncompressed recordings need no peers.
215
+ The base `parsers/mcap` and `viewers/mcap` entries import no runtime peers:
216
+ `parseMcap(bytes, { decompressLz4?, decompressZstd? }, parseOptions)` and
217
+ `mountMcapViewer(input, container, ctx, deps?, mountOptions?)` accept injected
218
+ bounded decoders (the optional third argument limits decoder history bytes).
219
+ Injected decoders may throw `McapDecompressionLimitError` to report resource
220
+ limits separately from malformed input.
221
+ `parsers/mcap/self-loading` exports `parseSelfLoadingMcap`
222
+ and `loadMcapDeps`. Missing/unknown codecs leave a partial document with any
223
+ available summary statistics and schemas. Incomplete scans display lower-bound
224
+ counts when exact summary counts are unavailable.
225
+
226
+ Defaults: 256 MiB input and cumulative chunk bytes, 1,000,000 records/map entries,
227
+ 5,000 retained messages, 4 KiB per payload/schema preview and an 8 MiB cumulative
228
+ preview budget. Structural encoding identifiers are read independently of the
229
+ preview budget (up to 16 KiB). Zstd frame content sizes and history windows are
230
+ checked before decoding; history uses the configured chunk byte limit.
231
+ Search/copy operate on retained samples; totals include every
232
+ scanned message. Uint64 values remain decimal strings and timestamps display
233
+ exact seconds relative to the recording's epoch. Chunk, data-section and summary
234
+ CRCs are checked when present; attachment payload CRCs are not checked. Limits
235
+ are cooperative; hosts can use a Worker for hard CPU/memory bounds. The viewer
236
+ supports shadow/scoped isolation and `styles/mcap.css`. Detection recognizes
237
+ the `.mcap` extension and binary magic, including extensionless recordings.
238
+ Host adapters must add `mcap` to their explicit viewer import maps.
239
+
240
+ The binary reader follows the [MCAP format specification](https://mcap.dev/spec).
241
+
242
+ ### Fonts (TTF / OTF / WOFF)
243
+
244
+ Preview local fonts with editable sample text, size and variable axis controls,
245
+ searchable Unicode characters, paginated glyphs, and font metadata and licenses.
246
+ No optional package is required. WOFF metadata uses the native
247
+ `DecompressionStream` API. Rendering requires `FontFace` and `document.fonts`;
248
+ hosts without these APIs can still inspect metadata. Fonts are removed from the
249
+ document font set when the viewer is disposed. Hosts using a Content Security
250
+ Policy must permit fonts loaded through the CSS Font Loading API.
251
+
252
+ ```ts
253
+ import { mountFontViewer } from 'omni-viewer-core/viewers/font';
254
+ import { parseFont } from 'omni-viewer-core/parsers/font';
255
+
256
+ const { result } = await parseFont(bytes);
257
+ const handle = await mountFontViewer({ fileName: 'sample.woff', data: bytes }, container, ctx);
258
+ // handle.dispose() releases the font and viewer.
259
+ ```
260
+
261
+ The parser reads sfnt/WOFF tables and cmap formats 0, 4, 6, 12, and 13 with input,
262
+ decompression, parsing, and character preview limits. WOFF2 and font collections
263
+ (TTC/OTC) are not supported. Scoped mounting uses `omni-viewer-core/styles/font.css`.
264
+ Format references: [OpenType](https://learn.microsoft.com/en-us/typography/opentype/spec/otff)
265
+ and [WOFF 1.0](https://www.w3.org/TR/WOFF/).
266
+
267
+ ### Arrow IPC / Feather V2
268
+
269
+ Read Arrow IPC files and streams, including Feather V2 with LZ4 or ZSTD
270
+ compression. Install the optional peers for the self-loading entry:
271
+
272
+ ```sh
273
+ npm install apache-arrow@^21.2.0 lz4js fzstd
274
+ ```
275
+
276
+ ```ts
277
+ import { mountSelfLoadingArrowViewer } from 'omni-viewer-core/viewers/arrow/self-loading';
278
+
279
+ const handle = await mountSelfLoadingArrowViewer(
280
+ { fileName: 'data.feather', data: bytes }, container, ctx,
281
+ { parse: { previewRows: 10_000 } }
282
+ );
283
+ // On close: handle.dispose();
284
+ ```
285
+
286
+ The base `parsers/arrow` and `viewers/arrow` entries import no runtime peers:
287
+ inject `{ arrow: Arrow, decompressLz4?, decompressZstd? }` into `parseArrow`
288
+ or `mountArrowViewer`. `parsers/arrow/self-loading` also exports
289
+ `parseSelfLoadingArrow` and `loadArrowDeps`.
290
+
291
+ The viewer offers a searchable, sortable, paginated table, schema and custom
292
+ metadata, JSON preview, TSV/JSON copy, and JSON export. Search, copy, and export
293
+ apply to retained preview rows, with explicit warnings when a limit is reached.
294
+ Int64/Uint64 and scaled decimals are exported as strings; nested values remain
295
+ structured. Defaults are 256 MiB input/decompressed buffers, 10,000 rows,
296
+ 1,024 schema fields, 1,000,000 values, and 16 MiB preview data. Parsing uses
297
+ cooperative limits; a host Worker is needed for a hard CPU/memory boundary.
298
+ Feather V1 is recognized and reports an unsupported-format message.
299
+ See [the Arrow viewer specification](docs/viewers/arrow.md) for the API and limits.
300
+
77
301
  ### Safetensors large-file input
78
302
 
79
303
  Pass browser `File`/`Blob` objects through the lazy source helper so only the
@@ -356,6 +580,37 @@ Routing is by the `.pte` extension, validated against the `ET12` identifier at
356
580
  offset 4; an extensionless program is detected from the same bytes. A `.ptd`
357
581
  data file (`FT01`) is not a program and is not claimed.
358
582
 
583
+ ### GeoJSON (.geojson)
584
+
585
+ ```ts
586
+ import { mountGeoJsonViewer } from 'omni-viewer-core/viewers/geojson';
587
+
588
+ const handle = await mountGeoJsonViewer(
589
+ { fileName: 'map.geojson', data: geoJsonBytes }, container, hostContext
590
+ );
591
+ handle.dispose();
592
+ ```
593
+
594
+ The dependency-free SVG map works offline and supports all seven geometry
595
+ types, Feature, and FeatureCollection. Zoom with the toolbar or mouse wheel,
596
+ drag to pan, and select a feature on the map or in the feature selector to
597
+ inspect its properties. Polygon holes, altitude values, nested properties,
598
+ duplicate property names, and original numeric tokens are preserved. The map
599
+ renders longitude/latitude directly, without basemap tiles or antimeridian
600
+ wrapping; legacy CRS metadata produces a warning rather than reprojection.
601
+ Scoped mounting uses `styles/geojson.css`.
602
+
603
+ `parseGeoJson` is also exported from `parsers/geojson` and returns
604
+ `ParseOutcome<GeoJsonDocument>`. Defaults: 128 MiB input, 10,000 features,
605
+ 200,000 vertices, 1,000,000 JSON nodes, and 64 nesting levels. Malformed
606
+ features are skipped with diagnostics. Feature/vertex limits return a partial
607
+ preview; invalid JSON and JSON parsing limits return typed failures. Property
608
+ display is capped at 500 rows and a 64 KiB preview budget per selection.
609
+ `.geojson` routes directly to the viewer; extensionless GeoJSON is detected
610
+ before generic JSON. Explicit `.json` files keep the JSON tree viewer.
611
+ See the [viewer specification](examples/geojson/SPEC.md) and
612
+ [sample map](examples/geojson/map.geojson).
613
+
359
614
  ### Jupyter notebooks (.ipynb)
360
615
 
361
616
  View nbformat 4 notebooks with their code and saved results in file order.
@@ -399,6 +654,46 @@ returns `partial` with diagnostics. Invalid JSON and unsupported major versions
399
654
  return typed failures. See [viewer details](examples/notebook/README.md) and the
400
655
  [sample notebook](examples/notebook/analysis.ipynb).
401
656
 
657
+ ### EML email (.eml)
658
+
659
+ The EML viewer displays decoded subjects, sender/recipient fields, dates,
660
+ plain-text and HTML bodies, all message headers, a bounded raw-source preview,
661
+ and attachments. It handles nested MIME messages, Base64/quoted-printable,
662
+ encoded header words, legacy body charsets, and extended attachment filenames.
663
+ Attached messages remain downloadable `.eml` files rather than becoming the
664
+ parent message's body. Extensionless messages are detected conservatively.
665
+
666
+ ```ts
667
+ import { mountEmlViewer } from 'omni-viewer-core/viewers/eml';
668
+ import { createCatalogI18n } from 'omni-viewer-core/i18n';
669
+
670
+ const handle = await mountEmlViewer(
671
+ { fileName: file.name, data: new Uint8Array(await file.arrayBuffer()) },
672
+ container,
673
+ {
674
+ assets: { resolveAssetUrl: async path => path },
675
+ i18n: createCatalogI18n('ko'),
676
+ logger: { log: (level, message) => console.log(level, message) },
677
+ // Optional: save: { saveFile: async (name, bytes, mimeType) => … },
678
+ // Optional: clipboard: { writeText: text => navigator.clipboard.writeText(text) }
679
+ }
680
+ );
681
+ // On file change / unmount:
682
+ handle.dispose();
683
+ ```
684
+
685
+ HTML runs in an opaque sandbox with scripts, navigation, forms, and remote
686
+ resources blocked. Embedded `cid:` raster images render locally. Attachment
687
+ filenames are sanitized; saving goes through the optional host `save` service.
688
+ Copy/save controls are disabled when their host service is absent. No MIME
689
+ dependency is required. Default parsing limits are 32 MiB input/decoded data,
690
+ 1,000 MIME parts, 32 nesting levels, and 4 MiB per body preview. Limits can be
691
+ configured through `parseEml(bytes, options)` from `parsers/eml`.
692
+
693
+ The default mount uses Shadow DOM. Scoped mounting requires
694
+ `omni-viewer-core/styles/eml.css`. Run `npm run build`, serve the repository
695
+ root, and open `examples/eml/` for sample messages and a local file picker.
696
+
402
697
  ### HAR network logs (.har)
403
698
 
404
699
  A `.har` file is the HTTP Archive a browser's network panel (or curl, Charles,
@@ -445,6 +740,46 @@ metadata get their own tabs. Routing is by the `.har` extension; an
445
740
  extensionless archive is recognized by content — a `log` object holding an
446
741
  `entries` array — and claimed before the generic JSON viewer.
447
742
 
743
+ ### JUnit XML test reports
744
+
745
+ A JUnit XML report is what nearly every test runner writes for CI: Gradle's
746
+ `build/test-results/**/TEST-*.xml`, Maven Surefire, `pytest --junitxml`,
747
+ vitest/Jest JUnit reporters, Go and Android tooling. It is an ordinary `.xml`,
748
+ so the generic XML viewer keeps the extension and `detectViewer` routes a file
749
+ to `junit` only when the supplied text sample has a `<testsuites>` root, or a
750
+ `<testsuite>` root carrying counts or a JUnit child element:
751
+
752
+ ```ts
753
+ import { detectViewer } from 'omni-viewer-core/registry';
754
+ import { mountJUnitViewer } from 'omni-viewer-core/viewers/junit';
755
+
756
+ const { viewerId } = detectViewer(file.name, undefined, undefined, undefined, text);
757
+ if (viewerId === 'junit') await mountJUnitViewer({ fileName: file.name, data: bytes }, container, { ...ctx, clipboard });
758
+ ```
759
+
760
+ `parseJUnit` is a contract parser (`ParseOutcome<JUnitDocument>`) built on the
761
+ core XML scanner, so it has no DOM dependency, never loads external entities,
762
+ and recovers a truncated report as `partial`. It reads nested suites, test cases
763
+ with their `failure`/`error`/`skipped` results (and Surefire's flaky/rerun
764
+ attempts), properties, and suite or case `system-out`/`system-err`. Writers
765
+ disagree about the declared totals, so every count is recomputed from the
766
+ cases; declared totals that differ are reported, not trusted. A `failure` or
767
+ `error` placed directly in a suite becomes a synthetic case so a setup error
768
+ shows in the list.
769
+
770
+ The viewer opens on the first failing test with a pass/fail verdict, an
771
+ outcome bar and summary figures (tests, passed, failed, errors, skipped, pass
772
+ rate, duration). The test list is filterable by status, suite subtree, and free
773
+ text over names, classes, and messages, with an opt-in toggle that extends the
774
+ search to stack traces and captured output. It is sortable by status severity,
775
+ name, suite, and time. The detail pane shows each result's message, type, and
776
+ trace, the captured output, and properties, and **Copy failure details** puts a
777
+ plain-text report on the clipboard. A suites tab lists nested suites with
778
+ rolled-up counts, and the source tab shows the report text. Defaults are 64 MiB
779
+ input, 100,000 cases, 64 KiB per trace or output block, and 16 MiB of captured
780
+ output in total. See the [viewer contract](docs/viewers/junit.md) and
781
+ [examples](examples/junit/README.md).
782
+
448
783
  ### EPUB e-books (.epub)
449
784
 
450
785
  EPUB 2.0.1 and EPUB 3.x books open in a reflowable reading pane with the
@@ -517,7 +852,8 @@ XPath (positions appear only where names repeat), the value, or the node's XML.
517
852
  Both panes are virtualized, and long single-line sources are split into
518
853
  display segments. Defaults are 64 MiB input, 500,000 nodes, and depth 1,000.
519
854
  ARXML and ReqIF keep their dedicated viewers by extension. An `.xml` whose root
520
- is an OpenVINO IR goes to the OpenVINO viewer when a text sample is provided.
855
+ is an OpenVINO IR or a JUnit test report goes to the OpenVINO or JUnit viewer
856
+ when a text sample is provided.
521
857
 
522
858
  ### Archive host integration
523
859
 
@@ -2,6 +2,17 @@
2
2
 
3
3
  This distribution includes the following third-party software.
4
4
 
5
+ ## Three.js (optional peer)
6
+
7
+ - Project: Three.js (`three`)
8
+ - Copyright: 2010-2025 three.js authors
9
+ - License: MIT
10
+ - Use: 3D mesh rendering, GLB/glTF/STL/OBJ loaders and orbit controls
11
+ - Source: https://github.com/mrdoob/three.js
12
+
13
+ Three.js is supplied by the consuming host; the core does not redistribute its
14
+ runtime. Retain the upstream MIT license when bundling it in an application.
15
+
5
16
  ## Hugging Face GGUF parser
6
17
 
7
18
  - Project: `@huggingface/gguf` and `@huggingface/tasks`
@@ -55,3 +66,15 @@ rhwp project, used only for tests and never published to the npm package.
55
66
  - License: MIT
56
67
  - Source: https://github.com/edwardkim/rhwp (`rhwp-studio/public/samples/`,
57
68
  `samples/valign_fixtures/`)
69
+
70
+ ## JUnit example reports
71
+
72
+ `examples/junit/jenkins-nested-testsuites.xml` and
73
+ `examples/junit/jenkins-suite-error.xml` are unmodified test fixtures from the
74
+ Jenkins JUnit plugin, used for examples and tests and never published to the
75
+ npm package.
76
+
77
+ - Project: jenkinsci/junit-plugin (commit `04461ad56caf19b05bc16015f23675883df83d43`)
78
+ - Copyright: (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
79
+ - License: MIT
80
+ - Source: https://github.com/jenkinsci/junit-plugin/tree/04461ad56caf19b05bc16015f23675883df83d43/src/test/resources/hudson/tasks/junit
@@ -1,6 +1,74 @@
1
1
  // German messages owned by core. Missing keys intentionally fall back to the
2
2
  // English source catalog in i18n/index.ts.
3
3
  export const CATALOG_DE = {
4
+ "font.fallback": "Fehlende Zeichen werden mit einer Ersatzschrift angezeigt.",
5
+ "font.title": "Schriftart",
6
+ "font.format": "Format",
7
+ "font.family": "Familie",
8
+ "font.style": "Stil",
9
+ "font.postScriptName": "PostScript-Name",
10
+ "font.version": "Version",
11
+ "font.glyphCount": "Glyphen",
12
+ "font.unitsPerEm": "Einheiten pro em",
13
+ "font.weight": "Schriftstärke",
14
+ "font.copyright": "Urheberrecht",
15
+ "font.license": "Lizenz",
16
+ "font.sample": "Beispieltext",
17
+ "font.defaultSample": "Franz jagt im komplett verwahrlosten Taxi quer durch Bayern.\nABCDEFGHIJKLMNOPQRSTUVWXYZ\nabcdefghijklmnopqrstuvwxyz 0123456789",
18
+ "font.size": "Schriftgröße",
19
+ "font.characters": "Zeichen",
20
+ "font.search": "Zeichen oder Unicode suchen (U+0041)",
21
+ "font.previous": "Zurück",
22
+ "font.next": "Weiter",
23
+ "font.page": "Seite {page} / {pages} · {count} Zeichen",
24
+ "font.glyph": "{code} · Glyphe {index}",
25
+ "font.empty": "Keine passenden Zeichen.",
26
+ "font.unavailable": "Die Schrift kann nicht dargestellt werden. Metadaten werden angezeigt.",
27
+ "diag.font.invalid": "Nicht unterstütztes Schriftformat. TTF, OTF oder WOFF erwartet.",
28
+ "diag.font.corrupted": "Die Schriftdaten sind unvollständig oder beschädigt.",
29
+ "diag.font.limit": "Die Schriftanalyse hat ein Ressourcenlimit überschritten.",
30
+ "diag.font.decompression": "Zum Lesen komprimierter WOFF-Tabellen wird DecompressionStream benötigt.",
31
+ "diag.font.cmap": "Keine unterstützte Zeichentabelle verfügbar.",
32
+ "diag.font.preview": "Die Zeichenvorschau hat ihr Limit erreicht. Die Suche gilt für die Vorschau.",
33
+ "arrow.title": "Arrow / Feather V2",
34
+ "arrow.summary": "{rows} Zeilen · {columns} Spalten · {batches} Datenblöcke",
35
+ "arrow.search": "Vorschauzeilen durchsuchen",
36
+ "arrow.table": "Tabelle",
37
+ "arrow.schema": "Schema",
38
+ "arrow.raw": "JSON",
39
+ "arrow.copyTable": "Tabelle kopieren",
40
+ "arrow.copyJson": "JSON kopieren",
41
+ "arrow.exportJson": "JSON exportieren",
42
+ "arrow.pageSize": "Zeilen pro Seite",
43
+ "arrow.matches": "{matched} / {loaded} Vorschauzeilen",
44
+ "arrow.empty": "Keine Vorschauzeilen vorhanden.",
45
+ "arrow.noMatches": "Keine passenden Zeilen.",
46
+ "arrow.actionFailed": "Aktion fehlgeschlagen. Bitte erneut versuchen.",
47
+ "diag.arrow.invalid": "Ungültige Arrow-IPC-Datei oder ungültiger Stream.",
48
+ "diag.arrow.corrupted": "Die Arrow-IPC-Daten sind abgeschnitten oder beschädigt.",
49
+ "diag.arrow.featherV1": "Feather V1 wird nicht unterstützt. Als Feather V2 (Arrow IPC) speichern.",
50
+ "diag.arrow.dependency": "Installieren Sie die optionale Abhängigkeit apache-arrow zum Lesen von Arrow IPC.",
51
+ "diag.arrow.compression": "Komprimiertes Arrow IPC benötigt einen LZ4/ZSTD-Decoder. Installieren Sie lz4js und fzstd oder übergeben Sie Decoder.",
52
+ "diag.arrow.limit": "Die Arrow-IPC-Verarbeitung hat ein Ressourcenlimit überschritten.",
53
+ "diag.arrow.preview": "{loaded} / {total} Zeilen innerhalb der Vorschaulimits. Suche, Kopieren und Export gelten für diese Vorschauzeilen.",
54
+ "geojson.title": "GeoJSON",
55
+ "geojson.propertiesTruncated": "Einige Eigenschaften wurden für diese Vorschau gekürzt.",
56
+ "geojson.summary": "{features} Objekte · {vertices} Stützpunkte · WGS84",
57
+ "geojson.features": "Objekte",
58
+ "geojson.feature": "Objekt {id}",
59
+ "geojson.map": "GeoJSON-Karte",
60
+ "geojson.hint": "Scrollen zum Zoomen, Ziehen zum Verschieben. Tastatur: +/− zum Zoomen, Pfeile zum Verschieben, 0 für die Gesamtansicht.",
61
+ "geojson.empty": "Keine Geometrie zum Anzeigen.",
62
+ "geojson.properties": "Eigenschaften",
63
+ "geojson.noSelection": "Objekt auswählen",
64
+ "geojson.noProperties": "Dieses Objekt hat keine Eigenschaften.",
65
+ "geojson.zoomIn": "Vergrößern",
66
+ "geojson.zoomOut": "Verkleinern",
67
+ "geojson.reset": "Gesamtansicht",
68
+ "diag.geojson.invalid": "Ungültiges GeoJSON-Dokument.",
69
+ "diag.geojson.geometry": "Objekt {index} wurde wegen ungültiger Geometrie oder Metadaten übersprungen.",
70
+ "diag.geojson.limit": "Die Ressourcengrenze der GeoJSON-Vorschau wurde erreicht. Einige Objekte werden möglicherweise ausgelassen.",
71
+ "diag.geojson.crs": "Veraltete CRS-Metadaten werden nicht angewendet. Koordinaten werden als WGS84-Längen-/Breitengrade interpretiert.",
4
72
  'audio.title': 'Audio', 'video.title': 'Video', 'dbc.title': 'CAN-Datenbank',
5
73
  'audio.play': 'Wiedergabe', 'audio.pause': 'Pause', 'audio.stop': 'Stopp',
6
74
  'audio.volume': 'Lautstärke',