Implementation: src/EnvelopeRenderer.Cli/Render. Sample:
sample-data/sample-envelope-template.xml.
<envelopeTemplate pageWidth="297" pageHeight="684">
<text x="120" y="240" font="Arial" size="12">Static label:</text>
<text x="120" y="225" font="Arial [Bold]" size="12" column="Full Name" />
<text x="120" y="210" font="Arial" size="12" column="Address 2" collapsible="true" />
<text x="200" y="600" font="Arial" size="20" angle="45">Rotated label</text>
</envelopeTemplate>
<envelopeTemplate>pageWidth, pageHeight — required, PDF points. Not a named paper size: Debenu Quick PDF
10.13's only named-size function (SetPageSize) covers ISO/ANSI paper sizes and DL, but no US
envelope sizes, so this template format goes straight to SetPageDimensions(width, height)
instead and lets the operator supply exact points for any envelope. A #10 envelope
(4.125” x 9.5”) is pageWidth="297" pageHeight="684" (1” = 72pt).<text>One page is rendered per CSV record, in the order the CSV rows appear, each carrying every
<text> element in the template.
x, y — required, PDF points, measured from the page's bottom-left corner (Debenu's
default coordinate origin — this template format doesn't call SetOrigin, so that default
holds).font — required. A TrueType font name as it appears in the Windows Fonts folder (e.g.
"Arial"), optionally with a style suffix: "Arial [Bold]", "Arial [Italic]",
"Arial [BoldItalic]". The font must be installed on the machine running the CLI; it is
always embedded in the output PDF so the reader doesn't need it installed too.size — required, positive, points.<text ...>Static label:</text>column="<CSV header>" — dynamic, pulled from that column per record. Column matching is
case-insensitive. Interpolating a column into a literal string (e.g. "Dear {FirstName},")
is out of scope for this template format — see the deferred story “Create a dynamic text
token from a CSV column.”A column that isn't a real CSV header fails the whole run before any page is rendered, not
partway through a large batch.
collapsible — optional, true or false (default false). Sprint 4, “Collapse blank
optional address lines consistently”: when true and this element's resolved text is blank
for a given CSV record, the element is skipped entirely for that record's page, and every
other <text> element that shares its x position (rounded to 2 decimal places) — a
“vertical stack,” the closest thing this format has to an address block — shifts upward to
close the gap that line would have left. A non-collapsible blank field still renders nothing
visible at its fixed position, exactly as before this story (no change for templates that don't
opt in). See src/EnvelopeRenderer.Cli/Render/AddressLineCollapser.cs
for the exact shift math (row height = the gap between a line and the one below it, using
original, not already-shifted, positions) and its design-decision rationale (X-position grouping
in lieu of an explicit block concept). The desktop designer's canvas previews the identical
math (EnvelopeRenderer.Desktop.Core.Design.AddressLineCollapser) against a loaded CSV's first
sample row, so what an operator sees while designing matches what the CLI renders.
column that doesn't exist
in the CSV's header row is always a hard failure for the whole run (see above) — it never
silently collapses, regardless of collapsible. Only a column that does exist, but whose
value is blank for a specific record, is eligible to collapse. The desktop designer surfaces
this distinction visually: a dynamic field bound to a column that isn't among the currently
loaded CSV's headers gets a dashed orange/red highlight (a real mapping problem), while a
collapsible field bound to a real column that's simply blank in the loaded sample data gets
no warning at all — it just doesn't render, exactly as intended.angle — optional, degrees, positive or negative, default 0. Sprint 4, “Set a rotation angle
for text and dynamic field elements”: rotates the element around the center of
its own bounding box (not around x/y) by this many degrees. Positive is
counterclockwise, confirmed empirically against the real Debenu Quick PDF Library 10.13 DLL
(not assumed) — see src/EnvelopeRenderer.Cli/Render/RotatedTextAnchorCalculator.cs‘s
class remarks for the probe methodology and result. Internally this calls Debenu's
DrawRotatedText(x, y, angle, text), which rotates around the given (x, y) anchor, not a
center — RotatedTextAnchorCalculator solves for the different anchor point that keeps the
unrotated bounding-box center fixed, using GetTextWidth/GetTextAscent/GetTextDescent to
measure that box. One more empirically-confirmed vendor quirk: DrawRotatedText rejects a
negative Angle outright (despite rotation being mathematically periodic) — the renderer
normalizes to Debenu's expected [0, 360) range before that specific call; a negative angle
in a template is fully supported and unaffected by this internal normalization. An angle of
0 (including when the attribute is absent) takes the exact same DrawText code path this
renderer used before this story — byte-for-byte unchanged output for every pre-existing
template.CLI_CONTRACT.md. Without one, every render fails at the save
step (SaveToFile/SaveToString both return error code 999) even though every earlier step
(page creation, font embedding, text drawing) succeeds — verified directly against the vendor
DLL, not assumed.Powered by TurnKey Linux.