TPDFlibDynamicComponentSession

Page layout, pagination, data types

Description

Composes a stateful component one destination page at a time without constructing content for later pages in advance

Each attempt runs on a clone inside an isolated Form XObject and commits the clone only after validated content is placed successfully

Extended placement can reject overflow, clip natural-size content or scale it down proportionally while aligning it within a destination slot

Pagination policy can allow normal splitting, defer the first partial fragment to a fresh region or require the whole component to remain visible before committing anything

Flow-space constraints can reject a short region before invoking the component or reserve enough height after its final fragment for a following component

A rejected result, failed drawing call or callback exception removes the temporary page, restores the caller-selected page and leaves both destination content and current component state unchanged

The attached TPDFlib instance remains caller-owned and has to outlive the session

Declaration

const
  PDF_DYNAMIC_COMPONENT_ERROR = 0;
  PDF_DYNAMIC_COMPONENT_MORE = 1;
  PDF_DYNAMIC_COMPONENT_COMPLETE = 2;
  PDF_DYNAMIC_COMPONENT_RETRY = 3;

type
  TPDFlibDynamicPageContext = record
    LibraryInstance: TPDFlib;
    PageNumber: Integer;
    CompositionIndex: Integer;
    DestinationLeft: Double;
    DestinationTop: Double;
    AvailableWidth: Double;
    AvailableHeight: Double;
  end;

  TPDFlibDynamicPageResult = record
    HasContent: Boolean;
    HasMoreContent: Boolean;
    UsedWidth: Double;
    UsedHeight: Double;
  end;

  TPDFlibDynamicOverflowPolicy = (dopReject, dopClip, dopScaleDown);
  TPDFlibDynamicHorizontalAlignment = (dhaLeft, dhaCenter, dhaRight);
  TPDFlibDynamicVerticalAlignment = (dvaTop, dvaCenter, dvaBottom);
  TPDFlibDynamicPaginationPolicy = (dppAllowSplit, dppKeepTogether,
    dppRequireComplete);
  TPDFlibDynamicRetryReason = (drrNone, drrKeepTogether,
    drrRequireComplete, drrMinimumSlotHeight, drrMinimumFollowingHeight);

  TPDFlibDynamicLayoutOptions = record
    ContentWidth: Double;
    ContentHeight: Double;
    OverflowPolicy: TPDFlibDynamicOverflowPolicy;
    HorizontalAlignment: TPDFlibDynamicHorizontalAlignment;
    VerticalAlignment: TPDFlibDynamicVerticalAlignment;
    PaginationPolicy: TPDFlibDynamicPaginationPolicy;
    MinimumSlotHeight: Double;
    MinimumFollowingHeight: Double;
  end;

  TPDFlibDynamicPlacementTransform = record
    M11, M12, M21, M22, MDX, MDY: Double;
  end;

  TPDFlibDynamicPagePlacement = record
    PageNumber: Integer;
    CompositionIndex: Integer;
    Left: Double;
    Top: Double;
    Width: Double;
    Height: Double;
    HasContent: Boolean;
    HasMoreContent: Boolean;
    SourceWidth: Double;
    SourceHeight: Double;
    SlotLeft: Double;
    SlotTop: Double;
    SlotWidth: Double;
    SlotHeight: Double;
    Transform: TPDFlibDynamicPlacementTransform;
    WasClipped: Boolean;
    WasScaled: Boolean;
  end;

  TPDFlibDynamicComponent = class
  public
    function CloneComponent: TPDFlibDynamicComponent; virtual; abstract;
    function Compose(const Context: TPDFlibDynamicPageContext;
      out ComposeResult: TPDFlibDynamicPageResult): Integer; virtual; abstract;
  end;

  TPDFlibDynamicComponentSession = class
  private
    FLibrary: TPDFlib;
    FCompositionCount: Integer;
    FCompleted: Boolean;
    FKeepTogetherRetryPending: Boolean;
    FLastRetryReason: TPDFlibDynamicRetryReason;
    FLastRequiredSlotHeight: Double;
  public
    constructor Create(ALibrary: TPDFlib;
      AComponent: TPDFlibDynamicComponent);
    function AttachComponent(AComponent: TPDFlibDynamicComponent): Integer;
    function Reset: Integer;
    function CloneCurrentComponent: TPDFlibDynamicComponent;
    class function CreateLayoutOptions(ContentWidth, ContentHeight: Double;
      OverflowPolicy: TPDFlibDynamicOverflowPolicy = dopReject;
      HorizontalAlignment: TPDFlibDynamicHorizontalAlignment = dhaLeft;
      VerticalAlignment: TPDFlibDynamicVerticalAlignment = dvaTop;
      PaginationPolicy: TPDFlibDynamicPaginationPolicy = dppAllowSplit;
      MinimumSlotHeight: Double = 0;
      MinimumFollowingHeight: Double = 0):
      TPDFlibDynamicLayoutOptions; static;
    function ComposeNext(Page: Integer; Left, Top, Width, Height: Double;
      out Placement: TPDFlibDynamicPagePlacement): Integer;
    function ComposeNextLayout(Page: Integer; SlotLeft, SlotTop, SlotWidth,
      SlotHeight: Double; const Options: TPDFlibDynamicLayoutOptions;
      out Placement: TPDFlibDynamicPagePlacement): Integer;
    property LibraryInstance: TPDFlib read FLibrary;
    property CompositionCount: Integer read FCompositionCount;
    property Completed: Boolean read FCompleted;
    property KeepTogetherRetryPending: Boolean
      read FKeepTogetherRetryPending;
    property LastRetryReason: TPDFlibDynamicRetryReason read FLastRetryReason;
    property LastRequiredSlotHeight: Double read FLastRequiredSlotHeight;
  end;

Component contract

Methods

CreateAttaches a caller-owned library and clones the supplied component into independent initial and current snapshots
AttachComponentAtomically replaces both snapshots with clones of a caller-owned component and clears completion state
ResetRestores a new current clone from the initial snapshot and resets the successful composition count
CloneCurrentComponentReturns a caller-owned clone for inspecting the current committed component state
CreateLayoutOptionsBuilds typed natural-canvas, overflow, alignment, pagination and flow-space settings for extended placement
ComposeNextComposes one page slot, validates and places its Form XObject, releases the capture handle and commits the working state
ComposeNextLayoutComposes on the configured natural canvas, applies overflow and alignment policy and returns source, slot and final transform data

Overflow and placement

dopRejectRejects content wider or taller than the slot without changing destination content or committed component state
dopClipKeeps scale 1 and intersects the page clipping path with the destination slot before invoking the Form XObject
dopScaleDownUses the smaller width or height ratio only when needed and never enlarges content

Alignment is resolved from the transformed used rectangle, so centred or trailing clipped content selects the corresponding visible portion

Transform reports the logical layout matrix [M11 M12 M21 M22 MDX MDY] from component-local coordinates to the final top-down document coordinates

SlotLeft, SlotTop, SlotWidth and SlotHeight preserve the caller-supplied region even when the final content origin lies outside it under clipping

Pagination policies

dppAllowSplitPreserves the default behaviour and commits any valid fragment whose callback reports more content
dppKeepTogetherReturns PDF_DYNAMIC_COMPONENT_RETRY for the first partial or non-visible trial, then permits splitting or the selected clipping fallback on the next valid attempt for the same uncommitted fragment
dppRequireCompleteReturns PDF_DYNAMIC_COMPONENT_RETRY until the callback reports no remaining content and the full used rectangle either fits the slot or remains visible through proportional scale-down

A retry discards the Form XObject trial, preserves the destination page, component snapshot, completion flag and composition count, and returns an empty Placement record

KeepTogetherRetryPending identifies the one-shot fallback state and is cleared after a successful commit, Reset or AttachComponent

Flow-space constraints

MinimumSlotHeightReturns PDF_DYNAMIC_COMPONENT_RETRY before cloning or invoking the component when the destination slot is shorter than the configured height
MinimumFollowingHeightApplies only to a final content fragment and requires its resolved height plus the reserved following height to fit the slot

A following-height retry discards the isolated trial while preserving earlier committed fragments, allowing a heading, label or short lead-in to move with the first usable region of the next component

LastRetryReason distinguishes whole-component, strict-completion, minimum-slot and following-space retries, while LastRequiredSlotHeight reports the known required height for flow-space constraints

The minimum-slot check runs before component cloning and Form capture, so repeatedly rejected short regions incur no component callback or temporary page cost

Return values

PDF_DYNAMIC_COMPONENT_ERRORThe request, component callback, result geometry or Form placement failed and no state was committed
PDF_DYNAMIC_COMPONENT_MOREContent was committed and the component requested another page slot
PDF_DYNAMIC_COMPONENT_COMPLETEThe component completed, or the session was already complete and no callback was made
PDF_DYNAMIC_COMPONENT_RETRYThe selected pagination or flow-space policy deferred placement without committing content or state

Example

type
  TNumberedPagesComponent = class(TPDFlibDynamicComponent)
  private
    FNextPage: Integer;
    FPageCount: Integer;
  public
    constructor Create(ANextPage, APageCount: Integer);
    function CloneComponent: TPDFlibDynamicComponent; override;
    function Compose(const Context: TPDFlibDynamicPageContext;
      out ComposeResult: TPDFlibDynamicPageResult): Integer; override;
  end;

constructor TNumberedPagesComponent.Create(ANextPage, APageCount: Integer);
begin
  inherited Create;
  FNextPage := ANextPage;
  FPageCount := APageCount;
end;

function TNumberedPagesComponent.CloneComponent: TPDFlibDynamicComponent;
begin
  Result := TNumberedPagesComponent.Create(FNextPage, FPageCount);
end;

function TNumberedPagesComponent.Compose(
  const Context: TPDFlibDynamicPageContext;
  out ComposeResult: TPDFlibDynamicPageResult): Integer;
begin
  Context.LibraryInstance.DrawText(0, 0, IntToStr(FNextPage));
  Inc(FNextPage);
  ComposeResult.HasContent := True;
  ComposeResult.HasMoreContent := FNextPage <= FPageCount;
  ComposeResult.UsedWidth := Context.AvailableWidth;
  ComposeResult.UsedHeight := 24;
  Result := 1;
end;

Template := TNumberedPagesComponent.Create(1, 10);
try
  Session := TPDFlibDynamicComponentSession.Create(PDF, Template);
  try
    repeat
      Status := Session.ComposeNext(PageNumber, 36, 36, 540, 720,
        Placement);
      if Status = PDF_DYNAMIC_COMPONENT_MORE then
        PageNumber := PDF.NewPage;
    until Status <> PDF_DYNAMIC_COMPONENT_MORE;
  finally
    Session.Free;
  end;
finally
  Template.Free;
end;

See also

BeginFormXObject, ReleaseCapturedPage, ApplyRepeatedPageFragment