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
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "document-svg",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Preview PDF and Office documents as page-by-page SVG from Node.js",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"svg",
|
|
7
|
+
"preview",
|
|
8
|
+
"pdf",
|
|
9
|
+
"pptx",
|
|
10
|
+
"xlsx",
|
|
11
|
+
"docx",
|
|
12
|
+
"electron"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT OR Apache-2.0",
|
|
15
|
+
"main": "index.js",
|
|
16
|
+
"types": "index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./index.d.ts",
|
|
20
|
+
"require": "./index.js",
|
|
21
|
+
"default": "./index.js"
|
|
22
|
+
},
|
|
23
|
+
"./preview-ui": {
|
|
24
|
+
"types": "./preview-ui.d.ts",
|
|
25
|
+
"require": "./preview-ui.js",
|
|
26
|
+
"default": "./preview-ui.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"index.js",
|
|
34
|
+
"index.d.ts",
|
|
35
|
+
"preview-ui.js",
|
|
36
|
+
"preview-ui.d.ts",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"THIRD_PARTY_NOTICES.md",
|
|
39
|
+
"THIRD_PARTY_LICENSES.txt",
|
|
40
|
+
"docs",
|
|
41
|
+
"examples",
|
|
42
|
+
"LICENSE-MIT",
|
|
43
|
+
"LICENSE-APACHE"
|
|
44
|
+
],
|
|
45
|
+
"napi": {
|
|
46
|
+
"binaryName": "document-svg",
|
|
47
|
+
"targets": [
|
|
48
|
+
"x86_64-apple-darwin",
|
|
49
|
+
"aarch64-apple-darwin",
|
|
50
|
+
"x86_64-pc-windows-msvc",
|
|
51
|
+
"aarch64-pc-windows-msvc",
|
|
52
|
+
"x86_64-unknown-linux-gnu",
|
|
53
|
+
"aarch64-unknown-linux-gnu",
|
|
54
|
+
"x86_64-unknown-linux-musl",
|
|
55
|
+
"aarch64-unknown-linux-musl"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "https://github.com/ryusui-hiro/document-svg.git",
|
|
61
|
+
"directory": "bindings/node"
|
|
62
|
+
},
|
|
63
|
+
"optionalDependencies": {
|
|
64
|
+
"document-svg-linux-x64-gnu": "0.1.1",
|
|
65
|
+
"document-svg-linux-arm64-gnu": "0.1.1",
|
|
66
|
+
"document-svg-linux-x64-musl": "0.1.1",
|
|
67
|
+
"document-svg-linux-arm64-musl": "0.1.1",
|
|
68
|
+
"document-svg-darwin-x64": "0.1.1",
|
|
69
|
+
"document-svg-darwin-arm64": "0.1.1",
|
|
70
|
+
"document-svg-win32-x64-msvc": "0.1.1",
|
|
71
|
+
"document-svg-win32-arm64-msvc": "0.1.1"
|
|
72
|
+
},
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public"
|
|
75
|
+
}
|
|
76
|
+
}
|
package/preview-ui.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Create an object URL suitable for an `<img>` src. */
|
|
2
|
+
export declare function createSvgPreviewUrl(svg: string): string
|
|
3
|
+
|
|
4
|
+
/** Create a validated self-contained data URL for cross-process previews. */
|
|
5
|
+
export declare function createSvgPreviewDataUrl(svg: string): string
|
|
6
|
+
|
|
7
|
+
/** Revoke an object URL previously returned by `createSvgPreviewUrl()`. */
|
|
8
|
+
export declare function revokeSvgPreviewUrl(url: string): void
|
|
9
|
+
|
|
10
|
+
/** Copy the exact SVG XML source as plain text. */
|
|
11
|
+
export declare function copySvgSourceToClipboard(svg: string): Promise<void>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Copy SVG as `image/svg+xml` when supported, with an exact-source text
|
|
15
|
+
* fallback. Call this from a user gesture such as a button click.
|
|
16
|
+
*
|
|
17
|
+
* The resolved value reports the MIME type that was actually copied.
|
|
18
|
+
*/
|
|
19
|
+
export declare function copySvgToClipboard(
|
|
20
|
+
svg: string,
|
|
21
|
+
): Promise<'image/svg+xml' | 'text/plain'>
|
package/preview-ui.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function assertSvg(svg, depth = 0) {
|
|
4
|
+
if (depth > 4 || (typeof svg === 'string' && svg.length > 64 * 1024 * 1024)) {
|
|
5
|
+
throw new TypeError('SVG preview size or nesting limit exceeded')
|
|
6
|
+
}
|
|
7
|
+
if (typeof svg !== 'string' || !/<svg(?:\s|>)/i.test(svg)) {
|
|
8
|
+
throw new TypeError('svg must be a complete SVG markup string')
|
|
9
|
+
}
|
|
10
|
+
if (
|
|
11
|
+
/<!DOCTYPE|<!ENTITY|<\s*(?:[\w.-]+:)?(?:script|foreignObject|iframe|object|embed|animate|animateMotion|animateTransform|set|discard)\b/i.test(svg) ||
|
|
12
|
+
/\son[a-z]+\s*=/i.test(svg) ||
|
|
13
|
+
/\b(?:href|xlink:href)\s*=\s*['"]\s*(?:https?:|javascript:|file:|blob:)/i.test(svg) ||
|
|
14
|
+
/@import\b|url\(\s*['"]?\s*(?:https?:|javascript:|file:|blob:)/i.test(svg)
|
|
15
|
+
) {
|
|
16
|
+
throw new TypeError('svg must not contain executable or external content')
|
|
17
|
+
}
|
|
18
|
+
const allowedReference = (value) => {
|
|
19
|
+
if (/^\s*#[a-z0-9_.:-]+\s*$/i.test(value) ||
|
|
20
|
+
/^\s*data:image\/(?:png|jpeg|gif|webp);base64,[a-z0-9+/=\s]*$/i.test(value)) return true
|
|
21
|
+
if (/^\s*data:image\/svg\+xml;base64,[a-z0-9+/=\s]+$/i.test(value)) {
|
|
22
|
+
try {
|
|
23
|
+
const bytes = Uint8Array.from(atob(value.split(',')[1]), (c) => c.charCodeAt(0))
|
|
24
|
+
assertSvg(new TextDecoder('utf-8', { fatal: true }).decode(bytes), depth + 1)
|
|
25
|
+
return true
|
|
26
|
+
} catch { return false }
|
|
27
|
+
}
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
for (const match of svg.matchAll(/\b(?:[\w.-]+:)?href\s*=\s*(['"])(.*?)\1/gis)) {
|
|
31
|
+
if (!allowedReference(match[2])) {
|
|
32
|
+
throw new TypeError('svg must not contain executable or external content')
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const match of svg.matchAll(/url\s*\(\s*(['"]?)(.*?)\1\s*\)/gis)) {
|
|
36
|
+
if (!/^#[a-z0-9_.:-]+$/i.test(match[2].trim())) {
|
|
37
|
+
throw new TypeError('svg must not contain executable or external content')
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (/xml:base\s*=|<\?xml-stylesheet\b/i.test(svg)) {
|
|
41
|
+
throw new TypeError('svg must not contain executable or external content')
|
|
42
|
+
}
|
|
43
|
+
for (const match of svg.matchAll(/\bstyle\s*=\s*(['"])(.*?)\1|<style\b[^>]*>(.*?)<\/style\s*>/gis)) {
|
|
44
|
+
if (/[\\&@]|\/\*/.test(match[2] ?? match[3])) {
|
|
45
|
+
throw new TypeError('svg must not contain executable or external content')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function browserGlobal(name) {
|
|
51
|
+
const value = globalThis[name]
|
|
52
|
+
if (value == null) {
|
|
53
|
+
throw new Error(`${name} is not available in this environment`)
|
|
54
|
+
}
|
|
55
|
+
return value
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create an object URL suitable for an <img> src. Revoke it when the preview
|
|
60
|
+
* is replaced or unmounted.
|
|
61
|
+
*/
|
|
62
|
+
function createSvgPreviewUrl(svg) {
|
|
63
|
+
assertSvg(svg)
|
|
64
|
+
const BlobConstructor = browserGlobal('Blob')
|
|
65
|
+
const Url = browserGlobal('URL')
|
|
66
|
+
if (typeof Url.createObjectURL !== 'function') {
|
|
67
|
+
throw new Error('URL.createObjectURL is not available in this environment')
|
|
68
|
+
}
|
|
69
|
+
return Url.createObjectURL(
|
|
70
|
+
new BlobConstructor([svg], { type: 'image/svg+xml;charset=utf-8' }),
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a self-contained data URL for renderers where blob URLs cannot cross
|
|
76
|
+
* process or Markdown boundaries. The SVG is validated before encoding.
|
|
77
|
+
*/
|
|
78
|
+
function createSvgPreviewDataUrl(svg) {
|
|
79
|
+
assertSvg(svg)
|
|
80
|
+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Revoke an object URL previously returned by createSvgPreviewUrl(). */
|
|
84
|
+
function revokeSvgPreviewUrl(url) {
|
|
85
|
+
const Url = browserGlobal('URL')
|
|
86
|
+
if (typeof Url.revokeObjectURL !== 'function') {
|
|
87
|
+
throw new Error('URL.revokeObjectURL is not available in this environment')
|
|
88
|
+
}
|
|
89
|
+
Url.revokeObjectURL(url)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function legacyCopyText(text) {
|
|
93
|
+
const documentObject = browserGlobal('document')
|
|
94
|
+
const textarea = documentObject.createElement('textarea')
|
|
95
|
+
textarea.value = text
|
|
96
|
+
textarea.setAttribute('readonly', '')
|
|
97
|
+
textarea.style.position = 'fixed'
|
|
98
|
+
textarea.style.opacity = '0'
|
|
99
|
+
documentObject.body.appendChild(textarea)
|
|
100
|
+
textarea.select()
|
|
101
|
+
try {
|
|
102
|
+
if (!documentObject.execCommand('copy')) {
|
|
103
|
+
throw new Error('the browser rejected the clipboard copy command')
|
|
104
|
+
}
|
|
105
|
+
} finally {
|
|
106
|
+
textarea.remove()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Copy the exact SVG XML source as plain text. */
|
|
111
|
+
async function copySvgSourceToClipboard(svg) {
|
|
112
|
+
assertSvg(svg)
|
|
113
|
+
const navigatorObject = globalThis.navigator
|
|
114
|
+
if (navigatorObject?.clipboard?.writeText) {
|
|
115
|
+
await navigatorObject.clipboard.writeText(svg)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
legacyCopyText(svg)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Copy SVG using image/svg+xml when the Async Clipboard API explicitly
|
|
123
|
+
* supports it. Otherwise copy the exact SVG XML as text/plain.
|
|
124
|
+
*
|
|
125
|
+
* Must be called from a user gesture such as a button click.
|
|
126
|
+
*/
|
|
127
|
+
async function copySvgToClipboard(svg) {
|
|
128
|
+
assertSvg(svg)
|
|
129
|
+
const navigatorObject = globalThis.navigator
|
|
130
|
+
const ClipboardItemConstructor = globalThis.ClipboardItem
|
|
131
|
+
const supportsSvg =
|
|
132
|
+
typeof ClipboardItemConstructor === 'function' &&
|
|
133
|
+
typeof ClipboardItemConstructor.supports === 'function' &&
|
|
134
|
+
ClipboardItemConstructor.supports('image/svg+xml')
|
|
135
|
+
|
|
136
|
+
if (supportsSvg && navigatorObject?.clipboard?.write) {
|
|
137
|
+
const BlobConstructor = browserGlobal('Blob')
|
|
138
|
+
try {
|
|
139
|
+
await navigatorObject.clipboard.write([
|
|
140
|
+
new ClipboardItemConstructor({
|
|
141
|
+
'image/svg+xml': new BlobConstructor([svg], {
|
|
142
|
+
type: 'image/svg+xml',
|
|
143
|
+
}),
|
|
144
|
+
}),
|
|
145
|
+
])
|
|
146
|
+
return 'image/svg+xml'
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (!navigatorObject.clipboard.writeText) {
|
|
149
|
+
throw error
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await copySvgSourceToClipboard(svg)
|
|
155
|
+
return 'text/plain'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
copySvgSourceToClipboard,
|
|
160
|
+
copySvgToClipboard,
|
|
161
|
+
createSvgPreviewDataUrl,
|
|
162
|
+
createSvgPreviewUrl,
|
|
163
|
+
revokeSvgPreviewUrl,
|
|
164
|
+
}
|