Fill a circle with text

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
9912
Prole
Posts: 8
Joined: Sun Aug 23, 2020 7:12 am

Fill a circle with text

Post by 9912 »

Hi, the title explains all, anyone knows how to fill a circle with horizontal text?
No text bending, just filling a circle with circle-shaped paragraph?
User avatar
darkfrei
Party member
Posts: 1169
Joined: Sat Feb 08, 2020 11:09 pm

Re: Fill a circle with text

Post by darkfrei »

9912 wrote: Sun Jan 08, 2023 8:52 pm Hi, the title explains all, anyone knows how to fill a circle with horizontal text?
No text bending, just filling a circle with circle-shaped paragraph?
1. Make text with love.graphics.printf and limited width https://love2d.org/wiki/love.graphics.printf
2. Get the width and height of current font and text https://love2d.org/wiki/Font:getWidth
3. Use math to get the x, y and radius of circle.
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
BrotSagtMist
Party member
Posts: 607
Joined: Fri Aug 06, 2021 10:30 pm

Re: Fill a circle with text

Post by BrotSagtMist »

I guess you need us to show a mockup of how you want it.
obey
9912
Prole
Posts: 8
Joined: Sun Aug 23, 2020 7:12 am

Re: Fill a circle with text

Post by 9912 »

like this, the use case for this is in case of having a round display (like zeblaze thor 5 smartwatch which runs Android not Android Wear)
Attachments
Screenshot_2023-01-08-16-11-24-976_com.microsoft.office.officehubrow_1.jpg
Screenshot_2023-01-08-16-11-24-976_com.microsoft.office.officehubrow_1.jpg (85.58 KiB) Viewed 1271 times
Last edited by 9912 on Sun Jan 08, 2023 9:17 pm, edited 1 time in total.
User avatar
Bigfoot71
Party member
Posts: 287
Joined: Fri Mar 11, 2022 11:07 am

Re: Fill a circle with text

Post by Bigfoot71 »

Like this ?

Code: Select all

function drawCircleText(text,x,y,r)

    local font = love.graphics.getFont()
    local text_w = font:getWidth(text)
    local text_h = font:getHeight()

    love.graphics.circle("line", x,y,r)
    love.graphics.print(text, x, y, nil, nil, nil, text_w/2, text_h/2)

end


function love.draw()

    drawCircleText(
        "Hello",
        love.graphics.getWidth()/2,
        love.graphics.getHeight()/2,
        50
    )

end
If this is the case and you want centered multiline, you may have to look at love.graphics.printf or break the string for each "\n" and loop to display each line centered. (I didn't quite understand the thing requested, but by counting the number of characters with this technique, maybe we can have exactly the same result as in the picture). To tell you the truth I have never tried either but if you need help with that don't hesitate to ask :)

Edit: Maybe this library could be useful: SYSL-Text.
Last edited by Bigfoot71 on Mon Jan 09, 2023 10:34 am, edited 1 time in total.
My avatar code for the curious :D V1, V2, V3.
User avatar
Bigfoot71
Party member
Posts: 287
Joined: Fri Mar 11, 2022 11:07 am

Re: Fill a circle with text

Post by Bigfoot71 »

I quickly wrote a function that tries to do as in your image, it can be optimized, it is mainly to roughly show the principle described by darkfrei.
However to do exactly as in your picture I do not hide from you that it will be work.

Code: Select all

function drawCircleText(text,x,y,width)

    local font = love.graphics.getFont()

    local _, wrapped_text = font:getWrap(text, width)
    local height = #wrapped_text * font:getHeight()

    local r = (width > height and width or height) * .6  -- I do not divide tails by two so that the text is not tails at the edge of the circle

    love.graphics.circle("line", x,y,r)
    --love.graphics.rectangle("line", x, y, width, height)

    love.graphics.printf(text, x, y, width, "center", nil, nil, nil, width/2, height/2)

end


function love.draw()

    drawCircleText(
        "Lörem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        love.graphics.getWidth()/2,
        love.graphics.getHeight()/2,
        150
    )

end
Here's what it might look like:
APEnrAc.png
APEnrAc.png (13.41 KiB) Viewed 457 times
You will notice that by removing the `ox` and `oy` parameters to printf you will be able to display it in a rectangle as well.
My avatar code for the curious :D V1, V2, V3.
User avatar
pgimeno
Party member
Posts: 3544
Joined: Sun Oct 18, 2015 2:58 pm

Re: Fill a circle with text

Post by pgimeno »

I've made this; it was an interesting challenge. While working on it I found what appears to be a bug in love's text justification code, so I wrote my own.

Code: Select all

local function genRoundText(text, font, radius, align, topMargin)
  local words, nwords
  if align == 'justify' then
    words = {}
    nwords = 0
  end
  local fontHeight = font:getHeight()
  if topMargin == 0 then
    topMargin = fontHeight
  end
  local textObject = love.graphics.newText(font)
  local y = -radius + topMargin
  while text ~= "" and math.abs(y) < radius do
    local ldist = math.floor(math.min(math.sqrt(radius^2 - y^2),
       math.sqrt(radius^2 - (y + fontHeight)^2)))

    local width, wrapped = font:getWrap(text, ldist * 2)
    local line = wrapped[1]
    if align == 'justify' then
      if wrapped[2] then
        -- Love's justify alignment is not working properly. Use our own algo.
        local total = 0
        for word in string.gmatch(line, '[^ ]+') do
          nwords = nwords + 1
          words[nwords] = word
          total = total + font:getWidth(word)
        end
        local to_distribute = ldist * 2 - total
        local x = 0
        local current = 0
        for i = 1, nwords do
          local ww = font:getWidth(words[i])
          textObject:add(words[i], -ldist + x, y)
          if i ~= nwords then
            local old = current
            current = math.floor(to_distribute * i / (nwords - 1))
            x = x + ww + (current - old)
          end
        end

        -- clear table for next line
        for i = nwords, 1, -1 do
          words[i] = nil
        end
        nwords = 0
      else
        align = 'left'
      end
    end
    if align ~= 'justify' then
      textObject:addf(wrapped[1], ldist*2, align, -ldist, y)
    end

    y = y + fontHeight
    text = text:sub(#wrapped[1] + 1)
  end
  return textObject
end

local font = love.graphics.setNewFont(27)
local txt = genRoundText("Lorem ipsum dolor sit amet"
.. " enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis."
.. " Maecenas malesuada elit lectus felis, malesuada ultrices. Curabitur"
.. " et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo"
.. " volutpat a, convallis ac, laoreet enim. Phasellus fermentum in, dolor."
.. " Pellentesque facilisis. Nulla imperdiet sit amet magna. Vestibulum"
.. " dapibus, mauris nec malesuada fames ac turpis velit, rhoncus eu, luctus"
.. " et interdum adipiscing wisi. Aliquam erat ac ipsum. Integer"
, font, 290, 'justify', 30)

function love.draw()
  love.graphics.draw(txt, 400, 300, 0, 1, 1, 0, 0)
  love.graphics.circle("line", 400, 300, 290)
end
It's expensive, so try to not call it every frame. It returns a Text object that you can draw at the centre of the circle.
Attachments
text-in-circle.png
text-in-circle.png (62.19 KiB) Viewed 1172 times
User avatar
Bigfoot71
Party member
Posts: 287
Joined: Fri Mar 11, 2022 11:07 am

Re: Fill a circle with text

Post by Bigfoot71 »

pgimeno wrote: Mon Jan 09, 2023 10:04 pm I've made this; it was an interesting challenge.
Wow, super cool! I wanted to understand your approach to the problem, so I restructured it a bit by commenting on the passages.

That way, if someone else like me wants to try to understand this code I will have tried to chew the work a bit ^^ (hoping that my retranslation of the comments is good to avoid misunderstandings)

Code: Select all

-- Function by pgimeno --

local function genRoundText(text, font, radius, align, topMargin)

  -- Word table pre-declaration (see https://love2d.org/forums/viewtopic.php?p=252702#p252702)

  local words, nwords
  if align == 'justify' then
    words = {}
    nwords = 0
  end

  -- If topMargin is 0, use default font height

  local fontHeight = font:getHeight()
  if topMargin == 0 then
    topMargin = fontHeight
  end

  -- Initializes an empty text object

  local textObject = love.graphics.newText(font)

  -- Positions the initial y position of the text

  local y = -radius + topMargin

  -- Keep adding text until all given text is used
  -- or the position y exceeds the radius of the circle

  while text ~= "" and math.abs(y) < radius do

    -- Calculation of the maximum distance between the text
    -- and the center of the circumference taking into account
    -- the high and low extremities of the current line of text.

    local ldist = math.floor(
        math.min(
            math.sqrt(radius^2 - y^2),
            math.sqrt(radius^2 - (y + fontHeight)^2)
        )
    )

    -- Wraps the text to the calculated maximum distance

    local _, wrapped = font:getWrap(text, ldist * 2)
    local line = wrapped[1]

    -- If the alignment is "justify", use our own justification feature

    if align == 'justify' then

      -- Checks if the line has been wrapped (if it is too long to fit
      -- within the calculated maximum distance)

      if wrapped[2] then

        -- Break the line into words
        -- and calculates the total distance occupied by the words

        local total = 0

        for word in string.gmatch(line, '[^ ]+') do
          nwords = nwords + 1
          words[nwords] = word
          total = total + font:getWidth(word)
        end

        -- Calculates the distance to distribute between the words

        local to_distribute = ldist * 2 - total
        local x = 0
        local current = 0

        -- Adds each word to the text object
        -- adjusting the spacing between them to justify the text

        for i = 1, nwords do

          local ww = font:getWidth(words[i])
          textObject:add(words[i], -ldist + x, y)

          if i ~= nwords then
            local old = current
            current = math.floor(to_distribute * i / (nwords - 1))
            x = x + ww + (current - old)
          end

        end

        -- Clear table for next line

        for i = nwords, 1, -1 do
          words[i] = nil
        end

        nwords = 0

      else
        align = 'left' -- If the line has not been wrapped, use the specified alignment
      end

    end

    -- If the alignment is not "justify", use the default alignment

    if align ~= 'justify' then
      textObject:addf(wrapped[1], ldist*2, align, -ldist, y)
    end

    -- Advance the y position for the next line

    y = y + fontHeight
    text = text:sub(#wrapped[1] + 1) -- Remove the used line from the remaining text string

  end

  return textObject -- Returns the created text object

end
Last edited by Bigfoot71 on Tue Jan 10, 2023 8:59 pm, edited 1 time in total.
My avatar code for the curious :D V1, V2, V3.
9912
Prole
Posts: 8
Joined: Sun Aug 23, 2020 7:12 am

Re: Fill a circle with text

Post by 9912 »

*looks the entire thread after logging in*

~Oh, what i´ve done, i've started a war lmao.

looked all replies (specially pgimeno's code) and it works with some params changed

Code: Select all

-- Function by pgimeno --

local function genRoundText(text, font, radius, align, topMargin)

  -- Initializes an empty text object

  local textObject = love.graphics.newText(font)

  -- If topMargin is 0, use default font height

  local fontHeight = font:getHeight()
  if topMargin == 0 then
    topMargin = fontHeight
  end

  -- Positions the initial y position of the text

  local y = -radius + topMargin

  -- Keep adding text until all given text is used
  -- or the position y exceeds the radius of the circle

  while text ~= "" and math.abs(y) < radius do

    -- Calculates the maximum left and right distance the text can occupy
    -- using the distance from the origin (0,0) to the nearest point on the circle

    local ldist = math.floor(math.min(math.sqrt(radius^2 - y^2),
       math.sqrt(radius^2 - (y + fontHeight)^2)))

    -- Wraps the text to the calculated maximum distance

    local _, wrapped = font:getWrap(text, ldist * 2)
    local line = wrapped[1]

    -- If the alignment is "justify", use our own justification feature

    if align == 'justify' then

      -- Checks if the line has been wrapped (if it is too long to fit
      -- within the calculated maximum distance)

      if wrapped[2] then

        -- Break the line into words
        -- and calculates the total distance occupied by the words

        local words = {}
        local nwords = 0
        local total = 0

        for word in string.gmatch(line, '[^ ]+') do
          nwords = nwords + 1
          words[nwords] = word
          total = total + font:getWidth(word)
        end

        -- Calculates the distance to distribute between the words

        local to_distribute = ldist * 2 - total
        local x = 0
        local current = 0

        -- Adds each word to the text object
        -- adjusting the spacing between them to justify the text

        for i = 1, nwords do
          local ww = font:getWidth(words[i])
          textObject:add(words[i], -ldist + x, y)
          if i ~= nwords then
            local old = current
            current = math.floor(to_distribute * i / (nwords - 1))
            x = x + ww + (current - old)
          end
        end

      else
        align = 'left' -- If the line has not been wrapped, use the default alignment
      end
    end

    -- If the alignment is not "justify", use the default alignment

    if align ~= 'justify' then
      textObject:addf(wrapped[1], ldist*2, align, -ldist, y)
    end

    -- Advance the y position for the next line

    y = y + fontHeight
    text = text:sub(#wrapped[1] + 1) -- Remove the used line from the remaining text string

  end

  return textObject -- Returns the created text object

end

function love.conf(t)
	t.console = true
end

love.window.updateMode(320,320)
local font = love.graphics.setNewFont(16)
local txt = genRoundText("Lorem ipsum dolor sit amet"
.. " enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis."
.. " Maecenas malesuada elit lectus felis, malesuada ultrices. Curabitur"
.. " et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo"
.. " volutpat a, convallis ac, laoreet enim. Phasellus fermentum in, dolor."
.. " Pellentesque facilisis. Nulla imperdiet sit amet magna. Vestibulum"
.. " dapibus, mauris nec malesuada fames ac turpis velit, rhoncus eu, luctus"
.. " et interdum adipiscing wisi. Aliquam erat ac ipsum. Integer"
, font, love.graphics.getWidth()/2, 'justify', love.graphics.getWidth()/1.6)

function love.draw()
  love.graphics.draw(txt, love.graphics.getWidth()/2, love.graphics.getHeight()/2, 0, 1, 1, 0, 0)
  love.graphics.circle("line", love.graphics.getWidth()/2, love.graphics.getHeight()/2, love.graphics.getWidth()/2)
end
The main idea is providing a way to show text in a round display of 320x320, the params i've used in this case are:
genRoundText(text, font, love.graphics.getWidth()/2, 'justify', love.graphics.getWidth()/(2*0.8)),
i've put getWidth()/2 due to the drawing function takes reference from the centre instead of (0,0), and put 0,8 on the topMargin to display the text like a visual novel on round display.

Idea: make a pull request on https://github.com/tanema/talkies to provide an option to override printing function

Löve file attached.
Attachments
220110-1209.png
220110-1209.png (16.8 KiB) Viewed 1109 times
rounddisplay.love
(1.6 KiB) Downloaded 34 times
9912
Prole
Posts: 8
Joined: Sun Aug 23, 2020 7:12 am

Re: Fill a circle with text

Post by 9912 »

I suggest to post the code snippet on a gist for future references or in a library.
Post Reply

Who is online

Users browsing this forum: No registered users and 51 guests