Inserts: floats and footnotes
htmlbag’s insert system handles content that gets lifted out of the normal body flow and placed at a specific edge of the page on which the marker was encountered. Three classes share one mechanism:
- Footnotes — bottom-of-page stack with separator rule and running number.
- Top-floats — top-of-page stack, no in-text glyph.
- Bottom-floats — bottom-of-page stack just above the footnote zone, no in-text glyph.
All three can coexist on a single page.
Painting order
A page is painted in four layers, top to bottom:
┌──────────────────────────────┐
│ Top-float stack │ ← elements with float: top|before
├──────────────────────────────┤
│ Body │ ← buffered paragraph flow
│ │
├──────────────────────────────┤
│ Bottom-float stack │ ← elements with float: bottom|after
├──────────────────────────────┤
│ Separator rule │
│ Footnote stack │ ← <fn> / class="footnote"
└──────────────────────────────┘The body cursor starts below the top-float stack and stops above the bottom-float and footnote zones; how much room each layer reserves is known before the body is painted, thanks to the two-pass page assembler (see Architecture below).
Footnote markup
<fn> (or any element with class="footnote") anchors a footnote at
the position where it appears in the body text. The footnote body is
lifted out, formatted with a running number, and placed at the bottom
of the page; the marker position in the body becomes a superscripted
call number.
<p>Body text<fn>Footnote body content.</fn> continues here.</p>The CSS counter footnote is incremented automatically.
Footnote layout knobs
Several CSSBuilder fields tune the footnote area; see
Library configuration.
Float markup
Floats are detected via the CSS float property — never via a tag
name. Both inline and block forms work:
<!-- inline (anchor inside a paragraph) -->
<p>Body<span style="float: top">Top-float content.</span> continues.</p>
<!-- block (between paragraphs, can carry full block content) -->
<div style="float: top">
<p>First float paragraph.</p>
<p>Second float paragraph.</p>
</div>Recognised values:
| Value | Class | Where it lands |
|---|---|---|
top |
top-float | top-of-page stack |
bottom |
bottom-float | above footnote zone |
Standard CSS float: left|right (side-floats with text wrap) is not
implemented.
Inline vs. block floats
| Form | HTML5-compliant | Multi-block content |
|---|---|---|
<span style="float: top"> (inline) |
✓ | inline only |
<div style="float: top"> (block) |
✓ | yes — paragraphs, tables, lists |
The inline form lets you anchor a float inside the running text where
the prose mentions it; the float body itself can only carry inline
content. The block form is the only way to put multi-paragraph content
(e.g. figure with caption) inside a float, since the HTML5 parser
auto-closes a <p> when a <div> appears as a direct child.
Stack order
Within a single class, floats stack in document order — the first marker encountered ends up at the top of its stack.
Multiple floats per page
The two-pass page assembler accumulates float reservations as the body flows; once the page is full, the buffered body and reservations ship together. Three figures from three paragraphs all share one page if they fit. There’s no per-float page break — the page just carries as many floats as the content allows.
Layout knobs
| Field | Default | Effect |
|---|---|---|
FootnoteSeparatorHeight |
0.4pt | thickness of the rule above footnotes |
FootnoteSeparatorSkip |
6pt | gap between body and the rule |
FootnoteInterSkip |
2pt | gap between consecutive footnotes |
FootnoteCallSizeRatio |
0.7 | superscript call size / body size |
FootnoteCallRiseRatio |
0.4 | call rise (PDF Ts) / body size |
FloatTopInterSkip |
6pt | gap between top floats / under stack |
FloatBottomInterSkip |
6pt | gap between bottom floats / over stack |
All are settable on CSSBuilder after New().
Architecture: two-pass page assembly
Body content does not go directly into the PDF — it is buffered in
CSSBuilder.pageBuf while the page builder iterates. Float and
footnote markers are committed to per-class accumulators
(pageInserts[InsertFloatTop], pageInserts[InsertFootnote], etc.).
When the page fills (the trial sum of top-stack + body + bottom-stack
- footnote-stack would exceed the content area), the page is shipped:
flushInsertspaints top-floats, then the buffered body below them, then bottom-floats above the footnote zone, then footnotes, then the PDF page is closed.
This lets the body cursor start at the correct y-position on every page — below the final top-float reservation — even though the floats may be encountered after some body content has already been buffered. A streaming page builder (without buffering) cannot do this, which is why earlier versions of htmlbag had to force a page break before each float-bearing paragraph.