Documentation
¶
Overview ¶
Package render lays HTML and CSS out onto a PDF page.
This file is its guardrail vocabulary, and it exists before the layout engine on purpose. §9 of the rendering proposal asks for the reporting layer to land *with* the engine rather than after it, and gives the reason: a reporting layer retrofitted onto a finished engine is how it becomes decorative. The engine grows into this, not the other way round.
What the guardrails are for ¶
Layout degrades *silently*. That is its characteristic failure and the whole argument of §6. A clipped paragraph, a 3pt caption and a heading full of tofu all produce a valid PDF that a caller has no programmatic way to distrust — the file opens, the text is selectable, nothing errors. So every way this engine can quietly produce a document that is not what was asked for is a named rule with an identifier, and a caller can decide for each one whether it is worth failing over.
Index ¶
- Constants
- Variables
- func PathOf(n *html.Node) string
- func ShapedText(v DrawText) string
- type Box
- type Built
- type ClearSide
- type Clip
- type Composed
- type Control
- type DirResolver
- type DrawImage
- type DrawText
- type Edges
- type FallbackFontSet
- type FillRect
- type Finding
- type FloatSide
- type FontSet
- type Fragment
- type Inner
- type Input
- type LineFragment
- type Marker
- type Op
- type Options
- type Outer
- type PageSize
- type Point
- type Policy
- type PositionScheme
- type Recorder
- type Rect
- func (r Rect) Bottom() style.Unit
- func (r Rect) Contains(inner Rect) bool
- func (r Rect) Empty() bool
- func (r Rect) Inset(e Edges) Rect
- func (r Rect) Intersect(o Rect) Rect
- func (r Rect) Origin() Point
- func (r Rect) Outset(e Edges) Rect
- func (r Rect) Right() style.Unit
- func (r Rect) Size() Size
- func (r Rect) String() string
- type ReplacedContent
- type ResourceResolver
- type Rule
- type Severity
- type Size
- type Source
- type Stylesheet
- type TextRun
- type TileImage
Constants ¶
const UserAgentCSS = `` /* 8670-byte string literal not displayed */
UserAgentCSS is the default stylesheet.
The lengths are the ones every browser converged on, and the em-based ones are em on purpose: a document that sets a larger font gets proportionally larger spacing, which is what an author expects and what a fixed pixel margin quietly fails to do.
Variables ¶
var ( A4 = PageSizePt(595.276, 841.89).WithMarginPt(56.7) // 20mm A5 = PageSizePt(419.528, 595.276).WithMarginPt(42.5) Letter = PageSizePt(612, 792).WithMarginPt(54) // 0.75in Legal = PageSizePt(612, 1008).WithMarginPt(54) )
The paper sizes a document generator is actually asked for.
var ErrNoResolver = errors.New("no resource resolver is configured, so nothing outside the document can be loaded")
ErrNoResolver is what the engine reports when a document refers to something and the caller configured no resolver.
var NoSource = Source{HTMLOffset: -1, CSSOffset: -1}
NoSource is a finding that is not tied to a place in the input.
Functions ¶
func PathOf ¶
PathOf renders an element's position in the document, so a finding about a stylesheet shared by many elements can still say which one it is about.
It is a readable path rather than a selector that would round-trip: it names the element chain with the identifiers and classes that distinguish it, which is what someone reading a report needs.
func ShapedText ¶
ShapedText is the string handed to the shaper for one text run.
The run's own text is in logical order and carries no direction of its own: a run of punctuation between two Hebrew words is right-to-left because of characters that are in other runs by now. The shaper applies UAX #9 to the string it is given, so left to itself it would answer for that string rather than for the paragraph the run came out of, and a lone bracket would come out facing the wrong way.
So the direction the layout resolved is stated to it, in the one vocabulary a string has for saying so: an explicit right-to-left override in front of the text. That is exactly what the character means; it is a default-ignorable code point, so the shaper drops it before any glyph is chosen; and what comes back is the run's glyphs in the order they are drawn, with rule L4's mirroring applied.
The override goes here and not into the run's text, because the run's text is what a reader copies out of the finished page.
Types ¶
type Box ¶
type Box struct {
Outer Outer
Inner Inner
// Element is the element that generated this box, or nil when nothing did.
// An anonymous box has no element and no style of its own — it inherits
// everything, which is what the specification means by anonymous.
Element *html.Node
// Style is the computed style of the generating element. An anonymous box
// carries the style it inherits from, so that a consumer never has to walk
// up looking for one.
Style style.ComputedStyle
// Text is the content of a text box, after white-space processing. It is
// empty for every other kind.
Text string
// FontSize is the computed font-size, resolved here rather than in the
// cascade because it is the one property whose value depends on its own
// parent's computed value: "font-size: 2em" means twice the parent's size,
// so it can only be resolved walking downwards.
//
// Only an element that *declared* one resolves; an element that inherited
// its font-size takes its parent's number unchanged. See fontSizeOf for why
// that distinction is the whole of the property's correctness.
//
// Every font-relative length in the box — its margins, its padding, its
// line height — is measured against this, so it has to exist before layout
// asks anything about geometry.
FontSize style.Unit
// ListItem marks a box that generates a marker — a bullet or a number.
ListItem bool
// ListValue is what a numbered marker counts to, taken from the "list-item"
// counter rather than from the item's position among its siblings.
//
// ListNumbered says whether it means anything, because zero is a value a
// list can legitimately be at — <ol start="0"> — and not a way of saying
// there is no counter. Reading the zero as "unset" numbered that list from
// one, which is the same shape of fault as a sentinel colliding with a real
// value elsewhere in this engine.
//
// The two differ whenever a document says so, and documents do: <ol start>,
// <li value>, a "counter-reset: list-item" on the list, an item that is not
// the first child, or a list whose items are not siblings at all. Counting
// siblings gets every one of those wrong, and gets them wrong quietly — the
// list is numbered, just not with the numbers the author asked for.
//
// Where there is no counter — a "display: list-item" that no rule increments
// — the position among the parent's list items is written here instead, by
// the block walk, which is the only place that knows it. ListNumbered stays
// false there, and says which of the two answered. Keeping the fallback in
// the same field is what lets everything that draws a marker read one number:
// it used to travel as an argument, and three of the five call sites had
// nothing to pass and passed zero.
ListValue int
ListNumbered bool
// Replaced is the content of a replaced element — the decoded image an
// <img> names — or nil for every other box.
//
// It is nil rather than empty when the content could not be loaded, and
// that is the whole of what CSS means by an element being replaced: an
// image that did not arrive makes the element an ordinary inline box
// holding its alt text, not a replaced one holding nothing. Layout
// therefore asks whether this is nil rather than asking what the element's
// tag is, and an <img> with a broken src goes down exactly the same path as
// a <span>.
Replaced *ReplacedContent
// Control is what makes a box a form control, or nil for every other box.
//
// It carries only what CSS cannot say — an intrinsic size in characters and
// in lines — for the reason ReplacedContent gives about itself: being a
// control changes where a box's auto width and height come from and nothing
// else about it. See control.go, and note what it deliberately is not: no
// PDF form field is produced from it and nothing about it is interactive.
Control *Control
// BackgroundImages is the pictures this box's background-image named, by the
// reference the stylesheet wrote.
//
// It is a map rather than a slice because the layers are read later, by
// layout, and matching a slice to them by index would depend on the loading
// pass and the reading pass agreeing about a value they parse separately —
// which is the sort of coupling that survives every test and breaks on the
// document that repeats one file in two layers.
//
// A reference that failed to load is absent rather than present and nil, so
// a layer naming it paints nothing. That is the same answer a broken <img>
// gets and for the same reason: the picture is missing, not the box.
BackgroundImages map[string]*ReplacedContent
// TableWrapper marks the anonymous box §17.4 puts around a table to hold it
// and its captions.
//
// It is a flag rather than an Inner of its own because the wrapper really is
// an ordinary flow root — block layout, margin collapsing, floats and
// positioning all treat it as one, and that is the point of it. The one thing
// that is not ordinary is its width, which is the table's rather than its
// containing block's, and that is the single question this answers.
TableWrapper bool
// Float and Clear are CSS 2.1 §9.5. They live on the box rather than being
// read out of Style at layout time for the same reason Outer and Inner do:
// whether a box is in the normal flow changes what the box tree itself is
// allowed to do to it — an anonymous block box is generated around a run of
// *in-flow* inline content, and a float in that run is not part of the run.
Float FloatSide
Clear ClearSide
// Position is CSS 2.1 §9.3's scheme, and it lives here for the same reason
// Float does and one more. An absolutely positioned box is out of flow, so
// the anonymous-box rules have to know about it before layout runs; and
// §9.7 blockifies it exactly as it blockifies a float, which is a
// computed-value rule and so belongs to the stage that computes the box.
Position PositionScheme
// ZIndex and ZAuto are §9.9's stacking level. They are read here rather than
// out of Style at paint time because painting order is decided by a
// traversal that has to sort boxes against each other, and a sort key that
// has to be re-parsed at every comparison is a sort that gets written once
// and then quietly avoided.
ZIndex int
ZAuto bool
// Order is the box's index in document order, which is the tie-break
// Appendix E uses between two positioned boxes at the same stacking level.
//
// It is recorded rather than derived because painting does not walk the tree
// in document order any more: an absolutely positioned box is placed after
// the flow has been laid out and hangs from whichever fragment could hold
// it, so its position among its siblings has been lost by the time anything
// needs to sort it. Two overlapping cards written one after the other would
// otherwise stack in whichever order the placement pass happened to reach
// them, which is stable, invisible and wrong.
Order int
Children []*Box
Parent *Box
// contains filtered or unexported fields
}
Box is a node of the box tree.
func BuildBoxes ¶
BuildBoxes turns a styled document into a box tree.
The root box is the one the document element generated. It is nil when the document produces no boxes at all, which is what "html { display: none }" means and is not an error.
type Built ¶
type Built struct {
// Document is the parsed tree, present even when findings were raised.
Document *html.Node
// Root is the root of the box tree, nil when the document produces no
// boxes — which "html { display: none }" legitimately does.
Root *Box
// Styles is every element's computed style.
Styles map[*html.Node]style.ComputedStyle
// Fonts is the set the document is to be laid out in: the caller's library
// with the faces the document's own @font-face rules loaded over it. It is
// never nil, and it is what a caller calling Layout directly must hand it —
// passing Input.Fonts instead would lay the document out without the fonts
// it brought.
Fonts FontSet
// Findings is everything worth telling the caller, ordered deterministically.
Findings []Finding
// Failed reports that something fired at Error severity, so a caller that
// went on to render would be rendering something it was told not to.
Failed bool
// Truncated reports that the finding list was cut.
Truncated bool
}
Built is the result of the stages that exist.
type ClearSide ¶
type ClearSide uint8
ClearSide is the value of the clear property: which sides of earlier floats a box refuses to sit beside.
type Clip ¶
Clip is the area an operation may mark.
Active is not redundant with an empty rectangle, and conflating the two would be the classic sentinel collision: "clip: rect(0, 0, 0, 0)" asks for a clip that admits nothing, which is the *opposite* of no clip at all. The engine never emits an operation whose clip is active and empty — there would be nothing to draw — so the distinction is only ever read one way, but a reader of the display list should not have to know that to be safe.
type Composed ¶
type Composed struct {
// Ops is the display list, in paint order.
Ops []Op
// Root is the fragment tree the display list was painted from.
Root *Fragment
// Scale is the factor of §5: 1 when the content fitted, less when it had to
// be shrunk. It is reported because a caller may want to refuse a document
// that only fitted by being made small.
Scale float64
// NaturalSize is what the content needed at its natural size, before any
// scaling. It is what a caller adjusting a template needs to know.
NaturalSize Size
// Findings is everything the guardrails raised, in a deterministic order.
Findings []Finding
// Refused is a rule having fired at Error severity. A backend that sees it
// should produce nothing: the caller was told not to render, rather than
// left to decide.
//
// It is not a summary of Findings and cannot be recomputed from them. A
// rule counts the moment it fires, before the list deduplicates and before
// the bound below cuts it — so a document refused by its six-hundredth
// finding is refused with that finding nowhere in the list. This field is
// the authority; the list is the explanation, when there is room for one.
Refused bool
// Truncated is the bound having stopped findings being recorded, so what
// Findings holds is some of them rather than all of them.
//
// A backend that reports findings has to say so. Presenting a cut list as a
// complete one is how "three problems" becomes what a reader believes about
// a document with four hundred.
Truncated bool
}
Composed is a document laid out and painted, ready for a backend.
func Compose ¶
Compose is everything between a document and a backend: build the box tree, lay it out on the sheet, decide the scale, check that what came out is worth producing, and paint it.
It stops one step short of a document, and that step is the only one that knows what a document is. A backend takes Ops and writes them — into a PDF, into a raster, into a test — and everything above this line is the same whichever it is.
type Control ¶
type Control struct {
Kind controlKind
// Chars is the intrinsic content width in "0" advances, or zero when the
// control has none and shrinks to fit instead.
Chars int
// Lines is the intrinsic content height in line boxes, or zero when the
// height is the content's own.
//
// It is a *used* height rather than a minimum, which is what makes a
// textarea with twenty lines of text in it two lines tall and scrolled:
// rows says how much of the control is on the page, not how much text it
// holds.
Lines int
}
Control is what a box needs to know to be laid out as a control.
It is a value on the Box for the same reason ReplacedContent is: being a control changes where a box's size comes from and nothing else about it. A control still floats, still positions, still takes part in a line, and every rule about those applies unchanged.
type DirResolver ¶
type DirResolver struct {
// contains filtered or unexported fields
}
DirResolver serves files from one directory and from nowhere else.
Containment is enforced by os.Root, which resolves every path component against the directory at the system-call level: a reference with "..", an absolute path, and a symbolic link pointing outside the directory are all refused by the kernel rather than by a string comparison this code performs. That distinction is the whole reason os.Root exists — a check on the name followed by an open is a race, and a check on the *resolved* name is one symlink away from being wrong.
A resolver holds an open directory handle and should be closed when the caller is done with it.
func NewDirResolver ¶
func NewDirResolver(dir string) (*DirResolver, error)
NewDirResolver opens dir as the only place a document may read from.
func (*DirResolver) Close ¶
func (d *DirResolver) Close() error
Close releases the directory handle.
func (*DirResolver) Resolve ¶
func (d *DirResolver) Resolve(ref string) ([]byte, error)
Resolve reads one file from the rooted directory.
func (*DirResolver) WithMaxBytes ¶
func (d *DirResolver) WithMaxBytes(n int64) *DirResolver
WithMaxBytes returns the resolver with a different size cap. A cap of zero or less is refused rather than treated as "no limit": an unbounded resolver is the thing this type exists to prevent, and a zero value in a configuration struct must not switch it off.
type DrawImage ¶
type DrawImage struct {
Rect Rect
Image image.Image
// Key identifies the source bytes. Two elements naming one file carry the
// same key, which is what lets the backend embed the picture once — a
// document with a logo in a header repeated on every row would otherwise
// carry it as many times as it is drawn.
Key string
// Clip is §11.1's clipping, when something clips this picture.
//
// It cannot be folded into Rect the way a fill's is, because Rect is where
// the picture is *stretched to*: narrowing it would squeeze the whole
// image into the visible strip rather than cutting the part that is not.
// A backend must intersect its own clipping path with this.
Clip Clip
}
DrawImage paints a decoded image to fill a rectangle.
The rectangle is the element's *content* box, which is where a replaced element's content goes: inside its padding, inside its border. The image is stretched to it rather than fitted, because the sizing rules upstream have already chosen a rectangle with the right shape — object-fit, which is what asks for anything else, is not implemented.
type DrawText ¶
type DrawText struct {
At Point
Text string
// RTL says the run reads right to left.
//
// The text is in *logical* order — the order it is written and read, which
// is what a reader copying it out of the page expects and what the string
// here has to be for the text of the document to survive. Which way the
// glyphs go is a separate fact, and it is one the backend has to be told
// rather than one it can work out: a run of punctuation between two Hebrew
// words is right-to-left because of its neighbours, and by the time the run
// reaches a backend the neighbours are gone.
RTL bool
Face *shape.Face
Size style.Unit
Color style.RGBA
// CharSpacing is letter-spacing: an extra advance after every character.
//
// It is a property of the drawing rather than of the position because layout
// already spent it — the run's width includes it, and the run after this one
// is placed accordingly — so a backend that ignored it would draw the glyphs
// bunched at the left of a gap the right size.
CharSpacing style.Unit
// Clip is §11.1's clipping, when something cuts this run.
//
// It is set only when the clip really does cut the run: a run wholly inside
// its clip carries none, and one wholly outside is not emitted at all. That
// is not an optimisation — it is what keeps the display list of a document
// whose text merely happens to sit inside an "overflow: hidden" box
// identical to the list of one that does not, which is what the reftest
// comparison needs to be able to say the two look the same.
Clip Clip
}
DrawText draws a run of text with the origin of its baseline at At.
The position is the baseline rather than the top of the line box, because that is what a text-drawing backend takes and because converting between them needs the face's metrics — which this stage has and the backend may not.
type Edges ¶
Edges is a value per side, in the order CSS writes them.
func (Edges) Horizontal ¶
Horizontal and Vertical are the sums along each axis, which the box-model arithmetic asks for constantly.
type FallbackFontSet ¶
type FallbackFontSet interface {
FontSet
// FaceFor returns a face that can set the whole of text, and whether one was
// found. The bold and italic flags are the ones the box asked for; a set is
// free to ignore them when the alternative is having no glyph at all.
FaceFor(text string, bold, italic bool) (*shape.Face, bool)
}
FallbackFontSet is a FontSet that can also be asked for a face by what it needs to set rather than by name.
The two questions are different, and the second one cannot be asked through FontSet: "give me Helvetica" is answerable from the family alone, while "give me something that can set this Hebrew word" needs the text. A set that implements this is offering coverage the named family does not have.
It stays optional because it is a different promise. A FontSet answers what the document asked for; a FallbackFontSet answers what the document *needs*, which is a substitution and is reported as one. A set that made that substitution silently from inside Face would be a set that could hide it, and that is the thing this design is against — see the note on FontSet above.
The substitution is per box rather than per character. A box whose text mixes scripts that no single face covers still reports a missing glyph, and the remaining step is to cut a run into per-face pieces the way shape.Stack does, which reaches into measurement, line breaking and the content stream.
type FillRect ¶
type FillRect struct {
Rect Rect
Color style.RGBA
// Overhang marks a fill whose position no layout decision accounted for: a
// text decoration, and the background and border of an inline box.
//
// It exists for the overflow-page guardrail, which is about *boxes* leaving
// the page and reads the display list to find them. Text is not checked by it
// at all — a glyph whose ascender reaches above the page top produces a
// DrawText, which the guard skips — so an overline over the same letters must
// be skipped too. Without this, "line-height: 0.3" on the first line of a page
// puts the overline a few pixels above the top edge, the guard fires at Error
// severity, and no document is produced at all: an overhang of two pixels
// turned into a refusal, from a rule whose whole purpose is to catch a wrong
// scale calculation.
//
// An inline box's decoration is the same case and reaches it by the same
// route. §10.6.1 gives the box a content area the height of its *font* rather
// than of the line it sits on, and §8.4 and §8.5 keep its vertical border and
// padding out of layout entirely — so a "line-height: 0.5" span, or one with
// ten pixels of padding, puts ink above the first line of a page that nothing
// in the flow ever measured. The scale-to-fit calculation cannot have
// accounted for it, so a guard checking that calculation must not read it.
Overhang bool
}
FillRect paints a rectangle in a solid colour.
type Finding ¶
type Finding struct {
// Rule is which guardrail fired.
Rule Rule
// Severity is what it did, after the caller's policy was applied.
Severity Severity
// Message says what happened, in terms of the author's input.
Message string
// Source is where in the input it happened.
Source Source
// Path is the DOM path of the element concerned, such as
// "html > body > div > p", or empty. It is what makes a finding actionable
// when the offset points at a stylesheet shared by many elements.
Path string
// Selector is the selector responsible, or empty.
Selector string
// Property is the declaration responsible, or empty.
Property string
}
Finding is one guardrail firing.
It satisfies pdf0's Violation interface — error, RuleID and ObjectNum — so findings from a render collect into one slice alongside those from ValidatePDFA and ValidatePDFUA, which is the whole point of that interface. The interface is satisfied structurally and is not imported here, so this package does not depend on the one that documents it.
ObjectNum is always 0, which the interface already documents as "not tied to a specific object": a layout finding is about a paragraph in the source, not about an object in the file it became.
func (Finding) Error ¶
Error renders the finding for a person, leading with the rule so that a list of them can be read down.
func (Finding) Unsupported ¶
Unsupported reports whether the finding is about something this engine does not implement.
type FontSet ¶
type FontSet interface {
// Face returns the face for a family in a weight and style, and whether the
// set has it. The family is matched case-insensitively, as CSS matches one.
Face(family string, bold, italic bool) (*shape.Face, bool)
}
FontSet supplies the faces a document is set in.
A set is asked for a family by name and answers whether it has one. It is not asked to do the fallback itself: which families to try, in what order, and what to do when none of them is there are decisions the engine makes and reports on, and a set that made them silently would be a set that could hide a substitution.
func StandardFonts ¶
func StandardFonts() FontSet
StandardFonts is a FontSet of the fourteen faces every PDF reader has built in.
They cost nothing to use — a standard font is named in the file rather than embedded — and they cover Latin, which makes them the right default for a document that has not said what it wants. They cover nothing else, and a document that needs more will be told, which is the point of reporting a missing glyph rather than drawing a box.
type Fragment ¶
type Fragment struct {
// Box is what generated this fragment.
Box *Box
// BorderRect is the border box in absolute page coordinates: the area a
// background paints and a border draws on.
BorderRect Rect
// Margin, Border and Padding are the resolved edges. They are kept rather
// than folded into rectangles because painting needs them separately — a
// border is drawn on its own width — and because the guardrails ask about
// them by name.
Margin, Border, Padding Edges
Children []*Fragment
// Lines is the inline content of a block container, in the same coordinates
// as its children.
//
// A fragment has children or lines, with one exception: a float is out of
// flow, so a block whose in-flow content is entirely inline can still have
// floated children beside those lines. The anonymous box rule deliberately
// does not wrap a float, because wrapping it would put it in a different
// formatting context from the text meant to run around it.
Lines []LineFragment
// Marker is the bullet or number a list item generates, nil otherwise. It
// is on the fragment rather than in the box tree because its text depends
// on the item's position among its siblings, which is not a property of the
// box.
Marker *Marker
// Offset is CSS 2.1 §9.4.3's relative displacement: how far the box is drawn
// from where the flow put it.
//
// It is carried rather than folded into BorderRect because folding it in
// during layout would be the classic way to get relative positioning wrong.
// A relatively positioned box still *occupies* its original space, so every
// question layout asks afterwards — where the next sibling goes, how tall
// the parent is, which band a line box has — must be answered against the
// unoffset position. Applying the offset in absolutise, which visits each
// fragment once and already translates each subtree by its parent's origin,
// moves the box and everything inside it and moves nothing else.
Offset Point
// contains filtered or unexported fields
}
Fragment is a box with a resolved geometry.
A box produces one fragment here. It will produce more than one when content can break — a paragraph across two columns, an inline across two lines — which is why this is a fragment rather than simply a laid-out box, and why the type is named for the thing that can be plural.
func Layout ¶
Layout positions a box tree inside an available width, returning the root fragment.
avail is the content box of the page: what §11 calls the page box minus its margins. Only the width constrains layout — the height is what comes out, and whether it fits is §5's scale-to-fit question, asked afterwards.
set supplies the faces; a nil one uses the fourteen standard faces, which need no embedding and cover Latin.
func (*Fragment) ContentRect ¶
ContentRect is the area the children were laid out in.
func (*Fragment) MarginRect ¶
MarginRect is the border box plus its margin, which is the space the box actually occupies in its parent's flow.
func (*Fragment) PaddingRect ¶
PaddingRect is the border box minus its border.
type Inner ¶
type Inner uint8
Inner is what formatting context a box establishes for its children.
const ( // InnerFlow is ordinary block-and-inline layout. InnerFlow Inner = iota // InnerFlowRoot is the same, but as an independent formatting context — // what an inline-block establishes, and what stops margins collapsing // through it. InnerFlowRoot // InnerFlex, InnerTable and the table-internal contexts are named here and // laid out later; naming them now is what lets the box tree be built once. InnerFlex InnerTable InnerTableRowGroup InnerTableRow InnerTableCell InnerTableCaption InnerTableColumnGroup InnerTableColumn // InnerText is a run of text, which has no children and no context. InnerText )
type Input ¶
type Input struct {
// HTML is the document source.
HTML string
// CSS is the author's stylesheets, in the order they apply.
CSS []Stylesheet
// Policy chooses what each rule does. A nil policy uses the defaults.
Policy Policy
// UserCSS is a stylesheet applied on the reader's behalf, which sits
// between the engine's defaults and the author's. It is separate from CSS
// because its origin is different, and origin is the strongest term in the
// cascade.
UserCSS string
// Resources supplies the bytes of the files the document refers to — the
// images an <img> or a background-image names, and the stylesheets a
// <link rel=stylesheet> does.
//
// A nil resolver loads nothing, which is the deliberate default: a document
// is untrusted input, and "src" and "href" are strings in it. See
// resource.go for what a resolver may and may not do, and NewDirResolver
// for the contained filesystem one.
Resources ResourceResolver
// Fonts is the caller's font library: the faces a document may name and
// have, before it brings any of its own. A nil set is the fourteen standard
// PDF faces.
//
// It is on the input rather than on the render options because an
// @font-face is part of the *document*, and the set a document is laid out
// in is the caller's library with the document's own faces over it. Build
// puts the two together and returns the result as Built.Fonts, which is
// what a caller calling Layout directly must pass on.
Fonts FontSet
}
Input is a document and the stylesheets to apply to it.
type LineFragment ¶
type LineFragment struct {
// Rect is the line box in the same coordinates as the fragment holding it.
Rect Rect
// Baseline is the distance from the top of the line box to the baseline the
// text sits on. Painting needs it, and it is not derivable afterwards —
// half-leading is split above and below the text.
Baseline style.Unit
// Runs are the pieces of text on the line, in *reading* order — the order
// they were written, which on a line that mixes directions is not the order
// they are drawn in. Where each one goes is its own X.
//
// The two are kept apart on purpose. The order here is the order the runs
// reach the content stream, and so the order a reader extracting text from
// the finished page gets them in; a right-to-left paragraph has to be drawn
// the way it reads and copied out the way it was written, and only the X
// decides the first.
Runs []TextRun
// Boxes are the fragments of the inline boxes that have a background or a
// border on this line, in tree order — so an inner box's decoration is
// painted over the box it is inside.
//
// They are fragments rather than a shape of their own because everything that
// paints a background already works on one, and they hang from the line
// rather than from the block's children for two reasons: Appendix E paints
// them with the line's own content and not with the block backgrounds, and
// one inline box produces one of them *per line* — see inlinepaint.go for
// §8.6's slice model, which is the whole of why this is plural.
//
// A box with nothing to draw has none. The overwhelming majority of the
// inline boxes in a document are an <em> or an <a> with no background and no
// border, and a rectangle for each of them on each line would be work in
// proportion to the document that nothing would ever read.
Boxes []*Fragment
}
LineFragment is a line box: one row of text within a block.
type Marker ¶
type Marker struct {
Text string
Face *shape.Face
Size style.Unit
// At is the origin of the marker's baseline, relative to the fragment's
// border box.
At Point
// Color is the item's own text colour: a marker takes the colour of the
// text it belongs to, which is why an author never sets it separately.
Color style.RGBA
}
Marker is the bullet or number drawn beside a list item.
type Op ¶
type Op interface {
// contains filtered or unexported methods
}
Op is one primitive of the display list.
The set is deliberately small. Anything a backend cannot draw directly is something this stage should have decomposed — a border is four filled bands rather than a "border" primitive, because a backend that had to understand border-collapse would be a second layout engine.
func Paint ¶
Paint turns a fragment tree into a display list, in painting order.
The order is CSS 2.1 Appendix E. It used to be tree order, with a note saying that this would stop being true the moment positioning arrived, and it has: tree order is painting order only while nothing is out of flow and nothing asks to be painted somewhere else in the stack. Both are now possible, so what is here is the real algorithm.
What a stacking context is, and what it is not ¶
§9.9 and Appendix E divide the tree into *stacking contexts*, each of which is painted as an atomic unit in the eight steps of §E.2. The root element makes one. So does a positioned box with a z-index that is not auto — and only that, which is the distinction the whole scheme turns on and the one that reads as a technicality until it bites. A positioned box with "z-index: auto" is painted as a unit, at the same level as one with "z-index: 0", but it does *not* make a stacking context: its own positioned descendants are hoisted out and sorted against its siblings rather than against each other. So a descendant with "z-index: -1" paints behind an ancestor whose z-index is auto and in front of one whose z-index is 0, from the same markup. Collapsing auto onto 0 gives a page where that descendant is simply not visible, which looks like a missing box rather than like a stacking bug.
What each step of Appendix E is for ¶
The eight steps exist to make three guarantees that tree order alone does not. Backgrounds of every ordinary block in a subtree are painted before any of the text in it, so a later sibling's background cannot cover an earlier one's words. Floats are a layer of their own between the two, so a floated image sits over the block backgrounds it overlaps and under the text that runs around it. And everything positioned is painted after everything that is not, which is what makes "position: relative" with no offsets at all a way to lift a box above its neighbours — a fact that looks like an accident of the specification and is relied on constantly.
What is not done ¶
Opacity and transforms also create stacking contexts and are not implemented, so neither appears here, and step 2 is a background image, which nothing draws yet. Every other step of §E.2 is present, reduced to the primitives this engine emits.
type Options ¶
type Options struct {
// Page is the sheet. The zero value is A4 with a 20mm margin.
//
// The faces are not here. They are on Input, because a document brings its
// own with @font-face and the set it is laid out in is the caller's library
// with the document's faces over it — see Input.Fonts and Built.Fonts.
Page PageSize
// MinScale is the floor §6.1 puts under scale-to-fit. A document that had
// to be shrunk past it is refused rather than produced illegibly. Zero uses
// the default of 0.5.
MinScale float64
// MinFontSizePt is the floor under an effective font size, in points. Zero
// uses the default of 6.
MinFontSizePt float64
// AllowScaleUp lets an underfull page be enlarged to fill the sheet. It is
// off by default because it is surprising and it degrades images.
AllowScaleUp bool
}
Options is what a caller can say about the sheet and about how hard the engine may try to make a document fit on it.
type Outer ¶
type Outer uint8
Outer is how a box participates in its parent's formatting context: as a block, as something in a line, or not at all.
const ( // OuterNone is "display: none". The element and everything inside it // produce no boxes, which is different from producing an invisible one: // nothing inside is laid out, measured or painted. OuterNone Outer = iota // OuterBlock takes a line of its own. OuterBlock // OuterInline sits in a line with its siblings. OuterInline )
type PageSize ¶
type PageSize struct {
// Width and Height are the whole sheet.
Width, Height style.Unit
// Margin is the space left around the content.
Margin Edges
}
PageSize is the sheet a document is laid out onto.
func PageSizePt ¶
PageSizePt builds a page size from a width and height in points, which is how paper is conventionally measured.
func (PageSize) Content ¶
Content is the area a document is laid out in: the sheet minus its margins.
func (PageSize) WithMarginPt ¶
WithMarginPt returns the page with a uniform margin in points.
type Policy ¶
Policy is a caller's choice of severity per rule. A rule absent from it keeps its default.
type PositionScheme ¶
type PositionScheme uint8
PositionScheme is the value of the position property.
It is named for the scheme rather than simply "Position" because a box also has a position in the geometric sense, and a field called Position holding "relative" beside a field called BorderRect holding a rectangle is a sentence nobody reads correctly twice.
const ( // PositionStatic is the normal flow: the box goes where the flow puts it and // the four offset properties do not apply to it at all. PositionStatic PositionScheme = iota // PositionRelative lays the box out in the normal flow and then offsets it // visually, leaving the space it occupied behind. PositionRelative // PositionAbsolute takes the box out of the flow entirely and resolves it // against the padding box of its nearest positioned ancestor. PositionAbsolute // PositionFixed is PositionAbsolute against the page box. In a medium with a // viewport that can scroll the two differ; in a paged one the viewport is // the page, and they do not. PositionFixed )
func (PositionScheme) String ¶
func (p PositionScheme) String() string
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
Recorder collects findings under a policy.
It applies the policy at the point of recording rather than at the end, so a rule set to Ignore costs nothing to raise — which matters because the callers are the inner loops of layout, and a guardrail that is expensive to check is one that gets checked less often than it should.
func NewRecorder ¶
NewRecorder prepares to collect findings under a policy. A nil policy uses the defaults.
func (*Recorder) Count ¶
Count returns how many times a rule fired, including occurrences that were deduplicated or dropped past the bound.
This is what lets a report say "flex-wrap was dropped 412 times" while showing the finding once, which is more useful than either the one or the four hundred on their own.
func (*Recorder) Findings ¶
Findings returns what was recorded, in a deterministic order.
The order is by rule, then by where in the input the finding came from, then by message — the same shape internal/finding.Sort gives every validator, because two runs over the same document must produce the same slice and several of the stages above range over maps.
func (*Recorder) Report ¶
Report records a finding, applying the policy.
It reports whether the finding was at Error severity, which is what a caller in a position to stop early wants to know.
func (*Recorder) ReportDetail ¶
ReportDetail records a finding that carries more than a message.
The Severity field of f is ignored — it is filled in from the policy, because a caller raising a finding should not be able to decide how serious it is. That decision belongs to whoever is rendering.
type Rect ¶
Rect is a rectangle: a position and an extent.
It is stored as position-and-size rather than as two corners because layout computes it that way — a box is placed and then given a width — and because a rectangle with a negative extent is then representable, which is worth being able to see rather than normalising away.
func (Rect) Contains ¶
Contains reports whether inner lies entirely within r.
It is what the overflow guardrails ask, so its edge behaviour matters: a box exactly filling its container is contained, since a border that lands on the boundary is not overflow.
func (Rect) Inset ¶
Inset shrinks a rectangle by an edge on each side, which is what stepping in from a border box to a padding box to a content box does.
The result is clamped to a non-negative extent. A padding wider than the box it is inside produces an empty content box rather than an inside-out one, and an inside-out rectangle is the shape that makes every later comparison give a plausible wrong answer.
func (Rect) Intersect ¶
Intersect is the rectangle two rectangles have in common.
Two rectangles that do not overlap produce one with a negative extent, which Empty reports and every consumer treats as nothing. It is not normalised to zero, for the reason Rect itself is not: a value that says "less than nothing" is worth being able to see.
The arithmetic cannot wrap. Every operation on a Unit saturates at the ends of its range, so the worst an intersection of two extreme rectangles can produce is a saturated extent — never a negative width that reads as a very large one, which is the failure that would turn a clip into an amplifier.
type ReplacedContent ¶
type ReplacedContent struct {
// Image is the decoded picture.
Image image.Image
// Width and Height are the intrinsic dimensions: the image's own pixel
// size, one image pixel to one CSS pixel.
Width, Height style.Unit
// Ratio is the intrinsic ratio, width divided by height, and is zero when
// there is none. It is kept as a number rather than recomputed from the two
// dimensions because CSS 2.1 §10.3.2 distinguishes an element that has a
// ratio from one that has dimensions, and a format can supply either
// without the other.
Ratio float64
// Key identifies the source bytes, so that a document naming one file
// twenty times embeds one image. It is a hash of the bytes rather than the
// reference, which also collapses the same picture reached by two names.
Key string
// Pixels is the image's pixel count, which is what the document budget was
// charged.
Pixels int64
}
ReplacedContent is what makes an element replaced: content with dimensions of its own that layout sizes rather than lays out.
It is a value on the box rather than a subclass of it because "replaced" is a property of the element's content and not of its box type — a replaced element is still inline or block, still floats, still positions, and every rule about those applies unchanged. The one thing that differs is where its width and height come from, which is what this carries.
type ResourceResolver ¶
type ResourceResolver interface {
// Resolve returns the bytes of the resource a document referred to.
Resolve(ref string) ([]byte, error)
}
ResourceResolver turns a reference written in a document into bytes.
It is deliberately not an io.Reader factory or a URL fetcher. A resolver is handed the reference exactly as the document wrote it, with no scheme and no leading slash — those are refused before it is called — and returns the whole resource or an error. Returning an error is normal: a missing image is a finding, not a failure of the render.
A resolver must bound what it returns. The engine caps what it will decode, but it cannot cap what a resolver allocates before returning, so a resolver reading from anywhere unbounded has to impose its own limit. DirResolver does.
type Rule ¶
type Rule string
Rule identifies a guardrail.
The identifiers are the ones §6 names, and they are strings rather than an enumeration because they travel: they are what a caller matches on, what a configuration file names, and what a report is grouped by. An integer would be none of those.
const ( // RuleUnsupportedProperty is a declaration parsed and then not applied. // §6.3 argues this is the highest-value guardrail for the least cost, and // it is: an engine implementing a subset *will* ignore declarations, and a // page where flex-wrap was dropped is plausible and wrong. RuleUnsupportedProperty Rule = "unsupported-property" // RuleUnsupportedElement is an element the engine does not lay out. RuleUnsupportedElement Rule = "unsupported-element" // RuleUnsupportedSelector is a selector outside the implemented subset, so // the rule using it never applied. RuleUnsupportedSelector Rule = "unsupported-selector" // RuleUnsupportedAtRule is an at-rule the engine does not act on. RuleUnsupportedAtRule Rule = "unsupported-at-rule" // RuleUnsupportedValue is a value that is correct CSS the engine cannot // resolve — a unit needing font metrics, a colour in a space needing // conversion. RuleUnsupportedValue Rule = "unsupported-value" // RuleInvalidMarkup and RuleInvalidCSS are input the engine refused. They // are not the same as the unsupported rules and must not be reported as // them: one says the author wrote something wrong, the other says the // engine does not do something. An author sent to the wrong one of those // looks for the wrong thing. RuleInvalidMarkup Rule = "invalid-markup" RuleInvalidCSS Rule = "invalid-css" // RuleFontFallback is a requested family that was not available, so the // text was set in something else. The metrics and the line breaks differ, // and nothing about the page says so. RuleFontFallback Rule = "font-fallback" // RuleUnsupportedScript is text this engine cannot break or order // correctly. §6.3 makes it an error by default, and is right to: unbroken or // unordered text still looks like text, so the failure mode looks like // success. RuleUnsupportedScript Rule = "unsupported-script" // RuleGlyphMissing is a character no available face has a glyph for. Tofu is // the purest form of silent garbage — a box where a letter should be, which // a reader blames on their PDF viewer. RuleGlyphMissing Rule = "glyph-missing" // The size thresholds of §6.1, which are checkable exactly because §5's // scaling is geometric: the effective size of an element is its natural size // times one number, so a threshold is a multiplication rather than an // iteration. // // RuleMinScale is the blunt one and probably the most useful: if the content // had to be shrunk past half to fit, the document is wrong, and no // per-element threshold is needed to say so. RuleMinScale Rule = "min-scale" // RuleMinFontSize is text that would be set below a legible size. RuleMinFontSize Rule = "min-font-size" // The layout-integrity rules of §6.2, which are about the geometry rather // than about a size. // // RuleUnbreakableOverflow is atomic content wider than the box holding it: a // long URL, a nowrap run, an oversized image. §6.2 calls it the classic // silent clip, and it is — the text is there, the box is there, and the part // past the edge is simply not drawn. RuleUnbreakableOverflow Rule = "unbreakable-overflow" // RuleTableColumnUnderflow is a table column narrower than the content in // it, so the content is cut off at the column edge. // // It is the table-shaped form of the silent clip §6.2 is named after, and it // has its own identifier because it has its own cause and its own fix: the // fixed table layout of §17.5.2.1 deliberately ignores what is in the cells, // so a column can end up narrower than its content and the specification // says so. That is a trade an author may want and may not know they made — // the table looks tidy and a word is missing from it. RuleTableColumnUnderflow Rule = "table-column-underflow" // RulePositionApproximated is a positioned box this engine placed by a // weaker rule than the one that applies to it. // // It is its own rule rather than an unsupported-value because the value *was* // supported: "position: absolute" was honoured, the box was taken out of the // flow and given offsets, and only the rectangle those offsets were measured // from is not the one §10.1 names. That produces the most deceptive shape of // wrongness this engine has — a box that is manifestly positioned, in a place // that looks deliberate, some tens of pixels from where the author put it. // Telling an author their declaration was ignored would send them looking for // a feature that is there. RulePositionApproximated Rule = "position-approximated" // RuleControlApproximated is a form control laid out as the static box a // printed page has, where that box is visibly not the widget a browser // would draw. // // It is its own rule rather than an unsupported-element because the element // *was* laid out: it has its size, its chrome and whatever text the markup // gave it, and it takes part in the flow like any other box. What is missing // is a widget — a slider's thumb at a position, the mark inside a checked // box, the options a drop-down is not showing — and every one of those is a // piece of information the document carries and the page does not. // // It fires only where the difference shows. A text field with a border round // its value is what a browser prints, so a text field says nothing; a rule // that fired on every control would be one nobody reads. RuleControlApproximated Rule = "control-approximated" // RuleOverflowPage is content outside the page box after scaling. // // It should be unreachable given §5: the scale is computed so that // everything fits. So it is a self-check as much as a guardrail — if it // fires, the scale computation is wrong, which is worth hearing about far // more than the overflow itself. RuleOverflowPage Rule = "overflow-page" // RuleResourceBlocked is a file the document referred to and this engine did // not load: there was no resolver, the reference named a URL scheme, it // pointed outside the directory the resolver was rooted at, or it could not // be read. // // It is its own rule rather than an unsupported-element, because the element // *was* laid out — an <img> whose image did not arrive is still a box, still // takes part in the line, and still shows its alt text. What is missing is // the picture, and a page with a rectangle of nothing where a chart belongs // is the silent failure §6 is named after. RuleResourceBlocked Rule = "resource-blocked" // RuleImageUndecodable is a resource that was loaded and did not become an // image: a format this engine has no decoder for, bytes that do not parse, // or a picture larger than it will decode. // // The last of those is the one worth having a rule for. A ten-kilobyte PNG // may declare sixty thousand pixels on a side, and refusing it is the only // safe answer — but the refusal has to be visible, or a document that a // caller believes contains a photograph contains a gap instead. RuleImageUndecodable Rule = "image-undecodable" // RuleFontUndecodable is a font an @font-face named, that arrived, and that // did not become a face: a container this engine does not unwrap, a format // hint naming one, or bytes that are not a font program. // // It is separate from RuleResourceBlocked for the same reason // image-undecodable is: "the file did not arrive" and "the file arrived and // was not usable" send an author to different places, and a font is the // case where the second is most likely — the web serves woff2 to everything // and this engine reads sfnt. RuleFontUndecodable Rule = "font-undecodable" // RuleLimit is a resource guard that tripped, or a run that was cancelled. // // It is spelled the same as internal/finding.LimitRule, and deliberately so: // every other part of pdf0 already reports "we stopped short" under that // identifier, and a caller that distinguishes "the input is bad" from // "pdf0 could not finish" should not have to learn a second spelling for // the second one. RuleLimit Rule = "limit" )
The rules this engine can currently report.
This is deliberately *not* the whole catalogue of §6. The geometry rules — min-font-size, unbreakable-overflow, overflow-page and the rest — arrive with the layout that can violate them, each together with the test that plants a violation and watches it fire. §6.5 asks for exactly that, and declaring a rule before anything can raise it would produce the decoration it warns against: an identifier in a catalogue that has never been seen to fire proves nothing at all. TestEveryRuleIsReachable holds that line.
type Severity ¶
type Severity uint8
Severity is what a rule does when it fires.
const ( // Ignore drops the finding entirely. It is not the default for anything — // a caller has to ask for silence. Ignore Severity = iota // Warn records the finding and lets the render finish. Warn // Error records the finding and makes the render fail, so no document is // returned. This is for the cases where a produced document would be worse // than none: one that looks finished and is not. Error )
type Source ¶
type Source struct {
// HTMLOffset is a byte offset into the HTML, or -1.
HTMLOffset int
// CSSOffset is a byte offset into the stylesheet, or -1.
CSSOffset int
// Sheet names which stylesheet CSSOffset is in, when there is more than
// one. It is empty for the document's own.
Sheet string
}
Source says where in the input a finding came from.
Both offsets are byte offsets into the document the caller supplied, and both are -1 when they do not apply. That is what lets a caller point an author at the markup or the stylesheet rather than at a description of it, and it cannot be recovered afterwards — which is why the html, css and style packages carry offsets at all.
type Stylesheet ¶
type Stylesheet struct {
// Name identifies the sheet in a finding — a filename, usually. It is empty
// for the document's own <style> content.
Name string
// Source is the CSS.
Source string
}
Stylesheet is one stylesheet with a name to report against.
type TextRun ¶
type TextRun struct {
Text string
// Face is what it is set in, and Size the font size.
Face *shape.Face
Size style.Unit
// X is the offset from the left of the line box, and Width the advance.
X, Width style.Unit
// Box is the inline box the text came from, which carries the colour and
// the decoration painting will need.
Box *Box
// Decorations are the lines ruled across this run: CSS 2.1 §16.3.1's
// underline, overline and line-through. They are on the run rather than
// derived from Box at paint time because a decoration belongs to whichever
// *ancestor* declared it, and that box's colour is the line's colour — see
// textdecoration.go, where the difference between propagating and inheriting
// is worked through.
Decorations []textDecoration
// RTL says the run reads right to left, so its glyphs are drawn from the
// right edge of its box towards the left and its brackets are mirrored.
//
// It is on the run rather than derived from the text because it is not a
// property of the text: a run of punctuation between two Hebrew words is
// right-to-left and has nothing in it that says so. The algorithm decided it
// from the neighbours, which are other runs by the time anything paints this
// one.
RTL bool
// LetterSpacing is what letter-spacing added after each character of this
// run. It is carried into painting as well as into the width because the two
// have to agree: the width decided where the next run starts, and glyphs
// drawn without the same spacing would leave a gap the size of the whole
// run's spacing before it.
LetterSpacing style.Unit
// Offset is §9.4.3's relative displacement, accumulated over the inline
// boxes this run sits inside.
//
// It is on the run rather than on a single fragment because a <span> that
// spans a line break has one fragment per line: a line box holds runs, and
// the box's own background and border are a Boxes entry on each of the lines
// it reaches. Both are moved by the same displacement and are given it
// separately — this one at paint time, the fragment's in absolutise, because
// a background image is placed against the rectangle its box is drawn at.
Offset Point
// Shift is how far this run's own baseline sits below the line box's,
// which is §10.8.1's vertical-align applied to the inline box the run came
// from. It is negative for a run that is raised.
//
// It is a length on the run rather than a line of its own because a line
// box has exactly one baseline — §10.8's alignment is *against* that
// baseline — and every run on the line is placed relative to it. Folding it
// into Offset would have been shorter and is wrong: Offset is §9.4.3's
// relative positioning, which moves a box after layout without changing
// anything about the line, whereas this displacement is part of what
// decided the line's height.
Shift style.Unit
}
TextRun is a piece of text on a line, set in one face at one size.
type TileImage ¶
type TileImage struct {
// Clip is the area painted. Nothing is drawn outside it, including the part
// of a tile that reaches past it.
Clip Rect
// Tile is the first tile: its position and its size.
Tile Rect
// StepX and StepY are the distance to the next tile on each axis, and are
// always greater than zero. An axis that does not repeat has a step of the
// tile's own size and a Clip no wider than one tile, so the neighbouring
// cells fall outside — which is what keeps a backend from needing a case for
// "does not repeat".
StepX, StepY style.Unit
Image image.Image
// Key identifies the source bytes, so a backend embeds one picture once.
Key string
}
TileImage paints a picture repeatedly across a rectangle.
It is one operation for a whole tiling rather than one per tile, and that is a decision about safety rather than about tidiness. The number of tiles is (area / tile size), and a stylesheet chooses both: "background-size: 0.001px" with "repeat" over an A4 page is four hundred billion placements. An engine that emitted one operation each would allocate until it died on a document an attacker wrote, and no cap on the *document* bounds it, because nothing in the document says the number.
So the tiling leaves layout as a description — where the first tile is, how far apart they are, and what area they may be drawn into — and the count appears nowhere. A backend expands it with the mechanism it has: PDF has tiling patterns, which are exactly this value. The count is still checked against maxBackgroundTiles before this is built, because whatever expands it is entitled not to be handed four hundred billion cells.
Source Files
¶
- background.go
- bidi.go
- border.go
- bordercollapse.go
- box.go
- boxsizing.go
- clamp.go
- content.go
- control.go
- counter.go
- finding.go
- flatten.go
- float.go
- floatindex.go
- font.go
- fontface.go
- geom.go
- image.go
- inline.go
- inlinepaint.go
- intrinsic.go
- layout.go
- line.go
- linemetrics.go
- marker.go
- page.go
- paint.go
- paragraph.go
- pipeline.go
- position.go
- quotes.go
- replaced.go
- resource.go
- spacing.go
- stylesheet.go
- table.go
- tablelayout.go
- textalign.go
- textchecks.go
- textdecoration.go
- uastyle.go
- visibility.go
- visualeffects.go