@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +50 -16
  3. package/agent-map.d.ts +32 -0
  4. package/agent-map.js +5 -3
  5. package/ascii-export.d.ts +10 -0
  6. package/ascii-export.js +21 -6
  7. package/capture-frames.js +5 -5
  8. package/color-tint.js +4 -0
  9. package/context-export.d.ts +23 -0
  10. package/context-export.js +4 -1
  11. package/filter.js +1 -1
  12. package/gif-export.d.ts +6 -0
  13. package/gif-export.js +3 -3
  14. package/html-export.d.ts +8 -0
  15. package/html-export.js +6 -4
  16. package/index.d.ts +2 -1
  17. package/index.js +2 -1
  18. package/package.json +16 -5
  19. package/pdf/LICENSE +21 -0
  20. package/pdf/README.md +22 -0
  21. package/pdf/REFERENCE.md +520 -0
  22. package/pdf/src/frontmatter.js +200 -0
  23. package/pdf/src/furniture.js +398 -0
  24. package/pdf/src/index.js +2415 -0
  25. package/pdf/src/mega/README.md +47 -0
  26. package/pdf/src/mega/diag.js +148 -0
  27. package/pdf/src/mega/flow.js +56 -0
  28. package/pdf/src/mega/fonts.js +287 -0
  29. package/pdf/src/mega/forms.js +479 -0
  30. package/pdf/src/mega/labels.js +43 -0
  31. package/pdf/src/mega/meta.js +145 -0
  32. package/pdf/src/nav.js +357 -0
  33. package/pdf/src/paginate.js +583 -0
  34. package/pdf/src/tagged.js +355 -0
  35. package/pdf/src/text-layer.js +1771 -0
  36. package/pdf/src/writer/encrypt.js +218 -0
  37. package/pdf/src/writer/font.js +507 -0
  38. package/pdf/src/writer/index.js +1135 -0
  39. package/pdf/src/writer/subset.js +387 -0
  40. package/pdf.d.ts +294 -0
  41. package/pdf.js +1 -0
  42. package/redact-inputs.js +6 -6
  43. package/vector/CONTRACT.md +906 -0
  44. package/vector/LICENSE +21 -0
  45. package/vector/README.md +35 -0
  46. package/vector/src/adapters/snapdom.js +795 -0
  47. package/vector/src/collect/box.js +1676 -0
  48. package/vector/src/collect/font.js +487 -0
  49. package/vector/src/collect/image.js +1458 -0
  50. package/vector/src/collect/mathml.js +272 -0
  51. package/vector/src/collect/text.js +3027 -0
  52. package/vector/src/css/color.js +823 -0
  53. package/vector/src/css/gradient.js +1126 -0
  54. package/vector/src/css/matrix.js +398 -0
  55. package/vector/src/css/radius.js +124 -0
  56. package/vector/src/css/shadow.js +201 -0
  57. package/vector/src/emit/figma-json.js +1995 -0
  58. package/vector/src/emit/h2d-vector.js +215 -0
  59. package/vector/src/emit/h2d.js +1147 -0
  60. package/vector/src/emit/pdf.js +2571 -0
  61. package/vector/src/emit/svg-flat.js +4842 -0
  62. package/vector/src/exclusion.js +13 -0
  63. package/vector/src/index.js +2910 -0
  64. package/vector/src/paint.js +2908 -0
  65. package/vector/src/public.js +57 -0
  66. package/vector/src/writer/encrypt.js +227 -0
  67. package/vector/src/writer/font.js +507 -0
  68. package/vector/src/writer/index.js +1148 -0
  69. package/vector/src/writer/subset.js +387 -0
  70. package/vector/svd/index.js +72 -0
  71. package/vector/svd/schema.js +985 -0
  72. package/vector.d.ts +28 -0
  73. package/vector.js +1 -0
  74. package/video-export.d.ts +6 -0
  75. package/video-export.js +7 -7
  76. package/pdf-image.d.ts +0 -18
  77. 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
+ }