Wrap Text in Rect

Moderators: LCMark, LCfraser

Post Reply
pink
Posts: 224
Joined: Wed Mar 12, 2014 6:18 pm

Wrap Text in Rect

Post by pink » Wed Aug 15, 2018 9:32 pm

Finally got this working, the following code takes 4 inputs:
-the rectangle which will contain the text
-the text (duh)
-the font to use
-the foreground color to use for the text

Here's a few notes:
-if the text is ultimately too long to fit in the box, it just gets cut off
-if any word is too long to fit in the width of the box, it just sits on a line by itself and gets truncated

Here's what I can't figure out yet:
-how can I pass an alignment as a param? I tried putting "top left" as a 5th param, and then "fill text tLine at pAlign..." but I keep getting errors
-how to put line breaks into this... still working on it

Code: Select all

public handler mpWrapTextinRect(in pTextBox as Rectangle,in pText as String,in pFont as Font,in pForeC as Paint)
	variable tRect as Rectangle
	variable tTextW as Number
	variable tTextH as Number
	variable tMaxRows as Number
	variable tWords as List
	variable tWord as String
	variable tLine as String
	variable tTestLine as String
	variable tRow as Number

	split pText by " " into tWords
	set the font of this canvas to pFont
	set the paint of this canvas to pForeC
	put the height of the layout bounds of text pText on this canvas into tTextH
	put the width of pTextBox into tTextW
	put the floor of ((the height of pTextBox)/tTextH) into tMaxRows
	if tMaxRows = 0 then
		return
	end if
	put 1 into tRow
	repeat for each element tWord in tWords
		put tLine & tWord into tTestLine
		if the width of the layout bounds of text tTestLine on this canvas > tTextW then
			fill text tLine at top left of pTextBox on this canvas
			add tTextH to the top of pTextBox
			put "" into tTestLine
			if tRow=tMaxRows then
				put "" into tLine
				exit repeat
			end if
			add 1 to tRow
			put tWord & " " into tLine
		else
			put tTestLine & " " into tLine
		end if
	end repeat
	fill text tLine at top left of pTextBox on this canvas
end handler
Here's an example OnPaint handler using it:

Code: Select all

public handler OnPaint()
	variable tFont as Font
        variable tRect as Rectangle
	variable tText as String

	put "A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools." into tText
	set the paint of this canvas to my background paint
	put rectangle [0,0,my width,my height] into tRect
	fill rectangle path of tRect on this canvas
        put font "Arial" at size 18 into tFont
        fill rectangle path of tRect on this canvas
	mpWrapTextinRect(tRect,tText,tFont,my foreground paint)
end handler
Greg (pink) Miller

MadPink, LLC
I'm Mad, Pink and Dangerous to Know

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3229
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Wrap Text in Rect

Post by bn » Thu Aug 16, 2018 4:31 pm

Greg,

regarding alignment I only found this solution

Code: Select all

 variable tAlign as String
 put "center" into tAlign
and then

Code: Select all

      if tAlign is "center" then
         fill text tLine at center of pTextBox on this canvas
      else if tAlign is "top left" then
         fill text tLine at top left of pTextBox on this canvas
      else if tAlign is "top right" then
         fill text tLine at top right of pTextBox on this canvas
      else
         fill text tLine at top left of pTextBox on this canvas -- fall back/default
      end if
of course you could set the alignment as a property and pass it etc.

Kind regards
Bernd

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3229
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Wrap Text in Rect

Post by bn » Thu Aug 16, 2018 7:10 pm

Greg,

as far as return is concerned I resorted to this solution

create some new variables and put a linefeed character into your list of words as a test.
I assume that if you pass the widget a string with a return from LCS it will be linefeed (ASCII 10).

Code: Select all

    variable tReturnChar as String
    get the char with code 10 -- linefeed used by LCS
    put the result into tReturnChar

    variable tContainsReturn as Boolean

    split pText by " " into tWords
    put tReturnChar after element 1 of tWords -- add a return after first word
when filling text add this

Code: Select all

put tLine contains tReturnChar into tContainsReturn -- true if it found a lineFeed
     if the width of the layout bounds of text tTestLine on this canvas > tTextW or tContainsReturn then
your code for filling text follows

(boy, to find the right syntax in LCB is tedious...)

Kind regards
Bernd

pink
Posts: 224
Joined: Wed Mar 12, 2014 6:18 pm

Re: Wrap Text in Rect

Post by pink » Fri Aug 17, 2018 1:09 pm

This was my solution for adding a return/newline in the handler.
(I replaced the exiting carriage return with " qzqz " and then in the repeat sequence it ends the current line and begins the next line.

Also, in this iteration I removed the paint, I think it is easier to set in the OnPaint handler.

The one thing I cannot crack is centering text. Everything I've done so far causes the centering to begin both vertically and horizontally in the center instead of centered at the top. I've tried manually making rectangles for each line, but it seems like "center" ignores any other constraints I make.

Code: Select all

private handler mpWrapTextinRect(in pTextBox as Rectangle,in pText as String,in pFont as Font)
	variable tRect as Rectangle
	variable tTextW as Number
	variable tTextH as Number
	variable tMaxRows as Number
	variable tWords as List
	variable tWord as String
	variable tLine as String
	variable tTestLine as String
	variable tRow as Number
	variable tText as Rectangle

	replace newline with " qzqz " in pText
	split pText by " " into tWords
	set the font of this canvas to pFont
	put the height of the layout bounds of text pText on this canvas into tTextH
	put the width of pTextBox into tTextW
	put the floor of ((the height of pTextBox)/tTextH) into tMaxRows
	if tMaxRows = 0 then
		return
	end if
	put 1 into tRow
	repeat for each element tWord in tWords
		put tLine & tWord into tTestLine
		if the width of the layout bounds of text tTestLine on this canvas > tTextW or tWord is "qzqz" then
	      fill text tLine at top left of pTextBox on this canvas
			add tTextH to the top of pTextBox
			put "" into tTestLine
			if tRow=tMaxRows then
				put "" into tLine
				exit repeat
			end if
			add 1 to tRow
			if tWord is "qzqz" then
				put "" into tLine
			else
				put tWord & " " into tLine
			end if
		else
			put tTestLine & " " into tLine
		end if
	end repeat
	fill text tLine at top left of pTextBox on this canvas
end handler
Greg (pink) Miller

MadPink, LLC
I'm Mad, Pink and Dangerous to Know

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3229
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Wrap Text in Rect

Post by bn » Fri Aug 17, 2018 3:02 pm

Greg,

I did not know newLine, good to know, thank you.

here is your handler set up to display text centered. I made a variable tAlign which is hardcoded here.
It would probably be easier to make it a property so you can set it via script or from the properties Inspector.
Then the "fill" command is conditionally executed depending on the content of tAlign.
This is the same solution I suggested above. Now I put it into your script and it works for me.

Code: Select all

 private handler mpWrapTextinRect(in pTextBox as Rectangle,in pText as String,in pFont as Font)
 	variable tRect as Rectangle
 	variable tTextW as Number
 	variable tTextH as Number
 	variable tMaxRows as Number
 	variable tWords as List
 	variable tWord as String
 	variable tLine as String
 	variable tTestLine as String
 	variable tRow as Number
 	variable tText as Rectangle

   variable tAlign as String -- <-- make a variable
   put "center" into tAlign -- <-- put "center", "top left" or "top right" into it

 	replace newline with " qzqz " in pText
 	split pText by " " into tWords
 	set the font of this canvas to pFont
 	put the height of the layout bounds of text pText on this canvas into tTextH
 	put the width of pTextBox into tTextW
 	put the floor of ((the height of pTextBox)/tTextH) into tMaxRows
 	if tMaxRows = 0 then
 		return
 	end if
 	put 1 into tRow
 	repeat for each element tWord in tWords
 		put tLine & tWord into tTestLine
 		if the width of the layout bounds of text tTestLine on this canvas > tTextW or tWord is "qzqz" then
 		
      if tAlign is "center" then
         fill text tLine at center of pTextBox on this canvas
      else if tAlign is "top left" then
         fill text tLine at top left of pTextBox on this canvas
      else if tAlign is "top right" then
         fill text tLine at top right of pTextBox on this canvas
      else
         fill text tLine at top left of pTextBox on this canvas -- fall back/default
      end if
 			add tTextH to the top of pTextBox
 			put "" into tTestLine
 			if tRow=tMaxRows then
 				put "" into tLine
 				exit repeat
 			end if
 			add 1 to tRow
 			if tWord is "qzqz" then
 				put "" into tLine
 			else
 				put tWord & " " into tLine
 			end if
 		else
 			put tTestLine & " " into tLine
 		end if
 	end repeat

   if tAlign is "center" then
     fill text tLine at center of pTextBox on this canvas
   else if tAlign is "top left" then
     fill text tLine at top left of pTextBox on this canvas
   else if tAlign is "top right" then
     fill text tLine at top right of pTextBox on this canvas
   else
     fill text tLine at top left of pTextBox on this canvas -- fall back/default
   end if
   
 end handler
Kind regards
Bernd

pink
Posts: 224
Joined: Wed Mar 12, 2014 6:18 pm

Re: Wrap Text in Rect

Post by pink » Fri Aug 17, 2018 3:23 pm

The problem with the centering is that it starts putting the text at what i would call "center center" instead of "top center" which is where I want it. What happens is that there is a large gap at the top of the box, and then all the text is at the bottom.

What I tried to do was to calculate the individual rectangle for each line of text, and then put it exactly where I thought it should be, but it always starts in the middle of the original box.
Greg (pink) Miller

MadPink, LLC
I'm Mad, Pink and Dangerous to Know

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3229
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Wrap Text in Rect

Post by bn » Fri Aug 17, 2018 3:29 pm

What happens is that there is a large gap at the top of the box, and then all the text is at the bottom
that it because your pTextBox is too tall, If you set its height to text height and not changing the topLeft or alteratively creating a new rect for filling that has the width of pTextBox and the height of the text and move that down then you have the text where you want it.

You can see this when logging the height of pTextBox.

Kind regards
Bernd

pink
Posts: 224
Joined: Wed Mar 12, 2014 6:18 pm

Re: Wrap Text in Rect

Post by pink » Fri Aug 17, 2018 4:07 pm

I figured out what I was doing wrong, and now this works... the problem I had in my non-working trials was that I was creating individual rectangles by adding tTextH to the top of pTextBox and then using the top and tTextH to set the bottom of pTextBox... however what I should have been doing was setting the height and not the bottom...

Code: Select all

private handler mpWrapTextinRect(in pTextBox as Rectangle,in pText as String,in pFont as Font,in pAlign as String)
  variable tRect as Rectangle
  variable tTextW as Number
  variable tTextH as Number
  variable tMaxRows as Number
  variable tWords as List
  variable tWord as String
  variable tLine as String
  variable tTestLine as String
  variable tRow as Number
  variable tText as Rectangle
  variable tVal as Number

  replace newline with " qzqz " in pText
  split pText by " " into tWords
  set the font of this canvas to pFont
  put the height of the layout bounds of text pText on this canvas into tTextH
  put the width of pTextBox into tTextW
  put the floor of ((the height of pTextBox)/tTextH) into tMaxRows
  if tMaxRows = 0 then
	  return
  end if
  put 1 into tRow

  repeat for each element tWord in tWords
	  put pTextBox into tText
	  put tLine & tWord into tTestLine
	  if the width of the layout bounds of text tTestLine on this canvas > tTextW or tWord is "qzqz" then
		  add (tTextH*(tRow-1)) to the top of tText
		  set the height of tText to tTextH
	  if pAlign is "center" then
		  fill text tLine at center of tText on this canvas
	  else if pAlign is "top right" then
		  fill text tLine at top right of tText on this canvas
	  else
		  fill text tLine at top left of tText on this canvas
	  end if

		  put "" into tTestLine
		  if tRow=tMaxRows then
			  put "" into tLine
			  exit repeat
		  end if
		  add 1 to tRow
		  if tWord is "qzqz" then
			  put "" into tLine
		  else
			  put tWord & " " into tLine
		  end if
	  else
		  put tTestLine & " " into tLine
	  end if
  end repeat

  add (tTextH*(tRow-1)) to the top of tText
  set the height of tText to tTextH
  if pAlign is "center" then
	 fill text tLine at center of tText on this canvas
  else if pAlign is "top right" then
	 fill text tLine at top right of tText on this canvas
  else
	 fill text tLine at top left of tText on this canvas
  end if
end handler
Greg (pink) Miller

MadPink, LLC
I'm Mad, Pink and Dangerous to Know

pink
Posts: 224
Joined: Wed Mar 12, 2014 6:18 pm

Re: Wrap Text in Rect

Post by pink » Fri Aug 17, 2018 5:12 pm

Okay, I swear this my last post for now because I am at work and I really should actually get to work...

I've removed the font as param, as it is easy to set via the system or in OnPaint
the pAlign param needs 2 words: [top,center,bottom] and [left,center,right]
so it can be aligned vertically and horizontally within the given rectangle
I also removed some extraneous variables that I stopped using

here is the full code of a widget using it:
https://github.com/madpink/labelbox-livecode-builder

here is the handler:

Code: Select all

private handler mpWrapTextinRect(in pTextBox as Rectangle,in pText as String,in pAlign as String)
  variable tTextW as Number
  variable tTextH as Number
  variable tMaxRows as Number
  variable tWords as List
  variable tWord as String
  variable tLine as String
  variable tTestLine as String
  variable tRow as Number
  variable tAlign as List
  variable tLinesofText as List

  split pAlign by " " into tAlign
  replace newline with " qzqz " in pText
  split pText by " " into tWords
  put the height of the layout bounds of text pText on this canvas into tTextH
  put the width of pTextBox into tTextW
  put the floor of ((the height of pTextBox)/tTextH) into tMaxRows
  if tMaxRows = 0 then
	  return
  end if
  put 1 into tRow

  repeat for each element tWord in tWords
	  put tLine & tWord into tTestLine
	  if the width of the layout bounds of text tTestLine on this canvas > tTextW or tWord is "qzqz" then
		  push tLine onto back of tLinesofText
		  put "" into tTestLine
		  if tRow=tMaxRows then
			  put "" into tLine
			  exit repeat
		  end if
		  add 1 to tRow
		  if tWord is "qzqz" then
			  put "" into tLine
		  else
			  put tWord & " " into tLine
		  end if
	  else
		  put tTestLine & " " into tLine
	  end if
   end repeat
	push tLine onto back of tLinesofText

	if element 1 of tAlign is "center" then
		add ((the height of pTextBox)-(tMaxRows*tTextH) + (tMaxRows-tRow)*tTextH)/2 to the top of pTextBox
	else if element 1 of tAlign is "bottom" then
		add ((the height of pTextBox)-(tMaxRows*tTextH) + (tMaxRows-tRow)*tTextH) to the top of pTextBox
	end if

	put 1 into tRow
	repeat for each element tLine in tLinesofText
		set the height of pTextBox to tTextH
  		if element 2 of tAlign is "center" then
	  		fill text tLine at center of pTextBox on this canvas
  		else if element 2 of tAlign is "right" then
	  		fill text tLine at top right of pTextBox on this canvas
  		else
	  		fill text tLine at top left of pTextBox on this canvas
  		end if
		add 1 to tRow
		add tTextH to the top of pTextBox
	end repeat
end handler
Greg (pink) Miller

MadPink, LLC
I'm Mad, Pink and Dangerous to Know

Post Reply

Return to “LiveCode Builder”