Skip to content

Docx writer: the paragraph after a table sits flush against it (Word drops the line lead) #11901

Open

Description

@sghng

Explain the problem.

Word attaches a paragraph's line-spacing lead below its text and
applies none of it between a table and the following paragraph. With a
reference doc whose body style carries no before-spacing (a
double-spaced manuscript template, APA in our case), the first
paragraph after a table glues to the table's bottom rule, while the
same paragraph after another paragraph shows a full blank line.
LibreOffice applies the lead at table boundaries, so rendering there
hides the bug; Word is the ground truth.

Reproduce (stock pandoc, with the default reference doc doctored to
a double-spaced zero-after body, i.e. any real manuscript template):

cd $(mktemp -d)
pandoc -o ref.docx --print-default-data-file reference.docx
mkdir x && cd x && unzip -q ../ref.docx
python3 - <<'PY'
p = 'word/styles.xml'
s = open(p).read()
s = s.replace('<w:spacing w:before="180" w:after="180" />',
              '<w:spacing w:before="0" w:after="0" w:line="480" w:lineRule="auto" />')
open(p, 'w').write(s)
PY
zip -q -r ../ref2.docx . && cd ..
cat > t.md <<'EOF'
| A | B |
|---|---|
| 1 | 2 |

Paragraph after the table.

Paragraph after a paragraph.
EOF
pandoc t.md --reference-doc=ref2.docx -o t.docx
unzip -p t.docx word/document.xml | grep -o 'w:spacing[^/]*/>'

Received: every paragraph relies on the style's spacing; none
carries direct spacing, so in Word the paragraph after the table has
nothing above it but the table rule (open t.docx in Word: the first
paragraph touches the rule; the second sits a full blank line below
the first).

Expected: the writer supplies boundary air: the paragraph
following a table carries direct w:spacing w:before="240" (half the
line pitch of a double-spaced line; full lead comes from the
paragraph's own lead below its text). A heading after a table reads
the same boundary mark, since Word gives a heading nothing above it
either. The shared FirstParagraph style cannot express this (it is
also the post-heading paragraph style).

Root cause: the docx writer emits no per-paragraph boundary
spacing at all (blockToOpenXML' for Para in
src/Text/Pandoc/Writers/Docx/OpenXML.hs builds w:pPr from
envParaProperties, which carries only style/indent state), and the
reference doc's styles were never expected to compensate for Word's
asymmetric lead at table boundaries.

Fix: mark the table boundary in the writer state and let the next
paragraph (or heading) consume it as direct w:before spacing;
intervening blocks absorb the mark, and containers (divs, table cells)
stay transparent. The value 240 twips = half the line pitch was
measured in Word (an initial 120 proved too small); the exact value
wants discussion.

Note

This is 11/13 of issues identified in an attempt to create an APA styled Word target from a Typst source. The issues are ordered logically so that fundamental fixes are applied first. I have fixed all of them locally and will split and upstream the fixes one by one.

Activity

  1. jgm commented on Sep 25, 2026

    @jgm
    Owner

    Would it fix things if the reference.docx included some before/after spacing in the style for table?

  2. sghng commented on Sep 25, 2026

    @sghng
    ContributorAuthor

    Would it fix things if the reference.docx included some before/after spacing in the style for table?

    Not really.

    • A table style has no spacing property. Spacing is a paragraph property; there is no "space after table" setting. Air below a table has to come from the paragraph after it.
    • That paragraph's style can't know its position. The default reference hides the bug by giving BodyText before=180 on every paragraph. Manuscript templates (e.g. APA) remove that on purpose, double spacing and the first-line indent already mark the breaks, and that's when the bug appears: Word puts a line's extra spacing below its text, so a paragraph after a paragraph shows a blank line of air, but a table has no "below" spacing, and the next paragraph's spacing sits below its own text, it glues to the table's bottom rule.
    • FirstParagraph, the writer's only positional style, is never assigned after tables and can't be, since it's shared with post-heading and post-list paragraphs and would air those boundaries equally.

    So the value can live in a reference, but only the writer knows the position, hence direct w:before on the paragraph after a table, which works with any reference doc.

  3. jgm commented on Sep 26, 2026

    @jgm
    Owner

    Have you tried with

    s = s.replace('<w:spacing w:before="180" w:after="180" />',
                  '<w:spacing w:before="480" w:after="0" w:line="480" w:lineRule="auto" />')
    

    Openxml docs say: "When determining the spacing between any two paragraphs, a consumer shall use the maximum of the inter-line spacing in each paragraph, the spacing after the first paragraph and the spacing before the second paragraph to determine the net spacing between the paragraphs."

    So, adding spacing before won't lead to an extra space between paragraphs, but it will ensure a space in this case. I'm guessing!

    Edit: it doesn't seem to work the way the docs say here...

  4. sghng commented on Sep 26, 2026

    @sghng
    ContributorAuthor

    @jgm I also tested your fix, it does have a problem, it inflates all paragraph spacing by 480 twips... For context, when I was doing the Typst --pandoc--> APA Word conversion, I prioritized reference.docx and Lua filter, the issues I filed can't be done with them (or at least I haven't found a solution without patching pandoc)

  5. jgm commented on Sep 26, 2026

    @jgm
    Owner

    Inserting this into the style for "Table" works fine as far as I can see:

        <w:pPr>
          <w:spacing w:after="480" />
        </w:pPr>
    Image
  6. jgm commented on Sep 26, 2026

    @jgm
    Owner

    Here is my modified ref.docx if you want to try it.

    ref.docx

  7. sghng commented on Sep 26, 2026

    @sghng
    ContributorAuthor

    @jgm I tried your modified ref.docx, and separately rebuilt the same edit into my own APA reference.docx. The generated document carries the after="480" spacing in the Table style, confirmed in the output XML. It has no effect: in Word the paragraph after the table stays glued, and LibreOffice renders pixel-identical output with and without it.

    The reason is in the document, not the reference. Every paragraph pandoc puts in a table cell carries a paragraph style: Compact (with its own before/after spacing of 36 twips) for simple tables, Body Text for tables with multi-paragraph cells. A paragraph style's spacing overrides a table style's w:pPr, so the table style's after-spacing never reaches any cell.

    This is a result of the #11864 and 7cf4d41 fix. compactifyTable makes every simple-table cell carry Compact, and multi-paragraph cells carry Body Text, so no pandoc-produced table has unstyled cell paragraphs for the table style to reach. The reference fix can only affect documents whose cells carry no paragraph style at all, which I'm guessing is what the document in your screenshot had.

    And dropping the styles is not an option in my case. The document is double spaced (APA manuscript), so the document default line spacing is double. An unstyled cell paragraph inherits that, and every row gets inflated, which is the row bloat of #11864 again. I pinned Compact to single line spacing precisely to keep cells tight; its 36 twips of before/after is deliberate small padding. So the cells must carry a style, and every style we could put on them defines its own after-spacing, which shadows whatever the table style tries to add.

    For completeness, the one table-style property that does reach cells regardless of paragraph styles is the cell bottom margin (tblCellMar). It engages, but the space lands inside every row, between the text and the rule. It pads the header and interior rows too, and never appears between the table and the following paragraph.

    So the reference can't carry this one, and direct w:before on the paragraph after the table remains the only mechanism that puts air in the right place.

  8. jgm commented on Sep 26, 2026

    @jgm
    Owner

    I'm a bit confused. See the screenshot above; this is my Word with the ref doc I sent you (double spaced, no space before or after on BodyText; but extra space after table), used to convert a markdown sample more or less like the one you gave. As you can see, the line after the table is plainly not glued; there is space between. This is using pandoc built from HEAD. If your reference doc gives different results, it must be because of some other difference in it.

  9. jgm commented on Sep 26, 2026

    @jgm
    Owner

    OK, when I added single spacing and no space before/after to Compact, I get

    Image

    which explains why you are seing something different.

  10. sghng commented on Sep 26, 2026

    @sghng
    ContributorAuthor

    @jgm The patched reference.docx I use is here: https://github.com/sghng/abstract/tree/main/tools, to use it simply run bun run abstract typ2docx file.typ (requires bun). This tool will construct the reference.docx on the fly by applying the patches I defined on the vinilla reference.docx.

  11. jgm commented on Sep 26, 2026

    @jgm
    Owner

    I don't like the idea of having the writer insert a fixed before space after a table. What about just having a special style for the paragraph following a table, similar to FirstParagraph?

  12. sghng commented on Sep 26, 2026

    @sghng
    ContributorAuthor

    I think that's the better solution. A few nuances to be deliberate about in the implementation:

    • Existing reference docs don't define the new style. For a style missing from the reference, the writer injects a bare placeholder definition, which is empty and based on nothing. A paragraph after a table would then lose everything it inherits from Body Text today, including the first-line indent in templates that use one. That's a silent regression on upgrade for anyone with a custom reference. Two ways out: fall back to Body Text when the reference lacks the style (today's behavior, nothing changes), or inject the placeholder with basedOn="BodyText" so inheritance survives.
    • The block after a table is not always a body paragraph. With --table-caption-position=below, it's the caption, which must keep its Caption style rather than take the new one. And the writer already inserts an empty paragraph between adjacent tables (separateTables, so Word doesn't merge them); that separator is also technically the paragraph after a table, and giving it the style would widen the gap between consecutive tables. The rule should be: only paragraphs that would otherwise get Body Text or FirstParagraph receive the new style.
    • Headings after a table can't take it. A heading can't wear a body paragraph style, so the boundary above a post-table heading stays glued unless headings get their own treatment. Might be fine to leave that out of scope.

    Changes: a new style as follows, and patching the writer.

    <w:style w:type="paragraph" w:customStyle="1" w:styleId="FirstParagraphAfterTable">
      <w:name w:val="First Paragraph After Table" />
      <w:basedOn w:val="BodyText" />
      <w:pPr>
        <w:spacing w:before="180" />
      </w:pPr>
    </w:style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions