UNPKG

pdfkit2

Version:

A PDF generation library for Node.js

414 lines (409 loc) 13.6 kB
(function() { var LineWrapper, number; LineWrapper = require('../line_wrapper'); number = require('../object').number; module.exports = { initText: function() { this.x = 0; this.y = 0; return this._lineGap = 0; }, lineGap: function(_lineGap) { this._lineGap = _lineGap; return this; }, moveDown: function(lines) { if (lines == null) { lines = 1; } this.y += this.currentLineHeight(true) * lines + this._lineGap; return this; }, moveUp: function(lines) { if (lines == null) { lines = 1; } this.y -= this.currentLineHeight(true) * lines + this._lineGap; return this; }, _text: function(text, x, y, options, lineCallback) { var j, len, line, ref, wrapper; options = this._initOptions(x, y, options); text = text == null ? '' : '' + text; if (options.wordSpacing) { text = text.replace(/\s{2,}/g, ' '); } if (options.width) { wrapper = this._wrapper; if (!wrapper) { wrapper = new LineWrapper(this, options); wrapper.on('line', lineCallback); } this._wrapper = options.continued ? wrapper : null; this._textOptions = options.continued ? options : null; wrapper.wrap(text, options); } else { ref = text.split('\n'); for (j = 0, len = ref.length; j < len; j++) { line = ref[j]; lineCallback(line, options); } } return this; }, text: function(text, x, y, options) { return this._text(text, x, y, options, this._line.bind(this)); }, widthOfString: function(string, options) { if (options == null) { options = {}; } return this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1); }, heightOfString: function(text, options) { var height, lineGap, ref, x, y; if (options == null) { options = {}; } ref = this, x = ref.x, y = ref.y; options = this._initOptions(options); options.height = 2e308; lineGap = options.lineGap || this._lineGap || 0; this._text(text, this.x, this.y, options, (function(_this) { return function(line, options) { return _this.y += _this.currentLineHeight(true) + lineGap; }; })(this)); height = this.y - y; this.x = x; this.y = y; return height; }, list: function(list, x, y, options, wrapper) { var flatten, i, indent, itemIndent, items, label, level, levels, listType, midLine, numbers, r, unit; options = this._initOptions(x, y, options); listType = options.listType || 'bullet'; unit = Math.round(this._font.ascender / 1000 * this._fontSize); midLine = unit / 2; r = options.bulletRadius || unit / 3; indent = options.textIndent || (listType === 'bullet' ? r * 5 : unit * 2); itemIndent = options.bulletIndent || (listType === 'bullet' ? r * 8 : unit * 2); level = 1; items = []; levels = []; numbers = []; flatten = function(list) { var i, item, j, len, n, results; n = 1; results = []; for (i = j = 0, len = list.length; j < len; i = ++j) { item = list[i]; if (Array.isArray(item)) { level++; flatten(item); results.push(level--); } else { items.push(item); levels.push(level); if (listType !== 'bullet') { results.push(numbers.push(n++)); } else { results.push(void 0); } } } return results; }; flatten(list); label = function(n) { var letter, text, times; switch (listType) { case 'numbered': return n + "."; case 'lettered': letter = String.fromCharCode((n - 1) % 26 + 65); times = Math.floor((n - 1) / 26 + 1); text = Array(times + 1).join(letter); return text + "."; } }; wrapper = new LineWrapper(this, options); wrapper.on('line', this._line.bind(this)); level = 1; i = 0; wrapper.on('firstLine', (function(_this) { return function() { var diff, l, text; if ((l = levels[i++]) !== level) { diff = itemIndent * (l - level); _this.x += diff; wrapper.lineWidth -= diff; level = l; } switch (listType) { case 'bullet': _this.circle(_this.x - indent + r, _this.y + midLine, r); return _this.fill(); case 'numbered': case 'lettered': text = label(numbers[i - 1]); return _this._fragment(text, _this.x - indent, _this.y, options); } }; })(this)); wrapper.on('sectionStart', (function(_this) { return function() { var pos; pos = indent + itemIndent * (level - 1); _this.x += pos; return wrapper.lineWidth -= pos; }; })(this)); wrapper.on('sectionEnd', (function(_this) { return function() { var pos; pos = indent + itemIndent * (level - 1); _this.x -= pos; return wrapper.lineWidth += pos; }; })(this)); wrapper.wrap(items.join('\n'), options); return this; }, _initOptions: function(x, y, options) { var key, ref, val; if (x == null) { x = {}; } if (options == null) { options = {}; } if (typeof x === 'object') { options = x; x = null; } options = (function() { var k, opts, v; opts = {}; for (k in options) { v = options[k]; opts[k] = v; } return opts; })(); if (this._textOptions) { ref = this._textOptions; for (key in ref) { val = ref[key]; if (key !== 'continued') { if (options[key] == null) { options[key] = val; } } } } if (x != null) { this.x = x; } if (y != null) { this.y = y; } if (options.lineBreak !== false) { if (options.width == null) { options.width = this.page.width - this.x - this.page.margins.right; } } options.columns || (options.columns = 0); if (options.columnGap == null) { options.columnGap = 18; } return options; }, _line: function(text, options, wrapper) { var lineGap; if (options == null) { options = {}; } this._fragment(text, this.x, this.y, options); lineGap = options.lineGap || this._lineGap || 0; if (!wrapper) { return this.x += this.widthOfString(text); } else { return this.y += this.currentLineHeight(true) + lineGap; } }, _fragment: function(text, x, y, options) { var addSegment, align, base, characterSpacing, commands, d, dy, encoded, encodedWord, flush, hadOffset, i, j, key, last, len, len1, lineWidth, lineY, m, mode, name, pos, positions, positionsWord, ref, ref1, ref2, renderedWidth, scale, skew, space, spaceWidth, textWidth, val, word, wordSpacing, words; text = ('' + text).replace(/\n/g, ''); if (text.length === 0) { return; } align = options.align || 'left'; wordSpacing = options.wordSpacing || 0; characterSpacing = options.characterSpacing || 0; if (options.width) { switch (align) { case 'right': textWidth = this.widthOfString(text.replace(/\s+$/, ''), options); x += options.lineWidth - textWidth; break; case 'center': x += options.lineWidth / 2 - options.textWidth / 2; break; case 'justify': words = text.trim().split(/\s+/); textWidth = this.widthOfString(text.replace(/\s+/g, ''), options); spaceWidth = this.widthOfString(' ') + characterSpacing; wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth); } } if (typeof options.baseline === 'number') { dy = -options.baseline; } else { switch (options.baseline) { case 'svg-middle': dy = 0.5 * this._font.xHeight; break; case 'middle': case 'svg-central': dy = 0.5 * (this._font.descender + this._font.ascender); break; case 'bottom': case 'ideographic': dy = this._font.descender; break; case 'alphabetic': dy = 0; break; case 'mathematical': dy = 0.5 * this._font.ascender; break; case 'hanging': dy = 0.8 * this._font.ascender; break; case 'top': dy = this._font.ascender; break; default: dy = this._font.ascender; } dy = dy / 1000 * this._fontSize; } renderedWidth = options.textWidth + (wordSpacing * (options.wordCount - 1)) + (characterSpacing * (text.length - 1)); if (options.link != null) { this.link(x, y, renderedWidth, this.currentLineHeight(), options.link); } if (options.underline || options.strike) { this.save(); if (!options.stroke) { this.strokeColor.apply(this, this._fillColor); } lineWidth = this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10); this.lineWidth(lineWidth); d = options.underline ? 1 : 2; lineY = y + this.currentLineHeight() / d; if (options.underline) { lineY -= lineWidth; } this.moveTo(x, lineY); this.lineTo(x + renderedWidth, lineY); this.stroke(); this.restore(); } this.save(); if (options.oblique) { if (typeof options.oblique === 'number') { skew = -Math.tan(options.oblique * Math.PI / 180); } else { skew = -0.25; } this.transform(1, 0, 0, 1, x, y); this.transform(1, 0, skew, 1, -skew * dy, 0); this.transform(1, 0, 0, 1, -x, -y); } this.transform(1, 0, 0, -1, 0, this.page.height); y = this.page.height - y - dy; if ((base = this.page.fonts)[name = this._font.id] == null) { base[name] = this._font.ref(); } this.addContent("BT"); this.addContent("1 0 0 1 " + (number(x)) + " " + (number(y)) + " Tm"); this.addContent("/" + this._font.id + " " + (number(this._fontSize)) + " Tf"); mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0; if (mode) { this.addContent(mode + " Tr"); } if (characterSpacing) { this.addContent((number(characterSpacing)) + " Tc"); } if (wordSpacing) { words = text.trim().split(/\s+/); wordSpacing += this.widthOfString(' ') + characterSpacing; wordSpacing *= 1000 / this._fontSize; encoded = []; positions = []; for (j = 0, len = words.length; j < len; j++) { word = words[j]; ref = this._font.encode(word, options.features), encodedWord = ref[0], positionsWord = ref[1]; encoded.push.apply(encoded, encodedWord); positions.push.apply(positions, positionsWord); space = {}; ref1 = positions[positions.length - 1]; for (key in ref1) { val = ref1[key]; space[key] = val; } space.xAdvance += wordSpacing; positions[positions.length - 1] = space; } } else { ref2 = this._font.encode(text, options.features), encoded = ref2[0], positions = ref2[1]; } scale = this._fontSize / 1000; commands = []; last = 0; hadOffset = false; addSegment = (function(_this) { return function(cur) { var advance, hex; if (last < cur) { hex = encoded.slice(last, cur).join(''); advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth; commands.push("<" + hex + "> " + (number(-advance))); } return last = cur; }; })(this); flush = (function(_this) { return function(i) { addSegment(i); if (commands.length > 0) { _this.addContent("[" + (commands.join(' ')) + "] TJ"); return commands.length = 0; } }; })(this); for (i = m = 0, len1 = positions.length; m < len1; i = ++m) { pos = positions[i]; if (pos.xOffset || pos.yOffset) { flush(i); this.addContent("1 0 0 1 " + (number(x + pos.xOffset * scale)) + " " + (number(y + pos.yOffset * scale)) + " Tm"); flush(i + 1); hadOffset = true; } else { if (hadOffset) { this.addContent("1 0 0 1 " + (number(x)) + " " + (number(y)) + " Tm"); hadOffset = false; } if (pos.xAdvance - pos.advanceWidth !== 0) { addSegment(i + 1); } } x += pos.xAdvance * scale; } flush(i); this.addContent("ET"); return this.restore(); } }; }).call(this);