From 8642430ead7bc7d47c81a86153cd1d1bd041d5df Mon Sep 17 00:00:00 2001 From: Oleksii Golub Date: Wed, 3 Jul 2024 22:23:56 +0300 Subject: [PATCH 1/6] added split_text_to_size plugin --- .../jsPDF/plugins/split_text_to_size.js | 393 ++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 cartridges/jsPDF/plugins/split_text_to_size.js diff --git a/cartridges/jsPDF/plugins/split_text_to_size.js b/cartridges/jsPDF/plugins/split_text_to_size.js new file mode 100644 index 0000000..7334d36 --- /dev/null +++ b/cartridges/jsPDF/plugins/split_text_to_size.js @@ -0,0 +1,393 @@ +/** @license + * MIT license. + * Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems + * 2014 Diego Casorran, https://github.com/diegocr + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ + +/** + * jsPDF split_text_to_size plugin + * + * @name split_text_to_size + * @module + */ +module.exports = function (API) { + "use strict"; + /** + * Returns an array of length matching length of the 'word' string, with each + * cell occupied by the width of the char in that position. + * + * @name getCharWidthsArray + * @function + * @param {string} text + * @param {Object} options + * @returns {Array} + */ + var getCharWidthsArray = (API.getCharWidthsArray = function(text, options) { + options = options || {}; + + var activeFont = options.font || this.internal.getFont(); + var fontSize = options.fontSize || this.internal.getFontSize(); + var charSpace = options.charSpace || this.internal.getCharSpace(); + + var widths = options.widths + ? options.widths + : activeFont.metadata.Unicode.widths; + var widthsFractionOf = widths.fof ? widths.fof : 1; + var kerning = options.kerning + ? options.kerning + : activeFont.metadata.Unicode.kerning; + var kerningFractionOf = kerning.fof ? kerning.fof : 1; + var doKerning = options.doKerning === false ? false : true; + var kerningValue = 0; + + var i; + var length = text.length; + var char_code; + var prior_char_code = 0; //for kerning + var default_char_width = widths[0] || widthsFractionOf; + var output = []; + + for (i = 0; i < length; i++) { + char_code = text.charCodeAt(i); + + if (typeof activeFont.metadata.widthOfString === "function") { + output.push( + (activeFont.metadata.widthOfGlyph( + activeFont.metadata.characterToGlyph(char_code) + ) + + charSpace * (1000 / fontSize) || 0) / 1000 + ); + } else { + if ( + doKerning && + typeof kerning[char_code] === "object" && + !isNaN(parseInt(kerning[char_code][prior_char_code], 10)) + ) { + kerningValue = + kerning[char_code][prior_char_code] / kerningFractionOf; + } else { + kerningValue = 0; + } + output.push( + (widths[char_code] || default_char_width) / widthsFractionOf + + kerningValue + ); + } + prior_char_code = char_code; + } + + return output; + }); + + /** + * Returns a widths of string in a given font, if the font size is set as 1 point. + * + * In other words, this is "proportional" value. For 1 unit of font size, the length + * of the string will be that much. + * + * Multiply by font size to get actual width in *points* + * Then divide by 72 to get inches or divide by (72/25.6) to get 'mm' etc. + * + * @name getStringUnitWidth + * @public + * @function + * @param {string} text + * @param {string} options + * @returns {number} result + */ + var getStringUnitWidth = (API.getStringUnitWidth = function(text, options) { + options = options || {}; + + var fontSize = options.fontSize || this.internal.getFontSize(); + var font = options.font || this.internal.getFont(); + var charSpace = options.charSpace || this.internal.getCharSpace(); + var result = 0; + + if (API.processArabic) { + text = API.processArabic(text); + } + + if (typeof font.metadata.widthOfString === "function") { + result = + font.metadata.widthOfString(text, fontSize, charSpace) / fontSize; + } else { + result = getCharWidthsArray + .apply(this, arguments) + .reduce(function(pv, cv) { + return pv + cv; + }, 0); + } + return result; + }); + + /** + returns array of lines + */ + var splitLongWord = function(word, widths_array, firstLineMaxLen, maxLen) { + var answer = []; + + // 1st, chop off the piece that can fit on the hanging line. + var i = 0, + l = word.length, + workingLen = 0; + while (i !== l && workingLen + widths_array[i] < firstLineMaxLen) { + workingLen += widths_array[i]; + i++; + } + // this is first line. + answer.push(word.slice(0, i)); + + // 2nd. Split the rest into maxLen pieces. + var startOfLine = i; + workingLen = 0; + while (i !== l) { + if (workingLen + widths_array[i] > maxLen) { + answer.push(word.slice(startOfLine, i)); + workingLen = 0; + startOfLine = i; + } + workingLen += widths_array[i]; + i++; + } + if (startOfLine !== i) { + answer.push(word.slice(startOfLine, i)); + } + + return answer; + }; + + // Note, all sizing inputs for this function must be in "font measurement units" + // By default, for PDF, it's "point". + var splitParagraphIntoLines = function(text, maxlen, options) { + // at this time works only on Western scripts, ones with space char + // separating the words. Feel free to expand. + + if (!options) { + options = {}; + } + + var line = [], + lines = [line], + line_length = options.textIndent || 0, + separator_length = 0, + current_word_length = 0, + word, + widths_array, + words = text.split(" "), + spaceCharWidth = getCharWidthsArray.apply(this, [" ", options])[0], + i, + l, + tmp, + lineIndent; + + if (options.lineIndent === -1) { + lineIndent = words[0].length + 2; + } else { + lineIndent = options.lineIndent || 0; + } + if (lineIndent) { + var pad = Array(lineIndent).join(" "), + wrds = []; + words.map(function(wrd) { + wrd = wrd.split(/\s*\n/); + if (wrd.length > 1) { + wrds = wrds.concat( + wrd.map(function(wrd, idx) { + return (idx && wrd.length ? "\n" : "") + wrd; + }) + ); + } else { + wrds.push(wrd[0]); + } + }); + words = wrds; + lineIndent = getStringUnitWidth.apply(this, [pad, options]); + } + + for (i = 0, l = words.length; i < l; i++) { + var force = 0; + + word = words[i]; + if (lineIndent && word[0] == "\n") { + word = word.substr(1); + force = 1; + } + widths_array = getCharWidthsArray.apply(this, [word, options]); + current_word_length = widths_array.reduce(function(pv, cv) { + return pv + cv; + }, 0); + + if ( + line_length + separator_length + current_word_length > maxlen || + force + ) { + if (current_word_length > maxlen) { + // this happens when you have space-less long URLs for example. + // we just chop these to size. We do NOT insert hiphens + tmp = splitLongWord.apply(this, [ + word, + widths_array, + maxlen - (line_length + separator_length), + maxlen + ]); + // first line we add to existing line object + line.push(tmp.shift()); // it's ok to have extra space indicator there + // last line we make into new line object + line = [tmp.pop()]; + // lines in the middle we apped to lines object as whole lines + while (tmp.length) { + lines.push([tmp.shift()]); // single fragment occupies whole line + } + current_word_length = widths_array + .slice(word.length - (line[0] ? line[0].length : 0)) + .reduce(function(pv, cv) { + return pv + cv; + }, 0); + } else { + // just put it on a new line + line = [word]; + } + + // now we attach new line to lines + lines.push(line); + line_length = current_word_length + lineIndent; + separator_length = spaceCharWidth; + } else { + line.push(word); + + line_length += separator_length + current_word_length; + separator_length = spaceCharWidth; + } + } + + var postProcess; + if (lineIndent) { + postProcess = function(ln, idx) { + return (idx ? pad : "") + ln.join(" "); + }; + } else { + postProcess = function(ln) { + return ln.join(" "); + }; + } + + return lines.map(postProcess); + }; + + /** + * Splits a given string into an array of strings. Uses 'size' value + * (in measurement units declared as default for the jsPDF instance) + * and the font's "widths" and "Kerning" tables, where available, to + * determine display length of a given string for a given font. + * + * We use character's 100% of unit size (height) as width when Width + * table or other default width is not available. + * + * @name splitTextToSize + * @public + * @function + * @param {string} text Unencoded, regular JavaScript (Unicode, UTF-16 / UCS-2) string. + * @param {number} size Nominal number, measured in units default to this instance of jsPDF. + * @param {Object} options Optional flags needed for chopper to do the right thing. + * @returns {Array} array Array with strings chopped to size. + */ + API.splitTextToSize = function(text, maxlen, options) { + "use strict"; + + options = options || {}; + + var fsize = options.fontSize || this.internal.getFontSize(), + newOptions = function(options) { + var widths = { + 0: 1 + }, + kerning = {}; + + if (!options.widths || !options.kerning) { + var f = this.internal.getFont(options.fontName, options.fontStyle), + encoding = "Unicode"; + // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE + // Actual JavaScript-native String's 16bit char codes used. + // no multi-byte logic here + + if (f.metadata[encoding]) { + return { + widths: f.metadata[encoding].widths || widths, + kerning: f.metadata[encoding].kerning || kerning + }; + } else { + return { + font: f.metadata, + fontSize: this.internal.getFontSize(), + charSpace: this.internal.getCharSpace() + }; + } + } else { + return { + widths: options.widths, + kerning: options.kerning + }; + } + }.call(this, options); + + // first we split on end-of-line chars + var paragraphs; + if (Array.isArray(text)) { + paragraphs = text; + } else { + paragraphs = String(text).split(/\r?\n/); + } + + // now we convert size (max length of line) into "font size units" + // at present time, the "font size unit" is always 'point' + // 'proportional' means, "in proportion to font size" + var fontUnit_maxLen = (1.0 * this.internal.scaleFactor * maxlen) / fsize; + // at this time, fsize is always in "points" regardless of the default measurement unit of the doc. + // this may change in the future? + // until then, proportional_maxlen is likely to be in 'points' + + // If first line is to be indented (shorter or longer) than maxLen + // we indicate that by using CSS-style "text-indent" option. + // here it's in font units too (which is likely 'points') + // it can be negative (which makes the first line longer than maxLen) + newOptions.textIndent = options.textIndent + ? (options.textIndent * 1.0 * this.internal.scaleFactor) / fsize + : 0; + newOptions.lineIndent = options.lineIndent; + + var i, + l, + output = []; + for (i = 0, l = paragraphs.length; i < l; i++) { + output = output.concat( + splitParagraphIntoLines.apply(this, [ + paragraphs[i], + fontUnit_maxLen, + newOptions + ]) + ); + } + + return output; + }; +}; From 5c6a76ec4e8dad20639fca0679175fc5e728a2ae Mon Sep 17 00:00:00 2001 From: Oleksii Golub Date: Wed, 3 Jul 2024 22:26:07 +0300 Subject: [PATCH 2/6] require split_text_to_size plugins in main.js --- cartridges/jsPDF/main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cartridges/jsPDF/main.js b/cartridges/jsPDF/main.js index 7ddf741..772d862 100644 --- a/cartridges/jsPDF/main.js +++ b/cartridges/jsPDF/main.js @@ -7,6 +7,7 @@ require('./libs/ttffont')(jsPDF.API); require('./plugins/addImage')(jsPDF.API); require('./plugins/total_pages')(jsPDF.API); require('./plugins/javascript')(jsPDF.API); +require('./plugins/split_text_to_size')(jsPDF.API); require('./plugins/autoprint')(jsPDF.API); require('./plugins/viewerpreferences')(jsPDF.API); require('./plugins/standard_fonts_metrics')(jsPDF.API); From 1448d297fe6213f02bb1ece8c2a43e2dad5ed710 Mon Sep 17 00:00:00 2001 From: Oleksii Golub Date: Wed, 3 Jul 2024 23:05:26 +0300 Subject: [PATCH 3/6] added link to controller jsPDF --- .../plugin_testlibraries/cartridge/controllers/jsPDF.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cartridges/plugin_testlibraries/cartridge/controllers/jsPDF.js b/cartridges/plugin_testlibraries/cartridge/controllers/jsPDF.js index 037488b..4d6f8ed 100644 --- a/cartridges/plugin_testlibraries/cartridge/controllers/jsPDF.js +++ b/cartridges/plugin_testlibraries/cartridge/controllers/jsPDF.js @@ -83,6 +83,11 @@ function generatePDF() { doc.setTextColor(0, 0, 255); doc.text(20, 160, 'This is blue.'); + doc.setFont('Open Sans', 'normal'); + + doc.setTextColor('#fffff'); + doc.textWithLink('This is link.', 20, 175, { url: 'http://www.google.com' }); + doc.addImage(jpg, 'jpg', 100, 200, 280, 210, undefined, 'none'); doc.addJS('app.alert("This is injected JS inside of the PDF.", 3);'); From 5e29544d50bd2d79dabc3087060e7ccdcf66f9c0 Mon Sep 17 00:00:00 2001 From: Oleksii Golub Date: Wed, 3 Jul 2024 23:06:47 +0300 Subject: [PATCH 4/6] Update split_text_to_size.js --- .../jsPDF/plugins/split_text_to_size.js | 716 +++++++++--------- 1 file changed, 358 insertions(+), 358 deletions(-) diff --git a/cartridges/jsPDF/plugins/split_text_to_size.js b/cartridges/jsPDF/plugins/split_text_to_size.js index 7334d36..215c1ef 100644 --- a/cartridges/jsPDF/plugins/split_text_to_size.js +++ b/cartridges/jsPDF/plugins/split_text_to_size.js @@ -31,363 +31,363 @@ * @module */ module.exports = function (API) { - "use strict"; - /** - * Returns an array of length matching length of the 'word' string, with each - * cell occupied by the width of the char in that position. - * - * @name getCharWidthsArray - * @function - * @param {string} text - * @param {Object} options - * @returns {Array} - */ - var getCharWidthsArray = (API.getCharWidthsArray = function(text, options) { - options = options || {}; - - var activeFont = options.font || this.internal.getFont(); - var fontSize = options.fontSize || this.internal.getFontSize(); - var charSpace = options.charSpace || this.internal.getCharSpace(); - - var widths = options.widths - ? options.widths - : activeFont.metadata.Unicode.widths; - var widthsFractionOf = widths.fof ? widths.fof : 1; - var kerning = options.kerning - ? options.kerning - : activeFont.metadata.Unicode.kerning; - var kerningFractionOf = kerning.fof ? kerning.fof : 1; - var doKerning = options.doKerning === false ? false : true; - var kerningValue = 0; - - var i; - var length = text.length; - var char_code; - var prior_char_code = 0; //for kerning - var default_char_width = widths[0] || widthsFractionOf; - var output = []; - - for (i = 0; i < length; i++) { - char_code = text.charCodeAt(i); - - if (typeof activeFont.metadata.widthOfString === "function") { - output.push( - (activeFont.metadata.widthOfGlyph( - activeFont.metadata.characterToGlyph(char_code) - ) + - charSpace * (1000 / fontSize) || 0) / 1000 - ); - } else { - if ( - doKerning && - typeof kerning[char_code] === "object" && - !isNaN(parseInt(kerning[char_code][prior_char_code], 10)) - ) { - kerningValue = - kerning[char_code][prior_char_code] / kerningFractionOf; - } else { - kerningValue = 0; - } - output.push( - (widths[char_code] || default_char_width) / widthsFractionOf + - kerningValue - ); - } - prior_char_code = char_code; - } - - return output; - }); - - /** - * Returns a widths of string in a given font, if the font size is set as 1 point. - * - * In other words, this is "proportional" value. For 1 unit of font size, the length - * of the string will be that much. - * - * Multiply by font size to get actual width in *points* - * Then divide by 72 to get inches or divide by (72/25.6) to get 'mm' etc. - * - * @name getStringUnitWidth - * @public - * @function - * @param {string} text - * @param {string} options - * @returns {number} result - */ - var getStringUnitWidth = (API.getStringUnitWidth = function(text, options) { - options = options || {}; - - var fontSize = options.fontSize || this.internal.getFontSize(); - var font = options.font || this.internal.getFont(); - var charSpace = options.charSpace || this.internal.getCharSpace(); - var result = 0; - - if (API.processArabic) { - text = API.processArabic(text); - } - - if (typeof font.metadata.widthOfString === "function") { - result = - font.metadata.widthOfString(text, fontSize, charSpace) / fontSize; - } else { - result = getCharWidthsArray - .apply(this, arguments) - .reduce(function(pv, cv) { - return pv + cv; - }, 0); - } - return result; - }); - - /** + "use strict"; + /** + * Returns an array of length matching length of the 'word' string, with each + * cell occupied by the width of the char in that position. + * + * @name getCharWidthsArray + * @function + * @param {string} text + * @param {Object} options + * @returns {Array} + */ + var getCharWidthsArray = (API.getCharWidthsArray = function(text, options) { + options = options || {}; + + var activeFont = options.font || this.internal.getFont(); + var fontSize = options.fontSize || this.internal.getFontSize(); + var charSpace = options.charSpace || this.internal.getCharSpace(); + + var widths = options.widths + ? options.widths + : activeFont.metadata.Unicode.widths; + var widthsFractionOf = widths.fof ? widths.fof : 1; + var kerning = options.kerning + ? options.kerning + : activeFont.metadata.Unicode.kerning; + var kerningFractionOf = kerning.fof ? kerning.fof : 1; + var doKerning = options.doKerning === false ? false : true; + var kerningValue = 0; + + var i; + var length = text.length; + var char_code; + var prior_char_code = 0; //for kerning + var default_char_width = widths[0] || widthsFractionOf; + var output = []; + + for (i = 0; i < length; i++) { + char_code = text.charCodeAt(i); + + if (typeof activeFont.metadata.widthOfString === "function") { + output.push( + (activeFont.metadata.widthOfGlyph( + activeFont.metadata.characterToGlyph(char_code) + ) + + charSpace * (1000 / fontSize) || 0) / 1000 + ); + } else { + if ( + doKerning && + typeof kerning[char_code] === "object" && + !isNaN(parseInt(kerning[char_code][prior_char_code], 10)) + ) { + kerningValue = + kerning[char_code][prior_char_code] / kerningFractionOf; + } else { + kerningValue = 0; + } + output.push( + (widths[char_code] || default_char_width) / widthsFractionOf + + kerningValue + ); + } + prior_char_code = char_code; + } + + return output; + }); + + /** + * Returns a widths of string in a given font, if the font size is set as 1 point. + * + * In other words, this is "proportional" value. For 1 unit of font size, the length + * of the string will be that much. + * + * Multiply by font size to get actual width in *points* + * Then divide by 72 to get inches or divide by (72/25.6) to get 'mm' etc. + * + * @name getStringUnitWidth + * @public + * @function + * @param {string} text + * @param {string} options + * @returns {number} result + */ + var getStringUnitWidth = (API.getStringUnitWidth = function(text, options) { + options = options || {}; + + var fontSize = options.fontSize || this.internal.getFontSize(); + var font = options.font || this.internal.getFont(); + var charSpace = options.charSpace || this.internal.getCharSpace(); + var result = 0; + + if (API.processArabic) { + text = API.processArabic(text); + } + + if (typeof font.metadata.widthOfString === "function") { + result = + font.metadata.widthOfString(text, fontSize, charSpace) / fontSize; + } else { + result = getCharWidthsArray + .apply(this, arguments) + .reduce(function(pv, cv) { + return pv + cv; + }, 0); + } + return result; + }); + + /** returns array of lines - */ - var splitLongWord = function(word, widths_array, firstLineMaxLen, maxLen) { - var answer = []; - - // 1st, chop off the piece that can fit on the hanging line. - var i = 0, - l = word.length, - workingLen = 0; - while (i !== l && workingLen + widths_array[i] < firstLineMaxLen) { - workingLen += widths_array[i]; - i++; - } - // this is first line. - answer.push(word.slice(0, i)); - - // 2nd. Split the rest into maxLen pieces. - var startOfLine = i; - workingLen = 0; - while (i !== l) { - if (workingLen + widths_array[i] > maxLen) { - answer.push(word.slice(startOfLine, i)); - workingLen = 0; - startOfLine = i; - } - workingLen += widths_array[i]; - i++; - } - if (startOfLine !== i) { - answer.push(word.slice(startOfLine, i)); - } - - return answer; - }; - - // Note, all sizing inputs for this function must be in "font measurement units" - // By default, for PDF, it's "point". - var splitParagraphIntoLines = function(text, maxlen, options) { - // at this time works only on Western scripts, ones with space char - // separating the words. Feel free to expand. - - if (!options) { - options = {}; - } - - var line = [], - lines = [line], - line_length = options.textIndent || 0, - separator_length = 0, - current_word_length = 0, - word, - widths_array, - words = text.split(" "), - spaceCharWidth = getCharWidthsArray.apply(this, [" ", options])[0], - i, - l, - tmp, - lineIndent; - - if (options.lineIndent === -1) { - lineIndent = words[0].length + 2; - } else { - lineIndent = options.lineIndent || 0; - } - if (lineIndent) { - var pad = Array(lineIndent).join(" "), - wrds = []; - words.map(function(wrd) { - wrd = wrd.split(/\s*\n/); - if (wrd.length > 1) { - wrds = wrds.concat( - wrd.map(function(wrd, idx) { - return (idx && wrd.length ? "\n" : "") + wrd; - }) - ); - } else { - wrds.push(wrd[0]); - } - }); - words = wrds; - lineIndent = getStringUnitWidth.apply(this, [pad, options]); - } - - for (i = 0, l = words.length; i < l; i++) { - var force = 0; - - word = words[i]; - if (lineIndent && word[0] == "\n") { - word = word.substr(1); - force = 1; - } - widths_array = getCharWidthsArray.apply(this, [word, options]); - current_word_length = widths_array.reduce(function(pv, cv) { - return pv + cv; - }, 0); - - if ( - line_length + separator_length + current_word_length > maxlen || - force - ) { - if (current_word_length > maxlen) { - // this happens when you have space-less long URLs for example. - // we just chop these to size. We do NOT insert hiphens - tmp = splitLongWord.apply(this, [ - word, - widths_array, - maxlen - (line_length + separator_length), - maxlen - ]); - // first line we add to existing line object - line.push(tmp.shift()); // it's ok to have extra space indicator there - // last line we make into new line object - line = [tmp.pop()]; - // lines in the middle we apped to lines object as whole lines - while (tmp.length) { - lines.push([tmp.shift()]); // single fragment occupies whole line - } - current_word_length = widths_array - .slice(word.length - (line[0] ? line[0].length : 0)) - .reduce(function(pv, cv) { - return pv + cv; - }, 0); - } else { - // just put it on a new line - line = [word]; - } - - // now we attach new line to lines - lines.push(line); - line_length = current_word_length + lineIndent; - separator_length = spaceCharWidth; - } else { - line.push(word); - - line_length += separator_length + current_word_length; - separator_length = spaceCharWidth; - } - } - - var postProcess; - if (lineIndent) { - postProcess = function(ln, idx) { - return (idx ? pad : "") + ln.join(" "); - }; - } else { - postProcess = function(ln) { - return ln.join(" "); - }; - } - - return lines.map(postProcess); - }; - - /** - * Splits a given string into an array of strings. Uses 'size' value - * (in measurement units declared as default for the jsPDF instance) - * and the font's "widths" and "Kerning" tables, where available, to - * determine display length of a given string for a given font. - * - * We use character's 100% of unit size (height) as width when Width - * table or other default width is not available. - * - * @name splitTextToSize - * @public - * @function - * @param {string} text Unencoded, regular JavaScript (Unicode, UTF-16 / UCS-2) string. - * @param {number} size Nominal number, measured in units default to this instance of jsPDF. - * @param {Object} options Optional flags needed for chopper to do the right thing. - * @returns {Array} array Array with strings chopped to size. - */ - API.splitTextToSize = function(text, maxlen, options) { - "use strict"; - - options = options || {}; - - var fsize = options.fontSize || this.internal.getFontSize(), - newOptions = function(options) { - var widths = { - 0: 1 - }, - kerning = {}; - - if (!options.widths || !options.kerning) { - var f = this.internal.getFont(options.fontName, options.fontStyle), - encoding = "Unicode"; - // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE - // Actual JavaScript-native String's 16bit char codes used. - // no multi-byte logic here - - if (f.metadata[encoding]) { - return { - widths: f.metadata[encoding].widths || widths, - kerning: f.metadata[encoding].kerning || kerning - }; - } else { - return { - font: f.metadata, - fontSize: this.internal.getFontSize(), - charSpace: this.internal.getCharSpace() - }; - } - } else { - return { - widths: options.widths, - kerning: options.kerning - }; - } - }.call(this, options); - - // first we split on end-of-line chars - var paragraphs; - if (Array.isArray(text)) { - paragraphs = text; - } else { - paragraphs = String(text).split(/\r?\n/); - } - - // now we convert size (max length of line) into "font size units" - // at present time, the "font size unit" is always 'point' - // 'proportional' means, "in proportion to font size" - var fontUnit_maxLen = (1.0 * this.internal.scaleFactor * maxlen) / fsize; - // at this time, fsize is always in "points" regardless of the default measurement unit of the doc. - // this may change in the future? - // until then, proportional_maxlen is likely to be in 'points' - - // If first line is to be indented (shorter or longer) than maxLen - // we indicate that by using CSS-style "text-indent" option. - // here it's in font units too (which is likely 'points') - // it can be negative (which makes the first line longer than maxLen) - newOptions.textIndent = options.textIndent - ? (options.textIndent * 1.0 * this.internal.scaleFactor) / fsize - : 0; - newOptions.lineIndent = options.lineIndent; - - var i, - l, - output = []; - for (i = 0, l = paragraphs.length; i < l; i++) { - output = output.concat( - splitParagraphIntoLines.apply(this, [ - paragraphs[i], - fontUnit_maxLen, - newOptions - ]) - ); - } - - return output; - }; + */ + var splitLongWord = function(word, widths_array, firstLineMaxLen, maxLen) { + var answer = []; + + // 1st, chop off the piece that can fit on the hanging line. + var i = 0, + l = word.length, + workingLen = 0; + while (i !== l && workingLen + widths_array[i] < firstLineMaxLen) { + workingLen += widths_array[i]; + i++; + } + // this is first line. + answer.push(word.slice(0, i)); + + // 2nd. Split the rest into maxLen pieces. + var startOfLine = i; + workingLen = 0; + while (i !== l) { + if (workingLen + widths_array[i] > maxLen) { + answer.push(word.slice(startOfLine, i)); + workingLen = 0; + startOfLine = i; + } + workingLen += widths_array[i]; + i++; + } + if (startOfLine !== i) { + answer.push(word.slice(startOfLine, i)); + } + + return answer; + }; + + // Note, all sizing inputs for this function must be in "font measurement units" + // By default, for PDF, it's "point". + var splitParagraphIntoLines = function(text, maxlen, options) { + // at this time works only on Western scripts, ones with space char + // separating the words. Feel free to expand. + + if (!options) { + options = {}; + } + + var line = [], + lines = [line], + line_length = options.textIndent || 0, + separator_length = 0, + current_word_length = 0, + word, + widths_array, + words = text.split(" "), + spaceCharWidth = getCharWidthsArray.apply(this, [" ", options])[0], + i, + l, + tmp, + lineIndent; + + if (options.lineIndent === -1) { + lineIndent = words[0].length + 2; + } else { + lineIndent = options.lineIndent || 0; + } + if (lineIndent) { + var pad = Array(lineIndent).join(" "), + wrds = []; + words.map(function(wrd) { + wrd = wrd.split(/\s*\n/); + if (wrd.length > 1) { + wrds = wrds.concat( + wrd.map(function(wrd, idx) { + return (idx && wrd.length ? "\n" : "") + wrd; + }) + ); + } else { + wrds.push(wrd[0]); + } + }); + words = wrds; + lineIndent = getStringUnitWidth.apply(this, [pad, options]); + } + + for (i = 0, l = words.length; i < l; i++) { + var force = 0; + + word = words[i]; + if (lineIndent && word[0] == "\n") { + word = word.substr(1); + force = 1; + } + widths_array = getCharWidthsArray.apply(this, [word, options]); + current_word_length = widths_array.reduce(function(pv, cv) { + return pv + cv; + }, 0); + + if ( + line_length + separator_length + current_word_length > maxlen || + force + ) { + if (current_word_length > maxlen) { + // this happens when you have space-less long URLs for example. + // we just chop these to size. We do NOT insert hiphens + tmp = splitLongWord.apply(this, [ + word, + widths_array, + maxlen - (line_length + separator_length), + maxlen + ]); + // first line we add to existing line object + line.push(tmp.shift()); // it's ok to have extra space indicator there + // last line we make into new line object + line = [tmp.pop()]; + // lines in the middle we apped to lines object as whole lines + while (tmp.length) { + lines.push([tmp.shift()]); // single fragment occupies whole line + } + current_word_length = widths_array + .slice(word.length - (line[0] ? line[0].length : 0)) + .reduce(function(pv, cv) { + return pv + cv; + }, 0); + } else { + // just put it on a new line + line = [word]; + } + + // now we attach new line to lines + lines.push(line); + line_length = current_word_length + lineIndent; + separator_length = spaceCharWidth; + } else { + line.push(word); + + line_length += separator_length + current_word_length; + separator_length = spaceCharWidth; + } + } + + var postProcess; + if (lineIndent) { + postProcess = function(ln, idx) { + return (idx ? pad : "") + ln.join(" "); + }; + } else { + postProcess = function(ln) { + return ln.join(" "); + }; + } + + return lines.map(postProcess); + }; + + /** + * Splits a given string into an array of strings. Uses 'size' value + * (in measurement units declared as default for the jsPDF instance) + * and the font's "widths" and "Kerning" tables, where available, to + * determine display length of a given string for a given font. + * + * We use character's 100% of unit size (height) as width when Width + * table or other default width is not available. + * + * @name splitTextToSize + * @public + * @function + * @param {string} text Unencoded, regular JavaScript (Unicode, UTF-16 / UCS-2) string. + * @param {number} size Nominal number, measured in units default to this instance of jsPDF. + * @param {Object} options Optional flags needed for chopper to do the right thing. + * @returns {Array} array Array with strings chopped to size. + */ + API.splitTextToSize = function(text, maxlen, options) { + "use strict"; + + options = options || {}; + + var fsize = options.fontSize || this.internal.getFontSize(), + newOptions = function(options) { + var widths = { + 0: 1 + }, + kerning = {}; + + if (!options.widths || !options.kerning) { + var f = this.internal.getFont(options.fontName, options.fontStyle), + encoding = "Unicode"; + // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE + // Actual JavaScript-native String's 16bit char codes used. + // no multi-byte logic here + + if (f.metadata[encoding]) { + return { + widths: f.metadata[encoding].widths || widths, + kerning: f.metadata[encoding].kerning || kerning + }; + } else { + return { + font: f.metadata, + fontSize: this.internal.getFontSize(), + charSpace: this.internal.getCharSpace() + }; + } + } else { + return { + widths: options.widths, + kerning: options.kerning + }; + } + }.call(this, options); + + // first we split on end-of-line chars + var paragraphs; + if (Array.isArray(text)) { + paragraphs = text; + } else { + paragraphs = String(text).split(/\r?\n/); + } + + // now we convert size (max length of line) into "font size units" + // at present time, the "font size unit" is always 'point' + // 'proportional' means, "in proportion to font size" + var fontUnit_maxLen = (1.0 * this.internal.scaleFactor * maxlen) / fsize; + // at this time, fsize is always in "points" regardless of the default measurement unit of the doc. + // this may change in the future? + // until then, proportional_maxlen is likely to be in 'points' + + // If first line is to be indented (shorter or longer) than maxLen + // we indicate that by using CSS-style "text-indent" option. + // here it's in font units too (which is likely 'points') + // it can be negative (which makes the first line longer than maxLen) + newOptions.textIndent = options.textIndent + ? (options.textIndent * 1.0 * this.internal.scaleFactor) / fsize + : 0; + newOptions.lineIndent = options.lineIndent; + + var i, + l, + output = []; + for (i = 0, l = paragraphs.length; i < l; i++) { + output = output.concat( + splitParagraphIntoLines.apply(this, [ + paragraphs[i], + fontUnit_maxLen, + newOptions + ]) + ); + } + + return output; + }; }; From a9b5eeaccfd15ff438a29ba3d0e89bbb6d56434d Mon Sep 17 00:00:00 2001 From: Oleksii Golub Date: Thu, 4 Jul 2024 11:54:24 +0300 Subject: [PATCH 5/6] Update jsPDF.js, replace tab to spaces --- .../plugin_testlibraries/cartridge/controllers/jsPDF.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cartridges/plugin_testlibraries/cartridge/controllers/jsPDF.js b/cartridges/plugin_testlibraries/cartridge/controllers/jsPDF.js index 4d6f8ed..ad6e26e 100644 --- a/cartridges/plugin_testlibraries/cartridge/controllers/jsPDF.js +++ b/cartridges/plugin_testlibraries/cartridge/controllers/jsPDF.js @@ -83,10 +83,10 @@ function generatePDF() { doc.setTextColor(0, 0, 255); doc.text(20, 160, 'This is blue.'); - doc.setFont('Open Sans', 'normal'); + doc.setFont('Open Sans', 'normal'); - doc.setTextColor('#fffff'); - doc.textWithLink('This is link.', 20, 175, { url: 'http://www.google.com' }); + doc.setTextColor('#fffff'); + doc.textWithLink('This is link.', 20, 175, { url: 'http://www.google.com' }); doc.addImage(jpg, 'jpg', 100, 200, 280, 210, undefined, 'none'); From 78a2234cb9ba0447ef82449061bc0ed67db69337 Mon Sep 17 00:00:00 2001 From: Oleksii Holub Date: Wed, 10 Jul 2024 15:31:29 +0300 Subject: [PATCH 6/6] fix lint errors --- .../jsPDF/plugins/split_text_to_size.js | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/cartridges/jsPDF/plugins/split_text_to_size.js b/cartridges/jsPDF/plugins/split_text_to_size.js index 215c1ef..a7d1309 100644 --- a/cartridges/jsPDF/plugins/split_text_to_size.js +++ b/cartridges/jsPDF/plugins/split_text_to_size.js @@ -23,6 +23,13 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ==================================================================== */ +/* eslint-disable valid-jsdoc */ +/* eslint-disable camelcase */ +/* eslint-disable no-else-return */ +/* eslint-disable one-var */ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-mixed-operators */ +/* eslint-disable no-shadow */ /** * jsPDF split_text_to_size plugin @@ -31,7 +38,7 @@ * @module */ module.exports = function (API) { - "use strict"; + 'use strict'; /** * Returns an array of length matching length of the 'word' string, with each * cell occupied by the width of the char in that position. @@ -42,7 +49,7 @@ module.exports = function (API) { * @param {Object} options * @returns {Array} */ - var getCharWidthsArray = (API.getCharWidthsArray = function(text, options) { + var getCharWidthsArray = (API.getCharWidthsArray = function (text, options) { options = options || {}; var activeFont = options.font || this.internal.getFont(); @@ -57,20 +64,21 @@ module.exports = function (API) { ? options.kerning : activeFont.metadata.Unicode.kerning; var kerningFractionOf = kerning.fof ? kerning.fof : 1; + // eslint-disable-next-line no-unneeded-ternary var doKerning = options.doKerning === false ? false : true; var kerningValue = 0; var i; var length = text.length; var char_code; - var prior_char_code = 0; //for kerning + var prior_char_code = 0; // for kerning var default_char_width = widths[0] || widthsFractionOf; var output = []; for (i = 0; i < length; i++) { char_code = text.charCodeAt(i); - if (typeof activeFont.metadata.widthOfString === "function") { + if (typeof activeFont.metadata.widthOfString === 'function') { output.push( (activeFont.metadata.widthOfGlyph( activeFont.metadata.characterToGlyph(char_code) @@ -80,7 +88,7 @@ module.exports = function (API) { } else { if ( doKerning && - typeof kerning[char_code] === "object" && + typeof kerning[char_code] === 'object' && !isNaN(parseInt(kerning[char_code][prior_char_code], 10)) ) { kerningValue = @@ -102,7 +110,7 @@ module.exports = function (API) { /** * Returns a widths of string in a given font, if the font size is set as 1 point. * - * In other words, this is "proportional" value. For 1 unit of font size, the length + * In other words, this is 'proportional' value. For 1 unit of font size, the length * of the string will be that much. * * Multiply by font size to get actual width in *points* @@ -115,7 +123,7 @@ module.exports = function (API) { * @param {string} options * @returns {number} result */ - var getStringUnitWidth = (API.getStringUnitWidth = function(text, options) { + var getStringUnitWidth = (API.getStringUnitWidth = function (text, options) { options = options || {}; var fontSize = options.fontSize || this.internal.getFontSize(); @@ -127,13 +135,13 @@ module.exports = function (API) { text = API.processArabic(text); } - if (typeof font.metadata.widthOfString === "function") { + if (typeof font.metadata.widthOfString === 'function') { result = font.metadata.widthOfString(text, fontSize, charSpace) / fontSize; } else { result = getCharWidthsArray .apply(this, arguments) - .reduce(function(pv, cv) { + .reduce(function (pv, cv) { return pv + cv; }, 0); } @@ -143,7 +151,7 @@ module.exports = function (API) { /** returns array of lines */ - var splitLongWord = function(word, widths_array, firstLineMaxLen, maxLen) { + var splitLongWord = function (word, widths_array, firstLineMaxLen, maxLen) { var answer = []; // 1st, chop off the piece that can fit on the hanging line. @@ -176,9 +184,9 @@ module.exports = function (API) { return answer; }; - // Note, all sizing inputs for this function must be in "font measurement units" - // By default, for PDF, it's "point". - var splitParagraphIntoLines = function(text, maxlen, options) { + // Note, all sizing inputs for this function must be in 'font measurement units' + // By default, for PDF, it's 'point'. + var splitParagraphIntoLines = function (text, maxlen, options) { // at this time works only on Western scripts, ones with space char // separating the words. Feel free to expand. @@ -193,8 +201,8 @@ module.exports = function (API) { current_word_length = 0, word, widths_array, - words = text.split(" "), - spaceCharWidth = getCharWidthsArray.apply(this, [" ", options])[0], + words = text.split(' '), + spaceCharWidth = getCharWidthsArray.apply(this, [' ', options])[0], i, l, tmp, @@ -206,14 +214,15 @@ module.exports = function (API) { lineIndent = options.lineIndent || 0; } if (lineIndent) { - var pad = Array(lineIndent).join(" "), + var pad = Array(lineIndent).join(' '), wrds = []; - words.map(function(wrd) { + // eslint-disable-next-line array-callback-return + words.map(function (wrd) { wrd = wrd.split(/\s*\n/); if (wrd.length > 1) { wrds = wrds.concat( - wrd.map(function(wrd, idx) { - return (idx && wrd.length ? "\n" : "") + wrd; + wrd.map(function (wrd, idx) { + return (idx && wrd.length ? '\n' : '') + wrd; }) ); } else { @@ -228,12 +237,13 @@ module.exports = function (API) { var force = 0; word = words[i]; - if (lineIndent && word[0] == "\n") { + // eslint-disable-next-line eqeqeq + if (lineIndent && word[0] == '\n') { word = word.substr(1); force = 1; } widths_array = getCharWidthsArray.apply(this, [word, options]); - current_word_length = widths_array.reduce(function(pv, cv) { + current_word_length = widths_array.reduce(function (pv, cv) { return pv + cv; }, 0); @@ -260,7 +270,7 @@ module.exports = function (API) { } current_word_length = widths_array .slice(word.length - (line[0] ? line[0].length : 0)) - .reduce(function(pv, cv) { + .reduce(function (pv, cv) { return pv + cv; }, 0); } else { @@ -282,12 +292,13 @@ module.exports = function (API) { var postProcess; if (lineIndent) { - postProcess = function(ln, idx) { - return (idx ? pad : "") + ln.join(" "); + postProcess = function (ln, idx) { + // eslint-disable-next-line block-scoped-var + return (idx ? pad : '') + ln.join(' '); }; } else { - postProcess = function(ln) { - return ln.join(" "); + postProcess = function (ln) { + return ln.join(' '); }; } @@ -297,7 +308,7 @@ module.exports = function (API) { /** * Splits a given string into an array of strings. Uses 'size' value * (in measurement units declared as default for the jsPDF instance) - * and the font's "widths" and "Kerning" tables, where available, to + * and the font's 'widths' and 'Kerning' tables, where available, to * determine display length of a given string for a given font. * * We use character's 100% of unit size (height) as width when Width @@ -311,13 +322,13 @@ module.exports = function (API) { * @param {Object} options Optional flags needed for chopper to do the right thing. * @returns {Array} array Array with strings chopped to size. */ - API.splitTextToSize = function(text, maxlen, options) { - "use strict"; + API.splitTextToSize = function (text, maxlen, options) { + 'use strict'; options = options || {}; var fsize = options.fontSize || this.internal.getFontSize(), - newOptions = function(options) { + newOptions = function (options) { var widths = { 0: 1 }, @@ -325,7 +336,7 @@ module.exports = function (API) { if (!options.widths || !options.kerning) { var f = this.internal.getFont(options.fontName, options.fontStyle), - encoding = "Unicode"; + encoding = 'Unicode'; // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE // Actual JavaScript-native String's 16bit char codes used. // no multi-byte logic here @@ -358,16 +369,16 @@ module.exports = function (API) { paragraphs = String(text).split(/\r?\n/); } - // now we convert size (max length of line) into "font size units" - // at present time, the "font size unit" is always 'point' - // 'proportional' means, "in proportion to font size" + // now we convert size (max length of line) into 'font size units' + // at present time, the 'font size unit' is always 'point' + // 'proportional' means, 'in proportion to font size' var fontUnit_maxLen = (1.0 * this.internal.scaleFactor * maxlen) / fsize; - // at this time, fsize is always in "points" regardless of the default measurement unit of the doc. + // at this time, fsize is always in 'points' regardless of the default measurement unit of the doc. // this may change in the future? // until then, proportional_maxlen is likely to be in 'points' // If first line is to be indented (shorter or longer) than maxLen - // we indicate that by using CSS-style "text-indent" option. + // we indicate that by using CSS-style 'text-indent' option. // here it's in font units too (which is likely 'points') // it can be negative (which makes the first line longer than maxLen) newOptions.textIndent = options.textIndent