UNPKG

pdfkit

Version:

A PDF generation library for Node.js

237 lines (214 loc) 8.66 kB
(function() { /* PDFFont - embeds fonts in PDF documents By Devon Govett */ var AFMFont, PDFFont, Subset, TTFFont, zlib; var __hasProp = Object.prototype.hasOwnProperty, __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (__hasProp.call(this, i) && this[i] === item) return i; } return -1; }; TTFFont = require('./font/ttf'); AFMFont = require('./font/afm'); Subset = require('./font/subset'); zlib = require('zlib'); PDFFont = (function() { var toUnicodeCmap; function PDFFont(document, filename, family, id) { var _ref; this.document = document; this.filename = filename; this.family = family; this.id = id; if (_ref = this.filename, __indexOf.call(this._standardFonts, _ref) >= 0) { this.embedStandard(); } else if (/\.(ttf|ttc)$/i.test(this.filename)) { this.ttf = TTFFont.open(this.filename, this.family); this.subset = new Subset(this.ttf); this.registerTTF(); } else if (/\.dfont$/i.test(this.filename)) { this.ttf = TTFFont.fromDFont(this.filename, this.family); this.subset = new Subset(this.ttf); this.registerTTF(); } else { throw new Error('Not a supported font format or standard PDF font.'); } } PDFFont.prototype.use = function(characters) { var _ref; return (_ref = this.subset) != null ? _ref.use(characters) : void 0; }; PDFFont.prototype.embed = function(fn) { if (this.isAFM) return fn(); return this.embedTTF(fn); }; PDFFont.prototype.encode = function(text) { var _ref; return ((_ref = this.subset) != null ? _ref.encodeText(text) : void 0) || text; }; PDFFont.prototype.registerTTF = function() { var e, gid, hi, i, low, raw, _ref; this.scaleFactor = 1000.0 / this.ttf.head.unitsPerEm; this.bbox = (function() { var _i, _len, _ref, _results; _ref = this.ttf.bbox; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { e = _ref[_i]; _results.push(Math.round(e * this.scaleFactor)); } return _results; }).call(this); this.stemV = 0; if (this.ttf.post.exists) { raw = this.ttf.post.italic_angle; hi = raw >> 16; low = raw & 0xFF; if (hi & 0x8000 !== 0) hi = -((hi ^ 0xFFFF) + 1); this.italicAngle = +("" + hi + "." + low); } else { this.italicAngle = 0; } this.ascender = Math.round(this.ttf.ascender * this.scaleFactor); this.decender = Math.round(this.ttf.decender * this.scaleFactor); this.lineGap = Math.round(this.ttf.lineGap * this.scaleFactor); this.capHeight = (this.ttf.os2.exists && this.ttf.os2.capHeight) || this.ascender; this.xHeight = (this.ttf.os2.exists && this.ttf.os2.xHeight) || 0; this.familyClass = (this.ttf.os2.exists && this.ttf.os2.familyClass || 0) >> 8; this.isSerif = (_ref = this.familyClass) === 1 || _ref === 2 || _ref === 3 || _ref === 4 || _ref === 5 || _ref === 7; this.isScript = this.familyClass === 10; this.flags = 0; if (this.ttf.post.isFixedPitch) this.flags |= 1 << 0; if (this.isSerif) this.flags |= 1 << 1; if (this.isScript) this.flags |= 1 << 3; if (this.italicAngle !== 0) this.flags |= 1 << 6; this.flags |= 1 << 5; this.cmap = this.ttf.cmap.unicode; if (!this.cmap) throw new Error('No unicode cmap for font'); this.hmtx = this.ttf.hmtx; this.charWidths = (function() { var _ref2, _results; _ref2 = this.cmap.codeMap; _results = []; for (i in _ref2) { gid = _ref2[i]; if (i >= 32) { _results.push(Math.round(this.hmtx.widths[gid] * this.scaleFactor)); } } return _results; }).call(this); return this.ref = this.document.ref({ Type: 'Font', Subtype: 'TrueType' }); }; PDFFont.prototype.embedTTF = function(fn) { var data; var _this = this; data = this.subset.encode(); return zlib.deflate(data, function(err, compressedData) { var charWidths, cmap, code, firstChar, glyph, key, ref, val; if (err) throw err; _this.fontfile = _this.document.ref({ Length: compressedData.length, Length1: data.length, Filter: 'FlateDecode' }); _this.fontfile.add(compressedData); _this.descriptor = _this.document.ref({ Type: 'FontDescriptor', FontName: _this.subset.postscriptName, FontFile2: _this.fontfile, FontBBox: _this.bbox, Flags: _this.flags, StemV: _this.stemV, ItalicAngle: _this.italicAngle, Ascent: _this.ascender, Descent: _this.decender, CapHeight: _this.capHeight, XHeight: _this.xHeight }); firstChar = +Object.keys(_this.subset.cmap)[0]; charWidths = (function() { var _ref, _results; _ref = this.subset.cmap; _results = []; for (code in _ref) { glyph = _ref[code]; _results.push(Math.round(this.ttf.hmtx.forGlyph(glyph).advance * this.scaleFactor)); } return _results; }).call(_this); cmap = _this.document.ref(); cmap.add(toUnicodeCmap(_this.subset.subset)); ref = { Type: 'Font', BaseFont: _this.subset.postscriptName, Subtype: 'TrueType', FontDescriptor: _this.descriptor, FirstChar: firstChar, LastChar: firstChar + charWidths.length - 1, Widths: _this.document.ref(charWidths), Encoding: 'MacRomanEncoding', ToUnicode: cmap }; for (key in ref) { val = ref[key]; _this.ref.data[key] = val; } return cmap.finalize(_this.document.compress, fn); }); }; toUnicodeCmap = function(map) { var code, codes, range, unicode, unicodeMap, _i, _len; unicodeMap = '/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <<\n /Registry (Adobe)\n /Ordering (UCS)\n /Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<00><ff>\nendcodespacerange'; codes = Object.keys(map).sort(function(a, b) { return a - b; }); range = []; for (_i = 0, _len = codes.length; _i < _len; _i++) { code = codes[_i]; if (range.length >= 100) { unicodeMap += "\n" + range.length + " beginbfchar\n" + (range.join('\n')) + "\nendbfchar"; range = []; } unicode = ('0000' + map[code].toString(16)).slice(-4); code = (+code).toString(16); range.push("<" + code + "><" + unicode + ">"); } if (range.length) { unicodeMap += "\n" + range.length + " beginbfchar\n" + (range.join('\n')) + "\nendbfchar\n"; } return unicodeMap += 'endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend'; }; PDFFont.prototype.embedStandard = function() { var font; this.isAFM = true; font = AFMFont.open(__dirname + ("/font/data/" + this.filename + ".afm")); this.ascender = font.ascender, this.decender = font.decender, this.bbox = font.bbox, this.lineGap = font.lineGap, this.charWidths = font.charWidths; return this.ref = this.document.ref({ Type: 'Font', BaseFont: this.filename, Subtype: 'Type1' }); }; PDFFont.prototype._standardFonts = ["Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique", "Helvetica", "Helvetica-Bold", "Helvetica-Oblique", "Helvetica-BoldOblique", "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic", "Symbol", "ZapfDingbats"]; PDFFont.prototype.widthOfString = function(string, size) { var charCode, i, scale, width, _ref; string = '' + string; width = 0; for (i = 0, _ref = string.length; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) { charCode = string.charCodeAt(i) - (this.isAFM ? 0 : 32); width += this.charWidths[charCode] || 0; } scale = size / 1000; return width * scale; }; PDFFont.prototype.lineHeight = function(size, includeGap) { var gap; if (includeGap == null) includeGap = false; gap = includeGap ? this.lineGap : 0; return (this.ascender + gap - this.decender) / 1000 * size; }; return PDFFont; })(); module.exports = PDFFont; }).call(this);