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:
+ *
+ * -
+ *
false
: Auto paging is disabled.
+ *
+ * -
+ *
true
or 'slice'
: Will cut shapes or text chunks across page breaks. Will possibly
+ * slice text in half, making it difficult to read.
+ *
+ * -
+ *
'text'
: Trys not to cut text in half across page breaks. Works best for documents consisting
+ * mostly of a single column of text.
+ *
+ *
+ * @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.
+ *
+ * -
+ *
false
: Auto paging is disabled.
+ *
+ * -
+ *
true
or 'slice'
: Will cut shapes or text chunks across page breaks. Will possibly
+ * slice text in half, making it difficult to read.
+ *
+ * -
+ *
'text'
: Trys not to cut text in half across page breaks. Works best for documents consisting
+ * mostly of a single column of text.
+ *
+ *
+ * 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;