From d51ce24d207c4ff0e394f765f94db21930177788 Mon Sep 17 00:00:00 2001 From: shaehn Date: Fri, 1 Nov 2019 21:08:07 -0400 Subject: [PATCH 1/3] Text changes fix #149 #52 #42 plus line additions This commit allows user to disable automatic text wrapping. It also fixes the vertical placement of text in textbox. Horizontal text justification has been added (unfortunately, does not work with html). New line drawing options now availbable, lineCap, lineJoin. Documentation updated to reflect changes. --- docs/Recipe.html | 713 +++++++++++++++++++++--- docs/Recipe.js.html | 544 +++++++++--------- docs/annotation.js.html | 684 +++++++++++------------ docs/appendPage.js.html | 92 ++-- docs/colors.js.html | 794 +++++++++++++------------- docs/encrypt.js.html | 214 ++++---- docs/font.js.html | 74 +-- docs/global.html | 20 +- docs/image.js.html | 252 ++++----- docs/index.html | 2 +- docs/info.js.html | 652 +++++++++++----------- docs/insertPage.js.html | 156 +++--- docs/overlay.js.html | 146 ++--- docs/page.js.html | 400 +++++++------- docs/split.js.html | 46 +- docs/text.helper.js.html | 311 +++++------ docs/text.js.html | 1039 ++++++++++++++++++++--------------- docs/utils.js.html | 120 ++-- docs/vector-line.js.html | 173 +++--- docs/vector-polygon.js.html | 225 ++++---- docs/vector.js.html | 615 ++++++++++----------- lib/text.helper.js | 49 +- lib/text.js | 253 ++++++--- lib/vector-line.js | 23 +- lib/vector-polygon.js | 13 +- lib/vector.helper.js | 56 +- tests/text-centering.js | 158 +++++- tests/vector.js | 28 +- 28 files changed, 4399 insertions(+), 3453 deletions(-) diff --git a/docs/Recipe.html b/docs/Recipe.html index ded2a59..ff11220 100644 --- a/docs/Recipe.html +++ b/docs/Recipe.html @@ -1437,11 +1437,11 @@

(static) chrom This file will be merged with existing set of known colors. The color values must be specified as hex values.

For example, -{ -'rgb': {'purple':'ff00ff', 'red':'#ff0000'}, -'cmyk': {'cyan':'ff000000', 'magenta':'%0,100,0,0'}, -'gray': {'grey':'#33'} -}

+ { + 'rgb': {'purple':'ff00ff', 'red':'#ff0000'}, + 'cmyk': {'cyan':'ff000000', 'magenta':'%0,100,0,0'}, + 'gray': {'grey':'#33'} + }

@@ -3002,7 +3002,7 @@

(static) elli
Source:
@@ -4914,7 +4914,7 @@

(static) lineSource:
@@ -5182,6 +5182,39 @@
Properties
+ + + opacity + + + + + +number + + + + + + + + + <optional>
+ + + + + + + + + + +

how transparent should line be, from 0: invisible to 1: opaque

+ + + + dash @@ -5210,7 +5243,139 @@
Properties
-

The dash style [number, number]

+

The dash pattern [dashSize, gapSize] or [dashAndGapSize]

+ + + + + + + dashPhase + + + + + +number + + + + + + + + + <optional>
+ + + + + + + + + + +

distance into dash pattern at which to start dash (default: 0, immediately)

+ + + + + + + lineCap + + + + + +string + + + + + + + + + <optional>
+ + + + + + + + + + +

open line end style, 'butt', 'round', or 'square' (default: 'round')

+ + + + + + + lineJoin + + + + + +string + + + + + + + + + <optional>
+ + + + + + + + + + +

joined line end style, 'miter', 'round', or 'bevel' (default: 'round')

+ + + + + + + miterLimit + + + + + +number + + + + + + + + + <optional>
+ + + + + + + + + + +

limit at which 'miter' joins are forced to 'bevel' (default: 1.414)

@@ -5524,15 +5689,78 @@
Properties
- fill + lineWidth -string -| +number + + + + + + + + + <optional>
+ + + + + + + + + + +

The line width

+ + + + + + + opacity + + + + + +number + + + + + + + + + <optional>
+ + + + + + + + + +

how transparent should line be, from 0: invisible to 1: opaque

+ + + + + + + dash + + + + + Array.<number> @@ -5553,14 +5781,14 @@
Properties
-

HexColor, PercentColor or DecimalColor

+

The dash pattern [dashSize, gapSize] or [dashAndGapSize]

- lineWidth + dashPhase @@ -5586,20 +5814,20 @@
Properties
-

The line width

+

distance into dash pattern at which to start dash (default: 0, immediately)

- opacity + lineCap -number +string @@ -5619,20 +5847,20 @@
Properties
-

The opacity

+

open line end style, 'butt', 'round', or 'square' (default: 'round')

- dash + lineJoin -Array.<number> +string @@ -5652,7 +5880,40 @@
Properties
-

The dash style [number, number]

+

joined line end style, 'miter', 'round', or 'bevel' (default: 'round')

+ + + + + + + miterLimit + + + + + +number + + + + + + + + + <optional>
+ + + + + + + + + + +

limit at which 'miter' joins are forced to 'bevel' (default: 1.414)

@@ -6540,12 +6801,136 @@
Parameters:
- coordinates + coordinates + + + + + +Array.<number> + + + + + + + + + + + + + + + + + +

The array of coordinate [[x,y], [m,n]]

+ + + + + + + options + + + + + +Object + + + + + + + + + <optional>
+ + + + + + + + + + +

The options

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + +
NameTypeAttributesDescription
color + + +string +| + +Array.<number> + + + + + + <optional>
+ + + + + +

HexColor, PercentColor or DecimalColor

stroke +string +| + Array.<number> @@ -6555,6 +6940,8 @@
Parameters:
+ <optional>
+ @@ -6564,20 +6951,23 @@
Parameters:
-

The array of coordinate [[x,y], [m,n]]

HexColor, PercentColor or DecimalColor

optionsfill -Object +string +| + +Array.<number> @@ -6597,44 +6987,53 @@
Parameters:
-

The options

-
Properties
+

HexColor, PercentColor or DecimalColor

lineWidth + +number - - - - - - - + + - - - + + - - + + + + + + + + - - + + - + + - + + - + + - + + - + + - + + - + + @@ -8043,6 +8436,8 @@
Properties
+ + @@ -8081,6 +8476,10 @@
Properties
+ + @@ -8114,6 +8513,12 @@
Properties
+ + @@ -8147,8 +8552,14 @@
Properties
+ + - + @@ -8180,8 +8591,14 @@
Properties
+ + - + @@ -8213,6 +8630,12 @@
Properties
+ + @@ -8246,8 +8669,14 @@
Properties
+ + - + @@ -8279,6 +8708,12 @@
Properties
+ + @@ -8315,6 +8750,10 @@
Properties
+ + @@ -8351,6 +8790,10 @@
Properties
+ + @@ -8387,6 +8830,10 @@
Properties
+ + @@ -8420,6 +8867,10 @@
Properties
+ + + @@ -8474,6 +8927,12 @@
Properties
+ + @@ -8507,6 +8966,10 @@
Properties
+ + @@ -8540,6 +9003,12 @@
Properties
+ + @@ -8576,6 +9045,12 @@
Properties
+ + @@ -8609,12 +9084,57 @@
Properties
+ + + + + + + + + + + + + + + + + + + + + + @@ -8642,8 +9162,15 @@
Properties
+ + - + @@ -8675,6 +9202,10 @@
Properties
+ + + @@ -8729,6 +9262,12 @@
Properties
+ + @@ -8765,6 +9304,10 @@
Properties
+ + @@ -8798,6 +9341,12 @@
Properties
+ + @@ -8834,6 +9383,10 @@
Properties
+ + @@ -8867,6 +9420,12 @@
Properties
+ + @@ -9062,7 +9621,7 @@
Parameters:

- Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
diff --git a/docs/Recipe.js.html b/docs/Recipe.js.html index dcb496d..dd52fa6 100644 --- a/docs/Recipe.js.html +++ b/docs/Recipe.js.html @@ -37,277 +37,277 @@

Recipe.js

-
const hummus = require('hummus');
-const path = require('path');
-const fs = require('fs');
-const streams = require('memory-streams');
-/**
- * @name Recipe
- * @desc Create a pdfDoc
- * @namespace
- * @constructor
- * @param {string} src - The file path or Buffer of the src file.
- * @param {string} output - The path of the output file.
- * @param {Object} [options] - The options for pdfDoc
- * @param {number} [options.version] - The pdf version
- * @param {string} [options.author] - The author
- * @param {string} [options.title] - The title
- * @param {string} [options.subject] - The subject
- * @param {string} [options.colorspace] - The default colorspace: rgb, cmyk, gray
- * @param {string[]} [options.keywords] - The array of keywords
- * @param {string} [options.password] - permission password
- * @param {string} [options.userPassword] - this 'view' password also enables encryption
- * @param {string} [options.ownerPassword] - this allows owner to 'edit' file
- * @param {string} [options.userProtectionFlag] - encryption security level (see permissions)
- * @param {string|string[]} [options.fontSrcPath] - directory location(s) of additional fonts
- */
-class Recipe {
-    constructor(src, output, options = {}) {
-        this.src = src;
-        // detect the src is Buffer or not
-        this.isBufferSrc = this.src instanceof Buffer;
-        this.isNewPDF = (!this.isBufferSrc && src.toLowerCase() === 'new');
-        this.encryptOptions = this._getEncryptOptions(options, this.isNewPDF);
-        this.options = Object.assign({}, options, this.encryptOptions);
-
-        if (this.isBufferSrc) {
-            this.outStream = new streams.WritableStream();
-            this.output = output;
-        } else {
-            this.output = output || src;
-            if (this.src) {
-                this.filename = path.basename(this.src);
-            }
-        }
-        this.hummus = hummus;
-        this.logFile = 'hummus-error.log';
-
-        this.textMarkupAnnotations = [
-            'Highlight', 'Underline', 'StrikeOut', 'Squiggly'
-        ];
-
-        this.annotationsToWrite = [];
-        this.annotations = [];
-        this.vectorsToWrite = [];
-
-        this.xObjects = [];
-
-        this.needToEncrypt = false;
-
-        this.needToInsertPages = false;
-
-        this._setParameters(options);
-        this._loadFonts(path.join(__dirname, '../fonts'));
-        if (options.fontSrcPath) {
-            this._loadFonts(options.fontSrcPath);
-        }
-        this._createWriter();
-    }
-
-    _createWriter() {
-        if (this.isNewPDF) {
-            this.writer = hummus.createWriter(this.output,
-                Object.assign( {}, this.encryptOptions, {
-                    version: this._getVersion(this.options.version)
-                })
-            );
-        } else {
-            this.read();
-            try {
-                if (this.isBufferSrc) {
-                    this.writer = hummus.createWriterToModify(
-                        new hummus.PDFRStreamForBuffer(this.src),
-                        new hummus.PDFStreamForResponse(this.outStream),
-                        Object.assign( {}, this.encryptOptions, {
-                            log: this.logFile
-                        })
-                    );
-                } else {
-                    this.writer = hummus.createWriterToModify(this.src,
-                        Object.assign( {}, this.encryptOptions, {
-                            modifiedFilePath: this.output,
-                            log: this.logFile
-                        })
-                    );
-                }
-            } catch (err) {
-                throw new Error(err);
-            }
-        }
-
-        this.info(this.options);
-    }
-
-    _getVersion(version) {
-        const supportedVersions = [
-            1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7
-        ];
-        if (!supportedVersions.includes(version)) {
-            version = 1.7;
-        }
-        version = hummus[`ePDFVersion${version * 10}`];
-
-        return version;
-    }
-
-    get position() {
-        const {
-            ox,
-            oy
-        } = this._reverseCoordinate(this._position.x, this._position.y);
-        return {
-            x: ox,
-            y: oy
-        };
-    }
-
-    read(inSrc) {
-        const isForExternal = (inSrc) ? true : false;
-        try {
-            let src = (isForExternal) ? inSrc : this.src;
-            if (this.isBufferSrc) {
-                src = new hummus.PDFRStreamForBuffer(this.src);
-            }
-            const pdfReader = hummus.createReader(src, this.encryptOptions);
-            const pages = pdfReader.getPagesCount();
-            if (pages == 0) {
-                // broken or modify password protected
-                throw 'HummusJS: Unable to read/edit PDF file (pages=0)';
-            }
-            const metadata = {
-                pages
-            };
-            for (var i = 0; i < pages; i++) {
-                const info = pdfReader.parsePage(i);
-                const dimensions = info.getMediaBox();
-                const rotate = info.getRotate();
-
-                let layout,
-                    width,
-                    height,
-                    pageSize;
-                let side1 = Math.abs(dimensions[2] - dimensions[0]);
-                let side2 = Math.abs(dimensions[3] - dimensions[1]);
-                if (side1 > side2 && rotate % 180 === 0) {
-                    layout = 'landscape';
-                } else
-                if (side1 < side2 && rotate % 180 !== 0) {
-                    layout = 'landscape';
-                } else {
-                    layout = 'portrait';
-                }
-
-                if (layout === 'landscape') {
-                    width = (side1 > side2) ? side1 : side2;
-                    height = (side1 > side2) ? side2 : side1;
-                } else {
-                    width = (side1 > side2) ? side2 : side1;
-                    height = (side1 > side2) ? side1 : side2;
-                }
-
-                pageSize = [width, height].sort((a, b) => {
-                    return (a > b) ? 1 : -1;
-                });
-
-                const page = {
-                    pageNumber: i + 1,
-                    mediaBox: dimensions,
-                    layout,
-                    rotate,
-                    width,
-                    height,
-                    size: pageSize,
-                    // usually 0
-                    offsetX: dimensions[0],
-                    offsetY: dimensions[1]
-                };
-                metadata[page.pageNumber] = page;
-            }
-            if (!isForExternal) {
-                this.pdfReader = pdfReader;
-                this.metadata = metadata;
-            }
-            return metadata;
-        } catch (err) {
-            throw new Error(err);
-        }
-    }
-
-    /**
-     * End the pdfDoc
-     * @function
-     * @memberof Recipe
-     * @param {function} callback - The callback function.
-     */
-    endPDF(callback) {
-        this._writeInfo();
-        this.writer.end();
-        // This is a temporary work around for copying context will overwrite the current one
-        // write annotations at the end.
-        if (
-            (this.annotations && this.annotations.length > 0) ||
-            (this.annotationsToWrite && this.annotationsToWrite.length > 0)
-        ) {
-            if (this.isBufferSrc) {
-                const oldStream = this.outStream;
-                this.outStream = new streams.WritableStream();
-
-                this.writer = hummus.createWriterToModify(
-                    new hummus.PDFRStreamForBuffer(oldStream.toBuffer()),
-                    new hummus.PDFStreamForResponse(this.outStream),
-                    Object.assign( {}, this.encryptOptions, {
-                        log: this.logFile
-                    })
-                );
-            } else {
-                this.writer = hummus.createWriterToModify(this.output,
-                    Object.assign( {}, this.encryptOptions, {
-                        modifiedFilePath: this.output,
-                        log: this.logFile
-                    })
-                );
-            }
-
-            this._writeAnnotations();
-            this._writeInfo();
-            this.writer.end();
-        }
-        if (this.needToInsertPages) {
-            if (this.isBufferSrc) {
-                // eslint-disable-next-line no-console
-                console.log('Feature: Inserting Pages is not supported in Buffer Mode yet.');
-            } else {
-                this._insertPages();
-            }
-        }
-        if (this.needToEncrypt) {
-            if (this.isBufferSrc) {
-                // eslint-disable-next-line no-console
-                console.log('Feature: Encryption is not supported in Buffer Mode yet.');
-            } else {
-                this._encrypt();
-            }
-        }
-
-        if (this.isBufferSrc && this.output) {
-            fs.writeFileSync(this.output, this.outStream.toBuffer());
-        }
-
-        if (callback) {
-            if (this.isBufferSrc) {
-                if (this.output) {
-                    return callback(this.output);
-                } else {
-                    return callback(this.outStream.toBuffer());
-                }
-            } else {
-                return callback();
-            }
-        }
-    }
-}
-
-module.exports = Recipe;
+            
const hummus = require('hummus');
+const path = require('path');
+const fs = require('fs');
+const streams = require('memory-streams');
+/**
+ * @name Recipe
+ * @desc Create a pdfDoc
+ * @namespace
+ * @constructor
+ * @param {string} src - The file path or Buffer of the src file.
+ * @param {string} output - The path of the output file.
+ * @param {Object} [options] - The options for pdfDoc
+ * @param {number} [options.version] - The pdf version
+ * @param {string} [options.author] - The author
+ * @param {string} [options.title] - The title
+ * @param {string} [options.subject] - The subject
+ * @param {string} [options.colorspace] - The default colorspace: rgb, cmyk, gray
+ * @param {string[]} [options.keywords] - The array of keywords
+ * @param {string} [options.password] - permission password
+ * @param {string} [options.userPassword] - this 'view' password also enables encryption
+ * @param {string} [options.ownerPassword] - this allows owner to 'edit' file
+ * @param {string} [options.userProtectionFlag] - encryption security level (see permissions)
+ * @param {string|string[]} [options.fontSrcPath] - directory location(s) of additional fonts
+ */
+class Recipe {
+    constructor(src, output, options = {}) {
+        this.src = src;
+        // detect the src is Buffer or not
+        this.isBufferSrc = this.src instanceof Buffer;
+        this.isNewPDF = (!this.isBufferSrc && src.toLowerCase() === 'new');
+        this.encryptOptions = this._getEncryptOptions(options, this.isNewPDF);
+        this.options = Object.assign({}, options, this.encryptOptions);
+
+        if (this.isBufferSrc) {
+            this.outStream = new streams.WritableStream();
+            this.output = output;
+        } else {
+            this.output = output || src;
+            if (this.src) {
+                this.filename = path.basename(this.src);
+            }
+        }
+        this.hummus = hummus;
+        this.logFile = 'hummus-error.log';
+
+        this.textMarkupAnnotations = [
+            'Highlight', 'Underline', 'StrikeOut', 'Squiggly'
+        ];
+
+        this.annotationsToWrite = [];
+        this.annotations = [];
+        this.vectorsToWrite = [];
+
+        this.xObjects = [];
+
+        this.needToEncrypt = false;
+
+        this.needToInsertPages = false;
+
+        this._setParameters(options);
+        this._loadFonts(path.join(__dirname, '../fonts'));
+        if (options.fontSrcPath) {
+            this._loadFonts(options.fontSrcPath);
+        }
+        this._createWriter();
+    }
+
+    _createWriter() {
+        if (this.isNewPDF) {
+            this.writer = hummus.createWriter(this.output,
+                Object.assign( {}, this.encryptOptions, {
+                    version: this._getVersion(this.options.version)
+                })
+            );
+        } else {
+            this.read();
+            try {
+                if (this.isBufferSrc) {
+                    this.writer = hummus.createWriterToModify(
+                        new hummus.PDFRStreamForBuffer(this.src),
+                        new hummus.PDFStreamForResponse(this.outStream),
+                        Object.assign( {}, this.encryptOptions, {
+                            log: this.logFile
+                        })
+                    );
+                } else {
+                    this.writer = hummus.createWriterToModify(this.src,
+                        Object.assign( {}, this.encryptOptions, {
+                            modifiedFilePath: this.output,
+                            log: this.logFile
+                        })
+                    );
+                }
+            } catch (err) {
+                throw new Error(err);
+            }
+        }
+
+        this.info(this.options);
+    }
+
+    _getVersion(version) {
+        const supportedVersions = [
+            1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7
+        ];
+        if (!supportedVersions.includes(version)) {
+            version = 1.7;
+        }
+        version = hummus[`ePDFVersion${version * 10}`];
+
+        return version;
+    }
+
+    get position() {
+        const {
+            ox,
+            oy
+        } = this._reverseCoordinate(this._position.x, this._position.y);
+        return {
+            x: ox,
+            y: oy
+        };
+    }
+
+    read(inSrc) {
+        const isForExternal = (inSrc) ? true : false;
+        try {
+            let src = (isForExternal) ? inSrc : this.src;
+            if (this.isBufferSrc) {
+                src = new hummus.PDFRStreamForBuffer(this.src);
+            }
+            const pdfReader = hummus.createReader(src, this.encryptOptions);
+            const pages = pdfReader.getPagesCount();
+            if (pages == 0) {
+                // broken or modify password protected
+                throw 'HummusJS: Unable to read/edit PDF file (pages=0)';
+            }
+            const metadata = {
+                pages
+            };
+            for (var i = 0; i < pages; i++) {
+                const info = pdfReader.parsePage(i);
+                const dimensions = info.getMediaBox();
+                const rotate = info.getRotate();
+
+                let layout,
+                    width,
+                    height,
+                    pageSize;
+                let side1 = Math.abs(dimensions[2] - dimensions[0]);
+                let side2 = Math.abs(dimensions[3] - dimensions[1]);
+                if (side1 > side2 && rotate % 180 === 0) {
+                    layout = 'landscape';
+                } else
+                if (side1 < side2 && rotate % 180 !== 0) {
+                    layout = 'landscape';
+                } else {
+                    layout = 'portrait';
+                }
+
+                if (layout === 'landscape') {
+                    width = (side1 > side2) ? side1 : side2;
+                    height = (side1 > side2) ? side2 : side1;
+                } else {
+                    width = (side1 > side2) ? side2 : side1;
+                    height = (side1 > side2) ? side1 : side2;
+                }
+
+                pageSize = [width, height].sort((a, b) => {
+                    return (a > b) ? 1 : -1;
+                });
+
+                const page = {
+                    pageNumber: i + 1,
+                    mediaBox: dimensions,
+                    layout,
+                    rotate,
+                    width,
+                    height,
+                    size: pageSize,
+                    // usually 0
+                    offsetX: dimensions[0],
+                    offsetY: dimensions[1]
+                };
+                metadata[page.pageNumber] = page;
+            }
+            if (!isForExternal) {
+                this.pdfReader = pdfReader;
+                this.metadata = metadata;
+            }
+            return metadata;
+        } catch (err) {
+            throw new Error(err);
+        }
+    }
+
+    /**
+     * End the pdfDoc
+     * @function
+     * @memberof Recipe
+     * @param {function} callback - The callback function.
+     */
+    endPDF(callback) {
+        this._writeInfo();
+        this.writer.end();
+        // This is a temporary work around for copying context will overwrite the current one
+        // write annotations at the end.
+        if (
+            (this.annotations && this.annotations.length > 0) ||
+            (this.annotationsToWrite && this.annotationsToWrite.length > 0)
+        ) {
+            if (this.isBufferSrc) {
+                const oldStream = this.outStream;
+                this.outStream = new streams.WritableStream();
+
+                this.writer = hummus.createWriterToModify(
+                    new hummus.PDFRStreamForBuffer(oldStream.toBuffer()),
+                    new hummus.PDFStreamForResponse(this.outStream),
+                    Object.assign( {}, this.encryptOptions, {
+                        log: this.logFile
+                    })
+                );
+            } else {
+                this.writer = hummus.createWriterToModify(this.output,
+                    Object.assign( {}, this.encryptOptions, {
+                        modifiedFilePath: this.output,
+                        log: this.logFile
+                    })
+                );
+            }
+
+            this._writeAnnotations();
+            this._writeInfo();
+            this.writer.end();
+        }
+        if (this.needToInsertPages) {
+            if (this.isBufferSrc) {
+                // eslint-disable-next-line no-console
+                console.log('Feature: Inserting Pages is not supported in Buffer Mode yet.');
+            } else {
+                this._insertPages();
+            }
+        }
+        if (this.needToEncrypt) {
+            if (this.isBufferSrc) {
+                // eslint-disable-next-line no-console
+                console.log('Feature: Encryption is not supported in Buffer Mode yet.');
+            } else {
+                this._encrypt();
+            }
+        }
+
+        if (this.isBufferSrc && this.output) {
+            fs.writeFileSync(this.output, this.outStream.toBuffer());
+        }
+
+        if (callback) {
+            if (this.isBufferSrc) {
+                if (this.output) {
+                    return callback(this.output);
+                } else {
+                    return callback(this.outStream.toBuffer());
+                }
+            } else {
+                return callback();
+            }
+        }
+    }
+}
+
+module.exports = Recipe;
 
@@ -320,7 +320,7 @@

Recipe.js


- Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
diff --git a/docs/annotation.js.html b/docs/annotation.js.html index 624c43d..34d8348 100644 --- a/docs/annotation.js.html +++ b/docs/annotation.js.html @@ -37,347 +37,347 @@

annotation.js

-
/**
- * Create a comment annotation
- * @name comment
- * @function
- * @memberof Recipe
- * @param {string} text - The text content
- * @param {number} x - The coordinate x
- * @param {number} y - The coordinate y
- * @param {Object} [options] - The options
- * @param {string} [options.title] - The title.
- * @param {string} [options.date] - The date.
- * @param {boolean} [options.open=false] - Open the annotation by default?
- * @param {boolean} [options.richText] - Display with rich text format, text will be transformed automatically, or you may pass in your own rich text starts with "<?xml..."
- * @param {'invisible'|'hidden'|'print'|'nozoom'|'norotate'|'noview'|'readonly'|'locked'|'togglenoview'} [options.flag] - The flag property
- */
-exports.comment = function comment(text = '', x, y, options = {}) {
-    this.annotationsToWrite.push({
-        subtype: 'Text',
-        pageNumber: this.pageNumber,
-        args: { text, x, y, options: Object.assign({ icon: 'Comment' }, options) }
-    });
-    return this;
-};
-
-/**
- * Create an annotation
- * @name annot
- * @function
- * @memberof Recipe
- * @todo support for rich texst RC
- * @todo support for opacity CA
- * @param {number} x - The coordinate x
- * @param {number} y - The coordinate y
- * @param {string} subtype - The markup annotation type 'Text'|'FreeText'|'Line'|'Square'|'Circle'|'Polygon'|'PolyLine'|'Highlight'|'Underline'|'Squiggly'|'StrikeOut'|'Stamp'|'Caret'|'Ink'|'FileAttachment'|'Sound'
- * @param {Object} [options] - The options
- * @param {string} [options.title] - The title.
- * @param {boolean} [options.open=false] - Open the annotation by default?
- * @param {'invisible'|'hidden'|'print'|'nozoom'|'norotate'|'noview'|'readonly'|'locked'|'togglenoview'} [options.flag] - The flag property
- * @param {'Comment'|'Key'|'Note'|'Help'|'NewParagraph'|'Paragraph'|'Insert'} [options.icon] - The icon of annotation.
- * @param {number} [options.width] - Width
- * @param {number} [options.height] - Height
- */
-exports.annot = function annot(x, y, subtype, options = { text: '', width: 0, height: 0 }) {
-    const { text, width, height } = options;
-    this.annotationsToWrite.push({
-        subtype,
-        args: { text, x, y, width, height, options },
-        pageNumber: this.pageNumber
-    });
-    return this;
-};
-
-// TODO: allow non-markup annots to be associated with markup annotations
-// Link, Popup, Movie, Widget, Screen, PrinterMark, TrapNet, Watermark, 3D
-exports._attachNonMarkupAnnot = function _attachNonMarkupAnnot() {
-
-};
-
-exports._annot = function _annot(subtype, args = {}, pageNumber) {
-    const { x, y, width, height, text, options } = args;
-    this._startDictionary(pageNumber);
-    const { rotate } = this.metadata[pageNumber];
-    let { nx, ny } = this._calibrateCoordinateForAnnots(x, y, 0, 0, pageNumber);
-
-    let nWidth = width;
-    let nHeight = height;
-
-    if (!options.followOriginalPageRotation) {
-        switch (rotate) {
-            case 90:
-                nWidth = height;
-                nHeight = width;
-                nx = nx - nWidth;
-                break;
-            case 180:
-                nx = nx - nWidth;
-                ny = ny - nHeight;
-                break;
-            case 270:
-                nWidth = height;
-                nHeight = width;
-                ny = ny - nHeight;
-                break;
-            default:
-        }
-    }
-
-    const params = Object.assign({
-        title: '',
-        subject: '',
-        date: new Date(),
-        open: false,
-        flag: '' // 'readonly'
-    }, options);
-
-    const ex = (nWidth) ? nWidth : 0;
-    const ey = (nHeight) ? nHeight : 0;
-    const position = [nx, ny, nx + ex, ny + ey];
-
-    this.dictionaryContext
-        .writeKey('Type')
-        .writeNameValue('Annot')
-        .writeKey('Subtype')
-        .writeNameValue(subtype)
-        .writeKey('L')
-        .writeBooleanValue(true)
-        .writeKey('Rect')
-        .writeRectangleValue(position)
-        .writeKey('Subj')
-        .writeLiteralStringValue(this.writer.createPDFTextString(params.subject).toBytesArray())
-        .writeKey('T')
-        .writeLiteralStringValue(this.writer.createPDFTextString(params.title).toBytesArray())
-        .writeKey('M')
-        .writeLiteralStringValue(this.writer.createPDFDate(params.date).toString())
-        .writeKey('Open')
-        .writeBooleanValue(params.open)
-        .writeKey('F')
-        .writeNumberValue(getFlagBitNumberByName(params.flag));
-
-    /**
-     * Rich Text Strings
-     * 12.7.3.4
-     */
-    if (text && options.richText) {
-        const richText = (text.substring(0, 4) !== '<?xml') ? contentToRC(text) : text;
-        const richTextContent = this.writer.createPDFTextString(richText).toBytesArray();
-        this.dictionaryContext
-            .writeKey('RC')
-            .writeLiteralStringValue(richTextContent);
-    } else
-    if (text) {
-        const textContent = this.writer.createPDFTextString(text).toBytesArray();
-        this.dictionaryContext
-            .writeKey('Contents')
-            .writeLiteralStringValue(textContent);
-    }
-
-    let { border, color } = options;
-
-    if (this._getTextMarkupAnnotationSubtype(subtype)) {
-        this.dictionaryContext.writeKey('QuadPoints');
-        const { _textHeight } = options;
-        const annotHeight = height;
-        const bx = nx;
-        const by = ny + ((_textHeight) ? 0 : -annotHeight);
-        const coordinates = [
-            [bx, by + annotHeight],
-            [bx + nWidth, by + annotHeight],
-            [bx, by],
-            [bx + nWidth, by],
-        ];
-        this.objectsContext.startArray();
-        coordinates.forEach(coord => {
-            coord.forEach(point => {
-                this.objectsContext.writeNumber(Math.round(point));
-            });
-        });
-        this.objectsContext
-            .endArray()
-            .endLine();
-
-        border = border || 0;
-        if (!color) {
-            switch (subtype) {
-                case 'Highlight':
-                    color = [255, 255, 0];
-                    break;
-                case 'StrikeOut':
-                    color = [255, 0, 0];
-                    break;
-                case 'Underline':
-                    color = [0, 255, 0];
-                    break;
-                case 'Squiggly':
-                    color = [0, 255, 0];
-                    break;
-                default:
-                    color = [0, 0, 0];
-                    break;
-            }
-        }
-    }
-
-    if (border != void(0)) {
-        this.dictionaryContext.writeKey('Border');
-        this.objectsContext
-            .startArray()
-            .writeNumber(0)
-            .writeNumber(0)
-            .writeNumber(border)
-            .endArray()
-            .endLine();
-    }
-
-    if (color) {
-        const rgb = this._colorNumberToRGB(this._transformColor(color));
-        this.dictionaryContext.writeKey('C');
-        this.objectsContext
-            .startArray()
-            .writeNumber(rgb.r / 255)
-            .writeNumber(rgb.g / 255)
-            .writeNumber(rgb.b / 255)
-            .endArray()
-            .endLine();
-    }
-
-    /* Display Icon */
-    if (params.icon) {
-        this.dictionaryContext
-            .writeKey('Name')
-            .writeNameValue(params.icon);
-    }
-    this._endDictionary(pageNumber);
-};
-
-exports._writeAnnotations = function _writeAnnotations() {
-    this.annotationsToWrite.forEach((annot) => {
-        this._annot(annot.subtype, annot.args, annot.pageNumber);
-    });
-    this.annotations.forEach((pageAnnots, index) => {
-        this._writeAnnotation(index);
-    });
-};
-
-exports._writeAnnotation = function _writeAnnotation(pageIndex) {
-    const pdfWriter = this.writer;
-    const copyingContext = pdfWriter.createPDFCopyingContextForModifiedFile();
-    const pageID = copyingContext.getSourceDocumentParser().getPageObjectID(pageIndex);
-    const pageObject = copyingContext.getSourceDocumentParser().parsePage(pageIndex).getDictionary().toJSObject();
-    const objectsContext = pdfWriter.getObjectsContext();
-
-    objectsContext.startModifiedIndirectObject(pageID);
-    const modifiedPageObject = pdfWriter.getObjectsContext().startDictionary();
-    Object.getOwnPropertyNames(pageObject).forEach((element) => {
-        const ignore = ['Annots'];
-        if (!ignore.includes(element)) {
-            modifiedPageObject.writeKey(element);
-            copyingContext.copyDirectObjectAsIs(pageObject[element]);
-        }
-    });
-
-    modifiedPageObject.writeKey('Annots');
-    objectsContext.startArray();
-    if (pageObject['Annots'] && pageObject['Annots'].toJSArray) {
-        pageObject['Annots'].toJSArray().forEach((annot) => {
-            objectsContext.writeIndirectObjectReference(annot.getObjectID());
-        });
-    }
-    this.annotations[pageIndex].forEach((item) => {
-        objectsContext.writeIndirectObjectReference(item);
-    });
-
-    objectsContext
-        .endArray()
-        .endLine()
-        .endDictionary(modifiedPageObject)
-        .endIndirectObject();
-};
-
-exports._startDictionary = function _startDictionary() {
-    this.objectsContext = this.writer.getObjectsContext();
-    this.dictionaryObject = this.objectsContext.startNewIndirectObject();
-    this.dictionaryContext = this.objectsContext.startDictionary();
-};
-
-exports._endDictionary = function _endDictionary(pageNumber) {
-    this.objectsContext
-        .endDictionary(this.dictionaryContext)
-        .endIndirectObject();
-    const pageIndex = pageNumber - 1;
-    this.annotations[pageIndex] = this.annotations[pageIndex] || [];
-    this.annotations[pageIndex].push(this.dictionaryObject);
-};
-
-exports._getTextMarkupAnnotationSubtype = function _getTextMarkupAnnotationSubtype(subtype = '') {
-    const matchedSubtype = this.textMarkupAnnotations.find(item => {
-        return item.toLowerCase() == subtype.toLowerCase();
-    });
-    return matchedSubtype;
-};
-
-/**
- * Get Flag Bit by Name
- * @description 12.5.3 Annotation Flags
- * @param {string} name
- */
-function getFlagBitNumberByName(name) {
-    switch (name.toLowerCase()) {
-        case 'invisible':
-            return 1;
-        case 'hidden':
-            return 2;
-        case 'print':
-            return 4;
-        case 'nozoom':
-            return 8;
-        case 'norotate':
-            return 16;
-        case 'noview':
-            return 32;
-        case 'readonly':
-            return 64;
-        case 'locked':
-            return 128;
-        case 'togglenoview':
-            return 256;
-            // 1.7+
-            // case 'lockedcontents':
-            //     return 512;
-        default:
-            return 0;
-    }
-}
-
-/**
- * Text Strings to Rich Text Strings
- * @todo Fix display issue for ol/ul in richText
- * @param {string} content
- * @description Support XHTML Elements:  '<p>' | '<span>' | '<b>' | '<i>'
- * @description Support CSS2 Style: 'text-align' | 'vertical-align' | 'font-size' | 'font-style' | 'font-weight' | 'font-family' | 'font' | 'color' | 'text-decoration' | 'font-stretch'
- */
-function contentToRC(content) {
-    content = content.replace('&nbsp;', ' ');
-    content = content.replace(/\r?\n|\r|\t/g, '');
-    let richText =
-        '<?xml version="1.0"?>' +
-        '<body ' +
-        'xmlns="http://www.w3.org/1999/xhtml"' +
-        // 'xmlns:xga=\"http://www.xfa.org/schema/xfa-data/1.0/\" ' +
-        // 'xfa:contentType=\"text/html\" ' +
-        // 'xfa:APIVersion=\"Acrobat:8.0.0\" ' +
-        // 'xfa:spec=\"2.4\" ' +
-        '>' +
-        content +
-        '</body>';
-    richText = richText
-        .replace(/<li>/g, '<p> • ')
-        .replace(/<(\/)li>/g, '</p>')
-        .replace(/<(\/)p>/g, '</p><br/>');
-    return richText;
-}
+            
/**
+ * Create a comment annotation
+ * @name comment
+ * @function
+ * @memberof Recipe
+ * @param {string} text - The text content
+ * @param {number} x - The coordinate x
+ * @param {number} y - The coordinate y
+ * @param {Object} [options] - The options
+ * @param {string} [options.title] - The title.
+ * @param {string} [options.date] - The date.
+ * @param {boolean} [options.open=false] - Open the annotation by default?
+ * @param {boolean} [options.richText] - Display with rich text format, text will be transformed automatically, or you may pass in your own rich text starts with "<?xml..."
+ * @param {'invisible'|'hidden'|'print'|'nozoom'|'norotate'|'noview'|'readonly'|'locked'|'togglenoview'} [options.flag] - The flag property
+ */
+exports.comment = function comment(text = '', x, y, options = {}) {
+    this.annotationsToWrite.push({
+        subtype: 'Text',
+        pageNumber: this.pageNumber,
+        args: { text, x, y, options: Object.assign({ icon: 'Comment' }, options) }
+    });
+    return this;
+};
+
+/**
+ * Create an annotation
+ * @name annot
+ * @function
+ * @memberof Recipe
+ * @todo support for rich texst RC
+ * @todo support for opacity CA
+ * @param {number} x - The coordinate x
+ * @param {number} y - The coordinate y
+ * @param {string} subtype - The markup annotation type 'Text'|'FreeText'|'Line'|'Square'|'Circle'|'Polygon'|'PolyLine'|'Highlight'|'Underline'|'Squiggly'|'StrikeOut'|'Stamp'|'Caret'|'Ink'|'FileAttachment'|'Sound'
+ * @param {Object} [options] - The options
+ * @param {string} [options.title] - The title.
+ * @param {boolean} [options.open=false] - Open the annotation by default?
+ * @param {'invisible'|'hidden'|'print'|'nozoom'|'norotate'|'noview'|'readonly'|'locked'|'togglenoview'} [options.flag] - The flag property
+ * @param {'Comment'|'Key'|'Note'|'Help'|'NewParagraph'|'Paragraph'|'Insert'} [options.icon] - The icon of annotation.
+ * @param {number} [options.width] - Width
+ * @param {number} [options.height] - Height
+ */
+exports.annot = function annot(x, y, subtype, options = { text: '', width: 0, height: 0 }) {
+    const { text, width, height } = options;
+    this.annotationsToWrite.push({
+        subtype,
+        args: { text, x, y, width, height, options },
+        pageNumber: this.pageNumber
+    });
+    return this;
+};
+
+// TODO: allow non-markup annots to be associated with markup annotations
+// Link, Popup, Movie, Widget, Screen, PrinterMark, TrapNet, Watermark, 3D
+exports._attachNonMarkupAnnot = function _attachNonMarkupAnnot() {
+
+};
+
+exports._annot = function _annot(subtype, args = {}, pageNumber) {
+    const { x, y, width, height, text, options } = args;
+    this._startDictionary(pageNumber);
+    const { rotate } = this.metadata[pageNumber];
+    let { nx, ny } = this._calibrateCoordinateForAnnots(x, y, 0, 0, pageNumber);
+
+    let nWidth = width;
+    let nHeight = height;
+
+    if (!options.followOriginalPageRotation) {
+        switch (rotate) {
+            case 90:
+                nWidth = height;
+                nHeight = width;
+                nx = nx - nWidth;
+                break;
+            case 180:
+                nx = nx - nWidth;
+                ny = ny - nHeight;
+                break;
+            case 270:
+                nWidth = height;
+                nHeight = width;
+                ny = ny - nHeight;
+                break;
+            default:
+        }
+    }
+
+    const params = Object.assign({
+        title: '',
+        subject: '',
+        date: new Date(),
+        open: false,
+        flag: '' // 'readonly'
+    }, options);
+
+    const ex = (nWidth) ? nWidth : 0;
+    const ey = (nHeight) ? nHeight : 0;
+    const position = [nx, ny, nx + ex, ny + ey];
+
+    this.dictionaryContext
+        .writeKey('Type')
+        .writeNameValue('Annot')
+        .writeKey('Subtype')
+        .writeNameValue(subtype)
+        .writeKey('L')
+        .writeBooleanValue(true)
+        .writeKey('Rect')
+        .writeRectangleValue(position)
+        .writeKey('Subj')
+        .writeLiteralStringValue(this.writer.createPDFTextString(params.subject).toBytesArray())
+        .writeKey('T')
+        .writeLiteralStringValue(this.writer.createPDFTextString(params.title).toBytesArray())
+        .writeKey('M')
+        .writeLiteralStringValue(this.writer.createPDFDate(params.date).toString())
+        .writeKey('Open')
+        .writeBooleanValue(params.open)
+        .writeKey('F')
+        .writeNumberValue(getFlagBitNumberByName(params.flag));
+
+    /**
+     * Rich Text Strings
+     * 12.7.3.4
+     */
+    if (text && options.richText) {
+        const richText = (text.substring(0, 4) !== '<?xml') ? contentToRC(text) : text;
+        const richTextContent = this.writer.createPDFTextString(richText).toBytesArray();
+        this.dictionaryContext
+            .writeKey('RC')
+            .writeLiteralStringValue(richTextContent);
+    } else
+    if (text) {
+        const textContent = this.writer.createPDFTextString(text).toBytesArray();
+        this.dictionaryContext
+            .writeKey('Contents')
+            .writeLiteralStringValue(textContent);
+    }
+
+    let { border, color } = options;
+
+    if (this._getTextMarkupAnnotationSubtype(subtype)) {
+        this.dictionaryContext.writeKey('QuadPoints');
+        const { _textHeight } = options;
+        const annotHeight = height;
+        const bx = nx;
+        const by = ny + ((_textHeight) ? 0 : -annotHeight);
+        const coordinates = [
+            [bx, by + annotHeight],
+            [bx + nWidth, by + annotHeight],
+            [bx, by],
+            [bx + nWidth, by],
+        ];
+        this.objectsContext.startArray();
+        coordinates.forEach(coord => {
+            coord.forEach(point => {
+                this.objectsContext.writeNumber(Math.round(point));
+            });
+        });
+        this.objectsContext
+            .endArray()
+            .endLine();
+
+        border = border || 0;
+        if (!color) {
+            switch (subtype) {
+                case 'Highlight':
+                    color = [255, 255, 0];
+                    break;
+                case 'StrikeOut':
+                    color = [255, 0, 0];
+                    break;
+                case 'Underline':
+                    color = [0, 255, 0];
+                    break;
+                case 'Squiggly':
+                    color = [0, 255, 0];
+                    break;
+                default:
+                    color = [0, 0, 0];
+                    break;
+            }
+        }
+    }
+
+    if (border != void(0)) {
+        this.dictionaryContext.writeKey('Border');
+        this.objectsContext
+            .startArray()
+            .writeNumber(0)
+            .writeNumber(0)
+            .writeNumber(border)
+            .endArray()
+            .endLine();
+    }
+
+    if (color) {
+        const rgb = this._colorNumberToRGB(this._transformColor(color));
+        this.dictionaryContext.writeKey('C');
+        this.objectsContext
+            .startArray()
+            .writeNumber(rgb.r / 255)
+            .writeNumber(rgb.g / 255)
+            .writeNumber(rgb.b / 255)
+            .endArray()
+            .endLine();
+    }
+
+    /* Display Icon */
+    if (params.icon) {
+        this.dictionaryContext
+            .writeKey('Name')
+            .writeNameValue(params.icon);
+    }
+    this._endDictionary(pageNumber);
+};
+
+exports._writeAnnotations = function _writeAnnotations() {
+    this.annotationsToWrite.forEach((annot) => {
+        this._annot(annot.subtype, annot.args, annot.pageNumber);
+    });
+    this.annotations.forEach((pageAnnots, index) => {
+        this._writeAnnotation(index);
+    });
+};
+
+exports._writeAnnotation = function _writeAnnotation(pageIndex) {
+    const pdfWriter = this.writer;
+    const copyingContext = pdfWriter.createPDFCopyingContextForModifiedFile();
+    const pageID = copyingContext.getSourceDocumentParser().getPageObjectID(pageIndex);
+    const pageObject = copyingContext.getSourceDocumentParser().parsePage(pageIndex).getDictionary().toJSObject();
+    const objectsContext = pdfWriter.getObjectsContext();
+
+    objectsContext.startModifiedIndirectObject(pageID);
+    const modifiedPageObject = pdfWriter.getObjectsContext().startDictionary();
+    Object.getOwnPropertyNames(pageObject).forEach((element) => {
+        const ignore = ['Annots'];
+        if (!ignore.includes(element)) {
+            modifiedPageObject.writeKey(element);
+            copyingContext.copyDirectObjectAsIs(pageObject[element]);
+        }
+    });
+
+    modifiedPageObject.writeKey('Annots');
+    objectsContext.startArray();
+    if (pageObject['Annots'] && pageObject['Annots'].toJSArray) {
+        pageObject['Annots'].toJSArray().forEach((annot) => {
+            objectsContext.writeIndirectObjectReference(annot.getObjectID());
+        });
+    }
+    this.annotations[pageIndex].forEach((item) => {
+        objectsContext.writeIndirectObjectReference(item);
+    });
+
+    objectsContext
+        .endArray()
+        .endLine()
+        .endDictionary(modifiedPageObject)
+        .endIndirectObject();
+};
+
+exports._startDictionary = function _startDictionary() {
+    this.objectsContext = this.writer.getObjectsContext();
+    this.dictionaryObject = this.objectsContext.startNewIndirectObject();
+    this.dictionaryContext = this.objectsContext.startDictionary();
+};
+
+exports._endDictionary = function _endDictionary(pageNumber) {
+    this.objectsContext
+        .endDictionary(this.dictionaryContext)
+        .endIndirectObject();
+    const pageIndex = pageNumber - 1;
+    this.annotations[pageIndex] = this.annotations[pageIndex] || [];
+    this.annotations[pageIndex].push(this.dictionaryObject);
+};
+
+exports._getTextMarkupAnnotationSubtype = function _getTextMarkupAnnotationSubtype(subtype = '') {
+    const matchedSubtype = this.textMarkupAnnotations.find(item => {
+        return item.toLowerCase() == subtype.toLowerCase();
+    });
+    return matchedSubtype;
+};
+
+/**
+ * Get Flag Bit by Name
+ * @description 12.5.3 Annotation Flags
+ * @param {string} name
+ */
+function getFlagBitNumberByName(name) {
+    switch (name.toLowerCase()) {
+        case 'invisible':
+            return 1;
+        case 'hidden':
+            return 2;
+        case 'print':
+            return 4;
+        case 'nozoom':
+            return 8;
+        case 'norotate':
+            return 16;
+        case 'noview':
+            return 32;
+        case 'readonly':
+            return 64;
+        case 'locked':
+            return 128;
+        case 'togglenoview':
+            return 256;
+            // 1.7+
+            // case 'lockedcontents':
+            //     return 512;
+        default:
+            return 0;
+    }
+}
+
+/**
+ * Text Strings to Rich Text Strings
+ * @todo Fix display issue for ol/ul in richText
+ * @param {string} content
+ * @description Support XHTML Elements:  '<p>' | '<span>' | '<b>' | '<i>'
+ * @description Support CSS2 Style: 'text-align' | 'vertical-align' | 'font-size' | 'font-style' | 'font-weight' | 'font-family' | 'font' | 'color' | 'text-decoration' | 'font-stretch'
+ */
+function contentToRC(content) {
+    content = content.replace('&nbsp;', ' ');
+    content = content.replace(/\r?\n|\r|\t/g, '');
+    let richText =
+        '<?xml version="1.0"?>' +
+        '<body ' +
+        'xmlns="http://www.w3.org/1999/xhtml"' +
+        // 'xmlns:xga=\"http://www.xfa.org/schema/xfa-data/1.0/\" ' +
+        // 'xfa:contentType=\"text/html\" ' +
+        // 'xfa:APIVersion=\"Acrobat:8.0.0\" ' +
+        // 'xfa:spec=\"2.4\" ' +
+        '>' +
+        content +
+        '</body>';
+    richText = richText
+        .replace(/<li>/g, '<p> • ')
+        .replace(/<(\/)li>/g, '</p>')
+        .replace(/<(\/)p>/g, '</p><br/>');
+    return richText;
+}
 
@@ -390,7 +390,7 @@

annotation.js


- Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
diff --git a/docs/appendPage.js.html b/docs/appendPage.js.html index 4573d22..bf69b32 100644 --- a/docs/appendPage.js.html +++ b/docs/appendPage.js.html @@ -37,51 +37,51 @@

appendPage.js

-
const hummus = require('hummus');
-const hummusUtils = require('./utils');
-
-/**
- * Append pages from the other pdf to the current pdf
- * @name appendPage
- * @function
- * @memberof Recipe
- * @param {string} pdfSrc - The path for the other pdf.
- * @param {number|number[]} pages - The page number or the array of page numbers to be appended.
- */
-exports.appendPage = function appendPage(pdfSrc, pages = []) {
-    if (!Array.isArray(pages) && !isNaN(pages)) {
-        pages = [pages];
-    }
-    const pdfReader = hummus.createReader(pdfSrc);
-    const pageCount = pdfReader.getPagesCount();
-    // prevent unmatched pagenumber
-    const transformPageNumber = (pageNum) => {
-        pageNum = (pageNum > pageCount) ? pageCount : pageNum;
-        pageNum = (pageNum < 1) ? 1 : pageNum;
-        return (pageNum - 1);
-    };
-    pages = pages.map((element) => {
-        if (Array.isArray(element)) {
-            return [
-                transformPageNumber(element[0]),
-                transformPageNumber(element[1])
-            ];
-        } else {
-            return [
-                transformPageNumber(element),
-                transformPageNumber(element)
-            ];
-        }
-    });
-    if (pages.length > 0) {
-        hummusUtils.appendPDFPagesFromPDFWithAnnotations(this.writer, pdfSrc, {
-            specificRanges: pages
-        });
-    } else {
-        hummusUtils.appendPDFPagesFromPDFWithAnnotations(this.writer, pdfSrc);
-    }
-    return this;
-};
+            
const hummus = require('hummus');
+const hummusUtils = require('./utils');
+
+/**
+ * Append pages from the other pdf to the current pdf
+ * @name appendPage
+ * @function
+ * @memberof Recipe
+ * @param {string} pdfSrc - The path for the other pdf.
+ * @param {number|number[]} pages - The page number or the array of page numbers to be appended.
+ */
+exports.appendPage = function appendPage(pdfSrc, pages = []) {
+    if (!Array.isArray(pages) && !isNaN(pages)) {
+        pages = [pages];
+    }
+    const pdfReader = hummus.createReader(pdfSrc);
+    const pageCount = pdfReader.getPagesCount();
+    // prevent unmatched pagenumber
+    const transformPageNumber = (pageNum) => {
+        pageNum = (pageNum > pageCount) ? pageCount : pageNum;
+        pageNum = (pageNum < 1) ? 1 : pageNum;
+        return (pageNum - 1);
+    };
+    pages = pages.map((element) => {
+        if (Array.isArray(element)) {
+            return [
+                transformPageNumber(element[0]),
+                transformPageNumber(element[1])
+            ];
+        } else {
+            return [
+                transformPageNumber(element),
+                transformPageNumber(element)
+            ];
+        }
+    });
+    if (pages.length > 0) {
+        hummusUtils.appendPDFPagesFromPDFWithAnnotations(this.writer, pdfSrc, {
+            specificRanges: pages
+        });
+    } else {
+        hummusUtils.appendPDFPagesFromPDFWithAnnotations(this.writer, pdfSrc);
+    }
+    return this;
+};
 
@@ -94,7 +94,7 @@

appendPage.js


- Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
diff --git a/docs/colors.js.html b/docs/colors.js.html index e84eb22..2297d3a 100644 --- a/docs/colors.js.html +++ b/docs/colors.js.html @@ -37,402 +37,402 @@

colors.js

-
const hummus = require('hummus');
-const fs = require('fs');
-
-this.knownColors = {  // knownColors.colorspace.colorName = value
-    rgb: {
-        red    : 'ff0000',
-        green  : '00ff00',
-        blue   : '0000ff',
-    },
-    cmyk: {
-        cyan   : 'ff000000',
-        magenta: '00ff0000',
-        yellow : '0000ff00',
-        black  : '000000ff'
-    },
-    gray: {
-        white  : 'ff',
-        black  : '00'
-    },
-    separation : {              // meant for printing, so using cmyk values initially
-        cyan   : 'ff000000',
-        magenta: '00ff0000',
-        yellow : '0000ff00',
-        black  : '000000ff',
-        nans   : '%,35,6,0'   // a great PDF collaborator!
-    }
-};
-
-/**
- * Associate color values to names
- *
- * The colorspace parameter is optional. When it is missing, the colorspace
- * is automatically determined by the given color value. Note that the special
- * PDF color space called 'separation' may also be used. The color value is then
- * treated as the alternative color when the named 'separation' color is unavailable.
- *
- * If the 'name' parameter is '!load', the second parameter is the name of a JSON
- * formatted file containing a formatted list of defined colors associated with the
- * color spaces rgb, cmyk, gray, or separation (think PANTONE color definitions).
- * This file will be merged with existing set of known colors. The color values
- * must be specified as hex values.
- *
- * For example,
- *   {
- *      'rgb':  {'purple':'ff00ff', 'red':'#ff0000'},
- *      'cmyk': {'cyan':'ff000000', 'magenta':'%0,100,0,0'},
- *      'gray': {'grey':'#33'}
- *   }
- *
- * @name chroma
- * @function
- * @memberof Recipe
- * @param {string} name - the name to be associated to given color value, or '!load'
- * @param {string|number[]} value - the color value (HexColor, DecimalColor, or PercentColor), or name of '!load' file
- * @param {string} colorspace - one of the followning: 'rgb', 'cmyk', 'gray', 'separation';
- */
-exports.chroma = function chroma(name, value, colorspace='') {
-    if (name) {
-        if (name === '!load') {
-            let newColors = JSON.parse(fs.readFileSync(value));
-            // Add new colors to existing colorspaces
-            for (let cs in newColors) {
-                if (this.knownColors[cs]) {
-                    Object.assign(this.knownColors[cs], newColors[cs]);
-                } else {
-                    throw new Error(`Unrecognized colorspace: ${cs}`);
-                }
-            }
-        } else {
-            if (Array.isArray(value)) {
-                value = arrayToHex(value);
-            } else if (value.startsWith('%')) {
-                value = percentToHex(value.replace('%', ''));
-            } else {
-                value = value.replace('#', '');
-            }
-
-            // Only deal with valid hex codes from the
-            // device colorspaces gray, rgb, and cmyk.
-            if (![2,6,8].includes(value.toString().length)) {
-                throw new Error('Color value has incorrect size for gray, rgb, or cmyk colorspaces');
-            }
-
-            // Determine colorspace by length of given input
-            // value when colorspace not provided in call.
-            if (colorspace === '') {
-                const colorSpaces = {2:'gray', 6:'rgb', 8:'cmyk'};
-                colorspace = colorSpaces[`${value.length}`];
-            } else if (!['rgb','cmyk','gray','separation'].includes(colorspace)) {
-                throw new Error(`Unknown colorspace: ${colorspace}.`);
-            }
-
-            if (colorspace) {
-                this.knownColors[colorspace][name] = value;
-            }
-        }
-    }
-
-    return this;
-};
-
-function createColorSpaces(self, colorName, color) {
-    const deviceCS =  {1:'DeviceGray', 3:'DeviceRGB', 4:'DeviceCMYK'};
-    const altCS = deviceCS[`${color.length}`];
-    this.colorSpaces = this.colorSpaces || {};
-    this.colorSpaces[altCS] = this.colorSpaces[altCS] || {};
-    let colorSpaceID = this.colorSpaces[altCS][colorName];
-
-    if (!colorSpaceID) {
-        const transformFunction = tintTransform(self, color);
-        self.pauseContext();
-        const objCxt = self.writer.getObjectsContext();
-        colorSpaceID = objCxt.startNewIndirectObject();
-        objCxt
-            .startArray()
-            .writeName('Separation')
-            .writeName(colorName)
-            .writeName(altCS)
-            .writeIndirectObjectReference(transformFunction)
-            .endArray(hummus.eTokenSeparatorEndLine)
-            .endIndirectObject();
-        self.resumeContext();
-        this.colorSpaces[altCS][colorName] = colorSpaceID;
-    }
-
-    return colorSpaceID;
-}
-
-function tintTransform(self, color) {
-    const rangeCount = color.length;
-    self.pauseContext();
-    const objCxt = self.writer.getObjectsContext();
-    const tintFuncID = objCxt.startNewIndirectObject();
-    const dict = objCxt.startDictionary();
-    dict
-        .writeKey('FunctionType')
-        .writeNumberValue(2)
-        .writeKey('Domain');
-    objCxt
-        .startArray()
-        .writeNumber(0.0)
-        .writeNumber(1.0)
-        .endArray();
-    dict.writeKey('Range');
-    objCxt.startArray();
-    for (let index = 0; index < rangeCount; index++) {
-        objCxt.writeNumber(0.0).writeNumber(1.0);
-    }
-    objCxt.endArray();
-    dict.writeKey('N');
-    dict.writeNumberValue(1);
-    dict.writeKey('C0');
-    objCxt.startArray();
-    for (let index = 0; index < rangeCount; index++) {
-        objCxt.writeNumber(0.0);
-    }
-    objCxt.endArray();
-
-    dict.writeKey('C1');
-    objCxt.startArray();
-    for (let index = 0; index < rangeCount; index++) {
-        objCxt.writeNumber(color[index]);
-    }
-    objCxt.endArray();
-    objCxt.endDictionary(dict);
-    objCxt.endIndirectObject();
-    self.resumeContext();
-
-    return tintFuncID;
-}
-
-exports._createExtGStates = function _createExtGStates(value) {
-    this.extGStates = this.extGStates || {};
-    if (this.extGStates[value]) {
-        return this.extGStates[value];
-    }
-
-    const write = (key, value) => {
-        this.pauseContext();
-        const objCxt = this.writer.getObjectsContext();
-        const gsId = objCxt.startNewIndirectObject();
-        const dict = objCxt.startDictionary();
-        dict.writeKey('type');
-        dict.writeNameValue('ExtGState');
-        dict.writeKey(key);
-        objCxt.writeNumber(value);
-        objCxt.endLine();
-        objCxt.endDictionary(dict);
-        objCxt.endIndirectObject(); // new here [seh]
-        this.resumeContext();
-        return gsId;
-    };
-    this.extGStates[value] = {
-        stroke: write('CA', value),
-        fill:   write('ca', value)
-    };
-    return this.extGStates[value];
-};
-
-function _defaultColor(colorspace='rgb') {
-    let defaultColor;
-    switch (colorspace) {
-        case 'cmyk':
-            defaultColor = 'FF000000';
-            break;
-        case 'gray':
-            defaultColor = '00';
-            break;
-        case 'rgb':
-        default:
-            defaultColor = '1777d1';
-            break;
-    }
-
-    return defaultColor;
-}
-
-/**
- * Convert given color code int color model object
- *
- * ColorModel consists of: {
- *   color: number,
- *   colorspace: string {'rgb', 'cmyk', 'gray'},
- *   (colorspace == 'rgb')  r, g, b
- *   (colorspace == 'cmyk') c, m, y, k
- *   (colorspace == 'gray') gray
- * }
- *
- * where r,g,b,c,m,y,k,gray are all numbers between 0 and 1
- *
- * @param {string} code the color encoding as HexColor
- * @param {string} colorspace the name of the colorspace of given color code
- * @param {string} colorName the name to be associated with given color code
- * @returns {any} the color model
- */
-function toColorModel(self, code, colorspace, colorName) {
-    const cmodel = {};
-    let color = hexToArray(code);
-
-    cmodel.color = parseInt(code, 16);
-
-    // The initial decider of color space is length of given 'code'.
-
-    switch (color.length) {
-        default:
-            color = hexToArray(_defaultColor('rgb'));
-            // purposely want to fall through to 'rgb' case below.
-        case 3:
-            cmodel.colorspace = 'rgb';
-            cmodel.r = color[0];
-            cmodel.g = color[1];
-            cmodel.b = color[2];
-            break;
-
-        case 4:
-            cmodel.colorspace = 'cmyk';
-            cmodel.c = color[0];
-            cmodel.m = color[1];
-            cmodel.y = color[2];
-            cmodel.k = color[3];
-            break;
-
-        case 1:
-            cmodel.colorspace = 'gray';
-            cmodel.gray = color[0];
-            break;
-    }
-
-    // When creating a separation color space,
-    // use the colorspace from above as the
-    // alternative color transformation when
-    // the named color is unavailable.
-    if (colorspace === 'separation' && colorName !== '') {
-        cmodel.colorspace = colorspace;
-        cmodel.colorName = colorName;
-        cmodel.colorspaceId = createColorSpaces(self, colorName, color);
-    }
-
-    return cmodel;
-}
-
-/**
- * Convert percentage string into hex string (x / 100 * 255)
- *
- * @param {string} code numbers separated by commas with values ranging between 0-100.
- * @returns {string} massaged hexadecimal string that can be used as input to hexToArray.
- */
-function percentToHex(code) {
-    return arrayToHex(code.split(',').map( x => Math.round(x * 2.55)) );
-}
-
-/**
- * Transform color code into numeric value or colorModel
- *
- * @param code color specification in form of HexColor (string, begins with '#'),
- *             DecimalColor (1, 3, or 4 element array with values between 0-255),
- *             PercentColor (string, begins with '%' followed by values separated
- *             by commas with values between 0-100)
- */
-exports._transformColor = function _transformColor(code = '', opt = {}) {
-    this.knownColors   = this.knownColors || {};
-    let colorspace     = opt.colorspace || 'rgb';
-    let wantColorModel = opt.wantColorModel || false;
-    let colorName      = opt.colorName || '';
-    let defaultColor   = _defaultColor(colorspace);
-    let transformation;
-
-    if (Array.isArray(code)) {
-        code = arrayToHex(code);
-    } else if (code.startsWith('#')){
-        code = code.replace('#', '');
-    } else if (code.startsWith('%')) {
-        code = percentToHex(code.replace('%', ''));
-    } else if (code !== '') {
-        let color = this.knownColors[colorspace][code];  // assuming code is a color name
-        if (!color) {
-            color = '';
-            code = defaultColor;
-        } else {
-            colorName = code;
-
-            // The following handles known colors in hex form,
-            // with or without initial '#' and percent form.
-            if (color.startsWith('#')) {
-                code = color.replace('#','');
-            } else if (color.startsWith('%')) {
-                code = percentToHex(color.replace('%', ''));
-            } else {
-                code = color;
-            }
-        }
-    }
-
-    // When colorspace is not explicitly given,
-    // use size of value to determine colorspace.
-    if (!opt.colorspace) {
-        colorspace = {2:'gray', 6:'rgb', 8:'cmyk'}[`${code.length}`] || 'rgb';
-        defaultColor = _defaultColor(colorspace);
-    }
-
-    // Suppply default color:
-    //  when colorspace is given and given color code does not have appropriate length, or
-    //  when colorspace is missing, verify allowable hex value sizes for rgb, cmyk, or gray.
-    if ( ['rgb','cmyk','gray'].includes(colorspace) && code.length != defaultColor.length ||
-         ! [2,6,8].includes(code.toString().length)) {
-        code = defaultColor;
-    }
-
-    if (wantColorModel) {
-        transformation = toColorModel(this, code, colorspace, colorName);
-        if ( colorName && !this.knownColors[colorspace][colorName]) {
-            this.chroma(colorName, code, colorspace);
-        }
-    } else {
-        transformation = parseInt(`0x${code.toUpperCase()}`, 16);
-    }
-
-    return transformation;
-};
-
-function arrayToHex(color = []) {
-    let code = '';
-    color.forEach((item) => {
-        let hex = item.toString(16);
-        hex = (hex.length == 1) ? '0' + hex : hex;
-        code += hex;
-    });
-    return code;
-}
-
-function hexToArray(hex) {
-    const result = /^#?([a-f\d]{2})([a-f\d]{2})?([a-f\d]{2})?([a-f\d]{2})?$/i.exec(hex);
-    return result
-        .reduce((array, item, index) => {
-            if (index >= 1 && index <= (hex.length / 2)) {
-                array.push(parseInt(item, 16) / 255);
-            }
-            return array;
-        }, []);
-}
-
-exports._colorNumberToRGB = (bigint) => {
-    if (!bigint) {
-        return {
-            r: 0,
-            g: 0,
-            b: 0
-        };
-    } else {
-        return {
-            r: (bigint >> 16) & 255,
-            g: (bigint >>  8) & 255,
-            b: bigint & 255
-        };
-    }
-};
+            
const hummus = require('hummus');
+const fs = require('fs');
+
+this.knownColors = {  // knownColors.colorspace.colorName = value
+    rgb: {
+        red    : 'ff0000',
+        green  : '00ff00',
+        blue   : '0000ff',
+    },
+    cmyk: {
+        cyan   : 'ff000000',
+        magenta: '00ff0000',
+        yellow : '0000ff00',
+        black  : '000000ff'
+    },
+    gray: {
+        white  : 'ff',
+        black  : '00'
+    },
+    separation : {              // meant for printing, so using cmyk values initially
+        cyan   : 'ff000000',
+        magenta: '00ff0000',
+        yellow : '0000ff00',
+        black  : '000000ff',
+        nans   : '%,35,6,0'   // a great PDF collaborator!
+    }
+};
+
+/**
+ * Associate color values to names
+ *
+ * The colorspace parameter is optional. When it is missing, the colorspace
+ * is automatically determined by the given color value. Note that the special
+ * PDF color space called 'separation' may also be used. The color value is then
+ * treated as the alternative color when the named 'separation' color is unavailable.
+ *
+ * If the 'name' parameter is '!load', the second parameter is the name of a JSON
+ * formatted file containing a formatted list of defined colors associated with the
+ * color spaces rgb, cmyk, gray, or separation (think PANTONE color definitions).
+ * This file will be merged with existing set of known colors. The color values
+ * must be specified as hex values.
+ *
+ * For example,
+ *   {
+ *      'rgb':  {'purple':'ff00ff', 'red':'#ff0000'},
+ *      'cmyk': {'cyan':'ff000000', 'magenta':'%0,100,0,0'},
+ *      'gray': {'grey':'#33'}
+ *   }
+ *
+ * @name chroma
+ * @function
+ * @memberof Recipe
+ * @param {string} name - the name to be associated to given color value, or '!load'
+ * @param {string|number[]} value - the color value (HexColor, DecimalColor, or PercentColor), or name of '!load' file
+ * @param {string} colorspace - one of the followning: 'rgb', 'cmyk', 'gray', 'separation';
+ */
+exports.chroma = function chroma(name, value, colorspace='') {
+    if (name) {
+        if (name === '!load') {
+            let newColors = JSON.parse(fs.readFileSync(value));
+            // Add new colors to existing colorspaces
+            for (let cs in newColors) {
+                if (this.knownColors[cs]) {
+                    Object.assign(this.knownColors[cs], newColors[cs]);
+                } else {
+                    throw new Error(`Unrecognized colorspace: ${cs}`);
+                }
+            }
+        } else {
+            if (Array.isArray(value)) {
+                value = arrayToHex(value);
+            } else if (value.startsWith('%')) {
+                value = percentToHex(value.replace('%', ''));
+            } else {
+                value = value.replace('#', '');
+            }
+
+            // Only deal with valid hex codes from the
+            // device colorspaces gray, rgb, and cmyk.
+            if (![2,6,8].includes(value.toString().length)) {
+                throw new Error('Color value has incorrect size for gray, rgb, or cmyk colorspaces');
+            }
+
+            // Determine colorspace by length of given input
+            // value when colorspace not provided in call.
+            if (colorspace === '') {
+                const colorSpaces = {2:'gray', 6:'rgb', 8:'cmyk'};
+                colorspace = colorSpaces[`${value.length}`];
+            } else if (!['rgb','cmyk','gray','separation'].includes(colorspace)) {
+                throw new Error(`Unknown colorspace: ${colorspace}.`);
+            }
+
+            if (colorspace) {
+                this.knownColors[colorspace][name] = value;
+            }
+        }
+    }
+
+    return this;
+};
+
+function createColorSpaces(self, colorName, color) {
+    const deviceCS =  {1:'DeviceGray', 3:'DeviceRGB', 4:'DeviceCMYK'};
+    const altCS = deviceCS[`${color.length}`];
+    this.colorSpaces = this.colorSpaces || {};
+    this.colorSpaces[altCS] = this.colorSpaces[altCS] || {};
+    let colorSpaceID = this.colorSpaces[altCS][colorName];
+
+    if (!colorSpaceID) {
+        const transformFunction = tintTransform(self, color);
+        self.pauseContext();
+        const objCxt = self.writer.getObjectsContext();
+        colorSpaceID = objCxt.startNewIndirectObject();
+        objCxt
+            .startArray()
+            .writeName('Separation')
+            .writeName(colorName)
+            .writeName(altCS)
+            .writeIndirectObjectReference(transformFunction)
+            .endArray(hummus.eTokenSeparatorEndLine)
+            .endIndirectObject();
+        self.resumeContext();
+        this.colorSpaces[altCS][colorName] = colorSpaceID;
+    }
+
+    return colorSpaceID;
+}
+
+function tintTransform(self, color) {
+    const rangeCount = color.length;
+    self.pauseContext();
+    const objCxt = self.writer.getObjectsContext();
+    const tintFuncID = objCxt.startNewIndirectObject();
+    const dict = objCxt.startDictionary();
+    dict
+        .writeKey('FunctionType')
+        .writeNumberValue(2)
+        .writeKey('Domain');
+    objCxt
+        .startArray()
+        .writeNumber(0.0)
+        .writeNumber(1.0)
+        .endArray();
+    dict.writeKey('Range');
+    objCxt.startArray();
+    for (let index = 0; index < rangeCount; index++) {
+        objCxt.writeNumber(0.0).writeNumber(1.0);
+    }
+    objCxt.endArray();
+    dict.writeKey('N');
+    dict.writeNumberValue(1);
+    dict.writeKey('C0');
+    objCxt.startArray();
+    for (let index = 0; index < rangeCount; index++) {
+        objCxt.writeNumber(0.0);
+    }
+    objCxt.endArray();
+
+    dict.writeKey('C1');
+    objCxt.startArray();
+    for (let index = 0; index < rangeCount; index++) {
+        objCxt.writeNumber(color[index]);
+    }
+    objCxt.endArray();
+    objCxt.endDictionary(dict);
+    objCxt.endIndirectObject();
+    self.resumeContext();
+
+    return tintFuncID;
+}
+
+exports._createExtGStates = function _createExtGStates(value) {
+    this.extGStates = this.extGStates || {};
+    if (this.extGStates[value]) {
+        return this.extGStates[value];
+    }
+
+    const write = (key, value) => {
+        this.pauseContext();
+        const objCxt = this.writer.getObjectsContext();
+        const gsId = objCxt.startNewIndirectObject();
+        const dict = objCxt.startDictionary();
+        dict.writeKey('type');
+        dict.writeNameValue('ExtGState');
+        dict.writeKey(key);
+        objCxt.writeNumber(value);
+        objCxt.endLine();
+        objCxt.endDictionary(dict);
+        objCxt.endIndirectObject(); // new here [seh]
+        this.resumeContext();
+        return gsId;
+    };
+    this.extGStates[value] = {
+        stroke: write('CA', value),
+        fill:   write('ca', value)
+    };
+    return this.extGStates[value];
+};
+
+function _defaultColor(colorspace='rgb') {
+    let defaultColor;
+    switch (colorspace) {
+        case 'cmyk':
+            defaultColor = 'FF000000';
+            break;
+        case 'gray':
+            defaultColor = '00';
+            break;
+        case 'rgb':
+        default:
+            defaultColor = '1777d1';
+            break;
+    }
+
+    return defaultColor;
+}
+
+/**
+ * Convert given color code int color model object
+ *
+ * ColorModel consists of: {
+ *   color: number,
+ *   colorspace: string {'rgb', 'cmyk', 'gray'},
+ *   (colorspace == 'rgb')  r, g, b
+ *   (colorspace == 'cmyk') c, m, y, k
+ *   (colorspace == 'gray') gray
+ * }
+ *
+ * where r,g,b,c,m,y,k,gray are all numbers between 0 and 1
+ *
+ * @param {string} code the color encoding as HexColor
+ * @param {string} colorspace the name of the colorspace of given color code
+ * @param {string} colorName the name to be associated with given color code
+ * @returns {any} the color model
+ */
+function toColorModel(self, code, colorspace, colorName) {
+    const cmodel = {};
+    let color = hexToArray(code);
+
+    cmodel.color = parseInt(code, 16);
+
+    // The initial decider of color space is length of given 'code'.
+
+    switch (color.length) {
+        default:
+            color = hexToArray(_defaultColor('rgb'));
+            // purposely want to fall through to 'rgb' case below.
+        case 3:
+            cmodel.colorspace = 'rgb';
+            cmodel.r = color[0];
+            cmodel.g = color[1];
+            cmodel.b = color[2];
+            break;
+
+        case 4:
+            cmodel.colorspace = 'cmyk';
+            cmodel.c = color[0];
+            cmodel.m = color[1];
+            cmodel.y = color[2];
+            cmodel.k = color[3];
+            break;
+
+        case 1:
+            cmodel.colorspace = 'gray';
+            cmodel.gray = color[0];
+            break;
+    }
+
+    // When creating a separation color space,
+    // use the colorspace from above as the
+    // alternative color transformation when
+    // the named color is unavailable.
+    if (colorspace === 'separation' && colorName !== '') {
+        cmodel.colorspace = colorspace;
+        cmodel.colorName = colorName;
+        cmodel.colorspaceId = createColorSpaces(self, colorName, color);
+    }
+
+    return cmodel;
+}
+
+/**
+ * Convert percentage string into hex string (x / 100 * 255)
+ *
+ * @param {string} code numbers separated by commas with values ranging between 0-100.
+ * @returns {string} massaged hexadecimal string that can be used as input to hexToArray.
+ */
+function percentToHex(code) {
+    return arrayToHex(code.split(',').map( x => Math.round(x * 2.55)) );
+}
+
+/**
+ * Transform color code into numeric value or colorModel
+ *
+ * @param code color specification in form of HexColor (string, begins with '#'),
+ *             DecimalColor (1, 3, or 4 element array with values between 0-255),
+ *             PercentColor (string, begins with '%' followed by values separated
+ *             by commas with values between 0-100)
+ */
+exports._transformColor = function _transformColor(code = '', opt = {}) {
+    this.knownColors   = this.knownColors || {};
+    let colorspace     = opt.colorspace || 'rgb';
+    let wantColorModel = opt.wantColorModel || false;
+    let colorName      = opt.colorName || '';
+    let defaultColor   = _defaultColor(colorspace);
+    let transformation;
+
+    if (Array.isArray(code)) {
+        code = arrayToHex(code);
+    } else if (code.startsWith('#')){
+        code = code.replace('#', '');
+    } else if (code.startsWith('%')) {
+        code = percentToHex(code.replace('%', ''));
+    } else if (code !== '') {
+        let color = this.knownColors[colorspace][code];  // assuming code is a color name
+        if (!color) {
+            color = '';
+            code = defaultColor;
+        } else {
+            colorName = code;
+
+            // The following handles known colors in hex form,
+            // with or without initial '#' and percent form.
+            if (color.startsWith('#')) {
+                code = color.replace('#','');
+            } else if (color.startsWith('%')) {
+                code = percentToHex(color.replace('%', ''));
+            } else {
+                code = color;
+            }
+        }
+    }
+
+    // When colorspace is not explicitly given,
+    // use size of value to determine colorspace.
+    if (!opt.colorspace) {
+        colorspace = {2:'gray', 6:'rgb', 8:'cmyk'}[`${code.length}`] || 'rgb';
+        defaultColor = _defaultColor(colorspace);
+    }
+
+    // Suppply default color:
+    //  when colorspace is given and given color code does not have appropriate length, or
+    //  when colorspace is missing, verify allowable hex value sizes for rgb, cmyk, or gray.
+    if ( ['rgb','cmyk','gray'].includes(colorspace) && code.length != defaultColor.length ||
+         ! [2,6,8].includes(code.toString().length)) {
+        code = defaultColor;
+    }
+
+    if (wantColorModel) {
+        transformation = toColorModel(this, code, colorspace, colorName);
+        if ( colorName && !this.knownColors[colorspace][colorName]) {
+            this.chroma(colorName, code, colorspace);
+        }
+    } else {
+        transformation = parseInt(`0x${code.toUpperCase()}`, 16);
+    }
+
+    return transformation;
+};
+
+function arrayToHex(color = []) {
+    let code = '';
+    color.forEach((item) => {
+        let hex = item.toString(16);
+        hex = (hex.length == 1) ? '0' + hex : hex;
+        code += hex;
+    });
+    return code;
+}
+
+function hexToArray(hex) {
+    const result = /^#?([a-f\d]{2})([a-f\d]{2})?([a-f\d]{2})?([a-f\d]{2})?$/i.exec(hex);
+    return result
+        .reduce((array, item, index) => {
+            if (index >= 1 && index <= (hex.length / 2)) {
+                array.push(parseInt(item, 16) / 255);
+            }
+            return array;
+        }, []);
+}
+
+exports._colorNumberToRGB = (bigint) => {
+    if (!bigint) {
+        return {
+            r: 0,
+            g: 0,
+            b: 0
+        };
+    } else {
+        return {
+            r: (bigint >> 16) & 255,
+            g: (bigint >>  8) & 255,
+            b: bigint & 255
+        };
+    }
+};
 
@@ -445,7 +445,7 @@

colors.js


- Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
diff --git a/docs/encrypt.js.html b/docs/encrypt.js.html index 1cf79ad..6e3a6ad 100644 --- a/docs/encrypt.js.html +++ b/docs/encrypt.js.html @@ -37,112 +37,112 @@

encrypt.js

-
const hummus = require('hummus');
-const fs = require('fs');
-
-
-/**
- * Encryption user access permissions
- *
- * This function supplies the numeric value for the encrypt function's 'userProtectionFlag'
- * option. When no argument is given, the default 'print' value is used.
- *
- * @name permission
- * @function
- * @memberof Recipe
- * @param {string} flags from the list print, modify, copy, edit, fillform, extract, assemble, and printbest
- * More than one may be specified by using a comma to separate the names in the input string.
- */
-exports.permission = function permission(flags='print') {
-
-    // https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
-
-    const userAccessPermissions = {  // see table on page 61 of above document
-        print    : 1<<2,   // allow printing
-        modify   : 1<<3,   // allow template creation, signing, filling form fields
-        copy     : 1<<4,   // allow content copying and copying for accessibility
-        edit     : 1<<5,   // allow commenting
-        fillform : 1<<8,   // allow filling of form fields
-        extract  : 1<<9,   // allow content copying for accessibility
-        assemble : 1<<10,  // unused
-        printbest: 1<<11   // allow high resolution printing when 'print' is allowed
-    };
-
-    const perms = flags.split(',').map((x)=>{return x.trim();});
-    let access = 0;
-    perms.forEach((perm) => {
-        if (!userAccessPermissions[perm]) {
-            throw new Error(`Unknown user access permission (${perm})`);
-        }
-        access += userAccessPermissions[perm];
-    });
-
-    return access;
-};
-
-exports._getEncryptOptions = function _getEncryptOptions(options, addPermissions=true) {
-    const encryptOptions = {};
-
-    const password = options.password || options.ownerPassword;
-    if (password) {
-        encryptOptions.password = password;
-        encryptOptions.ownerPassword = password;
-    }
-
-    if (options.userPassword) {
-        encryptOptions.userPassword = options.userPassword;
-        if (!encryptOptions.password) {
-            encryptOptions.password = options.userPassword;
-        }
-    }
-
-    if (addPermissions) {
-        if (options.userProtectionFlag) {
-            encryptOptions.userProtectionFlag = options.userProtectionFlag;
-        }
-    }
-
-    // Only attach encryption mechanism when attributes
-    // have been explicitly given in the incoming options.
-    if (Object.keys(encryptOptions) > 0 && !encryptOptions.userPassword) {
-        encryptOptions.userPassword = '';
-        if (!encryptOptions.userProtectionFlag) {
-            encryptOptions.userProtectionFlag = this.permission();
-        }
-    }
-
-    return encryptOptions;
-};
-
-/**
- * Encrypt the pdf
- * @name encrypt
- * @function
- * @memberof Recipe
- * @param {Object} options - The options
- * @param {string} [options.password] - The permission password.
- * @param {string} [options.ownerPassword] - The password for editing.
- * @param {string} [options.userPassword] - The password for viewing & encryption.
- * @param {number} [options.userProtectionFlag] - The flag for the security level.
- */
-exports.encrypt = function encrypt(options = {}) {
-    this.needToEncrypt = true;
-    this.encryption_ = this._getEncryptOptions(options);
-
-    return this;
-};
-
-// http://pdfhummus.com/post/147451287581/hummus-1058-and-pdf-writer-updates-encryption
-exports._encrypt = function _encrypt() {
-    if (!this.encryption_) {
-        return;
-    }
-
-    const tmp = this.output + '.tmp.pdf';
-    fs.renameSync(this.output, tmp);
-    hummus.recrypt(tmp, this.output, this.encryption_);
-    fs.unlinkSync(tmp);
-};
+            
const hummus = require('hummus');
+const fs = require('fs');
+
+
+/**
+ * Encryption user access permissions
+ *
+ * This function supplies the numeric value for the encrypt function's 'userProtectionFlag'
+ * option. When no argument is given, the default 'print' value is used.
+ *
+ * @name permission
+ * @function
+ * @memberof Recipe
+ * @param {string} flags from the list print, modify, copy, edit, fillform, extract, assemble, and printbest
+ * More than one may be specified by using a comma to separate the names in the input string.
+ */
+exports.permission = function permission(flags='print') {
+
+    // https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
+
+    const userAccessPermissions = {  // see table on page 61 of above document
+        print    : 1<<2,   // allow printing
+        modify   : 1<<3,   // allow template creation, signing, filling form fields
+        copy     : 1<<4,   // allow content copying and copying for accessibility
+        edit     : 1<<5,   // allow commenting
+        fillform : 1<<8,   // allow filling of form fields
+        extract  : 1<<9,   // allow content copying for accessibility
+        assemble : 1<<10,  // unused
+        printbest: 1<<11   // allow high resolution printing when 'print' is allowed
+    };
+
+    const perms = flags.split(',').map((x)=>{return x.trim();});
+    let access = 0;
+    perms.forEach((perm) => {
+        if (!userAccessPermissions[perm]) {
+            throw new Error(`Unknown user access permission (${perm})`);
+        }
+        access += userAccessPermissions[perm];
+    });
+
+    return access;
+};
+
+exports._getEncryptOptions = function _getEncryptOptions(options, addPermissions=true) {
+    const encryptOptions = {};
+
+    const password = options.password || options.ownerPassword;
+    if (password) {
+        encryptOptions.password = password;
+        encryptOptions.ownerPassword = password;
+    }
+
+    if (options.userPassword) {
+        encryptOptions.userPassword = options.userPassword;
+        if (!encryptOptions.password) {
+            encryptOptions.password = options.userPassword;
+        }
+    }
+
+    if (addPermissions) {
+        if (options.userProtectionFlag) {
+            encryptOptions.userProtectionFlag = options.userProtectionFlag;
+        }
+    }
+
+    // Only attach encryption mechanism when attributes
+    // have been explicitly given in the incoming options.
+    if (Object.keys(encryptOptions) > 0 && !encryptOptions.userPassword) {
+        encryptOptions.userPassword = '';
+        if (!encryptOptions.userProtectionFlag) {
+            encryptOptions.userProtectionFlag = this.permission();
+        }
+    }
+
+    return encryptOptions;
+};
+
+/**
+ * Encrypt the pdf
+ * @name encrypt
+ * @function
+ * @memberof Recipe
+ * @param {Object} options - The options
+ * @param {string} [options.password] - The permission password.
+ * @param {string} [options.ownerPassword] - The password for editing.
+ * @param {string} [options.userPassword] - The password for viewing & encryption.
+ * @param {number} [options.userProtectionFlag] - The flag for the security level.
+ */
+exports.encrypt = function encrypt(options = {}) {
+    this.needToEncrypt = true;
+    this.encryption_ = this._getEncryptOptions(options);
+
+    return this;
+};
+
+// http://pdfhummus.com/post/147451287581/hummus-1058-and-pdf-writer-updates-encryption
+exports._encrypt = function _encrypt() {
+    if (!this.encryption_) {
+        return;
+    }
+
+    const tmp = this.output + '.tmp.pdf';
+    fs.renameSync(this.output, tmp);
+    hummus.recrypt(tmp, this.output, this.encryption_);
+    fs.unlinkSync(tmp);
+};
 
@@ -155,7 +155,7 @@

encrypt.js


- Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
diff --git a/docs/font.js.html b/docs/font.js.html index 8ce9ece..b7c1d65 100644 --- a/docs/font.js.html +++ b/docs/font.js.html @@ -37,42 +37,42 @@

font.js

-
const fs = require('fs');
-const path = require('path');
-/**
- * Register a custom font
- * @name registerFont
- * @function
- * @memberof Recipe
- * @param {string} fontName - The font name will be used in text
- * @param {string} fontSrcPath - The path to the font file.
- */
-exports.registerFont = function registerFont(fontName = '', fontSrcPath = '') {
-    return this._registerFont(fontName, fontSrcPath);
-};
-
-exports._loadFonts = function _loadFonts(fontSrcPath) {
-    const fontTypes = ['.ttf', '.ttc', '.otf'];
-    const fontPaths = (typeof fontSrcPath === 'string') ? [fontSrcPath] : fontSrcPath;
-
-    for (let fpath of fontPaths) {
-        fs
-            .readdirSync(fpath)
-            .filter((file) => {
-                return fontTypes.includes(path.extname(file).toLowerCase());
-            })
-            .forEach((file) => {
-                const fontName = path.basename(file, path.extname(file));
-                return this._registerFont(fontName, path.join(fpath, file));
-            });
-    }
-};
-
-exports._registerFont = function _registerFont(fontName, fontSrcPath) {
-    this.fonts = this.fonts || {};
-    // check fontSrcPath
-    this.fonts[fontName.toLowerCase()] = fontSrcPath;
-};
+            
const fs = require('fs');
+const path = require('path');
+/**
+ * Register a custom font
+ * @name registerFont
+ * @function
+ * @memberof Recipe
+ * @param {string} fontName - The font name will be used in text
+ * @param {string} fontSrcPath - The path to the font file.
+ */
+exports.registerFont = function registerFont(fontName = '', fontSrcPath = '') {
+    return this._registerFont(fontName, fontSrcPath);
+};
+
+exports._loadFonts = function _loadFonts(fontSrcPath) {
+    const fontTypes = ['.ttf', '.ttc', '.otf'];
+    const fontPaths = (typeof fontSrcPath === 'string') ? [fontSrcPath] : fontSrcPath;
+
+    for (let fpath of fontPaths) {
+        fs
+            .readdirSync(fpath)
+            .filter((file) => {
+                return fontTypes.includes(path.extname(file).toLowerCase());
+            })
+            .forEach((file) => {
+                const fontName = path.basename(file, path.extname(file));
+                return this._registerFont(fontName, path.join(fpath, file));
+            });
+    }
+};
+
+exports._registerFont = function _registerFont(fontName, fontSrcPath) {
+    this.fonts = this.fonts || {};
+    // check fontSrcPath
+    this.fonts[fontName.toLowerCase()] = fontSrcPath;
+};
 
@@ -85,7 +85,7 @@

font.js


- Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
diff --git a/docs/global.html b/docs/global.html index d9d2a40..cfea0ee 100644 --- a/docs/global.html +++ b/docs/global.html @@ -127,7 +127,7 @@

_get
Source:
@@ -304,9 +304,9 @@

Parameters:
+ DecimalColor (1, 3, or 4 element array with values between 0-255), + PercentColor (string, begins with '%' followed by values separated + by commas with values between 0-100)

@@ -1302,11 +1302,11 @@

toColorMo

Convert given color code int color model object

ColorModel consists of: { -color: number, -colorspace: string {'rgb', 'cmyk', 'gray'}, -(colorspace == 'rgb') r, g, b -(colorspace == 'cmyk') c, m, y, k -(colorspace == 'gray') gray + color: number, + colorspace: string {'rgb', 'cmyk', 'gray'}, + (colorspace == 'rgb') r, g, b + (colorspace == 'cmyk') c, m, y, k + (colorspace == 'gray') gray }

where r,g,b,c,m,y,k,gray are all numbers between 0 and 1

@@ -1470,7 +1470,7 @@

Returns:

- Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
diff --git a/docs/image.js.html b/docs/image.js.html index 0179257..ff5db5d 100644 --- a/docs/image.js.html +++ b/docs/image.js.html @@ -37,131 +37,131 @@

image.js

-
/**
- * Place images to pdf
- * @name image
- * @function
- * @memberof Recipe
- * @param {string} imgSrc - The path for the image. [JPEG, PNG, TIFF]
- * @param {number} x - The coordinate x
- * @param {number} y - The coordinate y
- * @param {Object} [options] - The options
- * @param {number} [options.width] - The new width
- * @param {number} [options.height] - The new height
- * @param {number} [options.scale] - Scale the image from the original width and height.
- * @param {boolean} [options.keepAspectRatio=true] - Keep the aspect ratio.
- * @param {number} [options.opacity] - The opacity.
- * @param {string} [options.align] - 'center center'...
- */
-exports.image = function image(imgSrc, x, y, options = {}) {
-    const { width, height, offsetX, offsetY } = this._getImgOffset(imgSrc, options);
-    const imgOptions = {
-        transformation: {
-            fit: 'always',
-            // proportional: true,
-            width,
-            height
-        }
-    };
-    const { nx, ny } = this._calibrateCoordinate(x, y, offsetX, offsetY);
-
-    const _options = this._getPathOptions(options, nx, ny);
-    const gsId = _options.fillGsId;
-
-    // See if this image has been seen already, so as to not duplicate it.
-    let xObject = this.xObjects.find((element) => {
-        return element.get('name') == imgSrc;
-    });
-
-    if (xObject) {
-        _options.xObject = xObject;
-        _options.ratio = [width / xObject.get('width'), height / xObject.get('height')];
-    }
-
-    this._drawObject(this, nx, ny, width, height, _options, (ctx, xObject) => {
-
-        // Only new images visit here
-        xObject.set('type', 'image');
-        xObject.set('name', imgSrc);
-        xObject.set('width', width);
-        xObject.set('height', height);
-
-        this.xObjects.push(xObject);
-
-        ctx
-            .gs(xObject.getGsName(gsId))
-            .drawImage(0, 0, imgSrc, imgOptions);
-    });
-
-    return this;
-};
-
-exports._getImgOffset = function _getImgOffset(imgSrc = '', options = {}) {
-    // set default to true
-    options.keepAspectRatio = (options.keepAspectRatio == void 0) ?
-        true : options.keepAspectRatio;
-    const dimensions = this.writer.getImageDimensions(imgSrc);
-    const ratio = dimensions.width / dimensions.height;
-
-    let width = dimensions.width;
-    let height = dimensions.height;
-    if (options.scale) {
-        width = width * options.scale;
-        height = height * options.scale;
-    } else
-    if (options.width && !options.height) {
-        width = options.width;
-        height = options.width / ratio;
-    } else
-    if (!options.width && options.height) {
-        width = options.height * ratio;
-        height = options.height;
-    } else
-    if (options.width && options.height) {
-        if (!options.keepAspectRatio) {
-            width = options.width;
-            height = options.height;
-        } else {
-            // fit to the smaller
-            if (options.width / ratio <= options.height) {
-                width = options.width;
-                height = options.width / ratio;
-            } else {
-                width = options.height * ratio;
-                height = options.height;
-            }
-        }
-    }
-    let offsetX = 0;
-    let offsetY = -height;
-
-    if (options.align) {
-        const alignments = options.align.split(' ');
-        if (alignments[0]) {
-            switch (alignments[0]) {
-                case 'center':
-                    offsetX = -1 * width / 2;
-                    break;
-                case 'right':
-                    offsetX = width / 2;
-                    break;
-                default:
-            }
-        }
-        if (alignments[1]) {
-            switch (alignments[1]) {
-                case 'center':
-                    offsetY = -1 * height / 2;
-                    break;
-                case 'bottom':
-                    offsetY = height / 2;
-                    break;
-                default:
-            }
-        }
-    }
-    return { width, height, offsetX, offsetY };
-};
+            
/**
+ * Place images to pdf
+ * @name image
+ * @function
+ * @memberof Recipe
+ * @param {string} imgSrc - The path for the image. [JPEG, PNG, TIFF]
+ * @param {number} x - The coordinate x
+ * @param {number} y - The coordinate y
+ * @param {Object} [options] - The options
+ * @param {number} [options.width] - The new width
+ * @param {number} [options.height] - The new height
+ * @param {number} [options.scale] - Scale the image from the original width and height.
+ * @param {boolean} [options.keepAspectRatio=true] - Keep the aspect ratio.
+ * @param {number} [options.opacity] - The opacity.
+ * @param {string} [options.align] - 'center center'...
+ */
+exports.image = function image(imgSrc, x, y, options = {}) {
+    const { width, height, offsetX, offsetY } = this._getImgOffset(imgSrc, options);
+    const imgOptions = {
+        transformation: {
+            fit: 'always',
+            // proportional: true,
+            width,
+            height
+        }
+    };
+    const { nx, ny } = this._calibrateCoordinate(x, y, offsetX, offsetY);
+
+    const _options = this._getPathOptions(options, nx, ny);
+    const gsId = _options.fillGsId;
+
+    // See if this image has been seen already, so as to not duplicate it.
+    let xObject = this.xObjects.find((element) => {
+        return element.get('name') == imgSrc;
+    });
+
+    if (xObject) {
+        _options.xObject = xObject;
+        _options.ratio = [width / xObject.get('width'), height / xObject.get('height')];
+    }
+
+    this._drawObject(this, nx, ny, width, height, _options, (ctx, xObject) => {
+
+        // Only new images visit here
+        xObject.set('type', 'image');
+        xObject.set('name', imgSrc);
+        xObject.set('width', width);
+        xObject.set('height', height);
+
+        this.xObjects.push(xObject);
+
+        ctx
+            .gs(xObject.getGsName(gsId))
+            .drawImage(0, 0, imgSrc, imgOptions);
+    });
+
+    return this;
+};
+
+exports._getImgOffset = function _getImgOffset(imgSrc = '', options = {}) {
+    // set default to true
+    options.keepAspectRatio = (options.keepAspectRatio == void 0) ?
+        true : options.keepAspectRatio;
+    const dimensions = this.writer.getImageDimensions(imgSrc);
+    const ratio = dimensions.width / dimensions.height;
+
+    let width = dimensions.width;
+    let height = dimensions.height;
+    if (options.scale) {
+        width = width * options.scale;
+        height = height * options.scale;
+    } else
+    if (options.width && !options.height) {
+        width = options.width;
+        height = options.width / ratio;
+    } else
+    if (!options.width && options.height) {
+        width = options.height * ratio;
+        height = options.height;
+    } else
+    if (options.width && options.height) {
+        if (!options.keepAspectRatio) {
+            width = options.width;
+            height = options.height;
+        } else {
+            // fit to the smaller
+            if (options.width / ratio <= options.height) {
+                width = options.width;
+                height = options.width / ratio;
+            } else {
+                width = options.height * ratio;
+                height = options.height;
+            }
+        }
+    }
+    let offsetX = 0;
+    let offsetY = -height;
+
+    if (options.align) {
+        const alignments = options.align.split(' ');
+        if (alignments[0]) {
+            switch (alignments[0]) {
+                case 'center':
+                    offsetX = -1 * width / 2;
+                    break;
+                case 'right':
+                    offsetX = width / 2;
+                    break;
+                default:
+            }
+        }
+        if (alignments[1]) {
+            switch (alignments[1]) {
+                case 'center':
+                    offsetY = -1 * height / 2;
+                    break;
+                case 'bottom':
+                    offsetY = height / 2;
+                    break;
+                default:
+            }
+        }
+    }
+    return { width, height, offsetX, offsetY };
+};
 
@@ -174,7 +174,7 @@

image.js


- Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
diff --git a/docs/index.html b/docs/index.html index 56f003a..48ab3e3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -55,7 +55,7 @@

Home

Classes

  • diff --git a/docs/info.js.html b/docs/info.js.html index 9899000..b98ef9e 100644 --- a/docs/info.js.html +++ b/docs/info.js.html @@ -37,331 +37,331 @@

    info.js

    -
    const fs = require('fs');
    -const hummus = require('hummus');
    -/**
    - * @name info
    - * @desc Add new PDF information, or retrieve existing PDF information.
    - * @memberof Recipe
    - * @function
    - * @param {Object} [options] - The options (when missing obtains existing PDF information)
    - * @param {number} [options.version] - The pdf version
    - * @param {string} [options.author] - The author
    - * @param {string} [options.title] - The title
    - * @param {string} [options.subject] - The subject
    - * @param {string[]} [options.keywords] - The array of keywords
    - */
    -exports.info = function info(options) {
    -    let result;
    -
    -    if (! options) {
    -        result = this._readInfo();
    -        
    -    } else {
    -        this.toWriteInfo_ = this.toWriteInfo_ || {};
    -        Object.assign(this.toWriteInfo_, options);
    -        result = this;
    -    }
    -
    -    return result;
    -};
    -
    -exports._readInfo = function _readInfo() {
    -    if (!this.isNewPDF && !this.infoDictionary) {
    -        const copyFrom = this.isBufferSrc ? new hummus.PDFRStreamForBuffer(this.src) : this.src;
    -        const copyCtx  = this.writer.createPDFCopyingContext(copyFrom);
    -        const infoDict = copyCtx.getSourceDocumentParser().queryDictionaryObject(
    -            copyCtx.getSourceDocumentParser().getTrailer(), 'Info'
    -        );
    -    
    -        const oldInfo = (infoDict && infoDict.toJSObject) ? infoDict.toJSObject() : null;
    -    
    -        if (oldInfo) {
    -            this.infoDictionary = {}
    -            Object.getOwnPropertyNames(oldInfo).forEach((key) => {
    -                if (!oldInfo[key]) {
    -                    return;
    -                }
    -                const oldInforSrc = this._parseObjectByType(oldInfo[key]);
    -                if (!oldInforSrc) {
    -                    return;
    -                }
    -                switch (key) {
    -                    case 'Trapped':
    -                        if (oldInforSrc && oldInforSrc.value) {
    -                            this.infoDictionary.trapped = oldInforSrc.value;
    -                        }
    -                        break;
    -                    case 'CreationDate':
    -                        if (oldInforSrc && oldInforSrc.value) {
    -                            this.infoDictionary.creationDate = oldInforSrc.value;
    -                        }
    -                        break;
    -                    case 'ModDate':
    -                        if (oldInforSrc && oldInforSrc.value) {
    -                            this.infoDictionary.modDate = oldInforSrc.value;
    -                        }
    -                        break;
    -                    case 'Creator':
    -                        if (oldInforSrc && oldInforSrc.toText) {
    -                            this.infoDictionary.creator = oldInforSrc.toText();
    -                        }
    -                        break;
    -                    case 'Producer':
    -                        if (oldInforSrc && oldInforSrc.toText) {
    -                            this.infoDictionary.producer = oldInforSrc.toText();
    -                        }
    -                        break;
    -                    default:
    -                        if (oldInforSrc && oldInforSrc.toText) {
    -                            this.infoDictionary[key.toLowerCase()] = oldInforSrc.toText();
    -                        }
    -                }
    -            });
    -        }
    -    }
    -
    -    return this.infoDictionary;
    -}
    -
    -exports._writeInfo = function _writeInfo() {
    -    const options = this.toWriteInfo_ || {};
    -    const oldInfo = this._readInfo();
    -    /*
    -        #41, #48
    -        This issue is due to the unhandled process exit from HummusJS.
    -        I have to disable this part before it gets fixed in HummusJS.
    -    */
    -    
    -    const infoDictionary = this.writer.getDocumentContext().getInfoDictionary();
    -    const fields = [{
    -        key: 'author',
    -        type: 'string'
    -    }, {
    -        key: 'title',
    -        type: 'string'
    -    }, {
    -        key: 'subject',
    -        type: 'string'
    -    }, {
    -        key: 'keywords',
    -        type: 'array'
    -    }];
    -    // const ignores = [
    -    //     'CreationDate', 'Creator', 'ModDate', 'Producer'
    -    // ];
    -
    -    if (oldInfo) {
    -        Object.getOwnPropertyNames(oldInfo).forEach((key) => {
    -            if (!oldInfo[key]) {
    -                return;
    -            }
    -
    -            switch (key) {
    -                case 'trapped':
    -                    infoDictionary.trapped = oldInfo.trapped;
    -                    break;
    -                case 'creationDate':
    -                    infoDictionary.setCreationDate(oldInfo.creationDate);
    -                    break;
    -                case 'modDate':
    -                    infoDictionary.addAdditionalInfoEntry('source-ModDate', oldInfo.modDate);
    -                    break;
    -                case 'creator':
    -                    infoDictionary.addAdditionalInfoEntry('source-Creator', oldInfo.creator);
    -                    break;
    -                case 'producer':
    -                    infoDictionary.addAdditionalInfoEntry('source-Producer', oldInfo.producer);
    -                    break;
    -                default:
    -                    infoDictionary[key] = oldInfo[key];
    -            }
    -        });
    -    }
    -
    -    if (this.isNewPDF) {
    -        infoDictionary.setCreationDate(new Date());
    -    }
    -    infoDictionary.setModDate(new Date());
    -    infoDictionary.producer = 'PDFHummus (https://github.com/galkahana/HummusJS)';
    -    infoDictionary.creator = 'Hummus-Recipe (https://github.com/chunyenHuang/hummusRecipe)';
    -
    -    fields.forEach((item) => {
    -        let value = options[item.key];
    -        if (!value) {
    -            return;
    -        } else {
    -            switch (item.type) {
    -                case 'string':
    -                    value = value.toString();
    -                    break;
    -                case 'date':
    -                    value = new Date(value);
    -                    break;
    -                case 'array':
    -                    value = (Array.isArray(value)) ? value : [value];
    -                    break;
    -                default:
    -            }
    -        }
    -        if (item.func) {
    -            infoDictionary[item.func](value);
    -        } else {
    -            infoDictionary[item.key] = value;
    -        }
    -    });
    -    return this;
    -};
    -
    -/**
    - * @name custom
    - * @desc Add custom information to pdf
    - * @memberof Recipe
    - * @function
    - * @param {string} [key] - The key
    - * @param {string} [value] - The value
    - */
    -exports.custom = function custom(key, value) {
    -    const infoDictionary = this.writer.getDocumentContext().getInfoDictionary();
    -    infoDictionary.addAdditionalInfoEntry(key.toString(), value.toString());
    -    return this;
    -};
    -
    -exports.structure = function structure(output) {
    -    // PDF file format http://lotabout.me/orgwiki/pdf.html
    -    // const outputFileType = path.extname(output);
    -    const outputFile = fs.openSync(output, 'w');
    -    const hummus = this.hummus;
    -    const pdfReader = this.pdfReader;
    -
    -    const tabWidth = '  ';
    -    const structures = [
    -        'Info',
    -        'Root', // catalog
    -        'Size',
    -        'Prev',
    -        'ID',
    -        // 'Encrypt',
    -        // 'XRefStm'
    -    ];
    -
    -    const write = (item) => {
    -        const mIteratedObjectIDs = {};
    -        let mTabLevel = 0;
    -
    -        const addTabs = () => {
    -            let output = '';
    -            for (let i = 0; i < mTabLevel; ++i) {
    -                output += tabWidth;
    -            }
    -            return output;
    -        };
    -
    -        const logToFile = (inString) => {
    -            fs.writeSync(outputFile, addTabs() + inString + '\r\n');
    -        };
    -
    -        const iterateObjectTypes = (inObject) => {
    -            const type = inObject.getType();
    -            const label = hummus.getTypeLabel(type);
    -            let output = '';
    -            let objectID, jsArray, aDictionary, keys;
    -
    -            switch (type) {
    -                case hummus.ePDFObjectIndirectObjectReference:
    -                    ++mTabLevel;
    -                    objectID = inObject.toPDFIndirectObjectReference().getObjectID();
    -                    output += `Indirect object reference (${objectID}): `;
    -                    logToFile(output);
    -                    if (!Object.prototype.hasOwnProperty.call(mIteratedObjectIDs, objectID)) {
    -                        mIteratedObjectIDs[objectID] = true;
    -                        iterateObjectTypes(pdfReader.parseNewObject(objectID));
    -                    }
    -                    for (var i = 0; i < mTabLevel; ++i) {
    -                        output += ' ';
    -                    }
    -                    --mTabLevel;
    -                    return;
    -                case hummus.ePDFObjectArray:
    -                    jsArray = inObject.toPDFArray().toJSArray();
    -                    output += `- ${label} [${jsArray.length}]`;
    -                    logToFile(output);
    -                    ++mTabLevel;
    -                    jsArray.forEach((element) => {
    -                        iterateObjectTypes(element);
    -                    });
    -                    --mTabLevel;
    -                    break;
    -                case hummus.ePDFObjectDictionary:
    -                    aDictionary = inObject.toPDFDictionary().toJSObject();
    -                    keys = Object.getOwnPropertyNames(aDictionary).join(', ');
    -                    output += `- ${label} {${keys}}`;
    -                    logToFile(output);
    -                    ++mTabLevel;
    -                    Object.getOwnPropertyNames(aDictionary).forEach((element) => {
    -                        logToFile(element + ' *');
    -                        iterateObjectTypes(aDictionary[element]);
    -                    });
    -                    --mTabLevel;
    -                    break;
    -                case hummus.ePDFObjectStream:
    -                    output += 'Stream . iterating stream dictionary:';
    -                    logToFile(output);
    -                    iterateObjectTypes(inObject.toPDFStream().getDictionary());
    -                    break;
    -                default:
    -                    output += `${tabWidth}${label}: ${inObject}`;
    -                    logToFile(output);
    -            }
    -        };
    -
    -        const itemTrailer = pdfReader.queryDictionaryObject(pdfReader.getTrailer(), item);
    -        logToFile(item);
    -        iterateObjectTypes(itemTrailer);
    -    };
    -
    -    structures.forEach((item) => {
    -        write(item);
    -    });
    -
    -    fs.closeSync(outputFile);
    -    return this;
    -};
    -
    -exports._parseObjectByType = function _parseObjectByType(inObject) {
    -    if (!inObject) {
    -        return;
    -    }
    -    const hummus = this.hummus;
    -    const pdfReader = this.pdfReader;
    -    const type = inObject.getType();
    -    const label = hummus.getTypeLabel(type);
    -    const saveToObject = this.pdfStructure || {};
    -    let objectID, parsed, dictionaryObject, dictionary;
    -    switch (type) {
    -        case hummus.ePDFObjectIndirectObjectReference:
    -            objectID = inObject.toPDFIndirectObjectReference().getObjectID();
    -            parsed = pdfReader.parseNewObject(objectID);
    -            return this._parseObjectByType(parsed);
    -        case hummus.ePDFObjectArray:
    -            inObject.toPDFArray().toJSArray().forEach((element) => {
    -                this._parseObjectByType(element);
    -            });
    -            break;
    -        case hummus.ePDFObjectDictionary:
    -            dictionaryObject = inObject.toPDFDictionary().toJSObject();
    -            Object.getOwnPropertyNames(dictionaryObject).forEach((element) => {
    -                this._parseObjectByType(dictionaryObject[element]);
    -            });
    -            break;
    -        case hummus.ePDFObjectStream:
    -            dictionary = inObject.toPDFStream().getDictionary();
    -            return this._parseObjectByType(dictionary);
    -        default:
    -            saveToObject[`${label}-${Date.now()*Math.random()}`] = inObject;
    -            return inObject;
    -    }
    -};
    +            
    const fs = require('fs');
    +const hummus = require('hummus');
    +/**
    + * @name info
    + * @desc Add new PDF information, or retrieve existing PDF information.
    + * @memberof Recipe
    + * @function
    + * @param {Object} [options] - The options (when missing obtains existing PDF information)
    + * @param {number} [options.version] - The pdf version
    + * @param {string} [options.author] - The author
    + * @param {string} [options.title] - The title
    + * @param {string} [options.subject] - The subject
    + * @param {string[]} [options.keywords] - The array of keywords
    + */
    +exports.info = function info(options) {
    +    let result;
    +
    +    if (! options) {
    +        result = this._readInfo();
    +        
    +    } else {
    +        this.toWriteInfo_ = this.toWriteInfo_ || {};
    +        Object.assign(this.toWriteInfo_, options);
    +        result = this;
    +    }
    +
    +    return result;
    +};
    +
    +exports._readInfo = function _readInfo() {
    +    if (!this.isNewPDF && !this.infoDictionary) {
    +        const copyFrom = this.isBufferSrc ? new hummus.PDFRStreamForBuffer(this.src) : this.src;
    +        const copyCtx  = this.writer.createPDFCopyingContext(copyFrom);
    +        const infoDict = copyCtx.getSourceDocumentParser().queryDictionaryObject(
    +            copyCtx.getSourceDocumentParser().getTrailer(), 'Info'
    +        );
    +    
    +        const oldInfo = (infoDict && infoDict.toJSObject) ? infoDict.toJSObject() : null;
    +    
    +        if (oldInfo) {
    +            this.infoDictionary = {}
    +            Object.getOwnPropertyNames(oldInfo).forEach((key) => {
    +                if (!oldInfo[key]) {
    +                    return;
    +                }
    +                const oldInforSrc = this._parseObjectByType(oldInfo[key]);
    +                if (!oldInforSrc) {
    +                    return;
    +                }
    +                switch (key) {
    +                    case 'Trapped':
    +                        if (oldInforSrc && oldInforSrc.value) {
    +                            this.infoDictionary.trapped = oldInforSrc.value;
    +                        }
    +                        break;
    +                    case 'CreationDate':
    +                        if (oldInforSrc && oldInforSrc.value) {
    +                            this.infoDictionary.creationDate = oldInforSrc.value;
    +                        }
    +                        break;
    +                    case 'ModDate':
    +                        if (oldInforSrc && oldInforSrc.value) {
    +                            this.infoDictionary.modDate = oldInforSrc.value;
    +                        }
    +                        break;
    +                    case 'Creator':
    +                        if (oldInforSrc && oldInforSrc.toText) {
    +                            this.infoDictionary.creator = oldInforSrc.toText();
    +                        }
    +                        break;
    +                    case 'Producer':
    +                        if (oldInforSrc && oldInforSrc.toText) {
    +                            this.infoDictionary.producer = oldInforSrc.toText();
    +                        }
    +                        break;
    +                    default:
    +                        if (oldInforSrc && oldInforSrc.toText) {
    +                            this.infoDictionary[key.toLowerCase()] = oldInforSrc.toText();
    +                        }
    +                }
    +            });
    +        }
    +    }
    +
    +    return this.infoDictionary;
    +}
    +
    +exports._writeInfo = function _writeInfo() {
    +    const options = this.toWriteInfo_ || {};
    +    const oldInfo = this._readInfo();
    +    /*
    +        #41, #48
    +        This issue is due to the unhandled process exit from HummusJS.
    +        I have to disable this part before it gets fixed in HummusJS.
    +    */
    +    
    +    const infoDictionary = this.writer.getDocumentContext().getInfoDictionary();
    +    const fields = [{
    +        key: 'author',
    +        type: 'string'
    +    }, {
    +        key: 'title',
    +        type: 'string'
    +    }, {
    +        key: 'subject',
    +        type: 'string'
    +    }, {
    +        key: 'keywords',
    +        type: 'array'
    +    }];
    +    // const ignores = [
    +    //     'CreationDate', 'Creator', 'ModDate', 'Producer'
    +    // ];
    +
    +    if (oldInfo) {
    +        Object.getOwnPropertyNames(oldInfo).forEach((key) => {
    +            if (!oldInfo[key]) {
    +                return;
    +            }
    +
    +            switch (key) {
    +                case 'trapped':
    +                    infoDictionary.trapped = oldInfo.trapped;
    +                    break;
    +                case 'creationDate':
    +                    infoDictionary.setCreationDate(oldInfo.creationDate);
    +                    break;
    +                case 'modDate':
    +                    infoDictionary.addAdditionalInfoEntry('source-ModDate', oldInfo.modDate);
    +                    break;
    +                case 'creator':
    +                    infoDictionary.addAdditionalInfoEntry('source-Creator', oldInfo.creator);
    +                    break;
    +                case 'producer':
    +                    infoDictionary.addAdditionalInfoEntry('source-Producer', oldInfo.producer);
    +                    break;
    +                default:
    +                    infoDictionary[key] = oldInfo[key];
    +            }
    +        });
    +    }
    +
    +    if (this.isNewPDF) {
    +        infoDictionary.setCreationDate(new Date());
    +    }
    +    infoDictionary.setModDate(new Date());
    +    infoDictionary.producer = 'PDFHummus (https://github.com/galkahana/HummusJS)';
    +    infoDictionary.creator = 'Hummus-Recipe (https://github.com/chunyenHuang/hummusRecipe)';
    +
    +    fields.forEach((item) => {
    +        let value = options[item.key];
    +        if (!value) {
    +            return;
    +        } else {
    +            switch (item.type) {
    +                case 'string':
    +                    value = value.toString();
    +                    break;
    +                case 'date':
    +                    value = new Date(value);
    +                    break;
    +                case 'array':
    +                    value = (Array.isArray(value)) ? value : [value];
    +                    break;
    +                default:
    +            }
    +        }
    +        if (item.func) {
    +            infoDictionary[item.func](value);
    +        } else {
    +            infoDictionary[item.key] = value;
    +        }
    +    });
    +    return this;
    +};
    +
    +/**
    + * @name custom
    + * @desc Add custom information to pdf
    + * @memberof Recipe
    + * @function
    + * @param {string} [key] - The key
    + * @param {string} [value] - The value
    + */
    +exports.custom = function custom(key, value) {
    +    const infoDictionary = this.writer.getDocumentContext().getInfoDictionary();
    +    infoDictionary.addAdditionalInfoEntry(key.toString(), value.toString());
    +    return this;
    +};
    +
    +exports.structure = function structure(output) {
    +    // PDF file format http://lotabout.me/orgwiki/pdf.html
    +    // const outputFileType = path.extname(output);
    +    const outputFile = fs.openSync(output, 'w');
    +    const hummus = this.hummus;
    +    const pdfReader = this.pdfReader;
    +
    +    const tabWidth = '  ';
    +    const structures = [
    +        'Info',
    +        'Root', // catalog
    +        'Size',
    +        'Prev',
    +        'ID',
    +        // 'Encrypt',
    +        // 'XRefStm'
    +    ];
    +
    +    const write = (item) => {
    +        const mIteratedObjectIDs = {};
    +        let mTabLevel = 0;
    +
    +        const addTabs = () => {
    +            let output = '';
    +            for (let i = 0; i < mTabLevel; ++i) {
    +                output += tabWidth;
    +            }
    +            return output;
    +        };
    +
    +        const logToFile = (inString) => {
    +            fs.writeSync(outputFile, addTabs() + inString + '\r\n');
    +        };
    +
    +        const iterateObjectTypes = (inObject) => {
    +            const type = inObject.getType();
    +            const label = hummus.getTypeLabel(type);
    +            let output = '';
    +            let objectID, jsArray, aDictionary, keys;
    +
    +            switch (type) {
    +                case hummus.ePDFObjectIndirectObjectReference:
    +                    ++mTabLevel;
    +                    objectID = inObject.toPDFIndirectObjectReference().getObjectID();
    +                    output += `Indirect object reference (${objectID}): `;
    +                    logToFile(output);
    +                    if (!Object.prototype.hasOwnProperty.call(mIteratedObjectIDs, objectID)) {
    +                        mIteratedObjectIDs[objectID] = true;
    +                        iterateObjectTypes(pdfReader.parseNewObject(objectID));
    +                    }
    +                    for (var i = 0; i < mTabLevel; ++i) {
    +                        output += ' ';
    +                    }
    +                    --mTabLevel;
    +                    return;
    +                case hummus.ePDFObjectArray:
    +                    jsArray = inObject.toPDFArray().toJSArray();
    +                    output += `- ${label} [${jsArray.length}]`;
    +                    logToFile(output);
    +                    ++mTabLevel;
    +                    jsArray.forEach((element) => {
    +                        iterateObjectTypes(element);
    +                    });
    +                    --mTabLevel;
    +                    break;
    +                case hummus.ePDFObjectDictionary:
    +                    aDictionary = inObject.toPDFDictionary().toJSObject();
    +                    keys = Object.getOwnPropertyNames(aDictionary).join(', ');
    +                    output += `- ${label} {${keys}}`;
    +                    logToFile(output);
    +                    ++mTabLevel;
    +                    Object.getOwnPropertyNames(aDictionary).forEach((element) => {
    +                        logToFile(element + ' *');
    +                        iterateObjectTypes(aDictionary[element]);
    +                    });
    +                    --mTabLevel;
    +                    break;
    +                case hummus.ePDFObjectStream:
    +                    output += 'Stream . iterating stream dictionary:';
    +                    logToFile(output);
    +                    iterateObjectTypes(inObject.toPDFStream().getDictionary());
    +                    break;
    +                default:
    +                    output += `${tabWidth}${label}: ${inObject}`;
    +                    logToFile(output);
    +            }
    +        };
    +
    +        const itemTrailer = pdfReader.queryDictionaryObject(pdfReader.getTrailer(), item);
    +        logToFile(item);
    +        iterateObjectTypes(itemTrailer);
    +    };
    +
    +    structures.forEach((item) => {
    +        write(item);
    +    });
    +
    +    fs.closeSync(outputFile);
    +    return this;
    +};
    +
    +exports._parseObjectByType = function _parseObjectByType(inObject) {
    +    if (!inObject) {
    +        return;
    +    }
    +    const hummus = this.hummus;
    +    const pdfReader = this.pdfReader;
    +    const type = inObject.getType();
    +    const label = hummus.getTypeLabel(type);
    +    const saveToObject = this.pdfStructure || {};
    +    let objectID, parsed, dictionaryObject, dictionary;
    +    switch (type) {
    +        case hummus.ePDFObjectIndirectObjectReference:
    +            objectID = inObject.toPDFIndirectObjectReference().getObjectID();
    +            parsed = pdfReader.parseNewObject(objectID);
    +            return this._parseObjectByType(parsed);
    +        case hummus.ePDFObjectArray:
    +            inObject.toPDFArray().toJSArray().forEach((element) => {
    +                this._parseObjectByType(element);
    +            });
    +            break;
    +        case hummus.ePDFObjectDictionary:
    +            dictionaryObject = inObject.toPDFDictionary().toJSObject();
    +            Object.getOwnPropertyNames(dictionaryObject).forEach((element) => {
    +                this._parseObjectByType(dictionaryObject[element]);
    +            });
    +            break;
    +        case hummus.ePDFObjectStream:
    +            dictionary = inObject.toPDFStream().getDictionary();
    +            return this._parseObjectByType(dictionary);
    +        default:
    +            saveToObject[`${label}-${Date.now()*Math.random()}`] = inObject;
    +            return inObject;
    +    }
    +};
     
    @@ -374,7 +374,7 @@

    info.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/insertPage.js.html b/docs/insertPage.js.html index 4e59ada..66663d3 100644 --- a/docs/insertPage.js.html +++ b/docs/insertPage.js.html @@ -37,83 +37,83 @@

    insertPage.js

    -
    const hummus = require('hummus');
    -const fs = require('fs');
    -const hummusUtils = require('./utils');
    -/**
    - * Insert a page from the other pdf
    - * @name insertPage
    - * @function
    - * @memberof Recipe
    - * @param {number} afterPageNumber - The page number for insertion.
    - * @param {string} pdfSrc - The path for the other pdf
    - * @param {number} srcPageNumber - The page number to be insterted from the other pdf.
    - */
    -exports.insertPage = function insertPage(afterPageNumber, pdfSrc, srcPageNumber) {
    -    if (isNaN(afterPageNumber)) {
    -        throw new Error('The afterPageNumber is inValid.');
    -    }
    -    this.needToInsertPages = true;
    -    if (pdfSrc && srcPageNumber) {
    -        this.insertInformation = this.insertInformation || {};
    -        this.insertInformation[afterPageNumber] = this.insertInformation[afterPageNumber] || [];
    -        this.insertInformation[afterPageNumber].push({
    -            afterPageNumber,
    -            pdfSrc,
    -            srcPageNumber
    -        });
    -    }
    -    return this;
    -};
    -
    -exports._insertPages = function _insertPages() {
    -    if (!this.insertInformation) {
    -        throw new Error('No insertInformation');
    -    }
    -    const pagesForInsert = [0, ...Object.keys(this.metadata)
    -        .filter(item => !isNaN(item))
    -        .map(item => parseInt(item))
    -    ];
    -    const unlinkList = [];
    -    const tmp = this.output + '.tmp.pdf';
    -    unlinkList.push(tmp);
    -    fs.renameSync(this.output, tmp);
    -
    -    const pdfWriter = hummus.createWriter(this.output);
    -    let lastInsertedOriginal = 0;
    -    pagesForInsert.forEach((pageNumber) => {
    -        const toAppendPage = pageNumber - 1;
    -        if (toAppendPage >= 0) {
    -            const specificRanges = [
    -                [lastInsertedOriginal, toAppendPage]
    -            ];
    -            hummusUtils.appendPDFPagesFromPDFWithAnnotations(pdfWriter, tmp, {
    -                specificRanges
    -            });
    -        }
    -        lastInsertedOriginal = pageNumber;
    -
    -        const toInserts = this.insertInformation[pageNumber];
    -        if (toInserts) {
    -            toInserts.forEach((info) => {
    -                const specificRanges = [
    -                    [info.srcPageNumber - 1, info.srcPageNumber - 1]
    -                ];
    -                hummusUtils.appendPDFPagesFromPDFWithAnnotations(pdfWriter, info.pdfSrc, { specificRanges });
    -            });
    -        }
    -    });
    -    pdfWriter.end();
    -
    -    unlinkList.forEach((item) => {
    -        setTimeout(() => {
    -            if (fs.existsSync(item)) {
    -                fs.unlinkSync(item);
    -            }
    -        });
    -    });
    -    return this;
    -};
    +            
    const hummus = require('hummus');
    +const fs = require('fs');
    +const hummusUtils = require('./utils');
    +/**
    + * Insert a page from the other pdf
    + * @name insertPage
    + * @function
    + * @memberof Recipe
    + * @param {number} afterPageNumber - The page number for insertion.
    + * @param {string} pdfSrc - The path for the other pdf
    + * @param {number} srcPageNumber - The page number to be insterted from the other pdf.
    + */
    +exports.insertPage = function insertPage(afterPageNumber, pdfSrc, srcPageNumber) {
    +    if (isNaN(afterPageNumber)) {
    +        throw new Error('The afterPageNumber is inValid.');
    +    }
    +    this.needToInsertPages = true;
    +    if (pdfSrc && srcPageNumber) {
    +        this.insertInformation = this.insertInformation || {};
    +        this.insertInformation[afterPageNumber] = this.insertInformation[afterPageNumber] || [];
    +        this.insertInformation[afterPageNumber].push({
    +            afterPageNumber,
    +            pdfSrc,
    +            srcPageNumber
    +        });
    +    }
    +    return this;
    +};
    +
    +exports._insertPages = function _insertPages() {
    +    if (!this.insertInformation) {
    +        throw new Error('No insertInformation');
    +    }
    +    const pagesForInsert = [0, ...Object.keys(this.metadata)
    +        .filter(item => !isNaN(item))
    +        .map(item => parseInt(item))
    +    ];
    +    const unlinkList = [];
    +    const tmp = this.output + '.tmp.pdf';
    +    unlinkList.push(tmp);
    +    fs.renameSync(this.output, tmp);
    +
    +    const pdfWriter = hummus.createWriter(this.output);
    +    let lastInsertedOriginal = 0;
    +    pagesForInsert.forEach((pageNumber) => {
    +        const toAppendPage = pageNumber - 1;
    +        if (toAppendPage >= 0) {
    +            const specificRanges = [
    +                [lastInsertedOriginal, toAppendPage]
    +            ];
    +            hummusUtils.appendPDFPagesFromPDFWithAnnotations(pdfWriter, tmp, {
    +                specificRanges
    +            });
    +        }
    +        lastInsertedOriginal = pageNumber;
    +
    +        const toInserts = this.insertInformation[pageNumber];
    +        if (toInserts) {
    +            toInserts.forEach((info) => {
    +                const specificRanges = [
    +                    [info.srcPageNumber - 1, info.srcPageNumber - 1]
    +                ];
    +                hummusUtils.appendPDFPagesFromPDFWithAnnotations(pdfWriter, info.pdfSrc, { specificRanges });
    +            });
    +        }
    +    });
    +    pdfWriter.end();
    +
    +    unlinkList.forEach((item) => {
    +        setTimeout(() => {
    +            if (fs.existsSync(item)) {
    +                fs.unlinkSync(item);
    +            }
    +        });
    +    });
    +    return this;
    +};
     
    @@ -126,7 +126,7 @@

    insertPage.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/overlay.js.html b/docs/overlay.js.html index 81cdbb0..1027488 100644 --- a/docs/overlay.js.html +++ b/docs/overlay.js.html @@ -37,78 +37,78 @@

    overlay.js

    -
    const xObjectForm = require('./xObjectForm');
    -
    -/**
    - * Overlay a pdf to the current pdf
    - * @name overlay
    - * @function
    - * @memberof Recipe
    - * @param {string} pdfSrc - The path for the overlay pdf
    - * @param {number} x - The coordinate x
    - * @param {number} y - The coordinate y
    - * @param {number} [options.scale] - Scale the overlay pdf, default is 1
    - * @param {boolean} [options.keepAspectRatio] - To keep the aspect ratio when scaling, default is true
    - * @param {boolean} [options.fitWidth] - To set the width to 100% (use with keepAspectRatio=true)
    - * @param {boolean} [options.fitHeight] - To set the height to 100% (use with keepAspectRatio=true)
    - */
    -exports.overlay = function overlay(pdfSrc, x = 0, y = 0, options = {}) {
    -    // allow to have only 2 arguments input
    -    if (arguments.length == 2) {
    -        options = x || {};
    -        x = 0;
    -        y = 0;
    -    }
    -    // const pathOptions = this._getPathOptions(options);
    -    // const gsId = this._getPathOptions(options).fillGsId;
    -
    -    const { keepAspectRatio, fitWidth, fitHeight } = options;
    -    const scale = options.scale || 1;
    -
    -    const { width: pageWidth, height: pageHeight } = this.metadata[this.pageNumber];
    -
    -    const inMetadata = this.read(pdfSrc);
    -    const page = options.page && options.page <= inMetadata.pages ? options.page : 1;
    -    // Overlays page based on page number given in options, Useful for overlaying template PDF documents
    -    const { width, height } = inMetadata[page];
    -
    -    this.pauseContext();
    -    const xObject = new xObjectForm(this.writer, width, height);
    -    xObject.getContentContext()
    -        .q()
    -        .drawImage(0, 0, pdfSrc, { index: page-1 })
    -        .Q();
    -    xObject.end();
    -    this.resumeContext();
    -
    -    const context = this.pageContext;
    -    let scaleX = 1 * scale;
    -    let scaleY = 1 * scale;
    -
    -    if (fitWidth) {
    -        scaleX = pageWidth / width;
    -        if (keepAspectRatio) {
    -            scaleY = scaleX;
    -        }
    -    }
    -    if (fitHeight) {
    -        scaleY = pageHeight / height;
    -        if (keepAspectRatio) {
    -            scaleX = scaleY;
    -        }
    -    }
    -
    -    const posX = x;
    -    const posY = pageHeight - height * scaleY - y;
    -
    -    context
    -        .q()
    -        .cm(scaleX, 0, 0, scaleY, posX, posY)
    -        .doXObject(xObject)
    -        .Q();
    -
    -    return this;
    -};
    +            
    const xObjectForm = require('./xObjectForm');
    +
    +/**
    + * Overlay a pdf to the current pdf
    + * @name overlay
    + * @function
    + * @memberof Recipe
    + * @param {string} pdfSrc - The path for the overlay pdf
    + * @param {number} x - The coordinate x
    + * @param {number} y - The coordinate y
    + * @param {number} [options.scale] - Scale the overlay pdf, default is 1
    + * @param {boolean} [options.keepAspectRatio] - To keep the aspect ratio when scaling, default is true
    + * @param {boolean} [options.fitWidth] - To set the width to 100% (use with keepAspectRatio=true)
    + * @param {boolean} [options.fitHeight] - To set the height to 100% (use with keepAspectRatio=true)
    + */
    +exports.overlay = function overlay(pdfSrc, x = 0, y = 0, options = {}) {
    +    // allow to have only 2 arguments input
    +    if (arguments.length == 2) {
    +        options = x || {};
    +        x = 0;
    +        y = 0;
    +    }
    +    // const pathOptions = this._getPathOptions(options);
    +    // const gsId = this._getPathOptions(options).fillGsId;
    +
    +    const { keepAspectRatio, fitWidth, fitHeight } = options;
    +    const scale = options.scale || 1;
    +
    +    const { width: pageWidth, height: pageHeight } = this.metadata[this.pageNumber];
    +
    +    const inMetadata = this.read(pdfSrc);
    +    const page = options.page && options.page <= inMetadata.pages ? options.page : 1;
    +    // Overlays page based on page number given in options, Useful for overlaying template PDF documents
    +    const { width, height } = inMetadata[page];
    +
    +    this.pauseContext();
    +    const xObject = new xObjectForm(this.writer, width, height);
    +    xObject.getContentContext()
    +        .q()
    +        .drawImage(0, 0, pdfSrc, { index: page-1 })
    +        .Q();
    +    xObject.end();
    +    this.resumeContext();
    +
    +    const context = this.pageContext;
    +    let scaleX = 1 * scale;
    +    let scaleY = 1 * scale;
    +
    +    if (fitWidth) {
    +        scaleX = pageWidth / width;
    +        if (keepAspectRatio) {
    +            scaleY = scaleX;
    +        }
    +    }
    +    if (fitHeight) {
    +        scaleY = pageHeight / height;
    +        if (keepAspectRatio) {
    +            scaleX = scaleY;
    +        }
    +    }
    +
    +    const posX = x;
    +    const posY = pageHeight - height * scaleY - y;
    +
    +    context
    +        .q()
    +        .cm(scaleX, 0, 0, scaleY, posX, posY)
    +        .doXObject(xObject)
    +        .Q();
    +
    +    return this;
    +};
     
    @@ -121,7 +121,7 @@

    overlay.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/page.js.html b/docs/page.js.html index 7fb9f3d..2bfabd4 100644 --- a/docs/page.js.html +++ b/docs/page.js.html @@ -37,205 +37,205 @@

    page.js

    -
    const hummus = require('hummus');
    -
    -/**
    - * Create a new page
    - * @name createPage
    - * @function
    - * @memberof Recipe
    - * @param {number} pageWidth - The page width.
    - * @param {number} pageHeight - The page height.
    - */
    -exports.createPage = function createPage(pageWidth, pageHeight) {
    -    if (!pageWidth && !pageHeight) {
    -        pageWidth = pageWidth || this.default.pageWidth;
    -        pageHeight = pageHeight || this.default.pageHeight;
    -    } else
    -    if (pageWidth && !isNaN(pageWidth) && pageHeight && !isNaN(pageHeight)) {
    -        pageWidth = pageWidth || this.default.pageWidth;
    -        pageHeight = pageHeight || this.default.pageHeight;
    -    } else
    -    if (pageWidth && typeof (pageWidth) == 'string') {
    -        // const type = pageWidth;
    -        const rotate = pageHeight;
    -        let pageType = this.default.paperSizeTypes[pageWidth];
    -
    -        if (pageType) {
    -            pageWidth = pageType.pageWidth;
    -            pageHeight = pageType.pageHeight;
    -        } else {
    -            // use default
    -            pageWidth = this.default.pageWidth;
    -            pageHeight = this.default.pageHeight;
    -        }
    -        if (rotate && !isNaN(rotate)) {
    -            if (rotate % 180 != 0) {
    -                let temp = pageHeight;
    -                pageHeight = pageWidth;
    -                pageWidth = temp;
    -            }
    -        }
    -    }
    -    // from 0
    -    this.metadata.pageCount += 1;
    -    const pageNumber = this.metadata.pageCount;
    -    const dimensions = [0, 0, pageWidth, pageHeight];
    -    const layout = (pageWidth > pageHeight) ? 'landscape' : 'portrait';
    -    this.metadata[pageNumber] = {
    -        pageNumber,
    -        mediaBox: dimensions,
    -        layout,
    -        rotate: 0,
    -        width: pageWidth,
    -        height: pageHeight
    -    };
    -
    -    const page = this.writer.createPage();
    -    page.mediaBox = [0, 0, pageWidth, pageHeight];
    -
    -    this.page = page;
    -    this.pageNumber = pageNumber;
    -    this.pageContext = this.writer.startPageContentContext(this.page);
    -    this.editingPage = false;
    -
    -    this.moveTo(0, 0);
    -    return this;
    -};
    -
    -/**
    - * Finish a page
    - * @name endPage
    - * @function
    - * @memberof Recipe
    - */
    -exports.endPage = function endPage() {
    -    if (!this.page) {
    -        return this;
    -    }
    -
    -    if (this.page.endContext) {
    -        this.page.endContext();
    -        this.page.writePage();
    -    } else {
    -        this.writer.writePage(this.page);
    -    }
    -    // this.page = null;
    -    // this.pageContext = null;
    -    // this.pageNumber = 0;
    -
    -    return this;
    -};
    -
    -/**
    - * Start editing a page
    - * @name editPage
    - * @function
    - * @memberof Recipe
    - * @param {number} pageNumber - The page number to be edited.
    - */
    -exports.editPage = function editPage(pageNumber) {
    -    const pdfWriter = this.writer;
    -    const pageIndex = pageNumber - 1;
    -    const pageModifier = new hummus.PDFPageModifier(pdfWriter, pageIndex, true);
    -    this.page = pageModifier;
    -    this.pageNumber = pageNumber;
    -    this.pageContext = pageModifier.startContext().getContext();
    -    this.editingPage = true;
    -
    -    this._resumePageRotation(pageNumber);
    -
    -    if (this.debug) {
    -        const context = this.pageContext;
    -        const {
    -            width,
    -            height,
    -            mediaBox
    -        } = this.metadata[pageNumber];
    -        const startX = mediaBox[0];
    -        const startY = mediaBox[1];
    -        const textOptions = {
    -            font: this.writer.getFontForFile(this.fonts['helvetica-bold']),
    -            size: 50,
    -            colorspace: 'gray',
    -            color: 0x00
    -        };
    -        context.writeText(`[${startX}, ${startY}] is HERE`, startX, startY, textOptions);
    -        context.writeText(`[${startX}, width/2] is HERE`, startX, width / 2, textOptions);
    -        context.writeText(`[${startX}, height/2] is HERE`, startX, height / 2, textOptions);
    -        context.writeText(`[width/2, ${startY}] is HERE`, width / 2, startY, textOptions);
    -        context.writeText(`[height/2, ${startY}] is HERE`, height / 2, startY, textOptions);
    -    }
    -    return this;
    -};
    -
    -exports._resumePageRotation = function _resumePageRotation(pageNumber, context) {
    -    pageNumber = pageNumber || this.pageNumber;
    -    const {
    -        // layout,
    -        rotate,
    -        width,
    -        height,
    -        mediaBox
    -    } = this.metadata[pageNumber];
    -    context = context || this.pageContext;
    -    const startX = mediaBox[0];
    -    const startY = mediaBox[1];
    -
    -    switch (rotate) {
    -        case 90:
    -            context.cm(0, 1, -1, 0, height - startX, startY);
    -            break;
    -        case 180:
    -            context.cm(-1, 0, 0, -1, width, height);
    -            break;
    -        case 270:
    -            context.cm(0, -1, 1, 0, startX, width - startY);
    -            break;
    -
    -        default:
    -    }
    -    return this;
    -};
    -
    -/**
    - * Get page information
    - * @name pageInfo
    - * @function
    - * @memberof Recipe
    - * @param {number} pageNumber - The page number.
    - */
    -exports.pageInfo = function pageInfo(pageNumber) {
    -    const pageInfo = this.metadata[pageNumber];
    -    return {
    -        width: pageInfo.width,
    -        height: pageInfo.height,
    -        rotate: pageInfo.rotate,
    -        pageNumber
    -    };
    -};
    -
    -exports.pauseContext = function pauseContext() {
    -    if (this.page && this.page.endContext) {
    -        this.page.endContext();
    -        // this.writer.pausePageContentContext(this.pageContext);
    -    } else
    -    if (this.pageContext) {
    -        this.writer.pausePageContentContext(this.pageContext);
    -    }
    -};
    -
    -exports.resumeContext = function resumeContext() {
    -    if (!this.isNewPDF && this.page) {
    -        this.pageContext = this.page.startContext().getContext();
    -        this._resumePageRotation();
    -    }
    -};
    -
    -exports.getPageInfo = function getPageInfo() {
    -    const info = this.writer.getDocumentContext().getInfoDictionary();
    -    return info;
    -};
    +            
    const hummus = require('hummus');
    +
    +/**
    + * Create a new page
    + * @name createPage
    + * @function
    + * @memberof Recipe
    + * @param {number} pageWidth - The page width.
    + * @param {number} pageHeight - The page height.
    + */
    +exports.createPage = function createPage(pageWidth, pageHeight) {
    +    if (!pageWidth && !pageHeight) {
    +        pageWidth = pageWidth || this.default.pageWidth;
    +        pageHeight = pageHeight || this.default.pageHeight;
    +    } else
    +    if (pageWidth && !isNaN(pageWidth) && pageHeight && !isNaN(pageHeight)) {
    +        pageWidth = pageWidth || this.default.pageWidth;
    +        pageHeight = pageHeight || this.default.pageHeight;
    +    } else
    +    if (pageWidth && typeof (pageWidth) == 'string') {
    +        // const type = pageWidth;
    +        const rotate = pageHeight;
    +        let pageType = this.default.paperSizeTypes[pageWidth];
    +
    +        if (pageType) {
    +            pageWidth = pageType.pageWidth;
    +            pageHeight = pageType.pageHeight;
    +        } else {
    +            // use default
    +            pageWidth = this.default.pageWidth;
    +            pageHeight = this.default.pageHeight;
    +        }
    +        if (rotate && !isNaN(rotate)) {
    +            if (rotate % 180 != 0) {
    +                let temp = pageHeight;
    +                pageHeight = pageWidth;
    +                pageWidth = temp;
    +            }
    +        }
    +    }
    +    // from 0
    +    this.metadata.pageCount += 1;
    +    const pageNumber = this.metadata.pageCount;
    +    const dimensions = [0, 0, pageWidth, pageHeight];
    +    const layout = (pageWidth > pageHeight) ? 'landscape' : 'portrait';
    +    this.metadata[pageNumber] = {
    +        pageNumber,
    +        mediaBox: dimensions,
    +        layout,
    +        rotate: 0,
    +        width: pageWidth,
    +        height: pageHeight
    +    };
    +
    +    const page = this.writer.createPage();
    +    page.mediaBox = [0, 0, pageWidth, pageHeight];
    +
    +    this.page = page;
    +    this.pageNumber = pageNumber;
    +    this.pageContext = this.writer.startPageContentContext(this.page);
    +    this.editingPage = false;
    +
    +    this.moveTo(0, 0);
    +    return this;
    +};
    +
    +/**
    + * Finish a page
    + * @name endPage
    + * @function
    + * @memberof Recipe
    + */
    +exports.endPage = function endPage() {
    +    if (!this.page) {
    +        return this;
    +    }
    +
    +    if (this.page.endContext) {
    +        this.page.endContext();
    +        this.page.writePage();
    +    } else {
    +        this.writer.writePage(this.page);
    +    }
    +    // this.page = null;
    +    // this.pageContext = null;
    +    // this.pageNumber = 0;
    +
    +    return this;
    +};
    +
    +/**
    + * Start editing a page
    + * @name editPage
    + * @function
    + * @memberof Recipe
    + * @param {number} pageNumber - The page number to be edited.
    + */
    +exports.editPage = function editPage(pageNumber) {
    +    const pdfWriter = this.writer;
    +    const pageIndex = pageNumber - 1;
    +    const pageModifier = new hummus.PDFPageModifier(pdfWriter, pageIndex, true);
    +    this.page = pageModifier;
    +    this.pageNumber = pageNumber;
    +    this.pageContext = pageModifier.startContext().getContext();
    +    this.editingPage = true;
    +
    +    this._resumePageRotation(pageNumber);
    +
    +    if (this.debug) {
    +        const context = this.pageContext;
    +        const {
    +            width,
    +            height,
    +            mediaBox
    +        } = this.metadata[pageNumber];
    +        const startX = mediaBox[0];
    +        const startY = mediaBox[1];
    +        const textOptions = {
    +            font: this.writer.getFontForFile(this.fonts['helvetica-bold']),
    +            size: 50,
    +            colorspace: 'gray',
    +            color: 0x00
    +        };
    +        context.writeText(`[${startX}, ${startY}] is HERE`, startX, startY, textOptions);
    +        context.writeText(`[${startX}, width/2] is HERE`, startX, width / 2, textOptions);
    +        context.writeText(`[${startX}, height/2] is HERE`, startX, height / 2, textOptions);
    +        context.writeText(`[width/2, ${startY}] is HERE`, width / 2, startY, textOptions);
    +        context.writeText(`[height/2, ${startY}] is HERE`, height / 2, startY, textOptions);
    +    }
    +    return this;
    +};
    +
    +exports._resumePageRotation = function _resumePageRotation(pageNumber, context) {
    +    pageNumber = pageNumber || this.pageNumber;
    +    const {
    +        // layout,
    +        rotate,
    +        width,
    +        height,
    +        mediaBox
    +    } = this.metadata[pageNumber];
    +    context = context || this.pageContext;
    +    const startX = mediaBox[0];
    +    const startY = mediaBox[1];
    +
    +    switch (rotate) {
    +        case 90:
    +            context.cm(0, 1, -1, 0, height - startX, startY);
    +            break;
    +        case 180:
    +            context.cm(-1, 0, 0, -1, width, height);
    +            break;
    +        case 270:
    +            context.cm(0, -1, 1, 0, startX, width - startY);
    +            break;
    +
    +        default:
    +    }
    +    return this;
    +};
    +
    +/**
    + * Get page information
    + * @name pageInfo
    + * @function
    + * @memberof Recipe
    + * @param {number} pageNumber - The page number.
    + */
    +exports.pageInfo = function pageInfo(pageNumber) {
    +    const pageInfo = this.metadata[pageNumber];
    +    return {
    +        width: pageInfo.width,
    +        height: pageInfo.height,
    +        rotate: pageInfo.rotate,
    +        pageNumber
    +    };
    +};
    +
    +exports.pauseContext = function pauseContext() {
    +    if (this.page && this.page.endContext) {
    +        this.page.endContext();
    +        // this.writer.pausePageContentContext(this.pageContext);
    +    } else
    +    if (this.pageContext) {
    +        this.writer.pausePageContentContext(this.pageContext);
    +    }
    +};
    +
    +exports.resumeContext = function resumeContext() {
    +    if (!this.isNewPDF && this.page) {
    +        this.pageContext = this.page.startContext().getContext();
    +        this._resumePageRotation();
    +    }
    +};
    +
    +exports.getPageInfo = function getPageInfo() {
    +    const info = this.writer.getDocumentContext().getInfoDictionary();
    +    return info;
    +};
     
    @@ -248,7 +248,7 @@

    page.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/split.js.html b/docs/split.js.html index 5c20fe9..6e56fdd 100644 --- a/docs/split.js.html +++ b/docs/split.js.html @@ -37,28 +37,28 @@

    split.js

    -
    const hummus = require('hummus');
    -const path = require('path');
    -const hummusUtils = require('./utils');
    -
    -/**
    - * Split the pdf
    - * @name split
    - * @function
    - * @memberof Recipe
    - * @param {string} outputDir - The path for the output pdfs.
    - * @param {string} prefix - `${prefix}-${i+1}.pdf`.
    - */
    -exports.split = function split(outputDir = '', prefix) {
    -    prefix = prefix || this.filename;
    -    for (let i = 0; i < this.metadata.pages; i++) {
    -        const newPdf = path.join(outputDir, `${prefix}-${i+1}.pdf`);
    -        const pdfWriter = hummus.createWriter(newPdf);
    -        hummusUtils.appendPDFPageFromPDFWithAnnotations(pdfWriter, this.pdfReader, i);
    -        pdfWriter.end();
    -    }
    -    return this;
    -};
    +            
    const hummus = require('hummus');
    +const path = require('path');
    +const hummusUtils = require('./utils');
    +
    +/**
    + * Split the pdf
    + * @name split
    + * @function
    + * @memberof Recipe
    + * @param {string} outputDir - The path for the output pdfs.
    + * @param {string} prefix - `${prefix}-${i+1}.pdf`.
    + */
    +exports.split = function split(outputDir = '', prefix) {
    +    prefix = prefix || this.filename;
    +    for (let i = 0; i < this.metadata.pages; i++) {
    +        const newPdf = path.join(outputDir, `${prefix}-${i+1}.pdf`);
    +        const pdfWriter = hummus.createWriter(newPdf);
    +        hummusUtils.appendPDFPageFromPDFWithAnnotations(pdfWriter, this.pdfReader, i);
    +        pdfWriter.end();
    +    }
    +    return this;
    +};
     
    @@ -71,7 +71,7 @@

    split.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/text.helper.js.html b/docs/text.helper.js.html index 97fdadb..f93cdb2 100644 --- a/docs/text.helper.js.html +++ b/docs/text.helper.js.html @@ -37,177 +37,144 @@

    text.helper.js

    -
    exports.Word = class Word {
    -    constructor(word, pathOptions) {
    -        this._value = word;
    -        this._pathOptions = pathOptions;
    -    }
    -
    -    get value() {
    -        return this._value;
    -    }
    -
    -    get dimensions() {
    -        if (this._dimensions) {
    -            return this._dimensions;
    -        }
    -        this._dimensions = this._pathOptions.font.calculateTextDimensions(
    -            this._value, this._pathOptions.size
    -        );
    -        return this._dimensions;
    -    }
    -};
    -
    -exports.Line = class Line {
    -    constructor(width, height, size, pathOptions) {
    -        this._width = width || 999999999;
    -        this._height = height;
    -        this._pathOptions = pathOptions;
    -        this.size = size || pathOptions.size;
    -        this.wordObjects = [];
    -    }
    -
    -    addWord(wordObject) {
    -        this.wordObjects.push(wordObject);
    -    }
    -
    -    canFit(wordObject) {
    -        const tempValue = this.value + wordObject.value;
    -        const toWidth = this._pathOptions.font.calculateTextDimensions(
    -            tempValue, this.size
    -        ).width;
    -        return (toWidth <= this.width);
    -    }
    -
    -    replaceLastWord(wordObject) {
    -        this.wordObjects.pop();
    -        this.addWord(wordObject);
    -    }
    -
    -    get spaceWidth() {
    -        return this._pathOptions.font.calculateTextDimensions(
    -            'o', this.size
    -        ).width;
    -    }
    -
    -    get value() {
    -        const value = this.wordObjects.reduce((string, word) => {
    -            string += word.value;
    -            return string;
    -        }, '');
    -        return value;
    -    }
    -
    -    get currentWidth() {
    -        return this._pathOptions.font.calculateTextDimensions(
    -            this.value, this.size
    -        ).xMax;
    -    }
    -
    -    get textWidth() {
    -        return this.wordObjects.reduce((width, word) => {
    -            width += word.dimensions.width;
    -            return width;
    -        }, 0);
    -    }
    -
    -    get width() {
    -        return this._width;
    -    }
    -
    -    // dynamic adjust height based on word height?
    -    get height() {
    -        if (this._height) {
    -            return this._height;
    -        }
    -        const toHeight = this._pathOptions.font.calculateTextDimensions(
    -            this.value, this.size
    -        ).height; // ymax
    -        return toHeight + 20;
    -    }
    -};
    -
    -exports._getTextOffset = function _getTextOffset(text = '', options = {}) {
    -    if (!options.size || !options.font) {
    -        return;
    -    }
    -    let offsetX = 0;
    -    let offsetY = 0;
    -    const textDimensions = options.font.calculateTextDimensions(text, options.size);
    -    if (options.align) {
    -        const alignments = options.align.split(' ');
    -        if (alignments[0]) {
    -            switch (alignments[0]) {
    -                case 'center':
    -                    offsetX = -1 * textDimensions.width / 2;
    -                    break;
    -                case 'right':
    -                    offsetX = textDimensions.width / 2;
    -                    break;
    -                default:
    -            }
    -        }
    -        if (alignments[1]) {
    -            switch (alignments[1]) {
    -                case 'center':
    -                    offsetY = -1 * textDimensions.yMax / 2;
    -                    break;
    -                case 'bottom':
    -                    offsetY = textDimensions.yMax / 2;
    -                    break;
    -                default:
    -            }
    -        }
    -    }
    -    return {
    -        offsetX,
    -        offsetY
    -    };
    -};
    -
    -/**
    - * @todo handle page margin and padding
    - */
    -exports._getTextBoxOffset = function _getTextBoxOffset(textBox, options = {}) {
    -    let offsetX = 0;
    -    let offsetY = -textBox.firstLineHeight;
    -    const {
    -        width,
    -        height
    -    } = textBox;
    -    if (options.align) {
    -        const alignments = options.align.split(' ');
    -        if (alignments[0]) {
    -            switch (alignments[0]) {
    -                case 'center':
    -                    offsetX = -1 * width / 2;
    -                    break;
    -                case 'right':
    -                    offsetX = -width;
    -                    break;
    -                default:
    -            }
    -        }
    -        if (alignments[1]) {
    -            switch (alignments[1]) {
    -                case 'center':
    -                    offsetY = (textBox.isSimpleText) ?
    -                        -textBox.firstLineHeight / 2 :
    -                        height / 2 + offsetY;
    -                    break;
    -                case 'bottom':
    -                    offsetY = height + offsetY;
    -                    break;
    -                default:
    -            }
    -        }
    -    }
    -
    -    return {
    -        offsetX,
    -        offsetY
    -    };
    -};
    +            
    exports.Word = class Word {
    +    constructor(word, pathOptions) {
    +        this._value = word;
    +        this._pathOptions = pathOptions;
    +        this._text = (word === ' ') ? 'o' : word;  // allows space to get an actual dimension
    +    }
    +
    +    get value() {
    +        return this._value;
    +    }
    +
    +    get dimensions() {
    +        if (this._dimensions) {
    +            return this._dimensions;
    +        }
    +        this._dimensions = this._pathOptions.font.calculateTextDimensions(
    +            this._text, this._pathOptions.size
    +        );
    +        return this._dimensions;
    +    }
    +};
    +
    +exports.Line = class Line {
    +    constructor(width, height, size, pathOptions) {
    +        this._width = width || 999999999;
    +        this._height = height;
    +        this._pathOptions = pathOptions;
    +        this.size = size || pathOptions.size;
    +        this.wordObjects = [];
    +    }
    +
    +    addWord(wordObject) {
    +        this.wordObjects.push(wordObject);
    +    }
    +
    +    canFit(wordObject) {
    +        const tempValue = this.value + wordObject.value;
    +        const toWidth = this._pathOptions.font.calculateTextDimensions(
    +            tempValue, this.size
    +        ).xMax;
    +        return (toWidth <= this.width);
    +    }
    +
    +    replaceLastWord(wordObject) {
    +        this.wordObjects.pop();
    +        this.addWord(wordObject);
    +    }
    +
    +    get words() {
    +        return this.wordObjects;
    +    }
    +
    +    get spaceWidth() {
    +        return this._pathOptions.font.calculateTextDimensions(
    +            'o', this.size
    +        ).width;
    +    }
    +
    +    get value() {
    +        const value = this.wordObjects.reduce((string, word) => {
    +            string += word.value;
    +            return string;
    +        }, '');
    +        return value;
    +    }
    +
    +    get currentWidth() {
    +        return this._pathOptions.font.calculateTextDimensions(
    +            this.value, this.size
    +        ).xMax;
    +    }
    +
    +    get textWidth() {
    +        return this.wordObjects.reduce((width, word) => {
    +            width += word.dimensions.xMax;
    +            return width;
    +        }, 0);
    +    }
    +
    +    get width() {
    +        return this._width;
    +    }
    +
    +    // dynamic adjust height based on word height?
    +    get height() {
    +        if (this._height) {
    +            return this._height;
    +        }
    +        const toHeight = this._pathOptions.font.calculateTextDimensions(
    +            this.value, this.size
    +        ).height; // ymax
    +        return toHeight + 20;
    +    }
    +};
    +
    +/**
    + * @todo handle page margin and padding
    + */
    +exports._getTextBoxOffset = function _getTextBoxOffset(textBox, options = {}) {
    +    let offsetX = 0;
    +    let offsetY = -textBox.firstLineHeight;
    +    const {
    +        width,
    +        height
    +    } = textBox;
    +    if (options.align) {
    +        const alignments = options.align.split(' ');
    +        if (alignments[0]) {
    +            switch (alignments[0]) {
    +                case 'center':
    +                    offsetX = -1 * width / 2;
    +                    break;
    +                case 'right':
    +                    offsetX = -width;
    +                    break;
    +                default:
    +            }
    +        }
    +        if (alignments[1]) {
    +            switch (alignments[1]) {
    +                case 'center':
    +                    offsetY = (textBox.isSimpleText) ?
    +                        -textBox.firstLineHeight / 2 :
    +                        height / 2 + offsetY;
    +                    break;
    +                case 'bottom':
    +                    offsetY = height + offsetY;
    +                    break;
    +                default:
    +            }
    +        }
    +    }
    +
    +    return {
    +        offsetX,
    +        offsetY
    +    };
    +};
     
    @@ -220,7 +187,7 @@

    text.helper.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/text.js.html b/docs/text.js.html index 946c0b4..9e4c714 100644 --- a/docs/text.js.html +++ b/docs/text.js.html @@ -37,463 +37,586 @@

    text.js

    -
    const LineBreaker = require('linebreak');
    -const {
    -    Word,
    -    Line
    -} = require('./text.helper');
    -const {
    -    htmlToTextObjects
    -} = require('./htmlToTextObjects');
    -const xObjectForm = require('./xObjectForm');
    -
    -//  Table indicating how to specify coloration of elements
    -//  -------------------------------------------------------------------
    -// |Color | HexColor   | DecimalColor                   | PercentColor |
    -// |Space | (string)   | (array)                        | (string)     |
    -// |------+------------+--------------------------------+--------------|
    -// | Gray | #GG        | [gray]                         | %G           |
    -// |  RGB | #rrggbb    | [red, green, blue]             | %G           |
    -// | CMYK | #ccmmyykk  | [cyan, magenta, yellow, black] | %c,m,y,k     |
    -//  -------------------------------------------------------------------
    -//
    -//   HexColor component values (two hex digits) range from 00 to FF.
    -//   DecimalColor component values range from 0 to 255.
    -//   PercentColor component values range from 1 to 100.
    -
    -/**
    - * Write text elements
    - * @name text
    - * @function
    - * @todo support break words
    - * @memberof Recipe
    - * @param {string} text - The text content
    - * @param {number} x - The coordinate x
    - * @param {number} y - The coordinate y
    - * @param {Object} [options] - The options
    - * @param {string|number[]} [options.color] - Text color (HexColor, PercentColor or DecimalColor)
    - * @param {number} [options.opacity] - opacity
    - * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
    - * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: the text coordinate
    - * @param {string} [options.font] - The font. 'Arial', 'Helvetica'...
    - * @param {number} [options.size] - The line width
    - * @param {string} [options.align] - The alignment. 'center center'...
    - * @param {Object|Boolean} [options.highlight] - Text markup annotation.
    - * @param {Object|Boolean} [options.underline] - Text markup annotation.
    - * @param {Object|Boolean} [options.strikeOut] - Text markup annotation.
    - * @param {Object} [options.textBox] - Text Box to fit in.
    - * @param {number} [options.textBox.width] - Text Box width
    - * @param {number} [options.textBox.height] - Text Box fixed height
    - * @param {number} [options.textBox.minHeight] - Text Box minimum height
    - * @param {number|number[]} [options.textBox.padding] - Text Box padding, [top, right, bottom, left]
    - * @param {number} [options.textBox.lineHeight] - Text Box line height
    - * @param {string} [options.textBox.textAlign] - Text alignment inside text box
    - * @param {Object} [options.textBox.style] - Text Box styles
    - * @param {number} [options.textBox.style.lineWidth] - Text Box border width
    - * @param {string|number[]} [options.textBox.style.stroke] - Text Box border color  (HexColor, PercentColor or DecimalColor)
    - * @param {number[]} [options.textBox.style.dash] - Text Box border border dash style [number, number]
    - * @param {string|number[]} [options.textBox.style.fill] - Text Box border background color (HexColor, PercentColor or DecimalColor)
    - * @param {number} [options.textBox.style.opacity] - Text Box border background opacity
    - */
    -exports.text = function text(text = '', x, y, options = {}) {
    -    if (!this.pageContext) {
    -        return this;
    -    }
    -    const targetAnnotations = options;
    -    const originCoord = this._calibrateCoordinate(x, y, 0, 0, this.pageNumber);
    -    const pathOptions = this._getPathOptions(options, originCoord.nx, originCoord.ny);
    -    pathOptions.html = options.html;
    -    // const { width: pageWidth, height: pageHeight } = this.metadata[this.pageNumber];
    -
    -    const textObjects = (options.html) ? htmlToTextObjects(text) : [{
    -        value: text,
    -        tag: null,
    -        isBold: options.bold,
    -        isItalic: options.italic,
    -        attributes: [],
    -        styles: {},
    -        needsLineBreaker: false,
    -        size: pathOptions.size,
    -        sizeRatio: 1,
    -        sizeRatios: [1],
    -        link: null,
    -        childs: []
    -    }];
    -    const textBox = (!options.textBox) ? {
    -        // use page width with padding and margin
    -        isSimpleText: true,
    -        width: null,
    -        lineHeight: 0,
    -        padding: 0,
    -        minHeight: 0
    -    } : {
    -        width: options.textBox.width || 100,
    -        lineHeight: options.textBox.lineHeight,
    -        height: options.textBox.height,
    -        padding: (options.textBox.padding || 0),
    -        minHeight: options.textBox.minHeight || 0,
    -        style: options.textBox.style,
    -        textAlign: options.textBox.textAlign
    -    };
    -
    -    textBox.padding = (Array.isArray(textBox.padding)) ? textBox.padding : [textBox.padding];
    -    Object.assign(textBox, {
    -        paddingTop: textBox.padding[0],
    -        paddingRight: textBox.padding[1] || textBox.padding[0],
    -        paddingBottom: textBox.padding[2] || textBox.padding[0],
    -        paddingLeft: textBox.padding[2] || textBox.padding[1] || textBox.padding[0],
    -    });
    -    let firstLineHeight;
    -    // let lastLineHeight;
    -
    -    let totalHeight = 0;
    -    let toWriteTextObjects = [];
    -
    -    // const layerIndex = {};
    -
    -    const writeValue = (textObject) => {
    -        textObject.lineID = textObject.lineID || Date.now() * Math.random();
    -        textObject.lineID = (textObject.needsLineBreaker) ? Date.now() * Math.random() : textObject.lineID;
    -        if (textObject.value) {
    -            textObject.styles.color = (textObject.styles.color) ?
    -                this._transformColor(textObject.styles.color) : pathOptions.color;
    -            const {
    -                toWriteTextObjects: newToWriteObjects,
    -                paragraphHeight
    -            } = getToWriteTextObjects(textObject, pathOptions, textBox);
    -            toWriteTextObjects = [...toWriteTextObjects, ...newToWriteObjects];
    -            if (!firstLineHeight) {
    -                firstLineHeight = toWriteTextObjects[0].lineHeight;
    -            }
    -            totalHeight += paragraphHeight; // textObject.size;
    -        }
    -        if (textObject.tag && textObject.childs.length) {
    -            // console.log(textObject);
    -            if (!textObject.size) {
    -                textObject.size = pathOptions.size * textObject.sizeRatio;
    -                textObject.sizeRatios = [textObject.sizeRatio];
    -            }
    -            textObject.layer = textObject.layer || 0;
    -            textObject.layer++;
    -
    -            textObject.currentIndex = 0;
    -
    -            textObject.childs.forEach((child) => {
    -                if (textObject.tag == 'ul') {
    -                    child.prependValue = '* ';
    -                    child.layer = textObject.layer + 1;
    -                    // child.indent = 4 * child.layer;
    -                }
    -                if (textObject.tag == 'ol') {
    -                    if (child.tag != 'ol') {
    -                        textObject.currentIndex++;
    -                        child.prependValue = `${(textObject.currentIndex).toString()}. `;
    -                    }
    -                    child.layer = textObject.layer + 1;
    -                    // child.indent = 4 * child.layer;
    -                }
    -                if (textObject.tag == 'li') {
    -                    if (child.tag == 'ol' || child.tag == 'ul') {
    -                        child.layer = textObject.layer - 1;
    -                    }
    -                }
    -                if (textObject.prependValue) {
    -                    child.prependValue = (!['ol', 'ul'].includes(textObject.tag)) ?
    -                        textObject.prependValue : child.prependValue;
    -                    textObject.indent = 2 * textObject.layer;
    -                }
    -                if (textObject.indent) {
    -                    child.indent = child.indent || textObject.indent;
    -                }
    -                if (textObject.size) {
    -                    child.size = textObject.size * child.sizeRatio;
    -                    child.sizeRatios = [...textObject.sizeRatios, child.sizeRatio];
    -                }
    -                child.styles = Object.assign(child.styles, textObject.styles);
    -
    -                child.isBold = (textObject.isBold) ? textObject.isBold : child.isBold;
    -                child.isItalic = (textObject.isItalic) ? textObject.isItalic : child.isItalic;
    -                child.underline = (textObject.underline) ? textObject.underline : child.underline;
    -
    -                child.lineID = textObject.lineID;
    -                writeValue(child);
    -            });
    -        }
    -    };
    -    textObjects.forEach((textObject) => {
    -        writeValue(textObject);
    -    });
    -
    -    const calculatedHeight = totalHeight + textBox.paddingTop + textBox.paddingBottom;
    -
    -    if (!textBox.height) {
    -        textBox.height = calculatedHeight;
    -        textBox.height = (textBox.minHeight && textBox.minHeight >= textBox.height) ? textBox.minHeight : textBox.height;
    -    }
    -    if (!textBox.width) {
    -        textBox.width = toWriteTextObjects[0].lineWidth;
    -    }
    -    textBox.firstLineHeight = firstLineHeight;
    -    textBox.lastLineHeight = toWriteTextObjects[toWriteTextObjects.length - 1].lineHeight;
    -
    -    const {
    -        offsetX,
    -        offsetY
    -    } = this._getTextBoxOffset(textBox, pathOptions);
    -    const {
    -        nx,
    -        ny
    -    } = this._calibrateCoordinate(x, y, offsetX, offsetY);
    -    if (textBox.style) {
    -        textBox.height = textBox.firstLineHeight * toWriteTextObjects.length
    -                       + textBox.paddingTop + textBox.paddingBottom;
    -        this.rectangle(
    -            nx,
    -            ny - textBox.height + textBox.baselineHeight + textBox.paddingTop + textBox.paddingBottom,
    -            textBox.width, textBox.height, Object.assign(textBox.style, {
    -                useGivenCoords: true,
    -                rotation: pathOptions.rotation,
    -                rotationOrigin: [pathOptions.originX,pathOptions.originY],
    -            })
    -        );
    -    }
    -    const context = this.pageContext;
    -    // const space = 8;
    -    let currentY = ny + textBox.paddingBottom;
    -    let currentLineID;
    -    let currentLineWidth = 0;
    -    let toWriteContents = [];
    -
    -    toWriteTextObjects.forEach((toWriteTextObject, index) => {
    -        const {
    -            text,
    -            lineHeight,
    -            lineWidth,
    -            lineID,
    -            spaceWidth
    -        } = toWriteTextObject;
    -
    -        currentLineID = currentLineID || lineID;
    -        const isContinued = (currentLineID == lineID) ? true : false;
    -
    -        const getStartX = (startX, currentWriteOptions) => {
    -            let x = startX;
    -            let offsetX = 0;
    -            if (currentWriteOptions.align == 'center') {
    -                offsetX = (textBox.width - currentLineWidth) / 2;
    -            } else
    -            if (currentWriteOptions.align == 'right') {
    -                offsetX = (textBox.width - textBox.paddingRight - currentLineWidth);
    -            } else {
    -                offsetX = textBox.paddingLeft;
    -            }
    -            return x + offsetX;
    -        };
    -
    -        const writeText = (context, x, y, wto) => {
    -            const options = wto.writeOptions;
    -            const {lineHeight, lineWidth, text} = wto;
    -
    -            // write directly to page when not dealing with opacity, rotation and special colorspace.
    -            if ( options.opacity  === 1 && options.colorspace !== 'separation' &&
    -                (options.rotation === 0 || options.rotation === undefined) ) {
    -                context.writeText(text, x, y, options);
    -            } else {
    -                // ... otherwise use xObjectForm when rotation or opacity exists
    -                this.pauseContext();
    -                // https://github.com/galkahana/HummusJS/wiki/Use-the-pdf-drawing-operators
    -                const xObject = new xObjectForm(this.writer, lineWidth * 1.5, lineHeight * 2);
    -                const xObjectCtx = xObject.getContentContext();
    -
    -                xObjectCtx
    -                    .q()
    -                    .gs(xObject.getGsName(options.fillGsId))
    -                    .BT();
    -                xObject.fill(options.colorModel);
    -                xObjectCtx
    -                    .Tf(options.font, options.size)
    -                    .Tm(1, 0, 0, 1, 1, lineHeight)
    -                    .Tj(text)
    -                    .ET()
    -                    .Q();
    -                xObject.end();
    -                this.resumeContext();
    -                this.pageContext.q();
    -
    -                options.deltaY = lineHeight;
    -                this._setRotationContext(this.pageContext, x, y, options);
    -
    -                this.pageContext
    -                    .doXObject(xObject)
    -                    .Q();
    -            }
    -
    -            const { textHeight } = options;
    -
    -            for (let key in targetAnnotations) {
    -                const subtype = this._getTextMarkupAnnotationSubtype(key);
    -                if (subtype) {
    -                    const markupOption = (typeof (targetAnnotations[key]) != 'object') ? {} : targetAnnotations[key];
    -                    Object.assign(markupOption, {
    -                        height: textHeight * 1.4,
    -                        width: currentLineWidth,
    -                        text: markupOption.text || '',
    -                        _textHeight: textHeight
    -                    });
    -                    const {
    -                        ox,
    -                        oy
    -                    } = this._reverseCoordinate(x, y - textHeight * 0.2);
    -                    this.annot(ox, oy, subtype, markupOption);
    -                }
    -            }
    -        };
    -
    -        if (!isContinued) {
    -            toWriteContents.forEach((content) => {
    -                const x = getStartX(content.startX, content.wto.writeOptions);
    -                const y = currentY;
    -                writeText(context, x, y, content.wto);
    -            });
    -            toWriteContents = [];
    -            currentLineID = lineID;
    -            currentLineWidth = 0;
    -            currentY -= lineHeight;
    -        }
    -
    -        const startX = nx + currentLineWidth;
    -        if (text != '[@@DONOT_RENDER_THIS@@]') {
    -            toWriteContents.push({
    -                startX,
    -                wto: toWriteTextObject
    -            });
    -        }
    -
    -        currentLineWidth = currentLineWidth + lineWidth + spaceWidth;
    -
    -        // Processing last line?
    -        if (index == toWriteTextObjects.length - 1) {
    -            toWriteContents.forEach((content) => {
    -                const x = getStartX(content.startX, content.wto.writeOptions);
    -                const y = currentY;
    -                writeText(context, x, y, content.wto);
    -            });
    -        }
    -    });
    -
    -    return this;
    -};
    -
    -function getToWriteTextObjects(textObject = {}, pathOptions, textBox = {}) {
    -    const toWriteTextObjects = [];
    -    let text = ((textObject.prependValue) ? textObject.prependValue : '') +
    -        textObject.value +
    -        ((textObject.appendValue) ? textObject.appendValue : '');
    -
    -    const size = textObject.size || pathOptions.size;
    -    // Use the same string to get the same height for each string with the same font.
    -    // Need lowercase 'gjpqy' so descenders are included in text height.
    -    const textDimensions = pathOptions.font.calculateTextDimensions(
    -        'ABCDEFGHIJKLMNOPQRSTUVWXYZgjpqy', size
    -    );
    -    const textHeight = textDimensions.height;
    -
    -    pathOptions.textHeight = textHeight;
    -    textBox.lineHeight = textBox.lineHeight || textHeight;
    -    textBox.baselineHeight = textDimensions.yMax;
    -
    -    const breaker = new LineBreaker(text);
    -    const lines = [];
    -    const indent = (textObject.indent || 0);
    -    // const indentWidth = indent * size;
    -    const lineMaxWidth = (textBox.width) ? textBox.width - textBox.paddingLeft - textBox.paddingRight - indent : null;
    -    let newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions);
    -    let last = 0;
    -    let bk = breaker.nextBreak();
    -    let previousWord;
    -
    -    for (let i = 0; i < indent; i++) {
    -        newLine.addWord(new Word(' '));
    -    }
    -
    -    while (bk) {
    -        let nextWord = text.slice(last, bk.position);
    -        /**
    -         * Author: silverma (Marc Silverman)
    -         * #29 Is it possible to add multi-line text?
    -         * https://github.com/chunyenHuang/hummusRecipe/issues/29
    -         */
    -        if (bk.required) {
    -            nextWord = nextWord.trim();
    -        }
    -
    -        const word = new Word(nextWord, pathOptions);
    -        if (newLine.canFit(word)) {
    -            newLine.addWord(word);
    -        } else {
    -            // remove any trailing space on previous word so right justification works appropriately
    -            if ( previousWord ) {
    -                newLine.replaceLastWord(new Word(previousWord.value.trim(), pathOptions));
    -            }
    -            lines.push(newLine);
    -            newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions);
    -            for (let i = 0; i < indent; i++) {
    -                newLine.addWord(new Word(' '));
    -            }
    -            if (textObject.prependValue) {
    -                const space = Array(textObject.prependValue.length + 1).fill(' ').join('');
    -                newLine.addWord(new Word(space));
    -            }
    -            newLine.addWord(word);
    -        }
    -
    -        /**
    -         * Author: silverma (Marc Silverman)
    -         * #29 Is it possible to add multi-line text?
    -         * https://github.com/chunyenHuang/hummusRecipe/issues/29
    -         */
    -        if (bk.required) {
    -            lines.push(newLine);
    -            newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions);
    -        }
    -        previousWord = word;
    -        last = bk.position;
    -        bk = breaker.nextBreak();
    -    }
    -    lines.push(newLine);
    -
    -    const lineHeightMargin = 0;
    -    let lineHeight = (textHeight + lineHeightMargin >= textBox.lineHeight) ? textHeight + lineHeightMargin : textBox.lineHeight;
    -    const writeOptions = Object.assign({}, pathOptions, {
    -        color: textObject.styles.color,
    -        opacity: parseFloat(textObject.styles.opacity || pathOptions.opacity || 1),
    -        underline: textObject.underline || pathOptions.underline,
    -        size: textObject.size,
    -        align: textBox.textAlign,
    -        font: (textObject.isBold && textObject.isItalic) ? pathOptions.fonts.boldItalic : (textObject.isItalic) ? pathOptions.fonts.italic : (textObject.isBold) ? pathOptions.fonts.bold : pathOptions.font
    -    });
    -    let paragraphHeight = 0;
    -    lines.forEach((line, index) => {
    -        lineHeight = (lineHeight >= line.height) ? lineHeight : line.height;
    -
    -        // Space width only gets applied for HTML elements.
    -        // Otherwise, normal text alignments (center,right) are shifted left by a space.
    -        toWriteTextObjects.push({
    -            text: line.value,
    -            lineID: (index == 0) ? textObject.lineID : Date.now() * Math.random(),
    -            // @todo: handle line height to mimic html tag
    -            lineHeight,
    -            lineWidth: line.currentWidth,
    -            spaceWidth: (pathOptions.html) ? line.spaceWidth : 0,
    -            writeOptions
    -        });
    -        paragraphHeight += lineHeight;
    -    });
    -    return {
    -        toWriteTextObjects,
    -        paragraphHeight
    -    };
    -}
    +            
    const LineBreaker = require('linebreak');
    +const {
    +    Word,
    +    Line
    +} = require('./text.helper');
    +const {
    +    htmlToTextObjects
    +} = require('./htmlToTextObjects');
    +const xObjectForm = require('./xObjectForm');
    +
    +//  Table indicating how to specify coloration of elements
    +//  -------------------------------------------------------------------
    +// |Color | HexColor   | DecimalColor                   | PercentColor |
    +// |Space | (string)   | (array)                        | (string)     |
    +// |------+------------+--------------------------------+--------------|
    +// | Gray | #GG        | [gray]                         | %G           |
    +// |  RGB | #rrggbb    | [red, green, blue]             | %G           |
    +// | CMYK | #ccmmyykk  | [cyan, magenta, yellow, black] | %c,m,y,k     |
    +//  -------------------------------------------------------------------
    +//
    +//   HexColor component values (two hex digits) range from 00 to FF.
    +//   DecimalColor component values range from 0 to 255.
    +//   PercentColor component values range from 1 to 100.
    +
    +/**
    + * Write text elements
    + * @name text
    + * @function
    + * @todo support break words
    + * @memberof Recipe
    + * @param {string} text - The text content
    + * @param {number} x - The coordinate x
    + * @param {number} y - The coordinate y
    + * @param {Object} [options] - The options
    + * @param {string|number[]} [options.color] - Text color (HexColor, PercentColor or DecimalColor)
    + * @param {number} [options.opacity=1] - opacity
    + * @param {number} [options.rotation=0] - Accept: +/- 0 through 360.
    + * @param {number[]} [options.rotationOrigin=[x,y]] - [originX, originY]
    + * @param {string} [options.font=Helvetica] - The font. 'Arial', 'Helvetica'...
    + * @param {number} [options.size=14] - The font size
    + * @param {string} [options.align='left top'] - The alignment. 'center center'...
    + * @param {Object|Boolean} [options.highlight] - Text markup annotation.
    + * @param {Object|Boolean} [options.underline] - Text markup annotation.
    + * @param {Object|Boolean} [options.strikeOut] - Text markup annotation.
    + * @param {Object} [options.textBox] - Text Box to fit in.
    + * @param {number} [options.textBox.width=100] - Text Box width
    + * @param {number} [options.textBox.height] - Text Box fixed height
    + * @param {number} [options.textBox.minHeight=0] - Text Box minimum height
    + * @param {number|number[]} [options.textBox.padding=0] - Text Box padding, [top, right, bottom, left]
    + * @param {number} [options.textBox.lineHeight=0] - Text Box line height
    + * @param {string} [options.textBox.wrap='auto'] - Text wrapping mechanism, 'auto', 'clip', 'trim'.
    + * @param {string} [options.textBox.textAlign='left top'] - Alignment inside text box, specified as 'horizontal vertical',
    + * where horizontal is one of: 'left', 'center', 'right', 'justify' and veritical is one of: 'top', 'center', 'bottom'.
    + * @param {Object} [options.textBox.style] - Text Box styles
    + * @param {number} [options.textBox.style.lineWidth=2] - Text Box border width
    + * @param {string|number[]} [options.textBox.style.stroke] - Text Box border color  (HexColor, PercentColor or DecimalColor)
    + * @param {number[]} [options.textBox.style.dash=[]] - Text Box border border dash style [number, number]
    + * @param {string|number[]} [options.textBox.style.fill] - Text Box border background color (HexColor, PercentColor or DecimalColor)
    + * @param {number} [options.textBox.style.opacity=1] - Text Box border background opacity
    + */
    +exports.text = function text(text = '', x, y, options = {}) {
    +    if (!this.pageContext) {
    +        return this;
    +    }
    +    const targetAnnotations = options;
    +    const originCoord = this._calibrateCoordinate(x, y, 0, 0, this.pageNumber);
    +    const pathOptions = this._getPathOptions(options, originCoord.nx, originCoord.ny);
    +    pathOptions.html = options.html;
    +    // const { width: pageWidth, height: pageHeight } = this.metadata[this.pageNumber];
    +
    +    const textObjects = (options.html) ? htmlToTextObjects(text) : [{
    +        value: text,
    +        tag: null,
    +        isBold: options.bold,
    +        isItalic: options.italic,
    +        attributes: [],
    +        styles: {},
    +        needsLineBreaker: false,
    +        size: pathOptions.size,
    +        sizeRatio: 1,
    +        sizeRatios: [1],
    +        link: null,
    +        childs: []
    +    }];
    +    const textBox = (!options.textBox) ? {
    +        // use page width with padding and margin
    +        isSimpleText: true,
    +        width: null,
    +        lineHeight: 0,
    +        padding: 0,
    +        minHeight: 0,
    +        wrap: 'auto'
    +    } : {
    +        width: options.textBox.width || 100,
    +        lineHeight: options.textBox.lineHeight,
    +        height: options.textBox.height,
    +        padding: (options.textBox.padding || 0),
    +        minHeight: options.textBox.minHeight || 0,
    +        style: options.textBox.style,
    +        textAlign: options.textBox.textAlign,
    +        wrap: (options.textBox.wrap !== undefined) ? options.textBox.wrap : 'auto'
    +    };
    +
    +    // Allows user to enter a single number which will be used for all text box sides,
    +    // or an array of values [top, right, bottom, left] with any combination of missing sides.
    +    // Default value for a missing side is the value of the text box's opposite side (see below).
    +    //
    +    //               padding[0]
    +    //   padding[3]              padding[1]
    +    //               padding[2]
    +
    +    textBox.padding = (Array.isArray(textBox.padding)) ? textBox.padding : [textBox.padding];
    +
    +    Object.assign(textBox, {
    +        paddingTop:     textBox.padding[0],
    +        paddingRight:  (textBox.padding[1] !== undefined) ? textBox.padding[1] : textBox.padding[0],
    +        paddingBottom: (textBox.padding[2] !== undefined) ? textBox.padding[2] : textBox.padding[0],
    +        paddingLeft:   (textBox.padding[3] !== undefined) ? textBox.padding[3] :
    +                       (textBox.padding[1] !== undefined) ? textBox.padding[1] : textBox.padding[0]
    +    });
    +
    +    let firstLineHeight;
    +    let totalHeight = 0;
    +    let toWriteTextObjects = [];
    +
    +    const writeValue = (textObject) => {
    +        textObject.lineID = textObject.lineID || Date.now() * Math.random();
    +        textObject.lineID = (textObject.needsLineBreaker) ? Date.now() * Math.random() : textObject.lineID;
    +        if (textObject.value) {
    +            textObject.styles.color = (textObject.styles.color) ?
    +                this._transformColor(textObject.styles.color) : pathOptions.color;
    +            const {
    +                toWriteTextObjects: newToWriteObjects,
    +                paragraphHeight
    +            } = getToWriteTextObjects(textObject, pathOptions, textBox);
    +            toWriteTextObjects = [...toWriteTextObjects, ...newToWriteObjects];
    +            if (!firstLineHeight) {
    +                firstLineHeight = toWriteTextObjects[0].lineHeight;
    +            }
    +            totalHeight += paragraphHeight; // textObject.size;
    +        }
    +        if (textObject.tag && textObject.childs.length) {
    +            // console.log(textObject);
    +            if (!textObject.size) {
    +                textObject.size = pathOptions.size * textObject.sizeRatio;
    +                textObject.sizeRatios = [textObject.sizeRatio];
    +            }
    +            textObject.layer = textObject.layer || 0;
    +            textObject.layer++;
    +
    +            textObject.currentIndex = 0;
    +
    +            textObject.childs.forEach((child) => {
    +                if (textObject.tag == 'ul') {
    +                    child.prependValue = '* ';
    +                    child.layer = textObject.layer + 1;
    +                    // child.indent = 4 * child.layer;
    +                }
    +                if (textObject.tag == 'ol') {
    +                    if (child.tag != 'ol') {
    +                        textObject.currentIndex++;
    +                        child.prependValue = `${(textObject.currentIndex).toString()}. `;
    +                    }
    +                    child.layer = textObject.layer + 1;
    +                    // child.indent = 4 * child.layer;
    +                }
    +                if (textObject.tag == 'li') {
    +                    if (child.tag == 'ol' || child.tag == 'ul') {
    +                        child.layer = textObject.layer - 1;
    +                    }
    +                }
    +                if (textObject.prependValue) {
    +                    child.prependValue = (!['ol', 'ul'].includes(textObject.tag)) ?
    +                        textObject.prependValue : child.prependValue;
    +                    textObject.indent = 2 * textObject.layer;
    +                }
    +                if (textObject.indent) {
    +                    child.indent = child.indent || textObject.indent;
    +                }
    +                if (textObject.size) {
    +                    child.size = textObject.size * child.sizeRatio;
    +                    child.sizeRatios = [...textObject.sizeRatios, child.sizeRatio];
    +                }
    +                child.styles = Object.assign(child.styles, textObject.styles);
    +
    +                child.isBold = (textObject.isBold) ? textObject.isBold : child.isBold;
    +                child.isItalic = (textObject.isItalic) ? textObject.isItalic : child.isItalic;
    +                child.underline = (textObject.underline) ? textObject.underline : child.underline;
    +
    +                child.lineID = textObject.lineID;
    +                writeValue(child);
    +            });
    +        }
    +    };
    +    textObjects.forEach((textObject) => {
    +        writeValue(textObject);
    +    });
    +
    +    if (!textBox.width) {
    +        textBox.width = toWriteTextObjects[0].lineWidth;
    +    }
    +    textBox.firstLineHeight = firstLineHeight;
    +    textBox.lastLineHeight  = pathOptions.textHeight;
    +
    +    const {
    +        offsetX,
    +        offsetY
    +    } = this._getTextBoxOffset(textBox, pathOptions);
    +    const {
    +        nx,
    +        ny
    +    } = this._calibrateCoordinate(x, y, offsetX, offsetY);
    +
    +    let textYpos = ny;
    +
    +    if (textBox.style) {
    +        const textBoxWidth  = textBox.width + textBox.paddingLeft + textBox.paddingRight;
    +        const actualTextHeight = textBox.firstLineHeight * (toWriteTextObjects.length-1) + textBox.lastLineHeight
    +
    +        if (!textBox.height) {
    +            textBox.height = actualTextHeight + textBox.paddingTop + textBox.paddingBottom;
    +            
    +            if (textBox.minHeight && textBox.minHeight > textBox.height) {
    +                textBox.height = textBox.minHeight;
    +            }
    +        }
    +
    +        this.rectangle(
    +            nx - textBox.paddingLeft, 
    +            ny - textBox.height + textBox.firstLineHeight + textBox.paddingTop,
    +            textBoxWidth, textBox.height, Object.assign(textBox.style, {
    +                useGivenCoords: true,
    +                rotation: pathOptions.rotation,
    +                rotationOrigin: [pathOptions.originX,pathOptions.originY],
    +            })
    +        );
    +         
    +        // Determine vertical starting position of text within textBox
    +        switch (toWriteTextObjects[0].writeOptions.alignVertical) {
    +            case 'center':
    +                textYpos -= (textBox.height - actualTextHeight) / 2;
    +                break;
    +            case 'bottom':
    +                textYpos -= (textBox.height - actualTextHeight);
    +                break;
    +        }
    +    }
    +    
    +    const context = this.pageContext;
    +    // const space = 8;
    +    let currentY = textYpos;
    +    let currentLineID;
    +    let currentLineWidth = 0;
    +    let toWriteContents = [];
    +
    +    toWriteTextObjects.forEach((toWriteTextObject, index) => {
    +        const {
    +            text,
    +            lineHeight,
    +            lineWidth,
    +            lineID,
    +            spaceWidth
    +        } = toWriteTextObject;
    +
    +        currentLineID = currentLineID || lineID;
    +        const isContinued = (currentLineID == lineID) ? true : false;
    +
    +        const getStartX = (startX, currentWriteOptions) => {
    +            let offsetX = textBox.paddingLeft;
    +
    +            switch (currentWriteOptions.alignHorizontal) {
    +                case 'center':
    +                    offsetX = (textBox.width - currentLineWidth) / 2;
    +                    break;
    +                case 'right':
    +                    offsetX = (textBox.width - textBox.paddingRight - currentLineWidth);
    +                    break;
    +            }
    +            
    +            return startX + offsetX;
    +        };
    +
    +        const writeText = (context, x, y, wto) => {
    +            const options = wto.writeOptions;
    +            const {lineHeight, text, baseline, wordsInLine, textWidth} = wto;
    +
    +            // write directly to page when not dealing with opacity, rotation and special colorspace.
    +            if (options.opacity  === 1 && options.colorspace !== 'separation' &&
    +               (options.rotation === 0 || options.rotation === undefined)) {
    +                
    +                // Note that the last line of a text box ignores justification.
    +                if (options.alignHorizontal !== 'justify' || wto.lastLine) {
    +                    if (textBox.wrap !== 'auto') {  // This applies a clipping region around the text
    +                        this.pageContext.q();
    +                        this.pageContext
    +                            .m(nx,y+lineHeight)
    +                            .l(nx+textBox.width,y+lineHeight)
    +                            .l(nx+textBox.width,y)
    +                            .l(nx,y)
    +                            .h().W().n();
    +                    }
    +                    
    +                    context.writeText(text, x, y+baseline, options);
    +
    +                    if (textBox.wrap !== 'auto') {
    +                        this.pageContext.Q();
    +                    }
    +                } else {
    +                    justify(x, wordsInLine, textBox, textWidth, (word, xx) => {
    +                        context.writeText(word, xx, y+baseline, options);
    +                    });
    +                }            
    +            } else {
    +                this.pauseContext();
    +
    +                // https://github.com/galkahana/HummusJS/wiki/Use-the-pdf-drawing-operators
    +                const xObject = new xObjectForm(this.writer, textBox.width, lineHeight);
    +                const xObjectCtx = xObject.getContentContext();
    +                // Build form object
    +                xObjectCtx
    +                    .q()
    +                    .gs(xObject.getGsName(options.fillGsId))  // set graphic state (here opacity)
    +                    .BT();                                    // begin text context
    +                xObjectCtx.Tf(options.font, options.size);    // set font
    +                xObject.fill(options.colorModel);             // set color
    +
    +                if (options.alignHorizontal !== 'justify' || wto.lastLine) {
    +                    xObjectCtx
    +                        .Tm(1, 0, 0, 1, 0, baseline)          // set position in object
    +                        .Tj(text);                            // write text
    +                } else {
    +                    justify(0, wordsInLine, textBox, textWidth, (word, xx) => {
    +                        xObjectCtx
    +                        .Tm(1, 0, 0, 1, xx, baseline)         // set position in object
    +                        .Tj(word);                            // write word
    +                    });
    +                }
    +
    +                xObjectCtx
    +                    .ET()                                     // end text context
    +                    .Q();
    +                xObject.end();
    +
    +                this.resumeContext();
    +
    +                this.pageContext.q();
    +                this._setRotationContext(this.pageContext, x, y, options);
    +                this.pageContext
    +                    .doXObject(xObject)
    +                    .Q();
    +            }
    +
    +            const { textHeight } = options;
    +
    +            for (let key in targetAnnotations) {
    +                const subtype = this._getTextMarkupAnnotationSubtype(key);
    +                if (subtype) {
    +                    const markupOption = (typeof (targetAnnotations[key]) != 'object') ? {} : targetAnnotations[key];
    +                    Object.assign(markupOption, {
    +                        height: textHeight * 1.4,
    +                        width: currentLineWidth,
    +                        text: markupOption.text || '',
    +                        _textHeight: textHeight
    +                    });
    +                    const {
    +                        ox,
    +                        oy
    +                    } = this._reverseCoordinate(x, y - textHeight * 0.2);
    +                    this.annot(ox, oy, subtype, markupOption);
    +                }
    +            }
    +        };
    +
    +        if (!isContinued) {
    +            toWriteContents.forEach((content) => {
    +                const x = getStartX(content.startX, content.wto.writeOptions);
    +                const y = currentY;
    +                writeText(context, x, y, content.wto);
    +            });
    +            toWriteContents = [];
    +            currentLineID = lineID;
    +            currentLineWidth = 0;
    +            currentY -= lineHeight;
    +        }
    +
    +        const startX = nx + currentLineWidth;
    +        if (text != '[@@DONOT_RENDER_THIS@@]') {
    +            toWriteContents.push({
    +                startX,
    +                wto: toWriteTextObject
    +            });
    +        }
    +
    +        currentLineWidth = currentLineWidth + lineWidth + spaceWidth;
    +
    +        // Processing last line?
    +        if (index == toWriteTextObjects.length - 1) {
    +            toWriteContents.forEach((content) => {
    +                const x = getStartX(content.startX, content.wto.writeOptions);
    +                const y = currentY;
    +                writeText(context, x, y, content.wto);
    +            });
    +        }
    +    });
    +
    +    return this;
    +};
    +
    +function justify (x, wordsInLine, textBox, textWidth, callback) {
    +    // For some reason, textWidth is smaller than lineWidth. My suspicions lie in the fact
    +    // that spacing computations appear different depending on where the space is located.
    +    // What is noted though that if lineWidth is used in the calculations for text 
    +    // justitification, the text goes passed the right boundary. Due to the vagary in space
    +    // computation, the final wrinkle to make sure the last word in the line smacks up against
    +    // the right side boundary is to perform a special computation on the last word positioning
    +    // relative to that right side bounds.
    +    const spaceCount = wordsInLine.length-1;
    +    const spaceBetweenWords = ( wordsInLine.length > 1) ? (textBox.width - textWidth) / (spaceCount) : 0;
    +    const lineStart = x;
    +
    +    for (let index = 0; index < wordsInLine.length; index++) {
    +        const word = wordsInLine[index];
    +        
    +        callback(word.value, x)
    +        // Ready to compute last word spacing?
    +        if (index+1 === spaceCount) {
    +            x = lineStart + textBox.width - textBox.paddingRight - wordsInLine[index+1].dimensions.xMax;
    +        } else {
    +            x += word.dimensions.width + spaceBetweenWords;
    +        }
    +    }
    +}
    +
    +function nextWord (text, brk, previousPosition, pathOptions) {
    +    let nextWord = text.slice(previousPosition, brk.position);
    +    
    +    if (brk.required) {  // effectively saw a '\n' in text.
    +        nextWord = nextWord.trim();
    +    }
    +
    +    return new Word(nextWord, pathOptions);
    +}
    +
    +function getToWriteTextObjects(textObject = {}, pathOptions, textBox = {}) {
    +    const toWriteTextObjects = [];
    +    let text = ((textObject.prependValue) ? textObject.prependValue : '') +
    +        textObject.value +
    +        ((textObject.appendValue) ? textObject.appendValue : '');
    +
    +    const size = textObject.size || pathOptions.size;
    +    // Use the same string to get the same height for each string with the same font.
    +    // Need lowercase 'gjpqy' so descenders are included in text height.
    +    // Need special characters '|}' because they have ascenders that go beyond upper case letters.
    +    const textDimensions = pathOptions.font.calculateTextDimensions(
    +        'ABCDEFGHIJKLMNOPQRSTUVWXYZgjpqy|}', size
    +    );
    +    const textHeight = textDimensions.height;
    +
    +    pathOptions.textHeight = textHeight;
    +    textBox.lineHeight = textBox.lineHeight || textHeight;
    +    textBox.baselineHeight = textDimensions.yMax;
    +
    +    const breaker = new LineBreaker(text);
    +    const lines = [];
    +    const indent = (textObject.indent || 0);
    +    // const indentWidth = indent * size;
    +    const lineMaxWidth = (textBox.width) ? textBox.width - textBox.paddingLeft - textBox.paddingRight - indent : null;
    +    let newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions);
    +    let last = 0;
    +    let bk = breaker.nextBreak();
    +    let previousWord;
    +    let flushLine = false;
    +
    +    for (let i = 0; i < indent; i++) {
    +        newLine.addWord(new Word(' ', pathOptions));
    +    }
    +
    +    while (bk) {
    +        let word = nextWord(text, bk, last, pathOptions);
    +
    +        if (newLine.canFit(word)) {
    +            newLine.addWord(word);
    +        } else {
    +            // remove any trailing space on previous word so right justification works appropriately
    +            if ( previousWord && textBox.wrap === 'auto') {
    +                newLine.replaceLastWord(new Word(previousWord.value.trim(), pathOptions));
    +            }
    +            lines.push(newLine);
    +
    +            // now deal with text line wrap (what happens to text that doesn't fit in line)
    +            if (textBox.wrap !== 'auto') {
    +                flushLine = true;
    +                if (textBox.wrap === 'clip') {
    +                    newLine.addWord(word);
    +                }
    +            } else {
    +                // this is the auto wrap section
    +                newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions);
    +                for (let i = 0; i < indent; i++) {
    +                    newLine.addWord(new Word(' ', pathOptions));
    +                }
    +                if (textObject.prependValue) {
    +                    const space = Array(textObject.prependValue.length + 1).fill(' ').join('');
    +                    newLine.addWord(new Word(space, pathOptions));
    +                }
    +                newLine.addWord(word);
    +            }
    +        }
    +
    +        if (flushLine) {
    +            while (bk) {
    +                bk = breaker.nextBreak();
    +                if (bk && bk.required) {
    +                    flushLine = false;
    +                    newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions);
    +                    word = null;
    +                    break;
    +                }
    +            }
    +        } else {
    +            /**
    +             * Author: silverma (Marc Silverman)
    +             * #29 Is it possible to add multi-line text?
    +             * https://github.com/chunyenHuang/hummusRecipe/issues/29
    +             */
    +            if (bk.required) {
    +                lines.push(newLine);
    +                newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions);
    +            }
    +        }
    +       
    +        if (bk) {
    +            previousWord = word;
    +            last = bk.position;
    +            bk = breaker.nextBreak();
    +        }
    +    }
    +
    +    if (!flushLine) {
    +        lines.push(newLine);
    +    }
    +
    +    const lineHeightMargin = 0;
    +    let lineHeight = (textHeight + lineHeightMargin >= textBox.lineHeight) ? textHeight + lineHeightMargin : textBox.lineHeight;
    +    let [alignHorizontal, alignVertical] = (textBox.textAlign) ? textBox.textAlign.split(' ') : [];
    +    const writeOptions = Object.assign({}, pathOptions, {
    +        color: textObject.styles.color,
    +        opacity: parseFloat(textObject.styles.opacity || pathOptions.opacity || 1),
    +        underline: textObject.underline || pathOptions.underline,
    +        size: textObject.size,
    +        alignHorizontal: alignHorizontal,
    +        alignVertical: alignVertical,
    +        font: pathOptions.font
    +    });
    +    let paragraphHeight = 0;
    +    lines.forEach((line, index) => {
    +        lineHeight = (lineHeight >= line.height) ? lineHeight : line.height;
    +
    +        // Space width only gets applied for HTML elements.
    +        // Otherwise, normal text alignments (center,right) are shifted left by a space.
    +        toWriteTextObjects.push({
    +            text: line.value,
    +            lineID: (index == 0) ? textObject.lineID : Date.now() * Math.random(),
    +            // @todo: handle line height to mimic html tag
    +            lineHeight,
    +            baseline: lineHeight - textBox.baselineHeight,
    +            lineWidth: line.currentWidth,
    +            textWidth: line.textWidth,  // for justification
    +            spaceWidth: (pathOptions.html) ? line.spaceWidth : 0,
    +            wordsInLine: line.words,
    +            lastLine: (index === lines.length-1),
    +            writeOptions
    +        });
    +        paragraphHeight += lineHeight;
    +    });
    +    return {
    +        toWriteTextObjects,
    +        paragraphHeight
    +    };
    +}
     
    @@ -506,7 +629,7 @@

    text.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/utils.js.html b/docs/utils.js.html index 188a863..4f490fc 100644 --- a/docs/utils.js.html +++ b/docs/utils.js.html @@ -37,65 +37,65 @@

    utils.js

    -
    const ANNOTATION_PREFIX = 'Annots';
    -
    -/**
    - * Append PDF Page with annotations.
    - *
    - * @param {any} pdfWriter - Hummus writer.
    - * @param {string|any} sourcePDFPath - The path for the output pdfs or Reader stream.
    - * @param {number} pageNumber - page number.
    - * @param {any} [options={}] - appendPDFPageFromPDF options
    - */
    -function appendPDFPageFromPDFWithAnnotations(pdfWriter, sourcePDFPath, pageNumber) {
    -    const cpyCxt = pdfWriter.createPDFCopyingContext(sourcePDFPath);
    -    const cpyCxtParser = cpyCxt.getSourceDocumentParser();
    -    const pageDictionary = cpyCxtParser.parsePageDictionary(pageNumber);
    -
    -    if (!pageDictionary.exists(ANNOTATION_PREFIX)) {
    -        cpyCxt.appendPDFPageFromPDF(pageNumber);
    -    } else {
    -        let reffedObjects;
    -        pdfWriter.getEvents().once('OnPageWrite', params => {
    -            params.pageDictionaryContext.writeKey(ANNOTATION_PREFIX);
    -            reffedObjects = cpyCxt.copyDirectObjectWithDeepCopy(pageDictionary.queryObject(ANNOTATION_PREFIX));
    -        });
    -
    -        cpyCxt.appendPDFPageFromPDF(pageNumber);
    -
    -        if (reffedObjects && reffedObjects.length > 0) {
    -            cpyCxt.copyNewObjectsForDirectObject(reffedObjects);
    -        }
    -    }
    -}
    -
    -/**
    - * Append PDF Pages with annotations.
    - *
    - * @param {any} pdfWriter - Hummus writer.
    - * @param {string|any} sourcePDFPath - The path for the output pdfs or Reader stream.
    - * @param {any} [options={}] - appendPDFPagesFromPDF options
    - */
    -function appendPDFPagesFromPDFWithAnnotations(pdfWriter, sourcePDFPath, options = {}) {
    -    const cpyCxt = pdfWriter.createPDFCopyingContext(sourcePDFPath);
    -    const cpyCxtParser = cpyCxt.getSourceDocumentParser();
    -
    -    if (options.specificRanges && options.specificRanges.length) {
    -        for (const [start, end] of options.specificRanges) {
    -            for (let i = start; i <= end; ++i) {
    -                appendPDFPageFromPDFWithAnnotations(pdfWriter, sourcePDFPath, i);
    -            }
    -        }
    -    } else {
    -        for (let i = 0; i < cpyCxtParser.getPagesCount(); ++i) {
    -            appendPDFPageFromPDFWithAnnotations(pdfWriter, sourcePDFPath, i);
    -        }
    -    }
    -}
    -
    -exports.ANNOTATION_PREFIX = ANNOTATION_PREFIX;
    -exports.appendPDFPageFromPDFWithAnnotations = appendPDFPageFromPDFWithAnnotations;
    -exports.appendPDFPagesFromPDFWithAnnotations = appendPDFPagesFromPDFWithAnnotations;
    +            
    const ANNOTATION_PREFIX = 'Annots';
    +
    +/**
    + * Append PDF Page with annotations.
    + *
    + * @param {any} pdfWriter - Hummus writer.
    + * @param {string|any} sourcePDFPath - The path for the output pdfs or Reader stream.
    + * @param {number} pageNumber - page number.
    + * @param {any} [options={}] - appendPDFPageFromPDF options
    + */
    +function appendPDFPageFromPDFWithAnnotations(pdfWriter, sourcePDFPath, pageNumber) {
    +    const cpyCxt = pdfWriter.createPDFCopyingContext(sourcePDFPath);
    +    const cpyCxtParser = cpyCxt.getSourceDocumentParser();
    +    const pageDictionary = cpyCxtParser.parsePageDictionary(pageNumber);
    +
    +    if (!pageDictionary.exists(ANNOTATION_PREFIX)) {
    +        cpyCxt.appendPDFPageFromPDF(pageNumber);
    +    } else {
    +        let reffedObjects;
    +        pdfWriter.getEvents().once('OnPageWrite', params => {
    +            params.pageDictionaryContext.writeKey(ANNOTATION_PREFIX);
    +            reffedObjects = cpyCxt.copyDirectObjectWithDeepCopy(pageDictionary.queryObject(ANNOTATION_PREFIX));
    +        });
    +
    +        cpyCxt.appendPDFPageFromPDF(pageNumber);
    +
    +        if (reffedObjects && reffedObjects.length > 0) {
    +            cpyCxt.copyNewObjectsForDirectObject(reffedObjects);
    +        }
    +    }
    +}
    +
    +/**
    + * Append PDF Pages with annotations.
    + *
    + * @param {any} pdfWriter - Hummus writer.
    + * @param {string|any} sourcePDFPath - The path for the output pdfs or Reader stream.
    + * @param {any} [options={}] - appendPDFPagesFromPDF options
    + */
    +function appendPDFPagesFromPDFWithAnnotations(pdfWriter, sourcePDFPath, options = {}) {
    +    const cpyCxt = pdfWriter.createPDFCopyingContext(sourcePDFPath);
    +    const cpyCxtParser = cpyCxt.getSourceDocumentParser();
    +
    +    if (options.specificRanges && options.specificRanges.length) {
    +        for (const [start, end] of options.specificRanges) {
    +            for (let i = start; i <= end; ++i) {
    +                appendPDFPageFromPDFWithAnnotations(pdfWriter, sourcePDFPath, i);
    +            }
    +        }
    +    } else {
    +        for (let i = 0; i < cpyCxtParser.getPagesCount(); ++i) {
    +            appendPDFPageFromPDFWithAnnotations(pdfWriter, sourcePDFPath, i);
    +        }
    +    }
    +}
    +
    +exports.ANNOTATION_PREFIX = ANNOTATION_PREFIX;
    +exports.appendPDFPageFromPDFWithAnnotations = appendPDFPageFromPDFWithAnnotations;
    +exports.appendPDFPagesFromPDFWithAnnotations = appendPDFPagesFromPDFWithAnnotations;
     
    @@ -108,7 +108,7 @@

    utils.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/vector-line.js.html b/docs/vector-line.js.html index 4f984c8..5c1ff9a 100644 --- a/docs/vector-line.js.html +++ b/docs/vector-line.js.html @@ -37,87 +37,96 @@

    vector-line.js

    -
    /**
    - * move the current position to target position
    - * @name moveTo
    - * @function
    - * @memberof Recipe
    - * @param {number} x - The coordinate x
    - * @param {number} y - The coordinate y
    - */
    -exports.moveTo = function moveTo(x, y) {
    -    const { nx, ny } = this._calibrateCoordinate(x, y);
    -    this._position = {
    -        x: nx,
    -        y: ny
    -    };
    -    return this;
    -};
    -
    -/**
    - * Draw a line from current position
    - * @name lineTo
    - * @function
    - * @memberof Recipe
    - * @param {number} x - The coordinate x
    - * @param {number} y - The coordinate y
    - * @param {Object} [options] - The options
    - * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]} [options.fill] - HexColor, PercentColor or DecimalColor
    - * @param {number} [options.lineWidth] - The line width
    - * @param {number} [options.opacity] - The opacity
    - * @param {number[]} [options.dash] - The dash style [number, number]
    - */
    -exports.lineTo = function lineTo(x, y, options = {}) {
    -    const fromX = this._position.x;
    -    const fromY = this._position.y;
    -    const { nx, ny } = this._calibrateCoordinate(x, y);
    -    const context = this.pageContext;
    -    const pathOptions = this._getPathOptions(options);
    -    pathOptions.type = 'stroke';
    -
    -    if (pathOptions.stroke !== undefined) {
    -        pathOptions.color = pathOptions.stroke;
    -        pathOptions.colorspace = pathOptions.strokeModel.colorspace;
    -    }
    -
    -    context
    -        .q()
    -        .J(1)
    -        .j(1)
    -        .d(pathOptions.dash, pathOptions.dashPhase)
    -        .drawPath(fromX, fromY, nx, ny, pathOptions)
    -        .Q();
    -    this.moveTo(x, y);
    -    return this;
    -};
    -
    -/**
    - * Draw a line
    - * @name line
    - * @function
    - * @memberof Recipe
    - * @param {number[]} coordinates - The array of coordinate [[x,y], [m,n]]
    - * @param {Object} [options] - The options
    - * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    - * @param {number} [options.lineWidth] - The line width
    - * @param {number[]} [options.dash] - The dash style [number, number]
    - */
    -exports.line = function line(coordinates = [], options = {}) {
    -    coordinates.forEach((coordinate, index) => {
    -        if (index === 0) {
    -            this.moveTo(coordinate[0], coordinate[1]);
    -            if (this.editingPage) {  // hack to force out first line when editing page
    -                this.lineTo(coordinate[0], coordinate[1], options);
    -            }
    -        } else {
    -            this.lineTo(coordinate[0], coordinate[1], options);
    -        }
    -    });
    -    return this;
    -};
    +            
    /**
    + * move the current position to target position
    + * @name moveTo
    + * @function
    + * @memberof Recipe
    + * @param {number} x - The coordinate x
    + * @param {number} y - The coordinate y
    + */
    +exports.moveTo = function moveTo(x, y) {
    +    const { nx, ny } = this._calibrateCoordinate(x, y);
    +    this._position = {
    +        x: nx,
    +        y: ny
    +    };
    +    return this;
    +};
    +
    +/**
    + * Draw a line from current position
    + * @name lineTo
    + * @function
    + * @memberof Recipe
    + * @param {number} x - The coordinate x
    + * @param {number} y - The coordinate y
    + * @param {Object} [options] - The options
    + * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    + * @param {number} [options.lineWidth] - The line width
    + * @param {number} [options.opacity] - how transparent should line be, from 0: invisible to 1: opaque
    + * @param {number[]} [options.dash] - The dash pattern [dashSize, gapSize] or [dashAndGapSize]
    + * @param {number} [options.dashPhase] - distance into dash pattern at which to start dash (default: 0, immediately)
    + * @param {string} [options.lineCap] -  open line end style, 'butt', 'round', or 'square' (default: 'round')
    + * @param {string} [options.lineJoin] - joined line end style, 'miter', 'round', or 'bevel' (default: 'round')
    + * @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414)
    + * 
    + */
    +exports.lineTo = function lineTo(x, y, options = {}) {
    +    const fromX = this._position.x;
    +    const fromY = this._position.y;
    +    const { nx, ny } = this._calibrateCoordinate(x, y);
    +    const context = this.pageContext;
    +    const pathOptions = this._getPathOptions(options);
    +    pathOptions.type = 'stroke';
    +
    +    if (pathOptions.stroke !== undefined) {
    +        pathOptions.color = pathOptions.stroke;
    +        pathOptions.colorspace = pathOptions.strokeModel.colorspace;
    +    }
    +
    +    context
    +        .q()
    +        .J(pathOptions.lineCap)
    +        .j(pathOptions.lineJoin)
    +        .d(pathOptions.dash, pathOptions.dashPhase)
    +        .M(pathOptions.miterLimit)
    +        .drawPath(fromX, fromY, nx, ny, pathOptions)
    +        .Q();
    +    this.moveTo(x, y);
    +    return this;
    +};
    +
    +/**
    + * Draw a line
    + * @name line
    + * @function
    + * @memberof Recipe
    + * @param {number[]} coordinates - The array of coordinate [[x,y], [m,n]]
    + * @param {Object} [options] - The options
    + * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    + * @param {number} [options.lineWidth] - The line width
    + * @param {number} [options.opacity] - how transparent should line be, from 0: invisible to 1: opaque
    + * @param {number[]} [options.dash] - The dash pattern [dashSize, gapSize] or [dashAndGapSize]
    + * @param {number} [options.dashPhase] - distance into dash pattern at which to start dash (default: 0, immediately)
    + * @param {string} [options.lineCap] -  open line end style, 'butt', 'round', or 'square' (default: 'round')
    + * @param {string} [options.lineJoin] - joined line end style, 'miter', 'round', or 'bevel' (default: 'round')
    + * @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414)*/
    +exports.line = function line(coordinates = [], options = {}) {
    +    coordinates.forEach((coordinate, index) => {
    +        if (index === 0) {
    +            this.moveTo(coordinate[0], coordinate[1]);
    +            if (this.editingPage) {  // hack to force out first line when editing page
    +                this.lineTo(coordinate[0], coordinate[1], options);
    +            }
    +        } else {
    +            this.lineTo(coordinate[0], coordinate[1], options);
    +        }
    +    });
    +    return this;
    +};
     
    @@ -130,7 +139,7 @@

    vector-line.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/vector-polygon.js.html b/docs/vector-polygon.js.html index dd4a365..56315f9 100644 --- a/docs/vector-polygon.js.html +++ b/docs/vector-polygon.js.html @@ -37,116 +37,119 @@

    vector-polygon.js

    -
    //  Table indicating how to specify coloration of elements
    -//  -------------------------------------------------------------------
    -// |Color | HexColor   | DecimalColor                   | PercentColor |
    -// |Space | (string)   | (array)                        | (string)     |
    -// |------+------------+--------------------------------+--------------|
    -// | Gray | #GG        | [gray]                         | %G           |
    -// |  RGB | #rrggbb    | [red, green, blue]             | %r,g,b       |
    -// | CMYK | #ccmmyykk  | [cyan, magenta, yellow, black] | %c,m,y,k     |
    -//  -------------------------------------------------------------------
    -//
    -//   HexColor component values (two hex digits) range from 00 to FF.
    -//   DecimalColor component values range from 0 to 255.
    -//   PercentColor component values range from 1 to 100.
    -
    -/**
    - * Draw a polygon
    - * @name polygon
    - * @function
    - * @memberof Recipe
    - * @param {number[]} coordinates - The array of coordinate [[x,y], [m,n]]
    - * @param {Object} [options] - The options
    - * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]} [options.fill] - HexColor, PercentColor or DecimalColor
    - * @param {number} [options.lineWidth] - The line width
    - * @param {number} [options.opacity] - The opacity
    - * @param {number[]} [options.dash] - The dash style [number, number]
    - * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
    - * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
    - */
    -exports.polygon = function polygon(coordinates = [], options = {}) {
    -    // close polygon
    -    if (this._getDistance(coordinates[0], coordinates[coordinates.length - 1]) != 0) {
    -        coordinates.push(coordinates[0]);
    -    }
    -    let boundBox = [ /*minX, minY, maxX, maxY*/ ];
    -    coordinates.forEach((coord, index) => {
    -        if (index === 0) {
    -            boundBox = [coord[0], coord[1], coord[0], coord[1]];
    -        }
    -        boundBox[0] = (boundBox[0] > coord[0]) ? coord[0] : boundBox[0];
    -        boundBox[1] = (boundBox[1] > coord[1]) ? coord[1] : boundBox[1];
    -        boundBox[2] = (boundBox[2] < coord[0]) ? coord[0] : boundBox[2];
    -        boundBox[3] = (boundBox[3] < coord[1]) ? coord[1] : boundBox[3];
    -    });
    -
    -    const pathOptions = this._getPathOptions(options);
    -    let colorModel = pathOptions.colorModel;
    -    const margin = pathOptions.width * 2;
    -    const width  = Math.abs(boundBox[2] - boundBox[0]) + margin * 2;
    -    const height = Math.abs(boundBox[3] - boundBox[1]) + margin * 2;
    -    const startX = boundBox[0] - margin;
    -    const startY = boundBox[1] + height - margin;
    -    const { nx, ny } = this._calibrateCoordinate(startX, startY);
    -
    -    const drawPolygon = (context, coordinates) => {
    -        coordinates.forEach((coord, index) => {
    -            let nx = coord[0];
    -            let ny = coord[1];
    -            if (index === 0) {
    -                context.m(nx - startX,  startY -ny );
    -            } else {
    -                context.l(nx - startX,  startY -ny);
    -            }
    -        });
    -    };
    -
    -    const setPathOptions = (context) => {
    -        context
    -            .J(1)
    -            .j(1)
    -            .d(pathOptions.dash, pathOptions.dashPhase)
    -            .M(1.414);
    -    };
    -
    -    pathOptions.originX = nx + width/2;  // default rotation point
    -    pathOptions.originY = ny + height/2;
    -
    -    if (options.fill) {
    -
    -        if (pathOptions.fill !== undefined) {
    -            colorModel = pathOptions.fillModel;
    -        }
    -
    -        this._drawObject(this, nx, ny, width, height, pathOptions, (ctx, xObject) => {
    -            ctx.gs(xObject.getGsName(pathOptions.fillGsId)),
    -            setPathOptions(ctx);
    -            xObject.fill(colorModel);
    -            drawPolygon(ctx, coordinates);
    -            ctx.f();
    -        });
    -    }
    -
    -    if (options.stroke || options.color || !options.fill) {
    -
    -        if (pathOptions.stroke !== undefined) {
    -            colorModel = pathOptions.strokeModel;
    -        }
    -
    -        this._drawObject(this, nx, ny, width, height, pathOptions, (ctx, xObject) => {
    -            ctx.w(pathOptions.width);
    -            setPathOptions(ctx);
    -            xObject.stroke(colorModel);
    -            drawPolygon(ctx, coordinates);
    -            ctx.s();
    -        });
    -    }
    -
    -    return this;
    -};
    +            
    //  Table indicating how to specify coloration of elements
    +//  -------------------------------------------------------------------
    +// |Color | HexColor   | DecimalColor                   | PercentColor |
    +// |Space | (string)   | (array)                        | (string)     |
    +// |------+------------+--------------------------------+--------------|
    +// | Gray | #GG        | [gray]                         | %G           |
    +// |  RGB | #rrggbb    | [red, green, blue]             | %r,g,b       |
    +// | CMYK | #ccmmyykk  | [cyan, magenta, yellow, black] | %c,m,y,k     |
    +//  -------------------------------------------------------------------
    +//
    +//   HexColor component values (two hex digits) range from 00 to FF.
    +//   DecimalColor component values range from 0 to 255.
    +//   PercentColor component values range from 1 to 100.
    +
    +/**
    + * Draw a polygon
    + * @name polygon
    + * @function
    + * @memberof Recipe
    + * @param {number[]} coordinates - The array of coordinate [[x,y], [m,n]]
    + * @param {Object} [options] - The options
    + * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]} [options.fill] - HexColor, PercentColor or DecimalColor
    + * @param {number} [options.lineWidth] - The line width
    + * @param {number} [options.opacity] - The opacity
    + * @param {number[]} [options.dash] - The dash pattern [dashSize, gapSize] or [dashAndGapSize]
    + * @param {number} [options.dashPhase] - distance into dash pattern at which to start dash (default: 0, immediately)
    + * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
    + * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
    + * @param {string} [options.lineCap] -  open line end style, 'butt', 'round', or 'square' (default: 'round')
    + * @param {string} [options.lineJoin] - joined line end style, 'miter', 'round', or 'bevel' (default: 'round')
    + * @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414) */
    +exports.polygon = function polygon(coordinates = [], options = {}) {
    +    // close polygon
    +    if (this._getDistance(coordinates[0], coordinates[coordinates.length - 1]) != 0) {
    +        coordinates.push(coordinates[0]);
    +    }
    +    let boundBox = [ /*minX, minY, maxX, maxY*/ ];
    +    coordinates.forEach((coord, index) => {
    +        if (index === 0) {
    +            boundBox = [coord[0], coord[1], coord[0], coord[1]];
    +        }
    +        boundBox[0] = (boundBox[0] > coord[0]) ? coord[0] : boundBox[0];
    +        boundBox[1] = (boundBox[1] > coord[1]) ? coord[1] : boundBox[1];
    +        boundBox[2] = (boundBox[2] < coord[0]) ? coord[0] : boundBox[2];
    +        boundBox[3] = (boundBox[3] < coord[1]) ? coord[1] : boundBox[3];
    +    });
    +
    +    const pathOptions = this._getPathOptions(options);
    +    let colorModel = pathOptions.colorModel;
    +    const margin = pathOptions.width * 2;
    +    const width  = Math.abs(boundBox[2] - boundBox[0]) + margin * 2;
    +    const height = Math.abs(boundBox[3] - boundBox[1]) + margin * 2;
    +    const startX = boundBox[0] - margin;
    +    const startY = boundBox[1] + height - margin;
    +    const { nx, ny } = this._calibrateCoordinate(startX, startY);
    +
    +    const drawPolygon = (context, coordinates) => {
    +        coordinates.forEach((coord, index) => {
    +            let nx = coord[0];
    +            let ny = coord[1];
    +            if (index === 0) {
    +                context.m(nx - startX,  startY -ny );
    +            } else {
    +                context.l(nx - startX,  startY -ny);
    +            }
    +        });
    +    };
    +
    +    const setPathOptions = (context) => {
    +        context
    +            .J(pathOptions.lineCap)
    +            .j(pathOptions.lineJoin)
    +            .d(pathOptions.dash, pathOptions.dashPhase)
    +            .M(pathOptions.miterLimit);
    +    };
    +
    +    pathOptions.originX = nx + width/2;  // default rotation point
    +    pathOptions.originY = ny + height/2;
    +
    +    if (options.fill) {
    +
    +        if (pathOptions.fill !== undefined) {
    +            colorModel = pathOptions.fillModel;
    +        }
    +
    +        this._drawObject(this, nx, ny, width, height, pathOptions, (ctx, xObject) => {
    +            ctx.gs(xObject.getGsName(pathOptions.fillGsId)),
    +            setPathOptions(ctx);
    +            xObject.fill(colorModel);
    +            drawPolygon(ctx, coordinates);
    +            ctx.f();
    +        });
    +    }
    +
    +    if (options.stroke || options.color || !options.fill) {
    +
    +        if (pathOptions.stroke !== undefined) {
    +            colorModel = pathOptions.strokeModel;
    +        }
    +
    +        this._drawObject(this, nx, ny, width, height, pathOptions, (ctx, xObject) => {
    +            ctx.w(pathOptions.width);
    +            setPathOptions(ctx);
    +            xObject.stroke(colorModel);
    +            drawPolygon(ctx, coordinates);
    +            ctx.s();
    +        });
    +    }
    +
    +    return this;
    +};
     
    @@ -159,7 +162,7 @@

    vector-polygon.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/docs/vector.js.html b/docs/vector.js.html index 43f6a90..45c971c 100644 --- a/docs/vector.js.html +++ b/docs/vector.js.html @@ -37,312 +37,313 @@

    vector.js

    -
    //  Table indicating how to specify coloration of elements
    -//  -------------------------------------------------------------------
    -// |Color | HexColor   | DecimalColor                   | PercentColor |
    -// |Space | (string)   | (array)                        | (string)     |
    -// |------+------------+--------------------------------+--------------|
    -// | Gray | #GG        | [gray]                         | %G           |
    -// |  RGB | #rrggbb    | [red, green, blue]             | %r,g,b       |
    -// | CMYK | #ccmmyykk  | [cyan, magenta, yellow, black] | %c,m,y,k     |
    -//  -------------------------------------------------------------------
    -//
    -//   HexColor component values (two hex digits) range from 00 to FF.
    -//   DecimalColor component values range from 0 to 255.
    -//   PercentColor component values range from 1 to 100.
    -
    -/**
    - * Draw a circle
    - * @name circle
    - * @function
    - * @memberof Recipe
    - * @param {number} x - The coordinate x
    - * @param {number} y - The coordinate y
    - * @param {number} radius - The radius
    - * @param {Object} [options] - The options
    - * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]}[ options.fill] - HexColor, PercentColor or DecimalColor
    - * @param {number} [options.lineWidth] - The line width
    - * @param {number} [options.opacity] - The opacity
    - * @param {number[]} [options.dash] - The dash style [number, number]
    - */
    -exports.circle = function circle(x, y, radius, options = {}) {
    -    const {
    -        nx,
    -        ny
    -    } = this._calibrateCoordinate(x, y);
    -    const diameter = radius * 2;
    -
    -    if (options.fill) {
    -        const pathOptions = this._getPathOptions(options, nx, ny);
    -        pathOptions.type = 'fill';
    -
    -        if (pathOptions.fill !== undefined) {
    -            pathOptions.color = pathOptions.fill;
    -            pathOptions.colorspace = pathOptions.fillModel.colorspace;
    -        }
    -
    -        this._drawObject(this, nx - radius, ny - radius, diameter, diameter, pathOptions, (ctx, xObject) => {
    -            ctx
    -                .gs(xObject.getGsName(pathOptions.fillGsId))
    -                .drawCircle(radius, radius, radius, pathOptions);
    -        });
    -    }
    -    if (options.stroke || options.color || !options.fill) {
    -        const pathOptions = this._getPathOptions(options);
    -        pathOptions.type = 'stroke';
    -
    -        if (pathOptions.stroke !== undefined) {
    -            pathOptions.color = pathOptions.stroke;
    -            pathOptions.colorspace = pathOptions.strokeModel.colorspace;
    -        }
    -
    -        // To honor the given width and height of the enclosing square ...
    -
    -        this._drawObject(this, nx - radius, ny - radius, diameter, diameter, pathOptions, (ctx) => {
    -            ctx
    -                .d(pathOptions.dash, pathOptions.dashPhase)
    -                .drawCircle(radius, radius, radius - pathOptions.width / 2, pathOptions);
    -
    -            // ... requires adjusting the internal drawing to accomodate line thickness.
    -        });
    -    }
    -    return this;
    -};
    -
    -/**
    - * Draw a rectangle
    - * @name rectangle
    - * @function
    - * @memberof Recipe
    - * @param {number} x - The coordinate x
    - * @param {number} y - The coordinate y
    - * @param {number} width - The width
    - * @param {number} height - The height
    - * @param {Object} [options] - The options
    - * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]} [options.fill] - HexColor, PercentColor or DecimalColor
    - * @param {number} [options.lineWidth] - The line width
    - * @param {number} [options.opacity] - The opacity
    - * @param {number[]} [options.dash] - The dash style [number, number]
    - * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
    - * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
    - */
    -exports.rectangle = function rectangle(x, y, width, height, options = {}) {
    -    const { nx, ny } = (options.useGivenCoords) ? { nx: x, ny: y } : this._calibrateCoordinate(x, y, 0, -height);
    -
    -    const pathOptions = this._getPathOptions(options, nx, ny);
    -    let colorModel = pathOptions.colorModel;
    -
    -    if (options.fill) {
    -        pathOptions.type = 'fill';
    -
    -        if (pathOptions.fill !== undefined) {
    -            pathOptions.color = pathOptions.fill;
    -            pathOptions.colorspace = pathOptions.fillModel.colorspace;
    -            colorModel = pathOptions.fillModel;
    -        }
    -
    -        this._drawObject(this, nx, ny, width, height, pathOptions, (ctx, xObject) => {
    -            ctx.gs(xObject.getGsName(pathOptions.fillGsId));
    -
    -            if (options.borderRadius) {
    -                xObject.fill(colorModel);
    -                drawRoundedRectangle(ctx, 0, 0, width, height, options.borderRadius);
    -                ctx.f();
    -            } else {
    -                ctx.drawRectangle(0, 0, width, height, pathOptions);
    -            }
    -        });
    -    }
    -
    -    if (options.stroke || options.color || !options.fill) {
    -        pathOptions.type = 'stroke';
    -
    -        if (pathOptions.stroke !== undefined) {
    -            pathOptions.color = pathOptions.stroke;
    -            pathOptions.colorspace = pathOptions.strokeModel.colorspace;
    -            colorModel = pathOptions.strokeModel;
    -        }
    -
    -        // To honor the given width and height of the rectangle ...
    -
    -        this._drawObject(this, nx, ny, width, height, pathOptions, (ctx, xObject) => {
    -
    -            // ... requires adjusting the internal drawing to accomodate line thickness.
    -            const margin = pathOptions.width;
    -
    -            if (options.borderRadius) {
    -                xObject.stroke(colorModel);
    -                ctx
    -                    .w(pathOptions.width)
    -                    .d(pathOptions.dash, pathOptions.dashPhase);
    -
    -                drawRoundedRectangle(ctx, margin / 2, margin / 2, width - margin, height - margin, options.borderRadius);
    -                ctx.S();
    -            } else {
    -                ctx
    -                    .d(pathOptions.dash, pathOptions.dashPhase)
    -                    .drawRectangle(margin / 2, margin / 2, width - margin, height - margin, pathOptions);
    -            }
    -        });
    -    }
    -
    -    return this;
    -};
    -
    -function drawRoundedRectangle(ctx, left, bottom, width, height, radii) {
    -    let radius = [];
    -
    -    // populate radius array accordingly.
    -    // Missing element value comes from opposite corner.
    -    if (typeof radii === 'number') {
    -        radius = new Array(4).fill(radii);
    -    } else if (Array.isArray(radii)) {
    -        switch (radii.length) {
    -            case 1:
    -                radius = new Array(4).fill(radii[0]);
    -                break;
    -            case 2:
    -                radius = radii.slice(0);
    -                radius[2] = radii[0];
    -                radius[3] = radii[1];
    -                break;
    -            case 3:
    -                radius = radii.slice(0);
    -                radius[3] = radii[1];
    -                break;
    -            case 4:
    -                radius = radii;
    -                break;
    -        }
    -    }
    -    const K = 0.551784;
    -    const right = left + width;
    -    const top = bottom + height;
    -    ctx
    -        .m(left, top - radius[0]) // top-left
    -        .c(left, top - radius[0] * (1 - K), left + radius[0] * (1 - K), top, left + radius[0], top)
    -
    -        .l(right - radius[1], top) // top-right
    -        .c(right - radius[1] * (1 - K), top, right, top - radius[1] * (1 - K), right, top - radius[1])
    -
    -        .l(right, bottom + radius[2]) // bottom-right
    -        .c(right, bottom + radius[2] * (1 - K), right - radius[2] * (1 - K), bottom, right - radius[2], bottom)
    -
    -        .l(left + radius[3], bottom) // bottom-left
    -        .c(left + radius[3] * (1 - K), bottom, left, bottom + radius[3] * (1 - K), left, bottom + radius[3])
    -
    -        .l(left, top - radius[0]); // back to top-left
    -}
    -
    -/**
    - * Draw an ellipse
    - * @name ellipse
    - * @function
    - * @memberof Recipe
    - * @param {number} cx x-coordinate of center point of ellipse
    - * @param {number} cy y-coordinate of center point of ellipse
    - * @param {number} rx radius length from the center point along x-axis
    - * @param {number} ry radius length from the center point along y-axis
    - * @param {Object} options
    - * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    - * @param {string|number[]}[ options.fill] - HexColor, PercentColor or DecimalColor
    - * @param {number} [options.lineWidth] - The line width
    - * @param {number} [options.opacity] - The opacity
    - * @param {number[]} [options.dash] - The dash style [number, number]
    - * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
    - * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
    - */
    -exports.ellipse = function ellipse(cx, cy, rx, ry, options = {}) {
    -    const {
    -        nx,
    -        ny
    -    } = this._calibrateCoordinate(cx, cy);
    -
    -    const pathOptions = this._getPathOptions(options, nx, ny);
    -    let colorModel = pathOptions.colorModel;
    -
    -    const width = rx * 2;
    -    const height = ry * 2;
    -
    -    const drawEllipse = (ctx, x, y, w, h) => {
    -        const magic = 0.551784; // from https://www.tinaja.com/glib/ellipse4.pdf
    -        const ox = rx * magic; // control point offset horizontal
    -        const oy = ry * magic; // control point offset horizontal
    -        const xe = x + w; // x-end, opposite corner from origin
    -        const ye = y + h; // y-end, opposite corner from origin
    -        const xm = rx; // x-middle of enclosing rectangle
    -        const ym = ry; // y-middle of enclosing rectangle
    -
    -        ctx
    -            .m(x, ym)
    -            .c(x, ym - oy, xm - ox, y, xm, y)
    -            .c(xm + ox, y, xe, ym - oy, xe, ym)
    -            .c(xe, ym + oy, xm + ox, ye, xm, ye)
    -            .c(xm - ox, ye, x, ym + oy, x, ym);
    -    };
    -
    -    if (options.fill) {
    -
    -        if (pathOptions.fill !== undefined) {
    -            colorModel = pathOptions.fillModel;
    -        }
    -
    -
    -        this._drawObject(this, nx - rx, ny - ry, width, height, pathOptions, (ctx, xObject) => {
    -            ctx.gs(xObject.getGsName(pathOptions.fillGsId));
    -            xObject.fill(colorModel);
    -            drawEllipse(ctx, 0, 0, width, height);
    -            ctx.f();
    -        });
    -    }
    -
    -    if (options.stroke || options.color || !options.fill) {
    -
    -        if (pathOptions.stroke !== undefined) {
    -            colorModel = pathOptions.strokeModel;
    -        }
    -
    -        // To honor the given width and height of the enclosing rectangle ...
    -
    -        this._drawObject(this, nx - rx, ny - ry, width, height, pathOptions, (ctx, xObject) => {
    -            const margin = pathOptions.width / 2;
    -            xObject.stroke(colorModel);
    -            ctx
    -                .w(pathOptions.width)
    -                .d(pathOptions.dash, pathOptions.dashPhase);
    -
    -            // ... requires adjusting the internal drawing to accomodate line thickness.
    -            drawEllipse(ctx, margin, margin, width - pathOptions.width, height - pathOptions.width);
    -            ctx.S();
    -        });
    -    }
    -    return this;
    -};
    -
    -exports.lineWidth = function lineWidth() {
    -    return this;
    -};
    -
    -exports.fillOpacity = function fillOpacity() {
    -    return this;
    -};
    -
    -exports.fill = function fill() {
    -    return this;
    -};
    -
    -exports.stroke = function stroke() {
    -    return this;
    -};
    -
    -exports.fillAndStroke = function fillAndStroke() {
    -    return this;
    -};
    +            
    //  Table indicating how to specify coloration of elements
    +//  -------------------------------------------------------------------
    +// |Color | HexColor   | DecimalColor                   | PercentColor |
    +// |Space | (string)   | (array)                        | (string)     |
    +// |------+------------+--------------------------------+--------------|
    +// | Gray | #GG        | [gray]                         | %G           |
    +// |  RGB | #rrggbb    | [red, green, blue]             | %r,g,b       |
    +// | CMYK | #ccmmyykk  | [cyan, magenta, yellow, black] | %c,m,y,k     |
    +//  -------------------------------------------------------------------
    +//
    +//   HexColor component values (two hex digits) range from 00 to FF.
    +//   DecimalColor component values range from 0 to 255.
    +//   PercentColor component values range from 1 to 100.
    +
    +/**
    + * Draw a circle
    + * @name circle
    + * @function
    + * @memberof Recipe
    + * @param {number} x - The coordinate x
    + * @param {number} y - The coordinate y
    + * @param {number} radius - The radius
    + * @param {Object} [options] - The options
    + * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]}[ options.fill] - HexColor, PercentColor or DecimalColor
    + * @param {number} [options.lineWidth] - The line width
    + * @param {number} [options.opacity] - The opacity
    + * @param {number[]} [options.dash] - The dash style [number, number]
    + */
    +exports.circle = function circle(x, y, radius, options = {}) {
    +    const {
    +        nx,
    +        ny
    +    } = this._calibrateCoordinate(x, y);
    +    const diameter = radius * 2;
    +
    +    if (options.fill) {
    +        const pathOptions = this._getPathOptions(options, nx, ny);
    +        pathOptions.type = 'fill';
    +
    +        if (pathOptions.fill !== undefined) {
    +            pathOptions.color = pathOptions.fill;
    +            pathOptions.colorspace = pathOptions.fillModel.colorspace;
    +        }
    +
    +        this._drawObject(this, nx - radius, ny - radius, diameter, diameter, pathOptions, (ctx, xObject) => {
    +            ctx
    +                .gs(xObject.getGsName(pathOptions.fillGsId))
    +                .drawCircle(radius, radius, radius, pathOptions);
    +        });
    +    }
    +    if (options.stroke || options.color || !options.fill) {
    +        const pathOptions = this._getPathOptions(options);
    +        pathOptions.type = 'stroke';
    +
    +        if (pathOptions.stroke !== undefined) {
    +            pathOptions.color = pathOptions.stroke;
    +            pathOptions.colorspace = pathOptions.strokeModel.colorspace;
    +        }
    +
    +        // To honor the given width and height of the enclosing square ...
    +
    +        this._drawObject(this, nx - radius, ny - radius, diameter, diameter, pathOptions, (ctx) => {
    +            ctx
    +                .d(pathOptions.dash, pathOptions.dashPhase)
    +                .drawCircle(radius, radius, radius - pathOptions.width / 2, pathOptions);
    +
    +            // ... requires adjusting the internal drawing to accomodate line thickness.
    +        });
    +    }
    +    return this;
    +};
    +
    +/**
    + * Draw a rectangle
    + * @name rectangle
    + * @function
    + * @memberof Recipe
    + * @param {number} x - The coordinate x
    + * @param {number} y - The coordinate y
    + * @param {number} width - The width
    + * @param {number} height - The height
    + * @param {Object} [options] - The options
    + * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]} [options.fill] - HexColor, PercentColor or DecimalColor
    + * @param {number} [options.lineWidth] - The line width
    + * @param {number} [options.opacity] - The opacity
    + * @param {number[]} [options.dash] - The dash style [number, number]
    + * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
    + * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
    + */
    +exports.rectangle = function rectangle(x, y, width, height, options = {}) {
    +    const { nx, ny } = (options.useGivenCoords) ? { nx: x, ny: y } : this._calibrateCoordinate(x, y, 0, -height);
    +
    +    const pathOptions = this._getPathOptions(options, nx, ny);
    +    let colorModel = pathOptions.colorModel;
    +    pathOptions.useGivenCoords = options.useGivenCoords;
    +
    +    if (options.fill) {
    +        pathOptions.type = 'fill';
    +
    +        if (pathOptions.fill !== undefined) {
    +            pathOptions.color = pathOptions.fill;
    +            pathOptions.colorspace = pathOptions.fillModel.colorspace;
    +            colorModel = pathOptions.fillModel;
    +        }
    +
    +        this._drawObject(this, nx, ny, width, height, pathOptions, (ctx, xObject) => {
    +            ctx.gs(xObject.getGsName(pathOptions.fillGsId));
    +
    +            if (options.borderRadius) {
    +                xObject.fill(colorModel);
    +                drawRoundedRectangle(ctx, 0, 0, width, height, options.borderRadius);
    +                ctx.f();
    +            } else {
    +                ctx.drawRectangle(0, 0, width, height, pathOptions);
    +            }
    +        });
    +    }
    +
    +    if (options.stroke || options.color || !options.fill) {
    +        pathOptions.type = 'stroke';
    +
    +        if (pathOptions.stroke !== undefined) {
    +            pathOptions.color = pathOptions.stroke;
    +            pathOptions.colorspace = pathOptions.strokeModel.colorspace;
    +            colorModel = pathOptions.strokeModel;
    +        }
    +
    +        // To honor the given width and height of the rectangle ...
    +
    +        this._drawObject(this, nx, ny, width, height, pathOptions, (ctx, xObject) => {
    +
    +            // ... requires adjusting the internal drawing to accomodate line thickness.
    +            const margin = pathOptions.width;
    +
    +            if (options.borderRadius) {
    +                xObject.stroke(colorModel);
    +                ctx
    +                    .w(pathOptions.width)
    +                    .d(pathOptions.dash, pathOptions.dashPhase);
    +
    +                drawRoundedRectangle(ctx, margin / 2, margin / 2, width - margin, height - margin, options.borderRadius);
    +                ctx.S();
    +            } else {
    +                ctx
    +                    .d(pathOptions.dash, pathOptions.dashPhase)
    +                    .drawRectangle(margin / 2, margin / 2, width - margin, height - margin, pathOptions);
    +            }
    +        });
    +    }
    +
    +    return this;
    +};
    +
    +function drawRoundedRectangle(ctx, left, bottom, width, height, radii) {
    +    let radius = [];
    +
    +    // populate radius array accordingly.
    +    // Missing element value comes from opposite corner.
    +    if (typeof radii === 'number') {
    +        radius = new Array(4).fill(radii);
    +    } else if (Array.isArray(radii)) {
    +        switch (radii.length) {
    +            case 1:
    +                radius = new Array(4).fill(radii[0]);
    +                break;
    +            case 2:
    +                radius = radii.slice(0);
    +                radius[2] = radii[0];
    +                radius[3] = radii[1];
    +                break;
    +            case 3:
    +                radius = radii.slice(0);
    +                radius[3] = radii[1];
    +                break;
    +            case 4:
    +                radius = radii;
    +                break;
    +        }
    +    }
    +    const K = 0.551784;
    +    const right = left + width;
    +    const top = bottom + height;
    +    ctx
    +        .m(left, top - radius[0]) // top-left
    +        .c(left, top - radius[0] * (1 - K), left + radius[0] * (1 - K), top, left + radius[0], top)
    +
    +        .l(right - radius[1], top) // top-right
    +        .c(right - radius[1] * (1 - K), top, right, top - radius[1] * (1 - K), right, top - radius[1])
    +
    +        .l(right, bottom + radius[2]) // bottom-right
    +        .c(right, bottom + radius[2] * (1 - K), right - radius[2] * (1 - K), bottom, right - radius[2], bottom)
    +
    +        .l(left + radius[3], bottom) // bottom-left
    +        .c(left + radius[3] * (1 - K), bottom, left, bottom + radius[3] * (1 - K), left, bottom + radius[3])
    +
    +        .l(left, top - radius[0]); // back to top-left
    +}
    +
    +/**
    + * Draw an ellipse
    + * @name ellipse
    + * @function
    + * @memberof Recipe
    + * @param {number} cx x-coordinate of center point of ellipse
    + * @param {number} cy y-coordinate of center point of ellipse
    + * @param {number} rx radius length from the center point along x-axis
    + * @param {number} ry radius length from the center point along y-axis
    + * @param {Object} options
    + * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
    + * @param {string|number[]}[ options.fill] - HexColor, PercentColor or DecimalColor
    + * @param {number} [options.lineWidth] - The line width
    + * @param {number} [options.opacity] - The opacity
    + * @param {number[]} [options.dash] - The dash style [number, number]
    + * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
    + * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
    + */
    +exports.ellipse = function ellipse(cx, cy, rx, ry, options = {}) {
    +    const {
    +        nx,
    +        ny
    +    } = this._calibrateCoordinate(cx, cy);
    +
    +    const pathOptions = this._getPathOptions(options, nx, ny);
    +    let colorModel = pathOptions.colorModel;
    +
    +    const width = rx * 2;
    +    const height = ry * 2;
    +
    +    const drawEllipse = (ctx, x, y, w, h) => {
    +        const magic = 0.551784; // from https://www.tinaja.com/glib/ellipse4.pdf
    +        const ox = rx * magic; // control point offset horizontal
    +        const oy = ry * magic; // control point offset horizontal
    +        const xe = x + w; // x-end, opposite corner from origin
    +        const ye = y + h; // y-end, opposite corner from origin
    +        const xm = rx; // x-middle of enclosing rectangle
    +        const ym = ry; // y-middle of enclosing rectangle
    +
    +        ctx
    +            .m(x, ym)
    +            .c(x, ym - oy, xm - ox, y, xm, y)
    +            .c(xm + ox, y, xe, ym - oy, xe, ym)
    +            .c(xe, ym + oy, xm + ox, ye, xm, ye)
    +            .c(xm - ox, ye, x, ym + oy, x, ym);
    +    };
    +
    +    if (options.fill) {
    +
    +        if (pathOptions.fill !== undefined) {
    +            colorModel = pathOptions.fillModel;
    +        }
    +
    +
    +        this._drawObject(this, nx - rx, ny - ry, width, height, pathOptions, (ctx, xObject) => {
    +            ctx.gs(xObject.getGsName(pathOptions.fillGsId));
    +            xObject.fill(colorModel);
    +            drawEllipse(ctx, 0, 0, width, height);
    +            ctx.f();
    +        });
    +    }
    +
    +    if (options.stroke || options.color || !options.fill) {
    +
    +        if (pathOptions.stroke !== undefined) {
    +            colorModel = pathOptions.strokeModel;
    +        }
    +
    +        // To honor the given width and height of the enclosing rectangle ...
    +
    +        this._drawObject(this, nx - rx, ny - ry, width, height, pathOptions, (ctx, xObject) => {
    +            const margin = pathOptions.width / 2;
    +            xObject.stroke(colorModel);
    +            ctx
    +                .w(pathOptions.width)
    +                .d(pathOptions.dash, pathOptions.dashPhase);
    +
    +            // ... requires adjusting the internal drawing to accomodate line thickness.
    +            drawEllipse(ctx, margin, margin, width - pathOptions.width, height - pathOptions.width);
    +            ctx.S();
    +        });
    +    }
    +    return this;
    +};
    +
    +exports.lineWidth = function lineWidth() {
    +    return this;
    +};
    +
    +exports.fillOpacity = function fillOpacity() {
    +    return this;
    +};
    +
    +exports.fill = function fill() {
    +    return this;
    +};
    +
    +exports.stroke = function stroke() {
    +    return this;
    +};
    +
    +exports.fillAndStroke = function fillAndStroke() {
    +    return this;
    +};
     
    @@ -355,7 +356,7 @@

    vector.js


    - Documentation generated by JSDoc 3.6.3 on Wed Oct 02 2019 12:00:26 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme.
    diff --git a/lib/text.helper.js b/lib/text.helper.js index 516f52b..eea39b5 100644 --- a/lib/text.helper.js +++ b/lib/text.helper.js @@ -2,6 +2,7 @@ exports.Word = class Word { constructor(word, pathOptions) { this._value = word; this._pathOptions = pathOptions; + this._text = (word === ' ') ? 'o' : word; // allows space to get an actual dimension } get value() { @@ -13,7 +14,7 @@ exports.Word = class Word { return this._dimensions; } this._dimensions = this._pathOptions.font.calculateTextDimensions( - this._value, this._pathOptions.size + this._text, this._pathOptions.size ); return this._dimensions; } @@ -36,7 +37,7 @@ exports.Line = class Line { const tempValue = this.value + wordObject.value; const toWidth = this._pathOptions.font.calculateTextDimensions( tempValue, this.size - ).width; + ).xMax; return (toWidth <= this.width); } @@ -45,6 +46,10 @@ exports.Line = class Line { this.addWord(wordObject); } + get words() { + return this.wordObjects; + } + get spaceWidth() { return this._pathOptions.font.calculateTextDimensions( 'o', this.size @@ -67,7 +72,7 @@ exports.Line = class Line { get textWidth() { return this.wordObjects.reduce((width, word) => { - width += word.dimensions.width; + width += word.dimensions.xMax; return width; }, 0); } @@ -88,44 +93,6 @@ exports.Line = class Line { } }; -exports._getTextOffset = function _getTextOffset(text = '', options = {}) { - if (!options.size || !options.font) { - return; - } - let offsetX = 0; - let offsetY = 0; - const textDimensions = options.font.calculateTextDimensions(text, options.size); - if (options.align) { - const alignments = options.align.split(' '); - if (alignments[0]) { - switch (alignments[0]) { - case 'center': - offsetX = -1 * textDimensions.width / 2; - break; - case 'right': - offsetX = textDimensions.width / 2; - break; - default: - } - } - if (alignments[1]) { - switch (alignments[1]) { - case 'center': - offsetY = -1 * textDimensions.yMax / 2; - break; - case 'bottom': - offsetY = textDimensions.yMax / 2; - break; - default: - } - } - } - return { - offsetX, - offsetY - }; -}; - /** * @todo handle page margin and padding */ diff --git a/lib/text.js b/lib/text.js index 6d0ddb6..cdb632d 100644 --- a/lib/text.js +++ b/lib/text.js @@ -33,28 +33,30 @@ const xObjectForm = require('./xObjectForm'); * @param {number} y - The coordinate y * @param {Object} [options] - The options * @param {string|number[]} [options.color] - Text color (HexColor, PercentColor or DecimalColor) - * @param {number} [options.opacity] - opacity - * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0 - * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: the text coordinate - * @param {string} [options.font] - The font. 'Arial', 'Helvetica'... - * @param {number} [options.size] - The line width - * @param {string} [options.align] - The alignment. 'center center'... + * @param {number} [options.opacity=1] - opacity + * @param {number} [options.rotation=0] - Accept: +/- 0 through 360. + * @param {number[]} [options.rotationOrigin=[x,y]] - [originX, originY] + * @param {string} [options.font=Helvetica] - The font. 'Arial', 'Helvetica'... + * @param {number} [options.size=14] - The font size + * @param {string} [options.align='left top'] - The alignment. 'center center'... * @param {Object|Boolean} [options.highlight] - Text markup annotation. * @param {Object|Boolean} [options.underline] - Text markup annotation. * @param {Object|Boolean} [options.strikeOut] - Text markup annotation. * @param {Object} [options.textBox] - Text Box to fit in. - * @param {number} [options.textBox.width] - Text Box width + * @param {number} [options.textBox.width=100] - Text Box width * @param {number} [options.textBox.height] - Text Box fixed height - * @param {number} [options.textBox.minHeight] - Text Box minimum height - * @param {number|number[]} [options.textBox.padding] - Text Box padding, [top, right, bottom, left] - * @param {number} [options.textBox.lineHeight] - Text Box line height - * @param {string} [options.textBox.textAlign] - Text alignment inside text box + * @param {number} [options.textBox.minHeight=0] - Text Box minimum height + * @param {number|number[]} [options.textBox.padding=0] - Text Box padding, [top, right, bottom, left] + * @param {number} [options.textBox.lineHeight=0] - Text Box line height + * @param {string} [options.textBox.wrap='auto'] - Text wrapping mechanism, 'auto', 'clip', 'trim'. + * @param {string} [options.textBox.textAlign='left top'] - Alignment inside text box, specified as 'horizontal vertical', + * where horizontal is one of: 'left', 'center', 'right', 'justify' and veritical is one of: 'top', 'center', 'bottom'. * @param {Object} [options.textBox.style] - Text Box styles - * @param {number} [options.textBox.style.lineWidth] - Text Box border width + * @param {number} [options.textBox.style.lineWidth=2] - Text Box border width * @param {string|number[]} [options.textBox.style.stroke] - Text Box border color (HexColor, PercentColor or DecimalColor) - * @param {number[]} [options.textBox.style.dash] - Text Box border border dash style [number, number] + * @param {number[]} [options.textBox.style.dash=[]] - Text Box border border dash style [number, number] * @param {string|number[]} [options.textBox.style.fill] - Text Box border background color (HexColor, PercentColor or DecimalColor) - * @param {number} [options.textBox.style.opacity] - Text Box border background opacity + * @param {number} [options.textBox.style.opacity=1] - Text Box border background opacity */ exports.text = function text(text = '', x, y, options = {}) { if (!this.pageContext) { @@ -86,7 +88,8 @@ exports.text = function text(text = '', x, y, options = {}) { width: null, lineHeight: 0, padding: 0, - minHeight: 0 + minHeight: 0, + wrap: 'auto' } : { width: options.textBox.width || 100, lineHeight: options.textBox.lineHeight, @@ -94,7 +97,8 @@ exports.text = function text(text = '', x, y, options = {}) { padding: (options.textBox.padding || 0), minHeight: options.textBox.minHeight || 0, style: options.textBox.style, - textAlign: options.textBox.textAlign + textAlign: options.textBox.textAlign, + wrap: (options.textBox.wrap !== undefined) ? options.textBox.wrap : 'auto' }; // Allows user to enter a single number which will be used for all text box sides, @@ -116,13 +120,9 @@ exports.text = function text(text = '', x, y, options = {}) { }); let firstLineHeight; - // let lastLineHeight; - let totalHeight = 0; let toWriteTextObjects = []; - // const layerIndex = {}; - const writeValue = (textObject) => { textObject.lineID = textObject.lineID || Date.now() * Math.random(); textObject.lineID = (textObject.needsLineBreaker) ? Date.now() * Math.random() : textObject.lineID; @@ -211,12 +211,14 @@ exports.text = function text(text = '', x, y, options = {}) { ny } = this._calibrateCoordinate(x, y, offsetX, offsetY); + let textYpos = ny; + if (textBox.style) { const textBoxWidth = textBox.width + textBox.paddingLeft + textBox.paddingRight; + const actualTextHeight = textBox.firstLineHeight * (toWriteTextObjects.length-1) + textBox.lastLineHeight if (!textBox.height) { - textBox.height = textBox.firstLineHeight * (toWriteTextObjects.length-1) - + textBox.paddingTop + textBox.paddingBottom + textBox.lastLineHeight; + textBox.height = actualTextHeight + textBox.paddingTop + textBox.paddingBottom; if (textBox.minHeight && textBox.minHeight > textBox.height) { textBox.height = textBox.minHeight; @@ -232,10 +234,21 @@ exports.text = function text(text = '', x, y, options = {}) { rotationOrigin: [pathOptions.originX,pathOptions.originY], }) ); + + // Determine vertical starting position of text within textBox + switch (toWriteTextObjects[0].writeOptions.alignVertical) { + case 'center': + textYpos -= (textBox.height - actualTextHeight) / 2; + break; + case 'bottom': + textYpos -= (textBox.height - actualTextHeight); + break; + } } + const context = this.pageContext; // const space = 8; - let currentY = ny; + let currentY = textYpos; let currentLineID; let currentLineWidth = 0; let toWriteContents = []; @@ -253,43 +266,77 @@ exports.text = function text(text = '', x, y, options = {}) { const isContinued = (currentLineID == lineID) ? true : false; const getStartX = (startX, currentWriteOptions) => { - let x = startX; - let offsetX = 0; - if (currentWriteOptions.align == 'center') { - offsetX = (textBox.width - currentLineWidth) / 2; - } else - if (currentWriteOptions.align == 'right') { - offsetX = (textBox.width - textBox.paddingRight - currentLineWidth); - } else { - offsetX = textBox.paddingLeft; + let offsetX = textBox.paddingLeft; + + switch (currentWriteOptions.alignHorizontal) { + case 'center': + offsetX = (textBox.width - currentLineWidth) / 2; + break; + case 'right': + offsetX = (textBox.width - textBox.paddingRight - currentLineWidth); + break; } - return x + offsetX; + + return startX + offsetX; }; const writeText = (context, x, y, wto) => { const options = wto.writeOptions; - const {lineHeight, lineWidth, text, baseline} = wto; + const {lineHeight, text, baseline, wordsInLine, textWidth} = wto; // write directly to page when not dealing with opacity, rotation and special colorspace. if (options.opacity === 1 && options.colorspace !== 'separation' && (options.rotation === 0 || options.rotation === undefined)) { - context.writeText(text, x, y+baseline, options); + + // Note that the last line of a text box ignores justification. + if (options.alignHorizontal !== 'justify' || wto.lastLine) { + if (textBox.wrap !== 'auto') { // This applies a clipping region around the text + this.pageContext.q(); + this.pageContext + .m(nx,y+lineHeight) + .l(nx+textBox.width,y+lineHeight) + .l(nx+textBox.width,y) + .l(nx,y) + .h().W().n(); + } + + context.writeText(text, x, y+baseline, options); + + if (textBox.wrap !== 'auto') { + this.pageContext.Q(); + } + } else { + justify(x, wordsInLine, textBox, textWidth, (word, xx) => { + context.writeText(word, xx, y+baseline, options); + }); + } } else { this.pauseContext(); // https://github.com/galkahana/HummusJS/wiki/Use-the-pdf-drawing-operators - const xObject = new xObjectForm(this.writer, lineWidth * 1.5, lineHeight); + const xObject = new xObjectForm(this.writer, textBox.width, lineHeight); const xObjectCtx = xObject.getContentContext(); // Build form object xObjectCtx .q() .gs(xObject.getGsName(options.fillGsId)) // set graphic state (here opacity) .BT(); // begin text context + xObjectCtx.Tf(options.font, options.size); // set font xObject.fill(options.colorModel); // set color + + if (options.alignHorizontal !== 'justify' || wto.lastLine) { + xObjectCtx + .Tm(1, 0, 0, 1, 0, baseline) // set position in object + .Tj(text); // write text + } else { + justify(0, wordsInLine, textBox, textWidth, (word, xx) => { + xObjectCtx + .Tm(1, 0, 0, 1, xx, baseline) // set position in object + .Tj(word); // write word + }); + } + xObjectCtx - .Tf(options.font, options.size) // set font - .Tm(1, 0, 0, 1, 0, baseline) // set position in object - .Tj(text) // write text .ET() // end text context .Q(); xObject.end(); @@ -359,6 +406,41 @@ exports.text = function text(text = '', x, y, options = {}) { return this; }; +function justify (x, wordsInLine, textBox, textWidth, callback) { + // For some reason, textWidth is smaller than lineWidth. My suspicions lie in the fact + // that spacing computations appear different depending on where the space is located. + // What is noted though that if lineWidth is used in the calculations for text + // justitification, the text goes passed the right boundary. Due to the vagary in space + // computation, the final wrinkle to make sure the last word in the line smacks up against + // the right side boundary is to perform a special computation on the last word positioning + // relative to that right side bounds. + const spaceCount = wordsInLine.length-1; + const spaceBetweenWords = ( wordsInLine.length > 1) ? (textBox.width - textWidth) / (spaceCount) : 0; + const lineStart = x; + + for (let index = 0; index < wordsInLine.length; index++) { + const word = wordsInLine[index]; + + callback(word.value, x) + // Ready to compute last word spacing? + if (index+1 === spaceCount) { + x = lineStart + textBox.width - textBox.paddingRight - wordsInLine[index+1].dimensions.xMax; + } else { + x += word.dimensions.width + spaceBetweenWords; + } + } +} + +function nextWord (text, brk, previousPosition, pathOptions) { + let nextWord = text.slice(previousPosition, brk.position); + + if (brk.required) { // effectively saw a '\n' in text. + nextWord = nextWord.trim(); + } + + return new Word(nextWord, pathOptions); +} + function getToWriteTextObjects(textObject = {}, pathOptions, textBox = {}) { const toWriteTextObjects = []; let text = ((textObject.prependValue) ? textObject.prependValue : '') + @@ -387,66 +469,88 @@ function getToWriteTextObjects(textObject = {}, pathOptions, textBox = {}) { let last = 0; let bk = breaker.nextBreak(); let previousWord; + let flushLine = false; for (let i = 0; i < indent; i++) { - newLine.addWord(new Word(' ')); + newLine.addWord(new Word(' ', pathOptions)); } while (bk) { - let nextWord = text.slice(last, bk.position); - /** - * Author: silverma (Marc Silverman) - * #29 Is it possible to add multi-line text? - * https://github.com/chunyenHuang/hummusRecipe/issues/29 - */ - if (bk.required) { - nextWord = nextWord.trim(); - } + let word = nextWord(text, bk, last, pathOptions); - const word = new Word(nextWord, pathOptions); if (newLine.canFit(word)) { newLine.addWord(word); } else { // remove any trailing space on previous word so right justification works appropriately - if ( previousWord ) { + if ( previousWord && textBox.wrap === 'auto') { newLine.replaceLastWord(new Word(previousWord.value.trim(), pathOptions)); } lines.push(newLine); - newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions); - for (let i = 0; i < indent; i++) { - newLine.addWord(new Word(' ')); - } - if (textObject.prependValue) { - const space = Array(textObject.prependValue.length + 1).fill(' ').join(''); - newLine.addWord(new Word(space)); + + // now deal with text line wrap (what happens to text that doesn't fit in line) + if (textBox.wrap !== 'auto') { + flushLine = true; + if (textBox.wrap === 'clip') { + newLine.addWord(word); + } + } else { + // this is the auto wrap section + newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions); + for (let i = 0; i < indent; i++) { + newLine.addWord(new Word(' ', pathOptions)); + } + if (textObject.prependValue) { + const space = Array(textObject.prependValue.length + 1).fill(' ').join(''); + newLine.addWord(new Word(space, pathOptions)); + } + newLine.addWord(word); } - newLine.addWord(word); } - /** - * Author: silverma (Marc Silverman) - * #29 Is it possible to add multi-line text? - * https://github.com/chunyenHuang/hummusRecipe/issues/29 - */ - if (bk.required) { - lines.push(newLine); - newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions); + if (flushLine) { + while (bk) { + bk = breaker.nextBreak(); + if (bk && bk.required) { + flushLine = false; + newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions); + word = null; + break; + } + } + } else { + /** + * Author: silverma (Marc Silverman) + * #29 Is it possible to add multi-line text? + * https://github.com/chunyenHuang/hummusRecipe/issues/29 + */ + if (bk.required) { + lines.push(newLine); + newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions); + } + } + + if (bk) { + previousWord = word; + last = bk.position; + bk = breaker.nextBreak(); } - previousWord = word; - last = bk.position; - bk = breaker.nextBreak(); } - lines.push(newLine); + + if (!flushLine) { + lines.push(newLine); + } const lineHeightMargin = 0; let lineHeight = (textHeight + lineHeightMargin >= textBox.lineHeight) ? textHeight + lineHeightMargin : textBox.lineHeight; + let [alignHorizontal, alignVertical] = (textBox.textAlign) ? textBox.textAlign.split(' ') : []; const writeOptions = Object.assign({}, pathOptions, { color: textObject.styles.color, opacity: parseFloat(textObject.styles.opacity || pathOptions.opacity || 1), underline: textObject.underline || pathOptions.underline, size: textObject.size, - align: textBox.textAlign, - font: (textObject.isBold && textObject.isItalic) ? pathOptions.fonts.boldItalic : (textObject.isItalic) ? pathOptions.fonts.italic : (textObject.isBold) ? pathOptions.fonts.bold : pathOptions.font + alignHorizontal: alignHorizontal, + alignVertical: alignVertical, + font: pathOptions.font }); let paragraphHeight = 0; lines.forEach((line, index) => { @@ -461,7 +565,10 @@ function getToWriteTextObjects(textObject = {}, pathOptions, textBox = {}) { lineHeight, baseline: lineHeight - textBox.baselineHeight, lineWidth: line.currentWidth, + textWidth: line.textWidth, // for justification spaceWidth: (pathOptions.html) ? line.spaceWidth : 0, + wordsInLine: line.words, + lastLine: (index === lines.length-1), writeOptions }); paragraphHeight += lineHeight; diff --git a/lib/vector-line.js b/lib/vector-line.js index d321b0d..0abb171 100644 --- a/lib/vector-line.js +++ b/lib/vector-line.js @@ -25,10 +25,14 @@ exports.moveTo = function moveTo(x, y) { * @param {Object} [options] - The options * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor - * @param {string|number[]} [options.fill] - HexColor, PercentColor or DecimalColor * @param {number} [options.lineWidth] - The line width - * @param {number} [options.opacity] - The opacity - * @param {number[]} [options.dash] - The dash style [number, number] + * @param {number} [options.opacity] - how transparent should line be, from 0: invisible to 1: opaque + * @param {number[]} [options.dash] - The dash pattern [dashSize, gapSize] or [dashAndGapSize] + * @param {number} [options.dashPhase] - distance into dash pattern at which to start dash (default: 0, immediately) + * @param {string} [options.lineCap] - open line end style, 'butt', 'round', or 'square' (default: 'round') + * @param {string} [options.lineJoin] - joined line end style, 'miter', 'round', or 'bevel' (default: 'round') + * @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414) + * */ exports.lineTo = function lineTo(x, y, options = {}) { const fromX = this._position.x; @@ -45,9 +49,10 @@ exports.lineTo = function lineTo(x, y, options = {}) { context .q() - .J(1) - .j(1) + .J(pathOptions.lineCap) + .j(pathOptions.lineJoin) .d(pathOptions.dash, pathOptions.dashPhase) + .M(pathOptions.miterLimit) .drawPath(fromX, fromY, nx, ny, pathOptions) .Q(); this.moveTo(x, y); @@ -64,8 +69,12 @@ exports.lineTo = function lineTo(x, y, options = {}) { * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor * @param {number} [options.lineWidth] - The line width - * @param {number[]} [options.dash] - The dash style [number, number] - */ + * @param {number} [options.opacity] - how transparent should line be, from 0: invisible to 1: opaque + * @param {number[]} [options.dash] - The dash pattern [dashSize, gapSize] or [dashAndGapSize] + * @param {number} [options.dashPhase] - distance into dash pattern at which to start dash (default: 0, immediately) + * @param {string} [options.lineCap] - open line end style, 'butt', 'round', or 'square' (default: 'round') + * @param {string} [options.lineJoin] - joined line end style, 'miter', 'round', or 'bevel' (default: 'round') + * @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414)*/ exports.line = function line(coordinates = [], options = {}) { coordinates.forEach((coordinate, index) => { if (index === 0) { diff --git a/lib/vector-polygon.js b/lib/vector-polygon.js index ce77401..c7b66b4 100644 --- a/lib/vector-polygon.js +++ b/lib/vector-polygon.js @@ -24,10 +24,13 @@ * @param {string|number[]} [options.fill] - HexColor, PercentColor or DecimalColor * @param {number} [options.lineWidth] - The line width * @param {number} [options.opacity] - The opacity - * @param {number[]} [options.dash] - The dash style [number, number] + * @param {number[]} [options.dash] - The dash pattern [dashSize, gapSize] or [dashAndGapSize] + * @param {number} [options.dashPhase] - distance into dash pattern at which to start dash (default: 0, immediately) * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0 * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y - */ + * @param {string} [options.lineCap] - open line end style, 'butt', 'round', or 'square' (default: 'round') + * @param {string} [options.lineJoin] - joined line end style, 'miter', 'round', or 'bevel' (default: 'round') + * @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414) */ exports.polygon = function polygon(coordinates = [], options = {}) { // close polygon if (this._getDistance(coordinates[0], coordinates[coordinates.length - 1]) != 0) { @@ -67,10 +70,10 @@ exports.polygon = function polygon(coordinates = [], options = {}) { const setPathOptions = (context) => { context - .J(1) - .j(1) + .J(pathOptions.lineCap) + .j(pathOptions.lineJoin) .d(pathOptions.dash, pathOptions.dashPhase) - .M(1.414); + .M(pathOptions.miterLimit); }; pathOptions.originX = nx + width/2; // default rotation point diff --git a/lib/vector.helper.js b/lib/vector.helper.js index fbf5ebc..4eb8a16 100644 --- a/lib/vector.helper.js +++ b/lib/vector.helper.js @@ -21,15 +21,26 @@ exports._getPathOptions = function _getPathOptions(options = {}, originX, origin colorspace, colorName, colorArray: [], + lineCap: this._lineCap(), + lineJoin: this._lineJoin(), + miterLimit: 1.414, width: 2, align: options.align }; + if (options.font) { const matchedFont = this.fonts[options.font.toLowerCase()]; if (matchedFont) { pathOptions.font = this.writer.getFontForFile(matchedFont); } + } else { // default font used, now have to make final decision based on bold/italic considerations. + // Note, if this is not done explicitly, the font dimensions will be incorrect. + pathOptions.font = + (options.bold && options.italic) ? pathOptions.fonts.boldItalic : + (options.italic) ? pathOptions.fonts.italic : + (options.bold) ? pathOptions.fonts.bold : pathOptions.font; } + if (options.opacity == void(0) || isNaN(options.opacity)) { options.opacity = 1; } else { @@ -86,13 +97,28 @@ exports._getPathOptions = function _getPathOptions(options = {}, originX, origin pathOptions.skewY = options.skewY; } - // Page 127 + // Page 127 of PDF 1.7 specification pathOptions.dash = (Array.isArray(options.dash)) ? options.dash : []; pathOptions.dashPhase = (!isNaN(options.dashPhase)) ? options.dashPhase : 0; if (pathOptions.dash[0] == 0 && pathOptions.dash[1] == 0) { - pathOptions.dash = []; + pathOptions.dash = []; // no dash, solid unbroken line pathOptions.dashPhase = 0; } + + // Page 125-126 of PDF 1.7 specification + if (options.lineJoin !== void(0)) { + pathOptions.lineJoin = this._lineJoin(options.lineJoin); + } + if (options.lineCap !== void(0)) { + pathOptions.lineCap = this._lineCap(options.lineCap); + } + + if (options.miterLimit !== void(0)) { + if (!isNaN(options.miterLimit)) { + pathOptions.miterLimit = options.miterLimit; + } + } + return pathOptions; }; @@ -203,3 +229,29 @@ exports._drawObject = function _drawObject(self, x, y, width, height, options, c .doXObject(xObject) .Q(); }; + +exports._lineCap = function _lineCap (type) { + const round = 1; + let cap = round; + + if (type) { + const capStyle = ['butt', 'round', 'square']; + const capType = capStyle.indexOf(type); + cap = (capType !== -1) ? capType : round; + } + + return cap; +} + +exports._lineJoin = function _lineJoin (type) { + const round = 1; + let join = round; + + if (type) { + const joinStyle = ['miter', 'round', 'bevel']; + const joinType = joinStyle.indexOf(type); + join = (joinType !== -1) ? joinType : round; + } + + return join; +} diff --git a/tests/text-centering.js b/tests/text-centering.js index c1a3d0b..d2eb8c0 100644 --- a/tests/text-centering.js +++ b/tests/text-centering.js @@ -66,7 +66,7 @@ describe('Text - Centering', () => { .text('A\nAAA\nOOO\nVVV\n{0}', 30, 260, { color: '#000000', font: 'Arial', - size: 30, + size: 20, textBox: { width: 300, textAlign: 'center', @@ -75,26 +75,28 @@ describe('Text - Centering', () => { fill:'#ffff00' } }, - }) + }); - .text('<-- Note, using padding of\n 2 puts dots inside fill.', 340, 475) - .text('<-- Text with line height applied\n (1.16% font size).', 340, 525) - .text('A\nAAA\nOOO\nVVV\n|', 30, 425, { + let fs = 20; + recipe + .text('<-- Note, using padding of 2\n puts marker dots inside fill.', 340, 455) + .text('<-- Text with line height applied\n (1.16% font size).', 340, 490) + .text('A\nAAA\nOOO\nVVV\n|', 30, 400, { color: '#000000', font: 'Arial', - size: 30, + size: fs, textBox: { width: 300, textAlign: 'center', padding: 2, - lineHeight: 1.16*30, // percentage of font size + lineHeight: 1.16*fs, // percentage of font size style: { fill:'default' } }, }) - .circle(30,425,2,{stroke:'red'}) - .circle(330,425,2,{stroke:'red'}) + .circle(30,400,2,{stroke:'red'}) + .circle(330,400,2,{stroke:'red'}) .line([[180,25],[180,425]],{stroke:'#ff00ff',lineWidth:.5}); @@ -145,7 +147,7 @@ describe('Text - Centering', () => { } } }) - .line([[x-5,y],[x+w+5,y]], {lineWidth: .5, stroke: 'red'}) + .line([[x-5,y],[x+w+5,y]], {lineWidth: .5, stroke: 'red', lineCap: 'butt'}) .circle(x,y,2,{stroke:'red'}); y = 400; @@ -164,7 +166,7 @@ describe('Text - Centering', () => { } } }) - .line([[x-5,y+h],[x+w+5,y+h]], {lineWidth: .5, stroke: "#ff0000"}) + .line([[x-5,y+h],[x+w+5,y+h]], {lineWidth: .5, stroke: "red", lineCap: 'butt'}) .circle(x,y,2,{stroke:'red'}); x = 500; @@ -184,7 +186,7 @@ describe('Text - Centering', () => { } } }) - .line([[x+w,y-5],[x+w,y+h+5]], {lineWidth: .5, stroke: 'red'}) + .line([[x+w,y-5],[x+w,y+h+5]], {lineWidth: .5, stroke: 'red', lineCap: 'butt'}) .circle(x,y,2,{stroke:'red'}); x = 400; @@ -203,14 +205,61 @@ describe('Text - Centering', () => { } } }) - .line([[x,y-5],[x,y+h+5]], {lineWidth: .5, stroke: 'red'}) + .line([[x,y-5],[x,y+h+5]], {lineWidth: .5, stroke: 'red', lineCap: 'butt'}) .circle(x,y,2,{stroke:'red'}); + recipe + .text('Using horizontal "justify" setting with default "top" vertical setting, no height.', 40, 535, { + color: '#000000', + textBox: { + width: 130, + textAlign: 'justify', + style: { + stroke: '#ffff00', + lineWidth:.5, + } + } + }) + + .text('... now with "justify center" setting and specific height', 180, 535, { + textBox: { + width: 130, + height: 70, + textAlign: 'justify center', + style: { + stroke: '#ff0000', + lineWidth:.5, + } + } + }) + + .text('... finally, using "justify bottom" setting with specific height', 320, 535, { + color: 'blue', + textBox: { + width: 140, + height: 70, + textAlign: 'justify bottom', + style: { + stroke: '#ff00ff', + lineWidth:.5, + } + } + }) + + .text('Oh, wait a minute. How about a justification view without any box drawn. It gives you the sense of an old fashioned newspaper column.', 470, 535, { + color: '#000000', + size: 10, + textBox: { + width: 120, + height: 70, + textAlign: 'justify', + } + }); h = 70; recipe .circle(40,625,2,{stroke:'red'}) - .text(`text box without\nminHeight\n setting`, 40, 625, { + .text(`text box without\nheight or\nminHeight\n setting\nauto fits text`, 40, 625, { color: '#000000', textBox: { width: 100, @@ -223,19 +272,94 @@ describe('Text - Centering', () => { }) .circle(150,625,2,{stroke:'red'}) .circle(150,625+h,2,{stroke:'red'}) - .text(`minHeight: ${h}`, 150, 625, { + .text(`minHeight: ${h}\nBox will not collapse when not enough text to fill it. It will auto grow when text exceeds minHeight.`, 150, 625, { color: '#000000', + size: 9, textBox: { width: 100, + wrap: 'trim', minHeight: h, - textAlign: 'center', + // textAlign: 'right', style: { stroke: '#000000', lineWidth:.5, } } }) - + .text('text at\ncenter center', 265, 625,{ + color: '#000000', + textBox: { + width: 100, + minHeight: h, + textAlign: 'center center', + style: { + stroke: '#000000', + lineWidth:.5, + } + } + }) + .text('text at\ncenter bottom', 380, 625,{ + color: '#000000', + textBox: { + width: 100, + minHeight: h, + textAlign: 'center bottom', + style: { + stroke: '#000000', + lineWidth:.5, + } + } + }) + .text('text at\nright bottom', 500, 625,{ + color: '#000000', + textBox: { + width: 100, + minHeight: h, + textAlign: 'right bottom', + style: { + stroke: '#000000', + lineWidth:.5, + } + } + }) + .text('This is what happens when you use "height" and have too much text', 40, 725,{ + color: '#000000', + textBox: { + width: 125, + height: 40, + // textAlign: 'right bottom', + // textAlign: 'justify', + style: { + stroke: '#000000', + lineWidth:.5, + } + } + }) + .text('Using height and supplying textAlign: center center', 180, 725,{ + color: '#000000', + textBox: { + width: 120, + height: 40, + textAlign: 'center center', + style: { + stroke: '#000000', + lineWidth:.5, + } + } + }) + .text('Using "height" and supplying textAlign: right bottom', 310, 725,{ + color: '#000000', + textBox: { + width: 120, + height: 40, + textAlign: 'right bottom', + style: { + stroke: '#000000', + lineWidth:.5, + } + } + }) + recipe.endPage(); recipe.endPDF(done); }); diff --git a/tests/vector.js b/tests/vector.js index eb3d8cb..7777dfd 100644 --- a/tests/vector.js +++ b/tests/vector.js @@ -64,12 +64,12 @@ describe('Vector', () => { stroke: [255, 0, 255], dash: [5, 5] }) - .circle('center', 100, 60, { + .circle('center', 150, 60, { stroke: '#3b7721', fill: '#0e0e0e', opacity: 0.4 }) - .circle('center', 100, 30, { + .circle('center', 150, 30, { stroke: '#0032FF', dash: [5, 5] }) @@ -148,7 +148,29 @@ describe('Vector', () => { ], { lineWidth: 10, dash: [0, 0] - }) + }); + let x = 175; + let y = 11; + let h = 20; + recipe + .text('lineCaps:', 100, 5+h) + .line([[x,y], [x+20,y]], {lineWidth: 10, lineCap:'butt'}) + .text('butt', x+30, 5, {color:'red'}) + .line([[x,y+20], [x+20,y+20]], {lineWidth: 10, lineCap:'round'}) // default + .text('round',x+30, 5+h, {color:'green'}) + .line([[x,y+40], [x+20,y+40]], {lineWidth: 10, lineCap:'square'}) + .text('square', x+30, 5+2*h, {color:'red'}); + + x=300 + y=10 + recipe + .polygon([[x,y+10], [x+20,y+30], [x, y+50]], {lineWidth: 8, lineJoin:'miter', miterLimit:3}) + .text('miter', x-3,70,{rotation:-45, color:'red' }) + .polygon([[x+40,y+10],[x+60,y+30], [x+40, y+50]], {lineWidth: 8, lineJoin:'round'}) // default + .text('round', x+37,70,{rotation:-45, color:'green' }) + .polygon([[x+80,y+10],[x+100,y+30], [x+80, y+50]], {lineWidth: 8, lineJoin: 'bevel'}) + .text('bevel', x+77,70,{rotation:-45, color:'red' }) + .text(':lineJoins', 415, 33) .endPage() .endPDF(done); }); From 6bdc36b2f4a667c474fff8a6992ef3f7f32ccf70 Mon Sep 17 00:00:00 2001 From: shaehn Date: Mon, 4 Nov 2019 23:49:43 -0500 Subject: [PATCH 2/3] Added ellipsis setting to 'wrap' option #52 This checkin adds the value of 'ellipsis' to the 'wrap' option for those that want to see the 3 dots (...) for text that does not fit on a line inside a text box. --- lib/text.js | 44 +++++++++++++++++++++++++++++++++++------ tests/text-centering.js | 15 ++++++++++++-- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/text.js b/lib/text.js index cdb632d..e7816ef 100644 --- a/lib/text.js +++ b/lib/text.js @@ -48,7 +48,7 @@ const xObjectForm = require('./xObjectForm'); * @param {number} [options.textBox.minHeight=0] - Text Box minimum height * @param {number|number[]} [options.textBox.padding=0] - Text Box padding, [top, right, bottom, left] * @param {number} [options.textBox.lineHeight=0] - Text Box line height - * @param {string} [options.textBox.wrap='auto'] - Text wrapping mechanism, 'auto', 'clip', 'trim'. + * @param {string} [options.textBox.wrap='auto'] - Text wrapping mechanism, 'auto', 'clip', 'trim', 'ellipsis'. * @param {string} [options.textBox.textAlign='left top'] - Alignment inside text box, specified as 'horizontal vertical', * where horizontal is one of: 'left', 'center', 'right', 'justify' and veritical is one of: 'top', 'center', 'bottom'. * @param {Object} [options.textBox.style] - Text Box styles @@ -441,6 +441,39 @@ function nextWord (text, brk, previousPosition, pathOptions) { return new Word(nextWord, pathOptions); } +function elideNonFittingText(textBox, line, word, pathOptions) { + if (textBox.wrap === 'clip') { + line.addWord(word); + } else if (textBox.wrap === 'ellipsis') { + // This is more complicated than the other no-wrap options. + // It makes an initial attempt to take the word that was + // too big and make it shrink in size until it and the + // ellipsis character fit. If that doesn't work, one more + // stab is taken by trying to shrink the previous word + // that fit on the line. + const ellipsis = '…'; + let usingPreviousWord = false; + let tooBig = new Word(word.value.slice(0,-2)+ellipsis, pathOptions); + + while ( !line.canFit(tooBig)) { + + if (tooBig.value.length > 1) { + tooBig = new Word(tooBig.value.slice(0,-2)+ellipsis, pathOptions); + + // Try last word that fit in box? + } else if (!usingPreviousWord) { + tooBig = new Word(line.words.pop().value.slice(0,-2) + ellipsis, pathOptions); + usingPreviousWord = true; + + } else { + break; // give up, only get 2 shots at this. + } + } + + line.addWord(tooBig); + } +} + function getToWriteTextObjects(textObject = {}, pathOptions, textBox = {}) { const toWriteTextObjects = []; let text = ((textObject.prependValue) ? textObject.prependValue : '') + @@ -490,9 +523,8 @@ function getToWriteTextObjects(textObject = {}, pathOptions, textBox = {}) { // now deal with text line wrap (what happens to text that doesn't fit in line) if (textBox.wrap !== 'auto') { flushLine = true; - if (textBox.wrap === 'clip') { - newLine.addWord(word); - } + elideNonFittingText(textBox, newLine, word, pathOptions); + } else { // this is the auto wrap section newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions); @@ -509,13 +541,13 @@ function getToWriteTextObjects(textObject = {}, pathOptions, textBox = {}) { if (flushLine) { while (bk) { - bk = breaker.nextBreak(); - if (bk && bk.required) { + if (bk.required) { flushLine = false; newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions); word = null; break; } + bk = breaker.nextBreak(); } } else { /** diff --git a/tests/text-centering.js b/tests/text-centering.js index d2eb8c0..9588425 100644 --- a/tests/text-centering.js +++ b/tests/text-centering.js @@ -277,9 +277,7 @@ describe('Text - Centering', () => { size: 9, textBox: { width: 100, - wrap: 'trim', minHeight: h, - // textAlign: 'right', style: { stroke: '#000000', lineWidth:.5, @@ -359,6 +357,19 @@ describe('Text - Centering', () => { } } }) + .text('Showing text with\nellipsis when wrap\ndisabled on the last line', 440, 725, { + color: '#000000', + size: 10, + textBox: { + width: 100, + height: 40, + wrap: 'ellipsis', + style: { + stroke: '#000000', + lineWidth:.5, + } + } + }) recipe.endPage(); recipe.endPDF(done); From f0449dbe4c0485e9f0a2bf65af823733608e2597 Mon Sep 17 00:00:00 2001 From: shaehn Date: Tue, 5 Nov 2019 10:37:18 -0500 Subject: [PATCH 3/3] 'Wrap' value addition for issue #52 Allow user to specify 'wrap' option as true or false values, with true representing 'auto' matic wrapping and false representing 'ellipsis' line truncation marking. --- docs/text.js.html | 55 +++++++++++++++++++++++++++++++++++++++++------ lib/text.js | 15 ++++++++++--- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/docs/text.js.html b/docs/text.js.html index 9e4c714..33bcc3e 100644 --- a/docs/text.js.html +++ b/docs/text.js.html @@ -87,7 +87,9 @@

    text.js

    * @param {number} [options.textBox.minHeight=0] - Text Box minimum height * @param {number|number[]} [options.textBox.padding=0] - Text Box padding, [top, right, bottom, left] * @param {number} [options.textBox.lineHeight=0] - Text Box line height - * @param {string} [options.textBox.wrap='auto'] - Text wrapping mechanism, 'auto', 'clip', 'trim'. + * @param {string|Boolean} [options.textBox.wrap='auto'] - Text wrapping mechanism, may be true, false, + * 'auto', 'clip', 'trim', 'ellipsis'. All the option values that are not equivalent to 'auto' dictate + * how the text which does not fit on a line is to be truncated. True is equivalent to 'auto'. False is equivalent to 'ellipsis'. * @param {string} [options.textBox.textAlign='left top'] - Alignment inside text box, specified as 'horizontal vertical', * where horizontal is one of: 'left', 'center', 'right', 'justify' and veritical is one of: 'top', 'center', 'bottom'. * @param {Object} [options.textBox.style] - Text Box styles @@ -140,6 +142,13 @@

    text.js

    wrap: (options.textBox.wrap !== undefined) ? options.textBox.wrap : 'auto' }; + // allow user to treat wrap as boolean + if (textBox.wrap === true) { + textBox.wrap = 'auto'; + } else if (textBox.wrap === false) { + textBox.wrap = 'ellipsis' + } + // Allows user to enter a single number which will be used for all text box sides, // or an array of values [top, right, bottom, left] with any combination of missing sides. // Default value for a missing side is the value of the text box's opposite side (see below). @@ -480,6 +489,39 @@

    text.js

    return new Word(nextWord, pathOptions); } +function elideNonFittingText(textBox, line, word, pathOptions) { + if (textBox.wrap === 'clip') { + line.addWord(word); + } else if (textBox.wrap === 'ellipsis') { + // This is more complicated than the other no-wrap options. + // It makes an initial attempt to take the word that was + // too big and make it shrink in size until it and the + // ellipsis character fit. If that doesn't work, one more + // stab is taken by trying to shrink the previous word + // that fit on the line. + const ellipsis = '…'; + let usingPreviousWord = false; + let tooBig = new Word(word.value.slice(0,-2)+ellipsis, pathOptions); + + while ( !line.canFit(tooBig)) { + + if (tooBig.value.length > 1) { + tooBig = new Word(tooBig.value.slice(0,-2)+ellipsis, pathOptions); + + // Try last word that fit in box? + } else if (!usingPreviousWord) { + tooBig = new Word(line.words.pop().value.slice(0,-2) + ellipsis, pathOptions); + usingPreviousWord = true; + + } else { + break; // give up, only get 2 shots at this. + } + } + + line.addWord(tooBig); + } +} + function getToWriteTextObjects(textObject = {}, pathOptions, textBox = {}) { const toWriteTextObjects = []; let text = ((textObject.prependValue) ? textObject.prependValue : '') + @@ -529,9 +571,8 @@

    text.js

    // now deal with text line wrap (what happens to text that doesn't fit in line) if (textBox.wrap !== 'auto') { flushLine = true; - if (textBox.wrap === 'clip') { - newLine.addWord(word); - } + elideNonFittingText(textBox, newLine, word, pathOptions); + } else { // this is the auto wrap section newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions); @@ -548,13 +589,13 @@

    text.js

    if (flushLine) { while (bk) { - bk = breaker.nextBreak(); - if (bk && bk.required) { + if (bk.required) { flushLine = false; newLine = new Line(lineMaxWidth, textBox.lineHeight, size, pathOptions); word = null; break; } + bk = breaker.nextBreak(); } } else { /** @@ -629,7 +670,7 @@

    text.js


    - Documentation generated by JSDoc 3.5.5 on Fri Nov 01 2019 20:41:12 GMT-0400 (Eastern Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.5.5 on Tue Nov 05 2019 10:28:09 GMT-0500 (Eastern Standard Time) using the docdash theme.
    diff --git a/lib/text.js b/lib/text.js index e7816ef..6498cef 100644 --- a/lib/text.js +++ b/lib/text.js @@ -48,7 +48,9 @@ const xObjectForm = require('./xObjectForm'); * @param {number} [options.textBox.minHeight=0] - Text Box minimum height * @param {number|number[]} [options.textBox.padding=0] - Text Box padding, [top, right, bottom, left] * @param {number} [options.textBox.lineHeight=0] - Text Box line height - * @param {string} [options.textBox.wrap='auto'] - Text wrapping mechanism, 'auto', 'clip', 'trim', 'ellipsis'. + * @param {string|Boolean} [options.textBox.wrap='auto'] - Text wrapping mechanism, may be true, false, + * 'auto', 'clip', 'trim', 'ellipsis'. All the option values that are not equivalent to 'auto' dictate + * how the text which does not fit on a line is to be truncated. True is equivalent to 'auto'. False is equivalent to 'ellipsis'. * @param {string} [options.textBox.textAlign='left top'] - Alignment inside text box, specified as 'horizontal vertical', * where horizontal is one of: 'left', 'center', 'right', 'justify' and veritical is one of: 'top', 'center', 'bottom'. * @param {Object} [options.textBox.style] - Text Box styles @@ -101,6 +103,13 @@ exports.text = function text(text = '', x, y, options = {}) { wrap: (options.textBox.wrap !== undefined) ? options.textBox.wrap : 'auto' }; + // allow user to treat wrap as boolean + if (textBox.wrap === true) { + textBox.wrap = 'auto'; + } else if (textBox.wrap === false) { + textBox.wrap = 'ellipsis' + } + // Allows user to enter a single number which will be used for all text box sides, // or an array of values [top, right, bottom, left] with any combination of missing sides. // Default value for a missing side is the value of the text box's opposite side (see below). @@ -449,7 +458,7 @@ function elideNonFittingText(textBox, line, word, pathOptions) { // It makes an initial attempt to take the word that was // too big and make it shrink in size until it and the // ellipsis character fit. If that doesn't work, one more - // stab is taken by trying to shrink the previous word + // attempt is taken by trying to shrink the previous word // that fit on the line. const ellipsis = '…'; let usingPreviousWord = false; @@ -462,7 +471,7 @@ function elideNonFittingText(textBox, line, word, pathOptions) { // Try last word that fit in box? } else if (!usingPreviousWord) { - tooBig = new Word(line.words.pop().value.slice(0,-2) + ellipsis, pathOptions); + tooBig = new Word(line.words.pop().value.slice(0,-1) + ellipsis, pathOptions); usingPreviousWord = true; } else {
NameTypeAttributes + + <optional>
+ - + -
Description

The line width

coloropacity -string -| - -Array.<number> +number @@ -6654,22 +7053,19 @@
Properties
-

HexColor, PercentColor or DecimalColor

The opacity

strokedash -string -| - Array.<number> @@ -6690,23 +7086,20 @@
Properties
-

HexColor, PercentColor or DecimalColor

The dash pattern [dashSize, gapSize] or [dashAndGapSize]

filldashPhase -string -| - -Array.<number> +number @@ -6726,14 +7119,14 @@
Properties
-

HexColor, PercentColor or DecimalColor

distance into dash pattern at which to start dash (default: 0, immediately)

lineWidthrotation @@ -6759,20 +7152,20 @@
Properties
-

The line width

Accept: +/- 0 through 360. Default: 0

opacityrotationOrigin -number +Array.<number> @@ -6792,20 +7185,20 @@
Properties
-

The opacity

[originX, originY] Default: x, y

dashlineCap -Array.<number> +string @@ -6825,20 +7218,20 @@
Properties
-

The dash style [number, number]

open line end style, 'butt', 'round', or 'square' (default: 'round')

rotationlineJoin -number +string @@ -6858,20 +7251,20 @@
Properties
-

Accept: +/- 0 through 360. Default: 0

joined line end style, 'miter', 'round', or 'bevel' (default: 'round')

rotationOriginmiterLimit -Array.<number> +number @@ -6891,7 +7284,7 @@
Properties
-

[originX, originY] Default: x, y

limit at which 'miter' joins are forced to 'bevel' (default: 1.414)

DefaultDescription
+ +

Text color (HexColor, PercentColor or DecimalColor)

+ + 1 + +

opacity

+ + 0 + +

Accept: +/- 0 through 360. Default: 0

Accept: +/- 0 through 360.

+ + [x,y] + +

[originX, originY] Default: the text coordinate

[originX, originY]

+ + Helvetica + +

The font. 'Arial', 'Helvetica'...

+ + 14 + +

The line width

The font size

+ + 'left top' + +

The alignment. 'center center'...

+ +

Text markup annotation.

+ +

Text markup annotation.

+ +

Text markup annotation.

+ +

Text Box to fit in.

Properties
@@ -8439,6 +8890,8 @@
Properties
+
DefaultDescription
+ + 100 + +

Text Box width

+ +

Text Box fixed height

+ + 0 + +

Text Box minimum height

+ + 0 + +

Text Box padding, [top, right, bottom, left]

+ + 0 + +

Text Box line height

wrap + + +string + + + + + + <optional>
+ + + + + +
+ + 'auto' + +

Text wrapping mechanism, 'auto', 'clip', 'trim'.

textAlign + + 'left top' + +

Text alignment inside text box

Alignment inside text box, specified as 'horizontal vertical', +where horizontal is one of: 'left', 'center', 'right', 'justify' and veritical is one of: 'top', 'center', 'bottom'.

+ +

Text Box styles

Properties
@@ -8694,6 +9225,8 @@
Properties
+
DefaultDescription
+ + 2 + +

Text Box border width

+ +

Text Box border color (HexColor, PercentColor or DecimalColor)

+ + [] + +

Text Box border border dash style [number, number]

+ +

Text Box border background color (HexColor, PercentColor or DecimalColor)

+ + 1 + +

Text Box border background opacity

color specification in form of HexColor (string, begins with '#'), -DecimalColor (1, 3, or 4 element array with values between 0-255), -PercentColor (string, begins with '%' followed by values separated -by commas with values between 0-100)