Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,46 @@ follow semantic versioning; release dates are ISO 8601.

### Templates

- **The first invoice preset that paginates what it ports: `LumaStudioInvoice`.** A
studio invoice built around a cream sidebar — the two-line monogram and the wordmark
on a terracotta block at its head, a tinted quarter-disc, an arch and a sprig running
down the rest of it — beside a billing sheet that reads sender, title and metadata,
the billed-to and shipped-to pair across a rule, the priced service lines with a VAT
column, the totals stack closing on a filled total-due band, and the notes and bank
details above a dark sign-off band. Ships as `invoice.presets.LumaStudioInvoice` on
the structured invoice model, porting the rendered layout of the published standalone
`luma-co-studio-invoice` template. Unlike the CV presets promoted before it this one
flows: the line-items table repeats its dark header on the next page, the totals stack
and each closing block stay whole, and the paper tint, the sidebar column and the dark
foot band are page backgrounds, so every page carries the same frame and the folio
always has a dark ground. **The sign-off carries its own strip, which is where the
preset deliberately departs from the sheet it ports.** On the published template the
words rely on that background band, which is pinned to the paper's edge — right on a
one-page invoice, and white-on-cream in the middle of the last page of a longer one,
where the flow ends well above the paper's foot. Drawing the strip with the words
fixes it wherever they land; the cost is 7 440 of 2 173 720 pixels on the reference
sheet, where the strip starts three points above the background band it sits on, and
one extra layout node. It is flow content only because footer chrome carries text
today — one size, one colour, no glyph, where this band needs two faces and a disc —
so once a footer zone can hold a node the sign-off belongs in one and the strip goes
away. Its geometry is not a set of round numbers: the design was drawn
on a pixel grid, so the preset carries that grid as page ratios and states every
vertical gap as the white the drawing shows, subtracting the blank a line box already
carries — per family, because the three faces it sets fill different line boxes at the
same size. Letter-spaced runs are written letter by letter with an invisible inline
rectangle between the pairs, since a text style carries no tracking; the six spacings
are frozen constants rather than a measurement, because measuring at compose time
would tie the preset to the font artifact's resource layout. Amounts take their mark
from `StructuredInvoiceData.currencyCode()` — the code is the authority, so the sheet
cannot contradict itself — and the contact channels carry `tel:`, `mailto:` and
`https:` targets derived from their values, with a parenthesised trunk prefix dropped
from the dial target the way a caller dialling from abroad drops it. Guarded by a
smoke test (including the empty document, an unknown and a blank currency code, a
supplier with one registration and with none, the link targets, the repeated header
and folio on a continuation page, and the sign-off landing on its own ground), an
exact layout snapshot over both a one-page and a three-page invoice, and a
pixel-parity gate; the examples showcase gains `invoice-luma-studio-v2`.

- **`SerifHeadline` links its titles and lets its bands breathe.** Every title the
preset draws — a role, a project, a degree, an achievement — is now a link when its
entry carries one. And a band column keeps a gutter at its right edge, so its text
Expand Down
Binary file added assets/readme/examples/invoice-luma-studio-v2.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
import com.demcha.examples.templates.cv.v2.SerifHeadlineExample;
import com.demcha.examples.templates.invoice.ClassicInvoiceV2Example;
import com.demcha.examples.templates.invoice.ConsultingInvoiceV2Example;
import com.demcha.examples.templates.invoice.LumaStudioInvoiceV2Example;
import com.demcha.examples.templates.invoice.InvoiceCinematicFileExample;
import com.demcha.examples.templates.invoice.ModernInvoiceV2Example;
import com.demcha.examples.templates.proposal.CinematicProposalFileExample;
Expand Down Expand Up @@ -176,6 +177,7 @@ public static void main(String[] args) throws Exception {
System.out.println("Generated: " + ModernInvoiceV2Example.generate());
System.out.println("Generated: " + ClassicInvoiceV2Example.generate());
System.out.println("Generated: " + ConsultingInvoiceV2Example.generate());
System.out.println("Generated: " + LumaStudioInvoiceV2Example.generate());

// Proposals
System.out.println("Generated: " + ProposalCinematicFileExample.generate());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.demcha.examples.support;

import com.demcha.compose.document.templates.data.invoice.InvoiceBrand;
import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock;
import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead;
import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock;
import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock;
import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient;
import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines;
import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock;
import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData;
import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec;

import java.math.BigDecimal;
import java.util.List;

/**
* Shared sample data for the Luma Studio Invoice example.
*
* <p>The sample is the preset's reference content: a design studio's
* invoice with a monogram lockup, five labelled masthead metadata rows,
* a billed-to and a shipped-to party, five priced service lines with a VAT
* column, a subtotal / VAT stack closing on the total-due band, five bank
* fields, two closing notes and the sign-off band.</p>
*
* <p>Kept in lockstep with the qa module's
* {@code LumaStudioInvoiceFixtures} — the two modules cannot share a source
* file, so a content change here belongs there too.</p>
*/
public final class LumaStudioInvoiceSampleData {

/** The break the sheet stacks its multi-line notes on. */
private static final String NEWLINE = String.valueOf((char) 10);

private LumaStudioInvoiceSampleData() {
}

/** The single-page reference invoice. */
public static StructuredInvoiceDocumentSpec sample() {
return StructuredInvoiceDocumentSpec.from(StructuredInvoiceData.builder()
.brand(new InvoiceBrand(null, "LUMA & CO. STUDIO", null,
"Branding. Design. Digital.", "L", "&Co."))
.supplier(new InvoiceContactBlock("LUMA & CO. STUDIO",
List.of("Studio 3.02, The Loom", "14 Gower Street",
"London WC1E 6BT", "United Kingdom"),
"+44 (0)20 7946 0832", "hello@lumaandco.studio",
"www.lumaandco.studio",
"Company No.", "12578934", "VAT No.", "369 4567 89"))
.masthead(new InvoiceMasthead("INVOICE", List.of(
new InvoiceMasthead.Entry("INVOICE NO.", "INV-2024-0587", false),
new InvoiceMasthead.Entry("ISSUE DATE", "20 May 2024", false),
new InvoiceMasthead.Entry("DUE DATE", "19 June 2024", false),
new InvoiceMasthead.Entry("PAYMENT TERMS", "30 Days", false),
new InvoiceMasthead.Entry("CURRENCY", "GBP", false))))
.billTo(new InvoiceRecipient("BILL TO", "Northfield Consulting Ltd",
"Attn: Sarah Mitchell",
List.of("21 Jubilee Way", "London SE1 3SS", "United Kingdom"), "", ""))
.shipTo(new InvoiceRecipient("SHIP TO", "Northfield Consulting Ltd", "",
List.of("The Foundry, 2nd Floor", "17-19 Great Suffolk Street",
"London SE1 0NS", "United Kingdom"), "", ""))
.serviceLines(serviceLines())
.totals(new InvoiceTotalsBlock(List.of(
new InvoiceTotalsBlock.Row("SUBTOTAL", new BigDecimal("8500.00")),
new InvoiceTotalsBlock.Row("VAT (20%)", new BigDecimal("1700.00"))),
"TOTAL DUE", new BigDecimal("10200.00")))
.notes(new InvoiceNotesBlock("NOTES", List.of(
"Thank you for your business." + NEWLINE
+ "If you have any questions regarding" + NEWLINE
+ "this invoice, please get in touch.",
"All work remains the intellectual property" + NEWLINE
+ "of LUMA & CO. STUDIO until payment" + NEWLINE
+ "has been received in full."), "", ""))
.payment(new InvoicePaymentBlock("PAYMENT DETAILS", List.of(
new InvoicePaymentBlock.Field("BANK", "Starling Bank"),
new InvoicePaymentBlock.Field("SORT CODE", "60-83-71"),
new InvoicePaymentBlock.Field("ACCOUNT NO.", "98765432"),
new InvoicePaymentBlock.Field("IBAN", "GB36 SRLG 6083 7198 7654 32"),
new InvoicePaymentBlock.Field("BIC", "SRLGGB2L")),
"Please make payment by bank transfer to:",
"Payment is due by 19 June 2024.", "",
"LUMA & CO. STUDIO LTD",
"Thank you for choosing LUMA & CO. STUDIO."))
.currencyCode("GBP")
.build());
}

private static InvoiceServiceLines serviceLines() {
return new InvoiceServiceLines(
new InvoiceServiceLines.Columns("", "DESCRIPTION", "", "QTY", "UNIT PRICE",
"AMOUNT", "VAT"),
List.of(
line(1, "Brand Strategy Workshop",
"Discovery session, research & brand positioning.", "1200.00"),
line(2, "Visual Identity Design",
"Logo suite, colour palette, typography & guidelines.", "2650.00"),
line(3, "Website Design (Up to 8 pages)",
"UX/UI design for desktop and mobile.", "3200.00"),
line(4, "Copywriting", "Website copy & key messaging.", "850.00"),
line(5, "Project Management",
"Planning, coordination & client liaison.", "600.00")));
}

private static InvoiceServiceLines.Line line(int number, String title, String description,
String price) {
return new InvoiceServiceLines.Line(number, title, description, "", BigDecimal.ONE, "",
new BigDecimal(price), new BigDecimal(price), "20%");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ record Entry(String title, String description, List<String> tags, String codeUrl
// ===== Templates / Invoice =====
invoice("invoice-cinematic", "InvoiceCinematicFileExample", "Cinematic Invoice", "Layered ModernInvoice preset with theme-driven layout, advanced tables, and totals.", "invoice", "cinematic");
invoice("invoice-consulting-v2", "v2/ConsultingInvoiceV2Example", "Consulting Invoice", "The ConsultingInvoice preset on the structured invoice model — brand lockup with the caller's logo, labelled masthead metadata, priced service lines with service periods, a totals stack and bank payment fields.", "invoice");
invoice("invoice-luma-studio-v2", "v2/LumaStudioInvoiceV2Example", "Luma Studio Invoice", "The LumaStudioInvoice preset on the structured invoice model — a cream sidebar carrying the brand lockup and its ornament, a billed-to / shipped-to pair, priced service lines with a VAT column, and the notes and bank details above a sign-off band.", "invoice");
invoice("invoice-modern-v2", "v2/ModernInvoiceV2Example", "Modern Invoice", "The ModernInvoice preset composed straight from an InvoiceDocumentSpec — line items, totals and payment block driven by the BrandTheme rather than per-document styling.", "invoice");
invoice("invoice-classic-v2", "v2/ClassicInvoiceV2Example", "Classic Invoice", "The ClassicInvoice preset — letterhead header band, TOTAL DUE hero strip, BILL TO / FROM columns, and a dedicated Summary table after the line items.", "invoice");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.demcha.examples.templates.invoice;

import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.templates.api.DocumentTemplate;
import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec;
import com.demcha.compose.document.templates.invoice.presets.LumaStudioInvoice;
import com.demcha.examples.support.ExampleOutputPaths;
import com.demcha.examples.support.LumaStudioInvoiceSampleData;

import java.nio.file.Path;

/**
* Renders the layered {@code invoice.v2} Luma Studio Invoice preset against
* the shared structured invoice sample data.
*
* <p>Output:
* {@code examples/target/generated-pdfs/templates/invoice/invoice-luma-studio-v2.pdf}.</p>
*
* <p>The preset owns its page geometry — size, margins, the paper tint, the
* cream sidebar column and the sign-off band — so the session starts
* unconfigured. The lockup is drawn from the brand's monogram and wordmark,
* and the amounts take their mark from the data's currency code.</p>
*/
public final class LumaStudioInvoiceV2Example {

private LumaStudioInvoiceV2Example() {
}

/**
* @return absolute path of the rendered PDF
* @throws Exception if rendering fails
*/
public static Path generate() throws Exception {
Path outputFile = ExampleOutputPaths.prepare(
"templates/invoice", "invoice-luma-studio-v2.pdf");
StructuredInvoiceDocumentSpec spec = LumaStudioInvoiceSampleData.sample();
DocumentTemplate<StructuredInvoiceDocumentSpec> template = LumaStudioInvoice.create();

try (DocumentSession document = GraphCompose.document(outputFile).create()) {
template.compose(document, spec);
document.buildPdf();
}
return outputFile;
}

/**
* @param args ignored
* @throws Exception if rendering fails
*/
public static void main(String[] args) throws Exception {
System.out.println("Generated: " + generate());
}
}
Loading
Loading