auto linebreaks again

  • #1, by PanSMonday, 26. August 2019, 17:27 2 years ago
    I create a speechbubble function with the tutorial by Sebastian and put some modification in:

    - a dynamic drawline indicates how long the text will shown
    - split the text into single lines to centred them in the bubble

    But for the perfect use it needs autolinebreaks. As far as I can see its not so easy to do.

    There is no function to put a linebreak in a string after a specific amout of pixels. But is there a methode to put in a linebreak after amount of chars? Expample: Go to byte position x find the next space (%s) before this byte and replace it (\n)?

    my code:
    local bubbleText = {}
    local bubbleBorder = 25
    local bubbleXoffset = 5
    local screenBorder = 20
    
    local timeStartText
    
    function showBubble(text)
      if Conditions["cond_option_speechbubbles"].ConditionValue == true then
        if text.Owner:getId().tableId == eCharacters then
          bubbleText[text:getId().id] = {Text = text.CurrentText, Owner = text.Owner:getName(), Time = text.TimeToWait} --missing WaitForAudio Check
         
          timeStartText = getTime()
          text.CurrentText = ""
          
          if text.Background == false then
            Interfaces["int_interactions"].Visible = false
          end
        end
      end
    end
    
    function killBubble(text)
      bubbleText[text:getId().id] = nil
    
      if Conditions["cond_dont_show_menu"].ConditionValue == false and Conditions["cond_inventoryopen"].ConditionValue == false then 
          Interfaces["int_interactions"].Visible = true
      end
    end
    
    registerEventHandler("textStarted","showBubble")
    registerEventHandler("textStopped","killBubble")
    
    function frameBubble()
      for key, val in pairs(bubbleText) do
    
        local char     = Characters[val.Owner]
        local pos      = graphics.getCharacterTextPosition(char) 
        --graphics.font = char.Font  
        graphics.font  = Fonts.font_Speechbubbles          --use a default font for all chars
        
    
        local txt      = val.Text:gsub("","\n")
        local dim      = graphics.fontDimension(txt)
    
        local t_Textlines = {}
    
        for l in string.gmatch(txt,"[^\r\n]+") do  
          table.insert(t_Textlines, l)
        end
      
        local lines    = #t_Textlines
        dim.y = dim.y * lines
    
        local txtTime  = val.Time
        
        pos.x = pos.x - game.ScrollPosition.x - (dim.x+bubbleBorder)/2
        if pos.x  game.WindowResolution.x - dim.x - bubbleBorder then pos.x = game.WindowResolution.x - dim.x - bubbleBorder - screenBorder end
        pos.y = pos.y - game.ScrollPosition.y - (dim.y+bubbleBorder/2) - 20
        if pos.y  game.WindowResolution.y - dim.y - bubbleBorder then pos.y = game.WindowResolution.y - dim.y - bubbleBorder - screenBorder end
    
        local bubbleSprite = graphics.loadFromFile("vispath:gfx/interface/simple_speechbubble.png")
        local destRect = {x=pos.x-bubbleXoffset, y=pos.y, width=dim.x+bubbleBorder+bubbleXoffset*2, height=dim.y+bubbleBorder}
        local nineRect = {x=20, y=20, width=30, height=30}
        
        local pointer = nil
    
        --draw bubble pointer
        if char.Direction  300 then
          pointer = graphics.loadFromFile("vispath:gfx/interface/simple_speechbubble_pointer_left.png")
          pointer.position = {x = char.Position.x - game.ScrollPosition.x, y = pos.y + dim.y + 25}
        elseif char.Direction > 120 and char.Direction < 240 then
          pointer = graphics.loadFromFile("vispath:gfx/interface/simple_speechbubble_pointer_right.png")
          pointer.position = {x = char.Position.x - game.ScrollPosition.x - 20, y = pos.y + dim.y + 25}
        else
          pointer = graphics.loadFromFile("vispath:gfx/interface/simple_speechbubble_pointer_mid.png")
          pointer.position = {x = char.Position.x - game.ScrollPosition.x - 10, y = pos.y + dim.y + 25}
        end
    
        graphics.drawSpriteWithNineRect(bubbleSprite, destRect, nineRect, 0xffffff, 1.0)
        graphics.drawSprite(pointer, 1.0, 0xffffff)
        --graphics.drawFont(txt, math.floor( pos.x+bubbleBorder/2 + game.ScrollPosition.x ), math.floor( pos.y+bubbleBorder/2 + game.ScrollPosition.y ), 1.0)
    
        --centred text
        for i = 1, lines do
          local dimLine      = graphics.fontDimension(t_Textlines[i])      
          --print('line \"'..t_Textlines[i]..'\" is '..dimLine.x..' width')
          graphics.drawFont(t_Textlines[i], math.floor ( pos.x + (dim.x - dimLine.x)/2 + bubbleBorder/2 ), math.floor ( ( pos.y - dimLine.y) + dimLine.y*i + bubbleBorder/2 ), 1.0)
        end
        
        --draw a line for text show time
        local posLine = pos
        posLine.x = pos.x + dim.x/2 + bubbleBorder/2
        posLine.y = pos.y + dim.y + ( bubbleBorder/(3/2) )
    
        local remainTime = 0
        remainTime = txtTime - ( getTime() - timeStartText )
        local timeOffset = remainTime / ( txtTime / ( dim.x/2 ) )
    
        --graphics.drawFont(txt, math.floor( pos.x+bubbleBorder/2 + game.ScrollPosition.x ), math.floor( pos.y+bubbleBorder/2 + game.ScrollPosition.y ), 1.0)
        graphics.drawLine(math.floor( posLine.x - timeOffset ), math.floor(posLine.y),math.floor( posLine.x + timeOffset ), math.floor(posLine.y), 0xffffff, 1.0)
        
      end
    end
    
    graphics.addDrawFunc("frameBubble()", 1)
    

    Newbie

    73 Posts


  • #2, by sebastianTuesday, 27. August 2019, 03:12 2 years ago
    there is a linebreak feature for each font. Just set it for the used font in the speech bubbles and it should automatically break after X pixels (but still use full words):

    Thread Captain

    2346 Posts

  • #3, by PanSTuesday, 27. August 2019, 10:15 2 years ago
    No. That doesnt work. I think it has something to do with how the speechbubble function transfer the CurrentText to another function. And the drawtext function ignore autolinebreaks. Even graphics.fontDimension measures only the x-dimension correct for a text. The y-dimension is allways only the dimension of the fonts high with shadow and outerlines. Doesn't matter if there manual linebreaks. Because of this, my function counts the textlines and use this count as a multiplier. I suppose that happens only with truetype fonts and maybe with bitmap fonts everything is alright.

    Newbie

    73 Posts

  • #4, by PanSTuesday, 27. August 2019, 22:39 2 years ago
    Okey, I created my own auto line break function for speechbubbles. I request tips and improvement suggestions. I am still new in lua grin
    --script to break lines based on pixel width
    
    function autoLineBreak(lbText, lbFont)
      if not lbFont then lbFont = Fonts.default_Font end
    
      graphics.font   = lbFont
      local dimOrg    = graphics.fontDimension(lbText)
      local dimSpace  = lbFont.SpaceWidth
      local width     = lbFont.LineWidth
    
      if width > game.WindowResolution.x then width = game.WindowResolution.x - 20 end
    
      local t_textWords  = {}
    
      local dimLine   = 0
    
      lbText = lbText:gsub("<br/"..">"," <br/".." ") --necessary to easier find manual line breaks e.x. 'word<br/>word'
    
      for w in string.gmatch(lbText,"[^  ]+") do 
         dimLine  = dimLine + graphics.fontDimension(w).x
         if w == "<br/"..">" then
           dimLine = 0 - dimSpace
         elseif dimLine >= width then
           dimLine = graphics.fontDimension(w).x
           table.insert(t_textWords,"<br/"..">") 
         elseif (dimLine - graphics.fontDimension(w).x) > 0 then
           table.insert(t_textWords," ")
         end
    
         table.insert(t_textWords, w)
         dimLine  = dimLine + dimSpace
      end
    
      local newText  = ""
      
      for i=1, #t_textWords do 
        newText  = newText..tostring( t_textWords[i] )
      end
    
      return newText
    end
    
    And so the speechbubble function looks now:
    --speechbubble script
    
    local bubbleText = {}
    local bubbleBorder = 25
    local bubbleXoffset = 5
    local screenBorder = 20
    
    local timeStartText
    
    function showBubble(text)
      if Conditions["cond_option_speechbubbles"].ConditionValue == true then
        if text.Owner:getId().tableId == eCharacters then
          bubbleText[text:getId().id] = {Text = text.CurrentText, Owner = text.Owner:getName(), Time = text.TimeToWait}
    
          local countL = 0
          local textLines    = graphics.performLinebreaks(text.CurrentText)
          for _ in pairs(textLines) do countL = countL + 1 end
          
          timeStartText = getTime()
          text.CurrentText = ""
          
          if text.Background == false then
            Interfaces["int_interactions"].Visible = false
          end
        end
      end
    end
    
    function killBubble(text)
      bubbleText[text:getId().id] = nil
    
      if Conditions["cond_dont_show_menu"].ConditionValue == false and Conditions["cond_inventoryopen"].ConditionValue == false then 
          Interfaces["int_interactions"].Visible = true
      end
    end
    
    registerEventHandler("textStarted","showBubble")
    registerEventHandler("textStopped","killBubble")
    
    function frameBubble()
      for key, val in pairs(bubbleText) do
    
        local char     = Characters[val.Owner]
        local pos      = graphics.getCharacterTextPosition(char) 
        --graphics.font = char.Font  
        local bFont    = Fonts.font_Speechbubbles          --use a default font for al chars
        graphics.font  = bFont
        local txt      = val.Text
    
        txt            = autoLineBreak(txt, bFont)                    --custom auto breakline function
    
        txt            = txt:gsub("<br/"..">","\n")
        local dim      = graphics.fontDimension(txt)
    
        local t_Textlines = {}
    
        for l in string.gmatch(txt,"[^\r\n]+") do  
          table.insert(t_Textlines, l)
        end
      
        local lines    = #t_Textlines
        dim.y = dim.y * lines
        --print('text width: '..dim.x)
        if dim.x  game.WindowResolution.x - dim.x - bubbleBorder then pos.x = game.WindowResolution.x - dim.x - bubbleBorder - screenBorder end
        pos.y = pos.y - game.ScrollPosition.y - (dim.y+bubbleBorder/2) - 20
        if pos.y  game.WindowResolution.y - dim.y - bubbleBorder then pos.y = game.WindowResolution.y - dim.y - bubbleBorder - screenBorder end
    
        local bubbleSprite = graphics.loadFromFile("vispath:gfx/interface/simple_speechbubble.png")
        local destRect = {x=pos.x-bubbleXoffset, y=pos.y, width=dim.x+bubbleBorder+bubbleXoffset*2, height=dim.y+bubbleBorder}
        local nineRect = {x=20, y=20, width=30, height=30}
        
        local pointer = nil
    
        --draw bubble pointer
        if char.Direction  300 and dim.x > 60 then
          pointer = graphics.loadFromFile("vispath:gfx/interface/simple_speechbubble_pointer_left.png")
          pointer.position = {x = char.Position.x - game.ScrollPosition.x, y = pos.y + dim.y + 25}
        elseif char.Direction > 120 and char.Direction  60 then
          pointer = graphics.loadFromFile("vispath:gfx/interface/simple_speechbubble_pointer_right.png")
          pointer.position = {x = char.Position.x - game.ScrollPosition.x - 20, y = pos.y + dim.y + 25}
        else
          pointer = graphics.loadFromFile("vispath:gfx/interface/simple_speechbubble_pointer_mid.png")
          pointer.position = {x = char.Position.x - game.ScrollPosition.x - 10, y = pos.y + dim.y + 25}
        end
    
        graphics.drawSpriteWithNineRect(bubbleSprite, destRect, nineRect, 0xffffff, 1.0)
        graphics.drawSprite(pointer, 1.0, 0xffffff)
        --graphics.drawFont(txt, math.floor( pos.x+bubbleBorder/2 + game.ScrollPosition.x ), math.floor( pos.y+bubbleBorder/2 + game.ScrollPosition.y ), 1.0)
    
        --centred text
        for i = 1, lines do
          local dimLine      = graphics.fontDimension(t_Textlines[i])      
          --print('line \"'..t_Textlines[i]..'\" is '..dimLine.x..' width')
          graphics.drawFont(t_Textlines[i], math.floor ( pos.x + (dim.x - dimLine.x)/2 + bubbleBorder/2 + game.ScrollPosition.x ), math.floor ( ( pos.y - dimLine.y) + dimLine.y*i + bubbleBorder/2 + game.ScrollPosition.y), 1.0)
        end
        
        --draw a line for text show time
        local posLine = pos
        posLine.x = pos.x + dim.x/2 + bubbleBorder/2
        posLine.y = pos.y + dim.y + ( bubbleBorder/(3/2) )
    
        local remainTime = 0
        remainTime = txtTime - ( getTime() - timeStartText )
        local timeOffset = remainTime / ( txtTime / ( dim.x/2 ) )
    
        --graphics.drawFont(txt, math.floor( pos.x+bubbleBorder/2 + game.ScrollPosition.x ), math.floor( pos.y+bubbleBorder/2 + game.ScrollPosition.y ), 1.0)
        graphics.drawLine(math.floor( posLine.x - timeOffset ), math.floor(posLine.y),math.floor( posLine.x + timeOffset ), math.floor(posLine.y), 0xffffff, 1.0)
        
      end
    end
    
    graphics.addDrawFunc("frameBubble()", 1)
    

    Newbie

    73 Posts

  • #5, by sebastianTuesday, 27. August 2019, 23:16 2 years ago
    i somehow think that some problems occour with ttf fonts . Bitmapfonts work for me well with the in engine auto linebreak method.

    Thread Captain

    2346 Posts