mirror of
https://github.com/foliojs/pdfkit.git
synced 2025-12-08 20:15:54 +00:00
Should solve a huge number of issues. The regular express based word matching from before was not good. It was overzealous and caused the most bugs of anything in the project. It also didn't work at all for languages like Chinese which don't have spaces between words. The new wrapping algorithm supports all of this. It is a separate module at http://github.com/devongovett/linebreak.
130 lines
4.2 KiB
CoffeeScript
130 lines
4.2 KiB
CoffeeScript
{EventEmitter} = require 'events'
|
|
LineBreaker = require 'linebreak'
|
|
|
|
class LineWrapper extends EventEmitter
|
|
constructor: (@document) ->
|
|
@on 'firstLine', (options) =>
|
|
indent = options.indent or 0
|
|
@document.x += indent
|
|
@lineWidth -= indent
|
|
|
|
@once 'line', =>
|
|
@document.x -= indent
|
|
@lineWidth += indent
|
|
|
|
@on 'lastLine', (options) =>
|
|
align = options.align
|
|
options.align = 'left' if align is 'justify'
|
|
@lastLine = true
|
|
|
|
@once 'line', =>
|
|
@document.y += options.paragraphGap or 0
|
|
options.align = align
|
|
@lastLine = false
|
|
|
|
wrap: (text, 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
|
|
|
|
# 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
|
|
|
|
breaker = new LineBreaker(text)
|
|
last = null
|
|
buffer = ''
|
|
textWidth = 0
|
|
wc = 0
|
|
|
|
emitLine = =>
|
|
options.textWidth = textWidth + wordSpacing * (wc - 1)
|
|
options.wordCount = wc
|
|
options.lineWidth = @lineWidth
|
|
@emit 'line', buffer, options, this
|
|
|
|
spaceLeft = @lineWidth - indent
|
|
|
|
while bk = breaker.nextBreak()
|
|
if not last? or last.required
|
|
@emit 'firstLine', options, this
|
|
|
|
word = text.slice(last?.position or 0, bk.position)
|
|
w = wordWidths[word] ?= width(word, options) + charSpacing + wordSpacing
|
|
|
|
if w <= spaceLeft
|
|
buffer += word
|
|
lineWidth += w
|
|
wc++
|
|
|
|
if bk.required or w > spaceLeft
|
|
if bk.required
|
|
@emit 'lastLine', options, this
|
|
|
|
emitLine()
|
|
|
|
# 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
|
|
if bk.required
|
|
spaceLeft = @lineWidth - indent
|
|
buffer = ''
|
|
lineWidth = 0
|
|
wc = 0
|
|
else
|
|
# reset the space left and buffer
|
|
spaceLeft = @lineWidth - w
|
|
buffer = word
|
|
lineWidth = w
|
|
wc = 1
|
|
else
|
|
spaceLeft -= w
|
|
|
|
last = bk
|
|
|
|
if wc > 0
|
|
@emit 'lastLine', options, this
|
|
emitLine()
|
|
|
|
@emit 'sectionEnd', options, this
|
|
|
|
nextSection: (options) ->
|
|
@emit 'sectionEnd', options, this
|
|
|
|
if ++@column > @columns
|
|
x = @document.x
|
|
@document.addPage()
|
|
@column = 1
|
|
@startY = @document.page.margins.top
|
|
@maxY = @document.page.maxY()
|
|
@document.x = x
|
|
@emit 'pageBreak', options, this
|
|
|
|
else
|
|
@document.x += @lineWidth + @columnGap
|
|
@document.y = @startY
|
|
@emit 'columnBreak', options, this
|
|
|
|
@emit 'sectionStart', options, this
|
|
|
|
module.exports = LineWrapper
|