UNPKG

pdf-lib

Version:

Library for creating and modifying PDF files in JavaScript

228 lines (227 loc) 12.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; } Object.defineProperty(exports, "__esModule", { value: true }); var fontkit_1 = __importDefault(require("@pdf-lib/fontkit")); var first_1 = __importDefault(require("lodash/first")); var flatten_1 = __importDefault(require("lodash/flatten")); var sortBy_1 = __importDefault(require("lodash/sortBy")); var sortedUniqBy_1 = __importDefault(require("lodash/sortedUniqBy")); var sum_1 = __importDefault(require("lodash/sum")); var zip_1 = __importDefault(require("lodash/zip")); var pako_1 = __importDefault(require("pako")); var PDFDocument_1 = __importDefault(require("../../pdf-document/PDFDocument")); var pdf_objects_1 = require("../../pdf-objects"); var utils_1 = require("../../../utils"); var validate_1 = require("../../../utils/validate"); var CMap_1 = require("./CMap"); // prettier-ignore var makeFontFlags = function (options) { var flags = 0; // tslint:disable-next-line:no-bitwise var flipBit = function (bit) { flags |= (1 << (bit - 1)); }; if (options.fixedPitch) flipBit(1); if (options.serif) flipBit(2); if (options.symbolic) flipBit(3); if (options.script) flipBit(4); if (options.nonsymbolic) flipBit(6); if (options.italic) flipBit(7); if (options.allCap) flipBit(17); if (options.smallCap) flipBit(18); if (options.forceBold) flipBit(19); return flags; }; var addRandomSuffix = function (prefix) { return (prefix || 'Font') + "-rand_" + Math.floor(Math.random() * 10000); }; /** * This Factory supports embedded fonts. * * A note of thanks to the developers of https://github.com/devongovett/pdfkit, * as this class borrows heavily from: * https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/font/embedded.coffee */ var PDFEmbeddedFontFactory = /** @class */ (function () { function PDFEmbeddedFontFactory(fontData) { var _this = this; /** * Embeds the font into a [[PDFDocument]]. * * @param pdfDoc A `PDFDocument` object into which the font will be embedded. * * @returns A `PDFIndirectReference` to the font dictionary that was * embedded in the `PDFDocument`. */ this.embedFontIn = function (pdfDoc) { validate_1.validate(pdfDoc, validate_1.isInstance(PDFDocument_1.default), 'PDFFontFactory.embedFontIn: "pdfDoc" must be an instance of PDFDocument'); var fontName = addRandomSuffix(_this.font.postscriptName); return _this.embedFontDictionaryIn(pdfDoc, fontName); }; /** * Encode the JavaScript string into this font. JavaScript encodes strings in * Unicode, but embedded fonts use their own custom encodings. So this method * should be used to encode text before passing the encoded text to one of the * text showing operators, such as [[drawText]] or [[drawLinesOfText]]. * * @param text The string of text to be encoded. * * @returns A `PDFHexString` of the encoded text. */ this.encodeText = function (text) { var glyphs = _this.font.layout(text).glyphs; return pdf_objects_1.PDFHexString.fromString(glyphs.map(function (glyph) { return utils_1.toHexStringOfMinLength(glyph.id, 4); }).join('')); }; /** * Measures the width of the JavaScript string when displayed as glyphs of * this font of a particular `size`. * * @param text The string of text to be measured. * @param size The size to be used when calculating the text's width. * * @returns A `number` representing the width of the text. */ this.widthOfTextAtSize = function (text, size) { var glyphs = _this.font.layout(text).glyphs; // The advanceWidth takes into account kerning automatically, so we don't // have to do that manually like we do for the standard fonts. var widths = glyphs.map(function (glyph) { return glyph.advanceWidth * _this.scale; }); var scale = size / 1000; return sum_1.default(widths) * scale; }; /** * Measures the height of this font at a particular size. Note that the height * of the font is independent of the particular glyphs being displayed, so * this method does not accept a `text` param, like * [[PDFStandardFontFactory.widthOfTextAtSize]] does. */ this.heightOfFontAtSize = function (size) { var _a = _this.font, ascent = _a.ascent, descent = _a.descent, bbox = _a.bbox; var yTop = (ascent || bbox.maxY) * _this.scale; var yBottom = (descent || bbox.minY) * _this.scale; return ((yTop - yBottom) / 1000) * size; }; this.embedFontDictionaryIn = function (pdfDoc, fontName) { var cidFontDictRef = _this.embedCIDFontDictionaryIn(pdfDoc, fontName); var unicodeCMap = _this.embedUnicodeCmapIn(pdfDoc); var fontDict = pdf_objects_1.PDFDictionary.from({ Type: pdf_objects_1.PDFName.from('Font'), Subtype: pdf_objects_1.PDFName.from('Type0'), BaseFont: pdf_objects_1.PDFName.from(fontName), Encoding: pdf_objects_1.PDFName.from('Identity-H'), DescendantFonts: pdf_objects_1.PDFArray.fromArray([cidFontDictRef], pdfDoc.index), ToUnicode: unicodeCMap, }, pdfDoc.index); var fontDictRef = pdfDoc.register(fontDict); return fontDictRef; }; this.embedCIDFontDictionaryIn = function (pdfDoc, fontName) { var fontDescriptorRef = _this.embedFontDescriptorIn(pdfDoc, fontName); var widths = _this.deriveWidths(pdfDoc); var cidFontDict = pdf_objects_1.PDFDictionary.from({ Type: pdf_objects_1.PDFName.from('Font'), Subtype: pdf_objects_1.PDFName.from(_this.font.cff ? 'CIDFontType0' : 'CIDFontType2'), BaseFont: pdf_objects_1.PDFName.from(fontName), CIDSystemInfo: pdf_objects_1.PDFDictionary.from({ Registry: pdf_objects_1.PDFString.fromString('Adobe'), Ordering: pdf_objects_1.PDFString.fromString('Identity'), Supplement: pdf_objects_1.PDFNumber.fromNumber(0), }, pdfDoc.index), FontDescriptor: fontDescriptorRef, W: widths, }, pdfDoc.index); var cidFontRef = pdfDoc.register(cidFontDict); return cidFontRef; }; this.embedFontDescriptorIn = function (pdfDoc, fontName) { var fontFlags = _this.deriveFontFlags(); var fontStreamRef = _this.embedFontStreamIn(pdfDoc); var _a = _this.font, italicAngle = _a.italicAngle, ascent = _a.ascent, descent = _a.descent, capHeight = _a.capHeight, xHeight = _a.xHeight, bbox = _a.bbox; var fontDescriptor = pdf_objects_1.PDFDictionary.from({ Type: pdf_objects_1.PDFName.from('FontDescriptor'), FontName: pdf_objects_1.PDFName.from(fontName), Flags: fontFlags, FontBBox: pdf_objects_1.PDFArray.fromArray([ pdf_objects_1.PDFNumber.fromNumber(bbox.minX * _this.scale), pdf_objects_1.PDFNumber.fromNumber(bbox.minY * _this.scale), pdf_objects_1.PDFNumber.fromNumber(bbox.maxX * _this.scale), pdf_objects_1.PDFNumber.fromNumber(bbox.maxY * _this.scale), ], pdfDoc.index), ItalicAngle: pdf_objects_1.PDFNumber.fromNumber(italicAngle), Ascent: pdf_objects_1.PDFNumber.fromNumber(ascent * _this.scale), Descent: pdf_objects_1.PDFNumber.fromNumber(descent * _this.scale), CapHeight: pdf_objects_1.PDFNumber.fromNumber((capHeight || ascent) * _this.scale), XHeight: pdf_objects_1.PDFNumber.fromNumber((xHeight || 0) * _this.scale), // Not sure how to compute/find this, nor is anybody else really: // https://stackoverflow.com/questions/35485179/stemv-value-of-the-truetype-font StemV: pdf_objects_1.PDFNumber.fromNumber(0), }, pdfDoc.index); var fontFileKey = _this.font.cff ? 'FontFile3' : 'FontFile2'; fontDescriptor.set(fontFileKey, fontStreamRef); var fontDescriptorRef = pdfDoc.register(fontDescriptor); return fontDescriptorRef; }; this.embedFontStreamIn = function (pdfDoc) { var deflatedFontData = pako_1.default.deflate(_this.fontData); var fontStreamDict = pdf_objects_1.PDFDictionary.from({ Filter: pdf_objects_1.PDFName.from('FlateDecode'), Length: pdf_objects_1.PDFNumber.fromNumber(deflatedFontData.length), }, pdfDoc.index); if (_this.font.cff) { fontStreamDict.set('Subtype', pdf_objects_1.PDFName.from('CIDFontType0C')); } var fontStream = pdf_objects_1.PDFRawStream.from(fontStreamDict, deflatedFontData); var fontStreamRef = pdfDoc.register(fontStream); return fontStreamRef; }; this.embedUnicodeCmapIn = function (pdfDoc) { var streamContents = pako_1.default.deflate(utils_1.typedArrayFor(CMap_1.createCmap(_this.allGlyphsInFontSortedById))); var cmapStreamDict = pdf_objects_1.PDFDictionary.from({ Filter: pdf_objects_1.PDFName.from('FlateDecode'), Length: pdf_objects_1.PDFNumber.fromNumber(streamContents.length), }, pdfDoc.index); var cmapStream = pdf_objects_1.PDFRawStream.from(cmapStreamDict, streamContents); var cmapStreamRef = pdfDoc.register(cmapStream); return cmapStreamRef; }; this.deriveFontFlags = function () { // From: https://github.com/foliojs/pdfkit/blob/83f5f7243172a017adcf6a7faa5547c55982c57b/lib/font/embedded.js#L123-L129 var familyClass = _this.font['OS/2'] ? _this.font['OS/2'].sFamilyClass : 0; var flags = makeFontFlags({ fixedPitch: _this.font.post.isFixedPitch, serif: 1 <= familyClass && familyClass <= 7, symbolic: true, script: familyClass === 10, italic: _this.font.head.macStyle.italic, }); return pdf_objects_1.PDFNumber.fromNumber(flags); }; this.deriveWidths = function (pdfDoc) { var glyphs = _this.allGlyphsInFontSortedById; var sectionDelimiters = utils_1.mapIntoContiguousGroups(glyphs, function (glyph) { return glyph.id; }, function (glyph) { return pdf_objects_1.PDFNumber.fromNumber(glyph.id); }).map(function (id) { return first_1.default(id); }); var glyphsWidths = utils_1.mapIntoContiguousGroups(glyphs, function (glyph) { return glyph.id; }, function (glyph) { return pdf_objects_1.PDFNumber.fromNumber(glyph.advanceWidth * _this.scale); }).map(function (groups) { return pdf_objects_1.PDFArray.fromArray(groups, pdfDoc.index); }); var widths = flatten_1.default(zip_1.default(sectionDelimiters, glyphsWidths)); return pdf_objects_1.PDFArray.fromArray(widths, pdfDoc.index); }; validate_1.validate(fontData, validate_1.isInstance(Uint8Array), '"fontData" must be a Uint8Array'); this.fontData = fontData; this.font = fontkit_1.default.create(fontData); this.scale = 1000 / this.font.unitsPerEm; var glyphs = this.font.characterSet.map(function (cp) { return _this.font.glyphForCodePoint(cp); }); this.allGlyphsInFontSortedById = sortedUniqBy_1.default(sortBy_1.default(glyphs, 'id'), 'id'); } PDFEmbeddedFontFactory.for = function (fontData) { return new PDFEmbeddedFontFactory(fontData); }; return PDFEmbeddedFontFactory; }()); exports.default = PDFEmbeddedFontFactory;