All notable changes to this project will be documented in this file.
- TypeScript subpath exports not resolving under
moduleResolution: "node"(Issue #113) - AddedtypesVersionsfallback to npm package.json sodocxodus/reactanddocxodus/workersubpath imports resolve types correctly under all TypeScript module resolution modes. Also reordered export conditions to puttypesbeforeimportper TypeScript requirements.
- Incremental annotation overlay API (Issue #106) - Decouple HTML conversion from annotation projection to avoid full WASM re-conversion
ProjectAnnotationsOntoHtml()- Project a full annotation set onto already-converted HTMLAddAnnotationToHtml()- Add a single annotation to existing HTML without re-converting the documentRemoveAnnotationFromHtml()- Remove a single annotation by ID, unwrapping spans back to plain textGenerateVisibilityCss()- Generate CSS to hide/show annotations by label ID for instant togglingGenerateAnnotationCssString()- Generate annotation CSS separately for independent management- All methods available in .NET, WASM (JSExport), and npm TypeScript wrapper
- CSS-based label filtering enables responsive toggle without any re-rendering
- Paginated rendering: text clipped at page bottom + inconsistent paragraph spacing (Issue #114)
- Fixed
lineRuledefault handling: whenw:lineRuleis absent butw:lineis present, treat as "auto" per OOXML spec (ISO/IEC 29500). Previously the line value was ignored, causing accumulated line-height mismatches that clipped the last line on pages. - Fixed
contextualSpacinghandling: now suppresses bothspacingAfter(margin-bottom) ANDspacingBefore(margin-top) for consecutive same-style paragraphs. Previously onlyspacingAfterwas suppressed, leaving inconsistent inter-paragraph gaps. - Fixed pagination engine bottom margin over-reservation: the last block's bottom margin is no longer counted against page space since it's invisible (clipped by
overflow: hidden). This prevents premature page breaks where content would have been visible.
- Fixed
- Annotation projection fails on sanitized HTML (Issue #110) -
ProjectAnnotationsOntoHtml,AddAnnotationToHtml, andRemoveAnnotationFromHtmlnow handle HTML fragments with multiple root elements (e.g., DOMPurify-sanitized output) and HTML named entities ( ,–, etc.)- Root cause:
XElement.Parse()requires valid XML with a single root element; sanitized HTML strips<html>/<body>wrappers leaving multiple roots - Fix: Auto-wraps multi-root HTML in a synthetic container for parsing, unwraps on serialization; replaces common HTML entities with numeric XML equivalents
- Root cause:
- Table container missing top margin (Issue #108) - Tables preceded by paragraphs with no after-spacing now get a default
margin-top: 7.5ptfor visual separation- Also handles floating table spacing from
w:tblpPr(topFromText/bottomFromTextattributes) - Tables preceded by paragraphs with explicit after-spacing correctly skip the default margin
- Also handles floating table spacing from
- Move markup Word compatibility (Issue #96) - Documents with move operations no longer cause Word "unreadable content" warnings
- Root cause:
FixUpRevMarkIds()was overwriting IDs ofw:del/w:insafterFixUpRevisionIds()had already assigned unique IDs, causing collisions with move element IDs - Fix: Removed redundant
FixUpRevMarkIds()call -FixUpRevisionIds()already handles all revision element IDs correctly - Added
SimplifyMoveMarkupsetting to optionally convert move markup to simplew:del/w:insif desired - Added comprehensive ID uniqueness tests to prevent regression
DetectMovesnow defaults totrue(move detection is safe to use)
- Root cause:
- Footnote/endnote numbering - Fixed footnotes and endnotes displaying raw XML IDs instead of sequential display numbers
- Per ECMA-376,
w:idis a reference identifier, not the display number - Added
FootnoteNumberingTrackerclass to scan document and build XML ID → display number mapping - Footnotes/endnotes now render with sequential numbers (1, 2, 3...) based on document order
- Also fixed footnote ordering in the footnotes section to match document order
- Updated both regular and paginated rendering modes
- See
docs/ooxml_corner_cases.mdfor detailed documentation
- Per ECMA-376,
- Legal numbering continuation pattern - Fixed incorrect multi-level list numbering when items continue a flat sequence at different indentation levels
- Documents with items like 1., 2., 3. at level 0 followed by item at level 1 (with start=4) now render as "4." instead of "3.4"
- Added "continuation pattern" detection in
ListItemRetriever.csthat recognizes when a deeper-level item continues a flat list - When detected, uses level 0's format string, run properties, and paragraph properties with the current counter value
- Fixes underline appearing on continuation items when level 1's rPr has underline but level 0's doesn't
- Fixes tab/indentation spacing to use level 0's tab stops and indentation for consistency
- Updated
FormattingAssembler.csto useGetEffectiveLevel()in paragraph property stack and annotation functions - See
docs/ooxml_corner_cases.mdfor detailed documentation of this edge case
- Tab width calculation re-enabled in
WmlToHtmlConverterfor proper tab stop positioning- Previously disabled due to Azure font measurement failures; now uses estimation fallback
MetricsGetter._getTextWidth()returns character-based estimation when SkiaSharp measurement fails- Estimation formula:
charWidth = fontSize * 0.6 / 2per character (same as WASM builds) - Tab positioning now properly accounts for preceding text width
- Works in Azure, WASM, and environments without fonts installed
- Added Playwright visual tests for tab rendering verification
- Thread-safety issues in
WmlToHtmlConverterandFontFamilyHelperthat could cause corruption during concurrent document conversionsShadeCacheinWmlToHtmlConverternow usesConcurrentDictionaryfor thread-safe shade color cachingFontFamilyHelper._unknownFontsnow usesConcurrentDictionaryfor thread-safe font trackingFontFamilyHelper.KnownFamiliesnow usesLazy<T>for thread-safe lazy initialization- Added
WmlToHtmlConverter.ClearShadeCache()andFontFamilyHelper.ClearUnknownFontsCache()methods for memory management in long-running processes
- Target Framework: Changed from net45/net46/netstandard2.0 to .NET 8.0
- Open XML SDK: Upgraded from 2.8.1 to 3.2.0
- Graphics Library: Replaced System.Drawing with SkiaSharp 2.88.9
- Table Width DXA Support - Tables with DXA (twips) widths now render correctly
- Previously, only percentage widths were handled; DXA widths were ignored
- Tables with
w:tblW[@w:type="dxa"]now render with properwidth: XXptCSS - Conversion uses standard formula:
dxa / 20 = points - Addresses converter gaps #1 (Table Width Calculation)
- Borderless Table Detection - Tables without borders now get semantic markup
- Tables with
w:tblBordersset tonil/noneor missing getdata-borderless="true"attribute - Useful for identifying layout tables vs data tables
- Enables CSS-based styling for signature blocks and multi-column layouts
- Addresses converter gaps #3 (Borderless Table Detection)
- Tables with
- Document Language Attribute - HTML output now includes
langattribute for improved accessibility- New
DocumentLanguagesetting to manually override the language (default: auto-detect) <html>element now includeslangattribute (e.g.,<html lang="en-US">)- Language is auto-detected from:
w:themeFontLangin document settings- Default paragraph style's
w:rPr/w:lang - Falls back to "en-US"
- Foreign text spans get
langattribute when different from document default - Improves screen reader pronunciation and browser font selection
- Addresses converter gaps #10 (Document Language Attribute) and #11 (Foreign Text Spans)
- New
- Improved Font Fallback - Unknown fonts now get appropriate generic fallback, and CJK text gets language-specific font chains
- Unknown fonts are classified by name patterns and get proper fallback:
- Fonts with "sans" pattern →
font-family: 'FontName', sans-serif - Fonts with "mono", "code", "courier" patterns →
font-family: 'FontName', monospace - Other fonts default to serif fallback
- Fonts with "sans" pattern →
- Fixed Courier New and Lucida Console to include
monospacefallback (was missing) - CJK (Chinese, Japanese, Korean) text gets language-specific font fallback chains:
- Japanese (ja-JP):
'Noto Serif CJK JP', 'Yu Mincho', 'MS Mincho', ... - Simplified Chinese (zh-hans):
'Noto Serif CJK SC', 'Microsoft YaHei', 'SimSun', ... - Traditional Chinese (zh-hant):
'Noto Serif CJK TC', 'Microsoft JhengHei', 'PMingLiU', ... - Korean (ko):
'Noto Serif CJK KR', 'Malgun Gothic', 'Batang', ...
- Japanese (ja-JP):
- Addresses converter gaps #13 (Limited Font Fallback) and #14 (No CJK Font-Family Fallback Chain)
- Unknown fonts are classified by name patterns and get proper fallback:
- Theme Color Resolution - Document theme colors are now resolved to actual RGB values
- New
ResolveThemeColorssetting (default: true) enables theme color resolution - Reads color scheme from
theme1.xml(a:clrSchemeelement) - Supports all 12 theme colors: dk1, lt1, dk2, lt2, accent1-6, hlink, folHlink
- Applies
w:themeTint(lighten toward white) andw:themeShade(darken toward black) modifiers - Resolves
w:themeColorin run colors, paragraph shading, cell shading, and fills - Falls back to explicit color value if theme color not found
- Addresses converter gap #6 (Theme Colors Not Resolved)
- New
- @page CSS Rule - Optional CSS
@pagerule generation for print stylesheets- New
GeneratePageCsssetting (default: false) enables@pagerule generation - Reads page dimensions from
w:sectPr/w:pgSzand margins fromw:sectPr/w:pgMar - Generates CSS
@page { size: Xin Yin; margin: ... }rules - Supports US Letter, A4, and custom page sizes with proper inch conversions
- Useful for print stylesheets and PDF generation
- Addresses converter gap #1 (No Page/Document Setup CSS)
- New
- Unsupported Content Placeholders - Visual indicators for content that cannot be fully converted to HTML
- New
RenderUnsupportedContentPlaceholderssetting (default: false for backward compatibility) - Supports these unsupported content types:
- WMF/EMF images: Legacy Windows Metafile formats display
[WMF IMAGE]/[EMF IMAGE] - SVG images: Scalable Vector Graphics display
[SVG IMAGE] - Math equations (OMML): Office Math Markup displays
[MATH] - Form fields: Checkboxes, text inputs, dropdowns display
[CHECKBOX],[TEXT INPUT],[DROPDOWN] - Ruby annotations: East Asian text annotations display base text with
[RUBY]marker
- WMF/EMF images: Legacy Windows Metafile formats display
- Placeholders are styled with CSS (color-coded by type) and include:
data-content-typeattribute for the content typedata-element-nameattribute for the XML element nametitleattribute with descriptive tooltip
- New TypeScript enum
UnsupportedContentTypefor type-safe placeholder identification - See
docs/architecture/unsupported_content_placeholders.mdfor full documentation
- New
- External Annotation System (Issue #57) - Store annotations externally without modifying the DOCX file
- New
ExternalAnnotationSettype extendsOpenContractDocExportwith document binding:documentId: Unique identifier for the source documentdocumentHash: SHA256 hash for integrity validationcreatedAt,updatedAt: ISO 8601 timestampstextLabels,docLabelDefinitions: Label definitions keyed by ID
ExternalAnnotationManagerstatic class provides core functionality:ComputeDocumentHash(): SHA256 hash of document bytesCreateAnnotationSet(): Create annotation set from document (wraps OpenContractExporter)CreateAnnotation(): Create annotation from character offsetsCreateAnnotationFromSearch(): Create annotation by text search with occurrence indexFindTextOccurrences(): Find all occurrences of text in documentValidate(): Validate annotations against document (hash check + text verification)SerializeToJson()/DeserializeFromJson(): JSON serialization
ExternalAnnotationProjectorfor HTML projection:ProjectAnnotations(): Post-process HTML to wrap annotated text with styled spansConvertWithAnnotations(): Combined conversion + projection- Supports annotation labels (Above, Inline, Tooltip, None modes)
- CSS generation with customizable class prefix
- TypeScript/npm wrapper functions:
computeDocumentHash(): Get document hash for validationcreateExternalAnnotationSet(): Create annotation set from DOCXvalidateExternalAnnotations(): Validate annotations against documentconvertDocxToHtmlWithExternalAnnotations(): Convert with annotations projectedsearchTextOffsets(): Search for text occurrences in documentcreateAnnotation(),createAnnotationFromSearch(),findTextOccurrences(): Client-side helpers
- Full type definitions:
AnnotationLabel,ExternalAnnotationSet,ExternalAnnotationValidationResult, etc. - 21 unit tests covering hash computation, annotation creation, validation, serialization, and projection
- New
- OpenContracts Export Format (Issue #56) - Export documents to OpenContracts format for interoperability
- New
OpenContractExporter.Export()method for complete document export:title: Document title from core propertiescontent: Complete document text (paragraphs, tables, headers, footers, footnotes, endnotes)description: Optional document descriptionpageCount: Estimated page countpawlsFileContent: PAWLS-format page layout with token positionsdocLabels: Document-level labelslabelledText: Annotations including structural elements (sections, paragraphs, tables)relationships: Parent-child relationships between annotations
- Full text extraction ensures 100% text coverage:
- Main body paragraphs and tables
- Nested tables
- Headers and footers
- Footnotes and endnotes
- Content controls (structured document tags)
- PAWLS (Page-Aware Layout Segmentation) format for layout data:
- Page boundary information (width, height, index)
- Token positions (x, y, width, height, text)
- Supports annotation targeting by character offset
- Structural annotations automatically generated:
- Section annotations with page dimensions
- Paragraph annotations with text spans
- Table annotations with content ranges
- Parent-child relationships (section contains paragraphs)
- TypeScript API:
exportToOpenContract()function with full type definitions - WASM export:
DocumentConverter.ExportToOpenContract() - Compatible with OpenContracts ecosystem for document analysis
- New CLI tool:
docx2oc- Command-line tool for OpenContracts export- Usage:
docx2oc <input.docx> [output.json] - Default output: same filename with
.ocextension - Installable as .NET tool:
dotnet tool install --global Docx2OC
- Usage:
- New
- ReadyToRun and AOT Compilation - Performance optimizations to reduce cold-start times
- .NET library: Added
PublishReadyToRunfor pre-compiled native code during publish - WASM: Added
RunAOTCompilationfor Release builds to pre-compile IL to WebAssembly - Eliminates JIT warmup overhead (~180ms savings on first conversion in .NET)
- Provides consistent performance with no JIT variance in WASM
- .NET library: Added
- Lightweight WASM Image Handling - Images are now embedded as base64 data URIs without SkiaSharp native library
- Removed SkiaSharp native WASM dependency (~15MB+ savings in bundle size when native lib excluded)
- Images are passed through directly from DOCX using
ImageBytesproperty - Dimensions come from document markup (EMUs), not image decoding
- Browser natively decodes image formats (PNG, JPEG, GIF, etc.)
- Fallback handling: If SkiaSharp decode fails, images still work via raw bytes
- Added image handling tests for documents with embedded and hyperlinked images
- Frame Yielding for UI Responsiveness (Issue #44 Phase 1) - WASM operations now yield to the browser before heavy work begins
- All async functions in the npm wrapper (
convertDocxToHtml,compareDocuments,compareDocumentsToHtml,getRevisions,addAnnotation,addAnnotationWithTarget,getDocumentStructure) automatically yield using double-requestAnimationFramepattern - This allows React state updates (loading spinners, progress indicators) to paint before blocking WASM execution
- Transparent to consumers - no API changes required
- Gracefully skipped in non-browser environments (Node.js, SSR)
- All async functions in the npm wrapper (
- Web Worker Support for Non-blocking Operations (Issue #44 Phase 2) - Fully non-blocking WASM execution via Web Workers
- New
docxodus/workerexport provides worker-based API:import { createWorkerDocxodus } from 'docxodus/worker' - Worker API mirrors main API:
convertDocxToHtml,compareDocuments,compareDocumentsToHtml,getRevisions,getVersion - Main thread remains fully responsive during WASM execution - animations continue, user interactions work
- Zero-copy transfer of document bytes via Transferable for optimal performance
- Worker can be terminated when no longer needed
- New
- Document Metadata API for Lazy Loading (Issue #44 Phase 3) - Fast metadata extraction without full HTML rendering
- New
getDocumentMetadata()function returns document structure information:sections: Array of section metadata with page dimensions and content rangestotalParagraphs,totalTables: Document-wide content countshasFootnotes,hasEndnotes,hasComments,hasTrackedChanges: Feature detectionestimatedPageCount: Heuristic-based page count estimation
- Section metadata includes:
- Page dimensions:
pageWidthPt,pageHeightPt,marginTopPt, etc. (all values in points, 1pt = 1/72 inch) - Content area:
contentWidthPt,contentHeightPt - Header/footer heights:
headerPt,footerPt - Content tracking:
paragraphCount,tableCount,startParagraphIndex,endParagraphIndex - Header/footer presence:
hasHeader,hasFooter,hasFirstPageHeader,hasEvenPageHeader, etc.
- Page dimensions:
- Available in main API, worker API, and raw WASM:
DocumentConverter.GetDocumentMetadata() - Enables efficient lazy loading for paginated document viewing
- Security: Maximum document size limit of 100MB to prevent memory exhaustion
- Graceful handling of malformed documents and invalid header/footer references
- Known limitation: Section breaks inside tables or text boxes are not detected (see #51)
- New
- Page Range Rendering for Virtual Scrolling (Issue #31 Phase 4) - Render specific page ranges for lazy loading
- New
RenderPageRange()method inWmlToHtmlConverterrenders only specified pages - Page-to-block mapping uses heuristic-based estimation (paragraphs and tables per page)
- HTML output includes pagination metadata via data attributes:
data-start-page,data-end-page: Requested page rangedata-total-pages: Total estimated pages in documentdata-start-block,data-end-block: Block index range for rendered contentdata-block-index: Per-element block indices for tracking
- WASM exports:
DocumentConverter.RenderPageRange(),DocumentConverter.RenderPageRangeFull() - TypeScript wrapper:
renderPageRange()with full options support - Worker proxy support:
WorkerDocxodus.renderPageRange()for non-blocking execution - React components for virtual scrolling:
useVirtualPaginationhook: Manages viewport-aware page loading with IntersectionObserverVirtualPaginatedDocumentcomponent: Auto-renders visible pages plus configurable buffer
- All existing converter options supported (tracked changes, comments, headers/footers, etc.)
- Graceful handling of out-of-bounds page requests (internally clamped to valid range)
- New
- Custom Annotations - Full support for adding, removing, and rendering custom annotations on DOCX documents
AnnotationManagerclass for programmatic annotation CRUD operations:AddAnnotation(): Add annotation by text search or paragraph rangeRemoveAnnotation(): Remove annotation by IDGetAnnotations(): Retrieve all annotations from a documentGetAnnotation(): Get a specific annotation by IDHasAnnotations(): Check if document has any annotations
DocumentAnnotationclass with properties:Id: Unique annotation identifierLabelId: Category/type identifier for groupingLabel: Human-readable label textColor: Highlight color in hex format (e.g., "#FFEB3B")Author: Optional author nameCreated: Optional creation timestampMetadata: Custom key-value pairs
AnnotationRangeclass for specifying annotation targets:FromSearch(text, occurrence): Find text by searchFromParagraphs(start, end): Span paragraph indices
- Document Structure API for element-based annotation targeting:
DocumentStructureAnalyzer.Analyze(): Returns navigable tree of document elementsDocumentElementclass with path-based IDs (e.g.,doc/p-0,doc/tbl-0/tr-1/tc-2)- Supported element types:
Document,Paragraph,Run,Table,TableRow,TableCell,TableColumn,Hyperlink,Image TableColumnInfofor virtual column elements (columns aren't real OOXML elements)
AnnotationTargetclass with flexible targeting modes:Element(elementId): Target by element ID from structure analysisParagraph(index),ParagraphRange(start, end): Target by paragraph indexRun(paragraphIndex, runIndex): Target specific runTable(index),TableRow(tableIndex, rowIndex): Target tables/rowsTableCell(tableIndex, rowIndex, cellIndex): Target specific cellTableColumn(tableIndex, columnIndex): Metadata-only column annotationTextSearch(text, occurrence): Search text globallySearchInElement(elementId, text, occurrence): Search within specific element
- WASM methods:
GetDocumentStructure(),AddAnnotationWithTarget() - TypeScript helper functions:
findElementById(),findElementsByType(),getParagraphs(),getTables(),getTableColumns() - TypeScript targeting factories:
targetElement(),targetParagraph(),targetTableCell(), etc. - React
useDocumentStructurehook with structure navigation helpers - Annotations stored as Custom XML Part in DOCX (non-destructive)
- Bookmark-based text range marking for precise positioning
- HTML rendering with configurable label modes:
AnnotationLabelMode.Above: Floating label above highlightAnnotationLabelMode.Inline: Label at start of highlightAnnotationLabelMode.Tooltip: Label shown on hoverAnnotationLabelMode.None: Highlight only, no label
- New settings in
WmlToHtmlConverterSettings:RenderAnnotations: Enable/disable annotation renderingAnnotationLabelMode: Select label display modeAnnotationCssClassPrefix: Customize CSS class names (default: "annot-")IncludeAnnotationMetadata: Include metadata in HTML data attributes
- WASM/npm support:
getAnnotations(),addAnnotation(),removeAnnotation(),hasAnnotations()functionsAnnotation,AddAnnotationRequest,AddAnnotationResponse,RemoveAnnotationResponsetypesAnnotationLabelModeenumConversionOptionsextended with annotation rendering options
- React support:
useAnnotationshook for annotation state managementAnnotatedDocumentcomponent with click/hover event handlinguseDocxodushook extended with annotation methods
- 20 .NET unit tests and 21 Playwright browser tests for full coverage (including 11 for element-based targeting)
- Comment Rendering in HTML Converter - Full support for rendering Word document comments in HTML output
CommentRenderModeenum with three rendering modes:EndnoteStyle(default): Comments rendered at end of document with bidirectional anchor linksInline: Comments rendered as tooltips withtitleanddata-commentattributesMargin: Comments positioned in a flexbox-based margin column alongside content, with author/date headers and back-reference links
- New settings in
WmlToHtmlConverterSettings:RenderComments: Enable/disable comment renderingCommentRenderMode: Select rendering modeCommentCssClassPrefix: Customize CSS class names (default: "comment-")IncludeCommentMetadata: Include author/date in HTML output
- Comment highlighting with configurable CSS classes
- Full comment metadata support (author, date, initials)
- Margin mode includes print-friendly CSS media queries
- WASM/npm support via
commentRenderModeparameter and TypeScriptCommentRenderModeenum
- WebAssembly NPM Package (
docxodus) - Browser-based document comparison and HTML conversionwasm/DocxodusWasm/- .NET 8 WASM project with JSExport methodsnpm/- TypeScript wrapper with React hooks- Full document comparison (redlining) support in the browser
- DOCX to HTML conversion
- React hooks:
useDocxodus,useConversion,useComparison - Build script:
scripts/build-wasm.sh
- Native Move Markup in WmlComparer - Produces Word-native move tracking markup (
w:moveFrom/w:moveTo)- Compared documents now contain proper OpenXML move elements, not just
w:del/w:ins - Move pairs linked via
w:nameattribute for Word compatibility - Range markers (
w:moveFromRangeStart/w:moveFromRangeEnd,w:moveToRangeStart/w:moveToRangeEnd) properly paired - Microsoft Word shows moves in "Track Changes" panel as relocated content
- New
Movedvalue inWmlComparerRevisionTypeenum - New properties on
WmlComparerRevision:MoveGroupId(links source/destination),IsMoveSource(true=from, false=to) - New settings in
WmlComparerSettings:DetectMoves: Enable/disable move detection (default: true)MoveSimilarityThreshold: Jaccard similarity threshold 0.0-1.0 (default: 0.8)MoveMinimumWordCount: Minimum words to consider for move (default: 3)
- Uses word-level Jaccard similarity for accurate matching
- Respects
CaseInsensitivesetting for similarity comparison - Full WASM/npm support with new TypeScript helpers:
RevisionType.Movedenum valueisMove(),isMoveSource(),isMoveDestination()type guardsfindMovePair()function to find linked move revisionsmoveGroupIdandisMoveSourceproperties onRevisioninterface
- Compared documents now contain proper OpenXML move elements, not just
- Format Change Detection in WmlComparer - Detects and tracks formatting-only changes (
w:rPrChange)- When text content is identical but formatting changes (bold, italic, font size, etc.), produces native Word format change markup
- Compared documents now contain
w:rPrChangeelements that Microsoft Word recognizes in Track Changes - New
FormatChangedvalue inWmlComparerRevisionTypeenum - New
FormatChangeproperty onWmlComparerRevisionwith:OldProperties: Dictionary of original formatting propertiesNewProperties: Dictionary of new formatting propertiesChangedPropertyNames: List of what changed (e.g., "bold", "italic", "fontSize")
- New setting in
WmlComparerSettings:DetectFormatChanges: Enable/disable format change detection (default: true)
- Full WASM/npm support with new TypeScript helpers:
RevisionType.FormatChangedenum valueisFormatChange()type guardFormatChangeDetailsinterface witholdProperties,newProperties,changedPropertyNamesformatChangeproperty onRevisioninterface
- Improved Revision API - Better TypeScript support for the
getRevisions()APIRevisionTypeenum withInserted,Deleted, andMovedvalues for type-safe comparisonsisInsertion(),isDeletion(),isMove(),isMoveSource(),isMoveDestination()helper functionsfindMovePair()function to find the matching revision for a move- Comprehensive JSDoc documentation on the
Revisioninterface - All types are properly exported from the package
- Paginated Headers and Footers - Headers/footers now render correctly with pagination enabled
- When both
RenderHeadersAndFootersandRenderPagination=Paginatedare enabled, headers and footers appear on each page - Per-section header/footer support with section index tracking
- First page headers/footers supported (when
w:titlePgis set in document) - Even page headers/footers supported for different odd/even page layouts
- Headers/footers rendered into hidden registry for client-side cloning per-page
- New data attributes:
data-header-height,data-footer-heighton section elements - TypeScript
PageDimensionsinterface extended withheaderHeightandfooterHeight - CSS classes
.page-headerand.page-footerfor positioning within page boxes - Automatic hiding of system page number when document has footer content
- See
docs/architecture/paginated_headers_footers.mdfor full architecture details
- When both
- Per-page Footnote Rendering - Footnotes now appear at the bottom of each page where they are referenced
- When
RenderFootnotesAndEndnotes=truewithRenderPagination=Paginated, footnotes are distributed per-page - Footnote registry stores footnotes in a hidden container for client-side distribution
data-footnote-idattributes added to footnote references for tracking- Single-pass, forward-only pagination algorithm (lazy-loading compatible)
- Pagination engine measures footnote space and includes it in page layout calculations
- Footnotes render with separator line (
<hr>) above them - Footnote continuation: Long footnotes that don't fit on a page are split at paragraph boundaries and continue on subsequent pages (matching Word/Office behavior)
- Dynamic footnote area expansion: Footnote area can expand upward into body content space (up to 60% of page height) to fit more footnote content before splitting, reducing wasted space
- Endnotes remain at document end (not per-page) - traditional behavior preserved
- New TypeScript methods:
parseFootnoteRegistry(),extractFootnoteRefs(),measureFootnotesHeight(),addPageFootnotes(),splitFootnoteToFit(),measureContinuationHeight() - New TypeScript interfaces:
FootnoteContinuation,PartialFootnote - New TypeScript constants:
MAX_FOOTNOTE_AREA_RATIO(0.6),MIN_BODY_CONTENT_HEIGHT(72pt) - New CSS classes:
.page-footnotes,.footnote-item,.footnote-number,.footnote-content,.footnote-continuation
- When
SkiaSharpHelpers.cs- Color utilities for SkiaSharp compatibilityGetPackage()extension method inPtOpenXmlUtil.csfor SDK 3.x Package accessSkiaSharp.NativeAssets.Linux.NoDependenciespackage for Linux runtime support
-
React hooks loading state not rendering before WASM blocks (Issue #45) - Fixed
isConverting/isComparing/isLoadingstates in React hooks not painting before WASM execution blocks the main thread. AddedrequestAnimationFrameyielding after state updates in:useConversion:convert()functionuseComparison:compare()andcompareToHtml()functionsuseAnnotations:reload(),add(), andremove()functionsuseDocumentStructure:reload()function
-
Header/footer positioning in paginated mode - Fixed headers and footers overlapping with body content. Headers now properly constrain to the top margin area (
height: marginTop) and footers constrain to the bottom margin area (height: marginBottom). Uses flexbox layout for proper content alignment within constrained areas. -
DocumentBuilder relationship copying - Fixed bug where relationship IDs from source documents could incorrectly match existing IDs in target header/footer parts when using InsertId functionality. This caused validation errors like "The relationship 'rIdX' referenced by attribute 'r:embed' does not exist."
- Removed flawed early-return optimization in
CopyRelatedImage()that skipped processing when target part had matching relationship ID - Fixed diagram relationship handling (
R.dm,R.lo,R.qs,R.csattributes) to properly copy parts from source documents - Fixed chart and user shape relationship handling
- Fixed OLE object relationship handling
- Fixed external relationship attribute update to use correct attribute name parameter
- Removed flawed early-return optimization in
-
SpreadsheetWriter date handling - Fixed date cells being written with invalid ISO 8601 string format. Dates are now properly converted to Excel serial date numbers (days since December 30, 1899) which is required for transitional OOXML format.
-
WmlComparer null Unid handling - Fixed null reference exceptions when comparing documents with elements lacking Unid attributes.
-
WmlComparer footnote/endnote comparison (6 tests: WC-1660, WC-1670, WC-1710, WC-1720, WC-1750, WC-1760) - Fixed
AssignUnidToAllElementsto assign Unid to footnote/endnote elements themselves, enabling proper reconstruction of multi-paragraph footnotes/endnotes byCoalesceRecurse. -
WmlComparer table row comparison (1 test: WC-1500) - Added LCS-based row matching (
ApplyLcsToTableRows) for large tables (7+ rows) when content differs significantly, preventing cascading false differences from insertions/deletions in the middle of tables. -
WASM CDN loading CORS issue - Fixed cross-origin loading failures when WASM files are served from CDNs (jsDelivr, unpkg). The .NET WASM runtime uses
credentials:"same-origin"for fetch requests, which conflicts with CDN'sAccess-Control-Allow-Origin: *wildcard header. Build script now patchesdotnet.jsto usecredentials:"omit"for CDN compatibility. -
Vite bundler compatibility - Added
@vite-ignorecomment to dynamic import innpm/src/index.tsto prevent Vite from trying to analyze/resolve the WASM loader path during development builds. -
Pagination content overflow - Fixed content overflowing page boundaries in the paginated view. The issue was caused by applying CSS transform scale to the content area while using inconsistent coordinate systems for positioning. The fix applies the scale transform to the entire page box instead, ensuring proper clipping and consistent scaling of all page elements.
-
WmlComparer legal numbering preservation (Issue #1634) - Fixed comparison losing legal numbering (
w:isLgl) when comparing documents with different numbering styles. The comparer now properly merges numbering definitions from the revised document into the result:- Copies
abstractNumandnumelements from revised document when missing in original - Reuses existing definitions when content matches (regardless of ID)
- Remaps IDs when conflicts occur to avoid duplicates
- Copies
-
WmlToHtmlConverter null rPr crash - Fixed
InvalidOperationExceptioncrash inDefineRunStyleandGetLangAttributewhen converting runs withoutw:rPrelements. Changed.First()to.FirstOrDefault()with null checks to handle runs that have no explicit run properties gracefully.
- Replaced
FontPartType/ImagePartTypewithPartTypeInfopattern for SDK 3.x compatibility - Replaced
.Close()calls withDispose()pattern - Migrated all color handling from
System.Drawing.ColortoSKColor - Migrated font handling from
FontFamily/FontStyletoSKFontManager/SKTypeface - Migrated image handling from
Bitmap/ImageFormattoSKBitmap/SKEncodedImageFormat
- Updated
docs/architecture/wml_to_html_converter_gaps.mdwith comprehensive gap analysis including pagination mode limitations, DrawingML text handling, and prioritized fix recommendations
- 1051 passed, 0 failed, 1 skipped out of 1052 tests (~99.9% pass rate)
- Header/footer and footnote pagination changes tested via manual integration testing