diff --git a/src/modules/context2d.js b/src/modules/context2d.js index 2d30c8ea5..785dbc4ad 100644 --- a/src/modules/context2d.js +++ b/src/modules/context2d.js @@ -53,6 +53,8 @@ import { this.lastPoint = ctx.lastPoint || new Point(); this.lineDashOffset = ctx.lineDashOffset || 0.0; this.lineDash = ctx.lineDash || []; + this.margin = ctx.margin || [0, 0, 0, 0]; + this.prevPageLastElemOffset = ctx.prevPageLastElemOffset || 0; this.ignoreClearRect = typeof ctx.ignoreClearRect === "boolean" ? ctx.ignoreClearRect : true; @@ -164,18 +166,61 @@ import { } }); + /** + * Gets or sets the page margin when using auto paging. Has no effect when {@link autoPaging} is off. + * @name margin + * @type {number|number[]} + * @default [0, 0, 0, 0] + */ + Object.defineProperty(this, "margin", { + get: function() { + return _ctx.margin; + }, + set: function(value) { + var margin; + if (typeof value === "number") { + margin = [value, value, value, value]; + } else { + margin = new Array(4); + margin[0] = value[0]; + margin[1] = value.length >= 2 ? value[1] : margin[0]; + margin[2] = value.length >= 3 ? value[2] : margin[0]; + margin[3] = value.length >= 4 ? value[3] : margin[1]; + } + _ctx.margin = margin; + } + }); + var _autoPaging = false; /** - * @name autoPaging - * @type {boolean} - * @default true + * Gets or sets the auto paging mode. When auto paging is enabled, the context2d will automatically draw on the + * next page if a shape or text chunk doesn't fit entirely on the current page. The context2d will create new + * pages if required. + * + * Context2d supports different modes: + * + * @name Context2D#autoPaging + * @type {boolean|"slice"|"text"} + * @default false */ Object.defineProperty(this, "autoPaging", { get: function() { return _autoPaging; }, set: function(value) { - _autoPaging = Boolean(value); + _autoPaging = value; } }); @@ -1293,7 +1338,6 @@ import { return; } - y = getBaseline.call(this, y); var degs = rad2deg(this.ctx.transform.rotation); // We only use X axis as scale hint @@ -1331,7 +1375,6 @@ import { } maxWidth = isNaN(maxWidth) ? undefined : maxWidth; - y = getBaseline.call(this, y); var degs = rad2deg(this.ctx.transform.rotation); var scale = this.ctx.transform.scaleX; @@ -1534,6 +1577,15 @@ import { } }; + var hasMargins = function() { + return ( + this.margin[0] > 0 || + this.margin[1] > 0 || + this.margin[2] > 0 || + this.margin[3] > 0 + ); + }; + /** * Draws an image, canvas, or video onto the canvas * @@ -1623,13 +1675,26 @@ import { for (var i = min; i < max + 1; i++) { this.pdf.setPage(i); + var pageWidthMinusMargins = + this.pdf.internal.pageSize.width - this.margin[3] - this.margin[1]; + var topMargin = i === 1 ? this.posY + this.margin[0] : this.margin[0]; + var firstPageHeight = + this.pdf.internal.pageSize.height - + this.posY - + this.margin[0] - + this.margin[2]; + var pageHeightMinusMargins = + this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; + var previousPageHeightSum = + i === 1 ? 0 : firstPageHeight + (i - 2) * pageHeightMinusMargins; + if (this.ctx.clip_path.length !== 0) { var tmpPaths = this.path; clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); this.path = pathPositionRedo( clipPath, - this.posX, - -1 * this.pdf.internal.pageSize.height * (i - 1) + this.posY + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset ); drawPaths.call(this, "fill", true); this.path = tmpPaths; @@ -1637,9 +1702,25 @@ import { var tmpRect = JSON.parse(JSON.stringify(xRect)); tmpRect = pathPositionRedo( [tmpRect], - this.posX, - -1 * this.pdf.internal.pageSize.height * (i - 1) + this.posY + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset )[0]; + + const needsClipping = (i > min || i < max) && hasMargins.call(this); + + if (needsClipping) { + this.pdf.saveGraphicsState(); + this.pdf + .rect( + this.margin[3], + this.margin[0], + pageWidthMinusMargins, + pageHeightMinusMargins, + null + ) + .clip() + .discardPath(); + } this.pdf.addImage( img, "JPEG", @@ -1651,6 +1732,9 @@ import { null, angle ); + if (needsClipping) { + this.pdf.restoreGraphicsState(); + } } } else { this.pdf.addImage( @@ -1670,20 +1754,23 @@ import { var getPagesByPath = function(path, pageWrapX, pageWrapY) { var result = []; pageWrapX = pageWrapX || this.pdf.internal.pageSize.width; - pageWrapY = pageWrapY || this.pdf.internal.pageSize.height; + pageWrapY = + pageWrapY || + this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; + var yOffset = this.posY + this.ctx.prevPageLastElemOffset; switch (path.type) { default: case "mt": case "lt": - result.push(Math.floor((path.y + this.posY) / pageWrapY) + 1); + result.push(Math.floor((path.y + yOffset) / pageWrapY) + 1); break; case "arc": result.push( - Math.floor((path.y + this.posY - path.radius) / pageWrapY) + 1 + Math.floor((path.y + yOffset - path.radius) / pageWrapY) + 1 ); result.push( - Math.floor((path.y + this.posY + path.radius) / pageWrapY) + 1 + Math.floor((path.y + yOffset + path.radius) / pageWrapY) + 1 ); break; case "qct": @@ -1695,10 +1782,13 @@ import { path.x, path.y ); - result.push(Math.floor(rectOfQuadraticCurve.y / pageWrapY) + 1); + result.push( + Math.floor((rectOfQuadraticCurve.y + yOffset) / pageWrapY) + 1 + ); result.push( Math.floor( - (rectOfQuadraticCurve.y + rectOfQuadraticCurve.h) / pageWrapY + (rectOfQuadraticCurve.y + rectOfQuadraticCurve.h + yOffset) / + pageWrapY ) + 1 ); break; @@ -1713,15 +1803,18 @@ import { path.x, path.y ); - result.push(Math.floor(rectOfBezierCurve.y / pageWrapY) + 1); result.push( - Math.floor((rectOfBezierCurve.y + rectOfBezierCurve.h) / pageWrapY) + - 1 + Math.floor((rectOfBezierCurve.y + yOffset) / pageWrapY) + 1 + ); + result.push( + Math.floor( + (rectOfBezierCurve.y + rectOfBezierCurve.h + yOffset) / pageWrapY + ) + 1 ); break; case "rect": - result.push(Math.floor((path.y + this.posY) / pageWrapY) + 1); - result.push(Math.floor((path.y + path.h + this.posY) / pageWrapY) + 1); + result.push(Math.floor((path.y + yOffset) / pageWrapY) + 1); + result.push(Math.floor((path.y + path.h + yOffset) / pageWrapY) + 1); } for (var i = 0; i < result.length; i += 1) { @@ -1819,13 +1912,26 @@ import { this.lineWidth = lineWidth; this.lineJoin = lineJoin; + var pageWidthMinusMargins = + this.pdf.internal.pageSize.width - this.margin[3] - this.margin[1]; + var topMargin = k === 1 ? this.posY + this.margin[0] : this.margin[0]; + var firstPageHeight = + this.pdf.internal.pageSize.height - + this.posY - + this.margin[0] - + this.margin[2]; + var pageHeightMinusMargins = + this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; + var previousPageHeightSum = + k === 1 ? 0 : firstPageHeight + (k - 2) * pageHeightMinusMargins; + if (this.ctx.clip_path.length !== 0) { var tmpPaths = this.path; clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); this.path = pathPositionRedo( clipPath, - this.posX, - -1 * this.pdf.internal.pageSize.height * (k - 1) + this.posY + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset ); drawPaths.call(this, rule, true); this.path = tmpPaths; @@ -1833,11 +1939,28 @@ import { tmpPath = JSON.parse(JSON.stringify(origPath)); this.path = pathPositionRedo( tmpPath, - this.posX, - -1 * this.pdf.internal.pageSize.height * (k - 1) + this.posY + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset ); if (isClip === false || k === 0) { + const needsClipping = (k > min || k < max) && hasMargins.call(this); + if (needsClipping) { + this.pdf.saveGraphicsState(); + this.pdf + .rect( + this.margin[3], + this.margin[0], + pageWidthMinusMargins, + pageHeightMinusMargins, + null + ) + .clip() + .discardPath(); + } drawPaths.call(this, rule, isClip); + if (needsClipping) { + this.pdf.restoreGraphicsState(); + } } this.lineWidth = oldLineWidth; } @@ -2034,6 +2157,13 @@ import { } }; + var getTextBottom = function(yBaseLine) { + var height = + this.pdf.internal.getFontSize() / this.pdf.internal.scaleFactor; + var descent = height * (this.pdf.internal.getLineHeightFactor() - 1); + return yBaseLine + descent; + }; + Context2D.prototype.createLinearGradient = function createLinearGradient() { var canvasGradient = function canvasGradient() {}; @@ -2143,26 +2273,25 @@ import { break; } - var pt = this.ctx.transform.applyToPoint(new Point(options.x, options.y)); + var textDimensions = this.pdf.getTextDimensions(options.text); + var yBaseLine = getBaseline.call(this, options.y); + var yBottom = getTextBottom.call(this, yBaseLine); + var yTop = yBottom - textDimensions.h; + + var pt = this.ctx.transform.applyToPoint(new Point(options.x, yBaseLine)); var decomposedTransformationMatrix = this.ctx.transform.decompose(); var matrix = new Matrix(); matrix = matrix.multiply(decomposedTransformationMatrix.translate); matrix = matrix.multiply(decomposedTransformationMatrix.skew); matrix = matrix.multiply(decomposedTransformationMatrix.scale); - var textDimensions = this.pdf.getTextDimensions(options.text); - var textRect = this.ctx.transform.applyToRectangle( - new Rectangle(options.x, options.y, textDimensions.w, textDimensions.h) + var baselineRect = this.ctx.transform.applyToRectangle( + new Rectangle(options.x, yBaseLine, textDimensions.w, textDimensions.h) ); - var textXRect = matrix.applyToRectangle( - new Rectangle( - options.x, - options.y - textDimensions.h, - textDimensions.w, - textDimensions.h - ) + var textBounds = matrix.applyToRectangle( + new Rectangle(options.x, yTop, textDimensions.w, textDimensions.h) ); - var pageArray = getPagesByPath.call(this, textXRect); + var pageArray = getPagesByPath.call(this, textBounds); var pages = []; for (var ii = 0; ii < pageArray.length; ii += 1) { if (pages.indexOf(pageArray[ii]) === -1) { @@ -2173,28 +2302,43 @@ import { sortPages(pages); var clipPath, oldSize, oldLineWidth; - if (this.autoPaging === true) { + if (this.autoPaging) { var min = pages[0]; var max = pages[pages.length - 1]; for (var i = min; i < max + 1; i++) { this.pdf.setPage(i); + var topMargin = i === 1 ? this.posY + this.margin[0] : this.margin[0]; + var firstPageHeight = + this.pdf.internal.pageSize.height - + this.posY - + this.margin[0] - + this.margin[2]; + var pageHeightMinusBottomMargin = + this.pdf.internal.pageSize.height - this.margin[2]; + var pageHeightMinusMargins = + pageHeightMinusBottomMargin - this.margin[0]; + var pageWidthMinusRightMargin = + this.pdf.internal.pageSize.width - this.margin[1]; + var pageWidthMinusMargins = pageWidthMinusRightMargin - this.margin[3]; + var previousPageHeightSum = + i === 1 ? 0 : firstPageHeight + (i - 2) * pageHeightMinusMargins; + if (this.ctx.clip_path.length !== 0) { var tmpPaths = this.path; clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); this.path = pathPositionRedo( clipPath, - this.posX, - -1 * this.pdf.internal.pageSize.height * (i - 1) + this.posY + this.posX + this.margin[3], + -1 * previousPageHeightSum + topMargin ); drawPaths.call(this, "fill", true); this.path = tmpPaths; } - var tmpRect = JSON.parse(JSON.stringify(textRect)); - tmpRect = pathPositionRedo( - [tmpRect], - this.posX, - -1 * this.pdf.internal.pageSize.height * (i - 1) + this.posY + var textBoundsOnPage = pathPositionRedo( + [JSON.parse(JSON.stringify(textBounds))], + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset )[0]; if (options.scale >= 0.01) { @@ -2203,12 +2347,75 @@ import { oldLineWidth = this.lineWidth; this.lineWidth = oldLineWidth * options.scale; } - this.pdf.text(options.text, tmpRect.x, tmpRect.y, { - angle: options.angle, - align: textAlign, - renderingMode: options.renderingMode, - maxWidth: options.maxWidth - }); + + var doSlice = this.autoPaging !== "text"; + + if ( + doSlice || + textBoundsOnPage.y + textBoundsOnPage.h <= pageHeightMinusBottomMargin + ) { + if ( + doSlice || + (textBoundsOnPage.y >= topMargin && + textBoundsOnPage.x <= pageWidthMinusRightMargin) + ) { + var croppedText = doSlice + ? options.text + : this.pdf.splitTextToSize( + options.text, + options.maxWidth || + pageWidthMinusRightMargin - textBoundsOnPage.x + )[0]; + var baseLineRectOnPage = pathPositionRedo( + [JSON.parse(JSON.stringify(baselineRect))], + this.posX + this.margin[3], + -previousPageHeightSum + + topMargin + + this.ctx.prevPageLastElemOffset + )[0]; + + const needsClipping = + doSlice && (i > min || i < max) && hasMargins.call(this); + + if (needsClipping) { + this.pdf.saveGraphicsState(); + this.pdf + .rect( + this.margin[3], + this.margin[0], + pageWidthMinusMargins, + pageHeightMinusMargins, + null + ) + .clip() + .discardPath(); + } + + this.pdf.text( + croppedText, + baseLineRectOnPage.x, + baseLineRectOnPage.y, + { + angle: options.angle, + align: textAlign, + renderingMode: options.renderingMode + } + ); + + if (needsClipping) { + this.pdf.restoreGraphicsState(); + } + } + } else { + // This text is the last element of the page, but it got cut off due to the margin + // so we render it in the next page + + if (textBoundsOnPage.y < pageHeightMinusBottomMargin) { + // As a result, all other elements have their y offset increased + this.ctx.prevPageLastElemOffset += + pageHeightMinusBottomMargin - textBoundsOnPage.y; + } + } if (options.scale >= 0.01) { this.pdf.setFontSize(oldSize); diff --git a/src/modules/html.js b/src/modules/html.js index fe46e9de0..fd66e15cd 100644 --- a/src/modules/html.js +++ b/src/modules/html.js @@ -462,9 +462,13 @@ import { globalObject } from "../libs/globalObject.js"; ); delete options.onrendered; - pdf.context2d.autoPaging = true; + pdf.context2d.autoPaging = + typeof this.opt.autoPaging === "undefined" + ? true + : this.opt.autoPaging; pdf.context2d.posX = this.opt.x; pdf.context2d.posY = this.opt.y; + pdf.context2d.margin = this.opt.margin; pdf.context2d.fontFaces = fontFaces; if (fontFaces) { @@ -1019,7 +1023,22 @@ import { globalObject } from "../libs/globalObject.js"; * @param {HTMLElement|string} source The source HTMLElement or a string containing HTML. * @param {Object} [options] Collection of settings * @param {function} [options.callback] The mandatory callback-function gets as first parameter the current jsPDF instance - * @param {number|array} [options.margin] Array of margins [left, bottom, right, top] + * @param {(number|number[])=} [options.margin] Page margins [top, right, bottom, left]. Default is 0. + * @param {(boolean|'slice'|'text')=} [options.autoPaging] The auto paging mode. + * + * Default is true. * @param {string} [options.filename] name of the file * @param {HTMLOptionImage} [options.image] image settings when converting HTML to image * @param {Html2CanvasOptions} [options.html2canvas] html2canvas options diff --git a/test/reference/html-margin-page-break-image.pdf b/test/reference/html-margin-page-break-image.pdf new file mode 100644 index 000000000..4239fc56d Binary files /dev/null and b/test/reference/html-margin-page-break-image.pdf differ diff --git a/test/reference/html-margin-page-break-slice.pdf b/test/reference/html-margin-page-break-slice.pdf new file mode 100644 index 000000000..8303e85dd Binary files /dev/null and b/test/reference/html-margin-page-break-slice.pdf differ diff --git a/test/reference/html-margin-page-break-text.pdf b/test/reference/html-margin-page-break-text.pdf new file mode 100644 index 000000000..76d810500 Binary files /dev/null and b/test/reference/html-margin-page-break-text.pdf differ diff --git a/test/reference/html-margin-page-break.pdf b/test/reference/html-margin-page-break.pdf new file mode 100644 index 000000000..caa8d5f35 Binary files /dev/null and b/test/reference/html-margin-page-break.pdf differ diff --git a/test/reference/html-margin-x-y-text.pdf b/test/reference/html-margin-x-y-text.pdf new file mode 100644 index 000000000..5164f48d2 Binary files /dev/null and b/test/reference/html-margin-x-y-text.pdf differ diff --git a/test/reference/html-margin-x-y.pdf b/test/reference/html-margin-x-y.pdf new file mode 100644 index 000000000..6a8a16ab2 Binary files /dev/null and b/test/reference/html-margin-x-y.pdf differ diff --git a/test/reference/html-margin.pdf b/test/reference/html-margin.pdf new file mode 100644 index 000000000..6a8a16ab2 Binary files /dev/null and b/test/reference/html-margin.pdf differ diff --git a/test/reference/html-width-100-windowWidth-500.pdf b/test/reference/html-width-100-windowWidth-500.pdf index 4fb42d34d..7d7b94690 100644 Binary files a/test/reference/html-width-100-windowWidth-500.pdf and b/test/reference/html-width-100-windowWidth-500.pdf differ diff --git a/test/reference/html-width-210-windowWidth-1000.pdf b/test/reference/html-width-210-windowWidth-1000.pdf index c7704ddd0..d1d8839fc 100644 Binary files a/test/reference/html-width-210-windowWidth-1000.pdf and b/test/reference/html-width-210-windowWidth-1000.pdf differ diff --git a/test/reference/html-width-210-windowWidth-250.pdf b/test/reference/html-width-210-windowWidth-250.pdf index 287db7ba9..2d8fdbe64 100644 Binary files a/test/reference/html-width-210-windowWidth-250.pdf and b/test/reference/html-width-210-windowWidth-250.pdf differ diff --git a/test/reference/html-width-210-windowWidth-500.pdf b/test/reference/html-width-210-windowWidth-500.pdf index 228565a61..f32fe57f0 100644 Binary files a/test/reference/html-width-210-windowWidth-500.pdf and b/test/reference/html-width-210-windowWidth-500.pdf differ diff --git a/test/reference/html-width-300-windowWidth-500-scale-2.pdf b/test/reference/html-width-300-windowWidth-500-scale-2.pdf index 54058571e..d32682fd1 100644 Binary files a/test/reference/html-width-300-windowWidth-500-scale-2.pdf and b/test/reference/html-width-300-windowWidth-500-scale-2.pdf differ diff --git a/test/reference/html-width-300-windowWidth-500.pdf b/test/reference/html-width-300-windowWidth-500.pdf index 3cdfc67a5..249776e0b 100644 Binary files a/test/reference/html-width-300-windowWidth-500.pdf and b/test/reference/html-width-300-windowWidth-500.pdf differ diff --git a/test/reference/html-width-default-windowWidth-default.pdf b/test/reference/html-width-default-windowWidth-default.pdf index 227caa0e0..575828fde 100644 Binary files a/test/reference/html-width-default-windowWidth-default.pdf and b/test/reference/html-width-default-windowWidth-default.pdf differ diff --git a/test/reference/html-x-y.pdf b/test/reference/html-x-y.pdf new file mode 100644 index 000000000..6a8a16ab2 Binary files /dev/null and b/test/reference/html-x-y.pdf differ diff --git a/test/specs/context2d.spec.js b/test/specs/context2d.spec.js index e55810e01..6d6343a9b 100644 --- a/test/specs/context2d.spec.js +++ b/test/specs/context2d.spec.js @@ -676,4 +676,20 @@ describe("Context2D: standard tests", () => { "1. w" ]); }); + + it("margin property shorthands", () => { + const doc = new jsPDF(); + const ctx = doc.context2d; + expect(ctx.margin).toEqual([0, 0, 0, 0]); + ctx.margin = 1; + expect(ctx.margin).toEqual([1, 1, 1, 1]); + ctx.margin = [1]; + expect(ctx.margin).toEqual([1, 1, 1, 1]); + ctx.margin = [1, 2]; + expect(ctx.margin).toEqual([1, 2, 1, 2]); + ctx.margin = [1, 2, 3]; + expect(ctx.margin).toEqual([1, 2, 3, 2]); + ctx.margin = [1, 2, 3, 4]; + expect(ctx.margin).toEqual([1, 2, 3, 4]); + }); }); diff --git a/test/specs/html.spec.js b/test/specs/html.spec.js index c5f8318ce..1de962b0d 100644 --- a/test/specs/html.spec.js +++ b/test/specs/html.spec.js @@ -237,4 +237,162 @@ describe("Module: html", () => { comparePdf(doc.output(), "html-font-faces.pdf", "html"); }); + + it("html margin insets properly", async () => { + const doc = jsPDF({ floatPrecision: 2, unit: "pt" }); + doc.line(30, 10, 100, 10); + doc.line(30, 10, 30, 100); + await new Promise(resolve => + doc.html( + "
", + { + callback: resolve, + margin: [10, 30] + } + ) + ); + comparePdf(doc.output(), "html-margin.pdf", "html"); + }); + + it("html margin on page break", async () => { + const doc = jsPDF({ floatPrecision: 2, unit: "pt", format: [100, 100] }); + await new Promise(resolve => + doc.html( + "
", + { + callback: resolve, + margin: [10, 30, 10, 30] + } + ) + ); + const numberOfPages = doc.getNumberOfPages(); + const pageWidth = doc.internal.pageSize.getWidth(); + const pageHeight = doc.internal.pageSize.getHeight(); + for (let i = 1; i <= numberOfPages; i++) { + doc.setPage(i); + doc.rect(30, 10, pageWidth - 60, pageHeight - 20); + } + doc.line(0, 50, 100, 50); + comparePdf(doc.output(), "html-margin-page-break.pdf", "html"); + }); + + it("page break with image", async () => { + const doc = jsPDF({ floatPrecision: 2, unit: "pt", format: [100, 100] }); + await new Promise(resolve => + doc.html( + '', + { + callback: resolve, + margin: [10, 30, 10, 30] + } + ) + ); + const numberOfPages = doc.getNumberOfPages(); + const pageWidth = doc.internal.pageSize.getWidth(); + const pageHeight = doc.internal.pageSize.getHeight(); + for (let i = 1; i <= numberOfPages; i++) { + doc.setPage(i); + doc.rect(30, 10, pageWidth - 60, pageHeight - 20); + } + doc.line(0, 50, 100, 50); + comparePdf(doc.output(), "html-margin-page-break-image.pdf", "html"); + }); + + it("html x, y offsets properly", async () => { + const doc = jsPDF({ floatPrecision: 2, unit: "pt" }); + doc.line(30, 10, 100, 10); + doc.line(30, 10, 30, 100); + await new Promise(resolve => + doc.html( + "
", + { + callback: resolve, + x: 30, + y: 10 + } + ) + ); + comparePdf(doc.output(), "html-x-y.pdf", "html"); + }); + + it("html x, y + margin offsets properly", async () => { + const doc = jsPDF({ floatPrecision: 2, unit: "pt" }); + doc.line(30, 10, 100, 10); + doc.line(30, 10, 30, 100); + await new Promise(resolve => + doc.html( + "
", + { + callback: resolve, + x: 10, + y: 3, + margin: [7, 20] + } + ) + ); + comparePdf(doc.output(), "html-margin-x-y.pdf", "html"); + }); + + it("html x, y + margin offsets properly", async () => { + const doc = jsPDF({ floatPrecision: 2, unit: "pt" }); + doc.line(30, 10, 100, 10); + doc.line(30, 10, 30, 100); + await new Promise(resolve => + doc.html("Lorem Ipsum", { + callback: resolve, + x: 10, + y: 3, + margin: [7, 20] + }) + ); + comparePdf(doc.output(), "html-margin-x-y-text.pdf", "html"); + }); + + it("page break with autoPaging: 'text'", async () => { + const text = Array.from({ length: 200 }) + .map((_, i) => `ABC${i}`) + .join(" "); + + const doc = jsPDF({ floatPrecision: 2, unit: "pt" }); + await new Promise(resolve => + doc.html(`${text}`, { + callback: resolve, + margin: [10, 30, 10, 30], + autoPaging: "text" + }) + ); + + const numberOfPages = doc.getNumberOfPages(); + const pageWidth = doc.internal.pageSize.getWidth(); + const pageHeight = doc.internal.pageSize.getHeight(); + for (let i = 1; i <= numberOfPages; i++) { + doc.setPage(i); + doc.rect(30, 10, pageWidth - 60, pageHeight - 20); + } + comparePdf(doc.output(), "html-margin-page-break-text.pdf", "html"); + }); + + it("page break with autoPaging: 'slice'", async () => { + const text = Array.from({ length: 200 }) + .map((_, i) => `ABC${i}`) + .join(" "); + + const doc = jsPDF({ floatPrecision: 2, unit: "pt" }); + await new Promise(resolve => + doc.html(`${text}`, { + callback: resolve, + margin: [10, 30, 10, 30], + autoPaging: "slice" + }) + ); + + const numberOfPages = doc.getNumberOfPages(); + const pageWidth = doc.internal.pageSize.getWidth(); + const pageHeight = doc.internal.pageSize.getHeight(); + for (let i = 1; i <= numberOfPages; i++) { + doc.setPage(i); + doc.rect(30, 10, pageWidth - 60, pageHeight - 20); + } + comparePdf(doc.output(), "html-margin-page-break-slice.pdf", "html"); + }); }); diff --git a/types/index.d.ts b/types/index.d.ts index 516c94003..320b8c6ec 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -223,6 +223,7 @@ declare module "jspdf" { export interface HTMLOptions { callback?: (doc: jsPDF) => void; margin?: number | number[]; + autoPaging?: boolean | "slice" | "text"; filename?: string; image?: HTMLOptionImage; html2canvas?: Html2CanvasOptions; @@ -372,6 +373,7 @@ declare module "jspdf" { export interface Context2d { autoPaging: boolean; + margin: number[]; fillStyle: string | Gradient; filter: string; font: string;