.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:
let html = "<p>Hello <b>World</b></p>"
let data = html.data(using: .utf8)!
let attributed = NSAttributedString(htmlData: data, documentAttributes: nil)Or a SwiftUI AttributedString that preserves all custom attributes:
let attributed = try AttributedString(htmlData: data)Customizing Output
Control fonts, text scale, stylesheets, and more via the options dictionary:
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
- <doc:HTMLTablesOnMacOS>
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).
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:
<table><tr><td>A1</td><td>B1</td></tr>
<tr><td>A2</td><td>B2</td></tr></table>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).<p>
- A cell with multiple paragraphs (e.g. two elements) produces multiple paragraphs that all reference the same NSTextTableBlock instance.<caption>
- 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.[outerCellBlock, innerCellBlock]
- 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 , while outer-cell text before/after the inner table carries just [outerCellBlock].NSTextTableBlock
- Object identity is the grouping mechanism. All paragraphs of one cell share one 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.<tr>
- 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:
- <table width="80%"> 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).width
- Cells always get a 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").<td style="width: 120px">
- The stored width is the resolved content width: → width = 120; a <col width="120"> 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.height
- Heights are never imported. Legacy 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.width
- Quirk: only the legacy attribute on <table> 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.<col>
- /<colgroup> 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 <td>: 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).padding
- CSS /border- per edge on a cell → the corresponding layer/edge values; border--color → borderColor(for:) per edge.margin
- CSS 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 <table> → backgroundColor of the NSTextTable block.<td>
- On /<th> → backgroundColor of that cell's block.<tr>
- On → copied to each cell block of that row that has no own background (there is no row object to carry it).NSBackgroundColorAttributeName
- Cell and table backgrounds are not run-level attributes — they live exclusively on the blocks. (Contrast: a <div> 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:
- <th> → bold font (Times-Bold with default settings) and paragraph alignment .center. No block-level difference to <td>.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.baseWritingDirection
- The importer always sets an explicit (.leftToRight by default) — never .natural.<p>
- 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 <div> 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 <blockquote> 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 NSTextTableBlocks, the writer emits <table> 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.CoreTextLayoutFrame
5. 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.HTMLWriter
7. writes the model back out as <table>/<tr>/<td> 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: <p>Before</p><table><tr><td>A1</td><td>B1</td></tr><tr><td>A2</td><td>B2</td></tr></table><p>After</p>
--------------------------------------------------------------------------------
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: <table><thead><tr><th>Name</th><th>Value</th></tr></thead><tbody><tr><td>Pi</td><td>3.14</td></tr></tbody></table>
--------------------------------------------------------------------------------
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: <table><tr><td colspan="2">Wide</td><td>C1</td></tr><tr><td>A2</td><td>B2</td><td>C2</td></tr></table>
--------------------------------------------------------------------------------
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: <table><tr><td rowspan="2">Tall</td><td>B1</td></tr><tr><td>B2</td></tr></table>
--------------------------------------------------------------------------------
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: <table width="80%"><tr><td width="100">fixed 100</td><td width="50%">half</td></tr></table>
--------------------------------------------------------------------------------
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: <table border="2" cellpadding="5" cellspacing="3" bgcolor="#FFEEDD"><tr bgcolor="#DDEEFF"><td bgcolor="#EEFFDD">X</td><td>Y</td></tr></table>
--------------------------------------------------------------------------------
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: <table style="border-collapse: collapse;"><tr><td style="padding: 1px 2px 3px 4px; border-top: 1px solid #FF0000; border-right: 2px solid #00FF00; border-bottom: 3px solid #0000FF; border-left: 4px solid #FF00FF; background-color: #ABCDEF; width: 120px;">styled</td><td>plain</td></tr></table>
--------------------------------------------------------------------------------
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: <table><tr style="height: 50px"><td valign="top">top</td><td valign="bottom">bottom</td><td style="vertical-align: middle; text-align: right;">mid right</td></tr></table>
--------------------------------------------------------------------------------
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: <table><tr><td>Outer A<table><tr><td>Inner 1</td><td>Inner 2</td></tr></table>after inner</td><td>Outer B</td></tr></table>
--------------------------------------------------------------------------------
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: <table><caption>The Caption</caption><tr><td></td><td>B</td></tr></table>
--------------------------------------------------------------------------------
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: <table><tr><td><p>One</p><p>Two</p></td><td>B</td></tr></table>
--------------------------------------------------------------------------------
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: <div style="background-color: #FFFF00; padding: 10px; border: 1px solid #000000;">styled div</div><blockquote>quoted text</blockquote>
--------------------------------------------------------------------------------
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: <table style="table-layout: fixed; width: 300px;"><tr><td>A</td><td>B</td></tr></table>
--------------------------------------------------------------------------------
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: <table><tr><td height="50">legacy 50</td><td style="height: 40px">css 40</td></tr></table>
--------------------------------------------------------------------------------
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: <table><colgroup><col width="120"><col width="60"></colgroup><tr><td>A</td><td>B</td></tr></table>
--------------------------------------------------------------------------------
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: <table style="empty-cells: hide"><tr><td>A</td><td></td></tr></table>
--------------------------------------------------------------------------------
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: <table dir="rtl"><tr><td>one</td><td>two</td></tr></table>
--------------------------------------------------------------------------------
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: <table style="margin-left: 20px; width: 200px"><tr><td>A</td></tr></table>
--------------------------------------------------------------------------------
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: <table><tr><td style="min-width: 100px">min</td><td style="max-width: 30px">maxed</td></tr></table>
--------------------------------------------------------------------------------
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: <table><tr><td style="vertical-align: baseline">css bl</td><td valign="baseline">legacy bl</td></tr></table>
--------------------------------------------------------------------------------
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)SwiftTextHTML
- Add product dependency to the DTCoreText target (alongside DTFoundation temporarily)DTLog
- Keep DTFoundation for now — other files still use , 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
- HTMLParserDelegate
- Conform to (from SwiftText's HTMLParser module)init(html:options:documentAttributes:)
- Preserve the exact same public API: , generatedAttributedString(), willFlushCallback, parseErrorCallback, shouldKeepDocumentNodeTree, abortParsing()HTMLParser
- Internally use (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 LoggerNSString+DTURLEncoding
- — inline the few methods usedDTFoundation
2. Remove from Package.swiftExternals/DTFoundation
3. Remove submodule
Key Design Decisions
- ObjC compatibility: Swift builder gets @objc so existing ObjC consumers still work. Callback blocks remain ObjC-compatible types._tagStartHandlers
- Concurrency model: Preserve the existing 3-queue GCD model. No async/await yet.
- Tag handlers: /_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.hCore/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.__weak
- GCD capture semantics: ObjC /__strong dance → Swift [weak self]. Careful translation needed.@objc` annotation + generated header ensures API compatibility.
- Existing ObjC consumers:
---
.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
---