From 9a1bbd623aba57f33bebc711834b1e0084d52860 Mon Sep 17 00:00:00 2001 From: zhangshine Date: Thu, 29 Apr 2021 14:55:38 +0800 Subject: [PATCH] Fix font name escaping and convert js string to pdf name object --- src/jspdf.js | 12 ++------- src/libs/pdfname.js | 46 ++++++++++++++++++++++++++++++++ src/modules/utf8.js | 17 ++++++------ test/specs/pdfname.spec.mjs | 52 +++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 src/libs/pdfname.js create mode 100644 test/specs/pdfname.spec.mjs diff --git a/src/jspdf.js b/src/jspdf.js index 879a9a2d3..9820d1f0f 100644 --- a/src/jspdf.js +++ b/src/jspdf.js @@ -8,7 +8,7 @@ import { RGBColor } from "./libs/rgbcolor.js"; import { btoa } from "./libs/AtobBtoa.js"; import { console } from "./libs/console.js"; import { PDFSecurity } from "./libs/pdfsecurity.js"; - +import { toPDFName } from "./libs/pdfname.js"; /** * jsPDF's Internal PubSub Implementation. * Backward compatible rewritten on 2014 by @@ -1997,26 +1997,18 @@ function jsPDF(options) { }); var putFont = function(font) { - var pdfEscapeWithNeededParanthesis = function(text, flags) { - var addParanthesis = text.indexOf(" ") !== -1; // no space in string - return addParanthesis - ? "(" + pdfEscape(text, flags) + ")" - : pdfEscape(text, flags); - }; - events.publish("putFont", { font: font, out: out, newObject: newObject, putStream: putStream, - pdfEscapeWithNeededParanthesis: pdfEscapeWithNeededParanthesis }); if (font.isAlreadyPutted !== true) { font.objectNumber = newObject(); out("<<"); out("/Type /Font"); - out("/BaseFont /" + pdfEscapeWithNeededParanthesis(font.postScriptName)); + out("/BaseFont /" + toPDFName(font.postScriptName)); out("/Subtype /Type1"); if (typeof font.encoding === "string") { out("/Encoding /" + font.encoding); diff --git a/src/libs/pdfname.js b/src/libs/pdfname.js new file mode 100644 index 000000000..8b6b3e6ca --- /dev/null +++ b/src/libs/pdfname.js @@ -0,0 +1,46 @@ +/** + * Convert string to `PDF Name Object`. + * Detail: PDF Reference 1.3 - Chapter 3.2.4 Name Object + * @param str + */ +function toPDFName(str) { + // eslint-disable-next-line no-control-regex + if(/[^\u0000-\u00ff]/.test(str)){ // non ascii string + throw new Error('Invalid PDF Name Object: ' + str + ', Only accept ASCII characters.'); + } + var result = "", + strLength = str.length; + for (var i = 0; i < strLength; i++) { + var charCode = str.charCodeAt(i); + if ( + charCode < 0x21 || + charCode === 0x23 /* # */ || + charCode === 0x25 /* % */ || + charCode === 0x28 /* ( */ || + charCode === 0x29 /* ) */ || + charCode === 0x2f /* / */ || + charCode === 0x3c /* < */ || + charCode === 0x3e /* > */ || + charCode === 0x5b /* [ */ || + charCode === 0x5d /* ] */ || + charCode === 0x7b /* { */ || + charCode === 0x7d /* } */ || + charCode > 0x7e + ) { + // Char CharCode hexStr paddingHexStr Result + // "\t" 9 9 09 #09 + // " " 32 20 20 #20 + // "©" 169 a9 a9 #a9 + var hexStr = charCode.toString(16), + paddingHexStr = ("0" + hexStr).slice(-2); + + result += "#" + paddingHexStr; + } else { + // Other ASCII printable characters between 0x21 <= X <= 0x7e + result += str[i]; + } + } + return result; +} + +export { toPDFName }; diff --git a/src/modules/utf8.js b/src/modules/utf8.js index c7fd29ad7..91ce12852 100644 --- a/src/modules/utf8.js +++ b/src/modules/utf8.js @@ -1,4 +1,5 @@ import { jsPDF } from "../jspdf.js"; +import { toPDFName } from "../libs/pdfname.js"; /** * @name utf8 @@ -86,7 +87,6 @@ import { jsPDF } from "../jspdf.js"; var out = options.out; var newObject = options.newObject; var putStream = options.putStream; - var pdfEscapeWithNeededParanthesis = options.pdfEscapeWithNeededParanthesis; if ( font.metadata instanceof jsPDF.API.TTFFont && @@ -112,7 +112,7 @@ import { jsPDF } from "../jspdf.js"; var fontDescriptor = newObject(); out("<<"); out("/Type /FontDescriptor"); - out("/FontName /" + pdfEscapeWithNeededParanthesis(font.fontName)); + out("/FontName /" + toPDFName(font.fontName)); out("/FontFile2 " + fontTable + " 0 R"); out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox)); out("/Flags " + font.metadata.flags); @@ -127,7 +127,7 @@ import { jsPDF } from "../jspdf.js"; var DescendantFont = newObject(); out("<<"); out("/Type /Font"); - out("/BaseFont /" + pdfEscapeWithNeededParanthesis(font.fontName)); + out("/BaseFont /" + toPDFName(font.fontName)); out("/FontDescriptor " + fontDescriptor + " 0 R"); out("/W " + jsPDF.API.PDFObject.convert(widths)); out("/CIDToGIDMap /Identity"); @@ -147,7 +147,7 @@ import { jsPDF } from "../jspdf.js"; out("/Type /Font"); out("/Subtype /Type0"); out("/ToUnicode " + cmap + " 0 R"); - out("/BaseFont /" + pdfEscapeWithNeededParanthesis(font.fontName)); + out("/BaseFont /" + toPDFName(font.fontName)); out("/Encoding /" + font.encoding); out("/DescendantFonts [" + DescendantFont + " 0 R]"); out(">>"); @@ -169,7 +169,6 @@ import { jsPDF } from "../jspdf.js"; var out = options.out; var newObject = options.newObject; var putStream = options.putStream; - var pdfEscapeWithNeededParanthesis = options.pdfEscapeWithNeededParanthesis; if ( font.metadata instanceof jsPDF.API.TTFFont && @@ -200,7 +199,7 @@ import { jsPDF } from "../jspdf.js"; out("/FontFile2 " + fontTable + " 0 R"); out("/Flags 96"); out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox)); - out("/FontName /" + pdfEscapeWithNeededParanthesis(font.fontName)); + out("/FontName /" + toPDFName(font.fontName)); out("/ItalicAngle " + font.metadata.italicAngle); out("/Ascent " + font.metadata.ascender); out(">>"); @@ -215,7 +214,7 @@ import { jsPDF } from "../jspdf.js"; "<{ + it("ASCII control characters to pdf name", ()=>{ + expect(toPDFName("\t")).toBe("#09"); + }); + + it("ASCII printable characters to pdf name", ()=>{ + expect(toPDFName(" ")).toBe("#20"); + expect(toPDFName("!")).toBe("!"); + expect(toPDFName("#")).toBe("#23"); + expect(toPDFName("$")).toBe("$"); + expect(toPDFName("%")).toBe("#25"); + expect(toPDFName("&")).toBe("&"); + expect(toPDFName("'")).toBe("'"); + expect(toPDFName("(")).toBe("#28"); + expect(toPDFName(")")).toBe("#29"); + expect(toPDFName("*")).toBe("*"); + expect(toPDFName("+")).toBe("+"); + expect(toPDFName(",")).toBe(","); + expect(toPDFName("-")).toBe("-"); + expect(toPDFName(".")).toBe("."); + expect(toPDFName("/")).toBe("#2f"); + expect(toPDFName("0123456789")).toBe("0123456789"); + expect(toPDFName(":")).toBe(":"); + expect(toPDFName(";")).toBe(";"); + expect(toPDFName("<")).toBe("#3c"); + expect(toPDFName("=")).toBe("="); + expect(toPDFName(">")).toBe("#3e"); + expect(toPDFName("?")).toBe("?"); + expect(toPDFName("@")).toBe("@"); + expect(toPDFName("ABCDEFGHIJKLMNOPQRSTUVWXYZ")).toBe("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + expect(toPDFName("[")).toBe("#5b"); + expect(toPDFName("\\")).toBe("\\"); + expect(toPDFName("]")).toBe("#5d"); + expect(toPDFName("^")).toBe("^"); + expect(toPDFName("_")).toBe("_"); + expect(toPDFName("`")).toBe("`"); + expect(toPDFName("abcdefghijklmnopqrstuvwxyz")).toBe("abcdefghijklmnopqrstuvwxyz"); + expect(toPDFName("{")).toBe("#7b"); + expect(toPDFName("|")).toBe("|"); + expect(toPDFName("}")).toBe("#7d"); + expect(toPDFName("~")).toBe("~"); + expect(toPDFName("\u007f")).toBe("#7f"); + }); + + it("The extended ASCII codes to pdf name", ()=>{ + expect(toPDFName("©")).toBe("#a9"); + expect(toPDFName("®")).toBe("#ae"); + expect(toPDFName("ÿ")).toBe("#ff"); + }); +});