Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Drawing text to canvas - auto wrapping AND knowing size

Status
Not open for further replies.

djjd47130

Programmer
Nov 1, 2010
480
US
I have a canvas of any specified size. Most importantly, let's assume the width of this canvas is 200, and the height is supposed to automatically expand depending on the contents. Within this canvas, I am to draw a number of sections. Each new section is to automatically go underneath the last section which is already there. Within each section, I am to draw a large amount of text. The problem comes when trying to pre-specify how large I need to make this section in order to accomodate for the size of the text I need to draw. The text is to be automatically wrapped inside this section, therefore the section is always a fixed with, but variable height (depending on how many lines the text is wrapped to). In other words, how do I find out the height of a section before I draw the text in it? (and this is to be wrapped as well, so Canvas.TextHeight will not work).

In summary, I need to know...
- How to properly wrap the text to a fixed with / variable height on a canvas
- Identify Height text will be AFTER it is wrapped into multiple lines
- and/or how many lines there will be AFTER it is wrapped (as well as line spacing)

Think of sending/receiving SMS on an iPhone (which is what I'm trying to replicate). Each message is within a bubble - the text is wrapped inside each bubble, thus making the bubble grow with the more text it has to contain.

JD Solutions
 
Sample of what I'm trying to replicate:
2767974349_4ec432d071_o.jpg


JD Solutions
 
Also, any thoughts on what to base this component on? Currently I'm inheriting a TScrollingWinControl, but was thinking it may become easier if I use a TStringGrid instead.

JD Solutions
 
I have learned now how to use DrawTextEx which can handle word wrapping. Now it's a matter of finding out how high was the final wrapped text.

JD Solutions
 
I guess now what the question is...

OK I create a TBitmap on the spot - completely empty. I use DrawTextEx to place text on the canvas (with a given font) with word wrapping. This canvas is 500 pixels high to accommodate for the maximum size per section. After drawing onto this canvas, how can I detect what is the lowest-most pixel? Would I have to loop through the pixels on the canvas, starting from the bottom, until I find a pixel other than clNone? Or am I missing something that's already there?

JD Solutions
 
Here is a function I made in hopes to overcome this. While the function works, it is sadly slow. When I have a TBitmap which is 1,000 pixels in height, this function will take time to return the result. There must be a way to incorporate ScanLine in order to speed things up - but how to use ScanLine to determine whether or not there are any used pixels?
Code:
function CanvasSize(Canvas: TCanvas): TPoint;
var
  R: TRect;
  X, Y, H, W: Integer;
  D: Bool;
begin
  R:= Canvas.ClipRect;
  W:= R.Right - R.Left;
  H:= R.Bottom - R.Top;
  Result:= Point(W, H);
  D:= False;
  for X:= H - 1 downto 0 do begin
    for Y:= 0 to W - 1 do begin
      if Canvas.Pixels[Y, X] <> 16777215 then begin 
        Result.Y:= X;
        D:= True;           
        Break;              
      end;
    end;
    if D then Break;
  end;
  D:= False;
  for X:= W - 1 downto 0 do begin
    for Y:= 0 to H - 1 do begin
      if Canvas.Pixels[X, Y] <> 16777215 then begin
        Result.X:= Y;
        D:= True;
        Break;
      end;
    end;
    if D then Break;
  end;
end;

JD Solutions
 
I just learned one new thing about DrawTextEx - referencing to MSDN dwDTFormat supports a parameter called DT_CALCRECT and supposedly the function returns the calculated height - and at the same time supposedly does not actually draw anything. It is presumably exactly what I need - it's just using Windows API is a little tricky. Once perfected, I will share my findings and final results.

JD Solutions
 
HA! I'll be a monkey's uncle, that's actually my question in Stack Overflow I just posted earlier today :p The weird thing is, I wasn't notified that it was answered, so I didn't bother checking. Thanks lol

JD Solutions
 
They are good at getting answers to some pretty hard questions.

I didn't even check when the question was posted, I was just hit on how perfectly it dealt with the same issues you mentioned in your posts. Now, obviously, I know why...
 
So I got it all working now, looks good, controls good, and draws fairly quickly. There's a flicker when resizing it, I'll fix that with another intermediate TBitmap.

TJDConversation.png


Now I'm getting into some trickier drawing. I will need to draw a triangular piece coming out of the side of each bubble, just as a speech balloon. I'll also be implementing an image list association to be able to automatically show a display pic for each user. It's made in a way where one converses with all - in other words, one side of the screen is the viewer (locally), and all others are on the other side of the screen. The user is identified by an ID associated with each message: 0 = local viewer; 1 or larger = another user... and this number will correspond with the image index in the image list. It will then automatically draw the users' display images next to their messages. The image list will have to be manually maintained programmaticly to make sure the indexes match.

In the end, I will be incorporating it inside an ActiveX control I'm building for a chat system which I'm also building. This control will be able to work in anything that supports ActiveX, including web pages (at least in Internet Explorer) - and specifically a windows desktop gadget.

Another cool thing about how I designed it is that each message doesn't have any object. Instead, each message record is compacted inside a single string. These strings are stored in the list of rows in the grid. Upon drawing an item, I parse out the data from this string - faster, lighter, and easier actually. Don't have to create objects, maintain pointers, etc.


JD Solutions
 
I just have one final question for this before I begin building the final product. Currently, I am using a TDrawGrid (not much different from a TStringGrid) inside a TScrollBox. The TDrawGrid "Lst" is aligned to the top of the TScrollBox "Box". As more messages appear, Lst automatically grows higher, thus enabling the scroll bar in Box. This is the only way to be able to scroll down a grid without the top of a cell snapping to the top of the control.

What's your opinion? That's all. Would you continue using this method (if you already have to re-write the code anyway) or would you try something else? If the TScrollBox both had a canvas and the ability to preset its ClientHeight (scrollable area) that would be more than enough. Remember, I'm building this inside of a new custom component. I really don't like to create sub-components, set the parent, etc. as I would have to do with the string grid approach. I was thinking a TImage may do the trick too. This may prevent all the flicker from happening too.

The real question is what's the best base for this? I'm already assuming to use the TScrollBox (actually TScrollingWinControl) but there's no canvas or ability to preset the scrollable area. The scroll bars are automatically enabled when there's a control outside of the visible area. Otherwise, without any child control in the TScrollBox, there will never be any scroll bars or ability to scroll down at all.


JD Solutions
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top