highcharts_rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +106 -0
  9. data/Rakefile +6 -0
  10. data/highcharts_rails.gemspec +27 -0
  11. data/lib/highcharts_rails/version.rb +3 -0
  12. data/lib/highcharts_rails.rb +8 -0
  13. data/vendor/assets/javascripts/highcharts-3d.src.js +2139 -0
  14. data/vendor/assets/javascripts/highcharts-more.src.js +2982 -0
  15. data/vendor/assets/javascripts/highcharts.src.js +22947 -0
  16. data/vendor/assets/javascripts/js/highcharts-3d.src.js +2085 -0
  17. data/vendor/assets/javascripts/js/highcharts-more.src.js +2820 -0
  18. data/vendor/assets/javascripts/js/highcharts.src.js +20917 -0
  19. data/vendor/assets/javascripts/js/modules/accessibility.src.js +1072 -0
  20. data/vendor/assets/javascripts/js/modules/annotations.src.js +408 -0
  21. data/vendor/assets/javascripts/js/modules/boost.src.js +652 -0
  22. data/vendor/assets/javascripts/js/modules/broken-axis.src.js +338 -0
  23. data/vendor/assets/javascripts/js/modules/data.src.js +981 -0
  24. data/vendor/assets/javascripts/js/modules/drilldown.src.js +756 -0
  25. data/vendor/assets/javascripts/js/modules/exporting.src.js +953 -0
  26. data/vendor/assets/javascripts/js/modules/funnel.src.js +290 -0
  27. data/vendor/assets/javascripts/js/modules/gantt.src.js +791 -0
  28. data/vendor/assets/javascripts/js/modules/grid-axis.src.js +545 -0
  29. data/vendor/assets/javascripts/js/modules/heatmap.src.js +798 -0
  30. data/vendor/assets/javascripts/js/modules/no-data-to-display.src.js +150 -0
  31. data/vendor/assets/javascripts/js/modules/offline-exporting.src.js +492 -0
  32. data/vendor/assets/javascripts/js/modules/overlapping-datalabels.src.js +164 -0
  33. data/vendor/assets/javascripts/js/modules/series-label.src.js +606 -0
  34. data/vendor/assets/javascripts/js/modules/solid-gauge.src.js +305 -0
  35. data/vendor/assets/javascripts/js/modules/treemap.src.js +881 -0
  36. data/vendor/assets/javascripts/js/modules/xrange-series.src.js +254 -0
  37. data/vendor/assets/javascripts/js/themes/dark-blue.js +317 -0
  38. data/vendor/assets/javascripts/js/themes/dark-green.js +314 -0
  39. data/vendor/assets/javascripts/js/themes/dark-unica.js +243 -0
  40. data/vendor/assets/javascripts/js/themes/gray.js +326 -0
  41. data/vendor/assets/javascripts/js/themes/grid-light.js +99 -0
  42. data/vendor/assets/javascripts/js/themes/grid.js +131 -0
  43. data/vendor/assets/javascripts/js/themes/sand-signika.js +129 -0
  44. data/vendor/assets/javascripts/js/themes/skies.js +112 -0
  45. data/vendor/assets/javascripts/lib/canvg.src.js +3073 -0
  46. data/vendor/assets/javascripts/lib/jspdf.src.js +3031 -0
  47. data/vendor/assets/javascripts/lib/rgbcolor.src.js +299 -0
  48. data/vendor/assets/javascripts/lib/svg2pdf.src.js +1451 -0
  49. data/vendor/assets/javascripts/modules/accessibility.src.js +1072 -0
  50. data/vendor/assets/javascripts/modules/annotations.src.js +408 -0
  51. data/vendor/assets/javascripts/modules/boost.src.js +652 -0
  52. data/vendor/assets/javascripts/modules/broken-axis.src.js +338 -0
  53. data/vendor/assets/javascripts/modules/data.src.js +981 -0
  54. data/vendor/assets/javascripts/modules/drilldown.src.js +797 -0
  55. data/vendor/assets/javascripts/modules/exporting.src.js +882 -0
  56. data/vendor/assets/javascripts/modules/funnel.src.js +304 -0
  57. data/vendor/assets/javascripts/modules/gantt.src.js +815 -0
  58. data/vendor/assets/javascripts/modules/grid-axis.src.js +547 -0
  59. data/vendor/assets/javascripts/modules/heatmap.src.js +810 -0
  60. data/vendor/assets/javascripts/modules/no-data-to-display.src.js +161 -0
  61. data/vendor/assets/javascripts/modules/offline-exporting.src.js +492 -0
  62. data/vendor/assets/javascripts/modules/overlapping-datalabels.src.js +164 -0
  63. data/vendor/assets/javascripts/modules/series-label.src.js +606 -0
  64. data/vendor/assets/javascripts/modules/solid-gauge.src.js +316 -0
  65. data/vendor/assets/javascripts/modules/treemap.src.js +935 -0
  66. data/vendor/assets/javascripts/modules/xrange-series.src.js +276 -0
  67. data/vendor/assets/javascripts/themes/dark-blue.js +317 -0
  68. data/vendor/assets/javascripts/themes/dark-green.js +314 -0
  69. data/vendor/assets/javascripts/themes/dark-unica.js +243 -0
  70. data/vendor/assets/javascripts/themes/gray.js +326 -0
  71. data/vendor/assets/javascripts/themes/grid-light.js +99 -0
  72. data/vendor/assets/javascripts/themes/grid.js +131 -0
  73. data/vendor/assets/javascripts/themes/sand-signika.js +129 -0
  74. data/vendor/assets/javascripts/themes/skies.js +112 -0
  75. data/vendor/assets/stylesheets/highcharts.scss +610 -0
  76. metadata +161 -0
@@ -0,0 +1,1451 @@
1
+ /** @preserve
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2015 yWorks GmbH
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ */
24
+
25
+ /**
26
+ * Renders an svg element to a jsPDF document.
27
+ * For accurate results a DOM document is required (mainly used for text size measurement and image format conversion)
28
+ * @param element {HTMLElement} The svg element, which will be cloned, so the original stays unchanged.
29
+ * @param pdf {jsPDF} The jsPDF object.
30
+ * @param options {object} An object that may contain render options. Currently supported are:
31
+ * scale: The global factor by which everything is scaled.
32
+ * xOffset, yOffset: Offsets that are added to every coordinate AFTER scaling (They are not
33
+ * influenced by the scale attribute).
34
+ */
35
+ (function (global) {
36
+ var RGBColor;
37
+
38
+ var _pdf; // jsPDF pdf-document
39
+
40
+ var cToQ = 2 / 3; // ratio to convert quadratic bezier curves to cubic ones
41
+
42
+ // pathSegList is marked deprecated in chrome, so parse the d attribute manually if necessary
43
+ var getPathSegList = function (node) {
44
+ var pathSegList = node.pathSegList;
45
+ if (pathSegList) {
46
+ return pathSegList;
47
+ }
48
+
49
+ pathSegList = [];
50
+
51
+ var d = node.getAttribute("d");
52
+
53
+ var regex = /([a-df-zA-DF-Z])([^a-df-zA-DF-Z]*)/g,
54
+ match;
55
+ while (match = regex.exec(d)) {
56
+ var coords = parseFloats(match[2]);
57
+
58
+ var type = match[1];
59
+ var length = "zZ".indexOf(type) >= 0 ? 0 :
60
+ "hHvV".indexOf(type) >= 0 ? 1 :
61
+ "mMlLtT".indexOf(type) >= 0 ? 2 :
62
+ "sSqQ".indexOf(type) >= 0 ? 4 :
63
+ "cC".indexOf(type) >= 0 ? 6 : -1;
64
+
65
+ var i = 0;
66
+ do {
67
+ var pathSeg = {pathSegTypeAsLetter: type};
68
+ switch (type) {
69
+ case "h":
70
+ case "H":
71
+ pathSeg.x = coords[i];
72
+ break;
73
+
74
+ case "v":
75
+ case "V":
76
+ pathSeg.y = coords[i];
77
+ break;
78
+
79
+ case "c":
80
+ case "C":
81
+ pathSeg.x1 = coords[i + length - 6];
82
+ pathSeg.y1 = coords[i + length - 5];
83
+ case "s":
84
+ case "S":
85
+ pathSeg.x2 = coords[i + length - 4];
86
+ pathSeg.y2 = coords[i + length - 3];
87
+ case "t":
88
+ case "T":
89
+ case "l":
90
+ case "L":
91
+ case "m":
92
+ case "M":
93
+ pathSeg.x = coords[i + length - 2];
94
+ pathSeg.y = coords[i + length - 1];
95
+ break;
96
+
97
+ case "q":
98
+ case "Q":
99
+ pathSeg.x1 = coords[i];
100
+ pathSeg.y1 = coords[i + 1];
101
+ pathSeg.x = coords[i + 2];
102
+ pathSeg.y = coords[i + 3];
103
+ break;
104
+ // TODO: a,A
105
+ }
106
+
107
+ pathSegList.push(pathSeg);
108
+ i += length;
109
+ } while(i < coords.length);
110
+ }
111
+
112
+ pathSegList.getItem = function (i) {
113
+ return this[i]
114
+ };
115
+ pathSegList.numberOfItems = pathSegList.length;
116
+
117
+ return pathSegList;
118
+ };
119
+
120
+ // returns an attribute of a node, either from the node directly or from css
121
+ var getAttribute = function (node, propertyNode, propertyCss) {
122
+ propertyCss = propertyCss || propertyNode;
123
+ return node.getAttribute(propertyNode) || node.style[propertyCss];
124
+ };
125
+
126
+ var nodeIs = function (node, tagsString) {
127
+ return tagsString.split(",").indexOf(node.tagName.toLowerCase()) >= 0;
128
+ };
129
+
130
+ var forEachChild = function (node, fn) {
131
+ // copy list of children, as the original might be modified
132
+ var children = [];
133
+ for (var i = 0; i < node.childNodes.length; i++) {
134
+ var childNode = node.childNodes[i];
135
+ if (childNode.nodeName.charAt(0) !== "#")
136
+ children.push(childNode);
137
+ }
138
+ for (i = 0; i < children.length; i++) {
139
+ fn(i, children[i]);
140
+ }
141
+ };
142
+
143
+ var getAngle = function (from, to) {
144
+ return Math.atan2(to[1] - from[1], to[0] - from[0]);
145
+ };
146
+
147
+ // mirrors p1 at p2
148
+ var mirrorPoint = function (p1, p2) {
149
+ var dx = p2[0] - p1[0];
150
+ var dy = p2[1] - p1[1];
151
+
152
+ return [p1[0] + 2 * dx, p1[1] + 2 * dy];
153
+ };
154
+
155
+ // transforms a cubic bezier control point to a quadratic one: returns from + (2/3) * (to - from)
156
+ var toCubic = function (from, to) {
157
+ return [cToQ * (to[0] - from[0]) + from[0], cToQ * (to[1] - from[1]) + from[1]];
158
+ };
159
+
160
+ // extracts a control point from a previous path segment (for t,T,s,S segments)
161
+ var getControlPointFromPrevious = function (i, from, list, prevX, prevY) {
162
+ var prev = list.getItem(i - 1);
163
+ var p2;
164
+ if (i > 0 && (prev.pathSegTypeAsLetter === "C" || prev.pathSegTypeAsLetter === "S")) {
165
+ p2 = mirrorPoint([prev.x2, prev.y2], from);
166
+ } else if (i > 0 && (prev.pathSegTypeAsLetter === "c" || prev.pathSegTypeAsLetter === "s")) {
167
+ p2 = mirrorPoint([prev.x2 + prevX, prev.y2 + prevY], from);
168
+ } else {
169
+ p2 = [from[0], from[1]];
170
+ }
171
+ return p2;
172
+ };
173
+
174
+ // an id prefix to handle duplicate ids
175
+ var SvgPrefix = function (prefix) {
176
+ this.prefix = prefix;
177
+ this.id = 0;
178
+ this.nextChild = function () {
179
+ return new SvgPrefix("_" + this.id++ + "_" + this.get());
180
+ };
181
+ this.get = function () {
182
+ return this.prefix;
183
+ }
184
+ };
185
+
186
+ // returns the node for the specified id or incrementally removes prefixes to search "higher" levels
187
+ var getFromDefs = function (id, defs) {
188
+ var regExp = /_\d+_/;
189
+ while (!defs[id] && regExp.exec(id)) {
190
+ id = id.replace(regExp, "");
191
+ }
192
+ return defs[id];
193
+ };
194
+
195
+ // replace any newline characters by space and trim
196
+ var removeNewlinesAndTrim = function (str) {
197
+ return str.replace(/[\n\s\r]+/, " ").trim();
198
+ };
199
+
200
+ // clones the defs object (or basically any object)
201
+ var cloneDefs = function (defs) {
202
+ var clone = {};
203
+ for (var key in defs) {
204
+ if (defs.hasOwnProperty(key)) {
205
+ clone[key] = defs[key];
206
+ }
207
+ }
208
+ return clone;
209
+ };
210
+
211
+ // computes the transform directly applied at the node (such as viewbox scaling and the "transform" atrribute)
212
+ // x,y,cx,cy,r,... are omitted
213
+ var computeNodeTransform = function (node) {
214
+ var height, width, viewBoxHeight, viewBoxWidth, bounds, viewBox, y, x;
215
+ var nodeTransform = _pdf.unitMatrix;
216
+ if (nodeIs(node, "svg,g")) {
217
+ x = parseFloat(node.getAttribute("x")) || 0;
218
+ y = parseFloat(node.getAttribute("y")) || 0;
219
+
220
+ // jquery doesn't like camelCase notation...
221
+ viewBox = node.getAttribute("viewBox");
222
+ if (viewBox) {
223
+ bounds = parseFloats(viewBox);
224
+ viewBoxWidth = bounds[2] - bounds[0];
225
+ viewBoxHeight = bounds[3] - bounds[1];
226
+ width = parseFloat(node.getAttribute("width")) || viewBoxWidth;
227
+ height = parseFloat(node.getAttribute("height")) || viewBoxHeight;
228
+ nodeTransform = new _pdf.Matrix(width / viewBoxWidth, 0, 0, height / viewBoxHeight, x - bounds[0], y - bounds[1]);
229
+ } else {
230
+ nodeTransform = new _pdf.Matrix(1, 0, 0, 1, x, y);
231
+ }
232
+ } else if (nodeIs(node, "marker")) {
233
+ x = -parseFloat(node.getAttribute("refX")) || 0;
234
+ y = -parseFloat(node.getAttribute("refY")) || 0;
235
+
236
+ viewBox = node.getAttribute("viewBox");
237
+ if (viewBox) {
238
+ bounds = parseFloats(viewBox);
239
+ viewBoxWidth = bounds[2] - bounds[0];
240
+ viewBoxHeight = bounds[3] - bounds[1];
241
+ width = parseFloat(node.getAttribute("markerWidth")) || viewBoxWidth;
242
+ height = parseFloat(node.getAttribute("markerHeight")) || viewBoxHeight;
243
+
244
+ var s = new _pdf.Matrix(width / viewBoxWidth, 0, 0, height / viewBoxHeight, 0, 0);
245
+ var t = new _pdf.Matrix(1, 0, 0, 1, x, y);
246
+ nodeTransform = _pdf.matrixMult(t, s);
247
+ } else {
248
+ nodeTransform = new _pdf.Matrix(1, 0, 0, 1, x, y);
249
+ }
250
+ }
251
+
252
+ var transformString = node.getAttribute("transform");
253
+ if (!transformString)
254
+ return nodeTransform;
255
+ else
256
+ return _pdf.matrixMult(nodeTransform, parseTransform(transformString));
257
+ };
258
+
259
+ // parses the "points" string used by polygons and returns an array of points
260
+ var parsePointsString = function (string) {
261
+ var floats = parseFloats(string);
262
+ var result = [];
263
+ for (var i = 0; i < floats.length - 1; i += 2) {
264
+ var x = floats[i];
265
+ var y = floats[i + 1];
266
+ result.push([x, y]);
267
+ }
268
+ return result;
269
+ };
270
+
271
+ // parses the "transform" string
272
+ var parseTransform = function (transformString) {
273
+ if (!transformString)
274
+ return _pdf.unitMatrix;
275
+
276
+ var mRegex = /^\s*matrix\(([^\)]+)\)\s*/,
277
+ tRegex = /^\s*translate\(([^\)]+)\)\s*/,
278
+ rRegex = /^\s*rotate\(([^\)]+)\)\s*/,
279
+ sRegex = /^\s*scale\(([^\)]+)\)\s*/,
280
+ sXRegex = /^\s*skewX\(([^\)]+)\)\s*/,
281
+ sYRegex = /^\s*skewY\(([^\)]+)\)\s*/;
282
+
283
+ var resultMatrix = _pdf.unitMatrix, m;
284
+
285
+ while (transformString.length > 0) {
286
+ var match = mRegex.exec(transformString);
287
+ if (match) {
288
+ m = parseFloats(match[1]);
289
+ resultMatrix = _pdf.matrixMult(new _pdf.Matrix(m[0], m[1], m[2], m[3], m[4], m[5]), resultMatrix);
290
+ transformString = transformString.substr(match[0].length);
291
+ }
292
+ match = rRegex.exec(transformString);
293
+ if (match) {
294
+ m = parseFloats(match[1]);
295
+ var a = Math.PI * m[0] / 180;
296
+ resultMatrix = _pdf.matrixMult(new _pdf.Matrix(Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0), resultMatrix);
297
+ if (m[1] && m[2]) {
298
+ var t1 = new _pdf.Matrix(1, 0, 0, 1, m[1], m[2]);
299
+ var t2 = new _pdf.Matrix(1, 0, 0, 1, -m[1], -m[2]);
300
+ resultMatrix = _pdf.matrixMult(t2, _pdf.matrixMult(resultMatrix, t1));
301
+ }
302
+ transformString = transformString.substr(match[0].length);
303
+ }
304
+ match = tRegex.exec(transformString);
305
+ if (match) {
306
+ m = parseFloats(match[1]);
307
+ resultMatrix = _pdf.matrixMult(new _pdf.Matrix(1, 0, 0, 1, m[0], m[1] || 0), resultMatrix);
308
+ transformString = transformString.substr(match[0].length);
309
+ }
310
+ match = sRegex.exec(transformString);
311
+ if (match) {
312
+ m = parseFloats(match[1]);
313
+ if (!m[1])
314
+ m[1] = m[0];
315
+ resultMatrix = _pdf.matrixMult(new _pdf.Matrix(m[0], 0, 0, m[1], 0, 0), resultMatrix);
316
+ transformString = transformString.substr(match[0].length);
317
+ }
318
+ match = sXRegex.exec(transformString);
319
+ if (match) {
320
+ m = parseFloat(match[1]);
321
+ resultMatrix = _pdf.matrixMult(new _pdf.Matrix(1, 0, Math.tan(m), 1, 0, 0), resultMatrix);
322
+ transformString = transformString.substr(match[0].length);
323
+ }
324
+ match = sYRegex.exec(transformString);
325
+ if (match) {
326
+ m = parseFloat(match[1]);
327
+ resultMatrix = _pdf.matrixMult(new _pdf.Matrix(1, Math.tan(m), 0, 1, 0, 0), resultMatrix);
328
+ transformString = transformString.substr(match[0].length);
329
+ }
330
+ }
331
+ return resultMatrix;
332
+ };
333
+
334
+ // parses a comma, sign and/or whitespace separated string of floats and returns the single floats in an array
335
+ var parseFloats = function (str) {
336
+ var floats = [], match,
337
+ regex = /[+-]?(?:(?:\d+\.?\d*)|(?:\d*\.?\d+))(?:[eE][+-]?\d+)?/g;
338
+ while(match = regex.exec(str)) {
339
+ floats.push(parseFloat(match[0]));
340
+ }
341
+ return floats;
342
+ };
343
+
344
+ // extends RGBColor by rgba colors as RGBColor is not capable of it
345
+ var parseColor = function (colorString) {
346
+ var match = /\s*rgba\(((?:[^,\)]*,){3}[^,\)]*)\)\s*/.exec(colorString);
347
+ if (match) {
348
+ var floats = parseFloats(match[1]);
349
+ var color = new RGBColor("rgb(" + floats.slice(0,3).join(",") + ")");
350
+ color.a = floats[3];
351
+ return color;
352
+ } else {
353
+ return new RGBColor(colorString);
354
+ }
355
+ };
356
+
357
+ // multiplies a vector with a matrix: vec' = vec * matrix
358
+ var multVecMatrix = function (vec, matrix) {
359
+ var x = vec[0];
360
+ var y = vec[1];
361
+ return [
362
+ matrix.a * x + matrix.c * y + matrix.e,
363
+ matrix.b * x + matrix.d * y + matrix.f
364
+ ];
365
+ };
366
+
367
+ // returns the untransformed bounding box [x, y, width, height] of an svg element (quite expensive for path and polygon objects, as
368
+ // the whole points/d-string has to be processed)
369
+ var getUntransformedBBox = function (node) {
370
+ var i, minX, minY, maxX, maxY, viewBox, vb, boundingBox;
371
+ var pf = parseFloat;
372
+
373
+ if (nodeIs(node, "polygon")) {
374
+ var points = parsePointsString(node.getAttribute("points"));
375
+ minX = Number.POSITIVE_INFINITY;
376
+ minY = Number.POSITIVE_INFINITY;
377
+ maxX = Number.NEGATIVE_INFINITY;
378
+ maxY = Number.NEGATIVE_INFINITY;
379
+ for (i = 0; i < points.length; i++) {
380
+ var point = points[i];
381
+ minX = Math.min(minX, point[0]);
382
+ maxX = Math.max(maxX, point[0]);
383
+ minY = Math.min(minY, point[1]);
384
+ maxY = Math.max(maxY, point[1]);
385
+ }
386
+ boundingBox = [
387
+ minX,
388
+ minY,
389
+ maxX - minX,
390
+ maxY - minY
391
+ ];
392
+ } else if (nodeIs(node, "path")) {
393
+ var list = getPathSegList(node);
394
+ minX = Number.POSITIVE_INFINITY;
395
+ minY = Number.POSITIVE_INFINITY;
396
+ maxX = Number.NEGATIVE_INFINITY;
397
+ maxY = Number.NEGATIVE_INFINITY;
398
+ var x = 0, y = 0;
399
+ var prevX, prevY, newX, newY;
400
+ var p2, p3, to;
401
+ for (i = 0; i < list.numberOfItems; i++) {
402
+ var seg = list.getItem(i);
403
+ var cmd = seg.pathSegTypeAsLetter;
404
+ switch (cmd) {
405
+ case "H":
406
+ newX = seg.x;
407
+ newY = y;
408
+ break;
409
+ case "h":
410
+ newX = seg.x + x;
411
+ newY = y;
412
+ break;
413
+ case "V":
414
+ newX = x;
415
+ newY = seg.y;
416
+ break;
417
+ case "v":
418
+ newX = x;
419
+ newY = seg.y + y;
420
+ break;
421
+ case "C":
422
+ p2 = [seg.x1, seg.y1];
423
+ p3 = [seg.x2, seg.y2];
424
+ to = [seg.x, seg.y];
425
+ break;
426
+ case "c":
427
+ p2 = [seg.x1 + x, seg.y1 + y];
428
+ p3 = [seg.x2 + x, seg.y2 + y];
429
+ to = [seg.x + x, seg.y + y];
430
+ break;
431
+ case "S":
432
+ p2 = getControlPointFromPrevious(i, [x, y], list, prevX, prevY);
433
+ p3 = [seg.x2, seg.y2];
434
+ to = [seg.x, seg.y];
435
+ break;
436
+ case "s":
437
+ p2 = getControlPointFromPrevious(i, [x, y], list, prevX, prevY);
438
+ p3 = [seg.x2 + x, seg.y2 + y];
439
+ to = [seg.x + x, seg.y + y];
440
+ break;
441
+ case "Q":
442
+ pf = [seg.x1, seg.y1];
443
+ p2 = toCubic([x, y], pf);
444
+ p3 = toCubic([seg.x, seg.y], pf);
445
+ to = [seg.x, seg.y];
446
+ break;
447
+ case "q":
448
+ pf = [seg.x1 + x, seg.y1 + y];
449
+ p2 = toCubic([x, y], pf);
450
+ p3 = toCubic([x + seg.x, y + seg.y], pf);
451
+ to = [seg.x + x, seg.y + y];
452
+ break;
453
+ case "T":
454
+ p2 = getControlPointFromPrevious(i, [x, y], list, prevX, prevY);
455
+ p2 = toCubic([x, y], pf);
456
+ p3 = toCubic([seg.x, seg.y], pf);
457
+ to = [seg.x, seg.y];
458
+ break;
459
+ case "t":
460
+ pf = getControlPointFromPrevious(i, [x, y], list, prevX, prevY);
461
+ p2 = toCubic([x, y], pf);
462
+ p3 = toCubic([x + seg.x, y + seg.y], pf);
463
+ to = [seg.x + x, seg.y + y];
464
+ break;
465
+ // TODO: A,a
466
+ }
467
+ if ("sScCqQtT".indexOf(cmd) >= 0) {
468
+ prevX = x;
469
+ prevY = y;
470
+ }
471
+ if ("MLCSQT".indexOf(cmd) >= 0) {
472
+ x = seg.x;
473
+ y = seg.y;
474
+ } else if ("mlcsqt".indexOf(cmd) >= 0) {
475
+ x = seg.x + x;
476
+ y = seg.y + y;
477
+ } else if ("zZ".indexOf(cmd) < 0) {
478
+ x = newX;
479
+ y = newY;
480
+ }
481
+ if ("CSQTcsqt".indexOf(cmd) >= 0) {
482
+ minX = Math.min(minX, x, p2[0], p3[0], to[0]);
483
+ maxX = Math.max(maxX, x, p2[0], p3[0], to[0]);
484
+ minY = Math.min(minY, y, p2[1], p3[1], to[1]);
485
+ maxY = Math.max(maxY, y, p2[1], p3[1], to[1]);
486
+ } else {
487
+ minX = Math.min(minX, x);
488
+ maxX = Math.max(maxX, x);
489
+ minY = Math.min(minY, y);
490
+ maxY = Math.max(maxY, y);
491
+ }
492
+ }
493
+ boundingBox = [
494
+ minX,
495
+ minY,
496
+ maxX - minX,
497
+ maxY - minY
498
+ ];
499
+ } else if (nodeIs(node, "svg")) {
500
+ viewBox = node.getAttribute("viewBox");
501
+ if (viewBox) {
502
+ vb = parseFloats(viewBox);
503
+ }
504
+ return [
505
+ pf(node.getAttribute("x")) || (vb && vb[0]) || 0,
506
+ pf(node.getAttribute("y")) || (vb && vb[1]) || 0,
507
+ pf(node.getAttribute("width")) || (vb && vb[2]) || 0,
508
+ pf(node.getAttribute("height")) || (vb && vb[3]) || 0
509
+ ];
510
+ } else if (nodeIs(node, "g")) {
511
+ boundingBox = [0, 0, 0, 0];
512
+ forEachChild(node, function (i, node) {
513
+ var nodeBox = getUntransformedBBox(node);
514
+ boundingBox = [
515
+ Math.min(boundingBox[0], nodeBox[0]),
516
+ Math.min(boundingBox[1], nodeBox[1]),
517
+ Math.max(boundingBox[0] + boundingBox[2], nodeBox[0] + nodeBox[2]) - Math.min(boundingBox[0], nodeBox[0]),
518
+ Math.max(boundingBox[1] + boundingBox[3], nodeBox[1] + nodeBox[3]) - Math.min(boundingBox[1], nodeBox[1])
519
+ ];
520
+ });
521
+ } else if (nodeIs(node, "marker")) {
522
+ viewBox = node.getAttribute("viewBox");
523
+ if (viewBox) {
524
+ vb = parseFloats(viewBox);
525
+ }
526
+ return [
527
+ (vb && vb[0]) || 0,
528
+ (vb && vb[1]) || 0,
529
+ (vb && vb[2]) || pf(node.getAttribute("marker-width")) || 0,
530
+ (vb && vb[3]) || pf(node.getAttribute("marker-height")) || 0
531
+ ];
532
+ } else if (nodeIs(node, "pattern")) {
533
+ return [
534
+ pf(node.getAttribute("x")) || 0,
535
+ pf(node.getAttribute("y")) || 0,
536
+ pf(node.getAttribute("width")) || 0,
537
+ pf(node.getAttribute("height")) || 0
538
+ ]
539
+ } else {
540
+ // TODO: check if there are other possible coordinate attributes
541
+ var x1 = pf(node.getAttribute("x1")) || pf(node.getAttribute("x")) || pf((node.getAttribute("cx")) - pf(node.getAttribute("r"))) || 0;
542
+ var x2 = pf(node.getAttribute("x2")) || (x1 + pf(node.getAttribute("width"))) || (pf(node.getAttribute("cx")) + pf(node.getAttribute("r"))) || 0;
543
+ var y1 = pf(node.getAttribute("y1")) || pf(node.getAttribute("y")) || (pf(node.getAttribute("cy")) - pf(node.getAttribute("r"))) || 0;
544
+ var y2 = pf(node.getAttribute("y2")) || (y1 + pf(node.getAttribute("height"))) || (pf(node.getAttribute("cy")) + pf(node.getAttribute("r"))) || 0;
545
+ boundingBox = [
546
+ Math.min(x1, x2),
547
+ Math.min(y1, y2),
548
+ Math.max(x1, x2) - Math.min(x1, x2),
549
+ Math.max(y1, y2) - Math.min(y1, y2)
550
+ ];
551
+ }
552
+
553
+ if (!nodeIs(node, "marker,svg,g")) {
554
+ // add line-width
555
+ var lineWidth = getAttribute(node, "stroke-width") || 1;
556
+ var miterLimit = getAttribute(node, "stroke-miterlimit");
557
+ // miterLength / lineWidth = 1 / sin(phi / 2)
558
+ miterLimit && (lineWidth *= 0.5 / (Math.sin(Math.PI / 12)));
559
+ return [
560
+ boundingBox[0] - lineWidth,
561
+ boundingBox[1] - lineWidth,
562
+ boundingBox[2] + 2 * lineWidth,
563
+ boundingBox[3] + 2 * lineWidth
564
+ ];
565
+ }
566
+
567
+ return boundingBox;
568
+ };
569
+
570
+ // transforms a bounding box and returns a new rect that contains it
571
+ var transformBBox = function (box, matrix) {
572
+ var bl = multVecMatrix([box[0], box[1]], matrix);
573
+ var br = multVecMatrix([box[0] + box[2], box[1]], matrix);
574
+ var tl = multVecMatrix([box[0], box[1] + box[3]], matrix);
575
+ var tr = multVecMatrix([box[0] + box[2], box[1] + box[3]], matrix);
576
+
577
+ var bottom = Math.min(bl[1], br[1], tl[1], tr[1]);
578
+ var left = Math.min(bl[0], br[0], tl[0], tr[0]);
579
+ var top = Math.max(bl[1], br[1], tl[1], tr[1]);
580
+ var right = Math.max(bl[0], br[0], tl[0], tr[0]);
581
+
582
+ return [
583
+ left,
584
+ bottom,
585
+ right - left,
586
+ top - bottom
587
+ ]
588
+ };
589
+
590
+ // draws a polygon
591
+ var polygon = function (node, tfMatrix, colorMode, gradient, gradientMatrix) {
592
+ var points = parsePointsString(node.getAttribute("points"));
593
+ var lines = [{op: "m", c: multVecMatrix(points[0], tfMatrix)}];
594
+ for (var i = 1; i < points.length; i++) {
595
+ var p = points[i];
596
+ var to = multVecMatrix(p, tfMatrix);
597
+ lines.push({op: "l", c: to});
598
+ }
599
+ lines.push({op: "h"});
600
+ _pdf.path(lines, colorMode, gradient, gradientMatrix);
601
+ };
602
+
603
+ // draws an image (converts it to jpeg first, as jsPDF doesn't support png or other formats)
604
+ var image = function (node) {
605
+ // convert image to jpeg
606
+ var imageUrl = node.getAttribute("xlink:href") || node.getAttribute("href");
607
+ var image = new Image();
608
+ image.src = imageUrl;
609
+
610
+ var canvas = document.createElement("canvas");
611
+ var width = parseFloat(node.getAttribute("width")),
612
+ height = parseFloat(node.getAttribute("height")),
613
+ x = parseFloat(node.getAttribute("x") || 0),
614
+ y = parseFloat(node.getAttribute("y") || 0);
615
+ canvas.width = width;
616
+ canvas.height = height;
617
+ var context = canvas.getContext("2d");
618
+ context.fillStyle = "#fff";
619
+ context.fillRect(0, 0, width, height);
620
+ context.drawImage(image, 0, 0, width, height);
621
+ var jpegUrl = canvas.toDataURL("image/jpeg");
622
+
623
+ _pdf.addImage(jpegUrl,
624
+ "jpeg",
625
+ x,
626
+ y,
627
+ width,
628
+ height
629
+ );
630
+ };
631
+
632
+ // draws a path
633
+ var path = function (node, tfMatrix, svgIdPrefix, colorMode, gradient, gradientMatrix) {
634
+ var list = getPathSegList(node);
635
+ var markerEnd = node.getAttribute("marker-end"),
636
+ markerStart = node.getAttribute("marker-start"),
637
+ markerMid = node.getAttribute("marker-mid");
638
+
639
+ var getLinesFromPath = function (pathSegList, tfMatrix) {
640
+ var x = 0, y = 0;
641
+ var x0 = x, y0 = y;
642
+ var prevX, prevY, newX, newY;
643
+ var to, p, p2, p3;
644
+ var lines = [];
645
+ var markers = [];
646
+ var op;
647
+ var prevAngle = 0, curAngle;
648
+
649
+ var addMarker = function (angle, anchor, type) {
650
+ var cos = Math.cos(angle);
651
+ var sin = Math.sin(angle);
652
+ var tf;
653
+ tf = new _pdf.Matrix(cos, sin, -sin, cos, anchor[0], anchor[1]);
654
+ markers.push({type: type, tf: _pdf.matrixMult(tf, tfMatrix)});
655
+ };
656
+
657
+ for (var i = 0; i < list.numberOfItems; i++) {
658
+ var seg = list.getItem(i);
659
+ var cmd = seg.pathSegTypeAsLetter;
660
+ switch (cmd) {
661
+ case "M":
662
+ x0 = x;
663
+ y0 = y;
664
+ to = [seg.x, seg.y];
665
+ op = "m";
666
+ break;
667
+ case "m":
668
+ x0 = x;
669
+ y0 = y;
670
+ to = [seg.x + x, seg.y + y];
671
+ op = "m";
672
+ break;
673
+ case "L":
674
+ to = [seg.x, seg.y];
675
+ op = "l";
676
+ break;
677
+ case "l":
678
+ to = [seg.x + x, seg.y + y];
679
+ op = "l";
680
+ break;
681
+ case "H":
682
+ to = [seg.x, y];
683
+ op = "l";
684
+ newX = seg.x;
685
+ newY = y;
686
+ break;
687
+ case "h":
688
+ to = [seg.x + x, y];
689
+ op = "l";
690
+ newX = seg.x + x;
691
+ newY = y;
692
+ break;
693
+ case "V":
694
+ to = [x, seg.y];
695
+ op = "l";
696
+ newX = x;
697
+ newY = seg.y;
698
+ break;
699
+ case "v":
700
+ to = [x, seg.y + y];
701
+ op = "l";
702
+ newX = x;
703
+ newY = seg.y + y;
704
+ break;
705
+ case "C":
706
+ p2 = [seg.x1, seg.y1];
707
+ p3 = [seg.x2, seg.y2];
708
+ to = [seg.x, seg.y];
709
+ break;
710
+ case "c":
711
+ p2 = [seg.x1 + x, seg.y1 + y];
712
+ p3 = [seg.x2 + x, seg.y2 + y];
713
+ to = [seg.x + x, seg.y + y];
714
+ break;
715
+ case "S":
716
+ p2 = getControlPointFromPrevious(i, [x, y], list, prevX, prevY);
717
+ p3 = [seg.x2, seg.y2];
718
+ to = [seg.x, seg.y];
719
+ break;
720
+ case "s":
721
+ p2 = getControlPointFromPrevious(i, [x, y], list, prevX, prevY);
722
+ p3 = [seg.x2 + x, seg.y2 + y];
723
+ to = [seg.x + x, seg.y + y];
724
+ break;
725
+ case "Q":
726
+ p = [seg.x1, seg.y1];
727
+ p2 = toCubic([x, y], p);
728
+ p3 = toCubic([seg.x, seg.y], p);
729
+ to = [seg.x, seg.y];
730
+ break;
731
+ case "q":
732
+ p = [seg.x1 + x, seg.y1 + y];
733
+ p2 = toCubic([x, y], p);
734
+ p3 = toCubic([x + seg.x, y + seg.y], p);
735
+ to = [seg.x + x, seg.y + y];
736
+ break;
737
+ case "T":
738
+ p2 = getControlPointFromPrevious(i, [x, y], list, prevX, prevY);
739
+ p2 = toCubic([x, y], p);
740
+ p3 = toCubic([seg.x, seg.y], p);
741
+ to = [seg.x, seg.y];
742
+ break;
743
+ case "t":
744
+ p = getControlPointFromPrevious(i, [x, y], list, prevX, prevY);
745
+ p2 = toCubic([x, y], p);
746
+ p3 = toCubic([x + seg.x, y + seg.y], p);
747
+ to = [seg.x + x, seg.y + y];
748
+ break;
749
+ // TODO: A,a
750
+ case "Z":
751
+ case "z":
752
+ x = x0;
753
+ y = y0;
754
+ lines.push({op: "h"});
755
+ break;
756
+ }
757
+
758
+ var hasStartMarker = markerStart
759
+ && (i === 1
760
+ || ("mM".indexOf(cmd) < 0 && "mM".indexOf(list.getItem(i - 1).pathSegTypeAsLetter) >= 0));
761
+ var hasEndMarker = markerEnd
762
+ && (i === list.numberOfItems - 1
763
+ || ("mM".indexOf(cmd) < 0 && "mM".indexOf(list.getItem(i + 1).pathSegTypeAsLetter) >= 0));
764
+ var hasMidMarker = markerMid
765
+ && i > 0
766
+ && !(i === 1 && "mM".indexOf(list.getItem(i - 1).pathSegTypeAsLetter) >= 0);
767
+
768
+ if ("sScCqQtT".indexOf(cmd) >= 0) {
769
+ hasStartMarker && addMarker(getAngle([x, y], p2), [x, y], "start");
770
+ hasEndMarker && addMarker(getAngle(p3, to), to, "end");
771
+ if (hasMidMarker) {
772
+ curAngle = getAngle([x, y], p2);
773
+ curAngle = "mM".indexOf(list.getItem(i - 1).pathSegTypeAsLetter) >= 0 ?
774
+ curAngle : .5 * (prevAngle + curAngle);
775
+ addMarker(curAngle, [x, y], "mid");
776
+ }
777
+
778
+ prevAngle = getAngle(p3, to);
779
+
780
+ prevX = x;
781
+ prevY = y;
782
+ p2 = multVecMatrix(p2, tfMatrix);
783
+ p3 = multVecMatrix(p3, tfMatrix);
784
+ p = multVecMatrix(to, tfMatrix);
785
+ lines.push({
786
+ op: "c", c: [
787
+ p2[0], p2[1],
788
+ p3[0], p3[1],
789
+ p[0], p[1]
790
+ ]
791
+ });
792
+ } else if ("lLhHvVmM".indexOf(cmd) >= 0) {
793
+ curAngle = getAngle([x, y], to);
794
+ hasStartMarker && addMarker(curAngle, [x, y], "start");
795
+ hasEndMarker && addMarker(curAngle, to, "end");
796
+ if (hasMidMarker) {
797
+ var angle = "mM".indexOf(cmd) >= 0 ?
798
+ prevAngle : "mM".indexOf(list.getItem(i - 1).pathSegTypeAsLetter) >= 0 ?
799
+ curAngle : .5 * (prevAngle + curAngle);
800
+ addMarker(angle, [x, y], "mid");
801
+ }
802
+ prevAngle = curAngle;
803
+
804
+ p = multVecMatrix(to, tfMatrix);
805
+ lines.push({op: op, c: p});
806
+ }
807
+
808
+ if ("MLCSQT".indexOf(cmd) >= 0) {
809
+ x = seg.x;
810
+ y = seg.y;
811
+ } else if ("mlcsqt".indexOf(cmd) >= 0) {
812
+ x = seg.x + x;
813
+ y = seg.y + y;
814
+ } else if ("zZ".indexOf(cmd) < 0) {
815
+ x = newX;
816
+ y = newY;
817
+ }
818
+ }
819
+
820
+ return {lines: lines, markers: markers};
821
+ };
822
+ var lines = getLinesFromPath(list, tfMatrix);
823
+
824
+ if (markerEnd || markerStart || markerMid) {
825
+ for (var i = 0; i < lines.markers.length; i++) {
826
+ var marker = lines.markers[i];
827
+ var markerElement;
828
+ switch (marker.type) {
829
+ case "start":
830
+ markerElement = svgIdPrefix.get() + /url\(#(\w+)\)/.exec(markerStart)[1];
831
+ break;
832
+ case "end":
833
+ markerElement = svgIdPrefix.get() + /url\(#(\w+)\)/.exec(markerEnd)[1];
834
+ break;
835
+ case "mid":
836
+ markerElement = svgIdPrefix.get() + /url\(#(\w+)\)/.exec(markerMid)[1];
837
+ break;
838
+ }
839
+ _pdf.doFormObject(markerElement, marker.tf);
840
+ }
841
+ }
842
+
843
+ if (lines.lines.length > 0) {
844
+ _pdf.path(lines.lines, colorMode, gradient, gradientMatrix);
845
+ }
846
+ };
847
+
848
+ // draws the element referenced by a use node, makes use of pdf's XObjects/FormObjects so nodes are only written once
849
+ // to the pdf document. This highly reduces the file size and computation time.
850
+ var use = function (node, tfMatrix, svgIdPrefix) {
851
+ var url = (node.getAttribute("href") || node.getAttribute("xlink:href"));
852
+ // just in case someone has the idea to use empty use-tags, wtf???
853
+ if (!url)
854
+ return;
855
+
856
+ // get the size of the referenced form object (to apply the correct scaling)
857
+ var formObject = _pdf.getFormObject(svgIdPrefix.get() + url.substring(1));
858
+
859
+ // scale and position it right
860
+ var x = node.getAttribute("x") || 0;
861
+ var y = node.getAttribute("y") || 0;
862
+ var width = node.getAttribute("width") || formObject.width;
863
+ var height = node.getAttribute("height") || formObject.height;
864
+ var t = new _pdf.Matrix(width / formObject.width || 0, 0, 0, height / formObject.height || 0, x, y);
865
+ t = _pdf.matrixMult(t, tfMatrix);
866
+ _pdf.doFormObject(svgIdPrefix.get() + url.substring(1), t);
867
+ };
868
+
869
+ // draws a line
870
+ var line = function (node, tfMatrix) {
871
+ var p1 = multVecMatrix([parseFloat(node.getAttribute('x1')), parseFloat(node.getAttribute('y1'))], tfMatrix);
872
+ var p2 = multVecMatrix([parseFloat(node.getAttribute('x2')), parseFloat(node.getAttribute('y2'))], tfMatrix);
873
+ _pdf.line(p1[0], p1[1], p2[0], p2[1]);
874
+ };
875
+
876
+ // draws a rect
877
+ var rect = function (node, colorMode, gradient, gradientMatrix) {
878
+ _pdf.roundedRect(
879
+ parseFloat(node.getAttribute('x')) || 0,
880
+ parseFloat(node.getAttribute('y')) || 0,
881
+ parseFloat(node.getAttribute('width')),
882
+ parseFloat(node.getAttribute('height')),
883
+ parseFloat(node.getAttribute('rx')) || 0,
884
+ parseFloat(node.getAttribute('ry')) || 0,
885
+ colorMode,
886
+ gradient,
887
+ gradientMatrix
888
+ );
889
+ };
890
+
891
+ // draws an ellipse
892
+ var ellipse = function (node, colorMode, gradient, gradientMatrix) {
893
+ _pdf.ellipse(
894
+ parseFloat(node.getAttribute('cx')) || 0,
895
+ parseFloat(node.getAttribute('cy')) || 0,
896
+ parseFloat(node.getAttribute('rx')),
897
+ parseFloat(node.getAttribute('ry')),
898
+ colorMode,
899
+ gradient,
900
+ gradientMatrix
901
+ );
902
+ };
903
+
904
+ // draws a circle
905
+ var circle = function (node, colorMode, gradient, gradientMatrix) {
906
+ var radius = parseFloat(node.getAttribute('r')) || 0;
907
+ _pdf.ellipse(
908
+ parseFloat(node.getAttribute('cx')) || 0,
909
+ parseFloat(node.getAttribute('cy')) || 0,
910
+ radius,
911
+ radius,
912
+ colorMode,
913
+ gradient,
914
+ gradientMatrix
915
+ );
916
+ };
917
+
918
+ // applies text transformations to a text node
919
+ var transformText = function (node, text) {
920
+ var textTransform = getAttribute(node, "text-transform");
921
+ switch (textTransform) {
922
+ case "uppercase": return text.toUpperCase();
923
+ case "lowercase": return text.toLowerCase();
924
+ default: return text;
925
+ // TODO: capitalize, full-width
926
+ }
927
+ };
928
+
929
+ // draws a text element and its tspan children
930
+ var text = function (node, tfMatrix, hasFillColor, fillRGB) {
931
+ _pdf.saveGraphicsState();
932
+ setTextProperties(node, fillRGB);
933
+
934
+ var getTextOffset = function (textAnchor, width) {
935
+ var xOffset = 0;
936
+ switch (textAnchor) {
937
+ case 'end':
938
+ xOffset = width;
939
+ break;
940
+ case 'middle':
941
+ xOffset = width / 2;
942
+ break;
943
+ case 'start':
944
+ break;
945
+ }
946
+ return xOffset;
947
+ };
948
+
949
+ /**
950
+ * Convert em, px and bare number attributes to pixel values
951
+ */
952
+ var toPixels = function (value, pdfFontSize) {
953
+ var match;
954
+
955
+ // em
956
+ match = value && value.toString().match(/^([\-0-9.]+)em$/);
957
+ if (match) {
958
+ return parseFloat(match[1]) * pdfFontSize;
959
+ }
960
+
961
+ // pixels
962
+ match = value && value.toString().match(/^([\-0-9.]+)(px|)$/);
963
+ if (match) {
964
+ return parseFloat(match[1]);
965
+ }
966
+ return 0;
967
+ };
968
+
969
+ // creates an svg element and append the text node to properly measure the text size
970
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
971
+ svg.appendChild(node);
972
+ svg.setAttribute("visibility", "hidden");
973
+ document.body.appendChild(svg);
974
+
975
+ var box = node.getBBox();
976
+ var x, y, xOffset = 0;
977
+ var textAnchor = getAttribute(node, "text-anchor");
978
+ if (textAnchor) {
979
+ xOffset = getTextOffset(textAnchor, box.width);
980
+ }
981
+
982
+ var pdfFontSize = _pdf.getFontSize();
983
+ var textX = toPixels(node.getAttribute('x'), pdfFontSize);
984
+ var textY = toPixels(node.getAttribute('y'), pdfFontSize);
985
+ var m = _pdf.matrixMult(new _pdf.Matrix(1, 0, 0, 1, textX, textY), tfMatrix);
986
+
987
+ x = toPixels(node.getAttribute("dx"), pdfFontSize);
988
+ y = toPixels(node.getAttribute("dy"), pdfFontSize);
989
+
990
+ // when there are no tspans draw the text directly
991
+ if (node.childElementCount === 0) {
992
+ _pdf.text(
993
+ (x - xOffset),
994
+ y,
995
+ transformText(node, removeNewlinesAndTrim(node.textContent)),
996
+ void 0,
997
+ m
998
+ );
999
+ } else {
1000
+ // otherwise loop over tspans and position each relative to the previous one
1001
+ forEachChild(node, function (i, tSpan) {
1002
+ _pdf.saveGraphicsState();
1003
+ var tSpanColor = getAttribute(tSpan, "fill");
1004
+ setTextProperties(tSpan, tSpanColor && new RGBColor(tSpanColor));
1005
+ var extent = tSpan.getExtentOfChar(0);
1006
+ _pdf.text(
1007
+ extent.x - textX,//x - xOffset,
1008
+ extent.y + extent.height * 0.7 - textY, // 0.7 roughly mimicks the text baseline
1009
+ transformText(node, removeNewlinesAndTrim(tSpan.textContent)),
1010
+ void 0,
1011
+ m
1012
+ );
1013
+
1014
+ _pdf.restoreGraphicsState();
1015
+ });
1016
+
1017
+ }
1018
+
1019
+ document.body.removeChild(svg);
1020
+ _pdf.restoreGraphicsState();
1021
+ };
1022
+
1023
+ // As defs elements are allowed to appear after they are referenced, we search for them first
1024
+ var findAndRenderDefs = function (node, tfMatrix, defs, svgIdPrefix, withinDefs) {
1025
+ forEachChild(node, function (i, child) {
1026
+ if (child.tagName.toLowerCase() === "defs") {
1027
+ renderNode(child, tfMatrix, defs, svgIdPrefix, withinDefs);
1028
+ // prevent defs from being evaluated twice // TODO: make this better
1029
+ child.parentNode.removeChild(child);
1030
+ }
1031
+ });
1032
+ };
1033
+
1034
+ // processes a svg node
1035
+ var svg = function (node, tfMatrix, defs, svgIdPrefix, withinDefs) {
1036
+ // create a new prefix and clone the defs, as defs within the svg should not be visible outside
1037
+ var newSvgIdPrefix = svgIdPrefix.nextChild();
1038
+ var newDefs = cloneDefs(defs);
1039
+ findAndRenderDefs(node, tfMatrix, newDefs, newSvgIdPrefix, withinDefs);
1040
+ renderChildren(node, tfMatrix, newDefs, newSvgIdPrefix, withinDefs);
1041
+ };
1042
+
1043
+ // renders all children of a node
1044
+ var renderChildren = function (node, tfMatrix, defs, svgIdPrefix, withinDefs) {
1045
+ forEachChild(node, function (i, node) {
1046
+ renderNode(node, tfMatrix, defs, svgIdPrefix, withinDefs);
1047
+ });
1048
+ };
1049
+
1050
+ // adds a gradient to defs and the pdf document for later use, type is either "axial" or "radial"
1051
+ // opacity is only supported rudimentary by averaging over all stops
1052
+ // transforms are applied on use
1053
+ var putGradient = function (node, type, coords, defs, svgIdPrefix) {
1054
+ var colors = [];
1055
+ var opacitySum = 0;
1056
+ var hasOpacity = false;
1057
+ var gState;
1058
+ forEachChild(node, function (i, element) {
1059
+ // since opacity gradients are hard to realize, average the opacity over the control points
1060
+ if (element.tagName.toLowerCase() === "stop") {
1061
+ var color = new RGBColor(getAttribute(element, "stop-color"));
1062
+ colors.push({
1063
+ offset: parseFloat(element.getAttribute("offset")),
1064
+ color: [color.r, color.g, color.b]
1065
+ });
1066
+ var opacity = getAttribute(element, "stop-opacity");
1067
+ if (opacity && opacity != 1) {
1068
+ opacitySum += parseFloat(opacity);
1069
+ hasOpacity = true;
1070
+ }
1071
+ }
1072
+ });
1073
+
1074
+ if (hasOpacity) {
1075
+ gState = new _pdf.GState({opacity: opacitySum / coords.length});
1076
+ }
1077
+
1078
+ var pattern = new _pdf.ShadingPattern(type, coords, colors, gState);
1079
+ var id = svgIdPrefix.get() + node.getAttribute("id");
1080
+ _pdf.addShadingPattern(id, pattern);
1081
+ defs[id] = node;
1082
+ };
1083
+
1084
+ var pattern = function (node, defs, svgIdPrefix) {
1085
+ var id = svgIdPrefix.get() + node.getAttribute("id");
1086
+ defs[id] = node;
1087
+
1088
+ // the transformations directly at the node are written to the pattern transformation matrix
1089
+ var bBox = getUntransformedBBox(node);
1090
+ var pattern = new _pdf.TilingPattern([bBox[0], bBox[1], bBox[0] + bBox[2], bBox[1] + bBox[3]], bBox[2], bBox[3],
1091
+ null, computeNodeTransform(node));
1092
+
1093
+ _pdf.beginTilingPattern(pattern);
1094
+ // continue without transformation
1095
+ renderChildren(node, _pdf.unitMatrix, defs, svgIdPrefix, false);
1096
+ _pdf.endTilingPattern(id, pattern);
1097
+ };
1098
+
1099
+ function setTextProperties(node, fillRGB) {
1100
+ var fontFamily = getAttribute(node, "font-family");
1101
+ if (fontFamily) {
1102
+ _pdf.setFont(fontFamily);
1103
+ }
1104
+
1105
+ if (fillRGB && fillRGB.ok) {
1106
+ _pdf.setTextColor(fillRGB.r, fillRGB.g, fillRGB.b);
1107
+ }
1108
+
1109
+ var fontType;
1110
+ var fontWeight = getAttribute(node, "font-weight");
1111
+ if (fontWeight) {
1112
+ if (fontWeight === "bold") {
1113
+ fontType = "bold";
1114
+ }
1115
+ }
1116
+
1117
+ var fontStyle = getAttribute(node, "font-style");
1118
+ if (fontStyle) {
1119
+ if (fontStyle === "italic") {
1120
+ fontType += "italic";
1121
+ }
1122
+ }
1123
+ _pdf.setFontType(fontType);
1124
+
1125
+ var pdfFontSize = 16;
1126
+ var fontSize = getAttribute(node, "font-size");
1127
+ if (fontSize) {
1128
+ pdfFontSize = parseFloat(fontSize);
1129
+ _pdf.setFontSize(pdfFontSize);
1130
+ }
1131
+ }
1132
+
1133
+
1134
+ /**
1135
+ * Renders a svg node.
1136
+ * @param node The svg element
1137
+ * @param contextTransform The current transformation matrix
1138
+ * @param defs The defs map holding all svg nodes that can be referenced
1139
+ * @param svgIdPrefix The current id prefix
1140
+ * @param withinDefs True iff we are top-level within a defs node, so the target can be switched to an pdf form object
1141
+ */
1142
+ var renderNode = function (node, contextTransform, defs, svgIdPrefix, withinDefs) {
1143
+ var tfMatrix,
1144
+ hasFillColor = false,
1145
+ fillRGB = null,
1146
+ colorMode = null,
1147
+ fillUrl = null,
1148
+ fillData = null,
1149
+ bBox;
1150
+
1151
+ //
1152
+ // Decide about the render target and set the correct transformation
1153
+ //
1154
+
1155
+ // if we are within a defs node, start a new pdf form object and draw this node and all children on that instead
1156
+ // of the top-level page
1157
+ var targetIsFormObject = withinDefs && !nodeIs(node, "lineargradient,radialgradient,pattern");
1158
+ if (targetIsFormObject) {
1159
+
1160
+ // the transformations directly at the node are written to the pdf form object transformation matrix
1161
+ tfMatrix = computeNodeTransform(node);
1162
+ bBox = getUntransformedBBox(node);
1163
+
1164
+ _pdf.beginFormObject(bBox[0], bBox[1], bBox[2], bBox[3], tfMatrix);
1165
+
1166
+ // continue without transformation and set withinDefs to false to prevent child nodes from starting new form objects
1167
+ tfMatrix = _pdf.unitMatrix;
1168
+ withinDefs = false;
1169
+
1170
+ } else {
1171
+ tfMatrix = _pdf.matrixMult(computeNodeTransform(node), contextTransform);
1172
+ _pdf.saveGraphicsState();
1173
+ }
1174
+
1175
+ //
1176
+ // extract fill and stroke mode
1177
+ //
1178
+
1179
+ // fill mode
1180
+ if (nodeIs(node, "g,path,rect,text,ellipse,line,circle,polygon")) {
1181
+ function setDefaultColor() {
1182
+ fillRGB = new RGBColor("rgb(0, 0, 0)");
1183
+ hasFillColor = true;
1184
+ colorMode = "F";
1185
+ }
1186
+
1187
+ var fillColor = getAttribute(node, "fill");
1188
+ if (fillColor) {
1189
+ var url = /url\(#(\w+)\)/.exec(fillColor);
1190
+ if (url) {
1191
+ // probably a gradient (or something unsupported)
1192
+ fillUrl = svgIdPrefix.get() + url[1];
1193
+ var fill = getFromDefs(fillUrl, defs);
1194
+ if (fill && nodeIs(fill, "lineargradient,radialgradient")) {
1195
+
1196
+ // matrix to convert between gradient space and user space
1197
+ // for "userSpaceOnUse" this is the current transformation: tfMatrix
1198
+ // for "objectBoundingBox" or default, the gradient gets scaled and transformed to the bounding box
1199
+ var gradientUnitsMatrix = tfMatrix;
1200
+ if (!fill.hasAttribute("gradientUnits")
1201
+ || fill.getAttribute("gradientUnits").toLowerCase() === "objectboundingbox") {
1202
+ bBox || (bBox = getUntransformedBBox(node));
1203
+ gradientUnitsMatrix = new _pdf.Matrix(bBox[2], 0, 0, bBox[3], bBox[0], bBox[1]);
1204
+
1205
+ var nodeTransform = computeNodeTransform(node);
1206
+ gradientUnitsMatrix = _pdf.matrixMult(gradientUnitsMatrix, nodeTransform);
1207
+ }
1208
+
1209
+ // matrix that is applied to the gradient before any other transformations
1210
+ var gradientTransform = parseTransform(fill.getAttribute("gradientTransform"));
1211
+
1212
+ fillData = _pdf.matrixMult(gradientTransform, gradientUnitsMatrix);
1213
+ } else if (fill && nodeIs(fill, "pattern")) {
1214
+ var fillBBox, y, width, height, x;
1215
+ fillData = {};
1216
+
1217
+ var patternUnitsMatrix = _pdf.unitMatrix;
1218
+ if (!fill.hasAttribute("patternUnits")
1219
+ || fill.getAttribute("patternUnits").toLowerCase() === "objectboundingbox") {
1220
+ bBox || (bBox = getUntransformedBBox(node));
1221
+ patternUnitsMatrix = new _pdf.Matrix(1, 0, 0, 1, bBox[0], bBox[1]);
1222
+
1223
+ // TODO: slightly inaccurate (rounding errors? line width bBoxes?)
1224
+ fillBBox = getUntransformedBBox(fill);
1225
+ x = fillBBox[0] * bBox[0];
1226
+ y = fillBBox[1] * bBox[1];
1227
+ width = fillBBox[2] * bBox[2];
1228
+ height = fillBBox[3] * bBox[3];
1229
+ fillData.boundingBox = [x, y, x + width, y + height];
1230
+ fillData.xStep = width;
1231
+ fillData.yStep = height;
1232
+ }
1233
+
1234
+ var patternContentUnitsMatrix = _pdf.unitMatrix;
1235
+ if (fill.hasAttribute("patternContentUnits")
1236
+ && fill.getAttribute("patternContentUnits").toLowerCase() === "objectboundingbox") {
1237
+ bBox || (bBox = getUntransformedBBox(node));
1238
+ patternContentUnitsMatrix = new _pdf.Matrix(bBox[2], 0, 0, bBox[3], 0, 0);
1239
+
1240
+ fillBBox = fillData.boundingBox || getUntransformedBBox(fill);
1241
+ x = fillBBox[0] / bBox[0];
1242
+ y = fillBBox[1] / bBox[1];
1243
+ width = fillBBox[2] / bBox[2];
1244
+ height = fillBBox[3] / bBox[3];
1245
+ fillData.boundingBox = [x, y, x + width, y + height];
1246
+ fillData.xStep = width;
1247
+ fillData.yStep = height;
1248
+ }
1249
+
1250
+ fillData.matrix = _pdf.matrixMult(
1251
+ _pdf.matrixMult(patternContentUnitsMatrix, patternUnitsMatrix), tfMatrix);
1252
+
1253
+ colorMode = "F";
1254
+ } else {
1255
+ // unsupported fill argument (e.g. patterns) -> fill black
1256
+ fillUrl = fill = null;
1257
+ setDefaultColor();
1258
+ }
1259
+ } else {
1260
+ // plain color
1261
+ fillRGB = parseColor(fillColor);
1262
+ if (fillRGB.ok) {
1263
+ hasFillColor = true;
1264
+ colorMode = 'F';
1265
+ } else {
1266
+ colorMode = null;
1267
+ }
1268
+ }
1269
+ } else {
1270
+ // if no fill attribute is provided the default fill color is black
1271
+ setDefaultColor();
1272
+ }
1273
+
1274
+ // opacity is realized via a pdf graphics state
1275
+ var opacity = 1.0;
1276
+ var nodeOpacity = node.getAttribute("opacity") || node.getAttribute("fill-opacity");
1277
+ if (nodeOpacity) {
1278
+ opacity *= parseFloat(nodeOpacity);
1279
+ }
1280
+ if (fillRGB && typeof fillRGB.a === "number") {
1281
+ opacity *= fillRGB.a;
1282
+ }
1283
+ _pdf.setGState(new _pdf.GState({opacity: opacity}));
1284
+ }
1285
+
1286
+ if (nodeIs(node, "g,path,rect,ellipse,line,circle,polygon")) {
1287
+ // text has no fill color, so don't apply it until here
1288
+ if (hasFillColor) {
1289
+ _pdf.setFillColor(fillRGB.r, fillRGB.g, fillRGB.b);
1290
+ }
1291
+
1292
+ // stroke mode
1293
+ var strokeColor = node.getAttribute('stroke');
1294
+ if (strokeColor) {
1295
+ var strokeWidth;
1296
+ if (node.hasAttribute("stroke-width")) {
1297
+ strokeWidth = Math.abs(parseFloat(node.getAttribute('stroke-width')));
1298
+ _pdf.setLineWidth(strokeWidth);
1299
+ }
1300
+ var strokeRGB = new RGBColor(strokeColor);
1301
+ if (strokeRGB.ok) {
1302
+ _pdf.setDrawColor(strokeRGB.r, strokeRGB.g, strokeRGB.b);
1303
+ if (strokeWidth !== 0) {
1304
+ // pdf spec states: "A line width of 0 denotes the thinnest line that can be rendered at device resolution:
1305
+ // 1 device pixel wide". SVG, however, does not draw zero width lines.
1306
+ colorMode = (colorMode || "") + "D";
1307
+ }
1308
+ }
1309
+ if (node.hasAttribute("stroke-linecap")) {
1310
+ _pdf.setLineCap(node.getAttribute("stroke-linecap"));
1311
+ }
1312
+ if (node.hasAttribute("stroke-linejoin")) {
1313
+ _pdf.setLineJoin(node.getAttribute("stroke-linejoin"));
1314
+ }
1315
+ if (node.hasAttribute("stroke-dasharray")) {
1316
+ _pdf.setLineDashPattern(
1317
+ parseFloats(node.getAttribute("stroke-dasharray")),
1318
+ parseInt(node.getAttribute("stroke-dashoffset")) || 0
1319
+ );
1320
+ }
1321
+ if (node.hasAttribute("stroke-miterlimit")) {
1322
+ _pdf.setLineMiterLimit(parseFloat(node.getAttribute("stroke-miterlimit")));
1323
+ }
1324
+ }
1325
+ }
1326
+
1327
+ setTextProperties(node, fillRGB);
1328
+
1329
+ // do the actual drawing
1330
+ switch (node.tagName.toLowerCase()) {
1331
+ case 'svg':
1332
+ svg(node, tfMatrix, defs, svgIdPrefix, withinDefs);
1333
+ break;
1334
+ case 'g':
1335
+ findAndRenderDefs(node, tfMatrix, defs, svgIdPrefix, withinDefs);
1336
+ case 'a':
1337
+ case "marker":
1338
+ renderChildren(node, tfMatrix, defs, svgIdPrefix, withinDefs);
1339
+ break;
1340
+
1341
+ case 'defs':
1342
+ renderChildren(node, tfMatrix, defs, svgIdPrefix, true);
1343
+ break;
1344
+
1345
+ case 'use':
1346
+ use(node, tfMatrix, svgIdPrefix);
1347
+ break;
1348
+
1349
+ case 'line':
1350
+ line(node, tfMatrix);
1351
+ break;
1352
+
1353
+ case 'rect':
1354
+ _pdf.setCurrentTransformationMatrix(tfMatrix);
1355
+ rect(node, colorMode, fillUrl, fillData);
1356
+ break;
1357
+
1358
+ case 'ellipse':
1359
+ _pdf.setCurrentTransformationMatrix(tfMatrix);
1360
+ ellipse(node, colorMode, fillUrl, fillData);
1361
+ break;
1362
+
1363
+ case 'circle':
1364
+ _pdf.setCurrentTransformationMatrix(tfMatrix);
1365
+ circle(node, colorMode, fillUrl, fillData);
1366
+ break;
1367
+ case 'text':
1368
+ text(node, tfMatrix, hasFillColor, fillRGB);
1369
+ break;
1370
+
1371
+ case 'path':
1372
+ path(node, tfMatrix, svgIdPrefix, colorMode, fillUrl, fillData);
1373
+ break;
1374
+
1375
+ case 'polygon':
1376
+ polygon(node, tfMatrix, colorMode, fillUrl, fillData);
1377
+ break;
1378
+
1379
+ case 'image':
1380
+ _pdf.setCurrentTransformationMatrix(tfMatrix);
1381
+ image(node);
1382
+ break;
1383
+
1384
+ case "lineargradient":
1385
+ putGradient(node, "axial", [
1386
+ node.getAttribute("x1"),
1387
+ node.getAttribute("y1"),
1388
+ node.getAttribute("x2"),
1389
+ node.getAttribute("y2")
1390
+ ], defs, svgIdPrefix);
1391
+ break;
1392
+
1393
+ case "radialgradient":
1394
+ putGradient(node, "radial", [
1395
+ node.getAttribute("fx") || node.getAttribute("cx"),
1396
+ node.getAttribute("fy") || node.getAttribute("cy"),
1397
+ 0,
1398
+ node.getAttribute("cx") || 0,
1399
+ node.getAttribute("cy") || 0,
1400
+ node.getAttribute("r") || 0
1401
+ ], defs, svgIdPrefix);
1402
+ break;
1403
+
1404
+ case "pattern":
1405
+ pattern(node, defs, svgIdPrefix);
1406
+ break;
1407
+ }
1408
+
1409
+ // close either the formObject or the graphics context
1410
+ if (targetIsFormObject) {
1411
+ _pdf.endFormObject(svgIdPrefix.get() + node.getAttribute("id"));
1412
+ } else {
1413
+ _pdf.restoreGraphicsState();
1414
+ }
1415
+ };
1416
+
1417
+ // the actual svgToPdf function (see above)
1418
+ var svg2pdf = function (element, pdf, options) {
1419
+ _pdf = pdf;
1420
+
1421
+ var k = options.scale || 1.0,
1422
+ xOffset = options.xOffset || 0.0,
1423
+ yOffset = options.yOffset || 0.0;
1424
+
1425
+ // set offsets and scale everything by k
1426
+ _pdf.saveGraphicsState();
1427
+ _pdf.setCurrentTransformationMatrix(new _pdf.Matrix(k, 0, 0, k, xOffset, yOffset));
1428
+
1429
+ renderNode(element.cloneNode(true), _pdf.unitMatrix, {}, new SvgPrefix(""), false);
1430
+
1431
+ _pdf.restoreGraphicsState();
1432
+
1433
+ return _pdf;
1434
+ };
1435
+
1436
+ if (typeof define === "function" && define.amd) {
1437
+ define(["rgbcolor"], function (rgbcolor) {
1438
+ RGBColor = rgbcolor;
1439
+ return svg2pdf;
1440
+ });
1441
+ } else if (typeof module !== "undefined" && module.exports) {
1442
+ RGBColor = require("./rgbcolor.js");
1443
+ module.exports = svg2pdf;
1444
+ } else {
1445
+ RGBColor = global.RGBColor;
1446
+ global.svg2pdf = svg2pdf;
1447
+ // for compatibility reasons
1448
+ global.svgElementToPdf = svg2pdf;
1449
+ }
1450
+ return svg2pdf;
1451
+ }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this));