Page 1 of 2

[Unit] Adding a table of contents

Posted: Thu Mar 18, 2010 5:54 pm
by Sergey Tkachenko
Overview of "table of context" solutions for TRichView and ScaleRichView:
http://www.trichview.com/forums/viewtop ... 702#p34702

Unit RVTOC.pas:
rvtoc.zip
(3.47 KiB) Downloaded 248 times
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: TRVUnicodeString; 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: TRVUnicodeString; 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.

[+] Updates
2018-Apr-18: for compatibility with TRichView 17.3; fixed the margin problem in the RVReportHelper version
[+] Old versions
http://www.trichview.com/support/files/rvtoc.zip - for TRichView: 12.2.3 - 17.2

Posted: Thu Mar 18, 2010 6:03 pm
by Sergey Tkachenko
How to use

Example for TRVPrint

Code: Select all

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);
  
  // RV_UnitsToPixels is defined in RVFuncs unit
  LM := RV_UnitsToPixels(RVPrint.Margins.Left,  RVPrint.Units,  lpx) - phoX;
  TM := RV_UnitsToPixels(RVPrint.Margins.Top,   RVPrint.Units,  lpy) - phoY;
  RM := RV_UnitsToPixels(RVPrint.Margins.Right, RVPrint.Units,  lpx) - (phW-(phoX+Width));
  BM := RV_UnitsToPixels(RVPrint.Margins.Bottom, RVPrint.Units, lpy)- (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;

// Returning Width and Height or printable area (inside margins) of RVPrint.
// The returned values are measured in screen pixels

// 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.FindParaStyle(ParaStyle);
    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;
 end;
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.FindParaStyle(ParaStyle);
    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;
Update:
2018-Apr-18:
the code samples use new features of TRichView 17; they were made simpler; they do not use obsolete properties any more

Posted: Thu Mar 18, 2010 6:06 pm
by Sergey Tkachenko
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.

Update:
2, 3, and 4 have been implemented

Posted: Fri Mar 19, 2010 10:33 am
by jonjon
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.

Posted: Fri Mar 19, 2010 3:41 pm
by Sergey Tkachenko
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?

Posted: Fri Mar 19, 2010 4:00 pm
by jonjon
I think specifying ItemNo might be the best option to place it correctly.

Posted: Sun Mar 21, 2010 6:36 pm
by Sergey Tkachenko
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).

Posted: Wed Mar 24, 2010 5:44 pm
by allanj42
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?

Posted: Wed Mar 24, 2010 6:14 pm
by Sergey Tkachenko
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.

Posted: Fri Apr 23, 2010 3:51 pm
by jonjon
Any news on the RVReportHelper version ? Or is it impossible ?

Regards,

John.

Posted: Sun Apr 25, 2010 3:11 pm
by Sergey Tkachenko
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.

Posted: Fri May 14, 2010 8:15 pm
by Sergey Tkachenko
This feature is implemented for ScaleRichView, see DocViewer demo:
http://www.trichview.com/forums/viewtopic.php?t=3890

Posted: Thu May 26, 2011 2:23 pm
by jonjon
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.

Posted: Tue Jun 07, 2011 7:51 am
by jonjon
Sergey, any comment about my previous message ?

Posted: Tue Jun 07, 2011 6:06 pm
by Sergey Tkachenko
I need to study how MS Word creates TOC. I'll answer later in this week, sorry for delay.