Page 1 of 1
Getting coordinates for a letter
Posted: Thu Mar 27, 2008 11:25 am
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
Posted: Thu Mar 27, 2008 7:14 pm
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;
Posted: Fri Mar 28, 2008 9:11 am
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?
Posted: Fri Mar 28, 2008 9:41 am
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.
Posted: Fri Mar 28, 2008 12:09 pm
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]) ?
Posted: Sun Mar 30, 2008 9:03 am
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.
Posted: Tue Apr 01, 2008 11:23 am
by MIKLEI
I tried
rv.RVData.DrawItems.GetString(DrawItemNo, rv.RVData.Items);
but it only returns first character of the string?
Posted: Wed Apr 02, 2008 11:22 am
by MIKLEI
... does that have something to do with having unicode set to true (in RV + styles) ?
Posted: Thu Apr 03, 2008 1:52 pm
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