imagescript 3.2.1 → 3.4.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/README.md +35 -10
- package/THIRD_PARTY_NOTICES.md +298 -2
- package/dist/animation.d.ts +25 -8
- package/dist/animation.js +44 -13
- package/dist/codecs/av1/cdef.js +197 -0
- package/dist/codecs/av1/data.js +141 -0
- package/dist/codecs/av1/decode.js +1769 -0
- package/dist/codecs/av1/filmgrain.js +219 -0
- package/dist/codecs/av1/frame.js +114 -0
- package/dist/codecs/av1/ipred.js +491 -0
- package/dist/codecs/av1/itx.js +950 -0
- package/dist/codecs/av1/loopfilter.js +149 -0
- package/dist/codecs/av1/msac.js +161 -0
- package/dist/codecs/av1/obu.js +516 -0
- package/dist/codecs/av1/postfilter.js +28 -0
- package/dist/codecs/av1/restoration.js +193 -0
- package/dist/codecs/av1/tables.js +160 -0
- package/dist/codecs/avif.js +581 -0
- package/dist/codecs/bmp.js +317 -0
- package/dist/codecs/crc.js +5 -0
- package/dist/codecs/gif.js +10 -35
- package/dist/codecs/hdr.d.ts +25 -0
- package/dist/codecs/hdr.js +201 -0
- package/dist/codecs/ico.js +48 -0
- package/dist/codecs/jpeg.js +11 -28
- package/dist/codecs/jxl/bits.js +126 -0
- package/dist/codecs/jxl/blend.js +117 -0
- package/dist/codecs/jxl/color.js +116 -0
- package/dist/codecs/jxl/dct.js +404 -0
- package/dist/codecs/jxl/entropy.js +556 -0
- package/dist/codecs/jxl/features.js +325 -0
- package/dist/codecs/jxl/filters.js +119 -0
- package/dist/codecs/jxl/frame-header.js +195 -0
- package/dist/codecs/jxl/frame.js +319 -0
- package/dist/codecs/jxl/headers.js +249 -0
- package/dist/codecs/jxl/idct.js +484 -0
- package/dist/codecs/jxl/modular.js +688 -0
- package/dist/codecs/jxl/quant.js +219 -0
- package/dist/codecs/jxl/upsample.js +52 -0
- package/dist/codecs/jxl/vardct.js +472 -0
- package/dist/codecs/jxl.js +89 -0
- package/dist/codecs/netpbm.js +239 -0
- package/dist/codecs/png.js +22 -10
- package/dist/codecs/qoi.js +150 -0
- package/dist/codecs/tga.js +3 -1
- package/dist/codecs/tiff.js +7 -2
- package/dist/codecs/vp8-enc-dsp.js +932 -0
- package/dist/codecs/vp8-enc-tables.js +78 -0
- package/dist/codecs/vp8-enc.js +1930 -0
- package/dist/codecs/vp8.js +3 -1
- package/dist/codecs/vp8l.js +126 -58
- package/dist/codecs/webp.js +47 -11
- package/dist/codecs/zlib.js +81 -69
- package/dist/frame.d.ts +1 -0
- package/dist/frame.js +4 -2
- package/dist/gif.d.ts +1 -0
- package/dist/gif.js +2 -2
- package/dist/image-type.d.ts +15 -1
- package/dist/image-type.js +45 -2
- package/dist/image.d.ts +89 -13
- package/dist/image.js +266 -113
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -4
- package/dist/render/bidi.js +350 -0
- package/dist/render/buffer.js +362 -0
- package/dist/render/color.js +164 -0
- package/dist/render/colr.js +523 -0
- package/dist/render/font.js +173 -241
- package/dist/render/hinting.js +38 -22
- package/dist/render/layout.js +1553 -0
- package/dist/render/linebreak.js +210 -0
- package/dist/render/segment.js +106 -0
- package/dist/render/shape.js +984 -0
- package/dist/render/shapers/arabic.js +284 -0
- package/dist/render/shapers/hangul.js +142 -0
- package/dist/render/shapers/hebrew.js +49 -0
- package/dist/render/shapers/index.js +52 -0
- package/dist/render/shapers/indic.js +579 -0
- package/dist/render/shapers/khmer.js +104 -0
- package/dist/render/shapers/machine.js +129 -0
- package/dist/render/shapers/myanmar.js +121 -0
- package/dist/render/shapers/syllabic.js +100 -0
- package/dist/render/shapers/thai.js +125 -0
- package/dist/render/shapers/use.js +235 -0
- package/dist/render/svg.js +102 -23
- package/dist/render/text.js +373 -103
- package/dist/render/unicode-data.js +63 -0
- package/dist/render/unicode.js +50 -0
- package/dist/text-layout.d.ts +4 -1
- package/dist/text-layout.js +2 -2
- package/dist/util/alpha.js +43 -0
- package/dist/util/blur.js +20 -33
- package/dist/util/bytes.js +40 -0
- package/dist/util/limits.js +34 -0
- package/dist/util/options.js +7 -0
- package/dist/util/resample.js +126 -7
- package/dist/util/rotate.js +43 -0
- package/dist/version.js +1 -1
- package/licenses/HarfBuzz.txt +42 -0
- package/licenses/Unicode-3.0.txt +39 -0
- package/package.json +25 -2
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* The Universal Shaping Engine, ported from HarfBuzz (hb-ot-shaper-use.cc, hb-ot-shaper-use-machine.rl)
|
|
3
|
+
* Copyright © 2015 Mozilla Foundation, © 2015 Google, Inc., Old MIT license (see THIRD_PARTY_NOTICES.md and
|
|
4
|
+
* licenses/HarfBuzz.txt)
|
|
5
|
+
*/
|
|
6
|
+
import { isLigated, isSubstituted, ligComp, isUnicodeMark, isMark } from "../buffer.js";
|
|
7
|
+
import { compose, unicodeCategory } from "../shape.js";
|
|
8
|
+
import { useCategory } from "../unicode-data.js";
|
|
9
|
+
import { value } from "../unicode.js";
|
|
10
|
+
import { lazyScanner, cat, seq, alt, opt, star, plus, any } from "./machine.js";
|
|
11
|
+
import { forEachSyllable, insertDottedCircles, vowelConstraintsPreprocess } from "./syllabic.js";
|
|
12
|
+
import { dataCreate as arabicDataCreate, setupMasksArabic } from "./arabic.js";
|
|
13
|
+
const CATEGORIES = {
|
|
14
|
+
O: 0, B: 1, N: 4, GB: 5, CGJ: 6, SUB: 11, H: 12, HN: 13, ZWNJ: 14, WJ: 16, R: 18, CS: 43, IS: 44, Sk: 48, G: 49,
|
|
15
|
+
J: 50, SB: 51, SE: 52, HVM: 53, HM: 54, HR: 55, RK: 56, FAbv: 24, FBlw: 25, FPst: 26, MAbv: 27, MBlw: 28,
|
|
16
|
+
MPst: 29, MPre: 30, CMAbv: 31, CMBlw: 32, VAbv: 33, VBlw: 34, VPst: 35, VPre: 22, VMAbv: 37, VMBlw: 38,
|
|
17
|
+
VMPst: 39, VMPre: 23, SMAbv: 41, SMBlw: 42, FMAbv: 45, FMBlw: 46, FMPst: 47,
|
|
18
|
+
};
|
|
19
|
+
const findSyllables = lazyScanner(() => {
|
|
20
|
+
const s = (...c) => star(cat(...c));
|
|
21
|
+
const h = cat(12, 53, 44, 48);
|
|
22
|
+
const consonantModifiers = seq(s(31), s(32), star(alt(seq(h, cat(1)), cat(11)), s(31), s(32)));
|
|
23
|
+
const medialConsonants = seq(opt(cat(30)), opt(cat(27)), opt(cat(28)), opt(cat(29)));
|
|
24
|
+
const dependentVowels = alt(seq(s(22), s(33), s(34), s(35)), cat(12));
|
|
25
|
+
const vowelModifiers = seq(opt(cat(53)), s(23), s(37), s(38), s(39));
|
|
26
|
+
const finalConsonants = seq(s(24), s(25), s(26));
|
|
27
|
+
const finalModifiers = alt(seq(s(45), s(46)), opt(cat(47)));
|
|
28
|
+
const complexSyllableStart = seq(opt(cat(18, 43)), cat(1, 5));
|
|
29
|
+
const complexSyllableMiddle = seq(consonantModifiers, medialConsonants, dependentVowels, vowelModifiers, star(cat(48), cat(1)));
|
|
30
|
+
const complexSyllableTail = seq(complexSyllableMiddle, finalConsonants, finalModifiers);
|
|
31
|
+
const numberJoinerTerminatedClusterTail = seq(star(cat(13), cat(4)), cat(13));
|
|
32
|
+
const numeralClusterTail = plus(cat(13), cat(4));
|
|
33
|
+
const symbolClusterTail = alt(seq(plus(cat(41)), s(42)), plus(cat(42)));
|
|
34
|
+
const viramaTerminatedClusterTail = seq(consonantModifiers, cat(44, 56));
|
|
35
|
+
const viramaTerminatedCluster = seq(complexSyllableStart, viramaTerminatedClusterTail);
|
|
36
|
+
const sakotTerminatedClusterTail = seq(complexSyllableMiddle, cat(48));
|
|
37
|
+
const sakotTerminatedCluster = seq(complexSyllableStart, sakotTerminatedClusterTail);
|
|
38
|
+
const standardCluster = seq(complexSyllableStart, complexSyllableTail);
|
|
39
|
+
const tail = alt(complexSyllableTail, sakotTerminatedClusterTail, symbolClusterTail, viramaTerminatedClusterTail);
|
|
40
|
+
const brokenCluster = seq(opt(cat(18)), alt(tail, numberJoinerTerminatedClusterTail, numeralClusterTail));
|
|
41
|
+
const numberJoinerTerminatedCluster = seq(cat(4), numberJoinerTerminatedClusterTail);
|
|
42
|
+
const numeralCluster = seq(cat(4), opt(numeralClusterTail));
|
|
43
|
+
const symbolCluster = seq(cat(0, 5, 51), opt(tail));
|
|
44
|
+
const g = seq(cat(49), opt(cat(55)), opt(cat(54)), s(52));
|
|
45
|
+
const hieroglyphCluster = seq(s(51), g, star(cat(50), s(51), opt(g)));
|
|
46
|
+
const zwnj = opt(cat(14));
|
|
47
|
+
return [
|
|
48
|
+
[seq(viramaTerminatedCluster, zwnj), 0],
|
|
49
|
+
[seq(sakotTerminatedCluster, zwnj), 1],
|
|
50
|
+
[seq(standardCluster, zwnj), 2],
|
|
51
|
+
[seq(numberJoinerTerminatedCluster, zwnj), 3],
|
|
52
|
+
[seq(numeralCluster, zwnj), 4],
|
|
53
|
+
[seq(symbolCluster, zwnj), 5],
|
|
54
|
+
[seq(hieroglyphCluster, zwnj), 6],
|
|
55
|
+
[cat(47), 8],
|
|
56
|
+
[seq(brokenCluster, zwnj), 7],
|
|
57
|
+
[any, 8],
|
|
58
|
+
];
|
|
59
|
+
});
|
|
60
|
+
function setupSyllables(plan, _font, buffer) {
|
|
61
|
+
const info = buffer.info, kept = [];
|
|
62
|
+
for (let i = 0; i < info.length; i++) {
|
|
63
|
+
if (info[i].category === 6)
|
|
64
|
+
continue;
|
|
65
|
+
if (info[i].category === 14) {
|
|
66
|
+
let j = i + 1;
|
|
67
|
+
while (j < info.length && info[j].category === 6)
|
|
68
|
+
j++;
|
|
69
|
+
if (j < info.length && isUnicodeMark(info[j]))
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
kept.push(i);
|
|
73
|
+
}
|
|
74
|
+
let serial = 1;
|
|
75
|
+
for (const [start, end, type] of findSyllables(kept.map(i => info[i].category))) {
|
|
76
|
+
const from = kept[start], to = end < kept.length ? kept[end] : info.length;
|
|
77
|
+
for (let i = from; i < to; i++)
|
|
78
|
+
info[i].syllable = serial << 4 | type;
|
|
79
|
+
if (type === 7)
|
|
80
|
+
buffer.hasBrokenSyllable = true;
|
|
81
|
+
if (++serial === 16)
|
|
82
|
+
serial = 1;
|
|
83
|
+
}
|
|
84
|
+
setupRphfMask(plan, buffer);
|
|
85
|
+
setupTopographicalMasks(plan, buffer);
|
|
86
|
+
}
|
|
87
|
+
function setupRphfMask(plan, buffer) {
|
|
88
|
+
const mask = plan.data.rphfMask;
|
|
89
|
+
if (!mask)
|
|
90
|
+
return;
|
|
91
|
+
const info = buffer.info;
|
|
92
|
+
forEachSyllable(buffer, (start, end) => {
|
|
93
|
+
const limit = info[start].category === 18 ? 1 : Math.min(3, end - start);
|
|
94
|
+
for (let i = start; i < start + limit; i++)
|
|
95
|
+
info[i].mask |= mask;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const TOPOGRAPHICAL = ['isol', 'init', 'medi', 'fina'];
|
|
99
|
+
function setupTopographicalMasks(plan, buffer) {
|
|
100
|
+
if (plan.data.arabic)
|
|
101
|
+
return;
|
|
102
|
+
const masks = TOPOGRAPHICAL.map(tag => {
|
|
103
|
+
const m = plan.map.get1Mask(tag);
|
|
104
|
+
return m === plan.map.globalMask ? 0 : m;
|
|
105
|
+
});
|
|
106
|
+
const all = masks.reduce((a, b) => a | b, 0);
|
|
107
|
+
if (!all)
|
|
108
|
+
return;
|
|
109
|
+
const others = ~all, info = buffer.info;
|
|
110
|
+
let lastStart = 0, lastForm = 4;
|
|
111
|
+
forEachSyllable(buffer, (start, end) => {
|
|
112
|
+
const type = info[start].syllable & 0x0f;
|
|
113
|
+
if (type === 6 || type === 8)
|
|
114
|
+
lastForm = 4;
|
|
115
|
+
else {
|
|
116
|
+
const join = lastForm === 3 || lastForm === 0;
|
|
117
|
+
if (join) {
|
|
118
|
+
lastForm = lastForm === 3 ? 2 : 1;
|
|
119
|
+
for (let i = lastStart; i < start; i++)
|
|
120
|
+
info[i].mask = info[i].mask & others | masks[lastForm];
|
|
121
|
+
}
|
|
122
|
+
lastForm = join ? 3 : 0;
|
|
123
|
+
for (let i = start; i < end; i++)
|
|
124
|
+
info[i].mask = info[i].mask & others | masks[lastForm];
|
|
125
|
+
}
|
|
126
|
+
lastStart = start;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function clearSubstitutionFlags(_plan, _font, buffer) {
|
|
130
|
+
for (const info of buffer.info)
|
|
131
|
+
info.glyphProps &= ~16;
|
|
132
|
+
}
|
|
133
|
+
function recordRphf(plan, _font, buffer) {
|
|
134
|
+
const mask = plan.data.rphfMask;
|
|
135
|
+
if (!mask)
|
|
136
|
+
return;
|
|
137
|
+
const info = buffer.info;
|
|
138
|
+
forEachSyllable(buffer, (start, end) => {
|
|
139
|
+
for (let i = start; i < end && info[i].mask & mask; i++)
|
|
140
|
+
if (isSubstituted(info[i])) {
|
|
141
|
+
info[i].category = 18;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function recordPref(_plan, _font, buffer) {
|
|
147
|
+
const info = buffer.info;
|
|
148
|
+
forEachSyllable(buffer, (start, end) => {
|
|
149
|
+
for (let i = start; i < end; i++)
|
|
150
|
+
if (isSubstituted(info[i])) {
|
|
151
|
+
info[i].category = 22;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const isHalant = (info) => (info.category === 12 || info.category === 53 || info.category === 44) && !isLigated(info);
|
|
157
|
+
const POST_BASE = new Set([24, 25, 26, 45, 46, 47, 27, 28, 29, 30, 33, 34, 35, 22, 37,
|
|
158
|
+
38, 39, 23]);
|
|
159
|
+
const REORDERED = new Set([0, 1, 2, 5, 7]);
|
|
160
|
+
function reorderSyllable(buffer, start, end) {
|
|
161
|
+
const info = buffer.info;
|
|
162
|
+
if (!REORDERED.has(info[start].syllable & 0x0f))
|
|
163
|
+
return;
|
|
164
|
+
if (info[start].category === 18 && end - start > 1)
|
|
165
|
+
for (let i = start + 1; i < end; i++) {
|
|
166
|
+
const postBase = POST_BASE.has(info[i].category) || isHalant(info[i]);
|
|
167
|
+
if (postBase || i === end - 1) {
|
|
168
|
+
if (postBase)
|
|
169
|
+
i--;
|
|
170
|
+
buffer.mergeClusters(start, i + 1);
|
|
171
|
+
const t = info[start];
|
|
172
|
+
for (let k = start; k < i; k++)
|
|
173
|
+
info[k] = info[k + 1];
|
|
174
|
+
info[i] = t;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
let j = start;
|
|
179
|
+
for (let i = start; i < end; i++) {
|
|
180
|
+
const category = info[i].category;
|
|
181
|
+
if (isHalant(info[i]))
|
|
182
|
+
j = i + 1;
|
|
183
|
+
else if ((category === 22 || category === 23) && ligComp(info[i]) === 0 && j < i) {
|
|
184
|
+
buffer.mergeClusters(j, i + 1);
|
|
185
|
+
const t = info[i];
|
|
186
|
+
for (let k = i; k > j; k--)
|
|
187
|
+
info[k] = info[k - 1];
|
|
188
|
+
info[j] = t;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function reorder(_plan, font, buffer) {
|
|
193
|
+
insertDottedCircles(font, buffer, 7, 1, 18, -1);
|
|
194
|
+
forEachSyllable(buffer, (start, end) => reorderSyllable(buffer, start, end));
|
|
195
|
+
}
|
|
196
|
+
function collectFeatures(planner) {
|
|
197
|
+
const map = planner.map;
|
|
198
|
+
map.addGsubPause(setupSyllables);
|
|
199
|
+
map.enableFeature('locl', 64);
|
|
200
|
+
map.enableFeature('ccmp', 64);
|
|
201
|
+
map.enableFeature('nukt', 64);
|
|
202
|
+
map.enableFeature('akhn', 8 | 64);
|
|
203
|
+
map.addGsubPause(clearSubstitutionFlags);
|
|
204
|
+
map.addFeature('rphf', 8 | 64);
|
|
205
|
+
map.addGsubPause(recordRphf);
|
|
206
|
+
map.addGsubPause(clearSubstitutionFlags);
|
|
207
|
+
map.enableFeature('pref', 8 | 64);
|
|
208
|
+
map.addGsubPause(recordPref);
|
|
209
|
+
for (const tag of ['rkrf', 'abvf', 'blwf', 'half', 'pstf', 'vatu', 'cjct'])
|
|
210
|
+
map.enableFeature(tag, 8 | 64);
|
|
211
|
+
map.addGsubPause(reorder);
|
|
212
|
+
map.addGsubPause(null);
|
|
213
|
+
for (const tag of TOPOGRAPHICAL)
|
|
214
|
+
map.addFeature(tag);
|
|
215
|
+
map.addGsubPause(null);
|
|
216
|
+
for (const tag of ['abvs', 'blws', 'haln', 'pres', 'psts'])
|
|
217
|
+
map.enableFeature(tag, 8);
|
|
218
|
+
}
|
|
219
|
+
const ARABIC_JOINING = new Set(['Adlm', 'Arab', 'Chrs', 'Rohg', 'Mand', 'Mani', 'Mong', 'Nkoo', 'Ougr', 'Phag', 'Phlp', 'Sogd', 'Syrc']);
|
|
220
|
+
export const useShaper = {
|
|
221
|
+
collectFeatures,
|
|
222
|
+
dataCreate: (plan) => ({ rphfMask: plan.map.get1Mask('rphf'), arabic: ARABIC_JOINING.has(plan.script) ? arabicDataCreate(plan) : null }),
|
|
223
|
+
preprocessText: (_plan, buffer) => vowelConstraintsPreprocess(buffer),
|
|
224
|
+
compose: (_c, a, b) => isMark(unicodeCategory(a)) ? 0 : compose(a, b),
|
|
225
|
+
setupMasks: (plan, buffer) => {
|
|
226
|
+
const use = plan.data;
|
|
227
|
+
if (use.arabic)
|
|
228
|
+
setupMasksArabic(use.arabic, buffer, plan.script);
|
|
229
|
+
for (const info of buffer.info)
|
|
230
|
+
info.category = CATEGORIES[value(useCategory, info.codepoint)];
|
|
231
|
+
},
|
|
232
|
+
normalization: 3,
|
|
233
|
+
zeroWidthMarks: 1,
|
|
234
|
+
fallbackPosition: false,
|
|
235
|
+
};
|
package/dist/render/svg.js
CHANGED
|
@@ -173,16 +173,76 @@ function parseXML(text) {
|
|
|
173
173
|
}
|
|
174
174
|
return root;
|
|
175
175
|
}
|
|
176
|
+
function urlReference(value) {
|
|
177
|
+
let close = -1;
|
|
178
|
+
for (let open = value.indexOf('url('); open >= 0; open = value.indexOf('url(', open + 4)) {
|
|
179
|
+
if (close < open && (close = value.indexOf(')', open)) < 0)
|
|
180
|
+
return null;
|
|
181
|
+
let inner = value.slice(open + 4, close).trim();
|
|
182
|
+
if (inner.startsWith('"') || inner.startsWith("'"))
|
|
183
|
+
inner = inner.slice(1);
|
|
184
|
+
if (inner.endsWith('"') || inner.endsWith("'"))
|
|
185
|
+
inner = inner.slice(0, -1);
|
|
186
|
+
inner = inner.trim();
|
|
187
|
+
if (/^#[^'"\s]+$/.test(inner))
|
|
188
|
+
return inner.slice(1);
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
function stripComments(text) {
|
|
193
|
+
let out = '';
|
|
194
|
+
for (let i = 0;;) {
|
|
195
|
+
const start = text.indexOf('/*', i);
|
|
196
|
+
if (start < 0)
|
|
197
|
+
return out + text.slice(i);
|
|
198
|
+
out += text.slice(i, start);
|
|
199
|
+
const end = text.indexOf('*/', start + 2);
|
|
200
|
+
if (end < 0)
|
|
201
|
+
return out;
|
|
202
|
+
i = end + 2;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function blockEnd(text, open) {
|
|
206
|
+
let depth = 0;
|
|
207
|
+
for (let i = open; i < text.length; i++) {
|
|
208
|
+
if (text[i] === '{')
|
|
209
|
+
depth++;
|
|
210
|
+
else if (text[i] === '}' && !--depth)
|
|
211
|
+
return i;
|
|
212
|
+
}
|
|
213
|
+
return text.length;
|
|
214
|
+
}
|
|
176
215
|
function parseStyleSheet(text) {
|
|
177
216
|
const rules = [];
|
|
178
|
-
text = text
|
|
217
|
+
text = stripComments(text);
|
|
218
|
+
const cache = {};
|
|
219
|
+
const next = (char, i) => {
|
|
220
|
+
const cached = cache[char];
|
|
221
|
+
if (cached === undefined || cached !== -1 && cached < i)
|
|
222
|
+
cache[char] = text.indexOf(char, i);
|
|
223
|
+
return cache[char];
|
|
224
|
+
};
|
|
179
225
|
let order = 0;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (
|
|
226
|
+
for (let i = 0; i < text.length;) {
|
|
227
|
+
const start = i, open = next('{', i), close = next('}', i), at = next('@', i);
|
|
228
|
+
if (open < 0)
|
|
229
|
+
break;
|
|
230
|
+
if (close >= 0 && close < open) {
|
|
231
|
+
i = close + 1;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (at >= 0 && at < open) {
|
|
235
|
+
const semicolon = next(';', at);
|
|
236
|
+
i = semicolon >= 0 && semicolon < open ? semicolon + 1 : blockEnd(text, open) + 1;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const end = blockEnd(text, open);
|
|
240
|
+
const body = text.slice(open + 1, end);
|
|
241
|
+
i = end + 1;
|
|
242
|
+
if (body.includes('{'))
|
|
183
243
|
continue;
|
|
184
244
|
const declarations = parseDeclarations(body);
|
|
185
|
-
for (const selector of
|
|
245
|
+
for (const selector of text.slice(start, open).split(',')) {
|
|
186
246
|
const parsed = parseSelector(selector.trim());
|
|
187
247
|
if (parsed)
|
|
188
248
|
rules.push({ ...parsed, order: order++, declarations });
|
|
@@ -279,7 +339,7 @@ function parseSelector(selector) {
|
|
|
279
339
|
}
|
|
280
340
|
function parseDeclarations(text) {
|
|
281
341
|
const declarations = { normal: {}, important: {} };
|
|
282
|
-
text = text
|
|
342
|
+
text = stripComments(text);
|
|
283
343
|
for (const part of text.split(';')) {
|
|
284
344
|
const colon = part.indexOf(':');
|
|
285
345
|
if (colon === -1)
|
|
@@ -312,7 +372,10 @@ function validProperty(name, value) {
|
|
|
312
372
|
return true;
|
|
313
373
|
}
|
|
314
374
|
}
|
|
375
|
+
const MAX_INSTANCES = 100000;
|
|
376
|
+
const MAX_DASHES = 1000000;
|
|
315
377
|
class Document {
|
|
378
|
+
instances;
|
|
316
379
|
ids;
|
|
317
380
|
active;
|
|
318
381
|
root;
|
|
@@ -320,26 +383,28 @@ class Document {
|
|
|
320
383
|
constructor(root) {
|
|
321
384
|
this.ids = new Map();
|
|
322
385
|
this.active = new Set();
|
|
386
|
+
this.instances = 0;
|
|
323
387
|
const styles = [];
|
|
324
|
-
const
|
|
388
|
+
const stack = [root];
|
|
389
|
+
while (stack.length) {
|
|
390
|
+
const node = stack.pop();
|
|
325
391
|
if (node.attrs.id && !this.ids.has(node.attrs.id))
|
|
326
392
|
this.ids.set(node.attrs.id, node);
|
|
327
393
|
if (node.name === 'style' && node.svg && (!node.attrs.type || node.attrs.type === 'text/css'))
|
|
328
394
|
styles.push(node.text);
|
|
329
|
-
for (
|
|
330
|
-
|
|
331
|
-
|
|
395
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
396
|
+
node.children[i].parent = node;
|
|
397
|
+
stack.push(node.children[i]);
|
|
332
398
|
}
|
|
333
|
-
}
|
|
334
|
-
walk(root);
|
|
399
|
+
}
|
|
335
400
|
this.root = root;
|
|
336
401
|
this.rules = parseStyleSheet(styles.join('\n'));
|
|
337
402
|
}
|
|
338
403
|
reference(value) {
|
|
339
404
|
if (!value)
|
|
340
405
|
return null;
|
|
341
|
-
const
|
|
342
|
-
return
|
|
406
|
+
const id = urlReference(value) ?? (value.trim().startsWith('#') ? value.trim().slice(1) : null);
|
|
407
|
+
return id ? this.ids.get(id) || null : null;
|
|
343
408
|
}
|
|
344
409
|
href(node) {
|
|
345
410
|
return node.attrs.href || node.attrs['xlink:href'];
|
|
@@ -356,7 +421,7 @@ class Document {
|
|
|
356
421
|
const presentation = {};
|
|
357
422
|
for (const name in node.attrs)
|
|
358
423
|
if (PROPERTIES.has(name))
|
|
359
|
-
presentation[name] = node.attrs[name];
|
|
424
|
+
presentation[name] = node.attrs[name].trim();
|
|
360
425
|
apply(presentation);
|
|
361
426
|
const rules = this.rules.filter(rule => rule.match(node));
|
|
362
427
|
const inline = node.attrs.style ? parseDeclarations(node.attrs.style) : null;
|
|
@@ -406,6 +471,8 @@ class Document {
|
|
|
406
471
|
renderNode(node, context, root = false) {
|
|
407
472
|
if (!node.svg || UNSUPPORTED.has(node.name) || context.depth > 32 || !this.passesConditions(node))
|
|
408
473
|
return;
|
|
474
|
+
if (context.instanced && ++this.instances > MAX_INSTANCES)
|
|
475
|
+
throw new Error(`SVG: more than ${MAX_INSTANCES} elements rendered through <use> references`);
|
|
409
476
|
const style = this.computeStyle(node, context.style);
|
|
410
477
|
if (style.display === 'none')
|
|
411
478
|
return;
|
|
@@ -513,7 +580,7 @@ class Document {
|
|
|
513
580
|
outer = multiply(outer, [1, 0, 0, 1, parseLength(target.attrs.x, ctx.viewport, 16, 'x'), parseLength(target.attrs.y, ctx.viewport, 16, 'y')]);
|
|
514
581
|
const inner = viewBox ? multiply(outer, viewBoxTransform(viewBox, width, height, target.attrs.preserveAspectRatio)) : outer;
|
|
515
582
|
const opacity = clamp01(parseNumber(style.opacity, 1));
|
|
516
|
-
const innerContext = { ...ctx, matrix: inner, style, viewport: viewBox ? { width: viewBox[2], height: viewBox[3] } : { width, height } };
|
|
583
|
+
const innerContext = { ...ctx, matrix: inner, style, viewport: viewBox ? { width: viewBox[2], height: viewBox[3] } : { width, height }, instanced: true };
|
|
517
584
|
const draw = (layer) => /^(visible|auto)$/.test(target.attrs.overflow || '')
|
|
518
585
|
? this.renderChildren(target, { ...innerContext, layer })
|
|
519
586
|
: this.clipped({ ...ctx, matrix: outer, layer }, 0, 0, width, height, inner => this.renderChildren(target, { ...innerContext, layer: inner }));
|
|
@@ -526,7 +593,7 @@ class Document {
|
|
|
526
593
|
}
|
|
527
594
|
const computed = target.computed, computedParent = target.computedParent;
|
|
528
595
|
target.computed = null;
|
|
529
|
-
this.renderNode(target, { ...ctx, matrix });
|
|
596
|
+
this.renderNode(target, { ...ctx, matrix, instanced: true });
|
|
530
597
|
target.computed = computed;
|
|
531
598
|
target.computedParent = computedParent;
|
|
532
599
|
}
|
|
@@ -1142,9 +1209,8 @@ function shapePath(node, viewport, fontSize) {
|
|
|
1142
1209
|
}
|
|
1143
1210
|
function parsePath(d) {
|
|
1144
1211
|
const path = [];
|
|
1145
|
-
const tokens = /([MmZzLlHhVvCcSsQqTtAa])|([-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)/g;
|
|
1146
1212
|
const items = [];
|
|
1147
|
-
for (const m of d.matchAll(
|
|
1213
|
+
for (const m of d.matchAll(PATH_TOKENS))
|
|
1148
1214
|
items.push(m[1] ? { command: m[1] } : m[2]);
|
|
1149
1215
|
let i = 0;
|
|
1150
1216
|
let command = null;
|
|
@@ -1398,6 +1464,14 @@ function transformPoints(points, m) {
|
|
|
1398
1464
|
function dash(subpaths, pattern, offset) {
|
|
1399
1465
|
const total = pattern.reduce((a, b) => a + b, 0);
|
|
1400
1466
|
const result = [];
|
|
1467
|
+
let length = 0;
|
|
1468
|
+
for (const { points, closed } of subpaths) {
|
|
1469
|
+
const pts = closed ? [...points, points[0], points[1]] : points;
|
|
1470
|
+
for (let i = 0; i + 3 < pts.length; i += 2)
|
|
1471
|
+
length += Math.hypot(pts[i + 2] - pts[i], pts[i + 3] - pts[i + 1]);
|
|
1472
|
+
}
|
|
1473
|
+
if (!(length / total * pattern.length <= MAX_DASHES))
|
|
1474
|
+
return subpaths;
|
|
1401
1475
|
for (const { points, closed } of subpaths) {
|
|
1402
1476
|
const pts = closed ? [...points, points[0], points[1]] : points;
|
|
1403
1477
|
let index = 0;
|
|
@@ -1620,10 +1694,15 @@ function viewBoxTransform([vx, vy, vw, vh], width, height, preserve = 'xMidYMid
|
|
|
1620
1694
|
return [sx, 0, 0, sy, tx, ty];
|
|
1621
1695
|
}
|
|
1622
1696
|
const UNITS = { px: 1, pt: 4 / 3, pc: 16, mm: 96 / 25.4, cm: 96 / 2.54, in: 96 };
|
|
1697
|
+
const NUMBER = String.raw `[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?`;
|
|
1698
|
+
const LENGTH = new RegExp(`^(${NUMBER})\\s*(px|pt|pc|mm|cm|in|em|ex|%)?$`);
|
|
1699
|
+
const NUMBER_OR_PERCENTAGE = new RegExp(`^(${NUMBER})\\s*(%?)$`);
|
|
1700
|
+
const NUMBERS = new RegExp(NUMBER, 'g');
|
|
1701
|
+
const PATH_TOKENS = new RegExp(`([MmZzLlHhVvCcSsQqTtAa])|(${NUMBER})`, 'g');
|
|
1623
1702
|
function parseLength(value, viewport, fontSize = 16, axis = 'x') {
|
|
1624
1703
|
if (value === undefined || value === null)
|
|
1625
1704
|
return 0;
|
|
1626
|
-
const match =
|
|
1705
|
+
const match = LENGTH.exec(String(value).trim());
|
|
1627
1706
|
if (!match)
|
|
1628
1707
|
return 0;
|
|
1629
1708
|
const number = parseFloat(match[1]);
|
|
@@ -1650,13 +1729,13 @@ function parseNumber(value, fallback) {
|
|
|
1650
1729
|
function parseStrictNumber(value) {
|
|
1651
1730
|
if (value === undefined || value === null)
|
|
1652
1731
|
return null;
|
|
1653
|
-
const match =
|
|
1732
|
+
const match = NUMBER_OR_PERCENTAGE.exec(String(value).trim());
|
|
1654
1733
|
if (!match)
|
|
1655
1734
|
return null;
|
|
1656
1735
|
return match[2] ? parseFloat(match[1]) / 100 : parseFloat(match[1]);
|
|
1657
1736
|
}
|
|
1658
1737
|
function parseNumberList(value) {
|
|
1659
|
-
return [...String(value).matchAll(
|
|
1738
|
+
return [...String(value).matchAll(NUMBERS)].map(m => parseFloat(m[0]));
|
|
1660
1739
|
}
|
|
1661
1740
|
function parseDashArray(value, viewport, fontSize) {
|
|
1662
1741
|
if (!value || value === 'none')
|
|
@@ -1686,7 +1765,7 @@ function parseColor(value) {
|
|
|
1686
1765
|
return [0, 2, 4, 6].slice(0, hex.length / 2).map(i => parseInt(hex.slice(i, i + 2), 16) / 255).concat(hex.length === 6 ? [1] : []);
|
|
1687
1766
|
return null;
|
|
1688
1767
|
}
|
|
1689
|
-
match = /^(rgba?|hsla?)\(
|
|
1768
|
+
match = /^(rgba?|hsla?)\(([^)]*)\)$/.exec(value);
|
|
1690
1769
|
if (match) {
|
|
1691
1770
|
const parts = match[2].split(/\s*[,/]\s*|\s+/).filter(Boolean);
|
|
1692
1771
|
if (parts.length < 3)
|