No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

15KB

Envelope Template Format

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>
  <addressControl x="120" y="180" width="180" angle="15">
    <line font="Arial" size="12" column="Full Name" />
    <line font="Arial" size="12" column="Address 1" />
    <line font="Arial" size="12" column="Address 2" />
    <line font="Arial" size="12">
      <run column="City" />
      <run text=", " />
      <run column="State" />
      <run text=" " />
      <run column="ZIP" />
    </line>
  </addressControl>
</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> and <addressControl> 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.
  • Content is either the legacy single-run shape or the Sprint 5 multi-run shape — never both:
    • inline text content — static, printed as-is on every page, e.g. <text ...>Static label:</text>
    • column="<CSV header>" — dynamic, pulled from that column per record. Column matching is case-insensitive.
    • Sprint 5, “Mix static text and CSV fields within a single text element”: one or more <run> child elements, each either text="<literal>" (printed as-is) or column="<CSV header>" (resolved per record, same case-insensitive matching as the single-column form above), concatenated in document order at render time into the one string the page draws — e.g.:
      <text x="120" y="240" font="Arial" size="12">
        <run text="Attn: " />
        <run column="Full Name" />
        <run text=" " />
        <run column="Last Name" />
      </text>
      

      A <text> element with any <run> children must have no column attribute and no inline text of its own (mixing the two forms is a template error, same style as the legacy both-column-and-inline-text error). This is a genuinely new XML shape — it never appears in any template saved before Sprint 5 — so it cannot be confused with, and never changes the parsing of, the legacy single-run forms above; every pre-Sprint-5 template continues to parse and render byte-for-byte identically. Formatting/transforming a run's resolved value (date formatting, casing, truncation) remains out of scope — verbatim substitution only, matching the single-column form's existing behavior exactly.

A column (on either a <text> element or a <run> child) that isn't a real CSV header fails the whole run before any page is rendered, not partway through a large batch — evaluated per token when a <text> element has multiple runs, so one bad field reference fails the run even when it's mixed alongside other, valid runs on the same line.

Desktop designer editing convention (not part of the persisted format above): the Template Designer's properties panel lets an operator compose mixed content by typing plain text with {Column Name} bracket tokens (e.g. Attn: {Full Name}), parsed into the run sequence above when the field loses focus. This is a editing-time convention only — the persisted XML always uses the explicit <run> shape above, never inline {...} text. Known limitation: literal text that itself contains a matched {...} pair not intended as a field token (e.g. typing note: {see below} as literal text) is indistinguishable from a real field token by this convention and will be parsed as a field run bound to a column literally named “see below” — which then fails the per-token pre-flight check above if no such column exists. This is an accepted trade-off of the bracket convention (chosen as the faster-to-deliver option over a full protected-token-chip rich editor, per this story's sizing note) rather than a silently-swallowed edge case.

  • 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.

    • Distinguishing “intentionally blank” from “mapping error”: a 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 text 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.

    Static text rotates around the center of its own bounding box, preserving the original Sprint 4 behavior. 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.

    Dynamic and mixed-content text (any <text> with a column attribute or at least one field-run child) rotates around the fixed authored (x, y) anchor instead of a measured bounding-box center. This Sprint 6 rule prevents record-to-record drift: resolved text width can vary per CSV row, but the pivot cannot. The desktop canvas uses the same rule so the preview and the rendered PDF agree on placement for variable-width rotated content. 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.

  • width, height — optional, positive, PDF points, default absent (no box). Sprint 9, “Add an adjustable width and height with text wrapping...": when both are present, the element's content wraps within width and any wrapped content exceeding height is clipped (cut off), rather than growing the box or shrinking the text. A one-sided value (only width or only height) is accepted but has no effect — wrap-without-a-clip-boundary and clip-without-a-wrap- width both have no well-defined meaning, so it's treated the same as neither being present. Internally this calls Debenu's native DrawTextBox(x, top, width, height, text, options) (or DrawRotatedTextBox when angle is also non-zero — confirmed empirically to handle rotation and wrapping together in one native call, not something this renderer has to compose from separate steps) rather than a hand-rolled line-breaking algorithm; top is y + height, since (x, y) is the box's bottom-left corner for a boxed element (unlike the plain baseline-y meaning y has for an unboxed element). An element with no width/height (every template written before this story) takes the exact same plain DrawText/DrawRotatedText code path this renderer always used — byte-for-byte unchanged output. The desktop designer's canvas and preview panel approximate the same wrap/clip visually using GDI+'s own native rectangle-bounded text drawing — a documented design-time approximation, not a pixel-for-point guarantee, since GDI+ and Debenu are two independent text-layout engines with no shared line-breaking code path.

<addressControl>

Sprint 6 adds a grouped Address Control: a movable address block with one anchor point and an ordered list of child lines. The desktop designer saves it as an <addressControl> container, and the CLI renders it directly; the desktop app is not required at render time.

  • x, y — required, PDF points. This is the control's single anchor: the baseline position of the first line.
  • width — required, positive, PDF points. The designer uses this as the group's resize box. Current render output does not wrap or clip text to this width; each line still draws as one resolved text string.
  • lineSpacing — optional, positive multiplier, default 1.25. Line N+1's baseline is computed from the previous line's font size: previousY - previousSize * lineSpacing.
  • Child <line> elements are required; an address control must contain at least one line.
  • Each <line> has required font and positive size attributes, and supports the same content forms as <text>: inline literal text, column="CSV header", or explicit <run> children for mixed static/field content.
  • collapsible on a <line> is optional and defaults to true, unlike standalone <text>, whose default remains false. Set collapsible="false" on a line that must keep its row even when it resolves blank.
  • angle — optional, degrees, positive or negative, default 0. Sprint 8, “Rotate the whole Address Control as a single unit”: rotates the entire control — its box, every line's text, and any per-line highlight/selection overlay in the desktop designer — together as one rigid unit. Same positive is counterclockwise convention as standalone <text>'s own angle. Unlike a standalone element, an Address Control's box geometry (width, and height derived purely from each line's authored size/the control's lineSpacing) never depends on any record's resolved text, so the whole control always rotates around its own fixed, authored box center — no per-record pivot drift is possible here, and no fixed-anchor/bounding-box-center branch choice like standalone text's is needed. Internally, each visible line's own (collapse-adjusted) anchor is rotated as a rigid group around that fixed box center before being handed to the same DrawRotatedText path standalone rotated text already uses — angle="0" (including when the attribute is absent) takes the exact same unrotated code path as before this story, so every template written before Sprint 8 renders byte-for-byte unchanged.

At render time, the control expands into one draw per visible line, in child-line order. Blank collapsible lines are omitted and following lines in the same control shift upward using the same AddressLineCollapser rule described for standalone <text> elements; because every child line shares the control's single x, the existing stack rule and the control boundary agree. Static, field, and mixed-content lines all resolve per CSV record exactly like standalone text elements. The rotation pivot is always computed from the control's authored, unrotated x/y/width/ height, never from any record's collapse-shifted line positions, so two records differing only in whether a blank optional line collapses still rotate around the identical pivot.

Known gaps

  • Font resolution failure is blocking, not a warning — matches the Definition of Done's “missing-font behavior remains blocking” rule. A page is never silently rendered with a substitute font.
  • Rendering requires a Debenu Quick PDF Library license key at runtime — see the “Debenu license key” note in 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.
  • width/height (Sprint 9 wrap/clip) is standalone <text> only for now<addressControl> has no equivalent yet (its own width remains a resize-box value only, and it has no height attribute at all). A dependent follow-on story extends the same wrap/clip engine to Address Control lines; until then, a long Address Control line still runs unbounded exactly as before this story.

Powered by TurnKey Linux.