@zumer/snapdom-plugins 3.0.0 → 4.0.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/CHANGELOG.md +13 -0
- package/README.md +50 -16
- package/agent-map.d.ts +32 -0
- package/agent-map.js +5 -3
- package/ascii-export.d.ts +10 -0
- package/ascii-export.js +21 -6
- package/capture-frames.js +5 -5
- package/color-tint.js +4 -0
- package/context-export.d.ts +23 -0
- package/context-export.js +4 -1
- package/filter.js +1 -1
- package/gif-export.d.ts +6 -0
- package/gif-export.js +3 -3
- package/html-export.d.ts +8 -0
- package/html-export.js +6 -4
- package/index.d.ts +2 -1
- package/index.js +2 -1
- package/package.json +16 -5
- package/pdf/LICENSE +21 -0
- package/pdf/README.md +22 -0
- package/pdf/REFERENCE.md +520 -0
- package/pdf/src/frontmatter.js +200 -0
- package/pdf/src/furniture.js +398 -0
- package/pdf/src/index.js +2415 -0
- package/pdf/src/mega/README.md +47 -0
- package/pdf/src/mega/diag.js +148 -0
- package/pdf/src/mega/flow.js +56 -0
- package/pdf/src/mega/fonts.js +287 -0
- package/pdf/src/mega/forms.js +479 -0
- package/pdf/src/mega/labels.js +43 -0
- package/pdf/src/mega/meta.js +145 -0
- package/pdf/src/nav.js +357 -0
- package/pdf/src/paginate.js +583 -0
- package/pdf/src/tagged.js +355 -0
- package/pdf/src/text-layer.js +1771 -0
- package/pdf/src/writer/encrypt.js +218 -0
- package/pdf/src/writer/font.js +507 -0
- package/pdf/src/writer/index.js +1135 -0
- package/pdf/src/writer/subset.js +387 -0
- package/pdf.d.ts +294 -0
- package/pdf.js +1 -0
- package/redact-inputs.js +6 -6
- package/vector/CONTRACT.md +906 -0
- package/vector/LICENSE +21 -0
- package/vector/README.md +35 -0
- package/vector/src/adapters/snapdom.js +795 -0
- package/vector/src/collect/box.js +1676 -0
- package/vector/src/collect/font.js +487 -0
- package/vector/src/collect/image.js +1458 -0
- package/vector/src/collect/mathml.js +272 -0
- package/vector/src/collect/text.js +3027 -0
- package/vector/src/css/color.js +823 -0
- package/vector/src/css/gradient.js +1126 -0
- package/vector/src/css/matrix.js +398 -0
- package/vector/src/css/radius.js +124 -0
- package/vector/src/css/shadow.js +201 -0
- package/vector/src/emit/figma-json.js +1995 -0
- package/vector/src/emit/h2d-vector.js +215 -0
- package/vector/src/emit/h2d.js +1147 -0
- package/vector/src/emit/pdf.js +2571 -0
- package/vector/src/emit/svg-flat.js +4842 -0
- package/vector/src/exclusion.js +13 -0
- package/vector/src/index.js +2910 -0
- package/vector/src/paint.js +2908 -0
- package/vector/src/public.js +57 -0
- package/vector/src/writer/encrypt.js +227 -0
- package/vector/src/writer/font.js +507 -0
- package/vector/src/writer/index.js +1148 -0
- package/vector/src/writer/subset.js +387 -0
- package/vector/svd/index.js +72 -0
- package/vector/svd/schema.js +985 -0
- package/vector.d.ts +28 -0
- package/vector.js +1 -0
- package/video-export.d.ts +6 -0
- package/video-export.js +7 -7
- package/pdf-image.d.ts +0 -18
- package/pdf-image.js +0 -114
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `box-shadow` / `text-shadow` -> `{shadows: Effect[], warnings: string[]}`.
|
|
3
|
+
*
|
|
4
|
+
* The grammar is `<color>? && [<length>{2,4} && inset?]`, and the `&&` is the
|
|
5
|
+
* whole difficulty: the colour, the keyword and the length run may appear in any
|
|
6
|
+
* order, and only the lengths are ordered among themselves (x, y, blur, spread).
|
|
7
|
+
* Chromium serialises colour-first, Firefox has historically serialised
|
|
8
|
+
* colour-last, and an author's own string may do either — so nothing here may
|
|
9
|
+
* assume a position.
|
|
10
|
+
*
|
|
11
|
+
* `blur` is the CSS radius VERBATIM. SVG sigma is `blur / 2` and that division
|
|
12
|
+
* belongs to the emitter (svd invariant 5); doing it here would make the value
|
|
13
|
+
* mean two different things depending on who read it.
|
|
14
|
+
*
|
|
15
|
+
* Pure: no DOM, no `getComputedStyle`.
|
|
16
|
+
*/
|
|
17
|
+
import { parseColor } from './color.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Absolute units only. `em`/`rem`/`ex`/`ch`/`vw`… need a font or a viewport this
|
|
21
|
+
* module does not have and must not guess at; a computed style never contains
|
|
22
|
+
* them anyway, so meeting one means the input is an authored string and the
|
|
23
|
+
* honest answer is to say we could not read it.
|
|
24
|
+
*/
|
|
25
|
+
const ABS_UNITS = {
|
|
26
|
+
px: 1, pt: 96 / 72, pc: 16, in: 96, cm: 96 / 2.54, mm: 96 / 25.4, q: 96 / 25.4 / 4,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const NUMBER = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)([a-z%]*)$/i
|
|
30
|
+
|
|
31
|
+
/** `null` when the token is not a length — that is how the caller tells lengths from colours. */
|
|
32
|
+
function toPx(token) {
|
|
33
|
+
const m = NUMBER.exec(token)
|
|
34
|
+
if (!m) return null
|
|
35
|
+
const n = parseFloat(m[1])
|
|
36
|
+
if (!Number.isFinite(n)) return null
|
|
37
|
+
const unit = m[2].toLowerCase()
|
|
38
|
+
// A bare number is a length only when it is zero; `5` alone is invalid CSS and
|
|
39
|
+
// treating it as 5px would invent geometry.
|
|
40
|
+
if (!unit) return n === 0 ? 0 : null
|
|
41
|
+
const k = ABS_UNITS[unit]
|
|
42
|
+
return k === undefined ? null : n * k
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Split on `sep` at paren depth 0. `rgba(0, 0, 0, .5)` carries commas of its own
|
|
47
|
+
* and `color-mix(in srgb, a 40%, b)` carries both commas and spaces, so a naive
|
|
48
|
+
* `split(',')` cuts a single shadow into three unparseable fragments.
|
|
49
|
+
*/
|
|
50
|
+
function splitTop(str, sep) {
|
|
51
|
+
const out = []
|
|
52
|
+
let depth = 0
|
|
53
|
+
let start = 0
|
|
54
|
+
for (let i = 0; i < str.length; i++) {
|
|
55
|
+
const ch = str[i]
|
|
56
|
+
if (ch === '(') depth++
|
|
57
|
+
else if (ch === ')') { if (depth > 0) depth-- }
|
|
58
|
+
else if (depth === 0 && ch === sep) { out.push(str.slice(start, i)); start = i + 1 }
|
|
59
|
+
}
|
|
60
|
+
out.push(str.slice(start))
|
|
61
|
+
return out
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Same depth rule, on whitespace: `rgb(0 0 0 / 50%)` must survive as one token. */
|
|
65
|
+
function tokenize(str) {
|
|
66
|
+
const out = []
|
|
67
|
+
let depth = 0
|
|
68
|
+
let cur = ''
|
|
69
|
+
for (const ch of str) {
|
|
70
|
+
if (ch === '(') { depth++; cur += ch } else if (ch === ')') {
|
|
71
|
+
if (depth > 0) depth--
|
|
72
|
+
cur += ch
|
|
73
|
+
} else if (depth === 0 && /\s/.test(ch)) {
|
|
74
|
+
if (cur) { out.push(cur); cur = '' }
|
|
75
|
+
} else cur += ch
|
|
76
|
+
}
|
|
77
|
+
if (cur) out.push(cur)
|
|
78
|
+
return out
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse a `box-shadow` / `text-shadow` value.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} css the whole declaration, comma-separated
|
|
85
|
+
* @param {object} [options]
|
|
86
|
+
* @param {boolean} [options.inset=true] `false` for `text-shadow`, where neither
|
|
87
|
+
* the `inset` keyword nor a spread is legal
|
|
88
|
+
* @param {string} [options.currentColor] the element's used `color`, for the
|
|
89
|
+
* colourless form. The initial value of a
|
|
90
|
+
* shadow colour IS `currentColor`, and only
|
|
91
|
+
* the caller has it; without it the answer
|
|
92
|
+
* is opaque black plus a warning, which is
|
|
93
|
+
* wrong on every coloured shadow.
|
|
94
|
+
* @returns {{shadows: Array<{type:'dropShadow'|'innerShadow', color:number[],
|
|
95
|
+
* offset:number[], blur:number, spread:number, behind:boolean}>,
|
|
96
|
+
* warnings: string[]}}
|
|
97
|
+
* `shadows` in CSS order (first declared shadow first — the emitter paints it
|
|
98
|
+
* on top), `warnings` listing everything dropped, clamped or assumed.
|
|
99
|
+
*
|
|
100
|
+
* The warnings used to ride along as a non-enumerable property of the returned
|
|
101
|
+
* array, which both callers ignored: a shadow that fell back to opaque black
|
|
102
|
+
* reached the document with grade `E` and no diagnostic. A degradation channel
|
|
103
|
+
* nobody can forget is worth the signature change — see CONTRACT.md, which
|
|
104
|
+
* still documents the old `Effect[]` return.
|
|
105
|
+
*/
|
|
106
|
+
export function parseShadows(css, options = {}) {
|
|
107
|
+
const { inset: insetAllowed = true, currentColor = null } = options
|
|
108
|
+
const out = []
|
|
109
|
+
const warnings = []
|
|
110
|
+
// Resolved once: the same `color` serves every shadow in the list.
|
|
111
|
+
const current = currentColor ? parseColor(currentColor) : null
|
|
112
|
+
if (currentColor && !current) {
|
|
113
|
+
warnings.push(`shadow: currentColor "${currentColor}" could not be parsed; colourless shadows fall back to opaque black`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const src = typeof css === 'string' ? css.trim() : ''
|
|
117
|
+
if (!src || src === 'none') return { shadows: out, warnings }
|
|
118
|
+
|
|
119
|
+
for (const raw of splitTop(src, ',')) {
|
|
120
|
+
const decl = raw.trim()
|
|
121
|
+
if (!decl) continue
|
|
122
|
+
|
|
123
|
+
const lengths = []
|
|
124
|
+
const rest = []
|
|
125
|
+
let isInset = false
|
|
126
|
+
let bad = null
|
|
127
|
+
|
|
128
|
+
for (const token of tokenize(decl)) {
|
|
129
|
+
if (/^inset$/i.test(token)) { isInset = true; continue }
|
|
130
|
+
// A token that looks numeric is meant as a length, so a unit we cannot
|
|
131
|
+
// resolve is an error rather than an invitation to try it as a colour.
|
|
132
|
+
if (NUMBER.test(token)) {
|
|
133
|
+
const px = toPx(token)
|
|
134
|
+
if (px === null) { bad = token; break }
|
|
135
|
+
lengths.push(px)
|
|
136
|
+
continue
|
|
137
|
+
}
|
|
138
|
+
rest.push(token)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (bad) {
|
|
142
|
+
warnings.push(`shadow: unsupported length "${bad}" in "${decl}" — shadow dropped`)
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
if (lengths.length < 2 || lengths.length > 4) {
|
|
146
|
+
warnings.push(`shadow: expected 2-4 lengths in "${decl}", got ${lengths.length} — shadow dropped`)
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
if (isInset && !insetAllowed) {
|
|
150
|
+
warnings.push(`shadow: "inset" is not valid here ("${decl}") — shadow dropped`)
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let [x, y, blur = 0, spread = 0] = lengths
|
|
155
|
+
if (blur < 0) {
|
|
156
|
+
warnings.push(`shadow: negative blur ${blur} in "${decl}" — clamped to 0`)
|
|
157
|
+
blur = 0
|
|
158
|
+
}
|
|
159
|
+
if (spread !== 0 && !insetAllowed) {
|
|
160
|
+
warnings.push(`shadow: spread is not valid on text-shadow ("${decl}") — ignored`)
|
|
161
|
+
spread = 0
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let color = [0, 0, 0, 1]
|
|
165
|
+
const colorSrc = rest.length ? rest.join(' ') : ''
|
|
166
|
+
// The initial value of the colour IS `currentColor`, and Chromium serialises
|
|
167
|
+
// `filter: drop-shadow(0 1px 2px)` without one, so the colourless form is the
|
|
168
|
+
// common case rather than an authored curiosity.
|
|
169
|
+
if (!colorSrc || /^currentcolor$/i.test(colorSrc)) {
|
|
170
|
+
if (current) {
|
|
171
|
+
color = current.rgba
|
|
172
|
+
for (const w of current.warnings || []) warnings.push(`shadow color (currentColor) in "${decl}": ${w}`)
|
|
173
|
+
} else {
|
|
174
|
+
warnings.push(`shadow: no color in "${decl}" — currentColor was not supplied by the caller, assumed opaque black`)
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
// Anything left is the colour. Joining rather than taking rest[0] keeps
|
|
178
|
+
// legacy space-separated forms that slipped past the tokenizer readable.
|
|
179
|
+
const parsed = parseColor(colorSrc)
|
|
180
|
+
if (parsed) {
|
|
181
|
+
color = parsed.rgba
|
|
182
|
+
// `parseColor` approximates too (canvas round-trip, gamut mapping); its
|
|
183
|
+
// warnings are about THIS shadow's colour and die here if not forwarded.
|
|
184
|
+
for (const w of parsed.warnings || []) warnings.push(`shadow color in "${decl}": ${w}`)
|
|
185
|
+
} else {
|
|
186
|
+
warnings.push(`shadow: unparseable color "${colorSrc}" in "${decl}" — assumed opaque black`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
out.push({
|
|
191
|
+
type: isInset ? 'innerShadow' : 'dropShadow',
|
|
192
|
+
color,
|
|
193
|
+
offset: [x, y],
|
|
194
|
+
blur,
|
|
195
|
+
spread,
|
|
196
|
+
behind: false,
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { shadows: out, warnings }
|
|
201
|
+
}
|