@a3s-lab/office 0.33.0 → 0.34.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 CHANGED
@@ -56,12 +56,13 @@ workflows, and a separate Rust automation plane.
56
56
 
57
57
  ## Latest on `main`
58
58
 
59
- The `0.33.0` release plus the current `main` branch expose these capabilities
59
+ The `0.34.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
 
63
63
  | Editor | Latest capability | Public entry |
64
64
  | --- | --- | --- |
65
+ | Presentation | Author, reorder, preview, collaborate on, and play ordered appear, fade, fly-in, and zoom entrance animations with on-click, with-previous, and after-previous triggers; the bounded subset round-trips through native PPTX timing trees | Playground **Latest capabilities → 入场动画** · [Presentation reference](docs/latest/en/components/presentation.mdx#entrance-animations) |
65
66
  | Writer | Compare the current document with an imported DOCX, HTML, or TXT version and generate deterministic insertion, deletion, character-formatting, and paragraph-formatting revisions; combine a reviewed copy only after exact baseline verification | Playground **Latest capabilities → 文档比较** · [Document reference](docs/latest/en/components/document.mdx#document-compare-and-combine) |
66
67
  | Writer | Insert, customize, navigate, and explicitly refresh a typed Table of Contents with heading and native outline levels 1–9, hyperlinks, page numbers, alignment, four leader styles, and native DOCX `TOC` round trips | Playground **Latest capabilities → 可更新目录** · [Document reference](docs/latest/en/components/document.mdx#native-table-of-contents) |
67
68
  | Writer | Mark primary and secondary index terms, cross-references, and bold/italic page numbers; insert, navigate, customize, and refresh a bounded native index with editable DOCX `XE` and `INDEX` round trips | Playground **Latest capabilities → 原生索引** · [Document reference](docs/latest/en/components/document.mdx#native-document-index) |
@@ -70,7 +71,7 @@ detail in the documentation:
70
71
  | 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
72
  | 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) |
72
73
 
73
- All seven entries are reachable from the first Playground viewport. The six
74
+ All eight entries are reachable from the first Playground viewport. The seven
74
75
  content templates also remain under **新建**; **组织 PDF 页面** opens the normal
75
76
  PDF file workflow because page organization must operate on host-provided source
76
77
  bytes. The highlighted strip keeps each capability discoverable without a deep
@@ -163,7 +164,7 @@ baseline rather than one specific release.
163
164
  | Text, shapes, tables, charts, and images | **Partial** — typed editable objects with native import/export paths | Broader shapes, connectors, effects, SmartArt, and embedded chart editing |
164
165
  | Masters and layouts | **Partial** — import/export inheritance with editable common paths | Full visual master, layout, and placeholder authoring |
165
166
  | Transitions and timings | **Partial** — fade, push, wipe, split, cut, and click/automatic advance | Broader transition catalog and timing controls |
166
- | Animations and media | **Gap** — no production animation timeline, trigger model, audio, video, or recording path | Object animation, motion paths, media editing, and recording |
167
+ | Animations and media | **Partial** — the Work scene model authors, validates, copies, collaborates on, previews, and plays ordered appear/fade/fly-in/zoom entrance cues with click/with-previous/after-previous triggers and bounded timing; this supported entrance subset round-trips through native PPTX timing trees | Exit/emphasis effects, motion paths, audio, video, recording, and broader PPTX preservation |
167
168
  | Slideshow and presenter workflows | **Supported** — current/beginning start, keyboard playback, notes, timer, and responsive presenter view | Rehearsal, recording, ink/laser, and richer multi-display controls |
168
169
  | Review and collaboration | **Partial** — comments, shared presence, remote object locations, and host transport | Threads, assignments, mentions, and integrated cloud review |
169
170
  | PPTX, print, PDF, and video | **Partial** — PPTX round trip plus slide/notes/handout PDF models; no video export | Broader print controls, vector fidelity, media preservation, and video export |
@@ -1622,6 +1623,7 @@ without hard-coded return URLs.
1622
1623
 
1623
1624
  - [Live Playground](https://a3s-lab.github.io/Office/)
1624
1625
  - [Documentation center](https://a3s-lab.github.io/Office/docs/)
1626
+ - [A3S Office 0.34.0 documentation](https://a3s-lab.github.io/Office/docs/0.34.0/)
1625
1627
  - [A3S Office 0.33.0 documentation](https://a3s-lab.github.io/Office/docs/0.33.0/)
1626
1628
  - [A3S Office 0.32.0 documentation](https://a3s-lab.github.io/Office/docs/0.32.0/)
1627
1629
  - [A3S Office 0.31.0 documentation](https://a3s-lab.github.io/Office/docs/0.31.0/)
package/dist/0~5809.js ADDED
@@ -0,0 +1,123 @@
1
+ import { createOfficeId as createWorkId } from "./4121.js";
2
+ function createWorkSlideAnimation(elementId, effect, previous) {
3
+ return normalizeWorkSlideAnimation({
4
+ id: previous?.id ?? createWorkId('slide-animation'),
5
+ elementId,
6
+ effect,
7
+ trigger: previous?.trigger ?? 'on-click',
8
+ durationMs: previous?.durationMs ?? 500,
9
+ delayMs: previous?.delayMs ?? 0,
10
+ direction: 'fly-in' === effect ? previous?.direction ?? 'left' : void 0
11
+ });
12
+ }
13
+ function normalizeWorkSlideAnimation(animation) {
14
+ return {
15
+ ...animation,
16
+ durationMs: boundedInteger(animation.durationMs, 100, 60000),
17
+ delayMs: boundedInteger(animation.delayMs, 0, 60000),
18
+ direction: 'fly-in' === animation.effect ? normalizeWorkSlideAnimationDirection(animation.direction) : void 0
19
+ };
20
+ }
21
+ function workSlideAnimationForElement(slide, elementId) {
22
+ if (!elementId) return;
23
+ return slide.animations?.find((animation)=>animation.elementId === elementId);
24
+ }
25
+ function workSlideAnimationIndex(slide, animationId) {
26
+ if (!animationId) return -1;
27
+ return slide.animations?.findIndex((animation)=>animation.id === animationId) ?? -1;
28
+ }
29
+ function workSlideAnimationCues(animations) {
30
+ const cues = [];
31
+ let current;
32
+ for (const raw of animations ?? []){
33
+ const animation = normalizeWorkSlideAnimation(raw);
34
+ if (!current || 'on-click' === animation.trigger) {
35
+ current = {
36
+ automatic: 'on-click' !== animation.trigger,
37
+ items: [],
38
+ totalDurationMs: 0
39
+ };
40
+ cues.push(current);
41
+ }
42
+ const previous = current.items.at(-1);
43
+ const baseStart = 'after-previous' === animation.trigger ? previous?.endOffsetMs ?? 0 : 'with-previous' === animation.trigger ? previous?.startOffsetMs ?? 0 : 0;
44
+ const startOffsetMs = baseStart + animation.delayMs;
45
+ const endOffsetMs = startOffsetMs + animation.durationMs;
46
+ current.items.push({
47
+ animation,
48
+ startOffsetMs,
49
+ endOffsetMs
50
+ });
51
+ current.totalDurationMs = Math.max(current.totalDurationMs, endOffsetMs);
52
+ }
53
+ return cues;
54
+ }
55
+ function initialWorkSlideAnimationCueIndex(cues) {
56
+ return cues[0]?.automatic ? 0 : -1;
57
+ }
58
+ function removeWorkSlideAnimationsForElements(slide, elementIds) {
59
+ const animations = slide.animations?.filter((animation)=>!elementIds.has(animation.elementId));
60
+ if (animations?.length === slide.animations?.length) return slide;
61
+ return {
62
+ ...slide,
63
+ animations: animations?.length ? animations : void 0
64
+ };
65
+ }
66
+ function remapWorkSlideAnimations(animations, elementIds) {
67
+ const remapped = (animations ?? []).flatMap((animation)=>{
68
+ const elementId = elementIds.get(animation.elementId);
69
+ return elementId ? [
70
+ {
71
+ ...animation,
72
+ id: createWorkId('slide-animation'),
73
+ elementId
74
+ }
75
+ ] : [];
76
+ });
77
+ return remapped.length ? remapped : void 0;
78
+ }
79
+ function normalizeWorkSlideAnimationDirection(direction) {
80
+ return 'right' === direction || 'up' === direction || 'down' === direction ? direction : 'left';
81
+ }
82
+ function boundedInteger(value, minimum, maximum) {
83
+ const finite = Number.isFinite(value) ? Math.round(value) : minimum;
84
+ return Math.min(maximum, Math.max(minimum, finite));
85
+ }
86
+ const CARDINAL_DIRECTIONS = new Set([
87
+ 'left',
88
+ 'right',
89
+ 'up',
90
+ 'down'
91
+ ]);
92
+ const SPLIT_DIRECTIONS = new Set([
93
+ 'in',
94
+ 'out'
95
+ ]);
96
+ function createWorkSlideTransition(type, previous) {
97
+ const common = {
98
+ type,
99
+ speed: previous?.speed ?? 'medium',
100
+ advanceOnClick: previous?.advanceOnClick ?? true,
101
+ advanceAfterMs: previous?.advanceAfterMs
102
+ };
103
+ if ('push' === type || 'wipe' === type) return {
104
+ ...common,
105
+ direction: previous?.direction && CARDINAL_DIRECTIONS.has(previous.direction) ? previous.direction : 'left'
106
+ };
107
+ if ('split' === type) return {
108
+ ...common,
109
+ direction: previous?.direction && SPLIT_DIRECTIONS.has(previous.direction) ? previous.direction : 'out',
110
+ orientation: previous?.orientation ?? 'horizontal'
111
+ };
112
+ return common;
113
+ }
114
+ function slideTransitionDurationMilliseconds(transition) {
115
+ if (transition?.speed === 'fast') return 500;
116
+ if (transition?.speed === 'slow') return 2000;
117
+ return 1000;
118
+ }
119
+ function workSlideTransitionsEqual(left, right) {
120
+ if (!left || !right) return left === right;
121
+ return left.type === right.type && left.speed === right.speed && left.direction === right.direction && left.orientation === right.orientation && left.advanceOnClick === right.advanceOnClick && left.advanceAfterMs === right.advanceAfterMs;
122
+ }
123
+ export { createWorkSlideAnimation, createWorkSlideTransition, initialWorkSlideAnimationCueIndex, normalizeWorkSlideAnimation, remapWorkSlideAnimations, removeWorkSlideAnimationsForElements, slideTransitionDurationMilliseconds, workSlideAnimationCues, workSlideAnimationForElement, workSlideAnimationIndex, workSlideTransitionsEqual };
@@ -1,9 +1,9 @@
1
1
  import jszip from "jszip";
2
- import { descendants, directChildren, directChild, xmlNamespacePrefix, attribute, firstDescendant, parseXml } from "./4121.js";
2
+ import { directChildren, xmlNamespacePrefix, parseXml, firstDescendant, descendants, directChild, attribute, createOfficeId as createWorkId } from "./4121.js";
3
3
  import { normalizePresentationChartDataLabelPosition, normalizePresentationChartDataLabels, normalizePresentationChartSeriesStyle } from "./0~work-presentation-charts.js";
4
4
  import { parseXlsxChartSeriesStyle, xlsxChartSeriesMarkerXml, parseXlsxTrendline, isSupportedXlsxChartLegend, isSupportedXlsxChartPlotLayout, isSupportedXlsxChartSeriesFormatting, parseXlsxChartPlotLayout, parseXlsxChartLegend, xlsxChartSeriesShapePropertiesXml, parseXlsxErrorBars } from "./4166.js";
5
5
  import { workSpreadsheetChartSupportsErrorBars, workSpreadsheetChartSupportsGrouping, normalizeWorkSpreadsheetChartSmoothLines, workSpreadsheetChartSupportsSeriesAnalysis, normalizeWorkSpreadsheetChartSeriesStyle, normalizeWorkSpreadsheetErrorBars, normalizeWorkSpreadsheetChartOverlap, workSpreadsheetChartUsesNumericXAxis, workSpreadsheetChartSupportsBarSpacing, normalizeWorkSpreadsheetChartLegendOverlay, normalizeWorkSpreadsheetChartGapWidth, normalizeWorkSpreadsheetChartGrouping, workSpreadsheetChartSupportsTrendlines, workSpreadsheetChartSupportsSmoothLines, normalizeWorkSpreadsheetTrendline } from "./8715.js";
6
- import { createWorkSlideTransition } from "./0~work-presentation-transition.js";
6
+ import { normalizeWorkSlideAnimation, createWorkSlideTransition } from "./0~5809.js";
7
7
  const PRESENTATION_NAMESPACE = 'http://schemas.openxmlformats.org/presentationml/2006/main';
8
8
  const DRAWING_NAMESPACE = 'http://schemas.openxmlformats.org/drawingml/2006/main';
9
9
  const OFFICE_RELATIONSHIPS_NAMESPACE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
@@ -904,6 +904,377 @@ function finiteNumber(value) {
904
904
  function work_pptx_chart_series_analysis_chartPartNumber(path) {
905
905
  return Number(/chart(\d+)\.xml$/.exec(path)?.[1] ?? Number.MAX_SAFE_INTEGER);
906
906
  }
907
+ const PRESENTATIONML_NAMESPACE = 'http://schemas.openxmlformats.org/presentationml/2006/main';
908
+ function readPptxAnimations(document, elementIdByPptxShapeId) {
909
+ const timing = directChild(document.documentElement, 'timing');
910
+ if (!timing) return {
911
+ animations: [],
912
+ diagnostics: []
913
+ };
914
+ const diagnostics = [];
915
+ if (elementNamespace(timing) !== PRESENTATIONML_NAMESPACE) return {
916
+ animations: [],
917
+ diagnostics: [
918
+ {
919
+ code: 'pptx.animation.namespace',
920
+ message: 'A namespace-spoofed animation timing tree was ignored instead of being treated as trusted PresentationML.'
921
+ }
922
+ ]
923
+ };
924
+ const animations = [];
925
+ const animatedElements = new Set();
926
+ const effectNodes = pptxDescendants(timing, 'cTn').filter((node)=>[
927
+ 'clickEffect',
928
+ 'withEffect',
929
+ 'afterEffect'
930
+ ].includes(attribute(node, 'nodeType') ?? ''));
931
+ if (effectNodes.length > 256) diagnostics.push({
932
+ code: 'pptx.animation.limit',
933
+ message: "Only the first 256 object animation timing records on a slide are inspected and imported."
934
+ });
935
+ for (const node of effectNodes.slice(0, 256)){
936
+ if ('entr' !== attribute(node, 'presetClass')) {
937
+ diagnostics.push({
938
+ code: 'pptx.animation.class',
939
+ message: 'An emphasis, exit, motion-path, or malformed animation remains in the source PPTX only.'
940
+ });
941
+ continue;
942
+ }
943
+ const targets = Array.from(new Set(pptxDescendants(node, 'spTgt').map((target)=>attribute(target, 'spid')?.trim()).filter((target)=>Boolean(target))));
944
+ if (1 !== targets.length) {
945
+ diagnostics.push({
946
+ code: 'pptx.animation.target',
947
+ message: 'An entrance animation with a missing or ambiguous object target was ignored.'
948
+ });
949
+ continue;
950
+ }
951
+ const elementId = elementIdByPptxShapeId.get(targets[0]);
952
+ if (!elementId) {
953
+ diagnostics.push({
954
+ code: 'pptx.animation.target',
955
+ message: 'An entrance animation points to an unavailable slide object and was ignored.'
956
+ });
957
+ continue;
958
+ }
959
+ if (animatedElements.has(elementId)) {
960
+ diagnostics.push({
961
+ code: 'pptx.animation.duplicate-target',
962
+ message: 'Only the first supported entrance animation for an object is editable; additional effects remain in the source PPTX only.'
963
+ });
964
+ continue;
965
+ }
966
+ const effectResult = readPptxAnimationEffect(node);
967
+ if (!effectResult.effect) {
968
+ diagnostics.push({
969
+ code: 'pptx.animation.effect',
970
+ message: 'An entrance animation uses an unsupported or malformed effect and remains in the source PPTX only.'
971
+ });
972
+ continue;
973
+ }
974
+ const timingResult = readPptxAnimationTiming(node);
975
+ if (timingResult.normalized) diagnostics.push({
976
+ code: 'pptx.animation.timing',
977
+ message: 'An invalid or out-of-range animation duration or delay was normalized to safe editable bounds.'
978
+ });
979
+ const animation = normalizeWorkSlideAnimation({
980
+ id: createWorkId('slide-animation'),
981
+ elementId,
982
+ effect: effectResult.effect,
983
+ trigger: pptxAnimationTrigger(attribute(node, 'nodeType')),
984
+ durationMs: timingResult.durationMs,
985
+ delayMs: timingResult.delayMs,
986
+ direction: effectResult.direction
987
+ });
988
+ animations.push(animation);
989
+ animatedElements.add(elementId);
990
+ }
991
+ if (!effectNodes.length) diagnostics.push({
992
+ code: 'pptx.animation.structure',
993
+ message: 'The slide timing tree does not contain a supported object entrance sequence and remains in the source PPTX only.'
994
+ });
995
+ return {
996
+ animations,
997
+ diagnostics
998
+ };
999
+ }
1000
+ async function patchPptxAnimations(buffer, slides, registry) {
1001
+ if (!slides.some((slide)=>slide.animations?.length)) return buffer;
1002
+ const archive = await jszip.loadAsync(buffer);
1003
+ for (const [index, slide] of slides.entries()){
1004
+ if (!slide.animations?.length) continue;
1005
+ const path = `ppt/slides/slide${index + 1}.xml`;
1006
+ const entry = archive.file(path);
1007
+ if (!entry) throw new Error(`PPTX animation export is missing generated slide ${index + 1}.`);
1008
+ const document = parseXml(await entry.async('text'), path);
1009
+ normalizePptxShapeIds(document);
1010
+ const targetByMarker = pptxTargetIdsByMarker(document);
1011
+ const scope = `slide:${slide.id}`;
1012
+ const targets = slide.animations.map((animation)=>{
1013
+ const marker = registry.markerForElement(scope, animation.elementId);
1014
+ const ids = marker ? targetByMarker.get(marker) : void 0;
1015
+ if (!marker || ids?.length !== 1) throw new Error(`PPTX animation export expected one generated object for animation ${animation.id}, but found ${ids?.length ?? 0}.`);
1016
+ return {
1017
+ animation,
1018
+ spid: ids[0]
1019
+ };
1020
+ });
1021
+ writePptxAnimations(document, targets);
1022
+ archive.file(path, new XMLSerializer().serializeToString(document));
1023
+ }
1024
+ return archive.generateAsync({
1025
+ type: 'arraybuffer',
1026
+ compression: 'DEFLATE'
1027
+ });
1028
+ }
1029
+ function writePptxAnimations(document, targets) {
1030
+ const root = document.documentElement;
1031
+ directChild(root, 'timing')?.remove();
1032
+ if (!targets.length) return;
1033
+ const source = parseXml(pptxAnimationTimingXml(targets), 'PPTX animations');
1034
+ const timing = clonePptxAnimationElement(document, source.documentElement);
1035
+ const extensionList = directChild(root, 'extLst') ?? null;
1036
+ root.insertBefore(timing, extensionList);
1037
+ }
1038
+ function clonePptxAnimationElement(document, source) {
1039
+ const element = document.createElementNS(PRESENTATIONML_NAMESPACE, `p:${source.localName}`);
1040
+ for (const item of Array.from(source.attributes))if (!('xmlns' === item.name || item.name.startsWith('xmlns:'))) element.setAttribute(item.name, item.value);
1041
+ for (const child of Array.from(source.childNodes))element.append(child instanceof Element ? clonePptxAnimationElement(document, child) : document.createTextNode(child.textContent ?? ''));
1042
+ return element;
1043
+ }
1044
+ function readPptxAnimationEffect(node) {
1045
+ const presetId = attribute(node, 'presetID');
1046
+ const animationEffects = pptxDescendants(node, 'animEffect');
1047
+ const filters = animationEffects.map((effect)=>attribute(effect, 'filter')?.trim()).filter((filter)=>Boolean(filter));
1048
+ const attributes = new Set(pptxDescendants(node, 'attrName').map((name)=>name.textContent?.trim()));
1049
+ if (filters.some((filter)=>'fade' === filter)) return {
1050
+ effect: 'fade'
1051
+ };
1052
+ for (const filter of filters){
1053
+ const direction = directionFromPptxSlideFilter(filter);
1054
+ if (direction) return {
1055
+ effect: 'fly-in',
1056
+ direction
1057
+ };
1058
+ }
1059
+ if ('23' === presetId || pptxDescendants(node, 'animScale').length > 0 || attributes.has('ppt_w') && attributes.has('ppt_h')) return {
1060
+ effect: 'zoom'
1061
+ };
1062
+ if ('2' === presetId && (attributes.has('ppt_x') || attributes.has('ppt_y'))) return {
1063
+ effect: 'fly-in',
1064
+ direction: directionFromPptxMotion(node)
1065
+ };
1066
+ if ('1' === presetId && pptxDescendants(node, 'set').length > 0 && 0 === animationEffects.length && 0 === pptxDescendants(node, 'anim').length && 0 === pptxDescendants(node, 'animScale').length) return {
1067
+ effect: 'appear'
1068
+ };
1069
+ return {};
1070
+ }
1071
+ function readPptxAnimationTiming(node) {
1072
+ const directDuration = nonnegativeInteger(attribute(node, 'dur'));
1073
+ const behaviorDurations = pptxDirectChildren(pptxDirectChild(node, 'childTnLst') ?? node).flatMap((behavior)=>pptxDescendants(behavior, 'cTn')).map((time)=>nonnegativeInteger(attribute(time, 'dur'))).filter((duration)=>void 0 !== duration);
1074
+ const behaviorDuration = Math.max(0, ...behaviorDurations);
1075
+ const rawDuration = directDuration ?? (behaviorDuration || 500);
1076
+ const delayValue = attribute(pptxChildPath(node, 'stCondLst', 'cond') ?? node, 'delay');
1077
+ const rawDelay = nonnegativeInteger(delayValue) ?? 0;
1078
+ const normalized = normalizeWorkSlideAnimation({
1079
+ id: 'timing',
1080
+ elementId: 'timing',
1081
+ effect: 'fade',
1082
+ trigger: 'on-click',
1083
+ durationMs: rawDuration,
1084
+ delayMs: rawDelay
1085
+ });
1086
+ return {
1087
+ durationMs: normalized.durationMs,
1088
+ delayMs: normalized.delayMs,
1089
+ normalized: normalized.durationMs !== rawDuration || normalized.delayMs !== rawDelay || null !== attribute(node, 'dur') && void 0 === directDuration || null !== delayValue && void 0 === nonnegativeInteger(delayValue)
1090
+ };
1091
+ }
1092
+ function pptxAnimationTrigger(value) {
1093
+ if ('withEffect' === value) return 'with-previous';
1094
+ if ('afterEffect' === value) return 'after-previous';
1095
+ return 'on-click';
1096
+ }
1097
+ function directionFromPptxSlideFilter(filter) {
1098
+ if ('slide(fromLeft)' === filter) return 'left';
1099
+ if ('slide(fromRight)' === filter) return 'right';
1100
+ if ('slide(fromTop)' === filter) return 'up';
1101
+ if ('slide(fromBottom)' === filter) return 'down';
1102
+ }
1103
+ function directionFromPptxMotion(node) {
1104
+ const values = pptxDescendants(node, 'strVal').map((value)=>attribute(value, 'val') ?? '').join(' ');
1105
+ if (values.includes('-#ppt_w/2')) return 'right';
1106
+ if (values.includes('1+#ppt_h/2')) return 'down';
1107
+ if (values.includes('-#ppt_h/2')) return 'up';
1108
+ return 'left';
1109
+ }
1110
+ function pptxAnimationTimingXml(targets) {
1111
+ let timeNodeId = 1;
1112
+ const nextId = ()=>timeNodeId++;
1113
+ const rootId = nextId();
1114
+ const mainSequenceId = nextId();
1115
+ const targetByAnimationId = new Map(targets.map((target)=>[
1116
+ target.animation.id,
1117
+ target
1118
+ ]));
1119
+ const groups = pptxAnimationGroups(targets.map((target)=>target.animation));
1120
+ const groupXml = groups.map((group)=>{
1121
+ const outerId = nextId();
1122
+ const wrappers = group.wrappers.map((animations)=>{
1123
+ const wrapperId = nextId();
1124
+ const effects = animations.map((animation)=>{
1125
+ const target = targetByAnimationId.get(animation.id);
1126
+ if (!target) return '';
1127
+ return pptxAnimationEffectXml(target, nextId);
1128
+ }).join('');
1129
+ return `<p:par><p:cTn id="${wrapperId}" fill="hold"><p:stCondLst><p:cond delay="0"/></p:stCondLst><p:childTnLst>${effects}</p:childTnLst></p:cTn></p:par>`;
1130
+ }).join('');
1131
+ const delay = group.automatic ? '0' : 'indefinite';
1132
+ return `<p:par><p:cTn id="${outerId}" fill="hold"><p:stCondLst><p:cond delay="${delay}"/></p:stCondLst><p:childTnLst>${wrappers}</p:childTnLst></p:cTn></p:par>`;
1133
+ }).join('');
1134
+ const builds = targets.map(({ spid })=>`<p:bldP spid="${spid}" grpId="0" animBg="1"/>`).join('');
1135
+ return `<p:timing xmlns:p="${PRESENTATIONML_NAMESPACE}"><p:tnLst><p:par><p:cTn id="${rootId}" dur="indefinite" restart="never" nodeType="tmRoot"><p:childTnLst><p:seq concurrent="1" nextAc="seek"><p:cTn id="${mainSequenceId}" dur="indefinite" nodeType="mainSeq"><p:childTnLst>${groupXml}</p:childTnLst></p:cTn><p:prevCondLst><p:cond evt="onPrev" delay="0"><p:tgtEl><p:sldTgt/></p:tgtEl></p:cond></p:prevCondLst><p:nextCondLst><p:cond evt="onNext" delay="0"><p:tgtEl><p:sldTgt/></p:tgtEl></p:cond></p:nextCondLst></p:seq></p:childTnLst></p:cTn></p:par></p:tnLst><p:bldLst>${builds}</p:bldLst></p:timing>`;
1136
+ }
1137
+ function pptxAnimationGroups(animations) {
1138
+ const groups = [];
1139
+ let current;
1140
+ for (const animation of animations)if (current && 'on-click' !== animation.trigger) if ('with-previous' === animation.trigger) current.wrappers.at(-1)?.push(animation);
1141
+ else current.wrappers.push([
1142
+ animation
1143
+ ]);
1144
+ else {
1145
+ current = {
1146
+ automatic: 'on-click' !== animation.trigger,
1147
+ wrappers: [
1148
+ [
1149
+ animation
1150
+ ]
1151
+ ]
1152
+ };
1153
+ groups.push(current);
1154
+ }
1155
+ return groups;
1156
+ }
1157
+ function pptxAnimationEffectXml(target, nextId) {
1158
+ const animation = normalizeWorkSlideAnimation(target.animation);
1159
+ const preset = pptxAnimationPreset(animation);
1160
+ const leafId = nextId();
1161
+ const visibilityId = nextId();
1162
+ const visibility = `<p:set><p:cBhvr><p:cTn id="${visibilityId}" dur="1" fill="hold"><p:stCondLst><p:cond delay="0"/></p:stCondLst></p:cTn><p:tgtEl><p:spTgt spid="${target.spid}"/></p:tgtEl><p:attrNameLst><p:attrName>style.visibility</p:attrName></p:attrNameLst></p:cBhvr><p:to><p:strVal val="visible"/></p:to></p:set>`;
1163
+ const behavior = pptxAnimationBehaviorXml(animation, target.spid, nextId);
1164
+ return `<p:par><p:cTn id="${leafId}" presetID="${preset.id}" presetClass="entr" presetSubtype="${preset.subtype}" fill="hold" nodeType="${pptxAnimationNodeType(animation.trigger)}" dur="${animation.durationMs}"><p:stCondLst><p:cond delay="${animation.delayMs}"/></p:stCondLst><p:childTnLst>${visibility}${behavior}</p:childTnLst></p:cTn></p:par>`;
1165
+ }
1166
+ function pptxAnimationBehaviorXml(animation, spid, nextId) {
1167
+ if ('appear' === animation.effect) return '';
1168
+ if ('zoom' === animation.effect) return [
1169
+ 'ppt_w',
1170
+ 'ppt_h'
1171
+ ].map((attributeName)=>{
1172
+ const id = nextId();
1173
+ return `<p:anim calcMode="lin" valueType="num"><p:cBhvr><p:cTn id="${id}" dur="${animation.durationMs}" fill="hold"/><p:tgtEl><p:spTgt spid="${spid}"/></p:tgtEl><p:attrNameLst><p:attrName>${attributeName}</p:attrName></p:attrNameLst></p:cBhvr><p:tavLst><p:tav tm="0"><p:val><p:fltVal val="0"/></p:val></p:tav><p:tav tm="100000"><p:val><p:strVal val="#${attributeName}"/></p:val></p:tav></p:tavLst></p:anim>`;
1174
+ }).join('');
1175
+ const id = nextId();
1176
+ const filter = 'fade' === animation.effect ? 'fade' : pptxSlideFilter(animation.direction ?? 'left');
1177
+ return `<p:animEffect transition="in" filter="${filter}"><p:cBhvr><p:cTn id="${id}" dur="${animation.durationMs}"/><p:tgtEl><p:spTgt spid="${spid}"/></p:tgtEl></p:cBhvr></p:animEffect>`;
1178
+ }
1179
+ function pptxAnimationPreset(animation) {
1180
+ if ('appear' === animation.effect) return {
1181
+ id: 1,
1182
+ subtype: 0
1183
+ };
1184
+ if ('fade' === animation.effect) return {
1185
+ id: 10,
1186
+ subtype: 0
1187
+ };
1188
+ if ('zoom' === animation.effect) return {
1189
+ id: 23,
1190
+ subtype: 16
1191
+ };
1192
+ const subtypes = {
1193
+ up: 1,
1194
+ right: 2,
1195
+ down: 4,
1196
+ left: 8
1197
+ };
1198
+ return {
1199
+ id: 2,
1200
+ subtype: subtypes[animation.direction ?? 'left']
1201
+ };
1202
+ }
1203
+ function pptxAnimationNodeType(trigger) {
1204
+ if ('with-previous' === trigger) return 'withEffect';
1205
+ if ('after-previous' === trigger) return 'afterEffect';
1206
+ return 'clickEffect';
1207
+ }
1208
+ function pptxSlideFilter(direction) {
1209
+ if ('right' === direction) return 'slide(fromRight)';
1210
+ if ('up' === direction) return 'slide(fromTop)';
1211
+ if ('down' === direction) return 'slide(fromBottom)';
1212
+ return 'slide(fromLeft)';
1213
+ }
1214
+ function normalizePptxShapeIds(document) {
1215
+ for (const shapeTree of descendants(document, 'spTree')){
1216
+ const used = new Set();
1217
+ let candidate = 1;
1218
+ for (const properties of descendants(shapeTree, 'cNvPr')){
1219
+ const current = Number(attribute(properties, 'id'));
1220
+ if (Number.isInteger(current) && current > 0 && !used.has(current)) {
1221
+ used.add(current);
1222
+ continue;
1223
+ }
1224
+ while(used.has(candidate))candidate += 1;
1225
+ properties.setAttribute('id', String(candidate));
1226
+ used.add(candidate);
1227
+ candidate += 1;
1228
+ }
1229
+ }
1230
+ }
1231
+ function pptxTargetIdsByMarker(document) {
1232
+ const targets = new Map();
1233
+ for (const properties of descendants(document, 'cNvPr')){
1234
+ const marker = attribute(properties, 'name');
1235
+ const id = Number(attribute(properties, 'id'));
1236
+ if (!marker || !Number.isInteger(id) || id <= 0) continue;
1237
+ const ids = targets.get(marker) ?? [];
1238
+ ids.push(id);
1239
+ targets.set(marker, ids);
1240
+ }
1241
+ return targets;
1242
+ }
1243
+ function nonnegativeInteger(value) {
1244
+ if (null === value || '' === value.trim()) return;
1245
+ const number = Number(value);
1246
+ return Number.isSafeInteger(number) && number >= 0 ? number : void 0;
1247
+ }
1248
+ function elementNamespace(element) {
1249
+ if (element.namespaceURI) return element.namespaceURI;
1250
+ const separator = element.tagName.indexOf(':');
1251
+ const prefix = element.prefix ?? (separator >= 0 ? element.tagName.slice(0, separator) : void 0);
1252
+ const declaration = prefix ? `xmlns:${prefix}` : 'xmlns';
1253
+ let current = element;
1254
+ while(current){
1255
+ const namespace = current.getAttribute(declaration);
1256
+ if (namespace) return namespace;
1257
+ current = current.parentElement;
1258
+ }
1259
+ return null;
1260
+ }
1261
+ function pptxDescendants(parent, localName) {
1262
+ return descendants(parent, localName).filter((element)=>elementNamespace(element) === PRESENTATIONML_NAMESPACE);
1263
+ }
1264
+ function pptxDirectChildren(parent, localName) {
1265
+ return directChildren(parent, localName).filter((element)=>elementNamespace(element) === PRESENTATIONML_NAMESPACE);
1266
+ }
1267
+ function pptxDirectChild(parent, localName) {
1268
+ return pptxDirectChildren(parent, localName)[0];
1269
+ }
1270
+ function pptxChildPath(parent, ...localNames) {
1271
+ let current = parent;
1272
+ for (const localName of localNames){
1273
+ if (!current) return;
1274
+ current = pptxDirectChild(current, localName);
1275
+ }
1276
+ return current instanceof Element ? current : void 0;
1277
+ }
907
1278
  const EFFECT_TYPES = new Set([
908
1279
  'fade',
909
1280
  'push',
@@ -956,7 +1327,7 @@ function readPptxTransition(document) {
956
1327
  speed: readSpeed(element, diagnostics),
957
1328
  advanceOnClick: readBoolean(attribute(element, 'advClick'), true)
958
1329
  };
959
- const advanceAfterMs = nonnegativeInteger(attribute(element, 'advTm'));
1330
+ const advanceAfterMs = work_pptx_transition_nonnegativeInteger(attribute(element, 'advTm'));
960
1331
  if (void 0 !== advanceAfterMs) transition.advanceAfterMs = advanceAfterMs;
961
1332
  else if (null !== attribute(element, 'advTm')) diagnostics.push({
962
1333
  code: 'pptx.transition.timing',
@@ -1030,9 +1401,9 @@ function readBoolean(value, fallback) {
1030
1401
  if (null === value) return fallback;
1031
1402
  return '1' === value || 'true' === value.toLowerCase();
1032
1403
  }
1033
- function nonnegativeInteger(value) {
1404
+ function work_pptx_transition_nonnegativeInteger(value) {
1034
1405
  if (null === value) return;
1035
1406
  const number = Number(value);
1036
1407
  return Number.isSafeInteger(number) && number >= 0 ? number : void 0;
1037
1408
  }
1038
- export { loadPptxCommentAuthors, patchPptxChartDataLabels, patchPptxChartLayoutAndSeriesStyles, patchPptxChartSeriesAnalysis, patchPptxComments, patchPptxTransitions, readPptxChartDataLabels, readPptxChartLayoutAndSeriesStyles, readPptxChartSeriesAnalysis, readPptxSlideComments, readPptxTransition };
1409
+ export { loadPptxCommentAuthors, patchPptxAnimations, patchPptxChartDataLabels, patchPptxChartLayoutAndSeriesStyles, patchPptxChartSeriesAnalysis, patchPptxComments, patchPptxTransitions, readPptxAnimations, readPptxChartDataLabels, readPptxChartLayoutAndSeriesStyles, readPptxChartSeriesAnalysis, readPptxSlideComments, readPptxTransition };
@@ -2195,13 +2195,17 @@ function PdfToolbar({ annotationState, can, commands, editable, pageOrganization
2195
2195
  className: "work-pdf-page-organizer-trigger",
2196
2196
  "data-pdf-page-organizer-trigger": true,
2197
2197
  "aria-label": "组织 PDF 页面",
2198
+ title: "组织 PDF 页面",
2198
2199
  disabled: !can.openPageOrganizer(),
2199
2200
  onClick: commands.openPageOrganizer,
2200
2201
  children: [
2201
2202
  /*#__PURE__*/ jsx(LayoutGrid, {
2202
2203
  size: 15
2203
2204
  }),
2204
- "组织页面"
2205
+ /*#__PURE__*/ jsx("span", {
2206
+ className: "work-pdf-page-organizer-label",
2207
+ children: "组织页面"
2208
+ })
2205
2209
  ]
2206
2210
  })
2207
2211
  }),