Paste text with target font but keep styles

General TRichView support forum. Please post your questions here
Post Reply
Jim Knopf
Posts: 241
Joined: Mon Dec 30, 2013 10:07 pm
Location: Austria
Contact:

Paste text with target font but keep styles

Post by Jim Knopf »

Hello Sergey,
i give up. How can I transfer text to a TRichViewEdit with the following requirements:

In the target RV there is a certain formatting, e.g. Courier New 12.

In Word or somewhere else, I select text that is formatted in, say, USA light, and put it on the clipboard. This text has underlined and bold items, i.e. different TextStyles.

Now I want the text in my target RV to be formatted in Courier New as well, but the italic and bold items are taken over the same, but in Courier New, that the pasted text matches the existing one.

It is important that only the styles are taken from the other text and nothing else.

I have tried all sorts of things, even using a TRichEdit as a bridge, of course using another TRVEdit and transferring via stream or copy&paste, also paste directly, but nothing works cleanly.

Also here in the forum I have found nothing that has helped me.

Do you have any idea how I can solve this?

Regards
Martin
Image
Sergey Tkachenko
Site Admin
Posts: 17602
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Paste text with target font but keep styles

Post by Sergey Tkachenko »

1) The best solution is using StyleTemplates, they are analogs of styles in Microsoft Word.
If StyleTemplates are used, formatting of inserted RTF and DocX (and HTML) files are processed according to StyleTemplateInsertMode property. By default, formatting of inserted document is changed to formatting of the target document,
So, if font "USA Light" is defined in style of inserted document, and the target document has a style of the same name but formatted with "Courier New" font, "USA Light" will be changed to "Courier New" when inserting.

However, this solution works good only if formatting is made using styles. If formatting is made directly in document (inline formatting), this conversion will not be performed.

2) Another option is to assign rv.RTFReadProperties.ParaStyleMode = rv.RTFReadProperties.TextStyleMode = rvrsUseClosest.
In this case, when inserting (or loading) RTF or DocX, formatting of inserted/loaded document is mapped to the most similar existing formatting.
Please note that list markers are not loaded in this case.

3) If the solutions above are not ok for you, the only way is processing OnPaste event.
In this event, if the Clipboard contains RTF (CanPasteRTF method),
- paste this RTF in an invisible TRichViewEdit (let its name rveHidden)
- change properties rveHidden.Style.TextStyles[], replace FontName and maybe other properties
- save rveHiddent to TMemoryStream (rveHidden.SaveRVFToStream(Stream, False))
- insert this stream in the main editor (InsertRVFFromStreamEd)
- free Stream
- assign DoDefault = False (parameter of OnPaste)
Jim Knopf
Posts: 241
Joined: Mon Dec 30, 2013 10:07 pm
Location: Austria
Contact:

Re: Paste text with target font but keep styles

Post by Jim Knopf »

Okay, I did find a working solution, but it's not straightforward. I'm posting it here in case someone also has this problem (CurrRV is the current TRichViewEdit). Maybe Sergey has a simpler solution?

1. Paste manually:

Code: Select all

procedure TfMain.rvPaste(Sender: TCustomRichViewEdit; var DoDefault: Boolean);
begin
  if Clipboard.HasFormat(CF_TEXT) then
  begin
    ConvertRichtext;
    DoDefault := False;
  end;
  CurrRV.StartLiveSpelling;
end;
2. Paste routine:

Code: Select all

procedure TfMain.ConvertRichtext;
var I: Integer;
    OldCount, Diff: Integer;
    ItS, OfS, ItE, OfE: Integer;
    VS: Integer;
begin
  FStylNo := CurrRV.GetItemStyle(CurrRV.CurItemNo);
  FParaNo := CurrRV.GetItemPara(CurrRV.CurItemNo);
  VS := rv.VScrollPos;


  CurrRV.BeginUndoGroup(rvutInsert);
  CurrRV.SetUndoGroupMode(True);
  try
    if Clipboard.HasFormat(CF_TEXT) then
    begin
      OldCount := CurrRV.ItemCount;
      CurrRV.GetSelectionBounds(ItS, OfS, ItE, OfE, False);
      CurrRV.PasteRTF;
      Diff := CurrRV.ItemCount-OldCount;

      // Paragraph formatting
      CurrRV.SetSelectionBounds(ItS, CurrRV.GetOffsBeforeItem(ItS), ItS+Diff+1, CurrRV.GetOffsAfterItem(ItS+Diff+1));
      CurrRV.ApplyParaStyleConversion(700);

      // Text formatting
      for I := ItS+1 to CurrRV.ItemCount - OldCount + 2 do
      begin
        FFoSt := CurrRV.Style.TextStyles[CurrRV.GetItemStyle(I)].Style;
        CurrRV.SetSelectionBounds(I, CurrRV.GetOffsBeforeItem(I), I, CurrRV.GetOffsAfterItem(I));
        CurrRV.ApplyStyleConversion(700);
      end;

      // Cleanup - Correct several thing as double spaces
      CleanUp(CurrRV);
      rv.VScrollPos := VS;
      rv.SetSelectionBounds(ItS+Diff-1, CurrRV.GetOffsAfterItem(ItS+Diff-1), ItS+Diff-1, CurrRV.GetOffsAfterItem(ItS+Diff-1));
    end;
  finally
    CurrRV.SetUndoGroupMode(False);
  end;
end;
3. Paragraph formatting

Code: Select all

procedure TfMain.rvParaStyleConversion(Sender: TCustomRichViewEdit; StyleNo, UserData: Integer; AppliedToText: Boolean; var NewStyleNo: Integer);
var PI: TParaInfo;
begin
  PI := TParaInfo.Create(nil);
  try
    PI.Assign(rvs.ParaStyles[StyleNo]);
    case UserData of
       . . .
       // Insert, keep paragraph style
      700: begin
             PI.Assign(Sender.Style.ParaStyles[FParaNo]);
           end;
    end;
    NewStyleNo := rvs.FindParaStyle(PI);
  finally
    PI.Free;
  end;
end;
4. Text formatting

Code: Select all

procedure TfMain.rvStyleConversion(Sender: TCustomRichViewEdit; StyleNo, UserData: Integer; AppliedToText: Boolean; var NewStyleNo: Integer);
var FI: TFontInfo;
begin
    FI := TFontInfo.Create(nil);
    try
      FI.Assign(R.Style.TextStyles[StyleNo]);
      case UserData of
        . . .
        // Paste: keep text change style
        700: begin
               FI.Assign(R.Style.TextStyles[FStylNo]);
               FI.Style := FFoSt;
             end;
      end;
      NewStyleNo := R.Style.FindTextStyle(FI);
    finally
      FI.Free;
    end;
  end;
end;
Last edited by Jim Knopf on Sat Oct 21, 2023 9:50 am, edited 2 times in total.
Jim Knopf
Posts: 241
Joined: Mon Dec 30, 2013 10:07 pm
Location: Austria
Contact:

Re: Paste text with target font but keep styles

Post by Jim Knopf »

Oh - same moment posted :-)
Thank you, I'll try out your tips.
Sergey Tkachenko
Site Admin
Posts: 17602
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Paste text with target font but keep styles

Post by Sergey Tkachenko »

Thank you for the code.
Some notes:

1. You check for the presence of a plain text in the Clipboard, but you need to check for RTF.
The simplest way is rve.CanPasteRTF

2. The solution of pasting in the main editor and then applying conversion to the selection is not so simple. It's not necessary that all new content will be added as separate items. Inserted text items may be merged with existing text at the place of insertion. So, the solution with a hidden editor (see my previous reply) is more reliable. Additionally, it is simpler, because instead of ApplyStyleConversion you can simply modify properties of text and paragraph styles.

3. BeginUndoGroup and SetUndoGoupMode must always be called for TopLevelEditor: CurrRV.TopLevelEditor.BeginUndoGroup(rvutInsert) and so on.
Jim Knopf
Posts: 241
Joined: Mon Dec 30, 2013 10:07 pm
Location: Austria
Contact:

Re: Paste text with target font but keep styles

Post by Jim Knopf »

Hello Sergey,

thanks for your additions, I will make it more stable this way.

Have a nice weekend
Martin
Jim Knopf
Posts: 241
Joined: Mon Dec 30, 2013 10:07 pm
Location: Austria
Contact:

Re: Paste text with target font but keep styles

Post by Jim Knopf »

Hello Sergey,

thanks again for your tips!
Indeed, your solution with the invisible RVEdit works perfectly.

Here again the complete code, if someone else is also interested in this topic. The clean transfer of differently formatted text has always been an annoying problem in MS Word, too, which is why it is important to me to better support our users with this issue. Until now only plain text was allowed, but this is much more elegant.

1. OnPaste

Code: Select all

procedure TfMain.rvPaste(Sender: TCustomRichViewEdit; var DoDefault: Boolean);
begin
  ConvertRichtext;
  DoDefault := False;
  FPasted := True;
  CurrRV.StartLiveSpelling;
end;

2. Pasting and Converting

Code: Select all

procedure TfMain.ConvertRichtext;
var I: Integer;
    OldCount, Diff: Integer;
    ItS, OfS, ItE, OfE: Integer;
    VS: Integer;
    StyleNo, ParaNo: Integer;
    Stream: TMemoryStream;
begin
  StyleNo := CurrRV.GetItemStyle(CurrRV.CurItemNo);
  ParaNo := CurrRV.GetItemPara(CurrRV.CurItemNo);
  VS := CurrRV.VScrollPos;
  OldCount := CurrRV.ItemCount;
  CurrRV.GetSelectionBounds(ItS, OfS, ItE, OfE, False);

  CurrRV.TopLevelEditor.BeginUndoGroup(rvutInsert);
  CurrRV.TopLevelEditor.SetUndoGroupMode(True);
  try
    if rvH.CanPasteRTF then
    begin
      Stream := TMemoryStream.Create;
      try
        // Invisible edit
        rvH.PasteRTF;
        for I := 0 to rvsH.TextStyles.Count-1 do
        begin
          rvsH.TextStyles[I].FontName := CurrRV.Style.TextStyles[StyleNo].FontName;
          rvsH.TextStyles[I].Size     := CurrRV.Style.TextStyles[StyleNo].Size;
        end;
        for I := 0 to rvsH.ParaStyles.Count-1 do
        begin
          rvsH.ParaStyles[I].LeftIndent  := CurrRV.Style.ParaStyles[ParaNo].LeftIndent;
          rvsH.ParaStyles[I].RightIndent := CurrRV.Style.ParaStyles[ParaNo].RightIndent;
          rvsH.ParaStyles[I].SpaceBefore := CurrRV.Style.ParaStyles[ParaNo].SpaceBefore;
          rvsH.ParaStyles[I].SpaceAfter  := CurrRV.Style.ParaStyles[ParaNo].SpaceAfter;
          rvsH.ParaStyles[I].LineSpacing := CurrRV.Style.ParaStyles[ParaNo].LineSpacing;
        end;

        // Copy to text
        rvH.SaveRVFToStream(Stream, False);
        Stream.Position := 0;
        CurrRV.InsertRVFFromStreamEd(Stream);
        Diff := CurrRV.ItemCount-OldCount;

        // Cleanup (Change quotation marks, correct punctuation errors)
        for I := 0 to CurrRV.ItemCount-1 do
          PurgeText(CurrRV, I);
        CleanUp(CurrRV);
        CurrRV.Format;
        CurrRV.VScrollPos := VS;
        CurrRV.SetSelectionBounds(ItS+Diff-1, CurrRV.GetOffsAfterItem(ItS+Diff-1), 
                                  ItS+Diff-1, CurrRV.GetOffsAfterItem(ItS+Diff-1));
      finally
        Stream.Free;
      end;
    end;
  finally
    CurrRV.TopLevelEditor.SetUndoGroupMode(False);
  end;
end;
Regards
Martin
Post Reply