@a3s-lab/office 0.32.0 → 0.33.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 +59 -7
- package/dist/0~3539.js +2 -2
- package/dist/0~3557.js +1 -1
- package/dist/0~3635.js +1 -1
- package/dist/0~7048.js +1 -2
- package/dist/{0~6090.js → 0~8136.js} +71 -2
- package/dist/{0~4595.js → 0~9073.js} +3 -72
- package/dist/0~9445.js +1 -1
- package/dist/0~document-editor.js +2 -2
- package/dist/0~markdown-editor.js +2 -2
- package/dist/0~pdf-page-organization-engine.js +187 -0
- package/dist/0~pdf-viewer.js +1014 -30
- package/dist/0~presentation-editor.js +2 -2
- package/dist/0~spreadsheet-editor.js +2 -2
- package/dist/core.d.ts +1 -0
- package/dist/internal/features/work/editors/pdf-editor-extensions.d.ts +11 -0
- package/dist/internal/features/work/editors/pdf-page-organization-engine.d.ts +2 -0
- package/dist/internal/features/work/editors/pdf-page-organization-plan.d.ts +10 -0
- package/dist/internal/features/work/editors/pdf-page-organization-types.d.ts +68 -0
- package/dist/internal/features/work/editors/pdf-page-organization-worker-client.d.ts +2 -0
- package/dist/internal/features/work/editors/pdf-page-organization-worker-protocol.d.ts +13 -0
- package/dist/internal/features/work/editors/pdf-page-organization.d.ts +3 -0
- package/dist/internal/features/work/editors/pdf-page-organization.worker.d.ts +1 -0
- package/dist/internal/features/work/editors/pdf-page-organizer-dialog.d.ts +17 -0
- package/dist/internal/features/work/editors/pdf-thumbnail-rail.d.ts +4 -0
- package/dist/internal/features/work/editors/pdf-toolbar.d.ts +2 -1
- package/dist/internal/features/work/editors/pdf-viewer.d.ts +3 -1
- package/dist/internal/features/work/editors/use-pdf-page-organization.d.ts +38 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/pdf-page-organization.worker.js +19667 -0
- package/dist/pdf-page-organization.worker.js.LICENSE.txt +14 -0
- package/dist/react.d.ts +1 -0
- package/dist/styles.css +315 -0
- package/dist/vue.d.ts +3 -1
- package/dist/vue.js +2 -0
- package/dist/web-component.d.ts +3 -1
- package/dist/web-component.js +9 -0
- package/docs/latest/en/browser-editor-architecture.md +29 -6
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ workflows, and a separate Rust automation plane.
|
|
|
56
56
|
|
|
57
57
|
## Latest on `main`
|
|
58
58
|
|
|
59
|
-
The `0.
|
|
59
|
+
The `0.33.0` release plus the current `main` branch expose these capabilities
|
|
60
60
|
as normal user-facing Playground templates, with matching implementation
|
|
61
61
|
detail in the documentation:
|
|
62
62
|
|
|
@@ -68,10 +68,13 @@ detail in the documentation:
|
|
|
68
68
|
| Writer | Native character shading with exact `w:shd` patterns, foreground/background color identity, explicit resets, authoring, and DOCX reopen | Playground **Latest capabilities → 字符底纹** · [Document reference](docs/latest/en/components/document.mdx#native-character-shading) |
|
|
69
69
|
| Writer | Independent Latin, East Asian, and bidi proofing languages plus explicit `w:noProof` inclusion/exclusion | Playground **Latest capabilities → 校对语言** · [Document reference](docs/latest/en/components/document.mdx#native-proofing-languages) |
|
|
70
70
|
| Spreadsheet | Complete common Data Validation input, blank/dropdown, and Stop/Warning/Information error settings with native XLSX round trips | Playground **Latest capabilities → 数据验证** · [Spreadsheet reference](docs/latest/en/components/spreadsheet.mdx#data-validation) |
|
|
71
|
+
| PDF | Insert, delete, rotate, reorder, extract, merge, and split pages through a dedicated Web Worker, with Blob-level Undo/Redo and independent binary reopen verification | Playground **Latest capabilities → 组织 PDF 页面** · [PDF reference](docs/latest/en/components/pdf.mdx#page-organization) |
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
All seven entries are reachable from the first Playground viewport. The six
|
|
74
|
+
content templates also remain under **新建**; **组织 PDF 页面** opens the normal
|
|
75
|
+
PDF file workflow because page organization must operate on host-provided source
|
|
76
|
+
bytes. The highlighted strip keeps each capability discoverable without a deep
|
|
77
|
+
documentation section or an `?e2e=` fixture.
|
|
75
78
|
|
|
76
79
|
## See it working
|
|
77
80
|
|
|
@@ -108,9 +111,9 @@ The images below are committed visual-regression baselines from the real
|
|
|
108
111
|
</td>
|
|
109
112
|
<td width="50%" valign="top">
|
|
110
113
|
<a href="visual-tests/__snapshots__/linux/desktop-1280/pdf.png">
|
|
111
|
-
<img src="visual-tests/__snapshots__/linux/desktop-1280/pdf.png" alt="A3S Office PDF editor with search, annotation, save, and download controls">
|
|
114
|
+
<img src="visual-tests/__snapshots__/linux/desktop-1280/pdf.png" alt="A3S Office PDF editor with search, annotation, page organization, save, and download controls">
|
|
112
115
|
</a>
|
|
113
|
-
<br><sub><strong>PDF</strong> — PDFium rendering, forms, annotations, Yjs overlays, and save</sub>
|
|
116
|
+
<br><sub><strong>PDF</strong> — PDFium rendering, forms, annotations, page organization, Yjs overlays, and save</sub>
|
|
114
117
|
</td>
|
|
115
118
|
</tr>
|
|
116
119
|
</table>
|
|
@@ -173,7 +176,7 @@ baseline rather than one specific release.
|
|
|
173
176
|
| Search and text evidence | **Supported with boundaries** — browser search and bounded native text-layer evidence | Broader tagged-PDF reading order and accessibility extraction |
|
|
174
177
|
| Annotations, forms, and save | **Supported** — common annotations, appearance controls, form filling, history, and save | Broader stamps, measurements, form authoring, scripts, and signatures |
|
|
175
178
|
| Existing text, image, and object editing | **Gap** — no safe production content-stream editing path | Direct text/object editing with font and layout recovery |
|
|
176
|
-
| Page organization | **
|
|
179
|
+
| Page organization | **Supported with boundaries** — insert, delete, rotate, reorder, extract, merge, and split run in a dedicated Worker, replace one complete Blob per mutation, and participate in Undo/Redo | Broader document-level catalog preservation and signed/encrypted-file workflows |
|
|
177
180
|
| Compression, conversion, and OCR | **Gap / host boundary** — provider contracts are required for authoritative conversion and OCR | Integrated optimization, conversion, and scanned-document recognition |
|
|
178
181
|
| Signatures, protection, and redaction | **Gap / host boundary** — trusted identity and destructive-content guarantees are required | E-signing, certificate validation, encryption, sanitization, and true redaction |
|
|
179
182
|
| AI and real-time collaboration | **Host-owned** — typed page/text evidence, Yjs review records, presence, and provider-neutral ports | Typically bundled with account, storage, and model services |
|
|
@@ -433,6 +436,51 @@ viewer or pre-initialize the PDFium Worker. On the local reference machine it
|
|
|
433
436
|
improved median shell mount by 41.6 ms but did not improve the viewer-ready
|
|
434
437
|
median, so Worker initialization remains the measured boundary.
|
|
435
438
|
|
|
439
|
+
### Organize and persist PDF pages
|
|
440
|
+
|
|
441
|
+
Providing `onSave` enables the page organizer. Every insert, delete, rotate,
|
|
442
|
+
reorder, or merge produces one replacement PDF `Blob` and one page-history
|
|
443
|
+
record; extract and split return separate files without changing the source.
|
|
444
|
+
Use `onPageExport` when the host should persist those derived files instead of
|
|
445
|
+
letting the viewer download them:
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
import type { PdfPageOrganizationExport } from '@a3s-lab/office/react';
|
|
449
|
+
import { PdfViewer } from '@a3s-lab/office/react';
|
|
450
|
+
|
|
451
|
+
async function persistPageExports(
|
|
452
|
+
files: readonly PdfPageOrganizationExport[],
|
|
453
|
+
): Promise<boolean> {
|
|
454
|
+
await Promise.all(
|
|
455
|
+
files.map(({ fileName, pageCount, pdf }) =>
|
|
456
|
+
storage.put(fileName, pdf, { pageCount }),
|
|
457
|
+
),
|
|
458
|
+
);
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
<PdfViewer
|
|
463
|
+
fileName="contract.pdf"
|
|
464
|
+
loadSource={() => storage.get('contract.pdf')}
|
|
465
|
+
onSave={async (pdf) => {
|
|
466
|
+
await storage.put('contract.pdf', pdf);
|
|
467
|
+
return true;
|
|
468
|
+
}}
|
|
469
|
+
onPageExport={persistPageExports}
|
|
470
|
+
/>;
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Page jobs load lazily in a dedicated Web Worker. The primary PDF is limited to
|
|
474
|
+
256 MiB, a merged PDF to 128 MiB, and every result to 4,096 pages. Toolbar
|
|
475
|
+
Undo/Redo consumes native annotation or form history first, then page history.
|
|
476
|
+
Signed or encrypted PDFs fail closed; delete, reorder, and merge also reject
|
|
477
|
+
forms, outlines, or tagged structures whose page references cannot be safely
|
|
478
|
+
rewritten. Extract and split surface a diagnostic when document-level catalog
|
|
479
|
+
objects are intentionally not copied. Page organization is unavailable while
|
|
480
|
+
`collaboration` or `evidenceOverlay` is active because those modes identify an
|
|
481
|
+
immutable source PDF. See the complete
|
|
482
|
+
[PDF page-organization contract](docs/latest/en/components/pdf.mdx#page-organization).
|
|
483
|
+
|
|
436
484
|
### Persist a structured document snapshot
|
|
437
485
|
|
|
438
486
|
Hosts that round-trip controlled document values across a process or language
|
|
@@ -1547,6 +1595,9 @@ bun run lint
|
|
|
1547
1595
|
bun run typecheck
|
|
1548
1596
|
bun run test
|
|
1549
1597
|
bun run build
|
|
1598
|
+
bun run test:e2e:pdf-page-organization:check
|
|
1599
|
+
bun run test:e2e:pdf-page-organization
|
|
1600
|
+
bun run playground:visual:pdf-page-organization
|
|
1550
1601
|
bun run test:e2e:large-pdf:check
|
|
1551
1602
|
bun run test:e2e:large-pdf
|
|
1552
1603
|
bun run performance:pdf
|
|
@@ -1571,6 +1622,7 @@ without hard-coded return URLs.
|
|
|
1571
1622
|
|
|
1572
1623
|
- [Live Playground](https://a3s-lab.github.io/Office/)
|
|
1573
1624
|
- [Documentation center](https://a3s-lab.github.io/Office/docs/)
|
|
1625
|
+
- [A3S Office 0.33.0 documentation](https://a3s-lab.github.io/Office/docs/0.33.0/)
|
|
1574
1626
|
- [A3S Office 0.32.0 documentation](https://a3s-lab.github.io/Office/docs/0.32.0/)
|
|
1575
1627
|
- [A3S Office 0.31.0 documentation](https://a3s-lab.github.io/Office/docs/0.31.0/)
|
|
1576
1628
|
- [A3S Office 0.30.0 documentation](https://a3s-lab.github.io/Office/docs/0.30.0/)
|
package/dist/0~3539.js
CHANGED
|
@@ -6,8 +6,8 @@ import { effectiveSpreadsheetCalculationSettings, workSpreadsheetCombinationSeri
|
|
|
6
6
|
import { presentationChartLegendPositionLabel, presentationChartShowsLegend, normalizePresentationChartLegendPosition, presentationChartUsesNumericXAxis, normalizePresentationBubbleSizeRepresents, normalizePresentationBubbleScale, presentationChartAxes, normalizePresentationChartDataLabels, presentationChartTypeLabel, presentationChartDataLabelPositionLabel, normalizePresentationScatterStyle } from "./0~work-presentation-charts.js";
|
|
7
7
|
import { OfficeCheckbox, CommittedOfficeNumberField, CollectionState } from "./0~7048.js";
|
|
8
8
|
import { OfficeColorPicker } from "./0~3635.js";
|
|
9
|
-
import { OfficeSelect } from "./0~
|
|
10
|
-
import { office_text_field_OfficeTextField, CommittedOfficeTextField } from "./0~
|
|
9
|
+
import { OfficeSelect } from "./0~9073.js";
|
|
10
|
+
import { office_text_field_OfficeTextField, CommittedOfficeTextField } from "./0~8136.js";
|
|
11
11
|
function spreadsheetAgentMenuItems(selection, onAgentRequest, applyProposal) {
|
|
12
12
|
return [
|
|
13
13
|
{
|
package/dist/0~3557.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
2
|
-
import { matchesOfficeEditorKeyboardShortcut } from "./0~
|
|
2
|
+
import { matchesOfficeEditorKeyboardShortcut } from "./0~8136.js";
|
|
3
3
|
const DEFAULT_EXTENSION_PRIORITY = 100;
|
|
4
4
|
function createOfficeEditorExtension(config) {
|
|
5
5
|
const name = config.name.trim();
|
package/dist/0~3635.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Ban, Check, ChevronDown } from "lucide-react";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
|
-
import { button_Button, Popover } from "./0~
|
|
4
|
+
import { button_Button, Popover } from "./0~8136.js";
|
|
5
5
|
const THEME_COLORS = [
|
|
6
6
|
'#111827',
|
|
7
7
|
'#1f2937',
|
package/dist/0~7048.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useId, useRef, useState } from "react";
|
|
3
3
|
import { Check, ChevronDown, ChevronUp, Minus } from "lucide-react";
|
|
4
|
-
import { Dialog } from "./0~
|
|
5
|
-
import { button_Button, office_text_field_OfficeTextField, office_text_field_OfficeTextArea } from "./0~6090.js";
|
|
4
|
+
import { button_Button, office_text_field_OfficeTextArea, office_text_field_OfficeTextField, Dialog } from "./0~8136.js";
|
|
6
5
|
import { OFFICE_KERNEL_SPREADSHEET_MAX_ROWS as office_kernel_spreadsheet_protocol_OFFICE_KERNEL_SPREADSHEET_MAX_ROWS, isOfficeKernelSpreadsheetError } from "./4121.js";
|
|
7
6
|
function CollectionState({ children, icon, actions, tone = 'neutral', role, className = '' }) {
|
|
8
7
|
return /*#__PURE__*/ jsxs("div", {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Bot, LocateFixed, UsersRound, X } from "lucide-react";
|
|
3
4
|
import { createPortal } from "react-dom";
|
|
4
|
-
import { Bot, LocateFixed, UsersRound } from "lucide-react";
|
|
5
5
|
import { useOfficeCollaborationPresence, useOfficeCollaborationParticipantNavigation } from "./432.js";
|
|
6
6
|
const focusableSelector = 'button:not(:disabled), input:not(:disabled):not([type="hidden"]), textarea:not(:disabled), select:not(:disabled), a[href], [contenteditable="true"], [tabindex]:not([tabindex="-1"])';
|
|
7
7
|
const activeFocusScopes = [];
|
|
@@ -292,6 +292,75 @@ const icon_button_IconButton = /*#__PURE__*/ forwardRef(function({ label, toolti
|
|
|
292
292
|
children: children
|
|
293
293
|
});
|
|
294
294
|
});
|
|
295
|
+
function Dialog({ title, description, children, footer, onClose, closeDisabled = false, className, focusKey, restoreFocusTarget }) {
|
|
296
|
+
const titleId = useId();
|
|
297
|
+
const descriptionId = useId();
|
|
298
|
+
const portalRootRef = useRef(null);
|
|
299
|
+
if (!portalRootRef.current && "u" > typeof document) portalRootRef.current = officeOverlayPortalRoot(document, restoreFocusTarget?.());
|
|
300
|
+
const focusScope = useDialogFocusScope({
|
|
301
|
+
onEscape: onClose,
|
|
302
|
+
escapeDisabled: closeDisabled,
|
|
303
|
+
restoreFocusTarget
|
|
304
|
+
});
|
|
305
|
+
useEffect(()=>{
|
|
306
|
+
if (void 0 !== focusKey) focusScope.focusInitial();
|
|
307
|
+
}, [
|
|
308
|
+
focusKey,
|
|
309
|
+
focusScope.focusInitial
|
|
310
|
+
]);
|
|
311
|
+
const dialog = /*#__PURE__*/ jsx("dialog", {
|
|
312
|
+
open: true,
|
|
313
|
+
className: "ds-dialog-backdrop",
|
|
314
|
+
role: "presentation",
|
|
315
|
+
onCancel: (event)=>{
|
|
316
|
+
event.preventDefault();
|
|
317
|
+
if (!closeDisabled) onClose();
|
|
318
|
+
},
|
|
319
|
+
children: /*#__PURE__*/ jsxs("section", {
|
|
320
|
+
ref: focusScope.scopeRef,
|
|
321
|
+
className: `ds-dialog${className ? ` ${className}` : ''}`,
|
|
322
|
+
role: "dialog",
|
|
323
|
+
"aria-modal": "true",
|
|
324
|
+
"aria-labelledby": titleId,
|
|
325
|
+
"aria-describedby": description ? descriptionId : void 0,
|
|
326
|
+
onKeyDown: focusScope.handleKeyDown,
|
|
327
|
+
children: [
|
|
328
|
+
/*#__PURE__*/ jsxs("header", {
|
|
329
|
+
children: [
|
|
330
|
+
/*#__PURE__*/ jsxs("div", {
|
|
331
|
+
children: [
|
|
332
|
+
/*#__PURE__*/ jsx("h2", {
|
|
333
|
+
id: titleId,
|
|
334
|
+
children: title
|
|
335
|
+
}),
|
|
336
|
+
description && /*#__PURE__*/ jsx("p", {
|
|
337
|
+
id: descriptionId,
|
|
338
|
+
children: description
|
|
339
|
+
})
|
|
340
|
+
]
|
|
341
|
+
}),
|
|
342
|
+
/*#__PURE__*/ jsx(icon_button_IconButton, {
|
|
343
|
+
label: "关闭",
|
|
344
|
+
disabled: closeDisabled,
|
|
345
|
+
onClick: onClose,
|
|
346
|
+
children: /*#__PURE__*/ jsx(X, {
|
|
347
|
+
size: 16
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
]
|
|
351
|
+
}),
|
|
352
|
+
/*#__PURE__*/ jsx("div", {
|
|
353
|
+
className: "ds-dialog-body",
|
|
354
|
+
children: children
|
|
355
|
+
}),
|
|
356
|
+
footer && /*#__PURE__*/ jsx("footer", {
|
|
357
|
+
children: footer
|
|
358
|
+
})
|
|
359
|
+
]
|
|
360
|
+
})
|
|
361
|
+
});
|
|
362
|
+
return "u" > typeof document && portalRootRef.current ? /*#__PURE__*/ createPortal(dialog, portalRootRef.current) : dialog;
|
|
363
|
+
}
|
|
295
364
|
const office_text_field_OfficeTextField = /*#__PURE__*/ forwardRef(function({ className = '', type = 'text', ...props }, ref) {
|
|
296
365
|
return /*#__PURE__*/ jsx("input", {
|
|
297
366
|
ref: ref,
|
|
@@ -1340,4 +1409,4 @@ function useOfficeEditorWheelZoom({ enabled = true, scopeRef, onZoomIn, onZoomOu
|
|
|
1340
1409
|
scopeRef
|
|
1341
1410
|
]);
|
|
1342
1411
|
}
|
|
1343
|
-
export { CommittedOfficeTextArea, CommittedOfficeTextField, MarkdownSourcePresenceLayer, OfficePresenceAvatar, OfficeTiptapPresenceLayer, Popover, WorkOfficeCollaborationParticipants, button_Button, icon_button_IconButton, matchesAriaKeyShortcuts, matchesOfficeEditorKeyboardShortcut, moveOfficeGridMenuFocus, moveOfficeMenuFocus, officeOverlayPortalRoot, officePresenceColor, officePresenceColorStyle, office_text_field_OfficeFileInput, office_text_field_OfficeTextArea, office_text_field_OfficeTextField, stepOfficeZoom, useDialogFocusScope, useOfficeEditorWheelZoom, useOfficePublishPresenceLocation, useOfficeRemoteParticipants };
|
|
1412
|
+
export { CommittedOfficeTextArea, CommittedOfficeTextField, Dialog, MarkdownSourcePresenceLayer, OfficePresenceAvatar, OfficeTiptapPresenceLayer, Popover, WorkOfficeCollaborationParticipants, button_Button, icon_button_IconButton, matchesAriaKeyShortcuts, matchesOfficeEditorKeyboardShortcut, moveOfficeGridMenuFocus, moveOfficeMenuFocus, officeOverlayPortalRoot, officePresenceColor, officePresenceColorStyle, office_text_field_OfficeFileInput, office_text_field_OfficeTextArea, office_text_field_OfficeTextField, stepOfficeZoom, useDialogFocusScope, useOfficeEditorWheelZoom, useOfficePublishPresenceLocation, useOfficeRemoteParticipants };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Fragment as jsx_runtime_Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
|
-
import { ArrowUpRight, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Eye, Minus, Plus
|
|
5
|
-
import { WorkOfficeCollaborationParticipants, moveOfficeMenuFocus,
|
|
4
|
+
import { ArrowUpRight, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Eye, Minus, Plus } from "lucide-react";
|
|
5
|
+
import { WorkOfficeCollaborationParticipants, moveOfficeMenuFocus, Popover, matchesAriaKeyShortcuts, officeOverlayPortalRoot } from "./0~8136.js";
|
|
6
6
|
const CONTEXT_MENU_MARGIN = 8;
|
|
7
7
|
const CONTEXT_MENU_TAB_STOP_SELECTOR = 'a[href]:not([tabindex="-1"]), button:not(:disabled):not([tabindex="-1"]), input:not(:disabled):not([type="hidden"]):not([tabindex="-1"]), select:not(:disabled):not([tabindex="-1"]), textarea:not(:disabled):not([tabindex="-1"]), [contenteditable="true"]:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])';
|
|
8
8
|
function isWorkspaceContextMenuKeyboardEvent({ key, shiftKey }) {
|
|
@@ -274,75 +274,6 @@ function contextMenuAvailableBounds(ownerDocument, view, portalRoot) {
|
|
|
274
274
|
function clampCoordinate(value, minimum, maximum) {
|
|
275
275
|
return Math.max(minimum, Math.min(value, Math.max(minimum, maximum)));
|
|
276
276
|
}
|
|
277
|
-
function Dialog({ title, description, children, footer, onClose, closeDisabled = false, className, focusKey, restoreFocusTarget }) {
|
|
278
|
-
const titleId = useId();
|
|
279
|
-
const descriptionId = useId();
|
|
280
|
-
const portalRootRef = useRef(null);
|
|
281
|
-
if (!portalRootRef.current && "u" > typeof document) portalRootRef.current = officeOverlayPortalRoot(document, restoreFocusTarget?.());
|
|
282
|
-
const focusScope = useDialogFocusScope({
|
|
283
|
-
onEscape: onClose,
|
|
284
|
-
escapeDisabled: closeDisabled,
|
|
285
|
-
restoreFocusTarget
|
|
286
|
-
});
|
|
287
|
-
useEffect(()=>{
|
|
288
|
-
if (void 0 !== focusKey) focusScope.focusInitial();
|
|
289
|
-
}, [
|
|
290
|
-
focusKey,
|
|
291
|
-
focusScope.focusInitial
|
|
292
|
-
]);
|
|
293
|
-
const dialog = /*#__PURE__*/ jsx("dialog", {
|
|
294
|
-
open: true,
|
|
295
|
-
className: "ds-dialog-backdrop",
|
|
296
|
-
role: "presentation",
|
|
297
|
-
onCancel: (event)=>{
|
|
298
|
-
event.preventDefault();
|
|
299
|
-
if (!closeDisabled) onClose();
|
|
300
|
-
},
|
|
301
|
-
children: /*#__PURE__*/ jsxs("section", {
|
|
302
|
-
ref: focusScope.scopeRef,
|
|
303
|
-
className: `ds-dialog${className ? ` ${className}` : ''}`,
|
|
304
|
-
role: "dialog",
|
|
305
|
-
"aria-modal": "true",
|
|
306
|
-
"aria-labelledby": titleId,
|
|
307
|
-
"aria-describedby": description ? descriptionId : void 0,
|
|
308
|
-
onKeyDown: focusScope.handleKeyDown,
|
|
309
|
-
children: [
|
|
310
|
-
/*#__PURE__*/ jsxs("header", {
|
|
311
|
-
children: [
|
|
312
|
-
/*#__PURE__*/ jsxs("div", {
|
|
313
|
-
children: [
|
|
314
|
-
/*#__PURE__*/ jsx("h2", {
|
|
315
|
-
id: titleId,
|
|
316
|
-
children: title
|
|
317
|
-
}),
|
|
318
|
-
description && /*#__PURE__*/ jsx("p", {
|
|
319
|
-
id: descriptionId,
|
|
320
|
-
children: description
|
|
321
|
-
})
|
|
322
|
-
]
|
|
323
|
-
}),
|
|
324
|
-
/*#__PURE__*/ jsx(icon_button_IconButton, {
|
|
325
|
-
label: "关闭",
|
|
326
|
-
disabled: closeDisabled,
|
|
327
|
-
onClick: onClose,
|
|
328
|
-
children: /*#__PURE__*/ jsx(X, {
|
|
329
|
-
size: 16
|
|
330
|
-
})
|
|
331
|
-
})
|
|
332
|
-
]
|
|
333
|
-
}),
|
|
334
|
-
/*#__PURE__*/ jsx("div", {
|
|
335
|
-
className: "ds-dialog-body",
|
|
336
|
-
children: children
|
|
337
|
-
}),
|
|
338
|
-
footer && /*#__PURE__*/ jsx("footer", {
|
|
339
|
-
children: footer
|
|
340
|
-
})
|
|
341
|
-
]
|
|
342
|
-
})
|
|
343
|
-
});
|
|
344
|
-
return "u" > typeof document && portalRootRef.current ? /*#__PURE__*/ createPortal(dialog, portalRootRef.current) : dialog;
|
|
345
|
-
}
|
|
346
277
|
function OfficeSelect({ ariaLabel, value, options, onValueChange, disabled = false, placeholder = '请选择', className = '', ariaKeyShortcuts, initialFocus = false }) {
|
|
347
278
|
const reactId = useId().replaceAll(':', '');
|
|
348
279
|
const [open, setOpen] = useState(false);
|
|
@@ -1293,4 +1224,4 @@ function WorkOfficeZoomControls({ zoom, minimum = 50, maximum = 200, step = 10,
|
|
|
1293
1224
|
]
|
|
1294
1225
|
});
|
|
1295
1226
|
}
|
|
1296
|
-
export {
|
|
1227
|
+
export { OfficeSelect, Tabs, WorkOfficePreviewBar, WorkOfficeRibbon, WorkOfficeRibbonButton, WorkOfficeRibbonGroup, WorkOfficeStatusBar, WorkOfficeZoomControls, WorkspaceContextMenu, isWorkspaceContextMenuKeyboardEvent, workspaceContextMenuPosition };
|
package/dist/0~9445.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Minus, Plus, Table2 } from "lucide-react";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
4
|
import { materializeWorkFile } from "./9356.js";
|
|
5
|
-
import { Popover } from "./0~
|
|
5
|
+
import { Popover } from "./0~8136.js";
|
|
6
6
|
async function readOfficeFileAsDataUrl(file) {
|
|
7
7
|
const ownedFile = await materializeWorkFile(file);
|
|
8
8
|
return new Promise((resolve, reject)=>{
|
|
@@ -18,8 +18,8 @@ import { TableMap, isInTable, selectedRect } from "@tiptap/pm/tables";
|
|
|
18
18
|
import { closeHistory } from "@tiptap/pm/history";
|
|
19
19
|
import { documentTableSizing, transferChangedDocumentTextStatistics, documentPageBordersVisible, documentUnderlineStyle, DOCUMENT_PAGE_BORDER_EDGES, documentTableRowOptions, normalizeDocumentColumns, clampDocumentMargin, materializeLazyDocumentEditorRoot, documentKerningThresholdPoints, selectedDocumentIndexOptions, nextDocumentTabAlignment, DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE, normalizedTabPosition, documentCitationCount, resolveDocumentPageBorders, documentLazyHtmlChunkFragment, DocumentFontFamily, normalizeDocumentHiddenText, documentLegacyTextEffectsConflict, DOCUMENT_CHUNK_VISIBLE_IDS_META, selectedDocumentIndexDraft, MAX_DOCUMENT_IMAGE_RELATIVE_HEIGHT, retainAnchoredDocumentComments, DocumentTableRowIdentity, work_document_page_margins_twipsToMillimeters, DEFAULT_DOCUMENT_TABLE_CELL_FORMAT, resolveWorkDocumentEditorInput, documentCharacterPositionPoints, documentBulletListStyle, collectDocumentTextLayoutParagraphs, readOfficeDocumentCollaboration as readWorkOfficeDocumentCollaboration, normalizeDocumentCharacterScalePercent, DOCUMENT_PARAGRAPH_FORMAT_ATTRIBUTES, documentOrderedListState, normalizeDocumentPageChrome, documentTextStatistics, documentPageMarginsForLayout, invalidateDocumentLazyHtmlProjection, documentTableCellFormat, normalizeDocumentPageGeometry, normalizeDocumentImageLayer, documentImageProperties, isValidDocumentCitationTag, sanitizeDocumentPageChromeHtml, pageTwipsToMillimeters, normalizeDocumentPageBorders, importWorkDocumentFile, DocumentParagraphFormatting, documentModelHasTrustedInitialIntegrityFeatures, normalizeDocumentHtml, documentInitialSectionLayout, activeDocumentSection, documentParagraphDirection, documentParagraphPagination, isDocumentCharacterFormatMark, documentModelUsesWindowing, documentPageChromeLegacyFields, documentChunkMountedIds, collectDocumentCommentAnchors, documentTextCaseKeyboardShortcuts, DocumentTextStyle, canChangeDocumentIndent, documentCharacterSpacingPoints, documentKerningIsEffective, DocumentSuperscript, documentSectionById, activeDocumentBookmark, DocumentEquation, documentLazyHtmlProjectionFingerprint, documentLazyHtmlProjection, normalizeDocumentParagraphIndent, updateDocumentGutterPosition, DocumentStrike, mountWorkLiveDocumentCapture, registerDocumentPageSurfaceGeometry, documentHiddenTextKeyboardShortcut, documentHasIndex, syncDocumentContentFromHtml, documentLegacyTextEffectsCss, editorDocumentCaptionTargets, resolveDocumentPageChrome, documentImageLayoutOptions, MAX_DOCUMENT_NUMBERING_START, resolveDocumentPageSize, editorDocumentBookmarkReferenceTargets, serializeWorkDocumentNode, DOCUMENT_CHUNK_PAGINATION_META, documentModelForContent, createOfficeDocumentCollaborationBinding as createWorkOfficeDocumentCollaborationBinding, documentLegacyTextEffectsFromTextStyleAttributes, normalizeDocumentEmphasisMark, selectedDocumentChunkId, documentCommentViews, documentParagraphSpacing, normalizeDocumentTabStops, patchDocumentLazyHtmlProjection, normalizeDocumentTextCase, documentUnderlineColor, documentBookmarkNameExists, collectDocumentChanges, DocumentImage, selectedDocumentTableOfContentsOptions, resolveDocumentPageMargins, updateDocumentPageMarginMode, serializeDocumentParagraphFormatting, canSetDocumentTableRowRepeatHeader, normalizeDocumentCharacterSpacingTwips, DocumentCharacterFormatting, millimetersToPixels as work_document_layout_millimetersToPixels, DOCUMENT_PARAGRAPH_CHANGE_ATTRIBUTES, serializeDocumentCharacterFormatting, DOCUMENT_CHUNK_HYDRATION_META, documentTabLeaderLabel, createSchemaDerivedWorkDocumentModel, updateDocumentCustomPageMillimeters, DocumentUnderline, documentCommentDraftRange, documentParagraphTabStops, documentTableHorizontalAlignment, documentUnderlineKeyboardShortcuts, documentParagraphIndent, DocumentScriptFontFormatting, canInsertDocumentComment, resolveAllDocumentChanges, documentHasTableOfContents, documentTextLayoutBatches, normalizeDocumentImageWrapSide, updateDocumentPageOrientation, documentCitationStyleDetails, validateDocumentBookmarkName, setCustomDocumentColumns, updateDocumentPageMarginMillimeters, documentCitationStyle, normalizeDocumentCharacterPositionHalfPoints, documentTransactionsOnlyHydrateChunks, normalizeDocumentKerningThresholdHalfPoints, forgetSourceBlob as forgetWorkSourceBlob, DEFAULT_DOCUMENT_TABLE_CELL_MARGINS, DOCUMENT_TABLE_STYLE_OPTIONS, updateDocumentColumnWidth, updateDocumentPaperSizePreset, documentPageMetrics, documentStrikeStyle, DocumentHighlight, createDocumentBibliography, DocumentSubscript, selectedDocumentIndexEntry, updateDocumentPageChromeVariant, updateDocumentMirrorMargins, activeDocumentTableStyle, measureDocumentLayoutBlocksIncrementally, documentWordLineHeightFactor, normalizeDocumentLegacyTextEffect, createWorkDocumentExtensions } from "./4174.js";
|
|
20
20
|
import { useOfficeTaskPaneEscape, normalizeOfficeFontFamily, OfficeCheckbox, useOfficeDialog, useOfficeTaskPaneModal, CommittedOfficeNumberField, officeFontFamilyLabel, officeFontFamilies, CollectionState, createOfficeKernelClient, OfficeNumberField } from "./0~7048.js";
|
|
21
|
-
import { moveOfficeMenuFocus, office_text_field_OfficeTextArea, Popover, officeOverlayPortalRoot, button_Button, useOfficePublishPresenceLocation, OfficeTiptapPresenceLayer, icon_button_IconButton, office_text_field_OfficeTextField, useDialogFocusScope, useOfficeEditorWheelZoom, office_text_field_OfficeFileInput } from "./0~
|
|
22
|
-
import { WorkOfficePreviewBar, isWorkspaceContextMenuKeyboardEvent, Tabs, WorkOfficeZoomControls, WorkOfficeStatusBar,
|
|
21
|
+
import { moveOfficeMenuFocus, office_text_field_OfficeTextArea, Popover, officeOverlayPortalRoot, Dialog, button_Button, useOfficePublishPresenceLocation, OfficeTiptapPresenceLayer, icon_button_IconButton, office_text_field_OfficeTextField, useDialogFocusScope, useOfficeEditorWheelZoom, office_text_field_OfficeFileInput } from "./0~8136.js";
|
|
22
|
+
import { WorkOfficePreviewBar, isWorkspaceContextMenuKeyboardEvent, Tabs, WorkOfficeZoomControls, WorkOfficeStatusBar, WorkOfficeRibbon, workspaceContextMenuPosition, OfficeSelect, WorkspaceContextMenu, WorkOfficeRibbonGroup, WorkOfficeRibbonButton } from "./0~9073.js";
|
|
23
23
|
import { documentFontNameFromCssFamily, normalizeDocumentRunBorder, parseDocumentScriptFonts, parseDocumentRunShading, DOCUMENT_RUN_BORDER_STYLES, serializeDocumentRunBorder, documentScriptFontDirectFamily, serializeDocumentRunShading, patchDocumentScriptFonts, serializeDocumentScriptFonts, documentRunBorderIsVisible, cssDocumentFontFamily, isDocumentParagraphArtBorderStyle, visibleWorkDocumentOutlineItems, DEFAULT_DOCUMENT_TABLE_OF_CONTENTS_OPTIONS, createOfficeId as createWorkId, DEFAULT_DOCUMENT_INDEX_OPTIONS, normalizeDocumentLanguageTag, currentWorkDocumentOutlineItem, normalizeDocumentNoProof, parseDocumentRunBorder, DocumentParagraphIdentity, parseDocumentProofingLanguages, collectWorkDocumentOutline, documentScriptFontSlotFromHint, documentBorderPresentation, documentScriptFontFamilyForRendering, normalizeDocumentRunShading, normalizeDocumentScriptFontSlot, documentScriptFontSegments, patchDocumentProofingLanguages, serializeDocumentProofingLanguages, documentRunShadingDomAttributes } from "./4121.js";
|
|
24
24
|
import { normalizeDocumentHref, DOCUMENT_LINK_VALIDATION_MESSAGE } from "./0~work-document-links.js";
|
|
25
25
|
import { OfficeColorPicker, isOfficeShortcutBlocked } from "./0~3635.js";
|
|
@@ -11,8 +11,8 @@ import { Markdown } from "@tiptap/markdown";
|
|
|
11
11
|
import starter_kit from "@tiptap/starter-kit";
|
|
12
12
|
import { Bold, Code2, Columns2, Image, Italic, Link2, List, ListChecks, ListOrdered, Minus, PencilLine, Quote, Redo2, Strikethrough, Table2, Undo2, Unlink } from "lucide-react";
|
|
13
13
|
import { mergeOfficeTiptapExtensions, documentPlainTextAsHtml, customSelectionMenuItems } from "./0~5050.js";
|
|
14
|
-
import { OfficeSelect, isWorkspaceContextMenuKeyboardEvent, WorkOfficeZoomControls, WorkOfficeStatusBar,
|
|
15
|
-
import { button_Button, OfficeTiptapPresenceLayer, useOfficePublishPresenceLocation,
|
|
14
|
+
import { OfficeSelect, isWorkspaceContextMenuKeyboardEvent, WorkOfficeZoomControls, WorkOfficeStatusBar, WorkOfficeRibbon, workspaceContextMenuPosition, WorkspaceContextMenu, WorkOfficeRibbonGroup, WorkOfficeRibbonButton, WorkOfficePreviewBar } from "./0~9073.js";
|
|
15
|
+
import { stepOfficeZoom, MarkdownSourcePresenceLayer, button_Button, OfficeTiptapPresenceLayer, useOfficePublishPresenceLocation, Dialog, office_text_field_OfficeTextField, useOfficeEditorWheelZoom } from "./0~8136.js";
|
|
16
16
|
import { normalizeDocumentHref, DOCUMENT_LINK_VALIDATION_MESSAGE } from "./0~work-document-links.js";
|
|
17
17
|
import { readOfficeMarkdownCollaboration as readWorkOfficeMarkdownCollaboration, createOfficeMarkdownCollaborationBinding as createWorkOfficeMarkdownCollaborationBinding } from "./2591.js";
|
|
18
18
|
import { useOfficeEditorInitialFocus, useOfficeCollaborationLocationNavigator, WorkEditorLoadingState } from "./432.js";
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { PDFDocument, PDFName, degrees } from "pdf-lib";
|
|
2
|
+
import { validatedPdfPageSize, PdfPageOrganizationError, PDF_PAGE_ORGANIZATION_MAX_SOURCE_BYTES, validatedPdfInsertionIndex, validatedPdfSplitBoundaries, PDF_PAGE_ORGANIZATION_MAX_IMPORT_BYTES, validatedPdfPageOrder, validatedPdfPageIndexes } from "./0~pdf-viewer.js";
|
|
3
|
+
const RISKY_STRUCTURE_KEYS = [
|
|
4
|
+
'AcroForm',
|
|
5
|
+
'Outlines',
|
|
6
|
+
'StructTreeRoot'
|
|
7
|
+
];
|
|
8
|
+
const COPY_DIAGNOSTIC = {
|
|
9
|
+
code: 'pdf.pages.catalog-not-copied',
|
|
10
|
+
message: "Page-only exports do not copy document-level outlines, forms, tags, attachments, scripts, or signatures."
|
|
11
|
+
};
|
|
12
|
+
async function applyPdfPageOrganizationJob(job) {
|
|
13
|
+
const source = await loadBoundedPdf(job.source, PDF_PAGE_ORGANIZATION_MAX_SOURCE_BYTES, 'source');
|
|
14
|
+
assertPageCount(source.getPageCount());
|
|
15
|
+
if ('export' === job.kind) return exportPages(source, job.operation);
|
|
16
|
+
assertUnsigned(source);
|
|
17
|
+
return mutatePages(source, job.mutation, job.importSource);
|
|
18
|
+
}
|
|
19
|
+
async function mutatePages(source, mutation, importSource) {
|
|
20
|
+
const pageCount = source.getPageCount();
|
|
21
|
+
if ('rotate' === mutation.kind) {
|
|
22
|
+
const indexes = validatedPdfPageIndexes(mutation.pageIndexes, pageCount);
|
|
23
|
+
for (const pageIndex of indexes){
|
|
24
|
+
const page = source.getPage(pageIndex);
|
|
25
|
+
const current = page.getRotation().angle;
|
|
26
|
+
if (!Number.isSafeInteger(current) || current % 90 !== 0) throw new PdfPageOrganizationError('pdf.pages.unsupported-rotation', `Page ${pageIndex + 1} has unsupported rotation ${String(current)}.`);
|
|
27
|
+
page.setRotation(degrees(normalizeDegrees(current + mutation.degrees)));
|
|
28
|
+
}
|
|
29
|
+
return mutationResult(source);
|
|
30
|
+
}
|
|
31
|
+
if ('insert-blank' === mutation.kind) {
|
|
32
|
+
const index = validatedPdfInsertionIndex(mutation.index, pageCount);
|
|
33
|
+
const size = validatedPdfPageSize(mutation.size);
|
|
34
|
+
assertPageCount(pageCount + 1);
|
|
35
|
+
source.insertPage(index, [
|
|
36
|
+
size.width,
|
|
37
|
+
size.height
|
|
38
|
+
]);
|
|
39
|
+
return mutationResult(source);
|
|
40
|
+
}
|
|
41
|
+
if ('delete' === mutation.kind) {
|
|
42
|
+
assertNoRiskyStructure(source);
|
|
43
|
+
const indexes = validatedPdfPageIndexes(mutation.pageIndexes, pageCount);
|
|
44
|
+
if (indexes.length >= pageCount) throw new PdfPageOrganizationError('pdf.pages.empty-document', 'A PDF must retain at least one page.');
|
|
45
|
+
for (const pageIndex of [
|
|
46
|
+
...indexes
|
|
47
|
+
].sort((left, right)=>right - left))source.removePage(pageIndex);
|
|
48
|
+
return mutationResult(source);
|
|
49
|
+
}
|
|
50
|
+
if ('reorder' === mutation.kind) {
|
|
51
|
+
assertNoRiskyStructure(source);
|
|
52
|
+
const pageOrder = validatedPdfPageOrder(mutation.pageOrder, pageCount);
|
|
53
|
+
const reordered = await PDFDocument.create({
|
|
54
|
+
updateMetadata: false
|
|
55
|
+
});
|
|
56
|
+
copyMetadata(source, reordered);
|
|
57
|
+
const pages = await reordered.copyPages(source, pageOrder);
|
|
58
|
+
for (const page of pages)reordered.addPage(page);
|
|
59
|
+
return mutationResult(reordered);
|
|
60
|
+
}
|
|
61
|
+
const index = validatedPdfInsertionIndex(mutation.index, pageCount);
|
|
62
|
+
if (!importSource) throw new PdfPageOrganizationError('pdf.pages.missing-import', 'Choose a PDF to merge before applying this operation.');
|
|
63
|
+
assertNoRiskyStructure(source);
|
|
64
|
+
const imported = await loadBoundedPdf(importSource, PDF_PAGE_ORGANIZATION_MAX_IMPORT_BYTES, 'import');
|
|
65
|
+
assertSafeImport(imported);
|
|
66
|
+
assertPageCount(pageCount + imported.getPageCount());
|
|
67
|
+
const copied = await source.copyPages(imported, imported.getPageIndices());
|
|
68
|
+
for(let offset = 0; offset < copied.length; offset += 1){
|
|
69
|
+
const page = copied[offset];
|
|
70
|
+
if (page) source.insertPage(index + offset, page);
|
|
71
|
+
}
|
|
72
|
+
return mutationResult(source);
|
|
73
|
+
}
|
|
74
|
+
async function exportPages(source, operation) {
|
|
75
|
+
const pageCount = source.getPageCount();
|
|
76
|
+
if ('extract' === operation.kind) {
|
|
77
|
+
const indexes = validatedPdfPageIndexes(operation.pageIndexes, pageCount, {
|
|
78
|
+
preserveOrder: true
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
diagnostics: sourceHasRiskyStructure(source) ? [
|
|
82
|
+
COPY_DIAGNOSTIC
|
|
83
|
+
] : [],
|
|
84
|
+
files: [
|
|
85
|
+
await copyPageRange(source, indexes)
|
|
86
|
+
],
|
|
87
|
+
kind: 'exported'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const boundaries = validatedPdfSplitBoundaries(operation.splitAfterPageIndexes, pageCount);
|
|
91
|
+
const ranges = [];
|
|
92
|
+
let start = 0;
|
|
93
|
+
for (const boundary of boundaries){
|
|
94
|
+
ranges.push(range(start, boundary + 1));
|
|
95
|
+
start = boundary + 1;
|
|
96
|
+
}
|
|
97
|
+
ranges.push(range(start, pageCount));
|
|
98
|
+
return {
|
|
99
|
+
diagnostics: sourceHasRiskyStructure(source) ? [
|
|
100
|
+
COPY_DIAGNOSTIC
|
|
101
|
+
] : [],
|
|
102
|
+
files: await Promise.all(ranges.map((indexes)=>copyPageRange(source, indexes))),
|
|
103
|
+
kind: 'exported'
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function copyPageRange(source, indexes) {
|
|
107
|
+
const output = await PDFDocument.create({
|
|
108
|
+
updateMetadata: false
|
|
109
|
+
});
|
|
110
|
+
copyMetadata(source, output);
|
|
111
|
+
const pages = await output.copyPages(source, indexes);
|
|
112
|
+
for (const page of pages)output.addPage(page);
|
|
113
|
+
return {
|
|
114
|
+
pageCount: indexes.length,
|
|
115
|
+
source: await output.save()
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function mutationResult(source) {
|
|
119
|
+
return {
|
|
120
|
+
diagnostics: [],
|
|
121
|
+
kind: 'mutated',
|
|
122
|
+
pageCount: source.getPageCount(),
|
|
123
|
+
source: await source.save()
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function loadBoundedPdf(source, byteLimit, label) {
|
|
127
|
+
const byteLength = source.byteLength;
|
|
128
|
+
if (byteLength < 5 || byteLength > byteLimit) throw new PdfPageOrganizationError('pdf.pages.source-limit', `The PDF ${label} must be between 5 bytes and ${byteLimit} bytes.`);
|
|
129
|
+
try {
|
|
130
|
+
const pdf = await PDFDocument.load(source, {
|
|
131
|
+
ignoreEncryption: false,
|
|
132
|
+
throwOnInvalidObject: true,
|
|
133
|
+
updateMetadata: false
|
|
134
|
+
});
|
|
135
|
+
if (pdf.isEncrypted) throw new PdfPageOrganizationError('pdf.pages.encrypted-source', `The encrypted PDF ${label} cannot be reorganized without an authenticated decryption provider.`);
|
|
136
|
+
return pdf;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error instanceof PdfPageOrganizationError) throw error;
|
|
139
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
140
|
+
if (/encrypt/i.test(message)) throw new PdfPageOrganizationError('pdf.pages.encrypted-source', `The encrypted PDF ${label} cannot be reorganized without an authenticated decryption provider.`);
|
|
141
|
+
throw new PdfPageOrganizationError('pdf.pages.invalid-source', `The PDF ${label} is malformed or unsupported: ${message.slice(0, 240)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function assertPageCount(pageCount) {
|
|
145
|
+
if (!Number.isSafeInteger(pageCount) || pageCount < 1 || pageCount > 4096) throw new PdfPageOrganizationError('pdf.pages.page-limit', "PDF page organization supports 1 to 4096 pages per result.");
|
|
146
|
+
}
|
|
147
|
+
function assertUnsigned(source) {
|
|
148
|
+
if (source.catalog.has(PDFName.of('Perms'))) throw new PdfPageOrganizationError('pdf.pages.signed-source', 'This PDF declares a signature permission dictionary. Rewriting it would invalidate its trust evidence.');
|
|
149
|
+
}
|
|
150
|
+
function assertNoRiskyStructure(source) {
|
|
151
|
+
if (!sourceHasRiskyStructure(source)) return;
|
|
152
|
+
throw new PdfPageOrganizationError('pdf.pages.risky-structure', 'Delete, reorder, and merge are disabled for PDFs with forms, outlines, or tagged structure because page references cannot be rewritten safely.');
|
|
153
|
+
}
|
|
154
|
+
function assertSafeImport(source) {
|
|
155
|
+
if (!sourceHasRiskyStructure(source) && !source.catalog.has(PDFName.of('Perms'))) return;
|
|
156
|
+
throw new PdfPageOrganizationError('pdf.pages.risky-import', 'The PDF selected for merge contains forms, outlines, tags, or signature permissions that cannot be copied safely.');
|
|
157
|
+
}
|
|
158
|
+
function sourceHasRiskyStructure(source) {
|
|
159
|
+
return RISKY_STRUCTURE_KEYS.some((key)=>source.catalog.has(PDFName.of(key)));
|
|
160
|
+
}
|
|
161
|
+
function copyMetadata(source, target) {
|
|
162
|
+
setIfDefined(source.getTitle(), (value)=>target.setTitle(value));
|
|
163
|
+
setIfDefined(source.getAuthor(), (value)=>target.setAuthor(value));
|
|
164
|
+
setIfDefined(source.getSubject(), (value)=>target.setSubject(value));
|
|
165
|
+
setIfDefined(source.getCreator(), (value)=>target.setCreator(value));
|
|
166
|
+
setIfDefined(source.getProducer(), (value)=>target.setProducer(value));
|
|
167
|
+
setIfDefined(source.getCreationDate(), (value)=>target.setCreationDate(value));
|
|
168
|
+
setIfDefined(source.getModificationDate(), (value)=>target.setModificationDate(value));
|
|
169
|
+
const keywords = source.getKeywords();
|
|
170
|
+
if (keywords) target.setKeywords([
|
|
171
|
+
keywords
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
function setIfDefined(value, setter) {
|
|
175
|
+
if (void 0 !== value) setter(value);
|
|
176
|
+
}
|
|
177
|
+
function normalizeDegrees(value) {
|
|
178
|
+
const normalized = (value % 360 + 360) % 360;
|
|
179
|
+
if (0 === normalized || 90 === normalized || 180 === normalized || 270 === normalized) return normalized;
|
|
180
|
+
throw new PdfPageOrganizationError('pdf.pages.unsupported-rotation', `PDF page rotation ${value} is not a multiple of 90 degrees.`);
|
|
181
|
+
}
|
|
182
|
+
function range(start, end) {
|
|
183
|
+
return Array.from({
|
|
184
|
+
length: Math.max(0, end - start)
|
|
185
|
+
}, (_, index)=>start + index);
|
|
186
|
+
}
|
|
187
|
+
export { applyPdfPageOrganizationJob };
|