auto linebreaks again

  • #1, by PanSMonday, 26. August, 17:27 3 weeks 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)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107

    Newbie

    56 Posts


  • #2, by sebastianTuesday, 27. August, 03:12 3 weeks 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

    2320 Posts

  • #3, by PanSTuesday, 27. August, 10:15 3 weeks 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

    56 Posts

  • #4, by PanSTuesday, 27. August, 22:39 3 weeks 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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    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)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113

    Newbie

    56 Posts

  • #5, by sebastianTuesday, 27. August, 23:16 3 weeks 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

    2320 Posts

Write post