<commit_before>{EventEmitter} = require 'events'
LineBreaker = require 'linebreak'

class LineWrapper extends EventEmitter
  constructor: (@document, options) ->
    @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
    @startX    = @document.x
    @startY    = @document.y
    @column    = 1
    @ellipsis  = options.ellipsis
    @continuedX  = 0
    
    # calculate the maximum Y position the text can appear at
    if options.height?
      @height = options.height
      @maxY = @startY + options.height
    else
      @maxY = @document.page.maxY()
    
    # handle paragraph indents
    @on 'firstLine', (options) =>
      # if this is the first line of the text segment, and
      # we're continuing where we left off, indent that much
      # otherwise use the user specified indent option
      indent = @continuedX or @indent
      @document.x += indent
      @lineWidth -= indent
      
      @once 'line', =>
        @document.x -= indent
        @lineWidth += indent
        @continuedX = 0 unless options.continued
    
    # handle left aligning last lines of paragraphs
    @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
        
  wordWidth: (word) ->
    return @document.widthOfString(word, this) + @charSpacing + @wordSpacing
        
  eachWord: (text, fn) ->
    # setup a unicode line breaker
    breaker = new LineBreaker(text)
    last = null
    wordWidths = {}
    
    while bk = breaker.nextBreak()
      word = text.slice(last?.position or 0, bk.position)
      w = wordWidths[word] ?= @wordWidth word
      
      # if the word is longer than the whole line, chop it up
      # TODO: break by grapheme clusters, not JS string characters
      if w > @lineWidth
        # make some fake break objects
        lbk = last
        fbk = {}
        
        while word.length
          # fit as much of the word as possible into the space we have
          l = word.length
          while w > @spaceLeft
            w = @wordWidth word.slice(0, --l)
            
          # send a required break unless this is the last piece
          fbk.required = l < word.length
          shouldContinue = fn word.slice(0, l), w, fbk, lbk
          lbk = required: false
          
          # get the remaining piece of the word
          word = word.slice(l)
          w = @wordWidth word
          
          break if shouldContinue is no
      else
        # otherwise just emit the break as it was given to us
        shouldContinue = fn word, w, bk, last
        
      break if shouldContinue is no
      last = bk
      
    return
    
  wrap: (text, options) ->
    # override options from previous continued fragments
    @indent    = options.indent       if options.indent?
    @charSpacing = options.characterSpacing if options.characterSpacing?
    @wordSpacing = options.wordSpacing    if options.wordSpacing?
    @ellipsis  = options.ellipsis     if options.ellipsis?
    
    # 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()
        
    buffer = ''
    textWidth = 0
    wc = 0
    lc = 0
    
    y = @document.y # used to reset Y pos if options.continued (below)
    emitLine = =>
      options.textWidth = textWidth + @wordSpacing * (wc - 1)
      options.wordCount = wc
      options.lineWidth = @lineWidth<commit_msg>Fix center-aligned character spacing<commit_after>{EventEmitter} = require 'events'
LineBreaker = require 'linebreak'

class LineWrapper extends EventEmitter
  constructor: (@document, options) ->
    @indent    = options.indent or 0
    @characterSpacing = 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
    @startX    = @document.x
    @startY    = @document.y
    @column    = 1
    @ellipsis  = options.ellipsis
    @continuedX  = 0
    
    # calculate the maximum Y position the text can appear at
    if options.height?
      @height = options.height
      @maxY = @startY + options.height
    else
      @maxY = @document.page.maxY()
    
    # handle paragraph indents
    @on 'firstLine', (options) =>
      # if this is the first line of the text segment, and
      # we're continuing where we left off, indent that much
      # otherwise use the user specified indent option
      indent = @continuedX or @indent
      @document.x += indent
      @lineWidth -= indent
      
      @once 'line', =>
        @document.x -= indent
        @lineWidth += indent
        @continuedX = 0 unless options.continued
    
    # handle left aligning last lines of paragraphs
    @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
        
  wordWidth: (word) ->
    return @document.widthOfString(word, this) + @characterSpacing + @wordSpacing
        
  eachWord: (text, fn) ->
    # setup a unicode line breaker
    breaker = new LineBreaker(text)
    last = null
    wordWidths = {}
    
    while bk = breaker.nextBreak()
      word = text.slice(last?.position or 0, bk.position)
      w = wordWidths[word] ?= @wordWidth word
      
      # if the word is longer than the whole line, chop it up
      # TODO: break by grapheme clusters, not JS string characters
      if w > @lineWidth
        # make some fake break objects
        lbk = last
        fbk = {}
        
        while word.length
          # fit as much of the word as possible into the space we have
          l = word.length
          while w > @spaceLeft
            w = @wordWidth word.slice(0, --l)
            
          # send a required break unless this is the last piece
          fbk.required = l < word.length
          shouldContinue = fn word.slice(0, l), w, fbk, lbk
          lbk = required: false
          
          # get the remaining piece of the word
          word = word.slice(l)
          w = @wordWidth word
          
          break if shouldContinue is no
      else
        # otherwise just emit the break as it was given to us
        shouldContinue = fn word, w, bk, last
        
      break if shouldContinue is no
      last = bk
      
    return
    
  wrap: (text, options) ->
    # override options from previous continued fragments
    @indent    = options.indent       if options.indent?
    @characterSpacing = options.characterSpacing if options.characterSpacing?
    @wordSpacing = options.wordSpacing    if options.wordSpacing?
    @ellipsis  = options.ellipsis     if options.ellipsis?
    
    # 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()
        
    buffer = ''
    textWidth = 0
    wc = 0
    lc = 0
    
    y = @document.y # used to reset Y pos if options.continued (below)
    emitLine = =>
      options.textWidth = textWidth + @wordSpacing * (wc - 1)
      options.wordCount = wc
      options.lineWidth = @lineWidth
{'fraction_non_alphanumeric': 0.08601469470483912, 'fraction_numerical': 0.005067139599695972, 'mean_word_length': 2.049439938200077, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3271801', 'n_tokens_mistral': 2329, 'n_tokens_neox': 2261, 'n_words': 786}