Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ follow semantic versioning; release dates are ISO 8601.

### Public API

- **A DOCX export names a panel's right border its table cell's edge would cut off.** Word draws
a nested table's right border outside the table and, on screen, cuts off what passes its cell's
edge. A bordered panel reaching that edge has its table written narrower, out of its right
margin, so the border ends half a point short of the edge. Where the margin is too narrow, as
a shape container's always is, the border still passes it. The report said neither.
- The panel's note (`right border`) names how much narrower than the page draws the panel its
table is written, the editor's slack a panel sized round its content is given not counted,
and about how far its border still passes the cell's edge. A painted row is named the same
way, and so is a shape container written as a panel in a table cell.
- Measured in Word on Windows: Obsidian's cards' right borders stood 0.8 and 0.9pt left of the
page's for tables written 1.7pt narrower, and Editorial Proposal's glance card 0.7pt for
1.9pt.
- 28 panels of the corpus's 62 documents gain the note: 19 drawn in, and 9 passing the edge,
rota-cobalt's outlined chips and two payment panels. None of the DOCX changes.
- **A DOCX export names what a picture among a paragraph's words loses of its place, its width
or its paint.** Word stands an inline picture on the baseline, and the export moves it by
`w:position` to where the page sets it, from the layout's measure of the paragraph's first
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/backend-capability-matrix.md

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion docs/recipes/docx-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,14 @@ How it lands:
right border outside its right edge and, on screen, cuts off what passes its cell's edge
and draws the cell's gridline there; a bordered panel reaching its cell's text edge ends its
whole right border plus half a point short of that edge, the points taken from its right
margin and no more than it holds, so its text keeps its place and width.
margin and no more than it holds, so its text keeps its place and width. The report names
how much narrower than the page draws the panel its table is written (`right border`), the
editor's slack a panel sized round its content is given not counted; where its border then
stands is Word's (measured: 0.8pt left of the page's for a table 1.7pt narrower). What the
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.
- **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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6942,6 +6942,37 @@ private void writePanel(XWPFDocument document, DocumentNode node, ContainerPaint
*/
private static final double CLEAR_OF_THE_GRIDLINE_POINTS = 0.5;

/** What the report calls a panel's right border held inside its table cell. */
private static final String RIGHT_BORDER = "right border";

/**
* The report's note on a panel's right border that its table cell's edge reaches, or
* {@code null} where the border stands clear of it.
*
* <p>The table's narrowing is exact. Where its border then stands is Word's: drawn outside
* the table, it stood 0.6 to 1.4pt left of the page's in Word for tables 1.4 to 2.4pt
* narrower (measured). What passes the edge is reckoned with the table starting at the
* cell's text and its whole border outside it, as Word drew them, so it is about that much.
*
* @param narrower how much narrower than the page draws the panel its table is written, out
* of its right margin, in points
* @param past what of the border passes the cell's edge, in points
*/
private static String rightBorderHeldIn(double narrower, double past) {
String written = "its table written " + pointsOf(narrower) + "pt narrower than the page draws it";
if (past > 0.01) {
return (narrower > 0.01 ? written + ", all its right margin gives, and its right border, which Word "
+ "draws outside the table, still"
: "its right border, which Word draws outside its table,")
+ " reaching about " + pointsOf(past) + "pt past its table cell's edge, where Word cuts it off "
+ "on screen";
}
return narrower > 0.01
? written + ", out of its right margin, so its right border, which Word draws outside the table, "
+ "ends short of its table cell's edge, past which Word cuts it off on screen"
: null;
}

/**
* The space round a shape that is all its composed cell holds, where the cell sets it down its
* height as the page does.
Expand Down Expand Up @@ -7038,7 +7069,8 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
holdTheSpaceAboveOnItsPage(document);
}
holdTheSpaceAboveATable(document);
double width = panelWidth(node);
PanelWidth sized = panelWidth(node);
double width = sized.width();

double edge = insetLeft + margin.left();
// In the body the indent places the text, the padding in, and the margin is the whole
Expand All @@ -7054,6 +7086,9 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
// it, the points taken from its right margin and no more than it holds, so its text keeps
// its place and its width.
double shortOfTheEdge = 0;
// What of the border its right margin cannot take in, and the cell cannot hold, passes
// the cell's edge.
double pastTheEdge = 0;
double room = currentCell != null && Double.isFinite(currentCellWidth) && strokeWidth(borders.right()) > 0
? currentCellWidth - insetRight - Math.max(0, indent) - strokeWidth(borders.right())
- CLEAR_OF_THE_GRIDLINE_POINTS
Expand Down Expand Up @@ -7084,6 +7119,14 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
double given = halfLeft + Math.min(halfLeft, Math.max(0, padding.left()));
outer -= Math.max(0, Math.min(given, halfLeft + outer - (currentCellWidth - insetRight)));
}
if (Double.isFinite(room)) {
// The room ends at the cell's text and half a point clear of the gridline; what
// the cell's right margin, what holds the panel in there and that half point do
// not take passes the cell's edge — all of the border, in a cell too narrow to
// take any of it in.
pastTheEdge = Math.max(0, outer - room - insetRight - cellMarginOf(currentCell, false)
- CLEAR_OF_THE_GRIDLINE_POINTS);
}
setTableWidth(table, outer);
writeGrid(table, new double[]{outer});
CTTcPr properties = cellProperties(cell);
Expand All @@ -7105,6 +7148,12 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
if (first) {
reportFlattenedPaint(node, translucentFill, translucentSides, "its fill", "its borders");
reportBorderWidths(node, "its borders", true, widthsOf(borders));
// What the table gives up of the slack it was given past the page's width is not
// narrower than the page draws it.
String rightBorder = rightBorderHeldIn(Math.max(0, shortOfTheEdge - sized.slack()), pastTheEdge);
if (rightBorder != null) {
report.add(DocxExportReport.Severity.APPROXIMATED, RIGHT_BORDER, layout.pathOf(node), rightBorder);
}
}
DocumentInsets margins = insideTheBorders(padding, borders);
applyCellPadding(cell, new DocumentInsets(margins.top(), Math.max(0, margins.right() - shortOfTheEdge),
Expand Down Expand Up @@ -7383,12 +7432,21 @@ private void holdTheSpaceAboveATable(XWPFDocument document) {
}
}

/**
* How wide a panel is written, and how much of that is slack past the page's width.
*
* @param width the width, or {@code NaN} where it is left to the editor
* @param slack what of it the editor is given past the width the page draws
*/
private record PanelWidth(double width, double slack) {
}

/**
* How wide a panel is: as the layout placed it, or else what is left where it is written,
* less its own side margins — or unknown, left to the editor, where neither is known: no
* canvas, or a cell whose width this export did not write.
*/
private double panelWidth(DocumentNode node) {
private PanelWidth panelWidth(DocumentNode node) {
boolean known = currentCell != null ? Double.isFinite(currentCellWidth) : contentWidth < Double.MAX_VALUE;
// A shape's padding stands round its outline, as its margin does (writePanelPiece).
double sides = node.margin().left() + node.margin().right()
Expand All @@ -7403,13 +7461,14 @@ private double panelWidth(DocumentNode node) {
: shape.outline().width());
}
if (placed.isEmpty()) {
return available;
return new PanelWidth(available, 0);
}
// A panel sized round its content is exactly as wide as its longest line, which an
// editor setting the text in its own face can push onto a second line; it gets the
// slack an auto column gets, where there is room for it.
double room = Double.isFinite(available) ? available - placed.getAsDouble() : EDITOR_COLUMN_SLACK_POINTS;
return placed.getAsDouble() + Math.max(0, Math.min(EDITOR_COLUMN_SLACK_POINTS, room));
double slack = Math.max(0, Math.min(EDITOR_COLUMN_SLACK_POINTS, room));
return new PanelWidth(placed.getAsDouble() + slack, slack);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ record Entry(Fate fate, String note) {
+ "short of it then needs more than the page's height, the row "
+ "taller; any other is written";

// Word cuts off what passes a table cell's edge, and draws a nested table's right border
// outside the table: a panel reaching its cell's edge is narrowed out of its right margin.
static final String RIGHT_BORDER = "a panel's right border reaching its table cell's edge, its table written "
+ "narrower out of its right margin so the border ends half a point short "
+ "of the edge, which would cut it off; how much narrower than the page, "
+ "and about how far what the margin cannot take in still passes the edge, "
+ "named (right border)";

// A row in the flow is written as a panel holding its columns, its paint the panel's.
private static final String ROW_PAINT = "composed in a table cell, drawn as a shape where it frames no text "
+ "and not written where it does; in a page zone's line, in a band of "
Expand All @@ -153,9 +161,6 @@ record Entry(Fate fate, String note) {
+ "row's column set at its middle or foot, which Word does not "
+ "count there, the block set that much lower, not named but for "
+ "a shape container";
static final String RIGHT_BORDER = "a panel's right border reaching its table cell's edge, drawn up to its width "
+ "and half a point short of it so the cell's gridline does not cover it, not "
+ "named";
static final String CELL_PADDING = "a top or bottom a cell keeps where Word sets it, one opening with a table or "
+ "spanning rows, which Word gives the row's other cells too, not named";
static final String LIST_PICTURE = "in a list's item or marker, placed by the list's first line and the item's "
Expand All @@ -182,7 +187,7 @@ record Entry(Fate fate, String note) {

/** The known cases: a gap's note opens with one of them, or the ledger fails. */
private static final List<String> GAPS = List.of(TOP_PULL, BOTTOM_PULL, PADDING_BELOW_ZERO, SPACE_BELOW_AT_THE_FOOT,
RIGHT_BORDER, CELL_PADDING, LIST_PICTURE, SEAT_UNREAD, ROW_COLUMNS_UNLAID, DRAWING_OVER_TEXT,
CELL_PADDING, LIST_PICTURE, SEAT_UNREAD, ROW_COLUMNS_UNLAID, DRAWING_OVER_TEXT,
COMPOSED_STACK_LAYER, LAYER_PAST_THE_EDGE, LAYER_UNLAID, PANEL_WIDER_THAN_ITS_CELL);

private static final Map<Class<?>, Map<String, Entry>> NODES = new LinkedHashMap<>();
Expand Down Expand Up @@ -240,8 +245,8 @@ record Entry(Fate fate, String note) {
"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:" + PANEL_TRANSLUCENCY,
"stroke:GAP:" + RIGHT_BORDER + "; " + PANEL_BORDERS,
"cornerRadius:REPORTED", "borders:GAP:" + RIGHT_BORDER + "; " + PANEL_BORDERS,
"stroke:REPORTED:" + RIGHT_BORDER + "; " + PANEL_BORDERS,
"cornerRadius:REPORTED", "borders:REPORTED:" + RIGHT_BORDER + "; " + 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");
Expand Down Expand Up @@ -352,10 +357,10 @@ 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:" + ROW_PAINT, "stroke:GAP:" + RIGHT_BORDER + "; " + ROW_PAINT,
"fillColor:REPORTED:" + ROW_PAINT, "stroke:REPORTED:" + RIGHT_BORDER + "; " + ROW_PAINT,
"cornerRadius:REPORTED:a panel's corners, which a Word table cell does not round; with no paint "
+ "it paints nothing",
"borders:GAP:" + RIGHT_BORDER + "; " + ROW_PAINT,
"borders:REPORTED:" + RIGHT_BORDER + "; " + 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
Expand All @@ -369,8 +374,8 @@ record Entry(Fate fate, String note) {
"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:" + PANEL_TRANSLUCENCY,
"stroke:GAP:" + RIGHT_BORDER + "; " + PANEL_BORDERS,
"cornerRadius:REPORTED", "borders:GAP:" + RIGHT_BORDER + "; " + PANEL_BORDERS,
"stroke:REPORTED:" + RIGHT_BORDER + "; " + PANEL_BORDERS,
"cornerRadius:REPORTED", "borders:REPORTED:" + RIGHT_BORDER + "; " + 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",
Expand All @@ -394,7 +399,7 @@ record Entry(Fate fate, String note) {
"clipPolicy:REPORTED:where it cuts what its layers paint; composed in a table cell, on its table",
"fillColor:REPORTED:its translucency, as a panel's, flattened against the colour under it; "
+ "drawn, its alpha is written; any other is written",
"stroke:GAP:" + RIGHT_BORDER + "; its translucency, as a panel's border, flattened against the "
"stroke:REPORTED:" + RIGHT_BORDER + "; 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",
Expand Down
Loading
Loading