PDF Export

TunnelCAD exports cross-sections and reports to PDF through a layout-driven engine. A layout is an ICAD Paper Space drawing — authored in ICAD itself — that carries tagged viewports, text, and rectangles. At plot time, TunnelCAD walks each tagged entity, invokes a matching data provider, and produces a PDF page through ICAD’s plot pipeline.

Note

Single-section + batch (multi-page) PDF export are both shipping. Multi-section sheet booklets aren’t part of the PDF engine — when you need a multi-section sheet layout, use the Section DXF Atlas export (right-click a tunnel → Export Sections to DXF… → atlas mode) instead. The analytics, template, and volume PDF exports are planned to migrate to the layout engine in a future release.

Quick start

  1. Open a project and select a section.

  2. Choose File → Export Section to PDF… or click the Export PDF button on the Sections toolbar.

  3. Pick a layout (every new project starts with a seeded section-A4 default).

  4. Pick an output filename.

  5. Click OK.

Architecture

Four components separate cleanly:

Component

Unit

Responsibility

Storage

Project/UTunnelProjectIO.pas, Print/UProjectLayouts.pas

Layouts + assets live as XML subtrees inside the .tun project file

Layout executor

Print/ULayoutExecutor.pas

Loads a layout, walks tagged entities, calls providers, plots via ICAD

Provider registry

Print/UProviderRegistry.pas

IProvider interface + global RegisterProvider / LookupProvider

ICAD plot bridge

cad/PrintEngine/UCADPlotAPI.pas

Thin RenderCanvasToPDF API that drives ICAD’s existing plotter

Layouts

A layout is a regular ICAD drawing authored in Paper Space. Entities can be tagged with two reserved attributes on any TDrawObject:

  • provider — the provider kind (e.g. SECTION_VIEW, TEXT, DR_TABLE)

  • providerArgs — key=val;key=val argument string parsed by TArgMap

Untagged entities pass through as static layout content (title block lines, borders, company text).

Layouts live inside the .tun project as <Layout>…</Layout> nodes under a <Layouts> subtree. Binary assets (logos, photos) live as base64-encoded <Asset> nodes under <Assets>. New projects are seeded with a default section-A4 layout on creation.

Built-in providers

Provider

Role

Target

Purpose

TEXT

paper

text entity

Substitute %FIELD% tokens (or replace entire text via field=X) from the plot context

SECTION_VIEW

model

viewport

Draw all section line data into model space on layers named LINE_<DEF> and frame the viewport

DR_TABLE

paper

rectangle

Draw DR min/max/avg/std table (placeholder until analytics integration in Plan 3)

POLAR_CHART

paper

rectangle

Concentric deviation polar grid + data polyline (placeholder as above)

IMAGE

paper

rectangle

Insert a project asset (PNG / JPEG / BMP) at the slot rectangle as an embedded TCADImage

STATUS_STRIP

paper

rectangle

Chainage bar with one grey tick per section in the alignment plus a red tick at the current section

Layout TEXT placeholder fields

Layout text entities tagged with the TEXT provider can substitute project values either inline as %FIELD% tokens or whole-text via the field= argument. All numeric fields default to the project’s unit settings (Tunnel Settings → Units); override decimals or format with fmt= on the slot’s args.

No-argument fields

Field

Source

Default format

PROJECT_NAME

Project name

string

TUNNEL

Active alignment name

string

STATION / CHAINAGE

Section station

0.000

SECTION_NAME

Section name

string

TEMPLATE

Section template name

string

PAGE_INDEX / PAGE_COUNT

Layout pagination

integer

EASTING / NORTHING

Alignment world coordinates at the station

project CoordinateDecimals

ELEVATION

Alignment elevation

project ElevationDecimals

BEARING

Alignment bearing at the station

project AngularUnit + AngleDecimals (e.g. 100.0000g)

GRADIENT

Vertical gradient at the station, percent

project OffsetDecimals, % suffix

RADIUS

Horizontal radius at the station

project CoordinateDecimals; inf on straight runs

START_CHAINAGE / END_CHAINAGE

Alignment chainage range

project StationDecimals

ALIGNMENT_LENGTH

End − start chainage

project CoordinateDecimals

SECTION_INDEX / SECTION_COUNT

1-based index in the alignment / total sections

integer

POINT_COUNT

Total measured points on the section (sum across all line definitions)

integer

DATE_NOW / TIME_NOW

Now, override format via fmt=

yyyy-mm-dd / hh:nn

PROJECT_FILE

Project file path; literal (unsaved) when the project has not been saved

string

Parameterised fields (field= mode only)

These cannot be used inline as %FIELD%; set them via field= with the companion arg.

Field

Required arg

Description

AREA

vol=<volume-definition-name>

Cross-section area (m²) for the named volume definition at the current section.

POINT_COUNT

line=<line-definition-name> (optional)

Without line=, sums all lines (same as inline %POINT_COUNT%). With line=, returns only that one line’s point count.

For cumulative or total volume across the whole alignment, use the Mass Table dialog (right-click a volume definition → Mass Table…). The dialog computes both values and exports them to CSV / XLSX / PDF.

Tagging a text entity — step by step

Two modes, picked per text entity:

Mode

When to use

Inline %FIELD% substitution (default)

The entity’s existing text body holds a sentence with one or more %TOKEN% placeholders. The provider replaces only the tokens; everything else stays.

Whole-text field= mode

The entity should be replaced entirely by one resolved value (with optional prefix= / suffix=). Required for parameterised tokens like AREA and POINT_COUNT line=.

1. Drop the text in the layout
  1. Open the layout in the layout editor (paper-space tab on the Sections workspace).

  2. Use the standard CAD Text tool to place a text entity where you want the label. Type the default body — this is what shows in the layout when no project is loaded, and (in inline mode) it’s also the template the provider rewrites.

2. Tag it as TEXT
  1. Select the text entity on the paper canvas.

  2. In the right-side Provider Palette panel, click TEXT. The palette’s args form below the provider list switches to the TEXT schema (six widgets: field, fmt, prefix, suffix, vol, line).

  3. Configure the args (next section).

  4. Click Tag Selected. The entity now carries Provider=TEXT and the serialized args string. The Outline panel shows it; the Property Inspector has both rows.

To re-edit a tag later, select the entity again — the palette auto-loads its current Provider + args. Change values, click Tag Selected to save.

3. Configure the args form

The args form widgets:

Widget

Type

Effect

field

dropdown of every supported field

Empty → inline %FIELD% substitution mode. Set to a name → whole-text replacement mode.

fmt

text box

Optional override for numeric formatting. Examples: 0.000, 0, 0.00 m. For DATE_NOW / TIME_NOW it’s any FPC FormatDateTime pattern (yyyy-mm-dd hh:nn). Leave empty to use the project’s unit defaults.

prefix

text box

Prepended to the resolved value. Only honoured in field= mode — ignored when field is empty.

suffix

text box

Appended to the resolved value. Only honoured in field= mode.

vol

text box

The volume-definition name. Required when field=AREA; ignored otherwise. Case-insensitive.

line

text box

The line-definition name. Optional when field=POINT_COUNT (without it, all lines are summed). Ignored when field is anything else.

4. Worked examples

These are the most common patterns. The “ProviderArgs” column shows the serialized form the editor stores under the hood (key=val;key=val); in normal use you fill in widgets, you don’t type the string.

Inline mode — leave field empty

Type the tokens directly into the entity’s text body. Tokens are uppercase and surrounded by %.

Entity text body

ProviderArgs

Resolved output (sample)

Sta %STATION%

(empty)

Sta 125.500

Sta %STATION%, %SECTION_NAME%

(empty)

Sta 125.500, S-15

Tunnel: %TUNNEL%, axis %EASTING%E %NORTHING%N

(empty)

Tunnel: Bypass, axis 472301.250E 4218905.732N

Bearing %BEARING%, gradient %GRADIENT%

(empty)

Bearing 195.4220g, gradient 1.500%

Section %SECTION_INDEX% of %SECTION_COUNT%

(empty)

Section 12 of 47

Plotted %DATE_NOW% by %PROJECT_NAME%

(empty)

Plotted 2027-05-07 by Bypass-East-Phase2

The entity’s body is the template. Any text outside %…% is preserved literally.

Whole-text mode — set field to a token name

The entity’s existing text body is discarded and replaced by prefix + resolved + suffix.

field

Other args

Resolved output

Use for

STATION

(none)

125.500

A station label that is just the station, no surrounding sentence.

STATION

prefix=Sta , suffix= m

Sta 125.500 m

Same, with caption + unit.

BEARING

(none)

100.0000g

Default project units (gradians here).

BEARING

fmt=0.00

100.00g

Override decimals; the angular unit and g suffix come from the project.

RADIUS

prefix=R = , suffix= m

R = 250.000 m or R = inf m on a straight run

Horizontal radius label.

DATE_NOW

fmt=dd/mm/yyyy

07/05/2027

Custom date format.

AREA

vol=Excavation

87.530

Per-section excavation area.

AREA

vol=Excavation, prefix=Area = , suffix= m²

Area = 87.530 m²

Same with full caption.

POINT_COUNT

(none)

42

Total points on the section.

POINT_COUNT

line=Crown

12

Just the Crown line’s points.

POINT_COUNT

line=Crown, prefix=Crown: , suffix= pts

Crown: 12 pts

Caption + unit.

When you need multiple parameterised values on one label

You can’t write %AREA[Excavation]% inline (parameterised tokens are field-mode only). To show, e.g., excavation and lining areas on the same drawing, drop two text entities side by side:

  • Entity A: field=AREA, vol=Excavation, prefix=Excavation: , suffix= m²

  • Entity B: field=AREA, vol=Lining, prefix=Lining: , suffix= m²

5. Troubleshooting

Symptom

Cause

Fix

The token shows up literally as %STATION% in the PDF

Inline mode but the token is misspelled, lower-case, or uses an unsupported name

Tokens are uppercase and must match the supported list. The PDF export emits a warning diagnostic listing the offending field.

Field-mode entity shows the original text instead of the resolved value

field= is empty (still in inline mode)

Set the field dropdown to the desired token.

%AREA% shows up literally

AREA is a parameterised token; inline %AREA% is intentionally not supported

Switch to field=AREA mode and fill vol= with the volume-definition name.

Empty text where you expected an area / radius / bearing

The alignment has no engine geometry yet, or vol= references a name that doesn’t exist

Import the alignment geometry; verify the volume-definition name in Tunnel Settings → Volumes.

fmt=0.000 ignored on BEARING

fmt= only overrides decimals; the angular unit stays from project units

Change Tunnel Settings → Units → Angular Unit if you want degrees / DMS / radians instead of gradians.

Tagging an IMAGE entity — step by step

The IMAGE provider drops a project-embedded raster (logo, photo, scan) into the slot rectangle.

1. Register the image as a project asset

Images live in the project’s Assets collection — named binary blobs serialized inside the .tun file, so the layout travels with the project.

  1. Open the asset manager from one of:

    • Ribbon File → Manage Project Assets…

    • Right-click the project root in the Project Tree → Manage Project Assets…

  2. Click Add… → pick a PNG / JPEG / BMP file.

    • The MIME type is auto-detected from the file extension.

    • The asset name defaults to the filename stem (e.g. company_logo.png → asset name company_logo).

    • On a name collision the dialog prompts Replace / Rename / Cancel.

  3. The list view shows every registered asset with name, MIME type, and size in KB. Rename and Remove operate on the selected row.

  4. Close the dialog. Save the project (the assets are embedded as base64 in the .tun).

2. Drop a placeholder rectangle in the layout

In the layout editor, draw a regular CAD rectangle where the image should appear. The IMAGE provider sizes the bitmap to the rectangle’s bounds (no scaling preserves aspect — make the rectangle the intended display size).

3. Tag the rectangle as IMAGE

  1. Select the rectangle.

  2. In the Provider Palette, click IMAGE. The args form has one widget: asset.

  3. Type the asset name you registered in step 1 (e.g. company_logo) — case-insensitive.

  4. Click Tag Selected.

The rectangle now resolves to your bitmap on the next plot. The bottom-left of the rectangle becomes the bottom-left of the image; the rectangle’s width and height become the image’s world width and height.

4. Worked examples

ProviderArgs

Effect

asset=company_logo

Inserts the asset named company_logo into the slot rectangle.

asset= (empty)

Warning — IMAGE requires asset=; nothing is drawn.

asset=missing (name not in the asset list)

Warning — missing asset: missing; the placeholder X-rectangle is drawn so you can still see the slot.

If the asset’s MIME isn’t one of image/png / image/jpeg / image/bmp, the provider draws the placeholder rectangle and emits a unsupported MIME type diagnostic.

Editing layouts on the Sections tab

Layouts are paper-space tabs on the existing Sections tab workspace. Model space holds the current section’s geometry (auto-updated as you navigate); paper tabs compose the PDF output.

  1. Open a project. The Sections tab shows the current section in model space. A Layouts side panel docks on the right.

  2. Layouts → New Layout… creates an empty paper tab; Layouts → Load Layout… loads a dormant layout already in the project.

  3. Flip to the paper tab on the workspace tab strip. The side panel’s palette + args form become active.

  4. Draw entities on the paper tab with standard CAD tools, or click Insert on the palette to drop a default-sized placeholder. Tag existing entities via Tag Selected.

  5. Fill in provider arguments in the schema-driven form. Insert and Tag Selected both read the current args.

  6. The side panel’s Outline lists every tagged entity. The loaded-layouts list lets you unload without going through the menu.

  7. Navigate to another section — tagged entities on every loaded paper tab re-resolve automatically (live WYSIWYG). The viewport auto-frames the new model geometry.

  8. Layouts → Export Section to PDF… (or TUNPDF) plots the active paper tab. Layouts → Batch Export PDF… (or TUNPDFBATCH) plots a station range as a single multi-page PDF.

  9. The Property Inspector shows Provider and ProviderArgs rows for any selected entity — edit tags there too.

  10. Save the project — loaded layouts capture their live state into their IQMLBlobs; dormant layouts pass through unchanged.

Known limitations

  • DR data (for DR_TABLE and POLAR_CHART) is not wired. Radial deviations come from the analytics engine; Plan 3 will integrate them. For now both providers emit a warning and draw a placeholder frame.

  • No ICAD command-registry integration yet. TUNPDF / TUNPDFBATCH / TUNLAYOUT* are plain procedures invoked from menu items; typing them at the command line isn’t wired until TunnelCAD adopts ICAD’s command registry.

  • Sheet sets / per-section booklet output are out of scope. Use the Section DXF Atlas export (right-click a tunnel → Export Sections to DXF… → atlas mode) when you need a multi-section sheet layout.

Verifying the editor (manual smoke test)

After building TunnelCAD locally:

  1. Open TunnelCAD, create a new project → Sections tab active, Layouts side panel visible, no layouts loaded.

  2. Layouts → New Layout… → type a name → empty paper tab appears on the workspace tab strip.

  3. Flip to the paper tab. Click DR_TABLE in the palette → click Insert → a rectangle appears tagged with DR_TABLE. The outline lists it. The inspector shows Provider=DR_TABLE.

  4. Navigate to a section that has data → the DR_TABLE rectangle refreshes (placeholder content until Plan 3 wires analytics).

  5. Layouts → Export Section to PDF… → pick an output path → a valid single-page PDF is produced.

  6. Layouts → Batch Export PDF… → pick the loaded layout + a station range → a multi-page PDF is produced.

  7. Save project, close, reopen → the layout is present in the loader picker (dormant). Layouts → Load Layout… restores it with the same content.

Commands

  • TUNPDF — plot the active paper tab on the Sections workspace to PDF. Wired as ExecuteTunPDFCommand(Owner, Project, Section, Session) in Print/UCmdTunPDF.pas.

  • TUNPDFBATCH — plot a station range as a single multi-page PDF. Wired as ExecuteTunPDFBatch(Owner, Project, Session, Navigate) in Print/UCmdLayouts.pas.

  • TUNLAYOUTLOAD / TUNLAYOUTUNLOAD / TUNLAYOUTNEW / TUNLAYOUTDELETE / TUNLAYOUTIMPORT / TUNLAYOUTEXPORT — the load/unload lifecycle.

Extending the engine

To add a new provider — for example, a custom chart type:

  1. Create a new Print/UProviderX.pas unit implementing IProvider (name, role, Resolve(Ctx, Slot, Canvas, Args, Result, LayoutName)).

  2. Call RegisterProvider(TProviderX.Create) — usually from a RegisterProviderX procedure invoked at app startup.

  3. Author a layout in ICAD with a tagged entity using your new provider name and args.

  4. That’s it — the engine picks it up automatically.

See Print/UProviderText.pas and Print/UProviderSectionView.pas for worked examples of paper-role and model-role providers respectively.

Design documents

  • Spec: docs/superpowers/specs/2026-04-18-tunnel-pdf-export-design.md

  • Plan 1: docs/superpowers/plans/2026-04-18-tunnel-pdf-export-plan-1-engine-skeleton.md

  • Layout Editor: docs/superpowers/plans/2026-04-18-tunnel-layout-editor-plan.md