Page 1 of 1
Bad RTF table causing RichView to error
Posted: Sat Jul 12, 2025 8:53 pm
by jgkoehn
Greetings Sergey,
I am getting an error in this code:
First chance exception at $75D50144. Exception class EListError with message 'List index out of bounds (-1). TRVList is empty'.
I know it is bad RTF code on a Table (however, I can't always fix the bad RTF tables. These can be from users. Is there a way to handle this more robustly?)
Current code in RV22.2
Code: Select all
function TRVRTFReader.DoTable(WhatHappens: TRVRTFTableEventKind): Boolean;
begin
Result := True;
UpdateMarker;
if Assigned(FOnTable) then
FOnTable(Self, WhatHappens, Result);
AfterTableRow := WhatHappens in [rvf_tbl_RowEnd, rvf_tbl_TableEnd,
rvf_tbl_TableForcedEnd];
end;
Ideas?
Re: Bad RTF table causing RichView to error
Posted: Sun Jul 13, 2025 12:00 pm
by Sergey Tkachenko
This exception happens in try..except block, and finally LoadRTF function simply returns False.
The exception is not noticeable unless debugging in Delhi IDE.
Re: Bad RTF table causing RichView to error
Posted: Tue Jul 15, 2025 6:50 pm
by jgkoehn
Is this related Sergey?
It is showing up in the Richview interface but not always.
Re: Bad RTF table causing RichView to error
Posted: Tue Jul 15, 2025 9:46 pm
by jgkoehn
It is highly possible I've done something wrong and just have no idea on how to get to the bottom of it.
Re: Bad RTF table causing RichView to error
Posted: Wed Jul 16, 2025 11:46 am
by Sergey Tkachenko
If the error text is shown on TRichView itself, this mean that an exception happens while drawing content.
Most probably. it happens because TRichView is not formatted.
Do not forget to call Format after loading.
Re: Bad RTF table causing RichView to error
Posted: Thu Jul 17, 2025 12:53 am
by jgkoehn
I have found the problem but not sure why it exists:
Code: Select all
//SHIFT not pressed (UP)
if GetKeyState(VK_SHIFT) and $ff00 = 0 then begin
try
s := reader.GetItemTag(i);
p := Pos(':', s);
if (p>0) and(p<6) then continue;
reader.SetItemTag(i, ''); <------------if I comment this out I am fine
except
on E: Exception do begin
OutputDebugString(PChar('WMAfterPaste GetItemTag error: ' + E.Message));
Continue;
end;
end;
end;
reader.SetItemTag(i, ''); <------------if I comment this out I am fine
I have tried format, reformat, formattail, SetItemTagEd and many more items. This is in a Timer event. I can run through it just fine in Delphi12.3 debugger but as soon as I click on another open TRichViewEdit I get an this TRichViewItem list error.
The Style type is set to rvf_sInsertMerge.
And then I just start getting a cascade of TRVItemList errors......until my program crashes basically.
Re: Bad RTF table causing RichView to error
Posted: Thu Jul 17, 2025 8:59 am
by standay
You might check that i <> -1, and i <= reader.itemcount-1.
Stan
Re: Bad RTF table causing RichView to error
Posted: Thu Jul 17, 2025 8:01 pm
by jgkoehn
Here is the function it starts in readerpaste and ends in WMAfterPaste and it all seems to work fine until I click on a different reader....
Code: Select all
var
MuteReaderPasteDelegate: Boolean = False;
TextStyleBeforePaste, ParaStyleBeforePaste: Integer;
procedure TfmBookView.readerPaste(Sender: TCustomRichViewEdit;
var DoDefault: Boolean);
var
sItemNo, sItemOffs, eItemno, eItemOffs: Integer;
CurItem: Integer;
InsertWS: String;
begin
//So Reader paste doesn't take over edtTopics.
{ if edtTopics.Focused then begin
//edtTopics.PasteFromClipboard;
exit;
end else if edtVol.Focused then begin
//edtVol.PasteFromClipboard;
exit;
end;}
// Early validation
if not Assigned(reader) then begin
DoDefault := True;
Exit;
end;
// Ensure document is formatted before accessing properties
//reader.ReFormat; //keeps the caret and selection
// Safe bounds checking
if reader.ItemCount = 0 then begin
DoDefault := True;
Exit;
end;
try
CurItem := reader.CurItemNo;
// Validate CurItem is within bounds
if (CurItem < 0) or (CurItem >= reader.ItemCount) then begin
CurItem := 0;
end;
// Safe access to style properties
TextStyleBeforePaste := 0;
ParaStyleBeforePaste := 0;
if (reader.CurTextStyleNo >= 0) and (reader.CurTextStyleNo < reader.Style.TextStyles.Count) then
TextStyleBeforePaste := reader.CurTextStyleNo;
if (reader.CurParaStyleNo >= 0) and (reader.CurParaStyleNo < reader.Style.ParaStyles.Count) then
ParaStyleBeforePaste := reader.CurParaStyleNo;
//If Called from Paste To Editor but only from inside of Bookview this boolean helps with that.
if FVerseLinkPasteToEditor and
(TextStyleBeforePaste < reader.Style.TextStyles.Count) and
reader.Style.TextStyles[TextStyleBeforePaste].Jump then begin
//Check if the item is a jump link should be since that is where this came from.
reader.SetSelectionBounds(CurItem,reader.GetOffsAfterItem(CurItem),CurItem,reader.GetOffsAfterItem(CurItem));
InsertWS := _getGlobalIni.ReadString('general','default.bkv.paste.to.editor.str', ' ');
InsertWS := GlobalUtils.StringReplaceU(InsertWS,'#9', #9);
InsertWS := GlobalUtils.StringReplaceU(InsertWS,'#13', #13);
InsertWS := GlobalUtils.StringReplaceU(InsertWS,'#10', #10);
reader.InsertTextW(InsertWS); //Now insert a space, enter something the user desires.
end;
FVerseLinkPasteToEditor := False; //Unfortunately this should be done immeditately after the popup closes but I can't get it to trigger OnClose
//This problem can cause a user to paste in a link and could cause a problem but will work the second time because this will be set.
reader.GetSelectionBounds(sItemNo, sItemOffs, eItemno, eItemOffs, True);
if (sItemNo = eItemNo) and (sItemOffs = eItemOffs) and (sItemOffs = 1) then
Dec(CurItem);
// Validate CurItem again after potential decrement
if CurItem < 0 then CurItem := 0;
//call the WMAfterPaste in 300ms...
//need to use a timer because there is a Application.ProcessMessages; call in
//the uFrmCopyVerses that would 'eat' the PostMessage below
//FAfterPaste_Handle := Handle;
FAfterPaste_CurItem := CurItem;
FAfterPaste_ItemCount := reader.ItemCount;
// Only set timer if window handle is valid
{if IsWindow(Handle) then
SetTimer(Handle, 1, 300, @TimerProc_AfterPaste);}
// Use timer instead of Windows callback
FAfterPasteTimer.Enabled := False; // Stop any existing timer
FAfterPasteTimer.Enabled := True; // Start new timer
// Post message directly instead of using timer
//PostMessage(Handle, WM_AFTERPASTE, FAfterPaste_CurItem, FAfterPaste_ItemCount);
//This handler is called with CTRL+V. In that case, the Paste method of RVE
//is called which does NOT handle the HTML format. To handle HTML, we need
//to calle the RichViewActions.OnPaste action. So, if the clipboard contains
//HTML, with call the rvActionPaste2 action and set DoDefault to false, to not
//alow the RVE to handle the Paste itself.
if IsClipboardFormatAvailable(CFRV_HTML) and
(not IsClipboardFormatAvailable(CFRV_RVF)) and
(not IsClipboardFormatAvailable(CFRV_RTF)) and
(not MuteReaderPasteDelegate)then begin
try
MuteReaderPasteDelegate := True;
MainReaderForm.rvActionPaste2.Execute;
DoDefault := False;
finally
MuteReaderPasteDelegate := False;
end
end
else begin
DoDefault := True;
end;
except
on E: Exception do begin
debugOnScreen(PChar('readerPaste error: ' + E.Message));
DoDefault := True;
end;
end;
end;
Code: Select all
var
FixEswordLinksOnPasteRE: TRegExx = ();
{ This code tries to remove dead hyperlinks on paste... I am not sure at all
about it. Commented out above
Need to do ApplyStyleConversion here!!! }
procedure TFmBookView.WMAfterPaste(var msg: TMessage);
var
i, CurItem, ItemCount, c, p, minIndex, maxIndex: Integer;
RVTag: TRVTag;
cpd: TCheckpointData;
s: string;
name: TRVUnicodeString;
re: Boolean;
readerName: String;
begin
// Validate we're still in the correct context
if not (Self.Focused or Self.ContainsControl(Screen.ActiveControl)) then begin
debugOnScreen('WMAfterPaste: BookView no longer active, ignoring');
Exit;
end;
// Validate that the reader is still valid and assigned
if not Assigned(reader) then begin
debugOnScreen('WMAfterPaste: Reader not assigned, ignoring message');
Exit;
end;
// Check if reader handle is valid
if not reader.HandleAllocated then begin
debugOnScreen('WMAfterPaste: Reader handle not allocated, ignoring message');
Exit;
end;
// Validate that we're not in a destroying state
if (csDestroying in ComponentState) or (csLoading in ComponentState) then begin
debugOnScreen('WMAfterPaste: Component destroying/loading, ignoring message');
Exit;
end;
//RvBookUtil.ConvertToUnicodeAndMisc(reader.RVData, FCurModule, False, 0);
if (reader.BiDiMode = rvbdUnspecified) then
if RVShouldUserComplexRendering(reader) then
reader.BiDiMode := rvbdLeftToRight;
//remove possible protections from everything pasted
//with reader.Style do
if Assigned(FCurModule) then begin
//user module: remove protection
if FCurModule.IsUserModule then begin
for i:=0 to reader.Style.TextStyles.Count-1 do
reader.Style.TextStyles[i].Protection := [];
for i:=0 to reader.Style.ParaStyles.Count-1 do
reader.Style.ParaStyles[i].Options := reader.Style.ParaStyles[i].Options - [rvpaoReadOnly, rvpaoNoWrap,
rvpaoDoNotWantReturns];
end
end;
CurItem := msg.WParam;
ItemCount := msg.LParam;
// Validate parameters and get current item count
if (CurItem < 0) or (reader.ItemCount = 0) then Exit;
// Ensure CurItem is within valid range
if CurItem >= reader.ItemCount then
CurItem := reader.ItemCount - 1;
//Check if CurItem is a TBitmap if it is convert it to PNG
BitmapToPNG(CurItem);
// Calculate safe range for the loop
maxIndex := Min(CurItem + reader.ItemCount - ItemCount + 1, reader.ItemCount - 1);
// Additional safety check
if maxIndex < CurItem + 1 then
maxIndex := reader.ItemCount - 1;
minIndex := CurItem+1;
if minIndex > maxIndex then Exit; // Nothing to process
for i:=maxIndex downto minIndex do begin //JGK was CurItem+reader.ItemCount-ItemCount+1 do begin
// Double-check bounds before accessing
if (i < 0) or (i >= reader.ItemCount) then //just to be sure...
Break;
//SHIFT not pressed (UP)
if GetKeyState(VK_SHIFT) and $ff00 = 0 then begin
try
s := reader.GetItemTag(i);
p := Pos(':', s);
if (p>0) and(p<6) then continue;
//JGK ERROR THIS IS A PROBLEM
//IT needs used otherwise we have tags users don't need
//But it is causing a TRVItemList error cascade.
reader.SetItemTag(i, '');
except
on E: Exception do begin
debugOnScreen(PChar('WMAfterPaste GetItemTag error: ' + E.Message));
Continue;
end;
end;
end;
try
// Validate style index before accessing
if (reader.GetItemStyle(i) >= 0) and (reader.GetItemStyle(i) < reader.Style.TextStyles.Count) then begin
if reader.Style.TextStyles[reader.GetItemStyle(i)].Jump then begin
if reader.GetItemTag(i) = '' then
reader.Style.TextStyles[reader.GetItemStyle(i)].Jump := False;
end
end;
except
on E: Exception do begin
debugOnScreen(PChar('WMAfterPaste TextStyles error: ' + E.Message));
Continue;
end;
end;
//if pasting tables, add the rvtoEditing (see http://www.trichview.com/forums/viewtopic.php?p=10747#10747)
//bug when pasting from compare view to book view
try
if reader.GetItemStyle(i) = rvsTable then
with TRVTableItemInfo(reader.GetItem(i)) do
Options := Options + [rvtoEditing];
except
on E: Exception do begin
debugOnScreen(PChar('WMAfterPaste Table error: ' + E.Message));
Continue;
end;
end;
//remove checkpoint when pasting from bible view
try
cpd := reader.GetItemCheckpoint(i);
if ( cpd<>nil ) then begin
reader.GetCheckpointInfo(cpd, RVTag, name, re);
if GlobalUtils.StrSameStartU('_TW_VLIDX', RVTag) then
reader.RemoveCheckpointEd(i);
end;
except
on E: Exception do begin
debugOnScreen(PChar('WMAfterPaste Checkpoint error: ' + E.Message));
Continue;
end;
end;
end;
//if SHIFT is down, fix e-Sword style links: for pasting from eSword
if GetKeyState(VK_SHIFT) and $ff00 <> 0 then begin
c := reader.ItemCount-ItemCount;
if c>0 then begin
if not FixEswordLinksOnPasteRE.IsInit then FixEswordLinksOnPasteRE.Create(gc_AutoRefRegex, [roCompiled]);
// Safer bounds for RVIterateAllItems
maxIndex := Math.Min(CurItem+c+1, reader.ItemCount-1);
if maxIndex >= minIndex then begin
RVIterateAllItems(reader.RVData, [], DetectVRefsOnPasteCB, 'WMAfterPaste minIndex to maxIndex', minIndex,
maxIndex);
end else begin
// Fallback: use full range if calculation fails
maxIndex := reader.ItemCount - 1;
if maxIndex >= minIndex then begin
RVIterateAllItems(reader.RVData, [], DetectVRefsOnPasteCB, 'WMAfterPaste minIndex to maxIndex', minIndex, maxIndex);
end;
{$IFDEF DEBUG}
debugOnScreen(PChar('WMAfterPaste: Used fallback maxIndex ' + IntToStr(maxIndex)));
{$ENDIF}
end;
cmdAutoDetectVREFsClick(nil);
end;
end;
end;
if I comment out:
in WMAfterPaste it works fine but off course there are tags that we don't want.
Re: Bad RTF table causing RichView to error
Posted: Thu Jul 17, 2025 8:07 pm
by jgkoehn
OOps I missed the timer funciton it goes to before WMAfterPaste
This is to allow the paste function to complete.
Code: Select all
// Replace the timer callback with this method:
procedure TfmBookView.OnAfterPasteTimer(Sender: TObject);
begin
FAfterPasteTimer.Enabled := False;
try
// Validate we're still in the same context as when timer was set
if not IsWindow(Handle) then
Exit;
// Critical: Check if we're still the active/focused book view
if not (Self.Focused or Self.ContainsControl(Screen.ActiveControl)) then
Exit;
// Validate reader is still valid and hasn't changed
if not Assigned(reader) or not reader.HandleAllocated then
Exit;
// Validate the stored item count matches current state
//if reader.ItemCount <> FAfterPaste_ItemCount then
//Exit;
// Validate CurItem is still valid
if (FAfterPaste_CurItem < 0) or (FAfterPaste_CurItem >= reader.ItemCount) then
Exit;
PostMessage(Handle, WM_AFTERPASTE, FAfterPaste_CurItem, FAfterPaste_ItemCount);
except
on E: Exception do begin
debugOnScreen(PChar('OnAfterPasteTimer error: ' + E.Message));
end;
end;
end;
Re: Bad RTF table causing RichView to error
Posted: Thu Jul 17, 2025 9:05 pm
by standay
Sergey may have a better idea, but here's mine.
I'm not entirely sure what all the vars are that are in play, but maybe change this:
Code: Select all
for i:=maxIndex downto minIndex do begin //JGK was CurItem+reader.ItemCount-ItemCount+1 do begin
// Double-check bounds before accessing
if (i < 0) or (i >= reader.ItemCount) then //just to be sure...
Break;
...
To something like this:
Code: Select all
if (i > -1) and (i <= reader.ItemCount -1) then
for i := 0 to reader.ItemCount -1 do
begin
s := reader.GetItemTag(i);
p := Pos(':', s);
if not ((p>0) and (p<6)) then //or whatever the logic is you need
reader.SetItemTag(i, ''); //if just removing tag, should not need to loop in reverse
end;
It seems like your loop is still outside the range of reader items. Just a guess.
Stan