Appending RTF

General TRichView support forum. Please post your questions here
Post Reply
csterg
Posts: 306
Joined: Fri Nov 25, 2005 9:09 pm

Appending RTF

Post by csterg »

Hello Sergey,
i am still trying to find the best method for creating some complex documents that my app requires and i thought of using rtf.
The fact is that the document is actually a concatenation of many small paragraphs. Each paragraph has some XML tags that denote formatting (for example html tags, etc).

Since the methods so far (use the AddNL and ApplyStyleConversion, i described in previous post) seem complicated, i thought that i could use the LoadRTFFromStream.

This method seems to work also (and might be simpler for my case). What i want to ask is this: can i use an RTF fragment or should i use a complete RTF document?

For example:
it tried adding (with a Memory stream) the following: " This is a {\b bold } word". Unfortunately no bold appeared, although the plain text was there. The minimum code i needed to see the bold was this:
"{\rtf1{\fonttbl{\f0}}This is a \b bold \b0 word}".
Now, i suppose that if i need to also add colors and more fonts, i will need to create a colortbl and proper fonttbl.

My questions are these:
1. Why the first RTF fragment didn't work?
2. Do i need to always supply the rtf headers? For example, say that cf2 is blue. Do i need to have a colortbl for each fragment? Wouldn't be easier to say "{\cf2 This is blue}" and define once (for all fragments) that cf2 is for blue?. Is there a way to do this?
3. Since i suppose that adding always the RTF headers will have a deep impact on parsing time, is there a better solution than this?
4. Should i try to use RVF instead? Will it be faster? Is it easy to convert from RTF to RVF (string conversion)?
5. A last question: when using the LoadRTFFromStream method, a new paragraph is created for each fragment i insert: how can i avoid this? If not, how i could join the lines (even after formatting)?

Thanks for your help,
Costas
Sergey Tkachenko
Site Admin
Posts: 17310
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

1. Why the first RTF fragment didn't work?
Probably because some special actions are performed when the closing '}' of 'rtf1' block is processed.
2. Do i need to always supply the rtf headers? For example, say that cf2 is blue. Do i need to have a colortbl for each fragment? Wouldn't be easier to say "{\cf2 This is blue}" and define once (for all fragments) that cf2 is for blue?. Is there a way to do this?
(I am not sure that I understand the question correctly)
Each RTF loaded by LoadRTFFromStream must be self-containing, must have all necessary declarations.
3. Since i suppose that adding always the RTF headers will have a deep impact on parsing time, is there a better solution than this?
If using RTF - no, I am afraid.
But it's not necessary for RTF headers to be large. Font and color (if you use colors) tables must present, but they may include only fonts and colors which are really used in that RTF.
4. Should i try to use RVF instead? Will it be faster? Is it easy to convert from RTF to RVF (string conversion)?
See the help file, "RVF Specification" for details about RVF.
RVF is simple to generate without TRichView is it does not include declarations of text and paragraph styles (a predefined set of styles is used).
But probably the best solution is using RVXML ( http://www.trichview.com/resources/xml/ )
5. A last question: when using the LoadRTFFromStream method, a new paragraph is created for each fragment i insert: how can i avoid this? If not, how i could join the lines (even after formatting)?
Not possible using documented methods.
But it's quite simple: set SameAsPrev property of the first paragraph item to True. Besides, all items in the same paragraph must have the same value of ParaNo property (index of paragraph style), so you must correct them when joining two paragraphs of different style (there is a NormalizeRichView procedure doing this work, but making it yourself is faster). Calling Format is required after modifying these properties.

Some examples: http://www.trichview.com/forums/viewtopic.php?t=66

As for joining paragraphs as an editing operation (undoable by user), you can move caret to the beginning of paragraph (SetSelectionBounds) and simulate backspace key (SendMessage WM_KEYDOWN)
csterg
Posts: 306
Joined: Fri Nov 25, 2005 9:09 pm

Post by csterg »

5. A last question: when using the LoadRTFFromStream method, a new paragraph is created for each fragment i insert: how can i avoid this? If not, how i could join the lines (even after formatting)?
Not possible using documented methods.
But it's quite simple: set SameAsPrev property of the first paragraph item to True. Besides, all items in the same paragraph must have the same value of ParaNo property (index of paragraph style), so you must correct them when joining two paragraphs of different style (there is a NormalizeRichView procedure doing this work, but making it yourself is faster). Calling Format is required after modifying these properties.

Some examples: http://www.trichview.com/forums/viewtopic.php?t=66

As for joining paragraphs as an editing operation (undoable by user), you can move caret to the beginning of paragraph (SetSelectionBounds) and simulate backspace key (SendMessage WM_KEYDOWN)
The following code works:
ItemNo := rv.ItemCount-1;
rv.LoadRTFFromStream(ms);
rv.GetItem(ItemNo + 1).SameAsPrev := True;

Does setting the SameAsPrev correct the ParaNo for all items or i do need to do it manually after setting the SameAsPrev? (it is not clear from your previous comment)

Thanks for your answers and immediate support, they are so helpful.

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

Post by Sergey Tkachenko »

Code: Select all

ItemNo := rv.ItemCount; 
rv.LoadRTFFromStream(ms);
if ItemNo = rv.ItemCount then
  exit;
rv.GetItem(ItemNo).SameAsPrev := True;
if ItemNo = 0 then
  exit;
for i := ItemNo to rv.ItemCount-1 do
begin
  if not rv.GetItem(i).SameAsPrev then
    break;
  rv.GetItem(i).ParaNo := rv.GetItem(ItemNo-1).ParaNo;
end;
This example assumes that RTF documents cannot start from table or paragraph marker (they cannot have SameAsPrev=True)
csterg
Posts: 306
Joined: Fri Nov 25, 2005 9:09 pm

Post by csterg »

Hi Sergey,
OK, i extended the code to try and handle tables:

Code: Select all

function MemoryStreamFromString(const s: String): TMemoryStream;
begin
  result := TMemoryStream.Create;
  result.Write(s[1], Length(s));
  result.Position := 0;
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  RTF_TBL_2COLS = '{\trowd\cellx\cellx{{\intbl %s\cell}{\intbl %s\cell}}\intbl\row}';
var
  s, fonttbl: String;
  ItemNo, ParaNo: Integer;
  ms: TMemoryStream;
  i, c, Offs: Integer;
  trv: TCustomRVFormattedData;
  table: TRVTableItemInfo;
begin
  rv.Clear;
  rv.AddNL('0. ', 2, 0);
  rv.AddNL('1. ', 2, 0);
  rv.AddNL('a word here ', 3, -1);

  s := Format(RTF_TBL_2COLS, ['Column1', 'Column2']);
  s := Format('{\rtf1\ansi\ansicpg1252\deff0\deflang1033%s%s\uc0\fs20 %s}',
    [
      '{\fonttbl{\f0\fcharset0 Arial;}}', //fonttable
      '{\colortbl ;}', //colortable
      s //actual content
    ]);
  ms := MemoryStreamFromString(s); //this is obvious...
  ItemNo := rv.ItemCount;
  rv.LoadRTFFromStream(ms);
  rv.Format;


  //when loading RTF, a new paragraph is always added. Delete it...
  if rv.GetItemStyle(ItemNo)=rvsTable then begin
    //if this is a table, we will add all the previous paragraph to the beginning of this one
    i := ItemNo - 1;
    ParaNo := rv.getItemPara(i);
    table := TRVTableItemInfo(rv.GetItem(ItemNo));
    table.EditCell(0,0);
    c := 0;
    while (i>=0) do begin
      with rv.TopLevelEditor do
        SetSelectionBounds(0, GetOffsBeforeItem(0), 0, GetOffsBeforeItem(0));
      rv.TopLevelEditor.CurTextStyleNo := rv.GetItemStyle(i);
      rv.InsertText(rv.GetItemText(i));
      Inc(c);
      if rv.IsParaStart(i) then break;
      Dec(i);
    end;
    Inc(i);

    rv.SetSelectionBounds(ItemNo-c, rv.GetOffsBeforeItem(ItemNo-c), ItemNo-1,
      rv.GetOffsAfterItem(ItemNo-1));
    SendMessage(rv.Handle, WM_KEYDOWN, VK_DELETE,0);
    SendMessage(rv.Handle, WM_KEYDOWN, VK_DELETE,0);

    //rv.DeleteItems(ItemNo-c, c);
    //rv.ClearUndo;
    //rv.SetSelectionBounds(0,1,0,1);
    //rv.DeleteParas(ItemNo-1, ItemNo-1+c-1);
  end
  else begin
    if ItemNo < rv.ItemCount then rv.GetItem(ItemNo).SameAsPrev := True;
    for i := ItemNo to rv.ItemCount-1 do begin
      if not rv.GetItem(i).SameAsPrev then break;
      rv.GetItem(i).ParaNo := rv.GetItem(ItemNo-1).ParaNo;
    end;
  end;

  ms.Free;
  rv.Format;
  rv.SetFocus;
end;

Now, the above code has a single problem:
when i delete the paragraph i just copied (either with selecting and sending DEL key, or with DeleteParas or with DeleteItems) it seems that somehow an invalid document is produced because when i try to select the text in the cell, i get an AV.
If i select the content with the mouse and delete it, everything is OK. Can you spot the error?

Thanks,
Costas
Sergey Tkachenko
Site Admin
Posts: 17310
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

This code is overcomplicated.
The rules are simple: you cannot move to the previous paragraph:
- very first item,
- paragraph list marker,
- full line items (breaks, tables),
- item after full line item.

Code: Select all

uses RVItem;

ItemNo := rv.ItemCount; 
rv.LoadRTFFromStream(ms); 
if ItemNo = rv.ItemCount then 
  exit; 
if (ItemNo>0) and
   (rv.GetItemStyle(ItemNo)<>rvsListMarker) and
   not rv.GetItem(ItemNo).GetBoolValue(rvbpFullWidth) and
   not rv.GetItem(ItemNo-1).GetBoolValue(rvbpFullWidth) then
begin
  rv.GetItem(ItemNo).SameAsPrev := True; 
  if ItemNo = 0 then 
    exit; 
  for i := ItemNo to rv.ItemCount-1 do 
  begin 
    if not rv.GetItem(i).SameAsPrev then 
      break; 
    rv.GetItem(i).ParaNo := rv.GetItem(ItemNo-1).ParaNo; 
  end;
end;
Post Reply