Inserts (floats, footnotes)

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: flushInserts paints 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.