How do I use DrawText DT_CALCRECT properly? - delphi

I am generating a report that has a caption in the footer. I use DrawText to find out the caption's dimensions. The problem is the text is always clipped, but when I have a carriage return at the end of the text, all the text appears perfectly.
lClientRect := Rect(0, 0, 4770, 59);
lFlags := DT_CALCRECT or DT_EXPANDTABS or Alignments[Alignment]
or WordWraps[WordWrap] or DT_NOPREFIX or DT_TOP or DT_EXTERNALLEADING;
DrawText(lCanvas.Handle, PChar(lsCaption), Length(lsCaption), lClientRect, lFlags);
I examined the rect after the call to DrawText, and it is (0, 0, 4366, 59), but when I have a carriage return, it is (0, 0, 4366, 118).
I don't have any clue on what is happening. Any help will be appreciated.

The carriage return adds a second line of text to the string, thus doubling the height of the calculated rectangle. (Windows is flexible about whether a line-feed or carriage-return character starts a new line.)
As for why the text is clipped (on the bottom edge, I assume), it might be that you're calculating the size using a different font than you have when you draw the text.

Related

delphi canvas drawtext does not clip the text

I am using Windows 10, Delphi 7. Trying to make Pdf document with SynPDF using it's canvas directly. I need to draw in the rectangle only that part of the text that corresponds to the length of the rectangle, the rest cut off. I am using DrawText (and DrawTextEx) functions to write text in the given rectangle with alignment (TA_LEFT, TA_RIGHT, TA_CENTER).
The problem: these functions draw the text, but do not take into account the given boundaries - they do not clip(crop) this text.
var
R: TRect;
s: String;
begin
R:= Rect(50, 50, 120, 75);
Canvas.Brush.Color:=clYellow;
Canvas.Rectangle(R);
Canvas.Font.Name:='Arial';
Canvas.Font.Size:=10;
Canvas.Font.Style:=[];
Canvas.Brush.Style:= bsClear;
s:='Sample for text clipping';
DrawText(Canvas.Handle, PChar(s), -1, R, TA_LEFT or
{DT_END_ELLIPSIS or }DT_VCENTER or DT_SINGLELINE);
end;
If I add DT_END_ELLIPSIS it works correctly but adds three dots - I do not need dots. What I am doing wrong? Or I need to use other functions for my task?
Unfortunately, I'm not allowed (by StackOverflow) to add a photo with the result...

How to draw text on a canvas with left alignment?

I want to draw a rectangle on a canvas and I want to fill it with text. I've tried with the below code, but it only succeeded with right alignment.
Can anyone help me? How can I draw text on a canvas with left alignment?
{Header Table}
SetStyleHuruf(FCanvas, fsBold, 12, clWhite, 'Maiandra GD');
Brush.Color := color;
Rectangle(cx+50, cy-50, cx+370, cy - 30);
TextOut(round(cx + 215), cy-50, Name);`
Look at the definition of TextOut:
procedure TextOut(X, Y: Integer; const Text: string); override;
The description in help says:
Writes a string on the canvas, starting at the point (X,Y), ...
So, modify your code to draw the text at an X coordinate that is closer to the left border of the rectangle instead of the right right. For example:
TextOut(round(cx + 215), cy-50, Name);
Outputs the text 5 pixels from the left border of the rectangle
TextOut(round(cx + 55), cy-50, Name);
BTW, assuming cx is an integer, you dont need to use Round()
Instead of textout I would use DrawText function which is explained on the Microsoft site here
The DrawText function draws formatted text in the specified rectangle. It formats the text according to the specified method (expanding tabs, justifying characters, breaking lines, and so forth).
To specify additional formatting options, use the DrawTextEx function.
Here you find a great example how to use it

How can I copy TBitmap memory using with windows CopyMemory function

I have 1 bitmap object witdh : 1024px and height : 768 px
I want to cut this bitmap object to 2 part like left and right but I don't want to use DrawBitmap method in canvas because this method can use more CPU then CopyMemory.
I don't want to use this method ( leftImg.Canvas.DrawBitmap(MainBmp, RectF(0,0, MainBmp.Width div 2, bmp.Height),
RectF(0,0, leftImg.Width, leftImg.Height), 1, True); )
MainBmp := TBitmap.Create(1024, 768);
leftImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
rightImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
leftBits := PAlphaColorArray(leftImg.Scanline[0]);
CopyMemory(#leftBits[0], #MainBmp.StartLine[0], (MainBmp.Width div 2) * bmp.Height);
if I am doing like this he can copy but not left part of bitmap :( he copy half of top to bottom.
That drawing is exactly what I want to do.
After cut procces, i need like this without using any loop (like while or for)
Thanks
No can do! As you've found out image data is layout in the memory line by line (hence scanline). What you want could only be possible if it was column by column. Without any loops this is not possible.
As you noticed, a scanline is a row of pixels, from left to right. There is one scanline for each pixel of vertical height in the image.
Your 1024px x 768px images have 768 scanlines. Copying the first half of the data from scanlines yields you the top half of the image.
You wouldn't have to go through every pixel, you can skip ahead since everything is indexed.
However, since you want both halves, you're not wasting any work by going through the whole thing. As you iterate through the data, copy both the left and right parts out at the same time. So, for the first scanline, copy the first half of pixels to the left image and the rest of the pixels to the right image, go to the next line, and repeat.
This should be less work than DrawBitmap twice.
Also, rather than loading the image, displaying it, then splitting it, split it while you're loading the image.
You'll still need a loop, unless you want to write everything 768 times.
Technically, you could rotate the image and do it the way you want, but rotating it would require loops too, and you'd have to rotate it back when you're done.
Use the TCanvas.CopyRect() method to copy portions of one TCanvas to another TCanvas. It allows the two bitmaps to have different pixel formats. The OS will handle the differences internally for you:
MainBmp := TBitmap.Create(1024, 768);
leftImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
rightImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
leftImg.Canvas.CopyRect(
Rect(0, 0, leftImg.Width, leftImg.Height),
MainBmp.Canvas,
Rect(0, 0, leftImg.Width, leftImg.Height)
);
rightImg.Canvas.CopyRect(
Rect(0, 0, rightImg.Width, rightImg.Height),
MainBmp.Canvas,
Rect(leftBmp.Width, 0, rightImg.Width, rightImg.Height)
);

DrawText with DT_CALCRECT - Is there a way to calculate the height of the rect WITHOUT modifying the width (with large strings)?

I have a string that i need to calculate the Rect size (text height) when drawing. My implementation uses the DrawTextW() function with DT_WORDBREAK or DT_CALCRECT flags.
An example of my string:
thisisaverylonglonglonglineoftextthatneedstofitinsideagivenrectwidth
I can see in the MSDN docs, that DrawTextW() method states:
If the largest word is wider than the rectangle, the width is expanded. If the text is less than the width of the rectangle, the width is reduced. If there is only one line of text, DrawText modifies the right side of the rectangle so that it bounds the last character in the line.
however in the MSDN docs, then DrawTextExW() method does not state this.
So i tried to calculate the height using the DrawTextExW() method, however the result is the same as with DrawTextW() function, where it extends the width of the rect to fit the largest line of text.
So how can i correctly calculate the height of the text rect with a given (fixed) width when drawing a large string (with no spaces) where DT_WORDBREAK and DT_CALCRECT are specified?
EDIT:
As a side note, does anyone know how Microsoft Excel does cell text drawing? Is there an API call for this text drawing? This was where my original question stemmed from, however the way it is implemented in Excel is to draw the text and wordbreak/wordwrap on any character (not just a space).
You need to use the DT_WORD_ELLIPSIS flag in uFormat parameter (along with DT_WORDBREAK of course). That will prevent the widening due to long strings with no spaces. It still will not break those long strings though, but your width problem will be solved.
If you also specify DT_MODIFYSTRING, then you could kind of figure out where to break that long string yourself, prior to the final draw.
As for the difference between DrawText(W) and DrawTextEx(W): the latter provides tab formatting, setting margins and returns the actual number of drawn characters. There is no difference in (dimensioning) functionality.

Delphi Printer.Canvas.TextWidth property

I'm trying to set the column width for printing with my Delphi application. Whatever I type for the string doesn't make the width fewer. Actually I don't understand why the property returns a string, it should return width in pixels.
My code is
Printer.Canvas.TextWidth('M');
Edit: i understood it doesn't return a string but what does 'M' mean? what i m trying to do is making a column narrower. my code is located at sudrap.org/paste/text/19688
Edit: i m afraid i couldn t explain the problem clearly, i m sorry. i want it to print like this:
not like this:
Try to check TextRect function. Using this function you can specify the target rectangle where the text should be printed, so you can narrow your column.
uses Graphics;
var
Text: string;
TargetRect: TRect;
begin
Printer.BeginDoc;
Text := 'This is a very long text';
// now I'll specify the rectangle where the text will be printed
// it respects the rectangle, so the text cannot exceed these coordinates
// with the following values you will get the column width set to 50 px
TargetRect := Rect(Margin, Y, Margin + 50, Y + LineHeight);
Printer.Canvas.Font.Size := 11;
Printer.Canvas.Font.Name := 'Arial';
Printer.Canvas.Font.Style := [fsBold];
Printer.Canvas.TextRect(TargetRect, Text);
Printer.EndDoc;
end;
Except this you can get with the TextRect function set of the formatting flags which can help you to specify e.g. text alignment, word wrap etc. For instance if you would like to center the text horizontally in the specified rectangle [100;100], [250;117] you can use the following.
Text := 'Centered text';
TargetRect := Rect(100, 100, 250, 117);
Printer.Canvas.TextRect(TargetRect, Text, [tfCenter]);
Or in your case might be more useful word wrap. Here's an example with rectangle [100;100], [200;134] where the text is automatically wrapped by the TextRect function.
Text := 'This is a very long text';
TargetRect := Rect(100, 100, 200, 134);
Printer.Canvas.TextRect(TargetRect, Text, [tfWordBreak]);
If you use a fixed width font on the canvas, you should get the same result for all single-character strings. If you use a variable width font, each character will return a different width.
Printer.Canvas.Font.Name = 'Courier New';
Printer.Canvas.Font.Size = 13;
ColumnWidth := Printer.Canvas.TextWidth('M');
For different fonts or different font sizes, you will get different results.
I don't see how you're saying it returns text. If it were returning text your code wouldn't even compile, you would be getting errors when you tried to multiply a number by text. You even convert it to a string for display purposes.
Are you being mislead by the fact that with a variable-width font that you'll get different answers for different strings? You can even get different answers for the same letters in a different order. For some fonts "WAM" will produce a different answer than "WMA" because of how the W and A fit together.
Also, you're simply assuming that your labels are narrower than 15 M's. While this is generally the case it's not good programming practice. Instead, you should be asking for the width of each label and using something a bit above the biggest answer.
Finally, your handling of LineHeight is atrocious. Simply add 300 to y if that's what you really want although it should be some multiple of your line height, not a fixed value. You'll get VERY different results from your code off printers with different DPI settings.
Have you even tried stepping through this code with the debugger to see what's going on internally? Your output of the position to the printout suggests you aren't using the debugger.

Resources