highcharts_rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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));