Table of contents
tc-lib-pdf renders a subset of HTML and CSS directly, with no browser engine involved. Markup goes into a placed block on the page, and the engine resolves the cascade, the box model, floats, tables, and page breaks itself.
Table of contents
The Two Entry Points
public addHTMLCell(
string $html,
float $posx = 0,
float $posy = 0,
float $width = 0,
float $height = 0,
?TCellDef $cell = null,
array $styles = [],
): void
public getHTMLCell(/* the same parameters */): string
addHTMLCell() writes the result into the document. It accounts for automatic page and region breaks, appending content to each page stream the block reaches, so a block longer than the remaining space continues onto the following pages.
getHTMLCell() returns the PDF operators as a string and writes nothing. Append them yourself:
$pdf->page->addContent($pdf->getHTMLCell($html, 15, 15, 180));
Use getHTMLCell() when the content fits on the current page and you want control over where the operators land, for instance inside an XObject template or between other page content. Use addHTMLCell() for anything that may span a page break.
Both take positions and dimensions in the document unit selected on the constructor. The $cell argument overrides the padding and margin for this call alone, in points. $styles carries the cell border styles.
A minimal call:
$pdf->addHTMLCell(
html: '<h1>Invoice 2026-0184</h1><p>Due on 30 September 2026.</p>',
posx: 15,
posy: 20,
width: 180,
);
A font must be inserted and added to the page before any text renders. See /docs/fonts/ .
Where the Styles Come From
Three author sources are resolved in the normal cascade order:
- The global stylesheet, registered once and injected into every rendering call as the lowest-priority author source.
<style>blocks inside the markup passed to the call.styleattributes on individual elements.
A document rule beats a global rule at equal specificity. To force a global rule to win, mark it !important.
$pdf->setGlobalCSS('
body { font-family: helvetica; font-size: 10pt; color: #222; }
.card { border: 0.5pt solid #999; padding: 4pt; }
');
$pdf->addGlobalCSS('.badge { background-color: #eef; font-weight: bold; }');
| Method | Effect |
|---|---|
setGlobalCSS(string $css) | Replaces the global stylesheet. The argument has no surrounding <style> tags. |
addGlobalCSS(string $css) | Appends to the current global stylesheet. |
resetGlobalCSS() | Clears it. |
The global CSS goes through the same parser as document CSS, so @import, @media, and @spot rules work there too.
The Supported CSS Subset
The engine covers the parts of CSS that a paged document needs. /examples/E073_css_supported_categories/ enumerates the categories in its own markup and renders them, which makes it the reference for what actually works in the release you have installed.
- Cascade: source order, specificity, shorthand expansion and normalization, and the
inheritkeyword including its shorthand forms. - Selectors: type, class, id, descendant, child, the adjacent and general sibling combinators (
+and~), attribute selectors, and a subset of pseudo-classes including:empty. - Pseudo-elements:
::beforeand::afterwith text content. - Box model: margin, padding, border, width and height, and
inline-block. - Flow: floats,
clear, and positioning, plusfieldsetandlegend. - Typography:
font-size-adjust,font-variant,quotes,overflow-wrap,letter-spacing,font-stretch, andline-height. - Paged media:
orphans,widows, page break control, and theprintmedia type. - Colour: everything tc-lib-color parses, including spot colours addressed from CSS for prepress work.
CSS flexbox and grid are not implemented. Layout that depends on them needs a different tool; /comparison/ covers where the headless-browser renderers fit.
Defaults You Can Change
The engine applies its own defaults before the cascade runs. These set them:
| Method | What it controls |
|---|---|
setDefaultCSSMargin(float $top, $right, $bottom, $left) | Default element margin. |
setDefaultCSSPadding(float $top, $right, $bottom, $left) | Default element padding. |
setDefaultCSSBorderSpacing(float $vert, float $horiz) | Default table border spacing. |
setDefaultCellMargin(float $top, $right, $bottom, $left) | Default cell margin, stored in points. |
setDefaultCellPadding(float $top, $right, $bottom, $left) | Default cell padding, stored in points. |
setDefaultCellBorderPos(float $borderpos) | BORDERPOS_DEFAULT, BORDERPOS_EXTERNAL, or BORDERPOS_INTERNAL. |
setHtmlVSpace(array $tagvs) | Vertical space before and after each tag, as a fixed height or a number of line heights. |
setHTMLMonospaceFont(string $fontname) | The font used for <pre>, <code>, and <tt>. |
setHtmlImageImportResize(bool $enabled) | Import raster images at a reduced pixel size when they render smaller than their intrinsic size. |
setHTMLMonospaceFont() matters for tagged output. PDF/UA requires every font to be embedded, and the default Courier is a non-embedded standard-14 font, so a tagged document needs an embedded monospace font set here after inserting it with the font stack. See /docs/standards/
.
Tables
Tables carry the full structure tags: caption, colgroup, col, thead, tbody, and tfoot. Cells span rows and columns, both border models are supported, and a thead repeats at the top of every page the table crosses.
<table>
<thead>
<tr><th>Item</th><th>Qty</th><th>Amount</th></tr>
</thead>
<tbody>
<tr><td>Consulting</td><td>12</td><td>1,440.00</td></tr>
</tbody>
</table>
Widths accept pixel units as well as the document unit; see /examples/E077_html_table_header_px_unit/ for a header sized in pixels and repeated across pages. Floats and table clearance interact as CSS specifies, which /examples/E043_html_tables/ exercises.
Lists
Ordered, unordered, and nested lists are supported with their CSS list-style properties. /examples/E032_html_lists/ renders the variations.
Forms
Form controls written as markup are mapped to AcroForm widgets, with labels, field flags, and select precedence carried across. The result is an interactive PDF form rather than a picture of one.
<form>
<label for="name">Name</label>
<input type="text" name="name" value="">
<select name="country"><option value="it">Italy</option></select>
</form>
See /examples/E042_html_form/ for the markup side and /examples/E040_annotation_form/ for the widget API underneath.
Flow Across Pages
addHTMLCell() breaks the block at the page boundary and continues on the next page, honouring orphans and widows. /examples/E018_html_page_span/
shows a block long enough to cross several pages.
Two things interact with the flow and are worth knowing:
- Page margins bound the block. A cell placed inside the header or footer band behaves differently from one in the content area; /examples/E078_htmlcell_page_margins/ covers the cases.
- No-write regions and column regions constrain where text may go, and HTML flow respects both. See /examples/E080_no_write_regions/ and /examples/E019_page_regions/ .
Line height is resolved from CSS; /examples/E069_html_line_height/ shows the values and units side by side.
Images and Remote Assets in Markup
An <img> in the markup is loaded through the same file layer as everything else, which means the allowlists apply. A local path outside markupAllowedPaths and a remote host outside allowedHosts are both refused, and the document renders without the image. No exception is raised. See /docs/remote-resources/
.
SVG referenced from markup goes through the same path. For direct SVG placement, use addSVG() or getSetSVG(); see /examples/E030_svg/
.
Tagged and Accessible Output
In a PDF/UA mode the HTML renderer generates the structure tree from the markup: heading levels are mapped, text runs are tagged, alt text becomes /Alt on figures, and form fields get /TU descriptions. /examples/E015_pdfua/
, /examples/E016_pdfua1/
, and /examples/E017_pdfua2/
render the same document under each profile.
To build the tree by hand instead, use beginStructElem() and endStructElem(); see /examples/E063_manual_tag_tree/
. suspendPdfUaTagging() and resumePdfUaTagging() bracket content that should not enter the tree, and the suspensions nest.
Hyphenation
loadTexHyphenPatterns() and setTexHyphenPatterns() load a TeX pattern dictionary that the HTML renderer then applies to wrapped text. Soft hyphens in the markup are honoured on their own. See /examples/E028_text_hyphenation/
and /examples/E064_custom_hyphenation_dictionary/
.
enableZeroWidthBreakPoints(true) lets a line break inside an unbroken run that offers no space to break at, such as a long URL. /examples/E083_text_line_wrap/
renders the same string with the setting on and off.
Malformed Markup
tidyHTML(string $html, string $defcss) runs the input through Tidy and returns corrected markup, raising an exception when Tidy cannot parse it. Markup arriving from a CMS or from user input is worth passing through it before rendering.
Related Guides
- /docs/fonts/ : the font setup every rendering call depends on.
- /docs/remote-resources/ : the allowlists that govern images, fonts, and SVG referenced from markup.
- /docs/standards/ : PDF/UA tagging, and the conformance modes that change what the renderer emits.
- /features/ : the full capability index.
- /examples/ : the nine HTML and CSS examples, and 76 others.