# TechTitans POS — Invoice PDF Layout & Spacing Refinement

**Date:** 2026-11-12 · **Branch:** `techtitans/integration` · Local commit only (no push/deploy).
**Visual layout refinement only** — the invoice generator, numbering, status semantics, tax/discount/total
calculations and the metadata snapshot are untouched. NH POS / legacy portal untouched; no legacy PHP executed;
no real payments; **no invoice or payment amounts changed** (verified by the frozen-total assertions in the test suite).

Refines the existing branded invoice PDF (`App\Invoices\InvoiceService` → DomPDF →
`resources/views/invoices/pdf.blade.php`) into a spacious, professionally aligned document with consistent
margins on **every side of every page**. The previous redesign (commit `a913a9e`) introduced the brand and
6-column table but rendered content crowded against the paper edges and had an unreliable page counter; this
pass fixes the margin mechanism, the per-page footer, and interior spacing.

## 1. Root cause — why content touched the paper edges

The prior template reset `html, body { margin: 0 }` and put the page inset only on `@page { margin: 26px 30px 70px 30px }`.
Rasterizing the actual output revealed DomPDF **ignored the `@page` margin for the body**: the body frame bled to
within ~0.5 mm of the paper edge, while the separately-positioned fixed footer honoured a different offset — so the
two didn't agree, and on multi-page invoices the footer overlapped the table. The CSS `counter(pages)` also resolved
to `0` ("Page 1 of 0").

**Mechanism (proven empirically):** DomPDF derives each page's content area — including the top/bottom inset on
continuation pages — from the **root `<html>` frame's computed margin**, which it seeds from the `@page` rule. The
`html { margin: 0 }` reset collapsed that inset to zero. The fix is therefore to carry the page margin on **both**
`@page` **and** `html`, and zero **only** `body`:

```css
@page { size: A4 portrait; margin: 20mm 20mm 22mm 20mm; }
html  { margin: 20mm 20mm 22mm 20mm; }   /* the operative lever — do NOT reset to 0 */
*     { box-sizing: border-box; }
body  { margin: 0; padding: 0; }         /* no double inset */
```

No arbitrary per-element pixel offsets are used to simulate margins — the single page margin governs all sides.

## 2. Margins — measured on the rendered PDF

Verified by rasterizing the output (via `pdf-to-img`, prebuilt canvas, no build tools) and measuring the painted
content box against the A4 sheet. Target was a consistent ~20 mm printable margin on all sides, bottom slightly
deeper to host the footer band.

| Page | Top | Left | Right | Bottom (inset) | Footer overlap |
|---|---|---|---|---|---|
| Page 1 | 20.8 mm | 19.9 mm | 19.8 mm | 22 mm | none |
| Middle page (p2 of a 5-page invoice) | 19.9 mm | 19.9 mm | 19.8 mm | 22 mm | none (gap 27.7 mm) |

No full-width element touches the paper edge; the header band, teal divider, Bill To / Fulfillment, items table and
totals all align to the same 20 mm content area on every page.

## 3. Footer & page numbering (DomPDF-correct)

The footer is **no longer an HTML element**. It is painted directly into the reserved 22 mm bottom **margin band** on
every page by a trusted, Blade-authored `<script type="text/php">` block placed last in the body, using DomPDF
`page_line()` / `page_text()`:

- A hairline rule, then the business contact line (clamped to the content width so it never wraps or overruns),
  a teal **"Thank you for your business."**, and **"Page X of Y"**.
- Correct totals via `page_text`'s `{PAGE_COUNT}` placeholder — the only DomPDF mechanism that yields a real page
  total (CSS `counter(pages)` resolves to 0 under single-pass reflow).
- Because the footer lives in the margin band and body content stops at the bottom margin, it **physically cannot
  overlap** the table or totals on any page.

This required `InvoiceService::renderPdf()` to set `->setOption('isPhpEnabled', true)`. **Safe:** the script is our own
markup and every dynamic value is Blade-escaped (`{{ }}` / `json_encode`, no `{!! !!}` on user data).

## 4. Interior spacing refinements (CSS only)

Within the new content area, breathing room was added without changing structure or data:

- **Header:** logo `max-height:70px` aspect-preserved with `margin-bottom:11px`; business name `line-height:1.25`,
  `margin-bottom:5px`; address/contact block `line-height:1.55`; head band `padding-bottom:16px` above the teal divider.
  Invoice title bumped to 22 px, metadata right-aligned with `padding-right:14px` label gutter; status pill `margin-top:11px`.
- **Bill To / Fulfillment:** `margin-top:26px` separation from the header; stronger teal section labels
  (`letter-spacing:0.8px`); party name 11.5 px; 20 px inter-column gutter.
- **Items table:** 6 columns kept (Description · Qty · Unit price · Discount · Tax · Line total); cell padding
  `7px 7px → 9px 11px`; description column 42 %, qty 8 %; amounts right-aligned & `white-space:nowrap`; long
  descriptions/SKUs/modifiers wrap via `overflow-wrap:anywhere`; `thead` repeats on page breaks.
- **Financial summary:** `margin-top:22px`; Grand total made prominent (15 px, navy top-rule, soft `#eef4f8` fill,
  `8px 10px` padding); Amount paid and Balance due as distinct rows (balance teal at 0, red when owing); totals never
  reach the right edge (`5px 10px` cell padding inside the content area).

Restrained brand palette unchanged: navy `#123B55` / teal `#008C83`. Title and pill still driven strictly from
`document_type` / `payment_status_snapshot`.

## 5. PDF rendering checks (actual rasterized pages, not HTML previews)

All 10 required scenarios were generated as PDFs and rasterized to PNG; pages visually inspected (see §8):

| # | Scenario | Result |
|---|---|---|
| 1 | TechTitans **with logo** | Logo prominent, aspect preserved, not clipped; margins consistent |
| 2 | Tenant **without logo** (Kings Biryani) | Clean name-only header, no broken image |
| 3 | **Pro Forma** | PRO FORMA title + subtle watermark; margins intact |
| 4 | **Tax Invoice** | TAX INVOICE title, correct metadata |
| 5 | **Paid** | PAID pill, Balance due teal 0.00 |
| 6 | **Partially paid** | Amount paid > 0, Balance due red |
| 7 | **Discount + tax** | Discount (−AED) and Tax (amount + rate%) columns not cramped |
| 8 | **Long customer name** | Wraps cleanly inside Bill To column |
| 9 | **Long descriptions** | Wrap within the Description column, row height grows |
| 10 | **Multi-page** (5 pages) | Header repeats, footer "Page X of 5" on every page, no overlap, no blank pages |

Checks confirmed on the rendered pages: content inside margins on every page · header balanced (logo left / title
right) · logo not clipped · table fits content width · totals aligned and within the right margin · footer + page
numbers visible in the reserved band · no unexpected blank pages · **financial values identical to the source order**.

## 6. Tests & static analysis

- `tests/Feature/Orders/InvoicePdfTest.php` + `tests/Feature/Orders/OrderInvoiceNotificationTest.php` →
  **21 passed (70 assertions)**. The discount+tax test asserts the columns/summary match the **frozen** order totals,
  so the no-amounts-changed guarantee is enforced by the suite.
- **Pint:** `{"tool":"pint","result":"passed"}` on the changed files.
- **PHPStan** (`app/Invoices`, project `phpstan.neon`): **No errors.**

## 7. Files changed

| File | Change |
|---|---|
| `resources/views/invoices/pdf.blade.php` | Margin mechanism (`@page` + `html`, zero only `body`); footer moved to a `page_text`/`page_line` script in the bottom margin band; interior spacing/padding/line-height refinements. No data/logic. |
| `app/Invoices/InvoiceService.php` | `->setOption('isPhpEnabled', true)` so the trusted Blade footer script runs for a correct "Page X of Y". No amounts/numbering/status/brand touched. |
| `docs/techtitans/screenshots/invoice-spacing/` | 14 PNGs — `before-p1`, `after-p1`, `s01`–`s10` (multi-page `p1`/`p2`/`p3`). |
| `docs/techtitans/INVOICE-PDF-SPACING-REFINEMENT.md` | This report. |

Diff: 2 code files, +97 / −57. Frontend: none — the SPA downloads via the unchanged `/orders/{id}/invoice` route.

## 8. Before / after (screenshots in `docs/techtitans/screenshots/invoice-spacing/`)

- `before-p1.png` — prior template: content crowded toward the edges, footer/page-counter unreliable.
- `after-p1.png` — refined: even ~20 mm margins, balanced header, spacious padded table, prominent Grand total,
  footer band with "Page 1 of 2".
- `s02-nologo-p1.png` — no-logo header renders the business name only (no broken image).
- `s10-multipage-p2.png` — middle page of a 5-page invoice: repeated header, consistent margins, footer "Page 2 of 5"
  below the divider with no content overlap.

## 9. Commit (on `techtitans/integration`, no push)

- **Backend:** `0519a36` — `pdf.blade.php` (margins + footer + spacing), `InvoiceService.php` (`isPhpEnabled`),
  this report, and the 14 before/after screenshots.
