[Unit] Adding a table of contents

Demos, code samples. Only questions related to the existing topics are allowed here.
Sergey Tkachenko
Site Admin
Posts: 13637
Joined: Sat Aug 27, 2005 10:28 am
Contact:

[Unit] Adding a table of contents

Post by Sergey Tkachenko » Thu Mar 18, 2010 5:54 pm

Unit RVTOC.pas: http://www.trichview.com/support/files/rvtoc.zip (update: 2010-Apr-25)
Required version of TRichView: 12.2.3 (because of the new RVPrint.GetPageNo method + fix)

Image

It has the functions:

Code: Select all

function AddTableOfContents(rv: TCustomRichView; RVPrint: TRVPrint;
  Depth: Integer; const TextStyleNos, ParaStyleNos: array of Integer;
  ItemNo: Integer; const Title: String; TitleStyleNo, TitleParaNo: Integer): Boolean; overload;
function AddTableOfContents(RVReportHelper: TRVReportHelper; Canvas: TCanvas;
  PageWidth, FirstPageHeight, PageHeight: Integer;
  Depth: Integer; const TextStyleNos, ParaStyleNos: array of Integer;
  ItemNo: Integer; const Title: String; TitleStyleNo, TitleParaNo: Integer): Boolean; overload;
These procedures add a table of contents (TOC) to RichView.

Version for TRVPrint

TOC is added in rv.

TOC is inserted in the position specified in ItemNo.
Possible values of ItemNo:
* 0 - inserting at the beginning of rv;
* rv.ItemCount - inserting at the end of rv
* any value from 1 to rv.ItemCount-1 - inserting before the item with this index; this item must have a page break (i.e. rv.PageBreaksBeforeItems[ItemNo] must be True), otherwise this function does nothing and returns False.

This function returns True on successful inserting. If the document does not have headings with levels in range 1..Depth, this function does nothing, but still returns True.

RVPrint must be formatted before the call of this function. After the call, it is not formatted (call RVPrint.FormatPages again before printing).

Rv is not formatted after the call of this function (call rv.Format before displaying it).

Title is added before the TOC, using TitleStyleNo and TitleParaNo styles. Depth is a maximal heading level for TOC.

TextStyleNos and ParaStyleNos are arrays containing styles for TOC. They must have Depth items.
TitleStyleNo[0] and TitleParaNo[0] are used for adding heading level 1 in TOC,
TitleStyleNo[1] and TitleParaNo[2] are used for adding heading level 2 in TOC,
and so on.

TOC is added not as an editing operation! Undo is not possible. If called for TRichViewEdit, call ClearUndo method.

// TitleStyleNo, TextStyleNos[] are indices in rv.Style.TextStyles collection.
// TitleParaNo, ParaStyleNos[] are indices in rv.Style.ParaStyles collection.

TOC is added as Unicode strings in Delphi 2009-2010, and as ANSI strings in older version of Delphi (so foreign characters may be lost on conversion)

Version for TRVReportHelper

In this version of the function, the document is contained in RVReportHelper.RichView.

Additional parameters:
Canvas - a canvas that was used for RVReportHelper.Init(), or another canvas with the same resolution;
PageWidth - width of pages (used in Init).
FirstPageHeight - height of the first page (used in FormatNextPage)
PageHeight - height of other pages (used in FormatNextPage)
These parameters are used only if ItemNo<RVReportHelper.RichView.ItemCount (i.e. if TOC is added not to the end). If the TOC is added to the end, you can pass any values to these parameters.

RVReportHelper must be formatted before the call of this function.
After the call, it is not formatted.

Additional procedure

Code: Select all

procedure GetStylesForOutlineLevel(rv: TCustomRichView; Level: Integer;
  var StyleNo, ParaNo: Integer);
This procedure searches for the first occurence of the paragraph with OutlineLevel=Level, and returns:
- the style of this paragraph (in ParaNo),
- the style of text in this paragraph (in StyleNo).
Paragraphs without text are ignored. If there is no such paragraph, this procedure returns 0, 0.
These values can be used as TitleStyleNo, TitleParaNo parameters for AddTableOfContents.
Last edited by Sergey Tkachenko on Sun Apr 25, 2010 7:57 pm, edited 8 times in total.

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

Post by Sergey Tkachenko » Thu Mar 18, 2010 6:03 pm

How to use

Example for TRVPrint

Code: Select all

// Returning Width and Height or printable area (inside margins) of RVPrint.
// The returned values are measured in screen pixels
procedure GetPageSize(RVPrint: TRVPrint; 
                            var Width, Height: Integer); 
var DC: HDC; 
    phoX, phoY, phW, phH, lpy, lpx, LM, TM, RM, BM: Integer; 
begin 
  DC := RV_GetPrinterDC; // from PtblRV unit 

  Width  := GetDeviceCaps(DC, HORZRES); 
  Height := GetDeviceCaps(DC, VERTRES); 

  lpy := GetDeviceCaps(DC, LOGPIXELSY); 
  lpx := GetDeviceCaps(DC, LOGPIXELSX); 

  phoX := GetDeviceCaps(DC, PHYSICALOFFSETX); 
  phoY := GetDeviceCaps(DC, PHYSICALOFFSETY); 
  phW  := GetDeviceCaps(DC, PHYSICALWIDTH); 
  phH  := GetDeviceCaps(DC, PHYSICALHEIGHT); 

  LM := MulDiv(RVPrint.LeftMarginMM,   5*lpx, 127)- phoX; 
  TM := MulDiv(RVPrint.TopMarginMM,    5*lpy, 127)- phoY; 
  RM := MulDiv(RVPrint.RightMarginMM,  5*lpx, 127)- (phW-(phoX+Width)); 
  BM := MulDiv(RVPrint.BottomMarginMM, 5*lpy, 127)- (phH-(phoY+Height)); 

  if LM<0 then LM := 0; 
  if TM<0 then TM := 0; 
  if RM<0 then RM := 0; 
  if BM<0 then BM := 0; 

  dec(Width, LM+RM); 
  dec(Height, TM+BM); 

  DeleteDC(DC); 

  DC := GetDC(0); 
  Width  := MulDiv(Width,  GetDeviceCaps(DC, LOGPIXELSX), lpx); 
  Height := MulDiv(Height, GetDeviceCaps(DC, LOGPIXELSY), lpy); 
  ReleaseDC(0, DC); 

end; 

// Adds Count paragraph styles in rv.Style (or reusing existing styles, if possible).
// Indices of these styles are returned in ParaStyleNos
// (this array must have at least Count items).
// All these paragraphs have one right-aligned tab stop at the position
// equal to the width of printable area in RVPrint.
// Each next paragraph is indented by IndentStep.
procedure GenerateTOCParagraphs(rv: TCustomRichView; RVPrint: TRVPrint;
  Count, IndentStep: Integer; var ParaStyleNos: array of Integer);
var ParaStyle: TParaInfo;
    Width, Height, i: Integer;
begin
  GetPageSize(RVPrint, Width, Height);
  dec(Width, rv.LeftMargin+rv.RightMargin);
  ParaStyle := TParaInfo.Create(nil);
  try
    with ParaStyle.Tabs.Add do begin
      Position := Width-1;
      Align := rvtaRight;
      Leader := '.';
    end;
    for i := 0 to Count-1 do begin
      ParaStyle.LeftIndent := IndentStep*i;
      ParaStyleNos[i] :=
        rv.Style.ParaStyles.FindSuchStyle(0, ParaStyle, RVAllParaInfoProperties);
      if ParaStyleNos[i]<0 then begin
        rv.Style.ParaStyles.Add;
        ParaStyleNos[i] := rv.Style.ParaStyles.Count-1;
        rv.Style.ParaStyles[ParaStyleNos[i]].Assign(ParaStyle);
        rv.Style.ParaStyles[ParaStyleNos[i]].Standard := False;
      end;
    end;
  finally
    ParaStyle.Free;
  end;
end;

// Adding 3-level TOC
var ParaStylesNo: array [0..2] of Integer;
    TitleStyleNo, TitleParaNo: Integer;
begin
  RVPrint1.AssignSource(RichViewEdit1);
  RVPrint1.FormatPages(rvdoAll);
  GetStylesForOutlineLevel(RichViewEdit1, 1, TitleStyleNo, TitleParaNo);
  GenerateTOCParagraphs(RichViewEdit1, RVPrint1, 3, 24, ParaStylesNo);
  AddTableOfContents(RichViewEdit1, RVPrint1, 3, [0,0,0], ParaStylesNo, RichViewEdit1.ItemCount,
    'Table of Contents', TitleStyleNo, TitleParaNo);
  RichViewEdit1.Format;
Example for TRVReportHelper

Code: Select all

// Adds Count paragraph styles in RVReportHelper.RichView.Style
// (or reuses existing styles, if possible).
// Indices of these styles are returned in ParaStyleNos
// (this array must have at least Count items).
// All these paragraphs have one right-aligned tab stop at the position
// equal to the Width.
// Each next paragraph is indented by IndentStep.
procedure GenerateTOCParagraphs(RVReportHelper: TRVReportHelper; Width: Integer;
  Count, IndentStep: Integer; var ParaStyleNos: array of Integer);
var ParaStyle: TParaInfo;
    i: Integer;
begin
  dec(Width, RVReportHelper.RichView.LeftMargin+RVReportHelper.RichView.RightMargin);
  ParaStyle := TParaInfo.Create(nil);
  try
    with ParaStyle.Tabs.Add do begin
      Position := Width-1;
      Align := rvtaRight;
      Leader := '.';
    end;
    for i := 0 to Count-1 do begin
      ParaStyle.LeftIndent := IndentStep*i;
      ParaStyleNos[i] :=
        RVReportHelper.RichView.Style.ParaStyles.FindSuchStyle(0, ParaStyle, RVAllParaInfoProperties);
      if ParaStyleNos[i]<0 then begin
        RVReportHelper.RichView.Style.ParaStyles.Add;
        ParaStyleNos[i] := RVReportHelper.RichView.Style.ParaStyles.Count-1;
        RVReportHelper.RichView.Style.ParaStyles[ParaStyleNos[i]].Assign(ParaStyle);
        RVReportHelper.RichView.Style.ParaStyles[ParaStyleNos[i]].Standard := False;
      end;
    end;
  finally
    ParaStyle.Free;
  end;
end; 

// Adding 3-level TOC
// Variables: 
// rvh: TRVReportHelper
// Canvas - canvas used for rvh.Init
// PageWidth, PageHeight - page size


var ParaStylesNo: array [0..2] of Integer;
    TitleStyleNo, TitleParaNo: Integer;
begin
    rvh.Init(Canvas, PageWidth);
    while rvh.FormatNextPage(PageHeight) do;
    GetStylesForOutlineLevel(rvh.RichView, 1, TitleStyleNo, TitleParaNo);
    GenerateTOCParagraphs(rvh, PageWidth, 3, 24, ParaStylesNo);
    AddTableOfContents(rvh, Canvas, PageWidth, PageHeight, PageHeight,
      3, [0,0,0], ParaStylesNo, rvh.RichView.ItemCount,
      'Table of Contents', TitleStyleNo, TitleParaNo);
    rvh.Init(Canvas, PageWidth);
    while rvh.FormatNextPage(PageHeight) do;
Last edited by Sergey Tkachenko on Sun Apr 25, 2010 3:06 pm, edited 3 times in total.

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

Post by Sergey Tkachenko » Thu Mar 18, 2010 6:06 pm

Possible ways to improve:
1) Inserting as an editing operation (that can be undone by the user).
2) The same function for RVReportHelper instead of RVPrint.
3) The same function for ScaleRichView.
4) Working via Unicode even for old versions of Delphi (4-2007)

On request.

jonjon
Posts: 279
Joined: Sat Aug 27, 2005 4:19 pm

Post by jonjon » Fri Mar 19, 2010 10:33 am

Great stuff Sergey.

A couple of questions though. Does it work if the table of content will not fit on one page ? Also, sometimes there are cover pages or titles pages before the table of content: how could it be inserted just after those pages ?

Finally, the RVReportHelper would be nice.

Best regards.

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

Post by Sergey Tkachenko » Fri Mar 19, 2010 3:41 pm

Yes, multipage TOC is handled correctly.
As for title pages... How do you think it will be convenient to define them? Specifying ItemNo where to insert TOC?
Or do you want to insert it in editor, in the caret position?

jonjon
Posts: 279
Joined: Sat Aug 27, 2005 4:19 pm

Post by jonjon » Fri Mar 19, 2010 4:00 pm

I think specifying ItemNo might be the best option to place it correctly.

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

Post by Sergey Tkachenko » Sun Mar 21, 2010 6:36 pm

Updated. The parameter ToBeginning is superseded by the parameter ItemNo. Limitation: when inserting to the middle, there must be a hard page break at this ItemNo.

As for TRVReportHelper, it appears to be more difficult. The function needs to know not only Width specified in Init, but also heights of all pages specified in FormatNextPage, and heights of new pages (since adding TOC increases the count of pages).

allanj42
Posts: 10
Joined: Mon Mar 02, 2009 11:56 pm
Location: Canada

Post by allanj42 » Wed Mar 24, 2010 5:44 pm

Thank you, Sergey, for the GetPageNo() method and for the example.
I seem to have it working in my app, but I would like to better understand GetPageNo(). Where can I get documentation for RV 12.2.3 and/or this method? In particular, why is the first parameter necessary when I have already assigned an RVData to the RVPrint?

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

Post by Sergey Tkachenko » Wed Mar 24, 2010 6:14 pm

In GetPageNo, RVData parameter may be:
- rv.RVData, where rv is a TRichView control assigned in RVPrint.AssignSource
- table cell.
You can see how GetPageNo used in RVTOC.pas, in function BuildTOCStructure.

jonjon
Posts: 279
Joined: Sat Aug 27, 2005 4:19 pm

Post by jonjon » Fri Apr 23, 2010 3:51 pm

Any news on the RVReportHelper version ? Or is it impossible ?

Regards,

John.

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

Post by Sergey Tkachenko » Sun Apr 25, 2010 3:11 pm

I have updated the unit, a function for TRVReportHelper is included.
The messages above are updated as well, with information and example for TRVReportHelper.

This version has a limitation - only two values of a page height are possible: one for the first page, one for other pages. I believe it covers 99.9% of cases, since different heights for all pages are rarely needed.
Last edited by Sergey Tkachenko on Tue Sep 21, 2010 12:31 pm, edited 1 time in total.

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

Post by Sergey Tkachenko » Fri May 14, 2010 8:15 pm

This feature is implemented for ScaleRichView, see DocViewer demo:
http://www.trichview.com/forums/viewtopic.php?t=3890

jonjon
Posts: 279
Joined: Sat Aug 27, 2005 4:19 pm

Post by jonjon » Thu May 26, 2011 2:23 pm

Sergey,

How hard would it be to update the code to also produce a table of contents for a standard TRichView(Edit) which, instead of adding fixed page numbers, would export it using the "PAGEREF" RTF code such as Microsoft Word ?

If you already have a demo on how to achieve that, it would be very useful.

Thanks in advance,

John.

jonjon
Posts: 279
Joined: Sat Aug 27, 2005 4:19 pm

Post by jonjon » Tue Jun 07, 2011 7:51 am

Sergey, any comment about my previous message ?

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

Post by Sergey Tkachenko » Tue Jun 07, 2011 6:06 pm

I need to study how MS Word creates TOC. I'll answer later in this week, sorry for delay.

Post Reply