document-svg 0.1.1
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 +9 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +118 -0
- package/THIRD_PARTY_LICENSES.txt +9542 -0
- package/THIRD_PARTY_NOTICES.md +79 -0
- package/docs/preview.en.md +147 -0
- package/docs/preview.ja.md +147 -0
- package/docs/preview.zh-CN.md +147 -0
- package/examples/SvgPreview.tsx +58 -0
- package/examples/preview-to-html.cjs +69 -0
- package/index.d.ts +119 -0
- package/index.js +705 -0
- package/package.json +76 -0
- package/preview-ui.d.ts +21 -0
- package/preview-ui.js +164 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
## Current dependency audit and notice bundle
|
|
4
|
+
|
|
5
|
+
The current audit is in [docs/LICENSE_AUDIT.md](docs/LICENSE_AUDIT.md), with the
|
|
6
|
+
complete machine-readable inventory in [docs/DEPENDENCY_LICENSES.json](docs/DEPENDENCY_LICENSES.json).
|
|
7
|
+
[THIRD_PARTY_LICENSES.txt](THIRD_PARTY_LICENSES.txt) retains the original dependency
|
|
8
|
+
license/copyright texts and is included with the Rust, Node.js and Python distributions.
|
|
9
|
+
|
|
10
|
+
This software is based in part on the work of the Independent JPEG Group.
|
|
11
|
+
|
|
12
|
+
## Standard PDF font metrics
|
|
13
|
+
|
|
14
|
+
`src/pdf_base14.rs` rearranges numeric glyph advances from eight Adobe Core 14 AFM
|
|
15
|
+
files into Rust tables. Source revision, hashes and checked glyph counts are in the
|
|
16
|
+
dependency inventory. Original copyright/trademark notices and permission are in
|
|
17
|
+
[licenses/Adobe-Core14-AFM.txt](licenses/Adobe-Core14-AFM.txt) and the notice bundle.
|
|
18
|
+
The source is [tc-font-core14-afms](https://github.com/tecnickcom/tc-font-core14-afms/tree/0675784d24b28a55c607cad6b74596ce19ce333c).
|
|
19
|
+
|
|
20
|
+
## Optional cloud architecture assets
|
|
21
|
+
|
|
22
|
+
`authoring/cloud_icons.py` は、Azure・AWS・Google Cloudの公式アイコンを利用者のローカル
|
|
23
|
+
キャッシュへ取得する任意ツールです。素材とキャッシュ、デモ出力は配布に含めません。原本ZIPには配布元の同梱情報も保持します。
|
|
24
|
+
|
|
25
|
+
- Microsoft Azure: https://learn.microsoft.com/en-us/azure/architecture/icons/ 。構成図・研修資料・文書への利用が認められています。原形を維持し、製品名を添え、回転・反転・切り抜き・変形を行いません。
|
|
26
|
+
- AWS: https://aws.amazon.com/architecture/icons/ 。公式の構成図用素材を取得します。製品・リソース・カテゴリ・グループを区別します。
|
|
27
|
+
- Google Cloud: https://cloud.google.com/icons 。現行のcore product/category素材を取得します。個別製品と共通カテゴリの使い分けは公式Product icons overviewに従います。
|
|
28
|
+
|
|
29
|
+
取得元URL・確認日・リリース表記・ZIPのSHA-256は `authoring/icon-sources.json`、各SVGの
|
|
30
|
+
出典・原本内パス・SHA-256は生成する `catalog.json` に記録します。
|
|
31
|
+
|
|
32
|
+
## Runtime dependencies
|
|
33
|
+
|
|
34
|
+
このプロジェクトは、コア実行時依存をMIT、Apache-2.0、BSD-3-Clause、Zlib、IJGなどの許容的ライセンスへ限定します。GPL、AGPL、SSPL、非商用限定、ソース公開を要求する依存は採用していません。
|
|
35
|
+
|
|
36
|
+
直接依存:
|
|
37
|
+
|
|
38
|
+
| crate | resolved version | license |
|
|
39
|
+
|---|---:|---|
|
|
40
|
+
| anyhow | 1.0.104 | MIT OR Apache-2.0 |
|
|
41
|
+
| base64 | 0.23.1 | MIT OR Apache-2.0 |
|
|
42
|
+
| clap | 4.6.6 | MIT OR Apache-2.0 |
|
|
43
|
+
| emf-core | 0.1.0 | MIT |
|
|
44
|
+
| hayro-ccitt | 0.3.0 | Apache-2.0 OR MIT |
|
|
45
|
+
| jpeg-decoder | 0.3.2 | MIT OR Apache-2.0 |
|
|
46
|
+
| lopdf | 0.44.0 | MIT |
|
|
47
|
+
| png | 0.18.1 | MIT OR Apache-2.0 |
|
|
48
|
+
| quick-xml | 0.41.0 | MIT |
|
|
49
|
+
| rayon | 1.12.0 | MIT OR Apache-2.0 |
|
|
50
|
+
| resvg | 0.48.1 | Apache-2.0 OR MIT |
|
|
51
|
+
| serde | 1.0.229 | MIT OR Apache-2.0 |
|
|
52
|
+
| serde_json | 1.0.151 | MIT OR Apache-2.0 |
|
|
53
|
+
| stet-fonts | 0.4.1 | Apache-2.0 OR MIT |
|
|
54
|
+
| skrifa | 0.44.0 | MIT OR Apache-2.0 |
|
|
55
|
+
| zip | 8.6.0 | MIT |
|
|
56
|
+
|
|
57
|
+
言語バインディング専用の直接依存:
|
|
58
|
+
|
|
59
|
+
| crate | resolved version | license |
|
|
60
|
+
|---|---:|---|
|
|
61
|
+
| napi | 3.12.2 | MIT |
|
|
62
|
+
| napi-derive | 3.6.3 | MIT |
|
|
63
|
+
| napi-build | 2.4.1 | MIT |
|
|
64
|
+
| pyo3 | 0.29.2 | MIT OR Apache-2.0 |
|
|
65
|
+
| tempfile | 3.27.0 | MIT OR Apache-2.0 |
|
|
66
|
+
|
|
67
|
+
実行時依存`jpeg-encoder 0.6.1`は、PDF内のCMYK/YCCK JPEGをブラウザ互換RGB JPEGへ正規化するために使用し、ライセンスは(MIT OR Apache-2.0) AND IJGです。`tempfile`はコアのテストに加えてNode.js preview APIの一時出力管理にも使用します。
|
|
68
|
+
|
|
69
|
+
2026-08-23に次のコマンドで全推移依存のSPDX表現を確認し、必須のコピーレフト依存がないことを確認しました。複数ライセンスを`OR`で提示するcrateは、MIT、Apache-2.0または他の許容的選択肢を選択します。target固有推移依存`r-efi`の`MIT OR Apache-2.0 OR LGPL-2.1-or-later`からもMITを選択します。
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cargo metadata --format-version 1 \
|
|
73
|
+
| jq -r '.packages[] | [.name,.version,(.license // "MISSING")] | @tsv' \
|
|
74
|
+
| sort -u
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
リリース前には`Cargo.lock`を基準に同じ監査を再実行し、各crateの配布物に含まれるLICENSE/NOTICEも保持してください。QAだけに使う`pdftoppm`、`rsvg-convert`、LibreOfficeは外部CLIであり、このcrateへリンク・同梱しません。
|
|
78
|
+
|
|
79
|
+
参照元`pdfsvgpptx`はproprietary licenseです。そのコードをcopy/vendorせず、公開仕様と観察した入出力契約をもとに独立実装しています。参照QA文書、SVG、PPTX、画像はこの配布物へ含めません。
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# document-svg preview guide
|
|
2
|
+
|
|
3
|
+
[日本語](preview.ja.md) · [English](preview.en.md) · [简体中文](preview.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
`document-svg` is a Node.js module that turns PDF, PowerPoint, Excel, and Word files into page-by-page SVG previews. Its Rust-native conversion runs on a Node.js worker, so it does not block the event loop.
|
|
6
|
+
|
|
7
|
+
## What it can do
|
|
8
|
+
|
|
9
|
+
- Convert PDF, PPTX, XLSX, and DOCX files into complete SVG strings, one per page
|
|
10
|
+
- Produce in-memory previews without retaining output files
|
|
11
|
+
- Run in Node.js servers, Electron main processes, and server-side TypeScript
|
|
12
|
+
- Create Blob URLs or Data URLs for an `<img>`
|
|
13
|
+
- Copy an SVG image or its exact XML source to the clipboard
|
|
14
|
+
- Surface conversion warnings through `needsReview`
|
|
15
|
+
- Package SVG pages into PPTX, DOCX, or XLSX as vector images
|
|
16
|
+
|
|
17
|
+
This is not a browser-only WASM converter. Run document conversion in Node.js and use only `document-svg/preview-ui` in a renderer or browser context.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
After publication:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install document-svg
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
To try the repository build:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd bindings/node
|
|
31
|
+
npm install
|
|
32
|
+
npm run build
|
|
33
|
+
npm test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Node.js 18 or newer is required. A published release installs the native package matching the user's operating system and CPU.
|
|
37
|
+
|
|
38
|
+
## Minimal example: get SVG markup
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const { preview } = require('document-svg')
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const report = await preview('slides.pptx', { maxPages: 100 })
|
|
45
|
+
console.log(`${report.pageCount} pages`)
|
|
46
|
+
console.log(`review required: ${report.needsReview}`)
|
|
47
|
+
const firstPageSvg = report.pages[0]?.svg
|
|
48
|
+
console.log(firstPageSvg)
|
|
49
|
+
}
|
|
50
|
+
main().catch(console.error)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Each entry in `report.pages` includes its page number, complete SVG, dimensions in points, warnings, and estimated IR size. Temporary conversion files are removed before the Promise resolves.
|
|
54
|
+
|
|
55
|
+
## Display in an `<img>`
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import {
|
|
59
|
+
createSvgPreviewUrl,
|
|
60
|
+
revokeSvgPreviewUrl,
|
|
61
|
+
} from 'document-svg/preview-ui'
|
|
62
|
+
|
|
63
|
+
const url = createSvgPreviewUrl(svgMarkup)
|
|
64
|
+
imageElement.src = url
|
|
65
|
+
|
|
66
|
+
// Release the URL when replacing the page or unmounting the view.
|
|
67
|
+
revokeSvgPreviewUrl(url)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use a Data URL across process, Markdown, or renderer boundaries where Blob URLs cannot be shared:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { createSvgPreviewDataUrl } from 'document-svg/preview-ui'
|
|
74
|
+
imageElement.src = createSvgPreviewDataUrl(svgMarkup)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Data URLs are larger strings, so prefer Blob URLs within one renderer.
|
|
78
|
+
|
|
79
|
+
## React example
|
|
80
|
+
|
|
81
|
+
[`../examples/SvgPreview.tsx`](../examples/SvgPreview.tsx) includes Blob URL lifecycle management, copying, and an accessible `aria-live` status message.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
const report = await preview(filePath)
|
|
85
|
+
return <SvgPreview svg={report.pages[currentPage].svg} />
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Runnable HTML example
|
|
89
|
+
|
|
90
|
+
The included example converts every page into one standalone HTML preview:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run example:preview -- ./slides.pptx ./preview.html
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
See [`../examples/preview-to-html.cjs`](../examples/preview-to-html.cjs). It renders conversion warnings in the output and exits with status `2` when review is required.
|
|
97
|
+
|
|
98
|
+
## Copy to the clipboard
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import { copySvgToClipboard } from 'document-svg/preview-ui'
|
|
102
|
+
|
|
103
|
+
button.addEventListener('click', async () => {
|
|
104
|
+
const copiedType = await copySvgToClipboard(svgMarkup)
|
|
105
|
+
console.log(copiedType) // image/svg+xml or text/plain
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Clipboard APIs may require HTTPS or localhost and a user gesture such as a click. When the browser cannot write the SVG MIME type, the helper copies the exact SVG source as text.
|
|
110
|
+
|
|
111
|
+
## Warnings and limits
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const report = await preview('report.docx', {
|
|
115
|
+
maxInputBytes: 512 * 1024 * 1024,
|
|
116
|
+
maxZipEntryBytes: 128 * 1024 * 1024,
|
|
117
|
+
maxPages: 100,
|
|
118
|
+
maxXmlEvents: 20_000_000,
|
|
119
|
+
maxSvgBytes: 64 * 1024 * 1024,
|
|
120
|
+
maxTotalSvgBytes: 256 * 1024 * 1024,
|
|
121
|
+
jobs: 1,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (report.needsReview) {
|
|
125
|
+
console.warn(report.warnings)
|
|
126
|
+
console.warn(report.pages.flatMap((page) => page.warnings))
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`preview-ui` rejects SVG containing scripts, event attributes, external URLs, DOCTYPE declarations, or animation. Display previews through `<img>` instead of injecting them with `innerHTML`. Visually compare important files with their originals; zero warnings does not promise pixel-identical Office layout.
|
|
131
|
+
|
|
132
|
+
## Choosing an API
|
|
133
|
+
|
|
134
|
+
| API | Use case | Keeps files |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `preview(input, options)` | SVG strings for a UI | No |
|
|
137
|
+
| `convert(input, output, options)` | Batch conversion and saved artifacts | Yes |
|
|
138
|
+
| `reverse(input, output, options)` | Put SVG into an Office file | Yes |
|
|
139
|
+
| `createSvgPreviewUrl(svg)` | `<img>` in the same renderer | Blob URL only |
|
|
140
|
+
| `createSvgPreviewDataUrl(svg)` | Preview across process boundaries | No |
|
|
141
|
+
| `copySvgToClipboard(svg)` | Copy image or source | No |
|
|
142
|
+
|
|
143
|
+
The generated SVG aims to preserve editable appearance. It does not preserve Office semantics such as paragraphs, cells, or formulas.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
`MIT OR Apache-2.0`, at your option. When redistributing, you must retain copyright and license notices as required by the license you choose. See the [full terms](../LICENSE).
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# document-svg プレビューガイド
|
|
2
|
+
|
|
3
|
+
[日本語](preview.ja.md) · [English](preview.en.md) · [简体中文](preview.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
`document-svg`は、PDF・PowerPoint・Excel・Word文書を、アプリで安全に表示しやすいページ別SVGへ変換するNode.jsモジュールです。変換はRustのネイティブ処理をNode.jsのworkerで実行するため、イベントループを占有しません。
|
|
6
|
+
|
|
7
|
+
## できること
|
|
8
|
+
|
|
9
|
+
- PDF、PPTX、XLSX、DOCXを1ページずつ完全なSVG文字列へ変換
|
|
10
|
+
- 出力ファイルを残さないインメモリプレビュー
|
|
11
|
+
- Node.jsサーバー、Electronのmain process、サーバー側TypeScriptでの利用
|
|
12
|
+
- `<img>`向けBlob URL/Data URLの生成
|
|
13
|
+
- SVG画像またはSVGソースのクリップボードコピー
|
|
14
|
+
- 変換警告を`needsReview`で検出
|
|
15
|
+
- SVGページをPPTX、DOCX、XLSXへベクター画像として格納
|
|
16
|
+
|
|
17
|
+
純粋なブラウザだけで文書を変換するWASMモジュールではありません。文書変換はNode.js側で実行し、表示ヘルパー`document-svg/preview-ui`だけをrenderer/ブラウザ側で使います。
|
|
18
|
+
|
|
19
|
+
## インストール
|
|
20
|
+
|
|
21
|
+
公開後:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install document-svg
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
このリポジトリから試す場合:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd bindings/node
|
|
31
|
+
npm install
|
|
32
|
+
npm run build
|
|
33
|
+
npm test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Node.js 18以上が必要です。配布版では実行OS/CPUに合うネイティブパッケージも同時にインストールされます。
|
|
37
|
+
|
|
38
|
+
## 最小例: 文書をSVG文字列へ変換
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const { preview } = require('document-svg')
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const report = await preview('slides.pptx', { maxPages: 100 })
|
|
45
|
+
console.log(`${report.pageCount} pages`)
|
|
46
|
+
console.log(`review required: ${report.needsReview}`)
|
|
47
|
+
const firstPageSvg = report.pages[0]?.svg
|
|
48
|
+
console.log(firstPageSvg)
|
|
49
|
+
}
|
|
50
|
+
main().catch(console.error)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`report.pages`にはページ番号、SVG、pt単位の幅と高さ、警告、推定IRサイズが入ります。一時出力はPromiseの完了前に削除されます。
|
|
54
|
+
|
|
55
|
+
## `<img>`で表示する
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import {
|
|
59
|
+
createSvgPreviewUrl,
|
|
60
|
+
revokeSvgPreviewUrl,
|
|
61
|
+
} from 'document-svg/preview-ui'
|
|
62
|
+
|
|
63
|
+
const url = createSvgPreviewUrl(svgMarkup)
|
|
64
|
+
imageElement.src = url
|
|
65
|
+
|
|
66
|
+
// ページを差し替える時や画面を破棄する時に必ず解放する
|
|
67
|
+
revokeSvgPreviewUrl(url)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
プロセス境界、Markdown、Blob URLを共有できないrendererにはData URLを使えます。
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { createSvgPreviewDataUrl } from 'document-svg/preview-ui'
|
|
74
|
+
imageElement.src = createSvgPreviewDataUrl(svgMarkup)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Data URLは文字列が大きくなるため、同一画面内ではBlob URLを推奨します。
|
|
78
|
+
|
|
79
|
+
## React例
|
|
80
|
+
|
|
81
|
+
Blob URLの作成・解放、コピー操作、`aria-live`の状態表示まで含む実装は[`../examples/SvgPreview.tsx`](../examples/SvgPreview.tsx)にあります。
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
const report = await preview(filePath)
|
|
85
|
+
return <SvgPreview svg={report.pages[currentPage].svg} />
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 実行できるHTML生成例
|
|
89
|
+
|
|
90
|
+
同梱の例は、文書の全ページを1つのHTMLプレビューへ書き出します。
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run example:preview -- ./slides.pptx ./preview.html
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
実装は[`../examples/preview-to-html.cjs`](../examples/preview-to-html.cjs)を参照してください。変換警告がある場合はHTML内に表示し、終了コードを`2`にします。
|
|
97
|
+
|
|
98
|
+
## クリップボードへコピー
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import { copySvgToClipboard } from 'document-svg/preview-ui'
|
|
102
|
+
|
|
103
|
+
button.addEventListener('click', async () => {
|
|
104
|
+
const copiedType = await copySvgToClipboard(svgMarkup)
|
|
105
|
+
console.log(copiedType) // image/svg+xml または text/plain
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
クリップボードAPIはHTTPS/localhostなどのsecure contextと、クリック等のユーザー操作を要求する場合があります。ブラウザがSVG MIMEを扱えない場合は、正確なSVGソースをテキストとしてコピーします。
|
|
110
|
+
|
|
111
|
+
## 警告と安全上限
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const report = await preview('report.docx', {
|
|
115
|
+
maxInputBytes: 512 * 1024 * 1024,
|
|
116
|
+
maxZipEntryBytes: 128 * 1024 * 1024,
|
|
117
|
+
maxPages: 100,
|
|
118
|
+
maxXmlEvents: 20_000_000,
|
|
119
|
+
maxSvgBytes: 64 * 1024 * 1024,
|
|
120
|
+
maxTotalSvgBytes: 256 * 1024 * 1024,
|
|
121
|
+
jobs: 1,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (report.needsReview) {
|
|
125
|
+
console.warn(report.warnings)
|
|
126
|
+
console.warn(report.pages.flatMap((page) => page.warnings))
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`preview-ui`は`script`、イベント属性、外部URL、DOCTYPE、animation等を含むSVGを拒否します。表示には`innerHTML`ではなく`<img>`を使ってください。重要文書は元文書との目視比較を行い、警告ゼロを画素単位の完全一致と解釈しないでください。
|
|
131
|
+
|
|
132
|
+
## APIの使い分け
|
|
133
|
+
|
|
134
|
+
| API | 用途 | ファイルを残す |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `preview(input, options)` | UI向けSVG文字列 | いいえ |
|
|
137
|
+
| `convert(input, output, options)` | バッチ変換、成果物保存 | はい |
|
|
138
|
+
| `reverse(input, output, options)` | SVGをOffice形式へ格納 | はい |
|
|
139
|
+
| `createSvgPreviewUrl(svg)` | 同一rendererの`<img>`表示 | Blob URLのみ |
|
|
140
|
+
| `createSvgPreviewDataUrl(svg)` | プロセス境界を越える表示 | いいえ |
|
|
141
|
+
| `copySvgToClipboard(svg)` | 画像/ソースをコピー | いいえ |
|
|
142
|
+
|
|
143
|
+
変換後のSVGは編集可能な見た目を優先しますが、Officeの段落・セル・数式等の意味構造を保持するものではありません。
|
|
144
|
+
|
|
145
|
+
## ライセンス
|
|
146
|
+
|
|
147
|
+
`MIT OR Apache-2.0`。いずれかを選択できます。再配布時は、選択したライセンスに従い著作権表示・ライセンス文を必ず保持してください。[全文](../LICENSE)。
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# document-svg 预览指南
|
|
2
|
+
|
|
3
|
+
[日本語](preview.ja.md) · [English](preview.en.md) · [简体中文](preview.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
`document-svg` 是一个 Node.js 模块,可将 PDF、PowerPoint、Excel 和 Word 文档逐页转换为 SVG 预览。转换由 Rust 原生代码在 Node.js worker 中执行,不会阻塞事件循环。
|
|
6
|
+
|
|
7
|
+
## 可以做什么
|
|
8
|
+
|
|
9
|
+
- 将 PDF、PPTX、XLSX、DOCX 转换为每页一个完整的 SVG 字符串
|
|
10
|
+
- 在内存中生成预览,不保留输出文件
|
|
11
|
+
- 用于 Node.js 服务端、Electron main process 和服务端 TypeScript
|
|
12
|
+
- 为 `<img>` 创建 Blob URL 或 Data URL
|
|
13
|
+
- 将 SVG 图像或完整 XML 源码复制到剪贴板
|
|
14
|
+
- 通过 `needsReview` 报告转换警告
|
|
15
|
+
- 将 SVG 页面作为矢量图打包到 PPTX、DOCX 或 XLSX
|
|
16
|
+
|
|
17
|
+
它不是仅在浏览器中运行的 WASM 转换器。文档转换应在 Node.js 端执行;renderer 或浏览器端只使用`document-svg/preview-ui`。
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
发布后:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install document-svg
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
从本仓库试用:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd bindings/node
|
|
31
|
+
npm install
|
|
32
|
+
npm run build
|
|
33
|
+
npm test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
需要 Node.js 18 或更高版本。正式发布时,会同时安装与用户操作系统和CPU匹配的原生包。
|
|
37
|
+
|
|
38
|
+
## 最小示例:取得SVG字符串
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const { preview } = require('document-svg')
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const report = await preview('slides.pptx', { maxPages: 100 })
|
|
45
|
+
console.log(`${report.pageCount} pages`)
|
|
46
|
+
console.log(`review required: ${report.needsReview}`)
|
|
47
|
+
const firstPageSvg = report.pages[0]?.svg
|
|
48
|
+
console.log(firstPageSvg)
|
|
49
|
+
}
|
|
50
|
+
main().catch(console.error)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`report.pages`中的每个项目都包含页码、完整SVG、以pt为单位的宽高、警告和估算的IR大小。Promise完成前,临时转换文件会被删除。
|
|
54
|
+
|
|
55
|
+
## 在`<img>`中显示
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import {
|
|
59
|
+
createSvgPreviewUrl,
|
|
60
|
+
revokeSvgPreviewUrl,
|
|
61
|
+
} from 'document-svg/preview-ui'
|
|
62
|
+
|
|
63
|
+
const url = createSvgPreviewUrl(svgMarkup)
|
|
64
|
+
imageElement.src = url
|
|
65
|
+
|
|
66
|
+
// 更换页面或卸载视图时释放URL
|
|
67
|
+
revokeSvgPreviewUrl(url)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
如果需要跨进程、Markdown或renderer边界传递,Blob URL无法共享时可使用Data URL:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { createSvgPreviewDataUrl } from 'document-svg/preview-ui'
|
|
74
|
+
imageElement.src = createSvgPreviewDataUrl(svgMarkup)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Data URL字符串更大,因此在同一个renderer中建议使用Blob URL。
|
|
78
|
+
|
|
79
|
+
## React示例
|
|
80
|
+
|
|
81
|
+
[`../examples/SvgPreview.tsx`](../examples/SvgPreview.tsx)包含Blob URL的创建与释放、复制操作,以及可访问的`aria-live`状态提示。
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
const report = await preview(filePath)
|
|
85
|
+
return <SvgPreview svg={report.pages[currentPage].svg} />
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 可运行的HTML示例
|
|
89
|
+
|
|
90
|
+
附带示例可将文档的所有页面写入一个独立HTML预览文件:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run example:preview -- ./slides.pptx ./preview.html
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
实现请参阅[`../examples/preview-to-html.cjs`](../examples/preview-to-html.cjs)。转换警告会显示在HTML中;需要人工检查时,进程退出码为`2`。
|
|
97
|
+
|
|
98
|
+
## 复制到剪贴板
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import { copySvgToClipboard } from 'document-svg/preview-ui'
|
|
102
|
+
|
|
103
|
+
button.addEventListener('click', async () => {
|
|
104
|
+
const copiedType = await copySvgToClipboard(svgMarkup)
|
|
105
|
+
console.log(copiedType) // image/svg+xml 或 text/plain
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
剪贴板API可能要求HTTPS或localhost环境,并且必须由点击等用户操作触发。如果浏览器不支持写入SVG MIME类型,辅助函数会将完整SVG源码作为文本复制。
|
|
110
|
+
|
|
111
|
+
## 警告和安全限制
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const report = await preview('report.docx', {
|
|
115
|
+
maxInputBytes: 512 * 1024 * 1024,
|
|
116
|
+
maxZipEntryBytes: 128 * 1024 * 1024,
|
|
117
|
+
maxPages: 100,
|
|
118
|
+
maxXmlEvents: 20_000_000,
|
|
119
|
+
maxSvgBytes: 64 * 1024 * 1024,
|
|
120
|
+
maxTotalSvgBytes: 256 * 1024 * 1024,
|
|
121
|
+
jobs: 1,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (report.needsReview) {
|
|
125
|
+
console.warn(report.warnings)
|
|
126
|
+
console.warn(report.pages.flatMap((page) => page.warnings))
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`preview-ui`会拒绝包含脚本、事件属性、外部URL、DOCTYPE或动画的SVG。请通过`<img>`显示预览,不要用`innerHTML`注入。重要文档应与原文件进行目视比较;没有警告并不代表与Office像素级完全一致。
|
|
131
|
+
|
|
132
|
+
## API选择
|
|
133
|
+
|
|
134
|
+
| API | 用途 | 是否保留文件 |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `preview(input, options)` | 为UI返回SVG字符串 | 否 |
|
|
137
|
+
| `convert(input, output, options)` | 批量转换并保存结果 | 是 |
|
|
138
|
+
| `reverse(input, output, options)` | 将SVG放入Office文件 | 是 |
|
|
139
|
+
| `createSvgPreviewUrl(svg)` | 在同一renderer的`<img>`中显示 | 仅Blob URL |
|
|
140
|
+
| `createSvgPreviewDataUrl(svg)` | 跨进程边界显示 | 否 |
|
|
141
|
+
| `copySvgToClipboard(svg)` | 复制图像或源码 | 否 |
|
|
142
|
+
|
|
143
|
+
生成的SVG以保留可编辑外观为目标,但不会保留段落、单元格、公式等Office语义结构。
|
|
144
|
+
|
|
145
|
+
## 许可证
|
|
146
|
+
|
|
147
|
+
`MIT OR Apache-2.0`,您可以选择其中一种。再分发时,必须按照所选许可证保留版权声明和许可证文本。参阅[完整条款](../LICENSE)。
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useEffect, useState } from "react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
copySvgToClipboard,
|
|
5
|
+
createSvgPreviewUrl,
|
|
6
|
+
revokeSvgPreviewUrl,
|
|
7
|
+
} from "document-svg/preview-ui"
|
|
8
|
+
|
|
9
|
+
export interface SvgPreviewProps {
|
|
10
|
+
svg: string
|
|
11
|
+
alt?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function SvgPreview({
|
|
15
|
+
svg,
|
|
16
|
+
alt = "Office document preview",
|
|
17
|
+
}: SvgPreviewProps) {
|
|
18
|
+
const [previewUrl, setPreviewUrl] = useState<string>()
|
|
19
|
+
const [copyStatus, setCopyStatus] = useState("")
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const url = createSvgPreviewUrl(svg)
|
|
23
|
+
setPreviewUrl(url)
|
|
24
|
+
setCopyStatus("")
|
|
25
|
+
return () => revokeSvgPreviewUrl(url)
|
|
26
|
+
}, [svg])
|
|
27
|
+
|
|
28
|
+
async function copySvg() {
|
|
29
|
+
try {
|
|
30
|
+
const format = await copySvgToClipboard(svg)
|
|
31
|
+
setCopyStatus(
|
|
32
|
+
format === "image/svg+xml"
|
|
33
|
+
? "SVG画像をコピーしました"
|
|
34
|
+
: "SVGソースをコピーしました",
|
|
35
|
+
)
|
|
36
|
+
} catch (error) {
|
|
37
|
+
setCopyStatus(
|
|
38
|
+
error instanceof Error
|
|
39
|
+
? `コピーできませんでした: ${error.message}`
|
|
40
|
+
: "コピーできませんでした",
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<figure>
|
|
47
|
+
{previewUrl && <img src={previewUrl} alt={alt} />}
|
|
48
|
+
<figcaption>
|
|
49
|
+
<button type="button" onClick={copySvg}>
|
|
50
|
+
SVGをコピー
|
|
51
|
+
</button>
|
|
52
|
+
<span role="status" aria-live="polite">
|
|
53
|
+
{copyStatus}
|
|
54
|
+
</span>
|
|
55
|
+
</figcaption>
|
|
56
|
+
</figure>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { writeFile } = require('node:fs/promises')
|
|
4
|
+
const { resolve } = require('node:path')
|
|
5
|
+
|
|
6
|
+
const { preview } = require('document-svg')
|
|
7
|
+
const { createSvgPreviewDataUrl } = require('document-svg/preview-ui')
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
const input = process.argv[2]
|
|
11
|
+
const output = resolve(process.argv[3] || 'document-preview.html')
|
|
12
|
+
if (!input) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'Usage: node examples/preview-to-html.cjs INPUT.pdf|pptx|xlsx|docx [OUTPUT.html]',
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const report = await preview(resolve(input), {
|
|
19
|
+
maxPages: 100,
|
|
20
|
+
maxSvgBytes: 64 * 1024 * 1024,
|
|
21
|
+
maxTotalSvgBytes: 256 * 1024 * 1024,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const pages = report.pages
|
|
25
|
+
.map((page) => {
|
|
26
|
+
const url = createSvgPreviewDataUrl(page.svg)
|
|
27
|
+
return `<figure><img src="${url}" alt="Page ${page.number}"><figcaption>Page ${page.number}</figcaption></figure>`
|
|
28
|
+
})
|
|
29
|
+
.join('\n')
|
|
30
|
+
|
|
31
|
+
const warnings = report.warnings.length
|
|
32
|
+
? `<pre>${escapeHtml(report.warnings.join('\n'))}</pre>`
|
|
33
|
+
: '<p>No conversion warnings.</p>'
|
|
34
|
+
|
|
35
|
+
const html = `<!doctype html>
|
|
36
|
+
<html lang="en">
|
|
37
|
+
<meta charset="utf-8">
|
|
38
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
39
|
+
<title>Document SVG preview</title>
|
|
40
|
+
<style>
|
|
41
|
+
body { margin: 0 auto; max-width: 72rem; padding: 2rem; background: #eee; font: 16px system-ui; }
|
|
42
|
+
header, figure { background: white; padding: 1rem; border-radius: .5rem; }
|
|
43
|
+
figure { margin: 1rem 0; box-shadow: 0 2px 12px #0002; }
|
|
44
|
+
img { display: block; width: 100%; height: auto; }
|
|
45
|
+
figcaption { margin-top: .5rem; color: #555; }
|
|
46
|
+
</style>
|
|
47
|
+
<header><h1>${escapeHtml(report.sourceFormat.toUpperCase())} preview</h1>${warnings}</header>
|
|
48
|
+
<main>${pages}</main>
|
|
49
|
+
</html>`
|
|
50
|
+
|
|
51
|
+
await writeFile(output, html, { encoding: 'utf8', flag: 'wx' })
|
|
52
|
+
console.log(`Wrote ${report.pageCount} page(s) to ${output}`)
|
|
53
|
+
if (report.needsReview) process.exitCode = 2
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function escapeHtml(value) {
|
|
57
|
+
return value.replace(/[&<>"']/g, (character) => ({
|
|
58
|
+
'&': '&',
|
|
59
|
+
'<': '<',
|
|
60
|
+
'>': '>',
|
|
61
|
+
'"': '"',
|
|
62
|
+
"'": ''',
|
|
63
|
+
})[character])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
main().catch((error) => {
|
|
67
|
+
console.error(error instanceof Error ? error.message : error)
|
|
68
|
+
process.exitCode = 1
|
|
69
|
+
})
|