Jump to content

Break a string into separate lines depending on length


Dzsozi (h03)

Recommended Posts

Since wordBreak is forced to false if the colorCoded parameter is set to true at dxDrawText, I would like to draw multiple texts under each other. I would like to make a mission instruction label text, just like the one you can see in the default GTA games on the bottom of the screen, where I would like to use hex colorcodes for some highlights and nicer design. How can I break a string into separate lines (put it in a table I guess, but how), so I can make a loop and draw multiple dxDrawTexts with the correct text to continue after a new line was added? Could somebody help me out with the maths and string handling? I would be really grateful!

Link to comment
local function wordWrap(text, maxwidth, scale, font, colorcoded)
  local lines = {}
  local words = split(text, " ") -- this unfortunately will collapse 2+ spaces in a row into a single space
  local line = 1 -- begin with 1st line
  local word = 1 -- begin on 1st word
  while (words[word]) do -- while there are still words to read
    repeat
      lines[line] = lines[line] and (lines[line].." ") or "" -- appends space if line already exists, or defines it to an empty string
      lines[line] = lines[line]..words[word] -- append a new word to the this line
      word = word + 1 -- moves onto the next word (in preparation for checking whether to start a new line (that is, if next word won't fit)
    until ((not words[word]) or dxGetTextWidth(lines[line].." "..words[word], scale, font, colorcoded) >= maxwidth) -- jumps back to 'repeat' as soon as the code is out of words, or with a new word, it would overflow the maxwidth
    
    line = line + 1 -- moves onto the next line
  end -- jumps back to 'while' the a next word exists
  return lines
end

This code should work. It'll return a table of lines that you need to draw separately. The colorcoded parameter, when set to true, means dxGetTextWidth will remove #rrggbb from the text before calculating width since that's how it'll render it.

I have not measured the performance of this script, but for short sentences like less than 50 words it shouldn't cause any significant lags. It iterates based on word count rather than character count.

Edited by MrTasty
  • Like 1
Link to comment
5 hours ago, MrTasty said:

local function wordWrap(text, maxwidth, scale, font, colorcoded)
  local lines = {}
  local words = split(text, " ") -- this unfortunately will collapse 2+ spaces in a row into a single space
  local line = 1 -- begin with 1st line
  local word = 1 -- begin on 1st word
  while (words[word]) do -- while there are still words to read
    repeat
      lines[line] = lines[line] and (lines[line].." ") or "" -- appends space if line already exists, or defines it to an empty string
      lines[line] = lines[line]..words[word] -- append a new word to the this line
      word = word + 1 -- moves onto the next word (in preparation for checking whether to start a new line (that is, if next word won't fit)
    until ((not words[word]) or dxGetTextWidth(lines[line].." "..words[word], scale, font, colorcoded) >= maxwidth) -- jumps back to 'repeat' as soon as the code is out of words, or with a new word, it would overflow the maxwidth
    
    line = line + 1 -- moves onto the next line
  end -- jumps back to 'while' the a next word exists
  return lines
end

This code should work. It'll return a table of lines that you need to draw separately. The colorcoded parameter, when set to true, means dxGetTextWidth will remove #rrggbb from the text before calculating width since that's how it'll render it.

I have not measured the performance of this script, but for short sentences like less than 50 words it shouldn't cause any significant lags. It iterates based on word count rather than character count.

It works perfectly, thank you! I just have one more question. Is it possible to add the color code used in the previous line to the next one?

Link to comment
local function wordWrap(text, maxwidth, scale, font, colorcoded)
  local lines = {}
  local words = split(text, " ") -- this unfortunately will collapse 2+ spaces in a row into a single space
  local line = 1 -- begin with 1st line
  local word = 1 -- begin on 1st word
  local endlinecolor
  while (words[word]) do -- while there are still words to read
    repeat
      if colorcoded and (not lines[line]) and endlinecolor and (not string.find(words[word], "^#%x%x%x%x%x%x")) then -- if on a new line, and endline color is set and the upcoming word isn't beginning with a colorcode
        lines[line] = endlinecolor -- define this line as beginning with the color code
      end
      lines[line] = lines[line] or "" -- define the line if it doesnt exist

      if colorcoded then
        local rw = string.reverse(words[word]) -- reverse the string
        local x, y = string.find(rw, "%x%x%x%x%x%x#") -- and search for the first (last) occurance of a color code
        if x and y then
          endlinecolor = string.reverse(string.sub(rw, x, y)) -- stores it for the beginning of the next line
        end
      end
      
      lines[line] = lines[line]..words[word] -- append a new word to the this line
      lines[line] = lines[line] .. " " -- append space to the line

      word = word + 1 -- moves onto the next word (in preparation for checking whether to start a new line (that is, if next word won't fit)
    until ((not words[word]) or dxGetTextWidth(lines[line].." "..words[word], scale, font, colorcoded) > maxwidth) -- jumps back to 'repeat' as soon as the code is out of words, or with a new word, it would overflow the maxwidth
    
    lines[line] = string.sub(lines[line], 1, -2) -- removes the final space from this line
    if colorcoded then
      lines[line] = string.gsub(lines[line], "#%x%x%x%x%x%x$", "") -- removes trailing colorcodes
    end
    line = line + 1 -- moves onto the next line
  end -- jumps back to 'while' the a next word exists
  return lines
end

This variant will append the latestly used colorcode on every subsequent line to preserve color.

However, due to a number of more intensive functions used here, like string.find, string.reverse and string.gsub, this code runs 25% slower with colorcode enabled than the previous version, and only 5-6% slower without colorcode than the previous version. Same-version comparison yields that colorcoded being enabled slows it down by 22% compared to without.

Still pretty fast though, took around 0.000167465 seconds (0.167465 ms) (mean over 200 iterations) for the paragraph above to be processed by the function using own pure-lua implementations of split* and a simplified implementation of dxGetTextWidth (simply returning the length of characters excluding colorcodes), since I was using standalone Lua rather than via MTA client.

* Turns out, while checking the speed of MTA's split, my pure-lua implementation is a lot faster, like 10× faster. MTA's split (on the server side) managed to take up to 2 seconds to process the “However,…” paragraph.

  • Like 1
Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...