@jbrowse/svgcanvas 6.4.2 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context.test.js +67 -0
- package/dist/context.test.js.map +1 -0
- package/dist/index.d.ts +15 -56
- package/dist/index.js +154 -525
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/context.test.ts +71 -0
- package/src/index.ts +167 -652
- package/dist/drawImage.test.js +0 -76
- package/dist/drawImage.test.js.map +0 -1
- package/src/drawImage.test.ts +0 -81
- /package/dist/{drawImage.test.d.ts → context.test.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -9,50 +9,13 @@
|
|
|
9
9
|
* Copyright (c) 2014 Gliffy Inc.
|
|
10
10
|
* Copyright (c) 2021 Zeno Zeng
|
|
11
11
|
*
|
|
12
|
-
* Vendored and converted to ESM/TypeScript for pure ESM compatibility
|
|
12
|
+
* Vendored and converted to ESM/TypeScript for pure ESM compatibility, and cut
|
|
13
|
+
* down to the drawing calls the renderers make: what a multiple sequence
|
|
14
|
+
* alignment needs is rectangles, paths, arcs and a great many glyphs. Gradients,
|
|
15
|
+
* patterns, clipping, rotation, bezier curves, stroked text, shadows and image
|
|
16
|
+
* drawing are gone with the state they carried.
|
|
13
17
|
*/
|
|
14
18
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
15
|
-
function toString(obj) {
|
|
16
|
-
if (!obj) {
|
|
17
|
-
return obj;
|
|
18
|
-
}
|
|
19
|
-
if (typeof obj === 'string') {
|
|
20
|
-
return obj;
|
|
21
|
-
}
|
|
22
|
-
return obj + '';
|
|
23
|
-
}
|
|
24
|
-
function format(str, args) {
|
|
25
|
-
const keys = Object.keys(args);
|
|
26
|
-
for (const key of keys) {
|
|
27
|
-
str = str.replace(new RegExp('\\{' + key + '\\}', 'gi'), args[key]);
|
|
28
|
-
}
|
|
29
|
-
return str;
|
|
30
|
-
}
|
|
31
|
-
function randomString(holder) {
|
|
32
|
-
if (!holder) {
|
|
33
|
-
throw new Error('cannot create a random attribute name for an undefined object');
|
|
34
|
-
}
|
|
35
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz';
|
|
36
|
-
let randomstring = '';
|
|
37
|
-
do {
|
|
38
|
-
randomstring = '';
|
|
39
|
-
for (let i = 0; i < 12; i++) {
|
|
40
|
-
randomstring += chars[Math.floor(Math.random() * chars.length)];
|
|
41
|
-
}
|
|
42
|
-
} while (holder[randomstring]);
|
|
43
|
-
return randomstring;
|
|
44
|
-
}
|
|
45
|
-
function createNamedToNumberedLookup(items, radix = 10) {
|
|
46
|
-
const lookup = {};
|
|
47
|
-
const parts = items.split(',');
|
|
48
|
-
for (let i = 0; i < parts.length; i += 2) {
|
|
49
|
-
const entity = '&' + parts[i + 1] + ';';
|
|
50
|
-
const base10 = parseInt(parts[i], radix);
|
|
51
|
-
lookup[entity] = '&#' + base10 + ';';
|
|
52
|
-
}
|
|
53
|
-
lookup['\\xa0'] = ' ';
|
|
54
|
-
return lookup;
|
|
55
|
-
}
|
|
56
19
|
function getTextAnchor(textAlign) {
|
|
57
20
|
const mapping = {
|
|
58
21
|
left: 'start',
|
|
@@ -81,38 +44,12 @@ function getDominantBaseline(textBaseline) {
|
|
|
81
44
|
const TEXT_ATTR_DEFAULTS = {
|
|
82
45
|
'font-style': 'normal',
|
|
83
46
|
'font-weight': 'normal',
|
|
84
|
-
'font-variant': 'normal',
|
|
85
47
|
// the initial value is `auto`, which resolves to the alphabetic baseline
|
|
86
48
|
'dominant-baseline': 'alphabetic',
|
|
87
49
|
};
|
|
88
50
|
function omitDefault(attr, value) {
|
|
89
51
|
return value === TEXT_ATTR_DEFAULTS[attr] ? undefined : value;
|
|
90
52
|
}
|
|
91
|
-
const namedEntities = createNamedToNumberedLookup('50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
|
|
92
|
-
'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
|
|
93
|
-
'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
|
|
94
|
-
'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
|
|
95
|
-
'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
|
|
96
|
-
'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
|
|
97
|
-
'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
|
|
98
|
-
'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
|
|
99
|
-
'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
|
|
100
|
-
'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
|
|
101
|
-
'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
|
|
102
|
-
'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
|
|
103
|
-
't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
|
|
104
|
-
'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
|
|
105
|
-
'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
|
|
106
|
-
'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
|
|
107
|
-
'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
|
|
108
|
-
'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
|
|
109
|
-
'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
|
|
110
|
-
'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
|
|
111
|
-
'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
|
|
112
|
-
'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
|
|
113
|
-
'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
|
|
114
|
-
'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
|
|
115
|
-
'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
|
|
116
53
|
const STYLES = {
|
|
117
54
|
strokeStyle: {
|
|
118
55
|
svgAttr: 'stroke',
|
|
@@ -126,111 +63,40 @@ const STYLES = {
|
|
|
126
63
|
svg: null,
|
|
127
64
|
apply: 'fill',
|
|
128
65
|
},
|
|
129
|
-
lineCap: {
|
|
130
|
-
svgAttr: 'stroke-linecap',
|
|
131
|
-
canvas: 'butt',
|
|
132
|
-
svg: 'butt',
|
|
133
|
-
apply: 'stroke',
|
|
134
|
-
},
|
|
135
|
-
lineJoin: {
|
|
136
|
-
svgAttr: 'stroke-linejoin',
|
|
137
|
-
canvas: 'miter',
|
|
138
|
-
svg: 'miter',
|
|
139
|
-
apply: 'stroke',
|
|
140
|
-
},
|
|
141
|
-
miterLimit: {
|
|
142
|
-
svgAttr: 'stroke-miterlimit',
|
|
143
|
-
canvas: 10,
|
|
144
|
-
svg: 4,
|
|
145
|
-
apply: 'stroke',
|
|
146
|
-
},
|
|
147
66
|
lineWidth: {
|
|
148
67
|
svgAttr: 'stroke-width',
|
|
149
68
|
canvas: 1,
|
|
150
69
|
svg: 1,
|
|
151
70
|
apply: 'stroke',
|
|
152
71
|
},
|
|
153
|
-
globalAlpha: {
|
|
154
|
-
svgAttr: 'opacity',
|
|
155
|
-
canvas: 1,
|
|
156
|
-
svg: 1,
|
|
157
|
-
apply: 'fill stroke',
|
|
158
|
-
},
|
|
159
72
|
font: {
|
|
160
73
|
canvas: '10px sans-serif',
|
|
161
74
|
},
|
|
162
|
-
shadowColor: {
|
|
163
|
-
canvas: '#000000',
|
|
164
|
-
},
|
|
165
|
-
shadowOffsetX: {
|
|
166
|
-
canvas: 0,
|
|
167
|
-
},
|
|
168
|
-
shadowOffsetY: {
|
|
169
|
-
canvas: 0,
|
|
170
|
-
},
|
|
171
|
-
shadowBlur: {
|
|
172
|
-
canvas: 0,
|
|
173
|
-
},
|
|
174
75
|
textAlign: {
|
|
175
76
|
canvas: 'start',
|
|
176
77
|
},
|
|
177
78
|
textBaseline: {
|
|
178
79
|
canvas: 'alphabetic',
|
|
179
80
|
},
|
|
81
|
+
// null rather than an empty array: the canvas default is "no dashes", which is
|
|
82
|
+
// also the svg default, and the two have to compare equal or every stroke
|
|
83
|
+
// carries an empty stroke-dasharray
|
|
180
84
|
lineDash: {
|
|
181
85
|
svgAttr: 'stroke-dasharray',
|
|
182
|
-
canvas:
|
|
86
|
+
canvas: null,
|
|
183
87
|
svg: null,
|
|
184
88
|
apply: 'stroke',
|
|
185
89
|
},
|
|
186
90
|
};
|
|
187
|
-
|
|
188
|
-
__root;
|
|
189
|
-
__ctx;
|
|
190
|
-
constructor(gradientNode, ctx) {
|
|
191
|
-
this.__root = gradientNode;
|
|
192
|
-
this.__ctx = ctx;
|
|
193
|
-
}
|
|
194
|
-
addColorStop(offset, color) {
|
|
195
|
-
const stop = this.__ctx.__createElement('stop');
|
|
196
|
-
stop.setAttribute('offset', String(offset));
|
|
197
|
-
if (toString(color).includes('rgba')) {
|
|
198
|
-
const regex = /rgba\(\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi;
|
|
199
|
-
const matches = regex.exec(color);
|
|
200
|
-
if (matches) {
|
|
201
|
-
stop.setAttribute('stop-color', format('rgb({r},{g},{b})', {
|
|
202
|
-
r: matches[1],
|
|
203
|
-
g: matches[2],
|
|
204
|
-
b: matches[3],
|
|
205
|
-
}));
|
|
206
|
-
stop.setAttribute('stop-opacity', matches[4]);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
stop.setAttribute('stop-color', toString(color));
|
|
211
|
-
}
|
|
212
|
-
this.__root.appendChild(stop);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
class CanvasPattern {
|
|
216
|
-
__root;
|
|
217
|
-
__ctx;
|
|
218
|
-
constructor(pattern, ctx) {
|
|
219
|
-
this.__root = pattern;
|
|
220
|
-
this.__ctx = ctx;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
91
|
+
const rgbaRegex = /rgba\(\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d?\.?\d*)\s*\)/i;
|
|
223
92
|
export class Context {
|
|
224
93
|
width;
|
|
225
94
|
height;
|
|
226
|
-
enableMirroring;
|
|
227
95
|
canvas;
|
|
228
96
|
__document;
|
|
229
97
|
__ctx;
|
|
230
98
|
__canvas;
|
|
231
99
|
__root;
|
|
232
|
-
__ids;
|
|
233
|
-
__defs;
|
|
234
100
|
__currentElement;
|
|
235
101
|
__styleStack;
|
|
236
102
|
__groupStack;
|
|
@@ -238,48 +104,20 @@ export class Context {
|
|
|
238
104
|
__currentPosition;
|
|
239
105
|
__transformMatrix;
|
|
240
106
|
__transformMatrixStack;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
__fontUnderline;
|
|
245
|
-
__fontHref;
|
|
107
|
+
// the parsed font, keyed by the `font` string that produced it: parsing costs
|
|
108
|
+
// a DOM element and a CSS parse, and a text-heavy export asks per glyph
|
|
109
|
+
__fontCache = new Map();
|
|
246
110
|
// Style properties
|
|
247
111
|
strokeStyle;
|
|
248
112
|
fillStyle;
|
|
249
|
-
lineCap;
|
|
250
|
-
lineJoin;
|
|
251
|
-
miterLimit;
|
|
252
113
|
lineWidth;
|
|
253
|
-
globalAlpha;
|
|
254
114
|
font;
|
|
255
|
-
shadowColor;
|
|
256
|
-
shadowOffsetX;
|
|
257
|
-
shadowOffsetY;
|
|
258
|
-
shadowBlur;
|
|
259
115
|
textAlign;
|
|
260
116
|
textBaseline;
|
|
261
117
|
lineDash;
|
|
262
|
-
constructor(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (typeof o === 'number' && height !== undefined) {
|
|
266
|
-
options = { ...defaultOptions, width: o, height };
|
|
267
|
-
}
|
|
268
|
-
else if (!o) {
|
|
269
|
-
options = defaultOptions;
|
|
270
|
-
}
|
|
271
|
-
else if (typeof o === 'object') {
|
|
272
|
-
options = o;
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
options = defaultOptions;
|
|
276
|
-
}
|
|
277
|
-
this.width = options.width || defaultOptions.width;
|
|
278
|
-
this.height = options.height || defaultOptions.height;
|
|
279
|
-
this.enableMirroring =
|
|
280
|
-
options.enableMirroring !== undefined
|
|
281
|
-
? options.enableMirroring
|
|
282
|
-
: defaultOptions.enableMirroring;
|
|
118
|
+
constructor(width, height, options = {}) {
|
|
119
|
+
this.width = width;
|
|
120
|
+
this.height = height;
|
|
283
121
|
this.canvas = this;
|
|
284
122
|
this.__document = options.document || document;
|
|
285
123
|
if (options.ctx) {
|
|
@@ -295,31 +133,28 @@ export class Context {
|
|
|
295
133
|
this.__root = this.__document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
296
134
|
this.__root.setAttribute('version', '1.1');
|
|
297
135
|
this.__root.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
298
|
-
this.__root.
|
|
299
|
-
this.__root.setAttribute('
|
|
300
|
-
this.__root.setAttribute('height', String(this.height));
|
|
301
|
-
this.__ids = {};
|
|
302
|
-
this.__defs = this.__document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
303
|
-
this.__root.appendChild(this.__defs);
|
|
136
|
+
this.__root.setAttribute('width', String(width));
|
|
137
|
+
this.__root.setAttribute('height', String(height));
|
|
304
138
|
this.__currentElement = this.__document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
305
139
|
this.__root.appendChild(this.__currentElement);
|
|
306
140
|
this.__currentDefaultPath = '';
|
|
307
141
|
this.__currentPosition = {};
|
|
308
142
|
this.__transformMatrix = new DOMMatrix();
|
|
309
143
|
this.resetTransform();
|
|
310
|
-
this.__options = options;
|
|
311
|
-
this.__id = Math.random().toString(16).substring(2, 8);
|
|
312
144
|
}
|
|
313
145
|
__createElement(elementName, properties = {}, resetFill) {
|
|
314
146
|
const element = this.__document.createElementNS('http://www.w3.org/2000/svg', elementName);
|
|
315
147
|
if (resetFill) {
|
|
148
|
+
// a placeholder: whichever of fill()/stroke() paints this element
|
|
149
|
+
// overwrites its own, and __applyStyleToCurrentElement drops the other
|
|
150
|
+
// when it is the svg default anyway
|
|
316
151
|
element.setAttribute('fill', 'none');
|
|
317
152
|
element.setAttribute('stroke', 'none');
|
|
318
153
|
}
|
|
319
154
|
for (const key of Object.keys(properties)) {
|
|
320
155
|
// an unset property means "no such attribute", not the string
|
|
321
|
-
// "undefined". __applyText passes
|
|
322
|
-
// so without this every glyph in an export carries
|
|
156
|
+
// "undefined". __applyText passes optional attributes through
|
|
157
|
+
// unconditionally, so without this every glyph in an export carries
|
|
323
158
|
// text-decoration="undefined" -- invalid, and ~28 wasted bytes per letter
|
|
324
159
|
const value = properties[key];
|
|
325
160
|
if (value !== undefined && value !== null) {
|
|
@@ -347,77 +182,60 @@ export class Context {
|
|
|
347
182
|
}
|
|
348
183
|
return styleState;
|
|
349
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* A translation is what an element's own x/y already say, and repeating it as
|
|
187
|
+
* a matrix costs ~35 bytes on every glyph of an alignment. Anything else --
|
|
188
|
+
* the scale a sequence-logo letter is stretched by -- still needs the matrix.
|
|
189
|
+
*/
|
|
350
190
|
__applyTransformation(element, matrix) {
|
|
351
191
|
const { a, b, c, d, e, f } = matrix || this.getTransform();
|
|
192
|
+
if (a === 1 &&
|
|
193
|
+
b === 0 &&
|
|
194
|
+
c === 0 &&
|
|
195
|
+
d === 1 &&
|
|
196
|
+
element.hasAttribute('x') &&
|
|
197
|
+
element.hasAttribute('y')) {
|
|
198
|
+
if (e !== 0) {
|
|
199
|
+
element.setAttribute('x', String(Number(element.getAttribute('x')) + e));
|
|
200
|
+
}
|
|
201
|
+
if (f !== 0) {
|
|
202
|
+
element.setAttribute('y', String(Number(element.getAttribute('y')) + f));
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
352
206
|
element.setAttribute('transform', `matrix(${a} ${b} ${c} ${d} ${e} ${f})`);
|
|
353
207
|
}
|
|
354
208
|
__applyStyleToCurrentElement(type) {
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
if (currentStyleGroup) {
|
|
358
|
-
currentElement.setAttribute(type, '');
|
|
359
|
-
currentElement = currentStyleGroup.element;
|
|
360
|
-
for (const node of currentStyleGroup.children) {
|
|
361
|
-
node.setAttribute(type, '');
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
const keys = Object.keys(STYLES);
|
|
365
|
-
for (const key of keys) {
|
|
209
|
+
const currentElement = this.__currentElement;
|
|
210
|
+
for (const key of Object.keys(STYLES)) {
|
|
366
211
|
const style = STYLES[key];
|
|
367
212
|
const value = this[key];
|
|
368
|
-
if (style.apply) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
else if (style.apply.includes(type) && style.svg !== value) {
|
|
385
|
-
if ((style.svgAttr === 'stroke' || style.svgAttr === 'fill') &&
|
|
386
|
-
value &&
|
|
387
|
-
value.includes('rgba')) {
|
|
388
|
-
const regex = /rgba\(\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi;
|
|
389
|
-
const matches = regex.exec(value);
|
|
390
|
-
if (matches) {
|
|
391
|
-
currentElement.setAttribute(style.svgAttr, format('rgb({r},{g},{b})', {
|
|
392
|
-
r: matches[1],
|
|
393
|
-
g: matches[2],
|
|
394
|
-
b: matches[3],
|
|
395
|
-
}));
|
|
396
|
-
let opacity = Number(matches[4]);
|
|
397
|
-
const globalAlpha = this.globalAlpha;
|
|
398
|
-
if (globalAlpha != null) {
|
|
399
|
-
opacity *= globalAlpha;
|
|
400
|
-
}
|
|
401
|
-
currentElement.setAttribute(style.svgAttr + '-opacity', String(opacity));
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
let attr = style.svgAttr;
|
|
406
|
-
let val = value;
|
|
407
|
-
if (key === 'globalAlpha') {
|
|
408
|
-
attr = type + '-' + style.svgAttr;
|
|
409
|
-
if (currentElement.getAttribute(attr)) {
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
else if (key === 'lineWidth') {
|
|
414
|
-
const scale = this.__getTransformScale();
|
|
415
|
-
val = value * Math.max(scale.x, scale.y);
|
|
416
|
-
}
|
|
417
|
-
currentElement.setAttribute(attr, val);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
213
|
+
if (!style.apply?.includes(type) || style.svg === value) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const matches = (style.svgAttr === 'stroke' || style.svgAttr === 'fill') &&
|
|
217
|
+
typeof value === 'string' &&
|
|
218
|
+
value.includes('rgba')
|
|
219
|
+
? rgbaRegex.exec(value)
|
|
220
|
+
: null;
|
|
221
|
+
if (matches) {
|
|
222
|
+
// SVG has no rgba(): the alpha belongs in its own -opacity attribute
|
|
223
|
+
currentElement.setAttribute(style.svgAttr, `rgb(${matches[1]},${matches[2]},${matches[3]})`);
|
|
224
|
+
currentElement.setAttribute(`${style.svgAttr}-opacity`, matches[4]);
|
|
225
|
+
}
|
|
226
|
+
else if (key === 'lineWidth') {
|
|
227
|
+
const scale = this.__getTransformScale();
|
|
228
|
+
currentElement.setAttribute(style.svgAttr, String(value * Math.max(scale.x, scale.y)));
|
|
420
229
|
}
|
|
230
|
+
else {
|
|
231
|
+
currentElement.setAttribute(style.svgAttr, value);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// a filled element with no stroke does not have to say so: `none` is what
|
|
235
|
+
// svg assumes. The reverse is not true -- an unstroked fill defaults to
|
|
236
|
+
// black -- so a stroked element keeps its fill="none"
|
|
237
|
+
if (type === 'fill' && currentElement.getAttribute('stroke') === 'none') {
|
|
238
|
+
currentElement.removeAttribute('stroke');
|
|
421
239
|
}
|
|
422
240
|
}
|
|
423
241
|
__closestGroupOrSvg(node) {
|
|
@@ -427,22 +245,6 @@ export class Context {
|
|
|
427
245
|
}
|
|
428
246
|
return this.__closestGroupOrSvg(node.parentNode);
|
|
429
247
|
}
|
|
430
|
-
getSerializedSvg(fixNamedEntities) {
|
|
431
|
-
let serialized = new XMLSerializer().serializeToString(this.__root);
|
|
432
|
-
const xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi;
|
|
433
|
-
if (xmlns.test(serialized)) {
|
|
434
|
-
serialized = serialized.replace('xmlns="http://www.w3.org/2000/svg', 'xmlns:xlink="http://www.w3.org/1999/xlink');
|
|
435
|
-
}
|
|
436
|
-
if (fixNamedEntities) {
|
|
437
|
-
for (const key of Object.keys(namedEntities)) {
|
|
438
|
-
const regexp = new RegExp(key, 'gi');
|
|
439
|
-
if (regexp.test(serialized)) {
|
|
440
|
-
serialized = serialized.replace(regexp, namedEntities[key]);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return serialized;
|
|
445
|
-
}
|
|
446
248
|
getSvg() {
|
|
447
249
|
return this.__root;
|
|
448
250
|
}
|
|
@@ -452,8 +254,7 @@ export class Context {
|
|
|
452
254
|
this.__groupStack.push(parent);
|
|
453
255
|
parent.appendChild(group);
|
|
454
256
|
this.__currentElement = group;
|
|
455
|
-
|
|
456
|
-
this.__styleStack.push(style);
|
|
257
|
+
this.__styleStack.push(this.__getStyleState());
|
|
457
258
|
if (!this.__transformMatrixStack) {
|
|
458
259
|
this.__transformMatrixStack = [];
|
|
459
260
|
}
|
|
@@ -461,12 +262,10 @@ export class Context {
|
|
|
461
262
|
}
|
|
462
263
|
restore() {
|
|
463
264
|
this.__currentElement = this.__groupStack.pop();
|
|
464
|
-
this.__currentElementsToStyle = undefined;
|
|
465
265
|
if (!this.__currentElement) {
|
|
466
|
-
this.__currentElement = this.__root.childNodes[
|
|
266
|
+
this.__currentElement = this.__root.childNodes[0];
|
|
467
267
|
}
|
|
468
|
-
|
|
469
|
-
this.__applyStyleState(state);
|
|
268
|
+
this.__applyStyleState(this.__styleStack.pop());
|
|
470
269
|
if (this.__transformMatrixStack && this.__transformMatrixStack.length > 0) {
|
|
471
270
|
this.setTransform(this.__transformMatrixStack.pop());
|
|
472
271
|
}
|
|
@@ -475,14 +274,12 @@ export class Context {
|
|
|
475
274
|
this.__currentDefaultPath = '';
|
|
476
275
|
this.__currentPosition = {};
|
|
477
276
|
const path = this.__createElement('path', {}, true);
|
|
478
|
-
|
|
479
|
-
parent.appendChild(path);
|
|
277
|
+
this.__closestGroupOrSvg().appendChild(path);
|
|
480
278
|
this.__currentElement = path;
|
|
481
279
|
}
|
|
482
280
|
__applyCurrentDefaultPath() {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
currentElement.setAttribute('d', this.__currentDefaultPath);
|
|
281
|
+
if (this.__currentElement.nodeName === 'path') {
|
|
282
|
+
this.__currentElement.setAttribute('d', this.__currentDefaultPath);
|
|
486
283
|
}
|
|
487
284
|
}
|
|
488
285
|
__addPathCommand(command) {
|
|
@@ -494,10 +291,8 @@ export class Context {
|
|
|
494
291
|
this.beginPath();
|
|
495
292
|
}
|
|
496
293
|
this.__currentPosition = { x, y };
|
|
497
|
-
this.
|
|
498
|
-
|
|
499
|
-
y: this.__matrixTransform(x, y).y,
|
|
500
|
-
}));
|
|
294
|
+
const p = this.__matrixTransform(x, y);
|
|
295
|
+
this.__addPathCommand(`M ${p.x} ${p.y}`);
|
|
501
296
|
}
|
|
502
297
|
closePath() {
|
|
503
298
|
if (this.__currentDefaultPath) {
|
|
@@ -506,171 +301,108 @@ export class Context {
|
|
|
506
301
|
}
|
|
507
302
|
lineTo(x, y) {
|
|
508
303
|
this.__currentPosition = { x, y };
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}));
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
this.__addPathCommand(format('M {x} {y}', {
|
|
517
|
-
x: this.__matrixTransform(x, y).x,
|
|
518
|
-
y: this.__matrixTransform(x, y).y,
|
|
519
|
-
}));
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
|
|
523
|
-
this.__currentPosition = { x, y };
|
|
524
|
-
this.__addPathCommand(format('C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}', {
|
|
525
|
-
cp1x: this.__matrixTransform(cp1x, cp1y).x,
|
|
526
|
-
cp1y: this.__matrixTransform(cp1x, cp1y).y,
|
|
527
|
-
cp2x: this.__matrixTransform(cp2x, cp2y).x,
|
|
528
|
-
cp2y: this.__matrixTransform(cp2x, cp2y).y,
|
|
529
|
-
x: this.__matrixTransform(x, y).x,
|
|
530
|
-
y: this.__matrixTransform(x, y).y,
|
|
531
|
-
}));
|
|
304
|
+
const p = this.__matrixTransform(x, y);
|
|
305
|
+
this.__addPathCommand(this.__currentDefaultPath.includes('M')
|
|
306
|
+
? `L ${p.x} ${p.y}`
|
|
307
|
+
: `M ${p.x} ${p.y}`);
|
|
532
308
|
}
|
|
533
309
|
quadraticCurveTo(cpx, cpy, x, y) {
|
|
534
310
|
this.__currentPosition = { x, y };
|
|
535
|
-
this.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
x: this.__matrixTransform(x, y).x,
|
|
539
|
-
y: this.__matrixTransform(x, y).y,
|
|
540
|
-
}));
|
|
311
|
+
const cp = this.__matrixTransform(cpx, cpy);
|
|
312
|
+
const p = this.__matrixTransform(x, y);
|
|
313
|
+
this.__addPathCommand(`Q ${cp.x} ${cp.y} ${p.x} ${p.y}`);
|
|
541
314
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
315
|
+
// paint-order only says something when the element carries both a fill and a
|
|
316
|
+
// stroke, which is the collapsed-clade triangle and nothing else
|
|
317
|
+
__applyPaintOrder(order, other) {
|
|
318
|
+
const current = this.__currentElement;
|
|
319
|
+
if (current.nodeName === 'path' &&
|
|
320
|
+
current.hasAttribute(other) &&
|
|
321
|
+
current.getAttribute(other) !== 'none') {
|
|
322
|
+
current.setAttribute('paint-order', order);
|
|
545
323
|
}
|
|
324
|
+
}
|
|
325
|
+
stroke() {
|
|
326
|
+
this.__applyPaintOrder('fill stroke markers', 'fill');
|
|
546
327
|
this.__applyCurrentDefaultPath();
|
|
547
328
|
this.__applyStyleToCurrentElement('stroke');
|
|
548
329
|
}
|
|
549
330
|
fill() {
|
|
550
|
-
|
|
551
|
-
this.__currentElement.setAttribute('paint-order', 'stroke fill markers');
|
|
552
|
-
}
|
|
331
|
+
this.__applyPaintOrder('stroke fill markers', 'stroke');
|
|
553
332
|
this.__applyCurrentDefaultPath();
|
|
554
333
|
this.__applyStyleToCurrentElement('fill');
|
|
555
334
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
this.moveTo(x, y);
|
|
561
|
-
this.lineTo(x + width, y);
|
|
562
|
-
this.lineTo(x + width, y + height);
|
|
563
|
-
this.lineTo(x, y + height);
|
|
564
|
-
this.lineTo(x, y);
|
|
565
|
-
this.closePath();
|
|
566
|
-
}
|
|
567
|
-
__clearCanvas() {
|
|
568
|
-
const rootGroup = this.__root.childNodes[1];
|
|
569
|
-
this.__root.removeChild(rootGroup);
|
|
570
|
-
this.__currentElement = this.__document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
571
|
-
this.__root.appendChild(this.__currentElement);
|
|
572
|
-
this.__groupStack = [];
|
|
573
|
-
}
|
|
335
|
+
// A fill that happens to cover the context is still a fill: it paints over
|
|
336
|
+
// what is under it, exactly as the canvas would. Treating it as a clear threw
|
|
337
|
+
// the drawing away instead -- a full-width highlight band over an alignment
|
|
338
|
+
// took the export from 124 letters to 4.
|
|
574
339
|
fillRect(x, y, width, height) {
|
|
575
|
-
const { a, b, c, d, e, f } = this.getTransform();
|
|
576
|
-
if (JSON.stringify([a, b, c, d, e, f]) === JSON.stringify([1, 0, 0, 1, 0, 0])) {
|
|
577
|
-
if (x === 0 &&
|
|
578
|
-
y === 0 &&
|
|
579
|
-
width === this.width &&
|
|
580
|
-
height === this.height) {
|
|
581
|
-
this.__clearCanvas();
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
340
|
const rect = this.__createElement('rect', { x, y, width, height }, true);
|
|
585
|
-
|
|
586
|
-
parent.appendChild(rect);
|
|
341
|
+
this.__closestGroupOrSvg().appendChild(rect);
|
|
587
342
|
this.__currentElement = rect;
|
|
588
343
|
this.__applyTransformation(rect);
|
|
589
344
|
this.__applyStyleToCurrentElement('fill');
|
|
590
345
|
}
|
|
591
346
|
strokeRect(x, y, width, height) {
|
|
592
347
|
const rect = this.__createElement('rect', { x, y, width, height }, true);
|
|
593
|
-
|
|
594
|
-
parent.appendChild(rect);
|
|
348
|
+
this.__closestGroupOrSvg().appendChild(rect);
|
|
595
349
|
this.__currentElement = rect;
|
|
596
350
|
this.__applyTransformation(rect);
|
|
597
351
|
this.__applyStyleToCurrentElement('stroke');
|
|
598
352
|
}
|
|
599
353
|
clearRect(x, y, width, height) {
|
|
600
354
|
const { a, b, c, d, e, f } = this.getTransform();
|
|
601
|
-
if (
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
355
|
+
if (a === 1 &&
|
|
356
|
+
b === 0 &&
|
|
357
|
+
c === 0 &&
|
|
358
|
+
d === 1 &&
|
|
359
|
+
e === 0 &&
|
|
360
|
+
f === 0 &&
|
|
361
|
+
x === 0 &&
|
|
362
|
+
y === 0 &&
|
|
363
|
+
width === this.width &&
|
|
364
|
+
height === this.height) {
|
|
365
|
+
this.__clearCanvas();
|
|
366
|
+
return;
|
|
609
367
|
}
|
|
610
368
|
const rect = this.__createElement('rect', { x, y, width, height, fill: '#FFFFFF' }, true);
|
|
611
369
|
this.__applyTransformation(rect);
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
return new CanvasGradient(grad, this);
|
|
626
|
-
}
|
|
627
|
-
createRadialGradient(x0, y0, r0, x1, y1, r1) {
|
|
628
|
-
const grad = this.__createElement('radialGradient', {
|
|
629
|
-
id: randomString(this.__ids),
|
|
630
|
-
cx: x1 + 'px',
|
|
631
|
-
cy: y1 + 'px',
|
|
632
|
-
r: r1 + 'px',
|
|
633
|
-
fx: x0 + 'px',
|
|
634
|
-
fy: y0 + 'px',
|
|
635
|
-
gradientUnits: 'userSpaceOnUse',
|
|
636
|
-
});
|
|
637
|
-
this.__defs.appendChild(grad);
|
|
638
|
-
return new CanvasGradient(grad, this);
|
|
639
|
-
}
|
|
640
|
-
__applyText(text, x, y, action) {
|
|
370
|
+
this.__closestGroupOrSvg().appendChild(rect);
|
|
371
|
+
}
|
|
372
|
+
__clearCanvas() {
|
|
373
|
+
this.__root.removeChild(this.__root.childNodes[0]);
|
|
374
|
+
this.__currentElement = this.__document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
375
|
+
this.__root.appendChild(this.__currentElement);
|
|
376
|
+
this.__groupStack = [];
|
|
377
|
+
}
|
|
378
|
+
__parsedFont() {
|
|
379
|
+
const hit = this.__fontCache.get(this.font);
|
|
380
|
+
if (hit) {
|
|
381
|
+
return hit;
|
|
382
|
+
}
|
|
641
383
|
const el = document.createElement('span');
|
|
642
|
-
el.setAttribute('style',
|
|
643
|
-
|
|
384
|
+
el.setAttribute('style', `font:${this.font}`);
|
|
385
|
+
this.__fontCache.set(this.font, el.style);
|
|
386
|
+
return el.style;
|
|
387
|
+
}
|
|
388
|
+
fillText(text, x, y) {
|
|
389
|
+
const style = this.__parsedFont();
|
|
644
390
|
const parent = this.__closestGroupOrSvg();
|
|
645
391
|
const textElement = this.__createElement('text', {
|
|
646
392
|
'font-family': style.fontFamily,
|
|
647
393
|
'font-size': style.fontSize,
|
|
648
394
|
'font-style': omitDefault('font-style', style.fontStyle),
|
|
649
395
|
'font-weight': omitDefault('font-weight', style.fontWeight),
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
y: y,
|
|
396
|
+
x,
|
|
397
|
+
y,
|
|
653
398
|
'text-anchor': getTextAnchor(this.textAlign),
|
|
654
399
|
'dominant-baseline': omitDefault('dominant-baseline', getDominantBaseline(this.textBaseline)),
|
|
655
400
|
}, true);
|
|
656
401
|
textElement.appendChild(this.__document.createTextNode(text));
|
|
657
402
|
this.__currentElement = textElement;
|
|
658
403
|
this.__applyTransformation(textElement);
|
|
659
|
-
this.__applyStyleToCurrentElement(
|
|
660
|
-
|
|
661
|
-
if (this.__fontHref) {
|
|
662
|
-
const a = this.__createElement('a');
|
|
663
|
-
a.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', this.__fontHref);
|
|
664
|
-
a.appendChild(textElement);
|
|
665
|
-
finalElement = a;
|
|
666
|
-
}
|
|
667
|
-
parent.appendChild(finalElement);
|
|
668
|
-
}
|
|
669
|
-
fillText(text, x, y) {
|
|
670
|
-
this.__applyText(text, x, y, 'fill');
|
|
671
|
-
}
|
|
672
|
-
strokeText(text, x, y) {
|
|
673
|
-
this.__applyText(text, x, y, 'stroke');
|
|
404
|
+
this.__applyStyleToCurrentElement('fill');
|
|
405
|
+
parent.appendChild(textElement);
|
|
674
406
|
}
|
|
675
407
|
measureText(text) {
|
|
676
408
|
this.__ctx.font = this.font;
|
|
@@ -692,60 +424,31 @@ export class Context {
|
|
|
692
424
|
const startX = x + radius * Math.cos(startAngle);
|
|
693
425
|
const startY = y + radius * Math.sin(startAngle);
|
|
694
426
|
const sweepFlag = counterClockwise ? 0 : 1;
|
|
695
|
-
let largeArcFlag = 0;
|
|
696
427
|
let diff = endAngle - startAngle;
|
|
697
428
|
if (diff < 0) {
|
|
698
429
|
diff += 2 * Math.PI;
|
|
699
430
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const scaleY =
|
|
431
|
+
const largeArcFlag = counterClockwise
|
|
432
|
+
? diff > Math.PI
|
|
433
|
+
? 0
|
|
434
|
+
: 1
|
|
435
|
+
: diff > Math.PI
|
|
436
|
+
? 1
|
|
437
|
+
: 0;
|
|
438
|
+
const { x: scaleX, y: scaleY } = this.__getTransformScale();
|
|
439
|
+
const end = this.__matrixTransform(endX, endY);
|
|
708
440
|
this.lineTo(startX, startY);
|
|
709
|
-
this.__addPathCommand(
|
|
710
|
-
rx: radius * scaleX,
|
|
711
|
-
ry: radius * scaleY,
|
|
712
|
-
xAxisRotation: 0,
|
|
713
|
-
largeArcFlag,
|
|
714
|
-
sweepFlag,
|
|
715
|
-
endX: this.__matrixTransform(endX, endY).x,
|
|
716
|
-
endY: this.__matrixTransform(endX, endY).y,
|
|
717
|
-
}));
|
|
441
|
+
this.__addPathCommand(`A ${radius * scaleX} ${radius * scaleY} 0 ${largeArcFlag} ${sweepFlag} ${end.x} ${end.y}`);
|
|
718
442
|
this.__currentPosition = { x: endX, y: endY };
|
|
719
443
|
}
|
|
720
|
-
clip() {
|
|
721
|
-
const group = this.__closestGroupOrSvg();
|
|
722
|
-
const clipPath = this.__createElement('clipPath');
|
|
723
|
-
const id = randomString(this.__ids);
|
|
724
|
-
const newGroup = this.__createElement('g');
|
|
725
|
-
this.__applyCurrentDefaultPath();
|
|
726
|
-
group.removeChild(this.__currentElement);
|
|
727
|
-
clipPath.setAttribute('id', id);
|
|
728
|
-
clipPath.appendChild(this.__currentElement);
|
|
729
|
-
this.__defs.appendChild(clipPath);
|
|
730
|
-
group.setAttribute('clip-path', format('url(#{id})', { id }));
|
|
731
|
-
group.appendChild(newGroup);
|
|
732
|
-
this.__currentElement = newGroup;
|
|
733
|
-
}
|
|
734
444
|
setLineDash(dashArray) {
|
|
735
|
-
|
|
736
|
-
this.lineDash = dashArray.join(',');
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
this.lineDash = null;
|
|
740
|
-
}
|
|
445
|
+
this.lineDash = dashArray.length > 0 ? dashArray.join(',') : null;
|
|
741
446
|
}
|
|
742
447
|
setTransform(a, b, c, d, e, f) {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
this.__transformMatrix = new DOMMatrix([a, b, c, d, e, f]);
|
|
748
|
-
}
|
|
448
|
+
this.__transformMatrix =
|
|
449
|
+
a instanceof DOMMatrix
|
|
450
|
+
? new DOMMatrix([a.a, a.b, a.c, a.d, a.e, a.f])
|
|
451
|
+
: new DOMMatrix([a, b, c, d, e, f]);
|
|
749
452
|
}
|
|
750
453
|
getTransform() {
|
|
751
454
|
const { a, b, c, d, e, f } = this.__transformMatrix;
|
|
@@ -761,27 +464,10 @@ export class Context {
|
|
|
761
464
|
if (isNaN(x) || isNaN(y) || !isFinite(x) || !isFinite(y)) {
|
|
762
465
|
return;
|
|
763
466
|
}
|
|
764
|
-
|
|
765
|
-
this.setTransform(matrix);
|
|
766
|
-
}
|
|
767
|
-
rotate(angle) {
|
|
768
|
-
const matrix = this.getTransform().multiply(new DOMMatrix([
|
|
769
|
-
Math.cos(angle),
|
|
770
|
-
Math.sin(angle),
|
|
771
|
-
-Math.sin(angle),
|
|
772
|
-
Math.cos(angle),
|
|
773
|
-
0,
|
|
774
|
-
0,
|
|
775
|
-
]));
|
|
776
|
-
this.setTransform(matrix);
|
|
467
|
+
this.setTransform(this.getTransform().scale(x, y));
|
|
777
468
|
}
|
|
778
469
|
translate(x, y) {
|
|
779
|
-
|
|
780
|
-
this.setTransform(matrix);
|
|
781
|
-
}
|
|
782
|
-
transform(a, b, c, d, e, f) {
|
|
783
|
-
const matrix = this.getTransform().multiply(new DOMMatrix([a, b, c, d, e, f]));
|
|
784
|
-
this.setTransform(matrix);
|
|
470
|
+
this.setTransform(this.getTransform().translate(x, y));
|
|
785
471
|
}
|
|
786
472
|
__matrixTransform(x, y) {
|
|
787
473
|
return new DOMPoint(x, y).matrixTransform(this.__transformMatrix);
|
|
@@ -792,62 +478,5 @@ export class Context {
|
|
|
792
478
|
y: Math.hypot(this.__transformMatrix.c, this.__transformMatrix.d),
|
|
793
479
|
};
|
|
794
480
|
}
|
|
795
|
-
__getTransformRotation() {
|
|
796
|
-
return Math.atan2(this.__transformMatrix.b, this.__transformMatrix.a);
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* An <image> of the source, cropped and scaled the way the canvas call asks:
|
|
800
|
-
* a nested <svg> whose viewBox is the source rectangle maps it onto the
|
|
801
|
-
* destination rectangle exactly. Throws rather than dropping the image, so a
|
|
802
|
-
* renderer that draws one into an export finds out.
|
|
803
|
-
*/
|
|
804
|
-
drawImage(image, ...args) {
|
|
805
|
-
const source = image;
|
|
806
|
-
if (typeof source.toDataURL !== 'function') {
|
|
807
|
-
throw new TypeError('svgcanvas drawImage needs a source with toDataURL (a canvas); an OffscreenCanvas has to be copied onto one first');
|
|
808
|
-
}
|
|
809
|
-
const [sx, sy, sw, sh, dx, dy, dw, dh] = args.length >= 8
|
|
810
|
-
? args
|
|
811
|
-
: [
|
|
812
|
-
0,
|
|
813
|
-
0,
|
|
814
|
-
source.width,
|
|
815
|
-
source.height,
|
|
816
|
-
args[0],
|
|
817
|
-
args[1],
|
|
818
|
-
args[2] ?? source.width,
|
|
819
|
-
args[3] ?? source.height,
|
|
820
|
-
];
|
|
821
|
-
const group = this.__createElement('g');
|
|
822
|
-
const viewport = this.__createElement('svg', {
|
|
823
|
-
x: dx,
|
|
824
|
-
y: dy,
|
|
825
|
-
width: dw,
|
|
826
|
-
height: dh,
|
|
827
|
-
viewBox: `${sx} ${sy} ${sw} ${sh}`,
|
|
828
|
-
preserveAspectRatio: 'none',
|
|
829
|
-
});
|
|
830
|
-
const img = this.__createElement('image', {
|
|
831
|
-
href: source.toDataURL('image/png'),
|
|
832
|
-
width: source.width,
|
|
833
|
-
height: source.height,
|
|
834
|
-
preserveAspectRatio: 'none',
|
|
835
|
-
'image-rendering': this.imageSmoothingEnabled ? undefined : 'pixelated',
|
|
836
|
-
});
|
|
837
|
-
viewport.appendChild(img);
|
|
838
|
-
group.appendChild(viewport);
|
|
839
|
-
this.__closestGroupOrSvg().appendChild(group);
|
|
840
|
-
this.__currentElement = group;
|
|
841
|
-
this.__applyTransformation(group);
|
|
842
|
-
}
|
|
843
|
-
imageSmoothingEnabled = true;
|
|
844
|
-
// Stubs for unimplemented methods
|
|
845
|
-
drawFocusRing() { }
|
|
846
|
-
createImageData() { }
|
|
847
|
-
putImageData() { }
|
|
848
|
-
globalCompositeOperation() { }
|
|
849
|
-
createPattern() {
|
|
850
|
-
return null;
|
|
851
|
-
}
|
|
852
481
|
}
|
|
853
482
|
//# sourceMappingURL=index.js.map
|