diff --git a/CHANGELOG.md b/CHANGELOG.md index e957923b9..7c39df341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,22 @@ follow semantic versioning; release dates are ISO 8601. ### Public API +- **A DOCX export names a panel's left edge its table cell holds in.** + - The page sets a panel in a table cell with its left border centred on the cell's text edge, + half of it outside, or further out by a margin below zero. LibreOffice starts a nested table + at its cell's text, and Word does in most cells, so the panel stands up to that much right of + the page's: its left border and fill, and with a margin below zero its text too. The report + said nothing. + - The panel's note (`left edge`) names how far left of the cell's text the page sets it, up to + which the editors move it. + - Measured on Windows, 3pt accents: + - LibreOffice set them 1.5pt right of the page's, and a box's in a ruled table cell 1pt. + - Word set them 1.55 to 1.73pt right in a row's cell and in a table cell, filled or not, and + 0.04 to 0.17pt in a filled panel's cell or a row inside one. + - Engineering Resume's 2pt and 3pt card accents stood 1.05 and 1.71pt right in Word. + - A panel a placed row holds as a column had its left margin, which the row's cell already + starts past, counted again in its table's indent. It is counted once. + - 75 panels of the corpus's 62 documents gain the note. None of the DOCX changes. - **A DOCX export ends a panel where the page ends it.** - A panel's text is given a point of slack past the page's width, so an editor setting it in its own face keeps the page's line breaks. The slack widened the panel's table, so its fill and diff --git a/docs/architecture/backend-capability-matrix.md b/docs/architecture/backend-capability-matrix.md index 4ef55f769..bc073790f 100644 --- a/docs/architecture/backend-capability-matrix.md +++ b/docs/architecture/backend-capability-matrix.md @@ -73,7 +73,7 @@ Payload records live in `core` under | Inline vector shapes (`ParagraphShapeSpan`) | ✅ `PdfParagraphFragmentRenderHandler` | ⚠️ `PptxParagraphFragmentRenderHandler` + `PptxInlineGeometry` (distinct per-corner radii render with the top-left radius — single-adjust preset) | ⚠️ `DocxSemanticBackend.writeInlinePicture` + `DocxShapePictures` (a transparent PNG drawn by the shared `InlineSvgRasters` from the outline, fill and stroke — every outline kind, each layer centred in the run's box — placed as an inline picture is; the picture takes as far as the stroked ink reaches past the outline — half the stroke on an edge, more at a sharp corner's miter — and a pixel on each side, measured side by side, and is lowered by what it takes below, so no edge is cut and a shape takes that much more room in the line, which the text beside it gives back in whole tenths of a point a letter where it has letters on one line (`giveBackWidth`) — where none does, the shape's note says, from half a point, up to how far that moves it and what stands beside it in its line, as Word sets the line from the side its alignment names, the paragraph taken as one line; what the tenths miss, up to a twentieth of a point a letter and all of it in a run too long for a tenth a letter, is not named, a quarter point at most on the corpus; a list marker that draws a disc is its picture; at the top level of a `hangingIndent(true)` list that does not nest it is followed by a tab to where the layout starts the item's text, the item's lines hanging there, when the picture clears that stop, else by a space; Word could hold a shape in the line, `wp:inline` round a `wps:wsp`, which the export does not write, so each shape among the words is reported `APPROXIMATED` as a picture) | | Inline SVG (`ParagraphSvgSpan`) | ✅ `PdfParagraphFragmentRenderHandler` + `PdfPathPainter` | ⚠️ `PptxParagraphFragmentRenderHandler` + `PptxInlineGeometry` + `PptxInlineSvgRasterizer` (simple layers stay native; arbitrary clips, exact dash/cap/join styles, and off-viewBox art use a transparent PNG fallback — drawn by the shared `InlineSvgRasters`; gradient paints use their primary colour) | ⚠️ `DocxSemanticBackend.writeInlinePicture` (always the transparent PNG: the same layers the layout resolves, through `InlineSvgLayers`, drawn by the raster the PPTX fallback uses — `InlineSvgRasters`, four pixels a point — and placed as an inline picture is; emoji included, so an emoji is a picture rather than a character; every icon is reported `APPROXIMATED` as a picture, an icon's text as its description, and a gradient, which the raster draws flat in its first colour) | | Text an inline icon stands for — copy, search, extraction (`ParagraphSvgSpan.text`, set by `SvgIcon.withText` and on every `EmojiLibrary` emoji) | ✅ `PdfTextLayer` via `PdfRenderEnvironment.writeTextLayer`: one invisible glyph over the icon on the line's baseline, from a Type 3 font of empty glyphs whose `ToUnicode` states each text, a whole ZWJ sequence included; rendering mode 3, so nothing is painted. One font per document, a new one after 255 distinct texts; a text over 256 UTF-16 units is not written. A block icon (`addSvgIcon`, `SvgIcon.node`) writes no text. `ActualText` around the paths was measured to reach none of PDFBox, poppler, pdf.js and MuPDF — it replaces glyphs, and a drawing has none. In a right-to-left line the glyph sits between the words it was written between and states its whole text; reading such a line back, PDFBox reverses the emoji one UTF-16 unit at a time and poppler reverses the code points of a ZWJ sequence or a U+FE0F pair, while pdf.js and MuPDF keep it whole (measured; a reader-side reversal of the glyph's text) | ❌ the icon is drawn and its text is not written | ⚠️ the icon is a picture whose description (`docPr/@descr`) is its text — read by a screen reader, but not a character a reader copies or searches | -| Rectangle shape — fill, stroke, per-corner radii, side borders (`ShapeFragmentPayload`) | ✅ `PdfShapeFragmentRenderHandler` | ⚠️ `PptxShapeFragmentRenderHandler` (distinct per-corner radii render with the top-left radius on all corners — single-adjust `roundRect` preset — until custom geometry lands; uniform radii and side borders exact) | ⚠️ `DocxSemanticBackend.writeContainerChildren` — a `SectionNode` or `ContainerNode` carrying a fill, per-side borders or a uniform stroke is written as a one-cell table, and so is a `RowNode` in the flow that paints its box (`writePaintedRow`, through the same `writePanelPiece`), its columns a table nested in the cell without its padding — not in a band of layers or a stack's measured column, with a padding or margin it cannot hold, composed in a table cell or in a page zone, where it is its columns alone and its paint is reported: the cell's shading is the fill behind everything inside, its borders are the container's at its full height, and its margins are the padding on all four sides, less half of each border, with the table half a border wider on each side, so the text and the border land where the page draws them — in the body, the left margin is the whole padding, the table no wider on that side, and its indent places its text, as Word 16 and LibreOffice on Windows centre a body table's left border on its edge (an older LibreOffice, the Linux CI's, reads the indent as the table's edge and sets the text about a padding right). Word sets a cell's content its whole top border and its margin inside the row, and ends the row its margin and its whole bottom border past the content, so half of each border — and as much of its inner half as the padding falls short of it — reaches past the panel: the space round it takes that first, then its padding, the border then drawn that much inside the panel's edge, then, above, the room over its first line; what none of them takes is reported (`space above` on the block below, the panel's kind for its top border). What its content leaves below itself past where the page ends it — a last line held to an icon, a card's border ending it, which that card's padding takes first — comes out of its padding below first, the same way; a card's border ending a row's cell reaches past the row only by what the row's room under that cell does not hold, and one ending a table's composed cell is reported on the card, its row up to that much taller. A negative bottom margin pulls what follows up out of the space between them, and what that space cannot give is reported, as is the tenth of a point of the paragraph between any two tables where the space between them does not take it. A shape container's borders stay inside its outline. A panel the layout measures holds the page's height less its padding as its row's height, which Word and LibreOffice on Windows read as its content's, its margins and borders round it; a shape container holds its outline's, less its margins and its borders as wide as Word draws them, two and a half twips an eighth of a point rounded down. A panel's borders are written from two to ninety-six eighths of a point, as a table cell's rules and a rule are, and a width that, rounded to the eighth, is thinner or wider is named (`border width`). The table takes the width the layout placed the container at. The point of slack an editor's face is given past it comes out of the panel's right margin, so its fill and borders end where the page ends them; where the margin cannot hold it, or a line Word sets at its size to the half point runs further into the margin than the rest holds, the table keeps that much of the slack and the report names it (`right end`). In a cell, a panel whose left border hangs half its width left of the cell's text gives up what runs past the cell's text as Word starts it, half that border in — what more it gives up there than it would held in at the text coming off its right margin, and no more than its left side had given its text, the half border the table is wider by and what its left margin gave back of the other half —, since a nested table running past its cell widens the cell, fixed layout or not; one whose right border reaches the cell's edge — the cell's own right margin holds what passes its text, where the cell draws no right rule of its own that may cover it — ends that border and half a point short of it, the points taken from its right margin and no more than it holds, since Word draws a nested table's right border outside the table and, on screen, cuts off what passes the cell's edge, and the report names how much narrower than the page draws the panel its table is written, and about how far what the margin cannot take in still passes that edge (`right border`). A `keepTogether()` panel the layout held on one page is a row that may not split. Panels, rows and tables inside it are tables in its cell; a table cell no style fills is written white, as the page draws it. One deviation: the corner radius is dropped (a cell is rectangular) with one warning per export. A translucent fill is flattened against what Word paints under the panel: the panel or cell around it as written, or on the page the colour the layout paints there (`DocxLayoutMetrics.colourUnder`: the fills drawn before it, at its centre, a page background included and a row's own fill left out, what stands on it being written in the row's panel, on its shading). A translucent border is flattened against the panel's fill where it has one, since a cell's shading and borders are opaque; each is named in the report as `translucency`. An unpainted container is not a table: its paragraphs are indented by every enclosing margin and padding, and its rows and tables get `w:tblInd`. A standalone `ShapeNode` that is only a fill, with no stroke, radius, gradient or transform, and no taller than Word's thickest border (12pt) — an `addDivider` — is a rule, written as a line's is; any other `ShapeNode`, standalone or laid over others, is drawn by `DocxDrawings` as a floating DrawingML shape behind the text, where the layout placed it — `rect`, or `roundRect` with the largest corner radius, in its fill and outline colours with their alpha, the outline mitred at its corners as the page strokes it, each side border a line along its edge; a gradient paint and a transform are not carried, nor are a link, an outline entry or an anchor's bookmark — each named in the shape's report note, as unequal corners are —, and a shape with neither a fill colour nor an outline is dropped and reported. It is anchored in the paragraph whose text it stands beside, placed down from its top, so it moves with that text when the text above is edited; beside no text, in a body paragraph on its page (a table cell's only on a page with no other, reported), where it stays — except a drawing that is all a table cell holds, an icon beside its label (in a row of the flow, a layer stack or shape container that only draws, with no margins, on one page and painting nothing outside its box; in a composed cell, a layer stack of shapes with no margin or padding, the only drawing the cell holds, matched to the table's fragments inside the cell by kind and size), which is anchored in that cell's paragraph, held at its height and placed from its top and the cell's text column, so it moves with its row, and down with the paragraph's line where space is added above that line once it is placed, as a lifted row's cell starts lower; a drawing holding a line stays on the page —, and is drawn in front of the text inside a filled panel unless it frames text or a picture there; reported `APPROXIMATED` | +| Rectangle shape — fill, stroke, per-corner radii, side borders (`ShapeFragmentPayload`) | ✅ `PdfShapeFragmentRenderHandler` | ⚠️ `PptxShapeFragmentRenderHandler` (distinct per-corner radii render with the top-left radius on all corners — single-adjust `roundRect` preset — until custom geometry lands; uniform radii and side borders exact) | ⚠️ `DocxSemanticBackend.writeContainerChildren` — a `SectionNode` or `ContainerNode` carrying a fill, per-side borders or a uniform stroke is written as a one-cell table, and so is a `RowNode` in the flow that paints its box (`writePaintedRow`, through the same `writePanelPiece`), its columns a table nested in the cell without its padding — not in a band of layers or a stack's measured column, with a padding or margin it cannot hold, composed in a table cell or in a page zone, where it is its columns alone and its paint is reported: the cell's shading is the fill behind everything inside, its borders are the container's at its full height, and its margins are the padding on all four sides, less half of each border, with the table half a border wider on each side, so the text and the border land where the page draws them — in the body, the left margin is the whole padding, the table no wider on that side, and its indent places its text, as Word 16 and LibreOffice on Windows centre a body table's left border on its edge (an older LibreOffice, the Linux CI's, reads the indent as the table's edge and sets the text about a padding right). Word sets a cell's content its whole top border and its margin inside the row, and ends the row its margin and its whole bottom border past the content, so half of each border — and as much of its inner half as the padding falls short of it — reaches past the panel: the space round it takes that first, then its padding, the border then drawn that much inside the panel's edge, then, above, the room over its first line; what none of them takes is reported (`space above` on the block below, the panel's kind for its top border). What its content leaves below itself past where the page ends it — a last line held to an icon, a card's border ending it, which that card's padding takes first — comes out of its padding below first, the same way; a card's border ending a row's cell reaches past the row only by what the row's room under that cell does not hold, and one ending a table's composed cell is reported on the card, its row up to that much taller. A negative bottom margin pulls what follows up out of the space between them, and what that space cannot give is reported, as is the tenth of a point of the paragraph between any two tables where the space between them does not take it. A shape container's borders stay inside its outline. A panel the layout measures holds the page's height less its padding as its row's height, which Word and LibreOffice on Windows read as its content's, its margins and borders round it; a shape container holds its outline's, less its margins and its borders as wide as Word draws them, two and a half twips an eighth of a point rounded down. A panel's borders are written from two to ninety-six eighths of a point, as a table cell's rules and a rule are, and a width that, rounded to the eighth, is thinner or wider is named (`border width`). The table takes the width the layout placed the container at. The point of slack an editor's face is given past it comes out of the panel's right margin, so its fill and borders end where the page ends them; where the margin cannot hold it, or a line Word sets at its size to the half point runs further into the margin than the rest holds, the table keeps that much of the slack and the report names it (`right end`). In a cell, a panel whose left border hangs half its width left of the cell's text — or which a margin below zero sets further out — stands up to that much right of the page's, as LibreOffice, and Word in most cells — a row's or a table's, not a filled panel's or a row inside one —, start a nested table at the cell's text, which the report names (`left edge`); it gives up what runs past the cell's text as Word starts it, half that border in — what more it gives up there than it would held in at the text coming off its right margin, and no more than its left side had given its text, the half border the table is wider by and what its left margin gave back of the other half —, since a nested table running past its cell widens the cell, fixed layout or not; one whose right border reaches the cell's edge — the cell's own right margin holds what passes its text, where the cell draws no right rule of its own that may cover it — ends that border and half a point short of it, the points taken from its right margin and no more than it holds, since Word draws a nested table's right border outside the table and, on screen, cuts off what passes the cell's edge, and the report names how much narrower than the page draws the panel its table is written, and about how far what the margin cannot take in still passes that edge (`right border`). A `keepTogether()` panel the layout held on one page is a row that may not split. Panels, rows and tables inside it are tables in its cell; a table cell no style fills is written white, as the page draws it. One deviation: the corner radius is dropped (a cell is rectangular) with one warning per export. A translucent fill is flattened against what Word paints under the panel: the panel or cell around it as written, or on the page the colour the layout paints there (`DocxLayoutMetrics.colourUnder`: the fills drawn before it, at its centre, a page background included and a row's own fill left out, what stands on it being written in the row's panel, on its shading). A translucent border is flattened against the panel's fill where it has one, since a cell's shading and borders are opaque; each is named in the report as `translucency`. An unpainted container is not a table: its paragraphs are indented by every enclosing margin and padding, and its rows and tables get `w:tblInd`. A standalone `ShapeNode` that is only a fill, with no stroke, radius, gradient or transform, and no taller than Word's thickest border (12pt) — an `addDivider` — is a rule, written as a line's is; any other `ShapeNode`, standalone or laid over others, is drawn by `DocxDrawings` as a floating DrawingML shape behind the text, where the layout placed it — `rect`, or `roundRect` with the largest corner radius, in its fill and outline colours with their alpha, the outline mitred at its corners as the page strokes it, each side border a line along its edge; a gradient paint and a transform are not carried, nor are a link, an outline entry or an anchor's bookmark — each named in the shape's report note, as unequal corners are —, and a shape with neither a fill colour nor an outline is dropped and reported. It is anchored in the paragraph whose text it stands beside, placed down from its top, so it moves with that text when the text above is edited; beside no text, in a body paragraph on its page (a table cell's only on a page with no other, reported), where it stays — except a drawing that is all a table cell holds, an icon beside its label (in a row of the flow, a layer stack or shape container that only draws, with no margins, on one page and painting nothing outside its box; in a composed cell, a layer stack of shapes with no margin or padding, the only drawing the cell holds, matched to the table's fragments inside the cell by kind and size), which is anchored in that cell's paragraph, held at its height and placed from its top and the cell's text column, so it moves with its row, and down with the paragraph's line where space is added above that line once it is placed, as a lifted row's cell starts lower; a drawing holding a line stays on the page —, and is drawn in front of the text inside a filled panel unless it frames text or a picture there; reported `APPROXIMATED` | | Ellipse (`EllipseFragmentPayload`) | ✅ `PdfEllipseFragmentRenderHandler` | ✅ `PptxEllipseFragmentRenderHandler` | ⚠️ `DocxDrawings` — an `ellipse` shape anchored as a rectangle is; a shape container's elliptical outline is drawn the same way, and a picture that fills the container it clips takes the ellipse as its geometry; a transform is not carried, nor a link, an outline entry or an anchor's bookmark (each named in the ellipse's report note), and a shape in a filled panel is drawn in front of the text, over the cell's shading, unless it frames text or a picture; a shape is anchored as a rectangle is — except, as for a rectangle, a drawing that is all a table cell holds — in a row of the flow, a badge alone beside its text — which is anchored in that cell and moves with its row | | Line — dash pattern, line cap (`LineFragmentPayload`) | ✅ `PdfLineFragmentRenderHandler` | ⚠️ `PptxLineFragmentRenderHandler` (numeric dash arrays map to the generic dashed preset; solid lines and caps exact) | ⚠️ `DocxSemanticBackend.writeRule` — a horizontal line with no transform, stroked no thicker than Word's 12pt border, is Word's own rule: an empty paragraph whose bottom border is the stroke (colour, thickness in eighths of a point, a hairline thinner than Word's thinnest drawn at it and reported, `border width`), its ends as the paragraph's indents and the space above and below the stroke in its box as the paragraph's height and the space owed below it, and what Word's paragraph stands taller than the box — the tenth of a point of line a stroke with no room above it is held to, and the border counted in whole twips of its eighths — taken out of the space owed below and, past that, hung below the rule for the space above what follows to take, as a line hanging below a block is; a dash pattern becomes Word's dashed or dotted border, reported `APPROXIMATED`; a translucent stroke is flattened against the colour the page paints under it, a page background included, reported as `translucency`; the line cap and a link are not carried (both reported, a link as `rule link`; an outline entry on it is reported too); composed in a table's cell, it is written once, where the page draws it, and its table does not draw it again — unless it stands in a panel or a row inside the cell, or another drawing of its kind and size there cannot be told from it, where both stay and the report says it stands twice. A thicker line (holding its room in the flow and among the text of a stack of one layer, none in a table's cell, reported), a vertical or slanted line, and a line laid over others in a layer stack, canvas or shape container — a line among the text of a layer stack of one layer excepted, which is a rule —, is drawn by `DocxDrawings` as a `line` shape anchored as a rectangle is, ending in the page's cap — the dash pattern and a transform are not carried, nor a link, an outline entry or an anchor's bookmark (each named in the line's report note); a line in a page zone is dropped and reported | | Polygon (`PolygonFragmentPayload`) | ✅ `PdfPolygonFragmentRenderHandler` | ✅ `PptxPolygonFragmentRenderHandler` + `PptxInlineGeometry` | ⚠️ `DocxDrawings` + `DocxCustomGeometry` — `a:custGeom`, the vertex ring closed, its corners mitred as the page strokes them (LibreOffice bevels one sharper than 30 degrees), anchored as a rectangle is | diff --git a/docs/recipes/docx-export.md b/docs/recipes/docx-export.md index a626e1e4c..10363f1cc 100644 --- a/docs/recipes/docx-export.md +++ b/docs/recipes/docx-export.md @@ -620,7 +620,14 @@ How it lands: right margin cannot take in, a shape container's included, since its padding is set round its outline, still passes the cell's edge where the cell's own margin does not hold it, and is named with it, about how far: reckoned, as Word drew them, with the table starting at the - cell's text and its whole border outside it. + cell's text and its whole border outside it. On the left, the page centres a panel's left + border on its cell's text edge, half of it outside, or sets the panel further out by a margin + below zero. LibreOffice starts a nested table at its cell's text, and Word does in most cells: + the panel stands up to that much right of the page's, its left border and fill, and with a + margin below zero its text too, and the report names how far (`left edge`). Measured, 3pt + accents stood 1.5pt right of the page's in LibreOffice; in Word, 1.55 to 1.73pt in a row's + cell and in a table cell, filled or not, and 0.04 to 0.17pt in a filled panel's cell or a row + inside one. - **Keeping together.** A `keepTogether()` panel the layout placed on one page is a row Word may not split. Anchors on the blocks inside a panel carry as they do anywhere else, and so do their keeps, but Word and LibreOffice do not hold them where the panel's row breaks over diff --git a/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java b/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java index 91880828b..8edf4634a 100644 --- a/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java +++ b/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java @@ -848,7 +848,7 @@ private int startPageOf(DocumentNode node) { private DocumentNode leftMarginInCell; // What a row nested in a cell still hangs left once its first column has given what it can // (takeHang), handed to each of its cells: Word keeps a nested table inside its cell - // whatever the table's indent. + // whatever the table's indent, except in a filled panel's cell (leftEdgeHungOut). private double cellHang; // The cell being filled takes its row's hang as cellTextShift (a negative number): its // paragraphs' text moves left by it as far as each one's own indent goes, and never past @@ -6970,6 +6970,29 @@ private void writePanel(XWPFDocument document, DocumentNode node, ContainerPaint /** What the report calls a panel's right end, where its table is written past the page's. */ private static final String RIGHT_END = "right end"; + /** What the report calls a panel's left edge, where the page sets it left of its cell's text. */ + private static final String LEFT_EDGE = "left edge"; + + /** + * The report's note on a panel in a table cell that the page sets left of the cell's text — + * its left border centred on the text's edge, or a margin below zero. + * + *

Its table is written that far left of the cell's text. LibreOffice starts a nested table + * at its cell's text, and Word may. Measured on 3pt accents: LibreOffice set them 1.5pt right + * of the page's, a box's in a ruled table cell 1pt; Word 1.55 to 1.73pt right in a row's cell + * and in a table cell, filled or not, and 0.04 to 0.17pt in a filled panel's cell or a row + * inside one. A margin below zero moves the whole table, its text with it.

+ * + * @param hung how far left of the cell's text the page sets it, in points + * @param bled whether a margin below zero sets the panel there, not its left border alone + */ + private static String leftEdgeHungOut(double hung, boolean bled) { + return (bled ? "the panel" : "its left edge") + ", which the page sets " + pointsOf(hung) + + "pt left of its table cell's text, comes out up to about that much right of the page's" + + (bled ? ", its text with it" : "") + ": LibreOffice starts a nested table at its cell's text, " + + "and Word may"; + } + /** * Whether a panel paints up to its right end: a fill, or a border along its top, its foot or * its right side. A left border alone stands where the page draws it, however wide the table. @@ -6983,13 +7006,14 @@ private static boolean paintsItsRightEnd(DocumentColor fill, DocumentBorders bor * What a panel in a cell whose left border hangs left of the cell's text gives up of its * table's width, in points. * - *

Word starts such a panel half that border in from the cell's text, and a table that then - * runs past the cell widens the cell, fixed layout or not: CompactMono's rail, a 3pt accent at - * its column's edge, pushed the main column 2.3pt right in Word. It gives up what runs past, - * no more than its left side had given its text — the half border the table is wider by, and - * what of the other half its margin could give back — so its text keeps the page's width and - * the slack. A panel bled left by a negative margin keeps the rest, as its text would - * otherwise narrow and wrap.

+ *

Word, in a row's or a table's cell, starts such a panel half that border in from the + * cell's text (inside a filled panel it sets it where it is written: see + * {@link #leftEdgeHungOut}), and a table that then runs past the cell widens the cell, fixed + * layout or not: CompactMono's rail, a 3pt accent at its column's edge, pushed the main + * column 2.3pt right in Word. It gives up what runs past, no more than its left side had given + * its text — the half border the table is wider by, and what of the other half its margin + * could give back — so its text keeps the page's width and the slack. A panel bled left by a + * negative margin keeps the rest, as its text would otherwise narrow and wrap.

* * @param outer the table's width, in points * @param indent where the table starts from the cell's text, in points @@ -7221,24 +7245,26 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container double width = sized.width(); DocumentInsets margins = insideTheBorders(padding, borders); - double edge = insetLeft + margin.left(); + // A cell of a row the layout placed already starts past the child's left margin. + double edge = insetLeft + (node == leftMarginInCell ? margin.left() - node.margin().left() : margin.left()); // In the body the indent places the text, the padding in, and the margin is the whole // padding (see writePanel). boolean inTheBody = currentCell == null; double indent = inTheBody ? edge + padding.left() : edge - halfLeft; - // In a cell, Word starts a nested table no further left than the cell's text, draws its - // right border outside the table's right edge — measured in its PDF, a table ending at - // 566.0pt had its right border from 566.2 to 566.9 — and on screen cuts off whatever - // passes the cell's edge and draws the cell's gridline over it: a panel as wide as its - // cell lost its right border, EditorialProposal's glance card. A panel with a right - // border that reaches its cell's edge ends that border and a little more short of it, the - // points taken from its right margin and no more than it holds, so its text keeps its - // place and its width. The cell's own right margin, where the cell draws no rule of its - // own at that edge, holds what passes the cell's text — a row's gap beside the cell: held - // in at the text, the right borders of Panel's left-column cards and of Obsidian's, - // Metered's and Workspace's cards beside a gap stood 0.6 to 0.9pt left of the page's in - // Word, and 0.3 to 0.6pt right of it held at the edge. A ruled cell's margin, which its - // rule may cover, is left to the text. + // In a row's or a table's cell outside a filled panel, Word starts a nested table no + // further left than the cell's text (leftEdgeHungOut). It draws the table's right border + // outside its right edge — measured in its PDF, a table ending at 566.0pt had its right + // border from 566.2 to 566.9 — and on screen cuts off whatever passes the cell's edge and + // draws the cell's gridline over it: a panel as wide as its cell lost its right border, + // EditorialProposal's glance card. A panel with a right border that reaches its cell's + // edge ends that border and a little more short of it, the points taken from its right + // margin and no more than it holds, so its text keeps its place and its width. The cell's + // own right margin, where the cell draws no rule of its own at that edge, holds what + // passes the cell's text — a row's gap beside the cell: held in at the text, the right + // borders of Panel's left-column cards and of Obsidian's, Metered's and Workspace's cards + // beside a gap stood 0.6 to 0.9pt left of the page's in Word, and 0.3 to 0.6pt right of + // it held at the edge. A ruled cell's margin, which its rule may cover, is left to the + // text. double shortOfTheEdge = 0; // What of the border its right margin cannot take in, and the cell cannot hold, passes // the cell's edge. @@ -7322,6 +7348,11 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container if (rightBorder != null) { report.add(DocxExportReport.Severity.APPROXIMATED, RIGHT_BORDER, layout.pathOf(node), rightBorder); } + // Less than a tenth of a point is not seen (RIGHT_END). + if (currentCell != null && -indent >= 0.1) { + report.add(DocxExportReport.Severity.APPROXIMATED, LEFT_EDGE, layout.pathOf(node), + leftEdgeHungOut(-indent, edge < 0)); + } } applyCellPadding(cell, new DocumentInsets(margins.top(), Math.max(0, marginHeld - slackTaken), margins.bottom(), inTheBody ? padding.left() : margins.left())); @@ -7471,7 +7502,7 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container : tableProperties.addNewTblInd(); tableIndent.setType(STTblWidth.DXA); // Signed: a nested panel with no margin starts half its border left of the cell, and - // Word starts it at the cell's text. + // LibreOffice, and Word in most cells, start it at the cell's text (leftEdgeHungOut). tableIndent.setW(BigInteger.valueOf(Math.round(indent * POINT_TO_TWIP))); } if (last) { @@ -16905,7 +16936,8 @@ private boolean paintsAPanelOfItsOwn(RowNode node) { * panel's cell holds the padding as margins, which go no further than its edge. *
  • Its margin below zero above or below, or at a side in a cell: a panel owes the space * round it on its own, where its columns' table nets the margin against the padding, and - * Word starts a table no further left than its cell's text.
  • + * Word, except in a filled panel's cell, starts a table no further left than its cell's + * text. *
  • In a band of overlapping layers, or where a stack's column measures the space above it * or below it to the text inside it — the first block of a later layer, or the last a * layer below resumes after: the space would hold the row's padding, and so would its @@ -17047,10 +17079,10 @@ private void writeRow(XWPFDocument document, RowNode node, boolean inItsPanel) t // never draws. hideTableGrid(table); // Nested in a cell, a row hanging left cannot move out of it: Word keeps a nested table - // in its cell whatever its indent. Its first column gives the hang what it can, and its - // cells' text takes the rest (see cellHang). SerifHeadline hangs each column's section - // heading left by its dash, and in Word the titles stood that far right of the page's, - // 17pt in the main column. + // in its cell whatever its indent, except in a filled panel's cell (leftEdgeHungOut). Its + // first column gives the hang what it can, and its cells' text takes the rest (see + // cellHang). SerifHeadline hangs each column's section heading left by its dash, and in + // Word the titles stood that far right of the page's, 17pt in the main column. double hang = currentCell != null ? Math.max(0, -insetLeft) : 0; RowGeometry geometry = applyRowGeometry(table, node, hang, inItsPanel); boolean placedColumns = geometry.placed(); @@ -19411,7 +19443,8 @@ private static XWPFTable withAGrid(XWPFTable table) { * page's, 17pt in the main column.

    */ private void indentTable(XWPFTable table) { - // Nested in a cell, a hang is its cells' (writeRow): Word keeps the table in the cell. + // Nested in a cell, a hang is its cells' (writeRow): Word keeps the table in the cell, + // except in a filled panel's (leftEdgeHungOut). if (!(Math.abs(insetLeft) > 0.01) || currentCell != null && insetLeft < 0 || table.getRows().isEmpty() || table.getRow(0).getTableCells().isEmpty()) { return; diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxNodeFieldLedgerTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxNodeFieldLedgerTest.java index 2d2771b2d..2466873d9 100644 --- a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxNodeFieldLedgerTest.java +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxNodeFieldLedgerTest.java @@ -147,6 +147,11 @@ record Entry(Fate fate, String note) { + "given where its right margin cannot hold it, alone or beside a line Word sets " + "wider than the page, named (right end)"; + // LibreOffice starts a nested table at its cell's text, and Word may. + static final String LEFT_EDGE = "a panel's left edge in a table cell, its left border hanging out of the cell or " + + "its margin below zero, which LibreOffice, and Word in most cells, set at the " + + "cell's text: up to about how far right, named (left edge)"; + // A badge's initials, written as its shape's text, stand off its middle by the page's seat. static final String BADGE_SEAT = "in a text badge, its text set off the shape's middle by as much"; @@ -261,10 +266,11 @@ record Entry(Fate fate, String note) { "padding:REPORTED:in the chart's note; above and below; below a block a band or a column measures from, written"); node(ContainerNode.class, "name:INERT", "children:WRITTEN", "spacing:WRITTEN", "padding:GAP:" + PADDING_BELOW_ZERO + "; " + TOP_PULL + "; " + BOTTOM_PULL + "; any other is written", - "margin:GAP:" + TOP_PULL + "; " + BOTTOM_PULL + "; " + SPACE_BELOW_AT_THE_FOOT + "; " + PULL, - "fillColor:REPORTED:" + RIGHT_END + "; " + PANEL_TRANSLUCENCY, - "stroke:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + PANEL_BORDERS, - "cornerRadius:REPORTED", "borders:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + PANEL_BORDERS, + "margin:GAP:" + TOP_PULL + "; " + BOTTOM_PULL + "; " + SPACE_BELOW_AT_THE_FOOT + "; " + PULL + "; " + + LEFT_EDGE, + "fillColor:REPORTED:" + RIGHT_END + "; " + LEFT_EDGE + "; " + PANEL_TRANSLUCENCY, + "stroke:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + LEFT_EDGE + "; " + PANEL_BORDERS, + "cornerRadius:REPORTED", "borders:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + LEFT_EDGE + "; " + PANEL_BORDERS, "anchor:REPORTED:as a layer stack's column, or where it writes no paragraph, as of drawings alone, " + "which has no bookmark; otherwise, in the flow, it is bookmarked", "bookmarkOptions:REPORTED", "flowWidth:REPORTED:of an unpainted one, a panel in a table cell and a layer stack's column"); @@ -376,11 +382,11 @@ record Entry(Fate fate, String note) { + PADDING_BELOW_ZERO + "; any other is written", "margin:GAP:" + TOP_PULL + "; " + BOTTOM_PULL + "; " + SPACE_BELOW_AT_THE_FOOT + "; any other is written", - "fillColor:REPORTED:" + RIGHT_END + "; " + ROW_PAINT, - "stroke:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + ROW_PAINT, + "fillColor:REPORTED:" + RIGHT_END + "; " + LEFT_EDGE + "; " + ROW_PAINT, + "stroke:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + LEFT_EDGE + "; " + ROW_PAINT, "cornerRadius:REPORTED:a panel's corners, which a Word table cell does not round; with no paint " + "it paints nothing", - "borders:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + ROW_PAINT, + "borders:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + LEFT_EDGE + "; " + ROW_PAINT, "columns:GAP:" + ROW_COLUMNS_UNLAID + "; " + ROW_COLUMNS, "verticalAlign:WRITTEN", "arrangement:REPORTED:" + ROW_COLUMNS); // The wrappers the layout adds round a timeline's parts, public records the export writes @@ -392,10 +398,11 @@ record Entry(Fate fate, String note) { "slot:WRITTEN", "child:WRITTEN"); node(SectionNode.class, "name:INERT", "children:WRITTEN", "spacing:WRITTEN", "padding:GAP:" + PADDING_BELOW_ZERO + "; " + TOP_PULL + "; " + BOTTOM_PULL + "; any other is written", - "margin:GAP:" + TOP_PULL + "; " + BOTTOM_PULL + "; " + SPACE_BELOW_AT_THE_FOOT + "; " + PULL, - "fillColor:REPORTED:" + RIGHT_END + "; " + PANEL_TRANSLUCENCY, - "stroke:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + PANEL_BORDERS, - "cornerRadius:REPORTED", "borders:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + PANEL_BORDERS, + "margin:GAP:" + TOP_PULL + "; " + BOTTOM_PULL + "; " + SPACE_BELOW_AT_THE_FOOT + "; " + PULL + "; " + + LEFT_EDGE, + "fillColor:REPORTED:" + RIGHT_END + "; " + LEFT_EDGE + "; " + PANEL_TRANSLUCENCY, + "stroke:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + LEFT_EDGE + "; " + PANEL_BORDERS, + "cornerRadius:REPORTED", "borders:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + LEFT_EDGE + "; " + PANEL_BORDERS, "keepTogether:WRITTEN", "anchor:REPORTED:as a layer stack's column, or where it writes no paragraph, as of drawings alone, " + "which has no bookmark; otherwise, in the flow, it is bookmarked", @@ -417,18 +424,20 @@ record Entry(Fate fate, String note) { + "a box the layout does not give; one set left of its content in a table cell further than the " + "indents round it give", "clipPolicy:REPORTED:where it cuts what its layers paint; composed in a table cell, on its table", - "fillColor:REPORTED:" + RIGHT_END + "; its translucency, as a panel's, flattened against the colour " - + "under it; drawn, its alpha is written; any other is written", - "stroke:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; its translucency, as a panel's border, " + "fillColor:REPORTED:" + RIGHT_END + "; " + LEFT_EDGE + "; its translucency, as a panel's, flattened " + + "against the colour under it; drawn, its alpha is written; any other is written", + "stroke:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + LEFT_EDGE + "; its translucency, as a " + + "panel's border, " + "flattened against the " + "panel's fill or " + "the colour under it, and as a panel's, " + BORDER_WIDTH + "; drawn, its alpha and its width " + "are written; any other is written", - "padding:GAP:" + TOP_PULL + "; " + BOTTOM_PULL + "; written as a panel's in a table cell, round its " - + "outline as a margin " + "padding:GAP:" + TOP_PULL + "; " + BOTTOM_PULL + "; " + LEFT_EDGE + "; written as a panel's in a " + + "table cell, round its outline as a margin " + "is, and below it, where its table ends its cell and the cell sets what it holds at its middle or " + "foot, as far as Word does not count it there; any other is written", - "margin:GAP:" + TOP_PULL + "; " + BOTTOM_PULL + "; written as a panel's in a table cell, below it, " + "margin:GAP:" + TOP_PULL + "; " + BOTTOM_PULL + "; " + LEFT_EDGE + "; written as a panel's in a " + + "table cell, below it, " + "where its table ends " + "its cell " + "and the cell sets what it holds at its middle or foot, as far as Word does not count it " diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxPanelLeftEdgeTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxPanelLeftEdgeTest.java new file mode 100644 index 000000000..313f346ed --- /dev/null +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxPanelLeftEdgeTest.java @@ -0,0 +1,177 @@ +package com.demcha.compose.document.backend.semantic.docx; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.DocumentBorders; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * A panel in a table cell that the page sets left of the cell's text: its left border centred on + * the text's edge, or a margin below zero. LibreOffice starts a nested table at its cell's text, + * and Word may, so the report names how far left the page sets it. + */ +class DocxPanelLeftEdgeTest { + + private static final String SUBJECT = "left edge"; + private static final DocumentColor SURFACE = DocumentColor.rgb(238, 243, 249); + private static final DocumentColor OUTER = DocumentColor.rgb(250, 245, 230); + private static final DocumentColor ACCENT = DocumentColor.rgb(26, 86, 148); + private static final String HUNG = "its left edge, which the page sets 1.5pt left of its table cell's text, " + + "comes out up to about that much right of the page's: LibreOffice starts a " + + "nested table at its cell's text, and Word may"; + + @Test + void anAccentHangingOutOfARowsCellIsNamed() throws Exception { + List notes = notes(beside(card())); + + assertThat(notes).hasSize(1); + assertThat(notes.get(0).path()).contains("Card"); + assertThat(notes.get(0).detail()).isEqualTo(HUNG); + } + + @Test + void anAccentHangingOutOfACellInAFilledPanelIsNamedTheSameWay() throws Exception { + // Word sets it where it is written there, measured 0.04 to 0.17pt off the page's; + // LibreOffice still starts it at the cell's text. + List notes = notes(page -> page.addSection("Outer", outer -> outer.fillColor(OUTER) + .addRow(row -> row.spacing(12).weights(1, 1) + .addSection("Other", other -> other.addParagraph(p -> p.text("Other column"))) + .addSection("Card", card())))); + + assertThat(notes).hasSize(1); + assertThat(notes.get(0).detail()).isEqualTo(HUNG); + } + + @Test + void anAccentAloneIsNamed() throws Exception { + List notes = notes(beside(quote -> quote.accentLeft(ACCENT, 3) + .padding(DocumentInsets.of(8)).addParagraph(p -> p.text("A line set off by a rule")))); + + assertThat(notes).hasSize(1); + assertThat(notes.get(0).detail()).isEqualTo(HUNG); + } + + @Test + void aRowsColumnHoldsItsCardsLeftMarginOnce() throws Exception { + // The row's cell starts past the card's 6pt margin: its accent still hangs half out. + Consumer content = beside(card -> card().accept(card.margin(new DocumentInsets(0, 0, 0, 6)))); + List notes = notes(content); + + assertThat(notes).hasSize(1); + assertThat(notes.get(0).detail()).isEqualTo(HUNG); + try (XWPFDocument document = DocxExports.withLayout(400, 400, 30, content)) { + assertThat(document.getDocument().xmlText()).as("half the accent left of the cell's text, not 4.5pt right") + .containsPattern("]*w:w=\"-30\""); + } + } + + @Test + void aPaintedRowInAFilledPanelIsNamed() throws Exception { + List notes = notes(page -> page.addSection("Outer", outer -> outer.fillColor(OUTER) + .addRow(row -> row.name("Band").fillColor(SURFACE).padding(DocumentInsets.of(8)) + .borders(DocumentBorders.left(DocumentStroke.of(ACCENT, 3))) + .addParagraph(p -> p.text("Engineer")).addParagraph(p -> p.text("2019"))))); + + assertThat(notes).hasSize(1); + assertThat(notes.get(0).path()).contains("Band"); + assertThat(notes.get(0).detail()).isEqualTo(HUNG); + } + + @Test + void aMarginBelowZeroInAPanelsCellIsNamedForThePanel() throws Exception { + // Its whole table moves, its text with it. + List notes = notes(inABorderedPanel(card -> card + .borders(DocumentBorders.right(DocumentStroke.of(ACCENT, 1))).padding(DocumentInsets.of(8)) + .margin(new DocumentInsets(0, 0, 0, -4)).addParagraph(p -> p.text("Bled card")))); + + assertThat(notes).hasSize(1); + assertThat(notes.get(0).path()).contains("Card"); + assertThat(notes.get(0).detail()).isEqualTo("the panel, which the page sets 4pt left of its table cell's " + + "text, comes out up to about that much right of the page's, " + + "its text with it: LibreOffice starts a nested table at its " + + "cell's text, and Word may"); + } + + @Test + void aPanelThePageSetsInsideItsCellIsNotNamed() throws Exception { + // In from the panel's cell's text by more than half its accent. + assertThat(notes(inABorderedPanel(card -> card().accept(card.margin(new DocumentInsets(0, 0, 0, 2)))))) + .isEmpty(); + } + + @Test + void aBoxInATableCellIsNamed() throws Exception { + // Composed in a table cell, written as a panel: its 1.1pt border hangs half out. + List notes = notes(page -> page.addTable(t -> t + .columns(DocumentTableColumn.fixed(120), DocumentTableColumn.fixed(160)) + .rowCells(DocumentTableCell.text("Mon"), DocumentTableCell.node(new ShapeContainerBuilder() + .name("Shape").rectangle(150, 30).fillColor(SURFACE) + .stroke(DocumentStroke.of(ACCENT, 1.1)) + .position(new ParagraphBuilder().name("Label").text("Label").build(), 10, 0, + LayerAlign.CENTER_LEFT) + .build())))); + + assertThat(notes).hasSize(1); + assertThat(notes.get(0).detail()).startsWith("its left edge, which the page sets 0.55pt left of its " + + "table cell's text"); + } + + @Test + void aHairlineHangingLessThanATenthOfAPointIsNotNamed() throws Exception { + assertThat(notes(beside(card -> card.fillColor(SURFACE).padding(DocumentInsets.of(8)) + .borders(DocumentBorders.left(DocumentStroke.of(ACCENT, 0.15))) + .addParagraph(p -> p.text("Hairline card"))))).isEmpty(); + } + + @Test + void anAccentInTheBodyIsNotNamed() throws Exception { + // In the body the table starts at the panel's edge, its left border centred there. + assertThat(notes(page -> page.addSection("Card", card()))).isEmpty(); + } + + @Test + void aPanelBledLeftInTheBodyIsNotNamed() throws Exception { + // A body table is placed by its indent, into the page's margin as far as it is bled. + assertThat(notes(page -> page.addSection("Card", card -> card().accept( + card.margin(new DocumentInsets(0, 0, 0, -12)))))).isEmpty(); + } + + /** A filled card with a 3pt accent on its left, padded 8pt. */ + private static Consumer card() { + return card -> card.fillColor(SURFACE).accentLeft(ACCENT, 3).padding(DocumentInsets.of(8)) + .addParagraph(p -> p.text("Accented card")); + } + + /** The card as the second column of a row, beside a column of text. */ + private static Consumer beside(Consumer card) { + return page -> page.addRow(row -> row.spacing(12).weights(1, 1) + .addSection("Other", other -> other.addParagraph(p -> p.text("Other column"))) + .addSection("Card", card)); + } + + /** The card inside a panel with a border and no fill, padded 10pt. */ + private static Consumer inABorderedPanel(Consumer card) { + return page -> page.addSection("Outer", outer -> outer.stroke(DocumentStroke.of(ACCENT, 0.5)) + .padding(DocumentInsets.of(10)).addSection("Card", card)); + } + + private static List notes(Consumer content) throws Exception { + DocxExportReport report = DocxExports.report(400, 400, 30, content); + assertThat(report.bySubject()).as("laid out").doesNotContainKey("measured geometry"); + return report.bySubject().getOrDefault(SUBJECT, List.of()); + } +} diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxValueFieldLedgerTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxValueFieldLedgerTest.java index 8c388179e..4d06ef7e4 100644 --- a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxValueFieldLedgerTest.java +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxValueFieldLedgerTest.java @@ -31,6 +31,7 @@ import static com.demcha.compose.document.backend.semantic.docx.DocxNodeFieldLedgerTest.DRAWING_OVER_TEXT; import static com.demcha.compose.document.backend.semantic.docx.DocxNodeFieldLedgerTest.LAYER_PAST_THE_EDGE; import static com.demcha.compose.document.backend.semantic.docx.DocxNodeFieldLedgerTest.LAYER_UNLAID; +import static com.demcha.compose.document.backend.semantic.docx.DocxNodeFieldLedgerTest.LEFT_EDGE; import static com.demcha.compose.document.backend.semantic.docx.DocxNodeFieldLedgerTest.LIST_PICTURE; import static com.demcha.compose.document.backend.semantic.docx.DocxNodeFieldLedgerTest.PADDING_BELOW_ZERO; import static com.demcha.compose.document.backend.semantic.docx.DocxNodeFieldLedgerTest.PANEL_WIDER_THAN_ITS_CELL; @@ -266,7 +267,7 @@ class DocxValueFieldLedgerTest { + "a chart's or a spacer's no band or column measures from; a page field's beside other parts; a " + "picture's padding drawn beside its text or over its badge; a chip's; a shape container's at its " + "table cell's middle or foot; any other is written", - "left:GAP:" + PADDING_BELOW_ZERO + "left:GAP:" + PADDING_BELOW_ZERO + "; " + LEFT_EDGE + "; a picture's, a barcode's and a chart's, and a page reference's on the side its " + "alignment sets it from; a page field's at its sides; a picture's padding drawn beside its text or " + "over its badge; a chip's its shading does not hold; any other is written", @@ -296,7 +297,7 @@ class DocxValueFieldLedgerTest { "color:REPORTED:its translucency as a Word border — a panel's, a table cell's, a rule's — flattened " + "against the panel's fill or the colour under it; a chart's, with its drawing; drawn, its alpha is " + "written; any other is written", - "width:REPORTED:" + RIGHT_BORDER + "width:REPORTED:" + RIGHT_BORDER + "; " + LEFT_EDGE + "; as a Word border, a width that, rounded to the eighth of a point, is below a quarter " + "of a point or past 12pt, drawn at Word's thinnest or widest, and the room Word makes for a panel's " + "or a cell's border past the page's; a chart's, with its drawing; any other is written"); @@ -314,19 +315,19 @@ class DocxValueFieldLedgerTest { + "is held to half the shorter side, at the largest of them; a chart's, with its drawing; any other " + "is written"); value(com.demcha.compose.document.style.DocumentBorders.class, - "bottom:REPORTED:" + RIGHT_END + "bottom:REPORTED:" + RIGHT_END + "; " + LEFT_EDGE + "; as a panel's: its translucency, flattened against the panel's fill or the colour " + "under it; a width outside Word's quarter of a point to 12pt; its reach past the panel's box the " + "space below it and its padding do not take, on the block below; a row's where its paint is not " + "written; any other is written", - "left:REPORTED:as a panel's: its translucency, flattened against the panel's fill or the colour " + "left:REPORTED:" + LEFT_EDGE + "; as a panel's: its translucency, flattened against the panel's fill or the colour " + "under it, and a width outside Word's quarter of a point to 12pt; a row's where its paint is not " + "written; any other is written", - "right:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "right:REPORTED:" + RIGHT_BORDER + "; " + RIGHT_END + "; " + LEFT_EDGE + "; as a panel's: its translucency, flattened against the panel's fill or the colour " + "under it, and a width outside Word's quarter of a point to 12pt; a row's where its paint is not " + "written; any other is written", - "top:REPORTED:" + RIGHT_END + "top:REPORTED:" + RIGHT_END + "; " + LEFT_EDGE + "; as a panel's: its translucency, flattened against the panel's fill or the colour " + "under it; a width outside Word's quarter of a point to 12pt; its reach past the panel's box the " + "space above it, its padding and the room over its first line do not take, on the panel; a row's "