@docen/docx 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/converters/docx.d.mts +95 -5
- package/dist/converters/docx.mjs +732 -82
- package/dist/converters/html.d.mts +20 -5
- package/dist/converters/html.mjs +94 -8
- package/dist/converters/markdown.d.mts +1 -1
- package/dist/converters/markdown.mjs +1 -1
- package/dist/converters/patch.d.mts +1 -1
- package/dist/converters/patch.mjs +4 -5
- package/dist/converters/prepare.d.mts +15 -3
- package/dist/converters/prepare.mjs +50 -4
- package/dist/converters/styles.d.mts +85 -0
- package/dist/converters/styles.mjs +285 -0
- package/dist/core-C9VunJ8o.d.mts +174 -0
- package/dist/core-DT9IrTzN.mjs +670 -0
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/document-BH1y4qHM.d.mts +6 -0
- package/dist/editor.d.mts +1 -1
- package/dist/editor.mjs +3 -2
- package/dist/extensions/blockquote.d.mts +23 -0
- package/dist/extensions/blockquote.mjs +31 -0
- package/dist/extensions/code-block.d.mts +19 -0
- package/dist/extensions/code-block.mjs +23 -0
- package/dist/extensions/column-break.d.mts +31 -0
- package/dist/extensions/column-break.mjs +2 -0
- package/dist/extensions/details.d.mts +19 -0
- package/dist/extensions/details.mjs +18 -0
- package/dist/extensions/document.d.mts +2 -0
- package/dist/extensions/document.mjs +50 -0
- package/dist/extensions/extensions.d.mts +5 -5
- package/dist/extensions/extensions.mjs +4 -3
- package/dist/extensions/formatting-marks.d.mts +38 -0
- package/dist/extensions/formatting-marks.mjs +2 -0
- package/dist/extensions/heading.mjs +24 -3
- package/dist/extensions/image.d.mts +2 -2
- package/dist/extensions/image.mjs +244 -49
- package/dist/extensions/index.d.mts +4 -2
- package/dist/extensions/index.mjs +4 -2
- package/dist/extensions/mention.d.mts +25 -0
- package/dist/extensions/mention.mjs +44 -0
- package/dist/extensions/ordered-list.d.mts +25 -0
- package/dist/extensions/ordered-list.mjs +41 -0
- package/dist/extensions/page-break.d.mts +2 -0
- package/dist/extensions/page-break.mjs +2 -0
- package/dist/extensions/paragraph.d.mts +1 -1
- package/dist/extensions/paragraph.mjs +39 -4
- package/dist/extensions/passthrough.d.mts +2 -0
- package/dist/extensions/passthrough.mjs +2 -0
- package/dist/extensions/section-break.d.mts +44 -0
- package/dist/extensions/section-break.mjs +2 -0
- package/dist/extensions/table.mjs +29 -3
- package/dist/extensions/task-item.d.mts +27 -0
- package/dist/extensions/task-item.mjs +37 -0
- package/dist/extensions/text-style.mjs +20 -3
- package/dist/extensions/tiptap.d.mts +2 -2
- package/dist/extensions/tiptap.mjs +1 -3
- package/dist/extensions/types.d.mts +7 -5
- package/dist/extensions/utils.d.mts +2 -49
- package/dist/extensions/utils.mjs +222 -12
- package/dist/extensions/wpg-group.d.mts +2 -0
- package/dist/extensions/wpg-group.mjs +2 -0
- package/dist/extensions/wps-shape.d.mts +2 -0
- package/dist/extensions/wps-shape.mjs +2 -0
- package/dist/index.d.mts +127 -18
- package/dist/index.mjs +7 -2
- package/dist/paragraph-D8mpHo_o.d.mts +8 -0
- package/dist/{tiptap-TErPjuNJ.d.mts → tiptap-pZsNPsvV.d.mts} +1 -3
- package/dist/utils-D87vukzp.d.mts +128 -0
- package/package.json +40 -40
- package/dist/core-CFIQVRfx.mjs +0 -88
- package/dist/core-omBKMRtl.d.mts +0 -34
- package/dist/image-Ge1y6uam.d.mts +0 -47
- package/dist/paragraph-fhEXtAN2.d.mts +0 -16
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { r as Extension } from "../core-C9VunJ8o.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/extensions/section-break.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* SectionBreak — command extension that marks a paragraph as a section boundary.
|
|
6
|
+
*
|
|
7
|
+
* OOXML sections (sectPr) attach to a section's LAST paragraph's pPr, NOT a
|
|
8
|
+
* standalone node. So this extension provides only the `setSectionBreak`
|
|
9
|
+
* command (stamps sectionProperties on the current paragraph); the paragraph
|
|
10
|
+
* extension carries the sectionProperties/sectionHeaders/sectionFooters attrs,
|
|
11
|
+
* and DocxManager splits/merges sections in compile/resolve by reading them off
|
|
12
|
+
* the paragraph.
|
|
13
|
+
*
|
|
14
|
+
* The final section's sectPr rides on doc.attrs.sectionProperties (it lives at
|
|
15
|
+
* <w:body>'s end in OOXML). Single-section documents have no section-carrying
|
|
16
|
+
* paragraph at all.
|
|
17
|
+
*
|
|
18
|
+
* Next Page semantics: `setSectionBreak` stamps sectionProperties on the current
|
|
19
|
+
* paragraph (making it the section's last paragraph) AND inserts a fresh empty
|
|
20
|
+
* paragraph after it (the next section's first paragraph), then moves the
|
|
21
|
+
* selection into that new paragraph. The page-plugin's `forcesPageBreakAfter`
|
|
22
|
+
* treats a sectionProperties-bearing paragraph as a page break, so repaginate
|
|
23
|
+
* pushes the new paragraph onto the next page — and the caret follows. This
|
|
24
|
+
* mirrors Word's "Section Break (Next Page)".
|
|
25
|
+
*
|
|
26
|
+
* Split hygiene: pressing Enter inside a section-carrying paragraph must NOT
|
|
27
|
+
* split it. A split would place the new paragraph past the section boundary —
|
|
28
|
+
* forcesPageBreakAfter then pushes it onto the next page (next section), and
|
|
29
|
+
* splitBlock would copy sectionProperties onto it (a second break mark). Word
|
|
30
|
+
* instead inserts a fresh paragraph BEFORE the section's last paragraph, so the
|
|
31
|
+
* new paragraph stays in this section and the stamped paragraph remains last.
|
|
32
|
+
* The Enter shortcut does exactly that; non-section paragraphs fall through to
|
|
33
|
+
* the default Enter unchanged.
|
|
34
|
+
*/
|
|
35
|
+
declare const SectionBreak: Extension<any, any>;
|
|
36
|
+
declare module "@tiptap/core" {
|
|
37
|
+
interface Commands<ReturnType> {
|
|
38
|
+
sectionBreak: {
|
|
39
|
+
/** Insert a Next Page section break at the current paragraph. */setSectionBreak: () => ReturnType;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { SectionBreak };
|
|
@@ -85,12 +85,15 @@ const Table = Table$1.extend({
|
|
|
85
85
|
const w = a.width;
|
|
86
86
|
const numSize = typeof w.size === "string" ? parseFloat(w.size) : w.size;
|
|
87
87
|
if (w.type === "pct") {
|
|
88
|
-
if (
|
|
89
|
-
|
|
88
|
+
if (typeof w.size === "string" && w.size.includes("%")) styles.push(`width:${w.size}`);
|
|
89
|
+
else if (!Number.isNaN(numSize)) styles.push(`width:${numSize / 50}%`);
|
|
90
|
+
} else if (w.type === "auto") styles.push("width:100%");
|
|
91
|
+
else if (numSize != null) {
|
|
90
92
|
const css = twipToCss(numSize);
|
|
91
93
|
if (css) styles.push(`width:${css}`);
|
|
92
94
|
}
|
|
93
|
-
}
|
|
95
|
+
} else styles.push("width:100%");
|
|
96
|
+
styles.push("max-width:100%");
|
|
94
97
|
if (a.indent && typeof a.indent === "object") {
|
|
95
98
|
const ind = a.indent;
|
|
96
99
|
if (ind.size != null) {
|
|
@@ -120,7 +123,30 @@ const Table = Table$1.extend({
|
|
|
120
123
|
if (css) styles.push(`border-${side}:${css}`);
|
|
121
124
|
}
|
|
122
125
|
}
|
|
126
|
+
const firstRow = node.firstChild;
|
|
127
|
+
const cellPx = [];
|
|
128
|
+
if (firstRow) for (let i = 0; i < firstRow.childCount; i++) {
|
|
129
|
+
const cw = firstRow.child(i).attrs.colwidth;
|
|
130
|
+
if (Array.isArray(cw) && cw.length) for (const w of cw) cellPx.push(w || 0);
|
|
131
|
+
else cellPx.push(0);
|
|
132
|
+
}
|
|
133
|
+
const hasCellWidths = cellPx.some((w) => w > 0);
|
|
134
|
+
const tblGridPx = (a.columnWidths ?? []).map((w) => Math.round((w || 0) / 15));
|
|
135
|
+
const gridPx = tblGridPx.some((w) => w > 0) ? tblGridPx : hasCellWidths ? cellPx : tblGridPx;
|
|
123
136
|
if (styles.length > 0) attrs.style = styles.join(";");
|
|
137
|
+
if (gridPx.some((w) => w > 0)) {
|
|
138
|
+
const gridTotal = gridPx.reduce((sum, w) => sum + w, 0);
|
|
139
|
+
return [
|
|
140
|
+
"table",
|
|
141
|
+
attrs,
|
|
142
|
+
[
|
|
143
|
+
"colgroup",
|
|
144
|
+
{},
|
|
145
|
+
...gridPx.map((w) => ["col", { style: `width:${gridTotal > 0 ? (w / gridTotal * 100).toFixed(2) : 0}%` }])
|
|
146
|
+
],
|
|
147
|
+
["tbody", 0]
|
|
148
|
+
];
|
|
149
|
+
}
|
|
124
150
|
return [
|
|
125
151
|
"table",
|
|
126
152
|
attrs,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { A as TaskItem } from "../tiptap-pZsNPsvV.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/extensions/task-item.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* TaskItem extension — owns the DOCX expression of a task-list checkbox.
|
|
6
|
+
*
|
|
7
|
+
* DOCX has no native task list, but a clickable checkbox is reversible via an
|
|
8
|
+
* inline content-control SDT (w14:checkbox). Each task paragraph carries a
|
|
9
|
+
* leading checkbox SDT tagged "docen-task" so resolve can tell task items apart
|
|
10
|
+
* from ordinary paragraphs that happen to contain an SDT. The checked state
|
|
11
|
+
* round-trips through the SDT; DocxManager injects/strips the SDT at the
|
|
12
|
+
* paragraph boundary and rebuilds the taskList/taskItem tree.
|
|
13
|
+
*/
|
|
14
|
+
/** SDT tag marking our task-item checkbox content control. */
|
|
15
|
+
declare const TASK_CHECKBOX_TAG = "docen-task";
|
|
16
|
+
/**
|
|
17
|
+
* Inline checkbox SDT (w14:checkbox) for a task item, as a ParagraphChild.
|
|
18
|
+
* Tagged so resolve can distinguish task items from ordinary SDT-bearing
|
|
19
|
+
* paragraphs.
|
|
20
|
+
*/
|
|
21
|
+
declare function createTaskCheckbox(checked: boolean): Record<string, unknown>;
|
|
22
|
+
/** True if an inline ParagraphChild is our task checkbox SDT. */
|
|
23
|
+
declare function isTaskCheckbox(child: unknown): boolean;
|
|
24
|
+
/** Read the checked state of a task checkbox SDT child (false if not one). */
|
|
25
|
+
declare function readCheckboxState(child: unknown): boolean;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { TASK_CHECKBOX_TAG, TaskItem, createTaskCheckbox, isTaskCheckbox, readCheckboxState };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { TaskItem } from "./tiptap.mjs";
|
|
2
|
+
//#region src/extensions/task-item.ts
|
|
3
|
+
/**
|
|
4
|
+
* TaskItem extension — owns the DOCX expression of a task-list checkbox.
|
|
5
|
+
*
|
|
6
|
+
* DOCX has no native task list, but a clickable checkbox is reversible via an
|
|
7
|
+
* inline content-control SDT (w14:checkbox). Each task paragraph carries a
|
|
8
|
+
* leading checkbox SDT tagged "docen-task" so resolve can tell task items apart
|
|
9
|
+
* from ordinary paragraphs that happen to contain an SDT. The checked state
|
|
10
|
+
* round-trips through the SDT; DocxManager injects/strips the SDT at the
|
|
11
|
+
* paragraph boundary and rebuilds the taskList/taskItem tree.
|
|
12
|
+
*/
|
|
13
|
+
/** SDT tag marking our task-item checkbox content control. */
|
|
14
|
+
const TASK_CHECKBOX_TAG = "docen-task";
|
|
15
|
+
/**
|
|
16
|
+
* Inline checkbox SDT (w14:checkbox) for a task item, as a ParagraphChild.
|
|
17
|
+
* Tagged so resolve can distinguish task items from ordinary SDT-bearing
|
|
18
|
+
* paragraphs.
|
|
19
|
+
*/
|
|
20
|
+
function createTaskCheckbox(checked) {
|
|
21
|
+
return { sdt: { properties: {
|
|
22
|
+
tag: TASK_CHECKBOX_TAG,
|
|
23
|
+
checkbox: { checked }
|
|
24
|
+
} } };
|
|
25
|
+
}
|
|
26
|
+
/** True if an inline ParagraphChild is our task checkbox SDT. */
|
|
27
|
+
function isTaskCheckbox(child) {
|
|
28
|
+
if (typeof child !== "object" || child === null || !("sdt" in child)) return false;
|
|
29
|
+
return (child.sdt?.properties)?.tag === TASK_CHECKBOX_TAG;
|
|
30
|
+
}
|
|
31
|
+
/** Read the checked state of a task checkbox SDT child (false if not one). */
|
|
32
|
+
function readCheckboxState(child) {
|
|
33
|
+
if (!isTaskCheckbox(child)) return false;
|
|
34
|
+
return Boolean(child.sdt?.properties?.checkbox?.checked);
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { TASK_CHECKBOX_TAG, TaskItem, createTaskCheckbox, isTaskCheckbox, readCheckboxState };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TextStyle as TextStyle$1 } from "./tiptap.mjs";
|
|
2
|
-
import { characterSpacingFromCss, characterSpacingToCss, normalizeColorToHex,
|
|
2
|
+
import { characterSpacingFromCss, characterSpacingToCss, normalizeColorToHex, resolveFontFamilyCss, shadingFromCss, shadingToCss, sizeFromCss, sizeToCss } from "./utils.mjs";
|
|
3
3
|
//#region src/extensions/text-style.ts
|
|
4
4
|
/**
|
|
5
5
|
* TextStyle mark with office-open attrs.
|
|
@@ -27,6 +27,10 @@ function renderDocx(attrs) {
|
|
|
27
27
|
for (const [key, value] of Object.entries(attrs)) {
|
|
28
28
|
if (SKIP_KEYS.has(key)) continue;
|
|
29
29
|
if (value === null || value === void 0) continue;
|
|
30
|
+
if (key === "styleId") {
|
|
31
|
+
opts.style = value;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
30
34
|
opts[key] = value;
|
|
31
35
|
}
|
|
32
36
|
return opts;
|
|
@@ -34,6 +38,7 @@ function renderDocx(attrs) {
|
|
|
34
38
|
function parseDocx(opts) {
|
|
35
39
|
const resolved = typeof opts === "string" ? { text: opts } : opts;
|
|
36
40
|
const attrs = {};
|
|
41
|
+
if (resolved.style) attrs.styleId = resolved.style;
|
|
37
42
|
for (const [key, value] of Object.entries(resolved)) {
|
|
38
43
|
if (SKIP_KEYS.has(key)) continue;
|
|
39
44
|
attrs[key] = value ?? null;
|
|
@@ -49,10 +54,22 @@ const TextStyle = TextStyle$1.extend({
|
|
|
49
54
|
addAttributes() {
|
|
50
55
|
return {
|
|
51
56
|
...this.parent?.(),
|
|
57
|
+
styleId: {
|
|
58
|
+
default: null,
|
|
59
|
+
parseHTML: (element) => {
|
|
60
|
+
const m = (element.getAttribute("class") || "").match(/(?:^|\s)docx-char-(\S+)/);
|
|
61
|
+
return m ? m[1] : null;
|
|
62
|
+
},
|
|
63
|
+
renderHTML: (attributes) => {
|
|
64
|
+
const id = attributes.styleId;
|
|
65
|
+
return id ? { class: `docx-char-${id}` } : {};
|
|
66
|
+
}
|
|
67
|
+
},
|
|
52
68
|
color: {
|
|
53
69
|
default: null,
|
|
54
70
|
parseHTML: (element) => normalizeColorToHex(element.style.color || void 0) ?? null,
|
|
55
71
|
renderHTML: (attributes) => {
|
|
72
|
+
if (attributes.color === "auto") return {};
|
|
56
73
|
const hex = normalizeColorToHex(attributes.color);
|
|
57
74
|
return hex ? { style: `color:${hex}` } : {};
|
|
58
75
|
}
|
|
@@ -69,7 +86,7 @@ const TextStyle = TextStyle$1.extend({
|
|
|
69
86
|
default: null,
|
|
70
87
|
parseHTML: (element) => element.style.fontFamily || null,
|
|
71
88
|
renderHTML: (attributes) => {
|
|
72
|
-
const name =
|
|
89
|
+
const name = resolveFontFamilyCss(attributes.font);
|
|
73
90
|
return name ? { style: `font-family:${name}` } : {};
|
|
74
91
|
}
|
|
75
92
|
},
|
|
@@ -91,7 +108,7 @@ const TextStyle = TextStyle$1.extend({
|
|
|
91
108
|
parseHTML: (element) => shadingFromCss(element.style.backgroundColor),
|
|
92
109
|
renderHTML: (attributes) => {
|
|
93
110
|
const css = shadingToCss(attributes.shading);
|
|
94
|
-
return css ? { style: `background-color:${css}` } : {};
|
|
111
|
+
return css ? { style: `background-color:${css};color:contrast-color(${css})` } : {};
|
|
95
112
|
}
|
|
96
113
|
},
|
|
97
114
|
underline: attrNative(),
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { Blockquote, Bold, BulletList, Code, CodeBlockLowlight, Details, DetailsContent, DetailsSummary, Document,
|
|
1
|
+
import { A as TaskItem, C as Strike, D as TableCell, E as Table, F as Underline, M as Text, N as TextAlign, O as TableHeader, P as TextStyle, S as Paragraph, T as Superscript, _ as Link, a as CodeBlockLowlight, b as Mention, c as DetailsSummary, d as HardBreak, f as Heading, g as Italic, h as Image, i as Code, j as TaskList, k as TableRow, l as Document, m as HorizontalRule, n as Bold, o as Details, p as Highlight, r as BulletList, s as DetailsContent, t as Blockquote, u as Emoji, v as ListItem, w as Subscript, x as OrderedList, y as Mathematics } from "../tiptap-pZsNPsvV.mjs";
|
|
2
|
+
export { Blockquote, Bold, BulletList, Code, CodeBlockLowlight, Details, DetailsContent, DetailsSummary, Document, Emoji, HardBreak, Heading, Highlight, HorizontalRule, Image, Italic, Link, ListItem, Mathematics, Mention, OrderedList, Paragraph, Strike, Subscript, Superscript, Table, TableCell, TableHeader, TableRow, TaskItem, TaskList, Text, TextAlign, TextStyle, Underline };
|
|
@@ -28,6 +28,4 @@ import { Subscript } from "@tiptap/extension-subscript";
|
|
|
28
28
|
import { Superscript } from "@tiptap/extension-superscript";
|
|
29
29
|
import { TextStyle } from "@tiptap/extension-text-style";
|
|
30
30
|
import { TextAlign } from "@tiptap/extension-text-align";
|
|
31
|
-
|
|
32
|
-
import { ListKeymap } from "@tiptap/extension-list";
|
|
33
|
-
export { Blockquote, Bold, BulletList, Code, CodeBlockLowlight, Details, DetailsContent, DetailsSummary, Document, Dropcursor, Emoji, Gapcursor, HardBreak, Heading, Highlight, HorizontalRule, Image, Italic, Link, ListItem, ListKeymap, Mathematics, Mention, OrderedList, Paragraph, Strike, Subscript, Superscript, Table, TableCell, TableHeader, TableRow, TaskItem, TaskList, Text, TextAlign, TextStyle, TrailingNode, Underline, UndoRedo };
|
|
31
|
+
export { Blockquote, Bold, BulletList, Code, CodeBlockLowlight, Details, DetailsContent, DetailsSummary, Document, Emoji, HardBreak, Heading, Highlight, HorizontalRule, Image, Italic, Link, ListItem, Mathematics, Mention, OrderedList, Paragraph, Strike, Subscript, Superscript, Table, TableCell, TableHeader, TableRow, TaskItem, TaskList, Text, TextAlign, TextStyle, Underline };
|
|
@@ -3,19 +3,21 @@ import { RunOptions } from "@office-open/docx";
|
|
|
3
3
|
|
|
4
4
|
//#region src/extensions/types.d.ts
|
|
5
5
|
declare module "@tiptap/core" {
|
|
6
|
-
interface NodeConfig<Options, Storage> {
|
|
6
|
+
interface NodeConfig<Options = any, Storage = any> {
|
|
7
7
|
/**
|
|
8
|
-
* DOCX serialization: Tiptap JSON node → DOCX opts
|
|
9
|
-
*
|
|
8
|
+
* DOCX serialization: Tiptap JSON node → DOCX opts, or null when the node
|
|
9
|
+
* cannot be serialized (e.g. an image with no embedded data — DocxManager
|
|
10
|
+
* then drops it). Each node extension defines this to convert its attrs to
|
|
11
|
+
* DOCX properties.
|
|
10
12
|
*/
|
|
11
|
-
renderDocx?: (node: JSONContent) => Record<string, unknown
|
|
13
|
+
renderDocx?: (node: JSONContent) => Record<string, unknown> | null;
|
|
12
14
|
/**
|
|
13
15
|
* DOCX deserialization: DOCX opts → Tiptap JSON attrs.
|
|
14
16
|
* Each node extension defines this to convert DOCX properties back to attrs.
|
|
15
17
|
*/
|
|
16
18
|
parseDocx?: (opts: Record<string, unknown>) => Record<string, unknown>;
|
|
17
19
|
}
|
|
18
|
-
interface MarkConfig<Options, Storage> {
|
|
20
|
+
interface MarkConfig<Options = any, Storage = any> {
|
|
19
21
|
/**
|
|
20
22
|
* DOCX serialization: mark attrs → RunOptions properties.
|
|
21
23
|
* Each mark extension defines this to contribute run-level properties.
|
|
@@ -1,49 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region src/extensions/utils.d.ts
|
|
4
|
-
/** Normalize a CSS color value to hex (e.g., "red" → "#FF0000", "#ff0000" → "#FF0000"). */
|
|
5
|
-
declare function normalizeColorToHex(color: string | undefined): string | undefined;
|
|
6
|
-
/** Resolve a font value (string or OOXML rFonts { ascii, eastAsia, hAnsi, cs }) to a CSS family name. */
|
|
7
|
-
declare function resolveFontName(font: unknown): string | null;
|
|
8
|
-
/** CSS value (e.g., "18pt") → twip number. 1 pt = 20 twips, 1 px = 15 twips (96 DPI). */
|
|
9
|
-
declare function cssToTwip(value: string | undefined): number | undefined;
|
|
10
|
-
/** Twip value → CSS string (e.g., 360 → "18pt"). */
|
|
11
|
-
declare function twipToCss(value: number | string | undefined): string | null;
|
|
12
|
-
/** OOXML alignment → CSS text-align. */
|
|
13
|
-
declare function alignmentToCss(alignment: string | null | undefined): string | null;
|
|
14
|
-
/** CSS text-align → OOXML alignment. */
|
|
15
|
-
declare function alignmentFromCss(css: string | null | undefined): string | null;
|
|
16
|
-
/** Shading.fill → CSS background-color hex. */
|
|
17
|
-
declare function shadingToCss(shading: ShadingAttributesProperties | null | undefined): string | null;
|
|
18
|
-
/** CSS background-color → ShadingAttributesProperties (fill normalized to hex). */
|
|
19
|
-
declare function shadingFromCss(css: string | null | undefined): ShadingAttributesProperties | null;
|
|
20
|
-
declare function lineSpacingToCss(spacing: SpacingProperties | null | undefined): string | null;
|
|
21
|
-
declare function sizeToCss(size: number | null | undefined): string | null;
|
|
22
|
-
declare function sizeFromCss(css: string | null | undefined): number | null;
|
|
23
|
-
declare function characterSpacingToCss(spacing: number | null | undefined): string | null;
|
|
24
|
-
declare function characterSpacingFromCss(css: string | null | undefined): number | null;
|
|
25
|
-
/** Render a BorderOptions to CSS string. OOXML border.size is in eighths of a point. */
|
|
26
|
-
declare function renderBorderCSS(border: BorderOptions): string | null;
|
|
27
|
-
/**
|
|
28
|
-
* Compute all paragraph-level CSS styles from nested attrs.
|
|
29
|
-
* Shared by Paragraph and Heading extensions for node-level renderHTML.
|
|
30
|
-
* Attrs store office-open native values; mappers here convert to CSS.
|
|
31
|
-
*/
|
|
32
|
-
declare function renderParagraphStyles(attrs: Record<string, unknown>): string[];
|
|
33
|
-
/**
|
|
34
|
-
* Compute table cell CSS styles from nested attrs.
|
|
35
|
-
* Shared by TableCell and TableHeader extensions.
|
|
36
|
-
*/
|
|
37
|
-
declare function renderTableCellStyles(attrs: Record<string, unknown>): string[];
|
|
38
|
-
/** Parse text-align → OOXML alignment. */
|
|
39
|
-
declare function alignmentFromElement(el: HTMLElement): string | null;
|
|
40
|
-
/** Parse margin-left/right + text-indent → OOXML indent (twips). */
|
|
41
|
-
declare function indentFromElement(el: HTMLElement): IndentAttributesProperties | null;
|
|
42
|
-
/** Parse margin-top/bottom + line-height → OOXML spacing (twips). */
|
|
43
|
-
declare function spacingFromElement(el: HTMLElement): SpacingProperties | null;
|
|
44
|
-
/** Parse border-* → OOXML BordersOptions. */
|
|
45
|
-
declare function bordersFromElement(el: HTMLElement): BordersOptions | null;
|
|
46
|
-
/** Parse background-color → OOXML shading. */
|
|
47
|
-
declare function shadingFromElement(el: HTMLElement): ShadingAttributesProperties | null;
|
|
48
|
-
//#endregion
|
|
49
|
-
export { alignmentFromCss, alignmentFromElement, alignmentToCss, bordersFromElement, characterSpacingFromCss, characterSpacingToCss, cssToTwip, indentFromElement, lineSpacingToCss, normalizeColorToHex, renderBorderCSS, renderParagraphStyles, renderTableCellStyles, resolveFontName, shadingFromCss, shadingFromElement, shadingToCss, sizeFromCss, sizeToCss, spacingFromElement, twipToCss };
|
|
1
|
+
import { C as shadingFromElement, D as spacingFromElement, E as sizeToCss, O as twipToCss, S as shadingFromCss, T as sizeFromCss, _ as resolveFontFamilyCss, a as characterSpacingFromCss, b as sectionLinePitchCss, c as floatAnchorScope, d as lineSpacingToCss, f as normalizeColorToHex, g as renderTableCellStyles, h as renderRunStyles, i as bordersFromElement, k as twipsToMm, l as floatingToStyles, m as renderParagraphStyles, n as alignmentFromElement, o as characterSpacingToCss, p as renderBorderCSS, r as alignmentToCss, s as cssToTwip, t as alignmentFromCss, u as indentFromElement, v as resolveFontName, w as shadingToCss, x as sectionMarginCss, y as resolvePageSize } from "../utils-D87vukzp.mjs";
|
|
2
|
+
export { alignmentFromCss, alignmentFromElement, alignmentToCss, bordersFromElement, characterSpacingFromCss, characterSpacingToCss, cssToTwip, floatAnchorScope, floatingToStyles, indentFromElement, lineSpacingToCss, normalizeColorToHex, renderBorderCSS, renderParagraphStyles, renderRunStyles, renderTableCellStyles, resolveFontFamilyCss, resolveFontName, resolvePageSize, sectionLinePitchCss, sectionMarginCss, shadingFromCss, shadingFromElement, shadingToCss, sizeFromCss, sizeToCss, spacingFromElement, twipToCss, twipsToMm };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sectionMarginDefaults, sectionPageSizeDefaults } from "@office-open/docx";
|
|
1
2
|
//#region src/extensions/utils.ts
|
|
2
3
|
/** Common CSS named colors → hex */
|
|
3
4
|
const CSS_COLORS = {
|
|
@@ -30,9 +31,23 @@ const CSS_COLORS = {
|
|
|
30
31
|
salmon: "#FA8072",
|
|
31
32
|
tomato: "#FF6347"
|
|
32
33
|
};
|
|
33
|
-
/** Normalize a CSS color value to hex (e.g., "red" → "#FF0000", "#ff0000" → "#FF0000").
|
|
34
|
+
/** Normalize a CSS color value to hex (e.g., "red" → "#FF0000", "#ff0000" → "#FF0000").
|
|
35
|
+
* Accepts a string (CSS name/hex or bare OOXML hex), or an OOXML ColorOptions
|
|
36
|
+
* object ({ val, themeColor, themeTint, themeShade }) — the object form
|
|
37
|
+
* resolves to its val (the RGB fallback Word stores alongside themeColor) for
|
|
38
|
+
* CSS rendering. The themeColor/tint/shade are preserved verbatim in the attrs
|
|
39
|
+
* and round-trip back to the DOCX (see text-style/paragraph parseDocx), so
|
|
40
|
+
* theme semantics survive even though only val is rendered here. A pure theme
|
|
41
|
+
* reference with no val (rare — Word usually stores both) would need theme.xml
|
|
42
|
+
* to resolve and is left unset. */
|
|
34
43
|
function normalizeColorToHex(color) {
|
|
35
44
|
if (!color) return void 0;
|
|
45
|
+
if (typeof color === "object") {
|
|
46
|
+
const { val } = color;
|
|
47
|
+
return val ? normalizeColorToHex(val) : void 0;
|
|
48
|
+
}
|
|
49
|
+
if (typeof color !== "string") return void 0;
|
|
50
|
+
if (color === "auto") return void 0;
|
|
36
51
|
if (color.startsWith("#")) return color.length === 4 ? `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`.toUpperCase() : color.toUpperCase();
|
|
37
52
|
if (/^[0-9A-Fa-f]{6}$/.test(color)) return `#${color.toUpperCase()}`;
|
|
38
53
|
if (/^[0-9A-Fa-f]{3}$/.test(color)) return `#${color[0]}${color[0]}${color[1]}${color[1]}${color[2]}${color[2]}`.toUpperCase();
|
|
@@ -48,6 +63,26 @@ function resolveFontName(font) {
|
|
|
48
63
|
}
|
|
49
64
|
return null;
|
|
50
65
|
}
|
|
66
|
+
/** Resolve a font value to a CSS font-family list with an eastAsia fallback.
|
|
67
|
+
* Mirrors Word's per-Unicode-range font selection (MS-OE376): Basic Latin uses
|
|
68
|
+
* the ascii font, CJK Ideographs use eastAsia. CSS can't split a run by range,
|
|
69
|
+
* so list ascii first (it carries Basic Latin glyphs) then eastAsia — the
|
|
70
|
+
* browser falls back to eastAsia for CJK chars the ascii font lacks. Without
|
|
71
|
+
* this, CJK text renders in the ascii font (e.g. Times) instead of the
|
|
72
|
+
* document's CJK font (e.g. SimSun). Use this for font-family rendering;
|
|
73
|
+
* resolveFontName (single name) stays for UI/parse. */
|
|
74
|
+
function resolveFontFamilyCss(font) {
|
|
75
|
+
if (!font) return null;
|
|
76
|
+
if (typeof font === "string") return font;
|
|
77
|
+
if (typeof font === "object") {
|
|
78
|
+
const f = font;
|
|
79
|
+
const ascii = f.ascii || f.hAnsi;
|
|
80
|
+
const ea = f.eastAsia;
|
|
81
|
+
if (ascii && ea && ascii !== ea) return `"${ascii}","${ea}"`;
|
|
82
|
+
return ascii || ea || null;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
51
86
|
/** CSS value (e.g., "18pt") → twip number. 1 pt = 20 twips, 1 px = 15 twips (96 DPI). */
|
|
52
87
|
function cssToTwip(value) {
|
|
53
88
|
if (!value) return void 0;
|
|
@@ -109,12 +144,73 @@ function shadingFromCss(css) {
|
|
|
109
144
|
type: "clear"
|
|
110
145
|
} : null;
|
|
111
146
|
}
|
|
112
|
-
function lineSpacingToCss(spacing) {
|
|
147
|
+
function lineSpacingToCss(spacing, snapToGrid = null) {
|
|
113
148
|
if (!spacing?.line) return null;
|
|
114
149
|
const rule = spacing.lineRule;
|
|
115
|
-
if (rule === "exact" || rule === "exactly" || rule === "atLeast") return `${spacing.line / 20}pt`;
|
|
116
|
-
|
|
117
|
-
return
|
|
150
|
+
if (rule === "exact" || rule === "exactly" || rule === "atLeast") return `${Number(spacing.line) / 20}pt`;
|
|
151
|
+
if (snapToGrid === false) return `calc(var(--docen-font-metric, 1.2) * ${Number((Number(spacing.line) / 240).toFixed(2))} * var(--docen-line-base, 1em))`;
|
|
152
|
+
return `calc(var(--docen-font-metric, 1.2) * var(--docen-line-base, 1em) + var(--docen-line-pitch, 0pt))`;
|
|
153
|
+
}
|
|
154
|
+
/** twips → mm (1 in = 1440 tw = 25.4 mm), 2dp string. */
|
|
155
|
+
function twipsToMm(twips) {
|
|
156
|
+
return `${(twips / 1440 * 25.4).toFixed(2)}mm`;
|
|
157
|
+
}
|
|
158
|
+
/** Resolve a section's printable page dimensions (twips), honoring orientation.
|
|
159
|
+
* A landscape section commonly stores portrait dims (w<h) with
|
|
160
|
+
* `orientation: "landscape"` — swap width/height so width is the larger edge.
|
|
161
|
+
* Falls back to the engine's default page size (@office-open/docx
|
|
162
|
+
* `sectionPageSizeDefaults` = A4) when the size is absent or non-numeric — the
|
|
163
|
+
* engine's `stringifySectionPropertiesXml` fills an empty sectPr the same way,
|
|
164
|
+
* so edit-time geometry matches render/measure/generate/export. */
|
|
165
|
+
function resolvePageSize(size) {
|
|
166
|
+
const fallback = {
|
|
167
|
+
width: sectionPageSizeDefaults.WIDTH,
|
|
168
|
+
height: sectionPageSizeDefaults.HEIGHT
|
|
169
|
+
};
|
|
170
|
+
if (!size || typeof size !== "object") return fallback;
|
|
171
|
+
const s = size;
|
|
172
|
+
const w = typeof s.width === "number" ? s.width : void 0;
|
|
173
|
+
const h = typeof s.height === "number" ? s.height : void 0;
|
|
174
|
+
if (w == null || h == null) return fallback;
|
|
175
|
+
return s.orientation === "landscape" && w < h ? {
|
|
176
|
+
width: h,
|
|
177
|
+
height: w
|
|
178
|
+
} : {
|
|
179
|
+
width: w,
|
|
180
|
+
height: h
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** Page margin (twips) → CSS padding. Sides absent or non-numeric fall back to
|
|
184
|
+
* the engine's default margins (@office-open/docx `sectionMarginDefaults`:
|
|
185
|
+
* top/bottom 1440tw, left/right 1800tw — MS Office zh-CN "Normal"), matching
|
|
186
|
+
* the page-size fallback above and the engine's empty-sectPr behavior. Margins
|
|
187
|
+
* are left as-is: office-open returns them already rotated for a landscape
|
|
188
|
+
* section. */
|
|
189
|
+
function sectionMarginCss(margin) {
|
|
190
|
+
const def = sectionMarginDefaults;
|
|
191
|
+
const m = margin && typeof margin === "object" ? margin : {};
|
|
192
|
+
const num = (v, d) => typeof v === "number" ? v : d;
|
|
193
|
+
return `padding:${[
|
|
194
|
+
num(m.top, def.TOP),
|
|
195
|
+
num(m.right, def.RIGHT),
|
|
196
|
+
num(m.bottom, def.BOTTOM),
|
|
197
|
+
num(m.left, def.LEFT)
|
|
198
|
+
].map(twipsToMm).join(" ")}`;
|
|
199
|
+
}
|
|
200
|
+
/** Document grid linePitch (twips) → container CSS line-height.
|
|
201
|
+
* Per ECMA-376 snapToGrid, the grid linePitch is ADDED to each line (single-
|
|
202
|
+
* line paragraphs inherit it). So the container line-height (inherited by
|
|
203
|
+
* paragraphs without their own spacing) is the font's `normal` metric + the
|
|
204
|
+
* grid pitch, set only when the grid type enables line-snapping (not
|
|
205
|
+
* "default"). The --docen-line-pitch var carries the pitch so per-paragraph
|
|
206
|
+
* lineSpacingToCss can add the same pitch; --docen-font-metric is injected per
|
|
207
|
+
* paragraph by the editor; 1.2 is the fallback for static HTML export. */
|
|
208
|
+
function sectionLinePitchCss(grid) {
|
|
209
|
+
if (!grid || typeof grid !== "object") return [];
|
|
210
|
+
const g = grid;
|
|
211
|
+
if (!g.type || g.type === "default" || typeof g.linePitch !== "number" || !g.linePitch) return [];
|
|
212
|
+
const pitch = `${(g.linePitch / 20).toFixed(2)}pt`;
|
|
213
|
+
return [`line-height:calc(var(--docen-font-metric, 1.2) * 1em + ${pitch})`, `--docen-line-pitch:${pitch}`];
|
|
118
214
|
}
|
|
119
215
|
function sizeToCss(size) {
|
|
120
216
|
if (size == null) return null;
|
|
@@ -138,7 +234,7 @@ function characterSpacingFromCss(css) {
|
|
|
138
234
|
}
|
|
139
235
|
/** Render a BorderOptions to CSS string. OOXML border.size is in eighths of a point. */
|
|
140
236
|
function renderBorderCSS(border) {
|
|
141
|
-
if (!border || border.style === "none") return null;
|
|
237
|
+
if (!border || !border.style || border.style === "none" || border.style === "nil") return null;
|
|
142
238
|
const size = border.size != null ? `${border.size / 8}pt` : "1pt";
|
|
143
239
|
const cssStyle = {
|
|
144
240
|
single: "solid",
|
|
@@ -146,17 +242,85 @@ function renderBorderCSS(border) {
|
|
|
146
242
|
dotted: "dotted",
|
|
147
243
|
double: "double",
|
|
148
244
|
dotDash: "dashed",
|
|
149
|
-
dotDotDash: "dotted"
|
|
245
|
+
dotDotDash: "dotted",
|
|
246
|
+
dashSmallGap: "dashed"
|
|
150
247
|
}[border.style || "single"] || "solid";
|
|
151
248
|
const hex = border.color && border.color !== "auto" ? normalizeColorToHex(border.color) : null;
|
|
152
249
|
return hex ? `${cssStyle} ${size} ${hex}` : `${cssStyle} ${size}`;
|
|
153
250
|
}
|
|
251
|
+
const EMU_PER_PX = 9525;
|
|
252
|
+
const EMU_PER_PT = 12700;
|
|
253
|
+
/**
|
|
254
|
+
* Render a drawing's Floating anchor (wp:anchor) to CSS — shared by Image and
|
|
255
|
+
* WpgGroup so anchored drawings render consistently:
|
|
256
|
+
* - wrapNone (type 0): position:absolute at the EMU offset (text does not flow
|
|
257
|
+
* around it — "in front of text" / "behind text"); z-index lifts it above or
|
|
258
|
+
* below the text layer per behindDocument.
|
|
259
|
+
* - topAndBottom (3): float + clear:both.
|
|
260
|
+
* - square/tight/through (1/2/4): with an hPos.offset, float:right pulled to
|
|
261
|
+
* the offset via margin-right so text wraps beside it with no overlap;
|
|
262
|
+
* otherwise float on the wrap side (tight/through add shape-outside, src).
|
|
263
|
+
* Wrap margins (wp:distT/B/L/R) are EMU, rendered as pt.
|
|
264
|
+
*/
|
|
265
|
+
function floatingToStyles(floating, src, width) {
|
|
266
|
+
const f = floating;
|
|
267
|
+
if (!f) return [];
|
|
268
|
+
const styles = [];
|
|
269
|
+
styles.push(`z-index:${f.behindDocument ? -1 : f.zIndex ?? 1}`);
|
|
270
|
+
const wrapType = f.wrap?.type ?? 0;
|
|
271
|
+
const hOff = f.horizontalPosition?.offset;
|
|
272
|
+
const vOff = f.verticalPosition?.offset;
|
|
273
|
+
if (wrapType === 0) {
|
|
274
|
+
styles.push("position:absolute");
|
|
275
|
+
if (hOff != null) styles.push(`left:${(hOff / EMU_PER_PX).toFixed(1)}px`);
|
|
276
|
+
if (vOff != null) styles.push(`top:${(vOff / EMU_PER_PX).toFixed(1)}px`);
|
|
277
|
+
} else if (wrapType !== 3 && hOff != null && width != null) {
|
|
278
|
+
const offPx = (hOff / EMU_PER_PX).toFixed(1);
|
|
279
|
+
styles.push("float:right", `margin-right:calc(100% - ${offPx}px - ${width}px)`);
|
|
280
|
+
if (vOff != null) styles.push(`margin-top:${(vOff / EMU_PER_PX).toFixed(1)}px`);
|
|
281
|
+
const m = f.margins;
|
|
282
|
+
if (m?.left) styles.push(`margin-left:${(m.left / EMU_PER_PT).toFixed(1)}pt`);
|
|
283
|
+
} else if (wrapType === 3) styles.push("float:left", "clear:both");
|
|
284
|
+
else {
|
|
285
|
+
const side = f.wrap?.side;
|
|
286
|
+
const floatSide = side === "right" || side === "outside" ? "right" : "left";
|
|
287
|
+
styles.push(`float:${floatSide}`);
|
|
288
|
+
if ((wrapType === 2 || wrapType === 4) && src) styles.push(`shape-outside:url(${src})`);
|
|
289
|
+
}
|
|
290
|
+
if (!(wrapType !== 0 && wrapType !== 3 && hOff != null && width != null) && wrapType !== 0) {
|
|
291
|
+
const m = f.margins;
|
|
292
|
+
if (m) {
|
|
293
|
+
if (m.top) styles.push(`margin-top:${(m.top / EMU_PER_PT).toFixed(1)}pt`);
|
|
294
|
+
if (m.bottom) styles.push(`margin-bottom:${(m.bottom / EMU_PER_PT).toFixed(1)}pt`);
|
|
295
|
+
if (m.left) styles.push(`margin-left:${(m.left / EMU_PER_PT).toFixed(1)}pt`);
|
|
296
|
+
if (m.right) styles.push(`margin-right:${(m.right / EMU_PER_PT).toFixed(1)}pt`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return styles;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Where a wrapNone floating drawing's absolute top/left should resolve from.
|
|
303
|
+
*
|
|
304
|
+
* verticalPosition.relative (OOXML vRelativeFrom) defaults to "paragraph"
|
|
305
|
+
* (also "line"); "page"/"margin"/"column" scope to the page box. The drawing is
|
|
306
|
+
* position:absolute, so its offsetParent decides the origin: a
|
|
307
|
+
* paragraph-anchored drawing must sit inside a position:relative <p> (the editor
|
|
308
|
+
* CSS adds that via p:has([data-float-anchor])), otherwise top/left measure
|
|
309
|
+
* from the page top and the drawing floats over the heading/body instead of
|
|
310
|
+
* its own blank paragraph (verified on sample anchored drawing groups). Only
|
|
311
|
+
* relevant for wrapNone (type 0); the float-based wraps stay inline.
|
|
312
|
+
*/
|
|
313
|
+
function floatAnchorScope(floating) {
|
|
314
|
+
const vRel = floating?.verticalPosition?.relative;
|
|
315
|
+
if (vRel === "page" || vRel === "margin" || vRel === "column") return "page";
|
|
316
|
+
return "paragraph";
|
|
317
|
+
}
|
|
154
318
|
/**
|
|
155
319
|
* Compute all paragraph-level CSS styles from nested attrs.
|
|
156
320
|
* Shared by Paragraph and Heading extensions for node-level renderHTML.
|
|
157
321
|
* Attrs store office-open native values; mappers here convert to CSS.
|
|
158
322
|
*/
|
|
159
|
-
function renderParagraphStyles(attrs) {
|
|
323
|
+
function renderParagraphStyles(attrs, opts) {
|
|
160
324
|
const a = attrs;
|
|
161
325
|
const styles = [];
|
|
162
326
|
const align = alignmentToCss(a.alignment);
|
|
@@ -174,16 +338,18 @@ function renderParagraphStyles(attrs) {
|
|
|
174
338
|
if (h) styles.push(`text-indent:-${h}`);
|
|
175
339
|
} else if (a.indent.firstLineChars != null) styles.push(`text-indent:${a.indent.firstLineChars / 100}em`);
|
|
176
340
|
}
|
|
341
|
+
const markLineHeight = a.run?.size != null ? sizeToCss(a.run.size) : null;
|
|
342
|
+
if (markLineHeight) styles.push(`line-height:${markLineHeight}`);
|
|
177
343
|
if (a.spacing) {
|
|
178
344
|
const before = twipToCss(a.spacing.before);
|
|
179
345
|
if (before) styles.push(`margin-top:${before}`);
|
|
180
346
|
const after = twipToCss(a.spacing.after);
|
|
181
347
|
if (after) styles.push(`margin-bottom:${after}`);
|
|
182
|
-
const lh = lineSpacingToCss(a.spacing);
|
|
348
|
+
const lh = lineSpacingToCss(a.spacing, opts?.empty ? false : a.snapToGrid);
|
|
183
349
|
if (lh) styles.push(`line-height:${lh}`);
|
|
184
350
|
}
|
|
185
351
|
const bg = shadingToCss(a.shading);
|
|
186
|
-
if (bg) styles.push(`background-color:${bg}`);
|
|
352
|
+
if (bg) styles.push(`background-color:${bg}`, `color:contrast-color(${bg})`);
|
|
187
353
|
if (a.border) {
|
|
188
354
|
const sides = [
|
|
189
355
|
["top", a.border.top],
|
|
@@ -199,6 +365,35 @@ function renderParagraphStyles(attrs) {
|
|
|
199
365
|
return styles;
|
|
200
366
|
}
|
|
201
367
|
/**
|
|
368
|
+
* Compute run-level CSS (font/size/color/weight/…) from office-open run attrs.
|
|
369
|
+
* Shared by text-style marks and the styles→CSS generator (stylesToCss).
|
|
370
|
+
*/
|
|
371
|
+
function renderRunStyles(attrs) {
|
|
372
|
+
const a = attrs;
|
|
373
|
+
const styles = [];
|
|
374
|
+
if (a.bold) styles.push("font-weight:bold");
|
|
375
|
+
if (a.italic) styles.push("font-style:italic");
|
|
376
|
+
if (a.smallCaps) styles.push("font-variant:small-caps");
|
|
377
|
+
if (a.allCaps) styles.push("text-transform:uppercase");
|
|
378
|
+
const deco = [];
|
|
379
|
+
if (a.underline) deco.push("underline");
|
|
380
|
+
if (a.strike || a.doubleStrike) deco.push("line-through");
|
|
381
|
+
if (deco.length) styles.push(`text-decoration:${deco.join(" ")}`);
|
|
382
|
+
const font = resolveFontFamilyCss(a.font);
|
|
383
|
+
if (font) styles.push(`font-family:${font}`);
|
|
384
|
+
const size = sizeToCss(a.size);
|
|
385
|
+
if (size) styles.push(`font-size:${size}`);
|
|
386
|
+
const color = a.color === "auto" ? void 0 : normalizeColorToHex(a.color);
|
|
387
|
+
if (color) styles.push(`color:${color}`);
|
|
388
|
+
const spacing = characterSpacingToCss(a.characterSpacing);
|
|
389
|
+
if (spacing) styles.push(`letter-spacing:${spacing}`);
|
|
390
|
+
if (a.highlight) {
|
|
391
|
+
const hl = normalizeColorToHex(typeof a.highlight === "string" ? a.highlight : null);
|
|
392
|
+
if (hl) styles.push(`background-color:${hl}`, `color:contrast-color(${hl})`);
|
|
393
|
+
}
|
|
394
|
+
return styles;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
202
397
|
* Compute table cell CSS styles from nested attrs.
|
|
203
398
|
* Shared by TableCell and TableHeader extensions.
|
|
204
399
|
*/
|
|
@@ -207,8 +402,23 @@ function renderTableCellStyles(attrs) {
|
|
|
207
402
|
const styles = [];
|
|
208
403
|
if (a.noWrap) styles.push("white-space:nowrap");
|
|
209
404
|
const bg = shadingToCss(a.shading);
|
|
210
|
-
if (bg) styles.push(`background-color:${bg}`);
|
|
405
|
+
if (bg) styles.push(`background-color:${bg}`, `color:contrast-color(${bg})`);
|
|
211
406
|
if (a.verticalAlign) styles.push(`vertical-align:${a.verticalAlign}`);
|
|
407
|
+
if (a.margins) {
|
|
408
|
+
const m = a.margins;
|
|
409
|
+
const side = (s) => s && typeof s.size === "number" ? twipToCss(s.size) ?? "0pt" : "0pt";
|
|
410
|
+
styles.push(`padding:${side(m.top)} ${side(m.right)} ${side(m.bottom)} ${side(m.left)}`);
|
|
411
|
+
}
|
|
412
|
+
if (a.borders) for (const side of [
|
|
413
|
+
"top",
|
|
414
|
+
"right",
|
|
415
|
+
"bottom",
|
|
416
|
+
"left"
|
|
417
|
+
]) {
|
|
418
|
+
const b = a.borders[side];
|
|
419
|
+
if (!b) continue;
|
|
420
|
+
styles.push(`border-${side}:${renderBorderCSS(b) ?? "none"}`);
|
|
421
|
+
}
|
|
212
422
|
return styles;
|
|
213
423
|
}
|
|
214
424
|
/** Parse text-align → OOXML alignment. */
|
|
@@ -286,4 +496,4 @@ function shadingFromElement(el) {
|
|
|
286
496
|
return shadingFromCss(el.style.backgroundColor || null);
|
|
287
497
|
}
|
|
288
498
|
//#endregion
|
|
289
|
-
export { alignmentFromCss, alignmentFromElement, alignmentToCss, bordersFromElement, characterSpacingFromCss, characterSpacingToCss, cssToTwip, indentFromElement, lineSpacingToCss, normalizeColorToHex, renderBorderCSS, renderParagraphStyles, renderTableCellStyles, resolveFontName, shadingFromCss, shadingFromElement, shadingToCss, sizeFromCss, sizeToCss, spacingFromElement, twipToCss };
|
|
499
|
+
export { alignmentFromCss, alignmentFromElement, alignmentToCss, bordersFromElement, characterSpacingFromCss, characterSpacingToCss, cssToTwip, floatAnchorScope, floatingToStyles, indentFromElement, lineSpacingToCss, normalizeColorToHex, renderBorderCSS, renderParagraphStyles, renderRunStyles, renderTableCellStyles, resolveFontFamilyCss, resolveFontName, resolvePageSize, sectionLinePitchCss, sectionMarginCss, shadingFromCss, shadingFromElement, shadingToCss, sizeFromCss, sizeToCss, spacingFromElement, twipToCss, twipsToMm };
|