UNPKG

pdf.js

Version:

A PDF generation library for Node.js

211 lines (159 loc) 6.39 kB
WORD_RE = /([^ ,\/!.?:;\-\n]+[ ,\/!.?:;\-]*)|\n/g LineWrapper = require './line_wrapper' module.exports = initText: -> # Current coordinates @x = 0 @y = 0 @_lineGap = 0 # Keeps track of what has been set in the document @_textState = mode: 0 wordSpacing: 0 characterSpacing: 0 lineGap: (@_lineGap) -> return this moveDown: (lines = 1) -> @y += @currentLineHeight(true) * lines + @_lineGap return this moveUp: (lines = 1) -> @y -= @currentLineHeight(true) * lines + @_lineGap return this text: (text, x, y, options) -> options = @_initOptions(x, y, options) # Convert text to a string text = '' + text # if the wordSpacing option is specified, remove multiple consecutive spaces if options.wordSpacing text = text.replace(/\s{2,}/g, ' ') paragraphs = text.split '\n' # word wrapping if options.width wrapper = new LineWrapper(this) wrapper.on 'line', @_line.bind(this) wrapper.wrap(paragraphs, options) # render paragraphs as single lines else @_line line, options for line in paragraphs return this list: (list, x, y, options, wrapper) -> options = @_initOptions(x, y, options) r = Math.round (@_font.ascender / 1000 * @_fontSize) / 3 indent = options.textIndent or r * 5 itemIndent = options.bulletIndent or r * 8 level = 1 items = [] levels = [] flatten = (list) -> for item, i in list if Array.isArray(item) level++ flatten(item) level-- else items.push(item) levels.push(level) flatten(list) wrapper = new LineWrapper(this) wrapper.on 'line', @_line.bind(this) level = 1 i = 0 wrapper.on 'firstLine', => if (l = levels[i++]) isnt level diff = itemIndent * (l - level) @x += diff wrapper.lineWidth -= diff level = l @circle @x - indent + r, @y + r + (r / 2), r @fill() wrapper.on 'sectionStart', => pos = indent + itemIndent * (level - 1) @x += pos wrapper.lineWidth -= pos wrapper.on 'sectionEnd', => pos = indent + itemIndent * (level - 1) @x -= pos wrapper.lineWidth += pos wrapper.wrap(items, options) @x -= indent return this _initOptions: (x = {}, y, options = {}) -> if typeof x is 'object' options = x x = null # clone options object options = do -> opts = {} opts[k] = v for k, v of options return opts # Update the current position if x? or y? @x = x or @x @y = y or @y # wrap to margins if no x or y position passed else margins = @page.margins options.width ?= @page.width - @x - margins.right options.height ?= @page.height - @y - margins.bottom options.columns ||= 0 options.columnGap ?= 18 # 1/4 inch return options widthOfString: (string, options = {}) -> @_font.widthOfString(string, @_fontSize) + (options.characterSpacing or 0) * (string.length - 1) _line: (text, options) -> @_fragment text, @x, @y, options lineGap = options.lineGap or @_lineGap or 0 @y += @currentLineHeight(true) + lineGap _fragment: (text, x, y, options) -> text = '' + text return if text.length is 0 state = @_textState # handle options align = options.align or 'left' wordSpacing = options.wordSpacing or 0 characterSpacing = options.characterSpacing or 0 # text alignments if options.width switch align when 'right' x += options.lineWidth - options.textWidth when 'center' x += options.lineWidth / 2 - options.textWidth / 2 when 'justify' # split the line into words words = text.match(WORD_RE) break unless words # calculate the word spacing value textWidth = @widthOfString(text.replace(/\s+/g, ''), options) spaceWidth = @widthOfString(' ') + characterSpacing wordSpacing = (options.lineWidth - textWidth) / (words.length - 1) - spaceWidth # flip coordinate system y = @page.height - y - (@_font.ascender / 1000 * @_fontSize) # add current font to page if necessary @page.fonts[@_font.id] ?= @_font.ref # tell the font subset to use the characters @_font.use(text) # encode the text based on the font subset, # and then convert it to hex text = @_font.encode(text) text = (text.charCodeAt(i).toString(16) for i in [0...text.length]).join('') # begin the text object @addContent "BT" # text position @addContent "#{x} #{y} Td" # font and font size @addContent "/#{@_font.id} #{@_fontSize} Tf" # rendering mode mode = if options.fill and options.stroke then 2 else if options.stroke then 1 else 0 @addContent "#{mode} Tr" unless mode is state.mode # Word spacing @addContent wordSpacing + ' Tw' unless wordSpacing is state.wordSpacing # Character spacing @addContent characterSpacing + ' Tc' unless characterSpacing is state.characterSpacing # add the actual text @addContent "<#{text}> Tj" # end the text object @addContent "ET" # keep track of text states state.mode = mode state.wordSpacing = wordSpacing