@stll/folio-core 0.9.0 → 0.10.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/dist/ai-edits/headless.d.ts +52 -12
- package/dist/ai-edits/headless.js +181 -55
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/controller/layoutPipeline.js +2 -1
- package/dist/document-operations.d.ts +11 -2
- package/dist/document-operations.js +7 -1
- package/dist/document-stories.d.ts +10 -0
- package/dist/document-stories.js +27 -0
- package/dist/docx/corePropertiesParser.d.ts +8 -0
- package/dist/docx/corePropertiesParser.js +53 -0
- package/dist/docx/index.d.ts +2 -1
- package/dist/docx/index.js +2 -1
- package/dist/docx/metadataPrivacy.d.ts +40 -0
- package/dist/docx/metadataPrivacy.js +131 -0
- package/dist/docx/parser.js +4 -1
- package/dist/docx/rezip.d.ts +5 -4
- package/dist/docx/rezip.js +6 -8
- package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
- package/dist/layout-engine/measure/cache.js +2 -7
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
- package/dist/layout-engine/measure/measureParagraph.js +32 -36
- package/dist/layout-engine/measure/tableCellFloating.js +39 -33
- package/dist/layout-engine/types.d.ts +5 -3
- package/dist/prosemirror/attrs/index.js +1 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +1 -0
- package/dist/prosemirror/conversion/toProseDoc.js +40 -68
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -1
- package/dist/prosemirror/schema/nodes.d.ts +2 -1
- package/dist/redline.d.ts +26 -11
- package/dist/redline.js +95 -62
- package/dist/server.d.ts +5 -4
- package/dist/server.js +5 -4
- package/dist/version-comparison.d.ts +65 -11
- package/dist/version-comparison.js +187 -29
- package/package.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { findChildByLocalName, getTextContent, parseXmlDocument } from "./xmlParser.js";
|
|
2
|
+
//#region src/docx/corePropertiesParser.ts
|
|
3
|
+
const STRING_PROPERTIES = [
|
|
4
|
+
"title",
|
|
5
|
+
"subject",
|
|
6
|
+
"creator",
|
|
7
|
+
"keywords",
|
|
8
|
+
"description",
|
|
9
|
+
"lastModifiedBy"
|
|
10
|
+
];
|
|
11
|
+
const parseRevision = (value) => {
|
|
12
|
+
if (!/^\d+$/u.test(value)) return;
|
|
13
|
+
const revision = Number(value);
|
|
14
|
+
return Number.isSafeInteger(revision) ? revision : void 0;
|
|
15
|
+
};
|
|
16
|
+
const parseDate = (value) => {
|
|
17
|
+
const timestamp = Date.parse(value);
|
|
18
|
+
return Number.isNaN(timestamp) ? void 0 : new Date(timestamp);
|
|
19
|
+
};
|
|
20
|
+
/** Parse recognized package metadata from `docProps/core.xml`. */
|
|
21
|
+
const parseCoreProperties = (xml) => {
|
|
22
|
+
if (!xml) return;
|
|
23
|
+
const root = parseXmlDocument(xml);
|
|
24
|
+
if (!root) return;
|
|
25
|
+
const properties = {};
|
|
26
|
+
let recognizedPropertyCount = 0;
|
|
27
|
+
for (const property of STRING_PROPERTIES) {
|
|
28
|
+
const element = findChildByLocalName(root, property);
|
|
29
|
+
if (!element) continue;
|
|
30
|
+
properties[property] = getTextContent(element);
|
|
31
|
+
recognizedPropertyCount++;
|
|
32
|
+
}
|
|
33
|
+
const revisionElement = findChildByLocalName(root, "revision");
|
|
34
|
+
if (revisionElement) {
|
|
35
|
+
const revision = parseRevision(getTextContent(revisionElement));
|
|
36
|
+
if (revision !== void 0) {
|
|
37
|
+
properties.revision = revision;
|
|
38
|
+
recognizedPropertyCount++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const property of ["created", "modified"]) {
|
|
42
|
+
const element = findChildByLocalName(root, property);
|
|
43
|
+
if (!element) continue;
|
|
44
|
+
const date = parseDate(getTextContent(element));
|
|
45
|
+
if (date !== void 0) {
|
|
46
|
+
properties[property] = date;
|
|
47
|
+
recognizedPropertyCount++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return recognizedPropertyCount > 0 ? properties : void 0;
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
export { parseCoreProperties };
|
package/dist/docx/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCachedNumberingMap } from "./numberingParser.js";
|
|
2
2
|
import { DOCX_ENCRYPTION_ERROR_CODES, DocxEncryptionError, DocxEncryptionErrorCode, isDocxEncryptionError } from "./encryption/errors.js";
|
|
3
3
|
import { DecryptDocxOptions, DecryptDocxResult, decryptDocxIfNeeded, openDocxBuffer } from "./encryption/openEncryptedDocx.js";
|
|
4
|
-
|
|
4
|
+
import { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentMetadataProperty, FolioDocumentPrivacyArchiveError, FolioDocumentPrivacyOptions, FolioDocumentPrivacyReport, FolioDocumentPrivacyTransform, InvalidFolioDocumentPrivacyOptionsError, RewriteDocxMetadataPrivacyResult, isFolioDocumentPrivacyTransform, rewriteDocxMetadataPrivacy } from "./metadataPrivacy.js";
|
|
5
|
+
export { DOCX_ENCRYPTION_ERROR_CODES, type DecryptDocxOptions, type DecryptDocxResult, DocxEncryptionError, type DocxEncryptionErrorCode, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, type FolioDocumentMetadataProperty, FolioDocumentPrivacyArchiveError, type FolioDocumentPrivacyOptions, type FolioDocumentPrivacyReport, type FolioDocumentPrivacyTransform, InvalidFolioDocumentPrivacyOptionsError, type RewriteDocxMetadataPrivacyResult, decryptDocxIfNeeded, getCachedNumberingMap, isDocxEncryptionError, isFolioDocumentPrivacyTransform, openDocxBuffer, rewriteDocxMetadataPrivacy };
|
package/dist/docx/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCachedNumberingMap } from "./numberingParser.js";
|
|
2
2
|
import { DOCX_ENCRYPTION_ERROR_CODES, DocxEncryptionError, isDocxEncryptionError } from "./encryption/errors.js";
|
|
3
3
|
import { decryptDocxIfNeeded, openDocxBuffer } from "./encryption/openEncryptedDocx.js";
|
|
4
|
-
|
|
4
|
+
import { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentPrivacyArchiveError, InvalidFolioDocumentPrivacyOptionsError, isFolioDocumentPrivacyTransform, rewriteDocxMetadataPrivacy } from "./metadataPrivacy.js";
|
|
5
|
+
export { DOCX_ENCRYPTION_ERROR_CODES, DocxEncryptionError, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentPrivacyArchiveError, InvalidFolioDocumentPrivacyOptionsError, decryptDocxIfNeeded, getCachedNumberingMap, isDocxEncryptionError, isFolioDocumentPrivacyTransform, openDocxBuffer, rewriteDocxMetadataPrivacy };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/docx/metadataPrivacy.d.ts
|
|
2
|
+
declare const FOLIO_DOCUMENT_METADATA_PROPERTIES: readonly ["title", "subject", "creator", "keywords", "description", "lastModifiedBy", "revision", "created", "modified"];
|
|
3
|
+
type FolioDocumentMetadataProperty = (typeof FOLIO_DOCUMENT_METADATA_PROPERTIES)[number];
|
|
4
|
+
declare const FOLIO_DOCUMENT_PRIVACY_TRANSFORMS: readonly ["remove-attribution", "remove-timestamps", "remove-descriptive-metadata"];
|
|
5
|
+
type FolioDocumentPrivacyTransform = (typeof FOLIO_DOCUMENT_PRIVACY_TRANSFORMS)[number];
|
|
6
|
+
declare const isFolioDocumentPrivacyTransform: (value: unknown) => value is FolioDocumentPrivacyTransform;
|
|
7
|
+
type FolioDocumentPrivacyOptions = {
|
|
8
|
+
transforms: readonly FolioDocumentPrivacyTransform[];
|
|
9
|
+
};
|
|
10
|
+
type FolioDocumentPrivacyReport = {
|
|
11
|
+
appliedTransforms: FolioDocumentPrivacyTransform[];
|
|
12
|
+
removedMetadataProperties: FolioDocumentMetadataProperty[];
|
|
13
|
+
};
|
|
14
|
+
type RewriteDocxMetadataPrivacyResult = {
|
|
15
|
+
buffer: ArrayBuffer;
|
|
16
|
+
privacyReport: FolioDocumentPrivacyReport;
|
|
17
|
+
};
|
|
18
|
+
declare const InvalidFolioDocumentPrivacyOptionsError_base: import("better-result").TaggedErrorClass<"InvalidFolioDocumentPrivacyOptionsError", {
|
|
19
|
+
message: string;
|
|
20
|
+
receivedValue: unknown;
|
|
21
|
+
}>;
|
|
22
|
+
declare class InvalidFolioDocumentPrivacyOptionsError extends InvalidFolioDocumentPrivacyOptionsError_base {}
|
|
23
|
+
declare const FolioDocumentPrivacyArchiveError_base: import("better-result").TaggedErrorClass<"FolioDocumentPrivacyArchiveError", {
|
|
24
|
+
message: string;
|
|
25
|
+
reason: "input-too-large" | "load-failed" | "too-many-entries" | "core-properties-too-large";
|
|
26
|
+
cause?: unknown;
|
|
27
|
+
}>;
|
|
28
|
+
declare class FolioDocumentPrivacyArchiveError extends FolioDocumentPrivacyArchiveError_base {}
|
|
29
|
+
declare const PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM: {
|
|
30
|
+
readonly "remove-attribution": readonly ["creator", "lastModifiedBy"];
|
|
31
|
+
readonly "remove-timestamps": readonly ["created", "modified"];
|
|
32
|
+
readonly "remove-descriptive-metadata": readonly ["title", "subject", "keywords", "description"];
|
|
33
|
+
};
|
|
34
|
+
declare const resolveFolioDocumentPrivacyTransforms: (transforms: unknown) => FolioDocumentPrivacyTransform[];
|
|
35
|
+
/** Rewrite selected package metadata fields without changing other package parts. */
|
|
36
|
+
declare const rewriteDocxMetadataPrivacy: (buffer: ArrayBuffer, {
|
|
37
|
+
transforms
|
|
38
|
+
}: FolioDocumentPrivacyOptions) => Promise<RewriteDocxMetadataPrivacyResult>;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentMetadataProperty, FolioDocumentPrivacyArchiveError, FolioDocumentPrivacyOptions, FolioDocumentPrivacyReport, FolioDocumentPrivacyTransform, InvalidFolioDocumentPrivacyOptionsError, PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM, RewriteDocxMetadataPrivacyResult, isFolioDocumentPrivacyTransform, resolveFolioDocumentPrivacyTransforms, rewriteDocxMetadataPrivacy };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { elementToXml, getLocalName, parseXmlDocument } from "./xmlParser.js";
|
|
2
|
+
import { TaggedError } from "better-result";
|
|
3
|
+
import JSZip from "jszip";
|
|
4
|
+
//#region src/docx/metadataPrivacy.ts
|
|
5
|
+
const FOLIO_DOCUMENT_METADATA_PROPERTIES = Object.freeze([
|
|
6
|
+
"title",
|
|
7
|
+
"subject",
|
|
8
|
+
"creator",
|
|
9
|
+
"keywords",
|
|
10
|
+
"description",
|
|
11
|
+
"lastModifiedBy",
|
|
12
|
+
"revision",
|
|
13
|
+
"created",
|
|
14
|
+
"modified"
|
|
15
|
+
]);
|
|
16
|
+
const isFolioDocumentMetadataProperty = (value) => FOLIO_DOCUMENT_METADATA_PROPERTIES.some((property) => property === value);
|
|
17
|
+
const FOLIO_DOCUMENT_PRIVACY_TRANSFORMS = Object.freeze([
|
|
18
|
+
"remove-attribution",
|
|
19
|
+
"remove-timestamps",
|
|
20
|
+
"remove-descriptive-metadata"
|
|
21
|
+
]);
|
|
22
|
+
const isFolioDocumentPrivacyTransform = (value) => FOLIO_DOCUMENT_PRIVACY_TRANSFORMS.some((transform) => transform === value);
|
|
23
|
+
var InvalidFolioDocumentPrivacyOptionsError = class extends TaggedError("InvalidFolioDocumentPrivacyOptionsError")() {};
|
|
24
|
+
var FolioDocumentPrivacyArchiveError = class extends TaggedError("FolioDocumentPrivacyArchiveError")() {};
|
|
25
|
+
const PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM = {
|
|
26
|
+
"remove-attribution": ["creator", "lastModifiedBy"],
|
|
27
|
+
"remove-timestamps": ["created", "modified"],
|
|
28
|
+
"remove-descriptive-metadata": [
|
|
29
|
+
"title",
|
|
30
|
+
"subject",
|
|
31
|
+
"keywords",
|
|
32
|
+
"description"
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
const resolveFolioDocumentPrivacyTransforms = (transforms) => {
|
|
36
|
+
if (!Array.isArray(transforms) || transforms.some((transform) => !isFolioDocumentPrivacyTransform(transform))) throw new InvalidFolioDocumentPrivacyOptionsError({
|
|
37
|
+
message: "Document privacy received an unrecognized transform",
|
|
38
|
+
receivedValue: transforms
|
|
39
|
+
});
|
|
40
|
+
const requested = new Set(transforms);
|
|
41
|
+
return FOLIO_DOCUMENT_PRIVACY_TRANSFORMS.filter((transform) => requested.has(transform));
|
|
42
|
+
};
|
|
43
|
+
const XML_DECLARATION_PATTERN = /^\s*<\?xml[^?]*\?>/u;
|
|
44
|
+
const MAX_INPUT_BYTES = 50 * 1024 * 1024;
|
|
45
|
+
const MAX_ARCHIVE_ENTRIES = 5e3;
|
|
46
|
+
const MAX_CORE_PROPERTIES_BYTES = 1024 * 1024;
|
|
47
|
+
const loadPrivacyArchive = async (buffer) => {
|
|
48
|
+
if (buffer.byteLength > MAX_INPUT_BYTES) throw new FolioDocumentPrivacyArchiveError({
|
|
49
|
+
message: "Document privacy input exceeded the compressed-size limit",
|
|
50
|
+
reason: "input-too-large"
|
|
51
|
+
});
|
|
52
|
+
let zip;
|
|
53
|
+
try {
|
|
54
|
+
zip = await JSZip.loadAsync(buffer);
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
throw new FolioDocumentPrivacyArchiveError({
|
|
57
|
+
message: "Document privacy input is not a readable package",
|
|
58
|
+
reason: "load-failed",
|
|
59
|
+
cause
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (Object.values(zip.files).length > MAX_ARCHIVE_ENTRIES) throw new FolioDocumentPrivacyArchiveError({
|
|
63
|
+
message: "Document privacy input exceeded the package-entry limit",
|
|
64
|
+
reason: "too-many-entries"
|
|
65
|
+
});
|
|
66
|
+
const coreProperties = zip.file("docProps/core.xml");
|
|
67
|
+
if (!coreProperties) return zip;
|
|
68
|
+
const data = "_data" in coreProperties ? coreProperties._data : void 0;
|
|
69
|
+
const declaredBytes = typeof data === "object" && data !== null && "uncompressedSize" in data ? data.uncompressedSize : void 0;
|
|
70
|
+
if (typeof declaredBytes !== "number" || declaredBytes > MAX_CORE_PROPERTIES_BYTES) throw new FolioDocumentPrivacyArchiveError({
|
|
71
|
+
message: "Document privacy core properties exceeded the part-size limit",
|
|
72
|
+
reason: "core-properties-too-large"
|
|
73
|
+
});
|
|
74
|
+
return zip;
|
|
75
|
+
};
|
|
76
|
+
const rewriteCorePropertiesPrivacy = (xml, transforms) => {
|
|
77
|
+
const root = parseXmlDocument(xml);
|
|
78
|
+
if (!root?.elements) return {
|
|
79
|
+
xml,
|
|
80
|
+
removedMetadataProperties: []
|
|
81
|
+
};
|
|
82
|
+
const propertiesToRemove = /* @__PURE__ */ new Set();
|
|
83
|
+
for (const transform of transforms) for (const property of PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM[transform]) propertiesToRemove.add(property);
|
|
84
|
+
const removedPropertySet = /* @__PURE__ */ new Set();
|
|
85
|
+
root.elements = root.elements.filter((element) => {
|
|
86
|
+
if (element.type !== "element") return true;
|
|
87
|
+
const property = getLocalName(element.name ?? "");
|
|
88
|
+
if (!isFolioDocumentMetadataProperty(property)) return true;
|
|
89
|
+
if (!propertiesToRemove.has(property)) return true;
|
|
90
|
+
removedPropertySet.add(property);
|
|
91
|
+
return false;
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
xml: `${xml.match(XML_DECLARATION_PATTERN)?.at(0) ?? ""}${elementToXml(root)}`,
|
|
95
|
+
removedMetadataProperties: FOLIO_DOCUMENT_METADATA_PROPERTIES.filter((property) => removedPropertySet.has(property))
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
/** Rewrite selected package metadata fields without changing other package parts. */
|
|
99
|
+
const rewriteDocxMetadataPrivacy = async (buffer, { transforms }) => {
|
|
100
|
+
const appliedTransforms = resolveFolioDocumentPrivacyTransforms(transforms);
|
|
101
|
+
const zip = await loadPrivacyArchive(buffer);
|
|
102
|
+
const coreProperties = zip.file("docProps/core.xml");
|
|
103
|
+
if (!coreProperties) return {
|
|
104
|
+
buffer,
|
|
105
|
+
privacyReport: {
|
|
106
|
+
appliedTransforms,
|
|
107
|
+
removedMetadataProperties: []
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const rewritten = rewriteCorePropertiesPrivacy(await coreProperties.async("text"), appliedTransforms);
|
|
111
|
+
if (rewritten.removedMetadataProperties.length === 0) return {
|
|
112
|
+
buffer,
|
|
113
|
+
privacyReport: {
|
|
114
|
+
appliedTransforms,
|
|
115
|
+
removedMetadataProperties: []
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
zip.file("docProps/core.xml", rewritten.xml);
|
|
119
|
+
return {
|
|
120
|
+
buffer: await zip.generateAsync({
|
|
121
|
+
type: "arraybuffer",
|
|
122
|
+
compression: "DEFLATE"
|
|
123
|
+
}),
|
|
124
|
+
privacyReport: {
|
|
125
|
+
appliedTransforms,
|
|
126
|
+
removedMetadataProperties: rewritten.removedMetadataProperties
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
export { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentPrivacyArchiveError, InvalidFolioDocumentPrivacyOptionsError, PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM, isFolioDocumentPrivacyTransform, resolveFolioDocumentPrivacyTransforms, rewriteDocxMetadataPrivacy };
|
package/dist/docx/parser.js
CHANGED
|
@@ -6,6 +6,7 @@ import { applyThemeFontLang, parseTheme } from "./themeParser.js";
|
|
|
6
6
|
import { parseComments } from "./commentParser.js";
|
|
7
7
|
import { normalizeCommentReferences } from "./commentReferenceNormalization.js";
|
|
8
8
|
import { detectDocxConformanceClass } from "./conformance.js";
|
|
9
|
+
import { parseCoreProperties } from "./corePropertiesParser.js";
|
|
9
10
|
import { parseNumbering } from "./numberingParser.js";
|
|
10
11
|
import { extractAllTemplateVariables, parseDocumentBody } from "./documentParser.js";
|
|
11
12
|
import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
|
|
@@ -168,6 +169,7 @@ async function parseDocx(input, options = {}) {
|
|
|
168
169
|
onProgress("Loaded fonts", 95);
|
|
169
170
|
} else onProgress("Skipping font loading", 95);
|
|
170
171
|
onProgress("Assembling document...", 95);
|
|
172
|
+
const properties = timeStage("coreProperties", () => parseCoreProperties(raw.corePropsXml));
|
|
171
173
|
const document = {
|
|
172
174
|
package: {
|
|
173
175
|
conformanceClass: detectDocxConformanceClass(raw.documentXml),
|
|
@@ -182,7 +184,8 @@ async function parseDocx(input, options = {}) {
|
|
|
182
184
|
...footnotes !== void 0 ? { footnotes } : {},
|
|
183
185
|
...endnotes !== void 0 ? { endnotes } : {},
|
|
184
186
|
relationships: rels,
|
|
185
|
-
media
|
|
187
|
+
media,
|
|
188
|
+
...properties !== void 0 ? { properties } : {}
|
|
186
189
|
},
|
|
187
190
|
originalBuffer: raw.originalBuffer,
|
|
188
191
|
...templateVariables !== void 0 ? { templateVariables } : {},
|
package/dist/docx/rezip.d.ts
CHANGED
|
@@ -124,10 +124,11 @@ declare function hasUnmaterializedHeaderFooter(doc: document_d_exports.Document)
|
|
|
124
124
|
*/
|
|
125
125
|
declare function hasModelDrivenPictureWatermark(doc: document_d_exports.Document): boolean;
|
|
126
126
|
declare function collectHeaderFooterUpdates(doc: document_d_exports.Document): Map<string, string>;
|
|
127
|
-
/**
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
/** Update existing core-property values without synthesizing absent metadata. */
|
|
128
|
+
declare function updateCoreProperties(corePropsXml: string, {
|
|
129
|
+
updateModifiedDate,
|
|
130
|
+
modifiedBy
|
|
131
|
+
}: {
|
|
131
132
|
updateModifiedDate?: boolean;
|
|
132
133
|
modifiedBy?: string;
|
|
133
134
|
}): string;
|
package/dist/docx/rezip.js
CHANGED
|
@@ -1022,18 +1022,16 @@ function findNotePartEntry(zip, conventionalLowerPath) {
|
|
|
1022
1022
|
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.toLowerCase() === conventionalLowerPath) return file;
|
|
1023
1023
|
return null;
|
|
1024
1024
|
}
|
|
1025
|
-
/**
|
|
1026
|
-
|
|
1027
|
-
*/
|
|
1028
|
-
function updateCoreProperties(corePropsXml, options) {
|
|
1025
|
+
/** Update existing core-property values without synthesizing absent metadata. */
|
|
1026
|
+
function updateCoreProperties(corePropsXml, { updateModifiedDate, modifiedBy }) {
|
|
1029
1027
|
let result = corePropsXml;
|
|
1030
|
-
if (
|
|
1028
|
+
if (updateModifiedDate) {
|
|
1031
1029
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1032
1030
|
if (result.includes("<dcterms:modified")) result = result.replace(/<dcterms:modified[^<>]*>[^<]*<\/dcterms:modified>/u, `<dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified>`);
|
|
1033
|
-
else result = result.replace("</cp:coreProperties>", `<dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified></cp:coreProperties>`);
|
|
1034
1031
|
}
|
|
1035
|
-
if (
|
|
1036
|
-
|
|
1032
|
+
if (modifiedBy) {
|
|
1033
|
+
if (result.includes("<cp:lastModifiedBy")) result = result.replace(/<cp:lastModifiedBy>[^<]*<\/cp:lastModifiedBy>/u, `<cp:lastModifiedBy>${escapeXml(modifiedBy)}</cp:lastModifiedBy>`);
|
|
1034
|
+
}
|
|
1037
1035
|
return result;
|
|
1038
1036
|
}
|
|
1039
1037
|
/**
|
|
@@ -445,6 +445,7 @@ function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange) {
|
|
|
445
445
|
if (attrs.cropBottom != null) run.cropBottom = attrs.cropBottom;
|
|
446
446
|
if (attrs.cropLeft != null) run.cropLeft = attrs.cropLeft;
|
|
447
447
|
if (attrs.position !== void 0) run.position = attrs.position;
|
|
448
|
+
if (attrs.layoutInCell !== void 0) run.layoutInCell = attrs.layoutInCell;
|
|
448
449
|
if (trackedChange?.isInsertion) run.isInsertion = true;
|
|
449
450
|
if (trackedChange?.isDeletion) run.isDeletion = true;
|
|
450
451
|
if (trackedChange?.changeAuthor !== void 0) run.changeAuthor = trackedChange.changeAuthor;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
|
|
2
|
+
import { lineBreakPolicyCacheParts } from "./effectiveLineBreakPolicy.js";
|
|
2
3
|
//#region src/layout-engine/measure/cache.ts
|
|
3
4
|
/**
|
|
4
5
|
* Current max size for text width cache
|
|
@@ -168,13 +169,7 @@ function hashParagraphBlock(block) {
|
|
|
168
169
|
if (attrs.defaultFontFamily != null) parts.push(`dff:${attrs.defaultFontFamily}`);
|
|
169
170
|
if (attrs.suppressEmptyParagraphHeight) parts.push("sup");
|
|
170
171
|
if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
|
|
171
|
-
|
|
172
|
-
if (attrs.overflowPunctuation !== void 0) parts.push(`overflow-punct:${attrs.overflowPunctuation}`);
|
|
173
|
-
if (attrs.suppressAutoHyphens !== void 0) parts.push(`suppress-auto-hyphens:${attrs.suppressAutoHyphens}`);
|
|
174
|
-
const automaticHyphenation = attrs.automaticHyphenation;
|
|
175
|
-
if (automaticHyphenation) parts.push(`auto-hyphens:${automaticHyphenation.doNotHyphenateCaps}|${automaticHyphenation.consecutiveLineLimit}`);
|
|
176
|
-
const lineBreakRules = attrs.lineBreakRules;
|
|
177
|
-
if (lineBreakRules) parts.push(`line-break-rules:${lineBreakRules.noLineBreaksBefore?.language}|${lineBreakRules.noLineBreaksBefore?.characters}|${lineBreakRules.noLineBreaksAfter?.language}|${lineBreakRules.noLineBreaksAfter?.characters}|${lineBreakRules.useLegacyEthiopicAmharicRules}`);
|
|
172
|
+
parts.push(...lineBreakPolicyCacheParts(attrs));
|
|
178
173
|
const borders = attrs.borders;
|
|
179
174
|
if (borders) {
|
|
180
175
|
const signature = (border) => border ? `${border.width ?? ""},${border.style ?? ""},${border.color ?? ""}` : "";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ParagraphAttrs, TextRun } from "../types.js";
|
|
2
|
+
import { LineBreakPolicy } from "./lineBreakProvider.js";
|
|
3
|
+
|
|
4
|
+
//#region src/layout-engine/measure/effectiveLineBreakPolicy.d.ts
|
|
5
|
+
type EffectiveAutomaticHyphenation = Readonly<{
|
|
6
|
+
type: "disabled";
|
|
7
|
+
}> | Readonly<{
|
|
8
|
+
type: "enabled";
|
|
9
|
+
consecutiveLineLimit: number;
|
|
10
|
+
hyphenationZoneTwips: number;
|
|
11
|
+
}>;
|
|
12
|
+
type EffectiveLineBreakPolicy = Readonly<{
|
|
13
|
+
/** Exact provider inputs; omitted OOXML values stay omitted for custom providers. */provider: Readonly<LineBreakPolicy>; /** `w:overflowPunct` defaults to enabled when omitted. */
|
|
14
|
+
hangingPunctuation: boolean; /** Effective document and paragraph automatic-hyphenation state. */
|
|
15
|
+
automaticHyphenation: EffectiveAutomaticHyphenation;
|
|
16
|
+
}>;
|
|
17
|
+
type ResolveEffectiveLineBreakPolicyOptions = {
|
|
18
|
+
attrs: ParagraphAttrs | undefined;
|
|
19
|
+
run: TextRun;
|
|
20
|
+
};
|
|
21
|
+
/** Resolve all authored line-breaking inputs used while measuring one text run. */
|
|
22
|
+
declare const resolveEffectiveLineBreakPolicy: ({
|
|
23
|
+
attrs,
|
|
24
|
+
run
|
|
25
|
+
}: ResolveEffectiveLineBreakPolicyOptions) => EffectiveLineBreakPolicy;
|
|
26
|
+
/** Cache fragments for every paragraph attribute consumed by the resolver. */
|
|
27
|
+
declare const lineBreakPolicyCacheParts: (attrs: ParagraphAttrs) => readonly string[];
|
|
28
|
+
//#endregion
|
|
29
|
+
export { lineBreakPolicyCacheParts, resolveEffectiveLineBreakPolicy };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { hasCjk } from "../../utils/scriptSegments.js";
|
|
2
|
+
//#region src/layout-engine/measure/effectiveLineBreakPolicy.ts
|
|
3
|
+
const DEFAULT_HYPHENATION_ZONE_TWIPS = 360;
|
|
4
|
+
/** Resolve all authored line-breaking inputs used while measuring one text run. */
|
|
5
|
+
const resolveEffectiveLineBreakPolicy = ({ attrs, run }) => {
|
|
6
|
+
const locale = resolveRunLocale(run);
|
|
7
|
+
const rules = attrs?.lineBreakRules;
|
|
8
|
+
const before = rules?.noLineBreaksBefore;
|
|
9
|
+
const after = rules?.noLineBreaksAfter;
|
|
10
|
+
const automaticHyphenation = attrs?.automaticHyphenation;
|
|
11
|
+
return {
|
|
12
|
+
provider: {
|
|
13
|
+
...locale ? { locale } : {},
|
|
14
|
+
...attrs?.kinsoku !== void 0 ? { kinsoku: attrs.kinsoku } : {},
|
|
15
|
+
...before && languageMatches(before.language, locale) ? { noLineBreaksBefore: before.characters } : {},
|
|
16
|
+
...after && languageMatches(after.language, locale) ? { noLineBreaksAfter: after.characters } : {},
|
|
17
|
+
...rules?.useLegacyEthiopicAmharicRules ? { useLegacyEthiopicAmharicRules: true } : {},
|
|
18
|
+
...automaticHyphenation?.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: automaticHyphenation.doNotHyphenateCaps } : {},
|
|
19
|
+
...run.allCaps === true ? { renderedAllCaps: true } : {}
|
|
20
|
+
},
|
|
21
|
+
hangingPunctuation: attrs?.overflowPunctuation !== false,
|
|
22
|
+
automaticHyphenation: resolveAutomaticHyphenation(attrs)
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** Cache fragments for every paragraph attribute consumed by the resolver. */
|
|
26
|
+
const lineBreakPolicyCacheParts = (attrs) => {
|
|
27
|
+
const parts = [];
|
|
28
|
+
if (attrs.kinsoku !== void 0) parts.push(`kinsoku:${attrs.kinsoku}`);
|
|
29
|
+
if (attrs.overflowPunctuation !== void 0) parts.push(`overflow-punct:${attrs.overflowPunctuation}`);
|
|
30
|
+
if (attrs.suppressAutoHyphens !== void 0) parts.push(`suppress-auto-hyphens:${attrs.suppressAutoHyphens}`);
|
|
31
|
+
const automaticHyphenation = attrs.automaticHyphenation;
|
|
32
|
+
if (automaticHyphenation) parts.push(`auto-hyphens:${automaticHyphenation.doNotHyphenateCaps}|${automaticHyphenation.consecutiveLineLimit}|${automaticHyphenation.hyphenationZoneTwips}`);
|
|
33
|
+
const rules = attrs.lineBreakRules;
|
|
34
|
+
if (rules) parts.push(`line-break-rules:${rules.noLineBreaksBefore?.language}|${rules.noLineBreaksBefore?.characters}|${rules.noLineBreaksAfter?.language}|${rules.noLineBreaksAfter?.characters}|${rules.useLegacyEthiopicAmharicRules}`);
|
|
35
|
+
return parts;
|
|
36
|
+
};
|
|
37
|
+
const resolveAutomaticHyphenation = (attrs) => {
|
|
38
|
+
const automaticHyphenation = attrs?.automaticHyphenation;
|
|
39
|
+
if (automaticHyphenation?.enabled !== true || attrs?.suppressAutoHyphens === true) return { type: "disabled" };
|
|
40
|
+
return {
|
|
41
|
+
type: "enabled",
|
|
42
|
+
consecutiveLineLimit: automaticHyphenation.consecutiveLineLimit ?? 0,
|
|
43
|
+
hyphenationZoneTwips: automaticHyphenation.hyphenationZoneTwips ?? DEFAULT_HYPHENATION_ZONE_TWIPS
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
const resolveRunLocale = (run) => {
|
|
47
|
+
const language = run.language;
|
|
48
|
+
if (hasCjk(run.text)) return language?.eastAsia ?? language?.val;
|
|
49
|
+
if (run.rtl) return language?.bidi ?? language?.val;
|
|
50
|
+
return language?.val;
|
|
51
|
+
};
|
|
52
|
+
const languageMatches = (ruleLanguage, locale) => {
|
|
53
|
+
if (!ruleLanguage) return true;
|
|
54
|
+
if (!locale) return false;
|
|
55
|
+
const rule = ruleLanguage.toLowerCase();
|
|
56
|
+
const active = locale.toLowerCase();
|
|
57
|
+
return active === rule || active.startsWith(`${rule}-`) || rule.startsWith(`${active}-`);
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
export { lineBreakPolicyCacheParts, resolveEffectiveLineBreakPolicy };
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
|
|
2
2
|
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
3
3
|
import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
4
|
-
import { buildRunFontStyle, ptToPx } from "./measureHelpers.js";
|
|
4
|
+
import { buildRunFontStyle, ptToPx, twipsToPx } from "./measureHelpers.js";
|
|
5
5
|
import "./lineBreakProvider.js";
|
|
6
6
|
import { calculateTabWidth, pixelsToTwips } from "../../prosemirror/utils/tabCalculator.js";
|
|
7
7
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
8
8
|
import { hasCjk } from "../../utils/scriptSegments.js";
|
|
9
9
|
import { measuredLineAdvance } from "../lineFlow.js";
|
|
10
10
|
import { isFloatingImageRun } from "../types.js";
|
|
11
|
+
import { resolveEffectiveLineBreakPolicy } from "./effectiveLineBreakPolicy.js";
|
|
11
12
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
12
13
|
import { getFloatingAvailableWidth, getFloatingMargins } from "./floatingZones.js";
|
|
13
14
|
import { getListMarkerInlineWidth } from "./listMarkerWidth.js";
|
|
@@ -24,7 +25,7 @@ const DEFAULT_FONT_FAMILY = "Calibri";
|
|
|
24
25
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
25
26
|
const WIDTH_TOLERANCE = .5;
|
|
26
27
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
27
|
-
const JUSTIFY_SPACE_CONTRACTION_RATIO = .
|
|
28
|
+
const JUSTIFY_SPACE_CONTRACTION_RATIO = .075;
|
|
28
29
|
const JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO = .195;
|
|
29
30
|
const JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO = .23;
|
|
30
31
|
const JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO = .015;
|
|
@@ -51,6 +52,11 @@ function findMaxFittingLength(text, style, maxWidth, forceMin = false, policy) {
|
|
|
51
52
|
}
|
|
52
53
|
return forceMin && best === 0 ? boundaries.at(0) ?? 0 : best;
|
|
53
54
|
}
|
|
55
|
+
function exceedsHyphenationZone(line, zoneTwips) {
|
|
56
|
+
if (line.width <= 0) return true;
|
|
57
|
+
const visibleWidth = Math.max(0, line.width - line.trailingWhitespaceWidth);
|
|
58
|
+
return Math.max(0, line.availableWidth - visibleWidth) > twipsToPx(zoneTwips) + WIDTH_TOLERANCE;
|
|
59
|
+
}
|
|
54
60
|
/**
|
|
55
61
|
* Extract FontStyle from a run that carries RunFormatting (text, tab, or
|
|
56
62
|
* field). All three share the same formatting shape, so they measure the
|
|
@@ -60,31 +66,6 @@ function findMaxFittingLength(text, style, maxWidth, forceMin = false, policy) {
|
|
|
60
66
|
function runToFontStyle(run) {
|
|
61
67
|
return buildRunFontStyle(run, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE);
|
|
62
68
|
}
|
|
63
|
-
function languageMatches(ruleLanguage, locale) {
|
|
64
|
-
if (!ruleLanguage) return true;
|
|
65
|
-
if (!locale) return false;
|
|
66
|
-
const rule = ruleLanguage.toLowerCase();
|
|
67
|
-
const active = locale.toLowerCase();
|
|
68
|
-
return active === rule || active.startsWith(`${rule}-`) || rule.startsWith(`${active}-`);
|
|
69
|
-
}
|
|
70
|
-
function lineBreakPolicy(block, run) {
|
|
71
|
-
const language = run.language;
|
|
72
|
-
let locale = language?.val;
|
|
73
|
-
if (hasCjk(run.text)) locale = language?.eastAsia ?? language?.val;
|
|
74
|
-
else if (run.rtl) locale = language?.bidi ?? language?.val;
|
|
75
|
-
const rules = block.attrs?.lineBreakRules;
|
|
76
|
-
const before = rules?.noLineBreaksBefore;
|
|
77
|
-
const after = rules?.noLineBreaksAfter;
|
|
78
|
-
return {
|
|
79
|
-
...locale ? { locale } : {},
|
|
80
|
-
...block.attrs?.kinsoku !== void 0 ? { kinsoku: block.attrs.kinsoku } : {},
|
|
81
|
-
...before && languageMatches(before.language, locale) ? { noLineBreaksBefore: before.characters } : {},
|
|
82
|
-
...after && languageMatches(after.language, locale) ? { noLineBreaksAfter: after.characters } : {},
|
|
83
|
-
...rules?.useLegacyEthiopicAmharicRules ? { useLegacyEthiopicAmharicRules: true } : {},
|
|
84
|
-
...block.attrs?.automaticHyphenation?.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: block.attrs.automaticHyphenation.doNotHyphenateCaps } : {},
|
|
85
|
-
...run.allCaps === true ? { renderedAllCaps: true } : {}
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
69
|
/**
|
|
89
70
|
* Line-HEIGHT style for a text run. Word derives a CJK line's height from an
|
|
90
71
|
* East-Asian face, not the run's ascii font: real documents routinely put CJK
|
|
@@ -454,7 +435,10 @@ function computeTrailingGlueWidths(block) {
|
|
|
454
435
|
}
|
|
455
436
|
if (isSpaceOrTab(text.at(0))) continue;
|
|
456
437
|
const style = runToFontStyle(nextRun);
|
|
457
|
-
const firstBreak = findWordBreaks(text,
|
|
438
|
+
const firstBreak = findWordBreaks(text, resolveEffectiveLineBreakPolicy({
|
|
439
|
+
attrs: block.attrs,
|
|
440
|
+
run: nextRun
|
|
441
|
+
}).provider).at(0);
|
|
458
442
|
widths[index] = measureTextWidth(firstBreak === void 0 ? text : trimTrailingSpacesAndTabs(text.slice(0, firstBreak)), style) + (firstBreak === void 0 ? widths[index + 1] ?? 0 : 0);
|
|
459
443
|
}
|
|
460
444
|
return widths;
|
|
@@ -466,7 +450,10 @@ function computeProtectedCrossRunGlueWidths(block) {
|
|
|
466
450
|
if (!run || !isTextRun(run)) continue;
|
|
467
451
|
const trailing = /(\S+)(\s*)$/u.exec(run.text ?? "");
|
|
468
452
|
const token = trailing?.[1];
|
|
469
|
-
const policy =
|
|
453
|
+
const policy = resolveEffectiveLineBreakPolicy({
|
|
454
|
+
attrs: block.attrs,
|
|
455
|
+
run
|
|
456
|
+
}).provider;
|
|
470
457
|
if (!token || token.length !== 1 || !policy.locale?.toLocaleLowerCase().startsWith("cs")) continue;
|
|
471
458
|
let separator = trailing[2] ?? "";
|
|
472
459
|
let glueWidth = separator.length > 0 ? measureTextWidth(separator, runToFontStyle(run)) : 0;
|
|
@@ -512,7 +499,10 @@ function collectCrossRunWord({ block, startRunIndex, startChar }) {
|
|
|
512
499
|
if (runIndex !== startRunIndex && isSpaceOrTab(remainder[0])) break;
|
|
513
500
|
const remainingBudget = 256 - text.length;
|
|
514
501
|
const boundedRemainder = remainder.slice(0, remainingBudget + 1);
|
|
515
|
-
const firstBreak = findWordBreaks(boundedRemainder,
|
|
502
|
+
const firstBreak = findWordBreaks(boundedRemainder, resolveEffectiveLineBreakPolicy({
|
|
503
|
+
attrs: block.attrs,
|
|
504
|
+
run
|
|
505
|
+
}).provider).at(0);
|
|
516
506
|
const segmentText = trimTrailingSpacesAndTabs(firstBreak === void 0 ? boundedRemainder : boundedRemainder.slice(0, firstBreak));
|
|
517
507
|
if (segmentText.length === 0) break;
|
|
518
508
|
if (segmentText.length > remainingBudget) return;
|
|
@@ -534,7 +524,10 @@ function collectCrossRunWord({ block, startRunIndex, startChar }) {
|
|
|
534
524
|
return {
|
|
535
525
|
text,
|
|
536
526
|
width,
|
|
537
|
-
breaks: findHyphenationBreaks(text,
|
|
527
|
+
breaks: findHyphenationBreaks(text, resolveEffectiveLineBreakPolicy({
|
|
528
|
+
attrs: block.attrs,
|
|
529
|
+
run: firstRun
|
|
530
|
+
}).provider),
|
|
538
531
|
segments
|
|
539
532
|
};
|
|
540
533
|
}
|
|
@@ -944,7 +937,11 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
944
937
|
const textRun = run;
|
|
945
938
|
const text = textRun.text;
|
|
946
939
|
const style = runToFontStyle(textRun);
|
|
947
|
-
const
|
|
940
|
+
const effectiveLineBreakPolicy = resolveEffectiveLineBreakPolicy({
|
|
941
|
+
attrs: block.attrs,
|
|
942
|
+
run: textRun
|
|
943
|
+
});
|
|
944
|
+
const breakPolicy = effectiveLineBreakPolicy.provider;
|
|
948
945
|
const lineHeightStyle = cjkLineHeightStyle(textRun, style);
|
|
949
946
|
updateMaxFont(lineHeightStyle);
|
|
950
947
|
if (!text || text.length === 0) {
|
|
@@ -966,13 +963,12 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
966
963
|
const measuredWord = trimTrailingSpacesAndTabs(word);
|
|
967
964
|
const wordWidth = measureTextWidth(measuredWord, style);
|
|
968
965
|
const fullWordWidth = measureTextWidth(word, style);
|
|
969
|
-
const hangingPunctuationWidth = trailingHangingPunctuationWidth(measuredWord, style, breakPolicy,
|
|
966
|
+
const hangingPunctuationWidth = trailingHangingPunctuationWidth(measuredWord, style, breakPolicy, effectiveLineBreakPolicy.hangingPunctuation);
|
|
970
967
|
const isFirstLine = lines.length === 0;
|
|
971
968
|
const regularSpaceWidth = compressibleSpaceWidth(measuredWord, style);
|
|
972
969
|
const widthTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, isFirstLine ? firstLineJustifyFitStrategy : continuationJustifyFitStrategy, regularSpaceWidth) : WIDTH_TOLERANCE;
|
|
973
|
-
const automaticHyphenation =
|
|
974
|
-
const
|
|
975
|
-
const mayHyphenateLine = automaticHyphenation?.enabled === true && block.attrs?.suppressAutoHyphens !== true && (consecutiveLineLimit === 0 || consecutiveHyphenatedLines < consecutiveLineLimit);
|
|
970
|
+
const automaticHyphenation = effectiveLineBreakPolicy.automaticHyphenation;
|
|
971
|
+
const mayHyphenateLine = automaticHyphenation.type === "enabled" && (automaticHyphenation.consecutiveLineLimit === 0 || consecutiveHyphenatedLines < automaticHyphenation.consecutiveLineLimit) && exceedsHyphenationZone(currentLine, automaticHyphenation.hyphenationZoneTwips);
|
|
976
972
|
const isRunTail = nextBreak === text.length;
|
|
977
973
|
const crossRunWord = mayHyphenateLine && isRunTail && word.length > 0 && !isBreakChar(word.at(-1)) ? collectCrossRunWord({
|
|
978
974
|
block,
|