LibreOffice Module sd (master) 1
pptx-animations.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <o3tl/any.hxx>
21#include <o3tl/string_view.hxx>
22#include <oox/token/tokens.hxx>
23#include "epptooxml.hxx"
24#include <sax/fshelper.hxx>
25#include <sal/log.hxx>
26#include <rtl/math.hxx>
28
29#include <com/sun/star/animations/AnimationAdditiveMode.hpp>
30#include <com/sun/star/animations/AnimationCalcMode.hpp>
31#include <com/sun/star/animations/AnimationFill.hpp>
32#include <com/sun/star/animations/AnimationNodeType.hpp>
33#include <com/sun/star/animations/AnimationRestart.hpp>
34#include <com/sun/star/animations/AnimationTransformType.hpp>
35#include <com/sun/star/animations/AnimationValueType.hpp>
36#include <com/sun/star/animations/AnimationColorSpace.hpp>
37#include <com/sun/star/animations/Timing.hpp>
38#include <com/sun/star/animations/ValuePair.hpp>
39#include <com/sun/star/animations/XAnimateMotion.hpp>
40#include <com/sun/star/animations/XAnimateTransform.hpp>
41#include <com/sun/star/animations/XAnimationNode.hpp>
42#include <com/sun/star/animations/XAnimationNodeSupplier.hpp>
43#include <com/sun/star/animations/XAnimateColor.hpp>
44#include <com/sun/star/animations/XCommand.hpp>
45#include <com/sun/star/animations/XAudio.hpp>
46#include <com/sun/star/animations/XTransitionFilter.hpp>
47#include <com/sun/star/animations/XIterateContainer.hpp>
48#include <com/sun/star/container/XEnumerationAccess.hpp>
49#include <com/sun/star/presentation/EffectCommands.hpp>
50#include <com/sun/star/presentation/EffectNodeType.hpp>
51#include <com/sun/star/presentation/EffectPresetClass.hpp>
52#include <com/sun/star/presentation/ParagraphTarget.hpp>
53#include <com/sun/star/presentation/TextAnimationType.hpp>
54#include <com/sun/star/text/XSimpleText.hpp>
55#include <com/sun/star/drawing/XShape.hpp>
56#include <com/sun/star/beans/XPropertySet.hpp>
57#include <oox/export/utils.hxx>
60
61#include "pptexanimations.hxx"
62#include "pptx-animations.hxx"
65
66using namespace ::com::sun::star;
67using namespace ::com::sun::star::animations;
68using namespace ::com::sun::star::container;
69using namespace ::com::sun::star::presentation;
70using namespace ::com::sun::star::uno;
71using namespace ::ppt;
72using namespace oox::drawingml;
73using namespace oox::core;
74using namespace oox;
75
76using ::com::sun::star::beans::NamedValue;
77using ::com::sun::star::drawing::XDrawPage;
78using ::com::sun::star::drawing::XShape;
79using ::com::sun::star::text::XSimpleText;
80using ::sax_fastparser::FSHelperPtr;
81
82namespace
83{
84void WriteAnimationProperty(const FSHelperPtr& pFS, const Any& rAny, sal_Int32 nToken = 0)
85{
86 if (!rAny.hasValue())
87 return;
88
89 ValuePair aPair;
90
91 if (rAny >>= aPair)
92 {
93 double x, y;
94 if ((aPair.First >>= x) && (aPair.Second >>= y))
95 {
96 if (nToken == XML_by)
97 {
98 // MS needs ending values but we have offset values.
99 x += 1.0;
100 y += 1.0;
101 }
102 pFS->singleElementNS(XML_p, nToken, XML_x, OString::number(x * 100000), XML_y,
103 OString::number(y * 100000));
104 }
105 return;
106 }
107
108 sal_Int32 nRgb = {}; // spurious -Werror=maybe-uninitialized
109 double fDouble = {}; // spurious -Werror=maybe-uninitialized
110
111 TypeClass aClass = rAny.getValueType().getTypeClass();
112 bool bWriteToken
113 = nToken
114 && (aClass == TypeClass_LONG || aClass == TypeClass_DOUBLE || aClass == TypeClass_STRING);
115
116 if (bWriteToken)
117 pFS->startElementNS(XML_p, nToken);
118
119 switch (rAny.getValueType().getTypeClass())
120 {
121 case TypeClass_LONG:
122 if (!(rAny >>= nRgb))
123 {
124 assert(false);
125 }
126 pFS->singleElementNS(XML_a, XML_srgbClr, XML_val, I32SHEX(nRgb));
127 break;
128 case TypeClass_DOUBLE:
129 if (!(rAny >>= fDouble))
130 {
131 assert(false);
132 }
133 pFS->singleElementNS(XML_p, XML_fltVal, XML_val, OString::number(fDouble));
134 break;
135 case TypeClass_STRING:
136 pFS->singleElementNS(XML_p, XML_strVal, XML_val, *o3tl::doAccess<OUString>(rAny));
137 break;
138 default:
139 break;
140 }
141
142 if (bWriteToken)
143 pFS->endElementNS(XML_p, nToken);
144}
145
146void WriteAnimateColorColor(const FSHelperPtr& pFS, const Any& rAny, sal_Int32 nToken)
147{
148 if (!rAny.hasValue())
149 return;
150
151 sal_Int32 nColor = 0;
152 if (rAny >>= nColor)
153 {
154 pFS->startElementNS(XML_p, nToken);
155
156 if (nToken == XML_by)
157 {
158 // CT_TLByRgbColorTransform
159 SAL_WARN("sd.eppt", "Export p:rgb in p:by of animClr isn't implemented yet.");
160 }
161 else
162 {
163 // CT_Color
164 pFS->singleElementNS(XML_a, XML_srgbClr, XML_val, I32SHEX(nColor));
165 }
166
167 pFS->endElementNS(XML_p, nToken);
168 }
169
170 Sequence<double> aHSL(3);
171 if (!(rAny >>= aHSL))
172 return;
173
174 pFS->startElementNS(XML_p, nToken);
175
176 if (nToken == XML_by)
177 {
178 // CT_TLByHslColorTransform
179 pFS->singleElementNS(XML_p, XML_hsl, XML_h, OString::number(aHSL[0] * 60000), // ST_Angel
180 XML_s, OString::number(aHSL[1] * 100000), XML_l,
181 OString::number(aHSL[2] * 100000));
182 }
183 else
184 {
185 // CT_Color
186 SAL_WARN("sd.eppt", "Export p:hsl in p:from or p:to of animClr isn't implemented yet.");
187 }
188
189 pFS->endElementNS(XML_p, nToken);
190}
191
192void WriteAnimateTo(const FSHelperPtr& pFS, const Any& rValue, const OUString& rAttributeName)
193{
194 if (!rValue.hasValue())
195 return;
196
197 SAL_INFO("sd.eppt", "to attribute name: " << rAttributeName.toUtf8());
198
199 WriteAnimationProperty(pFS, AnimationExporter::convertAnimateValue(rValue, rAttributeName),
200 XML_to);
201}
202
203void WriteAnimateValues(const FSHelperPtr& pFS, const Reference<XAnimate>& rXAnimate)
204{
205 const Sequence<double> aKeyTimes = rXAnimate->getKeyTimes();
206 if (!aKeyTimes.hasElements())
207 return;
208 const Sequence<Any> aValues = rXAnimate->getValues();
209 const OUString& sFormula = rXAnimate->getFormula();
210 const OUString& rAttributeName = rXAnimate->getAttributeName();
211
212 SAL_INFO("sd.eppt", "animate values, formula: " << sFormula.toUtf8());
213
214 assert(aValues.getLength() == aKeyTimes.getLength());
215
216 pFS->startElementNS(XML_p, XML_tavLst);
217
218 for (int i = 0; i < aKeyTimes.getLength(); i++)
219 {
220 SAL_INFO("sd.eppt", "animate value " << i << ": " << aKeyTimes[i]);
221 if (aValues[i].hasValue())
222 {
223 pFS->startElementNS(XML_p, XML_tav, XML_fmla,
224 sax_fastparser::UseIf(sFormula, !sFormula.isEmpty()), XML_tm,
225 OString::number(static_cast<sal_Int32>(aKeyTimes[i] * 100000.0)));
226 pFS->startElementNS(XML_p, XML_val);
227 ValuePair aPair;
228 if (aValues[i] >>= aPair)
229 {
230 WriteAnimationProperty(
231 pFS, AnimationExporter::convertAnimateValue(aPair.First, rAttributeName));
232 WriteAnimationProperty(
233 pFS, AnimationExporter::convertAnimateValue(aPair.Second, rAttributeName));
234 }
235 else
236 WriteAnimationProperty(
237 pFS, AnimationExporter::convertAnimateValue(aValues[i], rAttributeName));
238
239 pFS->endElementNS(XML_p, XML_val);
240 pFS->endElementNS(XML_p, XML_tav);
241 }
242 }
243
244 pFS->endElementNS(XML_p, XML_tavLst);
245}
246
247// Write condition list ( either prevCondlst or nextCondlst ) of Seq.
248void WriteAnimationCondListForSeq(const FSHelperPtr& pFS, sal_Int32 nToken)
249{
250 const char* pEvent = (nToken == XML_prevCondLst) ? "onPrev" : "onNext";
251
252 pFS->startElementNS(XML_p, nToken);
253 pFS->startElementNS(XML_p, XML_cond, XML_evt, pEvent);
254 pFS->startElementNS(XML_p, XML_tgtEl);
255 pFS->singleElementNS(XML_p, XML_sldTgt);
256 pFS->endElementNS(XML_p, XML_tgtEl);
257 pFS->endElementNS(XML_p, XML_cond);
258 pFS->endElementNS(XML_p, nToken);
259}
260
261void WriteAnimationAttributeName(const FSHelperPtr& pFS, const OUString& rAttributeName)
262{
263 if (rAttributeName.isEmpty())
264 return;
265
266 pFS->startElementNS(XML_p, XML_attrNameLst);
267
268 SAL_INFO("sd.eppt", "write attribute name: " << rAttributeName.toUtf8());
269
270 if (rAttributeName == "X;Y")
271 {
272 pFS->startElementNS(XML_p, XML_attrName);
273 pFS->writeEscaped("ppt_x");
274 pFS->endElementNS(XML_p, XML_attrName);
275
276 pFS->startElementNS(XML_p, XML_attrName);
277 pFS->writeEscaped("ppt_y");
278 pFS->endElementNS(XML_p, XML_attrName);
279 }
280 else
281 {
284 const char* pAttribute = nullptr;
285
286 while (attrConv->mpAPIName != nullptr)
287 {
288 if (rAttributeName.equalsAscii(attrConv->mpAPIName))
289 {
290 pAttribute = attrConv->mpMSName;
291 break;
292 }
293 attrConv++;
294 }
295
296 if (pAttribute)
297 {
298 pFS->startElementNS(XML_p, XML_attrName);
299 pFS->writeEscaped(pAttribute);
300 pFS->endElementNS(XML_p, XML_attrName);
301 }
302 else
303 {
304 SAL_WARN("sd.eppt", "unhandled animation attribute name: " << rAttributeName);
305 }
306 }
307
308 pFS->endElementNS(XML_p, XML_attrNameLst);
309}
310
312sal_Int32 extractNodeType(const Reference<XAnimationNode>& rXNode)
313{
314 sal_Int16 nType = rXNode->getType();
315 sal_Int32 xmlNodeType = -1;
316 switch (nType)
317 {
318 case AnimationNodeType::ITERATE:
319 case AnimationNodeType::PAR:
320 xmlNodeType = XML_par;
321 break;
322 case AnimationNodeType::SEQ:
323 xmlNodeType = XML_seq;
324 break;
325 case AnimationNodeType::ANIMATE:
326 xmlNodeType = XML_anim;
327 break;
328 case AnimationNodeType::ANIMATEMOTION:
329 xmlNodeType = XML_animMotion;
330 break;
331 case AnimationNodeType::ANIMATETRANSFORM:
332 {
333 Reference<XAnimateTransform> xTransform(rXNode, UNO_QUERY);
334 if (xTransform.is())
335 {
336 if (xTransform->getTransformType() == AnimationTransformType::SCALE)
337 xmlNodeType = XML_animScale;
338 else if (xTransform->getTransformType() == AnimationTransformType::ROTATE)
339 xmlNodeType = XML_animRot;
340 }
341 break;
342 }
343 case AnimationNodeType::ANIMATECOLOR:
344 xmlNodeType = XML_animClr;
345 break;
346 case AnimationNodeType::SET:
347 xmlNodeType = XML_set;
348 break;
349 case AnimationNodeType::TRANSITIONFILTER:
350 xmlNodeType = XML_animEffect;
351 break;
352 case AnimationNodeType::COMMAND:
353 xmlNodeType = XML_cmd;
354 break;
355 case AnimationNodeType::AUDIO:
356 xmlNodeType = XML_audio;
357 break;
358 default:
359 SAL_WARN("sd.eppt", "unhandled animation node: " << nType);
360 break;
361 }
362 return xmlNodeType;
363}
364
366const char* convertAnimationRestart(sal_Int16 nRestart)
367{
368 const char* pRestart = nullptr;
369 switch (nRestart)
370 {
371 case AnimationRestart::ALWAYS:
372 pRestart = "always";
373 break;
374 case AnimationRestart::WHEN_NOT_ACTIVE:
375 pRestart = "whenNotActive";
376 break;
377 case AnimationRestart::NEVER:
378 pRestart = "never";
379 break;
380 }
381 return pRestart;
382}
383
385const char* convertEffectNodeType(sal_Int16 nType)
386{
387 const char* pNodeType = nullptr;
388 switch (nType)
389 {
390 case EffectNodeType::TIMING_ROOT:
391 pNodeType = "tmRoot";
392 break;
393 case EffectNodeType::MAIN_SEQUENCE:
394 pNodeType = "mainSeq";
395 break;
396 case EffectNodeType::ON_CLICK:
397 pNodeType = "clickEffect";
398 break;
399 case EffectNodeType::AFTER_PREVIOUS:
400 pNodeType = "afterEffect";
401 break;
402 case EffectNodeType::WITH_PREVIOUS:
403 pNodeType = "withEffect";
404 break;
405 case EffectNodeType::INTERACTIVE_SEQUENCE:
406 pNodeType = "interactiveSeq";
407 break;
408 }
409 return pNodeType;
410}
411
413const char* convertEffectPresetClass(sal_Int16 nPresetClass)
414{
415 const char* pPresetClass = nullptr;
416 switch (nPresetClass)
417 {
418 case EffectPresetClass::ENTRANCE:
419 pPresetClass = "entr";
420 break;
421 case EffectPresetClass::EXIT:
422 pPresetClass = "exit";
423 break;
424 case EffectPresetClass::EMPHASIS:
425 pPresetClass = "emph";
426 break;
427 case EffectPresetClass::MOTIONPATH:
428 pPresetClass = "path";
429 break;
430 case EffectPresetClass::OLEACTION:
431 pPresetClass = "verb"; // ?
432 break;
433 case EffectPresetClass::MEDIACALL:
434 pPresetClass = "mediacall";
435 break;
436 }
437 return pPresetClass;
438}
439
441const char* convertAnimationFill(sal_Int16 nFill)
442{
443 const char* pFill = nullptr;
444 switch (nFill)
445 {
446 case AnimationFill::FREEZE:
447 pFill = "hold";
448 break;
449 case AnimationFill::HOLD:
450 pFill = "hold";
451 break;
452 case AnimationFill::REMOVE:
453 pFill = "remove";
454 break;
455 case AnimationFill::TRANSITION:
456 pFill = "transition";
457 break;
458 }
459 return pFill;
460}
461
463const char* convertTextAnimationType(sal_Int16 nType)
464{
465 const char* sType = nullptr;
466 switch (nType)
467 {
468 case TextAnimationType::BY_PARAGRAPH:
469 sType = "el";
470 break;
471 case TextAnimationType::BY_LETTER:
472 sType = "lt";
473 break;
474 case TextAnimationType::BY_WORD:
475 default:
476 sType = "wd";
477 break;
478 }
479 return sType;
480}
481
482class PPTXAnimationExport
483{
484 void WriteAnimationNode(const NodeContextPtr& pContext);
485 void WriteAnimationNodeAnimate(sal_Int32 nXmlNodeType);
486 void WriteAnimationNodeAnimateInside(bool bSimple, bool bWriteTo = true);
487 void WriteAnimationNodeSeq();
488 void WriteAnimationNodeEffect();
489 void WriteAnimationNodeCommand();
491 void WriteAnimationNodeMedia();
492 void WriteAnimationNodeCommonPropsStart();
493 void WriteAnimationTarget(const Any& rTarget);
494 void WriteAnimationCondList(const std::vector<Cond>& rList, sal_Int32 nToken);
495 void WriteAnimationCond(const Cond& rCond);
496 const Reference<XAnimationNode>& getCurrentNode() const;
497
498 PowerPointExport& mrPowerPointExport;
499 const FSHelperPtr& mpFS;
500 const NodeContext* mpContext;
501
502 std::map<Reference<XAnimationNode>, sal_Int32> maAnimationNodeIdMap;
503 sal_Int32 GetNextAnimationNodeId(const Reference<XAnimationNode>& rNode);
504 sal_Int32 GetAnimationNodeId(const Reference<XAnimationNode>& rNode);
505
506public:
507 PPTXAnimationExport(PowerPointExport& rExport, const FSHelperPtr& pFS);
508 void WriteAnimations(const Reference<XDrawPage>& rXDrawPage);
509};
510
512bool IsAudioURL(std::u16string_view rURL)
513{
514 return o3tl::endsWithIgnoreAsciiCase(rURL, ".wav")
515 || o3tl::endsWithIgnoreAsciiCase(rURL, ".m4a");
516}
517
519bool IsVideoURL(std::u16string_view rURL) { return o3tl::endsWithIgnoreAsciiCase(rURL, ".mp4"); }
520}
521
522namespace oox::core
523{
524void WriteAnimations(const FSHelperPtr& pFS, const Reference<XDrawPage>& rXDrawPage,
525 PowerPointExport& rExport)
526{
527 PPTXAnimationExport aAnimationExport(rExport, pFS);
528 aAnimationExport.WriteAnimations(rXDrawPage);
529}
530}
531
532PPTXAnimationExport::PPTXAnimationExport(PowerPointExport& rExport, const FSHelperPtr& pFS)
533 : mrPowerPointExport(rExport)
534 , mpFS(pFS)
535 , mpContext(nullptr)
536{
537}
538
539const Reference<XAnimationNode>& PPTXAnimationExport::getCurrentNode() const
540{
541 assert(mpContext);
542 return mpContext->getNode();
543}
544
545void PPTXAnimationExport::WriteAnimationTarget(const Any& rTarget)
546{
547 sal_Int32 nParagraph = -1;
548 bool bParagraphTarget = false;
549
550 Reference<XShape> rXShape;
551 rTarget >>= rXShape;
552
553 if (!rXShape.is())
554 {
555 ParagraphTarget aParagraphTarget;
556 if (rTarget >>= aParagraphTarget)
557 rXShape = aParagraphTarget.Shape;
558 if (rXShape.is())
559 {
560 nParagraph = static_cast<sal_Int32>(aParagraphTarget.Paragraph);
561 Reference<XSimpleText> xText(rXShape, UNO_QUERY);
562 if (xText.is())
563 {
564 bParagraphTarget = true;
565 }
566 }
567 }
568
569 if (!rXShape.is())
570 return;
571
572 sal_Int32 nShapeID = mrPowerPointExport.GetShapeID(rXShape);
573
574 mpFS->startElementNS(XML_p, XML_tgtEl);
575 mpFS->startElementNS(XML_p, XML_spTgt, XML_spid, OString::number(nShapeID));
576 if (bParagraphTarget)
577 {
578 mpFS->startElementNS(XML_p, XML_txEl);
579 mpFS->singleElementNS(XML_p, XML_pRg, XML_st, OString::number(nParagraph), XML_end,
580 OString::number(nParagraph));
581 mpFS->endElementNS(XML_p, XML_txEl);
582 }
583 mpFS->endElementNS(XML_p, XML_spTgt);
584 mpFS->endElementNS(XML_p, XML_tgtEl);
585}
586
587void PPTXAnimationExport::WriteAnimationCondList(const std::vector<Cond>& rList, sal_Int32 nToken)
588{
589 if (rList.size() > 0)
590 {
591 mpFS->startElementNS(XML_p, nToken);
592
593 for (const Cond& rCond : rList)
594 WriteAnimationCond(rCond);
595
596 mpFS->endElementNS(XML_p, nToken);
597 }
598}
599
600void PPTXAnimationExport::WriteAnimationCond(const Cond& rCond)
601{
602 if (rCond.mpEvent)
603 {
604 sal_Int32 nId = -1;
605 if (rCond.mxShape.is())
606 {
607 mpFS->startElementNS(XML_p, XML_cond, XML_delay, rCond.getDelay(), XML_evt,
608 rCond.mpEvent);
609 WriteAnimationTarget(Any(rCond.mxShape));
610 mpFS->endElementNS(XML_p, XML_cond);
611 }
612 else if (rCond.mxNode.is() && (nId = GetAnimationNodeId(rCond.mxNode)) != -1)
613 {
614 mpFS->startElementNS(XML_p, XML_cond, XML_delay, rCond.getDelay(), XML_evt,
615 rCond.mpEvent);
616 mpFS->singleElementNS(XML_p, XML_tn, XML_val, OString::number(nId));
617 mpFS->endElementNS(XML_p, XML_cond);
618 }
619 else
620 {
621 mpFS->singleElementNS(XML_p, XML_cond, XML_delay, rCond.getDelay(), XML_evt,
622 rCond.mpEvent);
623 }
624 }
625 else
626 mpFS->singleElementNS(XML_p, XML_cond, XML_delay, rCond.getDelay());
627}
628
629void PPTXAnimationExport::WriteAnimationNodeAnimate(sal_Int32 nXmlNodeType)
630{
631 const Reference<XAnimationNode>& rXNode = getCurrentNode();
632 Reference<XAnimate> rXAnimate(rXNode, UNO_QUERY);
633 if (!rXAnimate.is())
634 return;
635
636 const char* pCalcMode = nullptr;
637 const char* pValueType = nullptr;
638 bool bSimple = (nXmlNodeType != XML_anim);
639 bool bTo = true;
640
641 if (!bSimple)
642 {
643 switch (rXAnimate->getCalcMode())
644 {
645 case AnimationCalcMode::DISCRETE:
646 pCalcMode = "discrete";
647 break;
648 case AnimationCalcMode::LINEAR:
649 pCalcMode = "lin";
650 break;
651 }
652
653 switch (AnimationExporter::GetValueTypeForAttributeName(rXAnimate->getAttributeName()))
654 {
655 case AnimationValueType::STRING:
656 pValueType = "str";
657 break;
658 case AnimationValueType::NUMBER:
659 pValueType = "num";
660 break;
661 case AnimationValueType::COLOR:
662 pValueType = "clr";
663 break;
664 }
665 }
666
667 if (nXmlNodeType == XML_animMotion)
668 {
669 OUString aPath;
670 Reference<XAnimateMotion> xMotion(rXNode, UNO_QUERY);
671 if (xMotion.is())
672 {
673 xMotion->getPath() >>= aPath;
675 if (::basegfx::utils::importFromSvgD(aPolyPoly, aPath, true, nullptr))
676 aPath = ::basegfx::utils::exportToSvgD(aPolyPoly, false, false, true, true);
677 }
678
679 mpFS->startElementNS(XML_p, nXmlNodeType, XML_origin, "layout", XML_path, aPath);
680 }
681 else if (nXmlNodeType == XML_animRot)
682 {
683 // when const char* is nullptr, the attribute is completely omitted in the output
684 const char* pBy = nullptr;
685 const char* pFrom = nullptr;
686 const char* pTo = nullptr;
687 OString aBy, aFrom, aTo;
688
689 Reference<XAnimateTransform> xTransform(rXNode, UNO_QUERY);
690 if (xTransform.is())
691 {
692 double value;
693 if (xTransform->getBy() >>= value)
694 {
695 aBy = OString::number(static_cast<int>(value * PER_DEGREE));
696 pBy = aBy.getStr();
697 }
698
699 if (xTransform->getFrom() >>= value)
700 {
701 aFrom = OString::number(static_cast<int>(value * PER_DEGREE));
702 pFrom = aFrom.getStr();
703 }
704
705 if (xTransform->getTo() >>= value)
706 {
707 aTo = OString::number(static_cast<int>(value * PER_DEGREE));
708 pTo = aTo.getStr();
709 }
710 }
711
712 mpFS->startElementNS(XML_p, nXmlNodeType, XML_by, pBy, XML_from, pFrom, XML_to, pTo);
713 }
714 else if (nXmlNodeType == XML_animClr)
715 {
716 Reference<XAnimateColor> xColor(rXNode, UNO_QUERY);
717 const char* pColorSpace = "rgb";
718 const char* pDirection = nullptr;
719 if (xColor.is() && xColor->getColorInterpolation() == AnimationColorSpace::HSL)
720 {
721 // Note: from, to, by can still be specified in any supported format.
722 pColorSpace = "hsl";
723 pDirection = xColor->getDirection() ? "cw" : "ccw";
724 }
725 mpFS->startElementNS(XML_p, nXmlNodeType, XML_clrSpc, pColorSpace, XML_dir, pDirection,
726 XML_calcmode, pCalcMode, XML_valueType, pValueType);
727 }
728 else
729 {
730 OUString sFrom, sTo, sBy;
731 if (rXAnimate.is() && nXmlNodeType == XML_anim)
732 {
733 OUString sAttributeName = rXAnimate->getAttributeName();
734 Any aFrom
735 = AnimationExporter::convertAnimateValue(rXAnimate->getFrom(), sAttributeName);
736 aFrom >>= sFrom;
737 Any aTo = AnimationExporter::convertAnimateValue(rXAnimate->getTo(), sAttributeName);
738 aTo >>= sTo;
739 Any aBy = AnimationExporter::convertAnimateValue(rXAnimate->getBy(), sAttributeName);
740 aBy >>= sBy;
741 }
742
743 mpFS->startElementNS(XML_p, nXmlNodeType, XML_calcmode, pCalcMode, XML_valueType,
744 pValueType, XML_from, sax_fastparser::UseIf(sFrom, !sFrom.isEmpty()),
745 XML_to, sax_fastparser::UseIf(sTo, !sTo.isEmpty()), XML_by,
746 sax_fastparser::UseIf(sBy, !sBy.isEmpty()));
747 bTo = sTo.isEmpty() && sFrom.isEmpty() && sBy.isEmpty();
748 }
749
750 WriteAnimationNodeAnimateInside(bSimple, bTo);
751 mpFS->endElementNS(XML_p, nXmlNodeType);
752}
753
754void PPTXAnimationExport::WriteAnimationNodeAnimateInside(bool bSimple, bool bWriteTo)
755{
756 const Reference<XAnimationNode>& rXNode = getCurrentNode();
757 Reference<XAnimate> rXAnimate(rXNode, UNO_QUERY);
758 if (!rXAnimate.is())
759 return;
760
761 const char* pAdditive = nullptr;
762
763 if (!bSimple)
764 {
765 switch (rXAnimate->getAdditive())
766 {
767 case AnimationAdditiveMode::BASE:
768 pAdditive = "base";
769 break;
770 case AnimationAdditiveMode::SUM:
771 pAdditive = "sum";
772 break;
773 case AnimationAdditiveMode::REPLACE:
774 pAdditive = "repl";
775 break;
776 case AnimationAdditiveMode::MULTIPLY:
777 pAdditive = "mult";
778 break;
779 case AnimationAdditiveMode::NONE:
780 pAdditive = "none";
781 break;
782 }
783 }
784
785 mpFS->startElementNS(XML_p, XML_cBhvr, XML_additive, pAdditive);
786 WriteAnimationNodeCommonPropsStart();
787
788 Reference<XIterateContainer> xIterate(rXNode->getParent(), UNO_QUERY);
789 WriteAnimationTarget(xIterate.is() ? xIterate->getTarget() : rXAnimate->getTarget());
790
791 Reference<XAnimateTransform> xTransform(rXNode, UNO_QUERY);
792
793 // The attribute name of AnimateTransform is "Transform", we have to fix it.
794 OUString sNewAttr;
795 if (xTransform.is() && xTransform->getTransformType() == AnimationTransformType::ROTATE)
796 sNewAttr = "Rotate";
797
798 WriteAnimationAttributeName(mpFS, xTransform.is() ? sNewAttr : rXAnimate->getAttributeName());
799
800 mpFS->endElementNS(XML_p, XML_cBhvr);
801 WriteAnimateValues(mpFS, rXAnimate);
802
803 Reference<XAnimateColor> xColor(rXNode, UNO_QUERY);
804
805 if (xColor.is())
806 {
807 WriteAnimateColorColor(mpFS, xColor->getBy(), XML_by);
808 WriteAnimateColorColor(mpFS, xColor->getFrom(), XML_from);
809 WriteAnimateColorColor(mpFS, xColor->getTo(), XML_to);
810 }
811 else if (xTransform.is() && xTransform->getTransformType() == AnimationTransformType::SCALE)
812 {
813 WriteAnimationProperty(mpFS, rXAnimate->getBy(), XML_by);
814 WriteAnimationProperty(mpFS, rXAnimate->getFrom(), XML_from);
815 WriteAnimationProperty(mpFS, rXAnimate->getTo(), XML_to);
816 }
817 else if (bWriteTo)
818 WriteAnimateTo(mpFS, rXAnimate->getTo(), rXAnimate->getAttributeName());
819}
820
821void PPTXAnimationExport::WriteAnimationNodeCommonPropsStart()
822{
823 const Reference<XAnimationNode>& rXNode = getCurrentNode();
824 std::optional<OString> sDuration;
825 std::optional<OString> sRepeatCount;
826 const char* pRestart = nullptr;
827 const char* pNodeType = nullptr;
828 const char* pPresetClass = nullptr;
829 const char* pFill = nullptr;
830 double fDuration = 0;
831 double fRepeatCount = 0;
832 Any aAny;
833 assert(mpContext);
834
835 aAny = rXNode->getDuration();
836 if (aAny.hasValue())
837 {
838 Timing eTiming;
839
840 if (aAny >>= eTiming)
841 {
842 if (eTiming == Timing_INDEFINITE)
843 sDuration = "indefinite";
844 }
845 else
846 aAny >>= fDuration;
847 }
848
849 pRestart = convertAnimationRestart(rXNode->getRestart());
850
851 sal_Int16 nType = mpContext->getEffectNodeType();
852 if (nType != -1)
853 {
854 pNodeType = convertEffectNodeType(nType);
855 if (nType == EffectNodeType::TIMING_ROOT)
856 {
857 if (!sDuration)
858 sDuration = "indefinite";
859 if (!pRestart)
860 pRestart = "never";
861 }
862 else if (nType == EffectNodeType::MAIN_SEQUENCE)
863 {
864 sDuration = "indefinite";
865 }
866 }
867
868 if (fDuration != 0)
869 sDuration = OString::number(static_cast<sal_Int32>(fDuration * 1000.0));
870
871 sal_uInt32 nPresetClass = mpContext->getEffectPresetClass();
872 if (nPresetClass != EffectPresetClass::CUSTOM)
873 pPresetClass = convertEffectPresetClass(nPresetClass);
874
875 sal_uInt32 nPresetId = 0;
876 bool bPresetId = false;
877 const OUString& rPresetId = mpContext->getEffectPresetId();
878 if (rPresetId.getLength() > 0)
879 {
880 nPresetId = AnimationExporter::GetPresetID(rPresetId, nPresetClass, bPresetId);
881 bPresetId = true;
882 }
883
884 sal_uInt32 nPresetSubType = 0;
885 bool bPresetSubType = false;
886 const OUString& sPresetSubType = mpContext->getEffectPresetSubType();
887 if (sPresetSubType.getLength() > 0)
888 {
889 nPresetSubType
890 = AnimationExporter::TranslatePresetSubType(nPresetClass, nPresetId, sPresetSubType);
891 bPresetSubType = true;
892 }
893
894 if (nType != EffectNodeType::TIMING_ROOT && nType != EffectNodeType::MAIN_SEQUENCE)
895 {
896 // it doesn't seem to work right on root and mainseq nodes
897 sal_Int16 nFill = AnimationExporter::GetFillMode(rXNode, AnimationFill::AUTO);
898 pFill = convertAnimationFill(nFill);
899 }
900
901 bool bAutoReverse = rXNode->getAutoReverse();
902
903 aAny = rXNode->getRepeatCount();
904 if (aAny.hasValue())
905 {
906 Timing eTiming;
907
908 if (aAny >>= eTiming)
909 {
910 if (eTiming == Timing_INDEFINITE)
911 sRepeatCount = "indefinite";
912 }
913 else
914 aAny >>= fRepeatCount;
915 }
916
917 if (fRepeatCount != 0)
918 sRepeatCount = OString::number(static_cast<sal_Int32>(fRepeatCount * 1000.0));
919
920 mpFS->startElementNS(
921 XML_p, XML_cTn, XML_id, OString::number(GetNextAnimationNodeId(rXNode)), XML_dur, sDuration,
922 XML_autoRev, sax_fastparser::UseIf("1", bAutoReverse), XML_restart, pRestart, XML_nodeType,
923 pNodeType, XML_fill, pFill, XML_presetClass, pPresetClass, XML_presetID,
924 sax_fastparser::UseIf(OString::number(nPresetId), bPresetId), XML_presetSubtype,
925 sax_fastparser::UseIf(OString::number(nPresetSubType), bPresetSubType), XML_repeatCount,
926 sRepeatCount);
927
928 WriteAnimationCondList(mpContext->getBeginCondList(), XML_stCondLst);
929 WriteAnimationCondList(mpContext->getEndCondList(), XML_endCondLst);
930
931 if (rXNode->getType() == AnimationNodeType::ITERATE)
932 {
933 Reference<XIterateContainer> xIterate(rXNode, UNO_QUERY);
934 if (xIterate.is())
935 {
936 const char* sType = convertTextAnimationType(xIterate->getIterateType());
937
938 mpFS->startElementNS(XML_p, XML_iterate, XML_type, sType);
939 mpFS->singleElementNS(XML_p, XML_tmAbs, XML_val,
940 OString::number(xIterate->getIterateInterval() * 1000));
941 mpFS->endElementNS(XML_p, XML_iterate);
942 }
943 }
944
945 const std::vector<NodeContextPtr>& aChildNodes = mpContext->getChildNodes();
946 if (!aChildNodes.empty())
947 {
948 bool bSubTnLst = false;
949 mpFS->startElementNS(XML_p, XML_childTnLst);
950 for (const NodeContextPtr& pChildContext : aChildNodes)
951 {
952 if (pChildContext->isValid())
953 {
954 if (pChildContext->isOnSubTnLst())
955 bSubTnLst = true;
956 else
957 WriteAnimationNode(pChildContext);
958 }
959 }
960 mpFS->endElementNS(XML_p, XML_childTnLst);
961
962 if (bSubTnLst)
963 {
964 mpFS->startElementNS(XML_p, XML_subTnLst);
965 for (const NodeContextPtr& pChildContext : aChildNodes)
966 {
967 if (pChildContext->isValid() && pChildContext->isOnSubTnLst())
968 WriteAnimationNode(pChildContext);
969 }
970 mpFS->endElementNS(XML_p, XML_subTnLst);
971 }
972 }
973 mpFS->endElementNS(XML_p, XML_cTn);
974}
975
976void PPTXAnimationExport::WriteAnimationNodeSeq()
977{
978 SAL_INFO("sd.eppt", "write animation node SEQ");
979
980 mpFS->startElementNS(XML_p, XML_seq);
981
982 WriteAnimationNodeCommonPropsStart();
983
984 WriteAnimationCondListForSeq(mpFS, XML_prevCondLst);
985 WriteAnimationCondListForSeq(mpFS, XML_nextCondLst);
986
987 mpFS->endElementNS(XML_p, XML_seq);
988}
989
990void PPTXAnimationExport::WriteAnimationNodeEffect()
991{
992 SAL_INFO("sd.eppt", "write animation node FILTER");
993 Reference<XTransitionFilter> xFilter(getCurrentNode(), UNO_QUERY);
994 if (xFilter.is())
995 {
997 xFilter->getTransition(), xFilter->getSubtype(), xFilter->getDirection());
998 const char* pMode = xFilter->getMode() ? "in" : "out";
999 mpFS->startElementNS(XML_p, XML_animEffect, XML_filter, pFilter, XML_transition, pMode);
1000
1001 WriteAnimationNodeAnimateInside(false);
1002
1003 mpFS->endElementNS(XML_p, XML_animEffect);
1004 }
1005}
1006
1007void PPTXAnimationExport::WriteAnimationNodeCommand()
1008{
1009 SAL_INFO("sd.eppt", "write animation node COMMAND");
1010 Reference<XCommand> xCommand(getCurrentNode(), UNO_QUERY);
1011 if (!xCommand.is())
1012 return;
1013
1014 const char* pType = "call";
1015 OString aCommand;
1016 switch (xCommand->getCommand())
1017 {
1018 case EffectCommands::VERB:
1019 pType = "verb";
1020 aCommand = "1"; /* FIXME hardcoded viewing */
1021 break;
1022 case EffectCommands::PLAY:
1023 {
1024 aCommand = "play";
1025 uno::Sequence<beans::NamedValue> aParamSeq;
1026 xCommand->getParameter() >>= aParamSeq;
1028 auto it = aMap.find("MediaTime");
1029 if (it != aMap.end())
1030 {
1031 double fMediaTime = 0;
1032 it->second >>= fMediaTime;
1033 // PowerPoint represents 0 as 0.0, so just use a single decimal.
1034 OString aMediaTime
1035 = rtl::math::doubleToString(fMediaTime, rtl_math_StringFormat_F, 1, '.');
1036 aCommand += "From(" + aMediaTime + ")";
1037 }
1038 break;
1039 }
1040 case EffectCommands::TOGGLEPAUSE:
1041 aCommand = "togglePause";
1042 break;
1043 case EffectCommands::STOP:
1044 aCommand = "stop";
1045 break;
1046 default:
1047 SAL_WARN("sd.eppt", "unknown command: " << xCommand->getCommand());
1048 break;
1049 }
1050
1051 mpFS->startElementNS(XML_p, XML_cmd, XML_type, pType, XML_cmd, aCommand.getStr());
1052
1053 WriteAnimationNodeAnimateInside(false);
1054 mpFS->startElementNS(XML_p, XML_cBhvr);
1055 WriteAnimationNodeCommonPropsStart();
1056 WriteAnimationTarget(xCommand->getTarget());
1057 mpFS->endElementNS(XML_p, XML_cBhvr);
1058
1059 mpFS->endElementNS(XML_p, XML_cmd);
1060}
1061
1062void PPTXAnimationExport::WriteAnimationNodeMedia()
1063{
1064 SAL_INFO("sd.eppt", "write animation node media");
1065 Reference<XAudio> xAudio(getCurrentNode(), UNO_QUERY);
1066
1067 OUString sUrl;
1068 uno::Reference<drawing::XShape> xShape;
1069 OUString sRelId;
1070 OUString sName;
1071
1072 if (!xAudio.is())
1073 {
1074 return;
1075 }
1076
1077 bool bValid = false;
1078 if ((xAudio->getSource() >>= sUrl) && !sUrl.isEmpty() && IsAudioURL(sUrl))
1079 {
1080 bValid = true;
1081 }
1082
1083 bool bVideo = false;
1084 if (!bValid)
1085 {
1086 if (xAudio->getSource() >>= xShape)
1087 {
1088 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
1089 bool bHasMediaURL = xShapeProps->getPropertySetInfo()->hasPropertyByName("MediaURL");
1090 if (bHasMediaURL && (xShapeProps->getPropertyValue("MediaURL") >>= sUrl))
1091 {
1092 bVideo = IsVideoURL(sUrl);
1093 bValid = IsAudioURL(sUrl) || bVideo;
1094 }
1095 }
1096 }
1097
1098 if (!bValid)
1099 return;
1100
1101 if (!xShape.is())
1102 {
1103 mrPowerPointExport.embedEffectAudio(mpFS, sUrl, sRelId, sName);
1104 }
1105
1106 if (bVideo)
1107 {
1108 mpFS->startElementNS(XML_p, XML_video);
1109 mpFS->startElementNS(XML_p, XML_cMediaNode);
1110 }
1111 else
1112 {
1113 bool bNarration = xAudio->getNarration();
1114 mpFS->startElementNS(XML_p, XML_audio, XML_isNarration, bNarration ? "1" : "0");
1115 bool bHideDuringShow = xAudio->getHideDuringShow();
1116 mpFS->startElementNS(XML_p, XML_cMediaNode, XML_showWhenStopped,
1117 bHideDuringShow ? "0" : "1");
1118 }
1119
1120 animations::Timing eTiming{};
1121 bool bLooping
1122 = (xAudio->getRepeatCount() >>= eTiming) && eTiming == animations::Timing_INDEFINITE;
1123 if (bVideo && bLooping)
1124 {
1125 mpFS->startElementNS(XML_p, XML_cTn, XML_repeatCount, "indefinite");
1126 }
1127 else
1128 {
1129 mpFS->startElementNS(XML_p, XML_cTn);
1130 }
1131 WriteAnimationCondList(mpContext->getBeginCondList(), XML_stCondLst);
1132 WriteAnimationCondList(mpContext->getEndCondList(), XML_endCondLst);
1133 mpFS->endElementNS(XML_p, XML_cTn);
1134
1135 mpFS->startElementNS(XML_p, XML_tgtEl);
1136 if (xShape.is())
1137 {
1138 sal_Int32 nShapeID = mrPowerPointExport.GetShapeID(xShape);
1139 mpFS->singleElementNS(XML_p, XML_spTgt, XML_spid, OString::number(nShapeID));
1140 }
1141 else
1142 {
1143 mpFS->singleElementNS(XML_p, XML_sndTgt, FSNS(XML_r, XML_embed),
1144 sax_fastparser::UseIf(sRelId, !sRelId.isEmpty()), XML_name,
1145 sax_fastparser::UseIf(sName, !sUrl.isEmpty()));
1146 }
1147 mpFS->endElementNS(XML_p, XML_tgtEl);
1148
1149 mpFS->endElementNS(XML_p, XML_cMediaNode);
1150 if (bVideo)
1151 {
1152 mpFS->endElementNS(XML_p, XML_video);
1153 }
1154 else
1155 {
1156 mpFS->endElementNS(XML_p, XML_audio);
1157 }
1158}
1159
1161{
1162 const NodeContext* pSavedContext = mpContext;
1163 mpContext = pContext.get();
1164
1165 const Reference<XAnimationNode>& rXNode = getCurrentNode();
1166
1167 SAL_INFO("sd.eppt", "export node type: " << rXNode->getType());
1168 sal_Int32 xmlNodeType = extractNodeType(rXNode);
1169
1170 switch (xmlNodeType)
1171 {
1172 case XML_par:
1173 mpFS->startElementNS(XML_p, xmlNodeType);
1174 WriteAnimationNodeCommonPropsStart();
1175 mpFS->endElementNS(XML_p, xmlNodeType);
1176 break;
1177 case XML_seq:
1178 WriteAnimationNodeSeq();
1179 break;
1180 case XML_animScale:
1181 case XML_animRot:
1182 case XML_anim:
1183 case XML_animMotion:
1184 case XML_animClr:
1185 case XML_set:
1186 WriteAnimationNodeAnimate(xmlNodeType);
1187 break;
1188 case XML_animEffect:
1189 WriteAnimationNodeEffect();
1190 break;
1191 case XML_cmd:
1192 WriteAnimationNodeCommand();
1193 break;
1194 case XML_audio:
1195 WriteAnimationNodeMedia();
1196 break;
1197 default:
1198 SAL_WARN("sd.eppt", "export ooxml node type: " << xmlNodeType);
1199 break;
1200 }
1201
1202 mpContext = pSavedContext;
1203}
1204
1205void PPTXAnimationExport::WriteAnimations(const Reference<XDrawPage>& rXDrawPage)
1206{
1207 Reference<XAnimationNodeSupplier> xNodeSupplier(rXDrawPage, UNO_QUERY);
1208 if (!xNodeSupplier.is())
1209 return;
1210
1211 const Reference<XAnimationNode> xNode(xNodeSupplier->getAnimationNode());
1212 if (!xNode.is())
1213 return;
1214
1215 Reference<XEnumerationAccess> xEnumerationAccess(xNode, UNO_QUERY);
1216 if (!xEnumerationAccess.is())
1217 return;
1218
1219 Reference<XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration();
1220 if (!(xEnumeration.is() && xEnumeration->hasMoreElements()))
1221 return;
1222
1223 auto pNodeContext = std::make_unique<NodeContext>(xNode, false, false);
1224 if (pNodeContext->isValid())
1225 {
1226 mpFS->startElementNS(XML_p, XML_timing);
1227 mpFS->startElementNS(XML_p, XML_tnLst);
1228
1229 WriteAnimationNode(pNodeContext);
1230
1231 mpFS->endElementNS(XML_p, XML_tnLst);
1232 mpFS->endElementNS(XML_p, XML_timing);
1233 }
1234}
1235
1236sal_Int32 PPTXAnimationExport::GetNextAnimationNodeId(const Reference<XAnimationNode>& xNode)
1237{
1238 sal_Int32 nId = mrPowerPointExport.GetNextAnimationNodeID();
1239 maAnimationNodeIdMap[xNode] = nId;
1240 return nId;
1241}
1242
1243sal_Int32 PPTXAnimationExport::GetAnimationNodeId(const Reference<XAnimationNode>& xNode)
1244{
1245 sal_Int32 nId = -1;
1246 const auto& aIter = maAnimationNodeIdMap.find(xNode);
1247 if (aIter != maAnimationNodeIdMap.end())
1248 {
1249 nId = aIter->second;
1250 }
1251 return nId;
1252}
1253
1254/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
OptionalString sType
static const char * FindTransitionName(const sal_Int16 nType, const sal_Int16 nSubType, const bool bDirection)
Any value
OString sFormula
float y
float x
FilterGroup & rTarget
OUString sName
constexpr sal_Int32 FSNS(sal_Int32 namespc, sal_Int32 element)
Environment aTo
Environment aFrom
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
int i
bool endsWithIgnoreAsciiCase(std::u16string_view s1, std::u16string_view s2, std::u16string_view *rest=nullptr)
void WriteAnimations(const FSHelperPtr &pFS, const Reference< XDrawPage > &rXDrawPage, PowerPointExport &rExport)
std::unique_ptr< NodeContext > NodeContextPtr
const ImplAttributeNameConversion * getAttributeConversionList()
SvStream & WriteAnimationNode(SvStream &rOut, AnimationNode const &rNode)
const char * UseIf(const char *s, bool bUse)
std::shared_ptr< FastSerializerHelper > FSHelperPtr
HashMap_OWString_Interface aMap
sal_Int16 nId
DefTokenId nToken
QPRO_FUNC_TYPE nType
ParserContextSharedPtr mpContext
const char * getDelay() const
css::uno::Reference< css::drawing::XShape > mxShape
css::uno::Reference< css::animations::XAnimationNode > mxNode
OUString aCommand
OString I32SHEX(sal_Int32 x)