Documentation page: PD4ML v42 — HTML5 Element Support Matrix. This page is also available as Markdown at html5-element-support-matrix.md.

PD4ML v42 — HTML5 Element Support Matrix

Each row describes the v42 engine's behavior: its CSS cascade, its built-in user-agent stylesheet, the presentational hints of legacy attributes, and its layout and painting. Where an attribute or CSS property is parsed but produces no visible effect, it's marked accordingly.

Canonical tag/attribute/CSS-property lists per the WHATWG HTML Living Standard and the CSS specifications. Comparisons against the older v40 engine are stated only where the v42 source itself carries an explicit comment documenting the change — nothing below is an assumption about v40 behavior.

Key cross-cutting findings

The dozen or so facts most worth knowing before relying on this engine for a given document — pulled out of the full matrix below because they cut across many tags or are easy to miss inside a single row.

  • Genuinely implemented — <form> family — real interactive AcroForm generation is wired for v42 (PdfTargetRenderer42#collectFormFields/emitFormFields, gated by PdfRenderContext#generateForms() off PD4ML.generateForms(...)), mirroring v40's own per-renderer scope call-for-call: <textarea> and <select> always get a real widget; <input> gets one for text/password/hidden/checkbox/radio (and any unrecognized type, per HTML5's "defaults to text" rule) but deliberately NOT for submit/reset/button/image/file/color/range; <button> (bare/submit or reset) gets a real SubmitForm/ResetForm push-button action that reads its nearest <form> ancestor's action/method. <label for> is purely cosmetic — no click-association code exists anywhere in this path. Every widget carries its own appearance stream (value, password bullets, wrapped textarea, selected option, highlighted list rows), laid out as Acrobat lays out a field it is editing; the AcroForm sets no NeedAppearances, and the fields' DA font is a non-embedded WinAnsi TrueType font.
  • Genuinely implemented — <meta charset> / http-equiv Content-Type decode the source when the calling code gives no encoding (readHTML(InputStream), readHTML(InputStream, URL), readHTML(URL)); a byte order mark comes first, an encoding passed explicitly by the caller wins, as a transport (HTTP) charset does.
  • Real gap — <plaintext> doesn't switch the tokenizer into a permanent raw-text state — markup after it is still parsed normally, unlike every real browser's (admittedly bizarre) spec-mandated behavior.
  • Real gap — <dialog open> is a complete no-op — content renders identically with or without the attribute, unlike the browser default of dialog:not([open]){display:none}.
  • Real gap — <xmp>, <listing> and <plaintext> get the monospace/white-space:pre rule but no display:block — they lay out as inline elements (side by side, no vertical margins), where browsers make all three blocks like <pre>.
  • Placeholder only — <iframe>, <embed>, <object>, <canvas>, <video>, <audio> all fall through the same generic branch: a sized light-gray box with a thin border and a centered label (the title, else the tag name), no fetch/decode/render of the referenced content at all. The one exception: an <object> with no data but with fallback content shows that fallback content instead.
  • Known limitation — <ol reversed> counts down from the number of <li> children only — it ignores both the list's start and any <li value=N>, unlike the forward-counting path.
  • Known limitation — The HTML5 "strip one leading newline after the opening tag" rule is implemented for <textarea> only — <pre> and <listing> both show a spurious leading blank line real browsers suppress.
  • Known limitation — <wbr> offers no line-break opportunity: a long word with a <wbr> in it overflows its box instead of breaking there (nor does a U+200B zero-width space; a soft hyphen &shy; does break).
  • Genuinely implemented — Flexbox and CSS Grid are real, working layout modes (dedicated FlexLayout/GridLayout), not just parsed-and-ignored. <svg> renders true vector output and <math> renders real MathML via JEuclid — neither is a placeholder.
  • Genuinely implemented — <details>/<summary> disclosure markers, <fieldset>/<legend> notched borders, <picture>/<source> media-query selection, table colspan/rowspan with per-segment collapsed borders, and <select> dropdown/listbox modes are all real, specifically-built implementations, not incidental side effects.
  • Bold nesting — <strong>/<b> use font-weight: bolder in the UA stylesheet, not a literal bold — this resolves relative to whatever weight is already inherited, so nesting under an already-bold/heavy ancestor compounds correctly (matching real browsers) instead of every level clamping to the same fixed weight.
  • PDF/UA structure tagging — headings get real distinct /H1–/H6 types (not a generic Paragraph tag), <section>/<nav>/<main>/<article> get distinct /Sect//Sect//Part//Art types (not one shared generic tag), <blockquote> gets /BlockQuote, <figure> gets /Figure and <figcaption>/<caption> get /Caption, <a> gets /Link, a real <img> and an inline <svg> get /Figure (the image's alternate text from alt, else title; the SVG's from aria-label, title or its own <title>), <math> gets /Formula with alternate text (its alttext, else the formula's text), <ul>/<ol> get /L with each <li> split into /Lbl (the marker) and /LBody, <dl> gets /L with <dt> as /LI+/Lbl and <dd> as /LI+/LBody, and <th> carries its /Scope (from scope, else from its position). The page and canvas fills are marked as artifacts. <aside>/<header>/<footer>/<address> get no structure element of their own — their text is tagged as plain paragraphs.

Document metadata, sectioning & grouping content

The document skeleton, landmarks, headings, and block-level grouping elements.

Tag Status Attributes CSS properties Comments
<html> Full lang (read generically for bidi/hyphenation elsewhere); dir="rtl"|"ltr" (case-insensitive) UA default: block, serif 16px — matches Chrome's actual default font, not a generic sans guess Auto-inserted by the tree builder if omitted.
<head> Full none read display:none Auto-inserted if missing; base/basefont/bgsound/link/meta/title/style/script/template met before any body content go into it. Met after body content has started, they stay where they are in the body (a <style> there still applies), as in browsers.
<title> Full none display:none Content genuinely consumed — Document.getTitle() returns the trimmed, entity-decoded text (RCDATA parsing, tags inside not recognized) of the first <title> in <head>. Used for the PDF document title and e.g. for CLI output filenames. A stray second <title>, or one inside <body>, is ignored.
<base> Full href (real relative-URL resolution); target (no navigation concept in PDF output) display:none Only the first <base href> in the document is honored, matching the HTML5 rule.
<link> Partial rel="stylesheet" (real external CSS loading, gated by config.loadExternalResources); href, media both honored; rel="alternate stylesheet" correctly excluded; other rel values (icon, preload, manifest…) inert display:none The one rel value that matters for a print/PDF target is fully wired.
<meta> Partial charset and http-equiv="Content-Type" (its charset= part); name="author"/"description"/"subject"/"keywords" (DOCX/RTF document properties); other http-equiv values (refresh etc.), name="viewport" not read display:none The encoding declaration is found by an HTML5-style prescan of the first bytes and decodes the source when the calling code gives no encoding: a byte order mark comes first, an encoding passed explicitly by the caller wins, as a transport (HTTP) charset does. viewport having no effect is correct/expected for a paged renderer.
<style> Full type (must be empty/text/css or the block is skipped), media display:none; content parsed as an author-origin stylesheet, incl. @import Cleanly wired into the cascade.
<body> Full text→color, bgcolor→background-color, background→background-image url(), link/vlink/alink→ the colors of the UA's a:any-link/a:visited/a:any-link:active rules; marginheight/marginwidth (and the older topmargin/bottommargin/leftmargin/rightmargin)→margins UA default display:block, no default margin — the page margins already inset the content Legacy presentational attributes fully covered. vlink/alink are read, but nothing is ever visited or active in a static conversion, so only link shows.
<article> <section> <nav> <aside> <header> <footer> <address> <main> Full none tag-specific block (UA); address also gets font-style:italic Flex/grid containers work identically here (layout mode is display-driven, not tag-specific). PDF/UA tagging: section→/Sect, nav→/Sect, main→/Part, article→/Art (each distinct, not one shared generic tag); aside/header/footer/address get no structure element of their own (their text is tagged as plain paragraphs). Accessibility-tagging nuance only, not a visual gap.
<h1>–<h6> Full none Per-level font-size/weight/margin matching Chrome's own defaults exactly (h1 2em…h6 0.67em) Tagged as distinct PDF/UA /H1–/H6 structure types.
<hgroup> Full none display:block only Plain block container — no special heading/subtitle grouping or outline behavior, which is an acceptable simplification for a static renderer.
<p> Full none display:block; margin:1em 0 Implicit-close set (on encountering pre/table/headings/lists/hr/etc.) closely matches the HTML5 spec's own optional-tag rules. PDF/UA-tagged as a generic /Div, same as <div> — not its own /P structure type.
<hr> Partial size→height (does not flatten into a solid bar, matching Chrome); noshade→border-style:solid; width/height legacy attrs; align (left/right/center position the rule box via its margins); color (not read at all — a legacy color="red" is a no-op) UA default border:1px inset #808080; height:0; margin:0.5em auto size/noshade/width/align genuinely wired; color is the one legacy attribute left unimplemented.
<pre> Partial none tag-specific display:block; font-family:monospace; white-space:pre; margin:1em 0; tabs advance to tab stops every tab-size spaces (8 by default) or every tab-size length, as in browsers No leading-newline-after-opening-tag stripping (unlike <textarea>, which does this correctly) — a real spec deviation, produces a spurious leading blank line.
<blockquote> Full cite (parsed, never read — correct, real browsers also give it no visual effect) margin:1em 40px PDF/UA-tagged /BlockQuote.
<ol> Partial start (parsed int, default 1); reversed (counts down from total <li> count); type="1"|"a"|"A"|"i"|"I"→list-style-type list-style-type:decimal; padding-left:40px Known bug: reversed always counts down to 1 from the number of items — it ignores the list's start and any <li value=N> override, unlike the forward-counting path. Marker glyph support covers decimal/alpha/roman/greek/disc/circle/square/disclosure — other CSS keywords (armenian, georgian, hebrew, cjk-decimal…) silently fall back to plain decimal even if they parse. PDF/UA-tagged /L.
<ul> Full type="disc"|"circle"|"square"|"none" list-style-type:disc; nested ul ul→circle, 3-deep→square Same marker-glyph coverage caveat as <ol> above. PDF/UA-tagged /L.
<menu> Full grouped identically to <ul> same as <ul> Matches the modern spec's treatment of <menu> as a <ul> synonym.
<li> Full value (own numbering + bumps subsequent forward-counted siblings; ignored inside <ol reversed>); type display:list-item; text-align:match-parent An orphan <li> with no list ancestor and no room for an outside marker falls back to inside-style positioning. PDF/UA-tagged as /LI with its marker as /Lbl and its content as /LBody.
<dl> Full none margin:1em 0 PDF/UA-tagged as a list (/L, list numbering None).
<dt> Full none font-weight:normal PDF/UA-tagged as /LI with a /Lbl.
<dd> Full none margin-left:40px PDF/UA-tagged as /LI with a /LBody.
<figure> Full none margin:1em 40px PDF/UA-tagged /Figure.
<figcaption> Full none display:block, no special margin An ordinary block child — correctly NOT split into a separate out-of-flow box the way <table>``<caption> is (that's a CSS2.1-specific requirement figcaption doesn't share). PDF/UA-tagged /Caption.
<div> Full none display:block; works identically as a flex/grid container Generic container — full CSS property registry applies.
<search> Full none display:block Newer HTML5 landmark — confirmed present in both the UA stylesheet and the parser's known-element/implicit-close sets, not falling back to inline.

Text-level semantics & embedded/media content

Inline formatting, edits, and everything that embeds external or generated content — images, frames, media, vector/math graphics.

Tag Status Attributes CSS properties Comments
<a> Full href (real PDF hyperlink annotation); title (PDF text annotation/tooltip); target, download (no-op — no window/download concept in PDF) a:any-link{color:#0000ee;text-decoration:underline}, a:visited{color:#551a8b} — a real :any-link match, so a bare <a name> with no href is deliberately NOT link-styled :any-link also matches area[href]/link[href]. PDF/UA-tagged /Link.
<em> <cite> <dfn> <var> <i> Full none font-style:italic Inline text formatting only — all five share one UA rule (i, cite, em, var, dfn { font-style: italic; }).
<strong> Full none font-weight:bolder Deliberately bolder, not an absolute bold — resolves relative to whatever weight is already inherited, so it compounds correctly under an already-bold/heavy ancestor (e.g. a font-weight:200 context) instead of clamping to one fixed weight, matching real browsers.
<small> Full none font-size:smaller —
<s> Full none text-decoration:line-through Shares its UA rule with obsolete <strike>.
<q> Full none special display:inline; q::before { content: open-quote }, q::after { content: close-quote } Automatic quotation marks as in browsers: nested quotes alternate (“ ” outside, ‘ ’ inside), and the quotes property sets other pairs.
<abbr> Full title [title]{text-decoration:dotted underline} — only fires when title is present —
<ruby> Partial none display:inline Plain container — no real ruby-column layout algorithm, relies entirely on <rt>'s superscript trick.
<rt> Partial none display:inline; font-size:0.6em; vertical-align:super Approximated via superscript, not a distinct above-base-text line.
<rp> Full none display:none Correct fallback-parenthesis suppression.
<data> Full value (inert, as in real browsers) none tag-specific Plain inline element, no special-casing found.
<time> Full datetime (inert) none tag-specific Same treatment as <data>.
<code> <samp> <kbd> Full none font-family:monospace Inline text formatting only.
<sub> <sup> Full none vertical-align + reduced font-size per UA rule Inline text formatting only.
<b> Full none font-weight:bolder Shares its UA rule and the same ancestor-compounding reasoning with <strong> above — not paired with <i>'s italic rule; the two are separate, non-overlapping declarations.
<u> <ins> Full none text-decoration:underline —
<del> Full none text-decoration:line-through —
<mark> Full none background-color:#ffff00; color:#000 —
<bdi> Full dir unicode-bidi:isolate Isolates its content, and without a dir takes its direction from its first strong character (FSI), as dir="auto" does on any element.
<bdo> Full dir unicode-bidi:bidi-override Real forced-direction override, feeding the same bidi machinery.
<span> Full none none tag-specific Plain generic inline container.
<br> Full clear (legacy, maps to CSS clear) display:inline Gets a real forcedBreak=true flag — a genuine hard line break.
<wbr> Not supported none display:inline Parsed as a void element but gives no line-break opportunity: a long word with a <wbr> in it stays on one line and overflows its box, where Chrome breaks it there. A U+200B zero-width space doesn't break either; a soft hyphen (&shy;) does.
<picture> Partial none tag-specific display:inline-block Real source selection: walks <source> children in order, evaluates each media query, picks the first match's srcset/src. type (MIME) matching on <source> is NOT checked — a type="image/webp" source is accepted regardless of whether the format can actually be decoded.
<source> Partial srcset, media, src — read only inside a <picture> parent n/a No dedicated handling inside <video>/<audio> (which never load any media at all — see below), so a <source> there is inert.
<img> Full src, srcset (real 1x/density and w-descriptor selection), alt (the PDF/UA /Figure alternate text, else title; never painted), width/height, hspace/vspace/border/align (legacy, mapped), usemap (see <area>) object-fit/object-position-x/-y genuinely implemented and used at paint time Real decode via javax.imageio.ImageIO (JPEG/PNG/GIF/BMP per JVM plugins — no WebP, no animated-GIF frames beyond ImageIO's single-frame default). JPEG EXIF orientation is read and baked in at load time. <img src="*.svg"> is rasterized through the same SVG parser as inline <svg> (ImageStore#svgRaster) but comes back opaque with a white background rather than transparent (the SVG module fills white before painting in this mode) — a real difference from a transparent source SVG. A <text>-bearing SVG renders its text, and a non-square viewBox-less SVG keeps its proportions (its height is probed independently of its width). A broken image deliberately shows no placeholder icon or alt text (a document meant for print shouldn't visibly show a broken link) — only a thin light-gray frame where it still reserves its declared width/height. A real <img> (not CSS backgrounds/generated content) is PDF/UA-tagged /Figure.
<iframe> Placeholder only src, srcdoc (not read); title shown as the placeholder's centered label; width/height (legacy, mapped) display:inline-block; border:2px inset default, 300×150 Confirmed inert: no nested-document fetch or render of any kind — a light-gray fill with a thin border, the tag's title (or bare tag name) centered as text.
<embed> Placeholder only src (not read); title shown as placeholder label; width/height mapped display:inline-block, default 300×150 Same generic gray-box placeholder as <iframe>/<object>/<canvas>/<video> — no fetch/render of the embedded resource.
<object> Placeholder only data (only checked for presence); title shown as placeholder label; width/height/hspace/vspace/border/align legacy attrs mapped display:inline-block, default 300×150 Same placeholder-box treatment — no PDF/image embedding of data is attempted. An <object> with no data but with fallback content (anything besides <param>s) shows that fallback content instead, laid out as an ordinary element, as browsers do.
<param> N/A name, value (not read) n/a Entirely inert: <object> never renders real content, and a <param> doesn't count as fallback content.
<video> Placeholder only width/height (legacy, mapped); src, poster, controls (not read) display:inline-block, default 300×150 box No frame decode, no poster extraction, no playback — a gray box labeled "video" only.
<audio> Placeholder only width/height (size the box, though not mapped to CSS); controls (not read) display:inline-block Own dedicated default sizing (300×54, matching Chrome's native control-bar height) but still no audio decode/playback — a gray box labeled "audio", drawn whether or not controls is present (browsers show nothing for an <audio> without controls).
<track> N/A none read n/a Recognized only as a void element; no caption/subtitle text ever surfaced — consistent with video/audio never actually playing.
<map> Partial name (or id), matched by an <img usemap="#…"> none tag-specific (inline) Not drawn itself; it is looked up as the image map of an <img usemap> — see <area>.
<area> Partial href, shape (rect, circle, poly, default), coords; the image's usemap n/a Image maps, in PDF output: each area with an href is a PDF link over its region of the image (a circle or polygon by its bounding rectangle; coordinates in CSS pixels from the image's top left corner, not scaled with it). ismap (server-side maps) does nothing, and DOCX/RTF output has no image maps.
<math> Full rendered via JEuclid, sized with the surrounding element's resolved font-size; alttext display:inline-block Genuine MathML rendering, not a placeholder — silently skipped (0×0) if disabled, JEuclid isn't on the classpath, or the formula fails to parse. PDF/UA-tagged /Formula, its alternate text from alttext, else the formula's own text.
<svg> Full width/height (legacy + CSS), viewbox display:inline-block; with a viewBox but no width/height, only the aspect ratio is derived (default 300 width) — deliberately does not auto-size to content the way a referenced <img src=*.svg> does Real vector rendering, painted directly onto the live graphics context (true vector PDF output, not rasterized) — needs no opaque backing fill, unlike a rasterized <img src=*.svg> (see above). Malformed/unsupported SVG is silently skipped, not fatal. Known caveat: narrow adjacent table cells with SVG can lose content. PDF/UA-tagged as /Figure, its alternate text from aria-label, title or the SVG's own <title>.
<canvas> Placeholder only width/height (legacy + CSS, real box sizing) display:inline-block, default 300×150 Confirmed no real 2D drawing context of any kind — same generic gray-box-with-label fallback as iframe/embed/object/video. Architecturally expected: there is no JS engine to run canvas-drawing script against.

Tables

Table structure, spanning, captions, and the collapsed/separated border models.

Tag Status Attributes CSS properties Comments
<table> Full border=N (1px outset gray border unless "0"; frame="void" clears border-style); cellspacing→border-spacing; width/height; align (left/right→float, center/middle→margin:auto) display:table; border-collapse; border-spacing-x/-y; table-layout; box-sizing:border-box align="center" centers the table box itself via margin:auto — a genuinely different code path from a replaced element's align (which uses vertical-align).
<caption> Full none special; width/height legacy attrs excluded caption-side:top|bottom — genuinely implemented CSS2.1 box-model split honored: caption is painted in its own band OUTSIDE the table's own border, not merged into it. PDF/UA-tagged /Caption inside the /Table.
<colgroup> Full width (legacy px) display:table-column-group —
<col> Full width display:table-column Contributes a genuine per-column width hint to the column-width distribution algorithm.
<thead> Full width/height excluded display:table-header-group; vertical-align:middle; border-color:inherit Tagged as PDF/UA THead structure.
<tbody> Full same exclusion as thead display:table-row-group Tagged TBody.
<tfoot> Full same exclusion display:table-footer-group Tagged TFoot.
<tr> Full width/height work display:table-row —
<td> Full colspan/rowspan (incl. rowspan="0" = span to end of row-group, clamped to [1,1000]); inherited cellpadding/border from ancestor <table>; nowrap→white-space:nowrap; width/height per-segment collapsed-border resolution for rowspan/colspan cells A colspan-split synthetic column is weighted differently from a genuine one so it doesn't over-shrink.
<th> Full same as <td> plus scope ("col"/"row"/"colgroup"/"rowgroup") font-weight:bold; text-align:center PDF/UA /TH with a /Scope attribute: from scope, else Column for a header in a header row and Row for a header starting a body row.

Forms

Form controls, their rendering as static graphics, and the interactive PDF form fields generated when forms mode is on.

Tag Status Attributes CSS properties Comments
<form> Partial action/method — read to populate a submit <button>'s own PDF SubmitForm action (from its nearest <form> ancestor) none special Real AcroForm generation is wired for v42 (PdfTargetRenderer42#collectFormFields/emitFormFields), gated behind the PD4ML.generateForms(...) flag reaching PdfRenderContext#generateForms(). With that flag off, a plain block container painting static graphics only.
<label> Partial for (no association code, in the forms implementation or anywhere else) cursor:default (inert in static PDF) Purely a visual container; click-association is moot without real interactivity.
<input> Full type: checkbox/radio (native 13×13 box, checkmark/dot only when checked), range (dedicated slider paint), color (swatch), image (real replaced element), submit/reset/button (grey chrome), file (dedicated control), hidden (display:none), password (masked); email/number/date/search/tel/url/week/month/time have no dedicated branch — fall back to plain type=text rendering, same as real browsers do for unsupported types; checked, disabled, value, placeholder (greyed when empty) all read; readonly/required only as CSS pseudo-classes for rendering; name and readonly are read for the AcroForm field when forms are on; maxlength not read checkbox/radio/range/color/image intrinsic sizing overrides With PD4ML.generateForms(...) on, text/password/hidden/checkbox/radio (and any unrecognized type, since HTML5 defaults an unknown type to text) each get a real interactive AcroForm widget, and the widget's own appearance stream takes over from this engine's flat paint; submit/reset/button/image/file/color/range deliberately get NO widget (static graphics only), matching v40's own long-standing scope exactly. Radio buttons sharing a name are grouped into one real AcroForm radio group.
<button> Full type=submit|button|reset — with forms mode on, reset gets a real PDF ResetForm action and a bare/submit button gets a real SubmitForm action (reading the nearest <form> ancestor's action/method); with forms mode off, no functional difference (static graphics only) grey button chrome, shared with input[type=submit/button/reset/file] Unlike <input type=submit> (excluded from widget generation), the distinct <button> element itself DOES get a real push-button widget when forms mode is on.
<select> Full multiple and size (genuinely switch dropdown vs. listbox rendering, default 4 rows if multiple); HTML5 "last <option selected> wins" enforced for non-multiple selects gray (not blue) row highlight in listbox mode — deliberate, matches unfocused-control rendering With forms mode on, always gets a real interactive PDF choice widget (a dropdown, size<=1, sets the Combo flag; a multi-row listbox, size>1, does not — backwards from what the underlying type constant names suggest, but that's v40's own real, traced behavior, not a v42 choice).
<datalist> N/A <input list> association — no code found display:none Correctly invisible; inert as expected (no interactivity in static output), matching real browsers when JS-driven autocomplete isn't relevant.
<optgroup> Full label display:block; font-weight:bold Rendered as a bold row header inside the listbox/dropdown.
<option> Partial selected (last-wins rule); value (not used for visual rendering, only text content shown — with forms mode on, it is the choice's export value); disabled (no per-option greyed rendering) display:block —
<textarea> Full rows/cols (both directly size the box); placeholder; HTML5 leading-LF-after-opening-tag correctly stripped white-space:pre-wrap; font-family:monospace; padding:2px Word-wraps content at box width; wrap granularity approximate vs. Chrome's char-count algorithm but functionally correct. With forms mode on, always gets a real interactive multiline AcroForm text widget (its own appearance stream carries the value; this engine's flat paint is suppressed).
<output> Full none special none special Renders as an ordinary inline element showing its text content — correct, real browsers give <output> no special rendering either.
<progress> Full value/max (genuinely drive fill-bar ratio); no value → fixed indeterminate segment display:inline-block; height:8px, 160px width Fill color sampled directly from Chrome per code comment.
<meter> Full value/min/max (fill ratio); low/high/optimum (the value's region) same box model as progress, 80px width The fill is green in the optimum region, amber in the suboptimal one and red in the even less good one, as in Chrome.
<fieldset> Full none special margin:0 2px; padding:.35em .75em .625em; border:2px groove #c0c0c0 Legend-centered-on-border + border-notch built from scratch, for every border style (not just solid). Known limitation noted in source: a float inside the legend, or a child laid out before the legend, isn't reserved for.
<legend> Full none special display:block; padding:0 2px Sized to its own text width, straddles the border midline — the "engraved" look.
<keygen> N/A obsolete, no crash none special Removed from the HTML5 spec in real browsers too — correctly a no-op void element.

Interactive & scripting elements

Disclosure widgets, dialogs, and everything that depends on script execution or Shadow DOM — neither of which exists in this engine.

Tag Status Attributes CSS properties Comments
<details> Full open (boolean, genuinely functional) display:block Closed state is real element-specific logic (not a CSS class): every child but the first <summary> (bare text included) is skipped outright during box construction when open is absent. A <details> without a <summary> gets a default one reading "Details", as in Chrome. Static conversion only — no interactive toggle in PDF output, fixed at render time.
<summary> Full none tag-specific display:list-item + list-style-type:disclosure-open|disclosure-closed + list-style-position:inside, applied only to the first <summary> child The disclosure triangle (pointing down when open, right when closed) is drawn as a filled shape about half an em tall rather than taken from a font, so it doesn't depend on the font set having the glyph. A block-level first child (e.g. <summary><p>) reserves a line height for the marker instead of the triangle overlapping the child's first character.
<dialog> Partial open — complete no-op display:block, no special rule Real divergence from spec: unlike real browsers' dialog:not([open]){display:none}, content renders identically with or without open. No modal top-layer/backdrop/auto-centering either — correctly and deliberately absent since that has no equivalent in static PDF output.
<script> N/A by design src, type, async, defer — all inert display:none No scripting engine exists anywhere in the codebase — content is tokenized as HTML5 raw text (so embedded JS syntax can't confuse the parser) then hidden, never executed. Any content a client-side script would inject is invisible to PD4ML.
<noscript> Full none display:inline The spec-correct "scripting disabled" branch — since PD4ML never executes scripts, this is the only behavior that makes sense, and it's what real browsers with JS off do too.
<template> Partial none tag-specific display:none No dedicated inert-DocumentFragment representation — falls through the ordinary display:none child-skip branch. Functionally equivalent to "not part of the rendered tree" for a non-interactive renderer, just implemented generically rather than as a special fragment type.
<slot> N/A name — inert none (generic default) No Shadow DOM support of any kind exists — treated exactly like any unknown tag, light-DOM children just render in place with no slot-projection semantics.

Obsolete & deprecated elements

Legacy HTML tags no longer part of the living standard — kept here because real-world documents (especially generated by old tools or Word) still contain plenty of them.

Tag Status Attributes CSS properties Comments
<center> Full none text-align:-webkit-center — a proprietary keyword implemented specifically so <center> also centers a normal-flow block child with its own definite width, not just inline content Matches real Chrome/Safari behavior, including the non-obvious block-centering quirk, rather than the CSS-spec-only text-align:center which would leave a block child flush left.
<dir> Partial type (not mapped to list-style-type the way it is for ol/ul/li — PresentationalHints only checks tag ol/ul/li) grouped with ul/menu: list-style-type:disc; padding-left:40px Rendered as a <ul> synonym for layout purposes, matching the spec's own "treat as ul" guidance — only its own rarely-used type attribute is left untranslated.
<font> Full color, face, size (1–7 legacy scale → 10/13/16/18/24/32/48px, base=3, +N/-N relative forms supported) maps to color/font-family/font-size Fully implemented legacy-attribute-to-CSS translation.
<basefont> Partial color; size, face not read for basefont specifically display:none — so the color mapping has no visible effect anyway Real legacy semantics (setting a default for following sibling content) not replicated, but moot since the element is hidden either way.
<big> Full none font-size:larger Paired UA rule with <small>.
<tt> Full none font-family:monospace —
<strike> Full none text-decoration:line-through Shares its rule with <s>/<del>.
<acronym> Full title [title]{text-decoration:dotted underline} Treated identically to <abbr> — correct parity with its historical merge into abbr's role.
<nobr> Full none white-space:nowrap Also gets real HTML5 "active formatting element" parsing semantics (auto-close-on-reopen), grouped with b/big/code/em/font/i/s/small/strike/strong/tt/u.
<xmp> Partial none font-family:monospace; white-space:pre; margin:1em 0 — but no display:block, so it lays out inline Correctly given HTML5's RAWTEXT tokenizer treatment — nested tags inside are literal text, not parsed as markup, matching the real spec/legacy-browser behavior. Unlike browsers, which make it a block like <pre>, it stays an inline element: two <xmp>s in a row sit side by side, with no vertical margins.
<listing> Partial none shares the pre/xmp/plaintext monospace/pre rule — and, like them, no display:block, so it lays out inline Deliberately NOT given raw-text treatment — and that's spec-correct: per the real WHATWG tokenizer, listing is not a RAWTEXT-state element (only script/style/xmp/iframe/noembed/noframes are). Easy to assume it behaves like xmp; it doesn't, in real browsers either. It is inline rather than a block (same gap as <xmp>), and the "strip one leading newline" rule isn't implemented for it (same gap as <pre>).
<plaintext> Real gap none shares the same monospace/pre rule — no display:block, so inline Genuine spec-compliance gap: HTML5 requires <plaintext> to permanently switch the tokenizer into a raw state for the rest of the document (no more tags recognized at all, ever, until EOF). This engine has no such state — content after <plaintext> continues to be parsed as ordinary markup, a real behavioral difference from every browser's (admittedly bizarre) handling of this tag. It also lays out inline, not as the block browsers make it.
<applet> N/A code, archive, width, height — all inert no UA rule at all — plain inline, not even boxed as a replaced element No Java-applet execution of any kind, confirmed absent (PD4ML being JVM-based does not imply an embedded applet runtime). Fallback content inside renders as plain inline text.
<bgsound> N/A src, loop — inert display:none Correctly hidden; no audio playback attempted (impossible in PDF output anyway).
<blink> N/A none no UA rule; unknown-element generic inline default Text content still shows, just no blink effect — expected/acceptable for a static PDF target, and already dropped from all real browsers.
<marquee> N/A by design behavior, direction, scrollamount — all inert display:block No scrolling/animation logic of any kind — renders as ordinary static block content, correct and deliberate for static PDF output, not a bug.
<frame> N/A src, name, noresize — all inert display:block No cross-document/navigation handling — renders as an empty block, no content loaded from src. Frame navigation has no single-document PDF equivalent, correctly not attempted.
<frameset> N/A rows, cols — inert display:block No special composition logic — just stacks its (empty) <frame> children as ordinary blocks.
<noframes> Full none display:none Correctly hidden, mirroring a frame-capable browser's treatment.
<noembed> Partial none no UA rule — missing from the display:none list that covers its rawtext siblings (noframes, basefont, bgsound, etc.) Minor inconsistency: given true raw-text tokenizer treatment (like script/style/xmp), but since nothing hides it, its literal text content renders visibly by default.
<menuitem> N/A inert no UA rule; unknown-element default Not referenced anywhere — consistent with real modern browsers, which also dropped this abandoned proposal.
<content> N/A select — inert none (unknown element default) Shadow DOM v0 tag; no shadow DOM support exists at all (see <slot>) — falls through as a plain unknown element, matching modern browsers.
<shadow> N/A none none (unknown element default) Shadow DOM v0's deprecated insertion-point tag, removed even earlier than <slot>/<content> — correctly falls through as unknown, matching every real modern browser.
<rb> <rtc> N/A none none (unknown element default) Not in the parser's known-element list — falls through as plain unknown inline elements, matching real browsers since both were formally dropped from the Ruby markup spec in 2021.

Methodology

Audited against the PD4ML v42 engine source (com.pd4ml.html.engine, its css and layout subpackages) — the built-in user-agent stylesheet, the legacy presentational-hints mapper, the CSS property registry, and the relevant layout/paint classes. No v40 comparison is asserted anywhere above unless the v42 source itself carries an explicit comment documenting the change. Where reading the source was not conclusive — the gaps listed above (<wbr>, <dialog>, <plaintext>, <ol reversed>, the leading newline in <pre>/<listing>, inline <xmp>/<listing>/<plaintext>), <meta charset> decoding, image maps and the PDF/UA structure types — the behavior was confirmed by converting small test documents with the v42 engine and inspecting the resulting layout and PDF.

Reconciled against the master source on 2026-11-06.