layout

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package layout turns a receipt.Receipt plus a printer.Profile into a Document: a fully resolved, positioned set of draw instructions (pure data — no pixels yet). It measures text, wraps lines, sizes columns/tables, and resolves Image/Asset elements into decoded pixel content.

layout is the only stage in the rendering pipeline that performs I/O (resolving named assets via an assets.Store) — after Build returns, nothing downstream ever touches receipt.Receipt, an assets.Store, or any provider again. It also owns the Font interface: the one interface in this codebase kept despite having a single implementation, as a deliberate, documented exception — see docs/ARCHITECTURE.md §2 and §11.

Index

Constants

View Source
const DividerThickness = 2

DividerThickness is the height in dots a receipt.Divider occupies at Size 1. Build and Paint share this one constant (see blockHeight) so the two stages can't disagree on divider height. Style ("solid"/"dashed") is not read here: it changes which pixels are painted, not the line's vertical extent. See docs/adr/0012-divider-thickness-default-and-scaling.md.

Variables

This section is empty.

Functions

func ResolveSize

func ResolveSize(size int) int

ResolveSize floors a Size/Weight value to the >= 1 scale factor each field's "0 or omitted means unscaled" convention promises. Exported so render/canvas resolves a Divider's Size the same way Build does (see canvas.blockHeight), without a second copy of the rule.

Types

type AlignedAsset

type AlignedAsset struct {
	Data  []byte
	Width int    // 0 = no explicit width requested (shrink-to-fit-only behavior, see resolveTargetWidth)
	Align string // "" (left, default) | "left" | "center" | "right"
}

AlignedAsset is a resolved receipt.Asset's pixel data plus its own Width/Align request. Unlike TableLine/ColumnsLine/BarcodeCaption it is not layout-synthesized content — it is one Asset's fields carried past the one resolution step (assets.Store.Get) only Build can perform. See docs/adr/0013-text-and-asset-alignment.md for why that resolution step, not "extra fields," forces a distinct type from receipt.Asset/Image.

func (AlignedAsset) Validate

func (AlignedAsset) Validate() error

Validate always succeeds — see TableLine.Validate's identical doc comment: AlignedAsset is never part of a client-supplied receipt.Receipt, it exists only as a Block.Element Build itself produces.

type BarcodeCaption

type BarcodeCaption struct {
	Content string
}

BarcodeCaption is one already-space-padded line of human-readable text printed beneath a receipt.Barcode when Barcode.ShowText is true — see TableLine for why Build produces a distinct Block-carrying type. render/canvas.Paint paints its Content through the same glyph-by-glyph path as receipt.Text (canvas.textContent). "Centered" here means alignPad's leading-space padding, not geometric alignment — see alignPad.

func (BarcodeCaption) Validate

func (BarcodeCaption) Validate() error

Validate always succeeds — see TableLine.Validate's identical doc comment: BarcodeCaption is never part of a client-supplied receipt.Receipt, it exists only as a Block.Element Build itself produces.

type Block

type Block struct {
	Y       int
	Element receipt.Element
	Style   Style
}

Block is a single receipt.Element positioned within a Document, styled per Style. Y is its vertical offset in dots from the top, computed by Build.

A single receipt.Text or receipt.Heading may become several Blocks, one per wrapped line (see Build's wrapText): each carries a copy with just its line's Content, so render/canvas.Paint never needs to know wrapping happened. Every Block carries a resolved Style, including element types with no styling (e.g. receipt.Spacer gets Style{Size: 1}), so Style.Size >= 1 is a universal invariant.

type ColumnsLine

type ColumnsLine struct {
	Content string
}

ColumnsLine is one already-wrapped, column-aligned line of a receipt.Columns's output — see TableLine for why Build produces a distinct Block-carrying type rather than reusing receipt.Text. render/canvas.Paint paints its Content through the same glyph-by-glyph path as receipt.Text (canvas.textContent).

func (ColumnsLine) Validate

func (ColumnsLine) Validate() error

Validate always succeeds — see TableLine.Validate's identical doc comment: ColumnsLine is never part of a client-supplied receipt.Receipt, it exists only as a Block.Element Build itself produces.

type Document

type Document struct {
	WidthDots int
	Blocks    []Block
	Font      Font
}

Document is the fully positioned, printer-agnostic intermediate representation between a Receipt and a Canvas: an ordered list of Blocks plus the Font they were measured against. render/canvas.Paint consumes it without touching a receipt.Receipt again — see docs/ARCHITECTURE.md §2, §4.

Font is carried here, rather than passed separately to each stage, so Build's measurements and Paint's glyphs can never come from different Font instances.

WidthDots is the printer-driven canvas width, set by Build from its printer.Profile. Zero means Build had no positive width to constrain to (see printer.Profile.WidthDots), and Paint falls back to sizing the Canvas to its content. There is no HeightDots: a printer.Profile declares no paper length (a continuous roll has none), so Paint computes height from content.

func Build

Build turns r into a Document: each element becomes one or more Blocks stacked top to bottom in Receipt order (Text/Heading wrap to one Block per line via wrapText), each advancing Y per its documented meaning in docs/ARCHITECTURE.md §3. The returned Document carries f and p.WidthDots so every later stage (e.g. render/canvas.Paint) measures and paints against the same Font and target width Build used.

Build is the only stage that resolves a receipt.Asset, via a.Get — the I/O that docs/ARCHITECTURE.md §4 reserves to layout. Because receipt.Asset holds no resolved bytes, Build carries them forward in a layout-local AlignedAsset Block (see docs/adr/0013-text-and-asset-alignment.md).

a may be nil: it is touched only once a receipt.Asset is actually matched, so a Receipt with no Asset never needs one. A receipt.Asset with a nil a is a wiring mistake, reported as apperr.KindPermanent rather than panicking. A missing asset surfaces a.Get's own Kind unchanged; invalid image data and any unsupported element type are apperr.KindPermanent (never skipped or given a placeholder position).

type EmbeddedFont

type EmbeddedFont struct{}

EmbeddedFont is the built-in Font: a fixed-width bitmap face compiled into the binary via golang.org/x/image/font/basicfont, so no font file is read at runtime and its glyphs are already pixels (docs/adr/0002-raster-rendering.md).

Its native resolution is basicfont.Face7x13 upscaled by nativeScale, baked in because real 203 DPI thermal hardware found 7x13 dots too small to read (docs/adr/0008-embedded-font-legibility.md). This is purely an internal resolution change — Style.Size keeps its documented meaning (an integer multiple of the native glyph, 1 or omitted still "unscaled"); "unscaled" now just means 14x26, not 7x13.

The zero value is ready to use.

func (EmbeddedFont) Glyph

func (EmbeddedFont) Glyph(r rune) (GlyphBitmap, int)

Glyph returns r's bitmap and advance. Runes outside the embedded face's range fall back to its replacement-character glyph (see basicfont.Face.Glyph) rather than an empty bitmap.

func (EmbeddedFont) LineHeight

func (EmbeddedFont) LineHeight() int

LineHeight returns the embedded face's line height, in dots.

func (EmbeddedFont) Measure

func (EmbeddedFont) Measure(s string) int

Measure returns the width of s, in dots, as the sum of each rune's advance.

type Font

type Font interface {
	// Measure returns the width of s, in dots, if painted with this Font.
	Measure(s string) int

	// LineHeight returns this Font's line height, in dots.
	LineHeight() int

	// Glyph returns the bitmap for r and the horizontal distance, in
	// dots, to advance before painting the next glyph. Fonts that lack a
	// glyph for r return a fallback bitmap rather than a zero value, so
	// callers never need to special-case missing glyphs.
	Glyph(r rune) (bitmap GlyphBitmap, advance int)
}

Font answers the two questions render/layout.Build needs to lay out text and render/canvas.Paint needs to draw it: how wide is this string, and what does this glyph look like. See docs/ARCHITECTURE.md §2.

type GlyphBitmap

type GlyphBitmap struct {
	Width, Height int
	Bits          []byte
}

GlyphBitmap is a single glyph's pixels: a Width x Height grid, one bit per pixel, set bits painted. Each row is packed MSB-first into whole bytes, padded with unset bits — row length is (Width+7)/8 bytes, and len(Bits) is Height times that.

func DecodeAlignedAssetBitmap

func DecodeAlignedAssetBitmap(a AlignedAsset, maxWidth int) (GlyphBitmap, error)

DecodeAlignedAssetBitmap is the AlignedAsset analogue of DecodeImageBitmap: decodes a.Data, scales it to a.Width (clamped to maxWidth) or, if a.Width is 0, to maxWidth (both via targetImageSize, shared with assetHeight so the stages can't disagree), then for a.Align "center"/"right" left-pads via alignBitmap so it paints at the right offset when blitted from x=0. a.Width == 0 and a.Align == "" reproduces DecodeImageBitmap's bitmap exactly — see docs/adr/0013-text-and-asset-alignment.md.

func DecodeImageBitmap

func DecodeImageBitmap(data []byte, maxWidth int) (GlyphBitmap, error)

DecodeImageBitmap decodes data as any supported raster format (see decodeImage), scales it to fit maxWidth (scaledImageSize), and converts it to a GlyphBitmap — the same 1bpp representation glyphs use, so render/canvas.Paint paints an Image Block with the one paintGlyph primitive (docs/ARCHITECTURE.md §4). Exported because Paint calls it directly; Build needs only imageHeight to advance Y. Build and Paint each decode Data once rather than threading a decoded bitmap through Block: that keeps Block the {Y, Element, Style} shape §2 documents and — since a GlyphBitmap holds a []byte — keeps Block comparable, which tests like TestBuild_Deterministic rely on.

func GenerateBarcodeBitmap

func GenerateBarcodeBitmap(b receipt.Barcode, maxWidth int) (GlyphBitmap, error)

GenerateBarcodeBitmap generates b's barcode as a GlyphBitmap — the same 1bpp representation DecodeImageBitmap and GenerateQRCodeBitmap produce, so render/canvas.Paint paints a Barcode Block with the one paintGlyph primitive (docs/ARCHITECTURE.md §4). Generation happens here at layout time, mirroring GenerateQRCodeBitmap.

b.Encode returns the barcode at native resolution (one pixel per module, one tall, boombuler/barcode's convention). The shared rasterizeImage helper up-/down-samples it to barcodeWidth x barcodeHeight via nearest-neighbour, stretching the single source row down the full height so each module paints as a solid bar — no barcode-specific scaling logic.

func GenerateQRCodeBitmap

func GenerateQRCodeBitmap(q receipt.QRCode, maxWidth int) (GlyphBitmap, error)

GenerateQRCodeBitmap generates q's QR code as a GlyphBitmap — the same 1bpp representation DecodeImageBitmap produces, so render/canvas.Paint paints a QRCode Block with the one paintGlyph primitive (docs/ARCHITECTURE.md §4). Generation happens here at layout time, so canvas never needs to know the bitmap came from an encoder.

qr.Encode returns the code at native resolution (one pixel per module, e.g. 21x21). The shared rasterizeImage helper up-/down-samples it to qrCodeSize via nearest-neighbour — no QR-specific scaling logic.

type ListLine added in v0.2.0

type ListLine struct {
	Content string
}

ListLine is one already-wrapped, marker-and-indent-composed line of a receipt.List's output — see TableLine for why Build produces a distinct Block-carrying type. render/canvas.Paint paints its Content through the same glyph-by-glyph path as receipt.Text (canvas.textContent).

func (ListLine) Validate added in v0.2.0

func (ListLine) Validate() error

Validate always succeeds — see TableLine.Validate's identical doc comment: ListLine is never part of a client-supplied receipt.Receipt, it exists only as a Block.Element Build itself produces.

type Style

type Style struct {
	Bold          bool
	Italic        bool
	Underline     bool
	Strikethrough bool
	Size          int
}

Style is a Block's fully resolved styling, produced by Build from the source receipt.Text or receipt.Heading (docs/ARCHITECTURE.md §3, docs/adr/0007-bitmap-text-styling.md). It is deliberately separate from Font: Font is the sole source of a glyph's unstyled base pixels, and render/canvas.Paint reads a Block's Style — never receipt fields directly — which is what keeps Heading from needing a second rendering path.

Size is always >= 1 on a Style Build produces (an omitted Size normalizes to 1), so no downstream code special-cases a zero Size.

type TableLine

type TableLine struct {
	Content string
}

TableLine is one already-wrapped, column-aligned line of a receipt.Table's output — the header row or one data row, split across as many TableLine Blocks as wrapping produced (see tableLines). Build produces a distinct type, rather than a receipt.Text one, so a Table-derived Block keeps its own identity through layout like every other element type: each carries its receipt.Element type to render/canvas.Paint, the one place per type that decides how it paints (docs/ARCHITECTURE.md §4). Paint paints Content through the same glyph-by-glyph path as receipt.Text (canvas.textContent).

func (TableLine) Validate

func (TableLine) Validate() error

Validate always succeeds. TableLine is never part of a client-supplied receipt.Receipt — it exists only as a Block.Element Build produces — so there is nothing to validate; the method exists only to satisfy receipt.Element.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL