diff --git a/src/jspdf.js b/src/jspdf.js index ee3ba1034..94f3a5f69 100644 --- a/src/jspdf.js +++ b/src/jspdf.js @@ -2001,7 +2001,7 @@ function jsPDF(options) { font: font, out: out, newObject: newObject, - putStream: putStream, + putStream: putStream }); if (font.isAlreadyPutted !== true) { diff --git a/src/libs/FileSaver.js b/src/libs/FileSaver.js index ad1d302d1..89586ea36 100644 --- a/src/libs/FileSaver.js +++ b/src/libs/FileSaver.js @@ -90,7 +90,8 @@ var saveAs = /* noop */ } : // Use download attribute first if possible (#193 Lumia mobile) unless this is a native app - (typeof HTMLAnchorElement !== "undefined" && "download" in HTMLAnchorElement.prototype) + typeof HTMLAnchorElement !== "undefined" && + "download" in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) { var URL = _global.URL || _global.webkitURL; var a = document.createElement("a"); diff --git a/src/libs/pdfname.js b/src/libs/pdfname.js index 8b6b3e6ca..9987d35f8 100644 --- a/src/libs/pdfname.js +++ b/src/libs/pdfname.js @@ -5,8 +5,11 @@ */ function toPDFName(str) { // eslint-disable-next-line no-control-regex - if(/[^\u0000-\u00ff]/.test(str)){ // non ascii string - throw new Error('Invalid PDF Name Object: ' + str + ', Only accept ASCII characters.'); + if (/[^\u0000-\u00ff]/.test(str)) { + // non ascii string + throw new Error( + "Invalid PDF Name Object: " + str + ", Only accept ASCII characters." + ); } var result = "", strLength = str.length; diff --git a/src/modules/cell.js b/src/modules/cell.js index 1c69dbaa8..fce190739 100644 --- a/src/modules/cell.js +++ b/src/modules/cell.js @@ -442,7 +442,10 @@ import { jsPDF } from "../jspdf.js"; }); } - if (autoSize || (Array.isArray(headers) && typeof headers[0] === "string")) { + if ( + autoSize || + (Array.isArray(headers) && typeof headers[0] === "string") + ) { var headerName; for (i = 0; i < headerNames.length; i += 1) { headerName = headerNames[i]; diff --git a/src/modules/context2d.js b/src/modules/context2d.js index eb7edee14..2d30c8ea5 100644 --- a/src/modules/context2d.js +++ b/src/modules/context2d.js @@ -51,6 +51,8 @@ import { this.currentPoint = ctx.currentPoint || new Point(); this.miterLimit = ctx.miterLimit || 10.0; this.lastPoint = ctx.lastPoint || new Point(); + this.lineDashOffset = ctx.lineDashOffset || 0.0; + this.lineDash = ctx.lineDash || []; this.ignoreClearRect = typeof ctx.ignoreClearRect === "boolean" ? ctx.ignoreClearRect : true; @@ -641,6 +643,33 @@ import { } }); + /** + * A float specifying the amount of the line dash offset. The default value is 0.0. + * + * @name lineDashOffset + * @default 0.0 + */ + Object.defineProperty(this, "lineDashOffset", { + get: function() { + return this.ctx.lineDashOffset; + }, + set: function(value) { + this.ctx.lineDashOffset = value; + setLineDash.call(this); + } + }); + + // Not HTML API + Object.defineProperty(this, "lineDash", { + get: function() { + return this.ctx.lineDash; + }, + set: function(value) { + this.ctx.lineDash = value; + setLineDash.call(this); + } + }); + // Not HTML API Object.defineProperty(this, "ignoreClearRect", { get: function() { @@ -652,6 +681,32 @@ import { }); }; + /** + * Sets the line dash pattern used when stroking lines. + * @name setLineDash + * @function + * @description It uses an array of values that specify alternating lengths of lines and gaps which describe the pattern. + */ + Context2D.prototype.setLineDash = function(dashArray) { + this.lineDash = dashArray; + }; + + /** + * gets the current line dash pattern. + * @name getLineDash + * @function + * @returns {Array} An Array of numbers that specify distances to alternately draw a line and a gap (in coordinate space units). If the number, when setting the elements, is odd, the elements of the array get copied and concatenated. For example, setting the line dash to [5, 15, 25] will result in getting back [5, 15, 25, 5, 15, 25]. + */ + Context2D.prototype.getLineDash = function() { + if (this.lineDash.length % 2) { + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getLineDash#return_value + return this.lineDash.concat(this.lineDash); + } else { + // The copied value is returned to prevent contamination from outside. + return this.lineDash.slice(); + } + }; + Context2D.prototype.fill = function() { pathPreProcess.call(this, "fill", false); }; @@ -1105,6 +1160,8 @@ import { this.lineCap = this.ctx.lineCap; this.lineWidth = this.ctx.lineWidth; this.lineJoin = this.ctx.lineJoin; + this.lineDash = this.ctx.lineDash; + this.lineDashOffset = this.ctx.lineDashOffset; } }; @@ -2383,4 +2440,32 @@ import { Math.round(maxy - miny) ); }; + + var getPrevLineDashValue = function(lineDash, lineDashOffset) { + return JSON.stringify({ + lineDash: lineDash, + lineDashOffset: lineDashOffset + }); + }; + + var setLineDash = function() { + // Avoid unnecessary line dash declarations. + if ( + !this.prevLineDash && + !this.ctx.lineDash.length && + !this.ctx.lineDashOffset + ) { + return; + } + + // Avoid unnecessary line dash declarations. + const nextLineDash = getPrevLineDashValue( + this.ctx.lineDash, + this.ctx.lineDashOffset + ); + if (this.prevLineDash !== nextLineDash) { + this.pdf.setLineDash(this.ctx.lineDash, this.ctx.lineDashOffset); + this.prevLineDash = nextLineDash; + } + }; })(jsPDF.API); diff --git a/test/reference/lineDash.pdf b/test/reference/lineDash.pdf new file mode 100644 index 000000000..e86d50702 Binary files /dev/null and b/test/reference/lineDash.pdf differ diff --git a/test/specs/context2d.spec.js b/test/specs/context2d.spec.js index 669f5af17..e55810e01 100644 --- a/test/specs/context2d.spec.js +++ b/test/specs/context2d.spec.js @@ -485,6 +485,99 @@ describe("Context2D: standard tests", () => { comparePdf(doc.output(), "moveTo_lineTo_stroke_fill.pdf", "context2d"); }); + it("context2d: setLineDash(), lineDashOffset", () => { + var doc = new jsPDF({ + orientation: "p", + unit: "pt", + format: "a4", + floatPrecision: 2 + }); + var ctx = doc.context2d; + + var y = 20; + var pad = 20; + + ctx.lineWidth = 5; + ctx.beginPath(); + ctx.moveTo(20, y); + ctx.lineTo(200, y); + ctx.stroke(); + y += pad; + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([10, 20]); + ctx.lineDashOffset = 10; + ctx.moveTo(20, y); + ctx.lineTo(200, y); + ctx.stroke(); + y += pad; + ctx.beginPath(); + ctx.setLineDash([]); + ctx.lineDashOffset = 0; + ctx.moveTo(20, y); + ctx.lineTo(200, y); + ctx.stroke(); + y += pad; + ctx.beginPath(); + ctx.setLineDash([10, 20]); + ctx.lineDashOffset = 10; + ctx.moveTo(20, y); + ctx.lineTo(200, y); + ctx.stroke(); + y += pad; + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([]); + ctx.lineDashOffset = 0; + ctx.moveTo(20, y); + ctx.lineTo(200, y); + ctx.stroke(); + y += pad; + ctx.restore(); + ctx.beginPath(); + ctx.moveTo(20, y); + ctx.lineTo(200, y); + ctx.stroke(); + y += pad; + ctx.save(); + ctx.beginPath(); + ctx.moveTo(20, y); + ctx.lineTo(200, y); + ctx.stroke(); + y += pad; + ctx.restore(); + ctx.beginPath(); + ctx.moveTo(20, y); + ctx.lineTo(200, y); + ctx.stroke(); + y += pad; + ctx.restore(); + ctx.beginPath(); + ctx.moveTo(20, y); + ctx.lineTo(200, y); + ctx.stroke(); + + comparePdf(doc.output(), "lineDash.pdf", "context2d"); + }); + + it("context2d: getLineDash()", () => { + var doc = new jsPDF({ + orientation: "p", + unit: "pt", + format: "a4", + floatPrecision: 2 + }); + var ctx = doc.context2d; + + expect(ctx.getLineDash()).toEqual([]); + + ctx.setLineDash([1, 2]); + expect(ctx.getLineDash()).toEqual([1, 2]); + + ctx.setLineDash([1, 2, 3]); + expect(ctx.getLineDash()).toEqual([1, 2, 3, 1, 2, 3]); + }); + it("context2d: textBaseline", () => { var doc = new jsPDF({ orientation: "p",