fix: keep the highlighter pen a docx run carries

Word's highlighter is w:highlight, a palette name; only w:shd's hex fill was
read, so 121 yellow runs of a fixture imported unmarked. 'none' maps to the
same 'auto' sentinel an empty w:shd gives, so it still cancels a style's pen.
This commit is contained in:
Steffen Becker committed 2026-08-08 08:56:38 +00:00
1 parent 361b14dc17
commit 993e64e933
2 files changed
+33 -1

No files matched your search

+11 -1
View File
@@ -27,7 +27,7 @@ export type RunProps = {
spacingTwip?: number; // w:spacing (character spacing, twentieths of a point)
font?: string; // explicit w:rFonts w:ascii/hAnsi
fontTheme?: 'minor' | 'major'; // w:rFonts w:asciiTheme/hAnsiTheme → theme1.xml font
highlightFill?: string; // run-level w:shd w:fill (text highlight)
highlightFill?: string; // text highlight: w:shd w:fill, or w:highlight's palette colour
};
// A numbering level definition (numbering.xml w:lvl). bulletFont is the level's
@@ -65,6 +65,15 @@ export function toggle(el: Element): boolean {
return v == null || !(v === 'false' || v === '0' || v === 'off');
}
// The highlighter pen's fixed palette (w:highlight names a colour where w:shd gives a
// hex). 'none' maps to the same sentinel as an empty w:shd, so it cancels an inherited pen.
const HIGHLIGHT_HEX: Record<string, string> = {
black: '000000', blue: '0000FF', cyan: '00FFFF', darkBlue: '000080', darkCyan: '008080',
darkGray: '808080', darkGreen: '008000', darkMagenta: '800080', darkRed: '800000',
darkYellow: '808000', green: '00FF00', lightGray: 'C0C0C0', magenta: 'FF00FF',
red: 'FF0000', white: 'FFFFFF', yellow: 'FFFF00', none: 'auto',
};
// w:rPr element → RunProps (later layers override earlier; absent keys inherit).
export function parseRunProps(rPr: Element | null | undefined): RunProps {
const p: RunProps = {};
@@ -93,6 +102,7 @@ export function parseRunProps(rPr: Element | null | undefined): RunProps {
break;
}
case 'shd': { const f = child.getAttributeNS(W, 'fill'); if (f && f !== 'auto') p.highlightFill = f; break; }
case 'highlight': { const c = HIGHLIGHT_HEX[wVal(child) ?? '']; if (c) p.highlightFill = c; break; }
}
}
return p;
+22
View File
@@ -0,0 +1,22 @@
// Word's highlighter pen is w:highlight (a palette name), not the w:shd hex fill.
import { describe, it, expect } from 'vitest';
import { parseRunProps, mergeRunProps, W } from '../../src/lib/import/docxStyles';
const rPr = (inner: string) =>
new DOMParser().parseFromString(`<w:rPr xmlns:w="${W}">${inner}</w:rPr>`, 'application/xml').documentElement;
describe('parseRunProps highlight', () => {
it('resolves a palette name to its hex', () => {
expect(parseRunProps(rPr('<w:highlight w:val="yellow"/>')).highlightFill).toBe('FFFF00');
});
it("lets a run's w:highlight none cancel the character style's pen", () => {
const base = parseRunProps(rPr('<w:highlight w:val="green"/>'));
const merged = mergeRunProps(base, parseRunProps(rPr('<w:highlight w:val="none"/>')));
expect(merged.highlightFill).toBe('auto');
});
it('ignores an unknown name rather than inventing a colour', () => {
expect(parseRunProps(rPr('<w:highlight w:val="chartreuse"/>')).highlightFill).toBeUndefined();
});
});