pdfmake 0.3.0 → 0.3.2

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.
@@ -60,7 +60,8 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
60
60
  'display': {inherit: false, initial: 'inline', values: {'none':'none', 'inline':'inline', 'block':'inline'}},
61
61
  'clip-path': {inherit: false, initial: 'none'},
62
62
  'mask': {inherit: false, initial: 'none'},
63
- 'overflow': {inherit: false, initial: 'hidden', values: {'hidden':'hidden', 'scroll':'hidden', 'visible':'visible'}}
63
+ 'overflow': {inherit: false, initial: 'hidden', values: {'hidden':'hidden', 'scroll':'hidden', 'visible':'visible'}},
64
+ 'vector-effect': {inherit: true, initial: 'none', values: {'none':'none', 'non-scaling-stroke':'non-scaling-stroke'}}
64
65
  };
65
66
 
66
67
  function docBeginGroup(bbox) {
@@ -113,11 +114,13 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
113
114
  doc.addContent('/' + name + ' gs');
114
115
  }
115
116
  function docCreatePattern(group, dx, dy, matrix) {
116
- let pattern = new (function PDFPattern() {})();
117
- pattern.group = group;
118
- pattern.dx = dx;
119
- pattern.dy = dy;
120
- pattern.matrix = matrix || [1, 0, 0, 1, 0, 0];
117
+ let pattern = {
118
+ type: 'PDFPattern',
119
+ group: group,
120
+ dx: dx,
121
+ dy: dy,
122
+ matrix: matrix || [1, 0, 0, 1, 0, 0],
123
+ }
121
124
  return pattern;
122
125
  }
123
126
  function docUsePattern(pattern, stroke) {
@@ -153,14 +156,62 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
153
156
  let mode = fill && stroke ? 2 : stroke ? 1 : fill ? 0 : 3;
154
157
  doc.addContent(mode + ' Tr');
155
158
  }
156
- function docWriteGlyph(glyph) {
157
- doc.addContent('<' + glyph + '> Tj');
159
+ function docWriteGlyphs(positions, font) {
160
+ let commands = [];
161
+ let commandStr = '';
162
+ const skew = font.fauxItalic ? -0.25 : 0;
163
+
164
+ // Add the given character to the 'TJ' command string.
165
+ function addChar(char) {
166
+ commandStr += char.glyph;
167
+ if (char.kern === 0) return;
168
+ commands.push(`<${commandStr}> ${validateNumber(char.kern)}`);
169
+ commandStr = '';
170
+ };
171
+
172
+ // Flush the current TJ command string to the output stream.
173
+ function flush() {
174
+ if (commandStr.length) {
175
+ commands.push(`<${commandStr}> 0`);
176
+ commandStr = '';
177
+ }
178
+ if (commands.length) {
179
+ doc.addContent(`[${commands.join(' ')}] TJ`);
180
+ commands = [];
181
+ }
182
+ };
183
+
184
+ for (let i = 0; i < positions.length; i++) {
185
+ const pos = positions[i];
186
+
187
+ if (pos.hidden || isEqual(pos.width, 0)) {
188
+ flush();
189
+ continue;
190
+ }
191
+
192
+ if (pos.continuous) {
193
+ addChar(pos);
194
+ continue;
195
+ }
196
+
197
+ // If this character is non-continuous, flush the command buffer.
198
+ flush();
199
+
200
+ // Start a new TJ command after writing a Text Matrix (Tm)
201
+ const cos = Math.cos(pos.rotate);
202
+ const sin = Math.sin(pos.rotate);
203
+ docSetTextMatrix(cos * pos.scale, sin * pos.scale, cos * skew - sin, sin * skew + cos, pos.x, pos.y);
204
+ addChar(pos);
205
+ };
206
+
207
+ // Flush any remaining characters in the buffer.
208
+ flush();
158
209
  }
159
210
  function docEndText() {
160
211
  doc.addContent('ET');
161
212
  }
162
213
  function docFillColor(color) {
163
- if (color[0].constructor.name === 'PDFPattern') {
214
+ if (color[0].type === 'PDFPattern') {
164
215
  doc.fillOpacity(color[1]);
165
216
  docUsePattern(color[0], false);
166
217
  } else {
@@ -168,13 +219,127 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
168
219
  }
169
220
  }
170
221
  function docStrokeColor(color) {
171
- if (color[0].constructor.name === 'PDFPattern') {
222
+ if (color[0].type === 'PDFPattern') {
172
223
  doc.strokeOpacity(color[1]);
173
224
  docUsePattern(color[0], true);
174
225
  } else {
175
226
  doc.strokeColor(color[0], color[1]);
176
227
  }
177
228
  }
229
+ // PDFKit doesn't accept any 0s in the dash array, but that's perfectly
230
+ // valid in SVG. So this function applys a dash array and offset, detecting
231
+ // any 0s in the dash array and updating it and the dash offset as needed to
232
+ // remove the zeros, but preserve the end result.
233
+ //
234
+ // `dashArray` must have an even number of elements
235
+ function docApplyDash(dashArray, dashOffset) {
236
+ let index;
237
+ // Anytime there's a 0 that isn't the first or last element of the array,
238
+ // we can remove it by combining the previous or next value. If it's a
239
+ // dash, then it's a zero-length dash between two spaces, so the dash can
240
+ // be eliminated and spaces combined by summing them, replacing all three
241
+ // values with the sum of the two spaces. If the 0 value is a space, then
242
+ // it's a zero-length space between two dashes, and the dashes can be
243
+ // similarly combined. So first we run that logic iteratively to remove
244
+ // all the 0s from the dash array that aren't the first or last element.
245
+ // Note that because we replace 3 values with one value, this doesn't
246
+ // change the even-ness of the length of dashArray.
247
+ while ((index = dashArray.slice(1, -1).indexOf(0)) !== -1) {
248
+ let actualIndex = index + 1;
249
+ let replacementValue = dashArray[actualIndex - 1] + dashArray[actualIndex + 1];
250
+ dashArray = dashArray
251
+ .slice(0, actualIndex - 1)
252
+ .concat([replacementValue])
253
+ .concat(dashArray.slice(actualIndex + 2));
254
+ }
255
+
256
+ // The stroke array only having two elements (a dash value and space
257
+ // value) is a special case.
258
+ if (dashArray.length === 2) {
259
+ if (dashArray[0] === 0) {
260
+ // Regardless of the space value, the dash length is zero, so we're
261
+ // not actually drawing a stroke. We can't describe that in a
262
+ // doc.dash() call in a way that PDFKit will accept, so we set the
263
+ // stroke opacity to zero as our best approximation.
264
+ doc.strokeOpacity(0);
265
+ return;
266
+ } else if (dashArray[1] === 0) {
267
+ // Regardless of the dash value, the space value is zero, meaning
268
+ // we're actually drawing a solid stroke, not a dashed one. We can
269
+ // make this happen by just emptying out the dash array.
270
+ dashArray = [];
271
+ }
272
+ } else {
273
+ if (dashArray[0] === 0) {
274
+ // The first dash is zero-length. We fix this by combining the first
275
+ // space (just after the first dash) with the last space and updating
276
+ // the dash offset accordingly. For example, if we had
277
+ //
278
+ // [ 0 4 3 2 5 1 ] (dash offset 0)
279
+ //
280
+ // ␣␣␣␣---␣␣-----␣
281
+ // ⎸
282
+ //
283
+ // we'd end up with
284
+ //
285
+ // [ 3 2 5 5 ] (dash offset -4)
286
+ //
287
+ // ---␣␣-----␣␣␣␣␣
288
+ // ⎸
289
+ //
290
+ // Another example where the dash array also ends with a 0:
291
+ //
292
+ // [ 0 4 3 2 5 0 ] (dash offset 0)
293
+ //
294
+ // ␣␣␣␣---␣␣-----
295
+ // ⎸
296
+ //
297
+ // we'd end up with
298
+ //
299
+ // [ 3 2 5 4 ] (dash offset -4)
300
+ //
301
+ // ---␣␣-----␣␣␣␣
302
+ // ⎸
303
+ dashOffset -= dashArray[1];
304
+ dashArray[dashArray.length - 1] += dashArray[1];
305
+ dashArray = dashArray.slice(2);
306
+ }
307
+ if (dashArray[dashArray.length - 1] === 0) {
308
+ // The last space is zero-length. We fix this by combining the last dash
309
+ // (just before the last space) with the first dash and updating the
310
+ // dash offset accordingly. For example, if we had
311
+ //
312
+ // [ 1 4 3 2 5 0 ] (dash offset 0)
313
+ //
314
+ // -␣␣␣␣---␣␣-----
315
+ // ⎸
316
+ //
317
+ // we'd end up with
318
+ //
319
+ // [ 6 4 3 2 ] (dash offset 5)
320
+ //
321
+ // ------␣␣␣␣---␣␣
322
+ // ⎸
323
+ //
324
+ dashOffset += dashArray[dashArray.length - 2];
325
+ dashArray[0] += dashArray[dashArray.length - 2];
326
+ dashArray = dashArray.slice(0, -2);
327
+ }
328
+ }
329
+
330
+ // Ensure the dash offset is non-negative (because of crbug.com/660850).
331
+ // First compute the total length of the dash array so we can add it to
332
+ // dash offset until dash offset is non-negative.
333
+ let length = 0;
334
+ for (let i = 0; i < dashArray.length; i++) {length += dashArray[i];}
335
+ if (length > 0) {
336
+ while (dashOffset < 0) {
337
+ dashOffset += length;
338
+ }
339
+ }
340
+
341
+ doc.dash(dashArray, {phase: dashOffset});
342
+ }
178
343
  function docInsertLink(x, y, w, h, url) {
179
344
  let ref = doc.ref({
180
345
  Type: 'Annot',
@@ -308,6 +473,11 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
308
473
  raw = (raw || '').trim();
309
474
  if (temp = NamedColors[raw]) {
310
475
  result = [temp.slice(), 1];
476
+ } else if (temp = raw.match(/^cmyk\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9.]+)\s*\)$/i)) {
477
+ temp[1] = parseInt(temp[1]); temp[2] = parseInt(temp[2]); temp[3] = parseInt(temp[3]); temp[4] = parseFloat(temp[4]);
478
+ if (temp[1] <= 100 && temp[2] <= 100 && temp[3] <= 100 && temp[4] <= 100) {
479
+ result = [temp.slice(1, 5), 1];
480
+ }
311
481
  } else if (temp = raw.match(/^rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9.]+)\s*\)$/i)) {
312
482
  temp[1] = parseInt(temp[1]); temp[2] = parseInt(temp[2]); temp[3] = parseInt(temp[3]); temp[4] = parseFloat(temp[4]);
313
483
  if (temp[1] < 256 && temp[2] < 256 && temp[3] < 256 && temp[4] <= 1) {
@@ -367,6 +537,11 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
367
537
  return new SvgShape().M(0, 0).L(doc.page.width, 0).L(doc.page.width, doc.page.height).L(0, doc.page.height)
368
538
  .transform(inverseMatrix(getGlobalMatrix())).getBoundingBox();
369
539
  }
540
+ function getPageScale() {
541
+ const bbox = getPageBBox();
542
+ const width = doc.page.width;
543
+ return width / bbox[2];
544
+ }
370
545
  function inverseMatrix(m) {
371
546
  let dt = m[0] * m[3] - m[1] * m[2];
372
547
  return [m[3] / dt, -m[1] / dt, -m[2] / dt, m[0] / dt, (m[2]*m[5] - m[3]*m[4]) / dt, (m[1]*m[4] - m[0]*m[5]) / dt];
@@ -556,7 +731,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
556
731
  if (selector.ids[i] !== elem.id) {return false;}
557
732
  }
558
733
  for (let i = 0; i < selector.classes.length; i++) {
559
- if (elem.classList.indexOf(selector.classes[i]) === -1) {return false;}
734
+ if (!elem.classList.contains(selector.classes[i])) {return false;}
560
735
  }
561
736
  return true;
562
737
  }
@@ -615,6 +790,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
615
790
  data.push({
616
791
  glyph: hex[i],
617
792
  unicode: unicode,
793
+ kern: pos[i].advanceWidth - pos[i].xAdvance,
618
794
  width: pos[i].advanceWidth * size / 1000,
619
795
  xOffset: pos[i].xOffset * size / 1000,
620
796
  yOffset: pos[i].yOffset * size / 1000,
@@ -1266,12 +1442,6 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
1266
1442
  break;
1267
1443
  case 'stroke-dashoffset':
1268
1444
  result = this.computeLength(value, this.getViewport());
1269
- if (result != null) {
1270
- if (result < 0) { // fix for crbug.com/660850
1271
- let dasharray = this.get('stroke-dasharray');
1272
- for (let j = 0; j < dasharray.length; j++) {result += dasharray[j];}
1273
- }
1274
- }
1275
1445
  break;
1276
1446
  }
1277
1447
  if (result != null) {return styleCache[key] = result;}
@@ -1322,7 +1492,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
1322
1492
  this.clip = function() {
1323
1493
  if (this.get('clip-path') !== 'none') {
1324
1494
  let clipPath = new SvgElemClipPath(this.get('clip-path'), null);
1325
- clipPath.useMask(this.getBoundingBox());
1495
+ clipPath.useMask((clipPath.attr('clipPathUnits') === 'objectBoundingBox') ? this.getBoundingBox() : null);
1326
1496
  return true;
1327
1497
  }
1328
1498
  };
@@ -1788,7 +1958,11 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
1788
1958
  this.drawInDocument = function(isClip, isMask) {
1789
1959
  if (this.get('visibility') === 'hidden' || !this.shape) {return;}
1790
1960
  doc.save();
1791
- this.transform();
1961
+ if (this.get('vector-effect') === 'non-scaling-stroke') {
1962
+ this.shape.transform(this.getTransformation());
1963
+ } else {
1964
+ this.transform();
1965
+ }
1792
1966
  this.clip();
1793
1967
  if (!isClip) {
1794
1968
  let masked = this.mask(),
@@ -1801,6 +1975,11 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
1801
1975
  stroke = this.getStroke(isClip, isMask),
1802
1976
  lineWidth = this.get('stroke-width'),
1803
1977
  lineCap = this.get('stroke-linecap');
1978
+
1979
+ if (this.get('vector-effect') === 'non-scaling-stroke') {
1980
+ lineWidth = lineWidth / getPageScale();
1981
+ }
1982
+
1804
1983
  if (fill || stroke) {
1805
1984
  if (fill) {
1806
1985
  docFillColor(fill);
@@ -1835,8 +2014,8 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
1835
2014
  doc.lineWidth(lineWidth)
1836
2015
  .miterLimit(this.get('stroke-miterlimit'))
1837
2016
  .lineJoin(this.get('stroke-linejoin'))
1838
- .lineCap(lineCap)
1839
- .dash(dashArray, {phase: dashOffset});
2017
+ .lineCap(lineCap);
2018
+ docApplyDash(dashArray, dashOffset);
1840
2019
  }
1841
2020
  for (let j = 0; j < subPaths.length; j++) {
1842
2021
  if (subPaths[j].totalLength > 0) {
@@ -1856,7 +2035,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
1856
2035
  markerEnd = this.get('marker-end');
1857
2036
  if (markerStart !== 'none' || markerMid !== 'none' || markerEnd !== 'none') {
1858
2037
  let markersPos = this.shape.getMarkers();
1859
- if (markerStart !== 'none') {
2038
+ if (markerStart !== 'none' && markersPos.length > 0) {
1860
2039
  let marker = new SvgElemMarker(markerStart, null);
1861
2040
  marker.drawMarker(false, isMask, markersPos[0], lineWidth);
1862
2041
  }
@@ -1866,7 +2045,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
1866
2045
  marker.drawMarker(false, isMask, markersPos[i], lineWidth);
1867
2046
  }
1868
2047
  }
1869
- if (markerEnd !== 'none') {
2048
+ if (markerEnd !== 'none' && markersPos.length > 0) {
1870
2049
  let marker = new SvgElemMarker(markerEnd, null);
1871
2050
  marker.drawMarker(false, isMask, markersPos[markersPos.length - 1], lineWidth);
1872
2051
  }
@@ -2028,6 +2207,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2028
2207
  this.useMask = function(bBox) {
2029
2208
  let group = docBeginGroup(getPageBBox());
2030
2209
  doc.save();
2210
+ doc.transform.apply(doc, this.get('transform'));
2031
2211
  if (this.attr('clipPathUnits') === 'objectBoundingBox') {
2032
2212
  doc.transform(bBox[2] - bBox[0], 0, 0, bBox[3] - bBox[1], bBox[0], bBox[1]);
2033
2213
  }
@@ -2056,7 +2236,6 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2056
2236
  w = this.getLength('width', this.getVWidth(), 1.2) * (bBox[2] - bBox[0]);
2057
2237
  h = this.getLength('height', this.getVHeight(), 1.2) * (bBox[3] - bBox[1]);
2058
2238
  }
2059
- doc.rect(x, y, w, h).clip();
2060
2239
  if (this.attr('maskContentUnits') === 'objectBoundingBox') {
2061
2240
  doc.transform(bBox[2] - bBox[0], 0, 0, bBox[3] - bBox[1], bBox[0], bBox[1]);
2062
2241
  }
@@ -2125,18 +2304,12 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2125
2304
  doc.lineWidth(strokeWidth)
2126
2305
  .miterLimit(this.get('stroke-miterlimit'))
2127
2306
  .lineJoin(this.get('stroke-linejoin'))
2128
- .lineCap(this.get('stroke-linecap'))
2129
- .dash(this.get('stroke-dasharray'), {phase:this.get('stroke-dashoffset')});
2307
+ .lineCap(this.get('stroke-linecap'));
2308
+ docApplyDash(this.get('stroke-dasharray'), this.get('stroke-dashoffset'));
2130
2309
  }
2131
2310
  docBeginText(this._font.font, this._font.size);
2132
2311
  docSetTextMode(!!fill, !!stroke);
2133
- for (let j = 0, pos = childElem._pos; j < pos.length; j++) {
2134
- if (!pos[j].hidden && isNotEqual(pos[j].width, 0)) {
2135
- let cos = Math.cos(pos[j].rotate), sin = Math.sin(pos[j].rotate), skew = (this._font.fauxItalic ? -0.25 : 0);
2136
- docSetTextMatrix(cos * pos[j].scale, sin * pos[j].scale, cos * skew - sin, sin * skew + cos, pos[j].x, pos[j].y);
2137
- docWriteGlyph(pos[j].glyph);
2138
- }
2139
- }
2312
+ docWriteGlyphs(childElem._pos, this._font);
2140
2313
  docEndText();
2141
2314
  }
2142
2315
  break;
@@ -2157,8 +2330,8 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2157
2330
  doc.lineWidth(this.get('stroke-width'))
2158
2331
  .miterLimit(this.get('stroke-miterlimit'))
2159
2332
  .lineJoin(this.get('stroke-linejoin'))
2160
- .lineCap(this.get('stroke-linecap'))
2161
- .dash(this.get('stroke-dasharray'), {phase:this.get('stroke-dashoffset')});
2333
+ .lineCap(this.get('stroke-linecap'));
2334
+ docApplyDash(this.get('stroke-dasharray'), this.get('stroke-dashoffset'));
2162
2335
  }
2163
2336
  for (let j = 0, pos = this._pos; j < pos.length; j++) {
2164
2337
  if (!pos[j].hidden && isNotEqual(pos[j].width, 0)) {
@@ -2234,6 +2407,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2234
2407
  let textScale = length / (endX - startX);
2235
2408
  if (textScale > 0 && textScale < Infinity) {
2236
2409
  for (let j = 0; j < pos.length; j++) {
2410
+ pos[j].continuous = false;
2237
2411
  pos[j].x = startX + textScale * (pos[j].x - startX);
2238
2412
  pos[j].scale *= textScale;
2239
2413
  pos[j].width *= textScale;
@@ -2243,6 +2417,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2243
2417
  if (pos.length >= 2) {
2244
2418
  let spaceDiff = (length - (endX - startX)) / (pos.length - 1);
2245
2419
  for (let j = 0; j < pos.length; j++) {
2420
+ pos[j].continuous = false;
2246
2421
  pos[j].x += j * spaceDiff;
2247
2422
  }
2248
2423
  }
@@ -2262,7 +2437,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2262
2437
  try {
2263
2438
  doc.font(fontNameorLink);
2264
2439
  } catch(e) {
2265
- warningCallback('SVGElemText: failed to open font "' + fontNameorLink + '" in PDFKit');
2440
+ warningCallback('SVGElemText: failed to open font "' + fontNameorLink + '" in PDFKit: ' + e.message);
2266
2441
  }
2267
2442
  currentElem._pos = [];
2268
2443
  currentElem._index = 0;
@@ -2273,7 +2448,22 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2273
2448
  letterSpacing = currentElem.get('letter-spacing'),
2274
2449
  textAnchor = currentElem.get('text-anchor'),
2275
2450
  textDirection = currentElem.get('direction'),
2276
- baseline = getBaseline(currentElem._font.font, currentElem._font.size, currentElem.get('alignment-baseline') || currentElem.get('dominant-baseline'), currentElem.get('baseline-shift'));
2451
+ // `alignment-baseline` and `baseline-shift` have no effect on
2452
+ // `<text>` elements according to the SVG spec. So, detect when
2453
+ // we're styling a `<text>` element and ignore
2454
+ // `alignment-baseline` (only factoring in `dominant-baseline`)
2455
+ // and `baseline-shift` (which can only have the default value of
2456
+ // `baseline`).
2457
+ //
2458
+ // Note that Chrome (as of v99) incorrectly factors in
2459
+ // `alignment-baseline` on `<text>` elements, while Firefox
2460
+ // correctly follows the spec and ignores it. This means that our
2461
+ // output will differ from Chrome's in these cases, but conform to
2462
+ // SVG specification.
2463
+ isTextElem = currentElem.name === 'text',
2464
+ baselineAttr = isTextElem ? currentElem.get('dominant-baseline') : (currentElem.get('alignment-baseline') || currentElem.get('dominant-baseline')),
2465
+ baselineShiftAttr = isTextElem ? 'baseline' : currentElem.get('baseline-shift'),
2466
+ baseline = getBaseline(currentElem._font.font, currentElem._font.size, baselineAttr, baselineShiftAttr);
2277
2467
  if (currentElem.name === 'textPath') {
2278
2468
  doAnchoring();
2279
2469
  currentX = currentY = 0;
@@ -2313,6 +2503,8 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2313
2503
  dyAttr = currentElem._dy[index],
2314
2504
  rotAttr = currentElem._rot[index],
2315
2505
  continuous = !(w === 0 && j === 0);
2506
+ if (letterSpacing !== 0) {continuous = false}
2507
+ if (wordSpacing !== 0) {continuous = false}
2316
2508
  if (xAttr !== undefined) {continuous = false; doAnchoring(); currentX = xAttr;}
2317
2509
  if (yAttr !== undefined) {continuous = false; doAnchoring(); currentY = yAttr;}
2318
2510
  if (dxAttr !== undefined) {continuous = false; currentX += dxAttr;}
@@ -2322,6 +2514,7 @@ var SVGtoPDF = function(doc, svg, x, y, options) {
2322
2514
  glyph: pos[j].glyph,
2323
2515
  rotate: (Math.PI / 180) * currentElem.chooseValue(rotAttr, currentElem._defRot),
2324
2516
  x: currentX + pos[j].xOffset,
2517
+ kern: pos[j].kern,
2325
2518
  y: currentY + baseline + pos[j].yOffset,
2326
2519
  width: pos[j].width,
2327
2520
  ascent: getAscent(currentElem._font.font, currentElem._font.size),
package/src/DocMeasure.js CHANGED
@@ -95,8 +95,10 @@ class DocMeasure {
95
95
  node._width = node._minWidth = node._maxWidth = node.cover.width;
96
96
  node._height = node._minHeight = node._maxHeight = node.cover.height;
97
97
  } else {
98
- node._width = node._minWidth = node._maxWidth = node.width || dimensions.width;
99
- node._height = node.height || (dimensions.height * node._width / dimensions.width);
98
+ let ratio = dimensions.width / dimensions.height;
99
+
100
+ node._width = node._minWidth = node._maxWidth = node.width || (node.height ? (node.height * ratio) : dimensions.width);
101
+ node._height = node.height || (node.width ? node.width / ratio : dimensions.height);
100
102
 
101
103
  if (isNumber(node.maxWidth) && node.maxWidth < node._width) {
102
104
  node._width = node._minWidth = node._maxWidth = node.maxWidth;
@@ -154,6 +156,15 @@ class DocMeasure {
154
156
 
155
157
  node.font = this.styleStack.getProperty('font');
156
158
 
159
+ // SVG requires a defined width and height
160
+ if (!isNumber(node._width) && !isNumber(node._height)) {
161
+ throw new Error('SVG is missing defined width and height.');
162
+ } else if (!isNumber(node._width)) {
163
+ throw new Error('SVG is missing defined width.');
164
+ } else if (!isNumber(node._height)) {
165
+ throw new Error('SVG is missing defined height.');
166
+ }
167
+
157
168
  // scale SVG based on final dimension
158
169
  node.svg = this.svgMeasure.writeDimensions(node.svg, { width: node._width, height: node._height });
159
170
 
@@ -237,7 +248,7 @@ class DocMeasure {
237
248
  return this.textInlines.sizeOfText('9. ', this.styleStack);
238
249
  }
239
250
 
240
- buildUnorderedMarker(styleStack, gapSize, type) {
251
+ buildUnorderedMarker(item, styleStack, gapSize, type) {
241
252
  function buildDisc(gapSize, color) {
242
253
  // TODO: ascender-based calculations
243
254
  let radius = gapSize.fontSize / 6;
@@ -284,7 +295,7 @@ class DocMeasure {
284
295
  }
285
296
 
286
297
  let marker;
287
- let color = styleStack.getProperty('markerColor') || styleStack.getProperty('color') || 'black';
298
+ let color = StyleContextStack.getStyleProperty(item, styleStack, 'markerColor', undefined) || styleStack.getProperty('color') || 'black';
288
299
 
289
300
  switch (type) {
290
301
  case 'circle':
@@ -311,7 +322,7 @@ class DocMeasure {
311
322
  return marker;
312
323
  }
313
324
 
314
- buildOrderedMarker(counter, styleStack, type, separator) {
325
+ buildOrderedMarker(item, counter, styleStack, type, separator) {
315
326
  function prepareAlpha(counter) {
316
327
  function toAlpha(num) {
317
328
  return (num >= 26 ? toAlpha((num / 26 >> 0) - 1) : '') + 'abcdefghijklmnopqrstuvwxyz'[num % 26 >> 0];
@@ -391,11 +402,11 @@ class DocMeasure {
391
402
  }
392
403
  }
393
404
 
394
- let textArray = { text: counterText };
395
- let markerColor = styleStack.getProperty('markerColor');
396
- if (markerColor) {
397
- textArray.color = markerColor;
398
- }
405
+ let markerColor = StyleContextStack.getStyleProperty(item, styleStack, 'markerColor', undefined) || styleStack.getProperty('color') || 'black';
406
+ let textArray = {
407
+ text: counterText,
408
+ color: markerColor
409
+ };
399
410
 
400
411
  return { _inlines: this.textInlines.buildInlines(textArray, styleStack).items };
401
412
  }
@@ -412,7 +423,7 @@ class DocMeasure {
412
423
  let item = items[i] = this.measureNode(items[i]);
413
424
 
414
425
  if (!item.ol && !item.ul) {
415
- item.listMarker = this.buildUnorderedMarker(style, node._gapSize, item.listType || node.type);
426
+ item.listMarker = this.buildUnorderedMarker(item, style, node._gapSize, item.listType || node.type);
416
427
  }
417
428
 
418
429
  node._minWidth = Math.max(node._minWidth, items[i]._minWidth + node._gapSize.width);
@@ -441,7 +452,7 @@ class DocMeasure {
441
452
 
442
453
  if (!item.ol && !item.ul) {
443
454
  let counterValue = isNumber(item.counter) ? item.counter : counter;
444
- item.listMarker = this.buildOrderedMarker(counterValue, style, item.listType || node.type, node.separator);
455
+ item.listMarker = this.buildOrderedMarker(item, counterValue, style, item.listType || node.type, node.separator);
445
456
  if (item.listMarker._inlines) {
446
457
  node._gapSize.width = Math.max(node._gapSize.width, item.listMarker._inlines[0].width);
447
458
  }
@@ -9,11 +9,22 @@ class OutputDocumentServer extends OutputDocument {
9
9
  */
10
10
  async write(filename) {
11
11
  const stream = await this.getStream();
12
- return new Promise((resolve) => {
13
- stream.pipe(fs.createWriteStream(filename));
12
+ const writeStream = fs.createWriteStream(filename);
13
+
14
+ const streamEnded = new Promise((resolve, reject) => {
14
15
  stream.on('end', resolve);
15
- stream.end();
16
+ stream.on('error', reject);
17
+ });
18
+
19
+ const writeClosed = new Promise((resolve, reject) => {
20
+ writeStream.on('close', resolve);
21
+ writeStream.on('error', reject);
16
22
  });
23
+
24
+ stream.pipe(writeStream);
25
+ stream.end();
26
+
27
+ await Promise.all([streamEnded, writeClosed]);
17
28
  }
18
29
 
19
30
  }
package/src/Printer.js CHANGED
@@ -102,12 +102,11 @@ class PdfPrinter {
102
102
 
103
103
  // if pageSize.height is set to Infinity, calculate the actual height of the page that
104
104
  // was laid out using the height of each of the items in the page.
105
- if (pageSize.height === Infinity) {
106
- let pageHeight = calculatePageHeight(pages, docDefinition.pageMargins);
107
- pages.forEach(page => {
108
- page.pageSize.height = pageHeight;
109
- });
110
- }
105
+ pages.forEach(page => {
106
+ if (page.pageSize.height === Infinity) {
107
+ page.pageSize.height = calculatePageHeight(page, page.pageMargins);
108
+ }
109
+ });
111
110
 
112
111
  const renderer = new Renderer(this.pdfKitDoc, options.progressCallback);
113
112
  renderer.renderPages(pages);
@@ -265,7 +264,7 @@ function embedFiles(docDefinition, pdfKitDoc) {
265
264
  }
266
265
  }
267
266
 
268
- function calculatePageHeight(pages, margins) {
267
+ function calculatePageHeight(page, margins) {
269
268
  function getItemHeight(item) {
270
269
  if (typeof item.item.getHeight === 'function') {
271
270
  return item.item.getHeight();
@@ -292,13 +291,11 @@ function calculatePageHeight(pages, margins) {
292
291
  let fixedMargins = normalizePageMargin(margins || 40);
293
292
  let height = fixedMargins.top;
294
293
 
295
- pages.forEach(page => {
296
- page.items.forEach(item => {
297
- let bottomPosition = getBottomPosition(item);
298
- if (bottomPosition > height) {
299
- height = bottomPosition;
300
- }
301
- });
294
+ page.items.forEach(item => {
295
+ let bottomPosition = getBottomPosition(item);
296
+ if (bottomPosition > height) {
297
+ height = bottomPosition;
298
+ }
302
299
  });
303
300
 
304
301
  height += fixedMargins.bottom;
package/src/Renderer.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import TextDecorator from './TextDecorator';
2
2
  import TextInlines from './TextInlines';
3
- import { isNumber } from './helpers/variableType';
3
+ import { isNumber, isString } from './helpers/variableType';
4
4
  import SVGtoPDF from './3rd-party/svg-to-pdfkit';
5
5
 
6
6
  const findFont = (fonts, requiredFonts, defaultFont) => {
@@ -138,7 +138,7 @@ class Renderer {
138
138
  textDecorator.drawBackground(line, x, y);
139
139
 
140
140
  //TODO: line.optimizeInlines();
141
- //TOOD: lines without differently styled inlines should be written to pdf as one stream
141
+ //TODO: lines without differently styled inlines should be written to pdf as one stream
142
142
  for (let i = 0, l = line.inlines.length; i < l; i++) {
143
143
  let inline = line.inlines[i];
144
144
  let shiftToBaseline = lineHeight - ((inline.font.ascender / 1000) * inline.fontSize) - descent;
@@ -329,7 +329,13 @@ class Renderer {
329
329
  }
330
330
 
331
331
  renderSVG(svg) {
332
- let options = Object.assign({ width: svg._width, height: svg._height, assumePt: true }, svg.options);
332
+ let options = {
333
+ width: svg._width,
334
+ height: svg._height,
335
+ assumePt: true,
336
+ useCSS: !isString(svg.svg),
337
+ ...svg.options
338
+ };
333
339
  options.fontCallback = (family, bold, italic) => {
334
340
  let fontsFamily = family.split(',').map(f => f.trim().replace(/('|")/g, ''));
335
341
  let font = findFont(this.pdfDocument.fonts, fontsFamily, svg.font || 'Roboto');