# PD4ML v42 — HTML5 Element Support Matrix

Every row below was verified directly against the v42 engine's own source (com.pd4ml.html.engine — the CSS cascade, the built-in user-agent stylesheet, the legacy-attribute presentational hints, and the layout/paint code) rather than inferred from documentation. Where an attribute or CSS property is parsed but produces no visible effect, it's marked accordingly instead of left ambiguous.

> 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.

* **Needs verification** — `<form>` family — `PD4ML.generateForms()` (real AcroForm field generation) has zero references anywhere under the v42 `html.engine` package; it appears wired only to the legacy v40 pipeline. Form controls may render as flat static graphics only under v42 despite the Programmer's Manual describing this as a supported feature. Worth an independent live check before depending on it.
* **Real gap** — `<meta charset>` / `http-equiv` is not read anywhere — source encoding is decided purely by BOM sniffing or a charset the calling code supplies explicitly.
* **Real gap** — `<q>` generates no automatic quotation marks (no `::before`/`::after` content rule in the UA stylesheet) — every real browser adds them by default.
* **Real gap** — `<bdi>` never gets the implicit `unicode-bidi: isolate` real browsers give it automatically; the isolation machinery exists in the engine but nothing applies it to a bare `<bdi>`.
* **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}`.
* **Placeholder only** — `<iframe>`, `<embed>`, `<object>`, `<canvas>`, `<video>` all fall through the same generic branch: a sized gray box with a centered label, no fetch/decode/render of the referenced content at all.
* **Known limitation** — `<ol reversed>` ignores any sibling `<li value=N>` override — the reversed-numbering path counts total children only, unlike the forward-counting path.
* **Known limitation** — `tab-size` parses into the cascade but the tab-expansion code has an 8-space width hardcoded — an authored value is never actually consulted.
* **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** — `<meter>` ignores `low`/`high`/`optimum` entirely — the fill is always a fixed green regardless of value zone.
* **Known limitation** — `<th scope>` is parsed but never inspected — PDF/UA table-header structure tagging picks TH vs. TD purely from tag name.
* **Unverified, likely gap** — `<wbr>` has no dedicated soft-break-opportunity marker in the line-breaking code, unlike `<br>`'s explicit hard-break flag — probably doesn't offer a genuine break point mid-word the way Chrome's does.
* **Known limitation** — No real HTML image-map support: `<area shape/coords>` produces no per-region PDF link annotations — only whole-image or whole-anchor hyperlinks work.
* **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.

## 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 are forced into it regardless of source position. |
| `<title>` | Full | none | `display:none` | Content genuinely consumed — `Document.getTitle()` returns the first `<title>`'s trimmed, entity-decoded text (RCDATA parsing, tags inside not recognized). Used e.g. for CLI output filenames. A stray second `<title>` 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~~, ~~http-equiv~~, ~~name="viewport"~~ — none read anywhere in the engine | `display:none` | Real gap: encoding correctness for meta-charset-only documents depends entirely on BOM sniffing or the caller supplying the right charset. `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>` | Partial | `text`→color, `bgcolor`→background-color, `background`→background-image url(), `link`→ a `--link-color` variable; ~~vlink~~, ~~alink~~ not read | UA default `display:block; margin:8px` | Legacy presentational attributes well covered except visited/active link colors. |
| `<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 is coarser than the full landmark set — `section`/`nav`/`main`/`article` map to a generic "Paragraph"-kind struct tag; `aside`/`header`/`footer`/`address` get no dedicated PDF role. 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 "Paragraph"-kind for PDF/UA, not a distinct H1–H6 heading structure type. |
| `<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. |
| `<hr>` | Partial | `size`→height (confirmed not to flatten into a solid bar, matching Chrome); `noshade`→`border-style:solid`; `width`/`height` legacy attrs; ~~color~~ (not read at all — a legacy `color="red"` is a no-op) | UA default `border:1px inset #808080; height:0` | size/noshade/width genuinely wired; color is the one legacy attribute left unimplemented. |
| `<pre>` | Partial | none tag-specific | `font-family:monospace; white-space:pre`; `tab-size` parses into the cascade but tab-expansion is hardcoded to 8 spaces — an authored value has no effect | 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` | — |
| `<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` ignores any sibling `<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. |
| `<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. |
| `<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); `type` | `display:list-item; text-align:start` | An orphan `<li>` with no list ancestor and no room for an outside marker falls back to inside-style positioning (documented prior fix). |
| `<dl>` | Full | none | `margin:1em 0` | — |
| `<dt>` | Full | none | `font-weight:normal` | — |
| `<dd>` | Full | none | `margin-left:40px` | — |
| `<figure>` | Full | none | `margin:1em 40px` | — |
| `<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). |
| `<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]`. |
| `<em>` `<cite>` `<dfn>` `<var>` | Full | none | `font-style:italic` | Inline text formatting only. |
| `<strong>` | Full | none | `font-weight:bold` | — |
| `<small>` | Full | none | `font-size:smaller` | — |
| `<s>` | Full | none | `text-decoration:line-through` | Shares its UA rule with obsolete `<strike>`. |
| `<q>` | Partial | none special | `display:inline` only | Real gap: no auto-generated quotation marks — no `::before`/`::after` `content:open-quote/close-quote` rule anywhere in the UA sheet. The `quotes` property and generated-content `open-quote`/`close-quote` values ARE implemented generically, so an author who writes the rule themselves gets real quote marks — the UA default just never supplies one. |
| `<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. |
| `<i>` `<b>` | Full | none | italic / bold | — |
| `<u>` `<ins>` | Full | none | `text-decoration:underline` | — |
| `<del>` | Full | none | `text-decoration:line-through` | — |
| `<mark>` | Full | none | `background-color:#ffff00; color:#000` | — |
| `<bdi>` | Partial | `dir` | no implicit `unicode-bidi:isolate` | Real gap: no UA rule sets isolation for a bare `<bdi>`, even though the isolation machinery (`InlineLayout.resolveBidiLevels()`) exists and is used elsewhere. An explicit `dir` attribute still works for direction via the generic `[dir=rtl i]`/`[dir=ltr i]` rules — only the automatic isolation half is missing. |
| `<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>` | Partial, unverified | none | `display:inline` | No dedicated soft-break-opportunity flag found anywhere (contrast with `<br>`'s explicit hard-break flag) — likely doesn't act as a genuine mid-word break point the way Chrome's does. Absence-of-evidence finding, not confirmed by render diff. |
| `<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` (used only as a broken-image fallback, not painted over a successfully loaded image), `width`/`height`, `hspace`/`vspace`/`border`/`align` (legacy, mapped) | `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>` but comes back opaque/white-background, not transparent. A broken image deliberately shows no placeholder icon or alt text (a document meant for print shouldn't visibly show a broken link) but still reserves its declared box space. |
| `<iframe>` | Placeholder only | ~~src~~, ~~srcdoc~~ (not read); `title` shown as the placeholder's centered label; `width`/`height` (legacy, mapped) | `border:2px inset` default | Confirmed inert: no nested-document fetch or render of any kind — a flat gray fill with a 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` | Same generic gray-box placeholder as `<iframe>`/`<object>`/`<canvas>`/`<video>` — no fetch/render of the embedded resource. |
| `<object>` | Placeholder only | ~~data~~ (not read); `title` shown as placeholder label; `width`/`height`/`hspace`/`vspace`/`border`/`align` legacy attrs mapped | `display:inline-block` | Same placeholder-box treatment — no PDF/image embedding of `data` is attempted. |
| `<param>` | N/A | ~~name~~, ~~value~~ (not read) | n/a | Entirely inert since its parent `<object>` never renders real 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 labeled gray box only. |
| `<audio>` | Placeholder only | ~~controls~~ (not read); width/height excluded from the legacy-attribute mapping tag list | `display:inline-block` | Own dedicated default sizing (300×54, matching Chrome's native control-bar height) but still no audio decode/playback — a sized placeholder only. |
| `<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>` | Not supported | `name` | n/a | Recognized as a structural tag; no layout/paint code references it. |
| `<area>` | Not supported | `href` (link-styled via `:any-link`); ~~shape~~, ~~coords~~, ~~usemap~~, ~~ismap~~ (no evidence any of these do anything) | n/a | No real HTML image-map support — only whole-image/whole-anchor hyperlinks work, not per-region clickable areas. |
| `<math>` | Full | rendered via JEuclid, sized with the surrounding element's resolved font-size | `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. Tagged as an Artifact (not Figure) for PDF/UA, since raw glyphs convey nothing to a screen reader. |
| `<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). Malformed/unsupported SVG is silently skipped, not fatal. Known caveat: narrow adjacent table cells with SVG can lose content. |
| `<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. |
| `<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>` | Partial | same as `<td>` plus ~~scope~~ ("col"/"row"/"colgroup"/"rowgroup" — parsed but never read anywhere) | `font-weight:bold; text-align:center` | Real gap: `scope` is inert for accessibility tagging — PDF/UA picks TH vs TD purely from tag name, not scope. |

## Forms

Form controls and their rendering as static graphics — and the one open question about real interactive PDF form fields.

| Tag | Status | Attributes | CSS properties | Comments |
| --- | --- | --- | --- | --- |
| `<form>` | Needs verification | ~~action~~, ~~method~~ (not read — plain block container, no submission) | none special | `PD4ML.generateForms()` (real AcroForm generation) has zero references anywhere under `html.engine` — wired only to the legacy v40 pipeline as far as source shows. For v42-rendered documents this flag may be a no-op; form controls paint as flat static graphics only. Flagged for independent live verification. |
| `<label>` | Partial | ~~for~~ (no association code found) | `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; ~~maxlength~~, ~~name~~ not read for rendering | checkbox/radio/range/color/image intrinsic sizing overrides | — |
| `<button>` | Partial | `type=submit\|button\|reset` — ~~no functional difference~~ (no real form submission in static PDF) | grey button chrome, shared with input[type=submit/button/reset/file] | Always renders its child content inside the button chrome regardless of type. |
| `<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 | — |
| `<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); ~~disabled~~ (no per-option greyed rendering found) | `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. |
| `<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>` | Partial | `value`/`min`/`max` (drive fill ratio); ~~low~~, ~~high~~, ~~optimum~~ (not read — fill is a fixed green regardless of zone) | same box model as progress, 80px width | Real browsers vary meter color by low/high/optimum zone; this engine does not. |
| `<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>` is skipped outright during tree construction when `open` is absent. 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 | Marker glyphs are real Unicode triangles (U+25BC open, U+25BA closed — substituted for U+25B6 which is missing from common Windows fonts). Documented bug fix: a block-level first child (e.g. `<summary><p>`) now correctly 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` (same 1–7 mapping as `<font>`); ~~face~~ not read for basefont specifically | `display:none` — so the color/size 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>` | Full | none | `font-family:monospace; white-space:pre` | Correctly given HTML5's RAWTEXT tokenizer treatment — nested tags inside are literal text, not parsed as markup, matching the real spec/legacy-browser behavior. |
| `<listing>` | Full | none | shares the pre/xmp/plaintext monospace/pre rule | 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. (The shared "strip one leading newline" rule isn't implemented for it, same gap as `<pre>`.) |
| `<plaintext>` | Real gap | none | shares the same monospace/pre rule | 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. |
| `<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. |

