
{*******************************************************}
{                                                       }
{       ScaleRichView                                   }
{       Adding table of contents                        }
{                                                       }
{       Copyright (c) Sergey Tkachenko                  }
{       svt@trichview.com                               }
{       http://www.trichview.com                        }
{                                                       }
{*******************************************************}


unit SRVTOC;

interface

uses SysUtils, Classes, RichView, RVClasses, CRVData, SclRView, CRVFData,
     RVMarker, RVLabelItem, RVTable, RVStyle, RVScroll, RVReport;

{

  This procedure adds a table of contents (TOC).
  Document is in srvFrom, TOC is added in srvTo.
  If srvTo=nil, TOC is added in srvFrom.

  TOC is inserted in the position specified in ItemNo.
  Possible values of ItemNo:
  * 0 - inserting at the beginning of srvTo;
  * rv.ItemCount - inserting at the end of srvTo
  * any value from 1 to srvTo.ItemCount-1 - inserting before the item with this index

  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.

  srvFrom must be formatted before the call of this function.
  After the call, srvTo is not formatted (call srvTo.Format)

  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!

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

function AddTableOfContentsSRV(srvFrom, srvTo : TSRichViewEdit;
  Depth: Integer; const TextStyleNos, ParaStyleNos: array of Integer;
  ItemNo: Integer; const Title: String; TitleStyleNo, TitleParaNo: Integer): Boolean;

{
  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.
}

procedure GetStylesForOutlineLevel(rv: TCustomRichView; Level: Integer;
  var StyleNo, ParaNo: Integer);

implementation

type
  TTOCItem = class
    Text: String;
    Level, PageNo: Integer;
  end;

function GetParagraphText(RVData: TCustomRVData; ItemNo: Integer): String;
  {..........................................}
  function GetItemText: String;
  begin
    if RVData.GetItemStyle(ItemNo)>=0 then
      Result := RVData.GetItemText(ItemNo)
    else if RVData.GetItemStyle(ItemNo)=rvsListMarker then
      Result := TRVMarkerItemInfo(RVData.GetItem(ItemNo)).DisplayString+' '
    else if RVData.GetItemStyle(ItemNo) = rvsTab then
      Result := ' '
    else if RVData.GetItem(ItemNo) is TRVLabelItemInfo then
      Result := TRVLabelItemInfo(RVData.GetItem(ItemNo)).Text
    else
      Result := '';
  end;
  {..........................................}
begin
  Result := GetItemText;
  inc(ItemNo);
  while (ItemNo<RVData.ItemCount) and not RVData.IsParaStart(ItemNo) do begin
    Result := Result+GetItemText;
    inc(ItemNo);
  end;
end;
{------------------------------------------------------------------------------}
function BuildTOCStructure(srv: TSRichViewEdit; Depth: Integer): TRVList;
var
   rv: TCustomRichView;
  {.........................................................}
  procedure DoBuildTOC(RVData: TCustomRVData; TOCList: TRVList);
  var i, r, c, Level: Integer;
      table: TRVTableItemInfo;
      TOCItem: TTOCItem;
  begin
    for i := 0 to RVData.ItemCount-1 do begin
      if RVData.IsParaStart(i) then begin
        Level := rv.Style.ParaStyles[RVData.GetItemPara(i)].OutlineLevel;
        if (Level>0) and (Level<=Depth) then begin
          TOCItem := TTOCItem.Create;
          TOCItem.Text := GetParagraphText(RVData, i);
          TOCItem.Level := Level-1;
          TOCItem.PageNo := srv.GetPageNo(TCustomRVFormattedData(RVData), i, RVData.GetOffsBeforeItem(i));
          TOCList.Add(TOCItem);
        end;
      end;
      if RVData.GetItemStyle(i)=rvsTable then begin
        table := TRVTableItemInfo(RVData.GetItem(i));
        for r := 0 to table.RowCount-1 do
          for c := 0 to table.ColCount-1 do
            if table.Cells[r,c]<>nil then
              DoBuildTOC(table.Cells[r,c].GetRVData, TOCList);
      end;
    end;
  end;
  {.........................................................}
begin
  rv := srv.RichViewEdit;
  Result := TRVList.Create;
  DoBuildTOC(rv.RVData, Result);
end;
{------------------------------------------------------------------------------}
procedure AddToc(rv: TCustomRichView; TOCList: TRVList;
  const TextStyleNos, ParaStyleNos: array of Integer;
  FirstItemToIncrement, Increment: Integer);
var i, Plus: Integer;
    TOCItem: TTOCItem;
begin
  Plus := 0;
  for i := 0 to TOCList.Count-1 do begin
    if i=FirstItemToIncrement then
      Plus := Increment;
    TOCItem := TTOCItem(TOCList.Items[i]);
    rv.AddNL(TOCItem.Text, TextStyleNos[TOCItem.Level], ParaStyleNos[TOCItem.Level]);
    rv.AddTab(TextStyleNos[TOCItem.Level], -1);
    rv.AddNL(IntToStr(TOCItem.PageNo+Plus), TextStyleNos[TOCItem.Level], -1);
  end;
end;
{------------------------------------------------------------------------------}
function AddTableOfContentsSRV(srvFrom, srvTo : TSRichViewEdit;
  Depth: Integer; const TextStyleNos, ParaStyleNos: array of Integer;
  ItemNo: Integer; const Title: String;
  TitleStyleNo, TitleParaNo: Integer): Boolean;
var TOCList: TRVList;
    TOCPageCount: Integer;
    rvFrom, rvTo: TCustomRichView;
    rv2: TCustomRichView;
    srv2 : TSRichViewEdit;
    Stream: TRVMemoryStream;
    TOCPageNo, TOCItemIndexAfterTOC: Integer;

    {....................................................}
    // Returns index of the first TOC item after the TOC
    function AddTOCTitleToTOC: Integer;
    var Level, i: Integer;
        TOCItem: TTOCItem;
    begin
      Level := rvTo.Style.ParaStyles[TitleParaNo].OutlineLevel;
      Result := TOCList.Count;
      for i := 0 to TOCList.Count-1 do
        if TTOCItem(TOCList.Items[i]).PageNo>=TOCPageNo then begin
          Result := i;
          break;
        end;
      if (Level>0) and (Level<=Depth) then begin
        TOCItem := TTOCItem.Create;
        TOCItem.Text := Title;
        TOCItem.Level := Level-1;
        TOCItem.PageNo := TOCPageNo;
        TOCList.Insert(Result, TOCItem);
        inc(Result);
      end;
    end;
    {....................................................}
    procedure CopyPageLayout; // srvFrom -> srv2
    begin
      rv2.Style.TextStyles := rvFrom.Style.TextStyles;
      rv2.Style.ParaStyles := rvFrom.Style.ParaStyles;
      rv2.Style.ListStyles := rvFrom.Style.ListStyles;            
      srv2.UnitsProgram := srvFrom.UnitsProgram;
      srv2.PageProperty.PageFormat := srvFrom.PageProperty.PageFormat;
      if srvFrom.PageProperty.PageFormat=srvfmCustom then begin
        srv2.PageProperty.PageWidth := srvFrom.PageProperty.PageWidth;
        srv2.PageProperty.PageHeight := srvFrom.PageProperty.PageHeight;
      end;
      srv2.PageProperty.LeftMargin := srvFrom.PageProperty.LeftMargin;
      srv2.PageProperty.RightMargin := srvFrom.PageProperty.RightMargin;
      srv2.PageProperty.TopMargin := srvFrom.PageProperty.TopMargin;
      srv2.PageProperty.BottomMargin := srvFrom.PageProperty.BottomMargin;
      srv2.PageProperty.HeaderY := srvFrom.PageProperty.HeaderY;
      srv2.PageProperty.FooterY := srvFrom.PageProperty.FooterY;
      srv2.PageProperty.HeaderVisible := srvFrom.PageProperty.HeaderVisible;
      srv2.PageProperty.FooterVisible := srvFrom.PageProperty.FooterVisible;
      if srvFrom.PageProperty.HeaderVisible then begin
        Stream := TRVMemoryStream.Create;
        try
          srvFrom.RVHeader.SaveRVFToStream(Stream, False);
          Stream.Position := 0;
          srv2.RVHeader.LoadRVFFromStream(Stream);
        finally
          Stream.Free;
        end;
        srv2.RVHeader.Format;
      end;
      if srvFrom.PageProperty.FooterVisible then begin
        Stream := TRVMemoryStream.Create;
        try
          srvFrom.RVFooter.SaveRVFToStream(Stream, False);
          Stream.Position := 0;
          srv2.RVFooter.LoadRVFFromStream(Stream);
        finally
          Stream.Free;
        end;
        srv2.RVFooter.Format;        
      end;
    end;
    {....................................................}

begin
  rvFrom := srvFrom.RichViewEdit;
  if srvTo=nil then
    srvTo := srvFrom;
  rvTo := srvTo.RichViewedit;

  Result := True;
  if not Result then
    exit;

  if srvTo = srvFrom then
    if ItemNo=rvTo.ItemCount then
      TOCPageNo := srvFrom.CalculatePageCount
    else
      TOCPageNo := srvFrom.GetPageNo(rvFrom.RVData, ItemNo, rvFrom.GetOffsBeforeItem(ItemNo))
  else
    TOCPageNo := 0;
  TOCList := BuildTOCStructure(srvFrom, Depth);
  if TOCList.Count=0 then begin
    TOCList.Free;
    exit;
  end;
  if TOCPageNo>0 then
    TOCItemIndexAfterTOC := AddTOCTitleToTOC
  else
    TOCItemIndexAfterTOC := -1;

  if ItemNo<rvTo.ItemCount then begin
    srv2 := TSRichViewEdit.Create(nil);
    try
      rv2 := srv2.RichViewEdit;
      srv2.Visible := False;
      srv2.Parent := srvFrom.Parent;
      CopyPageLayout;
      // building TOC in rv2, calculating TOCPageCount
      rv2.AddNL(Title, TitleStyleNo, TitleParaNo);
      AddTOC(rv2, TOCList, TextStyleNos, ParaStyleNos, TOCItemIndexAfterTOC, 1);
      TOCPageCount := srv2.CalculatePageCount;
      if srvTo=srvFrom then begin
        // building TOC again, adding TOCPageCount+1 to page numbers
        rv2.Clear;
        rv2.AddNL(Title, TitleStyleNo, TitleParaNo);
        AddTOC(rv2, TOCList, TextStyleNos, ParaStyleNos, TOCItemIndexAfterTOC, TOCPageCount);
      end;
      // copying this TOC from srv2 to the srvTo, adding page break(s)
      Stream := TRVMemoryStream.Create;
      try
        if ItemNo>0 then
          rv2.PageBreaksBeforeItems[0] := True;
        rvTo.PageBreaksBeforeItems[ItemNo] := True;
        rv2.SaveRVFToStream(Stream, False);
        Stream.Position := 0;
        rvTo.InsertRVFFromStream(Stream, ItemNo);
      finally
        Stream.Free;
      end;
    finally
      srv2.Free;
    end;
    end
  else begin
    rvTo.AddNL(Title, TitleStyleNo, TitleParaNo);
    if rvTo.ItemCount>1 then
      rvTo.PageBreaksBeforeItems[rvTo.ItemCount-1] := True;
    AddTOC(rvTo, TOCList, TextStyleNos, ParaStyleNos, 0, 0);
  end;
  TOCList.Free;
end;
{------------------------------------------------------------------------------}
procedure GetStylesForOutlineLevel(rv: TCustomRichView; Level: Integer;
  var StyleNo, ParaNo: Integer);
  {.........................................................}
  function DoGetStyle(RVData: TCustomRVData): Boolean;
  var i, r, c: Integer;
      table: TRVTableItemInfo;
  begin
    Result := False;
    for i := 0 to RVData.ItemCount-1 do begin
      if (RVData.GetItemStyle(i)>=0) and
        (rv.Style.ParaStyles[RVData.GetItemPara(i)].OutlineLevel=Level) then begin
        Result := True;
        StyleNo := RVData.GetItemStyle(i);
        ParaNo := RVData.GetItemPara(i);
        exit;
      end;
      if RVData.GetItemStyle(i)=rvsTable then begin
        table := TRVTableItemInfo(RVData.GetItem(i));
        for r := 0 to table.RowCount-1 do
          for c := 0 to table.ColCount-1 do
            if table.Cells[r,c]<>nil then begin
              Result := DoGetStyle(table.Cells[r,c].GetRVData);
              if Result then
                exit;
            end;
      end;
    end;
  end;
  {.........................................................}
begin
  ParaNo := 0;
  StyleNo := 0;
  DoGetStyle(rv.RVData);
end;

end.
