Skip to content

Commit 2bb6a89

Browse files
committed
Fixed #355 - missing images on fromHTML()
1 parent b675300 commit 2bb6a89

File tree

2 files changed

+98
-94
lines changed

2 files changed

+98
-94
lines changed

jspdf.plugin.addimage.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,7 @@
158158
return typeof value === 'undefined' || value === null;
159159
}
160160
, generateAliasFromData = function(data) {
161-
return typeof data === 'string' && Array.prototype.reduce &&
162-
data.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
161+
return typeof data === 'string' && jsPDFAPI.sHashCode(data);
163162
}
164163
, doesNotSupportImageType = function(type) {
165164
return supported_image_types.indexOf(type) === -1;
@@ -194,7 +193,6 @@
194193
}
195194
ctx.drawImage(element, 0, 0, canvas.width, canvas.height);
196195
}
197-
198196
return canvas.toDataURL((''+format).toLowerCase() == 'png' ? 'image/png' : 'image/jpeg');
199197
}
200198
,checkImagesForAlias = function(alias, images) {
@@ -293,6 +291,10 @@
293291
SLOW: 'SLOW'
294292
};
295293

294+
jsPDFAPI.sHashCode = function(str) {
295+
return Array.prototype.reduce && str.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
296+
};
297+
296298
jsPDFAPI.isString = function(object) {
297299
return typeof object === 'string';
298300
};
@@ -480,7 +482,7 @@
480482
jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias, compression) {
481483
'use strict'
482484

483-
if(typeof format === 'number') {
485+
if(typeof format !== 'string') {
484486
var tmp = h;
485487
h = w;
486488
w = y;
@@ -489,6 +491,12 @@
489491
format = tmp;
490492
}
491493

494+
if (isNaN(x) || isNaN(y))
495+
{
496+
console.error('jsPDF.addImage: Invalid coordinates', arguments);
497+
throw new Error('Invalid coordinates passed to jsPDF.addImage');
498+
}
499+
492500
var images = getImages.call(this), info;
493501

494502
if (!(info = checkImagesForAlias(imageData, images))) {

jspdf.plugin.from_html.js

Lines changed: 86 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,7 @@
165165
return UnitedNumberMap[css_line_height_string] = 1;
166166
};
167167
GetCSS = function (element) {
168-
var css,
169-
tmp,
170-
computedCSSElement;
168+
var css,tmp,computedCSSElement;
171169
computedCSSElement = (function (el) {
172170
var compCSS;
173171
compCSS = (function (el) {
@@ -202,16 +200,17 @@
202200
css["font-size"] = ResolveUnitedNumber(computedCSSElement("font-size")) || 1;
203201
css["line-height"] = ResolveUnitedNumber(computedCSSElement("line-height")) || 1;
204202
css["display"] = (computedCSSElement("display") === "inline" ? "inline" : "block");
205-
if (css["display"] === "block") {
206-
css["margin-top"] = ResolveUnitedNumber(computedCSSElement("margin-top")) || 0;
207-
css["margin-bottom"] = ResolveUnitedNumber(computedCSSElement("margin-bottom")) || 0;
208-
css["padding-top"] = ResolveUnitedNumber(computedCSSElement("padding-top")) || 0;
209-
css["padding-bottom"] = ResolveUnitedNumber(computedCSSElement("padding-bottom")) || 0;
210-
css["margin-left"] = ResolveUnitedNumber(computedCSSElement("margin-left")) || 0;
211-
css["margin-right"] = ResolveUnitedNumber(computedCSSElement("margin-right")) || 0;
212-
css["padding-left"] = ResolveUnitedNumber(computedCSSElement("padding-left")) || 0;
213-
css["padding-right"] = ResolveUnitedNumber(computedCSSElement("padding-right")) || 0;
214-
}
203+
204+
tmp = (css["display"] === "block");
205+
css["margin-top"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-top")) || 0;
206+
css["margin-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-bottom")) || 0;
207+
css["padding-top"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-top")) || 0;
208+
css["padding-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-bottom")) || 0;
209+
css["margin-left"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-left")) || 0;
210+
css["margin-right"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-right")) || 0;
211+
css["padding-left"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-left")) || 0;
212+
css["padding-right"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-right")) || 0;
213+
215214
//float and clearing of floats
216215
css["float"] = FloatMap[computedCSSElement("cssFloat")] || "none";
217216
css["clear"] = ClearMap[computedCSSElement("clear")] || "none";
@@ -330,7 +329,7 @@
330329
while (i < l) {
331330
cn = cns[i];
332331
if (typeof cn === "object") {
333-
332+
334333
//execute all watcher functions to e.g. reset floating
335334
renderer.executeWatchFunctions(cn);
336335

@@ -357,27 +356,32 @@
357356
renderer.pdf.addPage();
358357
renderer.y = renderer.pdf.margins_doc.top;
359358
}
360-
359+
361360
} else if (cn.nodeType === 1 && !SkipNode[cn.nodeName]) {
362361
/*** IMAGE RENDERING ***/
363-
if (cn.nodeName === "IMG" && images[cn.getAttribute("src")]) {
362+
var cached_image;
363+
if (cn.nodeName === "IMG") {
364+
var url = cn.getAttribute("src");
365+
cached_image = images[renderer.pdf.sHashCode(url) || url];
366+
}
367+
if (cached_image) {
364368
if ((renderer.pdf.internal.pageSize.height - renderer.pdf.margins_doc.bottom < renderer.y + cn.height) && (renderer.y > renderer.pdf.margins_doc.top)) {
365369
renderer.pdf.addPage();
366370
renderer.y = renderer.pdf.margins_doc.top;
367371
//check if we have to set back some values due to e.g. header rendering for new page
368372
renderer.executeWatchFunctions(cn);
369-
}
370-
373+
}
374+
371375
var imagesCSS = GetCSS(cn);
372376
var imageX = renderer.x;
373377
var fontToUnitRatio = 12 / renderer.pdf.internal.scaleFactor;
374-
378+
375379
//define additional paddings, margins which have to be taken into account for margin calculations
376380
var additionalSpaceLeft = (imagesCSS["margin-left"] + imagesCSS["padding-left"])*fontToUnitRatio;
377381
var additionalSpaceRight = (imagesCSS["margin-right"] + imagesCSS["padding-right"])*fontToUnitRatio;
378382
var additionalSpaceTop = (imagesCSS["margin-top"] + imagesCSS["padding-top"])*fontToUnitRatio;
379383
var additionalSpaceBottom = (imagesCSS["margin-bottom"] + imagesCSS["padding-bottom"])*fontToUnitRatio;
380-
384+
381385
//if float is set to right, move the image to the right border
382386
//add space if margin is set
383387
if (imagesCSS['float'] !== undefined && imagesCSS['float'] === 'right') {
@@ -386,56 +390,53 @@
386390
imageX += additionalSpaceLeft;
387391
}
388392

389-
renderer.pdf.addImage(images[cn.getAttribute("src")], imageX, renderer.y + additionalSpaceTop, cn.width, cn.height);
393+
renderer.pdf.addImage(cached_image, imageX, renderer.y + additionalSpaceTop, cn.width, cn.height);
394+
cached_image = undefined;
390395
//if the float prop is specified we have to float the text around the image
391-
if (imagesCSS['float'] !== undefined) {
392-
if (imagesCSS['float'] === 'right' || imagesCSS['float'] === 'left') {
393-
394-
//add functiont to set back coordinates after image rendering
395-
renderer.watchFunctions.push((function(diffX , thresholdY, diffWidth, el) {
396-
//undo drawing box adaptions which were set by floating
397-
if (renderer.y >= thresholdY) {
398-
renderer.x += diffX;
399-
renderer.settings.width += diffWidth;
400-
return true;
401-
} else if(el && el.nodeType === 1 && !SkipNode[el.nodeName] && renderer.x+el.width > (renderer.pdf.margins_doc.left + renderer.pdf.margins_doc.width)) {
402-
renderer.x += diffX;
403-
renderer.y = thresholdY;
404-
renderer.settings.width += diffWidth;
396+
if (imagesCSS['float'] === 'right' || imagesCSS['float'] === 'left') {
397+
//add functiont to set back coordinates after image rendering
398+
renderer.watchFunctions.push((function(diffX , thresholdY, diffWidth, el) {
399+
//undo drawing box adaptions which were set by floating
400+
if (renderer.y >= thresholdY) {
401+
renderer.x += diffX;
402+
renderer.settings.width += diffWidth;
403+
return true;
404+
} else if(el && el.nodeType === 1 && !SkipNode[el.nodeName] && renderer.x+el.width > (renderer.pdf.margins_doc.left + renderer.pdf.margins_doc.width)) {
405+
renderer.x += diffX;
406+
renderer.y = thresholdY;
407+
renderer.settings.width += diffWidth;
408+
return true;
409+
} else {
410+
return false;
411+
}
412+
}).bind(this, (imagesCSS['float'] === 'left') ? -cn.width-additionalSpaceLeft-additionalSpaceRight : 0, renderer.y+cn.height+additionalSpaceTop+additionalSpaceBottom, cn.width));
413+
//reset floating by clear:both divs
414+
//just set cursorY after the floating element
415+
renderer.watchFunctions.push((function(yPositionAfterFloating, pages, el) {
416+
if (renderer.y < yPositionAfterFloating && pages === renderer.pdf.internal.getNumberOfPages()) {
417+
if (el.nodeType === 1 && GetCSS(el).clear === 'both') {
418+
renderer.y = yPositionAfterFloating;
405419
return true;
406420
} else {
407421
return false;
408422
}
409-
}).bind(this, (imagesCSS['float'] === 'left') ? -cn.width-additionalSpaceLeft-additionalSpaceRight : 0, renderer.y+cn.height+additionalSpaceTop+additionalSpaceBottom, cn.width));
410-
411-
//reset floating by clear:both divs
412-
//just set cursorY after the floating element
413-
renderer.watchFunctions.push((function(yPositionAfterFloating, pages, el) {
414-
if (renderer.y < yPositionAfterFloating && pages === renderer.pdf.internal.getNumberOfPages()) {
415-
if (el.nodeType === 1 && GetCSS(el).clear === 'both') {
416-
renderer.y = yPositionAfterFloating;
417-
return true;
418-
} else {
419-
return false;
420-
}
421-
} else {
422-
return true;
423-
}
424-
}).bind(this, renderer.y+cn.height, renderer.pdf.internal.getNumberOfPages()));
425-
426-
//if floating is set we decrease the available width by the image width
427-
renderer.settings.width -= cn.width+additionalSpaceLeft+additionalSpaceRight;
428-
//if left just add the image width to the X coordinate
429-
if (imagesCSS['float'] === 'left') {
430-
renderer.x += cn.width+additionalSpaceLeft+additionalSpaceRight;
423+
} else {
424+
return true;
431425
}
426+
}).bind(this, renderer.y+cn.height, renderer.pdf.internal.getNumberOfPages()));
427+
428+
//if floating is set we decrease the available width by the image width
429+
renderer.settings.width -= cn.width+additionalSpaceLeft+additionalSpaceRight;
430+
//if left just add the image width to the X coordinate
431+
if (imagesCSS['float'] === 'left') {
432+
renderer.x += cn.width+additionalSpaceLeft+additionalSpaceRight;
432433
}
433-
//if no floating is set, move the rendering cursor after the image height
434434
} else {
435+
//if no floating is set, move the rendering cursor after the image height
435436
renderer.y += cn.height + additionalSpaceBottom;
436-
}
437-
438-
/*** TABLE RENDERING ***/
437+
}
438+
439+
/*** TABLE RENDERING ***/
439440
} else if (cn.nodeName === "TABLE") {
440441
table2json = tableToJson(cn, renderer);
441442
renderer.y += 10;
@@ -495,17 +496,17 @@
495496
images = {};
496497
loadImgs = function (element, renderer, elementHandlers, cb) {
497498
var imgs = element.getElementsByTagName('img'),
498-
l = imgs.length,
499+
l = imgs.length, found_images,
499500
x = 0;
500501
function done() {
501502
renderer.pdf.internal.events.publish('imagesLoaded');
502-
cb();
503+
cb(found_images);
503504
}
504505
function loadImage(url, width, height) {
505506
if (!url)
506507
return;
507508
var img = new Image();
508-
++x;
509+
found_images = ++x;
509510
img.crossOrigin = '';
510511
img.onerror = img.onload = function () {
511512
if(img.complete) {
@@ -517,8 +518,8 @@
517518
}
518519
//if valid image add to known images array
519520
if (img.width + img.height) {
520-
//TODO: use a hash since data URIs could greatly increase the memory usage
521-
images[url] = images[url] || img;
521+
var hash = renderer.pdf.sHashCode(url) || url;
522+
images[hash] = images[hash] || img;
522523
}
523524
}
524525
if(!--x) {
@@ -531,7 +532,7 @@
531532
loadImage(imgs[l].getAttribute("src"),imgs[l].width,imgs[l].height);
532533
return x || done();
533534
};
534-
checkForFooter = function (elem, renderer, elementHandlers, callback) {
535+
checkForFooter = function (elem, renderer, elementHandlers) {
535536
//check if we can found a <footer> element
536537
var footer = elem.getElementsByTagName("footer");
537538
if (footer.length > 0) {
@@ -596,11 +597,7 @@
596597

597598
//prevent footer rendering
598599
SkipNode['FOOTER'] = 1;
599-
600600
}
601-
602-
//footer preparation finished
603-
callback();
604601
};
605602
process = function (pdf, element, x, y, settings, callback) {
606603
if (!element)
@@ -625,20 +622,19 @@
625622
})(element.replace(/<\/?script[^>]*?>/gi, ''));
626623
}
627624
var r = new Renderer(pdf, x, y, settings);
628-
callback = callback || function () {};
629625

630626
// 1. load images
631627
// 2. prepare optional footer elements
632628
// 3. render content
633-
loadImgs.call(this, element, r, settings.elementHandlers, function () {
634-
checkForFooter.call(this, element, r, settings.elementHandlers, function () {
635-
DrillForContent(element, r, settings.elementHandlers);
636-
//send event dispose for final taks (e.g. footer totalpage replacement)
637-
r.pdf.internal.events.publish('htmlRenderingFinished');
638-
callback(r.dispose());
639-
});
629+
loadImgs.call(this, element, r, settings.elementHandlers, function (found_images) {
630+
checkForFooter( element, r, settings.elementHandlers);
631+
DrillForContent(element, r, settings.elementHandlers);
632+
//send event dispose for final taks (e.g. footer totalpage replacement)
633+
r.pdf.internal.events.publish('htmlRenderingFinished');
634+
r = r.dispose();
635+
if (typeof callback === 'function') callback(r);
636+
else if (found_images) console.error('jsPDF Warning: rendering issues? provide a callback to fromHTML!');
640637
});
641-
642638
return pdf;
643639
};
644640
Renderer.prototype.init = function () {
@@ -655,7 +651,7 @@
655651
y : this.y
656652
};
657653
};
658-
654+
659655
//Checks if we have to execute some watcher functions
660656
//e.g. to end text floating around an image
661657
Renderer.prototype.executeWatchFunctions = function(el) {
@@ -672,7 +668,7 @@
672668
this.watchFunctions = narray;
673669
}
674670
return ret;
675-
};
671+
};
676672

677673
Renderer.prototype.splitFragmentsIntoLines = function (fragments, styles) {
678674
var currentLineLength,
@@ -843,7 +839,7 @@
843839

844840
//stores the current indent of cursor position
845841
var currentIndent = 0;
846-
842+
847843
while (lines.length) {
848844
line = lines.shift();
849845
maxLineHeight = 0;
@@ -875,20 +871,20 @@
875871
i++;
876872
}
877873
this.y += maxLineHeight * fontToUnitRatio;
878-
874+
879875
//if some watcher function was executed sucessful, so e.g. margin and widths were changed,
880876
//reset line drawing and calculate position and lines again
881877
//e.g. to stop text floating around an image
882878
if (this.executeWatchFunctions(line[0][1]) && lines.length > 0) {
883879
var localFragments = [];
884880
var localStyles = [];
885-
//create fragement array of
881+
//create fragement array of
886882
lines.forEach(function(localLine) {
887883
var i = 0;
888884
var l = localLine.length;
889885
while (i !== l) {
890886
if (localLine[i][0]) {
891-
localFragments.push(localLine[i][0]+' ');
887+
localFragments.push(localLine[i][0]+' ');
892888
localStyles.push(localLine[i][1]);
893889
}
894890
++i;
@@ -897,10 +893,10 @@
897893
//split lines again due to possible coordinate changes
898894
lines = this.splitFragmentsIntoLines(PurgeWhiteSpace(localFragments), localStyles);
899895
//reposition the current cursor
900-
out("ET", "Q");
896+
out("ET", "Q");
901897
out("q", "BT", this.pdf.internal.getCoordinateString(this.x), this.pdf.internal.getVerticalCoordinateString(this.y), "Td");
902-
}
903-
898+
}
899+
904900
}
905901
if (cb && typeof cb === "function") {
906902
cb.call(this, this.x - 9, this.y - fontSize / 2);
@@ -961,7 +957,7 @@
961957
ClearMap = {
962958
none : 'none',
963959
both : 'both'
964-
};
960+
};
965961
UnitedNumberMap = {
966962
normal : 1
967963
};

0 commit comments

Comments
 (0)