[Demo] Making PDF with table of contents using LLPDFLib

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

[Demo] Making PDF with table of contents using LLPDFLib

Post by Sergey Tkachenko »

LLPDFLib is a free opensource Delphi component, https://github.com/sybrexsys/llPDFLib

TRichView already has a demo project showing how to use TRichView+LLPDFLib to create a PDF from RTF or RVF file.

We created an advanced version of this demo that supports a table of contents (TOC) in PDF:
LLPDFWithTOC.zip
(95.69 KiB) Downloaded 2516 times
A TOC is built using headings in documents (RTF, RVF, and we also added DocX).

You can see a checkbox "Build a table of contents":

LLPDFLib-TOC.png
LLPDFLib-TOC.png (55.93 KiB) Viewed 42841 times

If this checkbox is checked, and a file has headings, they are used to build TOC for PDF:

PDF-TOC.png
PDF-TOC.png (116.18 KiB) Viewed 42841 times

Ideas for improvements (not implemented in this demo):
  • allowing to specify the maximum level of headings used to build TOC (this demo uses all 6 levels of headings)
  • adding an option to ignore "Heading 1" (because many files have only a single "Heading 1" entry at the beginning, so it is useless)
edwinyzh
Posts: 104
Joined: Sun Jun 05, 2022 2:22 pm

Re: [Demo] Making PDF with table of contents using LLPDFLib

Post by edwinyzh »

Thanks for the great example.

How to ensure all images (including the large ones) to fit the width of the pdf pages? In practice I found large images exceed the right edge of the generated pdf...

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

How to resize images to fit the page

Post by Sergey Tkachenko »

A solution that adjusts images in table cells would be too complicated, so the solution below is only for images inside the main document.

First, we need to calculate maximal possible image width.
This demo sets page size according to values specified in DocParameters property, so we can calculate this value in this way:

Code: Select all

function TfrmCustomMakePDF.GetMaxImageWidth: TRVStyleLength;
var
  rv: TCustomRichView;
begin
  rv := FDocument.MainDoc.RichView;
  // page width minus page margins
  Result := rv.Style.RVUnitsToUnits(
   rv.DocParameters.PageWidth
   - rv.DocParameters.LeftMargin - rv.DocParameters.RightMargin,
   rv.DocParameters.Units);
  // and minus internal margins
  dec(Result, rv.Style.StandardPixelsToUnits(rv.LeftMargin + rv.RightMargin));
end;
The returned value is measured in FDocument.MainDoc.RichView.Style.Units (like most sizes in TRichView documents)

Now, the code for adjusting images. It is large because it takes into account all possible values that affect maximal width (including border and spacing around images, indents of paragraphs and lists).
The code resizes large images proportionally.

Code: Select all

procedure TfrmCustomMakePDF.AdjustImages;
var
  rv: TCustomRichView;
  MaxWidth, ImgWidth, ImgHeight, ExtraWidth, BorderWidth, ImgPadding, ImgMargin: TRVStyleLength;
  i: Integer;
  ImgName: TRVUnicodeString;
  Gr: TGraphic;
  VAlign: TRVVAlign;
  ImgTag: TRVTag;
  ListItemNo, ListNo, ListLevel, ListStartFrom: Integer;
  ListReset: Boolean;
  ParaStyle: TParaInfo;
begin
  MaxWidth := GetMaxImageWidth;
  rv := FDocument.MainDoc.RichView;
  for i := 0 to rv.ItemCount - 1 do
    if rv.GetItem(i) is TRVGraphicItemInfo then
    begin
      rv.GetPictureInfo(i, ImgName, Gr, VAlign, ImgTag);
      // getting image width (resized or original)
      rv.GetItemExtraIntProperty(i, rvepImageWidth, Integer(ImgWidth));
      if ImgWidth <= 0 then
        ImgWidth := rv.Style.StandardPixelsToUnits(Gr.Width);
      // getting extra item width (border and spacing)
      rv.GetItemExtraIntProperty(i, rvepBorderWidth, Integer(BorderWidth));
      rv.GetItemExtraIntProperty(i, rvepSpacing, Integer(ImgPadding));
      rv.GetItemExtraIntProperty(i, rvepOuterHSpacing, Integer(ImgMargin));
      ExtraWidth := (BorderWidth + ImgPadding + ImgMargin) * 2;

      ParaStyle := rv.Style.ParaStyles[rv.GetItemPara(i)];
      ListItemNo := rv.GetListMarkerInfo(i, ListNo, ListLevel, ListStartFrom, ListReset);
      if (ListItemNo >= 0) and (ListNo >= 0) then
      begin
        // adding indents of list to extra width
        inc(ExtraWidth,
          rv.Style.ListStyles[ListNo].Levels[ListLevel].LeftIndent +
          Max(rv.Style.ListStyles[ListNo].Levels[ListLevel].LeftIndent, 0));
        if rv.RVData.GetParaBiDiMode(rv.GetItemPara(ListItemNo)) = rvbdRightToLeft then
          inc(ExtraWidth, ParaStyle.LeftIndent)
        else
          inc(ExtraWidth, ParaStyle.RightIndent);
      end
      else
      begin
        // adding indents of paragraph to extra width
        inc(ExtraWidth,
          ParaStyle.LeftIndent + ParaStyle.RightIndent + Max(ParaStyle.FirstIndent, 0));
      end;
      // resizing image, if necessary
      if ImgWidth + ExtraWidth > MaxWidth then
      begin
        rv.GetItemExtraIntProperty(i, rvepImageHeight, Integer(ImgHeight));
        if ImgHeight <= 0 then
          ImgHeight := rv.Style.StandardPixelsToUnits(Gr.Height);
        rv.SetItemExtraIntProperty(i, rvepImageWidth, MaxWidth - ExtraWidth);
        rv.SetItemExtraIntProperty(i, rvepImageHeight,
          MulDiv(ImgHeight, MaxWidth - ExtraWidth, ImgWidth));
      end;
    end;
end;
Call this code after opening the file and before formatting the document, i.e. just after the case that calls LoadRVF/LoadRTF/LoadDocX.
edwinyzh
Posts: 104
Joined: Sun Jun 05, 2022 2:22 pm

Re: [Demo] Making PDF with table of contents using LLPDFLib

Post by edwinyzh »

Hi Sergey,

2022-12-18 update: Tested and it works.

Great! Thank you! I'll try it soon
Post Reply