pdf-lib
Version:
Library for creating and modifying PDF files in JavaScript
228 lines (227 loc) • 12.3 kB
JavaScript
"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;