### .Spi.Yml (.spi.yml) version: 1 builder: configs: - documentation_targets: [DTCoreText] --- ### .Travis.Yml (.travis.yml) language: objective-c osx_image: xcode12 script: - xcodebuild -project DTCoreText.xcodeproj -scheme DemoApp build -sdk iphonesimulator -arch i386 ONLY_ACTIVE_ARCH=NO | xcpretty -s - xcodebuild -project DTCoreText.xcodeproj -scheme "DTCoreText (iOS)" build test -sdk iphonesimulator | xcpretty -s - xcodebuild -project DTCoreText.xcodeproj -scheme "DTCoreText (Mac)" build | xcpretty -s --- ### Sources/DTCoreText/DTCoreText.Docc/DTCoreText (Sources/DTCoreText/DTCoreText.docc/DTCoreText.md) # ``DTCoreText`` Generate `NSAttributedString` and SwiftUI `AttributedString` from HTML on iOS, macOS, and tvOS. ## Overview DTCoreText parses HTML into attributed strings using CoreText, giving you full control over rich text display without a web view. It supports fonts, colors, links, images, lists, tables, floats (`float:left` / `float:right` on images and blocks, with text wrapping around them and `clear` support), text shadows, and custom HTML attributes. ### Generating Attributed Strings Create an `NSAttributedString` from HTML: ```swift let html = "

Hello World

" let data = html.data(using: .utf8)! let attributed = NSAttributedString(htmlData: data, documentAttributes: nil) ``` Or a SwiftUI `AttributedString` that preserves all custom attributes: ```swift let attributed = try AttributedString(htmlData: data) ``` ### Customizing Output Control fonts, text scale, stylesheets, and more via the options dictionary: ```swift let options: [String: Any] = [ DTDefaultFontFamily: "Helvetica Neue", NSTextSizeMultiplierDocumentOption: 1.5 ] let attributed = NSAttributedString(htmlData: data, options: options, documentAttributes: nil) ``` ### Rendering Rich Text Use ``DTAttributedTextView`` or ``DTAttributedLabel`` to render the attributed string with full support for inline images, links, and custom views. ## Topics ### Articles - ### Parsing HTML - ``HTMLAttributedStringBuilder`` - ``CSSStylesheet`` ### Layout - ``CoreTextLayoutFrame`` - ``CoreTextLayoutLine`` - ``CoreTextLayouter`` ### Text Attachments - ``TextAttachment`` - ``ImageTextAttachment`` - ``VideoTextAttachment`` - ``ObjectTextAttachment`` ### Font Handling - ``CoreTextFontDescriptor`` - ``CoreTextFontCollection`` ### SwiftUI Support - ``DTCoreTextAttributes`` - ``DTHeaderLevelKey`` - ``DTAnchorKey`` - ``DTTextBlocksKey`` - ``DTFieldKey`` ### HTML Elements - ``HTMLElement`` - ``HTMLParserNode`` ### Paragraph Styling - ``CoreTextParagraphStyle`` - ``TextBlock`` - ``TextTable`` - ``TextTableBlock`` - ``TextBlockConverter`` --- ### Sources/DTCoreText/DTCoreText.Docc/HTMLTablesOnMacOS (Sources/DTCoreText/DTCoreText.docc/HTMLTablesOnMacOS.md) # How macOS Represents HTML Tables Empirical documentation of how AppKit's HTML importer expresses tables in attributed strings, as the basis for table support in DTCoreText. ## Overview On macOS, `NSAttributedString(data:options:documentAttributes:)` with `NSHTMLTextDocumentType` represents HTML tables with three AppKit classes that have existed since macOS 10.4 but were missing from iOS until iOS 27: - `NSTextBlock` — the base class. Carries six dimensions (width, minimum/maximum width, height, minimum/maximum height, each with an absolute-or-percentage value type), per-edge widths for three box layers (padding, border, margin), per-edge border colors, a background color, and a vertical alignment. - `NSTextTable` (subclass of `NSTextBlock`) — one instance per table. Adds `numberOfColumns`, `layoutAlgorithm` (automatic/fixed), `collapsesBorders`, and `hidesEmptyCells`. - `NSTextTableBlock` (subclass of `NSTextBlock`) — one instance per cell. Adds a reference to its `table` plus the grid position: `startingRow`, `rowSpan`, `startingColumn`, `columnSpan`. These objects live in `NSParagraphStyle.textBlocks`, an array on the paragraph style of every paragraph that is inside a table cell. DTCoreText mirrors this with ``CoreTextParagraphStyle/textBlocks`` holding ``TextBlock`` objects; table support requires extending that model to match the full `NSTextBlock` API (tracked in [issue #1317](https://github.com/Cocoanetics/DTCoreText/issues/1317)). Everything below was determined by running representative HTML snippets through the system importer and dumping the result. The tool lives at `Tools/TableInvestigation/main.swift` (run with `swift Tools/TableInvestigation/main.swift`); a captured dump is checked in next to it as `sample-output.txt`. Findings were captured on macOS 26.6 (25G5028f). ## Characters and Paragraphs A table contributes **no characters of its own** to the string — no tabs, no delimiters, no attachment characters. Each cell becomes one or more ordinary paragraphs terminated by `\n`: ```
A1B1
A2B2
``` produces exactly `"A1\nB1\nA2\nB2\n"`. The table structure exists *only* in the `textBlocks` of the paragraph styles. - An **empty cell** is a bare `"\n"` paragraph (still carrying its `NSTextTableBlock`). - A **cell with multiple paragraphs** (e.g. two `

` elements) produces multiple paragraphs that all reference the *same* `NSTextTableBlock` instance. - A `` is emitted as a plain, center-aligned paragraph *before* the cell paragraphs, with **no text block at all** — it is not structurally part of the table. ## The textBlocks Array - The array contains only `NSTextTableBlock` instances. The `NSTextTable` itself never appears in the array; it is reachable through each cell block's `table` property. - For **nested tables**, the order is outermost first, innermost last. With a table inside a cell of an outer table, the inner cell's paragraphs carry `[outerCellBlock, innerCellBlock]`, while outer-cell text before/after the inner table carries just `[outerCellBlock]`. - **Object identity is the grouping mechanism.** All paragraphs of one cell share one `NSTextTableBlock` instance; all cells of one table point at one shared `NSTextTable` instance. Two cells with identical properties are still distinct instances. Any consumer (layout, HTML writer) must group by identity, not equality. ## Grid Geometry - `startingRow` / `startingColumn` are 0-based grid coordinates; `numberOfColumns` on the table counts grid columns including spans. - `colspan="2"` → `columnSpan = 2`; the next cell in the row starts at the skipped index (e.g. `startingColumn = 2`). - `rowspan="2"` → `rowSpan = 2`; the covered grid positions in following rows simply have **no block** — there is no placeholder. Grid occupancy must be reconstructed from the spans. - There is no object representing a row (``); rows exist only through the `startingRow` indices. Row-level HTML attributes are pushed down onto the row's cells (see colors below). ## Dimensions The importer **resolves every width to an absolute point value** — percentage value types never occur in imported strings, even when the HTML uses percentages: - `` resolved to `width = 627.19 pt` *absolute* — 80% of WebKit's default layout width of ~784 pt (800 pt minus 2 × 8 pt body margin). - Cells always get a `width` dimension with the resolved content width, even when the HTML specifies no width at all (the importer measures the laid-out content, e.g. `width = 14.67 pt` for "A1"). - The stored width is the resolved **content width**: `` constraint → cell `width = 118` (120 minus 2 × 1 pt default padding). Pixel values import 1:1 as points. - `min-width` / `max-width` → `minimumWidth` / `maximumWidth` dimensions, set *in addition to* the resolved `width`. - **Heights are never imported.** Legacy `height` attributes and CSS `height` on rows or cells are dropped; all height dimensions stay 0 and row heights come purely from content at layout time. - Quirk: only the legacy `width` *attribute* on `
` → `width = 120`; a `
` produces a width dimension on the `NSTextTable` block. CSS `width` on the table influences the resolved cell widths but is not stored on the table block itself. - ``/`` have no dedicated representation; their widths only influence the resolved cell widths. The percentage value type (`NSTextBlockPercentageValueType`) is therefore only relevant for programmatically built tables — a DT implementation may choose to *keep* percentages from HTML (richer, resizable layout) or resolve them like the system importer does. ## Box Layers and Edges Each block has three layers — padding, border, margin — with a width per `NSRectEdge`. In the flipped text coordinate system the edges mean: | NSRectEdge | Side | |------------|--------| | `minX` (0) | left | | `minY` (1) | top | | `maxX` (2) | right | | `maxY` (3) | bottom | (Verified with distinct per-edge CSS border widths and colors: `border-left: 4px #FF00FF` showed up as `border minX=4`, `borderColor minX=#FF00FF`, etc.) Observed mappings: - Default plain `
`: padding 1 pt on all edges, margin 0.5 pt on all edges, border 0. - `cellpadding="5"` → padding 5 pt on every cell edge. - `cellspacing="3"` → margin 1.5 pt on every cell edge — the spacing is split in half between adjacent cells. - `border="2"` on the table → border 2 pt on all edges of the `NSTextTable` block itself, plus a 1 pt border on every cell (the HTML separated-borders look). - CSS `padding`/`border-*` per edge on a cell → the corresponding layer/edge values; `border-*-color` → `borderColor(for:)` per edge. - CSS `margin` on the table → margin layer on the `NSTextTable` block (e.g. `margin-left: 20px` → `margin minX=20`). - `border-collapse: collapse` → `collapsesBorders = true` on the table, and the cells lose their default margins (no spacing). - Border colors default to opaque black on **all four edges of every block** (table and cells), even when the border width is 0. ## Colors - `bgcolor`/`background-color` on `` → `backgroundColor` of the `NSTextTable` block. - On `` → copied to each cell block of that row that has no own background (there is no row object to carry it). - Cell and table backgrounds are **not** run-level `NSBackgroundColorAttributeName` attributes — they live exclusively on the blocks. (Contrast: a `
` background *does* become a run attribute, see below.) ## Vertical Alignment - The importer's default for table cells is `.middleAlignment` — note that a freshly initialized `NSTextBlock` defaults to `.topAlignment` instead. - `valign` attribute and CSS `vertical-align` map directly: `top` → `.topAlignment`, `middle` → `.middleAlignment`, `bottom` → `.bottomAlignment`, `baseline` → `.baselineAlignment`. ## Paragraph-Level Effects Some table styling lands on the paragraph style or font rather than the blocks: - `
`/`` → `backgroundColor` of that cell's block. - On `
` → bold font (Times-Bold with default settings) and paragraph alignment `.center`. No block-level difference to ``. - `align`/`text-align` on cells → paragraph `alignment`. - `dir="rtl"` on the table → `baseWritingDirection = .rightToLeft` on the cell paragraphs. The grid indices remain in source order; visual mirroring is the layout engine's job. - The importer always sets an explicit `baseWritingDirection` (`.leftToRight` by default) — never `.natural`. - `

` inside a cell keeps its usual `paragraphSpacing` (12 pt); bare cell text has none. ## Layout Algorithm and Empty Cells - `table-layout: fixed` → `layoutAlgorithm = .fixedLayoutAlgorithm`; everything else is `.automaticLayoutAlgorithm`. Since the importer bakes resolved absolute widths anyway, the distinction matters mainly for relayout after edits. - `empty-cells: hide` → `hidesEmptyCells = true` on the table. ## What the System Importer Does *Not* Use Blocks For Only table cells produce text blocks. A `

` with padding, border, and background gets **no** `NSTextBlock`: the background color becomes a run-level background attribute and the padding/border are dropped entirely. A `
` becomes head/tail indents (±40 pt). DTCoreText's existing use of ``TextBlock`` for padded/background block elements is *richer* than what the system importer does — that behavior must be preserved when extending the class. ## Enum Raw Values and Defaults For a binary/archival-compatible DT layer the raw values must match exactly. Verified on macOS 26.6: | Enum | Values | |------|--------| | `NSTextBlock.Dimension` | width = 0, minimumWidth = 1, maximumWidth = 2, height = 4, minimumHeight = 5, maximumHeight = 6 — **note the gap at 3** | | `NSTextBlock.ValueType` | absoluteValueType = 0, percentageValueType = 1 | | `NSTextBlock.Layer` | **padding = −1**, border = 0, margin = 1 | | `NSTextBlock.VerticalAlignment` | top = 0, middle = 1, bottom = 2, baseline = 3 | | `NSTextTable.LayoutAlgorithm` | automatic = 0, fixed = 1 | | `NSRectEdge` | minX = 0, minY = 1, maxX = 2, maxY = 3 | Defaults of freshly initialized instances: - `NSTextBlock()` — all six dimensions 0 (absolute), all layer widths 0 (absolute), no border colors, no background color, `verticalAlignment = .topAlignment`. - `NSTextTable()` — `numberOfColumns = 0`, `layoutAlgorithm = .automaticLayoutAlgorithm`, `collapsesBorders = false`, `hidesEmptyCells = false`. ## The DT Compatibility Layer The full stack — model, parsing, layout and writing — is implemented: 1. ``TextBlock`` (`DTTextBlock`) carries the full `NSTextBlock` API: dimensions with value types, per-layer/per-edge widths with value types, per-edge border colors, and vertical alignment. The pre-existing `padding`/`backgroundColor` convenience API remains intact — `padding` maps onto the `.padding` layer. Edges are identified by `CGRectEdge`, whose raw values match `NSRectEdge` (which does not exist on iOS). 2. ``TextTable`` (`DTTextTable`) and ``TextTableBlock`` (`DTTextTableBlock`) mirror their NS counterparts with identical enum raw values. ``TextBlockConverter`` converts in both directions, preserving instance identity through per-converter caches — on macOS against the AppKit classes, and on iOS/tvOS/visionOS/Mac Catalyst 27 against the same classes that UIKit publishes with the 27 SDKs (built with Xcode 27 or later; on older SDKs the converter is compiled out). Its `addingNativeTextBlocks(to:)` / `addingTextBlocksAttribute(to:)` methods convert whole attributed strings between the DTCoreText text blocks attribute and native `NSParagraphStyle.textBlocks`. Verified on the iOS 27 simulator: TextKit 1 (`NSLayoutManager`) lays out and draws the converted tables natively, and the system HTML reader and writer round-trip them (the importer produces `NSTextTableBlock`s, the writer emits `` markup). Note that the 27 SDKs change the AppKit enum raw types from `NSUInteger` to `NSInteger` and soft-deprecate the `NSRectEdge`-based accessors in favor of new `CGRectEdge`-based ones. 3. ``TextTable`` and ``TextTableBlock`` compare **by identity**, exactly like their NS counterparts — and this turned out to be load-bearing, not stylistic: Foundation uniques value-equal attribute dictionaries *globally*, across attributed strings. With value-based equality, two equal-styled tables — even from two different documents parsed concurrently — get their runs mapped onto one canonical attribute dictionary, bleeding block instances between documents and destroying the identity grouping. (Plain ``TextBlock`` keeps its historical value-based equality.) 4. ``HTMLAttributedStringBuilder`` parses `table`/`tr`/`td`/`th`/`thead`/`tbody`/`tfoot`/`caption`/`col` into this model, matching the documented importer behavior: defaults of padding 1 pt / margin 0.5 pt / middle alignment, `cellspacing` split onto cell margins, `border` attribute giving cells 1 pt borders, `tr` colors pushed down to cells, bold+centered `th`, caption as a centered paragraph outside the table, no placeholder blocks for `rowspan`-covered positions. One deliberate difference: percentage widths are **kept** as percentage value types instead of being resolved to absolute points, so layout can resolve them against the actual frame width. 5. `CoreTextLayoutFrame` lays tables out as grids: a simplified automatic algorithm (explicit widths win and make content wrap, otherwise single-line content measurement with natural widths propagating recursively out of nested tables; leftover width from an explicit table width goes to the flexible columns), the fixed algorithm from the first row, row heights grown by `rowspan` cells, vertical alignment within row slots — including true first-baseline alignment across a row, per the `NSTextBlock.VerticalAlignment.baselineAlignment` documentation — plus justified cell text and recursive layout of nested tables. Cell and table border-box frames feed background and per-edge border drawing. With `collapsesBorders`, each boundary draws only the winning (wider) border — interior boundaries between cells and, per perimeter side, the wider of the table border and the outer cell borders — producing a single-line grid like CSS `border-collapse: collapse`. Current limitations: no full min/max-content width negotiation, no RTL column mirroring, and table content bypasses `numberOfLines` truncation. 6. ``TextBlock`` additionally models a per-edge **border style** (solid, dashed, dotted, double) — a DTCoreText extension, since `NSTextBlock` only stores width and color per edge (the system importer drops `border-style` entirely). The parser understands the full CSS border grammar: `border`/`border-{edge}` shorthands in any component order, `border-width`/`border-style`/`border-color` with 1–4 value lists in top/right/bottom/left order, per-edge longhands, the `thin`/`medium`/`thick` width keywords, the CSS rule that a style without a width implies a medium (3 px) border, and `none`/`hidden` removing an edge. The 3D styles (groove/ridge/inset/outset) render as solid. Converting to the NS classes drops the style information. 7. `HTMLWriter` writes the model back out as `
`/``/`
` markup with `colspan`/`rowspan` attributes and inline CSS for widths, backgrounds, borders, padding and vertical alignment — round-tripping structure and styling through the parser. The demo app contains three table snippets (`Tables.html`, `TableAlignment.html`, `TableWidths.html`) exercising these scenarios, and `TableRenderPreviewTests` renders them (plus eleven focused samples) to PNGs on macOS and iOS for visual inspection. The parity test suite (`TextBlockAppKitParityTests`) pins the model against AppKit: enum raw values, freshly-initialized defaults, identity-equality semantics, lossless round-trip conversion, and live system-importer fixtures for the structural findings above. `TableParsingTests` additionally compares DTCoreText's parser output structurally against the system importer for identical HTML. On iOS 27, `TextBlockUIKitParityTests` repeats the parity and round-trip checks against the UIKit classes and verifies the native handoff end to end: TextKit 1 grid layout (cells side by side, spans below), rendering to an image, and the system HTML writer/reader round-trip. --- ### Tools/TableInvestigation/Sample Output (Tools/TableInvestigation/sample-output.txt) ================================================================================ CONSTANTS (raw values of the AppKit enums, for DT* compatibility) -------------------------------------------------------------------------------- NSTextBlock.Dimension: .width = 0 .minWidth = 1 .maxWidth = 2 .height = 4 .minHeight = 5 .maxHeight = 6 NSTextBlock.ValueType: .absoluteValueType = 0 .percentageValueType = 1 NSTextBlock.Layer: .padding = -1 .border = 0 .margin = 1 NSTextBlock.VerticalAlignment: .topAlignment = 0 .middleAlignment = 1 .bottomAlignment = 2 .baselineAlignment = 3 NSTextTable.LayoutAlgorithm: .automaticLayoutAlgorithm = 0 .fixedLayoutAlgorithm = 1 NSRectEdge: .minX = 0 .minY = 1 .maxX = 2 .maxY = 3 Defaults of freshly created NSTextBlock(): dimensions: width=0(abs) minWidth=0(abs) maxWidth=0(abs) height=0(abs) minHeight=0(abs) maxHeight=0(abs) padding: minX=0(abs) minY=0(abs) maxX=0(abs) maxY=0(abs) border: minX=0(abs) minY=0(abs) maxX=0(abs) maxY=0(abs) margin: minX=0(abs) minY=0(abs) maxX=0(abs) maxY=0(abs) borderColor: minX=nil minY=nil maxX=nil maxY=nil backgroundColor: nil verticalAlignment: top Defaults of freshly created NSTextTable(): numberOfColumns=0 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false ================================================================================ CASE 01-simple HTML:

Before

A1B1
A2B2

After

-------------------------------------------------------------------------------- STRING: "Before\nA1\nB1\nA2\nB2\nAfter\n" P0 "Before\n" blocks=[] font=Times-Roman@12 paraSpacing=12 writingDirection=LTR P1 "A1\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P2 "B1\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR P3 "A2\n" blocks=[B3→T1 r1c0] font=Times-Roman@12 writingDirection=LTR P4 "B2\n" blocks=[B4→T1 r1c1] font=Times-Roman@12 writingDirection=LTR P5 "After\n" blocks=[] font=Times-Roman@12 paraSpacing=12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=14.6719(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=14.0156(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B3: NSTextTableBlock table=T1 startingRow=1 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=14.6719(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B4: NSTextTableBlock table=T1 startingRow=1 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=14.0156(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 02-th-thead HTML:
NameValue
Pi3.14
-------------------------------------------------------------------------------- STRING: "Name\nValue\nPi\n3.14\n" P0 "Name\n" blocks=[B1→T1 r0c0] font=Times-Bold@12 align=center writingDirection=LTR P1 "Value\n" blocks=[B2→T1 r0c1] font=Times-Bold@12 align=center writingDirection=LTR P2 "Pi\n" blocks=[B3→T1 r1c0] font=Times-Roman@12 writingDirection=LTR P3 "3.14\n" blocks=[B4→T1 r1c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=30(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=28.9062(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B3: NSTextTableBlock table=T1 startingRow=1 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=30(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B4: NSTextTableBlock table=T1 startingRow=1 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=28.9062(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 03-colspan HTML:
WideC1
A2B2C2
-------------------------------------------------------------------------------- STRING: "Wide\nC1\nA2\nB2\nC2\n" P0 "Wide\n" blocks=[B1→T1 r0c0 colSpan=2] font=Times-Roman@12 writingDirection=LTR P1 "C1\n" blocks=[B2→T1 r0c2] font=Times-Roman@12 writingDirection=LTR P2 "A2\n" blocks=[B3→T1 r1c0] font=Times-Roman@12 writingDirection=LTR P3 "B2\n" blocks=[B4→T1 r1c1] font=Times-Roman@12 writingDirection=LTR P4 "C2\n" blocks=[B5→T1 r1c2] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=3 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=2 dimensions: width=32.6875(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=2 columnSpan=1 dimensions: width=14.0156(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B3: NSTextTableBlock table=T1 startingRow=1 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=14.6719(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B4: NSTextTableBlock table=T1 startingRow=1 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=14.0156(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B5: NSTextTableBlock table=T1 startingRow=1 rowSpan=1 startingColumn=2 columnSpan=1 dimensions: width=14.0156(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 04-rowspan HTML:
TallB1
B2
-------------------------------------------------------------------------------- STRING: "Tall\nB1\nB2\n" P0 "Tall\n" blocks=[B1→T1 r0c0 rowSpan=2] font=Times-Roman@12 writingDirection=LTR P1 "B1\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR P2 "B2\n" blocks=[B3→T1 r1c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=2 startingColumn=0 columnSpan=1 dimensions: width=18.5(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=14.0156(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B3: NSTextTableBlock table=T1 startingRow=1 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=14.0156(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 05-widths HTML:
fixed 100half
-------------------------------------------------------------------------------- STRING: "fixed 100\nhalf\n" P0 "fixed 100\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "half\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false dimensions: width=627.188(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=308.594(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=308.594(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 06-legacy-attrs HTML:
XY
-------------------------------------------------------------------------------- STRING: "X\nY\n" P0 "X\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "Y\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false border: minX=2(abs) minY=2(abs) maxX=2(abs) maxY=2(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 backgroundColor: #FFEEDD a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=8.67188(abs) padding: minX=5(abs) minY=5(abs) maxX=5(abs) maxY=5(abs) border: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=1.5(abs) minY=1.5(abs) maxX=1.5(abs) maxY=1.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 backgroundColor: #EEFFDD a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=8.67188(abs) padding: minX=5(abs) minY=5(abs) maxX=5(abs) maxY=5(abs) border: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=1.5(abs) minY=1.5(abs) maxX=1.5(abs) maxY=1.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 backgroundColor: #DDEEFF a=1.00 verticalAlignment: middle ================================================================================ CASE 07-css-box HTML:
styledplain
-------------------------------------------------------------------------------- STRING: "styled\nplain\n" P0 "styled\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "plain\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=true hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=120(abs) padding: minX=4(abs) minY=1(abs) maxX=2(abs) maxY=3(abs) border: minX=4(abs) minY=1(abs) maxX=2(abs) maxY=3(abs) borderColor: minX=#FF00FF a=1.00 minY=#FF0000 a=1.00 maxX=#00FF00 a=1.00 maxY=#0000FF a=1.00 backgroundColor: #ABCDEF a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=24(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 08-align-valign HTML:
topbottommid right
-------------------------------------------------------------------------------- STRING: "top\nbottom\nmid right\n" P0 "top\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "bottom\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR P2 "mid right\n" blocks=[B3→T1 r0c2] font=Times-Roman@12 align=right writingDirection=LTR TABLES: T1: numberOfColumns=3 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=15.3438(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=34.0156(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: bottom B3: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=2 columnSpan=1 dimensions: width=44.3438(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 09-nested HTML:
Outer A
Inner 1Inner 2
after inner
Outer B
-------------------------------------------------------------------------------- STRING: "Outer A\nInner 1\nInner 2\nafter inner\nOuter B\n" P0 "Outer A\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "Inner 1\n" blocks=[B1→T1 r0c0 | B2→T2 r0c0] font=Times-Roman@12 writingDirection=LTR P2 "Inner 2\n" blocks=[B1→T1 r0c0 | B3→T2 r0c1] font=Times-Roman@12 writingDirection=LTR P3 "after inner\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P4 "Outer B\n" blocks=[B4→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 T2: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=78.6719(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T2 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=34.3281(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B3: NSTextTableBlock table=T2 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=34.3281(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B4: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=38.3281(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 10-empty-caption HTML:
The Caption
B
-------------------------------------------------------------------------------- STRING: "The Caption\n\nB\n" P0 "The Caption\n" blocks=[] font=Times-Roman@12 align=center writingDirection=LTR P1 "\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P2 "B\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=3.32812(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=24.6875(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 11-multiparagraph HTML:

One

Two

B
-------------------------------------------------------------------------------- STRING: "One\nTwo\nB\n" P0 "One\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 paraSpacing=12 writingDirection=LTR P1 "Two\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 paraSpacing=12 writingDirection=LTR P2 "B\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=21.1719(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=8.01562(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 12-div-blockquote HTML:
styled div
quoted text
-------------------------------------------------------------------------------- STRING: "styled div\nquoted text\n" P0 "styled div\n" blocks=[] font=Times-Roman@12 runBG=#FFFF00 a=1.00 writingDirection=LTR P1 "quoted text\n" blocks=[] font=Times-Roman@12 headIndent=40 tailIndent=-40 paraSpacing=12 writingDirection=LTR ================================================================================ CASE 13-fixed-layout HTML:
AB
-------------------------------------------------------------------------------- STRING: "A\nB\n" P0 "A\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "B\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=fixed collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=145(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=145(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 14-heights HTML:
legacy 50css 40
-------------------------------------------------------------------------------- STRING: "legacy 50\ncss 40\n" P0 "legacy 50\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "css 40\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=46.3125(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=29.6719(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 15-colgroup HTML:
AB
-------------------------------------------------------------------------------- STRING: "A\nB\n" P0 "A\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "B\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=118(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=58(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 16-empty-cells-hide HTML:
A
-------------------------------------------------------------------------------- STRING: "A\n\n" P0 "A\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=true borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=8.67188(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 17-rtl HTML:
onetwo
-------------------------------------------------------------------------------- STRING: "one\ntwo\n" P0 "one\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=RTL P1 "two\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=RTL TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=17.3281(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=18(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 18-table-margin HTML:
A
-------------------------------------------------------------------------------- STRING: "A\n" P0 "A\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=1 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false margin: minX=20(abs) minY=0(abs) maxX=0(abs) maxY=0(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=194(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 19-min-max-width HTML:
minmaxed
-------------------------------------------------------------------------------- STRING: "min\nmaxed\n" P0 "min\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "maxed\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=100(abs) minWidth=100(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=30(abs) maxWidth=30(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: middle ================================================================================ CASE 20-valign-baseline HTML:
css bllegacy bl
-------------------------------------------------------------------------------- STRING: "css bl\nlegacy bl\n" P0 "css bl\n" blocks=[B1→T1 r0c0] font=Times-Roman@12 writingDirection=LTR P1 "legacy bl\n" blocks=[B2→T1 r0c1] font=Times-Roman@12 writingDirection=LTR TABLES: T1: numberOfColumns=2 layoutAlgorithm=automatic collapsesBorders=false hidesEmptyCells=false borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 BLOCKS: B1: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=0 columnSpan=1 dimensions: width=27(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: baseline B2: NSTextTableBlock table=T1 startingRow=0 rowSpan=1 startingColumn=1 columnSpan=1 dimensions: width=43.6562(abs) padding: minX=1(abs) minY=1(abs) maxX=1(abs) maxY=1(abs) margin: minX=0.5(abs) minY=0.5(abs) maxX=0.5(abs) maxY=0.5(abs) borderColor: minX=#000000 a=1.00 minY=#000000 a=1.00 maxX=#000000 a=1.00 maxY=#000000 a=1.00 verticalAlignment: baseline DONE --- ### .Claude/Plan (.claude/plan.md) # Plan: Issue #1305 — Replace DTHTMLParser with SwiftText HTMLParser ## Problem DTCoreText depends on `DTFoundation` solely for `DTHTMLParser` (an ObjC libxml2 SAX wrapper). Issue #1305 asks us to swap it out for SwiftText's `HTMLParser` (also libxml2-backed, but with a Swift delegate protocol). This blocks removing the DTFoundation dependency entirely. The core challenge: `DTHTMLAttributedStringBuilder` implements `DTHTMLParserDelegate` (ObjC protocol). SwiftText's `HTMLParserDelegate` is a **pure Swift protocol** with Swift-typed parameters (`[String: String]` instead of `NSDictionary`). An ObjC class cannot conform to it. Therefore, **the builder must be migrated to Swift first**. ## Approach: Incremental Migration with ObjC Compatibility Add SwiftText as a package dependency (using the `SwiftTextHTML` product). Migrate the builder to Swift so it can conform to `HTMLParserDelegate`. Annotate with `@objc` to preserve backward compatibility. ## Phase 1: Add SwiftText dependency **`Package.swift` changes:** - Add SwiftText package dependency (from GitHub, with `HTML` trait) - Add `SwiftTextHTML` product dependency to the `DTCoreText` target (alongside DTFoundation temporarily) - Keep DTFoundation for now — other files still use `DTLog`, `NSString+DTURLEncoding`, etc. ## Phase 2: Migrate DTHTMLAttributedStringBuilder to Swift This is the critical step — the builder is ~1000 lines of ObjC with GCD concurrency and block-based tag handlers. 1. **Create `DTHTMLAttributedStringBuilder.swift`** in `Core/Source/` - `@objc public class DTHTMLAttributedStringBuilder: NSObject` - Conform to `HTMLParserDelegate` (from SwiftText's HTMLParser module) - Preserve the exact same public API: `init(html:options:documentAttributes:)`, `generatedAttributedString()`, `willFlushCallback`, `parseErrorCallback`, `shouldKeepDocumentNodeTree`, `abortParsing()` - Internally use `HTMLParser` (from SwiftText) instead of `DTHTMLParser` - The builder calls ObjC classes (DTHTMLElement, DTCSSStylesheet, etc.) — these bridge to Swift naturally 2. **Remove** `DTHTMLAttributedStringBuilder.h` and `.m` 3. **Update imports** — ObjC files that imported the builder header will get it via the generated `-Swift.h` header 4. **Validate** with the 50+ existing Swift tests in HTMLAttributedStringBuilderTests.swift ## Phase 3: Remove DTFoundation Dependency (future) 1. Audit remaining DTFoundation uses: - `DTLog.h` — replace with `os_log` or Swift `Logger` - `NSString+DTURLEncoding` — inline the few methods used 2. Remove `DTFoundation` from `Package.swift` 3. Remove `Externals/DTFoundation` submodule ## Key Design Decisions - **ObjC compatibility**: Swift builder gets `@objc` so existing ObjC consumers still work. Callback blocks remain ObjC-compatible types. - **Concurrency model**: Preserve the existing 3-queue GCD model. No async/await yet. - **Tag handlers**: `_tagStartHandlers`/`_tagEndHandlers` become `[String: () -> Void]` dictionaries. - **Delegate mapping** (1:1): | DTHTMLParserDelegate (ObjC) | HTMLParserDelegate (Swift) | |---|---| | `parser:didStartElement:attributes:` (NSDictionary) | `parser(_:didStartElement:attributes:)` ([String:String]) | | `parser:didEndElement:` | `parser(_:didEndElement:)` | | `parser:foundCharacters:` | `parser(_:foundCharacters:)` | | `parser:foundCDATA:` | `parser(_:foundCDATA:)` | | `parser:foundComment:` | `parser(_:foundComment:)` | | `parser:parseErrorOccurred:` (NSError) | `parser(_:parseErrorOccurred:)` (Error) | ## Files Changed **Modified:** - `Package.swift` — add SwiftText dependency, add SwiftTextHTML to DTCoreText target **New:** - `Core/Source/DTHTMLAttributedStringBuilder.swift` **Removed:** - `Core/Source/DTHTMLAttributedStringBuilder.h` - `Core/Source/DTHTMLAttributedStringBuilder.m` ## Risks & Mitigations - **Character accumulation**: SwiftText's parser accumulates characters before reporting (fewer `foundCharacters` calls). Should be an improvement but needs test validation. - **GCD capture semantics**: ObjC `__weak`/`__strong` dance → Swift `[weak self]`. Careful translation needed. - **Existing ObjC consumers**: `@objc` annotation + generated header ensures API compatibility. --- ### .Github/Workflows/Swift.Yml (.github/workflows/swift.yml) --- name: Swift on: push: branches: - main pull_request: branches: - main env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: build-macos: runs-on: macos-latest timeout-minutes: 15 steps: - uses: actions/checkout@v6 - name: Select Xcode 26.0 uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: "26.0" - name: Verify Swift version run: swift --version - name: Build & Test (macOS) run: | swift test --enable-swift-testing build-ios: runs-on: macos-latest timeout-minutes: 30 steps: - uses: actions/checkout@v6 - name: Select Xcode 26.0 uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: "26.0" - name: Verify Swift version run: swift --version # The runner image only ships runtimes for its default Xcode; after selecting # a pinned Xcode there may be no compatible iOS Simulator runtime. - name: Ensure a compatible iOS Simulator runtime is present run: | ios_version="$(xcodebuild -version | awk '/^Xcode / { split($2, v, "."); print v[1] "." v[2] }')" if ! xcrun simctl list runtimes available | grep -q "iOS ${ios_version} "; then sudo xcodebuild -downloadPlatform iOS \ -buildVersion "${ios_version}" \ -architectureVariant arm64 fi xcrun simctl list runtimes available | grep "iOS ${ios_version} " - name: Build for iOS Simulator run: | xcodebuild build \ -scheme DTCoreText-Package \ -destination "generic/platform=iOS Simulator" \ -skipPackagePluginValidation - name: Test on iOS Simulator run: | xcodebuild test \ -scheme DTCoreTextTests \ -destination "platform=iOS Simulator,name=iPhone 17,OS=latest" \ -parallel-testing-enabled NO \ -skipPackagePluginValidation ---