UNPKG

191 kBJavaScriptView Raw
1import requestAnimationFrame from 'raf';
2import RGBColor from 'rgbcolor';
3import { SVGPathData } from 'svg-pathdata';
4import { canvasRGBA } from 'stackblur-canvas';
5
6/**
7 * Options preset for `OffscreenCanvas`.
8 * @param config - Preset requirements.
9 * @param config.DOMParser - XML/HTML parser from string into DOM Document.
10 * @returns Preset object.
11 */ function offscreen() {
12 let { DOMParser: DOMParserFallback } = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
13 const preset = {
14 window: null,
15 ignoreAnimation: true,
16 ignoreMouse: true,
17 DOMParser: DOMParserFallback,
18 createCanvas (width, height) {
19 return new OffscreenCanvas(width, height);
20 },
21 async createImage (url) {
22 const response = await fetch(url);
23 const blob = await response.blob();
24 const img = await createImageBitmap(blob);
25 return img;
26 }
27 };
28 if (typeof globalThis.DOMParser !== 'undefined' || typeof DOMParserFallback === 'undefined') {
29 Reflect.deleteProperty(preset, 'DOMParser');
30 }
31 return preset;
32}
33
34/**
35 * Options preset for `node-canvas`.
36 * @param config - Preset requirements.
37 * @param config.DOMParser - XML/HTML parser from string into DOM Document.
38 * @param config.canvas - `node-canvas` exports.
39 * @param config.fetch - WHATWG-compatible `fetch` function.
40 * @returns Preset object.
41 */ function node(param) {
42 let { DOMParser , canvas , fetch } = param;
43 return {
44 window: null,
45 ignoreAnimation: true,
46 ignoreMouse: true,
47 DOMParser,
48 fetch,
49 createCanvas: canvas.createCanvas,
50 createImage: canvas.loadImage
51 };
52}
53
54var index = /*#__PURE__*/Object.freeze({
55 __proto__: null,
56 offscreen: offscreen,
57 node: node
58});
59
60/**
61 * HTML-safe compress white-spaces.
62 * @param str - String to compress.
63 * @returns String.
64 */ function compressSpaces(str) {
65 return str.replace(/(?!\u3000)\s+/gm, ' ');
66}
67/**
68 * HTML-safe left trim.
69 * @param str - String to trim.
70 * @returns String.
71 */ function trimLeft(str) {
72 return str.replace(/^[\n \t]+/, '');
73}
74/**
75 * HTML-safe right trim.
76 * @param str - String to trim.
77 * @returns String.
78 */ function trimRight(str) {
79 return str.replace(/[\n \t]+$/, '');
80}
81/**
82 * String to numbers array.
83 * @param str - Numbers string.
84 * @returns Numbers array.
85 */ function toNumbers(str) {
86 const matches = str.match(/-?(\d+(?:\.\d*(?:[eE][+-]?\d+)?)?|\.\d+)(?=\D|$)/gm);
87 return matches ? matches.map(parseFloat) : [];
88}
89/**
90 * String to matrix value.
91 * @param str - Numbers string.
92 * @returns Matrix value.
93 */ function toMatrixValue(str) {
94 const numbers = toNumbers(str);
95 const matrix = [
96 numbers[0] || 0,
97 numbers[1] || 0,
98 numbers[2] || 0,
99 numbers[3] || 0,
100 numbers[4] || 0,
101 numbers[5] || 0
102 ];
103 return matrix;
104}
105// Microsoft Edge fix
106const allUppercase = /^[A-Z-]+$/;
107/**
108 * Normalize attribute name.
109 * @param name - Attribute name.
110 * @returns Normalized attribute name.
111 */ function normalizeAttributeName(name) {
112 if (allUppercase.test(name)) {
113 return name.toLowerCase();
114 }
115 return name;
116}
117/**
118 * Parse external URL.
119 * @param url - CSS url string.
120 * @returns Parsed URL.
121 */ function parseExternalUrl(url) {
122 // single quotes [2]
123 // v double quotes [3]
124 // v v no quotes [4]
125 // v v v
126 const urlMatch = /url\(('([^']+)'|"([^"]+)"|([^'")]+))\)/.exec(url);
127 if (!urlMatch) {
128 return '';
129 }
130 return urlMatch[2] || urlMatch[3] || urlMatch[4] || '';
131}
132/**
133 * Transform floats to integers in rgb colors.
134 * @param color - Color to normalize.
135 * @returns Normalized color.
136 */ function normalizeColor(color) {
137 if (!color.startsWith('rgb')) {
138 return color;
139 }
140 let rgbParts = 3;
141 const normalizedColor = color.replace(/\d+(\.\d+)?/g, (num, isFloat)=>(rgbParts--) && isFloat ? String(Math.round(parseFloat(num))) : num
142 );
143 return normalizedColor;
144}
145
146// slightly modified version of https://github.com/keeganstreet/specificity/blob/master/specificity.js
147const attributeRegex = /(\[[^\]]+\])/g;
148const idRegex = /(#[^\s+>~.[:]+)/g;
149const classRegex = /(\.[^\s+>~.[:]+)/g;
150const pseudoElementRegex = /(::[^\s+>~.[:]+|:first-line|:first-letter|:before|:after)/gi;
151const pseudoClassWithBracketsRegex = /(:[\w-]+\([^)]*\))/gi;
152const pseudoClassRegex = /(:[^\s+>~.[:]+)/g;
153const elementRegex = /([^\s+>~.[:]+)/g;
154function findSelectorMatch(selector, regex) {
155 const matches = regex.exec(selector);
156 if (!matches) {
157 return [
158 selector,
159 0
160 ];
161 }
162 return [
163 selector.replace(regex, ' '),
164 matches.length
165 ];
166}
167/**
168 * Measure selector specificity.
169 * @param selector - Selector to measure.
170 * @returns Specificity.
171 */ function getSelectorSpecificity(selector) {
172 const specificity = [
173 0,
174 0,
175 0
176 ];
177 let currentSelector = selector.replace(/:not\(([^)]*)\)/g, ' $1 ').replace(/{[\s\S]*/gm, ' ');
178 let delta = 0;
179 [currentSelector, delta] = findSelectorMatch(currentSelector, attributeRegex);
180 specificity[1] += delta;
181 [currentSelector, delta] = findSelectorMatch(currentSelector, idRegex);
182 specificity[0] += delta;
183 [currentSelector, delta] = findSelectorMatch(currentSelector, classRegex);
184 specificity[1] += delta;
185 [currentSelector, delta] = findSelectorMatch(currentSelector, pseudoElementRegex);
186 specificity[2] += delta;
187 [currentSelector, delta] = findSelectorMatch(currentSelector, pseudoClassWithBracketsRegex);
188 specificity[1] += delta;
189 [currentSelector, delta] = findSelectorMatch(currentSelector, pseudoClassRegex);
190 specificity[1] += delta;
191 currentSelector = currentSelector.replace(/[*\s+>~]/g, ' ').replace(/[#.]/g, ' ');
192 [currentSelector, delta] = findSelectorMatch(currentSelector, elementRegex) // lgtm [js/useless-assignment-to-local]
193 ;
194 specificity[2] += delta;
195 return specificity.join('');
196}
197
198const PSEUDO_ZERO = 0.00000001;
199/**
200 * Vector magnitude.
201 * @param v
202 * @returns Number result.
203 */ function vectorMagnitude(v) {
204 return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2));
205}
206/**
207 * Ratio between two vectors.
208 * @param u
209 * @param v
210 * @returns Number result.
211 */ function vectorsRatio(u, v) {
212 return (u[0] * v[0] + u[1] * v[1]) / (vectorMagnitude(u) * vectorMagnitude(v));
213}
214/**
215 * Angle between two vectors.
216 * @param u
217 * @param v
218 * @returns Number result.
219 */ function vectorsAngle(u, v) {
220 return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vectorsRatio(u, v));
221}
222function CB1(t) {
223 return t * t * t;
224}
225function CB2(t) {
226 return 3 * t * t * (1 - t);
227}
228function CB3(t) {
229 return 3 * t * (1 - t) * (1 - t);
230}
231function CB4(t) {
232 return (1 - t) * (1 - t) * (1 - t);
233}
234function QB1(t) {
235 return t * t;
236}
237function QB2(t) {
238 return 2 * t * (1 - t);
239}
240function QB3(t) {
241 return (1 - t) * (1 - t);
242}
243
244class Property {
245 static empty(document) {
246 return new Property(document, 'EMPTY', '');
247 }
248 split() {
249 let separator = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : ' ';
250 const { document , name } = this;
251 return compressSpaces(this.getString()).trim().split(separator).map((value)=>new Property(document, name, value)
252 );
253 }
254 hasValue(zeroIsValue) {
255 const value = this.value;
256 return value !== null && value !== '' && (zeroIsValue || value !== 0) && typeof value !== 'undefined';
257 }
258 isString(regexp) {
259 const { value } = this;
260 const result = typeof value === 'string';
261 if (!result || !regexp) {
262 return result;
263 }
264 return regexp.test(value);
265 }
266 isUrlDefinition() {
267 return this.isString(/^url\(/);
268 }
269 isPixels() {
270 if (!this.hasValue()) {
271 return false;
272 }
273 const asString = this.getString();
274 switch(true){
275 case asString.endsWith('px'):
276 case /^[0-9]+$/.test(asString):
277 return true;
278 default:
279 return false;
280 }
281 }
282 setValue(value) {
283 this.value = value;
284 return this;
285 }
286 getValue(def) {
287 if (typeof def === 'undefined' || this.hasValue()) {
288 return this.value;
289 }
290 return def;
291 }
292 getNumber(def) {
293 if (!this.hasValue()) {
294 if (typeof def === 'undefined') {
295 return 0;
296 }
297 // @ts-expect-error Parse unknown value.
298 return parseFloat(def);
299 }
300 const { value } = this;
301 // @ts-expect-error Parse unknown value.
302 let n = parseFloat(value);
303 if (this.isString(/%$/)) {
304 n /= 100;
305 }
306 return n;
307 }
308 getString(def) {
309 if (typeof def === 'undefined' || this.hasValue()) {
310 return typeof this.value === 'undefined' ? '' : String(this.value);
311 }
312 return String(def);
313 }
314 getColor(def) {
315 let color = this.getString(def);
316 if (this.isNormalizedColor) {
317 return color;
318 }
319 this.isNormalizedColor = true;
320 color = normalizeColor(color);
321 this.value = color;
322 return color;
323 }
324 getDpi() {
325 return 96 // TODO: compute?
326 ;
327 }
328 getRem() {
329 return this.document.rootEmSize;
330 }
331 getEm() {
332 return this.document.emSize;
333 }
334 getUnits() {
335 return this.getString().replace(/[0-9.-]/g, '');
336 }
337 getPixels(axisOrIsFontSize) {
338 let processPercent = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false;
339 if (!this.hasValue()) {
340 return 0;
341 }
342 const [axis, isFontSize] = typeof axisOrIsFontSize === 'boolean' ? [
343 undefined,
344 axisOrIsFontSize
345 ] : [
346 axisOrIsFontSize
347 ];
348 const { viewPort } = this.document.screen;
349 switch(true){
350 case this.isString(/vmin$/):
351 return this.getNumber() / 100 * Math.min(viewPort.computeSize('x'), viewPort.computeSize('y'));
352 case this.isString(/vmax$/):
353 return this.getNumber() / 100 * Math.max(viewPort.computeSize('x'), viewPort.computeSize('y'));
354 case this.isString(/vw$/):
355 return this.getNumber() / 100 * viewPort.computeSize('x');
356 case this.isString(/vh$/):
357 return this.getNumber() / 100 * viewPort.computeSize('y');
358 case this.isString(/rem$/):
359 return this.getNumber() * this.getRem();
360 case this.isString(/em$/):
361 return this.getNumber() * this.getEm();
362 case this.isString(/ex$/):
363 return this.getNumber() * this.getEm() / 2;
364 case this.isString(/px$/):
365 return this.getNumber();
366 case this.isString(/pt$/):
367 return this.getNumber() * this.getDpi() * (1 / 72);
368 case this.isString(/pc$/):
369 return this.getNumber() * 15;
370 case this.isString(/cm$/):
371 return this.getNumber() * this.getDpi() / 2.54;
372 case this.isString(/mm$/):
373 return this.getNumber() * this.getDpi() / 25.4;
374 case this.isString(/in$/):
375 return this.getNumber() * this.getDpi();
376 case this.isString(/%$/) && isFontSize:
377 return this.getNumber() * this.getEm();
378 case this.isString(/%$/):
379 return this.getNumber() * viewPort.computeSize(axis);
380 default:
381 {
382 const n = this.getNumber();
383 if (processPercent && n < 1) {
384 return n * viewPort.computeSize(axis);
385 }
386 return n;
387 }
388 }
389 }
390 getMilliseconds() {
391 if (!this.hasValue()) {
392 return 0;
393 }
394 if (this.isString(/ms$/)) {
395 return this.getNumber();
396 }
397 return this.getNumber() * 1000;
398 }
399 getRadians() {
400 if (!this.hasValue()) {
401 return 0;
402 }
403 switch(true){
404 case this.isString(/deg$/):
405 return this.getNumber() * (Math.PI / 180);
406 case this.isString(/grad$/):
407 return this.getNumber() * (Math.PI / 200);
408 case this.isString(/rad$/):
409 return this.getNumber();
410 default:
411 return this.getNumber() * (Math.PI / 180);
412 }
413 }
414 getDefinition() {
415 const asString = this.getString();
416 const match = /#([^)'"]+)/.exec(asString);
417 const name = (match === null || match === void 0 ? void 0 : match[1]) || asString;
418 return this.document.definitions[name];
419 }
420 getFillStyleDefinition(element, opacity) {
421 let def = this.getDefinition();
422 if (!def) {
423 return null;
424 }
425 // gradient
426 if (typeof def.createGradient === 'function' && 'getBoundingBox' in element) {
427 return def.createGradient(this.document.ctx, element, opacity);
428 }
429 // pattern
430 if (typeof def.createPattern === 'function') {
431 if (def.getHrefAttribute().hasValue()) {
432 const patternTransform = def.getAttribute('patternTransform');
433 def = def.getHrefAttribute().getDefinition();
434 if (def && patternTransform.hasValue()) {
435 def.getAttribute('patternTransform', true).setValue(patternTransform.value);
436 }
437 }
438 if (def) {
439 return def.createPattern(this.document.ctx, element, opacity);
440 }
441 }
442 return null;
443 }
444 getTextBaseline() {
445 if (!this.hasValue()) {
446 return null;
447 }
448 const key = this.getString();
449 return Property.textBaselineMapping[key] || null;
450 }
451 addOpacity(opacity) {
452 let value = this.getColor();
453 const len = value.length;
454 let commas = 0;
455 // Simulate old RGBColor version, which can't parse rgba.
456 for(let i = 0; i < len; i++){
457 if (value[i] === ',') {
458 commas++;
459 }
460 if (commas === 3) {
461 break;
462 }
463 }
464 if (opacity.hasValue() && this.isString() && commas !== 3) {
465 const color = new RGBColor(value);
466 if (color.ok) {
467 color.alpha = opacity.getNumber();
468 value = color.toRGBA();
469 }
470 }
471 return new Property(this.document, this.name, value);
472 }
473 constructor(document, name, value){
474 this.document = document;
475 this.name = name;
476 this.value = value;
477 this.isNormalizedColor = false;
478 }
479}
480Property.textBaselineMapping = {
481 'baseline': 'alphabetic',
482 'before-edge': 'top',
483 'text-before-edge': 'top',
484 'middle': 'middle',
485 'central': 'middle',
486 'after-edge': 'bottom',
487 'text-after-edge': 'bottom',
488 'ideographic': 'ideographic',
489 'alphabetic': 'alphabetic',
490 'hanging': 'hanging',
491 'mathematical': 'alphabetic'
492};
493
494class ViewPort {
495 clear() {
496 this.viewPorts = [];
497 }
498 setCurrent(width, height) {
499 this.viewPorts.push({
500 width,
501 height
502 });
503 }
504 removeCurrent() {
505 this.viewPorts.pop();
506 }
507 getRoot() {
508 const [root] = this.viewPorts;
509 if (!root) {
510 return getDefault();
511 }
512 return root;
513 }
514 getCurrent() {
515 const { viewPorts } = this;
516 const current = viewPorts[viewPorts.length - 1];
517 if (!current) {
518 return getDefault();
519 }
520 return current;
521 }
522 get width() {
523 return this.getCurrent().width;
524 }
525 get height() {
526 return this.getCurrent().height;
527 }
528 computeSize(d) {
529 if (typeof d === 'number') {
530 return d;
531 }
532 if (d === 'x') {
533 return this.width;
534 }
535 if (d === 'y') {
536 return this.height;
537 }
538 return Math.sqrt(Math.pow(this.width, 2) + Math.pow(this.height, 2)) / Math.sqrt(2);
539 }
540 constructor(){
541 this.viewPorts = [];
542 }
543}
544ViewPort.DEFAULT_VIEWPORT_WIDTH = 800;
545ViewPort.DEFAULT_VIEWPORT_HEIGHT = 600;
546function getDefault() {
547 return {
548 width: ViewPort.DEFAULT_VIEWPORT_WIDTH,
549 height: ViewPort.DEFAULT_VIEWPORT_HEIGHT
550 };
551}
552
553class Point {
554 static parse(point) {
555 let defaultValue = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0;
556 const [x = defaultValue, y = defaultValue] = toNumbers(point);
557 return new Point(x, y);
558 }
559 static parseScale(scale) {
560 let defaultValue = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 1;
561 const [x = defaultValue, y = x] = toNumbers(scale);
562 return new Point(x, y);
563 }
564 static parsePath(path) {
565 const points = toNumbers(path);
566 const len = points.length;
567 const pathPoints = [];
568 for(let i = 0; i < len; i += 2){
569 pathPoints.push(new Point(points[i], points[i + 1]));
570 }
571 return pathPoints;
572 }
573 angleTo(point) {
574 return Math.atan2(point.y - this.y, point.x - this.x);
575 }
576 applyTransform(transform) {
577 const { x , y } = this;
578 const xp = x * transform[0] + y * transform[2] + transform[4];
579 const yp = x * transform[1] + y * transform[3] + transform[5];
580 this.x = xp;
581 this.y = yp;
582 }
583 constructor(x, y){
584 this.x = x;
585 this.y = y;
586 }
587}
588
589class Mouse {
590 isWorking() {
591 return this.working;
592 }
593 start() {
594 if (this.working) {
595 return;
596 }
597 const { screen , onClick , onMouseMove } = this;
598 const canvas = screen.ctx.canvas;
599 canvas.onclick = onClick;
600 canvas.onmousemove = onMouseMove;
601 this.working = true;
602 }
603 stop() {
604 if (!this.working) {
605 return;
606 }
607 const canvas = this.screen.ctx.canvas;
608 this.working = false;
609 canvas.onclick = null;
610 canvas.onmousemove = null;
611 }
612 hasEvents() {
613 return this.working && this.events.length > 0;
614 }
615 runEvents() {
616 if (!this.working) {
617 return;
618 }
619 const { screen: document , events , eventElements } = this;
620 const { style } = document.ctx.canvas;
621 let element;
622 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
623 if (style) {
624 style.cursor = '';
625 }
626 events.forEach((param, i)=>{
627 let { run } = param;
628 element = eventElements[i];
629 while(element){
630 run(element);
631 element = element.parent;
632 }
633 });
634 // done running, clear
635 this.events = [];
636 this.eventElements = [];
637 }
638 checkPath(element, ctx) {
639 if (!this.working || !ctx) {
640 return;
641 }
642 const { events , eventElements } = this;
643 events.forEach((param, i)=>{
644 let { x , y } = param;
645 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
646 if (!eventElements[i] && ctx.isPointInPath && ctx.isPointInPath(x, y)) {
647 eventElements[i] = element;
648 }
649 });
650 }
651 checkBoundingBox(element, boundingBox) {
652 if (!this.working || !boundingBox) {
653 return;
654 }
655 const { events , eventElements } = this;
656 events.forEach((param, i)=>{
657 let { x , y } = param;
658 if (!eventElements[i] && boundingBox.isPointInBox(x, y)) {
659 eventElements[i] = element;
660 }
661 });
662 }
663 mapXY(x, y) {
664 const { window , ctx } = this.screen;
665 const point = new Point(x, y);
666 let element = ctx.canvas;
667 while(element){
668 point.x -= element.offsetLeft;
669 point.y -= element.offsetTop;
670 element = element.offsetParent;
671 }
672 if (window === null || window === void 0 ? void 0 : window.scrollX) {
673 point.x += window.scrollX;
674 }
675 if (window === null || window === void 0 ? void 0 : window.scrollY) {
676 point.y += window.scrollY;
677 }
678 return point;
679 }
680 onClick(event) {
681 const { x , y } = this.mapXY(event.clientX, event.clientY);
682 this.events.push({
683 type: 'onclick',
684 x,
685 y,
686 run (eventTarget) {
687 if (eventTarget.onClick) {
688 eventTarget.onClick();
689 }
690 }
691 });
692 }
693 onMouseMove(event) {
694 const { x , y } = this.mapXY(event.clientX, event.clientY);
695 this.events.push({
696 type: 'onmousemove',
697 x,
698 y,
699 run (eventTarget) {
700 if (eventTarget.onMouseMove) {
701 eventTarget.onMouseMove();
702 }
703 }
704 });
705 }
706 constructor(screen){
707 this.screen = screen;
708 this.working = false;
709 this.events = [];
710 this.eventElements = [];
711 this.onClick = this.onClick.bind(this);
712 this.onMouseMove = this.onMouseMove.bind(this);
713 }
714}
715
716const defaultWindow = typeof window !== 'undefined' ? window : null;
717const defaultFetch$1 = typeof fetch !== 'undefined' ? fetch.bind(undefined) // `fetch` depends on context: `someObject.fetch(...)` will throw error.
718 : undefined;
719class Screen {
720 wait(checker) {
721 this.waits.push(checker);
722 }
723 ready() {
724 // eslint-disable-next-line @typescript-eslint/no-misused-promises
725 if (!this.readyPromise) {
726 return Promise.resolve();
727 }
728 return this.readyPromise;
729 }
730 isReady() {
731 if (this.isReadyLock) {
732 return true;
733 }
734 const isReadyLock = this.waits.every((_)=>_()
735 );
736 if (isReadyLock) {
737 this.waits = [];
738 if (this.resolveReady) {
739 this.resolveReady();
740 }
741 }
742 this.isReadyLock = isReadyLock;
743 return isReadyLock;
744 }
745 setDefaults(ctx) {
746 // initial values and defaults
747 ctx.strokeStyle = 'rgba(0,0,0,0)';
748 ctx.lineCap = 'butt';
749 ctx.lineJoin = 'miter';
750 ctx.miterLimit = 4;
751 }
752 setViewBox(param) {
753 let { document , ctx , aspectRatio , width , desiredWidth , height , desiredHeight , minX =0 , minY =0 , refX , refY , clip =false , clipX =0 , clipY =0 } = param;
754 // aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
755 const cleanAspectRatio = compressSpaces(aspectRatio).replace(/^defer\s/, '') // ignore defer
756 ;
757 const [aspectRatioAlign, aspectRatioMeetOrSlice] = cleanAspectRatio.split(' ');
758 const align = aspectRatioAlign || 'xMidYMid';
759 const meetOrSlice = aspectRatioMeetOrSlice || 'meet';
760 // calculate scale
761 const scaleX = width / desiredWidth;
762 const scaleY = height / desiredHeight;
763 const scaleMin = Math.min(scaleX, scaleY);
764 const scaleMax = Math.max(scaleX, scaleY);
765 let finalDesiredWidth = desiredWidth;
766 let finalDesiredHeight = desiredHeight;
767 if (meetOrSlice === 'meet') {
768 finalDesiredWidth *= scaleMin;
769 finalDesiredHeight *= scaleMin;
770 }
771 if (meetOrSlice === 'slice') {
772 finalDesiredWidth *= scaleMax;
773 finalDesiredHeight *= scaleMax;
774 }
775 const refXProp = new Property(document, 'refX', refX);
776 const refYProp = new Property(document, 'refY', refY);
777 const hasRefs = refXProp.hasValue() && refYProp.hasValue();
778 if (hasRefs) {
779 ctx.translate(-scaleMin * refXProp.getPixels('x'), -scaleMin * refYProp.getPixels('y'));
780 }
781 if (clip) {
782 const scaledClipX = scaleMin * clipX;
783 const scaledClipY = scaleMin * clipY;
784 ctx.beginPath();
785 ctx.moveTo(scaledClipX, scaledClipY);
786 ctx.lineTo(width, scaledClipY);
787 ctx.lineTo(width, height);
788 ctx.lineTo(scaledClipX, height);
789 ctx.closePath();
790 ctx.clip();
791 }
792 if (!hasRefs) {
793 const isMeetMinY = meetOrSlice === 'meet' && scaleMin === scaleY;
794 const isSliceMaxY = meetOrSlice === 'slice' && scaleMax === scaleY;
795 const isMeetMinX = meetOrSlice === 'meet' && scaleMin === scaleX;
796 const isSliceMaxX = meetOrSlice === 'slice' && scaleMax === scaleX;
797 if (align.startsWith('xMid') && (isMeetMinY || isSliceMaxY)) {
798 ctx.translate(width / 2 - finalDesiredWidth / 2, 0);
799 }
800 if (align.endsWith('YMid') && (isMeetMinX || isSliceMaxX)) {
801 ctx.translate(0, height / 2 - finalDesiredHeight / 2);
802 }
803 if (align.startsWith('xMax') && (isMeetMinY || isSliceMaxY)) {
804 ctx.translate(width - finalDesiredWidth, 0);
805 }
806 if (align.endsWith('YMax') && (isMeetMinX || isSliceMaxX)) {
807 ctx.translate(0, height - finalDesiredHeight);
808 }
809 }
810 // scale
811 switch(true){
812 case align === 'none':
813 ctx.scale(scaleX, scaleY);
814 break;
815 case meetOrSlice === 'meet':
816 ctx.scale(scaleMin, scaleMin);
817 break;
818 case meetOrSlice === 'slice':
819 ctx.scale(scaleMax, scaleMax);
820 break;
821 }
822 // translate
823 ctx.translate(-minX, -minY);
824 }
825 start(element) {
826 let { enableRedraw =false , ignoreMouse =false , ignoreAnimation =false , ignoreDimensions =false , ignoreClear =false , forceRedraw , scaleWidth , scaleHeight , offsetX , offsetY } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
827 const { mouse } = this;
828 const frameDuration = 1000 / Screen.FRAMERATE;
829 this.isReadyLock = false;
830 this.frameDuration = frameDuration;
831 this.readyPromise = new Promise((resolve)=>{
832 this.resolveReady = resolve;
833 });
834 if (this.isReady()) {
835 this.render(element, ignoreDimensions, ignoreClear, scaleWidth, scaleHeight, offsetX, offsetY);
836 }
837 if (!enableRedraw) {
838 return;
839 }
840 let now = Date.now();
841 let then = now;
842 let delta = 0;
843 const tick = ()=>{
844 now = Date.now();
845 delta = now - then;
846 if (delta >= frameDuration) {
847 then = now - delta % frameDuration;
848 if (this.shouldUpdate(ignoreAnimation, forceRedraw)) {
849 this.render(element, ignoreDimensions, ignoreClear, scaleWidth, scaleHeight, offsetX, offsetY);
850 mouse.runEvents();
851 }
852 }
853 this.intervalId = requestAnimationFrame(tick);
854 };
855 if (!ignoreMouse) {
856 mouse.start();
857 }
858 this.intervalId = requestAnimationFrame(tick);
859 }
860 stop() {
861 if (this.intervalId) {
862 requestAnimationFrame.cancel(this.intervalId);
863 this.intervalId = null;
864 }
865 this.mouse.stop();
866 }
867 shouldUpdate(ignoreAnimation, forceRedraw) {
868 // need update from animations?
869 if (!ignoreAnimation) {
870 const { frameDuration } = this;
871 const shouldUpdate1 = this.animations.reduce((shouldUpdate, animation)=>animation.update(frameDuration) || shouldUpdate
872 , false);
873 if (shouldUpdate1) {
874 return true;
875 }
876 }
877 // need update from redraw?
878 if (typeof forceRedraw === 'function' && forceRedraw()) {
879 return true;
880 }
881 if (!this.isReadyLock && this.isReady()) {
882 return true;
883 }
884 // need update from mouse events?
885 if (this.mouse.hasEvents()) {
886 return true;
887 }
888 return false;
889 }
890 render(element, ignoreDimensions, ignoreClear, scaleWidth, scaleHeight, offsetX, offsetY) {
891 const { viewPort , ctx , isFirstRender } = this;
892 const canvas = ctx.canvas;
893 viewPort.clear();
894 if (canvas.width && canvas.height) {
895 viewPort.setCurrent(canvas.width, canvas.height);
896 }
897 const widthStyle = element.getStyle('width');
898 const heightStyle = element.getStyle('height');
899 if (!ignoreDimensions && (isFirstRender || typeof scaleWidth !== 'number' && typeof scaleHeight !== 'number')) {
900 // set canvas size
901 if (widthStyle.hasValue()) {
902 canvas.width = widthStyle.getPixels('x');
903 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
904 if (canvas.style) {
905 canvas.style.width = "".concat(canvas.width, "px");
906 }
907 }
908 if (heightStyle.hasValue()) {
909 canvas.height = heightStyle.getPixels('y');
910 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
911 if (canvas.style) {
912 canvas.style.height = "".concat(canvas.height, "px");
913 }
914 }
915 }
916 let cWidth = canvas.clientWidth || canvas.width;
917 let cHeight = canvas.clientHeight || canvas.height;
918 if (ignoreDimensions && widthStyle.hasValue() && heightStyle.hasValue()) {
919 cWidth = widthStyle.getPixels('x');
920 cHeight = heightStyle.getPixels('y');
921 }
922 viewPort.setCurrent(cWidth, cHeight);
923 if (typeof offsetX === 'number') {
924 element.getAttribute('x', true).setValue(offsetX);
925 }
926 if (typeof offsetY === 'number') {
927 element.getAttribute('y', true).setValue(offsetY);
928 }
929 if (typeof scaleWidth === 'number' || typeof scaleHeight === 'number') {
930 const viewBox = toNumbers(element.getAttribute('viewBox').getString());
931 let xRatio = 0;
932 let yRatio = 0;
933 if (typeof scaleWidth === 'number') {
934 const widthStyle = element.getStyle('width');
935 if (widthStyle.hasValue()) {
936 xRatio = widthStyle.getPixels('x') / scaleWidth;
937 } else if (viewBox[2] && !isNaN(viewBox[2])) {
938 xRatio = viewBox[2] / scaleWidth;
939 }
940 }
941 if (typeof scaleHeight === 'number') {
942 const heightStyle = element.getStyle('height');
943 if (heightStyle.hasValue()) {
944 yRatio = heightStyle.getPixels('y') / scaleHeight;
945 } else if (viewBox[3] && !isNaN(viewBox[3])) {
946 yRatio = viewBox[3] / scaleHeight;
947 }
948 }
949 if (!xRatio) {
950 xRatio = yRatio;
951 }
952 if (!yRatio) {
953 yRatio = xRatio;
954 }
955 element.getAttribute('width', true).setValue(scaleWidth);
956 element.getAttribute('height', true).setValue(scaleHeight);
957 const transformStyle = element.getStyle('transform', true, true);
958 transformStyle.setValue("".concat(transformStyle.getString(), " scale(").concat(1 / xRatio, ", ").concat(1 / yRatio, ")"));
959 }
960 // clear and render
961 if (!ignoreClear) {
962 ctx.clearRect(0, 0, cWidth, cHeight);
963 }
964 element.render(ctx);
965 if (isFirstRender) {
966 this.isFirstRender = false;
967 }
968 }
969 constructor(ctx, { fetch =defaultFetch$1 , window =defaultWindow } = {}){
970 this.ctx = ctx;
971 this.viewPort = new ViewPort();
972 this.mouse = new Mouse(this);
973 this.animations = [];
974 this.waits = [];
975 this.frameDuration = 0;
976 this.isReadyLock = false;
977 this.isFirstRender = true;
978 this.intervalId = null;
979 this.window = window;
980 if (!fetch) {
981 throw new Error("Can't find 'fetch' in 'globalThis', please provide it via options");
982 }
983 this.fetch = fetch;
984 }
985}
986Screen.defaultWindow = defaultWindow;
987Screen.defaultFetch = defaultFetch$1;
988Screen.FRAMERATE = 30;
989Screen.MAX_VIRTUAL_PIXELS = 30000;
990
991const { defaultFetch } = Screen;
992const DefaultDOMParser = typeof DOMParser !== 'undefined' ? DOMParser : undefined;
993class Parser {
994 async parse(resource) {
995 if (resource.startsWith('<')) {
996 return this.parseFromString(resource);
997 }
998 return this.load(resource);
999 }
1000 parseFromString(xml) {
1001 const parser = new this.DOMParser();
1002 try {
1003 return this.checkDocument(parser.parseFromString(xml, 'image/svg+xml'));
1004 } catch (err) {
1005 return this.checkDocument(parser.parseFromString(xml, 'text/xml'));
1006 }
1007 }
1008 checkDocument(document) {
1009 const parserError = document.getElementsByTagName('parsererror')[0];
1010 if (parserError) {
1011 throw new Error(parserError.textContent || 'Unknown parse error');
1012 }
1013 return document;
1014 }
1015 async load(url) {
1016 const response = await this.fetch(url);
1017 const xml = await response.text();
1018 return this.parseFromString(xml);
1019 }
1020 constructor({ fetch =defaultFetch , DOMParser =DefaultDOMParser } = {}){
1021 if (!fetch) {
1022 throw new Error("Can't find 'fetch' in 'globalThis', please provide it via options");
1023 }
1024 if (!DOMParser) {
1025 throw new Error("Can't find 'DOMParser' in 'globalThis', please provide it via options");
1026 }
1027 this.fetch = fetch;
1028 this.DOMParser = DOMParser;
1029 }
1030}
1031
1032class Translate {
1033 apply(ctx) {
1034 const { x , y } = this.point;
1035 ctx.translate(x || 0, y || 0);
1036 }
1037 unapply(ctx) {
1038 const { x , y } = this.point;
1039 ctx.translate(-1 * x || 0, -1 * y || 0);
1040 }
1041 applyToPoint(point) {
1042 const { x , y } = this.point;
1043 point.applyTransform([
1044 1,
1045 0,
1046 0,
1047 1,
1048 x || 0,
1049 y || 0
1050 ]);
1051 }
1052 constructor(_, point){
1053 this.type = 'translate';
1054 this.point = Point.parse(point);
1055 }
1056}
1057
1058class Rotate {
1059 apply(ctx) {
1060 const { cx , cy , originX , originY , angle } = this;
1061 const tx = cx + originX.getPixels('x');
1062 const ty = cy + originY.getPixels('y');
1063 ctx.translate(tx, ty);
1064 ctx.rotate(angle.getRadians());
1065 ctx.translate(-tx, -ty);
1066 }
1067 unapply(ctx) {
1068 const { cx , cy , originX , originY , angle } = this;
1069 const tx = cx + originX.getPixels('x');
1070 const ty = cy + originY.getPixels('y');
1071 ctx.translate(tx, ty);
1072 ctx.rotate(-1 * angle.getRadians());
1073 ctx.translate(-tx, -ty);
1074 }
1075 applyToPoint(point) {
1076 const { cx , cy , angle } = this;
1077 const rad = angle.getRadians();
1078 point.applyTransform([
1079 1,
1080 0,
1081 0,
1082 1,
1083 cx || 0,
1084 cy || 0 // this.p.y
1085 ]);
1086 point.applyTransform([
1087 Math.cos(rad),
1088 Math.sin(rad),
1089 -Math.sin(rad),
1090 Math.cos(rad),
1091 0,
1092 0
1093 ]);
1094 point.applyTransform([
1095 1,
1096 0,
1097 0,
1098 1,
1099 -cx || 0,
1100 -cy || 0 // -this.p.y
1101 ]);
1102 }
1103 constructor(document, rotate, transformOrigin){
1104 this.type = 'rotate';
1105 const numbers = toNumbers(rotate);
1106 this.angle = new Property(document, 'angle', numbers[0]);
1107 this.originX = transformOrigin[0];
1108 this.originY = transformOrigin[1];
1109 this.cx = numbers[1] || 0;
1110 this.cy = numbers[2] || 0;
1111 }
1112}
1113
1114class Scale {
1115 apply(ctx) {
1116 const { scale: { x , y } , originX , originY } = this;
1117 const tx = originX.getPixels('x');
1118 const ty = originY.getPixels('y');
1119 ctx.translate(tx, ty);
1120 ctx.scale(x, y || x);
1121 ctx.translate(-tx, -ty);
1122 }
1123 unapply(ctx) {
1124 const { scale: { x , y } , originX , originY } = this;
1125 const tx = originX.getPixels('x');
1126 const ty = originY.getPixels('y');
1127 ctx.translate(tx, ty);
1128 ctx.scale(1 / x, 1 / y || x);
1129 ctx.translate(-tx, -ty);
1130 }
1131 applyToPoint(point) {
1132 const { x , y } = this.scale;
1133 point.applyTransform([
1134 x || 0,
1135 0,
1136 0,
1137 y || 0,
1138 0,
1139 0
1140 ]);
1141 }
1142 constructor(_, scale, transformOrigin){
1143 this.type = 'scale';
1144 const scaleSize = Point.parseScale(scale);
1145 // Workaround for node-canvas
1146 if (scaleSize.x === 0 || scaleSize.y === 0) {
1147 scaleSize.x = PSEUDO_ZERO;
1148 scaleSize.y = PSEUDO_ZERO;
1149 }
1150 this.scale = scaleSize;
1151 this.originX = transformOrigin[0];
1152 this.originY = transformOrigin[1];
1153 }
1154}
1155
1156class Matrix {
1157 apply(ctx) {
1158 const { originX , originY , matrix } = this;
1159 const tx = originX.getPixels('x');
1160 const ty = originY.getPixels('y');
1161 ctx.translate(tx, ty);
1162 ctx.transform(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
1163 ctx.translate(-tx, -ty);
1164 }
1165 unapply(ctx) {
1166 const { originX , originY , matrix } = this;
1167 const a = matrix[0];
1168 const b = matrix[2];
1169 const c = matrix[4];
1170 const d = matrix[1];
1171 const e = matrix[3];
1172 const f = matrix[5];
1173 const g = 0;
1174 const h = 0;
1175 const i = 1;
1176 const det = 1 / (a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g));
1177 const tx = originX.getPixels('x');
1178 const ty = originY.getPixels('y');
1179 ctx.translate(tx, ty);
1180 ctx.transform(det * (e * i - f * h), det * (f * g - d * i), det * (c * h - b * i), det * (a * i - c * g), det * (b * f - c * e), det * (c * d - a * f));
1181 ctx.translate(-tx, -ty);
1182 }
1183 applyToPoint(point) {
1184 point.applyTransform(this.matrix);
1185 }
1186 constructor(_, matrix, transformOrigin){
1187 this.type = 'matrix';
1188 this.matrix = toMatrixValue(matrix);
1189 this.originX = transformOrigin[0];
1190 this.originY = transformOrigin[1];
1191 }
1192}
1193
1194class Skew extends Matrix {
1195 constructor(document, skew, transformOrigin){
1196 super(document, skew, transformOrigin);
1197 this.type = 'skew';
1198 this.angle = new Property(document, 'angle', skew);
1199 }
1200}
1201
1202class SkewX extends Skew {
1203 constructor(document, skew, transformOrigin){
1204 super(document, skew, transformOrigin);
1205 this.type = 'skewX';
1206 this.matrix = [
1207 1,
1208 0,
1209 Math.tan(this.angle.getRadians()),
1210 1,
1211 0,
1212 0
1213 ];
1214 }
1215}
1216
1217class SkewY extends Skew {
1218 constructor(document, skew, transformOrigin){
1219 super(document, skew, transformOrigin);
1220 this.type = 'skewY';
1221 this.matrix = [
1222 1,
1223 Math.tan(this.angle.getRadians()),
1224 0,
1225 1,
1226 0,
1227 0
1228 ];
1229 }
1230}
1231
1232function parseTransforms(transform) {
1233 return compressSpaces(transform).trim().replace(/\)([a-zA-Z])/g, ') $1').replace(/\)(\s?,\s?)/g, ') ').split(/\s(?=[a-z])/);
1234}
1235function parseTransform(transform) {
1236 const [type = '', value = ''] = transform.split('(');
1237 return [
1238 type.trim(),
1239 value.trim().replace(')', '')
1240 ];
1241}
1242class Transform {
1243 static fromElement(document, element) {
1244 const transformStyle = element.getStyle('transform', false, true);
1245 if (transformStyle.hasValue()) {
1246 const [transformOriginXProperty, transformOriginYProperty = transformOriginXProperty] = element.getStyle('transform-origin', false, true).split();
1247 if (transformOriginXProperty && transformOriginYProperty) {
1248 const transformOrigin = [
1249 transformOriginXProperty,
1250 transformOriginYProperty
1251 ];
1252 return new Transform(document, transformStyle.getString(), transformOrigin);
1253 }
1254 }
1255 return null;
1256 }
1257 apply(ctx) {
1258 this.transforms.forEach((transform)=>transform.apply(ctx)
1259 );
1260 }
1261 unapply(ctx) {
1262 this.transforms.forEach((transform)=>transform.unapply(ctx)
1263 );
1264 }
1265 // TODO: applyToPoint unused ... remove?
1266 applyToPoint(point) {
1267 this.transforms.forEach((transform)=>transform.applyToPoint(point)
1268 );
1269 }
1270 constructor(document, transform1, transformOrigin){
1271 this.document = document;
1272 this.transforms = [];
1273 const data = parseTransforms(transform1);
1274 data.forEach((transform)=>{
1275 if (transform === 'none') {
1276 return;
1277 }
1278 const [type, value] = parseTransform(transform);
1279 const TransformType = Transform.transformTypes[type];
1280 if (TransformType) {
1281 this.transforms.push(new TransformType(this.document, value, transformOrigin));
1282 }
1283 });
1284 }
1285}
1286Transform.transformTypes = {
1287 translate: Translate,
1288 rotate: Rotate,
1289 scale: Scale,
1290 matrix: Matrix,
1291 skewX: SkewX,
1292 skewY: SkewY
1293};
1294
1295class Element {
1296 getAttribute(name) {
1297 let createIfNotExists = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false;
1298 const attr = this.attributes[name];
1299 if (!attr && createIfNotExists) {
1300 const attr = new Property(this.document, name, '');
1301 this.attributes[name] = attr;
1302 return attr;
1303 }
1304 return attr || Property.empty(this.document);
1305 }
1306 getHrefAttribute() {
1307 let href;
1308 for(const key in this.attributes){
1309 if (key === 'href' || key.endsWith(':href')) {
1310 href = this.attributes[key];
1311 break;
1312 }
1313 }
1314 return href || Property.empty(this.document);
1315 }
1316 getStyle(name) {
1317 let createIfNotExists = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false, skipAncestors = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false;
1318 const style = this.styles[name];
1319 if (style) {
1320 return style;
1321 }
1322 const attr = this.getAttribute(name);
1323 if (attr.hasValue()) {
1324 this.styles[name] = attr // move up to me to cache
1325 ;
1326 return attr;
1327 }
1328 if (!skipAncestors) {
1329 const { parent } = this;
1330 if (parent) {
1331 const parentStyle = parent.getStyle(name);
1332 if (parentStyle.hasValue()) {
1333 return parentStyle;
1334 }
1335 }
1336 }
1337 if (createIfNotExists) {
1338 const style = new Property(this.document, name, '');
1339 this.styles[name] = style;
1340 return style;
1341 }
1342 return Property.empty(this.document);
1343 }
1344 render(ctx) {
1345 // don't render display=none
1346 // don't render visibility=hidden
1347 if (this.getStyle('display').getString() === 'none' || this.getStyle('visibility').getString() === 'hidden') {
1348 return;
1349 }
1350 ctx.save();
1351 if (this.getStyle('mask').hasValue()) {
1352 const mask = this.getStyle('mask').getDefinition();
1353 if (mask) {
1354 this.applyEffects(ctx);
1355 mask.apply(ctx, this);
1356 }
1357 } else if (this.getStyle('filter').getValue('none') !== 'none') {
1358 const filter = this.getStyle('filter').getDefinition();
1359 if (filter) {
1360 this.applyEffects(ctx);
1361 filter.apply(ctx, this);
1362 }
1363 } else {
1364 this.setContext(ctx);
1365 this.renderChildren(ctx);
1366 this.clearContext(ctx);
1367 }
1368 ctx.restore();
1369 }
1370 setContext(_) {
1371 // NO RENDER
1372 }
1373 applyEffects(ctx) {
1374 // transform
1375 const transform = Transform.fromElement(this.document, this);
1376 if (transform) {
1377 transform.apply(ctx);
1378 }
1379 // clip
1380 const clipPathStyleProp = this.getStyle('clip-path', false, true);
1381 if (clipPathStyleProp.hasValue()) {
1382 const clip = clipPathStyleProp.getDefinition();
1383 if (clip) {
1384 clip.apply(ctx);
1385 }
1386 }
1387 }
1388 clearContext(_) {
1389 // NO RENDER
1390 }
1391 renderChildren(ctx) {
1392 this.children.forEach((child)=>{
1393 child.render(ctx);
1394 });
1395 }
1396 addChild(childNode) {
1397 const child = childNode instanceof Element ? childNode : this.document.createElement(childNode);
1398 child.parent = this;
1399 if (!Element.ignoreChildTypes.includes(child.type)) {
1400 this.children.push(child);
1401 }
1402 }
1403 matchesSelector(selector) {
1404 var ref;
1405 const { node } = this;
1406 if (typeof node.matches === 'function') {
1407 return node.matches(selector);
1408 }
1409 const styleClasses = (ref = node.getAttribute) === null || ref === void 0 ? void 0 : ref.call(node, 'class');
1410 if (!styleClasses || styleClasses === '') {
1411 return false;
1412 }
1413 return styleClasses.split(' ').some((styleClass)=>".".concat(styleClass) === selector
1414 );
1415 }
1416 addStylesFromStyleDefinition() {
1417 const { styles , stylesSpecificity } = this.document;
1418 let styleProp;
1419 for(const selector in styles){
1420 if (!selector.startsWith('@') && this.matchesSelector(selector)) {
1421 const style = styles[selector];
1422 const specificity = stylesSpecificity[selector];
1423 if (style) {
1424 for(const name in style){
1425 let existingSpecificity = this.stylesSpecificity[name];
1426 if (typeof existingSpecificity === 'undefined') {
1427 existingSpecificity = '000';
1428 }
1429 if (specificity && specificity >= existingSpecificity) {
1430 styleProp = style[name];
1431 if (styleProp) {
1432 this.styles[name] = styleProp;
1433 }
1434 this.stylesSpecificity[name] = specificity;
1435 }
1436 }
1437 }
1438 }
1439 }
1440 }
1441 removeStyles(element, ignoreStyles) {
1442 const toRestore1 = ignoreStyles.reduce((toRestore, name)=>{
1443 const styleProp = element.getStyle(name);
1444 if (!styleProp.hasValue()) {
1445 return toRestore;
1446 }
1447 const value = styleProp.getString();
1448 styleProp.setValue('');
1449 return [
1450 ...toRestore,
1451 [
1452 name,
1453 value
1454 ]
1455 ];
1456 }, []);
1457 return toRestore1;
1458 }
1459 restoreStyles(element, styles) {
1460 styles.forEach((param)=>{
1461 let [name, value] = param;
1462 element.getStyle(name, true).setValue(value);
1463 });
1464 }
1465 isFirstChild() {
1466 var ref;
1467 return ((ref = this.parent) === null || ref === void 0 ? void 0 : ref.children.indexOf(this)) === 0;
1468 }
1469 constructor(document, node, captureTextNodes = false){
1470 this.document = document;
1471 this.node = node;
1472 this.captureTextNodes = captureTextNodes;
1473 this.type = '';
1474 this.attributes = {};
1475 this.styles = {};
1476 this.stylesSpecificity = {};
1477 this.animationFrozen = false;
1478 this.animationFrozenValue = '';
1479 this.parent = null;
1480 this.children = [];
1481 if (!node || node.nodeType !== 1) {
1482 return;
1483 }
1484 // add attributes
1485 Array.from(node.attributes).forEach((attribute)=>{
1486 const nodeName = normalizeAttributeName(attribute.nodeName);
1487 this.attributes[nodeName] = new Property(document, nodeName, attribute.value);
1488 });
1489 this.addStylesFromStyleDefinition();
1490 // add inline styles
1491 if (this.getAttribute('style').hasValue()) {
1492 const styles = this.getAttribute('style').getString().split(';').map((_)=>_.trim()
1493 );
1494 styles.forEach((style)=>{
1495 if (!style) {
1496 return;
1497 }
1498 const [name, value] = style.split(':').map((_)=>_.trim()
1499 );
1500 if (name) {
1501 this.styles[name] = new Property(document, name, value);
1502 }
1503 });
1504 }
1505 const { definitions } = document;
1506 const id = this.getAttribute('id');
1507 // add id
1508 if (id.hasValue()) {
1509 if (!definitions[id.getString()]) {
1510 definitions[id.getString()] = this;
1511 }
1512 }
1513 Array.from(node.childNodes).forEach((childNode)=>{
1514 if (childNode.nodeType === 1) {
1515 this.addChild(childNode) // ELEMENT_NODE
1516 ;
1517 } else if (captureTextNodes && (childNode.nodeType === 3 || childNode.nodeType === 4)) {
1518 const textNode = document.createTextNode(childNode);
1519 if (textNode.getText().length > 0) {
1520 this.addChild(textNode) // TEXT_NODE
1521 ;
1522 }
1523 }
1524 });
1525 }
1526}
1527Element.ignoreChildTypes = [
1528 'title'
1529];
1530
1531class UnknownElement extends Element {
1532 constructor(document, node, captureTextNodes){
1533 super(document, node, captureTextNodes);
1534 }
1535}
1536
1537function wrapFontFamily(fontFamily) {
1538 const trimmed = fontFamily.trim();
1539 return /^('|")/.test(trimmed) ? trimmed : "\"".concat(trimmed, "\"");
1540}
1541function prepareFontFamily(fontFamily) {
1542 return typeof process === 'undefined' ? fontFamily : fontFamily.trim().split(',').map(wrapFontFamily).join(',');
1543}
1544/**
1545 * https://developer.mozilla.org/en-US/docs/Web/CSS/font-style
1546 * @param fontStyle
1547 * @returns CSS font style.
1548 */ function prepareFontStyle(fontStyle) {
1549 if (!fontStyle) {
1550 return '';
1551 }
1552 const targetFontStyle = fontStyle.trim().toLowerCase();
1553 switch(targetFontStyle){
1554 case 'normal':
1555 case 'italic':
1556 case 'oblique':
1557 case 'inherit':
1558 case 'initial':
1559 case 'unset':
1560 return targetFontStyle;
1561 default:
1562 if (/^oblique\s+(-|)\d+deg$/.test(targetFontStyle)) {
1563 return targetFontStyle;
1564 }
1565 return '';
1566 }
1567}
1568/**
1569 * https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
1570 * @param fontWeight
1571 * @returns CSS font weight.
1572 */ function prepareFontWeight(fontWeight) {
1573 if (!fontWeight) {
1574 return '';
1575 }
1576 const targetFontWeight = fontWeight.trim().toLowerCase();
1577 switch(targetFontWeight){
1578 case 'normal':
1579 case 'bold':
1580 case 'lighter':
1581 case 'bolder':
1582 case 'inherit':
1583 case 'initial':
1584 case 'unset':
1585 return targetFontWeight;
1586 default:
1587 if (/^[\d.]+$/.test(targetFontWeight)) {
1588 return targetFontWeight;
1589 }
1590 return '';
1591 }
1592}
1593class Font {
1594 static parse() {
1595 let font = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : '', inherit = arguments.length > 1 ? arguments[1] : void 0;
1596 let fontStyle = '';
1597 let fontVariant = '';
1598 let fontWeight = '';
1599 let fontSize = '';
1600 let fontFamily = '';
1601 const parts = compressSpaces(font).trim().split(' ');
1602 const set = {
1603 fontSize: false,
1604 fontStyle: false,
1605 fontWeight: false,
1606 fontVariant: false
1607 };
1608 parts.forEach((part)=>{
1609 switch(true){
1610 case !set.fontStyle && Font.styles.includes(part):
1611 if (part !== 'inherit') {
1612 fontStyle = part;
1613 }
1614 set.fontStyle = true;
1615 break;
1616 case !set.fontVariant && Font.variants.includes(part):
1617 if (part !== 'inherit') {
1618 fontVariant = part;
1619 }
1620 set.fontStyle = true;
1621 set.fontVariant = true;
1622 break;
1623 case !set.fontWeight && Font.weights.includes(part):
1624 if (part !== 'inherit') {
1625 fontWeight = part;
1626 }
1627 set.fontStyle = true;
1628 set.fontVariant = true;
1629 set.fontWeight = true;
1630 break;
1631 case !set.fontSize:
1632 if (part !== 'inherit') {
1633 fontSize = part.split('/')[0] || '';
1634 }
1635 set.fontStyle = true;
1636 set.fontVariant = true;
1637 set.fontWeight = true;
1638 set.fontSize = true;
1639 break;
1640 default:
1641 if (part !== 'inherit') {
1642 fontFamily += part;
1643 }
1644 }
1645 });
1646 return new Font(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit);
1647 }
1648 toString() {
1649 return [
1650 prepareFontStyle(this.fontStyle),
1651 this.fontVariant,
1652 prepareFontWeight(this.fontWeight),
1653 this.fontSize,
1654 // Wrap fontFamily only on nodejs and only for canvas.ctx
1655 prepareFontFamily(this.fontFamily)
1656 ].join(' ').trim();
1657 }
1658 constructor(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit){
1659 const inheritFont = inherit ? typeof inherit === 'string' ? Font.parse(inherit) : inherit : {};
1660 this.fontFamily = fontFamily || inheritFont.fontFamily;
1661 this.fontSize = fontSize || inheritFont.fontSize;
1662 this.fontStyle = fontStyle || inheritFont.fontStyle;
1663 this.fontWeight = fontWeight || inheritFont.fontWeight;
1664 this.fontVariant = fontVariant || inheritFont.fontVariant;
1665 }
1666}
1667Font.styles = 'normal|italic|oblique|inherit';
1668Font.variants = 'normal|small-caps|inherit';
1669Font.weights = 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit';
1670
1671class BoundingBox {
1672 get x() {
1673 return this.x1;
1674 }
1675 get y() {
1676 return this.y1;
1677 }
1678 get width() {
1679 return this.x2 - this.x1;
1680 }
1681 get height() {
1682 return this.y2 - this.y1;
1683 }
1684 addPoint(x, y) {
1685 if (typeof x !== 'undefined') {
1686 if (isNaN(this.x1) || isNaN(this.x2)) {
1687 this.x1 = x;
1688 this.x2 = x;
1689 }
1690 if (x < this.x1) {
1691 this.x1 = x;
1692 }
1693 if (x > this.x2) {
1694 this.x2 = x;
1695 }
1696 }
1697 if (typeof y !== 'undefined') {
1698 if (isNaN(this.y1) || isNaN(this.y2)) {
1699 this.y1 = y;
1700 this.y2 = y;
1701 }
1702 if (y < this.y1) {
1703 this.y1 = y;
1704 }
1705 if (y > this.y2) {
1706 this.y2 = y;
1707 }
1708 }
1709 }
1710 addX(x) {
1711 this.addPoint(x, 0);
1712 }
1713 addY(y) {
1714 this.addPoint(0, y);
1715 }
1716 addBoundingBox(boundingBox) {
1717 if (!boundingBox) {
1718 return;
1719 }
1720 const { x1 , y1 , x2 , y2 } = boundingBox;
1721 this.addPoint(x1, y1);
1722 this.addPoint(x2, y2);
1723 }
1724 sumCubic(t, p0, p1, p2, p3) {
1725 return Math.pow(1 - t, 3) * p0 + 3 * Math.pow(1 - t, 2) * t * p1 + 3 * (1 - t) * Math.pow(t, 2) * p2 + Math.pow(t, 3) * p3;
1726 }
1727 bezierCurveAdd(forX, p0, p1, p2, p3) {
1728 const b = 6 * p0 - 12 * p1 + 6 * p2;
1729 const a = -3 * p0 + 9 * p1 - 9 * p2 + 3 * p3;
1730 const c = 3 * p1 - 3 * p0;
1731 if (a === 0) {
1732 if (b === 0) {
1733 return;
1734 }
1735 const t = -c / b;
1736 if (0 < t && t < 1) {
1737 if (forX) {
1738 this.addX(this.sumCubic(t, p0, p1, p2, p3));
1739 } else {
1740 this.addY(this.sumCubic(t, p0, p1, p2, p3));
1741 }
1742 }
1743 return;
1744 }
1745 const b2ac = Math.pow(b, 2) - 4 * c * a;
1746 if (b2ac < 0) {
1747 return;
1748 }
1749 const t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
1750 if (0 < t1 && t1 < 1) {
1751 if (forX) {
1752 this.addX(this.sumCubic(t1, p0, p1, p2, p3));
1753 } else {
1754 this.addY(this.sumCubic(t1, p0, p1, p2, p3));
1755 }
1756 }
1757 const t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
1758 if (0 < t2 && t2 < 1) {
1759 if (forX) {
1760 this.addX(this.sumCubic(t2, p0, p1, p2, p3));
1761 } else {
1762 this.addY(this.sumCubic(t2, p0, p1, p2, p3));
1763 }
1764 }
1765 }
1766 // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
1767 addBezierCurve(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
1768 this.addPoint(p0x, p0y);
1769 this.addPoint(p3x, p3y);
1770 this.bezierCurveAdd(true, p0x, p1x, p2x, p3x);
1771 this.bezierCurveAdd(false, p0y, p1y, p2y, p3y);
1772 }
1773 addQuadraticCurve(p0x, p0y, p1x, p1y, p2x, p2y) {
1774 const cp1x = p0x + 2 / 3 * (p1x - p0x) // CP1 = QP0 + 2/3 *(QP1-QP0)
1775 ;
1776 const cp1y = p0y + 2 / 3 * (p1y - p0y) // CP1 = QP0 + 2/3 *(QP1-QP0)
1777 ;
1778 const cp2x = cp1x + 1 / 3 * (p2x - p0x) // CP2 = CP1 + 1/3 *(QP2-QP0)
1779 ;
1780 const cp2y = cp1y + 1 / 3 * (p2y - p0y) // CP2 = CP1 + 1/3 *(QP2-QP0)
1781 ;
1782 this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
1783 }
1784 isPointInBox(x, y) {
1785 const { x1 , y1 , x2 , y2 } = this;
1786 return x1 <= x && x <= x2 && y1 <= y && y <= y2;
1787 }
1788 constructor(x1 = Number.NaN, y1 = Number.NaN, x2 = Number.NaN, y2 = Number.NaN){
1789 this.x1 = x1;
1790 this.y1 = y1;
1791 this.x2 = x2;
1792 this.y2 = y2;
1793 this.addPoint(x1, y1);
1794 this.addPoint(x2, y2);
1795 }
1796}
1797
1798class RenderedElement extends Element {
1799 calculateOpacity() {
1800 let opacity = 1;
1801 // eslint-disable-next-line @typescript-eslint/no-this-alias, consistent-this
1802 let element = this;
1803 while(element){
1804 const opacityStyle = element.getStyle('opacity', false, true) // no ancestors on style call
1805 ;
1806 if (opacityStyle.hasValue(true)) {
1807 opacity *= opacityStyle.getNumber();
1808 }
1809 element = element.parent;
1810 }
1811 return opacity;
1812 }
1813 setContext(ctx) {
1814 let fromMeasure = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false;
1815 if (!fromMeasure) {
1816 // fill
1817 const fillStyleProp = this.getStyle('fill');
1818 const fillOpacityStyleProp = this.getStyle('fill-opacity');
1819 const strokeStyleProp = this.getStyle('stroke');
1820 const strokeOpacityProp = this.getStyle('stroke-opacity');
1821 if (fillStyleProp.isUrlDefinition()) {
1822 const fillStyle = fillStyleProp.getFillStyleDefinition(this, fillOpacityStyleProp);
1823 if (fillStyle) {
1824 ctx.fillStyle = fillStyle;
1825 }
1826 } else if (fillStyleProp.hasValue()) {
1827 if (fillStyleProp.getString() === 'currentColor') {
1828 fillStyleProp.setValue(this.getStyle('color').getColor());
1829 }
1830 const fillStyle = fillStyleProp.getColor();
1831 if (fillStyle !== 'inherit') {
1832 ctx.fillStyle = fillStyle === 'none' ? 'rgba(0,0,0,0)' : fillStyle;
1833 }
1834 }
1835 if (fillOpacityStyleProp.hasValue()) {
1836 const fillStyle = new Property(this.document, 'fill', ctx.fillStyle).addOpacity(fillOpacityStyleProp).getColor();
1837 ctx.fillStyle = fillStyle;
1838 }
1839 // stroke
1840 if (strokeStyleProp.isUrlDefinition()) {
1841 const strokeStyle = strokeStyleProp.getFillStyleDefinition(this, strokeOpacityProp);
1842 if (strokeStyle) {
1843 ctx.strokeStyle = strokeStyle;
1844 }
1845 } else if (strokeStyleProp.hasValue()) {
1846 if (strokeStyleProp.getString() === 'currentColor') {
1847 strokeStyleProp.setValue(this.getStyle('color').getColor());
1848 }
1849 const strokeStyle = strokeStyleProp.getString();
1850 if (strokeStyle !== 'inherit') {
1851 ctx.strokeStyle = strokeStyle === 'none' ? 'rgba(0,0,0,0)' : strokeStyle;
1852 }
1853 }
1854 if (strokeOpacityProp.hasValue()) {
1855 const strokeStyle = new Property(this.document, 'stroke', ctx.strokeStyle).addOpacity(strokeOpacityProp).getString();
1856 ctx.strokeStyle = strokeStyle;
1857 }
1858 const strokeWidthStyleProp = this.getStyle('stroke-width');
1859 if (strokeWidthStyleProp.hasValue()) {
1860 const newLineWidth = strokeWidthStyleProp.getPixels();
1861 ctx.lineWidth = !newLineWidth ? PSEUDO_ZERO // browsers don't respect 0 (or node-canvas? :-)
1862 : newLineWidth;
1863 }
1864 const strokeLinecapStyleProp = this.getStyle('stroke-linecap');
1865 const strokeLinejoinStyleProp = this.getStyle('stroke-linejoin');
1866 const strokeMiterlimitProp = this.getStyle('stroke-miterlimit');
1867 // NEED TEST
1868 // const pointOrderStyleProp = this.getStyle('paint-order');
1869 const strokeDasharrayStyleProp = this.getStyle('stroke-dasharray');
1870 const strokeDashoffsetProp = this.getStyle('stroke-dashoffset');
1871 if (strokeLinecapStyleProp.hasValue()) {
1872 ctx.lineCap = strokeLinecapStyleProp.getString();
1873 }
1874 if (strokeLinejoinStyleProp.hasValue()) {
1875 ctx.lineJoin = strokeLinejoinStyleProp.getString();
1876 }
1877 if (strokeMiterlimitProp.hasValue()) {
1878 ctx.miterLimit = strokeMiterlimitProp.getNumber();
1879 }
1880 // NEED TEST
1881 // if (pointOrderStyleProp.hasValue()) {
1882 // // ?
1883 // ctx.paintOrder = pointOrderStyleProp.getValue();
1884 // }
1885 if (strokeDasharrayStyleProp.hasValue() && strokeDasharrayStyleProp.getString() !== 'none') {
1886 const gaps = toNumbers(strokeDasharrayStyleProp.getString());
1887 if (typeof ctx.setLineDash !== 'undefined') {
1888 ctx.setLineDash(gaps);
1889 } else // @ts-expect-error Handle browser prefix.
1890 if (typeof ctx.webkitLineDash !== 'undefined') {
1891 // @ts-expect-error Handle browser prefix.
1892 ctx.webkitLineDash = gaps;
1893 } else // @ts-expect-error Handle browser prefix.
1894 if (typeof ctx.mozDash !== 'undefined' && !(gaps.length === 1 && gaps[0] === 0)) {
1895 // @ts-expect-error Handle browser prefix.
1896 ctx.mozDash = gaps;
1897 }
1898 const offset = strokeDashoffsetProp.getPixels();
1899 if (typeof ctx.lineDashOffset !== 'undefined') {
1900 ctx.lineDashOffset = offset;
1901 } else // @ts-expect-error Handle browser prefix.
1902 if (typeof ctx.webkitLineDashOffset !== 'undefined') {
1903 // @ts-expect-error Handle browser prefix.
1904 ctx.webkitLineDashOffset = offset;
1905 } else // @ts-expect-error Handle browser prefix.
1906 if (typeof ctx.mozDashOffset !== 'undefined') {
1907 // @ts-expect-error Handle browser prefix.
1908 ctx.mozDashOffset = offset;
1909 }
1910 }
1911 }
1912 // font
1913 this.modifiedEmSizeStack = false;
1914 if (typeof ctx.font !== 'undefined') {
1915 const fontStyleProp = this.getStyle('font');
1916 const fontStyleStyleProp = this.getStyle('font-style');
1917 const fontVariantStyleProp = this.getStyle('font-variant');
1918 const fontWeightStyleProp = this.getStyle('font-weight');
1919 const fontSizeStyleProp = this.getStyle('font-size');
1920 const fontFamilyStyleProp = this.getStyle('font-family');
1921 const font = new Font(fontStyleStyleProp.getString(), fontVariantStyleProp.getString(), fontWeightStyleProp.getString(), fontSizeStyleProp.hasValue() ? "".concat(fontSizeStyleProp.getPixels(true), "px") : '', fontFamilyStyleProp.getString(), Font.parse(fontStyleProp.getString(), ctx.font));
1922 fontStyleStyleProp.setValue(font.fontStyle);
1923 fontVariantStyleProp.setValue(font.fontVariant);
1924 fontWeightStyleProp.setValue(font.fontWeight);
1925 fontSizeStyleProp.setValue(font.fontSize);
1926 fontFamilyStyleProp.setValue(font.fontFamily);
1927 ctx.font = font.toString();
1928 if (fontSizeStyleProp.isPixels()) {
1929 this.document.emSize = fontSizeStyleProp.getPixels();
1930 this.modifiedEmSizeStack = true;
1931 }
1932 }
1933 if (!fromMeasure) {
1934 // effects
1935 this.applyEffects(ctx);
1936 // opacity
1937 ctx.globalAlpha = this.calculateOpacity();
1938 }
1939 }
1940 clearContext(ctx) {
1941 super.clearContext(ctx);
1942 if (this.modifiedEmSizeStack) {
1943 this.document.popEmSize();
1944 }
1945 }
1946 constructor(...args){
1947 super(...args);
1948 this.modifiedEmSizeStack = false;
1949 }
1950}
1951
1952class TextElement extends RenderedElement {
1953 setContext(ctx) {
1954 let fromMeasure = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false;
1955 super.setContext(ctx, fromMeasure);
1956 const textBaseline = this.getStyle('dominant-baseline').getTextBaseline() || this.getStyle('alignment-baseline').getTextBaseline();
1957 if (textBaseline) {
1958 ctx.textBaseline = textBaseline;
1959 }
1960 }
1961 initializeCoordinates() {
1962 this.x = 0;
1963 this.y = 0;
1964 this.leafTexts = [];
1965 this.textChunkStart = 0;
1966 this.minX = Number.POSITIVE_INFINITY;
1967 this.maxX = Number.NEGATIVE_INFINITY;
1968 }
1969 getBoundingBox(ctx) {
1970 if (this.type !== 'text') {
1971 return this.getTElementBoundingBox(ctx);
1972 }
1973 // first, calculate child positions
1974 this.initializeCoordinates();
1975 this.adjustChildCoordinatesRecursive(ctx);
1976 let boundingBox = null;
1977 // then calculate bounding box
1978 this.children.forEach((_, i)=>{
1979 const childBoundingBox = this.getChildBoundingBox(ctx, this, this, i);
1980 if (!boundingBox) {
1981 boundingBox = childBoundingBox;
1982 } else {
1983 boundingBox.addBoundingBox(childBoundingBox);
1984 }
1985 });
1986 return boundingBox;
1987 }
1988 getFontSize() {
1989 const { document , parent } = this;
1990 const inheritFontSize = Font.parse(document.ctx.font).fontSize;
1991 const fontSize = parent.getStyle('font-size').getNumber(inheritFontSize);
1992 return fontSize;
1993 }
1994 getTElementBoundingBox(ctx) {
1995 const fontSize = this.getFontSize();
1996 return new BoundingBox(this.x, this.y - fontSize, this.x + this.measureText(ctx), this.y);
1997 }
1998 getGlyph(font, text, i) {
1999 const char = text[i];
2000 let glyph;
2001 if (font.isArabic) {
2002 var ref;
2003 const len = text.length;
2004 const prevChar = text[i - 1];
2005 const nextChar = text[i + 1];
2006 let arabicForm = 'isolated';
2007 if ((i === 0 || prevChar === ' ') && i < len - 1 && nextChar !== ' ') {
2008 arabicForm = 'terminal';
2009 }
2010 if (i > 0 && prevChar !== ' ' && i < len - 1 && nextChar !== ' ') {
2011 arabicForm = 'medial';
2012 }
2013 if (i > 0 && prevChar !== ' ' && (i === len - 1 || nextChar === ' ')) {
2014 arabicForm = 'initial';
2015 }
2016 glyph = ((ref = font.arabicGlyphs[char]) === null || ref === void 0 ? void 0 : ref[arabicForm]) || font.glyphs[char];
2017 } else {
2018 glyph = font.glyphs[char];
2019 }
2020 if (!glyph) {
2021 glyph = font.missingGlyph;
2022 }
2023 return glyph;
2024 }
2025 getText() {
2026 return '';
2027 }
2028 getTextFromNode(node) {
2029 const textNode = node || this.node;
2030 const childNodes = Array.from(textNode.parentNode.childNodes);
2031 const index = childNodes.indexOf(textNode);
2032 const lastIndex = childNodes.length - 1;
2033 let text = compressSpaces(// textNode.value
2034 // || textNode.text
2035 textNode.textContent || '');
2036 if (index === 0) {
2037 text = trimLeft(text);
2038 }
2039 if (index === lastIndex) {
2040 text = trimRight(text);
2041 }
2042 return text;
2043 }
2044 renderChildren(ctx) {
2045 if (this.type !== 'text') {
2046 this.renderTElementChildren(ctx);
2047 return;
2048 }
2049 // first, calculate child positions
2050 this.initializeCoordinates();
2051 this.adjustChildCoordinatesRecursive(ctx);
2052 // then render
2053 this.children.forEach((_, i)=>{
2054 this.renderChild(ctx, this, this, i);
2055 });
2056 const { mouse } = this.document.screen;
2057 // Do not calc bounding box if mouse is not working.
2058 if (mouse.isWorking()) {
2059 mouse.checkBoundingBox(this, this.getBoundingBox(ctx));
2060 }
2061 }
2062 renderTElementChildren(ctx) {
2063 const { document , parent } = this;
2064 const renderText = this.getText();
2065 const customFont = parent.getStyle('font-family').getDefinition();
2066 if (customFont) {
2067 const { unitsPerEm } = customFont.fontFace;
2068 const ctxFont = Font.parse(document.ctx.font);
2069 const fontSize = parent.getStyle('font-size').getNumber(ctxFont.fontSize);
2070 const fontStyle = parent.getStyle('font-style').getString(ctxFont.fontStyle);
2071 const scale = fontSize / unitsPerEm;
2072 const text = customFont.isRTL ? renderText.split('').reverse().join('') : renderText;
2073 const dx = toNumbers(parent.getAttribute('dx').getString());
2074 const len = text.length;
2075 for(let i = 0; i < len; i++){
2076 const glyph = this.getGlyph(customFont, text, i);
2077 ctx.translate(this.x, this.y);
2078 ctx.scale(scale, -scale);
2079 const lw = ctx.lineWidth;
2080 ctx.lineWidth = ctx.lineWidth * unitsPerEm / fontSize;
2081 if (fontStyle === 'italic') {
2082 ctx.transform(1, 0, 0.4, 1, 0, 0);
2083 }
2084 glyph.render(ctx);
2085 if (fontStyle === 'italic') {
2086 ctx.transform(1, 0, -0.4, 1, 0, 0);
2087 }
2088 ctx.lineWidth = lw;
2089 ctx.scale(1 / scale, -1 / scale);
2090 ctx.translate(-this.x, -this.y);
2091 this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / unitsPerEm;
2092 if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {
2093 this.x += dx[i];
2094 }
2095 }
2096 return;
2097 }
2098 const { x , y } = this;
2099 // NEED TEST
2100 // if (ctx.paintOrder === 'stroke') {
2101 // if (ctx.strokeStyle) {
2102 // ctx.strokeText(renderText, x, y);
2103 // }
2104 // if (ctx.fillStyle) {
2105 // ctx.fillText(renderText, x, y);
2106 // }
2107 // } else {
2108 if (ctx.fillStyle) {
2109 ctx.fillText(renderText, x, y);
2110 }
2111 if (ctx.strokeStyle) {
2112 ctx.strokeText(renderText, x, y);
2113 }
2114 // }
2115 }
2116 applyAnchoring() {
2117 if (this.textChunkStart >= this.leafTexts.length) {
2118 return;
2119 }
2120 // This is basically the "Apply anchoring" part of https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm.
2121 // The difference is that we apply the anchoring as soon as a chunk is finished. This saves some extra looping.
2122 // Vertical text is not supported.
2123 const firstElement = this.leafTexts[this.textChunkStart];
2124 const textAnchor = firstElement.getStyle('text-anchor').getString('start');
2125 const isRTL = false // we treat RTL like LTR
2126 ;
2127 let shift = 0;
2128 if (textAnchor === 'start' && !isRTL || textAnchor === 'end' && isRTL) {
2129 shift = firstElement.x - this.minX;
2130 } else if (textAnchor === 'end' && !isRTL || textAnchor === 'start' && isRTL) {
2131 shift = firstElement.x - this.maxX;
2132 } else {
2133 shift = firstElement.x - (this.minX + this.maxX) / 2;
2134 }
2135 for(let i = this.textChunkStart; i < this.leafTexts.length; i++){
2136 this.leafTexts[i].x += shift;
2137 }
2138 // start new chunk
2139 this.minX = Number.POSITIVE_INFINITY;
2140 this.maxX = Number.NEGATIVE_INFINITY;
2141 this.textChunkStart = this.leafTexts.length;
2142 }
2143 adjustChildCoordinatesRecursive(ctx) {
2144 this.children.forEach((_, i)=>{
2145 this.adjustChildCoordinatesRecursiveCore(ctx, this, this, i);
2146 });
2147 this.applyAnchoring();
2148 }
2149 adjustChildCoordinatesRecursiveCore(ctx, textParent, parent, i1) {
2150 const child = parent.children[i1];
2151 if (child.children.length > 0) {
2152 child.children.forEach((_, i)=>{
2153 textParent.adjustChildCoordinatesRecursiveCore(ctx, textParent, child, i);
2154 });
2155 } else {
2156 // only leafs are relevant
2157 this.adjustChildCoordinates(ctx, textParent, parent, i1);
2158 }
2159 }
2160 adjustChildCoordinates(ctx, textParent, parent, i) {
2161 const child = parent.children[i];
2162 if (typeof child.measureText !== 'function') {
2163 return child;
2164 }
2165 ctx.save();
2166 child.setContext(ctx, true);
2167 const xAttr = child.getAttribute('x');
2168 const yAttr = child.getAttribute('y');
2169 const dxAttr = child.getAttribute('dx');
2170 const dyAttr = child.getAttribute('dy');
2171 const customFont = child.getStyle('font-family').getDefinition();
2172 const isRTL = Boolean(customFont === null || customFont === void 0 ? void 0 : customFont.isRTL);
2173 if (i === 0) {
2174 // First children inherit attributes from parent(s). Positional attributes
2175 // are only inherited from a parent to it's first child.
2176 if (!xAttr.hasValue()) {
2177 xAttr.setValue(child.getInheritedAttribute('x'));
2178 }
2179 if (!yAttr.hasValue()) {
2180 yAttr.setValue(child.getInheritedAttribute('y'));
2181 }
2182 if (!dxAttr.hasValue()) {
2183 dxAttr.setValue(child.getInheritedAttribute('dx'));
2184 }
2185 if (!dyAttr.hasValue()) {
2186 dyAttr.setValue(child.getInheritedAttribute('dy'));
2187 }
2188 }
2189 const width = child.measureText(ctx);
2190 if (isRTL) {
2191 textParent.x -= width;
2192 }
2193 if (xAttr.hasValue()) {
2194 // an "x" attribute marks the start of a new chunk
2195 textParent.applyAnchoring();
2196 child.x = xAttr.getPixels('x');
2197 if (dxAttr.hasValue()) {
2198 child.x += dxAttr.getPixels('x');
2199 }
2200 } else {
2201 if (dxAttr.hasValue()) {
2202 textParent.x += dxAttr.getPixels('x');
2203 }
2204 child.x = textParent.x;
2205 }
2206 textParent.x = child.x;
2207 if (!isRTL) {
2208 textParent.x += width;
2209 }
2210 if (yAttr.hasValue()) {
2211 child.y = yAttr.getPixels('y');
2212 if (dyAttr.hasValue()) {
2213 child.y += dyAttr.getPixels('y');
2214 }
2215 } else {
2216 if (dyAttr.hasValue()) {
2217 textParent.y += dyAttr.getPixels('y');
2218 }
2219 child.y = textParent.y;
2220 }
2221 textParent.y = child.y;
2222 // update the current chunk and it's bounds
2223 textParent.leafTexts.push(child);
2224 textParent.minX = Math.min(textParent.minX, child.x, child.x + width);
2225 textParent.maxX = Math.max(textParent.maxX, child.x, child.x + width);
2226 child.clearContext(ctx);
2227 ctx.restore();
2228 return child;
2229 }
2230 getChildBoundingBox(ctx, textParent, parent, i2) {
2231 const child = parent.children[i2];
2232 // not a text node?
2233 if (typeof child.getBoundingBox !== 'function') {
2234 return null;
2235 }
2236 const boundingBox = child.getBoundingBox(ctx);
2237 if (boundingBox) {
2238 child.children.forEach((_, i)=>{
2239 const childBoundingBox = textParent.getChildBoundingBox(ctx, textParent, child, i);
2240 boundingBox.addBoundingBox(childBoundingBox);
2241 });
2242 }
2243 return boundingBox;
2244 }
2245 renderChild(ctx, textParent, parent, i3) {
2246 const child = parent.children[i3];
2247 child.render(ctx);
2248 child.children.forEach((_, i)=>{
2249 textParent.renderChild(ctx, textParent, child, i);
2250 });
2251 }
2252 measureText(ctx) {
2253 const { measureCache } = this;
2254 if (~measureCache) {
2255 return measureCache;
2256 }
2257 const renderText = this.getText();
2258 const measure = this.measureTargetText(ctx, renderText);
2259 this.measureCache = measure;
2260 return measure;
2261 }
2262 measureTargetText(ctx, targetText) {
2263 if (!targetText.length) {
2264 return 0;
2265 }
2266 const { parent } = this;
2267 const customFont = parent.getStyle('font-family').getDefinition();
2268 if (customFont) {
2269 const fontSize = this.getFontSize();
2270 const text = customFont.isRTL ? targetText.split('').reverse().join('') : targetText;
2271 const dx = toNumbers(parent.getAttribute('dx').getString());
2272 const len = text.length;
2273 let measure = 0;
2274 for(let i = 0; i < len; i++){
2275 const glyph = this.getGlyph(customFont, text, i);
2276 measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;
2277 if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {
2278 measure += dx[i];
2279 }
2280 }
2281 return measure;
2282 }
2283 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
2284 if (!ctx.measureText) {
2285 return targetText.length * 10;
2286 }
2287 ctx.save();
2288 this.setContext(ctx, true);
2289 const { width: measure } = ctx.measureText(targetText);
2290 this.clearContext(ctx);
2291 ctx.restore();
2292 return measure;
2293 }
2294 /**
2295 * Inherits positional attributes from {@link TextElement} parent(s). Attributes
2296 * are only inherited from a parent to its first child.
2297 * @param name - The attribute name.
2298 * @returns The attribute value or null.
2299 */ getInheritedAttribute(name) {
2300 // eslint-disable-next-line @typescript-eslint/no-this-alias,consistent-this
2301 let current = this;
2302 while(current instanceof TextElement && current.isFirstChild() && current.parent){
2303 const parentAttr = current.parent.getAttribute(name);
2304 if (parentAttr.hasValue(true)) {
2305 return parentAttr.getString('0');
2306 }
2307 current = current.parent;
2308 }
2309 return null;
2310 }
2311 constructor(document, node, captureTextNodes){
2312 super(document, node, new.target === TextElement ? true : captureTextNodes);
2313 this.type = 'text';
2314 this.x = 0;
2315 this.y = 0;
2316 this.leafTexts = [];
2317 this.textChunkStart = 0;
2318 this.minX = Number.POSITIVE_INFINITY;
2319 this.maxX = Number.NEGATIVE_INFINITY;
2320 this.measureCache = -1;
2321 }
2322}
2323
2324class TSpanElement extends TextElement {
2325 getText() {
2326 return this.text;
2327 }
2328 constructor(document, node, captureTextNodes){
2329 super(document, node, new.target === TSpanElement ? true : captureTextNodes);
2330 this.type = 'tspan';
2331 // if this node has children, then they own the text
2332 this.text = this.children.length > 0 ? '' : this.getTextFromNode();
2333 }
2334}
2335
2336class TextNode extends TSpanElement {
2337 constructor(...args){
2338 super(...args);
2339 this.type = 'textNode';
2340 }
2341}
2342
2343class PathParser extends SVGPathData {
2344 reset() {
2345 this.i = -1;
2346 this.command = null;
2347 this.previousCommand = null;
2348 this.start = new Point(0, 0);
2349 this.control = new Point(0, 0);
2350 this.current = new Point(0, 0);
2351 this.points = [];
2352 this.angles = [];
2353 }
2354 isEnd() {
2355 const { i , commands } = this;
2356 return i >= commands.length - 1;
2357 }
2358 next() {
2359 const command = this.commands[++this.i];
2360 this.previousCommand = this.command;
2361 this.command = command;
2362 return command;
2363 }
2364 getPoint() {
2365 let xProp = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 'x', yProp = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 'y';
2366 const point = new Point(this.command[xProp], this.command[yProp]);
2367 return this.makeAbsolute(point);
2368 }
2369 getAsControlPoint(xProp, yProp) {
2370 const point = this.getPoint(xProp, yProp);
2371 this.control = point;
2372 return point;
2373 }
2374 getAsCurrentPoint(xProp, yProp) {
2375 const point = this.getPoint(xProp, yProp);
2376 this.current = point;
2377 return point;
2378 }
2379 getReflectedControlPoint() {
2380 const previousCommand = this.previousCommand.type;
2381 if (previousCommand !== SVGPathData.CURVE_TO && previousCommand !== SVGPathData.SMOOTH_CURVE_TO && previousCommand !== SVGPathData.QUAD_TO && previousCommand !== SVGPathData.SMOOTH_QUAD_TO) {
2382 return this.current;
2383 }
2384 // reflect point
2385 const { current: { x: cx , y: cy } , control: { x: ox , y: oy } } = this;
2386 const point = new Point(2 * cx - ox, 2 * cy - oy);
2387 return point;
2388 }
2389 makeAbsolute(point) {
2390 if (this.command.relative) {
2391 const { x , y } = this.current;
2392 point.x += x;
2393 point.y += y;
2394 }
2395 return point;
2396 }
2397 addMarker(point, from, priorTo) {
2398 const { points , angles } = this;
2399 // if the last angle isn't filled in because we didn't have this point yet ...
2400 if (priorTo && angles.length > 0 && !angles[angles.length - 1]) {
2401 angles[angles.length - 1] = points[points.length - 1].angleTo(priorTo);
2402 }
2403 this.addMarkerAngle(point, from ? from.angleTo(point) : null);
2404 }
2405 addMarkerAngle(point, angle) {
2406 this.points.push(point);
2407 this.angles.push(angle);
2408 }
2409 getMarkerPoints() {
2410 return this.points;
2411 }
2412 getMarkerAngles() {
2413 const { angles } = this;
2414 const len = angles.length;
2415 for(let i = 0; i < len; i++){
2416 if (!angles[i]) {
2417 for(let j = i + 1; j < len; j++){
2418 if (angles[j]) {
2419 angles[i] = angles[j];
2420 break;
2421 }
2422 }
2423 }
2424 }
2425 return angles;
2426 }
2427 constructor(path){
2428 super(path// Fix spaces after signs.
2429 .replace(/([+\-.])\s+/gm, '$1')// Remove invalid part.
2430 .replace(/[^MmZzLlHhVvCcSsQqTtAae\d\s.,+-].*/g, ''));
2431 this.control = new Point(0, 0);
2432 this.start = new Point(0, 0);
2433 this.current = new Point(0, 0);
2434 this.command = null;
2435 this.commands = this.commands;
2436 this.i = -1;
2437 this.previousCommand = null;
2438 this.points = [];
2439 this.angles = [];
2440 }
2441}
2442
2443class PathElement extends RenderedElement {
2444 path(ctx) {
2445 const { pathParser } = this;
2446 const boundingBox = new BoundingBox();
2447 pathParser.reset();
2448 if (ctx) {
2449 ctx.beginPath();
2450 }
2451 while(!pathParser.isEnd()){
2452 switch(pathParser.next().type){
2453 case PathParser.MOVE_TO:
2454 this.pathM(ctx, boundingBox);
2455 break;
2456 case PathParser.LINE_TO:
2457 this.pathL(ctx, boundingBox);
2458 break;
2459 case PathParser.HORIZ_LINE_TO:
2460 this.pathH(ctx, boundingBox);
2461 break;
2462 case PathParser.VERT_LINE_TO:
2463 this.pathV(ctx, boundingBox);
2464 break;
2465 case PathParser.CURVE_TO:
2466 this.pathC(ctx, boundingBox);
2467 break;
2468 case PathParser.SMOOTH_CURVE_TO:
2469 this.pathS(ctx, boundingBox);
2470 break;
2471 case PathParser.QUAD_TO:
2472 this.pathQ(ctx, boundingBox);
2473 break;
2474 case PathParser.SMOOTH_QUAD_TO:
2475 this.pathT(ctx, boundingBox);
2476 break;
2477 case PathParser.ARC:
2478 this.pathA(ctx, boundingBox);
2479 break;
2480 case PathParser.CLOSE_PATH:
2481 this.pathZ(ctx, boundingBox);
2482 break;
2483 }
2484 }
2485 return boundingBox;
2486 }
2487 getBoundingBox(_ctx) {
2488 return this.path();
2489 }
2490 getMarkers() {
2491 const { pathParser } = this;
2492 const points = pathParser.getMarkerPoints();
2493 const angles = pathParser.getMarkerAngles();
2494 const markers = points.map((point, i)=>[
2495 point,
2496 angles[i]
2497 ]
2498 );
2499 return markers;
2500 }
2501 renderChildren(ctx) {
2502 this.path(ctx);
2503 this.document.screen.mouse.checkPath(this, ctx);
2504 const fillRuleStyleProp = this.getStyle('fill-rule');
2505 if (ctx.fillStyle !== '') {
2506 if (fillRuleStyleProp.getString('inherit') !== 'inherit') {
2507 ctx.fill(fillRuleStyleProp.getString());
2508 } else {
2509 ctx.fill();
2510 }
2511 }
2512 if (ctx.strokeStyle !== '') {
2513 if (this.getAttribute('vector-effect').getString() === 'non-scaling-stroke') {
2514 ctx.save();
2515 ctx.setTransform(1, 0, 0, 1, 0, 0);
2516 ctx.stroke();
2517 ctx.restore();
2518 } else {
2519 ctx.stroke();
2520 }
2521 }
2522 const markers = this.getMarkers();
2523 if (markers) {
2524 const markersLastIndex = markers.length - 1;
2525 const markerStartStyleProp = this.getStyle('marker-start');
2526 const markerMidStyleProp = this.getStyle('marker-mid');
2527 const markerEndStyleProp = this.getStyle('marker-end');
2528 if (markerStartStyleProp.isUrlDefinition()) {
2529 const marker = markerStartStyleProp.getDefinition();
2530 const [point, angle] = markers[0];
2531 marker.render(ctx, point, angle);
2532 }
2533 if (markerMidStyleProp.isUrlDefinition()) {
2534 const marker = markerMidStyleProp.getDefinition();
2535 for(let i = 1; i < markersLastIndex; i++){
2536 const [point, angle] = markers[i];
2537 marker.render(ctx, point, angle);
2538 }
2539 }
2540 if (markerEndStyleProp.isUrlDefinition()) {
2541 const marker = markerEndStyleProp.getDefinition();
2542 const [point, angle] = markers[markersLastIndex];
2543 marker.render(ctx, point, angle);
2544 }
2545 }
2546 }
2547 static pathM(pathParser) {
2548 const point = pathParser.getAsCurrentPoint();
2549 pathParser.start = pathParser.current;
2550 return {
2551 point
2552 };
2553 }
2554 pathM(ctx, boundingBox) {
2555 const { pathParser } = this;
2556 const { point } = PathElement.pathM(pathParser);
2557 const { x , y } = point;
2558 pathParser.addMarker(point);
2559 boundingBox.addPoint(x, y);
2560 if (ctx) {
2561 ctx.moveTo(x, y);
2562 }
2563 }
2564 static pathL(pathParser) {
2565 const { current } = pathParser;
2566 const point = pathParser.getAsCurrentPoint();
2567 return {
2568 current,
2569 point
2570 };
2571 }
2572 pathL(ctx, boundingBox) {
2573 const { pathParser } = this;
2574 const { current , point } = PathElement.pathL(pathParser);
2575 const { x , y } = point;
2576 pathParser.addMarker(point, current);
2577 boundingBox.addPoint(x, y);
2578 if (ctx) {
2579 ctx.lineTo(x, y);
2580 }
2581 }
2582 static pathH(pathParser) {
2583 const { current , command } = pathParser;
2584 const point = new Point((command.relative ? current.x : 0) + command.x, current.y);
2585 pathParser.current = point;
2586 return {
2587 current,
2588 point
2589 };
2590 }
2591 pathH(ctx, boundingBox) {
2592 const { pathParser } = this;
2593 const { current , point } = PathElement.pathH(pathParser);
2594 const { x , y } = point;
2595 pathParser.addMarker(point, current);
2596 boundingBox.addPoint(x, y);
2597 if (ctx) {
2598 ctx.lineTo(x, y);
2599 }
2600 }
2601 static pathV(pathParser) {
2602 const { current , command } = pathParser;
2603 const point = new Point(current.x, (command.relative ? current.y : 0) + command.y);
2604 pathParser.current = point;
2605 return {
2606 current,
2607 point
2608 };
2609 }
2610 pathV(ctx, boundingBox) {
2611 const { pathParser } = this;
2612 const { current , point } = PathElement.pathV(pathParser);
2613 const { x , y } = point;
2614 pathParser.addMarker(point, current);
2615 boundingBox.addPoint(x, y);
2616 if (ctx) {
2617 ctx.lineTo(x, y);
2618 }
2619 }
2620 static pathC(pathParser) {
2621 const { current } = pathParser;
2622 const point = pathParser.getPoint('x1', 'y1');
2623 const controlPoint = pathParser.getAsControlPoint('x2', 'y2');
2624 const currentPoint = pathParser.getAsCurrentPoint();
2625 return {
2626 current,
2627 point,
2628 controlPoint,
2629 currentPoint
2630 };
2631 }
2632 pathC(ctx, boundingBox) {
2633 const { pathParser } = this;
2634 const { current , point , controlPoint , currentPoint } = PathElement.pathC(pathParser);
2635 pathParser.addMarker(currentPoint, controlPoint, point);
2636 boundingBox.addBezierCurve(current.x, current.y, point.x, point.y, controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
2637 if (ctx) {
2638 ctx.bezierCurveTo(point.x, point.y, controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
2639 }
2640 }
2641 static pathS(pathParser) {
2642 const { current } = pathParser;
2643 const point = pathParser.getReflectedControlPoint();
2644 const controlPoint = pathParser.getAsControlPoint('x2', 'y2');
2645 const currentPoint = pathParser.getAsCurrentPoint();
2646 return {
2647 current,
2648 point,
2649 controlPoint,
2650 currentPoint
2651 };
2652 }
2653 pathS(ctx, boundingBox) {
2654 const { pathParser } = this;
2655 const { current , point , controlPoint , currentPoint } = PathElement.pathS(pathParser);
2656 pathParser.addMarker(currentPoint, controlPoint, point);
2657 boundingBox.addBezierCurve(current.x, current.y, point.x, point.y, controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
2658 if (ctx) {
2659 ctx.bezierCurveTo(point.x, point.y, controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
2660 }
2661 }
2662 static pathQ(pathParser) {
2663 const { current } = pathParser;
2664 const controlPoint = pathParser.getAsControlPoint('x1', 'y1');
2665 const currentPoint = pathParser.getAsCurrentPoint();
2666 return {
2667 current,
2668 controlPoint,
2669 currentPoint
2670 };
2671 }
2672 pathQ(ctx, boundingBox) {
2673 const { pathParser } = this;
2674 const { current , controlPoint , currentPoint } = PathElement.pathQ(pathParser);
2675 pathParser.addMarker(currentPoint, controlPoint, controlPoint);
2676 boundingBox.addQuadraticCurve(current.x, current.y, controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
2677 if (ctx) {
2678 ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
2679 }
2680 }
2681 static pathT(pathParser) {
2682 const { current } = pathParser;
2683 const controlPoint = pathParser.getReflectedControlPoint();
2684 pathParser.control = controlPoint;
2685 const currentPoint = pathParser.getAsCurrentPoint();
2686 return {
2687 current,
2688 controlPoint,
2689 currentPoint
2690 };
2691 }
2692 pathT(ctx, boundingBox) {
2693 const { pathParser } = this;
2694 const { current , controlPoint , currentPoint } = PathElement.pathT(pathParser);
2695 pathParser.addMarker(currentPoint, controlPoint, controlPoint);
2696 boundingBox.addQuadraticCurve(current.x, current.y, controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
2697 if (ctx) {
2698 ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
2699 }
2700 }
2701 static pathA(pathParser) {
2702 const { current , command } = pathParser;
2703 let { rX , rY , xRot , lArcFlag , sweepFlag } = command;
2704 const xAxisRotation = xRot * (Math.PI / 180);
2705 const currentPoint = pathParser.getAsCurrentPoint();
2706 // Conversion from endpoint to center parameterization
2707 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
2708 // x1', y1'
2709 const currp = new Point(Math.cos(xAxisRotation) * (current.x - currentPoint.x) / 2 + Math.sin(xAxisRotation) * (current.y - currentPoint.y) / 2, -Math.sin(xAxisRotation) * (current.x - currentPoint.x) / 2 + Math.cos(xAxisRotation) * (current.y - currentPoint.y) / 2);
2710 // adjust radii
2711 const l = Math.pow(currp.x, 2) / Math.pow(rX, 2) + Math.pow(currp.y, 2) / Math.pow(rY, 2);
2712 if (l > 1) {
2713 rX *= Math.sqrt(l);
2714 rY *= Math.sqrt(l);
2715 }
2716 // cx', cy'
2717 let s = (lArcFlag === sweepFlag ? -1 : 1) * Math.sqrt((Math.pow(rX, 2) * Math.pow(rY, 2) - Math.pow(rX, 2) * Math.pow(currp.y, 2) - Math.pow(rY, 2) * Math.pow(currp.x, 2)) / (Math.pow(rX, 2) * Math.pow(currp.y, 2) + Math.pow(rY, 2) * Math.pow(currp.x, 2)));
2718 if (isNaN(s)) {
2719 s = 0;
2720 }
2721 const cpp = new Point(s * rX * currp.y / rY, s * -rY * currp.x / rX);
2722 // cx, cy
2723 const centp = new Point((current.x + currentPoint.x) / 2 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y, (current.y + currentPoint.y) / 2 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y);
2724 // initial angle
2725 const a1 = vectorsAngle([
2726 1,
2727 0
2728 ], [
2729 (currp.x - cpp.x) / rX,
2730 (currp.y - cpp.y) / rY
2731 ]) // θ1
2732 ;
2733 // angle delta
2734 const u = [
2735 (currp.x - cpp.x) / rX,
2736 (currp.y - cpp.y) / rY
2737 ];
2738 const v = [
2739 (-currp.x - cpp.x) / rX,
2740 (-currp.y - cpp.y) / rY
2741 ];
2742 let ad = vectorsAngle(u, v) // Δθ
2743 ;
2744 if (vectorsRatio(u, v) <= -1) {
2745 ad = Math.PI;
2746 }
2747 if (vectorsRatio(u, v) >= 1) {
2748 ad = 0;
2749 }
2750 return {
2751 currentPoint,
2752 rX,
2753 rY,
2754 sweepFlag,
2755 xAxisRotation,
2756 centp,
2757 a1,
2758 ad
2759 };
2760 }
2761 pathA(ctx, boundingBox) {
2762 const { pathParser } = this;
2763 const { currentPoint , rX , rY , sweepFlag , xAxisRotation , centp , a1 , ad } = PathElement.pathA(pathParser);
2764 // for markers
2765 const dir = 1 - sweepFlag ? 1 : -1;
2766 const ah = a1 + dir * (ad / 2);
2767 const halfWay = new Point(centp.x + rX * Math.cos(ah), centp.y + rY * Math.sin(ah));
2768 pathParser.addMarkerAngle(halfWay, ah - dir * Math.PI / 2);
2769 pathParser.addMarkerAngle(currentPoint, ah - dir * Math.PI);
2770 boundingBox.addPoint(currentPoint.x, currentPoint.y) // TODO: this is too naive, make it better
2771 ;
2772 if (ctx && !isNaN(a1) && !isNaN(ad)) {
2773 const r = rX > rY ? rX : rY;
2774 const sx = rX > rY ? 1 : rX / rY;
2775 const sy = rX > rY ? rY / rX : 1;
2776 ctx.translate(centp.x, centp.y);
2777 ctx.rotate(xAxisRotation);
2778 ctx.scale(sx, sy);
2779 ctx.arc(0, 0, r, a1, a1 + ad, Boolean(1 - sweepFlag));
2780 ctx.scale(1 / sx, 1 / sy);
2781 ctx.rotate(-xAxisRotation);
2782 ctx.translate(-centp.x, -centp.y);
2783 }
2784 }
2785 static pathZ(pathParser) {
2786 pathParser.current = pathParser.start;
2787 }
2788 pathZ(ctx, boundingBox) {
2789 PathElement.pathZ(this.pathParser);
2790 if (ctx) {
2791 // only close path if it is not a straight line
2792 if (boundingBox.x1 !== boundingBox.x2 && boundingBox.y1 !== boundingBox.y2) {
2793 ctx.closePath();
2794 }
2795 }
2796 }
2797 constructor(document, node, captureTextNodes){
2798 super(document, node, captureTextNodes);
2799 this.type = 'path';
2800 this.pathParser = new PathParser(this.getAttribute('d').getString());
2801 }
2802}
2803
2804class SVGElement extends RenderedElement {
2805 setContext(ctx) {
2806 var ref;
2807 const { document } = this;
2808 const { screen , window } = document;
2809 const canvas = ctx.canvas;
2810 screen.setDefaults(ctx);
2811 if ('style' in canvas && typeof ctx.font !== 'undefined' && window && typeof window.getComputedStyle !== 'undefined') {
2812 ctx.font = window.getComputedStyle(canvas).getPropertyValue('font');
2813 const fontSizeProp = new Property(document, 'fontSize', Font.parse(ctx.font).fontSize);
2814 if (fontSizeProp.hasValue()) {
2815 document.rootEmSize = fontSizeProp.getPixels('y');
2816 document.emSize = document.rootEmSize;
2817 }
2818 }
2819 // create new view port
2820 if (!this.getAttribute('x').hasValue()) {
2821 this.getAttribute('x', true).setValue(0);
2822 }
2823 if (!this.getAttribute('y').hasValue()) {
2824 this.getAttribute('y', true).setValue(0);
2825 }
2826 let { width , height } = screen.viewPort;
2827 if (!this.getStyle('width').hasValue()) {
2828 this.getStyle('width', true).setValue('100%');
2829 }
2830 if (!this.getStyle('height').hasValue()) {
2831 this.getStyle('height', true).setValue('100%');
2832 }
2833 if (!this.getStyle('color').hasValue()) {
2834 this.getStyle('color', true).setValue('black');
2835 }
2836 const refXAttr = this.getAttribute('refX');
2837 const refYAttr = this.getAttribute('refY');
2838 const viewBoxAttr = this.getAttribute('viewBox');
2839 const viewBox = viewBoxAttr.hasValue() ? toNumbers(viewBoxAttr.getString()) : null;
2840 const clip = !this.root && this.getStyle('overflow').getValue('hidden') !== 'visible';
2841 let minX = 0;
2842 let minY = 0;
2843 let clipX = 0;
2844 let clipY = 0;
2845 if (viewBox) {
2846 minX = viewBox[0];
2847 minY = viewBox[1];
2848 }
2849 if (!this.root) {
2850 width = this.getStyle('width').getPixels('x');
2851 height = this.getStyle('height').getPixels('y');
2852 if (this.type === 'marker') {
2853 clipX = minX;
2854 clipY = minY;
2855 minX = 0;
2856 minY = 0;
2857 }
2858 }
2859 screen.viewPort.setCurrent(width, height);
2860 // Default value of transform-origin is center only for root SVG elements
2861 // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform-origin
2862 if (this.node // is not temporary SVGElement
2863 && (!this.parent || ((ref = this.node.parentNode) === null || ref === void 0 ? void 0 : ref.nodeName) === 'foreignObject') && this.getStyle('transform', false, true).hasValue() && !this.getStyle('transform-origin', false, true).hasValue()) {
2864 this.getStyle('transform-origin', true, true).setValue('50% 50%');
2865 }
2866 super.setContext(ctx);
2867 ctx.translate(this.getAttribute('x').getPixels('x'), this.getAttribute('y').getPixels('y'));
2868 if (viewBox) {
2869 width = viewBox[2];
2870 height = viewBox[3];
2871 }
2872 document.setViewBox({
2873 ctx,
2874 aspectRatio: this.getAttribute('preserveAspectRatio').getString(),
2875 width: screen.viewPort.width,
2876 desiredWidth: width,
2877 height: screen.viewPort.height,
2878 desiredHeight: height,
2879 minX,
2880 minY,
2881 refX: refXAttr.getValue(),
2882 refY: refYAttr.getValue(),
2883 clip,
2884 clipX,
2885 clipY
2886 });
2887 if (viewBox) {
2888 screen.viewPort.removeCurrent();
2889 screen.viewPort.setCurrent(width, height);
2890 }
2891 }
2892 clearContext(ctx) {
2893 super.clearContext(ctx);
2894 this.document.screen.viewPort.removeCurrent();
2895 }
2896 /**
2897 * Resize SVG to fit in given size.
2898 * @param width
2899 * @param height
2900 * @param preserveAspectRatio
2901 */ resize(width) {
2902 let height = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : width, preserveAspectRatio = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false;
2903 const widthAttr = this.getAttribute('width', true);
2904 const heightAttr = this.getAttribute('height', true);
2905 const viewBoxAttr = this.getAttribute('viewBox');
2906 const styleAttr = this.getAttribute('style');
2907 const originWidth = widthAttr.getNumber(0);
2908 const originHeight = heightAttr.getNumber(0);
2909 if (preserveAspectRatio) {
2910 if (typeof preserveAspectRatio === 'string') {
2911 this.getAttribute('preserveAspectRatio', true).setValue(preserveAspectRatio);
2912 } else {
2913 const preserveAspectRatioAttr = this.getAttribute('preserveAspectRatio');
2914 if (preserveAspectRatioAttr.hasValue()) {
2915 preserveAspectRatioAttr.setValue(preserveAspectRatioAttr.getString().replace(/^\s*(\S.*\S)\s*$/, '$1'));
2916 }
2917 }
2918 }
2919 widthAttr.setValue(width);
2920 heightAttr.setValue(height);
2921 if (!viewBoxAttr.hasValue()) {
2922 viewBoxAttr.setValue("0 0 ".concat(originWidth || width, " ").concat(originHeight || height));
2923 }
2924 if (styleAttr.hasValue()) {
2925 const widthStyle = this.getStyle('width');
2926 const heightStyle = this.getStyle('height');
2927 if (widthStyle.hasValue()) {
2928 widthStyle.setValue("".concat(width, "px"));
2929 }
2930 if (heightStyle.hasValue()) {
2931 heightStyle.setValue("".concat(height, "px"));
2932 }
2933 }
2934 }
2935 constructor(...args){
2936 super(...args);
2937 this.type = 'svg';
2938 this.root = false;
2939 }
2940}
2941
2942class RectElement extends PathElement {
2943 path(ctx) {
2944 const x = this.getAttribute('x').getPixels('x');
2945 const y = this.getAttribute('y').getPixels('y');
2946 const width = this.getStyle('width', false, true).getPixels('x');
2947 const height = this.getStyle('height', false, true).getPixels('y');
2948 const rxAttr = this.getAttribute('rx');
2949 const ryAttr = this.getAttribute('ry');
2950 let rx = rxAttr.getPixels('x');
2951 let ry = ryAttr.getPixels('y');
2952 if (rxAttr.hasValue() && !ryAttr.hasValue()) {
2953 ry = rx;
2954 }
2955 if (ryAttr.hasValue() && !rxAttr.hasValue()) {
2956 rx = ry;
2957 }
2958 rx = Math.min(rx, width / 2);
2959 ry = Math.min(ry, height / 2);
2960 if (ctx) {
2961 const KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
2962 ctx.beginPath() // always start the path so we don't fill prior paths
2963 ;
2964 if (height > 0 && width > 0) {
2965 ctx.moveTo(x + rx, y);
2966 ctx.lineTo(x + width - rx, y);
2967 ctx.bezierCurveTo(x + width - rx + KAPPA * rx, y, x + width, y + ry - KAPPA * ry, x + width, y + ry);
2968 ctx.lineTo(x + width, y + height - ry);
2969 ctx.bezierCurveTo(x + width, y + height - ry + KAPPA * ry, x + width - rx + KAPPA * rx, y + height, x + width - rx, y + height);
2970 ctx.lineTo(x + rx, y + height);
2971 ctx.bezierCurveTo(x + rx - KAPPA * rx, y + height, x, y + height - ry + KAPPA * ry, x, y + height - ry);
2972 ctx.lineTo(x, y + ry);
2973 ctx.bezierCurveTo(x, y + ry - KAPPA * ry, x + rx - KAPPA * rx, y, x + rx, y);
2974 ctx.closePath();
2975 }
2976 }
2977 return new BoundingBox(x, y, x + width, y + height);
2978 }
2979 getMarkers() {
2980 return null;
2981 }
2982 constructor(...args){
2983 super(...args);
2984 this.type = 'rect';
2985 }
2986}
2987
2988class CircleElement extends PathElement {
2989 path(ctx) {
2990 const cx = this.getAttribute('cx').getPixels('x');
2991 const cy = this.getAttribute('cy').getPixels('y');
2992 const r = this.getAttribute('r').getPixels();
2993 if (ctx && r > 0) {
2994 ctx.beginPath();
2995 ctx.arc(cx, cy, r, 0, Math.PI * 2, false);
2996 ctx.closePath();
2997 }
2998 return new BoundingBox(cx - r, cy - r, cx + r, cy + r);
2999 }
3000 getMarkers() {
3001 return null;
3002 }
3003 constructor(...args){
3004 super(...args);
3005 this.type = 'circle';
3006 }
3007}
3008
3009class EllipseElement extends PathElement {
3010 path(ctx) {
3011 const KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
3012 const rx = this.getAttribute('rx').getPixels('x');
3013 const ry = this.getAttribute('ry').getPixels('y');
3014 const cx = this.getAttribute('cx').getPixels('x');
3015 const cy = this.getAttribute('cy').getPixels('y');
3016 if (ctx && rx > 0 && ry > 0) {
3017 ctx.beginPath();
3018 ctx.moveTo(cx + rx, cy);
3019 ctx.bezierCurveTo(cx + rx, cy + KAPPA * ry, cx + KAPPA * rx, cy + ry, cx, cy + ry);
3020 ctx.bezierCurveTo(cx - KAPPA * rx, cy + ry, cx - rx, cy + KAPPA * ry, cx - rx, cy);
3021 ctx.bezierCurveTo(cx - rx, cy - KAPPA * ry, cx - KAPPA * rx, cy - ry, cx, cy - ry);
3022 ctx.bezierCurveTo(cx + KAPPA * rx, cy - ry, cx + rx, cy - KAPPA * ry, cx + rx, cy);
3023 ctx.closePath();
3024 }
3025 return new BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
3026 }
3027 getMarkers() {
3028 return null;
3029 }
3030 constructor(...args){
3031 super(...args);
3032 this.type = 'ellipse';
3033 }
3034}
3035
3036class LineElement extends PathElement {
3037 getPoints() {
3038 return [
3039 new Point(this.getAttribute('x1').getPixels('x'), this.getAttribute('y1').getPixels('y')),
3040 new Point(this.getAttribute('x2').getPixels('x'), this.getAttribute('y2').getPixels('y'))
3041 ];
3042 }
3043 path(ctx) {
3044 const [{ x: x0 , y: y0 }, { x: x1 , y: y1 }] = this.getPoints();
3045 if (ctx) {
3046 ctx.beginPath();
3047 ctx.moveTo(x0, y0);
3048 ctx.lineTo(x1, y1);
3049 }
3050 return new BoundingBox(x0, y0, x1, y1);
3051 }
3052 getMarkers() {
3053 const [p0, p1] = this.getPoints();
3054 const a = p0.angleTo(p1);
3055 return [
3056 [
3057 p0,
3058 a
3059 ],
3060 [
3061 p1,
3062 a
3063 ]
3064 ];
3065 }
3066 constructor(...args){
3067 super(...args);
3068 this.type = 'line';
3069 }
3070}
3071
3072class PolylineElement extends PathElement {
3073 path(ctx) {
3074 const { points } = this;
3075 const [{ x: x0 , y: y0 }] = points;
3076 const boundingBox = new BoundingBox(x0, y0);
3077 if (ctx) {
3078 ctx.beginPath();
3079 ctx.moveTo(x0, y0);
3080 }
3081 points.forEach((param)=>{
3082 let { x , y } = param;
3083 boundingBox.addPoint(x, y);
3084 if (ctx) {
3085 ctx.lineTo(x, y);
3086 }
3087 });
3088 return boundingBox;
3089 }
3090 getMarkers() {
3091 const { points } = this;
3092 const lastIndex = points.length - 1;
3093 const markers = [];
3094 points.forEach((point, i)=>{
3095 if (i === lastIndex) {
3096 return;
3097 }
3098 markers.push([
3099 point,
3100 point.angleTo(points[i + 1])
3101 ]);
3102 });
3103 if (markers.length > 0) {
3104 markers.push([
3105 points[points.length - 1],
3106 markers[markers.length - 1][1]
3107 ]);
3108 }
3109 return markers;
3110 }
3111 constructor(document, node, captureTextNodes){
3112 super(document, node, captureTextNodes);
3113 this.type = 'polyline';
3114 this.points = [];
3115 this.points = Point.parsePath(this.getAttribute('points').getString());
3116 }
3117}
3118
3119class PolygonElement extends PolylineElement {
3120 path(ctx) {
3121 const boundingBox = super.path(ctx);
3122 const [{ x , y }] = this.points;
3123 if (ctx) {
3124 ctx.lineTo(x, y);
3125 ctx.closePath();
3126 }
3127 return boundingBox;
3128 }
3129 constructor(...args){
3130 super(...args);
3131 this.type = 'polygon';
3132 }
3133}
3134
3135class PatternElement extends Element {
3136 createPattern(ctx, _, parentOpacityProp) {
3137 const width = this.getStyle('width').getPixels('x', true);
3138 const height = this.getStyle('height').getPixels('y', true);
3139 // render me using a temporary svg element
3140 const patternSvg = new SVGElement(this.document, null);
3141 patternSvg.attributes.viewBox = new Property(this.document, 'viewBox', this.getAttribute('viewBox').getValue());
3142 patternSvg.attributes.width = new Property(this.document, 'width', "".concat(width, "px"));
3143 patternSvg.attributes.height = new Property(this.document, 'height', "".concat(height, "px"));
3144 patternSvg.attributes.transform = new Property(this.document, 'transform', this.getAttribute('patternTransform').getValue());
3145 patternSvg.children = this.children;
3146 const patternCanvas = this.document.createCanvas(width, height);
3147 const patternCtx = patternCanvas.getContext('2d');
3148 const xAttr = this.getAttribute('x');
3149 const yAttr = this.getAttribute('y');
3150 if (xAttr.hasValue() && yAttr.hasValue()) {
3151 patternCtx.translate(xAttr.getPixels('x', true), yAttr.getPixels('y', true));
3152 }
3153 if (parentOpacityProp.hasValue()) {
3154 this.styles['fill-opacity'] = parentOpacityProp;
3155 } else {
3156 Reflect.deleteProperty(this.styles, 'fill-opacity');
3157 }
3158 // render 3x3 grid so when we transform there's no white space on edges
3159 for(let x = -1; x <= 1; x++){
3160 for(let y = -1; y <= 1; y++){
3161 patternCtx.save();
3162 patternSvg.attributes.x = new Property(this.document, 'x', x * patternCanvas.width);
3163 patternSvg.attributes.y = new Property(this.document, 'y', y * patternCanvas.height);
3164 patternSvg.render(patternCtx);
3165 patternCtx.restore();
3166 }
3167 }
3168 const pattern = ctx.createPattern(patternCanvas, 'repeat');
3169 return pattern;
3170 }
3171 constructor(...args){
3172 super(...args);
3173 this.type = 'pattern';
3174 }
3175}
3176
3177class MarkerElement extends Element {
3178 render(ctx, point, angle) {
3179 if (!point) {
3180 return;
3181 }
3182 const { x , y } = point;
3183 const orient = this.getAttribute('orient').getString('auto');
3184 const markerUnits = this.getAttribute('markerUnits').getString('strokeWidth');
3185 ctx.translate(x, y);
3186 if (orient === 'auto') {
3187 ctx.rotate(angle);
3188 }
3189 if (markerUnits === 'strokeWidth') {
3190 ctx.scale(ctx.lineWidth, ctx.lineWidth);
3191 }
3192 ctx.save();
3193 // render me using a temporary svg element
3194 const markerSvg = new SVGElement(this.document);
3195 markerSvg.type = this.type;
3196 markerSvg.attributes.viewBox = new Property(this.document, 'viewBox', this.getAttribute('viewBox').getValue());
3197 markerSvg.attributes.refX = new Property(this.document, 'refX', this.getAttribute('refX').getValue());
3198 markerSvg.attributes.refY = new Property(this.document, 'refY', this.getAttribute('refY').getValue());
3199 markerSvg.attributes.width = new Property(this.document, 'width', this.getAttribute('markerWidth').getValue());
3200 markerSvg.attributes.height = new Property(this.document, 'height', this.getAttribute('markerHeight').getValue());
3201 markerSvg.attributes.overflow = new Property(this.document, 'overflow', this.getAttribute('overflow').getValue());
3202 markerSvg.attributes.fill = new Property(this.document, 'fill', this.getAttribute('fill').getColor('black'));
3203 markerSvg.attributes.stroke = new Property(this.document, 'stroke', this.getAttribute('stroke').getValue('none'));
3204 markerSvg.children = this.children;
3205 markerSvg.render(ctx);
3206 ctx.restore();
3207 if (markerUnits === 'strokeWidth') {
3208 ctx.scale(1 / ctx.lineWidth, 1 / ctx.lineWidth);
3209 }
3210 if (orient === 'auto') {
3211 ctx.rotate(-angle);
3212 }
3213 ctx.translate(-x, -y);
3214 }
3215 constructor(...args){
3216 super(...args);
3217 this.type = 'marker';
3218 }
3219}
3220
3221class DefsElement extends Element {
3222 render() {
3223 // NOOP
3224 }
3225 constructor(...args){
3226 super(...args);
3227 this.type = 'defs';
3228 }
3229}
3230
3231class GElement extends RenderedElement {
3232 getBoundingBox(ctx) {
3233 const boundingBox = new BoundingBox();
3234 this.children.forEach((child)=>{
3235 boundingBox.addBoundingBox(child.getBoundingBox(ctx));
3236 });
3237 return boundingBox;
3238 }
3239 constructor(...args){
3240 super(...args);
3241 this.type = 'g';
3242 }
3243}
3244
3245class GradientElement extends Element {
3246 getGradientUnits() {
3247 return this.getAttribute('gradientUnits').getString('objectBoundingBox');
3248 }
3249 createGradient(ctx, element, parentOpacityProp) {
3250 // eslint-disable-next-line @typescript-eslint/no-this-alias, consistent-this
3251 let stopsContainer = this;
3252 if (this.getHrefAttribute().hasValue()) {
3253 stopsContainer = this.getHrefAttribute().getDefinition();
3254 this.inheritStopContainer(stopsContainer);
3255 }
3256 const { stops } = stopsContainer;
3257 const gradient = this.getGradient(ctx, element);
3258 if (!gradient) {
3259 return this.addParentOpacity(parentOpacityProp, stops[stops.length - 1].color);
3260 }
3261 stops.forEach((stop)=>{
3262 gradient.addColorStop(stop.offset, this.addParentOpacity(parentOpacityProp, stop.color));
3263 });
3264 if (this.getAttribute('gradientTransform').hasValue()) {
3265 // render as transformed pattern on temporary canvas
3266 const { document } = this;
3267 const { MAX_VIRTUAL_PIXELS } = Screen;
3268 const { viewPort } = document.screen;
3269 const rootView = viewPort.getRoot();
3270 const rect = new RectElement(document);
3271 rect.attributes.x = new Property(document, 'x', -MAX_VIRTUAL_PIXELS / 3);
3272 rect.attributes.y = new Property(document, 'y', -MAX_VIRTUAL_PIXELS / 3);
3273 rect.attributes.width = new Property(document, 'width', MAX_VIRTUAL_PIXELS);
3274 rect.attributes.height = new Property(document, 'height', MAX_VIRTUAL_PIXELS);
3275 const group = new GElement(document);
3276 group.attributes.transform = new Property(document, 'transform', this.getAttribute('gradientTransform').getValue());
3277 group.children = [
3278 rect
3279 ];
3280 const patternSvg = new SVGElement(document);
3281 patternSvg.attributes.x = new Property(document, 'x', 0);
3282 patternSvg.attributes.y = new Property(document, 'y', 0);
3283 patternSvg.attributes.width = new Property(document, 'width', rootView.width);
3284 patternSvg.attributes.height = new Property(document, 'height', rootView.height);
3285 patternSvg.children = [
3286 group
3287 ];
3288 const patternCanvas = document.createCanvas(rootView.width, rootView.height);
3289 const patternCtx = patternCanvas.getContext('2d');
3290 patternCtx.fillStyle = gradient;
3291 patternSvg.render(patternCtx);
3292 return patternCtx.createPattern(patternCanvas, 'no-repeat');
3293 }
3294 return gradient;
3295 }
3296 inheritStopContainer(stopsContainer) {
3297 this.attributesToInherit.forEach((attributeToInherit)=>{
3298 if (!this.getAttribute(attributeToInherit).hasValue() && stopsContainer.getAttribute(attributeToInherit).hasValue()) {
3299 this.getAttribute(attributeToInherit, true).setValue(stopsContainer.getAttribute(attributeToInherit).getValue());
3300 }
3301 });
3302 }
3303 addParentOpacity(parentOpacityProp, color) {
3304 if (parentOpacityProp.hasValue()) {
3305 const colorProp = new Property(this.document, 'color', color);
3306 return colorProp.addOpacity(parentOpacityProp).getColor();
3307 }
3308 return color;
3309 }
3310 constructor(document, node, captureTextNodes){
3311 super(document, node, captureTextNodes);
3312 this.attributesToInherit = [
3313 'gradientUnits'
3314 ];
3315 this.stops = [];
3316 const { stops , children } = this;
3317 children.forEach((child)=>{
3318 if (child.type === 'stop') {
3319 stops.push(child);
3320 }
3321 });
3322 }
3323}
3324
3325class LinearGradientElement extends GradientElement {
3326 getGradient(ctx, element) {
3327 const isBoundingBoxUnits = this.getGradientUnits() === 'objectBoundingBox';
3328 const boundingBox = isBoundingBoxUnits ? element.getBoundingBox(ctx) : null;
3329 if (isBoundingBoxUnits && !boundingBox) {
3330 return null;
3331 }
3332 if (!this.getAttribute('x1').hasValue() && !this.getAttribute('y1').hasValue() && !this.getAttribute('x2').hasValue() && !this.getAttribute('y2').hasValue()) {
3333 this.getAttribute('x1', true).setValue(0);
3334 this.getAttribute('y1', true).setValue(0);
3335 this.getAttribute('x2', true).setValue(1);
3336 this.getAttribute('y2', true).setValue(0);
3337 }
3338 const x1 = isBoundingBoxUnits ? boundingBox.x + boundingBox.width * this.getAttribute('x1').getNumber() : this.getAttribute('x1').getPixels('x');
3339 const y1 = isBoundingBoxUnits ? boundingBox.y + boundingBox.height * this.getAttribute('y1').getNumber() : this.getAttribute('y1').getPixels('y');
3340 const x2 = isBoundingBoxUnits ? boundingBox.x + boundingBox.width * this.getAttribute('x2').getNumber() : this.getAttribute('x2').getPixels('x');
3341 const y2 = isBoundingBoxUnits ? boundingBox.y + boundingBox.height * this.getAttribute('y2').getNumber() : this.getAttribute('y2').getPixels('y');
3342 if (x1 === x2 && y1 === y2) {
3343 return null;
3344 }
3345 return ctx.createLinearGradient(x1, y1, x2, y2);
3346 }
3347 constructor(document, node, captureTextNodes){
3348 super(document, node, captureTextNodes);
3349 this.type = 'linearGradient';
3350 this.attributesToInherit.push('x1', 'y1', 'x2', 'y2');
3351 }
3352}
3353
3354class RadialGradientElement extends GradientElement {
3355 getGradient(ctx, element) {
3356 const isBoundingBoxUnits = this.getGradientUnits() === 'objectBoundingBox';
3357 const boundingBox = element.getBoundingBox(ctx);
3358 if (isBoundingBoxUnits && !boundingBox) {
3359 return null;
3360 }
3361 if (!this.getAttribute('cx').hasValue()) {
3362 this.getAttribute('cx', true).setValue('50%');
3363 }
3364 if (!this.getAttribute('cy').hasValue()) {
3365 this.getAttribute('cy', true).setValue('50%');
3366 }
3367 if (!this.getAttribute('r').hasValue()) {
3368 this.getAttribute('r', true).setValue('50%');
3369 }
3370 const cx = isBoundingBoxUnits ? boundingBox.x + boundingBox.width * this.getAttribute('cx').getNumber() : this.getAttribute('cx').getPixels('x');
3371 const cy = isBoundingBoxUnits ? boundingBox.y + boundingBox.height * this.getAttribute('cy').getNumber() : this.getAttribute('cy').getPixels('y');
3372 let fx = cx;
3373 let fy = cy;
3374 if (this.getAttribute('fx').hasValue()) {
3375 fx = isBoundingBoxUnits ? boundingBox.x + boundingBox.width * this.getAttribute('fx').getNumber() : this.getAttribute('fx').getPixels('x');
3376 }
3377 if (this.getAttribute('fy').hasValue()) {
3378 fy = isBoundingBoxUnits ? boundingBox.y + boundingBox.height * this.getAttribute('fy').getNumber() : this.getAttribute('fy').getPixels('y');
3379 }
3380 const r = isBoundingBoxUnits ? (boundingBox.width + boundingBox.height) / 2 * this.getAttribute('r').getNumber() : this.getAttribute('r').getPixels();
3381 const fr = this.getAttribute('fr').getPixels();
3382 return ctx.createRadialGradient(fx, fy, fr, cx, cy, r);
3383 }
3384 constructor(document, node, captureTextNodes){
3385 super(document, node, captureTextNodes);
3386 this.type = 'radialGradient';
3387 this.attributesToInherit.push('cx', 'cy', 'r', 'fx', 'fy', 'fr');
3388 }
3389}
3390
3391class StopElement extends Element {
3392 constructor(document, node, captureTextNodes){
3393 super(document, node, captureTextNodes);
3394 this.type = 'stop';
3395 const offset = Math.max(0, Math.min(1, this.getAttribute('offset').getNumber()));
3396 const stopOpacity = this.getStyle('stop-opacity');
3397 let stopColor = this.getStyle('stop-color', true);
3398 if (stopColor.getString() === '') {
3399 stopColor.setValue('#000');
3400 }
3401 if (stopOpacity.hasValue()) {
3402 stopColor = stopColor.addOpacity(stopOpacity);
3403 }
3404 this.offset = offset;
3405 this.color = stopColor.getColor();
3406 }
3407}
3408
3409class AnimateElement extends Element {
3410 getProperty() {
3411 const attributeType = this.getAttribute('attributeType').getString();
3412 const attributeName = this.getAttribute('attributeName').getString();
3413 if (attributeType === 'CSS') {
3414 return this.parent.getStyle(attributeName, true);
3415 }
3416 return this.parent.getAttribute(attributeName, true);
3417 }
3418 calcValue() {
3419 const { initialUnits } = this;
3420 const { progress , from , to } = this.getProgress();
3421 // tween value linearly
3422 let newValue = from.getNumber() + (to.getNumber() - from.getNumber()) * progress;
3423 if (initialUnits === '%') {
3424 newValue *= 100 // numValue() returns 0-1 whereas properties are 0-100
3425 ;
3426 }
3427 return "".concat(newValue).concat(initialUnits);
3428 }
3429 update(delta) {
3430 const { parent } = this;
3431 const prop = this.getProperty();
3432 // set initial value
3433 if (!this.initialValue) {
3434 this.initialValue = prop.getString();
3435 this.initialUnits = prop.getUnits();
3436 }
3437 // if we're past the end time
3438 if (this.duration > this.maxDuration) {
3439 const fill = this.getAttribute('fill').getString('remove');
3440 // loop for indefinitely repeating animations
3441 if (this.getAttribute('repeatCount').getString() === 'indefinite' || this.getAttribute('repeatDur').getString() === 'indefinite') {
3442 this.duration = 0;
3443 } else if (fill === 'freeze' && !this.frozen) {
3444 this.frozen = true;
3445 if (parent && prop) {
3446 parent.animationFrozen = true;
3447 parent.animationFrozenValue = prop.getString();
3448 }
3449 } else if (fill === 'remove' && !this.removed) {
3450 this.removed = true;
3451 if (parent && prop) {
3452 prop.setValue(parent.animationFrozen ? parent.animationFrozenValue : this.initialValue);
3453 }
3454 return true;
3455 }
3456 return false;
3457 }
3458 this.duration += delta;
3459 // if we're past the begin time
3460 let updated = false;
3461 if (this.begin < this.duration) {
3462 let newValue = this.calcValue() // tween
3463 ;
3464 const typeAttr = this.getAttribute('type');
3465 if (typeAttr.hasValue()) {
3466 // for transform, etc.
3467 const type = typeAttr.getString();
3468 newValue = "".concat(type, "(").concat(newValue, ")");
3469 }
3470 prop.setValue(newValue);
3471 updated = true;
3472 }
3473 return updated;
3474 }
3475 getProgress() {
3476 const { document , values } = this;
3477 let progress = (this.duration - this.begin) / (this.maxDuration - this.begin);
3478 let from;
3479 let to;
3480 if (values.hasValue()) {
3481 const p = progress * (values.getValue().length - 1);
3482 const lb = Math.floor(p);
3483 const ub = Math.ceil(p);
3484 let value;
3485 value = values.getValue()[lb];
3486 from = new Property(document, 'from', value ? parseFloat(value) : 0);
3487 value = values.getValue()[ub];
3488 to = new Property(document, 'to', value ? parseFloat(value) : 0);
3489 progress = (p - lb) / (ub - lb);
3490 } else {
3491 from = this.from;
3492 to = this.to;
3493 }
3494 return {
3495 progress,
3496 from,
3497 to
3498 };
3499 }
3500 constructor(document, node, captureTextNodes){
3501 super(document, node, captureTextNodes);
3502 this.type = 'animate';
3503 this.duration = 0;
3504 this.initialUnits = '';
3505 this.removed = false;
3506 this.frozen = false;
3507 document.screen.animations.push(this);
3508 this.begin = this.getAttribute('begin').getMilliseconds();
3509 this.maxDuration = this.begin + this.getAttribute('dur').getMilliseconds();
3510 this.from = this.getAttribute('from');
3511 this.to = this.getAttribute('to');
3512 this.values = new Property(document, 'values', null);
3513 const valuesAttr = this.getAttribute('values');
3514 if (valuesAttr.hasValue()) {
3515 this.values.setValue(valuesAttr.getString().split(';'));
3516 }
3517 }
3518}
3519
3520class AnimateColorElement extends AnimateElement {
3521 calcValue() {
3522 const { progress , from , to } = this.getProgress();
3523 const colorFrom = new RGBColor(from.getColor());
3524 const colorTo = new RGBColor(to.getColor());
3525 if (colorFrom.ok && colorTo.ok) {
3526 // tween color linearly
3527 const r = colorFrom.r + (colorTo.r - colorFrom.r) * progress;
3528 const g = colorFrom.g + (colorTo.g - colorFrom.g) * progress;
3529 const b = colorFrom.b + (colorTo.b - colorFrom.b) * progress;
3530 // ? alpha
3531 return "rgb(".concat(Math.floor(r), ", ").concat(Math.floor(g), ", ").concat(Math.floor(b), ")");
3532 }
3533 return this.getAttribute('from').getColor();
3534 }
3535 constructor(...args){
3536 super(...args);
3537 this.type = 'animateColor';
3538 }
3539}
3540
3541class AnimateTransformElement extends AnimateElement {
3542 calcValue() {
3543 const { progress , from: from1 , to: to1 } = this.getProgress();
3544 // tween value linearly
3545 const transformFrom = toNumbers(from1.getString());
3546 const transformTo = toNumbers(to1.getString());
3547 const newValue = transformFrom.map((from, i)=>{
3548 const to = transformTo[i];
3549 return from + (to - from) * progress;
3550 }).join(' ');
3551 return newValue;
3552 }
3553 constructor(...args){
3554 super(...args);
3555 this.type = 'animateTransform';
3556 }
3557}
3558
3559class FontFaceElement extends Element {
3560 constructor(document, node, captureTextNodes){
3561 super(document, node, captureTextNodes);
3562 this.type = 'font-face';
3563 this.ascent = this.getAttribute('ascent').getNumber();
3564 this.descent = this.getAttribute('descent').getNumber();
3565 this.unitsPerEm = this.getAttribute('units-per-em').getNumber();
3566 }
3567}
3568
3569class GlyphElement extends PathElement {
3570 constructor(document, node, captureTextNodes){
3571 super(document, node, captureTextNodes);
3572 this.type = 'glyph';
3573 this.horizAdvX = this.getAttribute('horiz-adv-x').getNumber();
3574 this.unicode = this.getAttribute('unicode').getString();
3575 this.arabicForm = this.getAttribute('arabic-form').getString();
3576 }
3577}
3578
3579class MissingGlyphElement extends GlyphElement {
3580 constructor(...args){
3581 super(...args);
3582 this.type = 'missing-glyph';
3583 this.horizAdvX = 0;
3584 }
3585}
3586
3587class FontElement extends Element {
3588 render() {
3589 // NO RENDER
3590 }
3591 constructor(document, node, captureTextNodes){
3592 super(document, node, captureTextNodes);
3593 this.type = 'font';
3594 this.isArabic = false;
3595 this.glyphs = {};
3596 this.arabicGlyphs = {};
3597 this.isRTL = false;
3598 this.horizAdvX = this.getAttribute('horiz-adv-x').getNumber();
3599 const { definitions } = document;
3600 const { children } = this;
3601 for (const child of children){
3602 if (child instanceof FontFaceElement) {
3603 this.fontFace = child;
3604 const fontFamilyStyle = child.getStyle('font-family');
3605 if (fontFamilyStyle.hasValue()) {
3606 definitions[fontFamilyStyle.getString()] = this;
3607 }
3608 } else if (child instanceof MissingGlyphElement) {
3609 this.missingGlyph = child;
3610 } else if (child instanceof GlyphElement) {
3611 if (child.arabicForm) {
3612 this.isRTL = true;
3613 this.isArabic = true;
3614 const arabicGlyph = this.arabicGlyphs[child.unicode];
3615 if (typeof arabicGlyph === 'undefined') {
3616 this.arabicGlyphs[child.unicode] = {
3617 [child.arabicForm]: child
3618 };
3619 } else {
3620 arabicGlyph[child.arabicForm] = child;
3621 }
3622 } else {
3623 this.glyphs[child.unicode] = child;
3624 }
3625 }
3626 }
3627 }
3628}
3629
3630class TRefElement extends TextElement {
3631 getText() {
3632 const element = this.getHrefAttribute().getDefinition();
3633 if (element) {
3634 const firstChild = element.children[0];
3635 if (firstChild) {
3636 return firstChild.getText();
3637 }
3638 }
3639 return '';
3640 }
3641 constructor(...args){
3642 super(...args);
3643 this.type = 'tref';
3644 }
3645}
3646
3647class AElement extends TextElement {
3648 getText() {
3649 return this.text;
3650 }
3651 renderChildren(ctx) {
3652 if (this.hasText) {
3653 // render as text element
3654 super.renderChildren(ctx);
3655 const { document , x , y } = this;
3656 const { mouse } = document.screen;
3657 const fontSize = new Property(document, 'fontSize', Font.parse(document.ctx.font).fontSize);
3658 // Do not calc bounding box if mouse is not working.
3659 if (mouse.isWorking()) {
3660 mouse.checkBoundingBox(this, new BoundingBox(x, y - fontSize.getPixels('y'), x + this.measureText(ctx), y));
3661 }
3662 } else if (this.children.length > 0) {
3663 // render as temporary group
3664 const g = new GElement(this.document);
3665 g.children = this.children;
3666 g.parent = this;
3667 g.render(ctx);
3668 }
3669 }
3670 onClick() {
3671 const { window } = this.document;
3672 if (window) {
3673 window.open(this.getHrefAttribute().getString());
3674 }
3675 }
3676 onMouseMove() {
3677 const ctx = this.document.ctx;
3678 ctx.canvas.style.cursor = 'pointer';
3679 }
3680 constructor(document, node1, captureTextNodes){
3681 super(document, node1, captureTextNodes);
3682 this.type = 'a';
3683 const { childNodes } = node1;
3684 const firstChild = childNodes[0];
3685 const hasText = childNodes.length > 0 && Array.from(childNodes).every((node)=>node.nodeType === 3
3686 );
3687 this.hasText = hasText;
3688 this.text = hasText ? this.getTextFromNode(firstChild) : '';
3689 }
3690}
3691
3692class TextPathElement extends TextElement {
3693 getText() {
3694 return this.text;
3695 }
3696 path(ctx) {
3697 const { dataArray } = this;
3698 if (ctx) {
3699 ctx.beginPath();
3700 }
3701 dataArray.forEach((param)=>{
3702 let { type , points } = param;
3703 switch(type){
3704 case PathParser.LINE_TO:
3705 if (ctx) {
3706 ctx.lineTo(points[0], points[1]);
3707 }
3708 break;
3709 case PathParser.MOVE_TO:
3710 if (ctx) {
3711 ctx.moveTo(points[0], points[1]);
3712 }
3713 break;
3714 case PathParser.CURVE_TO:
3715 if (ctx) {
3716 ctx.bezierCurveTo(points[0], points[1], points[2], points[3], points[4], points[5]);
3717 }
3718 break;
3719 case PathParser.QUAD_TO:
3720 if (ctx) {
3721 ctx.quadraticCurveTo(points[0], points[1], points[2], points[3]);
3722 }
3723 break;
3724 case PathParser.ARC:
3725 {
3726 const [cx, cy, rx, ry, theta, dTheta, psi, fs] = points;
3727 const r = rx > ry ? rx : ry;
3728 const scaleX = rx > ry ? 1 : rx / ry;
3729 const scaleY = rx > ry ? ry / rx : 1;
3730 if (ctx) {
3731 ctx.translate(cx, cy);
3732 ctx.rotate(psi);
3733 ctx.scale(scaleX, scaleY);
3734 ctx.arc(0, 0, r, theta, theta + dTheta, Boolean(1 - fs));
3735 ctx.scale(1 / scaleX, 1 / scaleY);
3736 ctx.rotate(-psi);
3737 ctx.translate(-cx, -cy);
3738 }
3739 break;
3740 }
3741 case PathParser.CLOSE_PATH:
3742 if (ctx) {
3743 ctx.closePath();
3744 }
3745 break;
3746 }
3747 });
3748 }
3749 renderChildren(ctx) {
3750 this.setTextData(ctx);
3751 ctx.save();
3752 const textDecoration = this.parent.getStyle('text-decoration').getString();
3753 const fontSize = this.getFontSize();
3754 const { glyphInfo } = this;
3755 const fill = ctx.fillStyle;
3756 if (textDecoration === 'underline') {
3757 ctx.beginPath();
3758 }
3759 glyphInfo.forEach((glyph, i)=>{
3760 const { p0 , p1 , rotation , text: partialText } = glyph;
3761 ctx.save();
3762 ctx.translate(p0.x, p0.y);
3763 ctx.rotate(rotation);
3764 if (ctx.fillStyle) {
3765 ctx.fillText(partialText, 0, 0);
3766 }
3767 if (ctx.strokeStyle) {
3768 ctx.strokeText(partialText, 0, 0);
3769 }
3770 ctx.restore();
3771 if (textDecoration === 'underline') {
3772 if (i === 0) {
3773 ctx.moveTo(p0.x, p0.y + fontSize / 8);
3774 }
3775 ctx.lineTo(p1.x, p1.y + fontSize / 5);
3776 }
3777 // // To assist with debugging visually, uncomment following
3778 //
3779 // ctx.beginPath();
3780 // if (i % 2)
3781 // ctx.strokeStyle = 'red';
3782 // else
3783 // ctx.strokeStyle = 'green';
3784 // ctx.moveTo(p0.x, p0.y);
3785 // ctx.lineTo(p1.x, p1.y);
3786 // ctx.stroke();
3787 // ctx.closePath();
3788 });
3789 if (textDecoration === 'underline') {
3790 ctx.lineWidth = fontSize / 20;
3791 ctx.strokeStyle = fill;
3792 ctx.stroke();
3793 ctx.closePath();
3794 }
3795 ctx.restore();
3796 }
3797 getLetterSpacingAt() {
3798 let idx = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0;
3799 return this.letterSpacingCache[idx] || 0;
3800 }
3801 findSegmentToFitChar(ctx, anchor, textFullWidth, fullPathWidth, spacesNumber, inputOffset, dy, c, charI) {
3802 let offset = inputOffset;
3803 let glyphWidth = this.measureText(ctx, c);
3804 if (c === ' ' && anchor === 'justify' && textFullWidth < fullPathWidth) {
3805 glyphWidth += (fullPathWidth - textFullWidth) / spacesNumber;
3806 }
3807 if (charI > -1) {
3808 offset += this.getLetterSpacingAt(charI);
3809 }
3810 const splineStep = this.textHeight / 20;
3811 const p0 = this.getEquidistantPointOnPath(offset, splineStep, 0);
3812 const p1 = this.getEquidistantPointOnPath(offset + glyphWidth, splineStep, 0);
3813 const segment = {
3814 p0,
3815 p1
3816 };
3817 const rotation = p0 && p1 ? Math.atan2(p1.y - p0.y, p1.x - p0.x) : 0;
3818 if (dy) {
3819 const dyX = Math.cos(Math.PI / 2 + rotation) * dy;
3820 const dyY = Math.cos(-rotation) * dy;
3821 segment.p0 = {
3822 ...p0,
3823 x: p0.x + dyX,
3824 y: p0.y + dyY
3825 };
3826 segment.p1 = {
3827 ...p1,
3828 x: p1.x + dyX,
3829 y: p1.y + dyY
3830 };
3831 }
3832 offset += glyphWidth;
3833 return {
3834 offset,
3835 segment,
3836 rotation
3837 };
3838 }
3839 measureText(ctx, text) {
3840 const { measuresCache } = this;
3841 const targetText = text || this.getText();
3842 if (measuresCache.has(targetText)) {
3843 return measuresCache.get(targetText);
3844 }
3845 const measure = this.measureTargetText(ctx, targetText);
3846 measuresCache.set(targetText, measure);
3847 return measure;
3848 }
3849 // This method supposes what all custom fonts already loaded.
3850 // If some font will be loaded after this method call, <textPath> will not be rendered correctly.
3851 // You need to call this method manually to update glyphs cache.
3852 setTextData(ctx) {
3853 if (this.glyphInfo) {
3854 return;
3855 }
3856 const renderText = this.getText();
3857 const chars = renderText.split('');
3858 const spacesNumber = renderText.split(' ').length - 1;
3859 const dx = this.parent.getAttribute('dx').split().map((_)=>_.getPixels('x')
3860 );
3861 const dy = this.parent.getAttribute('dy').getPixels('y');
3862 const anchor = this.parent.getStyle('text-anchor').getString('start');
3863 const thisSpacing = this.getStyle('letter-spacing');
3864 const parentSpacing = this.parent.getStyle('letter-spacing');
3865 let letterSpacing = 0;
3866 if (!thisSpacing.hasValue() || thisSpacing.getValue() === 'inherit') {
3867 letterSpacing = parentSpacing.getPixels();
3868 } else if (thisSpacing.hasValue()) {
3869 if (thisSpacing.getValue() !== 'initial' && thisSpacing.getValue() !== 'unset') {
3870 letterSpacing = thisSpacing.getPixels();
3871 }
3872 }
3873 // fill letter-spacing cache
3874 const letterSpacingCache = [];
3875 const textLen = renderText.length;
3876 this.letterSpacingCache = letterSpacingCache;
3877 for(let i1 = 0; i1 < textLen; i1++){
3878 letterSpacingCache.push(typeof dx[i1] !== 'undefined' ? dx[i1] : letterSpacing);
3879 }
3880 const dxSum = letterSpacingCache.reduce((acc, cur, i)=>i === 0 ? 0 : acc + cur || 0
3881 , 0);
3882 const textWidth = this.measureText(ctx);
3883 const textFullWidth = Math.max(textWidth + dxSum, 0);
3884 this.textWidth = textWidth;
3885 this.textHeight = this.getFontSize();
3886 this.glyphInfo = [];
3887 const fullPathWidth = this.getPathLength();
3888 const startOffset = this.getStyle('startOffset').getNumber(0) * fullPathWidth;
3889 let offset = 0;
3890 if (anchor === 'middle' || anchor === 'center') {
3891 offset = -textFullWidth / 2;
3892 }
3893 if (anchor === 'end' || anchor === 'right') {
3894 offset = -textFullWidth;
3895 }
3896 offset += startOffset;
3897 chars.forEach((char, i)=>{
3898 // Find such segment what distance between p0 and p1 is approx. width of glyph
3899 const { offset: nextOffset , segment , rotation } = this.findSegmentToFitChar(ctx, anchor, textFullWidth, fullPathWidth, spacesNumber, offset, dy, char, i);
3900 offset = nextOffset;
3901 if (!segment.p0 || !segment.p1) {
3902 return;
3903 }
3904 // const width = this.getLineLength(
3905 // segment.p0.x,
3906 // segment.p0.y,
3907 // segment.p1.x,
3908 // segment.p1.y
3909 // );
3910 // Note: Since glyphs are rendered one at a time, any kerning pair data built into the font will not be used.
3911 // Can foresee having a rough pair table built in that the developer can override as needed.
3912 // Or use "dx" attribute of the <text> node as a naive replacement
3913 // const kern = 0;
3914 // placeholder for future implementation
3915 // const midpoint = this.getPointOnLine(
3916 // kern + width / 2.0,
3917 // segment.p0.x, segment.p0.y, segment.p1.x, segment.p1.y
3918 // );
3919 this.glyphInfo.push({
3920 // transposeX: midpoint.x,
3921 // transposeY: midpoint.y,
3922 text: chars[i],
3923 p0: segment.p0,
3924 p1: segment.p1,
3925 rotation
3926 });
3927 });
3928 }
3929 parsePathData(path) {
3930 this.pathLength = -1 // reset path length
3931 ;
3932 if (!path) {
3933 return [];
3934 }
3935 const pathCommands = [];
3936 const { pathParser } = path;
3937 pathParser.reset();
3938 // convert l, H, h, V, and v to L
3939 while(!pathParser.isEnd()){
3940 const { current } = pathParser;
3941 const startX = current ? current.x : 0;
3942 const startY = current ? current.y : 0;
3943 const command = pathParser.next();
3944 let nextCommandType = command.type;
3945 let points = [];
3946 switch(command.type){
3947 case PathParser.MOVE_TO:
3948 this.pathM(pathParser, points);
3949 break;
3950 case PathParser.LINE_TO:
3951 nextCommandType = this.pathL(pathParser, points);
3952 break;
3953 case PathParser.HORIZ_LINE_TO:
3954 nextCommandType = this.pathH(pathParser, points);
3955 break;
3956 case PathParser.VERT_LINE_TO:
3957 nextCommandType = this.pathV(pathParser, points);
3958 break;
3959 case PathParser.CURVE_TO:
3960 this.pathC(pathParser, points);
3961 break;
3962 case PathParser.SMOOTH_CURVE_TO:
3963 nextCommandType = this.pathS(pathParser, points);
3964 break;
3965 case PathParser.QUAD_TO:
3966 this.pathQ(pathParser, points);
3967 break;
3968 case PathParser.SMOOTH_QUAD_TO:
3969 nextCommandType = this.pathT(pathParser, points);
3970 break;
3971 case PathParser.ARC:
3972 points = this.pathA(pathParser);
3973 break;
3974 case PathParser.CLOSE_PATH:
3975 PathElement.pathZ(pathParser);
3976 break;
3977 }
3978 if (command.type !== PathParser.CLOSE_PATH) {
3979 pathCommands.push({
3980 type: nextCommandType,
3981 points,
3982 start: {
3983 x: startX,
3984 y: startY
3985 },
3986 pathLength: this.calcLength(startX, startY, nextCommandType, points)
3987 });
3988 } else {
3989 pathCommands.push({
3990 type: PathParser.CLOSE_PATH,
3991 points: [],
3992 pathLength: 0
3993 });
3994 }
3995 }
3996 return pathCommands;
3997 }
3998 pathM(pathParser, points) {
3999 const { x , y } = PathElement.pathM(pathParser).point;
4000 points.push(x, y);
4001 }
4002 pathL(pathParser, points) {
4003 const { x , y } = PathElement.pathL(pathParser).point;
4004 points.push(x, y);
4005 return PathParser.LINE_TO;
4006 }
4007 pathH(pathParser, points) {
4008 const { x , y } = PathElement.pathH(pathParser).point;
4009 points.push(x, y);
4010 return PathParser.LINE_TO;
4011 }
4012 pathV(pathParser, points) {
4013 const { x , y } = PathElement.pathV(pathParser).point;
4014 points.push(x, y);
4015 return PathParser.LINE_TO;
4016 }
4017 pathC(pathParser, points) {
4018 const { point , controlPoint , currentPoint } = PathElement.pathC(pathParser);
4019 points.push(point.x, point.y, controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
4020 }
4021 pathS(pathParser, points) {
4022 const { point , controlPoint , currentPoint } = PathElement.pathS(pathParser);
4023 points.push(point.x, point.y, controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
4024 return PathParser.CURVE_TO;
4025 }
4026 pathQ(pathParser, points) {
4027 const { controlPoint , currentPoint } = PathElement.pathQ(pathParser);
4028 points.push(controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
4029 }
4030 pathT(pathParser, points) {
4031 const { controlPoint , currentPoint } = PathElement.pathT(pathParser);
4032 points.push(controlPoint.x, controlPoint.y, currentPoint.x, currentPoint.y);
4033 return PathParser.QUAD_TO;
4034 }
4035 pathA(pathParser) {
4036 let { rX , rY , sweepFlag , xAxisRotation , centp , a1 , ad } = PathElement.pathA(pathParser);
4037 if (sweepFlag === 0 && ad > 0) {
4038 ad -= 2 * Math.PI;
4039 }
4040 if (sweepFlag === 1 && ad < 0) {
4041 ad += 2 * Math.PI;
4042 }
4043 return [
4044 centp.x,
4045 centp.y,
4046 rX,
4047 rY,
4048 a1,
4049 ad,
4050 xAxisRotation,
4051 sweepFlag
4052 ];
4053 }
4054 calcLength(x, y, commandType, points) {
4055 let len = 0;
4056 let p1 = null;
4057 let p2 = null;
4058 let t = 0;
4059 switch(commandType){
4060 case PathParser.LINE_TO:
4061 return this.getLineLength(x, y, points[0], points[1]);
4062 case PathParser.CURVE_TO:
4063 // Approximates by breaking curve into 100 line segments
4064 len = 0;
4065 p1 = this.getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]);
4066 for(t = 0.01; t <= 1; t += 0.01){
4067 p2 = this.getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[5]);
4068 len += this.getLineLength(p1.x, p1.y, p2.x, p2.y);
4069 p1 = p2;
4070 }
4071 return len;
4072 case PathParser.QUAD_TO:
4073 // Approximates by breaking curve into 100 line segments
4074 len = 0;
4075 p1 = this.getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]);
4076 for(t = 0.01; t <= 1; t += 0.01){
4077 p2 = this.getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]);
4078 len += this.getLineLength(p1.x, p1.y, p2.x, p2.y);
4079 p1 = p2;
4080 }
4081 return len;
4082 case PathParser.ARC:
4083 {
4084 // Approximates by breaking curve into line segments
4085 len = 0;
4086 const start = points[4];
4087 // 4 = theta
4088 const dTheta = points[5];
4089 // 5 = dTheta
4090 const end = points[4] + dTheta;
4091 let inc = Math.PI / 180;
4092 // 1 degree resolution
4093 if (Math.abs(start - end) < inc) {
4094 inc = Math.abs(start - end);
4095 }
4096 // Note: for purpose of calculating arc length, not going to worry about rotating X-axis by angle psi
4097 p1 = this.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0);
4098 if (dTheta < 0) {
4099 for(t = start - inc; t > end; t -= inc){
4100 p2 = this.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
4101 len += this.getLineLength(p1.x, p1.y, p2.x, p2.y);
4102 p1 = p2;
4103 }
4104 } else {
4105 for(t = start + inc; t < end; t += inc){
4106 p2 = this.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
4107 len += this.getLineLength(p1.x, p1.y, p2.x, p2.y);
4108 p1 = p2;
4109 }
4110 }
4111 p2 = this.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0);
4112 len += this.getLineLength(p1.x, p1.y, p2.x, p2.y);
4113 return len;
4114 }
4115 }
4116 return 0;
4117 }
4118 getPointOnLine(dist, p1x, p1y, p2x, p2y) {
4119 let fromX = arguments.length > 5 && arguments[5] !== void 0 ? arguments[5] : p1x, fromY = arguments.length > 6 && arguments[6] !== void 0 ? arguments[6] : p1y;
4120 const m = (p2y - p1y) / (p2x - p1x + PSEUDO_ZERO);
4121 let run = Math.sqrt(dist * dist / (1 + m * m));
4122 if (p2x < p1x) {
4123 run *= -1;
4124 }
4125 let rise = m * run;
4126 let pt = null;
4127 if (p2x === p1x) {
4128 pt = {
4129 x: fromX,
4130 y: fromY + rise
4131 };
4132 } else if ((fromY - p1y) / (fromX - p1x + PSEUDO_ZERO) === m) {
4133 pt = {
4134 x: fromX + run,
4135 y: fromY + rise
4136 };
4137 } else {
4138 let ix = 0;
4139 let iy = 0;
4140 const len = this.getLineLength(p1x, p1y, p2x, p2y);
4141 if (len < PSEUDO_ZERO) {
4142 return null;
4143 }
4144 let u = (fromX - p1x) * (p2x - p1x) + (fromY - p1y) * (p2y - p1y);
4145 u /= len * len;
4146 ix = p1x + u * (p2x - p1x);
4147 iy = p1y + u * (p2y - p1y);
4148 const pRise = this.getLineLength(fromX, fromY, ix, iy);
4149 const pRun = Math.sqrt(dist * dist - pRise * pRise);
4150 run = Math.sqrt(pRun * pRun / (1 + m * m));
4151 if (p2x < p1x) {
4152 run *= -1;
4153 }
4154 rise = m * run;
4155 pt = {
4156 x: ix + run,
4157 y: iy + rise
4158 };
4159 }
4160 return pt;
4161 }
4162 getPointOnPath(distance) {
4163 const fullLen = this.getPathLength();
4164 let cumulativePathLength = 0;
4165 let p = null;
4166 if (distance < -0.00005 || distance - 0.00005 > fullLen) {
4167 return null;
4168 }
4169 const { dataArray } = this;
4170 for (const command of dataArray){
4171 if (command && (command.pathLength < 0.00005 || cumulativePathLength + command.pathLength + 0.00005 < distance)) {
4172 cumulativePathLength += command.pathLength;
4173 continue;
4174 }
4175 const delta = distance - cumulativePathLength;
4176 let currentT = 0;
4177 switch(command.type){
4178 case PathParser.LINE_TO:
4179 p = this.getPointOnLine(delta, command.start.x, command.start.y, command.points[0], command.points[1], command.start.x, command.start.y);
4180 break;
4181 case PathParser.ARC:
4182 {
4183 const start = command.points[4];
4184 // 4 = theta
4185 const dTheta = command.points[5];
4186 // 5 = dTheta
4187 const end = command.points[4] + dTheta;
4188 currentT = start + delta / command.pathLength * dTheta;
4189 if (dTheta < 0 && currentT < end || dTheta >= 0 && currentT > end) {
4190 break;
4191 }
4192 p = this.getPointOnEllipticalArc(command.points[0], command.points[1], command.points[2], command.points[3], currentT, command.points[6]);
4193 break;
4194 }
4195 case PathParser.CURVE_TO:
4196 currentT = delta / command.pathLength;
4197 if (currentT > 1) {
4198 currentT = 1;
4199 }
4200 p = this.getPointOnCubicBezier(currentT, command.start.x, command.start.y, command.points[0], command.points[1], command.points[2], command.points[3], command.points[4], command.points[5]);
4201 break;
4202 case PathParser.QUAD_TO:
4203 currentT = delta / command.pathLength;
4204 if (currentT > 1) {
4205 currentT = 1;
4206 }
4207 p = this.getPointOnQuadraticBezier(currentT, command.start.x, command.start.y, command.points[0], command.points[1], command.points[2], command.points[3]);
4208 break;
4209 }
4210 if (p) {
4211 return p;
4212 }
4213 break;
4214 }
4215 return null;
4216 }
4217 getLineLength(x1, y1, x2, y2) {
4218 return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
4219 }
4220 getPathLength() {
4221 if (this.pathLength === -1) {
4222 this.pathLength = this.dataArray.reduce((length, command)=>command.pathLength > 0 ? length + command.pathLength : length
4223 , 0);
4224 }
4225 return this.pathLength;
4226 }
4227 getPointOnCubicBezier(pct, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) {
4228 const x = p4x * CB1(pct) + p3x * CB2(pct) + p2x * CB3(pct) + p1x * CB4(pct);
4229 const y = p4y * CB1(pct) + p3y * CB2(pct) + p2y * CB3(pct) + p1y * CB4(pct);
4230 return {
4231 x,
4232 y
4233 };
4234 }
4235 getPointOnQuadraticBezier(pct, p1x, p1y, p2x, p2y, p3x, p3y) {
4236 const x = p3x * QB1(pct) + p2x * QB2(pct) + p1x * QB3(pct);
4237 const y = p3y * QB1(pct) + p2y * QB2(pct) + p1y * QB3(pct);
4238 return {
4239 x,
4240 y
4241 };
4242 }
4243 getPointOnEllipticalArc(cx, cy, rx, ry, theta, psi) {
4244 const cosPsi = Math.cos(psi);
4245 const sinPsi = Math.sin(psi);
4246 const pt = {
4247 x: rx * Math.cos(theta),
4248 y: ry * Math.sin(theta)
4249 };
4250 return {
4251 x: cx + (pt.x * cosPsi - pt.y * sinPsi),
4252 y: cy + (pt.x * sinPsi + pt.y * cosPsi)
4253 };
4254 }
4255 // TODO need some optimisations. possibly build cache only for curved segments?
4256 buildEquidistantCache(inputStep, inputPrecision) {
4257 const fullLen = this.getPathLength();
4258 const precision = inputPrecision || 0.25 // accuracy vs performance
4259 ;
4260 const step = inputStep || fullLen / 100;
4261 if (!this.equidistantCache || this.equidistantCache.step !== step || this.equidistantCache.precision !== precision) {
4262 // Prepare cache
4263 this.equidistantCache = {
4264 step,
4265 precision,
4266 points: []
4267 };
4268 // Calculate points
4269 let s = 0;
4270 for(let l = 0; l <= fullLen; l += precision){
4271 const p0 = this.getPointOnPath(l);
4272 const p1 = this.getPointOnPath(l + precision);
4273 if (!p0 || !p1) {
4274 continue;
4275 }
4276 s += this.getLineLength(p0.x, p0.y, p1.x, p1.y);
4277 if (s >= step) {
4278 this.equidistantCache.points.push({
4279 x: p0.x,
4280 y: p0.y,
4281 distance: l
4282 });
4283 s -= step;
4284 }
4285 }
4286 }
4287 }
4288 getEquidistantPointOnPath(targetDistance, step, precision) {
4289 this.buildEquidistantCache(step, precision);
4290 if (targetDistance < 0 || targetDistance - this.getPathLength() > 0.00005) {
4291 return null;
4292 }
4293 const idx = Math.round(targetDistance / this.getPathLength() * (this.equidistantCache.points.length - 1));
4294 return this.equidistantCache.points[idx] || null;
4295 }
4296 constructor(document, node, captureTextNodes){
4297 super(document, node, captureTextNodes);
4298 this.type = 'textPath';
4299 this.textWidth = 0;
4300 this.textHeight = 0;
4301 this.pathLength = -1;
4302 this.glyphInfo = null;
4303 this.letterSpacingCache = [];
4304 this.measuresCache = new Map([
4305 [
4306 '',
4307 0
4308 ]
4309 ]);
4310 const pathElement = this.getHrefAttribute().getDefinition();
4311 this.text = this.getTextFromNode();
4312 this.dataArray = this.parsePathData(pathElement);
4313 }
4314}
4315
4316// groups: 1: mime-type (+ charset), 2: mime-type (w/o charset), 3: charset, 4: base64?, 5: body
4317const dataUriRegex = /^\s*data:(([^/,;]+\/[^/,;]+)(?:;([^,;=]+=[^,;=]+))?)?(?:;(base64))?,(.*)$/i;
4318class ImageElement extends RenderedElement {
4319 async loadImage(href) {
4320 try {
4321 const image = await this.document.createImage(href);
4322 this.image = image;
4323 } catch (err) {
4324 console.error("Error while loading image \"".concat(href, "\":"), err);
4325 }
4326 this.loaded = true;
4327 }
4328 async loadSvg(href) {
4329 const match = dataUriRegex.exec(href);
4330 if (match) {
4331 const data = match[5];
4332 if (data) {
4333 if (match[4] === 'base64') {
4334 this.image = atob(data);
4335 } else {
4336 this.image = decodeURIComponent(data);
4337 }
4338 }
4339 } else {
4340 try {
4341 const response = await this.document.fetch(href);
4342 const svg = await response.text();
4343 this.image = svg;
4344 } catch (err) {
4345 console.error("Error while loading image \"".concat(href, "\":"), err);
4346 }
4347 }
4348 this.loaded = true;
4349 }
4350 renderChildren(ctx) {
4351 const { document , image , loaded } = this;
4352 const x = this.getAttribute('x').getPixels('x');
4353 const y = this.getAttribute('y').getPixels('y');
4354 const width = this.getStyle('width').getPixels('x');
4355 const height = this.getStyle('height').getPixels('y');
4356 if (!loaded || !image || !width || !height) {
4357 return;
4358 }
4359 ctx.save();
4360 ctx.translate(x, y);
4361 if (typeof image === 'string') {
4362 const subDocument = document.canvg.forkString(ctx, image, {
4363 ignoreMouse: true,
4364 ignoreAnimation: true,
4365 ignoreDimensions: true,
4366 ignoreClear: true,
4367 offsetX: 0,
4368 offsetY: 0,
4369 scaleWidth: width,
4370 scaleHeight: height
4371 });
4372 const { documentElement } = subDocument.document;
4373 if (documentElement) {
4374 documentElement.parent = this;
4375 }
4376 void subDocument.render();
4377 } else {
4378 document.setViewBox({
4379 ctx,
4380 aspectRatio: this.getAttribute('preserveAspectRatio').getString(),
4381 width,
4382 desiredWidth: image.width,
4383 height,
4384 desiredHeight: image.height
4385 });
4386 if (this.loaded) {
4387 if (!('complete' in image) || image.complete) {
4388 ctx.drawImage(image, 0, 0);
4389 }
4390 }
4391 }
4392 ctx.restore();
4393 }
4394 getBoundingBox() {
4395 const x = this.getAttribute('x').getPixels('x');
4396 const y = this.getAttribute('y').getPixels('y');
4397 const width = this.getStyle('width').getPixels('x');
4398 const height = this.getStyle('height').getPixels('y');
4399 return new BoundingBox(x, y, x + width, y + height);
4400 }
4401 constructor(document, node, captureTextNodes){
4402 super(document, node, captureTextNodes);
4403 this.type = 'image';
4404 this.loaded = false;
4405 const href = this.getHrefAttribute().getString();
4406 if (!href) {
4407 return;
4408 }
4409 const isSvg = href.endsWith('.svg') || /^\s*data:image\/svg\+xml/i.test(href);
4410 document.images.push(this);
4411 if (!isSvg) {
4412 void this.loadImage(href);
4413 } else {
4414 void this.loadSvg(href);
4415 }
4416 }
4417}
4418
4419class SymbolElement extends RenderedElement {
4420 render(_) {
4421 // NO RENDER
4422 }
4423 constructor(...args){
4424 super(...args);
4425 this.type = 'symbol';
4426 }
4427}
4428
4429class SVGFontLoader {
4430 async load(fontFamily, url) {
4431 try {
4432 const { document } = this;
4433 const svgDocument = await document.canvg.parser.load(url);
4434 const fonts = svgDocument.getElementsByTagName('font');
4435 Array.from(fonts).forEach((fontNode)=>{
4436 const font = document.createElement(fontNode);
4437 document.definitions[fontFamily] = font;
4438 });
4439 } catch (err) {
4440 console.error("Error while loading font \"".concat(url, "\":"), err);
4441 }
4442 this.loaded = true;
4443 }
4444 constructor(document){
4445 this.document = document;
4446 this.loaded = false;
4447 document.fonts.push(this);
4448 }
4449}
4450
4451class StyleElement extends Element {
4452 constructor(document, node, captureTextNodes){
4453 super(document, node, captureTextNodes);
4454 this.type = 'style';
4455 const css = compressSpaces(Array.from(node.childNodes)// NEED TEST
4456 .map((_)=>_.textContent
4457 ).join('').replace(/(\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, '') // remove comments
4458 .replace(/@import.*;/g, '') // remove imports
4459 );
4460 const cssDefs = css.split('}');
4461 cssDefs.forEach((_1)=>{
4462 const def = _1.trim();
4463 if (!def) {
4464 return;
4465 }
4466 const cssParts = def.split('{');
4467 const cssClasses = cssParts[0].split(',');
4468 const cssProps = cssParts[1].split(';');
4469 cssClasses.forEach((_)=>{
4470 const cssClass = _.trim();
4471 if (!cssClass) {
4472 return;
4473 }
4474 const props = document.styles[cssClass] || {};
4475 cssProps.forEach((cssProp)=>{
4476 const prop = cssProp.indexOf(':');
4477 const name = cssProp.substr(0, prop).trim();
4478 const value = cssProp.substr(prop + 1, cssProp.length - prop).trim();
4479 if (name && value) {
4480 props[name] = new Property(document, name, value);
4481 }
4482 });
4483 document.styles[cssClass] = props;
4484 document.stylesSpecificity[cssClass] = getSelectorSpecificity(cssClass);
4485 if (cssClass === '@font-face') {
4486 const fontFamily = props['font-family'].getString().replace(/"|'/g, '');
4487 const srcs = props.src.getString().split(',');
4488 srcs.forEach((src)=>{
4489 if (src.indexOf('format("svg")') > 0) {
4490 const url = parseExternalUrl(src);
4491 if (url) {
4492 void new SVGFontLoader(document).load(fontFamily, url);
4493 }
4494 }
4495 });
4496 }
4497 });
4498 });
4499 }
4500}
4501StyleElement.parseExternalUrl = parseExternalUrl;
4502
4503class UseElement extends RenderedElement {
4504 setContext(ctx) {
4505 super.setContext(ctx);
4506 const xAttr = this.getAttribute('x');
4507 const yAttr = this.getAttribute('y');
4508 if (xAttr.hasValue()) {
4509 ctx.translate(xAttr.getPixels('x'), 0);
4510 }
4511 if (yAttr.hasValue()) {
4512 ctx.translate(0, yAttr.getPixels('y'));
4513 }
4514 }
4515 path(ctx) {
4516 const { element } = this;
4517 if (element) {
4518 element.path(ctx);
4519 }
4520 }
4521 renderChildren(ctx) {
4522 const { document , element } = this;
4523 if (element) {
4524 let tempSvg = element;
4525 if (element.type === 'symbol') {
4526 // render me using a temporary svg element in symbol cases (http://www.w3.org/TR/SVG/struct.html#UseElement)
4527 tempSvg = new SVGElement(document);
4528 tempSvg.attributes.viewBox = new Property(document, 'viewBox', element.getAttribute('viewBox').getString());
4529 tempSvg.attributes.preserveAspectRatio = new Property(document, 'preserveAspectRatio', element.getAttribute('preserveAspectRatio').getString());
4530 tempSvg.attributes.overflow = new Property(document, 'overflow', element.getAttribute('overflow').getString());
4531 tempSvg.children = element.children;
4532 // element is still the parent of the children
4533 element.styles.opacity = new Property(document, 'opacity', this.calculateOpacity());
4534 }
4535 if (tempSvg.type === 'svg') {
4536 const widthStyle = this.getStyle('width', false, true);
4537 const heightStyle = this.getStyle('height', false, true);
4538 // if symbol or svg, inherit width/height from me
4539 if (widthStyle.hasValue()) {
4540 tempSvg.attributes.width = new Property(document, 'width', widthStyle.getString());
4541 }
4542 if (heightStyle.hasValue()) {
4543 tempSvg.attributes.height = new Property(document, 'height', heightStyle.getString());
4544 }
4545 }
4546 const oldParent = tempSvg.parent;
4547 tempSvg.parent = this;
4548 tempSvg.render(ctx);
4549 tempSvg.parent = oldParent;
4550 }
4551 }
4552 getBoundingBox(ctx) {
4553 const { element } = this;
4554 if (element) {
4555 return element.getBoundingBox(ctx);
4556 }
4557 return null;
4558 }
4559 elementTransform() {
4560 const { document , element } = this;
4561 if (!element) {
4562 return null;
4563 }
4564 return Transform.fromElement(document, element);
4565 }
4566 get element() {
4567 if (!this.cachedElement) {
4568 this.cachedElement = this.getHrefAttribute().getDefinition();
4569 }
4570 return this.cachedElement;
4571 }
4572 constructor(...args){
4573 super(...args);
4574 this.type = 'use';
4575 }
4576}
4577
4578function imGet(img, x, y, width, _height, rgba) {
4579 return img[y * width * 4 + x * 4 + rgba];
4580}
4581function imSet(img, x, y, width, _height, rgba, val) {
4582 img[y * width * 4 + x * 4 + rgba] = val;
4583}
4584function m(matrix, i, v) {
4585 const mi = matrix[i];
4586 return mi * v;
4587}
4588function c(a, m1, m2, m3) {
4589 return m1 + Math.cos(a) * m2 + Math.sin(a) * m3;
4590}
4591class FeColorMatrixElement extends Element {
4592 apply(ctx, _x, _y, width, height) {
4593 // assuming x==0 && y==0 for now
4594 const { includeOpacity , matrix } = this;
4595 const srcData = ctx.getImageData(0, 0, width, height);
4596 for(let y = 0; y < height; y++){
4597 for(let x = 0; x < width; x++){
4598 const r = imGet(srcData.data, x, y, width, height, 0);
4599 const g = imGet(srcData.data, x, y, width, height, 1);
4600 const b = imGet(srcData.data, x, y, width, height, 2);
4601 const a = imGet(srcData.data, x, y, width, height, 3);
4602 let nr = m(matrix, 0, r) + m(matrix, 1, g) + m(matrix, 2, b) + m(matrix, 3, a) + m(matrix, 4, 1);
4603 let ng = m(matrix, 5, r) + m(matrix, 6, g) + m(matrix, 7, b) + m(matrix, 8, a) + m(matrix, 9, 1);
4604 let nb = m(matrix, 10, r) + m(matrix, 11, g) + m(matrix, 12, b) + m(matrix, 13, a) + m(matrix, 14, 1);
4605 let na = m(matrix, 15, r) + m(matrix, 16, g) + m(matrix, 17, b) + m(matrix, 18, a) + m(matrix, 19, 1);
4606 if (includeOpacity) {
4607 nr = 0;
4608 ng = 0;
4609 nb = 0;
4610 na *= a / 255;
4611 }
4612 imSet(srcData.data, x, y, width, height, 0, nr);
4613 imSet(srcData.data, x, y, width, height, 1, ng);
4614 imSet(srcData.data, x, y, width, height, 2, nb);
4615 imSet(srcData.data, x, y, width, height, 3, na);
4616 }
4617 }
4618 ctx.clearRect(0, 0, width, height);
4619 ctx.putImageData(srcData, 0, 0);
4620 }
4621 constructor(document, node, captureTextNodes){
4622 super(document, node, captureTextNodes);
4623 this.type = 'feColorMatrix';
4624 let matrix = toNumbers(this.getAttribute('values').getString());
4625 switch(this.getAttribute('type').getString('matrix')){
4626 case 'saturate':
4627 {
4628 const s = matrix[0];
4629 /* eslint-disable array-element-newline */ matrix = [
4630 0.213 + 0.787 * s,
4631 0.715 - 0.715 * s,
4632 0.072 - 0.072 * s,
4633 0,
4634 0,
4635 0.213 - 0.213 * s,
4636 0.715 + 0.285 * s,
4637 0.072 - 0.072 * s,
4638 0,
4639 0,
4640 0.213 - 0.213 * s,
4641 0.715 - 0.715 * s,
4642 0.072 + 0.928 * s,
4643 0,
4644 0,
4645 0,
4646 0,
4647 0,
4648 1,
4649 0,
4650 0,
4651 0,
4652 0,
4653 0,
4654 1
4655 ];
4656 break;
4657 }
4658 case 'hueRotate':
4659 {
4660 const a = matrix[0] * Math.PI / 180;
4661 /* eslint-disable array-element-newline */ matrix = [
4662 c(a, 0.213, 0.787, -0.213),
4663 c(a, 0.715, -0.715, -0.715),
4664 c(a, 0.072, -0.072, 0.928),
4665 0,
4666 0,
4667 c(a, 0.213, -0.213, 0.143),
4668 c(a, 0.715, 0.285, 0.14),
4669 c(a, 0.072, -0.072, -0.283),
4670 0,
4671 0,
4672 c(a, 0.213, -0.213, -0.787),
4673 c(a, 0.715, -0.715, 0.715),
4674 c(a, 0.072, 0.928, 0.072),
4675 0,
4676 0,
4677 0,
4678 0,
4679 0,
4680 1,
4681 0,
4682 0,
4683 0,
4684 0,
4685 0,
4686 1
4687 ];
4688 break;
4689 }
4690 case 'luminanceToAlpha':
4691 /* eslint-disable array-element-newline */ matrix = [
4692 0,
4693 0,
4694 0,
4695 0,
4696 0,
4697 0,
4698 0,
4699 0,
4700 0,
4701 0,
4702 0,
4703 0,
4704 0,
4705 0,
4706 0,
4707 0.2125,
4708 0.7154,
4709 0.0721,
4710 0,
4711 0,
4712 0,
4713 0,
4714 0,
4715 0,
4716 1
4717 ];
4718 break;
4719 }
4720 this.matrix = matrix;
4721 this.includeOpacity = this.getAttribute('includeOpacity').hasValue();
4722 }
4723}
4724
4725class MaskElement extends Element {
4726 apply(ctx, element) {
4727 const { document } = this;
4728 // render as temp svg
4729 let x = this.getAttribute('x').getPixels('x');
4730 let y = this.getAttribute('y').getPixels('y');
4731 let width = this.getStyle('width').getPixels('x');
4732 let height = this.getStyle('height').getPixels('y');
4733 if (!width && !height) {
4734 const boundingBox = new BoundingBox();
4735 this.children.forEach((child)=>{
4736 boundingBox.addBoundingBox(child.getBoundingBox(ctx));
4737 });
4738 x = Math.floor(boundingBox.x1);
4739 y = Math.floor(boundingBox.y1);
4740 width = Math.floor(boundingBox.width);
4741 height = Math.floor(boundingBox.height);
4742 }
4743 const ignoredStyles = this.removeStyles(element, MaskElement.ignoreStyles);
4744 const maskCanvas = document.createCanvas(x + width, y + height);
4745 const maskCtx = maskCanvas.getContext('2d');
4746 document.screen.setDefaults(maskCtx);
4747 this.renderChildren(maskCtx);
4748 // convert mask to alpha with a fake node
4749 // TODO: refactor out apply from feColorMatrix
4750 new FeColorMatrixElement(document, {
4751 nodeType: 1,
4752 childNodes: [],
4753 attributes: [
4754 {
4755 nodeName: 'type',
4756 value: 'luminanceToAlpha'
4757 },
4758 {
4759 nodeName: 'includeOpacity',
4760 value: 'true'
4761 }
4762 ]
4763 }).apply(maskCtx, 0, 0, x + width, y + height);
4764 const tmpCanvas = document.createCanvas(x + width, y + height);
4765 const tmpCtx = tmpCanvas.getContext('2d');
4766 document.screen.setDefaults(tmpCtx);
4767 element.render(tmpCtx);
4768 tmpCtx.globalCompositeOperation = 'destination-in';
4769 tmpCtx.fillStyle = maskCtx.createPattern(maskCanvas, 'no-repeat');
4770 tmpCtx.fillRect(0, 0, x + width, y + height);
4771 ctx.fillStyle = tmpCtx.createPattern(tmpCanvas, 'no-repeat');
4772 ctx.fillRect(0, 0, x + width, y + height);
4773 // reassign mask
4774 this.restoreStyles(element, ignoredStyles);
4775 }
4776 render(_) {
4777 // NO RENDER
4778 }
4779 constructor(...args){
4780 super(...args);
4781 this.type = 'mask';
4782 }
4783}
4784MaskElement.ignoreStyles = [
4785 'mask',
4786 'transform',
4787 'clip-path'
4788];
4789
4790const noop = ()=>{
4791// NOOP
4792};
4793class ClipPathElement extends Element {
4794 apply(ctx) {
4795 const { document } = this;
4796 const contextProto = Reflect.getPrototypeOf(ctx);
4797 const { beginPath , closePath } = ctx;
4798 if (contextProto) {
4799 contextProto.beginPath = noop;
4800 contextProto.closePath = noop;
4801 }
4802 Reflect.apply(beginPath, ctx, []);
4803 this.children.forEach((child)=>{
4804 if (!('path' in child)) {
4805 return;
4806 }
4807 let transform = 'elementTransform' in child ? child.elementTransform() : null // handle <use />
4808 ;
4809 if (!transform) {
4810 transform = Transform.fromElement(document, child);
4811 }
4812 if (transform) {
4813 transform.apply(ctx);
4814 }
4815 child.path(ctx);
4816 if (contextProto) {
4817 contextProto.closePath = closePath;
4818 }
4819 if (transform) {
4820 transform.unapply(ctx);
4821 }
4822 });
4823 Reflect.apply(closePath, ctx, []);
4824 ctx.clip();
4825 if (contextProto) {
4826 contextProto.beginPath = beginPath;
4827 contextProto.closePath = closePath;
4828 }
4829 }
4830 render(_) {
4831 // NO RENDER
4832 }
4833 constructor(...args){
4834 super(...args);
4835 this.type = 'clipPath';
4836 }
4837}
4838
4839class FilterElement extends Element {
4840 apply(ctx, element) {
4841 // render as temp svg
4842 const { document , children } = this;
4843 const boundingBox = 'getBoundingBox' in element ? element.getBoundingBox(ctx) : null;
4844 if (!boundingBox) {
4845 return;
4846 }
4847 let px = 0;
4848 let py = 0;
4849 children.forEach((child)=>{
4850 const efd = child.extraFilterDistance || 0;
4851 px = Math.max(px, efd);
4852 py = Math.max(py, efd);
4853 });
4854 const width = Math.floor(boundingBox.width);
4855 const height = Math.floor(boundingBox.height);
4856 const tmpCanvasWidth = width + 2 * px;
4857 const tmpCanvasHeight = height + 2 * py;
4858 if (tmpCanvasWidth < 1 || tmpCanvasHeight < 1) {
4859 return;
4860 }
4861 const x = Math.floor(boundingBox.x);
4862 const y = Math.floor(boundingBox.y);
4863 const ignoredStyles = this.removeStyles(element, FilterElement.ignoreStyles);
4864 const tmpCanvas = document.createCanvas(tmpCanvasWidth, tmpCanvasHeight);
4865 const tmpCtx = tmpCanvas.getContext('2d');
4866 document.screen.setDefaults(tmpCtx);
4867 tmpCtx.translate(-x + px, -y + py);
4868 element.render(tmpCtx);
4869 // apply filters
4870 children.forEach((child)=>{
4871 if (typeof child.apply === 'function') {
4872 child.apply(tmpCtx, 0, 0, tmpCanvasWidth, tmpCanvasHeight);
4873 }
4874 });
4875 // render on me
4876 ctx.drawImage(tmpCanvas, 0, 0, tmpCanvasWidth, tmpCanvasHeight, x - px, y - py, tmpCanvasWidth, tmpCanvasHeight);
4877 this.restoreStyles(element, ignoredStyles);
4878 }
4879 render(_) {
4880 // NO RENDER
4881 }
4882 constructor(...args){
4883 super(...args);
4884 this.type = 'filter';
4885 }
4886}
4887FilterElement.ignoreStyles = [
4888 'filter',
4889 'transform',
4890 'clip-path'
4891];
4892
4893class FeDropShadowElement extends Element {
4894 apply(_, _x, _y, _width, _height) {
4895 // TODO: implement
4896 }
4897 constructor(document, node, captureTextNodes){
4898 super(document, node, captureTextNodes);
4899 this.type = 'feDropShadow';
4900 this.addStylesFromStyleDefinition();
4901 }
4902}
4903
4904class FeMorphologyElement extends Element {
4905 apply(_, _x, _y, _width, _height) {
4906 // TODO: implement
4907 }
4908 constructor(...args){
4909 super(...args);
4910 this.type = 'feMorphology';
4911 }
4912}
4913
4914class FeCompositeElement extends Element {
4915 apply(_, _x, _y, _width, _height) {
4916 // TODO: implement
4917 }
4918 constructor(...args){
4919 super(...args);
4920 this.type = 'feComposite';
4921 }
4922}
4923
4924class FeGaussianBlurElement extends Element {
4925 apply(ctx, x, y, width, height) {
4926 const { document , blurRadius } = this;
4927 const body = document.window ? document.window.document.body : null;
4928 const canvas = ctx.canvas;
4929 // StackBlur requires canvas be on document
4930 canvas.id = document.getUniqueId();
4931 if (body) {
4932 canvas.style.display = 'none';
4933 body.appendChild(canvas);
4934 }
4935 canvasRGBA(canvas, x, y, width, height, blurRadius);
4936 if (body) {
4937 body.removeChild(canvas);
4938 }
4939 }
4940 constructor(document, node, captureTextNodes){
4941 super(document, node, captureTextNodes);
4942 this.type = 'feGaussianBlur';
4943 this.blurRadius = Math.floor(this.getAttribute('stdDeviation').getNumber());
4944 this.extraFilterDistance = this.blurRadius;
4945 }
4946}
4947
4948class TitleElement extends Element {
4949 constructor(...args){
4950 super(...args);
4951 this.type = 'title';
4952 }
4953}
4954
4955class DescElement extends Element {
4956 constructor(...args){
4957 super(...args);
4958 this.type = 'desc';
4959 }
4960}
4961
4962const elements = {
4963 'svg': SVGElement,
4964 'rect': RectElement,
4965 'circle': CircleElement,
4966 'ellipse': EllipseElement,
4967 'line': LineElement,
4968 'polyline': PolylineElement,
4969 'polygon': PolygonElement,
4970 'path': PathElement,
4971 'pattern': PatternElement,
4972 'marker': MarkerElement,
4973 'defs': DefsElement,
4974 'linearGradient': LinearGradientElement,
4975 'radialGradient': RadialGradientElement,
4976 'stop': StopElement,
4977 'animate': AnimateElement,
4978 'animateColor': AnimateColorElement,
4979 'animateTransform': AnimateTransformElement,
4980 'font': FontElement,
4981 'font-face': FontFaceElement,
4982 'missing-glyph': MissingGlyphElement,
4983 'glyph': GlyphElement,
4984 'text': TextElement,
4985 'tspan': TSpanElement,
4986 'tref': TRefElement,
4987 'a': AElement,
4988 'textPath': TextPathElement,
4989 'image': ImageElement,
4990 'g': GElement,
4991 'symbol': SymbolElement,
4992 'style': StyleElement,
4993 'use': UseElement,
4994 'mask': MaskElement,
4995 'clipPath': ClipPathElement,
4996 'filter': FilterElement,
4997 'feDropShadow': FeDropShadowElement,
4998 'feMorphology': FeMorphologyElement,
4999 'feComposite': FeCompositeElement,
5000 'feColorMatrix': FeColorMatrixElement,
5001 'feGaussianBlur': FeGaussianBlurElement,
5002 'title': TitleElement,
5003 'desc': DescElement
5004};
5005
5006function createCanvas(width, height) {
5007 const canvas = document.createElement('canvas');
5008 canvas.width = width;
5009 canvas.height = height;
5010 return canvas;
5011}
5012async function createImage(src) {
5013 let anonymousCrossOrigin = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false;
5014 const image = document.createElement('img');
5015 if (anonymousCrossOrigin) {
5016 image.crossOrigin = 'Anonymous';
5017 }
5018 return new Promise((resolve, reject)=>{
5019 image.onload = ()=>{
5020 resolve(image);
5021 };
5022 image.onerror = (_event, _source, _lineno, _colno, error)=>{
5023 reject(error);
5024 };
5025 image.src = src;
5026 });
5027}
5028const DEFAULT_EM_SIZE = 12;
5029class Document {
5030 bindCreateImage(createImage1, anonymousCrossOrigin) {
5031 if (typeof anonymousCrossOrigin === 'boolean') {
5032 return (source, forceAnonymousCrossOrigin)=>createImage1(source, typeof forceAnonymousCrossOrigin === 'boolean' ? forceAnonymousCrossOrigin : anonymousCrossOrigin)
5033 ;
5034 }
5035 return createImage1;
5036 }
5037 get window() {
5038 return this.screen.window;
5039 }
5040 get fetch() {
5041 return this.screen.fetch;
5042 }
5043 get ctx() {
5044 return this.screen.ctx;
5045 }
5046 get emSize() {
5047 const { emSizeStack } = this;
5048 return emSizeStack[emSizeStack.length - 1] || DEFAULT_EM_SIZE;
5049 }
5050 set emSize(value) {
5051 const { emSizeStack } = this;
5052 emSizeStack.push(value);
5053 }
5054 popEmSize() {
5055 const { emSizeStack } = this;
5056 emSizeStack.pop();
5057 }
5058 getUniqueId() {
5059 return "canvg".concat(++this.uniqueId);
5060 }
5061 isImagesLoaded() {
5062 return this.images.every((_)=>_.loaded
5063 );
5064 }
5065 isFontsLoaded() {
5066 return this.fonts.every((_)=>_.loaded
5067 );
5068 }
5069 createDocumentElement(document) {
5070 const documentElement = this.createElement(document.documentElement);
5071 documentElement.root = true;
5072 documentElement.addStylesFromStyleDefinition();
5073 this.documentElement = documentElement;
5074 return documentElement;
5075 }
5076 createElement(node) {
5077 const elementType = node.nodeName.replace(/^[^:]+:/, '');
5078 const ElementType = Document.elementTypes[elementType];
5079 if (ElementType) {
5080 return new ElementType(this, node);
5081 }
5082 return new UnknownElement(this, node);
5083 }
5084 createTextNode(node) {
5085 return new TextNode(this, node);
5086 }
5087 setViewBox(config) {
5088 this.screen.setViewBox({
5089 document: this,
5090 ...config
5091 });
5092 }
5093 constructor(canvg, { rootEmSize =DEFAULT_EM_SIZE , emSize =DEFAULT_EM_SIZE , createCanvas: createCanvas1 = Document.createCanvas , createImage: createImage2 = Document.createImage , anonymousCrossOrigin } = {}){
5094 this.canvg = canvg;
5095 this.definitions = {};
5096 this.styles = {};
5097 this.stylesSpecificity = {};
5098 this.images = [];
5099 this.fonts = [];
5100 this.emSizeStack = [];
5101 this.uniqueId = 0;
5102 this.screen = canvg.screen;
5103 this.rootEmSize = rootEmSize;
5104 this.emSize = emSize;
5105 this.createCanvas = createCanvas1;
5106 this.createImage = this.bindCreateImage(createImage2, anonymousCrossOrigin);
5107 this.screen.wait(()=>this.isImagesLoaded()
5108 );
5109 this.screen.wait(()=>this.isFontsLoaded()
5110 );
5111 }
5112}
5113Document.createCanvas = createCanvas;
5114Document.createImage = createImage;
5115Document.elementTypes = elements;
5116
5117/**
5118 * SVG renderer on canvas.
5119 */ class Canvg {
5120 /**
5121 * Create Canvg instance from SVG source string or URL.
5122 * @param ctx - Rendering context.
5123 * @param svg - SVG source string or URL.
5124 * @param options - Rendering options.
5125 * @returns Canvg instance.
5126 */ static async from(ctx, svg) {
5127 let options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
5128 const parser = new Parser(options);
5129 const svgDocument = await parser.parse(svg);
5130 return new Canvg(ctx, svgDocument, options);
5131 }
5132 /**
5133 * Create Canvg instance from SVG source string.
5134 * @param ctx - Rendering context.
5135 * @param svg - SVG source string.
5136 * @param options - Rendering options.
5137 * @returns Canvg instance.
5138 */ static fromString(ctx, svg) {
5139 let options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
5140 const parser = new Parser(options);
5141 const svgDocument = parser.parseFromString(svg);
5142 return new Canvg(ctx, svgDocument, options);
5143 }
5144 /**
5145 * Create new Canvg instance with inherited options.
5146 * @param ctx - Rendering context.
5147 * @param svg - SVG source string or URL.
5148 * @param options - Rendering options.
5149 * @returns Canvg instance.
5150 */ fork(ctx, svg) {
5151 let options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
5152 return Canvg.from(ctx, svg, {
5153 ...this.options,
5154 ...options
5155 });
5156 }
5157 /**
5158 * Create new Canvg instance with inherited options.
5159 * @param ctx - Rendering context.
5160 * @param svg - SVG source string.
5161 * @param options - Rendering options.
5162 * @returns Canvg instance.
5163 */ forkString(ctx, svg) {
5164 let options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
5165 return Canvg.fromString(ctx, svg, {
5166 ...this.options,
5167 ...options
5168 });
5169 }
5170 /**
5171 * Document is ready promise.
5172 * @returns Ready promise.
5173 */ ready() {
5174 return this.screen.ready();
5175 }
5176 /**
5177 * Document is ready value.
5178 * @returns Is ready or not.
5179 */ isReady() {
5180 return this.screen.isReady();
5181 }
5182 /**
5183 * Render only first frame, ignoring animations and mouse.
5184 * @param options - Rendering options.
5185 */ async render() {
5186 let options = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
5187 this.start({
5188 enableRedraw: true,
5189 ignoreAnimation: true,
5190 ignoreMouse: true,
5191 ...options
5192 });
5193 await this.ready();
5194 this.stop();
5195 }
5196 /**
5197 * Start rendering.
5198 * @param options - Render options.
5199 */ start() {
5200 let options = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
5201 const { documentElement , screen , options: baseOptions } = this;
5202 screen.start(documentElement, {
5203 enableRedraw: true,
5204 ...baseOptions,
5205 ...options
5206 });
5207 }
5208 /**
5209 * Stop rendering.
5210 */ stop() {
5211 this.screen.stop();
5212 }
5213 /**
5214 * Resize SVG to fit in given size.
5215 * @param width
5216 * @param height
5217 * @param preserveAspectRatio
5218 */ resize(width) {
5219 let height = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : width, preserveAspectRatio = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false;
5220 this.documentElement.resize(width, height, preserveAspectRatio);
5221 }
5222 /**
5223 * Main constructor.
5224 * @param ctx - Rendering context.
5225 * @param svg - SVG Document.
5226 * @param options - Rendering options.
5227 */ constructor(ctx, svg, options = {}){
5228 this.parser = new Parser(options);
5229 this.screen = new Screen(ctx, options);
5230 this.options = options;
5231 const document = new Document(this, options);
5232 const documentElement = document.createDocumentElement(svg);
5233 this.document = document;
5234 this.documentElement = documentElement;
5235 }
5236}
5237
5238export { AElement, AnimateColorElement, AnimateElement, AnimateTransformElement, BoundingBox, CB1, CB2, CB3, CB4, Canvg, CircleElement, ClipPathElement, DefsElement, DescElement, Document, Element, EllipseElement, FeColorMatrixElement, FeCompositeElement, FeDropShadowElement, FeGaussianBlurElement, FeMorphologyElement, FilterElement, Font, FontElement, FontFaceElement, GElement, GlyphElement, GradientElement, ImageElement, LineElement, LinearGradientElement, MarkerElement, MaskElement, Matrix, MissingGlyphElement, Mouse, PSEUDO_ZERO, Parser, PathElement, PathParser, PatternElement, Point, PolygonElement, PolylineElement, Property, QB1, QB2, QB3, RadialGradientElement, RectElement, RenderedElement, Rotate, SVGElement, SVGFontLoader, Scale, Screen, Skew, SkewX, SkewY, StopElement, StyleElement, SymbolElement, TRefElement, TSpanElement, TextElement, TextPathElement, TitleElement, Transform, Translate, UnknownElement, UseElement, ViewPort, compressSpaces, elements, getSelectorSpecificity, normalizeAttributeName, normalizeColor, parseExternalUrl, index as presets, toMatrixValue, toNumbers, trimLeft, trimRight, vectorMagnitude, vectorsAngle, vectorsRatio };
5239//# sourceMappingURL=index.js.map