Recent

Author Topic: The problem of drawing lines under strings  (Read 10998 times)

TRon

  • Hero Member
  • *****
  • Posts: 4377
Re: The problem of drawing lines under strings
« Reply #15 on: January 30, 2024, 05:17:03 am »
Just here to state that trying with synedit got me nowhere as well. This is starting to look more and more as a defeat though I'm not very well versed with LCL.
Today is tomorrow's yesterday.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8572
Re: The problem of drawing lines under strings
« Reply #16 on: January 30, 2024, 08:50:14 am »
In fact, it's just an input field with parallel lines of water under the string. Water parallel lines are used to enhance the display. The full effect is shown below.

I don't know whether this will help in practice, but considering terminology for searching in case anybody's done it before.

Forget "water", since that sounds like the wavy lines that many programs use to indicate errors (which is, of course, an interesting question in itself).

What you're looking for is "notebook ruling", or "exercise book ruling".

I can't answer it myself, but I'm pretty sure that there has been previous discussion of colouring alternative lines in e.g. Synedit, as an alternative to "music ruled" paper used for long listings and reports.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

jianwt

  • Full Member
  • ***
  • Posts: 164
Re: The problem of drawing lines under strings
« Reply #17 on: January 30, 2024, 12:38:19 pm »

@TRon
I've been experimenting with some of synedit's features today, and I've found that synedit doesn't really suit my needs. The main thing is that its wordwrap (Chinese garbled code) limits its use.

@MarkMLl
"notebook ruling", or "exercise book ruling".  I searched. Nothing.

Now, I have found that TRichView can realize this function on windows. But it is not available under linux.
Code: Pascal  [Select][+][-]
  1.     Canvas.Pen.Color := $EE9900;
  2.     Canvas.Pen.Style := psSolid;
  3.     Canvas.Pen.Width := 1;
  4.    
  5.     Sender.RVData.Item2FirstDrawItem(Sender.FirstItemVisible, FirstDrawItemNo);
  6.     Sender.RVData.Item2LastDrawItem(Sender.LastItemVisible, LastDrawItemNo);
  7.  
  8.     for i := FirstDrawItemNo to LastDrawItemNo do
  9.       if Sender.RVData.DrawItems[i].FromNewLine then
  10.       begin
  11.         Y := Sender.RVData.DrawItems[i].Top + Sender.RVData.DrawItems[i].Height- (Sender.VScrollPos * Sender.VSmallStep);
  12.         Canvas.MoveTo(0, Y);
  13.         Canvas.LineTo(Sender.ClientWidth, Y);
  14.  

rvk

  • Hero Member
  • *****
  • Posts: 7042
Re: The problem of drawing lines under strings
« Reply #18 on: January 30, 2024, 02:19:37 pm »
The problem with TRichMemo (and lots of other components) is that they use a widget class which is hard to reach.
Most widgets don't expose the PAINT procedure.

Closest I could come to simulating something simular was using a TPaintBox drawn behind the TRichMemo and using that to draw what you want. You need to set the TRichMemo.Transparent to true (which appears to be cross-platform).

Code: Pascal  [Select][+][-]
  1. procedure TForm1.PaintBox1Paint(Sender: TObject);
  2. var
  3.   cv: TCanvas;
  4.   X, LineHeight: Integer;
  5.   AMetric: TParaMetric;
  6. begin
  7.   cv := TPaintBox(Sender).Canvas;
  8.   cv.Brush.Color := clWhite;
  9.   cv.FillRect(0, 0, Width, Height);
  10.   cv.Pen.Color := clRed;
  11.   X := 0;
  12.   while X < cv.Height do
  13.   begin
  14.     cv.Line(0, X, Width, X);
  15.     // if not GetParaMetric(Ps, AMetric) then exit;
  16.     LineHeight := 16; // calculate this for this position !! (2xfont.size?)
  17.     X := X + LineHeight;
  18.   end;
  19.   RichMemo1.Invalidate; // needed to redraw RichMemo ?
  20. end;
  21.  
  22. procedure TForm1.FormCreate(Sender: TObject);
  23. begin
  24.   PaintBox1.Top := RichMemo1.Top;
  25.   PaintBox1.Left := RichMemo1.Left;
  26.   PaintBox1.Height := RichMemo1.Height;
  27.   PaintBox1.Width := RichMemo1.Width;
  28.   RichMemo1.Transparent := true;
  29. end;

Not perfect yet. You'll need to calculate the line height per line in RichMemo but it shows that you can draw "underneath" the TRichMemo.

Of course when TRichMemo is changed in size you would need to do the same with TPaintBox.
I didn't try yet to make TPainBox a child of TRichMemo (maybe that works too).

(There is no way to expose the canvas from TRichMemo like in Delphi or catch the WM_PAINT, which seems very difficult ???)

« Last Edit: January 30, 2024, 02:21:54 pm by rvk »

Josh

  • Hero Member
  • *****
  • Posts: 1456
Re: The problem of drawing lines under strings
« Reply #19 on: January 30, 2024, 03:04:09 pm »
Hi

tcustomdrawncontols has tcdedit component

Its a very basic component that you will probably need to expand upon to get the functionality of your needs, but it has a paint method you can override.

the code has no error checking...  ;D
« Last Edit: January 30, 2024, 07:27:43 pm by Josh »
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8572
Re: The problem of drawing lines under strings
« Reply #20 on: January 30, 2024, 07:18:42 pm »
tcustom contols has tcdedit component

Is this Custom /Drawn/ controls i.e. https://lazarus-ccr.sourceforge.io/docs/lcl/customdrawncontrols/tcdedit.html etc.?

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Josh

  • Hero Member
  • *****
  • Posts: 1456
Re: The problem of drawing lines under strings
« Reply #21 on: January 30, 2024, 07:28:55 pm »
yep, typo.

fingers not going same speed as thinking.

The best way to get accurate information on the forum is to post something wrong and wait for corrections.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8572
Re: The problem of drawing lines under strings
« Reply #22 on: January 30, 2024, 09:58:27 pm »
Typo or no typo, looks nice. With a nod to RVK's demo, I'd have thought that by now things like per-line background colouration and per-word underlines (for proofreaders etc., see image) would be accessible for any editor.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

jianwt

  • Full Member
  • ***
  • Posts: 164
Re: The problem of drawing lines under strings
« Reply #23 on: January 31, 2024, 05:57:54 am »
@rvk、@Josh、@MarkMLl
Thank you all for your help. I've received all your good ideas.

According to rvk method, I wrote a DEMO, but there are still problems;
My idea of drawing horizontal lines is: calculate the height of RichMemo font + RichMemo line spacing /2 to get the Y-axis position of each line;

2 questions to ask for help.
1. How to accurately calculate the height of RichMemo font (pixel);
2. How to accurately measure RichMemo1 line spacing (pixels).

When changing the font size, the horizontal line can adapt to the font size.

rvk

  • Hero Member
  • *****
  • Posts: 7042
Re: The problem of drawing lines under strings
« Reply #24 on: January 31, 2024, 11:08:08 am »
According to rvk method, I wrote a DEMO, but there are still problems;
My idea of drawing horizontal lines is: calculate the height of RichMemo font + RichMemo line spacing /2 to get the Y-axis position of each line;

2 questions to ask for help.
1. How to accurately calculate the height of RichMemo font (pixel);
2. How to accurately measure RichMemo1 line spacing (pixels).
Yeah, calculating lineheight in RichMemo is really hard. In Windows it wouldn't be a problem because you can just do SendMessage to the richedit component. With the widget mechanisme and cross-platform this doesn't work in TRichMemo.

I did however find a workaround. There is a CharAtPos function which should work on all platforms (?)
It will give you the character (inside the text) at a specific x,y point.
We can (mis)use that to gain information about the line height.
We begin at pixel 1,1 and increment the Y pixel by 1 until we reach a new character (because when just adding 1, 2, 3 you still get the first character back, until you are over the threshold of that line).

Below is a complete example. Just drop a TRichMemo on a form and double click the form itself to create the OnCreate.
Now paste everything below under the implementation.

I've created a small ruler class (TMyRulerForRichMemo) which, when created, sticks to the owner.
The rest of the code in FormCreate is just to fill the RichMemo with some text for testing.

Code: Pascal  [Select][+][-]
  1. type
  2.   TMyRulerForRichMemo = class(TPaintBox)
  3.   protected
  4.     procedure Paint; override;
  5.   public
  6.     constructor Create(AOwnerRichMemo: TRichMemo); reintroduce; overload;
  7.   end;
  8.  
  9. constructor TMyRulerForRichMemo.Create(AOwnerRichMemo: TRichMemo);
  10. begin
  11.   inherited Create(AOwnerRichMemo);
  12.   if AOwnerRichMemo.Parent = nil then exit;
  13.   Parent := AOwnerRichMemo.Parent;
  14.   Top := AOwnerRichMemo.Top;
  15.   Left := AOwnerRichMemo.Left;
  16.   Height := AOwnerRichMemo.Height;
  17.   Width := AOwnerRichMemo.Width;
  18.   AOwnerRichMemo.Transparent := True;
  19. end;
  20.  
  21. procedure TMyRulerForRichMemo.Paint;
  22. var
  23.   X, Q1, Q2: integer;
  24. begin
  25.   Canvas.Brush.Color := clWhite;
  26.   Canvas.FillRect(0, 0, Width, Height);
  27.   Canvas.Pen.Color := clRed;
  28.   X := 1; // we begin at the top
  29.   while X < Canvas.Height do
  30.   begin
  31.     // first we determine the character at 1,1
  32.     Q1 := TRichMemo(Owner).CharAtPos(1, X); // EM_CHARFROMPOS
  33.     Q2 := Q1;
  34.     // next we are going down until the character is different
  35.     while (Q2 = Q1) and (X < Canvas.Height) do
  36.     begin
  37.       Inc(X);
  38.       Q2 := TRichMemo(Owner).CharAtPos(1, X); // EM_CHARFROMPOS
  39.     end;
  40.     // Yes... X is now at a new character so we have a new line
  41.     Canvas.Line(0, X, Width, X);
  42.   end;
  43.   TRichMemo(Owner).Invalidate;
  44. end;
  45.  
  46. procedure TForm1.FormCreate(Sender: TObject);
  47. var
  48.   I: integer;
  49. begin
  50.   { Ruler := } TMyRulerForRichMemo.Create(RichMemo1);
  51.   // we don't need the Ruler variable anywhere
  52.  
  53.   RichMemo1.Clear;
  54.   RichMemo1.Font.Name := 'Calibri';
  55.   RichMemo1.Font.Size := 11;
  56.  
  57.   RichMemo1.SelStart := 9999999;
  58.   RichMemo1.SelText := 'aaaaaaaaaa' + RichMemo1.Lines.LineBreak;
  59.   RichMemo1.SelAttributes.Size := 11;
  60.  
  61.   RichMemo1.SelStart := 9999999;
  62.   RichMemo1.SelText := 'bbbbbbbbbb' + RichMemo1.Lines.LineBreak;
  63.   RichMemo1.SelAttributes.Size := 16;
  64.  
  65.   RichMemo1.SelStart := 9999999;
  66.   RichMemo1.SelText := 'cccccccccc' + RichMemo1.Lines.LineBreak;
  67.   RichMemo1.SelAttributes.Size := 20;
  68.  
  69.   RichMemo1.SelStart := 9999999;
  70.   RichMemo1.SelText := 'ddd';
  71.   RichMemo1.SelAttributes.Size := 11;
  72.  
  73.   RichMemo1.SelStart := 9999999;
  74.   RichMemo1.SelText := 'XXXX';
  75.   RichMemo1.SelAttributes.Size := 30;
  76.  
  77.   RichMemo1.SelStart := 9999999;
  78.   RichMemo1.SelText := 'dddd' + RichMemo1.Lines.LineBreak;
  79.   RichMemo1.SelAttributes.Size := 11;
  80.  
  81.   for I := 6 to 26 do
  82.   begin
  83.     RichMemo1.SelStart := 9999999;
  84.     RichMemo1.SelText := StringOfChar(chr(64 + I), 10) + RichMemo1.Lines.LineBreak;
  85.     RichMemo1.SelAttributes.Size := 11;
  86.   end;
  87.  
  88.   RichMemo1.SelStart := 0;
  89.  
  90. end;

There is still some very very small artifact when scrolling (see second image) but that's probably a minor issue.

(BTW. If your TRichMemo can change size during running you'll need to create a resize event for the ruler. Let me know if that's needed.

jianwt

  • Full Member
  • ***
  • Posts: 164
Re: The problem of drawing lines under strings
« Reply #25 on: January 31, 2024, 01:40:25 pm »

@rvk,Thank you very much for your help. It worked very well. Thank you so much for helping the newbie.

“(BTW. If your TRichMemo can change size during running you'll need to create a resize event for the ruler. Let me know if that's needed.”
I need this feature.
Another requirement I have is that RichMemo is initially empty when I use it, that is, I need to manually input the string. How to have an initial horizontal line in the first line when RichMemo is initially empty, and then input characters on this horizontal line? That is, you have a horizontal line first, and then you type characters on it.
See the image below for the effect.

rvk

  • Hero Member
  • *****
  • Posts: 7042
Re: The problem of drawing lines under strings
« Reply #26 on: January 31, 2024, 01:50:30 pm »
“(BTW. If your TRichMemo can change size during running you'll need to create a resize event for the ruler. Let me know if that's needed.”
I need this feature.
Below is the code with resizing.

Another requirement I have is that RichMemo is initially empty when I use it, that is, I need to manually input the string. How to have an initial horizontal line in the first line when RichMemo is initially empty, and then input characters on this horizontal line? That is, you have a horizontal line first, and then you type characters on it.
The problem with that is that I use the characters below every line to check if you need to draw a line above.
(So on every character change when going downwards)

But for the last line (and empty line at first) there are no characters below it. So it can't calculate the height.

I'm not sure if we can get those properties (cross-platform).

This is the code with resize.
I also found out that the artifacts at the side during scrolling come from the scrolling of the window AFTER drawing the lines.
Still figuring this out...

Code: Pascal  [Select][+][-]
  1. type
  2.   TMyRulerForRichMemo = class(TPaintBox)
  3.   protected
  4.     procedure Paint; override;
  5.   public
  6.     constructor Create(AOwnerRichMemo: TRichMemo); reintroduce; overload;
  7.     destructor Destroy; override;
  8.     procedure DoResize(Sender: TObject);
  9.   end;
  10.  
  11. constructor TMyRulerForRichMemo.Create(AOwnerRichMemo: TRichMemo);
  12. begin
  13.   inherited Create(AOwnerRichMemo);
  14.  
  15.   if TRichMemo(Owner).Parent = nil then exit;
  16.   TRichMemo(Owner).AddHandlerOnResize(@DoResize, false);
  17.   Parent := TRichMemo(Owner).Parent;
  18.   TRichMemo(Owner).Transparent := True;
  19.  
  20.   // We have a 2 pixel border which scrolls on invalidate !!
  21.   // TRichMemo(Owner).BorderStyle := bsNone;
  22.   // above will work but will result in characters against the border
  23.  
  24. end;
  25.  
  26. destructor TMyRulerForRichMemo.Destroy;
  27. begin
  28.   // TRichMemo(Owner).RemoveAllHandlersOfObject(Self);
  29.   // not needed, owner owns this object
  30. end;
  31.  
  32. procedure TMyRulerForRichMemo.DoResize(Sender: TObject);
  33. begin
  34.   Top := TRichMemo(Owner).Top;
  35.   Left := TRichMemo(Owner).Left;
  36.   Height := TRichMemo(Owner).Height;
  37.   Width := TRichMemo(Owner).Width;
  38. end;
  39.  
  40. procedure TMyRulerForRichMemo.Paint;
  41. var
  42.   X, Q1, Q2: integer;
  43. begin
  44.  
  45.   // Canvas.Lock; // is this needed yet?
  46.   try
  47.     Canvas.Brush.Style := bsSolid;
  48.     Canvas.Brush.Color := clWhite;
  49.     Canvas.FillRect(0, 0, Width, Height);
  50.  
  51.     // Draw the recangle around TRichMemo (because we set transparency)
  52.     Canvas.Brush.Style := bsSolid;
  53.     Canvas.Brush.Color := clBlack;
  54.     Canvas.FrameRect(0, 0, Width, Height);
  55.  
  56.     // Now draw the rulers
  57.     Canvas.Pen.Color := clBlue;
  58.     Canvas.Pen.Width := 1;
  59.     X := 1; // we begin at the top
  60.     while X < Canvas.Height do
  61.     begin
  62.       // first we determine the character at 1,1
  63.       Q1 := TRichMemo(Owner).CharAtPos(1, X); // EM_CHARFROMPOS
  64.       Q2 := Q1;
  65.       // next we are going down until the character is different
  66.       while (Q2 = Q1) and (X < Canvas.Height) do
  67.       begin
  68.         Inc(X);
  69.         Q2 := TRichMemo(Owner).CharAtPos(1, X); // EM_CHARFROMPOS
  70.       end;
  71.       // Yes... X is now at a new character so we have a new line
  72.       Canvas.Line(0, X, Width, X);
  73.     end;
  74.  
  75.   finally
  76.  
  77.     // Canvas.UnLock;
  78.     TRichMemo(Owner).Invalidate;
  79.  
  80.   end;
  81. end;
  82.  
  83. procedure TForm1.FormCreate(Sender: TObject);
  84. begin
  85.   { Ruler := } TMyRulerForRichMemo.Create(RichMemo1);
  86.   // we don't need the Ruler variable anywhere
  87.  
  88.   RichMemo1.Clear;
  89.  
  90. end;

Edit: Woops. Of course variable X should be Y  :-[
« Last Edit: January 31, 2024, 02:24:20 pm by rvk »

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: The problem of drawing lines under strings
« Reply #27 on: January 31, 2024, 09:10:57 pm »
Nice done @rvk  O:-)
If you wanna more tinker on, here are problems I found and attached as image to show how I mean the next lines
- flickering
- the font parameter or something else has impact on font, it does change "boldness" while typing
- wordwrap does not work

Lazarus 3.99 (rev main_3_99-1338-geb5db964c6) FPC 3.2.2 x86_64-win64-win32/win64
« Last Edit: January 31, 2024, 09:17:02 pm by KodeZwerg »
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

rvk

  • Hero Member
  • *****
  • Posts: 7042
Re: The problem of drawing lines under strings
« Reply #28 on: January 31, 2024, 09:52:44 pm »
Nice done @rvk  O:-)
If you wanna more tinker on, here are problems I found and attached as image to show how I mean the next lines
- flickering
- the font parameter or something else has impact on font, it does change "boldness" while typing
- wordwrap does not work

Lazarus 3.99 (rev main_3_99-1338-geb5db964c6) FPC 3.2.2 x86_64-win64-win32/win64
I'm not sure if TS wanted only a line under each line with linebreak or under each line period.
The code could be adjusted to check if there is a linebreak between the last and next found character/line (Q1 and Q2 in the procedure). I'll look into that.

I saw flickering only if I draw to much. I also noticed that the artifacts during scrolling (2 pixels at the sides when font size changes) is because the paintbox doesn't get redrawn each time but the lines scroll because the underlying richmemo scrolls. So I'm still thinking about a solution. I tried redraw on each change selection but that only intensified the flickering. So I'm stil thinking about that.

I haven't noticed changes in the font. I don't touch the richmemo itself so that would be strange. The lines also don't get bigger for me. I'll look into that again tomorrow.

vfclists

  • Hero Member
  • *****
  • Posts: 1165
    • HowTos Considered Harmful?
Re: The problem of drawing lines under strings
« Reply #29 on: January 31, 2024, 10:03:21 pm »
There are some awesome guys on this forum :) :) :)
Lazarus 3.0/FPC 3.2.2

 

TinyPortal © 2005-2018