Simplified Chinese | English
FeatherDoc is a modernized C++ library for reading and writing Microsoft Word
.docx files.
- CMake 3.20+
- C++20
- MSVC-friendly build setup
- Lightweight document editing APIs for paragraphs, runs, tables, images, lists, and style references
- MSVC-safe XML parsing on
open() - Streamed ZIP rewrite path on
save()
cmake -S . -B build
cmake --build buildTop-level builds enable BUILD_CLI by default, so the featherdoc_cli
utility is built alongside the library unless you pass -DBUILD_CLI=OFF.
Open an x64 Visual Studio Developer Command Prompt first, or initialize the
environment with VsDevCmd.bat -arch=x64 -host_arch=x64, then run:
cmake -S . -B build-msvc-nmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DBUILD_SAMPLES=ON -DBUILD_CLI=ON
cmake --build build-msvc-nmake
ctest --test-dir build-msvc-nmake --output-on-failure --timeout 60On Windows hosts with Microsoft Word installed you can run a visual smoke check that:
- builds a dedicated sample document covering table layout features
- exports the generated
.docxthrough Word's rendering engine as PDF - renders each PDF page to PNG for manual or AI-assisted review
powershell -ExecutionPolicy Bypass -File .\scripts\run_word_visual_smoke.ps1For the fixed-grid merge/unmerge sample quartet specifically, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_fixed_grid_merge_unmerge_regression.ps1For a before/after Word-rendered check of the new template-table CLI row/cell commands, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_visual_regression.ps1That regression now generates a dedicated baseline document covering body,
section-header, and section-footer template tables, applies
set-template-table-cell-text, append-template-table-row,
insert-template-table-row-after, insert-template-table-row-before, and
remove-template-table-row to each target table in turn, then captures
Word-rendered baseline/mutated evidence plus per-case and aggregate
before/after contact sheets under
output/template-table-cli-visual-regression/.
For a dedicated before/after Word-rendered check of the bookmark-targeted table workflow, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_bookmark_visual_regression.ps1That regression generates a body-only baseline document with one retained
table plus one target table addressed by both a bookmark paragraph immediately
before it and a bookmark inside its first data cell. It applies
set-template-table-row-texts and
set-template-table-cell-block-texts through --bookmark, then writes
Word-rendered baseline/mutated evidence plus per-case and aggregate
before/after contact sheets under
output/template-table-cli-bookmark-visual-regression/.
For a dedicated Word-rendered overlap check of floating-image anchor z-order, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_floating_image_z_order_visual_regression.ps1That regression builds the dedicated sample_floating_image_z_order_visual
fixture, verifies both anchored images through CLI inspection and extraction,
then captures the rendered first page so you can confirm the orange floating
image overlaps and appears above the blue one under
output/floating-image-z-order-visual-regression/.
For a before/after Word-rendered check of section-scoped template-table
row/cell commands across --kind default/even/first, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_section_kind_visual_regression.ps1That regression generates one shared baseline document covering
section-header --kind default, section-header --kind even,
section-footer --kind first, and section-footer --kind default, applies
the same row/cell mutation chain to each target table, then captures the
relevant Word-rendered page-01 / page-02 / page-03 evidence instead of
only the first page so the kind-specific rendering path is visible under
output/template-table-cli-section-kind-visual-regression/.
For a dedicated before/after Word-rendered check of template-table row
commands across section-scoped --kind default/even/first, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_section_kind_row_visual_regression.ps1That regression generates four dedicated baseline documents covering
section-header --kind default insert-row-before,
section-header --kind even append-row,
section-footer --kind first remove-row, and
section-footer --kind default insert-row-after, applies the matching
row mutation plus set-template-table-cell-text where needed, then captures
the relevant Word-rendered page-01 / page-02 / page-03 evidence under
output/template-table-cli-section-kind-row-visual-regression/.
For a before/after Word-rendered check of template-table column commands across
section-scoped --kind default/even/first, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_section_kind_column_visual_regression.ps1That regression generates four dedicated baseline documents covering
section-header --kind default insert-before,
section-header --kind even remove-column,
section-footer --kind first insert-after, and
section-footer --kind default remove-column, applies the matching
column mutation chain plus set-template-table-cell-text where needed, then
captures the relevant Word-rendered page-01 / page-02 / page-03
evidence under
output/template-table-cli-section-kind-column-visual-regression/.
For a before/after Word-rendered check of template-table merge/unmerge
commands across section-scoped --kind default/even/first, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_section_kind_merge_unmerge_visual_regression.ps1That regression generates four dedicated baseline documents covering
section-header --kind default merge-right,
section-header --kind even unmerge-right,
section-footer --kind first merge-down, and
section-footer --kind default unmerge-down, applies the matching
merge/unmerge CLI mutation, verifies the underlying XML merge markers, then
captures the relevant Word-rendered page-01 / page-02 / page-03
evidence under
output/template-table-cli-section-kind-merge-unmerge-visual-regression/.
For a before/after Word-rendered check of the new template-table CLI merge/unmerge commands, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_merge_unmerge_visual_regression.ps1That regression generates four dedicated baseline documents covering
section-header horizontal merge, section-footer vertical merge, and
body horizontal/vertical unmerge scenarios, applies
merge-template-table-cells or unmerge-template-table-cells, then captures
Word-rendered baseline/mutated evidence plus per-case and aggregate
before/after contact sheets under
output/template-table-cli-merge-unmerge-visual-regression/.
For a before/after Word-rendered check of the template-table CLI row/cell
commands on direct header / footer parts, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_direct_visual_regression.ps1That regression generates one shared baseline document covering direct
header, direct footer, and body target tables, applies
set-template-table-cell-text, append-template-table-row,
insert-template-table-row-after, insert-template-table-row-before, and
remove-template-table-row through the direct-part selector model, then
captures Word-rendered baseline/mutated evidence plus per-case and aggregate
before/after contact sheets under
output/template-table-cli-direct-visual-regression/.
For a before/after Word-rendered check of the new template-table CLI column commands, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_column_visual_regression.ps1That regression generates three dedicated baseline documents covering
section-header insert-before, section-footer insert-after, and body
remove-column scenarios, applies insert-template-table-column-before,
insert-template-table-column-after, remove-template-table-column, and
set-template-table-cell-text where needed, then captures Word-rendered
baseline/mutated evidence plus per-case and aggregate before/after contact
sheets under output/template-table-cli-column-visual-regression/.
For a before/after Word-rendered check of the same template-table column
commands on direct header / footer parts, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_direct_column_visual_regression.ps1That regression generates three dedicated baseline documents covering direct
header insert-before, direct footer insert-after, and body
remove-column scenarios, applies the same CLI column mutations, then captures
Word-rendered baseline/mutated evidence plus per-case and aggregate
before/after contact sheets under
output/template-table-cli-direct-column-visual-regression/.
For a before/after Word-rendered check of template-table merge/unmerge commands
on direct header / footer parts, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_template_table_cli_direct_merge_unmerge_visual_regression.ps1That regression generates three dedicated baseline documents covering direct
header merge-right, direct footer unmerge-down, and body merge-down
scenarios, applies merge-template-table-cells or
unmerge-template-table-cells, then captures Word-rendered baseline/mutated
evidence plus per-case and aggregate before/after contact sheets under
output/template-table-cli-direct-merge-unmerge-visual-regression/.
If you want the quartet regression to immediately emit an AI review task package after the Word screenshot bundle is ready, run:
powershell -ExecutionPolicy Bypass -File .\scripts\run_fixed_grid_merge_unmerge_regression.ps1 `
-PrepareReviewTask `
-ReviewMode review-onlyThat script builds the four dedicated sample targets, generates one .docx
per case under output/fixed-grid-merge-unmerge-regression/, and, unless you
pass -SkipVisual, also runs the existing Word PDF/PNG capture flow under each
case's visual/ directory. It also writes a root-level aggregate contact sheet
plus review_manifest.json, review_checklist.md, and final_review.md so
human or AI reviewers can inspect the whole fixed-grid quartet from one place.
When -PrepareReviewTask is enabled, it also invokes
prepare_word_review_task.ps1 automatically and records the generated task
metadata back into the root summary.json and review_manifest.json.
Artifacts are written under output/word-visual-smoke/, including the source
.docx and exported .pdf, plus an evidence/ directory for per-page .png
renders and contact sheets, a report/ directory for the generated checklist
and summary data, generated review_result.json and final_review.md
skeletons, and a reserved repair/ directory for iterative fix candidates.
The script now also validates two things before you trust the result:
- generated smoke samples must contain the expected core DOCX ZIP entries
- exported PDF page count must match both
summary.jsonand the rendered PNG count
Pass -InputDocx <path> when you want to run the same render-and-capture flow
against an existing document instead of the bundled smoke sample. The
recommended review-and-repair workflow is documented in
docs/automation/word_visual_workflow_zh.rst.
Use -SkipBuild only when the executable under -BuildDir is known to be
current. If the smoke sample generator is stale, rerun without -SkipBuild or
rebuild that directory first.
When you want to hand the task to an AI agent consistently, generate a review task package first:
powershell -ExecutionPolicy Bypass -File .\scripts\prepare_word_review_task.ps1 `
-DocxPath .\path\to\target.docx `
-Mode review-onlyFor the fixed-grid regression bundle generated by
run_fixed_grid_merge_unmerge_regression.ps1, use:
powershell -ExecutionPolicy Bypass -File .\scripts\prepare_word_review_task.ps1 `
-FixedGridRegressionRoot .\output\fixed-grid-merge-unmerge-regression `
-Mode review-onlyUse -Mode review-and-repair when the agent is allowed to fix generation
logic and rerun the visual review loop. The script generates a task directory
with task_prompt.md, task_manifest.json, and dedicated evidence/,
report/, and repair/ directories, so the AI no longer has to guess paths
or output structure. The intended handoff flow is:
- Run
prepare_word_review_task.ps1. - Open the generated
task_prompt.md. - Send the full prompt to the AI agent and require it to first run
scripts/run_word_visual_smoke.ps1 -InputDocx ... -OutputDir <task dir>. - Require the agent to review the generated PDF/PNG evidence and write its
conclusion back into
report/review_result.jsonandreport/final_review.md. - If the mode is
review-and-repair, allow the agent to iterate underrepair/fix-XX/and rerun the full visual review loop after each fix.
Every task generation also refreshes stable pointer files under
output/word-visual-smoke/tasks/, including latest_task.json plus a
source-kind-specific file such as
latest_fixed-grid-regression-bundle_task.json or
latest_template-table-cli-selector-visual-regression-bundle_task.json, so
external automation can resolve the newest task package without guessing
timestamped directory names.
When you want to consume those pointers directly, use:
powershell -ExecutionPolicy Bypass -File .\scripts\open_latest_word_review_task.ps1Or for the newest fixed-grid bundle task specifically:
powershell -ExecutionPolicy Bypass -File .\scripts\open_latest_word_review_task.ps1 `
-SourceKind fixed-grid-regression-bundle `
-PrintPromptOr for a curated visual regression bundle emitted by the release gate:
powershell -ExecutionPolicy Bypass -File .\scripts\open_latest_word_review_task.ps1 `
-SourceKind template-table-cli-selector-visual-regression-bundle `
-PrintPromptFor automation-friendly raw outputs, use:
powershell -ExecutionPolicy Bypass -File .\scripts\open_latest_word_review_task.ps1 `
-SourceKind fixed-grid-regression-bundle `
-PrintField task_dir
powershell -ExecutionPolicy Bypass -File .\scripts\open_latest_word_review_task.ps1 `
-SourceKind fixed-grid-regression-bundle `
-PromptOnlyFor the fixed-grid path specifically, the shorter wrappers are:
powershell -ExecutionPolicy Bypass -File .\scripts\open_latest_fixed_grid_review_task.ps1 `
-PrintField task_dir
powershell -ExecutionPolicy Bypass -File .\scripts\print_latest_fixed_grid_review_prompt.ps1For fixed-grid bundle tasks, the task package also copies the aggregate contact sheet, bundle summary, bundle review manifest, aggregate first-page PNGs, plus the source bundle's checklist/final-review markdown into the task directory, so the reviewer can start from a single visual bundle instead of discovering each case path manually.
The screenshots below come from actual Word-rendered validation artifacts saved in the repository, so README readers can see the current layout coverage, rendering quality, and screenshot-backed review surface directly. The focused row below intentionally mixes two standalone fixed-grid closure samples, the aggregate quartet signoff bundle, and one Chinese business-template result so the library's editing surface is visible at a glance.
Top: the full 6-page Word visual smoke contact sheet generated from the current validation flow, covering tables, pagination, merges, text direction, fixed-grid width editing, and mixed RTL/LTR/CJK review content.
Bottom row, left to right: a standalone merge_right() closure check after reopen, a standalone merge_down() closure check after reopen, the aggregate fixed-grid quartet signoff bundle, and a Chinese invoice template rendered through Microsoft Word. The first two screenshots come from sample_merge_right_fixed_grid.cpp and sample_merge_down_fixed_grid.cpp; they make the fixed-width closure signal obvious without needing to read XML. The fourth image comes from sample_chinese_template.cpp and shows CJK text plus business-table output in Word rather than synthetic mockups.
To regenerate the exact focused gallery pages, build and run the dedicated sample targets first. Adjust the executable path if your generator places the sample binaries somewhere other than the build root:
cmake --build build-msvc-nmake --target `
featherdoc_sample_merge_right_fixed_grid `
featherdoc_sample_merge_down_fixed_grid `
featherdoc_sample_chinese_template
.\build-msvc-nmake\featherdoc_sample_merge_right_fixed_grid.exe `
.\output\sample-merge-right-fixed-grid\merge_right_fixed_grid.docx
.\build-msvc-nmake\featherdoc_sample_merge_down_fixed_grid.exe `
.\output\sample-merge-down-fixed-grid\merge_down_fixed_grid.docx
.\build-msvc-nmake\featherdoc_sample_chinese_template.exe `
.\samples\chinese_invoice_template.docx `
.\output\sample-chinese-template\sample_chinese_invoice_output.docx
powershell -ExecutionPolicy Bypass -File .\scripts\run_word_visual_smoke.ps1 `
-InputDocx .\output\sample-merge-right-fixed-grid\merge_right_fixed_grid.docx `
-OutputDir .\output\word-visual-sample-merge-right-fixed-grid
powershell -ExecutionPolicy Bypass -File .\scripts\run_word_visual_smoke.ps1 `
-InputDocx .\output\sample-merge-down-fixed-grid\merge_down_fixed_grid.docx `
-OutputDir .\output\word-visual-sample-merge-down-fixed-grid
powershell -ExecutionPolicy Bypass -File .\scripts\run_word_visual_smoke.ps1 `
-InputDocx .\output\sample-chinese-template\sample_chinese_invoice_output.docx `
-OutputDir .\output\word-visual-sample-chinese-templateTo rerun the fixed-grid quartet that feeds the aggregate contact sheet and prepare a ready-to-review task package:
powershell -ExecutionPolicy Bypass -File .\scripts\run_fixed_grid_merge_unmerge_regression.ps1 `
-PrepareReviewTask `
-ReviewMode review-onlyTo refresh the repository-side gallery PNGs after those sample renders or a new release-gate pass, use:
powershell -ExecutionPolicy Bypass -File .\scripts\refresh_readme_visual_assets.ps1To jump from these screenshots to a review-task handoff, start with VISUAL_VALIDATION_QUICKSTART.md and then use VISUAL_VALIDATION.md or VISUAL_VALIDATION.zh-CN.md for the longer mapping.
For a one-shot local gate that also refreshes the repository gallery, use:
powershell -ExecutionPolicy Bypass -File .\scripts\run_word_visual_release_gate.ps1 `
-RefreshReadmeAssetsAfter the screenshot-backed review tasks you care about are signed off, including any curated visual regression bundles emitted by the gate, sync the final verdict back into the gate summary with:
powershell -ExecutionPolicy Bypass -File .\scripts\sync_latest_visual_review_verdict.ps1That shortest sync path now walks every latest_*_task.json pointer it can
resolve under the task root, so later curated visual-regression bundle tasks
from run_word_visual_release_gate.ps1 are promoted together with the classic
document / fixed-grid / section-page-setup / page-number-fields tasks.
If you need to override the inferred gate/release paths manually, use:
powershell -ExecutionPolicy Bypass -File .\scripts\sync_visual_review_verdict.ps1 `
-GateSummaryJson .\output\word-visual-release-gate\report\gate_summary.jsonfeatherdoc_cli is a small command-line wrapper around the current
inspection and editing APIs for sections, styles, numbering, page setup,
bookmarks, images, and template parts.
featherdoc_cli inspect-sections input.docx
featherdoc_cli inspect-sections input.docx --json
featherdoc_cli inspect-styles input.docx --style Strong --json
featherdoc_cli inspect-runs input.docx 1 --run 0 --json
featherdoc_cli inspect-template-runs input.docx 1 --run 0 --json
featherdoc_cli inspect-numbering input.docx --definition 1 --json
featherdoc_cli inspect-page-setup input.docx --section 1 --json
featherdoc_cli inspect-bookmarks input.docx --part header --index 0 --bookmark header_rows --json
featherdoc_cli inspect-images input.docx --relationship-id rId5 --json
featherdoc_cli ensure-table-style input.docx ReportTable --name "Report Table" --based-on TableGrid --output styled.docx --json
featherdoc_cli inspect-header-parts input.docx --json
featherdoc_cli inspect-footer-parts input.docx
featherdoc_cli insert-section input.docx 1 --no-inherit --output inserted.docx --json
featherdoc_cli copy-section-layout input.docx 0 2 --output copied.docx
featherdoc_cli move-section input.docx 2 0 --output reordered.docx
featherdoc_cli remove-section input.docx 3 --output trimmed.docx
featherdoc_cli assign-section-header input.docx 2 0 --kind even --output shared-header.docx --json
featherdoc_cli assign-section-footer input.docx 2 1 --output shared-footer.docx --json
featherdoc_cli remove-section-header input.docx 2 --kind even --output detached-header.docx
featherdoc_cli remove-section-footer input.docx 1 --kind first --output detached-footer.docx
featherdoc_cli remove-header-part input.docx 1 --output headers-pruned.docx
featherdoc_cli move-header-part input.docx 1 0 --output headers-reordered.docx --json
featherdoc_cli remove-footer-part input.docx 0 --output footers-pruned.docx --json
featherdoc_cli move-footer-part input.docx 1 0 --output footers-reordered.docx --json
featherdoc_cli show-section-header input.docx 1 --kind even
featherdoc_cli show-section-footer input.docx 2 --json
featherdoc_cli set-section-footer input.docx 0 --text "Page 1" --output footer.docx --json
featherdoc_cli set-section-header input.docx 2 --kind even --text-file header.txt --json
featherdoc_cli append-page-number-field input.docx --part section-header --section 1 --output page-number.docx --json
featherdoc_cli set-template-table-from-json report.docx --bookmark line_items_table --patch-file row_patch.json --output report-updated.docx --json
featherdoc_cli set-template-tables-from-json report.docx --patch-file multi_table_patch.json --output report-updated.docx --json
featherdoc_cli validate-template input.docx --part body --slot customer:text --slot line_items:table_rows --json
featherdoc_cli validate-template-schema input.docx --target section-header --section 0 --slot header_title:text --target section-footer --section 0 --slot footer_company:text --slot footer_summary:block --json
featherdoc_cli validate-template-schema input.docx --schema-file template-schema.json --json
featherdoc_cli export-template-schema input.docx --output template-schema.json --json
featherdoc_cli export-template-schema input.docx --section-targets --output section-template-schema.json --json
featherdoc_cli export-template-schema input.docx --resolved-section-targets --output resolved-section-template-schema.json --json
featherdoc_cli normalize-template-schema template-schema.json --output normalized-template-schema.json --json
featherdoc_cli lint-template-schema template-schema.json --json
featherdoc_cli repair-template-schema template-schema.json --output repaired-template-schema.json --json
featherdoc_cli merge-template-schema shared-template-schema.json invoice-template-schema.json --output merged-template-schema.json --json
featherdoc_cli patch-template-schema committed-template-schema.json --patch-file schema.patch.json --output patched-template-schema.json --json
featherdoc_cli build-template-schema-patch committed-schema.json generated-schema.json --output schema.patch.json --json
featherdoc_cli diff-template-schema old-template-schema.json new-template-schema.json --json
featherdoc_cli diff-template-schema committed-schema.json generated-schema.json --fail-on-diff --json
featherdoc_cli check-template-schema input.docx --schema-file committed-schema.json --resolved-section-targets --output generated-schema.json --json
pwsh -ExecutionPolicy Bypass -File .\scripts\freeze_template_schema_baseline.ps1 -InputDocx .\template.docx -SchemaOutput .\template.schema.json -ResolvedSectionTargets
pwsh -ExecutionPolicy Bypass -File .\scripts\check_template_schema_baseline.ps1 -InputDocx .\template.docx -SchemaFile .\template.schema.json -ResolvedSectionTargets -GeneratedSchemaOutput .\generated-template.schema.json -RepairedSchemaOutput .\repaired-template.schema.json
pwsh -ExecutionPolicy Bypass -File .\scripts\check_template_schema_manifest.ps1 -ManifestPath .\baselines\template-schema\manifest.json -BuildDir build-codex-clang-compat -RepairedSchemaOutputDir .\output\template-schema-manifest-repairs
pwsh -ExecutionPolicy Bypass -File .\scripts\register_template_schema_manifest_entry.ps1 -Name template-name -InputDocx .\template.docx
pwsh -ExecutionPolicy Bypass -File .\scripts\new_project_template_smoke_onboarding_plan.ps1 -ManifestPath .\samples\project_template_smoke.manifest.json -BuildDir build-codex-clang-compat
pwsh -ExecutionPolicy Bypass -File .\scripts\discover_project_template_smoke_candidates.ps1 -ManifestPath .\samples\project_template_smoke.manifest.json
pwsh -ExecutionPolicy Bypass -File .\scripts\discover_project_template_smoke_candidates.ps1 -ManifestPath .\samples\project_template_smoke.manifest.json -Json -IncludeRegistered -IncludeExcluded -OutputPath .\output\project-template-smoke\candidate_discovery.json -FailOnUnregistered
pwsh -ExecutionPolicy Bypass -File .\scripts\check_project_template_smoke_manifest.ps1 -ManifestPath .\samples\project_template_smoke.manifest.json -BuildDir build-codex-clang-compat -CheckPaths
pwsh -ExecutionPolicy Bypass -File .\scripts\describe_project_template_smoke_manifest.ps1 -ManifestPath .\samples\project_template_smoke.manifest.json -SummaryJson .\output\project-template-smoke\summary.json -BuildDir build-codex-clang-compat
pwsh -ExecutionPolicy Bypass -File .\scripts\register_project_template_smoke_manifest_entry.ps1 -Name contract-template -ManifestPath .\samples\project_template_smoke.manifest.json -InputDocx .\samples\chinese_invoice_template.docx -SchemaValidationFile .\baselines\template-schema\chinese_invoice_template.schema.json -SchemaBaselineFile .\baselines\template-schema\chinese_invoice_template.schema.json -VisualSmokeOutputDir .\output\project-template-smoke\contract-template-visual -ReplaceExisting
pwsh -ExecutionPolicy Bypass -File .\scripts\run_project_template_smoke.ps1 -ManifestPath .\samples\project_template_smoke.manifest.json -BuildDir build-codex-clang-compat -OutputDir output/project-template-smoke
pwsh -ExecutionPolicy Bypass -File .\scripts\sync_project_template_smoke_visual_verdict.ps1 -SummaryJson .\output\project-template-smoke\summary.json
pwsh -ExecutionPolicy Bypass -File .\scripts\render_template_document.ps1 -InputDocx .\samples\chinese_invoice_template.docx -PlanPath .\samples\chinese_invoice_template.render_plan.json -OutputDocx .\output\rendered\invoice.docx -SummaryJson .\output\rendered\invoice.render.summary.json -BuildDir build-codex-clang-compat -SkipBuildFor project-level smoke checks across several real templates, use
scripts/run_project_template_smoke.ps1. Each manifest entry can point at a
committed .docx directly or prepare one first via prepare_sample_target /
prepare_argument, then opt into any combination of template_validations,
schema_validation, schema_baseline, and optional visual_smoke. The
wrapper writes per-entry artifacts plus aggregate summary.json and
summary.md so you can review a whole template pack in one place. See
samples/project_template_smoke.manifest.json for a runnable repository
example. schema_baseline results now also record whether the committed schema
is lint-clean, how many lint issues were found, and any repaired candidate path
emitted by the baseline gate.
To validate the manifest contract before running the full harness, use
scripts/check_project_template_smoke_manifest.ps1. The sample manifest now
includes a local $schema reference to
samples/project_template_smoke.manifest.schema.json, so JSON-aware editors
can surface field completions and shape errors earlier. The runtime harness
also performs the same upfront validation and stops immediately on malformed
entries.
To maintain the manifest itself, use
scripts/describe_project_template_smoke_manifest.ps1 for a readable overview
of registered entries plus their latest smoke status, and
scripts/register_project_template_smoke_manifest_entry.ps1 to add or update
one entry without hand-editing JSON. register_* accepts direct
-SchemaValidationFile / -SchemaBaselineFile flags for common cases and can
also load complex template_validations or schema_validation.targets arrays
from JSON files via -TemplateValidationsFile and
-SchemaValidationTargetsFile. Before adding real templates, run
scripts/new_project_template_smoke_onboarding_plan.ps1 for a non-mutating
onboarding plan that combines candidate discovery with per-template
freeze_template_schema_baseline.ps1 and
register_project_template_smoke_manifest_entry.ps1 commands. The plan writes
plan.json, plan.md, and candidate_discovery.json under output/ so you
can review schema baseline paths, visual smoke output directories, and final
strict-preflight commands before touching the manifest. You can also run
scripts/discover_project_template_smoke_candidates.ps1 to list tracked
.docx / .dotx candidates that are not yet registered and print ready-to-run
register_project_template_smoke_manifest_entry.ps1 commands with unique
suggested entry names. Add -FailOnUnregistered when you want the same scan to
act as a coverage gate and return a non-zero exit code while any tracked
template candidate is still missing from the manifest; intentional non-template
fixtures can be listed under candidate_exclusions. The sample manifest now includes the committed
samples/chinese_invoice_template.docx as a real-template entry with schema
validation, schema-baseline checking, and Word visual smoke. When a reviewer later updates any referenced
review_result.json, rerun
scripts/sync_project_template_smoke_visual_verdict.ps1 to refresh both
summary.json and summary.md with the latest entry-level
review_status/review_verdict, top-level visual_verdict, and pending or
undetermined visual-review counts. The describe helper now surfaces those
latest visual verdict fields as part of the maintenance view. When the same
smoke summary is already wired into run_release_candidate_checks.ps1, pass
-ReleaseCandidateSummaryJson <report/summary.json> -RefreshReleaseBundle to
the sync script so it also rewrites the release-candidate summary.json,
final_review.md, START_HERE.md, ARTIFACT_GUIDE.md,
REVIEWER_CHECKLIST.md, and release_handoff.md without rerunning the full
preflight.
When you want a single-template render entrypoint instead of a repository-level
smoke manifest, use scripts/render_template_document.ps1. It reads one JSON
plan and chains fill-bookmarks, replace-bookmark-paragraphs,
replace-bookmark-table-rows, and apply-bookmark-block-visibility into one
repeatable .docx render flow. The script treats missing bookmarks as a hard
failure so template drift is caught early, and it can emit a machine-readable
summary JSON for CI or review tooling. See
samples/template_render_plan.schema.json and
samples/chinese_invoice_template.render_plan.json for a runnable example.
If you do not want to hand-author the first render plan, start with
scripts/export_template_render_plan.ps1. It inspects body, header-part, and
footer-part bookmarks, classifies text, block, table_rows, and
block_range bookmarks into the four render-plan arrays, and writes a draft
JSON that can be edited and then fed back into
render_template_document.ps1. The draft uses TODO: <bookmark_name>
placeholders for text and paragraph slots, keeps table-row drafts empty, and
defaults block-visibility entries to visible: true.
If you want to keep business data separate from the exported draft, follow with
scripts/patch_template_render_plan.ps1. It reads one base render plan plus a
patch document, matches patch entries by bookmark_name plus optional
part/index/section/kind selectors, and overwrites only the payload fields in
the base plan. When a bookmark name is unique, the patch can stay minimal and
only provide bookmark_name plus text, paragraphs, rows, or visible.
Pass -RequireComplete when you want the script to fail on leftover TODO
placeholders or empty table-row drafts before rendering. See
samples/chinese_invoice_template.render_patch.json for a runnable example.
If you want one command that starts from a template plus a patch JSON and ends
with a final .docx, use scripts/render_template_document_from_patch.ps1.
It runs export, patch, and render in order, resolves featherdoc_cli once for
the whole pipeline, enables -RequireComplete on the patch step by default,
and can optionally keep the exported draft plus the patched render plan for
review.
pwsh -ExecutionPolicy Bypass -File .\scripts\render_template_document_from_patch.ps1 -InputDocx .\samples\chinese_invoice_template.docx -PatchPlanPath .\samples\chinese_invoice_template.render_patch.json -OutputDocx .\output\rendered\invoice.from-patch.docx -SummaryJson .\output\rendered\invoice.from-patch.summary.json -DraftPlanOutput .\output\rendered\invoice.draft.render-plan.json -PatchedPlanOutput .\output\rendered\invoice.filled.render-plan.json -BuildDir build-codex-clang-compat -SkipBuildIf you want to keep the edit surface at the business-data level instead of
hand-editing patch JSON, use
scripts/convert_render_data_to_patch_plan.ps1 together with
samples/template_render_data_mapping.schema.json. The mapping file binds one
JSON source path to each bookmark_text, bookmark_paragraphs,
bookmark_table_rows, or bookmark_block_visibility entry, so the generated
patch stays reproducible while business data remains schema-shaped.
If you already have a render-plan draft and want a starting mapping file instead
of writing one from scratch, run
scripts/export_render_data_mapping_draft.ps1. It copies bookmark targets plus
optional part/index/section/kind selectors and fills each source with an
editable path derived from the bookmark name.
pwsh -ExecutionPolicy Bypass -File .\scripts\export_render_data_mapping_draft.ps1 -InputPlan .\samples\chinese_invoice_template.render_plan.json -OutputMapping .\output\rendered\invoice.render_data_mapping.draft.json -SummaryJson .\output\rendered\invoice.render_data_mapping.draft.summary.json -SourceRoot dataIf you want a ready-to-edit data workspace directly from a .docx template,
run scripts/prepare_template_render_data_workspace.ps1. It chains
export_template_render_plan, export_render_data_mapping_draft,
export_render_data_skeleton, and lint_render_data_mapping into one entry
point, producing a render plan, mapping draft, editable data skeleton, and a
workspace summary. This is the most direct setup when users should edit JSON
business data first and render the final document later. The prepared
workspace now also includes a START_HERE.zh-CN.md guide that points users to
the JSON file they should edit first, the validation command that confirms the
edited data is complete, and the render command they should run next.
pwsh -ExecutionPolicy Bypass -File .\scripts\prepare_template_render_data_workspace.ps1 -InputDocx .\samples\chinese_invoice_template.docx -WorkspaceDir .\output\rendered\invoice.workspace -SummaryJson .\output\rendered\invoice.workspace\summary.json -BuildDir build-codex-clang-compat -SkipBuildAfter the workspace is prepared, use
scripts/render_template_document_from_workspace.ps1 as the matching wrapper.
It resolves the template, mapping, and data JSON from the workspace summary or
workspace directory, so the user-facing flow becomes:
- edit the data JSON,
- run
validate_render_data_mapping.ps1 -WorkspaceDir ... -RequireCompleteand confirmstatus=completedplusremaining_placeholder_count=0, - render the final
.docxfrom the workspace.
If the data file still contains scaffold placeholders such as TODO:, the
wrapper reports that directly instead of surfacing a lower-level patch/render
error first. Pass -ReportMarkdown to also write a human-readable report, so
users can open the .md file for the verdict and remaining slots before reading
the JSON summary. Remaining-slot entries also use the mapping to point back to
the data JSON fields users should inspect.
pwsh -ExecutionPolicy Bypass -File .\scripts\validate_render_data_mapping.ps1 -WorkspaceDir .\output\rendered\invoice.workspace -SummaryJson .\output\rendered\invoice.workspace\validation.summary.json -ReportMarkdown .\output\rendered\invoice.workspace\validation.report.md -BuildDir build-codex-clang-compat -SkipBuild -RequireCompletepwsh -ExecutionPolicy Bypass -File .\scripts\render_template_document_from_workspace.ps1 -WorkspaceDir .\output\rendered\invoice.workspace -OutputDocx .\output\rendered\invoice.final.docx -SummaryJson .\output\rendered\invoice.workspace\render.summary.jsonFor direct edits to an existing .docx, use
scripts/edit_document_from_plan.ps1. The JSON file is an edit instruction
plan, not a JSON copy of the whole Word document: the script applies the
operations to the original .docx in order, then writes a new .docx plus a
summary. This is the matching workflow for “existing document + edit
instructions = new document”.
{
"operations": [
{
"op": "replace_bookmark_text",
"bookmark": "customer_name",
"text": "FeatherDoc Co."
},
{
"op": "replace_bookmark_paragraphs",
"bookmark": "note_lines",
"paragraphs": ["First paragraph", "Second paragraph"]
},
{
"op": "replace_bookmark_table_rows",
"bookmark": "line_item_row",
"rows": [["Service", "Description", "Amount"]]
},
{
"op": "set_table_cell_text",
"table_index": 0,
"row_index": 3,
"cell_index": 2,
"text": "4,000.00"
},
{
"op": "set_table_cell_fill",
"table_index": 0,
"row_index": 3,
"cell_index": 2,
"background_color": "FFF2CC"
},
{
"op": "set_table_cell_border",
"table_index": 0,
"row_index": 3,
"cell_index": 2,
"edge": "right",
"style": "single",
"thickness": 16,
"color": "FF0000"
},
{
"op": "set_table_row_height",
"table_index": 0,
"row_index": 3,
"height_twips": 720,
"rule": "exact"
},
{
"op": "set_table_cell_vertical_alignment",
"table_index": 0,
"row_index": 3,
"cell_index": 2,
"alignment": "center"
},
{
"op": "set_table_cell_horizontal_alignment",
"table_index": 0,
"row_index": 3,
"cell_index": 2,
"alignment": "right"
},
{
"op": "replace_text",
"find": "old term",
"replace": "new term"
},
{
"op": "set_text_style",
"text_contains": "new term",
"bold": true,
"font_family": "Segoe UI",
"east_asia_font_family": "Microsoft YaHei",
"language": "en-US",
"east_asia_language": "zh-CN",
"color": "C00000",
"font_size_points": 14
},
{
"op": "delete_paragraph_contains",
"text_contains": "optional note"
},
{
"op": "set_paragraph_horizontal_alignment",
"text_contains": "First paragraph",
"alignment": "center"
},
{
"op": "set_paragraph_spacing",
"text_contains": "First paragraph",
"before_twips": 120,
"after_twips": 240,
"line_twips": 360,
"line_rule": "exact"
},
{
"op": "set_table_column_width",
"table_index": 0,
"column_index": 1,
"width_twips": 5200
},
{
"op": "merge_table_cells",
"table_index": 0,
"row_index": 3,
"cell_index": 0,
"direction": "right",
"count": 2
}
]
}Table-cell vertical alignment supports top / center / bottom / both;
table-cell and normal body-paragraph horizontal alignment both support left /
center / right / both. Table layout edits include column width and
cell merge/unmerge in the right or down direction. Cell appearance edits
include background fill, single-edge border style, border
thickness, and border color. Text style edits include bold, text color, font size,
Latin font, CJK font, primary language, and East Asian language metadata.
Paragraph layout edits include spacing before, spacing after, and line spacing.
Body paragraphs can be targeted by paragraph_index or by text_contains.
If a document has no bookmarks, use replace_text for ordinary
body text replacement; it searches the merged visible paragraph text, so it
handles common Word run splitting. For vertical centering to be visible in Word,
pair it with set_table_row_height so the row has an explicit height.
Use merge_table_cells to merge table cells and unmerge_table_cells to split
an existing merge; count controls how many cells the merge spans.
pwsh -ExecutionPolicy Bypass -File .\scripts\edit_document_from_plan.ps1 -InputDocx .\samples\chinese_invoice_template.docx -EditPlan .\output\rendered\invoice.edit_plan.json -OutputDocx .\output\rendered\invoice.edited.docx -SummaryJson .\output\rendered\invoice.edit.summary.json -BuildDir build-codex-clang-compat -SkipBuildTo verify that the direct-edit output also looks right after Word renders it, run the focused visual regression. It creates the edited DOCX, exports the baseline and edited documents through Microsoft Word, renders PNG evidence, and writes a before/after contact sheet:
pwsh -ExecutionPolicy Bypass -File .\scripts\run_edit_document_from_plan_visual_regression.ps1 -BuildDir build-codex-clang-compat -SkipBuildpwsh -ExecutionPolicy Bypass -File .\scripts\convert_render_data_to_patch_plan.ps1 -DataPath .\samples\chinese_invoice_template.render_data.json -MappingPath .\samples\chinese_invoice_template.render_data_mapping.json -OutputPatch .\output\rendered\invoice.generated.render_patch.json -SummaryJson .\output\rendered\invoice.generated.render_patch.summary.jsonFor faster feedback while editing mappings, run
scripts/lint_render_data_mapping.ps1 before template export or rendering. It
checks selector shape, duplicate targets inside each category, and optional
business-data source paths without opening the .docx template.
pwsh -ExecutionPolicy Bypass -File .\scripts\lint_render_data_mapping.ps1 -MappingPath .\samples\chinese_invoice_template.render_data_mapping.json -DataPath .\samples\chinese_invoice_template.render_data.json -SummaryJson .\output\rendered\invoice.mapping.lint.summary.jsonBefore rendering, you can validate the whole template + mapping + data
contract with scripts/validate_render_data_mapping.ps1. The wrapper exports a
draft render plan, converts business data into a patch, and replays that patch
onto the draft so missing mappings, invalid source paths, duplicate targets,
or leftover placeholders fail before the final .docx step.
pwsh -ExecutionPolicy Bypass -File .\scripts\validate_render_data_mapping.ps1 -InputDocx .\samples\chinese_invoice_template.docx -MappingPath .\samples\chinese_invoice_template.render_data_mapping.json -DataPath .\samples\chinese_invoice_template.render_data.json -SummaryJson .\output\rendered\invoice.validation.summary.json -DraftPlanOutput .\output\rendered\invoice.validation.draft.render-plan.json -GeneratedPatchOutput .\output\rendered\invoice.validation.generated.render_patch.json -PatchedPlanOutput .\output\rendered\invoice.validation.patched.render-plan.json -BuildDir build-codex-clang-compat -SkipBuild -RequireCompleteFor the full .docx + business JSON + mapping = final .docx workflow, use
scripts/render_template_document_from_data.ps1. It runs export, convert,
patch, and render as one direct pipeline, keeps -RequireComplete semantics on
the patch step by default, and still lets you preserve the generated patch plus
draft / patched render plans when needed.
pwsh -ExecutionPolicy Bypass -File .\scripts\render_template_document_from_data.ps1 -InputDocx .\samples\chinese_invoice_template.docx -DataPath .\samples\chinese_invoice_template.render_data.json -MappingPath .\samples\chinese_invoice_template.render_data_mapping.json -OutputDocx .\output\rendered\invoice.from-data.docx -SummaryJson .\output\rendered\invoice.from-data.summary.json -PatchPlanOutput .\output\rendered\invoice.generated.render_patch.json -DraftPlanOutput .\output\rendered\invoice.draft.render-plan.json -PatchedPlanOutput .\output\rendered\invoice.filled.render-plan.json -BuildDir build-codex-clang-compat -SkipBuildinspect-sections prints the current section count together with per-section
header/footer attachment flags for default, first, and even references.
The mutating commands save in place by default; pass --output <path> to write
to a separate .docx. Pass --json to inspect-sections when you need the
same section layout information in a machine-readable object. The mutating
commands also accept --json and emit command, ok, in_place, sections,
headers, and footers, plus command-specific fields such as section,
source, target, part, and kind.
inspect-header-parts / inspect-footer-parts list loaded part indexes in the
same order consumed by assign-section-* and remove-*-part. Their output
includes each part's relationship id, package entry path, section references,
and paragraph text. Pass --json when you need the same information as a
machine-readable object.
Additional representative command groups:
# Paragraphs, runs, styles, and numbering
featherdoc_cli inspect-paragraphs input.docx --paragraph 4 --json
featherdoc_cli set-paragraph-style input.docx 4 Heading2 --output styled-paragraph.docx --json
featherdoc_cli clear-paragraph-style input.docx 4 --output cleared-paragraph-style.docx --json
featherdoc_cli set-run-style input.docx 4 1 Strong --output styled-run.docx --json
featherdoc_cli clear-run-style input.docx 4 1 --output cleared-run-style.docx --json
featherdoc_cli set-run-font-family input.docx 4 1 Consolas --output font-run.docx --json
featherdoc_cli clear-run-font-family input.docx 4 1 --output cleared-run-font.docx --json
featherdoc_cli set-run-language input.docx 4 1 en-US --output language-run.docx --json
featherdoc_cli clear-run-language input.docx 4 1 --output cleared-run-language.docx --json
featherdoc_cli inspect-default-run-properties input.docx --json
featherdoc_cli set-default-run-properties input.docx --font-family "Segoe UI" --east-asia-font-family "Microsoft YaHei" --language en-US --east-asia-language zh-CN --rtl true --output default-run-properties.docx --json
featherdoc_cli clear-default-run-properties input.docx --primary-language --rtl --output cleared-default-run-properties.docx --json
featherdoc_cli inspect-style-run-properties input.docx Normal --json
featherdoc_cli materialize-style-run-properties input.docx Normal --output materialized-style-run-properties.docx --json
featherdoc_cli set-style-run-properties input.docx Normal --font-family "Segoe UI" --east-asia-font-family "Microsoft YaHei" --language en-US --east-asia-language zh-CN --rtl true --paragraph-bidi true --output style-run-properties.docx --json
featherdoc_cli clear-style-run-properties input.docx Normal --primary-language --rtl --paragraph-bidi --output cleared-style-run-properties.docx --json
featherdoc_cli inspect-style-inheritance input.docx Normal --json
featherdoc_cli inspect-paragraph-style-properties input.docx Heading1 --json
featherdoc_cli set-paragraph-style-properties input.docx Heading1 --next-style BodyText --outline-level 1 --output updated-paragraph-style-properties.docx --json
featherdoc_cli clear-paragraph-style-properties input.docx Heading1 --next-style --outline-level --output cleared-paragraph-style-properties.docx --json
featherdoc_cli rebase-character-style-based-on input.docx ReviewStrong Strong --output rebased-character-style.docx --json
featherdoc_cli rebase-paragraph-style-based-on input.docx Heading2 Normal --output rebased-paragraph-style.docx --json
featherdoc_cli ensure-paragraph-style input.docx ReviewHeading --name "Review Heading" --based-on Heading1 --output ensured-paragraph-style.docx --json
featherdoc_cli ensure-character-style input.docx ReviewStrong --name "Review Strong" --based-on Strong --output ensured-character-style.docx --json
featherdoc_cli ensure-numbering-definition input.docx --definition-name OutlineReview --numbering-level 0:decimal:1:%1. --output numbering.docx --json
featherdoc_cli ensure-style-linked-numbering input.docx --definition-name HeadingReview --numbering-level 0:decimal:1:%1. --numbering-level 1:decimal:1:%1.%2. --style-link Heading1:0 --style-link Heading2:1 --output linked-style-numbering.docx --json
featherdoc_cli set-paragraph-numbering input.docx 6 --definition 12 --level 0 --output numbered.docx --json
featherdoc_cli set-paragraph-style-numbering input.docx Heading2 --definition-name HeadingReview --numbering-level 0:decimal:1:%1. --style-level 1 --output style-numbering.docx --json
featherdoc_cli clear-paragraph-style-numbering input.docx Heading2 --output cleared-style-numbering.docx --json
featherdoc_cli set-paragraph-list input.docx 6 --kind bullet --level 1 --output bulleted.docx --json
featherdoc_cli restart-paragraph-list input.docx 10 --kind decimal --level 0 --output restarted-list.docx --json
featherdoc_cli clear-paragraph-list input.docx 10 --output cleared-list.docx --json
# Template inspection and bookmark-driven edits
featherdoc_cli inspect-template-paragraphs input.docx --part header --index 0 --paragraph 0 --json
featherdoc_cli inspect-template-tables input.docx --part body --table 0 --json
featherdoc_cli inspect-template-table-rows input.docx 0 --row 1 --json
featherdoc_cli inspect-template-table-cells input.docx 0 --row 1 --cell 1 --json
featherdoc_cli replace-bookmark-text input.docx customer_name --text "Ada Lovelace" --output bookmark-text.docx --json
featherdoc_cli fill-bookmarks input.docx --set customer_name "Ada Lovelace" --set invoice_no INV-001 --output filled.docx --json
featherdoc_cli fill-bookmarks input.docx --set-file customer_name customer_name.txt --set-file invoice_no invoice_no.txt --output filled-from-files.docx --json
featherdoc_cli replace-bookmark-paragraphs input.docx notes --paragraph "Line one" --paragraph "Line two" --output bookmark-paragraphs.docx --json
featherdoc_cli replace-bookmark-paragraphs input.docx notes --paragraph-file note-1.txt --paragraph-file note-2.txt --output bookmark-paragraphs-files.docx --json
featherdoc_cli replace-bookmark-table input.docx line_items --row "SKU-1" --cell "2" --cell "$10" --output bookmark-table.docx --json
featherdoc_cli replace-bookmark-table-rows input.docx line_items --row "SKU-2" --cell "4" --cell "$20" --output bookmark-table-rows.docx --json
featherdoc_cli replace-bookmark-table-rows input.docx line_items --row-file sku.txt --cell-file qty.txt --cell-file price.txt --output bookmark-table-rows-files.docx --json
featherdoc_cli remove-bookmark-block input.docx optional_section --output bookmark-block-removed.docx --json
featherdoc_cli set-bookmark-block-visibility input.docx optional_section --visible false --output bookmark-hidden.docx --json
featherdoc_cli apply-bookmark-block-visibility input.docx --hide optional_section --show totals --output bookmark-visibility.docx --json
# Images and page fields
featherdoc_cli replace-bookmark-image input.docx logo assets/logo.png --width 120 --height 40 --output bookmark-image.docx --json
featherdoc_cli replace-bookmark-floating-image input.docx hero assets/hero.png --width 320 --height 180 --horizontal-reference margin --vertical-reference paragraph --wrap-mode square --output bookmark-floating-image.docx --json
featherdoc_cli extract-image input.docx exported.png --relationship-id rId5 --json
featherdoc_cli replace-image input.docx replacement.png --relationship-id rId5 --output image-replaced.docx --json
featherdoc_cli remove-image input.docx --relationship-id rId5 --output image-removed.docx --json
featherdoc_cli append-image input.docx badge.png --width 96 --height 48 --output image-appended.docx --json
featherdoc_cli append-total-pages-field input.docx --part section-footer --section 1 --kind first --output total-pages.docx --jsonassign-section-header / assign-section-footer make a section reuse an
already loaded header/footer part by index. remove-section-header /
remove-section-footer detach one section-level reference kind without
removing the underlying part if it is still used elsewhere. remove-header-part
/ remove-footer-part drop one loaded part entirely and detach every section
reference that points at it. move-header-part / move-footer-part reorder the
loaded part indexes while keeping section references bound to the same
underlying relationship ids.
show-section-header / show-section-footer print the referenced paragraphs
one line per paragraph. set-section-header / set-section-footer rewrite the
target part as plain paragraphs from --text or --text-file, and create the
requested section reference automatically when it does not exist yet.
show-section-header / show-section-footer also accept --json, which emits
part, section, kind, present, and paragraphs fields for scriptable
inspection.
The short block above is intentionally representative rather than exhaustive.
For the full CLI surface, including template-table row/cell editing, style and
numbering inspection, image inspection and replacement, page setup mutation,
and page-number field insertion, see docs/index.rst.
cmake --install build --prefix installThe installed package now also carries repository-facing metadata,
visual-validation preview assets, repro guides, release-artifact templates, and legal files under
share/FeatherDoc, including:
CHANGELOG.mdREADME.mdREADME.zh-CN.mdRELEASE_ARTIFACT_TEMPLATE.mdRELEASE_ARTIFACT_TEMPLATE.zh-CN.mdVISUAL_VALIDATION_QUICKSTART.mdVISUAL_VALIDATION_QUICKSTART.zh-CN.mdVISUAL_VALIDATION.mdVISUAL_VALIDATION.zh-CN.mdvisual-validation/LICENSELICENSE.upstream-mitNOTICELEGAL.md
VISUAL_VALIDATION_QUICKSTART*.md is the shortest screenshot -> command ->
review-task entry when you are browsing share/FeatherDoc directly.
VISUAL_VALIDATION*.md then expands the same preview PNGs back to the
source-checkout commands that regenerate them and to the review-task wrappers
that consume the resulting evidence.
RELEASE_ARTIFACT_TEMPLATE*.md gives a copy-paste release-body skeleton that
points back to the installed previews, repro commands, and preflight evidence
files.
If you only need the one-shot commands, copy these:
pwsh -ExecutionPolicy Bypass -File <repo-root>\scripts\run_word_visual_release_gate.ps1
pwsh -ExecutionPolicy Bypass -File <repo-root>\scripts\run_release_candidate_checks.ps1
pwsh -ExecutionPolicy Bypass -File <repo-root>\scripts\open_latest_word_review_task.ps1
pwsh -ExecutionPolicy Bypass -File <repo-root>\scripts\open_latest_fixed_grid_review_task.ps1 -PrintPrompt
pwsh -ExecutionPolicy Bypass -File <repo-root>\scripts\sync_latest_visual_review_verdict.ps1To validate the installed package from a clean external CMake consumer, run:
pwsh -ExecutionPolicy Bypass -File .\scripts\run_install_find_package_smoke.ps1 `
-BuildDir build-msvc-nmake `
-InstallDir build-msvc-install `
-ConsumerBuildDir build-msvc-install-consumer `
-Generator "NMake Makefiles" `
-Config ReleaseThis is the same install + find_package smoke path that the Windows CI now
uses after the main build, tests, and sample runs.
A local run_release_candidate_checks.ps1 execution also writes
output/release-candidate-checks/START_HERE.md; use that summary-root note as
the first entry before you open report/ARTIFACT_GUIDE.md or
REVIEWER_CHECKLIST.md.
If you also want release-preflight to gate a template DOCX against a committed
schema baseline, pass -TemplateSchemaInputDocx, -TemplateSchemaBaseline,
and one of -TemplateSchemaSectionTargets /
-TemplateSchemaResolvedSectionTargets. The wrapper then records that check in
report/summary.json, including whether the committed schema is lint-clean,
and fails the whole preflight on either schema drift or schema-maintenance
issues.
If you want the same preflight to gate every repository-registered template
schema baseline at once, pass -TemplateSchemaManifestPath instead. The
wrapper then runs check_template_schema_manifest.ps1, records the manifest
status plus entry/drift/dirty-baseline counts in report/summary.json, and
fails the whole preflight when any registered baseline drifts or any committed
schema baseline needs repair.
If you also want release-preflight to exercise a real-template regression pack,
pass -ProjectTemplateSmokeManifestPath. The wrapper then runs
run_project_template_smoke.ps1, records the manifest path, summary path,
entry counts, failed-entry count, and aggregated project-template
visual_verdict in report/summary.json, and threads the same status through
START_HERE.md, ARTIFACT_GUIDE.md, REVIEWER_CHECKLIST.md, and
release_handoff.md. Add -ProjectTemplateSmokeRequireFullCoverage when the
same preflight should also fail on any tracked .docx / .dotx candidate that
is neither registered in the smoke manifest nor listed under
candidate_exclusions; the wrapper writes the full scan to
project-template-smoke/candidate_discovery.json and surfaces the
registered/unregistered/excluded counts in the release bundle.
When you want to add or refresh a repository-level baseline without manually
editing baselines/template-schema/manifest.json, prefer
register_template_schema_manifest_entry.ps1. It prepares a generated fixture
when needed, freezes the normalized schema baseline, and writes or updates the
matching manifest entry in one step. Before writing the manifest, it now runs
the same baseline gate used by CI, so dirty schemas or document/schema drift are
rejected at registration time unless you explicitly pass -SkipBaselineCheck.
The same workflow now also uploads a separate windows-msvc-release-metadata
artifact containing build-msvc-install/share/FeatherDoc/**, a root-level
RELEASE_METADATA_START_HERE.md, and
output/release-candidate-checks-ci/START_HERE.md plus
output/release-candidate-checks-ci/report/**, including the generated
ARTIFACT_GUIDE.md, REVIEWER_CHECKLIST.md, release_handoff.md,
release_body.zh-CN.md, and release_summary.zh-CN.md. Open
RELEASE_METADATA_START_HERE.md first, then continue into START_HERE.md,
ARTIFACT_GUIDE.md, and REVIEWER_CHECKLIST.md. That cloud artifact
intentionally keeps the visual gate skipped; the final screenshot-backed Word
verdict still belongs to the local Windows preflight path.
After a later local screenshot review updates the task verdicts, run
pwsh -ExecutionPolicy Bypass -File .\scripts\sync_latest_visual_review_verdict.ps1
for the shortest auto-detected path. If you need to override paths manually,
keep using sync_visual_review_verdict.ps1 with explicit
-GateSummaryJson / -ReleaseCandidateSummaryJson arguments. Both routes can
promote the final Word verdict into the saved summaries and refresh
START_HERE.md, the guide/checklist, and the release-facing notes without
rerunning the full preflight.
Those refreshed entry points now surface not only the top-level visual verdict,
but also the section page setup verdict, the page number fields verdict,
every curated visual-regression bundle verdict, and the matching review-task
paths / bundle-specific
open_latest_word_review_task.ps1 -SourceKind <bundle-key>-visual-regression-bundle
shortcuts.
list(APPEND CMAKE_PREFIX_PATH "/path/to/FeatherDoc/install")
find_package(FeatherDoc CONFIG REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE FeatherDoc::FeatherDoc)The generated package config also exposes:
FeatherDoc_VERSIONFeatherDoc_DESCRIPTIONFeatherDoc_PACKAGE_DATA_DIR
#include <featherdoc.hpp>
#include <iostream>
int main() {
featherdoc::Document doc("file.docx");
if (const auto error = doc.open()) {
const auto& error_info = doc.last_error();
std::cerr << error.message();
if (!error_info.detail.empty()) {
std::cerr << ": " << error_info.detail;
}
if (!error_info.entry_name.empty()) {
std::cerr << " [entry=" << error_info.entry_name << ']';
}
if (error_info.xml_offset.has_value()) {
std::cerr << " [xml_offset=" << *error_info.xml_offset << ']';
}
std::cerr << '\n';
return 1;
}
for (auto paragraph : doc.paragraphs()) {
std::string text;
for (auto run : paragraph.runs()) {
text += run.get_text();
}
std::cout << text << '\n';
}
for (auto table : doc.tables()) {
for (auto row : table.rows()) {
for (auto cell : row.cells()) {
for (auto paragraph : cell.paragraphs()) {
std::string text;
for (auto run : paragraph.runs()) {
text += run.get_text();
}
std::cout << text << '\n';
}
}
}
}
return 0;
}Run represents WordprocessingML text runs, not whole lines. If one logical line
is split into multiple runs, concatenate run.get_text() values inside a
paragraph before printing. Text inside tables is traversed through
doc.tables() -> rows() -> cells() -> paragraphs().
If you mainly enter the project through the English README, start from the job you want to get done:
- Create, open, save, or diagnose a document:
create_empty(),open(),save(),save_as(),path(),last_error() - Edit body text:
paragraphs(),runs(),set_text(), plus paragraph/run insertion and removal helpers - Work with tables and layout:
append_table(), row/column insertion,merge_right(),merge_down(),unmerge_*(), fixed-grid width APIs, and layout-mode helpers - Fill templates or validate bookmarks:
list_bookmarks(),validate_template(),fill_bookmarks(),replace_bookmark_with_*(),body_template(),header_template(),footer_template() - Work with images and page-number fields:
append_image(),append_floating_image(),replace_*image(),append_page_number_field(),append_total_pages_field() - Work with sections, headers/footers, and page setup:
inspect_sections(),get_section_page_setup(),set_section_page_setup(),ensure_*header*(),ensure_*footer*(),append_section(),insert_section(),move_section(),move_header_part(),move_footer_part() - Work with styles, numbering, and language metadata:
list_styles(),find_style(),ensure_*style(),ensure_numbering_definition(),ensure_style_linked_numbering(),set_paragraph_style_numbering(),default_run_*(),style_run_*(),resolve_style_properties(),materialize_style_run_properties(),rebase_character_style_based_on(),set_paragraph_style_based_on(),set_paragraph_style_next_style(),set_paragraph_style_outline_level(),rebase_paragraph_style_based_on() - Prefer the CLI for scriptable inspection or one-shot rewrites:
inspect-*,validate-template,append-page-number-field,set-section-page-setup
For fuller parameter details, runnable samples, and edge-case notes, continue
into docs/index.rst; its Task-Oriented API Map and
Task-Oriented Sample And CLI Map mirror the same entry points in more detail.
Use Paragraph::set_text(...) when you want to replace one paragraph's body
content in place while preserving paragraph-level properties such as style or
bidirectional settings. Use Paragraph::insert_paragraph_before(...) or
insert_paragraph_after(...) when you need to add a sibling paragraph around
an existing anchor paragraph, insert_paragraph_like_before() /
insert_paragraph_like_after() when you want the new paragraph to inherit the
anchor paragraph's paragraph-level properties, Run::insert_run_before(...) /
insert_run_after(...) when you need to add sibling runs around an existing
anchor run, Run::insert_run_like_before() / insert_run_like_after() to
clone the anchor run's formatting into a new empty sibling run, Run::remove()
to drop one run from a paragraph, and
Paragraph::remove() when you want to delete one paragraph without leaving an
invalid container behind. Paragraph::remove() intentionally refuses to remove
section-break paragraphs or the last required paragraph inside a body, header,
footer, or table cell container.
auto paragraph = doc.paragraphs();
paragraph.set_text("anchor");
auto prepended = paragraph.insert_paragraph_before("Lead-in");
prepended.add_run(" note");
auto cloned = paragraph.insert_paragraph_like_after();
cloned.set_text("Another paragraph with the same paragraph style");
auto anchor = paragraph.runs();
anchor.insert_run_before("left ", featherdoc::formatting_flag::bold);
anchor.insert_run_after(" right");
auto cloned_run = anchor.insert_run_like_before();
cloned_run.set_text("styled clone ");
auto removable_run = paragraph.add_run(" temporary");
removable_run.remove();
auto removable_paragraph = paragraph.insert_paragraph_after("Delete me");
removable_paragraph.remove();For a runnable "edit an existing document and save it back" example, build
featherdoc_sample_edit_existing from samples/sample_edit_existing.cpp.
FeatherDoc already supports opening an existing .docx, mutating paragraphs,
runs, table cells, inline body images, headers, footers, and bookmark-backed
template regions, and saving the result back to disk.
For a focused "reopen and replace existing header/footer images" example,
build featherdoc_sample_edit_existing_part_images from
samples/sample_edit_existing_part_images.cpp.
For a focused "reopen and append body/header/footer paragraphs through
TemplatePart handles" example, build
featherdoc_sample_edit_existing_part_paragraphs from
samples/sample_edit_existing_part_paragraphs.cpp.
For a focused "reopen and insert body/header/footer paragraphs before existing
anchor paragraphs" example, build featherdoc_sample_insert_paragraph_before
from samples/sample_insert_paragraph_before.cpp.
For a focused "reopen and clone paragraph formatting around existing
body/header/footer anchor paragraphs" example, build
featherdoc_sample_insert_paragraph_like_existing from
samples/sample_insert_paragraph_like_existing.cpp.
For a focused "reopen and insert runs around existing body/header/footer anchor
runs" example, build featherdoc_sample_insert_run_around_existing from
samples/sample_insert_run_around_existing.cpp.
For a focused "reopen and clone run formatting around existing body/header/footer
anchor runs" example, build featherdoc_sample_insert_run_like_existing from
samples/sample_insert_run_like_existing.cpp.
For a focused "reopen and append new images to existing body/header/footer
parts" example, build featherdoc_sample_edit_existing_part_append_images
from samples/sample_edit_existing_part_append_images.cpp.
Use append_table(row_count, column_count) when you need to create a new body
table programmatically. The returned Table can be extended with
append_row(), widened with append_cell(), or used as an anchor for
insert_table_before(...) / insert_table_after(...) when you need to insert
new sibling tables around an existing one. Use insert_table_like_before() /
insert_table_like_after() when you want to clone the current table's layout
and formatting into a new empty sibling table.
auto table = doc.append_table(2, 2);
auto first_row = table.rows();
auto first_cell = first_row.cells();
first_cell.set_text("r0c0");
first_cell.next();
first_cell.set_text("r0c1");
auto extra_row = table.append_row();
auto extra_cell = extra_row.cells();
extra_cell.set_text("tail");
extra_row.append_cell().set_text("tail-2");
auto inserted = table.insert_table_after(1, 2);
inserted.rows().cells().set_text("inserted");Use Table::set_width_twips(...), set_column_width_twips(...),
clear_column_width(), set_style_id(...), set_border(...),
set_layout_mode(...), set_alignment(...), set_indent_twips(...),
set_cell_spacing_twips(...), set_cell_margin_twips(...), and
set_style_look(...)
alongside TableCell::set_text(...), get_text(), set_width_twips(...),
Table::remove(), insert_table_before(), insert_table_after(),
insert_table_like_before(), insert_table_like_after(),
TableCell::remove(), insert_cell_before(), insert_cell_after(),
TableRow::remove(), insert_row_before(),
insert_row_after(),
merge_right(...), merge_down(...), unmerge_right(), unmerge_down(),
set_vertical_alignment(...),
set_border(...),
set_fill_color(...), and set_margin_twips(...) when you need lightweight
table layout editing without dropping down to raw WordprocessingML.
width_twips() reports an explicit dxa width when present,
column_width_twips(column_index) reports the current w:tblGrid width for
one grid column, style_id() reports the current table style reference,
style_look() reports the current first/last row or column emphasis together
with row/column banding flags, layout_mode() reports the current auto-fit
mode, alignment() / indent_twips() report table placement,
cell_spacing_twips() reports inter-cell spacing, and cell_margin_twips()
reports per-edge default cell margins,
height_twips() / height_rule() report the current row height override,
cant_split() reports whether Word keeps the row on one page,
repeats_header() reports whether a row repeats as a table header,
Table::remove() deletes one table while refusing to leave the parent
container without the required block content and retargets the wrapper to the
next surviving table when possible (otherwise the previous one).
TableCell::remove() deletes one unmerged column across the whole table while
refusing to remove the last remaining column, refusing any column that
intersects a horizontal merge span, and retargeting the wrapper to the next
surviving cell when possible (otherwise the previous one). When the table uses
explicit w:tblGrid widths, removal also drops the matching w:gridCol
entry so the deleted column's saved grid width disappears with it.
TableCell::insert_cell_before() and insert_cell_after() clone the target
column structure across every row, expand w:tblGrid, clear copied
w:gridSpan / w:vMerge markup on the inserted cells, copy the source-side
w:gridCol width into the inserted grid column, and refuse insertion
boundaries that would land inside a horizontal merge span.
On tables that are already fixed-layout, or that later switch to
fixed-layout, with explicit widths for every w:gridCol,
set_layout_mode(featherdoc::table_layout_mode::fixed),
set_column_width_twips(...), TableCell::merge_right(),
unmerge_right(), TableCell::insert_cell_before(), insert_cell_after(),
and TableCell::remove() also normalize each cell's w:tcW to match the
grid, while clear_column_width() drops w:tcW from any cell that still
covers the cleared grid column so stale width hints do not linger.
Switching to autofit or calling clear_layout_mode() does not erase the
saved w:gridCol / w:tcW widths; it only changes the layout algorithm Word
uses when rendering the table.
Likewise, set_width_twips(...) and clear_width() only rewrite w:tblW;
they do not rescale or discard the saved w:gridCol / w:tcW widths.
When a table later re-runs fixed-grid normalization, any manual
TableCell::set_width_twips(...) / clear_width() edits on cells covered by
that complete grid are overwritten back to the summed w:gridCol widths.
For reopened legacy fixed-layout tables whose saved w:tcW no longer matches
w:gridCol, reapplying set_layout_mode(featherdoc::table_layout_mode::fixed)
forces that same normalization pass.
The same normalization also runs when those reopened fixed-layout tables go
through explicit span or column edits such as merge_right(),
unmerge_right(), set_column_width_twips(...), insert_cell_before() /
insert_cell_after(), or TableCell::remove().
clear_column_width() follows the same reopened fixed-layout path for the
cleared grid column, but only clears w:tcW on cells that still cover that
column instead of re-normalizing unrelated stale cell widths.
Plain non-grid edits such as TableCell::set_text(...),
set_fill_color(...), and set_border(...) do not trigger that
normalization on their own.
The same is true for row-only structural edits: TableRow::insert_row_before(),
insert_row_after(), and remove() keep reopened stale w:tcW values as-is
instead of re-normalizing them from w:tblGrid.
unmerge_right() splits one pure horizontal merged cell back into standalone
cells in the current row while keeping the anchor text in the leftmost cell and
leaving restored siblings empty for later editing.
insert_table_before() and insert_table_after() create a new empty sibling
table directly before or after the selected table and retarget the wrapper to
the inserted table. insert_table_like_before() and
insert_table_like_after() clone the selected table's structure plus
table/row/cell formatting into a new sibling table, clear the cloned cell
content, and retarget the wrapper to the inserted table.
TableRow::remove() deletes one row while refusing to remove the last
remaining row, insert_row_before() and insert_row_after() clone the
current row structure into a new empty row directly before or after it while
refusing rows that participate in vertical merge chains, and
unmerge_down() removes one whole vertical merge chain even when the current
wrapper points at a continuation cell, leaving the restored lower cells empty
until the caller writes new content. column_span() reports the current
horizontal span. TableCell::set_text(...)
replaces one cell's body with a single paragraph while preserving cell-level
properties such as shading, margins, borders, and explicit width.
auto table = doc.append_table(1, 3);
table.set_width_twips(7200);
table.set_column_width_twips(0, 1800);
table.set_column_width_twips(1, 2400);
table.set_column_width_twips(2, 3000);
table.set_style_id("TableGrid");
table.set_style_look({true, false, false, false, true, false});
table.set_layout_mode(featherdoc::table_layout_mode::fixed);
table.set_alignment(featherdoc::table_alignment::center);
table.set_indent_twips(240);
table.set_cell_spacing_twips(120);
table.set_cell_margin_twips(featherdoc::cell_margin_edge::left, 96);
table.set_cell_margin_twips(featherdoc::cell_margin_edge::right, 96);
table.set_border(featherdoc::table_border_edge::inside_vertical,
{featherdoc::border_style::single, 8, "808080", 0});
auto row = table.rows();
row.set_height_twips(360, featherdoc::row_height_rule::exact);
row.set_cant_split();
row.set_repeats_header();
auto cell = row.cells();
cell.set_width_twips(2400);
cell.set_vertical_alignment(featherdoc::cell_vertical_alignment::center);
cell.set_fill_color("D9EAF7");
cell.set_margin_twips(featherdoc::cell_margin_edge::left, 120);
cell.set_margin_twips(featherdoc::cell_margin_edge::right, 120);
cell.paragraphs().add_run("Merged title");
cell.merge_right(1);
cell.set_border(featherdoc::cell_border_edge::bottom,
{featherdoc::border_style::thick, 12, "000000", 0});
auto next_row = table.append_row(3);
auto merged_column = next_row.cells();
merged_column.paragraphs().add_run("Below");
cell = row.cells();
cell.merge_down(1);
auto inserted_row = row.insert_row_after();
inserted_row.cells().set_text("Inserted");
auto inserted_before = next_row.insert_row_before();
inserted_before.cells().set_text("Inserted above");
std::cout << cell.column_span() << '\n'; // 2style_look() only changes the style-routing flags stored in w:tblLook, so
visible differences depend on the table style definition present in the
document. It is useful when you want to keep the same style id but switch which
rows or columns that style treats as emphasized or banded.
For runnable insertion examples, build featherdoc_sample_insert_table_row
from samples/sample_insert_table_row.cpp for the insert_row_after() flow,
or featherdoc_sample_insert_table_row_before from
samples/sample_insert_table_row_before.cpp for the insert_row_before()
flow. Both samples create a seed table, reopen the saved .docx, insert a
cloned row in the middle, and write the updated result back out.
For a runnable column-insertion example, build
featherdoc_sample_insert_table_column from
samples/sample_insert_table_column.cpp. It seeds a two-column table, reopens
the saved .docx, inserts one cloned column after the first column and
another before the result column, and writes the updated four-column result
back out.
For a runnable unmerge example, build featherdoc_sample_unmerge_table_cells
from samples/sample_unmerge_table_cells.cpp. It creates one horizontal merge
and one vertical merge, reopens the saved .docx, splits them back into
standalone cells, and continues editing the restored cells.
For a runnable table-removal example, build featherdoc_sample_remove_table
from samples/sample_remove_table.cpp. It creates three body tables, reopens
the saved .docx, removes the temporary middle table, and continues editing
the following table through the same wrapper.
For a runnable table-column removal example, build
featherdoc_sample_remove_table_column from
samples/sample_remove_table_column.cpp. It creates a three-column body
table, reopens the saved .docx, removes the temporary middle column, and
continues editing the surviving result column through the same cell wrapper.
For a runnable table-insertion example, build
featherdoc_sample_insert_table_around_existing from
samples/sample_insert_table_around_existing.cpp. It reopens a saved .docx,
inserts new tables directly before and after an existing anchor table, and
continues editing the surrounding tables through the returned wrappers.
For a runnable styled-table cloning example, build
featherdoc_sample_insert_table_like_existing from
samples/sample_insert_table_like_existing.cpp. It reopens a saved .docx,
duplicates an existing table's layout and styling into new empty sibling
tables, fills the clones, and keeps the original anchor table editable.
For a runnable table-spacing edit example, build
featherdoc_sample_edit_existing_table_spacing from
samples/sample_edit_existing_table_spacing.cpp. It reopens a saved .docx,
adds tblCellSpacing to an existing table, and makes the new gutters visible
in Word's rendered output.
For a runnable table-column-width edit example, build
featherdoc_sample_edit_existing_table_column_widths from
samples/sample_edit_existing_table_column_widths.cpp. It reopens a saved
.docx, rewrites w:tblGrid column widths on an existing table, and makes
the narrow/medium/wide column split visible in Word's rendered output.
For a runnable table-style-look edit example, build
featherdoc_sample_edit_existing_table_style_look from
samples/sample_edit_existing_table_style_look.cpp. It reopens a saved
.docx, updates tblLook on an existing table, and keeps the original table
style reference in place.
Use append_image(path) to append an inline image at its intrinsic pixel size,
or append_image(path, width_px, height_px) when you want explicit scaling.
These APIs are available on both Document and TemplatePart, so you can
append images to the main body or to an existing body/header/footer part
wrapper. The current image support is limited to .png, .jpg, .jpeg,
.gif, and .bmp.
doc.append_image("logo.png");
doc.append_image("badge.png", 96, 48);
auto header_template = doc.section_header_template(0);
header_template.append_image("header-logo.png", 144, 48);Use append_floating_image(path, options) or
append_floating_image(path, width_px, height_px, options) when you want an
anchored wp:anchor image with explicit margin/page-relative offsets.
floating_image_options currently lets you pick horizontal/vertical reference
frames, pixel offsets, whether the image sits behind text, whether overlap is
allowed, the anchor z-order, rectangular wrap modes plus wrap distances, and
basic crop values. The same API works on Document and TemplatePart.
featherdoc::floating_image_options options;
options.horizontal_reference =
featherdoc::floating_image_horizontal_reference::margin;
options.horizontal_offset_px = 460;
options.vertical_reference =
featherdoc::floating_image_vertical_reference::margin;
options.vertical_offset_px = 24;
options.z_order = 32;
options.wrap_mode = featherdoc::floating_image_wrap_mode::square;
options.wrap_distance_left_px = 12;
options.wrap_distance_right_px = 12;
options.crop = featherdoc::floating_image_crop{90U, 0U, 140U, 0U};
doc.append_floating_image("badge.png", 144, 48, options);
auto body_template = doc.body_template();
body_template.append_floating_image("stamp.png", 128, 48, options);Use inline_images() to inspect inline images that already exist in the main
document body. Each returned inline_image_info includes the image index,
relationship id, media entry path, display name, content type, and rendered
pixel size derived from wp:extent.
Use drawing_images() when you need the full picture list from an existing
document part, including both wp:inline and wp:anchor drawings. Each
returned drawing_image_info includes the same metadata plus a
drawing_image_placement value so you can distinguish inline and anchored
objects.
Use extract_drawing_image(index, path) to copy any existing drawing-backed
image out of the .docx, and replace_drawing_image(index, path) to swap one
inline or anchored image with a new file while preserving the current rendered
size and placement XML. Use remove_drawing_image(index) to delete one inline
or anchored drawing and drop its media part on the next save once no
relationship still points at it.
const auto drawings = doc.drawing_images();
for (const auto& image : drawings) {
if (image.placement == featherdoc::drawing_image_placement::anchored_object) {
doc.remove_drawing_image(image.index);
}
}Use extract_inline_image(index, path) to copy one existing inline body image
out of the .docx, and replace_inline_image(index, path) to swap one body
image with a new file while preserving the current displayed size.
remove_inline_image(index) deletes only inline images. Replacement retargets
only the selected relationship. If the old media part becomes unreferenced
afterwards, FeatherDoc removes it from the next saved archive.
const auto images = doc.inline_images();
if (!images.empty()) {
doc.extract_inline_image(images[0].index, "first-image.bin");
doc.remove_inline_image(images[0].index);
}The same drawing_images(), extract_drawing_image(...),
remove_drawing_image(...), replace_drawing_image(...), inline_images(),
extract_inline_image(...), remove_inline_image(...), and
replace_inline_image(...) APIs are also available on TemplatePart
handles when you need existing body/header/footer drawings from already loaded
parts.
For runnable visual samples, build featherdoc_sample_floating_images from
samples/sample_floating_images.cpp when you want mixed inline/floating body
drawings, or featherdoc_sample_remove_images from
samples/sample_remove_images.cpp when you want a minimal existing-image
removal workflow.
Use set_paragraph_list(paragraph, kind, level) to attach managed bullet or
decimal numbering to a paragraph. Call restart_paragraph_list(paragraph, kind, level) when you want a fresh managed list sequence that starts over at the
default marker for that kind, and clear_paragraph_list(paragraph) when you
want to remove the list marker from that paragraph again.
auto item = doc.paragraphs();
doc.set_paragraph_list(item, featherdoc::list_kind::bullet);
item.add_run("first item");
auto nested = item.insert_paragraph_after("");
doc.set_paragraph_list(nested, featherdoc::list_kind::decimal, 1);
nested.add_run("nested item");
auto restarted = nested.insert_paragraph_after("");
doc.restart_paragraph_list(restarted, featherdoc::list_kind::decimal);
restarted.add_run("restarted item 1");For a runnable list-restart example, build featherdoc_sample_restart_paragraph_list
from samples/sample_restart_paragraph_list.cpp. It reopens a saved .docx,
starts a second decimal list from 1., and keeps the restarted sequence
consistent in Word's rendered output.
Use set_paragraph_style(paragraph, style_id) and set_run_style(run, style_id) to attach paragraph/run style references. When the source document
does not already contain word/styles.xml, FeatherDoc creates a minimal styles
part automatically. The generated catalog currently includes Normal,
Heading1, Heading2, Quote, Emphasis, and Strong.
auto paragraph = doc.paragraphs();
doc.set_paragraph_style(paragraph, "Heading1");
auto styled_run = paragraph.add_run("Styled heading");
doc.set_run_style(styled_run, "Strong");
doc.clear_run_style(styled_run);
doc.clear_paragraph_style(paragraph);paragraph.add_run("bold text", featherdoc::formatting_flag::bold);
paragraph.add_run(
"mixed style",
featherdoc::formatting_flag::bold |
featherdoc::formatting_flag::italic |
featherdoc::formatting_flag::underline
);Document now uses std::filesystem::path, and both open() and save()
return std::error_code so callers can inspect the failure reason directly.
if (const auto error = doc.save()) {
const auto& error_info = doc.last_error();
std::cerr << error.message() << '\n';
std::cerr << error_info.detail << '\n';
return 1;
}last_error() exposes structured context for the most recent failure:
code: the samestd::error_codereturned byopen()/save()detail: richer human-readable explanationentry_name: failing ZIP entry when relevantxml_offset: parse offset for malformed XML
Use save_as(path) when you want to keep the current source document path
unchanged and write the modified document to a different output file.
if (const auto error = doc.save_as("copy.docx")) {
std::cerr << error.message() << '\n';
return 1;
}Use replace_bookmark_text(name, replacement) when you want to rewrite the
content enclosed by a named bookmark range. The method returns the number of
bookmark ranges replaced.
if (doc.replace_bookmark_text("customer_name", "FeatherDoc User") == 0) {
std::cerr << "bookmark not found\n";
return 1;
}Use fill_bookmarks(...) when you want a first high-level template API for
batch text filling. It accepts bookmark bindings, rewrites every matching
bookmark range, and reports which requested fields were missing.
const auto result = doc.fill_bookmarks({
{"customer_name", "FeatherDoc User"},
{"invoice_no", "INV-2026-0001"},
{"due_date", "2026-04-30"},
});
if (!result) {
for (const auto& missing : result.missing_bookmarks) {
std::cerr << "missing bookmark: " << missing << '\n';
}
}Use replace_bookmark_with_paragraphs(...) when a bookmark occupies its own
paragraph and should expand into zero or more plain-text paragraphs. Passing an
empty list removes the placeholder paragraph entirely.
doc.replace_bookmark_with_paragraphs(
"line_items",
{
"Apple",
"Pear",
"Orange",
});Use remove_bookmark_block(...) when that same standalone bookmark paragraph
should simply be deleted without constructing an empty replacement list.
doc.remove_bookmark_block("optional_note");For a runnable example, build featherdoc_sample_remove_bookmark_block from
samples/sample_remove_bookmark_block.cpp. It opens the Chinese invoice
template, fills core fields, expands the item table, removes the standalone
note_lines bookmark block, and saves the result for visual review.
Use replace_bookmark_with_table_rows(...) when a bookmark occupies its own
paragraph inside a template table row and that row should expand into zero or
more cloned rows. The template row's row/cell properties are preserved, while
each generated cell body is rewritten to a single plain-text paragraph. Passing
an empty list removes the template row entirely.
doc.replace_bookmark_with_table_rows(
"line_item_row",
{
{"Apple", "2"},
{"Pear", "5"},
{"Orange", "1"},
});For a runnable Chinese business example, build
featherdoc_sample_chinese_template from
samples/sample_chinese_template.cpp. It opens
samples/chinese_invoice_template.docx, fills Chinese customer fields,
expands a bookmarked table row into a three-column quote table, and writes a
finished output document.
Use replace_bookmark_with_table(...) when a bookmark occupies its own
paragraph and should be replaced by a generated table block.
doc.replace_bookmark_with_table(
"line_items",
{
{"Name", "Qty"},
{"Apple", "2"},
{"Pear", "5"},
});Use replace_bookmark_with_image(...) when a bookmark occupies its own
paragraph and should become an inline image paragraph. The overload without
dimensions uses the source image size; the second overload lets you scale it.
doc.replace_bookmark_with_image("company_logo", "logo.png");
doc.replace_bookmark_with_image("stamp", "stamp.png", 96, 48);Use replace_bookmark_with_floating_image(...) when that standalone bookmark
paragraph should instead become an anchored image paragraph. The floating
placement uses the same floating_image_options struct as
append_floating_image(...).
featherdoc::floating_image_options options;
options.horizontal_reference =
featherdoc::floating_image_horizontal_reference::page;
options.horizontal_offset_px = 24;
doc.replace_bookmark_with_floating_image("callout", "badge.png", 144, 48,
options);Use body_template(), header_template(index), footer_template(index),
section_header_template(section_index, kind), and
section_footer_template(section_index, kind) when you want the same
bookmark-based template APIs on an already loaded body/header/footer part.
Each method returns a lightweight TemplatePart handle. A valid handle
supports entry_name(), paragraphs(), append_paragraph(...), tables(),
append_table(...), replace_bookmark_text(...), fill_bookmarks(...),
replace_bookmark_with_paragraphs(...), remove_bookmark_block(...),
replace_bookmark_with_table_rows(...), and
replace_bookmark_with_table(...), replace_bookmark_with_image(...),
replace_bookmark_with_floating_image(...),
drawing_images(), extract_drawing_image(...),
remove_drawing_image(...), replace_drawing_image(...),
inline_images(), extract_inline_image(...),
remove_inline_image(...), replace_inline_image(...),
set_bookmark_block_visibility(...), and
apply_bookmark_block_visibility(...).
Missing section-specific references return an empty handle instead of creating
a new part implicitly.
auto header_template = doc.section_header_template(0);
if (header_template) {
auto note = header_template.append_paragraph("Header note");
note.add_run(" added after opening the template part.");
auto summary_table = header_template.append_table(2, 2);
summary_table.rows().cells().set_text("Header cell");
header_template.replace_bookmark_with_image("header_logo", "logo.png");
header_template.replace_bookmark_with_table_rows(
"line_item_row",
{
{"Apple", "2"},
{"Pear", "5"},
});
}
auto footer_template = doc.section_footer_template(0);
if (footer_template) {
footer_template.fill_bookmarks({
{"company_name", "Acme Corp"},
});
footer_template.remove_bookmark_block("optional_legal_notice");
footer_template.replace_bookmark_with_paragraphs(
"footer_lines",
{
"First line",
"Second line",
});
}When you need one document-level contract for multiple parts at once, use
Document::validate_template_schema(...) and group requirements by body,
header, footer, or section-scoped header/footer targets.
const auto result = doc.validate_template_schema({
{
{featherdoc::template_schema_part_kind::section_header, std::nullopt, 0U,
featherdoc::section_reference_kind::default_reference},
{"header_title", featherdoc::template_slot_kind::text, true},
},
{
{featherdoc::template_schema_part_kind::section_footer, std::nullopt, 0U,
featherdoc::section_reference_kind::default_reference},
{"footer_signature", featherdoc::template_slot_kind::text, true},
},
});For a runnable document-level sample, build
featherdoc_sample_template_schema_validation from
samples/sample_template_schema_validation.cpp. The CLI equivalent is
featherdoc_cli validate-template-schema ..., which also accepts
--schema-file <path> for reusable JSON schema contracts.
The schema-file flow accepts either compact string slots such as
"header_title:text" or structured slot objects:
{
"targets": [
{
"part": "section-header",
"section": 0,
"kind": "default",
"slots": [
{ "bookmark": "header_title", "kind": "text" },
{ "bookmark_name": "header_note", "kind": "block", "required": true },
{ "bookmark": "header_rows", "kind": "table_rows", "count": 1 }
]
}
]
}A ready-to-run example lives at
samples/template_schema_validation.schema.json.
If you do not want to hand-author the first schema, start from an existing template:
featherdoc_cli export-template-schema input.docx --output template-schema.json --json
featherdoc_cli validate-template-schema input.docx --schema-file template-schema.json --jsonexport-template-schema currently emits body plus loaded header[index] /
footer[index] targets by default. Pass --section-targets when you want the
same export rewritten as section-header / section-footer targets for direct
section references. Pass --resolved-section-targets when you need the
effective per-section header/footer view after following linked-to-previous
references; that export also includes metadata such as
resolved_from_section / linked_to_previous, and the JSON can still be fed
back into validate-template-schema --schema-file .... All three modes
serialize representable bookmark kinds using the same lightweight
classification returned by list_bookmarks(). If you need a stable schema file
for reviews or commits, run normalize-template-schema; if you need to compare
two revisions, use diff-template-schema to get added / removed / changed
targets directly. If you need to gate a committed schema in review or CI, use
lint-template-schema; it returns 0 for clean schemas and 1 when it finds
duplicate target identities, duplicate slot names, non-canonical ordering, or
leaked entry_name metadata. If you want the safe canonical rewrite for those
maintenance issues, run repair-template-schema; it strips entry_name,
merges duplicate target identities using later-definition-wins semantics, folds
duplicate slot names, and then normalizes target / slot order. If you need to
compose a shared schema with a
document-specific overlay, use merge-template-schema; later files upsert
matching targets and replace same-bookmark slot definitions before the merged
result is normalized. If you need to maintain a committed schema in place, use
patch-template-schema with a patch file that can upsert_targets,
remove_targets, remove_slots, and rename_slots; the patched result is
normalized before it is printed or written. If you already have a reviewed left
and right schema pair and want a reusable patch file, use
build-template-schema-patch; it stays correctness-first, but when a target
keeps the same full identity it prefers slot-level remove_slots,
rename_slots, and partial upsert_targets. Only identity changes fall back
to whole-target remove_targets plus upsert_targets, so applying the patch
to the left schema still reproduces the normalized right schema.
Add --fail-on-diff when you want the diff command to behave like a CI gate
and return a non-zero exit code on schema drift. If you want a single command
that exports, normalizes, compares, and gates against a committed baseline, use
check-template-schema; it returns 0 on match and 1 on drift, and can
optionally write the normalized generated schema with --output. If you prefer
a higher-level repository entrypoint, the wrapper scripts
scripts/freeze_template_schema_baseline.ps1 and
scripts/check_template_schema_baseline.ps1 provide the same workflow with
optional CLI auto-build/reuse logic. The baseline wrapper now runs
lint-template-schema before the document-vs-baseline comparison and can write
an optional repaired candidate with -RepairedSchemaOutput when the committed
schema needs cleanup. scripts/check_template_schema_manifest.ps1 applies the
same gate across the repository manifest, reports dirty_baseline_count in its
summary.json, and can emit per-entry repaired candidates with
-RepairedSchemaOutputDir.
A patch-template-schema patch file is a JSON object with zero or more of these
arrays:
upsert_targets: regular exported schema targets, merged with the same target and slot-replacement rules asmerge-template-schemaremove_targets: target selectors matched by full target identity, includingpart,index/part_index,section,kind,resolved_from_section, andlinked_to_previousremove_slots: the same selector fields plusbookmarkorbookmark_name; matching slots are removed and empty targets are prunedrename_slots: the same selector fields plusbookmark/bookmark_namefor the old slot name andnew_bookmark/new_bookmark_namefor the new slot nameentry_nameis ignored in selectors, so exported JSON can be copied into a patch file after trimming unrelated fields{}is also valid and represents a no-op patch; that is whatbuild-template-schema-patchemits when two schemas are already equivalentbuild-template-schema-patchonly emitsrename_slotswhen the left/right target identity is unchanged and the old/new slot shapes match uniquely; if that confidence is missing, it falls back to remove/add style patch entries
Example patch file:
{
"remove_targets": [
{
"part": "section-footer",
"section": 1,
"kind": "default"
}
],
"remove_slots": [
{
"part": "body",
"bookmark": "summary_block"
},
{
"part": "section-header",
"section": 1,
"kind": "default",
"resolved_from_section": 0,
"linked_to_previous": true,
"bookmark_name": "legacy_header_note"
}
],
"rename_slots": [
{
"part": "section-header",
"section": 1,
"kind": "default",
"resolved_from_section": 0,
"linked_to_previous": true,
"bookmark": "header_title",
"new_bookmark": "document_title"
}
],
"upsert_targets": [
{
"part": "body",
"slots": [
{
"bookmark": "customer",
"kind": "text",
"count": 2
},
{
"bookmark": "invoice_no",
"kind": "text"
}
]
}
]
}append_paragraph(...) appends a new paragraph to the existing body/header/footer
part and returns the appended Paragraph, so you can immediately continue
editing it through add_run(...), set_text(...), or bidi-related paragraph
APIs.
For a runnable existing-part table example, build
featherdoc_sample_edit_existing_part_tables from
samples/sample_edit_existing_part_tables.cpp. It creates a default header,
adds three header tables, reopens the saved .docx, removes the temporary
middle table, and continues editing the following header table through the same
wrapper.
For a runnable existing-part image-append example, build
featherdoc_sample_edit_existing_part_append_images from
samples/sample_edit_existing_part_append_images.cpp. It seeds body/header/
footer content, reopens the saved .docx, and appends new inline or floating
images through TemplatePart handles before saving the edited result back out.
Use set_bookmark_block_visibility(name, visible) or
apply_bookmark_block_visibility(...) when a bookmark pair should guard an
optional block of sibling content such as paragraphs or tables. The template
must place w:bookmarkStart in its own paragraph, w:bookmarkEnd in a later
paragraph, and both marker paragraphs must share the same parent container.
When visible is true, FeatherDoc keeps the content between them and removes
only the marker paragraphs. When visible is false, FeatherDoc removes the
whole block including both marker paragraphs.
const auto visibility = doc.apply_bookmark_block_visibility({
{"promo_block", false},
{"legal_block", true},
});
if (!visibility) {
for (const auto& missing : visibility.missing_bookmarks) {
std::cerr << "missing block bookmark: " << missing << '\n';
}
}Use header_count(), footer_count(), header_paragraphs(index), and
footer_paragraphs(index) when you need to read or edit paragraphs stored in
existing header/footer parts.
for (std::size_t i = 0; i < doc.header_count(); ++i) {
for (auto paragraph = doc.header_paragraphs(i); paragraph.has_next();
paragraph.next()) {
for (auto run = paragraph.runs(); run.has_next(); run.next()) {
std::cout << run.get_text() << '\n';
}
}
}Use ensure_header_paragraphs() and ensure_footer_paragraphs() when you need
to create and attach a default header/footer to the document's body-level
section properties before editing it.
auto header = doc.ensure_header_paragraphs();
header.add_run("Generated header");
auto footer = doc.ensure_footer_paragraphs();
footer.add_run("Page 1");Use section_count(), section_header_paragraphs(section_index, kind), and
section_footer_paragraphs(section_index, kind) when you need to resolve the
existing header/footer reference attached to a specific section.
for (std::size_t i = 0; i < doc.section_count(); ++i) {
auto header = doc.section_header_paragraphs(i);
if (header.has_next()) {
std::cout << header.runs().get_text() << '\n';
}
}Use ensure_section_header_paragraphs(section_index, kind) and
ensure_section_footer_paragraphs(section_index, kind) when you need to create
and attach a missing section-specific header/footer reference before editing it.
When kind is first_page or even_page, FeatherDoc also enables the
required WordprocessingML switches (w:titlePg or
word/settings.xml -> w:evenAndOddHeaders) automatically.
auto even_header = doc.ensure_section_header_paragraphs(
1, featherdoc::section_reference_kind::even_page);
even_header.add_run("Even page header");
auto first_footer = doc.ensure_section_footer_paragraphs(
1, featherdoc::section_reference_kind::first_page);
first_footer.add_run("First page footer");Use assign_section_header_paragraphs(section_index, header_index, kind) and
assign_section_footer_paragraphs(section_index, footer_index, kind) when you
need multiple sections to reuse an existing header/footer part instead of
creating a new one. Each call only rebinds the requested kind, so reuse
across multiple kinds on the same section needs one call per kind.
auto shared_header = doc.assign_section_header_paragraphs(1, 0);
shared_header.runs().set_text("Shared header");
auto shared_footer = doc.assign_section_footer_paragraphs(1, 0);
shared_footer.runs().set_text("Shared footer");
auto shared_first_footer = doc.assign_section_footer_paragraphs(
1, 0, featherdoc::section_reference_kind::first_page);
shared_first_footer.runs().set_text("Shared footer");Use remove_section_header_reference(section_index, kind) and
remove_section_footer_reference(section_index, kind) to detach a specific
section-level reference without touching other kinds already attached to the
same section.
doc.remove_section_header_reference(1);
doc.remove_section_footer_reference(
1, featherdoc::section_reference_kind::first_page);When a header/footer part is no longer referenced from document.xml,
save() / save_as() automatically omit the orphaned part together with the
matching document.xml.rels relationship and [Content_Types].xml override.
Removing the last first-page or even-page reference also drops w:titlePg or
w:evenAndOddHeaders when that flag is no longer needed.
Use remove_header_part(index) and remove_footer_part(index) when you want to
drop one loaded header/footer part entirely. The matching section references are
detached in memory, header_count() / footer_count() shrink immediately, and
the orphaned ZIP entries are omitted on the next save.
doc.remove_header_part(1);
doc.remove_footer_part(1);Use copy_section_header_references(source_section, target_section) and
copy_section_footer_references(source_section, target_section) when one
section should adopt another section's current header/footer reference layout.
The target side is replaced for that reference family, so stale first / even
references are removed automatically.
doc.copy_section_header_references(0, 1);
doc.copy_section_footer_references(0, 1);Use replace_section_header_text(section_index, replacement, kind) and
replace_section_footer_text(section_index, replacement, kind) when you want
to rewrite a section-specific header/footer as plain paragraph text in one step.
The replacement text is split on newlines, and the requested reference is
created automatically when it is missing.
doc.replace_section_header_text(0, "Title line\nSubtitle line");
doc.replace_section_footer_text(
0, "First page footer", featherdoc::section_reference_kind::first_page);Use append_section(inherit_header_footer) to append a new final section at the
end of the document. By default it inherits the previous final section's
header/footer reference layout; passing false appends the new section without
copying those references.
doc.append_section();
doc.append_section(false);Use insert_section(section_index, inherit_header_footer) to insert a new
section after an existing section. By default the inserted section inherits the
referenced section's current header/footer reference layout; passing false
creates the new section break without copying those references.
doc.insert_section(0);
doc.insert_section(1, false);Use remove_section(section_index) to remove one section while preserving the
document content around it. Removing a non-final section collapses its break so
that content flows into the following section; removing the final section makes
the previous section become the new final section.
doc.remove_section(1);Use move_section(source_section_index, target_section_index) to reorder whole
sections. The section content and its header/footer reference layout move
together, and target_section_index is the final index of the moved section
after reordering.
doc.move_section(2, 0);Use create_empty() when you want to build a new .docx document from scratch
without relying on an existing template archive.
featherdoc::Document doc("new-file.docx");
if (const auto error = doc.create_empty()) {
std::cerr << error.message() << '\n';
return 1;
}
doc.paragraphs().add_run("Hello FeatherDoc");
if (const auto error = doc.save()) {
std::cerr << error.message() << '\n';
return 1;
}Use the default run font/language APIs when you want Chinese/CJK text to carry
explicit w:rFonts and w:lang metadata instead of relying on Word's fallback
heuristics.
featherdoc::Document doc("zh-demo.docx");
if (const auto error = doc.create_empty()) {
std::cerr << error.message() << '\n';
return 1;
}
if (!doc.set_default_run_font_family("Segoe UI") ||
!doc.set_default_run_east_asia_font_family("Microsoft YaHei") ||
!doc.set_default_run_language("en-US") ||
!doc.set_default_run_east_asia_language("zh-CN")) {
std::cerr << "failed to configure default run fonts/languages\n";
return 1;
}
auto run = doc.paragraphs().add_run("你好,FeatherDoc。这里是一段中文/CJK 文本。");
if (!run.has_next()) {
std::cerr << "failed to append Chinese/CJK paragraph\n";
return 1;
}
if (const auto error = doc.save()) {
std::cerr << error.message() << '\n';
return 1;
}The CLI now exposes the same docDefaults surface through
inspect-default-run-properties, set-default-run-properties, and
clear-default-run-properties when you need the same default font/language/RTL
edits from scripts instead of C++.
When one paragraph needs its own override, call run.set_font_family(...),
run.set_east_asia_font_family(...), run.set_language(...), and
run.set_east_asia_language(...) on the returned Run.
Use run.clear_primary_language(), run.clear_east_asia_font_family(),
run.clear_east_asia_language(), and run.clear_bidi_language() when only
one CJK/RTL override should be removed while preserving the rest of the run
formatting. run.clear_language() still removes all w:lang attributes
(w:val, w:eastAsia, and w:bidi) in one call.
Use doc.inspect_paragraph_runs(paragraph_index) or
doc.inspect_paragraph_run(paragraph_index, run_index) when you need a
library-level summary of run style/font/language/RTL metadata without going
through the CLI helpers.
Use doc.inspect_paragraphs() or doc.inspect_paragraph(paragraph_index) when
you need paragraph-level style/bidi/numbering/run-count metadata from the core
library.
Use doc.inspect_tables() or doc.inspect_table(table_index) when you need
table-level style/width/grid/text metadata from the core library.
Use featherdoc_cli inspect-tables when you need the same table inspection
metadata from the command line.
Use doc.inspect_table_cells(table_index) or
doc.inspect_table_cell(table_index, row_index, cell_index) when you need
cell-level width/span/layout/text metadata from the core library.
Use featherdoc_cli inspect-table-cells when you need the same cell
inspection metadata from the command line.
Use featherdoc_cli inspect-table-rows when you need body-table row
height/page-break/header-repeat/cell-text metadata from the command line.
Use featherdoc_cli set-table-cell-text when you need to replace a specific
table cell's plain text from the command line.
Use featherdoc_cli set-table-cell-fill and
clear-table-cell-fill when you need to add or remove a body-table cell
background fill from the command line.
Use featherdoc_cli set-table-cell-vertical-alignment and
clear-table-cell-vertical-alignment when you need to edit body-table cell
vertical alignment from the command line.
Use featherdoc_cli set-table-cell-text-direction and
clear-table-cell-text-direction when you need to edit body-table cell text
direction from the command line.
Use featherdoc_cli set-table-cell-width and
clear-table-cell-width when you need to assign or remove an explicit
body-table cell width from the command line.
Use featherdoc_cli set-table-cell-margin and
clear-table-cell-margin when you need to edit one edge of a body-table
cell's internal margin from the command line.
Use featherdoc_cli set-table-cell-border and
clear-table-cell-border when you need to edit one edge of a body-table
cell border from the command line.
Use featherdoc_cli set-table-row-height and
clear-table-row-height when you need to assign or remove an explicit
body-table row height override from the command line.
Use featherdoc_cli set-table-row-cant-split and
clear-table-row-cant-split when you need to control whether a body-table
row is allowed to split across pages from the command line.
Use featherdoc_cli set-table-row-repeat-header and
clear-table-row-repeat-header when you need to control whether a body-table
row repeats as a header row from the command line.
Use featherdoc_cli append-table-row, insert-table-row-before,
insert-table-row-after, and remove-table-row when you need to append,
insert, or remove body-table rows from the command line. When no explicit
--cell-count is provided, appended rows default to the current table column
count.
Use featherdoc_cli merge-table-cells when you need to merge a specific body
table cell to the right or downward from the command line.
Use featherdoc_cli unmerge-table-cells when you need to split an existing
horizontal or vertical body-table merge from the command line.
Use featherdoc_cli merge-template-table-cells when you need to merge a
specific table cell inside a template-part table selected through
--part/--index/--section/--kind.
Use featherdoc_cli unmerge-template-table-cells when you need to split an
existing horizontal or vertical merge inside those template-part tables from
the command line.
Template-table inspection and mutation commands that accept a positional
<table-index> also accept --bookmark <name> as an alternative selector.
That bookmark can live inside the target table or immediately before it, which
makes it the recommended way to anchor "the table on this page" in a DOCX
workflow where rendered page numbers are not stable.
For example, if page3_target_table is a bookmark placed inside the table you
want to edit on a rendered page, you can drive the mutation without guessing a
table index:
featherdoc_cli set-template-table-cell-text report.docx --bookmark page3_target_table 1 2 --text "Updated value" --output report-updated.docx --json
featherdoc_cli set-template-table-row-texts report-updated.docx --bookmark page3_target_table 3 --row "Item A" --cell "3" --cell "99.00" --row "Item B" --cell "1" --cell "18.00" --output report-updated.docx --json
featherdoc_cli set-template-table-cell-block-texts report-updated.docx --bookmark page3_target_table 3 1 --row "North" --cell "120" --row "South" --cell "98" --output report-updated.docx --json
featherdoc_cli append-template-table-row report-updated.docx --bookmark page3_target_table --output report-updated.docx --jsonThe C++ API exposes the same workflow on TemplatePart: call
find_table_by_bookmark(...) on body_template(), header_template(),
footer_template(), section_header_template(), or
section_footer_template() and mutate the returned Table handle directly.
That Table handle now also supports direct indexed helpers such as
find_row(...), find_cell(...), set_cell_text(...), and
set_row_texts(...), plus batch helpers such as set_rows_texts(...) and
set_cell_block_texts(...), so page-local table edits no longer require
manual next() loops in user code.
Use doc.inspect_sections() or doc.inspect_section(section_index) when you
need a section/header/footer summary of both the explicit default/first/even
references stored on each section and the resolved linked-to-previous fallback
chain, including the underlying word/headerN.xml / word/footerN.xml entry
names, the source section index that currently supplies each slot, and the
document-wide w:evenAndOddHeaders setting from word/settings.xml when it
can be read.
The same inspection summaries are also available on TemplatePart handles
returned by body_template(), header_template(), footer_template(),
section_header_template(), and section_footer_template().
For a runnable end-to-end version, build featherdoc_sample_chinese from
samples/sample_chinese.cpp with -DBUILD_SAMPLES=ON.
open()now keeps XML buffer ownership on the FeatherDoc side before parsing, which avoids cross-library allocator mismatches in shared-library builds.save()now streamsdocument.xmldirectly into the output archive instead of materializing one large intermediate string.save()also copies non-XML ZIP entries chunk-by-chunk, which avoids loading each entry into heap memory before writing it back.
- Password-protected or encrypted
.docxfiles are not supported yet. - Section-specific header/footer references can now be created and rebound
through
ensure_section_header_paragraphs()/ensure_section_footer_paragraphs()andassign_section_header_paragraphs()/assign_section_footer_paragraphs(), and removed throughremove_section_header_reference()/remove_section_footer_reference(). Whole parts can also be dropped throughremove_header_part()/remove_footer_part(), and section reference layouts can be copied throughcopy_section_header_references()/copy_section_footer_references(). New sections can now be appended or inserted after an existing section throughappend_section()/insert_section(), removed throughremove_section(), and reordered throughmove_section(). Header/footer part indexes can now also be reordered throughmove_header_part()/move_footer_part(). - Word equations (
OMML) are not surfaced through a typed equation API. - Tables can now be appended, extended structurally, given explicit cell,
column, and table widths, merged horizontally and vertically, assigned
table/cell
borders, switched between fixed and autofit layout, aligned/indented within
the page, pointed at existing table style ids, given basic table-level
default cell margins and cell shading/margins, assigned row heights,
controlled for page splitting, assigned cell vertical alignment, marked to
repeat header rows, and retuned through
tblLookstyle-routing flags, but there is still no high-level API for custom table style definitions or floating table positioning. - Paragraphs can now be attached to managed bullet and decimal lists and can
restart managed list sequences. Custom numbering definitions and
paragraph-style numbering are now supported through
ensure_numbering_definition(...)andset_paragraph_style_numbering(...), and multi-style shared outline linking is now available throughensure_style_linked_numbering(...), but there is still no richer import/export or override-management layer for existing numbering catalogs. - Paragraph and run style references can now be attached and cleared, and a
minimal
word/styles.xmlis created automatically when needed. Style catalog inspection and minimal paragraph/character/table style definition editing are now available throughlist_styles(),find_style(),find_style_usage(),resolve_style_properties(), and theensure_*_style(...)helpers. Effective inherited font/language/RTL/paragraph-bidi inspection is now available, andmaterialize_style_run_properties(...),rebase_paragraph_style_based_on(...), andrebase_character_style_based_on(...)can now freeze supported inherited properties onto the child style before abasedOnrewrite. Broader style-linked numbering and higher-level style refactoring workflows are still missing. - Bookmark-based template filling now works across body, header, and footer
parts through
fill_bookmarks(...), the standalone replacement helpers, andTemplateParthandles returned bybody_template(),header_template(),footer_template(),section_header_template(), andsection_footer_template(). Conditional block visibility is now supported throughset_bookmark_block_visibility(...)andapply_bookmark_block_visibility(...), andvalidate_template(...)now covers slot declarations, missing required slots, duplicate names, malformed placeholders, unexpected bookmarks, kind mismatches, and occurrence constraints. Document-level multi-part schema validation is now available throughvalidate_template_schema(...)plusfeatherdoc_cli validate-template-schema, and reusable JSON schema files can now be fed through--schema-file, but there is still no richer schema mutation layer or external schema-management toolchain. - Images can now be appended as inline body drawings, enumerated through
inline_images()or the broaderdrawing_images(), extracted throughextract_inline_image(...)/extract_drawing_image(...), removed throughremove_inline_image(...)/remove_drawing_image(...), and replaced throughreplace_inline_image(...)/replace_drawing_image(...). Floating body image creation is now supported throughappend_floating_image(...), and bookmark-based floating image replacement is available throughreplace_bookmark_with_floating_image(...)across body, header, and footerTemplateParthandles. Rectangular wrapping, crop values, overlap control, and anchor z-order are now exposed throughfloating_image_options, but more advanced drawing behaviors are still not surfaced as high-level APIs.
The core implementation is now split into focused translation units instead of
living in a single large .cpp file:
src/document.cpp:Documentopen/save flow, archive handling, and error reportingsrc/document_image.cpp: inline body image insertion, enumeration, extraction, replacement, media part allocation, and drawing relationship updatessrc/document_numbering.cpp: managed paragraph list numbering, numbering part attachment, and numbering definition generationsrc/document_styles.cpp: paragraph/run style references andword/styles.xmlattachment/persistencesrc/document_template.cpp: bookmark-based template filling and batch replacement APIssrc/paragraph.cpp: paragraph traversal, run creation, paragraph insertion, and paragraph-property cloningsrc/image_helpers.cpp/src/image_helpers.hpp: image binary loading plus file format and size detection helperssrc/run.cpp: run traversal, text/property edits, and run insertion/removalsrc/table.cpp: table creation plus row/cell traversal and editing helperssrc/xml_helpers.cpp/src/xml_helpers.hpp: internal XML helper utilities shared by the modulessrc/constants.cpp: exported constants and error-category plumbingcli/featherdoc_cli.cpp: scriptable inspection and editing utility for sections, styles, numbering, page setup, bookmarks, images, and template parts
This layout keeps archive I/O, XML navigation, and public API objects easier to reason about and extend independently.
pugixml1.15kuba--/zip0.3.8doctest2.5.1
- Simplified Chinese README:
README.zh-CN.md - Changelog:
CHANGELOG.md - Sphinx docs entry:
docs/index.rst - Project identity guide:
docs/project_identity_zh.rst - Current direction guide (Chinese):
docs/current_direction_zh.rst - Release policy guide:
docs/release_policy_zh.rst - Chinese license guide:
docs/licensing_zh.rst - Repository legal notes:
LEGAL.md - Distribution notice summary:
NOTICE
FeatherDoc should be treated as its own actively-shaped fork rather than a passive mirror of the historical upstream project.
- The product goal is a
.docxengine for formal document workflows, covering document processing, structured editing, generation, and verification. - Modern C++ and clearer API semantics take priority over preserving obsolete compatibility patterns.
- MSVC buildability is a real support target, not a best-effort afterthought.
- Error diagnostics, save/open behavior, and core-path performance are first class concerns.
- Project positioning, licensing, documentation, and repository metadata follow the current FeatherDoc direction.
If you need the current planning document that explains what to build next, see
docs/current_direction_zh.rst.
If this project helps your work, you can support ongoing maintenance via the following support QR codes.
Left: Alipay. Right: WeChat Appreciation.
This fork should be described as source-available rather than open source for its fork-specific modifications.
- Fork-specific FeatherDoc modifications are distributed under the
non-commercial source-available terms in
LICENSE. - Upstream DuckX-derived portions still keep the original MIT text preserved in
LICENSE.upstream-mit. - Bundled third-party dependencies keep their own original licenses.
- Practical Chinese reading guide:
docs/licensing_zh.rst - Short repository legal guide:
LEGAL.md - Distribution notice file:
NOTICE






