Getting coordinates for a letter

General TRichView support forum. Please post your questions here
Post Reply
MIKLEI
Posts: 7
Joined: Thu Jan 10, 2008 9:24 am

Getting coordinates for a letter

Post by MIKLEI »

I have 2 RichViews as follows:

EDITOR is used for editing
VIEWER is on another form and monitor, and it shows the exact same text, formatted with the same styles, only all the text sizes are much larger (multiplied by about 4) so that the viewer usually displays only part of the editor text. Keeping the text and styles in sync works fine.

I'd like the editor to show which part of the text is currently visible in the viewer, perhaps by drawing a polyline or a bounding box around it.

By calling ClientToDocument and GetItemAt on the viewer, I should be able tell which item(s) are visible, and the OffsetInItem parameter tells how many letters of the item(s) are visible.

Now that I have the Item, I could call GetItemClientCoords to find where it is on the editor. The problem is that GetItemClientCoords returns the to-left corner of the item, but there's nothing for the nth letter as in GetItemAt.

I suspect the answer lies somewhere in TCustomRVFormattedData or RVLinear, but I can't find any documentation on those.

So the question is, how to get coordinates of the nth letter in a text item?

Thanks,

Mika
Sergey Tkachenko
Site Admin
Posts: 17557
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Ok, it's possible to do using undocumented methods.

Let we have RichView1 and RichView2, containing the same document, may be with different font sizes. For example, it can be loaded like this:

Code: Select all

procedure TForm1.Button1Click(Sender: TObject);
var i: Integer;
begin
  if OpenDialog1.Execute then begin
    RichView1.LoadRVF(OpenDialog1.FileName);
    RichView1.Format;
    RichView2.LoadRVF(OpenDialog1.FileName);
    for i := 0 to RVStyle2.TextStyles.Count-1 do
      RVStyle2.TextStyles[i].Size := RVStyle2.TextStyles[i].Size*2;
    RichView2.Format;
  end;
end;
We will scroll RichView2 when RichView1 is scrolled. We can use RichView1.FirstItemVisible, but using GetItemAt gives more accurate results. If we call it with Strict=False parameter, it should find the closest item.

Code: Select all

uses RVItem, CRVData, CRVFData, DLines;
procedure TForm1.RichView1VScrolled(Sender: TObject);
var pt: TPoint;
  RVData: TCustomRVFormattedData;
  ItemNo, Offs, DItemNo, DOffs: Integer;
  Loc: TRVStoreSubRVData;
begin
  pt := RichView1.ClientToDocument(Point(0,0));
  if not RichView1.GetItemAt(pt.X, pt.Y, RVData, ItemNo, Offs, False) then
    exit;
  // if we in table cell, finding item index of this table
  while RVData<>RichView1.RVData do begin
    RVData.GetParentInfo(ItemNo, Loc);
    Loc.Free;
    RVData := TCustomRVFormattedData(RVData.GetAbsoluteParentData);
  end;
  RichView2.RVData.Item2DrawItem(ItemNo, Offs, DItemNo, DOffs);
  RichView2.ScrollTo(RichView2.RVData.DrawItems[DItemNo].Top);
end;
MIKLEI
Posts: 7
Joined: Thu Jan 10, 2008 9:24 am

Post by MIKLEI »

Thanks,

but I think it's not quite what I'm after. I don't want to scroll the other richview, just show where the other is scrolled at. It should work like this:

- Richview2 scrolls automatically, with a timer
- RV1 is scrolled (and edited) by the user, and it just shows what part of the text is currently visible in RV2.

So RV1 controls text content + styles, both scroll independently, RV1 shows RV2's visible text bounds (a bit like a scrollbar does). So is there a way to get the reverse of GetItemAt function, where you'd get X and Y from ItemNo and (character) offset?

Another question, what would be the best way to make the (timed) scrolling as smooth as possible?
Sergey Tkachenko
Site Admin
Posts: 17557
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Locations of specific characters are not stored (they are calculated when necessary).
But it's possible to get more accurate coordinates than GetItemCoords, if this is a text item occupying several lines. You can get top left coords of the specific line, as it was shown in my code:
1) Get ItemNo and Offset in item
2) Convert to DrawItemNo and Offset in drawing item (RVData.Item2DrawItem)
3) Use RVData.DrawItems[DrawItemNo]'s Left and Top.
MIKLEI
Posts: 7
Joined: Thu Jan 10, 2008 9:24 am

Post by MIKLEI »

Sorry, but I don't get it...

I tried this (ignored all code relating to tables as I won't be using any):

Code: Select all


procedure TViewerForm.TextSizePosScrollChanged;
var LRVData: TCustomRVFormattedData;
      pt: TPoint;
      TopItem, TopCharOffset, DrawItemNo, DrawItemOffset: integer;
begin
// Find item at top left corner of viewer
pt := Viewer.ClientToDocument(Point(0, 0));
Viewer.GetItemAt(pt.X, pt.Y, LRVData, TopItem, TopCharOffset, false);

// Find corresponding draw item in editor
Editor.RVData.Item2DrawItem(TopItem, TopCharOffset, DrawItemNo, DrawItemOffset);

if DrawItemNo >= 0 then
  with  Editor.RVData.DrawItems[DrawItemNo] do
       begin
       // Get bounding box of the line in editor, that contains top line of viewer
       BoundingRect := Rect(Left, Top, Left + Width, Top + Height);
       end;
end;
This way I can only find lines in the editor that are (fully or partially) visible in the viewer. When I scroll down the viewer, the DrawItemOffset variable changes when lines disappear to the top, but obviously it doesn't change the BoundingRect. Is there a way to find the X coordinate of the nth character on a line (=DrawItems[n]) ?
proxy3d
ScaleRichView Developer
Posts: 307
Joined: Mon Aug 07, 2006 9:37 am

Post by proxy3d »

Answer from Sergey:

Positions of characters are not stored.
They are calculated when necessary.
You can calculate them yourself.
First, you need to apply style to some Canvas:
RVStyle1.ApplyStyle(Canvas, StyleNo, rvbdUnspecified, True).

Next, get drawing item text
s := rv.RVData.DrawItems.GetString(DrawItemNo, rv.RVData.Items);

Next, use RVU_GetTextExtentExPoint from RVUni.pas:
RVU_GetTextExtentExPoint(Canvas, s, SOMEBIGINTEGERVALUE, SOMEUNUSEDBOOLEANVAR, DX, rv.RVData.GetItemOptions(ItemNo), SOMEUNUZEDTSIZEVAR),
where DX is a pointer to array of integers containing at least Length(s)+1 items. This function fills this array with X coordinates of characters (and the last item is a coordinate of the end of this string). After adding these coordinates to rv.RVData.DrawItems[DrawItemNo].Left, you get a position of character in TRichView document.
MIKLEI
Posts: 7
Joined: Thu Jan 10, 2008 9:24 am

Post by MIKLEI »

I tried

rv.RVData.DrawItems.GetString(DrawItemNo, rv.RVData.Items);

but it only returns first character of the string?
MIKLEI
Posts: 7
Joined: Thu Jan 10, 2008 9:24 am

Post by MIKLEI »

... does that have something to do with having unicode set to true (in RV + styles) ?
proxy3d
ScaleRichView Developer
Posts: 307
Joined: Mon Aug 07, 2006 9:37 am

Post by proxy3d »

Answer from Sergey:

For Unicode text items, this function returns "raw Unicode" string, where each Unicode character is represented by two adjacent Chars. For English letters, the second byte of each character is #0, but the string does not end at this #0.

For converting to WideString, you can use function RVU_RawUnicodeToWideString(const s: String):WideString; from RVUni.pas
Post Reply