Skip to content
Closed
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
4 changes: 4 additions & 0 deletions news/changelog-1.10.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ All changes included in 1.10:

- ([#14530](https://github.com/quarto-dev/quarto-cli/pull/14530)): Add `quarto.*` Pandoc template variable namespace. `format.language` is now exposed as `$quarto.language.<key>$` in custom Pandoc templates via the defaults-file `variables:` section, with no leakage into rendered output.

### `docx`

- ([#14624](https://github.com/quarto-dev/quarto-cli/issues/14624)): Add `line-numbers` option to enable continuous line numbering in Word output.

### `pdf`

- ([#13588](https://github.com/quarto-dev/quarto-cli/issues/13588)): Fix Lua error when rendering PDF with `reference-location: margin` and a footnote alongside a figure with `fig-cap`. (author: @mcanouil)
Expand Down
65 changes: 57 additions & 8 deletions src/format/docx/format-docx.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
/*
* format-docx.ts
*
* Copyright (C) 2020-2022 Posit Software, PBC
*
*/
* format-docx.ts
*
* Copyright (C) 2020-2022 Posit Software, PBC
*/

import { kFilterParams } from "../../config/constants.ts";
import { Format } from "../../config/types.ts";
import { Format, FormatExtras, PandocFlags } from "../../config/types.ts";
import { mergeConfigs } from "../../core/config.ts";
import { formatResourcePath } from "../../core/resources.ts";
import { createWordprocessorFormat } from "../formats-shared.ts";
import { unzip, zip } from "../../core/zip.ts";
import { join, resolve } from "../../deno_ral/path.ts";

const kIconCaution = "icon-caution";
const kIconImportant = "icon-important";
const kIconNote = "icon-note";
const kIconTip = "icon-tip";
const kIconWarning = "icon-warning";

const kLineNumbers = "line-numbers";

export function docxFormat(): Format {
return mergeConfigs(
createWordprocessorFormat("MS Word", "docx"),
{
formatExtras: () => {
return {
formatExtras: (
_input: string,
_markdown: string,
_flags: PandocFlags,
format: Format,
): FormatExtras => {
const extras: FormatExtras = {
[kFilterParams]: {
[kIconCaution]: iconPath("caution.png"),
[kIconImportant]: iconPath("important.png"),
Expand All @@ -31,6 +39,15 @@ export function docxFormat(): Format {
[kIconWarning]: iconPath("warning.png"),
},
};
// `line-numbers: true` enables continuous Word line numbering. Word
// stores it in each section's <w:sectPr>, which pandoc writes from the
// reference doc after filters run, so it can only be applied by a
// post-processor on the finished file (see addDocxLineNumbers).
const lineNumbers = format.metadata[kLineNumbers];
if (lineNumbers === true || lineNumbers === "true") {
extras.postprocessors = [addDocxLineNumbers];
}
return extras;
},
extensions: {
book: {
Expand All @@ -44,3 +61,35 @@ export function docxFormat(): Format {
function iconPath(icon: string) {
return formatResourcePath("docx", icon);
}

// Turn on continuous line numbering by adding <w:lnNumType> to the section
// properties (<w:sectPr>) already present in the rendered document. Because we
// edit the existing sections — which carry the header references and page size
// — the running head and paper size (Letter, A4, etc.) are preserved.
async function addDocxLineNumbers(
output: string,
): Promise<{ supporting?: string[]; resources?: string[] } | void> {
const outputPath = resolve(output);
const tempDir = await Deno.makeTempDir();
try {
await unzip(outputPath, tempDir);
const documentXml = join(tempDir, "word", "document.xml");
const xml = await Deno.readTextFile(documentXml);
// Add line numbering to each section that lacks it (idempotent). The regex
// assumes sections are not nested, which holds for pandoc/Quarto output.
const updated = xml.replace(
/<w:sectPr\b[\s\S]*?<\/w:sectPr>/g,
(sectPr) =>
sectPr.includes("<w:lnNumType") ? sectPr : sectPr.replace(
"</w:sectPr>",
'<w:lnNumType w:countBy="1" w:restart="continuous"/></w:sectPr>',
),
);
await Deno.writeTextFile(documentXml, updated);
// Repackage the edited document parts back into the .docx.
await Deno.remove(outputPath);
await zip(["."], outputPath, { cwd: tempDir });
} finally {
await Deno.remove(tempDir, { recursive: true });
}
}
8 changes: 8 additions & 0 deletions src/resources/schema/document-options.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
Use the specified file as a style reference in producing a docx,
pptx, or odt file.

- name: line-numbers
tags:
formats: [docx]
schema: boolean
default: false
description: |
Add continuous line numbering to the margins of the document.

- name: brand
schema:
ref: brand-path-bool-light-dark
Expand Down
17 changes: 17 additions & 0 deletions tests/docs/smoke-all/2026/06/27/docx-line-numbers.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
format:
docx:
line-numbers: true
_quarto:
tests:
docx:
ensureDocxXpath:
-
- "//w:lnNumType"
- []
---

# Section

A paragraph of text so that the rendered Word document has several lines, which
`line-numbers: true` should number continuously in the margin.
Loading