Documentation
¶
Overview ¶
Package css is the box-generation stage: it walks an html.Document, drives the pkg/css cascade per element, and emits a cssbox tree. Box generation stores the computed style on each box and normalizes the tree with anonymous-box fixups, so the layout engine receives a well-formed tree (a block container's children are either all block-level or all inline-level). It produces no pixels.
Index ¶
- func Build(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, ...) (*cssbox.Box, error)
- func BuildWithFonts(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, ...) (root *cssbox.Box, faces []gcss.FontFace, err error)
- func BuildWithFontsAndPages(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, ...) (root *cssbox.Box, faces []gcss.FontFace, pages gcss.Stylesheet, err error)
- func BuildWithFontsPagesRunning(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, ...) (root *cssbox.Box, faces []gcss.FontFace, pages gcss.Stylesheet, ...)
- func BuildWithFontsPagesRunningMedia(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, ...) (root *cssbox.Box, faces []gcss.FontFace, pages gcss.Stylesheet, ...)
- type BackgroundImageContent
- type BorderEdge
- type ControlContent
- type Engine
- func (e *Engine) Layout(ctx context.Context, root *cssbox.Box, viewportW float64) (pages *layout.Pages, err error)
- func (e *Engine) LayoutPaged(ctx context.Context, root *cssbox.Box, viewportW, pageH float64) (pages *layout.Pages, err error)
- func (e *Engine) LayoutPagedDoc(ctx context.Context, root *cssbox.Box, cfg PagedConfig) (pages *layout.Pages, err error)
- type Fragment
- type GlyphFragment
- type ImageContent
- type LineFragment
- type PagedConfig
- type PositionedInfo
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Build ¶
func Build(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, logf func(string, ...any)) (*cssbox.Box, error)
Build generates a cssbox tree from a parsed HTML document (see BuildWithFonts; this form discards the collected @font-face table for callers that do not need it). loader resolves <link rel=stylesheet> refs (may be nil → links skipped); logf receives degradation messages (may be nil). Signature unchanged for existing callers.
func BuildWithFonts ¶
func BuildWithFonts(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, logf func(string, ...any)) (root *cssbox.Box, faces []gcss.FontFace, err error)
BuildWithFonts is Build plus the aggregated @font-face rules collected from every origin sheet (UA + <style> + <link>), so the caller can hand them to the face cache. It discards the aggregated @page rules (see BuildWithFontsAndPages). It never panics on malformed input: a recover at the entry boundary returns whatever tree was built so far (and the faces collected so far).
func BuildWithFontsAndPages ¶
func BuildWithFontsAndPages(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, logf func(string, ...any)) (root *cssbox.Box, faces []gcss.FontFace, pages gcss.Stylesheet, err error)
BuildWithFontsAndPages is BuildWithFonts plus the aggregated @page rules collected from every origin sheet (so the caller can resolve paged-media geometry). The @page rules are returned as a single Stylesheet (only its Pages are populated) ready for ResolvePage. Like BuildWithFonts it never panics on malformed input.
func BuildWithFontsPagesRunning ¶
func BuildWithFontsPagesRunning(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, logf func(string, ...any)) (root *cssbox.Box, faces []gcss.FontFace, pages gcss.Stylesheet, running map[string]*cssbox.Box, err error)
BuildWithFontsPagesRunning is BuildWithFontsAndPages plus the running elements collected out of normal flow: a box whose computed position is running(name) (CSS GCPM) generates no in-flow fragment and is instead recorded in the returned map under its name (last one wins on a duplicate name). A @page margin box re-places it via content: element(name). The map is empty when no element uses running(), so a document with no running elements builds an identical tree (byte-identical for every existing caller). Like BuildWithFonts it never panics on malformed input.
func BuildWithFontsPagesRunningMedia ¶
func BuildWithFontsPagesRunningMedia(ctx context.Context, doc *html.Document, loader resource.ResourceLoader, media gcss.Media, logf func(string, ...any)) (root *cssbox.Box, faces []gcss.FontFace, pages gcss.Stylesheet, running map[string]*cssbox.Box, err error)
BuildWithFontsPagesRunningMedia is BuildWithFontsPagesRunning with an explicit media context: the cascade honors @media rules for media (plus MediaAll rules), so a PDF writer can request MediaPrint. The other Build* helpers default to MediaScreen, so every existing caller is unchanged (byte-identical).
Types ¶
type BackgroundImageContent ¶
type BackgroundImageContent struct {
Img image.Image
IntrinsicW, IntrinsicH float64
OriginX, OriginY, OriginW, OriginH float64
ClipX, ClipY, ClipW, ClipH float64
SizeKind layout.BgSizeKind
SizeW, SizeH float64 // resolved px per axis for BgSizeExplicit (≤0 = auto)
PosXFrac, PosYFrac float64
PosXPx, PosYPx float64
PosXIsPct, PosYIsPct bool
RepeatX, RepeatY bool
}
BackgroundImageContent is a fragment's resolved CSS background image plus the geometry the painter needs, all in page-space points. The origin box is where the image is sized and positioned (background-origin); the clip box is the paint area it is confined to (background-clip) — the two differ when the properties differ. It is flattened into a layout.BackgroundImageItem in paint order (behind the box's content, after its background color, before its border).
type BorderEdge ¶
type BorderEdge struct {
Width float64
Color color.RGBA
Style layout.BorderStyle
}
BorderEdge is one side of a fragment's border box. A zero edge (Width == 0 or Style == layout.BorderNone) paints nothing. The four edges of a Fragment are held in a [4]BorderEdge indexed by layout.EdgeSide, so Border[layout.EdgeTop] is the top edge, Border[layout.EdgeLeft] the left, and so on.
type ControlContent ¶
type ControlContent struct {
Kind cssbox.ControlKind
Text string
Placeholder bool
Checked bool
Disabled bool
Face *font.Face
FontSizePt float64
CX, CY, CW, CH float64
}
ControlContent is a form control's paint payload carried on a Fragment, painted in the content box (CX,CY,CW,CH, page space, shifting with the fragment).
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine lays out a cssbox tree into a positioned fragment tree at a fixed viewport width (the single-tall-page model). Safe for concurrent use: its only shared state is the face cache (itself concurrent). Build with New.
The engine implements the block formatting context (this file) and delegates inline-level content to the inline formatting context (inline.go); the two meet at layoutInline, the documented hook a block box establishing an inline formatting context calls.
func New ¶
func New(faces *layoutfont.FaceCache, loader resource.ResourceLoader, logf func(string, ...any)) *Engine
New returns an Engine that resolves fonts through faces, decodes replaced-element images (e.g. <img>) through loader, and logs unsupported or degraded cases through logf. A nil faces builds a fresh cache; a nil loader means images cannot be fetched (every <img> degrades to a placeholder); a nil logf is a no-op — so callers need supply only what they have.
func (*Engine) Layout ¶
func (e *Engine) Layout(ctx context.Context, root *cssbox.Box, viewportW float64) (pages *layout.Pages, err error)
Layout lays out root at viewportW points and returns a single tall page sized viewportW × content-height. It honors ctx cancellation (checked between block children, like the flat engine checks between blocks) and never panics on malformed input: a recover at the page boundary returns whatever was built — at minimum a single empty page.
func (*Engine) LayoutPaged ¶
func (e *Engine) LayoutPaged(ctx context.Context, root *cssbox.Box, viewportW, pageH float64) (pages *layout.Pages, err error)
LayoutPaged lays out root at viewportW points and fragments the result into pageH-tall pages, returning one-or-more pages. pageH <= 0 means no pagination: it returns exactly what Layout returns (a single tall page sized to the content height) — the byte-identical path. Otherwise it builds the same fragment tree as Layout (via the shared layoutTree), then splits the document's top-level in-flow block fragments into pageH-tall pages, breaking between block boundaries and at forced page breaks (break-before / break-after: page|always and the legacy page-break-* aliases).
It never panics on malformed input: a recover at the page boundary returns a single empty pageH-tall page. It degrades gracefully — a single block taller than a page overflows its page rather than splitting (logged once per over-tall block). position:relative blocks paginate normally (a top-level one is routed to its bucket's page; one nested under a static wrapper rides its nearest top-level ancestor's page), and a relative block's abs descendants and its own border/clip follow it (shiftFragment moves them). Only absolute/fixed boxes whose containing block is the page (not a paginated relative ancestor) are undistributed and ride the first page — a documented deferral. The html/body wrapper's border/background is fragmented per page.
func (*Engine) LayoutPagedDoc ¶
func (e *Engine) LayoutPagedDoc(ctx context.Context, root *cssbox.Box, cfg PagedConfig) (pages *layout.Pages, err error)
LayoutPagedDoc lays out root paginated per a PagedConfig: it resolves the page size and margins from the document's @page rules (combined with any API override) and fragments the document into pages, placing each page's content inside the @page margin box and emitting the @page margin boxes (running headers/footers) on each page.
When cfg.Paged is false it delegates to Layout (a single tall page) — the byte-identical path. Otherwise the LAYOUT width is page 0's content-box width (the page width minus its horizontal @page margins, or the fallback width when there is no @page rule), so existing geometry is preserved while content is inset by the margins. It never panics (the same page-boundary recover as Layout).
type Fragment ¶
type Fragment struct {
X, Y, W, H float64 // the BORDER box rectangle in page space
Background color.RGBA // zero-alpha => no background fill
Border [4]BorderEdge // indexed by layout.EdgeSide (EdgeTop, EdgeRight, EdgeBottom, EdgeLeft)
Lines []LineFragment // inline content (set for a box establishing an inline formatting context)
Children []*Fragment // child box fragments (block children; atomic inline boxes)
Image *ImageContent // decoded replaced-element image (set for a replaced box), painted in the content box
Control *ControlContent // form-control widget (set for a control replaced box), painted in the content box
BgImage *BackgroundImageContent // decoded CSS background image (set when the box has a decodable background-image), painted behind content
DebugTag string // optional label for test lookup; not used in paint
// Box is the source cssbox.Box this fragment was produced from, retained so the
// flatten/paint stage can read style-driven paint facts that are not pre-resolved
// onto the fragment — today the stacking z-index (Box.Style.ZIndex/ZIndexAuto),
// later opacity/isolation and SPA-snapshot re-flow. Set after layout; the flatten
// stage only READS it and never mutates it, so the fragment tree stays safe to
// share across the concurrent render fan-out — which holds only because layout has
// fully completed before any flatten begins (there is no incremental relayout in
// this engine yet). A nil Box reads as the initial style (z-index auto):
// anonymous/synthetic fragments and the page root need not set it.
Box *cssbox.Box
// IsFloat marks a fragment produced by a floated box. The float paint phases
// skip such subtrees during the in-flow passes and paint them in the float pass
// instead (CSS 2.1 Appendix E).
IsFloat bool
// IsBFC marks a fragment that establishes a block formatting context (the page
// root and inline-blocks). Such a fragment owns the float-layer paint sequencing
// for the floats placed in its BFC (held in Floats); a non-BFC fragment recurses
// normally within each phase.
IsBFC bool
// Floats holds the fragments of floats placed in this fragment's BFC, painted in
// their own layer (after in-flow block decorations, before in-flow inline
// content). Set only on an IsBFC fragment. Kept separate from Children so in-flow
// tree order is untouched.
Floats []*Fragment
// HeaderBottom is the page-space bottom Y of a table's repeatable <thead> rows, or
// 0 for a non-table or a table with no header. Pagination clones the cells above
// this line onto each continuation page so a long table keeps its column headings.
//
// It is a Y rather than a row count because the head/body/footer distinction is
// flattened away by grid construction — by the time a fragment exists, a header
// cell is indistinguishable from a body cell except by position.
HeaderBottom float64
// IsPositioned marks a fragment produced by a positioned box (relative,
// absolute, or fixed). The stacking pass lifts such a fragment out of the
// in-flow decoration/content passes and paints it in the positioned layer
// instead (CSS 2.1 Appendix E). For a relative box (which IS in flow) this
// moves only its painting; its in-flow space stays reserved.
IsPositioned bool
// RelOffsetX/RelOffsetY is a relatively-positioned box's paint-time offset
// (CSS 9.4.3). Applied as a translate over the fragment's flattened item range
// when the positioned layer paints it (NOT by shiftFragment/translateFragment,
// which do not recurse Positioned). Zero for absolute/fixed (their position is
// baked into the fragment coordinates by the abs-pos pass).
RelOffsetX, RelOffsetY float64
// IsStackingContext marks a fragment that establishes a stacking context (the
// root and every positioned box). Such a fragment owns the Appendix E phase
// ordering for its subtree, ending with its positioned layer.
IsStackingContext bool
// Positioned holds the fragments of positioned descendants painted in this
// stacking context's positioned layer. Kept separate from Children so in-flow
// tree order is untouched; a descendant in Positioned is skipped in the in-flow
// passes (IsPositioned) so it paints exactly once. AppendItems z-index-sorts these
// into three Appendix E bands (negatives before decorations, the z:auto/0 middle
// after in-flow content, positives last); see sortedPositioned and AppendItems.
Positioned []*Fragment
// Collapsed holds the resolved border-collapse:collapse edge strips for a table
// fragment (nil for every other fragment — so non-collapse pages are byte-identical).
// Painted via the normal border path (BorderKind items) after the cell backgrounds
// and cell content are emitted, so the grid lines paint on top of cell fills.
// In the same page space as the fragment's border box.
Collapsed []layout.BorderItem
// Clips marks a fragment whose box has overflow ≠ visible: the stacking pass
// brackets its contents (descendant decorations, floats, in-flow content, and the
// CB-owned subset of its positioned layer) with a ClipPush(ClipRect)/ClipPop pair,
// so they paint clipped to the padding box. The fragment's OWN background/border
// paint OUTSIDE the bracket (a box does not clip its own border box). A clipping
// fragment is always a BFC (overflow≠visible establishes one), so AppendItems
// reaches it via the IsStackingContext||IsBFC branch.
Clips bool
// ClipRect is the clip rectangle when Clips is true: the padding box (the border
// box deflated by the border widths), in page space. Zero when !Clips.
ClipRect rect
// PositionedInfo parallels Positioned: per-entry clip metadata telling the stacking
// pass how to clip each positioned descendant painted in THIS holder's positioned
// layer. len(PositionedInfo) == len(Positioned) when set; a nil/short slice reads as
// the zero value (CBOwned=false, no clip chain) — the safe default, consulted only on
// a clipping fragment.
PositionedInfo []PositionedInfo
}
Fragment is one positioned box in page space (points, Y-down, origin at the page top-left). Produced by the CSS layout engine; read-only after layout, so a fragment tree may be shared across the render fan-out without locks. Paint order follows CSS 2.1 Appendix E for a fragment that establishes a block formatting context (IsBFC): in-flow block backgrounds/borders, then floats, then in-flow inline content. A non-BFC fragment keeps the simpler parent-before-child tree order (its own background and border, then its content, then its children). See AppendItems.
Fragment is the recursive analogue of layout.Item: the layout engine emits this tree, and AppendItems flattens it into the flat layout.Page.Items slice the paint stage already consumes. The flatten is a pure read of the tree; it never mutates it, preserving the read-only-after-layout contract.
func (*Fragment) AppendItems ¶
AppendItems appends f's drawing primitives, and its descendants', to dst in CSS 2.1 Appendix E paint order, returning the extended slice. For a fragment that establishes a stacking context (IsStackingContext — the root and every positioned box) OR a block formatting context (IsBFC — inline-blocks and floats), the positioned layer is split by z-index into three bands (sortedPositioned): NEGATIVE z paints BEFORE the context's in-flow decorations (Appendix E step 2, behind in-flow content); then in-flow block decorations, the float layer, and in-flow inline content/images (steps 3–5, each skipping floated AND positioned subtrees); then the MIDDLE band (z:auto / z:0 in document order, step 6); then the POSITIVE band (step 7). The sort is STABLE so equal keys keep document order — a context whose positioned boxes are all z:auto produces the same stream as the prior document-order pass (byte-identical for the existing corpus). A plain BFC that is not a stacking context has an empty positioned layer, so all three bands are empty and the order reduces to decorations → floats → content. A non-BFC, non-stacking fragment paints self then recurses children (skipping floated and positioned children), unchanged.
A clipping fragment (Clips) brackets its CONTENTS — children's decorations, floats, in-flow content, the CB-owned subset of each band — with a ClipPush(ClipRect)/ClipPop pair (its own background/border paint outside it). CB-owned negatives paint inside the bracket behind the children; escaped entries (CB is an ancestor) paint outside it. An entry carrying a ClipChain (a positioned descendant that bubbled through an overflow≠visible box on its way to this holder) is itself bracketed by that chain's rects, so it is clipped to the intervening box even when it paints in this layer.
A relatively-positioned entry carries a paint-time RelOffset, applied via translateItems over its freshly-flattened range. AppendItems never mutates the fragment tree (the sort packs a local copy; only appended dst items are translated), so it is safe on a tree shared across the render fan-out.
func (*Fragment) Page ¶
Page returns a single Page sized widthPt × heightPt whose Items are the flattened drawing primitives of the fragment tree rooted at f. It is called once for the single-tall-page output model and once per page by the pagination pass (paginate), which flattens each page's shallow-cloned root wrapper. It feeds the same paint.PaintPage path as the flat (DOCX) engine's output.
type GlyphFragment ¶
type GlyphFragment struct {
Outline *render.Path
X float64
// AdvancePt is the glyph's horizontal advance in page-space points. It sizes a
// text-decoration underline span: the span's right edge is the last glyph's
// X+AdvancePt (the visible glyph extent is approximated by the pen advance —
// adequate for underlines). The CSS engine always sets it; a zero value would
// shorten an underline's trailing edge by the last glyph's width.
AdvancePt float64
SizePt float64
Color color.RGBA
// Underline marks a glyph whose box has text-decoration: underline; consecutive
// underlined glyphs on a line are painted with one underline rule (see
// appendSelfContent).
Underline bool
// Strike marks a glyph whose box has text-decoration: line-through; consecutive
// struck glyphs on a line are painted with one mid-glyph rule (see appendStrikes,
// called alongside appendUnderlines in appendSelfContent).
Strike bool
// BaselineShiftPt raises (positive) or lowers (negative) this glyph relative to the
// line baseline, in page-space points — vertical-align: super/sub. The glyph's paint
// Y is ln.BaselineY - BaselineShiftPt (up = smaller Y). Zero (the default) leaves the
// glyph on the line baseline, so a run without super/sub is unchanged.
BaselineShiftPt float64
// Face, GID, and Runes carry font identity for text-emitting backends (the PDF
// writer). Face is nil for a glyph with no identity; the rasterizer ignores them.
Face *font.Face
GID uint16
Runes []rune
}
GlyphFragment is one positioned glyph on a line. It mirrors layout.GlyphItem so flattening is a direct copy. Outline is in em units, Y up (as the font face returns it); X is the pen origin on the baseline in page space (the Y comes from the owning LineFragment's BaselineY). A nil Outline (e.g. whitespace) is skipped.
type ImageContent ¶
type ImageContent struct {
Img image.Image
CX, CY, CW, CH float64
Fit layout.ObjectFit
// PosX, PosY are the object-position as fractions of the content box's free space
// (0.5/0.5 = centered, the default). See layout.ImageItem.
PosX, PosY float64
}
ImageContent is a decoded replaced-element image carried on a Fragment. CX,CY, CW,CH is the fragment's content box in the same frame as the fragment's own border box (so it shifts with the fragment), resolved at layout time by deflating the border box by the box's border+padding. Fit is the object-fit mapping. A nil Img means decode failed: the fragment still reserves its box (a sized placeholder), but no image is painted.
type LineFragment ¶
type LineFragment struct {
BaselineY float64 // page-space Y of the line's baseline
Glyphs []GlyphFragment
}
LineFragment is one positioned line box of an inline formatting context: a single baseline shared by all its glyphs. The engine produces one per line after line-breaking; flattening emits each glyph as a layout.GlyphItem on this baseline.
type PagedConfig ¶
type PagedConfig struct {
Paged bool
FallbackW float64
FallbackH float64
ExplicitSize bool
Pages gcss.Stylesheet
// Running holds the document's out-of-flow running elements (CSS GCPM
// position:running(name)), keyed by name. A @page margin box whose content is
// element(name) re-paints Running[name] on every page. Empty/nil when no element
// uses running() — the byte-identical path (element() never fires).
Running map[string]*cssbox.Box
}
PagedConfig is the resolved paged-media configuration handed to LayoutPagedDoc by the doctaculous backend: whether to paginate at all, a fallback page size (from WithPageSize / the Letter default), an explicit-size flag (so an API size overrides an @page size), and the parsed @page rules (for size, margins, and margin boxes).
The fields combine like CSS Paged Media + the API override rule:
- Paged false → a single tall page (LayoutPagedDoc delegates to Layout).
- FallbackW/H → the page size when no @page `size` applies (the WithPageSize value, else Letter).
- ExplicitSize → the caller passed WithPageSize: its size wins over any @page `size` (but @page margins/margin-boxes still apply).
- Pages → the document's @page rules (may be empty).
type PositionedInfo ¶
type PositionedInfo struct {
// CBOwned reports that Positioned[i]'s containing block IS this holder fragment.
// A clipping holder paints a CB-owned entry INSIDE its own clip bracket; a
// non-CB-owned (bubbled-through) entry paints after ClipPop, outside this holder's
// own clip.
CBOwned bool
// ClipChain holds the padding-box rects of every overflow≠visible box the descendant
// passed THROUGH between itself and this holder, outermost-first. Empty for the
// common case. When non-empty, the positioned phase brackets THIS entry's emitted
// item range in a nested ClipPush(rect)…ClipPop for each rect — so a positioned
// descendant of a non-positioned overflow:hidden box is cut at that box's padding box
// even though it paints in an ancestor's layer (CSS: every overflow≠visible ancestor
// between the box and its CB clips it). The holder's OWN clip (when CBOwned) is
// applied by the bracket, NOT by this chain.
ClipChain []rect
}
PositionedInfo is one entry of a Fragment's PositionedInfo slice (parallel to Positioned): how to clip the matching positioned descendant when it paints in this holder's positioned layer.
Source Files
¶
- anon.go
- background.go
- baseline.go
- block.go
- build.go
- canvasbg.go
- control.go
- counters.go
- cropmarks.go
- flex.go
- flexgridpage.go
- floats.go
- fragment.go
- fragmentpage.go
- grid.go
- grid_place.go
- grid_track.go
- image.go
- inline.go
- itemfix.go
- marginbox.go
- measure.go
- pagemodel.go
- paginate.go
- positioning.go
- replaced.go
- runningelement.go
- stringsnapshot.go
- table.go
- tableborder.go
- tablefix.go
- tablepage.go