@a3s-lab/office 0.36.0 → 0.37.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/README.md +13 -6
- package/dist/0~7048.js +93 -1
- package/dist/0~spreadsheet-editor.js +388 -35
- package/dist/3266.js +366 -6
- package/dist/4121.js +1942 -1939
- package/dist/core.js +30 -3
- package/dist/internal/features/work/editors/spreadsheet-table-totals.d.ts +39 -0
- package/dist/internal/features/work/editors/spreadsheet-table.d.ts +11 -1
- package/dist/internal/features/work/work-types.d.ts +14 -0
- package/dist/internal/kernel/office-kernel-spreadsheet-fallback-formula.d.ts +11 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/office-kernel.worker.js +93 -1
- package/dist/styles.css +120 -0
- package/docs/latest/en/browser-editor-architecture.md +8 -2
- package/package.json +4 -1
package/dist/4121.js
CHANGED
|
@@ -68,2146 +68,2146 @@ function nonNegativeInteger(value) {
|
|
|
68
68
|
const number = Number(value);
|
|
69
69
|
return Number.isSafeInteger(number) && number >= 0 ? number : null;
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
const DEFAULT_DOCUMENT_INDEX_OPTIONS = {
|
|
72
|
+
columns: 1,
|
|
73
|
+
format: 'indented',
|
|
74
|
+
rightAlignPageNumbers: true,
|
|
75
|
+
leader: 'dot'
|
|
76
|
+
};
|
|
77
|
+
const MAX_DOCUMENT_INDEX_ENTRIES = 512;
|
|
78
|
+
const MAX_DOCUMENT_INDEX_MARKERS = 2048;
|
|
79
|
+
const MAX_DOCUMENT_INDEX_TERM_LENGTH = 240;
|
|
80
|
+
const INDEX_ENTRY_SELECTOR = '[data-document-index-entry]';
|
|
81
|
+
const INDEX_SELECTOR = '[data-document-index]';
|
|
82
|
+
const INDEX_ID_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/i;
|
|
83
|
+
function normalizeDocumentIndexEntryDraft(source) {
|
|
84
|
+
const mainEntry = normalizedIndexTerm(source?.mainEntry);
|
|
85
|
+
if (!mainEntry) return null;
|
|
86
|
+
const subEntry = normalizedIndexTerm(source?.subEntry);
|
|
87
|
+
const crossReference = normalizedIndexTerm(source?.crossReference);
|
|
88
|
+
return {
|
|
89
|
+
mainEntry,
|
|
90
|
+
subEntry,
|
|
91
|
+
crossReference,
|
|
92
|
+
pageBold: !crossReference && Boolean(source?.pageBold),
|
|
93
|
+
pageItalic: !crossReference && Boolean(source?.pageItalic)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function normalizeDocumentIndexEntry(source, fallbackId = 'index-entry') {
|
|
97
|
+
const value = normalizeDocumentIndexEntryDraft(source);
|
|
73
98
|
if (!value) return null;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const rgb = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*([\d.]+))?\s*\)$/i.exec(value);
|
|
79
|
-
if (!rgb) return null;
|
|
80
|
-
const channels = rgb.slice(1, 4).map(Number);
|
|
81
|
-
if (channels.some((channel)=>channel < 0 || channel > 255)) return null;
|
|
82
|
-
if (void 0 !== rgb[4] && 0 === Number(rgb[4])) return 'transparent';
|
|
83
|
-
if (void 0 !== rgb[4] && 1 !== Number(rgb[4])) return null;
|
|
84
|
-
return `#${channels.map((channel)=>channel.toString(16).padStart(2, '0')).join('')}`;
|
|
99
|
+
return {
|
|
100
|
+
id: validIndexId(source?.id) ?? fallbackId,
|
|
101
|
+
...value
|
|
102
|
+
};
|
|
85
103
|
}
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
} else if (0xfe === bytes[0] && 0xff === bytes[1]) {
|
|
94
|
-
encoding = 'utf-16be';
|
|
95
|
-
offset = 2;
|
|
96
|
-
} else if (0x3c === bytes[0] && 0 === bytes[1] && 0x3f === bytes[2] && 0 === bytes[3]) encoding = 'utf-16le';
|
|
97
|
-
else if (0 === bytes[0] && 0x3c === bytes[1] && 0 === bytes[2] && 0x3f === bytes[3]) encoding = 'utf-16be';
|
|
98
|
-
try {
|
|
99
|
-
return new TextDecoder(encoding, {
|
|
100
|
-
fatal: true
|
|
101
|
-
}).decode(bytes.subarray(offset));
|
|
102
|
-
} catch {
|
|
103
|
-
throw new Error(`${label} uses an invalid ${encoding} XML encoding.`);
|
|
104
|
-
}
|
|
104
|
+
function normalizeDocumentIndexOptions(source) {
|
|
105
|
+
return {
|
|
106
|
+
columns: boundedColumns(source?.columns),
|
|
107
|
+
format: source?.format === 'run-in' ? 'run-in' : 'indented',
|
|
108
|
+
rightAlignPageNumbers: source?.rightAlignPageNumbers !== false,
|
|
109
|
+
leader: indexLeader(source?.leader) ?? 'dot'
|
|
110
|
+
};
|
|
105
111
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
function normalizeDocumentIndexValue(source) {
|
|
113
|
+
return {
|
|
114
|
+
id: validIndexId(source.id) ?? 'document-index',
|
|
115
|
+
options: normalizeDocumentIndexOptions(source.options),
|
|
116
|
+
entries: normalizeGeneratedEntries(source.entries),
|
|
117
|
+
truncated: Boolean(source.truncated)
|
|
118
|
+
};
|
|
110
119
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const pending = entry.async('uint8array').then((bytes)=>decodeXmlBytes(bytes, partPath));
|
|
132
|
-
this.textCache.set(partPath, pending);
|
|
133
|
-
try {
|
|
134
|
-
return await pending;
|
|
135
|
-
} catch (error) {
|
|
136
|
-
this.textCache.delete(partPath);
|
|
137
|
-
throw error;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
async xml(partPath) {
|
|
141
|
-
return parseXml(await this.text(partPath), partPath);
|
|
142
|
-
}
|
|
143
|
-
async bytes(partPath) {
|
|
144
|
-
const entry = this.zip.file(partPath);
|
|
145
|
-
if (!entry) throw new Error(`Office package part is missing: ${partPath}`);
|
|
146
|
-
return entry.async('uint8array');
|
|
147
|
-
}
|
|
148
|
-
async relationships(sourcePart) {
|
|
149
|
-
const partPath = relationshipsPartPath(sourcePart);
|
|
150
|
-
if (!this.has(partPath)) return new Map();
|
|
151
|
-
const document = await this.xml(partPath);
|
|
152
|
-
return new Map(descendants(document, 'Relationship').map((element)=>{
|
|
153
|
-
const relationship = {
|
|
154
|
-
id: attribute(element, 'Id') ?? '',
|
|
155
|
-
target: resolvePartTarget(sourcePart, attribute(element, 'Target') ?? ''),
|
|
156
|
-
type: attribute(element, 'Type') ?? '',
|
|
157
|
-
targetMode: attribute(element, 'TargetMode') ?? void 0
|
|
120
|
+
function buildDocumentIndexEntries(document, options = {}) {
|
|
121
|
+
const grouped = new Map();
|
|
122
|
+
let markerCount = 0;
|
|
123
|
+
document.descendants((node, position)=>{
|
|
124
|
+
if ('documentIndexEntry' !== node.type.name) return;
|
|
125
|
+
markerCount += 1;
|
|
126
|
+
if (markerCount > MAX_DOCUMENT_INDEX_MARKERS) return;
|
|
127
|
+
const marker = normalizeDocumentIndexEntry(node.attrs, `index-entry-${markerCount}`);
|
|
128
|
+
if (!marker) return;
|
|
129
|
+
const key = indexEntryKey(marker);
|
|
130
|
+
let group = grouped.get(key);
|
|
131
|
+
if (!group) {
|
|
132
|
+
group = {
|
|
133
|
+
entry: {
|
|
134
|
+
mainEntry: marker.mainEntry,
|
|
135
|
+
subEntry: marker.subEntry,
|
|
136
|
+
crossReference: marker.crossReference,
|
|
137
|
+
pages: []
|
|
138
|
+
},
|
|
139
|
+
pages: new Map()
|
|
158
140
|
};
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
141
|
+
grouped.set(key, group);
|
|
142
|
+
}
|
|
143
|
+
if (marker.crossReference) return;
|
|
144
|
+
const pageNumber = positiveInteger(options.resolveContext?.(position)?.pageNumber) ?? fallbackDocumentPageNumber(document, position);
|
|
145
|
+
const existing = group.pages.get(pageNumber);
|
|
146
|
+
if (existing) {
|
|
147
|
+
existing.pageBold ||= marker.pageBold;
|
|
148
|
+
existing.pageItalic ||= marker.pageItalic;
|
|
149
|
+
if (!existing.targetIds.includes(marker.id)) existing.targetIds.push(marker.id);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
group.pages.set(pageNumber, {
|
|
153
|
+
pageNumber,
|
|
154
|
+
pageBold: marker.pageBold,
|
|
155
|
+
pageItalic: marker.pageItalic,
|
|
156
|
+
targetIds: [
|
|
157
|
+
marker.id
|
|
158
|
+
]
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
const collator = new Intl.Collator(void 0, {
|
|
162
|
+
numeric: true,
|
|
163
|
+
sensitivity: 'base',
|
|
164
|
+
usage: 'sort'
|
|
165
|
+
});
|
|
166
|
+
const allEntries = Array.from(grouped.values()).map(({ entry, pages })=>({
|
|
167
|
+
...entry,
|
|
168
|
+
pages: Array.from(pages.values()).sort((left, right)=>left.pageNumber - right.pageNumber)
|
|
169
|
+
})).sort((left, right)=>collator.compare(left.mainEntry, right.mainEntry) || collator.compare(left.subEntry, right.subEntry) || collator.compare(left.crossReference, right.crossReference));
|
|
170
|
+
return {
|
|
171
|
+
entries: allEntries.slice(0, MAX_DOCUMENT_INDEX_ENTRIES),
|
|
172
|
+
truncated: markerCount > MAX_DOCUMENT_INDEX_MARKERS || allEntries.length > MAX_DOCUMENT_INDEX_ENTRIES
|
|
173
|
+
};
|
|
165
174
|
}
|
|
166
|
-
function
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return
|
|
175
|
+
function documentIndexEntryHtml(source) {
|
|
176
|
+
const value = normalizeDocumentIndexEntry(source);
|
|
177
|
+
if (!value) return '';
|
|
178
|
+
const detail = indexEntryDisplay(value);
|
|
179
|
+
return [
|
|
180
|
+
`<span data-document-index-entry="true" data-index-entry-id="${escapeHtmlAttribute(value.id)}" data-index-main-entry="${escapeHtmlAttribute(value.mainEntry)}" data-index-sub-entry="${escapeHtmlAttribute(value.subEntry)}" data-index-cross-reference="${escapeHtmlAttribute(value.crossReference)}" data-index-page-bold="${String(value.pageBold)}" data-index-page-italic="${String(value.pageItalic)}" class="work-document-index-entry" contenteditable="false" aria-label="索引项:${escapeHtmlAttribute(detail)}">`,
|
|
181
|
+
'<span aria-hidden="true">索引项</span>',
|
|
182
|
+
`<strong>${escapeHtml(detail)}</strong>`,
|
|
183
|
+
'</span>'
|
|
184
|
+
].join('');
|
|
171
185
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
186
|
+
function documentIndexHtml(source) {
|
|
187
|
+
const value = normalizeDocumentIndexValue(source);
|
|
188
|
+
const rows = value.entries.length ? value.entries.map(documentIndexRowHtml) : [
|
|
189
|
+
'<li class="work-document-index-empty">没有已标记的索引项</li>'
|
|
190
|
+
];
|
|
191
|
+
const status = value.truncated ? `仅显示前 ${MAX_DOCUMENT_INDEX_ENTRIES} 项` : `${value.entries.length} 项`;
|
|
192
|
+
return [
|
|
193
|
+
`<div data-document-index="true" data-index-id="${escapeHtmlAttribute(value.id)}" data-index-columns="${value.options.columns}" data-index-format="${value.options.format}" data-index-right-align-page-numbers="${String(value.options.rightAlignPageNumbers)}" data-index-leader="${value.options.leader}" data-index-entries="${escapeHtmlAttribute(JSON.stringify(value.entries))}" data-index-truncated="${String(value.truncated)}" class="work-document-index" contenteditable="false" aria-label="索引">`,
|
|
194
|
+
`<div class="work-document-index-header"><strong>索引</strong><span>${status}</span></div>`,
|
|
195
|
+
'<ol class="work-document-index-list">',
|
|
196
|
+
...rows,
|
|
197
|
+
'</ol></div>'
|
|
198
|
+
].join('');
|
|
183
199
|
}
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
200
|
+
function documentIndexEntryFromElement(element, index = 1) {
|
|
201
|
+
return normalizeDocumentIndexEntry({
|
|
202
|
+
id: element.dataset.indexEntryId,
|
|
203
|
+
mainEntry: element.dataset.indexMainEntry,
|
|
204
|
+
subEntry: element.dataset.indexSubEntry,
|
|
205
|
+
crossReference: element.dataset.indexCrossReference,
|
|
206
|
+
pageBold: 'true' === element.dataset.indexPageBold,
|
|
207
|
+
pageItalic: 'true' === element.dataset.indexPageItalic
|
|
208
|
+
}, `index-entry-${index}`);
|
|
192
209
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
function directChildren(parent, localName) {
|
|
209
|
-
return Array.from(parent.children).filter((element)=>!localName || element.localName === localName);
|
|
210
|
-
}
|
|
211
|
-
function directChild(parent, localName) {
|
|
212
|
-
return directChildren(parent, localName)[0];
|
|
213
|
-
}
|
|
214
|
-
function descendants(parent, localName) {
|
|
215
|
-
return Array.from(parent.querySelectorAll('*')).filter((element)=>element.localName === localName);
|
|
216
|
-
}
|
|
217
|
-
function firstDescendant(parent, localName) {
|
|
218
|
-
if (!parent) return;
|
|
219
|
-
return descendants(parent, localName)[0];
|
|
210
|
+
function documentIndexValueFromElement(element, index = 1) {
|
|
211
|
+
return normalizeDocumentIndexValue({
|
|
212
|
+
id: validIndexId(element.dataset.indexId) ?? `document-index-${index}`,
|
|
213
|
+
options: {
|
|
214
|
+
columns: Number(element.dataset.indexColumns),
|
|
215
|
+
format: 'run-in' === element.dataset.indexFormat ? 'run-in' : 'indented',
|
|
216
|
+
rightAlignPageNumbers: 'false' !== element.dataset.indexRightAlignPageNumbers,
|
|
217
|
+
leader: indexLeader(element.dataset.indexLeader) ?? 'dot'
|
|
218
|
+
},
|
|
219
|
+
entries: parseGeneratedEntries(element.dataset.indexEntries),
|
|
220
|
+
truncated: 'true' === element.dataset.indexTruncated
|
|
221
|
+
});
|
|
220
222
|
}
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
function normalizeDocumentIndexesHtml(source) {
|
|
224
|
+
const document = new DOMParser().parseFromString(source, 'text/html');
|
|
225
|
+
const usedEntryIds = new Set();
|
|
226
|
+
for (const [index, element] of Array.from(document.body.querySelectorAll(INDEX_ENTRY_SELECTOR)).entries()){
|
|
227
|
+
const value = documentIndexEntryFromElement(element, index + 1);
|
|
228
|
+
if (!value) {
|
|
229
|
+
element.remove();
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
value.id = uniqueIndexId(value.id, 'index-entry', index + 1, usedEntryIds);
|
|
233
|
+
element.replaceWith(document.createRange().createContextualFragment(documentIndexEntryHtml(value)));
|
|
226
234
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return segments.join('/');
|
|
235
|
-
}
|
|
236
|
-
function contentTypeForPart(partPath) {
|
|
237
|
-
const extension = partPath.split('.').pop()?.toLowerCase();
|
|
238
|
-
const types = {
|
|
239
|
-
apng: 'image/apng',
|
|
240
|
-
bmp: 'image/bmp',
|
|
241
|
-
emf: 'image/emf',
|
|
242
|
-
gif: 'image/gif',
|
|
243
|
-
jpeg: 'image/jpeg',
|
|
244
|
-
jpg: 'image/jpeg',
|
|
245
|
-
png: 'image/png',
|
|
246
|
-
svg: 'image/svg+xml',
|
|
247
|
-
tif: 'image/tiff',
|
|
248
|
-
tiff: 'image/tiff',
|
|
249
|
-
webp: 'image/webp',
|
|
250
|
-
wmf: 'image/wmf'
|
|
251
|
-
};
|
|
252
|
-
return types[extension ?? ''] ?? 'application/octet-stream';
|
|
235
|
+
const usedIndexIds = new Set();
|
|
236
|
+
for (const [index, element] of Array.from(document.body.querySelectorAll(INDEX_SELECTOR)).entries()){
|
|
237
|
+
const value = documentIndexValueFromElement(element, index + 1);
|
|
238
|
+
value.id = uniqueIndexId(value.id, 'document-index', index + 1, usedIndexIds);
|
|
239
|
+
element.replaceWith(document.createRange().createContextualFragment(documentIndexHtml(value)));
|
|
240
|
+
}
|
|
241
|
+
return document.body.innerHTML;
|
|
253
242
|
}
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
const chunkSize = 32768;
|
|
257
|
-
for(let offset = 0; offset < bytes.length; offset += chunkSize)binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
|
|
258
|
-
return `data:${contentType};base64,${btoa(binary)}`;
|
|
243
|
+
function indexLeader(value) {
|
|
244
|
+
return 'dot' === value || 'dash' === value || 'underline' === value || 'none' === value ? value : null;
|
|
259
245
|
}
|
|
260
|
-
function
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
246
|
+
function documentIndexRowHtml(entry) {
|
|
247
|
+
const term = entry.subEntry ? `<span class="work-document-index-main">${escapeHtml(entry.mainEntry)}</span><span class="work-document-index-sub">${escapeHtml(entry.subEntry)}</span>` : `<span class="work-document-index-main">${escapeHtml(entry.mainEntry)}</span>`;
|
|
248
|
+
const pages = entry.crossReference ? `<span class="work-document-index-cross-reference">参见 ${escapeHtml(entry.crossReference)}</span>` : entry.pages.map((page)=>{
|
|
249
|
+
const target = page.targetIds[0] ?? '';
|
|
250
|
+
const classes = [
|
|
251
|
+
page.pageBold ? 'bold' : '',
|
|
252
|
+
page.pageItalic ? 'italic' : ''
|
|
253
|
+
].filter(Boolean).join(' ');
|
|
254
|
+
return `<a href="#${escapeHtmlAttribute(target)}" data-index-target="${escapeHtmlAttribute(target)}" tabindex="-1"${classes ? ` class="${classes}"` : ''}>${page.pageNumber}</a>`;
|
|
255
|
+
}).join('<span aria-hidden="true">, </span>');
|
|
256
|
+
return `<li data-index-main-entry="${escapeHtmlAttribute(entry.mainEntry)}" data-index-sub-entry="${escapeHtmlAttribute(entry.subEntry)}">${term}<i aria-hidden="true"></i><span class="work-document-index-pages">${pages}</span></li>`;
|
|
265
257
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.usedColors.add(marker);
|
|
282
|
-
this.patches.push({
|
|
283
|
-
kind,
|
|
284
|
-
marker,
|
|
285
|
-
reference
|
|
258
|
+
function normalizeGeneratedEntries(source) {
|
|
259
|
+
const entries = [];
|
|
260
|
+
for (const item of source.slice(0, MAX_DOCUMENT_INDEX_ENTRIES)){
|
|
261
|
+
if (!item || 'object' != typeof item) continue;
|
|
262
|
+
const candidate = item;
|
|
263
|
+
const draft = normalizeDocumentIndexEntryDraft({
|
|
264
|
+
mainEntry: normalizedIndexTerm(candidate.mainEntry),
|
|
265
|
+
subEntry: normalizedIndexTerm(candidate.subEntry),
|
|
266
|
+
crossReference: normalizedIndexTerm(candidate.crossReference)
|
|
267
|
+
});
|
|
268
|
+
if (draft) entries.push({
|
|
269
|
+
mainEntry: draft.mainEntry,
|
|
270
|
+
subEntry: draft.subEntry,
|
|
271
|
+
crossReference: draft.crossReference,
|
|
272
|
+
pages: draft.crossReference ? [] : normalizeIndexPages(candidate.pages)
|
|
286
273
|
});
|
|
287
|
-
return marker;
|
|
288
274
|
}
|
|
275
|
+
return entries;
|
|
289
276
|
}
|
|
290
|
-
function
|
|
291
|
-
|
|
277
|
+
function normalizeIndexPages(source) {
|
|
278
|
+
if (!Array.isArray(source)) return [];
|
|
279
|
+
const pages = [];
|
|
280
|
+
const seen = new Set();
|
|
281
|
+
for (const item of source.slice(0, MAX_DOCUMENT_INDEX_MARKERS)){
|
|
282
|
+
if (!item || 'object' != typeof item) continue;
|
|
283
|
+
const candidate = item;
|
|
284
|
+
const pageNumber = positiveInteger(candidate.pageNumber);
|
|
285
|
+
if (!pageNumber || seen.has(pageNumber)) continue;
|
|
286
|
+
const targetIds = Array.isArray(candidate.targetIds) ? candidate.targetIds.flatMap((value)=>validIndexId(value) ? [
|
|
287
|
+
String(value)
|
|
288
|
+
] : []).slice(0, 64) : [];
|
|
289
|
+
if (targetIds.length) {
|
|
290
|
+
seen.add(pageNumber);
|
|
291
|
+
pages.push({
|
|
292
|
+
pageNumber,
|
|
293
|
+
pageBold: Boolean(candidate.pageBold),
|
|
294
|
+
pageItalic: Boolean(candidate.pageItalic),
|
|
295
|
+
targetIds
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return pages.sort((left, right)=>left.pageNumber - right.pageNumber);
|
|
292
300
|
}
|
|
293
|
-
function
|
|
294
|
-
if (!
|
|
301
|
+
function parseGeneratedEntries(source) {
|
|
302
|
+
if (!source) return [];
|
|
295
303
|
try {
|
|
296
|
-
const parsed = JSON.parse(
|
|
297
|
-
|
|
298
|
-
const resolved = normalizeColor('string' == typeof parsed.resolved ? parsed.resolved : null);
|
|
299
|
-
const tint = byteHex(parsed.tint);
|
|
300
|
-
const shade = byteHex(parsed.shade);
|
|
301
|
-
if (!theme || !resolved) return null;
|
|
302
|
-
return {
|
|
303
|
-
theme,
|
|
304
|
-
resolved,
|
|
305
|
-
...tint ? {
|
|
306
|
-
tint
|
|
307
|
-
} : {},
|
|
308
|
-
...shade ? {
|
|
309
|
-
shade
|
|
310
|
-
} : {}
|
|
311
|
-
};
|
|
304
|
+
const parsed = JSON.parse(source);
|
|
305
|
+
return Array.isArray(parsed) ? normalizeGeneratedEntries(parsed) : [];
|
|
312
306
|
} catch {
|
|
313
|
-
return
|
|
307
|
+
return [];
|
|
314
308
|
}
|
|
315
309
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
]));
|
|
323
|
-
const entries = Object.values(archive.files).filter((entry)=>!entry.dir && /^word\/(?:document|header\d+|footer\d+|footnotes|endnotes|comments)\.xml$/.test(entry.name));
|
|
324
|
-
for (const entry of entries){
|
|
325
|
-
const document = parseXml(await entry.async('text'), entry.name);
|
|
326
|
-
let changed = false;
|
|
327
|
-
for (const element of Array.from(document.getElementsByTagName('*')))for (const target of themePatchTargets(element.localName)){
|
|
328
|
-
const marker = wordAttribute(element, target.directAttribute)?.toUpperCase();
|
|
329
|
-
const patch = marker ? byMarker.get(marker) : void 0;
|
|
330
|
-
if (patch && patch.kind === target.kind) {
|
|
331
|
-
setWordAttribute(document, element, target.directAttribute, patch.reference.resolved.slice(1).toUpperCase());
|
|
332
|
-
setWordAttribute(document, element, target.themeAttribute, patch.reference.theme);
|
|
333
|
-
setOptionalWordAttribute(document, element, target.tintAttribute, patch.reference.tint);
|
|
334
|
-
setOptionalWordAttribute(document, element, target.shadeAttribute, patch.reference.shade);
|
|
335
|
-
changed = true;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
if (changed) archive.file(entry.name, new XMLSerializer().serializeToString(document));
|
|
339
|
-
}
|
|
340
|
-
return archive.generateAsync({
|
|
341
|
-
type: 'arraybuffer'
|
|
310
|
+
function fallbackDocumentPageNumber(document, position) {
|
|
311
|
+
let pageNumber = 1;
|
|
312
|
+
document.descendants((node, offset)=>{
|
|
313
|
+
if (offset >= position) return false;
|
|
314
|
+
if ('pageBreak' === node.type.name) pageNumber += 1;
|
|
315
|
+
return true;
|
|
342
316
|
});
|
|
317
|
+
return pageNumber;
|
|
343
318
|
}
|
|
344
|
-
function
|
|
345
|
-
if ('color' === localName) return [
|
|
346
|
-
{
|
|
347
|
-
kind: 'color',
|
|
348
|
-
directAttribute: 'val',
|
|
349
|
-
themeAttribute: 'themeColor',
|
|
350
|
-
tintAttribute: 'themeTint',
|
|
351
|
-
shadeAttribute: 'themeShade'
|
|
352
|
-
}
|
|
353
|
-
];
|
|
354
|
-
if ('u' === localName) return [
|
|
355
|
-
{
|
|
356
|
-
kind: 'underline',
|
|
357
|
-
directAttribute: 'color',
|
|
358
|
-
themeAttribute: 'themeColor',
|
|
359
|
-
tintAttribute: 'themeTint',
|
|
360
|
-
shadeAttribute: 'themeShade'
|
|
361
|
-
}
|
|
362
|
-
];
|
|
363
|
-
if ('shd' === localName) return [
|
|
364
|
-
{
|
|
365
|
-
kind: 'fill',
|
|
366
|
-
directAttribute: 'fill',
|
|
367
|
-
themeAttribute: 'themeFill',
|
|
368
|
-
tintAttribute: 'themeFillTint',
|
|
369
|
-
shadeAttribute: 'themeFillShade'
|
|
370
|
-
},
|
|
371
|
-
{
|
|
372
|
-
kind: 'shadingColor',
|
|
373
|
-
directAttribute: 'color',
|
|
374
|
-
themeAttribute: 'themeColor',
|
|
375
|
-
tintAttribute: 'themeTint',
|
|
376
|
-
shadeAttribute: 'themeShade'
|
|
377
|
-
}
|
|
378
|
-
];
|
|
319
|
+
function indexEntryKey(entry) {
|
|
379
320
|
return [
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
'start',
|
|
385
|
-
'end'
|
|
386
|
-
].includes(localName) ? [
|
|
387
|
-
{
|
|
388
|
-
kind: 'border',
|
|
389
|
-
directAttribute: 'color',
|
|
390
|
-
themeAttribute: 'themeColor',
|
|
391
|
-
tintAttribute: 'themeTint',
|
|
392
|
-
shadeAttribute: 'themeShade'
|
|
393
|
-
}
|
|
394
|
-
] : [];
|
|
321
|
+
entry.mainEntry,
|
|
322
|
+
entry.subEntry,
|
|
323
|
+
entry.crossReference
|
|
324
|
+
].map((value)=>value.normalize('NFKC').toLocaleLowerCase()).join('\u0000');
|
|
395
325
|
}
|
|
396
|
-
function
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
return normalized;
|
|
326
|
+
function indexEntryDisplay(entry) {
|
|
327
|
+
const term = entry.subEntry ? `${entry.mainEntry} › ${entry.subEntry}` : entry.mainEntry;
|
|
328
|
+
return entry.crossReference ? `${term} · 参见 ${entry.crossReference}` : term;
|
|
400
329
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
330
|
+
function normalizedIndexTerm(value) {
|
|
331
|
+
return 'string' == typeof value ? value.replace(/\s+/g, ' ').trim().slice(0, MAX_DOCUMENT_INDEX_TERM_LENGTH) : '';
|
|
332
|
+
}
|
|
333
|
+
function boundedColumns(value) {
|
|
334
|
+
const number = Number(value);
|
|
335
|
+
return Number.isInteger(number) && number >= 1 && number <= 4 ? number : 1;
|
|
336
|
+
}
|
|
337
|
+
function positiveInteger(value) {
|
|
338
|
+
const number = Number(value);
|
|
339
|
+
return Number.isSafeInteger(number) && number > 0 ? Math.min(999999, number) : null;
|
|
340
|
+
}
|
|
341
|
+
function validIndexId(value) {
|
|
342
|
+
return 'string' == typeof value && INDEX_ID_PATTERN.test(value) ? value : null;
|
|
343
|
+
}
|
|
344
|
+
function uniqueIndexId(source, prefix, index, used) {
|
|
345
|
+
if (!used.has(source)) {
|
|
346
|
+
used.add(source);
|
|
347
|
+
return source;
|
|
407
348
|
}
|
|
408
|
-
|
|
349
|
+
let suffix = index;
|
|
350
|
+
while(used.has(`${prefix}-${suffix}`))suffix += 1;
|
|
351
|
+
const id = `${prefix}-${suffix}`;
|
|
352
|
+
used.add(id);
|
|
353
|
+
return id;
|
|
409
354
|
}
|
|
410
|
-
function
|
|
411
|
-
|
|
412
|
-
const normalized = value.trim().toUpperCase();
|
|
413
|
-
return /^[0-9A-F]{2}$/.test(normalized) ? normalized : void 0;
|
|
355
|
+
function escapeHtml(value) {
|
|
356
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
414
357
|
}
|
|
415
|
-
function
|
|
416
|
-
return
|
|
358
|
+
function escapeHtmlAttribute(value) {
|
|
359
|
+
return escapeHtml(value).replaceAll('"', '"').replaceAll("'", ''');
|
|
417
360
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
361
|
+
const DOCUMENT_SCRIPT_FONTS_ATTRIBUTE = "data-office-script-fonts";
|
|
362
|
+
const DOCUMENT_SCRIPT_FONT_SLOT_ATTRIBUTE = "data-office-script-font-slot";
|
|
363
|
+
const MAX_FONT_NAME_LENGTH = 127;
|
|
364
|
+
const MAX_SERIALIZED_SCRIPT_FONTS_LENGTH = 4096;
|
|
365
|
+
const SCRIPT_FONT_KEYS = new Set([
|
|
366
|
+
'ascii',
|
|
367
|
+
'highAnsi',
|
|
368
|
+
'eastAsia',
|
|
369
|
+
'complexScript',
|
|
370
|
+
'hint'
|
|
371
|
+
]);
|
|
372
|
+
const SCRIPT_FONT_FACE_KEYS = new Set([
|
|
373
|
+
'name',
|
|
374
|
+
'theme',
|
|
375
|
+
'resolved'
|
|
376
|
+
]);
|
|
377
|
+
const SCRIPT_FONT_HINTS = new Set([
|
|
378
|
+
'default',
|
|
379
|
+
'eastAsia',
|
|
380
|
+
'cs'
|
|
381
|
+
]);
|
|
382
|
+
const THEME_FONTS = new Set([
|
|
383
|
+
'majorEastAsia',
|
|
384
|
+
'majorBidi',
|
|
385
|
+
'majorAscii',
|
|
386
|
+
'majorHAnsi',
|
|
387
|
+
'minorEastAsia',
|
|
388
|
+
'minorBidi',
|
|
389
|
+
'minorAscii',
|
|
390
|
+
'minorHAnsi'
|
|
391
|
+
]);
|
|
392
|
+
const SLOT_FALLBACK_ORDER = {
|
|
393
|
+
ascii: [
|
|
394
|
+
'ascii',
|
|
395
|
+
'highAnsi',
|
|
396
|
+
'eastAsia',
|
|
397
|
+
'complexScript'
|
|
398
|
+
],
|
|
399
|
+
highAnsi: [
|
|
400
|
+
'highAnsi',
|
|
401
|
+
'ascii',
|
|
402
|
+
'eastAsia',
|
|
403
|
+
'complexScript'
|
|
404
|
+
],
|
|
405
|
+
eastAsia: [
|
|
406
|
+
'eastAsia',
|
|
407
|
+
'highAnsi',
|
|
408
|
+
'ascii',
|
|
409
|
+
'complexScript'
|
|
410
|
+
],
|
|
411
|
+
complexScript: [
|
|
412
|
+
'complexScript',
|
|
413
|
+
'highAnsi',
|
|
414
|
+
'ascii',
|
|
415
|
+
'eastAsia'
|
|
416
|
+
]
|
|
417
|
+
};
|
|
418
|
+
const NEUTRAL_SCRIPT_CHARACTER = /^[\p{Cc}\p{Cf}\p{M}\p{N}\p{P}\p{S}\p{Z}]$/u;
|
|
419
|
+
function normalizeDocumentScriptFonts(source) {
|
|
420
|
+
if (!isRecordWithKeys(source, SCRIPT_FONT_KEYS)) return null;
|
|
421
|
+
const normalized = {};
|
|
422
|
+
for (const slot of scriptFontSlots){
|
|
423
|
+
if (void 0 === source[slot]) continue;
|
|
424
|
+
const face = normalizeDocumentScriptFontFace(source[slot]);
|
|
425
|
+
if (!face) return null;
|
|
426
|
+
normalized[slot] = face;
|
|
427
|
+
}
|
|
428
|
+
if (void 0 !== source.hint) {
|
|
429
|
+
const hint = normalizeDocumentScriptFontHint(source.hint);
|
|
430
|
+
if (!hint) return null;
|
|
431
|
+
normalized.hint = hint;
|
|
432
|
+
}
|
|
433
|
+
return Object.keys(normalized).length ? normalized : null;
|
|
421
434
|
}
|
|
422
|
-
function
|
|
423
|
-
|
|
424
|
-
|
|
435
|
+
function serializeDocumentScriptFonts(source) {
|
|
436
|
+
const fonts = normalizeDocumentScriptFonts(source);
|
|
437
|
+
return fonts ? JSON.stringify(fonts) : null;
|
|
425
438
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
'between',
|
|
433
|
-
'bar'
|
|
434
|
-
];
|
|
435
|
-
const DOCUMENT_PARAGRAPH_BORDER_STYLES = [
|
|
436
|
-
'nil',
|
|
437
|
-
'none',
|
|
438
|
-
'single',
|
|
439
|
-
'thick',
|
|
440
|
-
'double',
|
|
441
|
-
'dotted',
|
|
442
|
-
'dashed',
|
|
443
|
-
'dotDash',
|
|
444
|
-
'dotDotDash',
|
|
445
|
-
'triple',
|
|
446
|
-
'thinThickSmallGap',
|
|
447
|
-
'thickThinSmallGap',
|
|
448
|
-
'thinThickThinSmallGap',
|
|
449
|
-
'thinThickMediumGap',
|
|
450
|
-
'thickThinMediumGap',
|
|
451
|
-
'thinThickThinMediumGap',
|
|
452
|
-
'thinThickLargeGap',
|
|
453
|
-
'thickThinLargeGap',
|
|
454
|
-
'thinThickThinLargeGap',
|
|
455
|
-
'wave',
|
|
456
|
-
'doubleWave',
|
|
457
|
-
'dashSmallGap',
|
|
458
|
-
'dashDotStroked',
|
|
459
|
-
'threeDEmboss',
|
|
460
|
-
'threeDEngrave',
|
|
461
|
-
'outset',
|
|
462
|
-
'inset',
|
|
463
|
-
'apples',
|
|
464
|
-
'archedScallops',
|
|
465
|
-
'babyPacifier',
|
|
466
|
-
'babyRattle',
|
|
467
|
-
'balloons3Colors',
|
|
468
|
-
'balloonsHotAir',
|
|
469
|
-
'basicBlackDashes',
|
|
470
|
-
'basicBlackDots',
|
|
471
|
-
'basicBlackSquares',
|
|
472
|
-
'basicThinLines',
|
|
473
|
-
'basicWhiteDashes',
|
|
474
|
-
'basicWhiteDots',
|
|
475
|
-
'basicWhiteSquares',
|
|
476
|
-
'basicWideInline',
|
|
477
|
-
'basicWideMidline',
|
|
478
|
-
'basicWideOutline',
|
|
479
|
-
'bats',
|
|
480
|
-
'birds',
|
|
481
|
-
'birdsFlight',
|
|
482
|
-
'cabins',
|
|
483
|
-
'cakeSlice',
|
|
484
|
-
'candyCorn',
|
|
485
|
-
'celticKnotwork',
|
|
486
|
-
'certificateBanner',
|
|
487
|
-
'chainLink',
|
|
488
|
-
'champagneBottle',
|
|
489
|
-
'checkedBarBlack',
|
|
490
|
-
'checkedBarColor',
|
|
491
|
-
'checkered',
|
|
492
|
-
'christmasTree',
|
|
493
|
-
'circlesLines',
|
|
494
|
-
'circlesRectangles',
|
|
495
|
-
'classicalWave',
|
|
496
|
-
'clocks',
|
|
497
|
-
'compass',
|
|
498
|
-
'confetti',
|
|
499
|
-
'confettiGrays',
|
|
500
|
-
'confettiOutline',
|
|
501
|
-
'confettiStreamers',
|
|
502
|
-
'confettiWhite',
|
|
503
|
-
'cornerTriangles',
|
|
504
|
-
'couponCutoutDashes',
|
|
505
|
-
'couponCutoutDots',
|
|
506
|
-
'crazyMaze',
|
|
507
|
-
'creaturesButterfly',
|
|
508
|
-
'creaturesFish',
|
|
509
|
-
'creaturesInsects',
|
|
510
|
-
'creaturesLadyBug',
|
|
511
|
-
'crossStitch',
|
|
512
|
-
'cup',
|
|
513
|
-
'decoArch',
|
|
514
|
-
'decoArchColor',
|
|
515
|
-
'decoBlocks',
|
|
516
|
-
'diamondsGray',
|
|
517
|
-
'doubleD',
|
|
518
|
-
'doubleDiamonds',
|
|
519
|
-
'earth1',
|
|
520
|
-
'earth2',
|
|
521
|
-
'eclipsingSquares1',
|
|
522
|
-
'eclipsingSquares2',
|
|
523
|
-
'eggsBlack',
|
|
524
|
-
'fans',
|
|
525
|
-
'film',
|
|
526
|
-
'firecrackers',
|
|
527
|
-
'flowersBlockPrint',
|
|
528
|
-
'flowersDaisies',
|
|
529
|
-
'flowersModern1',
|
|
530
|
-
'flowersModern2',
|
|
531
|
-
'flowersPansy',
|
|
532
|
-
'flowersRedRose',
|
|
533
|
-
'flowersRoses',
|
|
534
|
-
'flowersTeacup',
|
|
535
|
-
'flowersTiny',
|
|
536
|
-
'gems',
|
|
537
|
-
'gingerbreadMan',
|
|
538
|
-
'gradient',
|
|
539
|
-
'handmade1',
|
|
540
|
-
'handmade2',
|
|
541
|
-
'heartBalloon',
|
|
542
|
-
'heartGray',
|
|
543
|
-
'hearts',
|
|
544
|
-
'heebieJeebies',
|
|
545
|
-
'holly',
|
|
546
|
-
'houseFunky',
|
|
547
|
-
'hypnotic',
|
|
548
|
-
'iceCreamCones',
|
|
549
|
-
'lightBulb',
|
|
550
|
-
'lightning1',
|
|
551
|
-
'lightning2',
|
|
552
|
-
'mapPins',
|
|
553
|
-
'mapleLeaf',
|
|
554
|
-
'mapleMuffins',
|
|
555
|
-
'marquee',
|
|
556
|
-
'marqueeToothed',
|
|
557
|
-
'moons',
|
|
558
|
-
'mosaic',
|
|
559
|
-
'musicNotes',
|
|
560
|
-
'northwest',
|
|
561
|
-
'ovals',
|
|
562
|
-
'packages',
|
|
563
|
-
'palmsBlack',
|
|
564
|
-
'palmsColor',
|
|
565
|
-
'paperClips',
|
|
566
|
-
'papyrus',
|
|
567
|
-
'partyFavor',
|
|
568
|
-
'partyGlass',
|
|
569
|
-
'pencils',
|
|
570
|
-
'people',
|
|
571
|
-
'peopleWaving',
|
|
572
|
-
'peopleHats',
|
|
573
|
-
'poinsettias',
|
|
574
|
-
'postageStamp',
|
|
575
|
-
'pumpkin1',
|
|
576
|
-
'pushPinNote2',
|
|
577
|
-
'pushPinNote1',
|
|
578
|
-
'pyramids',
|
|
579
|
-
'pyramidsAbove',
|
|
580
|
-
'quadrants',
|
|
581
|
-
'rings',
|
|
582
|
-
'safari',
|
|
583
|
-
'sawtooth',
|
|
584
|
-
'sawtoothGray',
|
|
585
|
-
'scaredCat',
|
|
586
|
-
'seattle',
|
|
587
|
-
'shadowedSquares',
|
|
588
|
-
'sharksTeeth',
|
|
589
|
-
'shorebirdTracks',
|
|
590
|
-
'skyrocket',
|
|
591
|
-
'snowflakeFancy',
|
|
592
|
-
'snowflakes',
|
|
593
|
-
'sombrero',
|
|
594
|
-
'southwest',
|
|
595
|
-
'stars',
|
|
596
|
-
'starsTop',
|
|
597
|
-
'stars3d',
|
|
598
|
-
'starsBlack',
|
|
599
|
-
'starsShadowed',
|
|
600
|
-
'sun',
|
|
601
|
-
'swirligig',
|
|
602
|
-
'tornPaper',
|
|
603
|
-
'tornPaperBlack',
|
|
604
|
-
'trees',
|
|
605
|
-
'triangleParty',
|
|
606
|
-
'triangles',
|
|
607
|
-
'tribal1',
|
|
608
|
-
'tribal2',
|
|
609
|
-
'tribal3',
|
|
610
|
-
'tribal4',
|
|
611
|
-
'tribal5',
|
|
612
|
-
'tribal6',
|
|
613
|
-
'triangle1',
|
|
614
|
-
'triangle2',
|
|
615
|
-
'triangleCircle1',
|
|
616
|
-
'triangleCircle2',
|
|
617
|
-
'shapes1',
|
|
618
|
-
'shapes2',
|
|
619
|
-
'twistedLines1',
|
|
620
|
-
'twistedLines2',
|
|
621
|
-
'vine',
|
|
622
|
-
'waveline',
|
|
623
|
-
'weavingAngles',
|
|
624
|
-
'weavingBraid',
|
|
625
|
-
'weavingRibbon',
|
|
626
|
-
'weavingStrips',
|
|
627
|
-
'whiteFlowers',
|
|
628
|
-
'woodwork',
|
|
629
|
-
'xIllusions',
|
|
630
|
-
'zanyTriangles',
|
|
631
|
-
'zigZag',
|
|
632
|
-
'zigZagStitch'
|
|
633
|
-
];
|
|
634
|
-
const BORDER_STYLE_SET = new Set(DOCUMENT_PARAGRAPH_BORDER_STYLES);
|
|
635
|
-
const BORDER_EDGE_SET = new Set(DOCUMENT_PARAGRAPH_BORDER_EDGES);
|
|
636
|
-
const BORDER_PROPERTY_SET = new Set([
|
|
637
|
-
'style',
|
|
638
|
-
'color',
|
|
639
|
-
'size',
|
|
640
|
-
'space',
|
|
641
|
-
'shadow',
|
|
642
|
-
'frame'
|
|
643
|
-
]);
|
|
644
|
-
const LINE_BORDER_STYLES = new Set(DOCUMENT_PARAGRAPH_BORDER_STYLES.slice(0, 27));
|
|
645
|
-
const DASHED_BORDER_STYLES = new Set([
|
|
646
|
-
'dashed',
|
|
647
|
-
'dashSmallGap',
|
|
648
|
-
'dashDotStroked',
|
|
649
|
-
'dotDash',
|
|
650
|
-
'dotDotDash'
|
|
651
|
-
]);
|
|
652
|
-
const DOUBLE_BORDER_STYLES = new Set([
|
|
653
|
-
'double',
|
|
654
|
-
'triple',
|
|
655
|
-
'thinThickSmallGap',
|
|
656
|
-
'thickThinSmallGap',
|
|
657
|
-
'thinThickThinSmallGap',
|
|
658
|
-
'thinThickMediumGap',
|
|
659
|
-
'thickThinMediumGap',
|
|
660
|
-
'thinThickThinMediumGap',
|
|
661
|
-
'thinThickLargeGap',
|
|
662
|
-
'thickThinLargeGap',
|
|
663
|
-
'thinThickThinLargeGap',
|
|
664
|
-
'doubleWave'
|
|
665
|
-
]);
|
|
666
|
-
const MAX_SERIALIZED_PARAGRAPH_BORDERS = 32768;
|
|
667
|
-
const POINTS_TO_PIXELS = 96 / 72;
|
|
668
|
-
function normalizeDocumentParagraphBorders(source) {
|
|
669
|
-
if (!source || 'object' != typeof source || Array.isArray(source)) return null;
|
|
670
|
-
const record = source;
|
|
671
|
-
if (Object.keys(record).some((key)=>!BORDER_EDGE_SET.has(key))) return null;
|
|
672
|
-
const borders = {};
|
|
673
|
-
for (const edge of DOCUMENT_PARAGRAPH_BORDER_EDGES){
|
|
674
|
-
if (void 0 === record[edge]) continue;
|
|
675
|
-
const border = normalizeDocumentParagraphBorder(record[edge]);
|
|
676
|
-
if (!border) return null;
|
|
677
|
-
borders[edge] = border;
|
|
439
|
+
function parseDocumentScriptFonts(source) {
|
|
440
|
+
if (!source || source.length > MAX_SERIALIZED_SCRIPT_FONTS_LENGTH) return null;
|
|
441
|
+
try {
|
|
442
|
+
return normalizeDocumentScriptFonts(JSON.parse(source));
|
|
443
|
+
} catch {
|
|
444
|
+
return null;
|
|
678
445
|
}
|
|
679
|
-
return Object.keys(borders).length ? borders : null;
|
|
680
446
|
}
|
|
681
|
-
function
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const shadow = optionalBoolean(record.shadow);
|
|
695
|
-
const frame = optionalBoolean(record.frame);
|
|
696
|
-
if (null === shadow || null === frame) return null;
|
|
447
|
+
function documentScriptFontsFromElement(element) {
|
|
448
|
+
return parseDocumentScriptFonts(element.getAttribute(DOCUMENT_SCRIPT_FONTS_ATTRIBUTE));
|
|
449
|
+
}
|
|
450
|
+
function documentScriptFontSlotFromElement(element) {
|
|
451
|
+
return normalizeDocumentScriptFontSlot(element.getAttribute(DOCUMENT_SCRIPT_FONT_SLOT_ATTRIBUTE));
|
|
452
|
+
}
|
|
453
|
+
function documentScriptFontsDomAttributes(source, slot) {
|
|
454
|
+
const fonts = normalizeDocumentScriptFonts(source);
|
|
455
|
+
const normalizedSlot = normalizeDocumentScriptFontSlot(slot);
|
|
456
|
+
if (!fonts) return {};
|
|
457
|
+
const serialized = serializeDocumentScriptFonts(fonts);
|
|
458
|
+
if (!serialized) return {};
|
|
459
|
+
const family = documentScriptFontFamily(fonts, normalizedSlot ?? documentScriptFontSlotFromHint(fonts.hint));
|
|
697
460
|
return {
|
|
698
|
-
|
|
699
|
-
...
|
|
700
|
-
|
|
701
|
-
} : {},
|
|
702
|
-
...void 0 !== size ? {
|
|
703
|
-
size
|
|
704
|
-
} : {},
|
|
705
|
-
...void 0 !== space ? {
|
|
706
|
-
space
|
|
707
|
-
} : {},
|
|
708
|
-
...void 0 !== shadow ? {
|
|
709
|
-
shadow
|
|
461
|
+
[DOCUMENT_SCRIPT_FONTS_ATTRIBUTE]: serialized,
|
|
462
|
+
...normalizedSlot ? {
|
|
463
|
+
[DOCUMENT_SCRIPT_FONT_SLOT_ATTRIBUTE]: normalizedSlot
|
|
710
464
|
} : {},
|
|
711
|
-
...
|
|
712
|
-
|
|
465
|
+
...family ? {
|
|
466
|
+
style: `font-family: ${family}`
|
|
713
467
|
} : {}
|
|
714
468
|
};
|
|
715
469
|
}
|
|
716
|
-
function
|
|
717
|
-
|
|
718
|
-
if (!
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
470
|
+
function documentScriptFontFamily(source, slot) {
|
|
471
|
+
const fonts = normalizeDocumentScriptFonts(source);
|
|
472
|
+
if (!fonts) return;
|
|
473
|
+
const families = [];
|
|
474
|
+
const seen = new Set();
|
|
475
|
+
for (const candidate of documentScriptFontFallbackSlots(slot)){
|
|
476
|
+
const family = documentScriptFontFaceFamily(fonts[candidate]);
|
|
477
|
+
const key = family?.toLocaleLowerCase();
|
|
478
|
+
if (!(!family || !key || seen.has(key))) {
|
|
479
|
+
seen.add(key);
|
|
480
|
+
families.push(cssFontFamily(family));
|
|
481
|
+
}
|
|
723
482
|
}
|
|
483
|
+
return families.length ? families.join(', ') : void 0;
|
|
724
484
|
}
|
|
725
|
-
function
|
|
726
|
-
|
|
727
|
-
if (!borders) return;
|
|
728
|
-
return JSON.stringify(Object.fromEntries(DOCUMENT_PARAGRAPH_BORDER_EDGES.flatMap((edge)=>{
|
|
729
|
-
const border = borders[edge];
|
|
730
|
-
return border ? [
|
|
731
|
-
[
|
|
732
|
-
edge,
|
|
733
|
-
serializedBorder(border)
|
|
734
|
-
]
|
|
735
|
-
] : [];
|
|
736
|
-
})));
|
|
485
|
+
function documentScriptFontFallbackSlots(slot) {
|
|
486
|
+
return SLOT_FALLBACK_ORDER[slot];
|
|
737
487
|
}
|
|
738
|
-
function
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
488
|
+
function documentScriptFontFamilyForRendering(source, slot, currentFontFamily) {
|
|
489
|
+
const projected = documentScriptFontFamily(source, slot);
|
|
490
|
+
const safeCurrent = safeCssFontFamilyList(currentFontFamily);
|
|
491
|
+
if (!safeCurrent || !projected) return projected;
|
|
492
|
+
const currentPrimary = documentFontNameFromCssFamily(safeCurrent);
|
|
493
|
+
const projectedPrimary = documentFontNameFromCssFamily(projected);
|
|
494
|
+
return currentPrimary && projectedPrimary && currentPrimary.toLocaleLowerCase() === projectedPrimary.toLocaleLowerCase() ? safeCurrent : projected;
|
|
495
|
+
}
|
|
496
|
+
function documentScriptFontDirectFamily(source, slot) {
|
|
497
|
+
const fonts = normalizeDocumentScriptFonts(source);
|
|
498
|
+
return fonts ? documentScriptFontFaceFamily(fonts[slot]) ?? null : null;
|
|
499
|
+
}
|
|
500
|
+
function documentScriptFontsForAllText(fontFamily) {
|
|
501
|
+
const name = documentFontNameFromCssFamily(fontFamily);
|
|
502
|
+
if (!name) return null;
|
|
503
|
+
const face = {
|
|
504
|
+
name,
|
|
505
|
+
resolved: name
|
|
743
506
|
};
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
value: css.color
|
|
766
|
-
},
|
|
767
|
-
size: cssWidthToEighthPoints(css.width)
|
|
768
|
-
};
|
|
507
|
+
return {
|
|
508
|
+
ascii: face,
|
|
509
|
+
highAnsi: face,
|
|
510
|
+
eastAsia: face,
|
|
511
|
+
complexScript: face,
|
|
512
|
+
hint: 'default'
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function patchDocumentScriptFonts(source, patch, fallbackFontFamily) {
|
|
516
|
+
const current = normalizeDocumentScriptFonts(source) ?? documentScriptFontsForAllText(fallbackFontFamily) ?? {};
|
|
517
|
+
const next = {
|
|
518
|
+
...current
|
|
519
|
+
};
|
|
520
|
+
if (void 0 !== patch.latin) {
|
|
521
|
+
const face = directFontFace(patch.latin);
|
|
522
|
+
if (face) {
|
|
523
|
+
next.ascii = face;
|
|
524
|
+
next.highAnsi = face;
|
|
525
|
+
} else {
|
|
526
|
+
delete next.ascii;
|
|
527
|
+
delete next.highAnsi;
|
|
769
528
|
}
|
|
770
529
|
}
|
|
771
|
-
|
|
530
|
+
if (void 0 !== patch.eastAsia) {
|
|
531
|
+
const face = directFontFace(patch.eastAsia);
|
|
532
|
+
if (face) next.eastAsia = face;
|
|
533
|
+
else delete next.eastAsia;
|
|
534
|
+
}
|
|
535
|
+
if (void 0 !== patch.complexScript) {
|
|
536
|
+
const face = directFontFace(patch.complexScript);
|
|
537
|
+
if (face) next.complexScript = face;
|
|
538
|
+
else delete next.complexScript;
|
|
539
|
+
}
|
|
540
|
+
return normalizeDocumentScriptFonts(next);
|
|
772
541
|
}
|
|
773
|
-
function
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
542
|
+
function documentScriptFontSegments(text, hint = 'default', forceComplexScript = false) {
|
|
543
|
+
if (!text) return [];
|
|
544
|
+
if (forceComplexScript) return [
|
|
545
|
+
{
|
|
546
|
+
from: 0,
|
|
547
|
+
to: text.length,
|
|
548
|
+
slot: 'complexScript'
|
|
549
|
+
}
|
|
550
|
+
];
|
|
551
|
+
const characters = [];
|
|
552
|
+
let offset = 0;
|
|
553
|
+
for (const character of text){
|
|
554
|
+
const from = offset;
|
|
555
|
+
offset += character.length;
|
|
556
|
+
characters.push({
|
|
557
|
+
from,
|
|
558
|
+
to: offset,
|
|
559
|
+
slot: strongDocumentScriptFontSlot(character)
|
|
560
|
+
});
|
|
791
561
|
}
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
562
|
+
const fallback = documentScriptFontSlotFromHint(hint);
|
|
563
|
+
let previous = null;
|
|
564
|
+
for(let index = 0; index < characters.length; index += 1){
|
|
565
|
+
const entry = characters[index];
|
|
566
|
+
if (!entry) continue;
|
|
567
|
+
if (entry.slot) {
|
|
568
|
+
previous = entry.slot;
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
let next = previous;
|
|
572
|
+
if (!next) for(let cursor = index + 1; cursor < characters.length; cursor += 1){
|
|
573
|
+
const candidate = characters[cursor]?.slot;
|
|
574
|
+
if (candidate) {
|
|
575
|
+
next = candidate;
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
entry.slot = next ?? fallback;
|
|
580
|
+
}
|
|
581
|
+
const segments = [];
|
|
582
|
+
for (const entry of characters){
|
|
583
|
+
const slot = entry.slot ?? fallback;
|
|
584
|
+
const prior = segments[segments.length - 1];
|
|
585
|
+
if (prior?.slot === slot && prior.to === entry.from) prior.to = entry.to;
|
|
586
|
+
else segments.push({
|
|
587
|
+
from: entry.from,
|
|
588
|
+
to: entry.to,
|
|
589
|
+
slot
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return segments;
|
|
803
593
|
}
|
|
804
|
-
function
|
|
805
|
-
return
|
|
594
|
+
function normalizeDocumentScriptFontSlot(value) {
|
|
595
|
+
return scriptFontSlots.includes(value) ? value : null;
|
|
806
596
|
}
|
|
807
|
-
function
|
|
808
|
-
|
|
809
|
-
const record = source;
|
|
810
|
-
if (Object.keys(record).some((key)=>'value' !== key && 'theme' !== key)) return null;
|
|
811
|
-
const theme = parseDocxThemeReference('string' == typeof record.theme ? record.theme : record.theme ? JSON.stringify(record.theme) : void 0);
|
|
812
|
-
const direct = 'auto' === record.value ? 'auto' : 'string' == typeof record.value ? normalizeCssColor(record.value) : null;
|
|
813
|
-
const resolved = direct ?? theme?.resolved ?? null;
|
|
814
|
-
if (!resolved || 'transparent' === resolved) return null;
|
|
815
|
-
if (theme && resolved !== theme.resolved && !('auto' === resolved && 'none' === theme.theme && '#000000' === theme.resolved)) return null;
|
|
816
|
-
return {
|
|
817
|
-
value: resolved,
|
|
818
|
-
...theme ? {
|
|
819
|
-
theme
|
|
820
|
-
} : {}
|
|
821
|
-
};
|
|
597
|
+
function normalizeDocumentScriptFontHint(value) {
|
|
598
|
+
return SCRIPT_FONT_HINTS.has(value) ? value : null;
|
|
822
599
|
}
|
|
823
|
-
function
|
|
824
|
-
|
|
825
|
-
return {
|
|
826
|
-
style: border.style,
|
|
827
|
-
...color ? {
|
|
828
|
-
color
|
|
829
|
-
} : {},
|
|
830
|
-
...void 0 !== border.size ? {
|
|
831
|
-
size: border.size
|
|
832
|
-
} : {},
|
|
833
|
-
...void 0 !== border.space ? {
|
|
834
|
-
space: border.space
|
|
835
|
-
} : {},
|
|
836
|
-
...void 0 !== border.shadow ? {
|
|
837
|
-
shadow: border.shadow
|
|
838
|
-
} : {},
|
|
839
|
-
...void 0 !== border.frame ? {
|
|
840
|
-
frame: border.frame
|
|
841
|
-
} : {}
|
|
842
|
-
};
|
|
600
|
+
function normalizeDocumentThemeFont(value) {
|
|
601
|
+
return THEME_FONTS.has(value) ? value : null;
|
|
843
602
|
}
|
|
844
|
-
function
|
|
845
|
-
|
|
603
|
+
function documentScriptFontSlotFromHint(hint) {
|
|
604
|
+
if ('eastAsia' === hint) return 'eastAsia';
|
|
605
|
+
if ('cs' === hint) return 'complexScript';
|
|
606
|
+
return 'ascii';
|
|
607
|
+
}
|
|
608
|
+
function documentFontNameFromCssFamily(value) {
|
|
609
|
+
if ('string' != typeof value) return null;
|
|
610
|
+
const source = value.trim();
|
|
611
|
+
if (!source) return null;
|
|
612
|
+
let family = '';
|
|
613
|
+
const quote = source[0];
|
|
614
|
+
if ('"' === quote || "'" === quote) {
|
|
615
|
+
let closed = false;
|
|
616
|
+
for(let index = 1; index < source.length; index += 1){
|
|
617
|
+
const character = source[index];
|
|
618
|
+
if (character === quote) {
|
|
619
|
+
closed = true;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
if ('\\' !== character) {
|
|
623
|
+
family += character;
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
const decoded = decodeCssEscape(source, index + 1);
|
|
627
|
+
if (!decoded) return null;
|
|
628
|
+
family += decoded.value;
|
|
629
|
+
index = decoded.end - 1;
|
|
630
|
+
}
|
|
631
|
+
if (!closed) return null;
|
|
632
|
+
} else family = source.split(',')[0] ?? '';
|
|
633
|
+
return normalizeDocumentFontName(family);
|
|
634
|
+
}
|
|
635
|
+
function cssDocumentFontFamily(value) {
|
|
636
|
+
const family = normalizeDocumentFontName(value);
|
|
637
|
+
return family ? cssFontFamily(family) : null;
|
|
638
|
+
}
|
|
639
|
+
function normalizeDocumentFontName(value) {
|
|
640
|
+
if ('string' != typeof value) return null;
|
|
641
|
+
const normalized = value.trim();
|
|
642
|
+
return normalized && normalized.length <= MAX_FONT_NAME_LENGTH && !/[\p{Cc}\p{Cs}]/u.test(normalized) ? normalized : null;
|
|
643
|
+
}
|
|
644
|
+
const scriptFontSlots = [
|
|
645
|
+
'ascii',
|
|
646
|
+
'highAnsi',
|
|
647
|
+
'eastAsia',
|
|
648
|
+
'complexScript'
|
|
649
|
+
];
|
|
650
|
+
function normalizeDocumentScriptFontFace(source) {
|
|
651
|
+
if (!isRecordWithKeys(source, SCRIPT_FONT_FACE_KEYS)) return null;
|
|
652
|
+
const name = void 0 === source.name ? void 0 : normalizeDocumentFontName(source.name);
|
|
653
|
+
const resolved = void 0 === source.resolved ? void 0 : normalizeDocumentFontName(source.resolved);
|
|
654
|
+
const theme = void 0 === source.theme ? void 0 : normalizeDocumentThemeFont(source.theme);
|
|
655
|
+
if (void 0 !== source.name && !name || void 0 !== source.resolved && !resolved || null === theme || !name && !theme && !resolved) return null;
|
|
846
656
|
return {
|
|
847
|
-
|
|
657
|
+
...name ? {
|
|
658
|
+
name
|
|
659
|
+
} : {},
|
|
848
660
|
...theme ? {
|
|
849
|
-
theme
|
|
661
|
+
theme
|
|
662
|
+
} : {},
|
|
663
|
+
...resolved ? {
|
|
664
|
+
resolved
|
|
850
665
|
} : {}
|
|
851
666
|
};
|
|
852
667
|
}
|
|
853
|
-
function
|
|
854
|
-
if (
|
|
855
|
-
|
|
668
|
+
function directFontFace(value) {
|
|
669
|
+
if (null === value) return null;
|
|
670
|
+
const name = documentFontNameFromCssFamily(value) ?? normalizeDocumentFontName(value);
|
|
671
|
+
return name ? {
|
|
672
|
+
name,
|
|
673
|
+
resolved: name
|
|
674
|
+
} : null;
|
|
856
675
|
}
|
|
857
|
-
function
|
|
858
|
-
|
|
859
|
-
return 'boolean' == typeof value ? value : null;
|
|
676
|
+
function documentScriptFontFaceFamily(face) {
|
|
677
|
+
return face?.resolved ?? face?.name;
|
|
860
678
|
}
|
|
861
|
-
function
|
|
862
|
-
if (
|
|
863
|
-
|
|
679
|
+
function strongDocumentScriptFontSlot(character) {
|
|
680
|
+
if (NEUTRAL_SCRIPT_CHARACTER.test(character)) return null;
|
|
681
|
+
const codePoint = character.codePointAt(0);
|
|
682
|
+
if (void 0 === codePoint) return null;
|
|
683
|
+
if (isComplexScriptCodePoint(codePoint)) return 'complexScript';
|
|
684
|
+
if (isEastAsianCodePoint(codePoint)) return 'eastAsia';
|
|
685
|
+
return codePoint <= 0x7f ? 'ascii' : 'highAnsi';
|
|
864
686
|
}
|
|
865
|
-
function
|
|
866
|
-
|
|
867
|
-
color: 'transparent',
|
|
868
|
-
style: 'none',
|
|
869
|
-
width: 0
|
|
870
|
-
};
|
|
871
|
-
const width = Math.min(16, isDocumentParagraphArtBorderStyle(border.style) ? border.size * POINTS_TO_PIXELS : border.size / 6);
|
|
872
|
-
return {
|
|
873
|
-
color: border.color && 'auto' !== border.color.value ? border.color.value : '#000000',
|
|
874
|
-
style: borderStyleToCssStyle(border.style),
|
|
875
|
-
width
|
|
876
|
-
};
|
|
687
|
+
function isComplexScriptCodePoint(codePoint) {
|
|
688
|
+
return codePoint >= 0x0590 && codePoint <= 0x08ff || codePoint >= 0x0900 && codePoint <= 0x109f || codePoint >= 0x1780 && codePoint <= 0x18af || codePoint >= 0x1900 && codePoint <= 0x1cff || codePoint >= 0xa800 && codePoint <= 0xa8ff || codePoint >= 0xa980 && codePoint <= 0xa9df || codePoint >= 0xaa00 && codePoint <= 0xaa7f || codePoint >= 0xabc0 && codePoint <= 0xabff || codePoint >= 0xfb1d && codePoint <= 0xfdff || codePoint >= 0xfe70 && codePoint <= 0xfeff || codePoint >= 0x10a00 && codePoint <= 0x10fff || codePoint >= 0x11000 && codePoint <= 0x11fff || codePoint >= 0x1e900 && codePoint <= 0x1edff || codePoint >= 0x1ee00 && codePoint <= 0x1eeff;
|
|
877
689
|
}
|
|
878
|
-
function
|
|
879
|
-
|
|
880
|
-
if ('dotted' === style) return 'dotted';
|
|
881
|
-
if (DASHED_BORDER_STYLES.has(style)) return 'dashed';
|
|
882
|
-
if (DOUBLE_BORDER_STYLES.has(style)) return 'double';
|
|
883
|
-
if ('inset' === style || 'threeDEngrave' === style) return 'inset';
|
|
884
|
-
if ('outset' === style || 'threeDEmboss' === style) return 'outset';
|
|
885
|
-
return 'solid';
|
|
690
|
+
function isEastAsianCodePoint(codePoint) {
|
|
691
|
+
return codePoint >= 0x1100 && codePoint <= 0x11ff || codePoint >= 0x2e80 && codePoint <= 0xa4cf || codePoint >= 0xac00 && codePoint <= 0xd7af || codePoint >= 0xf900 && codePoint <= 0xfaff || codePoint >= 0xfe10 && codePoint <= 0xfe6f || codePoint >= 0xff00 && codePoint <= 0xffef || codePoint >= 0x20000 && codePoint <= 0x323af;
|
|
886
692
|
}
|
|
887
|
-
function
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
693
|
+
function cssFontFamily(value) {
|
|
694
|
+
return /^(?:-?[\p{L}_])[\p{L}\p{N}_-]*$/u.test(value) ? value : `"${Array.from(value, cssStringCharacter).join('')}"`;
|
|
695
|
+
}
|
|
696
|
+
function cssStringCharacter(character) {
|
|
697
|
+
return /[\\":;{}<>]/u.test(character) ? `\\${character.codePointAt(0)?.toString(16)} ` : character;
|
|
698
|
+
}
|
|
699
|
+
function safeCssFontFamilyList(value) {
|
|
700
|
+
if ('string' != typeof value) return null;
|
|
701
|
+
const source = value.trim();
|
|
702
|
+
if (!source || source.length > 1024 || /[;{}]/u.test(source)) return null;
|
|
703
|
+
const tokens = [];
|
|
704
|
+
let start = 0;
|
|
705
|
+
let quote = '';
|
|
706
|
+
for(let index = 0; index < source.length; index += 1){
|
|
707
|
+
const character = source[index] ?? '';
|
|
708
|
+
if (quote) {
|
|
709
|
+
if ('\\' === character) {
|
|
710
|
+
index += 1;
|
|
711
|
+
if (index >= source.length) return null;
|
|
712
|
+
} else if (character === quote) quote = '';
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if ('"' === character || "'" === character) {
|
|
716
|
+
quote = character;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
if (',' === character) {
|
|
720
|
+
tokens.push(source.slice(start, index).trim());
|
|
721
|
+
start = index + 1;
|
|
722
|
+
}
|
|
903
723
|
}
|
|
904
|
-
|
|
724
|
+
if (quote) return null;
|
|
725
|
+
tokens.push(source.slice(start).trim());
|
|
726
|
+
return tokens.length && tokens.every(validCssFontFamilyToken) ? source : null;
|
|
905
727
|
}
|
|
906
|
-
function
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if ('none' === style || 'hidden' === style) return {
|
|
912
|
-
color: '#000000',
|
|
913
|
-
style: 'none',
|
|
914
|
-
width: 0
|
|
915
|
-
};
|
|
916
|
-
return color && 'transparent' !== color && Number.isFinite(width) && width > 0 ? {
|
|
917
|
-
color,
|
|
918
|
-
style,
|
|
919
|
-
width
|
|
920
|
-
} : null;
|
|
728
|
+
function validCssFontFamilyToken(value) {
|
|
729
|
+
if (!value) return false;
|
|
730
|
+
if (value.startsWith('"')) return /^"(?:[^"\\\r\n\f]|\\(?:[\da-f]{1,6}\s?|[^\r\n\f]))*"$/iu.test(value) && null !== documentFontNameFromCssFamily(value);
|
|
731
|
+
if (value.startsWith("'")) return /^'(?:[^'\\\r\n\f]|\\(?:[\da-f]{1,6}\s?|[^\r\n\f]))*'$/iu.test(value) && null !== documentFontNameFromCssFamily(value);
|
|
732
|
+
return /[\p{L}_]/u.test(value) && /^[\p{L}_-][\p{L}\p{N}_ -]*$/u.test(value) && !/^(?:inherit|initial|revert(?:-layer)?|unset)$/iu.test(value);
|
|
921
733
|
}
|
|
922
|
-
function
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
734
|
+
function decodeCssEscape(source, start) {
|
|
735
|
+
if (start >= source.length) return null;
|
|
736
|
+
const hexadecimal = /^[\da-f]{1,6}/i.exec(source.slice(start))?.[0];
|
|
737
|
+
if (hexadecimal) {
|
|
738
|
+
const codePoint = Number.parseInt(hexadecimal, 16);
|
|
739
|
+
if (0 === codePoint || codePoint > 0x10ffff || codePoint >= 0xd800 && codePoint <= 0xdfff) return null;
|
|
740
|
+
let end = start + hexadecimal.length;
|
|
741
|
+
if (/\s/u.test(source[end] ?? '')) end += 1;
|
|
742
|
+
return {
|
|
743
|
+
value: String.fromCodePoint(codePoint),
|
|
744
|
+
end
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
const value = source[start];
|
|
748
|
+
return !value || /[\r\n\f]/u.test(value) ? null : {
|
|
749
|
+
value,
|
|
750
|
+
end: start + 1
|
|
751
|
+
};
|
|
926
752
|
}
|
|
927
|
-
function
|
|
928
|
-
|
|
929
|
-
if ('dashed' === style) return 'dashed';
|
|
930
|
-
if ('dotted' === style) return 'dotted';
|
|
931
|
-
if ('inset' === style || 'groove' === style) return 'inset';
|
|
932
|
-
if ('outset' === style || 'ridge' === style) return 'outset';
|
|
933
|
-
return 'single';
|
|
753
|
+
function isRecordWithKeys(value, allowed) {
|
|
754
|
+
return 'object' == typeof value && null !== value && !Array.isArray(value) && Object.keys(value).every((key)=>allowed.has(key));
|
|
934
755
|
}
|
|
935
|
-
|
|
936
|
-
|
|
756
|
+
const DOCUMENT_PROOFING_LANGUAGES_ATTRIBUTE = 'data-office-proofing-languages';
|
|
757
|
+
const DOCUMENT_NO_PROOF_ATTRIBUTE = 'data-office-no-proof';
|
|
758
|
+
const PROOFING_LANGUAGE_KEYS = new Set([
|
|
759
|
+
'latin',
|
|
760
|
+
'eastAsia',
|
|
761
|
+
'bidi'
|
|
762
|
+
]);
|
|
763
|
+
const PROOFING_LANGUAGE_ORDER = [
|
|
764
|
+
'latin',
|
|
765
|
+
'eastAsia',
|
|
766
|
+
'bidi'
|
|
767
|
+
];
|
|
768
|
+
const MAX_LANGUAGE_TAG_LENGTH = 85;
|
|
769
|
+
const MAX_SERIALIZED_PROOFING_LANGUAGES_BYTES = 384;
|
|
770
|
+
function normalizeDocumentLanguageTag(source) {
|
|
771
|
+
if ('string' != typeof source) return null;
|
|
772
|
+
if (!source || source !== source.trim() || source.length > MAX_LANGUAGE_TAG_LENGTH || /[\p{Cc}\p{Cs}]/u.test(source)) return null;
|
|
773
|
+
return /^(?:x-none|[a-z0-9]{1,8}(?:-[a-z0-9]{1,8})*)$/iu.test(source) ? source : null;
|
|
937
774
|
}
|
|
938
|
-
function
|
|
939
|
-
|
|
775
|
+
function normalizeDocumentProofingLanguages(source) {
|
|
776
|
+
if (!isRecord(source)) return null;
|
|
777
|
+
const keys = Object.keys(source);
|
|
778
|
+
if (!keys.length || keys.some((key)=>!PROOFING_LANGUAGE_KEYS.has(key))) return null;
|
|
779
|
+
const normalized = {};
|
|
780
|
+
for (const key of PROOFING_LANGUAGE_ORDER){
|
|
781
|
+
if (void 0 === source[key]) continue;
|
|
782
|
+
const language = normalizeDocumentLanguageTag(source[key]);
|
|
783
|
+
if (!language) return null;
|
|
784
|
+
normalized[key] = language;
|
|
785
|
+
}
|
|
786
|
+
return Object.keys(normalized).length ? normalized : null;
|
|
940
787
|
}
|
|
941
|
-
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
const border = normalizeDocumentParagraphBorder(source);
|
|
947
|
-
return border && !isDocumentParagraphArtBorderStyle(border.style) ? border : null;
|
|
788
|
+
function serializeDocumentProofingLanguages(source) {
|
|
789
|
+
const normalized = normalizeDocumentProofingLanguages(source);
|
|
790
|
+
if (!normalized) return;
|
|
791
|
+
const serialized = JSON.stringify(normalized);
|
|
792
|
+
return serialized.length <= MAX_SERIALIZED_PROOFING_LANGUAGES_BYTES ? serialized : void 0;
|
|
948
793
|
}
|
|
949
|
-
function
|
|
950
|
-
if ('string' != typeof source) return
|
|
951
|
-
if (!source
|
|
794
|
+
function parseDocumentProofingLanguages(source) {
|
|
795
|
+
if ('string' != typeof source) return normalizeDocumentProofingLanguages(source);
|
|
796
|
+
if (!source || source.length > MAX_SERIALIZED_PROOFING_LANGUAGES_BYTES) return null;
|
|
952
797
|
try {
|
|
953
|
-
|
|
798
|
+
const normalized = normalizeDocumentProofingLanguages(JSON.parse(source));
|
|
799
|
+
return normalized && JSON.stringify(normalized) === source ? normalized : null;
|
|
954
800
|
} catch {
|
|
955
801
|
return null;
|
|
956
802
|
}
|
|
957
803
|
}
|
|
958
|
-
function
|
|
959
|
-
|
|
960
|
-
if (
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
804
|
+
function normalizeDocumentNoProof(source) {
|
|
805
|
+
if (true === source || 'true' === source || '1' === source) return true;
|
|
806
|
+
if (false === source || 'false' === source || '0' === source) return false;
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
function documentProofingLanguagesFromElement(element) {
|
|
810
|
+
return parseDocumentProofingLanguages(element.getAttribute(DOCUMENT_PROOFING_LANGUAGES_ATTRIBUTE));
|
|
811
|
+
}
|
|
812
|
+
function documentNoProofFromElement(element) {
|
|
813
|
+
return normalizeDocumentNoProof(element.getAttribute(DOCUMENT_NO_PROOF_ATTRIBUTE));
|
|
814
|
+
}
|
|
815
|
+
function documentProofingLanguageForScript(source, slot) {
|
|
816
|
+
const languages = normalizeDocumentProofingLanguages(source);
|
|
817
|
+
if (!languages) return;
|
|
818
|
+
if ('eastAsia' === slot) return languages.eastAsia ?? languages.latin;
|
|
819
|
+
if ('complexScript' === slot) return languages.bidi ?? languages.latin;
|
|
820
|
+
return languages.latin ?? languages.eastAsia ?? languages.bidi;
|
|
821
|
+
}
|
|
822
|
+
function documentProofingDomAttributes(languagesSource, noProofSource, slot) {
|
|
823
|
+
const languages = normalizeDocumentProofingLanguages(languagesSource);
|
|
824
|
+
const serialized = serializeDocumentProofingLanguages(languages);
|
|
825
|
+
const noProof = normalizeDocumentNoProof(noProofSource);
|
|
826
|
+
const language = documentProofingLanguageForScript(languages, slot);
|
|
827
|
+
return {
|
|
828
|
+
...serialized ? {
|
|
829
|
+
[DOCUMENT_PROOFING_LANGUAGES_ATTRIBUTE]: serialized
|
|
974
830
|
} : {},
|
|
975
|
-
...
|
|
976
|
-
|
|
831
|
+
...null === noProof ? {} : {
|
|
832
|
+
[DOCUMENT_NO_PROOF_ATTRIBUTE]: String(noProof)
|
|
833
|
+
},
|
|
834
|
+
...slot ? {
|
|
835
|
+
[DOCUMENT_SCRIPT_FONT_SLOT_ATTRIBUTE]: slot
|
|
977
836
|
} : {},
|
|
978
|
-
...
|
|
979
|
-
|
|
837
|
+
...language && 'x-none' !== language ? {
|
|
838
|
+
lang: language
|
|
980
839
|
} : {},
|
|
981
|
-
...
|
|
982
|
-
|
|
840
|
+
...true === noProof ? {
|
|
841
|
+
spellcheck: 'false'
|
|
983
842
|
} : {}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
function parseDocumentRunBorderElement(element) {
|
|
987
|
-
const semantic = parseDocumentRunBorder(element.getAttribute(DOCUMENT_RUN_BORDER_ATTRIBUTE));
|
|
988
|
-
return semantic ?? documentRunBorderFromCss(element);
|
|
843
|
+
};
|
|
989
844
|
}
|
|
990
|
-
function
|
|
991
|
-
const
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
const presentation = documentBorderPresentation(border);
|
|
995
|
-
const declarations = [
|
|
996
|
-
`border: ${work_document_run_border_formatPixels(presentation.width)}px ${presentation.style} ${presentation.color}`,
|
|
997
|
-
`padding: ${work_document_run_border_formatPixels((border.space ?? 0) * work_document_run_border_POINTS_TO_PIXELS)}px`,
|
|
998
|
-
'box-decoration-break: clone',
|
|
999
|
-
'-webkit-box-decoration-break: clone'
|
|
1000
|
-
];
|
|
1001
|
-
if (border.shadow && presentation.width > 0) declarations.push(`box-shadow: 2px 2px 0 ${presentation.color}`);
|
|
1002
|
-
return {
|
|
1003
|
-
[DOCUMENT_RUN_BORDER_ATTRIBUTE]: serialized,
|
|
1004
|
-
style: declarations.join('; ')
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
function documentRunBorderIsVisible(source) {
|
|
1008
|
-
const border = normalizeDocumentRunBorder(source);
|
|
1009
|
-
return Boolean(border && documentBorderPresentation(border).width > 0);
|
|
1010
|
-
}
|
|
1011
|
-
function documentRunBorderFromCss(element) {
|
|
1012
|
-
const style = element.style.borderStyle.trim();
|
|
1013
|
-
if (!style) return null;
|
|
1014
|
-
if ('none' === style || 'hidden' === style) return {
|
|
1015
|
-
style: 'none'
|
|
845
|
+
function patchDocumentProofingLanguages(source, patch) {
|
|
846
|
+
const current = normalizeDocumentProofingLanguages(source) ?? {};
|
|
847
|
+
const next = {
|
|
848
|
+
...current
|
|
1016
849
|
};
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
} : {}
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
function cssBorderStyle(style) {
|
|
1033
|
-
if ('double' === style) return 'double';
|
|
1034
|
-
if ('dashed' === style) return 'dashed';
|
|
1035
|
-
if ('dotted' === style) return 'dotted';
|
|
1036
|
-
if ('inset' === style || 'groove' === style) return 'inset';
|
|
1037
|
-
if ('outset' === style || 'ridge' === style) return 'outset';
|
|
1038
|
-
return 'single';
|
|
850
|
+
for (const slot of PROOFING_LANGUAGE_ORDER){
|
|
851
|
+
const value = patch[slot];
|
|
852
|
+
if (void 0 === value) continue;
|
|
853
|
+
if (null === value) {
|
|
854
|
+
delete next[slot];
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const language = normalizeDocumentLanguageTag(value);
|
|
858
|
+
if (!language) return null;
|
|
859
|
+
next[slot] = language;
|
|
860
|
+
}
|
|
861
|
+
return Object.keys(next).length ? next : null;
|
|
1039
862
|
}
|
|
1040
|
-
function
|
|
1041
|
-
return
|
|
863
|
+
function isRecord(source) {
|
|
864
|
+
return 'object' == typeof source && null !== source && !Array.isArray(source);
|
|
1042
865
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
'
|
|
1047
|
-
|
|
1048
|
-
'
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
'
|
|
1055
|
-
|
|
1056
|
-
'
|
|
1057
|
-
'thinHorzCross',
|
|
1058
|
-
'thinDiagCross',
|
|
1059
|
-
'pct5',
|
|
1060
|
-
'pct10',
|
|
1061
|
-
'pct12',
|
|
1062
|
-
'pct15',
|
|
1063
|
-
'pct20',
|
|
1064
|
-
'pct25',
|
|
1065
|
-
'pct30',
|
|
1066
|
-
'pct35',
|
|
1067
|
-
'pct37',
|
|
1068
|
-
'pct40',
|
|
1069
|
-
'pct45',
|
|
1070
|
-
'pct50',
|
|
1071
|
-
'pct55',
|
|
1072
|
-
'pct60',
|
|
1073
|
-
'pct62',
|
|
1074
|
-
'pct65',
|
|
1075
|
-
'pct70',
|
|
1076
|
-
'pct75',
|
|
1077
|
-
'pct80',
|
|
1078
|
-
'pct85',
|
|
1079
|
-
'pct87',
|
|
1080
|
-
'pct90',
|
|
1081
|
-
'pct95'
|
|
1082
|
-
]);
|
|
1083
|
-
function normalizeDocumentParagraphShading(source) {
|
|
1084
|
-
if (!source || 'object' != typeof source) return null;
|
|
1085
|
-
const value = source;
|
|
1086
|
-
const pattern = value.pattern;
|
|
1087
|
-
if ('string' != typeof pattern || !work_document_paragraph_shading_DOCUMENT_PARAGRAPH_SHADING_PATTERNS.has(pattern)) return null;
|
|
1088
|
-
const color = normalizeShadingColor(value.color);
|
|
1089
|
-
const fill = normalizeShadingColor(value.fill);
|
|
1090
|
-
if (void 0 !== value.color && !color || void 0 !== value.fill && !fill) return null;
|
|
1091
|
-
return {
|
|
1092
|
-
pattern: pattern,
|
|
1093
|
-
...color ? {
|
|
1094
|
-
color
|
|
1095
|
-
} : {},
|
|
1096
|
-
...fill ? {
|
|
1097
|
-
fill
|
|
1098
|
-
} : {}
|
|
1099
|
-
};
|
|
866
|
+
function normalizeCssColor(source) {
|
|
867
|
+
const value = source?.trim().toLowerCase();
|
|
868
|
+
if (!value) return null;
|
|
869
|
+
if ('transparent' === value) return 'transparent';
|
|
870
|
+
const shortHex = /^#([0-9a-f]{3})$/i.exec(value);
|
|
871
|
+
if (shortHex?.[1]) return `#${Array.from(shortHex[1]).map((channel)=>`${channel}${channel}`).join('')}`;
|
|
872
|
+
if (/^#[0-9a-f]{6}$/i.test(value)) return value;
|
|
873
|
+
const rgb = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*([\d.]+))?\s*\)$/i.exec(value);
|
|
874
|
+
if (!rgb) return null;
|
|
875
|
+
const channels = rgb.slice(1, 4).map(Number);
|
|
876
|
+
if (channels.some((channel)=>channel < 0 || channel > 255)) return null;
|
|
877
|
+
if (void 0 !== rgb[4] && 0 === Number(rgb[4])) return 'transparent';
|
|
878
|
+
if (void 0 !== rgb[4] && 1 !== Number(rgb[4])) return null;
|
|
879
|
+
return `#${channels.map((channel)=>channel.toString(16).padStart(2, '0')).join('')}`;
|
|
1100
880
|
}
|
|
1101
|
-
function
|
|
1102
|
-
|
|
1103
|
-
|
|
881
|
+
function decodeXmlBytes(bytes, label) {
|
|
882
|
+
let encoding = 'utf-8';
|
|
883
|
+
let offset = 0;
|
|
884
|
+
if (0xef === bytes[0] && 0xbb === bytes[1] && 0xbf === bytes[2]) offset = 3;
|
|
885
|
+
else if (0xff === bytes[0] && 0xfe === bytes[1]) {
|
|
886
|
+
encoding = 'utf-16le';
|
|
887
|
+
offset = 2;
|
|
888
|
+
} else if (0xfe === bytes[0] && 0xff === bytes[1]) {
|
|
889
|
+
encoding = 'utf-16be';
|
|
890
|
+
offset = 2;
|
|
891
|
+
} else if (0x3c === bytes[0] && 0 === bytes[1] && 0x3f === bytes[2] && 0 === bytes[3]) encoding = 'utf-16le';
|
|
892
|
+
else if (0 === bytes[0] && 0x3c === bytes[1] && 0 === bytes[2] && 0x3f === bytes[3]) encoding = 'utf-16be';
|
|
1104
893
|
try {
|
|
1105
|
-
return
|
|
894
|
+
return new TextDecoder(encoding, {
|
|
895
|
+
fatal: true
|
|
896
|
+
}).decode(bytes.subarray(offset));
|
|
1106
897
|
} catch {
|
|
1107
|
-
|
|
898
|
+
throw new Error(`${label} uses an invalid ${encoding} XML encoding.`);
|
|
1108
899
|
}
|
|
1109
900
|
}
|
|
1110
|
-
function
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
return
|
|
1114
|
-
pattern: shading.pattern,
|
|
1115
|
-
...shading.color ? {
|
|
1116
|
-
color: serializedShadingColor(shading.color)
|
|
1117
|
-
} : {},
|
|
1118
|
-
...shading.fill ? {
|
|
1119
|
-
fill: serializedShadingColor(shading.fill)
|
|
1120
|
-
} : {}
|
|
1121
|
-
});
|
|
901
|
+
function serializeUtf8Xml(document) {
|
|
902
|
+
const serialized = new XMLSerializer().serializeToString(document);
|
|
903
|
+
const body = serialized.replace(/^\s*<\?xml[^?]*\?>\s*/i, '');
|
|
904
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${body}`;
|
|
1122
905
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
const expected = normalizeCssColor(presentation.backgroundColor);
|
|
1129
|
-
if (background && expected && background !== expected) {
|
|
1130
|
-
if ('transparent' === background) return {
|
|
1131
|
-
pattern: 'nil'
|
|
1132
|
-
};
|
|
1133
|
-
if (paragraphShadingBackgroundUsesForeground(semantic)) return {
|
|
1134
|
-
...semantic,
|
|
1135
|
-
color: {
|
|
1136
|
-
value: background
|
|
1137
|
-
}
|
|
1138
|
-
};
|
|
1139
|
-
return {
|
|
1140
|
-
...semantic,
|
|
1141
|
-
fill: {
|
|
1142
|
-
value: background
|
|
1143
|
-
}
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
return semantic;
|
|
906
|
+
class OoxmlPackage {
|
|
907
|
+
zip;
|
|
908
|
+
textCache = new Map();
|
|
909
|
+
constructor(zip){
|
|
910
|
+
this.zip = zip;
|
|
1147
911
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
912
|
+
static async load(buffer) {
|
|
913
|
+
return new OoxmlPackage(await jszip.loadAsync(buffer));
|
|
914
|
+
}
|
|
915
|
+
has(partPath) {
|
|
916
|
+
return Boolean(this.zip.file(partPath));
|
|
917
|
+
}
|
|
918
|
+
paths(prefix) {
|
|
919
|
+
return Object.keys(this.zip.files).filter((path)=>path.startsWith(prefix) && !this.zip.files[path]?.dir);
|
|
920
|
+
}
|
|
921
|
+
async text(partPath) {
|
|
922
|
+
const cached = this.textCache.get(partPath);
|
|
923
|
+
if (cached) return cached;
|
|
924
|
+
const entry = this.zip.file(partPath);
|
|
925
|
+
if (!entry) throw new Error(`Office package part is missing: ${partPath}`);
|
|
926
|
+
const pending = entry.async('uint8array').then((bytes)=>decodeXmlBytes(bytes, partPath));
|
|
927
|
+
this.textCache.set(partPath, pending);
|
|
928
|
+
try {
|
|
929
|
+
return await pending;
|
|
930
|
+
} catch (error) {
|
|
931
|
+
this.textCache.delete(partPath);
|
|
932
|
+
throw error;
|
|
1152
933
|
}
|
|
1153
|
-
}
|
|
934
|
+
}
|
|
935
|
+
async xml(partPath) {
|
|
936
|
+
return parseXml(await this.text(partPath), partPath);
|
|
937
|
+
}
|
|
938
|
+
async bytes(partPath) {
|
|
939
|
+
const entry = this.zip.file(partPath);
|
|
940
|
+
if (!entry) throw new Error(`Office package part is missing: ${partPath}`);
|
|
941
|
+
return entry.async('uint8array');
|
|
942
|
+
}
|
|
943
|
+
async relationships(sourcePart) {
|
|
944
|
+
const partPath = relationshipsPartPath(sourcePart);
|
|
945
|
+
if (!this.has(partPath)) return new Map();
|
|
946
|
+
const document = await this.xml(partPath);
|
|
947
|
+
return new Map(descendants(document, 'Relationship').map((element)=>{
|
|
948
|
+
const relationship = {
|
|
949
|
+
id: attribute(element, 'Id') ?? '',
|
|
950
|
+
target: resolvePartTarget(sourcePart, attribute(element, 'Target') ?? ''),
|
|
951
|
+
type: attribute(element, 'Type') ?? '',
|
|
952
|
+
targetMode: attribute(element, 'TargetMode') ?? void 0
|
|
953
|
+
};
|
|
954
|
+
return [
|
|
955
|
+
relationship.id,
|
|
956
|
+
relationship
|
|
957
|
+
];
|
|
958
|
+
}));
|
|
959
|
+
}
|
|
1154
960
|
}
|
|
1155
|
-
function
|
|
1156
|
-
const
|
|
1157
|
-
const
|
|
1158
|
-
if (
|
|
1159
|
-
|
|
1160
|
-
const styles = [
|
|
1161
|
-
`background-color: ${presentation.backgroundColor}`,
|
|
1162
|
-
presentation.backgroundImage ? `background-image: ${presentation.backgroundImage}` : '',
|
|
1163
|
-
presentation.backgroundSize ? `background-size: ${presentation.backgroundSize}` : ''
|
|
1164
|
-
].filter(Boolean);
|
|
1165
|
-
return {
|
|
1166
|
-
'data-office-paragraph-shading': serialized,
|
|
1167
|
-
style: styles.join('; ')
|
|
1168
|
-
};
|
|
961
|
+
function parseXml(source, label = 'Office XML') {
|
|
962
|
+
const document = new DOMParser().parseFromString(source, 'application/xml');
|
|
963
|
+
const error = descendants(document, 'parsererror')[0];
|
|
964
|
+
if (error) throw new Error(`${label} is not valid XML: ${error.textContent?.trim() || 'parse error'}`);
|
|
965
|
+
return document;
|
|
1169
966
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
theme
|
|
1182
|
-
} : {}
|
|
1183
|
-
};
|
|
967
|
+
const xmlElementPatterns = new Map();
|
|
968
|
+
function xmlContainsAnyElement(source, localNames) {
|
|
969
|
+
if (!source || !localNames.length) return false;
|
|
970
|
+
const key = localNames.join('\u0000');
|
|
971
|
+
let pattern = xmlElementPatterns.get(key);
|
|
972
|
+
if (!pattern) {
|
|
973
|
+
const alternatives = localNames.map((name)=>name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
974
|
+
pattern = new RegExp(`<(?:[A-Za-z_][\\w.-]*:)?(?:${alternatives})(?=[\\s/>])`);
|
|
975
|
+
xmlElementPatterns.set(key, pattern);
|
|
976
|
+
}
|
|
977
|
+
return pattern.test(source);
|
|
1184
978
|
}
|
|
1185
|
-
function
|
|
1186
|
-
const
|
|
1187
|
-
return
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
};
|
|
979
|
+
function attribute(element, name) {
|
|
980
|
+
const direct = element.getAttribute(name);
|
|
981
|
+
if (null !== direct) return direct;
|
|
982
|
+
const localName = name.includes(':') ? name.slice(name.indexOf(':') + 1) : name;
|
|
983
|
+
return Array.from(element.attributes).find((item)=>{
|
|
984
|
+
const itemLocalName = item.localName.includes(':') ? item.localName.slice(item.localName.indexOf(':') + 1) : item.localName;
|
|
985
|
+
return itemLocalName === localName && (!name.includes(':') || item.name === name);
|
|
986
|
+
})?.value ?? null;
|
|
1193
987
|
}
|
|
1194
|
-
function
|
|
1195
|
-
if (
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
const background = shadingColor(shading.fill, 'transparent');
|
|
1200
|
-
if ('clear' === shading.pattern) return {
|
|
1201
|
-
backgroundColor: background
|
|
1202
|
-
};
|
|
1203
|
-
if ('solid' === shading.pattern) return {
|
|
1204
|
-
backgroundColor: foreground
|
|
1205
|
-
};
|
|
1206
|
-
const percentage = shadingPercentage(shading.pattern);
|
|
1207
|
-
if (null !== percentage) {
|
|
1208
|
-
const inverted = percentage > 50;
|
|
1209
|
-
const dotColor = inverted ? background : foreground;
|
|
1210
|
-
const baseColor = inverted ? foreground : background;
|
|
1211
|
-
const density = Math.min(50, inverted ? 100 - percentage : percentage);
|
|
1212
|
-
const spacing = Math.max(2, Math.round(9 - density / 7));
|
|
1213
|
-
return {
|
|
1214
|
-
backgroundColor: baseColor,
|
|
1215
|
-
backgroundImage: `radial-gradient(circle, ${dotColor} 0 1px, transparent 1.2px)`,
|
|
1216
|
-
backgroundSize: `${spacing}px ${spacing}px`
|
|
1217
|
-
};
|
|
988
|
+
function xmlNamespacePrefix(element, namespace) {
|
|
989
|
+
if (!namespace) return element.prefix;
|
|
990
|
+
if ('function' == typeof element.lookupPrefix) {
|
|
991
|
+
const prefix = element.lookupPrefix(namespace);
|
|
992
|
+
if (prefix) return prefix;
|
|
1218
993
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
function shadingColor(color, fallback) {
|
|
1230
|
-
return color && 'auto' !== color.value ? color.value : fallback;
|
|
994
|
+
let current = element;
|
|
995
|
+
while(current){
|
|
996
|
+
if (current.namespaceURI === namespace && current.prefix) return current.prefix;
|
|
997
|
+
const declaration = Array.from(current.attributes).find((item)=>item.value === namespace && ('xmlns' === item.name || item.name.startsWith('xmlns:')));
|
|
998
|
+
if (declaration?.name.startsWith('xmlns:')) return declaration.name.slice(6);
|
|
999
|
+
current = current.parentElement;
|
|
1000
|
+
}
|
|
1001
|
+
return null;
|
|
1231
1002
|
}
|
|
1232
|
-
function
|
|
1233
|
-
|
|
1234
|
-
return match?.[1] ? Number(match[1]) : null;
|
|
1003
|
+
function directChildren(parent, localName) {
|
|
1004
|
+
return Array.from(parent.children).filter((element)=>!localName || element.localName === localName);
|
|
1235
1005
|
}
|
|
1236
|
-
function
|
|
1237
|
-
|
|
1238
|
-
const percentage = shadingPercentage(shading.pattern);
|
|
1239
|
-
return null !== percentage && percentage > 50;
|
|
1006
|
+
function directChild(parent, localName) {
|
|
1007
|
+
return directChildren(parent, localName)[0];
|
|
1240
1008
|
}
|
|
1241
|
-
function
|
|
1242
|
-
|
|
1243
|
-
0,
|
|
1244
|
-
90
|
|
1245
|
-
];
|
|
1246
|
-
if (pattern.includes('DiagCross')) return [
|
|
1247
|
-
45,
|
|
1248
|
-
-45
|
|
1249
|
-
];
|
|
1250
|
-
if (pattern.includes('VertStripe')) return [
|
|
1251
|
-
90
|
|
1252
|
-
];
|
|
1253
|
-
if (pattern.includes('ReverseDiagStripe')) return [
|
|
1254
|
-
-45
|
|
1255
|
-
];
|
|
1256
|
-
if (pattern.includes('DiagStripe')) return [
|
|
1257
|
-
45
|
|
1258
|
-
];
|
|
1259
|
-
return [
|
|
1260
|
-
0
|
|
1261
|
-
];
|
|
1009
|
+
function descendants(parent, localName) {
|
|
1010
|
+
return Array.from(parent.querySelectorAll('*')).filter((element)=>element.localName === localName);
|
|
1262
1011
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
'blue',
|
|
1267
|
-
'cyan',
|
|
1268
|
-
'darkBlue',
|
|
1269
|
-
'darkCyan',
|
|
1270
|
-
'darkGray',
|
|
1271
|
-
'darkGreen',
|
|
1272
|
-
'darkMagenta',
|
|
1273
|
-
'darkRed',
|
|
1274
|
-
'darkYellow',
|
|
1275
|
-
'green',
|
|
1276
|
-
'lightGray',
|
|
1277
|
-
'magenta',
|
|
1278
|
-
'none',
|
|
1279
|
-
'red',
|
|
1280
|
-
'white',
|
|
1281
|
-
'yellow'
|
|
1282
|
-
]);
|
|
1283
|
-
const HIGHLIGHT_COLORS = {
|
|
1284
|
-
black: '#000000',
|
|
1285
|
-
blue: '#0000ff',
|
|
1286
|
-
cyan: '#00ffff',
|
|
1287
|
-
darkBlue: '#000080',
|
|
1288
|
-
darkCyan: '#008080',
|
|
1289
|
-
darkGray: '#808080',
|
|
1290
|
-
darkGreen: '#008000',
|
|
1291
|
-
darkMagenta: '#800080',
|
|
1292
|
-
darkRed: '#800000',
|
|
1293
|
-
darkYellow: '#808000',
|
|
1294
|
-
green: '#00ff00',
|
|
1295
|
-
lightGray: '#c0c0c0',
|
|
1296
|
-
magenta: '#ff00ff',
|
|
1297
|
-
none: 'transparent',
|
|
1298
|
-
red: '#ff0000',
|
|
1299
|
-
white: '#ffffff',
|
|
1300
|
-
yellow: '#ffff00'
|
|
1301
|
-
};
|
|
1302
|
-
function normalizeDocumentHighlight(source) {
|
|
1303
|
-
return 'string' == typeof source && DOCUMENT_HIGHLIGHT_VALUES.has(source) ? source : null;
|
|
1012
|
+
function firstDescendant(parent, localName) {
|
|
1013
|
+
if (!parent) return;
|
|
1014
|
+
return descendants(parent, localName)[0];
|
|
1304
1015
|
}
|
|
1305
|
-
function
|
|
1306
|
-
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1309
|
-
|
|
1016
|
+
function childPath(parent, ...localNames) {
|
|
1017
|
+
let current = parent;
|
|
1018
|
+
for (const name of localNames){
|
|
1019
|
+
if (!current) return;
|
|
1020
|
+
current = directChild(current, name);
|
|
1021
|
+
}
|
|
1022
|
+
return current instanceof Element ? current : void 0;
|
|
1310
1023
|
}
|
|
1311
|
-
function
|
|
1312
|
-
|
|
1313
|
-
|
|
1024
|
+
function resolvePartTarget(sourcePart, target) {
|
|
1025
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target)) return target;
|
|
1026
|
+
const segments = target.startsWith('/') ? [] : sourcePart.split('/').slice(0, -1);
|
|
1027
|
+
for (const segment of target.replace(/^\/+/, '').split('/'))if (segment && '.' !== segment) if ('..' === segment) segments.pop();
|
|
1028
|
+
else segments.push(segment);
|
|
1029
|
+
return segments.join('/');
|
|
1314
1030
|
}
|
|
1315
|
-
function
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1031
|
+
function contentTypeForPart(partPath) {
|
|
1032
|
+
const extension = partPath.split('.').pop()?.toLowerCase();
|
|
1033
|
+
const types = {
|
|
1034
|
+
apng: 'image/apng',
|
|
1035
|
+
bmp: 'image/bmp',
|
|
1036
|
+
emf: 'image/emf',
|
|
1037
|
+
gif: 'image/gif',
|
|
1038
|
+
jpeg: 'image/jpeg',
|
|
1039
|
+
jpg: 'image/jpeg',
|
|
1040
|
+
png: 'image/png',
|
|
1041
|
+
svg: 'image/svg+xml',
|
|
1042
|
+
tif: 'image/tiff',
|
|
1043
|
+
tiff: 'image/tiff',
|
|
1044
|
+
webp: 'image/webp',
|
|
1045
|
+
wmf: 'image/wmf'
|
|
1046
|
+
};
|
|
1047
|
+
return types[extension ?? ''] ?? 'application/octet-stream';
|
|
1320
1048
|
}
|
|
1321
|
-
function
|
|
1322
|
-
|
|
1049
|
+
function bytesToDataUrl(bytes, contentType) {
|
|
1050
|
+
let binary = '';
|
|
1051
|
+
const chunkSize = 32768;
|
|
1052
|
+
for(let offset = 0; offset < bytes.length; offset += chunkSize)binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
|
|
1053
|
+
return `data:${contentType};base64,${btoa(binary)}`;
|
|
1323
1054
|
}
|
|
1324
|
-
function
|
|
1325
|
-
const
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
style: `background-color: ${color}`
|
|
1330
|
-
} : {};
|
|
1055
|
+
function relationshipsPartPath(sourcePart) {
|
|
1056
|
+
const separator = sourcePart.lastIndexOf('/');
|
|
1057
|
+
const directory = separator >= 0 ? sourcePart.slice(0, separator + 1) : '';
|
|
1058
|
+
const fileName = separator >= 0 ? sourcePart.slice(separator + 1) : sourcePart;
|
|
1059
|
+
return `${directory}_rels/${fileName}.rels`;
|
|
1331
1060
|
}
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
]){
|
|
1355
|
-
const color = source[name];
|
|
1356
|
-
if (void 0 !== color) {
|
|
1357
|
-
if (!isRecordWithKeys(color, RUN_SHADING_COLOR_KEYS)) return null;
|
|
1358
|
-
if (void 0 !== color.theme && !isRecordWithKeys(color.theme, THEME_REFERENCE_KEYS)) return null;
|
|
1359
|
-
}
|
|
1061
|
+
const WORD_NAMESPACE = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
|
|
1062
|
+
class DocxThemePatchCollector {
|
|
1063
|
+
patches = [];
|
|
1064
|
+
nextMarker = 1;
|
|
1065
|
+
usedColors;
|
|
1066
|
+
constructor(sourceHtml){
|
|
1067
|
+
this.usedColors = sourceColors(sourceHtml);
|
|
1068
|
+
}
|
|
1069
|
+
marker(kind, reference, currentColor) {
|
|
1070
|
+
if (!reference || normalizeColor(currentColor) !== reference.resolved) return null;
|
|
1071
|
+
let marker = '';
|
|
1072
|
+
do {
|
|
1073
|
+
marker = (0xf00000 + this.nextMarker).toString(16).padStart(6, '0').toUpperCase();
|
|
1074
|
+
this.nextMarker += 1;
|
|
1075
|
+
}while (this.usedColors.has(marker))
|
|
1076
|
+
this.usedColors.add(marker);
|
|
1077
|
+
this.patches.push({
|
|
1078
|
+
kind,
|
|
1079
|
+
marker,
|
|
1080
|
+
reference
|
|
1081
|
+
});
|
|
1082
|
+
return marker;
|
|
1360
1083
|
}
|
|
1361
|
-
return normalizeDocumentParagraphShading(source);
|
|
1362
1084
|
}
|
|
1363
|
-
function
|
|
1364
|
-
|
|
1365
|
-
|
|
1085
|
+
function serializeDocxThemeReference(reference) {
|
|
1086
|
+
return reference ? JSON.stringify(reference) : void 0;
|
|
1087
|
+
}
|
|
1088
|
+
function parseDocxThemeReference(value) {
|
|
1089
|
+
if (!value) return null;
|
|
1366
1090
|
try {
|
|
1367
|
-
|
|
1091
|
+
const parsed = JSON.parse(value);
|
|
1092
|
+
const theme = 'string' == typeof parsed.theme ? parsed.theme.trim() : '';
|
|
1093
|
+
const resolved = normalizeColor('string' == typeof parsed.resolved ? parsed.resolved : null);
|
|
1094
|
+
const tint = byteHex(parsed.tint);
|
|
1095
|
+
const shade = byteHex(parsed.shade);
|
|
1096
|
+
if (!theme || !resolved) return null;
|
|
1097
|
+
return {
|
|
1098
|
+
theme,
|
|
1099
|
+
resolved,
|
|
1100
|
+
...tint ? {
|
|
1101
|
+
tint
|
|
1102
|
+
} : {},
|
|
1103
|
+
...shade ? {
|
|
1104
|
+
shade
|
|
1105
|
+
} : {}
|
|
1106
|
+
};
|
|
1368
1107
|
} catch {
|
|
1369
1108
|
return null;
|
|
1370
1109
|
}
|
|
1371
1110
|
}
|
|
1372
|
-
function
|
|
1373
|
-
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1111
|
+
async function patchDocxThemeReferences(buffer, patches) {
|
|
1112
|
+
if (!patches.length) return buffer;
|
|
1113
|
+
const archive = await jszip.loadAsync(buffer);
|
|
1114
|
+
const byMarker = new Map(patches.map((patch)=>[
|
|
1115
|
+
patch.marker,
|
|
1116
|
+
patch
|
|
1117
|
+
]));
|
|
1118
|
+
const entries = Object.values(archive.files).filter((entry)=>!entry.dir && /^word\/(?:document|header\d+|footer\d+|footnotes|endnotes|comments)\.xml$/.test(entry.name));
|
|
1119
|
+
for (const entry of entries){
|
|
1120
|
+
const document = parseXml(await entry.async('text'), entry.name);
|
|
1121
|
+
let changed = false;
|
|
1122
|
+
for (const element of Array.from(document.getElementsByTagName('*')))for (const target of themePatchTargets(element.localName)){
|
|
1123
|
+
const marker = wordAttribute(element, target.directAttribute)?.toUpperCase();
|
|
1124
|
+
const patch = marker ? byMarker.get(marker) : void 0;
|
|
1125
|
+
if (patch && patch.kind === target.kind) {
|
|
1126
|
+
setWordAttribute(document, element, target.directAttribute, patch.reference.resolved.slice(1).toUpperCase());
|
|
1127
|
+
setWordAttribute(document, element, target.themeAttribute, patch.reference.theme);
|
|
1128
|
+
setOptionalWordAttribute(document, element, target.tintAttribute, patch.reference.tint);
|
|
1129
|
+
setOptionalWordAttribute(document, element, target.shadeAttribute, patch.reference.shade);
|
|
1130
|
+
changed = true;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (changed) archive.file(entry.name, new XMLSerializer().serializeToString(document));
|
|
1134
|
+
}
|
|
1135
|
+
return archive.generateAsync({
|
|
1136
|
+
type: 'arraybuffer'
|
|
1137
|
+
});
|
|
1376
1138
|
}
|
|
1377
|
-
function
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
pattern: 'nil'
|
|
1386
|
-
};
|
|
1387
|
-
return paragraphShadingBackgroundUsesForeground(semantic) ? {
|
|
1388
|
-
...semantic,
|
|
1389
|
-
color: {
|
|
1390
|
-
value: background
|
|
1139
|
+
function themePatchTargets(localName) {
|
|
1140
|
+
if ('color' === localName) return [
|
|
1141
|
+
{
|
|
1142
|
+
kind: 'color',
|
|
1143
|
+
directAttribute: 'val',
|
|
1144
|
+
themeAttribute: 'themeColor',
|
|
1145
|
+
tintAttribute: 'themeTint',
|
|
1146
|
+
shadeAttribute: 'themeShade'
|
|
1391
1147
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1148
|
+
];
|
|
1149
|
+
if ('u' === localName) return [
|
|
1150
|
+
{
|
|
1151
|
+
kind: 'underline',
|
|
1152
|
+
directAttribute: 'color',
|
|
1153
|
+
themeAttribute: 'themeColor',
|
|
1154
|
+
tintAttribute: 'themeTint',
|
|
1155
|
+
shadeAttribute: 'themeShade'
|
|
1396
1156
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1157
|
+
];
|
|
1158
|
+
if ('shd' === localName) return [
|
|
1159
|
+
{
|
|
1160
|
+
kind: 'fill',
|
|
1161
|
+
directAttribute: 'fill',
|
|
1162
|
+
themeAttribute: 'themeFill',
|
|
1163
|
+
tintAttribute: 'themeFillTint',
|
|
1164
|
+
shadeAttribute: 'themeFillShade'
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
kind: 'shadingColor',
|
|
1168
|
+
directAttribute: 'color',
|
|
1169
|
+
themeAttribute: 'themeColor',
|
|
1170
|
+
tintAttribute: 'themeTint',
|
|
1171
|
+
shadeAttribute: 'themeShade'
|
|
1172
|
+
}
|
|
1173
|
+
];
|
|
1174
|
+
return [
|
|
1175
|
+
'top',
|
|
1176
|
+
'right',
|
|
1177
|
+
'bottom',
|
|
1178
|
+
'left',
|
|
1179
|
+
'start',
|
|
1180
|
+
'end'
|
|
1181
|
+
].includes(localName) ? [
|
|
1182
|
+
{
|
|
1183
|
+
kind: 'border',
|
|
1184
|
+
directAttribute: 'color',
|
|
1185
|
+
themeAttribute: 'themeColor',
|
|
1186
|
+
tintAttribute: 'themeTint',
|
|
1187
|
+
shadeAttribute: 'themeShade'
|
|
1188
|
+
}
|
|
1189
|
+
] : [];
|
|
1413
1190
|
}
|
|
1414
|
-
function
|
|
1415
|
-
|
|
1191
|
+
function normalizeColor(value) {
|
|
1192
|
+
const normalized = value?.trim().toLowerCase();
|
|
1193
|
+
if (!normalized || !/^#[0-9a-f]{6}$/.test(normalized)) return null;
|
|
1194
|
+
return normalized;
|
|
1416
1195
|
}
|
|
1417
|
-
|
|
1418
|
-
const
|
|
1419
|
-
const
|
|
1420
|
-
const
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1423
|
-
'highAnsi',
|
|
1424
|
-
'eastAsia',
|
|
1425
|
-
'complexScript',
|
|
1426
|
-
'hint'
|
|
1427
|
-
]);
|
|
1428
|
-
const SCRIPT_FONT_FACE_KEYS = new Set([
|
|
1429
|
-
'name',
|
|
1430
|
-
'theme',
|
|
1431
|
-
'resolved'
|
|
1432
|
-
]);
|
|
1433
|
-
const SCRIPT_FONT_HINTS = new Set([
|
|
1434
|
-
'default',
|
|
1435
|
-
'eastAsia',
|
|
1436
|
-
'cs'
|
|
1437
|
-
]);
|
|
1438
|
-
const THEME_FONTS = new Set([
|
|
1439
|
-
'majorEastAsia',
|
|
1440
|
-
'majorBidi',
|
|
1441
|
-
'majorAscii',
|
|
1442
|
-
'majorHAnsi',
|
|
1443
|
-
'minorEastAsia',
|
|
1444
|
-
'minorBidi',
|
|
1445
|
-
'minorAscii',
|
|
1446
|
-
'minorHAnsi'
|
|
1447
|
-
]);
|
|
1448
|
-
const SLOT_FALLBACK_ORDER = {
|
|
1449
|
-
ascii: [
|
|
1450
|
-
'ascii',
|
|
1451
|
-
'highAnsi',
|
|
1452
|
-
'eastAsia',
|
|
1453
|
-
'complexScript'
|
|
1454
|
-
],
|
|
1455
|
-
highAnsi: [
|
|
1456
|
-
'highAnsi',
|
|
1457
|
-
'ascii',
|
|
1458
|
-
'eastAsia',
|
|
1459
|
-
'complexScript'
|
|
1460
|
-
],
|
|
1461
|
-
eastAsia: [
|
|
1462
|
-
'eastAsia',
|
|
1463
|
-
'highAnsi',
|
|
1464
|
-
'ascii',
|
|
1465
|
-
'complexScript'
|
|
1466
|
-
],
|
|
1467
|
-
complexScript: [
|
|
1468
|
-
'complexScript',
|
|
1469
|
-
'highAnsi',
|
|
1470
|
-
'ascii',
|
|
1471
|
-
'eastAsia'
|
|
1472
|
-
]
|
|
1473
|
-
};
|
|
1474
|
-
const NEUTRAL_SCRIPT_CHARACTER = /^[\p{Cc}\p{Cf}\p{M}\p{N}\p{P}\p{S}\p{Z}]$/u;
|
|
1475
|
-
function normalizeDocumentScriptFonts(source) {
|
|
1476
|
-
if (!work_document_script_fonts_isRecordWithKeys(source, SCRIPT_FONT_KEYS)) return null;
|
|
1477
|
-
const normalized = {};
|
|
1478
|
-
for (const slot of scriptFontSlots){
|
|
1479
|
-
if (void 0 === source[slot]) continue;
|
|
1480
|
-
const face = normalizeDocumentScriptFontFace(source[slot]);
|
|
1481
|
-
if (!face) return null;
|
|
1482
|
-
normalized[slot] = face;
|
|
1483
|
-
}
|
|
1484
|
-
if (void 0 !== source.hint) {
|
|
1485
|
-
const hint = normalizeDocumentScriptFontHint(source.hint);
|
|
1486
|
-
if (!hint) return null;
|
|
1487
|
-
normalized.hint = hint;
|
|
1196
|
+
function sourceColors(source) {
|
|
1197
|
+
const colors = new Set();
|
|
1198
|
+
for (const match of source.matchAll(/#([0-9a-f]{6})\b/gi))if (match[1]) colors.add(match[1].toUpperCase());
|
|
1199
|
+
for (const match of source.matchAll(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/gi)){
|
|
1200
|
+
const channels = match.slice(1, 4).map(Number);
|
|
1201
|
+
if (!channels.some((channel)=>channel < 0 || channel > 255)) colors.add(channels.map((channel)=>channel.toString(16).padStart(2, '0')).join('').toUpperCase());
|
|
1488
1202
|
}
|
|
1489
|
-
return
|
|
1203
|
+
return colors;
|
|
1490
1204
|
}
|
|
1491
|
-
function
|
|
1492
|
-
|
|
1493
|
-
|
|
1205
|
+
function byteHex(value) {
|
|
1206
|
+
if ('string' != typeof value) return;
|
|
1207
|
+
const normalized = value.trim().toUpperCase();
|
|
1208
|
+
return /^[0-9A-F]{2}$/.test(normalized) ? normalized : void 0;
|
|
1494
1209
|
}
|
|
1495
|
-
function
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1210
|
+
function wordAttribute(element, name) {
|
|
1211
|
+
return element.getAttributeNS(WORD_NAMESPACE, name) ?? element.getAttribute(`w:${name}`);
|
|
1212
|
+
}
|
|
1213
|
+
function setWordAttribute(document, element, name, value) {
|
|
1214
|
+
const prefix = xmlNamespacePrefix(document.documentElement, WORD_NAMESPACE) ?? 'w';
|
|
1215
|
+
element.setAttributeNS(WORD_NAMESPACE, `${prefix}:${name}`, value);
|
|
1216
|
+
}
|
|
1217
|
+
function setOptionalWordAttribute(document, element, name, value) {
|
|
1218
|
+
if (value) setWordAttribute(document, element, name, value);
|
|
1219
|
+
else element.removeAttributeNS(WORD_NAMESPACE, name);
|
|
1220
|
+
}
|
|
1221
|
+
const DOCUMENT_PARAGRAPH_BORDERS_ATTRIBUTE = 'data-office-paragraph-borders';
|
|
1222
|
+
const DOCUMENT_PARAGRAPH_BORDER_EDGES = [
|
|
1223
|
+
'top',
|
|
1224
|
+
'left',
|
|
1225
|
+
'bottom',
|
|
1226
|
+
'right',
|
|
1227
|
+
'between',
|
|
1228
|
+
'bar'
|
|
1229
|
+
];
|
|
1230
|
+
const DOCUMENT_PARAGRAPH_BORDER_STYLES = [
|
|
1231
|
+
'nil',
|
|
1232
|
+
'none',
|
|
1233
|
+
'single',
|
|
1234
|
+
'thick',
|
|
1235
|
+
'double',
|
|
1236
|
+
'dotted',
|
|
1237
|
+
'dashed',
|
|
1238
|
+
'dotDash',
|
|
1239
|
+
'dotDotDash',
|
|
1240
|
+
'triple',
|
|
1241
|
+
'thinThickSmallGap',
|
|
1242
|
+
'thickThinSmallGap',
|
|
1243
|
+
'thinThickThinSmallGap',
|
|
1244
|
+
'thinThickMediumGap',
|
|
1245
|
+
'thickThinMediumGap',
|
|
1246
|
+
'thinThickThinMediumGap',
|
|
1247
|
+
'thinThickLargeGap',
|
|
1248
|
+
'thickThinLargeGap',
|
|
1249
|
+
'thinThickThinLargeGap',
|
|
1250
|
+
'wave',
|
|
1251
|
+
'doubleWave',
|
|
1252
|
+
'dashSmallGap',
|
|
1253
|
+
'dashDotStroked',
|
|
1254
|
+
'threeDEmboss',
|
|
1255
|
+
'threeDEngrave',
|
|
1256
|
+
'outset',
|
|
1257
|
+
'inset',
|
|
1258
|
+
'apples',
|
|
1259
|
+
'archedScallops',
|
|
1260
|
+
'babyPacifier',
|
|
1261
|
+
'babyRattle',
|
|
1262
|
+
'balloons3Colors',
|
|
1263
|
+
'balloonsHotAir',
|
|
1264
|
+
'basicBlackDashes',
|
|
1265
|
+
'basicBlackDots',
|
|
1266
|
+
'basicBlackSquares',
|
|
1267
|
+
'basicThinLines',
|
|
1268
|
+
'basicWhiteDashes',
|
|
1269
|
+
'basicWhiteDots',
|
|
1270
|
+
'basicWhiteSquares',
|
|
1271
|
+
'basicWideInline',
|
|
1272
|
+
'basicWideMidline',
|
|
1273
|
+
'basicWideOutline',
|
|
1274
|
+
'bats',
|
|
1275
|
+
'birds',
|
|
1276
|
+
'birdsFlight',
|
|
1277
|
+
'cabins',
|
|
1278
|
+
'cakeSlice',
|
|
1279
|
+
'candyCorn',
|
|
1280
|
+
'celticKnotwork',
|
|
1281
|
+
'certificateBanner',
|
|
1282
|
+
'chainLink',
|
|
1283
|
+
'champagneBottle',
|
|
1284
|
+
'checkedBarBlack',
|
|
1285
|
+
'checkedBarColor',
|
|
1286
|
+
'checkered',
|
|
1287
|
+
'christmasTree',
|
|
1288
|
+
'circlesLines',
|
|
1289
|
+
'circlesRectangles',
|
|
1290
|
+
'classicalWave',
|
|
1291
|
+
'clocks',
|
|
1292
|
+
'compass',
|
|
1293
|
+
'confetti',
|
|
1294
|
+
'confettiGrays',
|
|
1295
|
+
'confettiOutline',
|
|
1296
|
+
'confettiStreamers',
|
|
1297
|
+
'confettiWhite',
|
|
1298
|
+
'cornerTriangles',
|
|
1299
|
+
'couponCutoutDashes',
|
|
1300
|
+
'couponCutoutDots',
|
|
1301
|
+
'crazyMaze',
|
|
1302
|
+
'creaturesButterfly',
|
|
1303
|
+
'creaturesFish',
|
|
1304
|
+
'creaturesInsects',
|
|
1305
|
+
'creaturesLadyBug',
|
|
1306
|
+
'crossStitch',
|
|
1307
|
+
'cup',
|
|
1308
|
+
'decoArch',
|
|
1309
|
+
'decoArchColor',
|
|
1310
|
+
'decoBlocks',
|
|
1311
|
+
'diamondsGray',
|
|
1312
|
+
'doubleD',
|
|
1313
|
+
'doubleDiamonds',
|
|
1314
|
+
'earth1',
|
|
1315
|
+
'earth2',
|
|
1316
|
+
'eclipsingSquares1',
|
|
1317
|
+
'eclipsingSquares2',
|
|
1318
|
+
'eggsBlack',
|
|
1319
|
+
'fans',
|
|
1320
|
+
'film',
|
|
1321
|
+
'firecrackers',
|
|
1322
|
+
'flowersBlockPrint',
|
|
1323
|
+
'flowersDaisies',
|
|
1324
|
+
'flowersModern1',
|
|
1325
|
+
'flowersModern2',
|
|
1326
|
+
'flowersPansy',
|
|
1327
|
+
'flowersRedRose',
|
|
1328
|
+
'flowersRoses',
|
|
1329
|
+
'flowersTeacup',
|
|
1330
|
+
'flowersTiny',
|
|
1331
|
+
'gems',
|
|
1332
|
+
'gingerbreadMan',
|
|
1333
|
+
'gradient',
|
|
1334
|
+
'handmade1',
|
|
1335
|
+
'handmade2',
|
|
1336
|
+
'heartBalloon',
|
|
1337
|
+
'heartGray',
|
|
1338
|
+
'hearts',
|
|
1339
|
+
'heebieJeebies',
|
|
1340
|
+
'holly',
|
|
1341
|
+
'houseFunky',
|
|
1342
|
+
'hypnotic',
|
|
1343
|
+
'iceCreamCones',
|
|
1344
|
+
'lightBulb',
|
|
1345
|
+
'lightning1',
|
|
1346
|
+
'lightning2',
|
|
1347
|
+
'mapPins',
|
|
1348
|
+
'mapleLeaf',
|
|
1349
|
+
'mapleMuffins',
|
|
1350
|
+
'marquee',
|
|
1351
|
+
'marqueeToothed',
|
|
1352
|
+
'moons',
|
|
1353
|
+
'mosaic',
|
|
1354
|
+
'musicNotes',
|
|
1355
|
+
'northwest',
|
|
1356
|
+
'ovals',
|
|
1357
|
+
'packages',
|
|
1358
|
+
'palmsBlack',
|
|
1359
|
+
'palmsColor',
|
|
1360
|
+
'paperClips',
|
|
1361
|
+
'papyrus',
|
|
1362
|
+
'partyFavor',
|
|
1363
|
+
'partyGlass',
|
|
1364
|
+
'pencils',
|
|
1365
|
+
'people',
|
|
1366
|
+
'peopleWaving',
|
|
1367
|
+
'peopleHats',
|
|
1368
|
+
'poinsettias',
|
|
1369
|
+
'postageStamp',
|
|
1370
|
+
'pumpkin1',
|
|
1371
|
+
'pushPinNote2',
|
|
1372
|
+
'pushPinNote1',
|
|
1373
|
+
'pyramids',
|
|
1374
|
+
'pyramidsAbove',
|
|
1375
|
+
'quadrants',
|
|
1376
|
+
'rings',
|
|
1377
|
+
'safari',
|
|
1378
|
+
'sawtooth',
|
|
1379
|
+
'sawtoothGray',
|
|
1380
|
+
'scaredCat',
|
|
1381
|
+
'seattle',
|
|
1382
|
+
'shadowedSquares',
|
|
1383
|
+
'sharksTeeth',
|
|
1384
|
+
'shorebirdTracks',
|
|
1385
|
+
'skyrocket',
|
|
1386
|
+
'snowflakeFancy',
|
|
1387
|
+
'snowflakes',
|
|
1388
|
+
'sombrero',
|
|
1389
|
+
'southwest',
|
|
1390
|
+
'stars',
|
|
1391
|
+
'starsTop',
|
|
1392
|
+
'stars3d',
|
|
1393
|
+
'starsBlack',
|
|
1394
|
+
'starsShadowed',
|
|
1395
|
+
'sun',
|
|
1396
|
+
'swirligig',
|
|
1397
|
+
'tornPaper',
|
|
1398
|
+
'tornPaperBlack',
|
|
1399
|
+
'trees',
|
|
1400
|
+
'triangleParty',
|
|
1401
|
+
'triangles',
|
|
1402
|
+
'tribal1',
|
|
1403
|
+
'tribal2',
|
|
1404
|
+
'tribal3',
|
|
1405
|
+
'tribal4',
|
|
1406
|
+
'tribal5',
|
|
1407
|
+
'tribal6',
|
|
1408
|
+
'triangle1',
|
|
1409
|
+
'triangle2',
|
|
1410
|
+
'triangleCircle1',
|
|
1411
|
+
'triangleCircle2',
|
|
1412
|
+
'shapes1',
|
|
1413
|
+
'shapes2',
|
|
1414
|
+
'twistedLines1',
|
|
1415
|
+
'twistedLines2',
|
|
1416
|
+
'vine',
|
|
1417
|
+
'waveline',
|
|
1418
|
+
'weavingAngles',
|
|
1419
|
+
'weavingBraid',
|
|
1420
|
+
'weavingRibbon',
|
|
1421
|
+
'weavingStrips',
|
|
1422
|
+
'whiteFlowers',
|
|
1423
|
+
'woodwork',
|
|
1424
|
+
'xIllusions',
|
|
1425
|
+
'zanyTriangles',
|
|
1426
|
+
'zigZag',
|
|
1427
|
+
'zigZagStitch'
|
|
1428
|
+
];
|
|
1429
|
+
const BORDER_STYLE_SET = new Set(DOCUMENT_PARAGRAPH_BORDER_STYLES);
|
|
1430
|
+
const BORDER_EDGE_SET = new Set(DOCUMENT_PARAGRAPH_BORDER_EDGES);
|
|
1431
|
+
const BORDER_PROPERTY_SET = new Set([
|
|
1432
|
+
'style',
|
|
1433
|
+
'color',
|
|
1434
|
+
'size',
|
|
1435
|
+
'space',
|
|
1436
|
+
'shadow',
|
|
1437
|
+
'frame'
|
|
1438
|
+
]);
|
|
1439
|
+
const LINE_BORDER_STYLES = new Set(DOCUMENT_PARAGRAPH_BORDER_STYLES.slice(0, 27));
|
|
1440
|
+
const DASHED_BORDER_STYLES = new Set([
|
|
1441
|
+
'dashed',
|
|
1442
|
+
'dashSmallGap',
|
|
1443
|
+
'dashDotStroked',
|
|
1444
|
+
'dotDash',
|
|
1445
|
+
'dotDotDash'
|
|
1446
|
+
]);
|
|
1447
|
+
const DOUBLE_BORDER_STYLES = new Set([
|
|
1448
|
+
'double',
|
|
1449
|
+
'triple',
|
|
1450
|
+
'thinThickSmallGap',
|
|
1451
|
+
'thickThinSmallGap',
|
|
1452
|
+
'thinThickThinSmallGap',
|
|
1453
|
+
'thinThickMediumGap',
|
|
1454
|
+
'thickThinMediumGap',
|
|
1455
|
+
'thinThickThinMediumGap',
|
|
1456
|
+
'thinThickLargeGap',
|
|
1457
|
+
'thickThinLargeGap',
|
|
1458
|
+
'thinThickThinLargeGap',
|
|
1459
|
+
'doubleWave'
|
|
1460
|
+
]);
|
|
1461
|
+
const MAX_SERIALIZED_PARAGRAPH_BORDERS = 32768;
|
|
1462
|
+
const POINTS_TO_PIXELS = 96 / 72;
|
|
1463
|
+
function normalizeDocumentParagraphBorders(source) {
|
|
1464
|
+
if (!source || 'object' != typeof source || Array.isArray(source)) return null;
|
|
1465
|
+
const record = source;
|
|
1466
|
+
if (Object.keys(record).some((key)=>!BORDER_EDGE_SET.has(key))) return null;
|
|
1467
|
+
const borders = {};
|
|
1468
|
+
for (const edge of DOCUMENT_PARAGRAPH_BORDER_EDGES){
|
|
1469
|
+
if (void 0 === record[edge]) continue;
|
|
1470
|
+
const border = normalizeDocumentParagraphBorder(record[edge]);
|
|
1471
|
+
if (!border) return null;
|
|
1472
|
+
borders[edge] = border;
|
|
1501
1473
|
}
|
|
1474
|
+
return Object.keys(borders).length ? borders : null;
|
|
1502
1475
|
}
|
|
1503
|
-
function
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1476
|
+
function normalizeDocumentParagraphBorder(source) {
|
|
1477
|
+
if (!source || 'object' != typeof source || Array.isArray(source)) return null;
|
|
1478
|
+
const record = source;
|
|
1479
|
+
if (Object.keys(record).some((key)=>!BORDER_PROPERTY_SET.has(key))) return null;
|
|
1480
|
+
const style = record.style;
|
|
1481
|
+
if ('string' != typeof style || !BORDER_STYLE_SET.has(style)) return null;
|
|
1482
|
+
const normalizedStyle = style;
|
|
1483
|
+
const color = normalizeBorderColor(record.color);
|
|
1484
|
+
if (void 0 !== record.color && !color) return null;
|
|
1485
|
+
const size = optionalInteger(record.size);
|
|
1486
|
+
if (null === size || void 0 !== size && !validBorderSize(normalizedStyle, size)) return null;
|
|
1487
|
+
const space = optionalInteger(record.space);
|
|
1488
|
+
if (null === space || void 0 !== space && (space < 0 || space > 31)) return null;
|
|
1489
|
+
const shadow = optionalBoolean(record.shadow);
|
|
1490
|
+
const frame = optionalBoolean(record.frame);
|
|
1491
|
+
if (null === shadow || null === frame) return null;
|
|
1516
1492
|
return {
|
|
1517
|
-
|
|
1518
|
-
...
|
|
1519
|
-
|
|
1493
|
+
style: normalizedStyle,
|
|
1494
|
+
...color ? {
|
|
1495
|
+
color
|
|
1520
1496
|
} : {},
|
|
1521
|
-
...
|
|
1522
|
-
|
|
1497
|
+
...void 0 !== size ? {
|
|
1498
|
+
size
|
|
1499
|
+
} : {},
|
|
1500
|
+
...void 0 !== space ? {
|
|
1501
|
+
space
|
|
1502
|
+
} : {},
|
|
1503
|
+
...void 0 !== shadow ? {
|
|
1504
|
+
shadow
|
|
1505
|
+
} : {},
|
|
1506
|
+
...void 0 !== frame ? {
|
|
1507
|
+
frame
|
|
1523
1508
|
} : {}
|
|
1524
1509
|
};
|
|
1525
1510
|
}
|
|
1526
|
-
function
|
|
1527
|
-
|
|
1528
|
-
if (!
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
const key = family?.toLocaleLowerCase();
|
|
1534
|
-
if (!(!family || !key || seen.has(key))) {
|
|
1535
|
-
seen.add(key);
|
|
1536
|
-
families.push(cssFontFamily(family));
|
|
1537
|
-
}
|
|
1511
|
+
function parseDocumentParagraphBorders(source) {
|
|
1512
|
+
if ('string' != typeof source) return normalizeDocumentParagraphBorders(source);
|
|
1513
|
+
if (!source.trim() || source.length > MAX_SERIALIZED_PARAGRAPH_BORDERS) return null;
|
|
1514
|
+
try {
|
|
1515
|
+
return normalizeDocumentParagraphBorders(JSON.parse(source));
|
|
1516
|
+
} catch {
|
|
1517
|
+
return null;
|
|
1538
1518
|
}
|
|
1539
|
-
return families.length ? families.join(', ') : void 0;
|
|
1540
|
-
}
|
|
1541
|
-
function documentScriptFontFallbackSlots(slot) {
|
|
1542
|
-
return SLOT_FALLBACK_ORDER[slot];
|
|
1543
|
-
}
|
|
1544
|
-
function documentScriptFontFamilyForRendering(source, slot, currentFontFamily) {
|
|
1545
|
-
const projected = documentScriptFontFamily(source, slot);
|
|
1546
|
-
const safeCurrent = safeCssFontFamilyList(currentFontFamily);
|
|
1547
|
-
if (!safeCurrent || !projected) return projected;
|
|
1548
|
-
const currentPrimary = documentFontNameFromCssFamily(safeCurrent);
|
|
1549
|
-
const projectedPrimary = documentFontNameFromCssFamily(projected);
|
|
1550
|
-
return currentPrimary && projectedPrimary && currentPrimary.toLocaleLowerCase() === projectedPrimary.toLocaleLowerCase() ? safeCurrent : projected;
|
|
1551
|
-
}
|
|
1552
|
-
function documentScriptFontDirectFamily(source, slot) {
|
|
1553
|
-
const fonts = normalizeDocumentScriptFonts(source);
|
|
1554
|
-
return fonts ? documentScriptFontFaceFamily(fonts[slot]) ?? null : null;
|
|
1555
1519
|
}
|
|
1556
|
-
function
|
|
1557
|
-
const
|
|
1558
|
-
if (!
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
hint: 'default'
|
|
1569
|
-
};
|
|
1520
|
+
function serializeDocumentParagraphBorders(source) {
|
|
1521
|
+
const borders = normalizeDocumentParagraphBorders(source);
|
|
1522
|
+
if (!borders) return;
|
|
1523
|
+
return JSON.stringify(Object.fromEntries(DOCUMENT_PARAGRAPH_BORDER_EDGES.flatMap((edge)=>{
|
|
1524
|
+
const border = borders[edge];
|
|
1525
|
+
return border ? [
|
|
1526
|
+
[
|
|
1527
|
+
edge,
|
|
1528
|
+
serializedBorder(border)
|
|
1529
|
+
]
|
|
1530
|
+
] : [];
|
|
1531
|
+
})));
|
|
1570
1532
|
}
|
|
1571
|
-
function
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
|
|
1533
|
+
function parseDocumentParagraphBordersElement(element) {
|
|
1534
|
+
const semantic = parseDocumentParagraphBorders(element.getAttribute(DOCUMENT_PARAGRAPH_BORDERS_ATTRIBUTE));
|
|
1535
|
+
if (!semantic) return paragraphBordersFromCss(element);
|
|
1536
|
+
const edited = {
|
|
1537
|
+
...semantic
|
|
1575
1538
|
};
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
else delete next.eastAsia;
|
|
1590
|
-
}
|
|
1591
|
-
if (void 0 !== patch.complexScript) {
|
|
1592
|
-
const face = directFontFace(patch.complexScript);
|
|
1593
|
-
if (face) next.complexScript = face;
|
|
1594
|
-
else delete next.complexScript;
|
|
1595
|
-
}
|
|
1596
|
-
return normalizeDocumentScriptFonts(next);
|
|
1597
|
-
}
|
|
1598
|
-
function documentScriptFontSegments(text, hint = 'default', forceComplexScript = false) {
|
|
1599
|
-
if (!text) return [];
|
|
1600
|
-
if (forceComplexScript) return [
|
|
1601
|
-
{
|
|
1602
|
-
from: 0,
|
|
1603
|
-
to: text.length,
|
|
1604
|
-
slot: 'complexScript'
|
|
1605
|
-
}
|
|
1606
|
-
];
|
|
1607
|
-
const characters = [];
|
|
1608
|
-
let offset = 0;
|
|
1609
|
-
for (const character of text){
|
|
1610
|
-
const from = offset;
|
|
1611
|
-
offset += character.length;
|
|
1612
|
-
characters.push({
|
|
1613
|
-
from,
|
|
1614
|
-
to: offset,
|
|
1615
|
-
slot: strongDocumentScriptFontSlot(character)
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1618
|
-
const fallback = documentScriptFontSlotFromHint(hint);
|
|
1619
|
-
let previous = null;
|
|
1620
|
-
for(let index = 0; index < characters.length; index += 1){
|
|
1621
|
-
const entry = characters[index];
|
|
1622
|
-
if (!entry) continue;
|
|
1623
|
-
if (entry.slot) {
|
|
1624
|
-
previous = entry.slot;
|
|
1625
|
-
continue;
|
|
1626
|
-
}
|
|
1627
|
-
let next = previous;
|
|
1628
|
-
if (!next) for(let cursor = index + 1; cursor < characters.length; cursor += 1){
|
|
1629
|
-
const candidate = characters[cursor]?.slot;
|
|
1630
|
-
if (candidate) {
|
|
1631
|
-
next = candidate;
|
|
1632
|
-
break;
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
entry.slot = next ?? fallback;
|
|
1636
|
-
}
|
|
1637
|
-
const segments = [];
|
|
1638
|
-
for (const entry of characters){
|
|
1639
|
-
const slot = entry.slot ?? fallback;
|
|
1640
|
-
const prior = segments[segments.length - 1];
|
|
1641
|
-
if (prior?.slot === slot && prior.to === entry.from) prior.to = entry.to;
|
|
1642
|
-
else segments.push({
|
|
1643
|
-
from: entry.from,
|
|
1644
|
-
to: entry.to,
|
|
1645
|
-
slot
|
|
1646
|
-
});
|
|
1647
|
-
}
|
|
1648
|
-
return segments;
|
|
1649
|
-
}
|
|
1650
|
-
function normalizeDocumentScriptFontSlot(value) {
|
|
1651
|
-
return scriptFontSlots.includes(value) ? value : null;
|
|
1652
|
-
}
|
|
1653
|
-
function normalizeDocumentScriptFontHint(value) {
|
|
1654
|
-
return SCRIPT_FONT_HINTS.has(value) ? value : null;
|
|
1655
|
-
}
|
|
1656
|
-
function normalizeDocumentThemeFont(value) {
|
|
1657
|
-
return THEME_FONTS.has(value) ? value : null;
|
|
1658
|
-
}
|
|
1659
|
-
function documentScriptFontSlotFromHint(hint) {
|
|
1660
|
-
if ('eastAsia' === hint) return 'eastAsia';
|
|
1661
|
-
if ('cs' === hint) return 'complexScript';
|
|
1662
|
-
return 'ascii';
|
|
1663
|
-
}
|
|
1664
|
-
function documentFontNameFromCssFamily(value) {
|
|
1665
|
-
if ('string' != typeof value) return null;
|
|
1666
|
-
const source = value.trim();
|
|
1667
|
-
if (!source) return null;
|
|
1668
|
-
let family = '';
|
|
1669
|
-
const quote = source[0];
|
|
1670
|
-
if ('"' === quote || "'" === quote) {
|
|
1671
|
-
let closed = false;
|
|
1672
|
-
for(let index = 1; index < source.length; index += 1){
|
|
1673
|
-
const character = source[index];
|
|
1674
|
-
if (character === quote) {
|
|
1675
|
-
closed = true;
|
|
1676
|
-
break;
|
|
1677
|
-
}
|
|
1678
|
-
if ('\\' !== character) {
|
|
1679
|
-
family += character;
|
|
1539
|
+
for (const edge of [
|
|
1540
|
+
'top',
|
|
1541
|
+
'left',
|
|
1542
|
+
'bottom',
|
|
1543
|
+
'right'
|
|
1544
|
+
]){
|
|
1545
|
+
const border = semantic[edge];
|
|
1546
|
+
const css = cssBorder(element, edge);
|
|
1547
|
+
if (!(!css || border && sameBorderPresentation(border, css))) {
|
|
1548
|
+
if ('none' === css.style) {
|
|
1549
|
+
edited[edge] = {
|
|
1550
|
+
style: 'nil'
|
|
1551
|
+
};
|
|
1680
1552
|
continue;
|
|
1681
1553
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1554
|
+
edited[edge] = {
|
|
1555
|
+
...border ?? {
|
|
1556
|
+
style: 'single'
|
|
1557
|
+
},
|
|
1558
|
+
style: cssStyleToBorderStyle(css.style),
|
|
1559
|
+
color: {
|
|
1560
|
+
value: css.color
|
|
1561
|
+
},
|
|
1562
|
+
size: cssWidthToEighthPoints(css.width)
|
|
1563
|
+
};
|
|
1686
1564
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
return normalizeDocumentFontName(family);
|
|
1565
|
+
}
|
|
1566
|
+
return normalizeDocumentParagraphBorders(edited);
|
|
1690
1567
|
}
|
|
1691
|
-
function
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1568
|
+
function documentParagraphBordersDomAttributes(source) {
|
|
1569
|
+
const borders = normalizeDocumentParagraphBorders(source);
|
|
1570
|
+
const serialized = serializeDocumentParagraphBorders(borders);
|
|
1571
|
+
if (!borders || !serialized) return {};
|
|
1572
|
+
const styles = [];
|
|
1573
|
+
const shadows = [];
|
|
1574
|
+
for (const edge of [
|
|
1575
|
+
'top',
|
|
1576
|
+
'left',
|
|
1577
|
+
'bottom',
|
|
1578
|
+
'right'
|
|
1579
|
+
]){
|
|
1580
|
+
const border = borders[edge];
|
|
1581
|
+
if (!border) continue;
|
|
1582
|
+
const presentation = documentBorderPresentation(border);
|
|
1583
|
+
styles.push(`border-${edge}: ${formatPixels(presentation.width)}px ${presentation.style} ${presentation.color}`);
|
|
1584
|
+
if (border.space) styles.push(`padding-${edge}: ${formatPixels(border.space * POINTS_TO_PIXELS)}px`);
|
|
1585
|
+
if (border.shadow && presentation.width > 0) shadows.push(`2px 2px 0 ${presentation.color}`);
|
|
1586
|
+
}
|
|
1587
|
+
const between = borders.between ? documentBorderPresentation(borders.between) : null;
|
|
1588
|
+
if (between && between.width > 0) shadows.push(`inset 0 -${formatPixels(between.width)}px 0 ${between.color}`);
|
|
1589
|
+
const bar = borders.bar ? documentBorderPresentation(borders.bar) : null;
|
|
1590
|
+
if (bar && bar.width > 0) shadows.push(`inset ${formatPixels(bar.width)}px 0 0 ${bar.color}`);
|
|
1591
|
+
if (shadows.length) styles.push(`box-shadow: ${shadows.join(', ')}`);
|
|
1592
|
+
return {
|
|
1593
|
+
[DOCUMENT_PARAGRAPH_BORDERS_ATTRIBUTE]: serialized,
|
|
1594
|
+
...styles.length ? {
|
|
1595
|
+
style: styles.join('; ')
|
|
1596
|
+
} : {}
|
|
1597
|
+
};
|
|
1694
1598
|
}
|
|
1695
|
-
function
|
|
1696
|
-
|
|
1697
|
-
const normalized = value.trim();
|
|
1698
|
-
return normalized && normalized.length <= MAX_FONT_NAME_LENGTH && !/[\p{Cc}\p{Cs}]/u.test(normalized) ? normalized : null;
|
|
1599
|
+
function isDocumentParagraphArtBorderStyle(style) {
|
|
1600
|
+
return !LINE_BORDER_STYLES.has(style);
|
|
1699
1601
|
}
|
|
1700
|
-
|
|
1701
|
-
'
|
|
1702
|
-
|
|
1703
|
-
'
|
|
1704
|
-
'
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
if (!
|
|
1708
|
-
|
|
1709
|
-
const resolved = void 0 === source.resolved ? void 0 : normalizeDocumentFontName(source.resolved);
|
|
1710
|
-
const theme = void 0 === source.theme ? void 0 : normalizeDocumentThemeFont(source.theme);
|
|
1711
|
-
if (void 0 !== source.name && !name || void 0 !== source.resolved && !resolved || null === theme || !name && !theme && !resolved) return null;
|
|
1602
|
+
function normalizeBorderColor(source) {
|
|
1603
|
+
if (!source || 'object' != typeof source || Array.isArray(source)) return null;
|
|
1604
|
+
const record = source;
|
|
1605
|
+
if (Object.keys(record).some((key)=>'value' !== key && 'theme' !== key)) return null;
|
|
1606
|
+
const theme = parseDocxThemeReference('string' == typeof record.theme ? record.theme : record.theme ? JSON.stringify(record.theme) : void 0);
|
|
1607
|
+
const direct = 'auto' === record.value ? 'auto' : 'string' == typeof record.value ? normalizeCssColor(record.value) : null;
|
|
1608
|
+
const resolved = direct ?? theme?.resolved ?? null;
|
|
1609
|
+
if (!resolved || 'transparent' === resolved) return null;
|
|
1610
|
+
if (theme && resolved !== theme.resolved && !('auto' === resolved && 'none' === theme.theme && '#000000' === theme.resolved)) return null;
|
|
1712
1611
|
return {
|
|
1713
|
-
|
|
1714
|
-
name
|
|
1715
|
-
} : {},
|
|
1612
|
+
value: resolved,
|
|
1716
1613
|
...theme ? {
|
|
1717
1614
|
theme
|
|
1718
|
-
} : {},
|
|
1719
|
-
...resolved ? {
|
|
1720
|
-
resolved
|
|
1721
1615
|
} : {}
|
|
1722
1616
|
};
|
|
1723
1617
|
}
|
|
1724
|
-
function
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1618
|
+
function serializedBorder(border) {
|
|
1619
|
+
const color = border.color ? serializedBorderColor(border.color) : void 0;
|
|
1620
|
+
return {
|
|
1621
|
+
style: border.style,
|
|
1622
|
+
...color ? {
|
|
1623
|
+
color
|
|
1624
|
+
} : {},
|
|
1625
|
+
...void 0 !== border.size ? {
|
|
1626
|
+
size: border.size
|
|
1627
|
+
} : {},
|
|
1628
|
+
...void 0 !== border.space ? {
|
|
1629
|
+
space: border.space
|
|
1630
|
+
} : {},
|
|
1631
|
+
...void 0 !== border.shadow ? {
|
|
1632
|
+
shadow: border.shadow
|
|
1633
|
+
} : {},
|
|
1634
|
+
...void 0 !== border.frame ? {
|
|
1635
|
+
frame: border.frame
|
|
1636
|
+
} : {}
|
|
1637
|
+
};
|
|
1742
1638
|
}
|
|
1743
|
-
function
|
|
1744
|
-
|
|
1639
|
+
function serializedBorderColor(color) {
|
|
1640
|
+
const theme = serializeDocxThemeReference(color.theme ?? null);
|
|
1641
|
+
return {
|
|
1642
|
+
value: color.value,
|
|
1643
|
+
...theme ? {
|
|
1644
|
+
theme: JSON.parse(theme)
|
|
1645
|
+
} : {}
|
|
1646
|
+
};
|
|
1745
1647
|
}
|
|
1746
|
-
function
|
|
1747
|
-
|
|
1648
|
+
function optionalInteger(value) {
|
|
1649
|
+
if (void 0 === value) return;
|
|
1650
|
+
return 'number' == typeof value && Number.isSafeInteger(value) ? value : null;
|
|
1748
1651
|
}
|
|
1749
|
-
function
|
|
1750
|
-
|
|
1652
|
+
function optionalBoolean(value) {
|
|
1653
|
+
if (void 0 === value) return;
|
|
1654
|
+
return 'boolean' == typeof value ? value : null;
|
|
1751
1655
|
}
|
|
1752
|
-
function
|
|
1753
|
-
|
|
1656
|
+
function validBorderSize(style, size) {
|
|
1657
|
+
if ('nil' === style || 'none' === style) return size >= 0 && size <= 96;
|
|
1658
|
+
return isDocumentParagraphArtBorderStyle(style) ? size >= 1 && size <= 31 : size >= 2 && size <= 96;
|
|
1754
1659
|
}
|
|
1755
|
-
function
|
|
1756
|
-
if ('
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
if (index >= source.length) return null;
|
|
1768
|
-
} else if (character === quote) quote = '';
|
|
1769
|
-
continue;
|
|
1770
|
-
}
|
|
1771
|
-
if ('"' === character || "'" === character) {
|
|
1772
|
-
quote = character;
|
|
1773
|
-
continue;
|
|
1774
|
-
}
|
|
1775
|
-
if (',' === character) {
|
|
1776
|
-
tokens.push(source.slice(start, index).trim());
|
|
1777
|
-
start = index + 1;
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
if (quote) return null;
|
|
1781
|
-
tokens.push(source.slice(start).trim());
|
|
1782
|
-
return tokens.length && tokens.every(validCssFontFamilyToken) ? source : null;
|
|
1660
|
+
function documentBorderPresentation(border) {
|
|
1661
|
+
if ('nil' === border.style || 'none' === border.style || !border.size) return {
|
|
1662
|
+
color: 'transparent',
|
|
1663
|
+
style: 'none',
|
|
1664
|
+
width: 0
|
|
1665
|
+
};
|
|
1666
|
+
const width = Math.min(16, isDocumentParagraphArtBorderStyle(border.style) ? border.size * POINTS_TO_PIXELS : border.size / 6);
|
|
1667
|
+
return {
|
|
1668
|
+
color: border.color && 'auto' !== border.color.value ? border.color.value : '#000000',
|
|
1669
|
+
style: borderStyleToCssStyle(border.style),
|
|
1670
|
+
width
|
|
1671
|
+
};
|
|
1783
1672
|
}
|
|
1784
|
-
function
|
|
1785
|
-
if (
|
|
1786
|
-
if (
|
|
1787
|
-
if (
|
|
1788
|
-
|
|
1673
|
+
function borderStyleToCssStyle(style) {
|
|
1674
|
+
if ('nil' === style || 'none' === style) return 'none';
|
|
1675
|
+
if ('dotted' === style) return 'dotted';
|
|
1676
|
+
if (DASHED_BORDER_STYLES.has(style)) return 'dashed';
|
|
1677
|
+
if (DOUBLE_BORDER_STYLES.has(style)) return 'double';
|
|
1678
|
+
if ('inset' === style || 'threeDEngrave' === style) return 'inset';
|
|
1679
|
+
if ('outset' === style || 'threeDEmboss' === style) return 'outset';
|
|
1680
|
+
return 'solid';
|
|
1789
1681
|
}
|
|
1790
|
-
function
|
|
1791
|
-
|
|
1792
|
-
const
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1682
|
+
function paragraphBordersFromCss(element) {
|
|
1683
|
+
const borders = {};
|
|
1684
|
+
for (const edge of [
|
|
1685
|
+
'top',
|
|
1686
|
+
'left',
|
|
1687
|
+
'bottom',
|
|
1688
|
+
'right'
|
|
1689
|
+
]){
|
|
1690
|
+
const css = cssBorder(element, edge);
|
|
1691
|
+
if (css && 'none' !== css.style) borders[edge] = {
|
|
1692
|
+
style: cssStyleToBorderStyle(css.style),
|
|
1693
|
+
color: {
|
|
1694
|
+
value: css.color
|
|
1695
|
+
},
|
|
1696
|
+
size: cssWidthToEighthPoints(css.width)
|
|
1801
1697
|
};
|
|
1802
1698
|
}
|
|
1803
|
-
|
|
1804
|
-
return !value || /[\r\n\f]/u.test(value) ? null : {
|
|
1805
|
-
value,
|
|
1806
|
-
end: start + 1
|
|
1807
|
-
};
|
|
1808
|
-
}
|
|
1809
|
-
function work_document_script_fonts_isRecordWithKeys(value, allowed) {
|
|
1810
|
-
return 'object' == typeof value && null !== value && !Array.isArray(value) && Object.keys(value).every((key)=>allowed.has(key));
|
|
1699
|
+
return normalizeDocumentParagraphBorders(borders);
|
|
1811
1700
|
}
|
|
1812
|
-
|
|
1813
|
-
const
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
'
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
'
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
if ('string' != typeof source) return null;
|
|
1828
|
-
if (!source || source !== source.trim() || source.length > MAX_LANGUAGE_TAG_LENGTH || /[\p{Cc}\p{Cs}]/u.test(source)) return null;
|
|
1829
|
-
return /^(?:x-none|[a-z0-9]{1,8}(?:-[a-z0-9]{1,8})*)$/iu.test(source) ? source : null;
|
|
1701
|
+
function cssBorder(element, edge) {
|
|
1702
|
+
const style = element.style.getPropertyValue(`border-${edge}-style`).trim();
|
|
1703
|
+
const width = Number.parseFloat(element.style.getPropertyValue(`border-${edge}-width`));
|
|
1704
|
+
const color = normalizeCssColor(element.style.getPropertyValue(`border-${edge}-color`));
|
|
1705
|
+
if (!style) return null;
|
|
1706
|
+
if ('none' === style || 'hidden' === style) return {
|
|
1707
|
+
color: '#000000',
|
|
1708
|
+
style: 'none',
|
|
1709
|
+
width: 0
|
|
1710
|
+
};
|
|
1711
|
+
return color && 'transparent' !== color && Number.isFinite(width) && width > 0 ? {
|
|
1712
|
+
color,
|
|
1713
|
+
style,
|
|
1714
|
+
width
|
|
1715
|
+
} : null;
|
|
1830
1716
|
}
|
|
1831
|
-
function
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
const normalized = {};
|
|
1836
|
-
for (const key of PROOFING_LANGUAGE_ORDER){
|
|
1837
|
-
if (void 0 === source[key]) continue;
|
|
1838
|
-
const language = normalizeDocumentLanguageTag(source[key]);
|
|
1839
|
-
if (!language) return null;
|
|
1840
|
-
normalized[key] = language;
|
|
1841
|
-
}
|
|
1842
|
-
return Object.keys(normalized).length ? normalized : null;
|
|
1717
|
+
function sameBorderPresentation(border, css) {
|
|
1718
|
+
const expected = documentBorderPresentation(border);
|
|
1719
|
+
if ('none' === expected.style) return 'none' === css.style && 0 === css.width;
|
|
1720
|
+
return expected.color === css.color && expected.style === css.style && Math.abs(expected.width - css.width) < 0.01;
|
|
1843
1721
|
}
|
|
1844
|
-
function
|
|
1845
|
-
|
|
1846
|
-
if (
|
|
1847
|
-
|
|
1848
|
-
|
|
1722
|
+
function cssStyleToBorderStyle(style) {
|
|
1723
|
+
if ('double' === style) return 'double';
|
|
1724
|
+
if ('dashed' === style) return 'dashed';
|
|
1725
|
+
if ('dotted' === style) return 'dotted';
|
|
1726
|
+
if ('inset' === style || 'groove' === style) return 'inset';
|
|
1727
|
+
if ('outset' === style || 'ridge' === style) return 'outset';
|
|
1728
|
+
return 'single';
|
|
1849
1729
|
}
|
|
1850
|
-
function
|
|
1851
|
-
|
|
1852
|
-
|
|
1730
|
+
function cssWidthToEighthPoints(width) {
|
|
1731
|
+
return Math.max(2, Math.min(96, Math.round(6 * width)));
|
|
1732
|
+
}
|
|
1733
|
+
function formatPixels(value) {
|
|
1734
|
+
return Number(value.toFixed(3)).toString();
|
|
1735
|
+
}
|
|
1736
|
+
const DOCUMENT_RUN_BORDER_ATTRIBUTE = 'data-office-run-border';
|
|
1737
|
+
const DOCUMENT_RUN_BORDER_STYLES = DOCUMENT_PARAGRAPH_BORDER_STYLES.slice(0, 27);
|
|
1738
|
+
const MAX_SERIALIZED_RUN_BORDER_BYTES = 4096;
|
|
1739
|
+
const work_document_run_border_POINTS_TO_PIXELS = 96 / 72;
|
|
1740
|
+
function normalizeDocumentRunBorder(source) {
|
|
1741
|
+
const border = normalizeDocumentParagraphBorder(source);
|
|
1742
|
+
return border && !isDocumentParagraphArtBorderStyle(border.style) ? border : null;
|
|
1743
|
+
}
|
|
1744
|
+
function parseDocumentRunBorder(source) {
|
|
1745
|
+
if ('string' != typeof source) return normalizeDocumentRunBorder(source);
|
|
1746
|
+
if (!source.trim() || source.length > MAX_SERIALIZED_RUN_BORDER_BYTES) return null;
|
|
1853
1747
|
try {
|
|
1854
|
-
|
|
1855
|
-
return normalized && JSON.stringify(normalized) === source ? normalized : null;
|
|
1748
|
+
return normalizeDocumentRunBorder(JSON.parse(source));
|
|
1856
1749
|
} catch {
|
|
1857
1750
|
return null;
|
|
1858
1751
|
}
|
|
1859
1752
|
}
|
|
1860
|
-
function
|
|
1861
|
-
|
|
1862
|
-
if (
|
|
1863
|
-
|
|
1753
|
+
function serializeDocumentRunBorder(source) {
|
|
1754
|
+
const border = normalizeDocumentRunBorder(source);
|
|
1755
|
+
if (!border) return;
|
|
1756
|
+
const theme = serializeDocxThemeReference(border.color?.theme ?? null);
|
|
1757
|
+
return JSON.stringify({
|
|
1758
|
+
style: border.style,
|
|
1759
|
+
...border.color ? {
|
|
1760
|
+
color: {
|
|
1761
|
+
value: border.color.value,
|
|
1762
|
+
...theme ? {
|
|
1763
|
+
theme: JSON.parse(theme)
|
|
1764
|
+
} : {}
|
|
1765
|
+
}
|
|
1766
|
+
} : {},
|
|
1767
|
+
...void 0 !== border.size ? {
|
|
1768
|
+
size: border.size
|
|
1769
|
+
} : {},
|
|
1770
|
+
...void 0 !== border.space ? {
|
|
1771
|
+
space: border.space
|
|
1772
|
+
} : {},
|
|
1773
|
+
...void 0 !== border.shadow ? {
|
|
1774
|
+
shadow: border.shadow
|
|
1775
|
+
} : {},
|
|
1776
|
+
...void 0 !== border.frame ? {
|
|
1777
|
+
frame: border.frame
|
|
1778
|
+
} : {}
|
|
1779
|
+
});
|
|
1864
1780
|
}
|
|
1865
|
-
function
|
|
1866
|
-
|
|
1781
|
+
function parseDocumentRunBorderElement(element) {
|
|
1782
|
+
const semantic = parseDocumentRunBorder(element.getAttribute(DOCUMENT_RUN_BORDER_ATTRIBUTE));
|
|
1783
|
+
return semantic ?? documentRunBorderFromCss(element);
|
|
1867
1784
|
}
|
|
1868
|
-
function
|
|
1869
|
-
|
|
1785
|
+
function documentRunBorderDomAttributes(source) {
|
|
1786
|
+
const border = normalizeDocumentRunBorder(source);
|
|
1787
|
+
const serialized = serializeDocumentRunBorder(border);
|
|
1788
|
+
if (!border || !serialized) return {};
|
|
1789
|
+
const presentation = documentBorderPresentation(border);
|
|
1790
|
+
const declarations = [
|
|
1791
|
+
`border: ${work_document_run_border_formatPixels(presentation.width)}px ${presentation.style} ${presentation.color}`,
|
|
1792
|
+
`padding: ${work_document_run_border_formatPixels((border.space ?? 0) * work_document_run_border_POINTS_TO_PIXELS)}px`,
|
|
1793
|
+
'box-decoration-break: clone',
|
|
1794
|
+
'-webkit-box-decoration-break: clone'
|
|
1795
|
+
];
|
|
1796
|
+
if (border.shadow && presentation.width > 0) declarations.push(`box-shadow: 2px 2px 0 ${presentation.color}`);
|
|
1797
|
+
return {
|
|
1798
|
+
[DOCUMENT_RUN_BORDER_ATTRIBUTE]: serialized,
|
|
1799
|
+
style: declarations.join('; ')
|
|
1800
|
+
};
|
|
1870
1801
|
}
|
|
1871
|
-
function
|
|
1872
|
-
const
|
|
1873
|
-
|
|
1874
|
-
if ('eastAsia' === slot) return languages.eastAsia ?? languages.latin;
|
|
1875
|
-
if ('complexScript' === slot) return languages.bidi ?? languages.latin;
|
|
1876
|
-
return languages.latin ?? languages.eastAsia ?? languages.bidi;
|
|
1802
|
+
function documentRunBorderIsVisible(source) {
|
|
1803
|
+
const border = normalizeDocumentRunBorder(source);
|
|
1804
|
+
return Boolean(border && documentBorderPresentation(border).width > 0);
|
|
1877
1805
|
}
|
|
1878
|
-
function
|
|
1879
|
-
const
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1806
|
+
function documentRunBorderFromCss(element) {
|
|
1807
|
+
const style = element.style.borderStyle.trim();
|
|
1808
|
+
if (!style) return null;
|
|
1809
|
+
if ('none' === style || 'hidden' === style) return {
|
|
1810
|
+
style: 'none'
|
|
1811
|
+
};
|
|
1812
|
+
const width = Number.parseFloat(element.style.borderWidth);
|
|
1813
|
+
const color = normalizeCssColor(element.style.borderColor);
|
|
1814
|
+
if (!Number.isFinite(width) || width <= 0 || !color || 'transparent' === color) return null;
|
|
1815
|
+
const padding = Number.parseFloat(element.style.padding);
|
|
1816
|
+
return normalizeDocumentRunBorder({
|
|
1817
|
+
style: cssBorderStyle(style),
|
|
1818
|
+
color: {
|
|
1819
|
+
value: color
|
|
1889
1820
|
},
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1821
|
+
size: Math.max(2, Math.min(96, Math.round(6 * width))),
|
|
1822
|
+
...Number.isFinite(padding) && padding >= 0 ? {
|
|
1823
|
+
space: Math.max(0, Math.min(31, Math.round(padding / work_document_run_border_POINTS_TO_PIXELS)))
|
|
1824
|
+
} : {}
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
function cssBorderStyle(style) {
|
|
1828
|
+
if ('double' === style) return 'double';
|
|
1829
|
+
if ('dashed' === style) return 'dashed';
|
|
1830
|
+
if ('dotted' === style) return 'dotted';
|
|
1831
|
+
if ('inset' === style || 'groove' === style) return 'inset';
|
|
1832
|
+
if ('outset' === style || 'ridge' === style) return 'outset';
|
|
1833
|
+
return 'single';
|
|
1834
|
+
}
|
|
1835
|
+
function work_document_run_border_formatPixels(value) {
|
|
1836
|
+
return Number(value.toFixed(3)).toString();
|
|
1837
|
+
}
|
|
1838
|
+
const work_document_paragraph_shading_DOCUMENT_PARAGRAPH_SHADING_PATTERNS = new Set([
|
|
1839
|
+
'nil',
|
|
1840
|
+
'clear',
|
|
1841
|
+
'solid',
|
|
1842
|
+
'horzStripe',
|
|
1843
|
+
'vertStripe',
|
|
1844
|
+
'reverseDiagStripe',
|
|
1845
|
+
'diagStripe',
|
|
1846
|
+
'horzCross',
|
|
1847
|
+
'diagCross',
|
|
1848
|
+
'thinHorzStripe',
|
|
1849
|
+
'thinVertStripe',
|
|
1850
|
+
'thinReverseDiagStripe',
|
|
1851
|
+
'thinDiagStripe',
|
|
1852
|
+
'thinHorzCross',
|
|
1853
|
+
'thinDiagCross',
|
|
1854
|
+
'pct5',
|
|
1855
|
+
'pct10',
|
|
1856
|
+
'pct12',
|
|
1857
|
+
'pct15',
|
|
1858
|
+
'pct20',
|
|
1859
|
+
'pct25',
|
|
1860
|
+
'pct30',
|
|
1861
|
+
'pct35',
|
|
1862
|
+
'pct37',
|
|
1863
|
+
'pct40',
|
|
1864
|
+
'pct45',
|
|
1865
|
+
'pct50',
|
|
1866
|
+
'pct55',
|
|
1867
|
+
'pct60',
|
|
1868
|
+
'pct62',
|
|
1869
|
+
'pct65',
|
|
1870
|
+
'pct70',
|
|
1871
|
+
'pct75',
|
|
1872
|
+
'pct80',
|
|
1873
|
+
'pct85',
|
|
1874
|
+
'pct87',
|
|
1875
|
+
'pct90',
|
|
1876
|
+
'pct95'
|
|
1877
|
+
]);
|
|
1878
|
+
function normalizeDocumentParagraphShading(source) {
|
|
1879
|
+
if (!source || 'object' != typeof source) return null;
|
|
1880
|
+
const value = source;
|
|
1881
|
+
const pattern = value.pattern;
|
|
1882
|
+
if ('string' != typeof pattern || !work_document_paragraph_shading_DOCUMENT_PARAGRAPH_SHADING_PATTERNS.has(pattern)) return null;
|
|
1883
|
+
const color = normalizeShadingColor(value.color);
|
|
1884
|
+
const fill = normalizeShadingColor(value.fill);
|
|
1885
|
+
if (void 0 !== value.color && !color || void 0 !== value.fill && !fill) return null;
|
|
1886
|
+
return {
|
|
1887
|
+
pattern: pattern,
|
|
1888
|
+
...color ? {
|
|
1889
|
+
color
|
|
1895
1890
|
} : {},
|
|
1896
|
-
...
|
|
1897
|
-
|
|
1891
|
+
...fill ? {
|
|
1892
|
+
fill
|
|
1898
1893
|
} : {}
|
|
1899
1894
|
};
|
|
1900
1895
|
}
|
|
1901
|
-
function
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1896
|
+
function parseDocumentParagraphShading(source) {
|
|
1897
|
+
if ('string' != typeof source) return normalizeDocumentParagraphShading(source);
|
|
1898
|
+
if (!source.trim()) return null;
|
|
1899
|
+
try {
|
|
1900
|
+
return normalizeDocumentParagraphShading(JSON.parse(source));
|
|
1901
|
+
} catch {
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
function serializeDocumentParagraphShading(source) {
|
|
1906
|
+
const shading = normalizeDocumentParagraphShading(source);
|
|
1907
|
+
if (!shading) return;
|
|
1908
|
+
return JSON.stringify({
|
|
1909
|
+
pattern: shading.pattern,
|
|
1910
|
+
...shading.color ? {
|
|
1911
|
+
color: serializedShadingColor(shading.color)
|
|
1912
|
+
} : {},
|
|
1913
|
+
...shading.fill ? {
|
|
1914
|
+
fill: serializedShadingColor(shading.fill)
|
|
1915
|
+
} : {}
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
function parseDocumentParagraphShadingElement(element) {
|
|
1919
|
+
const semantic = parseDocumentParagraphShading(element.dataset.officeParagraphShading);
|
|
1920
|
+
const background = normalizeCssColor(element.style.backgroundColor);
|
|
1921
|
+
if (semantic) {
|
|
1922
|
+
const presentation = documentParagraphShadingPresentation(semantic);
|
|
1923
|
+
const expected = normalizeCssColor(presentation.backgroundColor);
|
|
1924
|
+
if (background && expected && background !== expected) {
|
|
1925
|
+
if ('transparent' === background) return {
|
|
1926
|
+
pattern: 'nil'
|
|
1927
|
+
};
|
|
1928
|
+
if (paragraphShadingBackgroundUsesForeground(semantic)) return {
|
|
1929
|
+
...semantic,
|
|
1930
|
+
color: {
|
|
1931
|
+
value: background
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
return {
|
|
1935
|
+
...semantic,
|
|
1936
|
+
fill: {
|
|
1937
|
+
value: background
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1912
1940
|
}
|
|
1913
|
-
|
|
1914
|
-
if (!language) return null;
|
|
1915
|
-
next[slot] = language;
|
|
1941
|
+
return semantic;
|
|
1916
1942
|
}
|
|
1917
|
-
return
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1943
|
+
return background && 'transparent' !== background ? {
|
|
1944
|
+
pattern: 'clear',
|
|
1945
|
+
fill: {
|
|
1946
|
+
value: background
|
|
1947
|
+
}
|
|
1948
|
+
} : null;
|
|
1921
1949
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
const INDEX_SELECTOR = '[data-document-index]';
|
|
1933
|
-
const INDEX_ID_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/i;
|
|
1934
|
-
function normalizeDocumentIndexEntryDraft(source) {
|
|
1935
|
-
const mainEntry = normalizedIndexTerm(source?.mainEntry);
|
|
1936
|
-
if (!mainEntry) return null;
|
|
1937
|
-
const subEntry = normalizedIndexTerm(source?.subEntry);
|
|
1938
|
-
const crossReference = normalizedIndexTerm(source?.crossReference);
|
|
1950
|
+
function documentParagraphShadingDomAttributes(source) {
|
|
1951
|
+
const shading = normalizeDocumentParagraphShading(source);
|
|
1952
|
+
const serialized = serializeDocumentParagraphShading(shading);
|
|
1953
|
+
if (!shading || !serialized) return {};
|
|
1954
|
+
const presentation = documentParagraphShadingPresentation(shading);
|
|
1955
|
+
const styles = [
|
|
1956
|
+
`background-color: ${presentation.backgroundColor}`,
|
|
1957
|
+
presentation.backgroundImage ? `background-image: ${presentation.backgroundImage}` : '',
|
|
1958
|
+
presentation.backgroundSize ? `background-size: ${presentation.backgroundSize}` : ''
|
|
1959
|
+
].filter(Boolean);
|
|
1939
1960
|
return {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
crossReference,
|
|
1943
|
-
pageBold: !crossReference && Boolean(source?.pageBold),
|
|
1944
|
-
pageItalic: !crossReference && Boolean(source?.pageItalic)
|
|
1961
|
+
'data-office-paragraph-shading': serialized,
|
|
1962
|
+
style: styles.join('; ')
|
|
1945
1963
|
};
|
|
1946
1964
|
}
|
|
1947
|
-
function
|
|
1948
|
-
|
|
1949
|
-
|
|
1965
|
+
function normalizeShadingColor(source) {
|
|
1966
|
+
if (!source || 'object' != typeof source) return null;
|
|
1967
|
+
const value = source;
|
|
1968
|
+
const theme = parseDocxThemeReference('string' == typeof value.theme ? value.theme : value.theme ? JSON.stringify(value.theme) : void 0);
|
|
1969
|
+
const direct = 'auto' === value.value ? 'auto' : 'string' == typeof value.value ? normalizeCssColor(value.value) : null;
|
|
1970
|
+
const resolved = direct ?? theme?.resolved ?? null;
|
|
1971
|
+
if (!resolved || 'transparent' === resolved) return null;
|
|
1972
|
+
if (theme && resolved !== theme.resolved) return null;
|
|
1950
1973
|
return {
|
|
1951
|
-
|
|
1952
|
-
...
|
|
1974
|
+
value: resolved,
|
|
1975
|
+
...theme ? {
|
|
1976
|
+
theme
|
|
1977
|
+
} : {}
|
|
1953
1978
|
};
|
|
1954
1979
|
}
|
|
1955
|
-
function
|
|
1980
|
+
function serializedShadingColor(color) {
|
|
1981
|
+
const theme = serializeDocxThemeReference(color.theme ?? null);
|
|
1956
1982
|
return {
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1983
|
+
value: color.value,
|
|
1984
|
+
...theme ? {
|
|
1985
|
+
theme: JSON.parse(theme)
|
|
1986
|
+
} : {}
|
|
1961
1987
|
};
|
|
1962
1988
|
}
|
|
1963
|
-
function
|
|
1964
|
-
return {
|
|
1965
|
-
|
|
1966
|
-
options: normalizeDocumentIndexOptions(source.options),
|
|
1967
|
-
entries: normalizeGeneratedEntries(source.entries),
|
|
1968
|
-
truncated: Boolean(source.truncated)
|
|
1989
|
+
function documentParagraphShadingPresentation(shading) {
|
|
1990
|
+
if ('nil' === shading.pattern) return {
|
|
1991
|
+
backgroundColor: 'transparent'
|
|
1969
1992
|
};
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
const
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
const existing = group.pages.get(pageNumber);
|
|
1997
|
-
if (existing) {
|
|
1998
|
-
existing.pageBold ||= marker.pageBold;
|
|
1999
|
-
existing.pageItalic ||= marker.pageItalic;
|
|
2000
|
-
if (!existing.targetIds.includes(marker.id)) existing.targetIds.push(marker.id);
|
|
2001
|
-
return;
|
|
2002
|
-
}
|
|
2003
|
-
group.pages.set(pageNumber, {
|
|
2004
|
-
pageNumber,
|
|
2005
|
-
pageBold: marker.pageBold,
|
|
2006
|
-
pageItalic: marker.pageItalic,
|
|
2007
|
-
targetIds: [
|
|
2008
|
-
marker.id
|
|
2009
|
-
]
|
|
2010
|
-
});
|
|
2011
|
-
});
|
|
2012
|
-
const collator = new Intl.Collator(void 0, {
|
|
2013
|
-
numeric: true,
|
|
2014
|
-
sensitivity: 'base',
|
|
2015
|
-
usage: 'sort'
|
|
2016
|
-
});
|
|
2017
|
-
const allEntries = Array.from(grouped.values()).map(({ entry, pages })=>({
|
|
2018
|
-
...entry,
|
|
2019
|
-
pages: Array.from(pages.values()).sort((left, right)=>left.pageNumber - right.pageNumber)
|
|
2020
|
-
})).sort((left, right)=>collator.compare(left.mainEntry, right.mainEntry) || collator.compare(left.subEntry, right.subEntry) || collator.compare(left.crossReference, right.crossReference));
|
|
1993
|
+
const foreground = shadingColor(shading.color, '#000000');
|
|
1994
|
+
const background = shadingColor(shading.fill, 'transparent');
|
|
1995
|
+
if ('clear' === shading.pattern) return {
|
|
1996
|
+
backgroundColor: background
|
|
1997
|
+
};
|
|
1998
|
+
if ('solid' === shading.pattern) return {
|
|
1999
|
+
backgroundColor: foreground
|
|
2000
|
+
};
|
|
2001
|
+
const percentage = shadingPercentage(shading.pattern);
|
|
2002
|
+
if (null !== percentage) {
|
|
2003
|
+
const inverted = percentage > 50;
|
|
2004
|
+
const dotColor = inverted ? background : foreground;
|
|
2005
|
+
const baseColor = inverted ? foreground : background;
|
|
2006
|
+
const density = Math.min(50, inverted ? 100 - percentage : percentage);
|
|
2007
|
+
const spacing = Math.max(2, Math.round(9 - density / 7));
|
|
2008
|
+
return {
|
|
2009
|
+
backgroundColor: baseColor,
|
|
2010
|
+
backgroundImage: `radial-gradient(circle, ${dotColor} 0 1px, transparent 1.2px)`,
|
|
2011
|
+
backgroundSize: `${spacing}px ${spacing}px`
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
const thin = shading.pattern.startsWith('thin');
|
|
2015
|
+
const width = thin ? 1 : 2;
|
|
2016
|
+
const period = thin ? 7 : 6;
|
|
2017
|
+
const stripe = (angle)=>`repeating-linear-gradient(${angle}deg, ${foreground} 0 ${width}px, transparent ${width}px ${period}px)`;
|
|
2018
|
+
const angles = shadingPatternAngles(shading.pattern);
|
|
2021
2019
|
return {
|
|
2022
|
-
|
|
2023
|
-
|
|
2020
|
+
backgroundColor: background,
|
|
2021
|
+
backgroundImage: angles.map(stripe).join(', ')
|
|
2024
2022
|
};
|
|
2025
2023
|
}
|
|
2026
|
-
function
|
|
2027
|
-
|
|
2028
|
-
if (!value) return '';
|
|
2029
|
-
const detail = indexEntryDisplay(value);
|
|
2030
|
-
return [
|
|
2031
|
-
`<span data-document-index-entry="true" data-index-entry-id="${escapeHtmlAttribute(value.id)}" data-index-main-entry="${escapeHtmlAttribute(value.mainEntry)}" data-index-sub-entry="${escapeHtmlAttribute(value.subEntry)}" data-index-cross-reference="${escapeHtmlAttribute(value.crossReference)}" data-index-page-bold="${String(value.pageBold)}" data-index-page-italic="${String(value.pageItalic)}" class="work-document-index-entry" contenteditable="false" aria-label="索引项:${escapeHtmlAttribute(detail)}">`,
|
|
2032
|
-
'<span aria-hidden="true">索引项</span>',
|
|
2033
|
-
`<strong>${escapeHtml(detail)}</strong>`,
|
|
2034
|
-
'</span>'
|
|
2035
|
-
].join('');
|
|
2024
|
+
function shadingColor(color, fallback) {
|
|
2025
|
+
return color && 'auto' !== color.value ? color.value : fallback;
|
|
2036
2026
|
}
|
|
2037
|
-
function
|
|
2038
|
-
const
|
|
2039
|
-
|
|
2040
|
-
|
|
2027
|
+
function shadingPercentage(pattern) {
|
|
2028
|
+
const match = /^pct(\d+)$/.exec(pattern);
|
|
2029
|
+
return match?.[1] ? Number(match[1]) : null;
|
|
2030
|
+
}
|
|
2031
|
+
function paragraphShadingBackgroundUsesForeground(shading) {
|
|
2032
|
+
if ('solid' === shading.pattern) return true;
|
|
2033
|
+
const percentage = shadingPercentage(shading.pattern);
|
|
2034
|
+
return null !== percentage && percentage > 50;
|
|
2035
|
+
}
|
|
2036
|
+
function shadingPatternAngles(pattern) {
|
|
2037
|
+
if (pattern.includes('HorzCross')) return [
|
|
2038
|
+
0,
|
|
2039
|
+
90
|
|
2040
|
+
];
|
|
2041
|
+
if (pattern.includes('DiagCross')) return [
|
|
2042
|
+
45,
|
|
2043
|
+
-45
|
|
2044
|
+
];
|
|
2045
|
+
if (pattern.includes('VertStripe')) return [
|
|
2046
|
+
90
|
|
2047
|
+
];
|
|
2048
|
+
if (pattern.includes('ReverseDiagStripe')) return [
|
|
2049
|
+
-45
|
|
2050
|
+
];
|
|
2051
|
+
if (pattern.includes('DiagStripe')) return [
|
|
2052
|
+
45
|
|
2041
2053
|
];
|
|
2042
|
-
const status = value.truncated ? `仅显示前 ${MAX_DOCUMENT_INDEX_ENTRIES} 项` : `${value.entries.length} 项`;
|
|
2043
2054
|
return [
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
'<ol class="work-document-index-list">',
|
|
2047
|
-
...rows,
|
|
2048
|
-
'</ol></div>'
|
|
2049
|
-
].join('');
|
|
2055
|
+
0
|
|
2056
|
+
];
|
|
2050
2057
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2058
|
+
const DOCUMENT_HIGHLIGHT_ATTRIBUTE = 'data-office-highlight';
|
|
2059
|
+
const DOCUMENT_HIGHLIGHT_VALUES = new Set([
|
|
2060
|
+
'black',
|
|
2061
|
+
'blue',
|
|
2062
|
+
'cyan',
|
|
2063
|
+
'darkBlue',
|
|
2064
|
+
'darkCyan',
|
|
2065
|
+
'darkGray',
|
|
2066
|
+
'darkGreen',
|
|
2067
|
+
'darkMagenta',
|
|
2068
|
+
'darkRed',
|
|
2069
|
+
'darkYellow',
|
|
2070
|
+
'green',
|
|
2071
|
+
'lightGray',
|
|
2072
|
+
'magenta',
|
|
2073
|
+
'none',
|
|
2074
|
+
'red',
|
|
2075
|
+
'white',
|
|
2076
|
+
'yellow'
|
|
2077
|
+
]);
|
|
2078
|
+
const HIGHLIGHT_COLORS = {
|
|
2079
|
+
black: '#000000',
|
|
2080
|
+
blue: '#0000ff',
|
|
2081
|
+
cyan: '#00ffff',
|
|
2082
|
+
darkBlue: '#000080',
|
|
2083
|
+
darkCyan: '#008080',
|
|
2084
|
+
darkGray: '#808080',
|
|
2085
|
+
darkGreen: '#008000',
|
|
2086
|
+
darkMagenta: '#800080',
|
|
2087
|
+
darkRed: '#800000',
|
|
2088
|
+
darkYellow: '#808000',
|
|
2089
|
+
green: '#00ff00',
|
|
2090
|
+
lightGray: '#c0c0c0',
|
|
2091
|
+
magenta: '#ff00ff',
|
|
2092
|
+
none: 'transparent',
|
|
2093
|
+
red: '#ff0000',
|
|
2094
|
+
white: '#ffffff',
|
|
2095
|
+
yellow: '#ffff00'
|
|
2096
|
+
};
|
|
2097
|
+
function normalizeDocumentHighlight(source) {
|
|
2098
|
+
return 'string' == typeof source && DOCUMENT_HIGHLIGHT_VALUES.has(source) ? source : null;
|
|
2060
2099
|
}
|
|
2061
|
-
function
|
|
2062
|
-
return
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
format: 'run-in' === element.dataset.indexFormat ? 'run-in' : 'indented',
|
|
2067
|
-
rightAlignPageNumbers: 'false' !== element.dataset.indexRightAlignPageNumbers,
|
|
2068
|
-
leader: indexLeader(element.dataset.indexLeader) ?? 'dot'
|
|
2069
|
-
},
|
|
2070
|
-
entries: parseGeneratedEntries(element.dataset.indexEntries),
|
|
2071
|
-
truncated: 'true' === element.dataset.indexTruncated
|
|
2072
|
-
});
|
|
2100
|
+
function documentHighlightFromDocxValue(source) {
|
|
2101
|
+
if ('string' != typeof source) return null;
|
|
2102
|
+
const normalized = source.trim().toLowerCase();
|
|
2103
|
+
for (const value of DOCUMENT_HIGHLIGHT_VALUES)if (value.toLowerCase() === normalized) return value;
|
|
2104
|
+
return null;
|
|
2073
2105
|
}
|
|
2074
|
-
function
|
|
2075
|
-
const
|
|
2076
|
-
|
|
2077
|
-
for (const [index, element] of Array.from(document.body.querySelectorAll(INDEX_ENTRY_SELECTOR)).entries()){
|
|
2078
|
-
const value = documentIndexEntryFromElement(element, index + 1);
|
|
2079
|
-
if (!value) {
|
|
2080
|
-
element.remove();
|
|
2081
|
-
continue;
|
|
2082
|
-
}
|
|
2083
|
-
value.id = uniqueIndexId(value.id, 'index-entry', index + 1, usedEntryIds);
|
|
2084
|
-
element.replaceWith(document.createRange().createContextualFragment(documentIndexEntryHtml(value)));
|
|
2085
|
-
}
|
|
2086
|
-
const usedIndexIds = new Set();
|
|
2087
|
-
for (const [index, element] of Array.from(document.body.querySelectorAll(INDEX_SELECTOR)).entries()){
|
|
2088
|
-
const value = documentIndexValueFromElement(element, index + 1);
|
|
2089
|
-
value.id = uniqueIndexId(value.id, 'document-index', index + 1, usedIndexIds);
|
|
2090
|
-
element.replaceWith(document.createRange().createContextualFragment(documentIndexHtml(value)));
|
|
2091
|
-
}
|
|
2092
|
-
return document.body.innerHTML;
|
|
2106
|
+
function documentHighlightCssColor(source) {
|
|
2107
|
+
const value = normalizeDocumentHighlight(source);
|
|
2108
|
+
return value ? HIGHLIGHT_COLORS[value] : null;
|
|
2093
2109
|
}
|
|
2094
|
-
function
|
|
2095
|
-
|
|
2110
|
+
function documentHighlightForCssColor(source) {
|
|
2111
|
+
const color = normalizeCssColor('string' == typeof source ? source : null);
|
|
2112
|
+
if (!color) return null;
|
|
2113
|
+
for (const [value, candidate] of Object.entries(HIGHLIGHT_COLORS))if (candidate === color) return value;
|
|
2114
|
+
return null;
|
|
2096
2115
|
}
|
|
2097
|
-
function
|
|
2098
|
-
|
|
2099
|
-
const pages = entry.crossReference ? `<span class="work-document-index-cross-reference">参见 ${escapeHtml(entry.crossReference)}</span>` : entry.pages.map((page)=>{
|
|
2100
|
-
const target = page.targetIds[0] ?? '';
|
|
2101
|
-
const classes = [
|
|
2102
|
-
page.pageBold ? 'bold' : '',
|
|
2103
|
-
page.pageItalic ? 'italic' : ''
|
|
2104
|
-
].filter(Boolean).join(' ');
|
|
2105
|
-
return `<a href="#${escapeHtmlAttribute(target)}" data-index-target="${escapeHtmlAttribute(target)}" tabindex="-1"${classes ? ` class="${classes}"` : ''}>${page.pageNumber}</a>`;
|
|
2106
|
-
}).join('<span aria-hidden="true">, </span>');
|
|
2107
|
-
return `<li data-index-main-entry="${escapeHtmlAttribute(entry.mainEntry)}" data-index-sub-entry="${escapeHtmlAttribute(entry.subEntry)}">${term}<i aria-hidden="true"></i><span class="work-document-index-pages">${pages}</span></li>`;
|
|
2116
|
+
function documentHighlightFromElement(element) {
|
|
2117
|
+
return normalizeDocumentHighlight(element.getAttribute(DOCUMENT_HIGHLIGHT_ATTRIBUTE)) ?? documentHighlightForCssColor(element.style.backgroundColor);
|
|
2108
2118
|
}
|
|
2109
|
-
function
|
|
2110
|
-
const
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
subEntry: normalizedIndexTerm(candidate.subEntry),
|
|
2117
|
-
crossReference: normalizedIndexTerm(candidate.crossReference)
|
|
2118
|
-
});
|
|
2119
|
-
if (draft) entries.push({
|
|
2120
|
-
mainEntry: draft.mainEntry,
|
|
2121
|
-
subEntry: draft.subEntry,
|
|
2122
|
-
crossReference: draft.crossReference,
|
|
2123
|
-
pages: draft.crossReference ? [] : normalizeIndexPages(candidate.pages)
|
|
2124
|
-
});
|
|
2125
|
-
}
|
|
2126
|
-
return entries;
|
|
2119
|
+
function documentHighlightDomAttributes(source) {
|
|
2120
|
+
const value = normalizeDocumentHighlight(source);
|
|
2121
|
+
const color = documentHighlightCssColor(value);
|
|
2122
|
+
return value && color ? {
|
|
2123
|
+
[DOCUMENT_HIGHLIGHT_ATTRIBUTE]: value,
|
|
2124
|
+
style: `background-color: ${color}`
|
|
2125
|
+
} : {};
|
|
2127
2126
|
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2127
|
+
const DOCUMENT_RUN_SHADING_ATTRIBUTE = 'data-office-run-shading';
|
|
2128
|
+
const MAX_SERIALIZED_RUN_SHADING_BYTES = 4096;
|
|
2129
|
+
const RUN_SHADING_KEYS = new Set([
|
|
2130
|
+
'pattern',
|
|
2131
|
+
'color',
|
|
2132
|
+
'fill'
|
|
2133
|
+
]);
|
|
2134
|
+
const RUN_SHADING_COLOR_KEYS = new Set([
|
|
2135
|
+
'value',
|
|
2136
|
+
'theme'
|
|
2137
|
+
]);
|
|
2138
|
+
const THEME_REFERENCE_KEYS = new Set([
|
|
2139
|
+
'theme',
|
|
2140
|
+
'resolved',
|
|
2141
|
+
'tint',
|
|
2142
|
+
'shade'
|
|
2143
|
+
]);
|
|
2144
|
+
function normalizeDocumentRunShading(source) {
|
|
2145
|
+
if (!work_document_run_shading_isRecordWithKeys(source, RUN_SHADING_KEYS)) return null;
|
|
2146
|
+
for (const name of [
|
|
2147
|
+
'color',
|
|
2148
|
+
'fill'
|
|
2149
|
+
]){
|
|
2150
|
+
const color = source[name];
|
|
2151
|
+
if (void 0 !== color) {
|
|
2152
|
+
if (!work_document_run_shading_isRecordWithKeys(color, RUN_SHADING_COLOR_KEYS)) return null;
|
|
2153
|
+
if (void 0 !== color.theme && !work_document_run_shading_isRecordWithKeys(color.theme, THEME_REFERENCE_KEYS)) return null;
|
|
2148
2154
|
}
|
|
2149
2155
|
}
|
|
2150
|
-
return
|
|
2156
|
+
return normalizeDocumentParagraphShading(source);
|
|
2151
2157
|
}
|
|
2152
|
-
function
|
|
2153
|
-
if (
|
|
2158
|
+
function parseDocumentRunShading(source) {
|
|
2159
|
+
if ('string' != typeof source) return normalizeDocumentRunShading(source);
|
|
2160
|
+
if (!source.trim() || source.length > MAX_SERIALIZED_RUN_SHADING_BYTES) return null;
|
|
2154
2161
|
try {
|
|
2155
|
-
|
|
2156
|
-
return Array.isArray(parsed) ? normalizeGeneratedEntries(parsed) : [];
|
|
2162
|
+
return normalizeDocumentRunShading(JSON.parse(source));
|
|
2157
2163
|
} catch {
|
|
2158
|
-
return
|
|
2164
|
+
return null;
|
|
2159
2165
|
}
|
|
2160
2166
|
}
|
|
2161
|
-
function
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
if ('pageBreak' === node.type.name) pageNumber += 1;
|
|
2166
|
-
return true;
|
|
2167
|
-
});
|
|
2168
|
-
return pageNumber;
|
|
2169
|
-
}
|
|
2170
|
-
function indexEntryKey(entry) {
|
|
2171
|
-
return [
|
|
2172
|
-
entry.mainEntry,
|
|
2173
|
-
entry.subEntry,
|
|
2174
|
-
entry.crossReference
|
|
2175
|
-
].map((value)=>value.normalize('NFKC').toLocaleLowerCase()).join('\u0000');
|
|
2176
|
-
}
|
|
2177
|
-
function indexEntryDisplay(entry) {
|
|
2178
|
-
const term = entry.subEntry ? `${entry.mainEntry} › ${entry.subEntry}` : entry.mainEntry;
|
|
2179
|
-
return entry.crossReference ? `${term} · 参见 ${entry.crossReference}` : term;
|
|
2180
|
-
}
|
|
2181
|
-
function normalizedIndexTerm(value) {
|
|
2182
|
-
return 'string' == typeof value ? value.replace(/\s+/g, ' ').trim().slice(0, MAX_DOCUMENT_INDEX_TERM_LENGTH) : '';
|
|
2183
|
-
}
|
|
2184
|
-
function boundedColumns(value) {
|
|
2185
|
-
const number = Number(value);
|
|
2186
|
-
return Number.isInteger(number) && number >= 1 && number <= 4 ? number : 1;
|
|
2187
|
-
}
|
|
2188
|
-
function positiveInteger(value) {
|
|
2189
|
-
const number = Number(value);
|
|
2190
|
-
return Number.isSafeInteger(number) && number > 0 ? Math.min(999999, number) : null;
|
|
2191
|
-
}
|
|
2192
|
-
function validIndexId(value) {
|
|
2193
|
-
return 'string' == typeof value && INDEX_ID_PATTERN.test(value) ? value : null;
|
|
2167
|
+
function serializeDocumentRunShading(source) {
|
|
2168
|
+
const shading = normalizeDocumentRunShading(source);
|
|
2169
|
+
const serialized = shading ? serializeDocumentParagraphShading(shading) : void 0;
|
|
2170
|
+
return serialized && serialized.length <= MAX_SERIALIZED_RUN_SHADING_BYTES ? serialized : void 0;
|
|
2194
2171
|
}
|
|
2195
|
-
function
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2172
|
+
function parseDocumentRunShadingElement(element) {
|
|
2173
|
+
const semantic = parseDocumentRunShading(element.getAttribute(DOCUMENT_RUN_SHADING_ATTRIBUTE));
|
|
2174
|
+
if (!semantic) return null;
|
|
2175
|
+
if (element.hasAttribute(DOCUMENT_HIGHLIGHT_ATTRIBUTE)) return semantic;
|
|
2176
|
+
const background = normalizeCssColor(element.style.backgroundColor);
|
|
2177
|
+
const expected = normalizeCssColor(documentParagraphShadingPresentation(semantic).backgroundColor);
|
|
2178
|
+
if (!background || !expected || background === expected) return semantic;
|
|
2179
|
+
if ('transparent' === background) return {
|
|
2180
|
+
pattern: 'nil'
|
|
2181
|
+
};
|
|
2182
|
+
return paragraphShadingBackgroundUsesForeground(semantic) ? {
|
|
2183
|
+
...semantic,
|
|
2184
|
+
color: {
|
|
2185
|
+
value: background
|
|
2186
|
+
}
|
|
2187
|
+
} : {
|
|
2188
|
+
...semantic,
|
|
2189
|
+
fill: {
|
|
2190
|
+
value: background
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
2205
2193
|
}
|
|
2206
|
-
function
|
|
2207
|
-
|
|
2194
|
+
function documentRunShadingDomAttributes(source) {
|
|
2195
|
+
const shading = normalizeDocumentRunShading(source);
|
|
2196
|
+
const serialized = serializeDocumentRunShading(shading);
|
|
2197
|
+
if (!shading || !serialized) return {};
|
|
2198
|
+
const paragraphAttributes = documentParagraphShadingDomAttributes(shading);
|
|
2199
|
+
const style = [
|
|
2200
|
+
paragraphAttributes.style,
|
|
2201
|
+
'box-decoration-break: clone',
|
|
2202
|
+
'-webkit-box-decoration-break: clone'
|
|
2203
|
+
].filter(Boolean).join('; ');
|
|
2204
|
+
return {
|
|
2205
|
+
[DOCUMENT_RUN_SHADING_ATTRIBUTE]: serialized,
|
|
2206
|
+
style
|
|
2207
|
+
};
|
|
2208
2208
|
}
|
|
2209
|
-
function
|
|
2210
|
-
return
|
|
2209
|
+
function work_document_run_shading_isRecordWithKeys(source, allowed) {
|
|
2210
|
+
return 'object' == typeof source && null !== source && !Array.isArray(source) && Object.keys(source).every((key)=>allowed.has(key));
|
|
2211
2211
|
}
|
|
2212
2212
|
const DOCUMENT_PARAGRAPH_ID_ATTRIBUTE = 'data-office-paragraph-id';
|
|
2213
2213
|
const DOCUMENT_PARAGRAPH_TEXT_ID_ATTRIBUTE = 'data-office-paragraph-text-id';
|
|
@@ -2909,7 +2909,7 @@ const WORK_TEMPLATES = [
|
|
|
2909
2909
|
id: 'structured-references',
|
|
2910
2910
|
kind: 'spreadsheet',
|
|
2911
2911
|
name: '结构化引用',
|
|
2912
|
-
description: '
|
|
2912
|
+
description: '表名、当前行公式、插入行自动填充与汇总行',
|
|
2913
2913
|
accent: '#0f7b61'
|
|
2914
2914
|
},
|
|
2915
2915
|
{
|
|
@@ -3685,7 +3685,7 @@ function structuredReferenceTemplateSheets() {
|
|
|
3685
3685
|
fc: '#215446',
|
|
3686
3686
|
bg: '#dff3ec'
|
|
3687
3687
|
});
|
|
3688
|
-
sales[7][1] = styledCell('=
|
|
3688
|
+
sales[7][1] = styledCell('=SUBTOTAL(109,Sales[Units])', {
|
|
3689
3689
|
bl: 1,
|
|
3690
3690
|
fc: '#215446',
|
|
3691
3691
|
bg: '#dff3ec',
|
|
@@ -3694,7 +3694,7 @@ function structuredReferenceTemplateSheets() {
|
|
|
3694
3694
|
t: 'n'
|
|
3695
3695
|
}
|
|
3696
3696
|
});
|
|
3697
|
-
sales[7][3] = styledCell('=
|
|
3697
|
+
sales[7][3] = styledCell('=SUBTOTAL(109,Sales[Revenue])', {
|
|
3698
3698
|
bl: 1,
|
|
3699
3699
|
fc: '#215446',
|
|
3700
3700
|
bg: '#dff3ec',
|
|
@@ -3751,17 +3751,20 @@ function structuredReferenceTemplateSheets() {
|
|
|
3751
3751
|
},
|
|
3752
3752
|
columns: [
|
|
3753
3753
|
{
|
|
3754
|
-
name: 'Item'
|
|
3754
|
+
name: 'Item',
|
|
3755
|
+
totalsLabel: 'Total'
|
|
3755
3756
|
},
|
|
3756
3757
|
{
|
|
3757
|
-
name: 'Units'
|
|
3758
|
+
name: 'Units',
|
|
3759
|
+
totalsFunction: 'sum'
|
|
3758
3760
|
},
|
|
3759
3761
|
{
|
|
3760
3762
|
name: 'Unit price'
|
|
3761
3763
|
},
|
|
3762
3764
|
{
|
|
3763
3765
|
name: 'Revenue',
|
|
3764
|
-
calculatedFormula: '=[@Units]*[@[Unit price]]'
|
|
3766
|
+
calculatedFormula: '=[@Units]*[@[Unit price]]',
|
|
3767
|
+
totalsFunction: 'sum'
|
|
3765
3768
|
}
|
|
3766
3769
|
],
|
|
3767
3770
|
filters: [],
|