@portone/docx-editor 0.3.0 → 0.4.0
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/CHANGELOG.md +46 -0
- package/dist/DocxEditor.js +1 -0
- package/dist/core.d.ts +1 -1
- package/dist/docx/comments/writing.js +14 -8
- package/dist/docx/exportDocx.js +20 -4
- package/dist/docx/formatting/attrs.d.ts +20 -2
- package/dist/docx/formatting/attrs.js +57 -2
- package/dist/docx/formatting/context.d.ts +10 -2
- package/dist/docx/formatting/context.js +23 -5
- package/dist/docx/formatting/resolve.d.ts +10 -4
- package/dist/docx/formatting/resolve.js +31 -11
- package/dist/docx/formatting/styles.d.ts +25 -2
- package/dist/docx/formatting/styles.js +84 -3
- package/dist/docx/hyperlink.d.ts +14 -4
- package/dist/docx/hyperlink.js +4 -1
- package/dist/docx/importDocx.js +46 -37
- package/dist/docx/importTable.d.ts +2 -2
- package/dist/docx/importTable.js +52 -55
- package/dist/docx/invariants.js +26 -13
- package/dist/docx/newLists.d.ts +30 -0
- package/dist/docx/newLists.js +24 -1
- package/dist/docx/numberingPlanner.d.ts +6 -3
- package/dist/docx/numberingPlanner.js +38 -7
- package/dist/docx/session.d.ts +1 -1
- package/dist/docx/session.js +11 -1
- package/dist/docx/tableFormatting/conditions.d.ts +76 -0
- package/dist/docx/tableFormatting/conditions.js +196 -0
- package/dist/docx/tableFormatting/editing.d.ts +7 -3
- package/dist/docx/tableFormatting/editing.js +4 -5
- package/dist/docx/tableFormatting/reading.d.ts +49 -19
- package/dist/docx/tableFormatting/reading.js +109 -35
- package/dist/docx/tableFormatting.d.ts +1 -0
- package/dist/docx/tableFormatting.js +1 -0
- package/dist/docx/tableTemplate.js +12 -12
- package/dist/editor/commands/formatting/propertyCommands.js +6 -1
- package/dist/editor/commands/listCommands.d.ts +6 -3
- package/dist/editor/commands/listCommands.js +73 -28
- package/dist/editor/commands/paragraphCommands.js +6 -1
- package/dist/editor/createEditor.js +1 -1
- package/dist/editor/editorDocument.d.ts +7 -6
- package/dist/editor/editorDocument.js +18 -6
- package/dist/editor/externalClipboard.d.ts +13 -2
- package/dist/editor/externalClipboard.js +64 -34
- package/dist/editor/paragraphEdits.d.ts +16 -1
- package/dist/editor/paragraphEdits.js +13 -5
- package/dist/editor/paragraphPlacement.d.ts +11 -0
- package/dist/editor/paragraphPlacement.js +18 -0
- package/dist/editor/plugins/imagePaste.js +7 -4
- package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
- package/dist/editor/plugins/numberingDecorations.js +43 -2
- package/dist/editor/plugins/paragraphDisplay.js +9 -3
- package/dist/editor/plugins/tableDisplay.js +19 -5
- package/dist/model/format.d.ts +50 -0
- package/dist/model/format.js +65 -0
- package/dist/numbering/listRegistry.d.ts +37 -0
- package/dist/numbering/listRegistry.js +112 -0
- package/dist/numbering/listTemplate.d.ts +26 -17
- package/dist/numbering/listTemplate.js +66 -20
- package/dist/numbering/markers.d.ts +15 -6
- package/dist/numbering/markers.js +26 -59
- package/dist/numbering/parseNumbering.d.ts +70 -5
- package/dist/numbering/parseNumbering.js +100 -26
- package/dist/numbering/spellers.d.ts +15 -0
- package/dist/numbering/spellers.js +96 -0
- package/dist/numbering/writeNumbering.d.ts +5 -3
- package/dist/numbering/writeNumbering.js +15 -15
- package/dist/ooxml/conformance.d.ts +28 -0
- package/dist/ooxml/conformance.js +20 -0
- package/dist/ooxml/errors.d.ts +5 -5
- package/dist/ooxml/names.d.ts +9 -0
- package/dist/ooxml/names.js +8 -1
- package/dist/ooxml/partSplice.d.ts +1 -1
- package/dist/ooxml/partSplice.js +10 -3
- package/dist/ooxml/xml.d.ts +8 -9
- package/dist/ooxml/xml.js +6 -6
- package/dist/page/PageGuides.d.ts +1 -1
- package/dist/page/PageGuides.js +0 -13
- package/dist/page/pageLayout.d.ts +0 -10
- package/dist/page/pageLayout.js +3 -10
- package/dist/page/usePageLayout.d.ts +0 -7
- package/dist/page/usePageLayout.js +2 -9
- package/dist/schema/attrRoles.js +8 -1
- package/dist/schema/docxSchema.js +32 -3
- package/dist/styles/classNames.d.ts +13 -2
- package/dist/styles/classNames.js +13 -2
- package/dist/styles.css +16 -18
- package/dist/table/cellFormatting.js +1 -2
- package/dist/table/gridBorders.d.ts +11 -15
- package/dist/table/gridBorders.js +32 -18
- package/package.json +1 -1
package/dist/docx/newLists.d.ts
CHANGED
|
@@ -5,7 +5,16 @@
|
|
|
5
5
|
* writer rather than being read off it.
|
|
6
6
|
*/
|
|
7
7
|
import type { Node as PMNode } from "prosemirror-model";
|
|
8
|
+
import { type NewList } from "../numbering/parseNumbering";
|
|
8
9
|
import type { SessionStore } from "./session";
|
|
10
|
+
/**
|
|
11
|
+
* How the main part relates the numbering part, and what the package declares that part to hold.
|
|
12
|
+
*
|
|
13
|
+
* The reader finds the part by this relationship and the writer relates a new one under it, so
|
|
14
|
+
* neither can name it differently from the other.
|
|
15
|
+
*/
|
|
16
|
+
export declare const NUMBERING_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering";
|
|
17
|
+
export declare const NUMBERING_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml";
|
|
9
18
|
/**
|
|
10
19
|
* The numbering ids that appeared during editing and have no definition.
|
|
11
20
|
*
|
|
@@ -13,8 +22,29 @@ import type { SessionStore } from "./session";
|
|
|
13
22
|
* because exporting such a document without editing it must not disturb numbering.xml.
|
|
14
23
|
*/
|
|
15
24
|
export declare function newNumIds(doc: PMNode, session: SessionStore): number[];
|
|
25
|
+
/** The lists started during editing, split by whether a definition was registered for each */
|
|
26
|
+
export interface StartedLists {
|
|
27
|
+
/** The definition each list was started with, which is what goes into numbering.xml */
|
|
28
|
+
readonly defined: ReadonlyMap<number, NewList>;
|
|
29
|
+
/** The numbers a list was started under that no definition stands behind */
|
|
30
|
+
readonly unregistered: readonly number[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The lists started during editing, each against the definition it was started with.
|
|
34
|
+
*
|
|
35
|
+
* The definition is the one the document node carries (`numbering/listRegistry`), which the list
|
|
36
|
+
* commands and the clipboard record as they start a list. A number carrying none is reported here
|
|
37
|
+
* rather than written bare, which the export invariants turn into a refusal.
|
|
38
|
+
*/
|
|
39
|
+
export declare function startedLists(doc: PMNode, session: SessionStore): StartedLists;
|
|
16
40
|
/** The numbering part as it was opened, which a new definition is spliced into. null when the document has none */
|
|
17
41
|
export declare function numberingPartOf(session: SessionStore): {
|
|
18
42
|
path: string;
|
|
19
43
|
bytes: Uint8Array;
|
|
20
44
|
} | null;
|
|
45
|
+
/**
|
|
46
|
+
* Whether the definition of a new list has somewhere to go: the numbering part the document was
|
|
47
|
+
* opened with, or one written from scratch, which only a package that can declare what the new
|
|
48
|
+
* part holds may take.
|
|
49
|
+
*/
|
|
50
|
+
export declare function canDefineNewList(session: SessionStore): boolean;
|
package/dist/docx/newLists.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// src/docx/newLists.ts
|
|
2
2
|
import { toParagraphFormat } from "../model/format.js";
|
|
3
|
+
import { NEW_LISTS_ATTR, newListsOf } from "../numbering/listRegistry.js";
|
|
3
4
|
import { parseNumbering } from "../numbering/parseNumbering.js";
|
|
5
|
+
import { R_NS } from "../ooxml/xml.js";
|
|
6
|
+
import { CONTENT_TYPES_PATH } from "./packageParts.js";
|
|
7
|
+
var NUMBERING_REL_TYPE = `${R_NS}/numbering`;
|
|
8
|
+
var NUMBERING_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml";
|
|
4
9
|
function collectNumIds(node, into) {
|
|
5
10
|
const visit = (candidate) => {
|
|
6
11
|
if (candidate.type.name !== "paragraph") return true;
|
|
@@ -25,12 +30,30 @@ function newNumIds(doc, session) {
|
|
|
25
30
|
const atOpen = numIdsAtOpen(session);
|
|
26
31
|
return Array.from(numIdsIn(doc)).filter((numId) => !defined.has(numId) && !atOpen.has(numId)).sort((a, b) => a - b);
|
|
27
32
|
}
|
|
33
|
+
function startedLists(doc, session) {
|
|
34
|
+
const registered = newListsOf(doc.attrs[NEW_LISTS_ATTR]);
|
|
35
|
+
const defined = /* @__PURE__ */ new Map();
|
|
36
|
+
const unregistered = [];
|
|
37
|
+
for (const numId of newNumIds(doc, session)) {
|
|
38
|
+
const list = registered.get(numId);
|
|
39
|
+
if (list === void 0) unregistered.push(numId);
|
|
40
|
+
else defined.set(numId, list);
|
|
41
|
+
}
|
|
42
|
+
return { defined, unregistered };
|
|
43
|
+
}
|
|
28
44
|
function numberingPartOf(session) {
|
|
29
45
|
const path = session.numberingPartPath;
|
|
30
46
|
const bytes = path === null ? void 0 : session.parts.get(path);
|
|
31
47
|
return path === null || bytes === void 0 ? null : { path, bytes };
|
|
32
48
|
}
|
|
49
|
+
function canDefineNewList(session) {
|
|
50
|
+
return numberingPartOf(session) !== null || session.parts.has(CONTENT_TYPES_PATH);
|
|
51
|
+
}
|
|
33
52
|
export {
|
|
53
|
+
NUMBERING_CONTENT_TYPE,
|
|
54
|
+
NUMBERING_REL_TYPE,
|
|
55
|
+
canDefineNewList,
|
|
34
56
|
newNumIds,
|
|
35
|
-
numberingPartOf
|
|
57
|
+
numberingPartOf,
|
|
58
|
+
startedLists
|
|
36
59
|
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The numbering part with the definition
|
|
2
|
+
* The numbering part with the definition every list started during editing was started with
|
|
3
|
+
* spliced in, and the part itself when the document arrived without one.
|
|
3
4
|
*
|
|
4
|
-
* The original text is left as is and only the new definitions go in.
|
|
5
|
-
*
|
|
5
|
+
* The original text is left as is and only the new definitions go in. A document that has no
|
|
6
|
+
* numbering part gets one written from scratch, with the relationship that finds it and the
|
|
7
|
+
* declaration of what it holds asked of the context, so the first list a document takes costs it
|
|
8
|
+
* nothing outside this planner. Nothing is written when no list was started.
|
|
6
9
|
*/
|
|
7
10
|
import type { PartPlanner } from "./partPlan";
|
|
8
11
|
export declare const numberingPlanner: PartPlanner;
|
|
@@ -1,17 +1,48 @@
|
|
|
1
1
|
// src/docx/numberingPlanner.ts
|
|
2
2
|
import { addListDefinitions } from "../numbering/writeNumbering.js";
|
|
3
|
+
import { elementXml, xmlnsAttr } from "../ooxml/element.js";
|
|
4
|
+
import { NAMESPACES, wName } from "../ooxml/names.js";
|
|
5
|
+
import { ensureRootDeclarations } from "../ooxml/partSplice.js";
|
|
3
6
|
import { decodeUtf8, encodeUtf8 } from "../ooxml/xml.js";
|
|
4
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
NUMBERING_CONTENT_TYPE,
|
|
9
|
+
NUMBERING_REL_TYPE,
|
|
10
|
+
numberingPartOf,
|
|
11
|
+
startedLists
|
|
12
|
+
} from "./newLists.js";
|
|
13
|
+
import { availablePartPath } from "./packageParts.js";
|
|
14
|
+
import { directoryOf } from "./relationships.js";
|
|
15
|
+
function emptyNumberingPart() {
|
|
16
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + elementXml(wName("numbering"), [xmlnsAttr("w")]);
|
|
17
|
+
}
|
|
5
18
|
var numberingPlanner = {
|
|
6
19
|
name: "numbering",
|
|
7
|
-
plan(doc, session) {
|
|
8
|
-
const
|
|
20
|
+
plan(doc, session, context) {
|
|
21
|
+
const { defined } = startedLists(doc, session);
|
|
22
|
+
if (defined.size === 0) return null;
|
|
9
23
|
const original = numberingPartOf(session);
|
|
10
|
-
if (
|
|
24
|
+
if (original === null) {
|
|
25
|
+
const path = session.numberingPartPath ?? availablePartPath(session.parts, session.mainPartPath, "numbering");
|
|
26
|
+
if (session.numberingPartPath === null) {
|
|
27
|
+
context.relationships.add({
|
|
28
|
+
type: NUMBERING_REL_TYPE,
|
|
29
|
+
target: path.slice(directoryOf(session.mainPartPath).length)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
context.contentTypes.addOverride(path, NUMBERING_CONTENT_TYPE);
|
|
33
|
+
return /* @__PURE__ */ new Map([
|
|
34
|
+
[
|
|
35
|
+
path,
|
|
36
|
+
encodeUtf8(addListDefinitions(emptyNumberingPart(), defined), false)
|
|
37
|
+
]
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
11
40
|
const { text, hadBom } = decodeUtf8(original.bytes);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
41
|
+
const rewritten = ensureRootDeclarations(
|
|
42
|
+
addListDefinitions(text, defined),
|
|
43
|
+
{ namespaces: { w: NAMESPACES.w } }
|
|
44
|
+
);
|
|
45
|
+
return /* @__PURE__ */ new Map([[original.path, encodeUtf8(rewritten, hadBom)]]);
|
|
15
46
|
}
|
|
16
47
|
};
|
|
17
48
|
export {
|
package/dist/docx/session.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { Node as PMNode } from "prosemirror-model";
|
|
|
11
11
|
import type { DocumentDefaults } from "../model/format";
|
|
12
12
|
import { type Numbering, type NumberingOptions } from "../numbering/parseNumbering";
|
|
13
13
|
import type { ImportedComments } from "./comments";
|
|
14
|
-
import type
|
|
14
|
+
import { type FormattingContext, type ParagraphStyleOption } from "./formatting";
|
|
15
15
|
import type { HeadersFooters } from "./headersFooters";
|
|
16
16
|
import type { PageGeometry } from "./pageGeometry";
|
|
17
17
|
export interface ImportedBlock {
|
package/dist/docx/session.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
parseNumbering
|
|
4
4
|
} from "../numbering/parseNumbering.js";
|
|
5
|
+
import {
|
|
6
|
+
numberingOptionsFor
|
|
7
|
+
} from "./formatting.js";
|
|
5
8
|
var sessionsOpened = 0;
|
|
6
9
|
function newSessionId() {
|
|
7
10
|
sessionsOpened += 1;
|
|
@@ -65,7 +68,14 @@ function documentPartPath(session) {
|
|
|
65
68
|
return sessionOf(session).mainPartPath;
|
|
66
69
|
}
|
|
67
70
|
function documentNumbering(session, options) {
|
|
68
|
-
|
|
71
|
+
const store = sessionOf(session);
|
|
72
|
+
return parseNumbering(store.numberingXml, {
|
|
73
|
+
...numberingOptionsFor(
|
|
74
|
+
store.formatting.styles,
|
|
75
|
+
store.formatting.themeFonts
|
|
76
|
+
),
|
|
77
|
+
...options
|
|
78
|
+
});
|
|
69
79
|
}
|
|
70
80
|
var BODY_STORY_KEY = "body";
|
|
71
81
|
function blockKey(session, storyKey, index) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which parts of a table one cell belongs to, and what its table style dresses those parts with.
|
|
3
|
+
*
|
|
4
|
+
* A table style says one thing for the header row, another for the first column, another for the
|
|
5
|
+
* banded rows (`w:tblStylePr`), and `w:tblLook` says which of those a table takes at all. Every
|
|
6
|
+
* caller that asks what a cell falls back on - the import, a table created fresh, the lines derived
|
|
7
|
+
* again after the grid moved, and a cell formatting edit - asks here, so a cell is dressed the same
|
|
8
|
+
* whichever of them asked.
|
|
9
|
+
*
|
|
10
|
+
* A conditional format writes the lines of the part it dresses rather than of every cell in it:
|
|
11
|
+
* its four sides are drawn where a cell lies on the edge of that part and its inside lines between
|
|
12
|
+
* the cells within it, which is the same rule the table's own lines are drawn by.
|
|
13
|
+
*/
|
|
14
|
+
import type { BandSizes, CellMargins, InsideBorders, TableFormat, TableStyleConditions, TableStyleOverrideType } from "../../model/format";
|
|
15
|
+
import { type CellDefaults, type GridRect, type GridSize } from "./reading";
|
|
16
|
+
/** Which parts of its table style a table takes (`w:tblLook`, §17.4.55) */
|
|
17
|
+
export interface TableLook {
|
|
18
|
+
firstRow: boolean;
|
|
19
|
+
lastRow: boolean;
|
|
20
|
+
firstColumn: boolean;
|
|
21
|
+
lastColumn: boolean;
|
|
22
|
+
noHBand: boolean;
|
|
23
|
+
noVBand: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What a table writing no `w:tblLook` at all takes: the banded rows and columns, and neither the
|
|
27
|
+
* header row, the last row, nor the edge columns (§17.4.55). Switching the banding off is
|
|
28
|
+
* something a table has to say, which is why both band settings are worded as `no`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const NO_LOOK: TableLook;
|
|
31
|
+
/**
|
|
32
|
+
* Which parts of its table style this table takes.
|
|
33
|
+
*
|
|
34
|
+
* The six attributes are what a document writes today; where one of them is missing the legacy
|
|
35
|
+
* bitmask answers for it, so a document written either way reads the same.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readTableLook(tblPr: Element | null): TableLook;
|
|
38
|
+
/** One conditional format a cell takes, and the part of the grid it dresses */
|
|
39
|
+
export interface CellCondition {
|
|
40
|
+
type: TableStyleOverrideType;
|
|
41
|
+
/** Its four lines are drawn on the edge of this block, and its inside lines within it */
|
|
42
|
+
region: GridRect;
|
|
43
|
+
}
|
|
44
|
+
/** Where a cell sits, and which parts of the table it therefore belongs to */
|
|
45
|
+
export interface CellPlacement {
|
|
46
|
+
rect: GridRect;
|
|
47
|
+
grid: GridSize;
|
|
48
|
+
/** Lowest first, in the order §17.7.6 lays them over one another */
|
|
49
|
+
conditions: readonly CellCondition[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The parts of the table this cell belongs to, lowest first.
|
|
53
|
+
*
|
|
54
|
+
* A cell sits in a row band by the row it starts in and in a column band by the column it starts
|
|
55
|
+
* in, and it is a corner only where the table takes both the row and the column that meet there.
|
|
56
|
+
*/
|
|
57
|
+
export declare function cellPlacement(rect: GridRect, grid: GridSize, look: TableLook, bands: BandSizes): CellPlacement;
|
|
58
|
+
/** What a table lays down for its cells to draw. What its style laid down is not in the `tblPr`, so it comes along separately */
|
|
59
|
+
export interface TableCellSources {
|
|
60
|
+
outer: TableFormat | null;
|
|
61
|
+
inside: InsideBorders;
|
|
62
|
+
margins: CellMargins;
|
|
63
|
+
/** The parts of its style the table takes */
|
|
64
|
+
look: TableLook;
|
|
65
|
+
bands: BandSizes;
|
|
66
|
+
/** What the style dresses each of those parts with */
|
|
67
|
+
conditions: TableStyleConditions;
|
|
68
|
+
/** The style the paragraphs inside the cells resolve against. null for a table wearing none */
|
|
69
|
+
styleId: string | null;
|
|
70
|
+
}
|
|
71
|
+
export declare const NO_CELL_SOURCES: TableCellSources;
|
|
72
|
+
/**
|
|
73
|
+
* What the cell covering this block of the grid falls back on: the table's own lines and margins,
|
|
74
|
+
* with every conditional format its style dresses a part it belongs to with laid over them.
|
|
75
|
+
*/
|
|
76
|
+
export declare function cellDefaultsFor(rect: GridRect, grid: GridSize, sources: TableCellSources): CellDefaults;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// src/docx/tableFormatting/conditions.ts
|
|
2
|
+
import { ST_OnOff } from "../../ooxml/simpleTypes.js";
|
|
3
|
+
import { borderLineOfCss, wAttr } from "../../ooxml/units.js";
|
|
4
|
+
import { childByLocalName } from "../../ooxml/xml.js";
|
|
5
|
+
import {
|
|
6
|
+
layerCellMargins,
|
|
7
|
+
NO_BAND_SIZES,
|
|
8
|
+
NO_CELL_MARGINS,
|
|
9
|
+
NO_INSIDE_BORDERS,
|
|
10
|
+
NO_TABLE_STYLE_CONDITIONS
|
|
11
|
+
} from "./reading.js";
|
|
12
|
+
var NO_LOOK = {
|
|
13
|
+
firstRow: false,
|
|
14
|
+
lastRow: false,
|
|
15
|
+
firstColumn: false,
|
|
16
|
+
lastColumn: false,
|
|
17
|
+
noHBand: false,
|
|
18
|
+
noVBand: false
|
|
19
|
+
};
|
|
20
|
+
var LEGACY_LOOK_BITS = {
|
|
21
|
+
firstRow: 32,
|
|
22
|
+
lastRow: 64,
|
|
23
|
+
firstColumn: 128,
|
|
24
|
+
lastColumn: 256,
|
|
25
|
+
noHBand: 512,
|
|
26
|
+
noVBand: 1024
|
|
27
|
+
};
|
|
28
|
+
var SHORT_HEX = /^[0-9a-fA-F]{4}$/;
|
|
29
|
+
function legacyLook(value) {
|
|
30
|
+
const bits = value !== null && SHORT_HEX.test(value) ? Number.parseInt(value, 16) : 0;
|
|
31
|
+
return {
|
|
32
|
+
firstRow: (bits & LEGACY_LOOK_BITS.firstRow) !== 0,
|
|
33
|
+
lastRow: (bits & LEGACY_LOOK_BITS.lastRow) !== 0,
|
|
34
|
+
firstColumn: (bits & LEGACY_LOOK_BITS.firstColumn) !== 0,
|
|
35
|
+
lastColumn: (bits & LEGACY_LOOK_BITS.lastColumn) !== 0,
|
|
36
|
+
noHBand: (bits & LEGACY_LOOK_BITS.noHBand) !== 0,
|
|
37
|
+
noVBand: (bits & LEGACY_LOOK_BITS.noVBand) !== 0
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function readTableLook(tblPr) {
|
|
41
|
+
const el = tblPr ? childByLocalName(tblPr, "tblLook") : null;
|
|
42
|
+
if (!el) return NO_LOOK;
|
|
43
|
+
const legacy = legacyLook(wAttr(el, "val"));
|
|
44
|
+
const written = (name) => ST_OnOff.parse(wAttr(el, name)) ?? legacy[name];
|
|
45
|
+
return {
|
|
46
|
+
firstRow: written("firstRow"),
|
|
47
|
+
lastRow: written("lastRow"),
|
|
48
|
+
firstColumn: written("firstColumn"),
|
|
49
|
+
lastColumn: written("lastColumn"),
|
|
50
|
+
noHBand: written("noHBand"),
|
|
51
|
+
noVBand: written("noVBand")
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function bandWidth(size) {
|
|
55
|
+
return size ?? 1;
|
|
56
|
+
}
|
|
57
|
+
function bandedRows(grid, look) {
|
|
58
|
+
return [look.firstRow ? 1 : 0, look.lastRow ? grid.rows - 1 : grid.rows];
|
|
59
|
+
}
|
|
60
|
+
function bandedColumns(grid, look) {
|
|
61
|
+
return [
|
|
62
|
+
look.firstColumn ? 1 : 0,
|
|
63
|
+
look.lastColumn ? grid.cols - 1 : grid.cols
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
function bandAt(at, [from, to], size) {
|
|
67
|
+
if (at < from || at >= to) return null;
|
|
68
|
+
const index = Math.floor((at - from) / size);
|
|
69
|
+
return {
|
|
70
|
+
first: index % 2 === 0,
|
|
71
|
+
start: from + index * size,
|
|
72
|
+
end: Math.min(from + (index + 1) * size, to)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function rowsOf(grid, top, bottom) {
|
|
76
|
+
return { top, bottom, left: 0, right: grid.cols };
|
|
77
|
+
}
|
|
78
|
+
function columnsOf(grid, left, right) {
|
|
79
|
+
return { top: 0, bottom: grid.rows, left, right };
|
|
80
|
+
}
|
|
81
|
+
function cellPlacement(rect, grid, look, bands) {
|
|
82
|
+
const found = [];
|
|
83
|
+
const add = (type, region) => {
|
|
84
|
+
found.push({ type, region });
|
|
85
|
+
};
|
|
86
|
+
add("wholeTable", rowsOf(grid, 0, grid.rows));
|
|
87
|
+
const column = look.noVBand ? null : bandAt(rect.left, bandedColumns(grid, look), bandWidth(bands.col));
|
|
88
|
+
if (column) {
|
|
89
|
+
add(
|
|
90
|
+
column.first ? "band1Vert" : "band2Vert",
|
|
91
|
+
columnsOf(grid, column.start, column.end)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const row = look.noHBand ? null : bandAt(rect.top, bandedRows(grid, look), bandWidth(bands.row));
|
|
95
|
+
if (row) {
|
|
96
|
+
add(
|
|
97
|
+
row.first ? "band1Horz" : "band2Horz",
|
|
98
|
+
rowsOf(grid, row.start, row.end)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const firstRow = look.firstRow && rect.top === 0;
|
|
102
|
+
const lastRow = look.lastRow && rect.bottom === grid.rows && grid.rows > 1;
|
|
103
|
+
const firstCol = look.firstColumn && rect.left === 0;
|
|
104
|
+
const lastCol = look.lastColumn && rect.right === grid.cols && grid.cols > 1;
|
|
105
|
+
if (firstRow) add("firstRow", rowsOf(grid, 0, 1));
|
|
106
|
+
if (lastRow) add("lastRow", rowsOf(grid, grid.rows - 1, grid.rows));
|
|
107
|
+
if (firstCol) add("firstCol", columnsOf(grid, 0, 1));
|
|
108
|
+
if (lastCol) add("lastCol", columnsOf(grid, grid.cols - 1, grid.cols));
|
|
109
|
+
const corner = (type, top, left) => {
|
|
110
|
+
add(type, { top, bottom: top + 1, left, right: left + 1 });
|
|
111
|
+
};
|
|
112
|
+
if (firstRow && firstCol) corner("nwCell", 0, 0);
|
|
113
|
+
if (firstRow && lastCol) corner("neCell", 0, grid.cols - 1);
|
|
114
|
+
if (lastRow && firstCol) corner("swCell", grid.rows - 1, 0);
|
|
115
|
+
if (lastRow && lastCol) corner("seCell", grid.rows - 1, grid.cols - 1);
|
|
116
|
+
return { rect, grid, conditions: found };
|
|
117
|
+
}
|
|
118
|
+
var NO_CELL_SOURCES = {
|
|
119
|
+
outer: null,
|
|
120
|
+
inside: NO_INSIDE_BORDERS,
|
|
121
|
+
margins: NO_CELL_MARGINS,
|
|
122
|
+
look: NO_LOOK,
|
|
123
|
+
bands: NO_BAND_SIZES,
|
|
124
|
+
conditions: NO_TABLE_STYLE_CONDITIONS,
|
|
125
|
+
styleId: null
|
|
126
|
+
};
|
|
127
|
+
function edgesIn(rect, region) {
|
|
128
|
+
return {
|
|
129
|
+
top: rect.top === region.top,
|
|
130
|
+
bottom: rect.bottom === region.bottom,
|
|
131
|
+
left: rect.left === region.left,
|
|
132
|
+
right: rect.right === region.right
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function tableBorderDefaults(edges, outer, inside) {
|
|
136
|
+
return {
|
|
137
|
+
top: borderLineOfCss(
|
|
138
|
+
edges.top ? outer?.borderTop ?? null : inside.horizontal
|
|
139
|
+
),
|
|
140
|
+
bottom: borderLineOfCss(
|
|
141
|
+
edges.bottom ? outer?.borderBottom ?? null : inside.horizontal
|
|
142
|
+
),
|
|
143
|
+
left: borderLineOfCss(
|
|
144
|
+
edges.left ? outer?.borderLeft ?? null : inside.vertical
|
|
145
|
+
),
|
|
146
|
+
right: borderLineOfCss(
|
|
147
|
+
edges.right ? outer?.borderRight ?? null : inside.vertical
|
|
148
|
+
)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function conditionBorderDefaults(base, format, edges) {
|
|
152
|
+
const line = (onEdge, edge, within) => borderLineOfCss(onEdge ? edge : within);
|
|
153
|
+
return {
|
|
154
|
+
top: line(edges.top, format.borders.top, format.inside.horizontal) ?? base.top,
|
|
155
|
+
bottom: line(edges.bottom, format.borders.bottom, format.inside.horizontal) ?? base.bottom,
|
|
156
|
+
left: line(edges.left, format.borders.left, format.inside.vertical) ?? base.left,
|
|
157
|
+
right: line(edges.right, format.borders.right, format.inside.vertical) ?? base.right
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function cellDefaultsFor(rect, grid, sources) {
|
|
161
|
+
const placement = cellPlacement(rect, grid, sources.look, sources.bands);
|
|
162
|
+
const whole = { top: 0, bottom: grid.rows, left: 0, right: grid.cols };
|
|
163
|
+
let borders = tableBorderDefaults(
|
|
164
|
+
edgesIn(rect, whole),
|
|
165
|
+
sources.outer,
|
|
166
|
+
sources.inside
|
|
167
|
+
);
|
|
168
|
+
let margins = sources.margins;
|
|
169
|
+
let background = null;
|
|
170
|
+
let verticalAlign = null;
|
|
171
|
+
for (const { type, region } of placement.conditions) {
|
|
172
|
+
const format = sources.conditions[type];
|
|
173
|
+
if (!format) continue;
|
|
174
|
+
borders = conditionBorderDefaults(borders, format, edgesIn(rect, region));
|
|
175
|
+
margins = layerCellMargins(margins, format.margins);
|
|
176
|
+
background = format.background ?? background;
|
|
177
|
+
verticalAlign = format.verticalAlign ?? verticalAlign;
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
borders,
|
|
181
|
+
margins,
|
|
182
|
+
background,
|
|
183
|
+
verticalAlign,
|
|
184
|
+
placement: {
|
|
185
|
+
tableStyleId: sources.styleId,
|
|
186
|
+
conditions: placement.conditions.map((condition) => condition.type)
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export {
|
|
191
|
+
NO_CELL_SOURCES,
|
|
192
|
+
NO_LOOK,
|
|
193
|
+
cellDefaultsFor,
|
|
194
|
+
cellPlacement,
|
|
195
|
+
readTableLook
|
|
196
|
+
};
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* only the child and attributes named by the edit are changed, and unsupported formatting stays
|
|
4
4
|
* untouched.
|
|
5
5
|
*/
|
|
6
|
-
import type { CellFormat,
|
|
7
|
-
import { type
|
|
6
|
+
import type { CellFormat, CellVerticalAlign, RowFormat } from "../../model/format";
|
|
7
|
+
import { type CellDefaults } from "./reading";
|
|
8
8
|
export interface CellProps {
|
|
9
9
|
/** The whole `<w:tcPr>...</w:tcPr>` XML. null for a cell with no formatting */
|
|
10
10
|
tcPr: string | null;
|
|
@@ -45,11 +45,15 @@ export type CellFormatEdit =
|
|
|
45
45
|
/**
|
|
46
46
|
* The cell formatting XML and display values after one piece of formatting is changed.
|
|
47
47
|
*
|
|
48
|
+
* What the cell falls back on is what an edit measures against: switching a side off writes `none`
|
|
49
|
+
* over whatever line was being drawn there, and recoloring writes only where a line is drawn at
|
|
50
|
+
* all, so the defaults handed in have to be the ones the cell is actually drawn with.
|
|
51
|
+
*
|
|
48
52
|
* null when there is nothing to do: a fragment whose shape could not be made out, a value that
|
|
49
53
|
* cannot be written into the document, or a cell already in the state the job wants. In every one
|
|
50
54
|
* of those cases the cell keeps its original XML.
|
|
51
55
|
*/
|
|
52
|
-
export declare function editCellProps(tcPr: string | null, edit: CellFormatEdit, defaults?:
|
|
56
|
+
export declare function editCellProps(tcPr: string | null, edit: CellFormatEdit, defaults?: CellDefaults): CellProps | null;
|
|
53
57
|
/**
|
|
54
58
|
* Whether the cell's own formatting draws a line on any side.
|
|
55
59
|
* A border color has nothing to act on when it does not, because the lines on screen are then the
|
|
@@ -19,8 +19,7 @@ import {
|
|
|
19
19
|
} from "../../ooxml/simpleTypes.js";
|
|
20
20
|
import { normalizeHex } from "../../ooxml/units.js";
|
|
21
21
|
import {
|
|
22
|
-
|
|
23
|
-
NO_CELL_MARGINS,
|
|
22
|
+
NO_CELL_DEFAULTS,
|
|
24
23
|
readCellProps,
|
|
25
24
|
readRowFormat
|
|
26
25
|
} from "./reading.js";
|
|
@@ -212,15 +211,15 @@ function editedProps(edit, props, defaults) {
|
|
|
212
211
|
return editedPadding(props, edit.values);
|
|
213
212
|
}
|
|
214
213
|
}
|
|
215
|
-
function editCellProps(tcPr, edit, defaults =
|
|
214
|
+
function editCellProps(tcPr, edit, defaults = NO_CELL_DEFAULTS) {
|
|
216
215
|
const props = tcPr === null ? EMPTY_TC_PR : parseProps(tcPr);
|
|
217
216
|
if (!props) return null;
|
|
218
|
-
const edited = editedProps(edit, props, defaults);
|
|
217
|
+
const edited = editedProps(edit, props, defaults.borders);
|
|
219
218
|
if (!edited) return null;
|
|
220
219
|
const rendered = renderProps(edited);
|
|
221
220
|
const next = rendered === "" ? null : rendered;
|
|
222
221
|
if (next === tcPr) return null;
|
|
223
|
-
return { tcPr: next, format: readCellProps(next, defaults
|
|
222
|
+
return { tcPr: next, format: readCellProps(next, defaults) };
|
|
224
223
|
}
|
|
225
224
|
function drawsOwnCellBorder(tcPr) {
|
|
226
225
|
const props = tcPr === null ? null : parseProps(tcPr);
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Reads table formatting for display. Table borders and margins are folded into cell values so CSS
|
|
3
3
|
* collapsed-border resolution matches OOXML inheritance without changing exported XML.
|
|
4
4
|
*/
|
|
5
|
-
import type
|
|
5
|
+
import { type BandSizes, type CellFormat, type CellMargins, type CellStyleFormat, type CellVerticalAlign, type InsideBorders, type RowFormat, type RunFormat, type TableFormat, type TableStyleConditions, type TableStyleOverrideType, type TableWidth } from "../../model/format";
|
|
6
6
|
import { type BorderLine } from "../../ooxml/units";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export type
|
|
7
|
+
import type { ParagraphPlacement } from "../formatting/resolve";
|
|
8
|
+
import type { ParagraphFormatLayer } from "../formatting/tabStops";
|
|
9
|
+
export type { BandSizes, CellMargins, CellStyleFormat, InsideBorders, TableStyleConditions, TableStyleOverrideType, } from "../../model/format";
|
|
10
10
|
/**
|
|
11
11
|
* The width written down by `<w:tblW>` or `<w:tcW>`.
|
|
12
12
|
* If we cannot make out what it means it is null, and such a width goes back out unchanged on export.
|
|
@@ -50,7 +50,7 @@ export interface CellBorderDefaults {
|
|
|
50
50
|
right: BorderLine | null;
|
|
51
51
|
}
|
|
52
52
|
export declare const NO_BORDER_DEFAULTS: CellBorderDefaults;
|
|
53
|
-
/** Which sides of a cell lie on the edge of the table
|
|
53
|
+
/** Which sides of a cell lie on the edge of a part of the table */
|
|
54
54
|
export interface GridEdges {
|
|
55
55
|
top: boolean;
|
|
56
56
|
bottom: boolean;
|
|
@@ -69,19 +69,48 @@ export interface GridSize {
|
|
|
69
69
|
cols: number;
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
72
|
+
* Everything one cell falls back on for what it did not write down itself, worked out from where
|
|
73
|
+
* it sits in the grid by `cellDefaultsFor`. Everything that reads or edits a cell takes one of
|
|
74
|
+
* these, so the lines a cell draws and the lines an edit measures against are always the same.
|
|
75
75
|
*/
|
|
76
|
-
export
|
|
76
|
+
export interface CellDefaults {
|
|
77
|
+
borders: CellBorderDefaults;
|
|
78
|
+
margins: CellMargins;
|
|
79
|
+
background: string | null;
|
|
80
|
+
verticalAlign: CellVerticalAlign | null;
|
|
81
|
+
/** What the paragraphs inside the cell resolve their formatting against */
|
|
82
|
+
placement: ParagraphPlacement;
|
|
83
|
+
}
|
|
84
|
+
export declare const NO_CELL_DEFAULTS: CellDefaults;
|
|
85
|
+
export declare const NO_BAND_SIZES: BandSizes;
|
|
86
|
+
/** How many rows and columns one band of a table style is made of, read from the style's `w:tblPr` */
|
|
87
|
+
export declare function readBandSizes(tblPr: Element | null): BandSizes;
|
|
88
|
+
export declare function layerBandSizes(style: BandSizes, over: BandSizes): BandSizes;
|
|
89
|
+
export declare const NO_CELL_STYLE_FORMAT: CellStyleFormat;
|
|
90
|
+
export declare const NO_TABLE_STYLE_CONDITIONS: TableStyleConditions;
|
|
91
|
+
/**
|
|
92
|
+
* The formatting one conditional format of a table style lays down (`w:tblStylePr`).
|
|
93
|
+
* Its `w:tblPr` speaks for the region as a whole and its `w:tcPr` for the cells in it, so the
|
|
94
|
+
* cell values are read with the table values underneath them.
|
|
95
|
+
*/
|
|
96
|
+
export interface ConditionalTableFormat {
|
|
97
|
+
paragraph: ParagraphFormatLayer;
|
|
98
|
+
run: RunFormat;
|
|
99
|
+
table: TableFormat;
|
|
100
|
+
cell: CellStyleFormat;
|
|
101
|
+
}
|
|
77
102
|
/**
|
|
78
|
-
*
|
|
103
|
+
* What a conditional format lays down for the cells of its region.
|
|
79
104
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
105
|
+
* The `w:tcPr` of a conditional format writes the lines of the region rather than of every cell in
|
|
106
|
+
* it: `insideH` and `insideV` are the lines between the cells it covers, which is how the header
|
|
107
|
+
* row of a style with `insideV` set to `nil` is drawn as one unbroken band.
|
|
83
108
|
*/
|
|
84
|
-
export declare function
|
|
109
|
+
export declare function readCellStyleFormat(tcPr: Element | null, tblPr: Element | null): CellStyleFormat;
|
|
110
|
+
/** Lays one conditional format over the one a style further up the `basedOn` chain wrote for the same part */
|
|
111
|
+
export declare function layerCellStyleFormat(base: CellStyleFormat, over: CellStyleFormat): CellStyleFormat;
|
|
112
|
+
/** The conditions a table style lays down for its cells alone, which is what a table carries for its cells to draw */
|
|
113
|
+
export declare function cellConditionsOf(conditions: Readonly<Partial<Record<TableStyleOverrideType, ConditionalTableFormat>>>): TableStyleConditions;
|
|
85
114
|
/** The name of the table style `w:tblStyle` points at. null if it points at none */
|
|
86
115
|
export declare function tblStyleIdOf(tblPr: Element | null): string | null;
|
|
87
116
|
export declare function readTableFormat(tblPr: Element | null): TableFormat | null;
|
|
@@ -89,11 +118,12 @@ export declare function readRowFormat(trPr: Element | null): RowFormat | null;
|
|
|
89
118
|
/**
|
|
90
119
|
* Cell formatting.
|
|
91
120
|
* A side for which the cell has not written down its own border uses the line that side falls on,
|
|
92
|
-
* which is the table's outer border
|
|
93
|
-
*
|
|
94
|
-
*
|
|
121
|
+
* which is the table's outer border, its inside line, or the line a conditional format of its
|
|
122
|
+
* style draws there, depending on where the cell sits.
|
|
123
|
+
* The fill, the vertical alignment and the padding work the same way: what the cell wrote down
|
|
124
|
+
* wins, and what it falls back on lies underneath. null if there is nothing at all to draw.
|
|
95
125
|
*/
|
|
96
|
-
export declare function readCellFormat(tcPr: Element | null, defaults
|
|
126
|
+
export declare function readCellFormat(tcPr: Element | null, defaults?: CellDefaults): CellFormat | null;
|
|
97
127
|
/** The inside lines the table lays down, read from the table's own formatting XML */
|
|
98
128
|
export declare function insideBordersOf(tblPr: string | null): InsideBorders;
|
|
99
129
|
/** The cell margins the table lays down, read from the table's own formatting XML */
|
|
@@ -102,5 +132,5 @@ export declare function cellMarginsOf(tblPr: string | null): CellMargins;
|
|
|
102
132
|
* Reads a cell formatting fragment we operated on back into display values, along the same path
|
|
103
133
|
* the import takes. The same fragment always yields the same display values.
|
|
104
134
|
*/
|
|
105
|
-
export declare function readCellProps(tcPr: string | null, defaults
|
|
135
|
+
export declare function readCellProps(tcPr: string | null, defaults?: CellDefaults): CellFormat | null;
|
|
106
136
|
/** The formatting a cell holds. The original XML and the display values read out of it form a pair */
|