This guide shows how to build a branded document with the public font and asset registration APIs that shipped in Phases 25 through 28. The branded path stays behind the same truthful scope boundaries as the rest of Rendro: no silent fallback, no system-font discovery, no remote asset fetching.
Overview
Rendro ships one branded canonical recipe, Rendro.Recipes.BrandedInvoice, to
prove the end-to-end path for a registered font plus a registered logo asset.
The demo assets are library-owned examples, not built-in defaults for every
document you render.
Registering brand fonts
Use Rendro.Document.register_embedded_font/3 to register a logical font name
against a concrete path or binary source:
# docs-contract: branding-register-assets
doc =
Rendro.Document.new()
|> Rendro.Document.register_embedded_font(
:brand_heading,
{:path, Rendro.Branded.font_path()}
)
|> Rendro.Document.register_image(
:company_logo,
{:path, Rendro.Branded.logo_path()}
)
assert Map.has_key?(doc.font_registry.fonts, :brand_heading)
assert match?(%{source: :embedded}, doc.font_registry.fonts[:brand_heading])
assert Map.has_key?(doc.asset_registry.assets, :company_logo)Registering logo assets
Images follow the same {:path, _} or {:binary, _} source-tuple contract as
embedded fonts. Rendro.Branded.logo_path/0 resolves the shipped demo logo
through Application.app_dir/2, so the same call works in tests, doctests, and
consumer apps pulling Rendro from Hex.
defmodule MyApp.Branding do
def apply(doc) do
doc
|> Rendro.Document.register_embedded_font(:brand_heading, {:path, "/path/to/brand.ttf"})
|> Rendro.Document.register_image(:company_logo, {:path, "/path/to/logo.png"})
end
endBrandedInvoice tiered composition
The zero-to-one path uses the recipe directly:
# docs-contract: branding-tiered-document
data = %{
id: "INV-2026-101",
date: ~D[2026-04-30],
items: [
%{name: "Consulting", qty: 10, price: 2500},
%{name: "Support", qty: 1, price: 500}
],
brand: %{font_name: :brand_heading, logo_name: :company_logo}
}
doc = Rendro.Recipes.BrandedInvoice.document(data)
assert doc.page_template == :branded_invoice
{:ok, pdf} = Rendro.render(doc, deterministic: true)
assert binary_part(pdf, 0, 5) == "%PDF-"
assert pdf =~ "/FontFile2"
assert pdf =~ "/Type /XObject"The escape-hatch path exposes the same template and sections if you need to compose the document manually:
# docs-contract: branding-tiered-template
data = %{
id: "INV-2026-102",
date: ~D[2026-04-30],
items: [%{name: "Consulting", qty: 1, price: 1000}],
brand: %{font_name: :brand_heading, logo_name: :company_logo}
}
template = Rendro.Recipes.BrandedInvoice.page_template()
sections = Rendro.Recipes.BrandedInvoice.sections(data)
doc =
Rendro.Document.new()
|> Rendro.Document.register_embedded_font(
data.brand.font_name,
{:path, Rendro.Branded.font_path()}
)
|> Rendro.Document.register_image(
data.brand.logo_name,
{:path, Rendro.Branded.logo_path()}
)
|> Rendro.Document.add_template(template)
|> Rendro.Document.set_template(template.name)
|> then(fn current ->
Enum.reduce(sections, current, &Rendro.Document.add_section(&2, &1))
end)
assert doc.page_template == :branded_invoice
assert Enum.map(doc.sections, & &1.region) |> Enum.sort() == [:body, :footer, :header, :logo]Failure diagnostics
When a document references an image that was never registered, Rendro returns a
typed %Rendro.Error{} instead of silently omitting the block.
| Error tuple | When it occurs | What to check |
|---|---|---|
{:error, %Rendro.Error{stage: :measure, reason: {:missing_asset, logical_name}}} | A Rendro.Image references a logical name that is absent from the document asset registry. | Register the image on the document before rendering, or correct the logical name used in %Rendro.Image{} content or Rendro.Component.image/2. |
# docs-contract: branding-missing-asset-diagnostic
template = Rendro.Recipes.BrandedInvoice.page_template()
doc =
Rendro.Document.new()
|> Rendro.Document.add_template(template)
|> Rendro.Document.set_template(template.name)
|> Rendro.Document.add_section(
Rendro.section(
name: :missing_logo,
region: :logo,
content: [Rendro.Component.image(:missing_logo, fit: {64, 64})]
)
)
assert {:error, %Rendro.Error{stage: :measure, reason: {:missing_asset, :missing_logo}}} =
Rendro.render(doc)