Page 1 of 1

[Demo] Making PDF with table of contents using LLPDFLib

Posted: Mon Jun 13, 2022 2:31 pm
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 2741 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 45988 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 45988 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)

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

Posted: Tue Jul 05, 2022 5:53 pm
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.

How to resize images to fit the page

Posted: Wed Jul 27, 2022 10:40 am
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.

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

Posted: Fri Jul 29, 2022 7:55 am
by edwinyzh
Hi Sergey,

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

Great! Thank you! I'll try it soon