pdf.js
Version:
A PDF generation library for Node.js
117 lines (91 loc) • 4.4 kB
text/coffeescript
# This regular expression is used for splitting a string into wrappable words
WORD_RE = /([^ ,\/!.?:;\-\n]+[ ,\/!.?:;\-]*)|\n/g
{EventEmitter} = require 'events'
class LineWrapper extends EventEmitter
constructor: (@document) ->
@on 'firstLine', (options) =>
indent = options.indent or 0
@document.x += indent
options.lineWidth -= indent
@once 'line', =>
@document.x -= indent
options.lineWidth += indent
@on 'lastLine', (options) =>
align = options.align
options.align = 'left' if align is 'justify'
@once 'line', =>
@document.y += options.paragraphGap or 0
options.align = align
wrap: (paragraphs, options) ->
width = @document.widthOfString.bind(@document)
indent = options.indent or 0
charSpacing = options.characterSpacing or 0
wordSpacing = options.wordSpacing is 0
@columns = options.columns or 1
@columnGap = options.columnGap ? 18 # 1/4 inch
@lineWidth = (options.width - (@columnGap * (@columns - 1))) / @columns
@startY = @document.y
@column = 1
# calculate the maximum Y position the text can appear at
@maxY = @startY + options.height - @document.currentLineHeight()
# make sure we're actually on the page
# and that the first line of is never by
# itself at the bottom of a page (orphans)
nextY = @document.y + @document.currentLineHeight(true)
if @document.y > @maxY or nextY > @maxY
@nextSection()
# word width cache
wordWidths = {}
@emit 'sectionStart', options, this
for text, i in paragraphs
@emit 'firstLine', options, this
# split the line into words
words = text.match(WORD_RE)
# space left on the line to fill with words
spaceLeft = @lineWidth - indent
options.lineWidth = spaceLeft
len = words.length
buffer = ''
wc = 0
for word, wi in words
w = wordWidths[word] ?= width(word, options) + charSpacing + wordSpacing
if w > spaceLeft
options.textWidth = width(buffer.trim(), options) + wordSpacing * (wc - 1)
@emit 'line', buffer.trim(), options, this
# if we've reached the edge of the page,
# continue on a new page or column
if @document.y > @maxY
@nextSection()
# reset the space left and buffer
spaceLeft = @lineWidth - w
buffer = if word is '\n' then '' else word
wc = 1
else
# add the word to the buffer
spaceLeft -= w
buffer += word
wc++
# add the last line
@emit 'lastLine', options, this
options.textWidth = width(buffer.trim(), options) + wordSpacing * (wc - 1)
@emit 'line', buffer.trim(), options, this
# make sure that the first line of a paragraph is never by
# itself at the bottom of a page (orphans)
nextY = @document.y + @document.currentLineHeight(true)
if i < paragraphs.length - 1 and nextY > @maxY
@nextSection()
@emit 'sectionEnd', options, this
nextSection: (options) ->
@emit 'sectionEnd', options, this
if ++@column > @columns
@document.addPage()
@column = 1
@startY = @document.page.margins.top
@maxY = @document.page.maxY()
@emit 'pageBreak', options, this
else
@document.x += @lineWidth + @columnGap
@document.y = @startY
@emit 'columnBreak', options, this
@emit 'sectionStart', options, this
module.exports = LineWrapper