Bad RTF table causing RichView to error

General TRichView support forum. Please post your questions here
Post Reply
jgkoehn
Posts: 317
Joined: Thu Feb 20, 2020 9:32 pm

Bad RTF table causing RichView to error

Post 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?
Sergey Tkachenko
Site Admin
Posts: 17867
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Bad RTF table causing RichView to error

Post 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.
jgkoehn
Posts: 317
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post by jgkoehn »

Is this related Sergey?
It is showing up in the Richview interface but not always.
Attachments
Screenshot 2025-07-13 072233.png
Screenshot 2025-07-13 072233.png (30.67 KiB) Viewed 2291 times
jgkoehn
Posts: 317
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post 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.
Sergey Tkachenko
Site Admin
Posts: 17867
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Bad RTF table causing RichView to error

Post 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.
jgkoehn
Posts: 317
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post 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.
standay
Posts: 294
Joined: Fri Jun 18, 2021 3:07 pm

Re: Bad RTF table causing RichView to error

Post by standay »

You might check that i <> -1, and i <= reader.itemcount-1.

Stan
jgkoehn
Posts: 317
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post 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:

Code: Select all

reader.SetItemTag(i, '');
in WMAfterPaste it works fine but off course there are tags that we don't want.
jgkoehn
Posts: 317
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post 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;
standay
Posts: 294
Joined: Fri Jun 18, 2021 3:07 pm

Re: Bad RTF table causing RichView to error

Post 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
Post Reply