/src/libreoffice/oox/source/export/drawingml.cxx
Line | Count | Source |
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 <config_features.h> |
21 | | |
22 | | #include <oox/drawingml/diagram/diagramhelper_oox.hxx> |
23 | | #include <config_folders.h> |
24 | | #include <rtl/bootstrap.hxx> |
25 | | #include <sal/log.hxx> |
26 | | #include <oox/core/xmlfilterbase.hxx> |
27 | | #include <oox/export/drawingml.hxx> |
28 | | #include <oox/export/utils.hxx> |
29 | | #include <oox/helper/propertyset.hxx> |
30 | | #include <oox/drawingml/color.hxx> |
31 | | #include <drawingml/fillproperties.hxx> |
32 | | #include <drawingml/fontworkhelpers.hxx> |
33 | | #include <drawingml/textparagraph.hxx> |
34 | | #include <oox/token/namespaces.hxx> |
35 | | #include <oox/token/properties.hxx> |
36 | | #include <oox/token/relationship.hxx> |
37 | | #include <oox/token/tokens.hxx> |
38 | | #include <oox/drawingml/drawingmltypes.hxx> |
39 | | #include <svtools/unitconv.hxx> |
40 | | #include <sax/fastattribs.hxx> |
41 | | #include <comphelper/diagnose_ex.hxx> |
42 | | #include <comphelper/processfactory.hxx> |
43 | | #include <comphelper/string.hxx> |
44 | | #include <i18nlangtag/languagetag.hxx> |
45 | | #include <basegfx/matrix/b2dhommatrixtools.hxx> |
46 | | #include <basegfx/range/b2drange.hxx> |
47 | | #include <basegfx/utils/gradienttools.hxx> |
48 | | |
49 | | #include <numeric> |
50 | | #include <optional> |
51 | | #include <string_view> |
52 | | #include <set> |
53 | | |
54 | | #include <com/sun/star/awt/CharSet.hpp> |
55 | | #include <com/sun/star/awt/FontDescriptor.hpp> |
56 | | #include <com/sun/star/awt/FontSlant.hpp> |
57 | | #include <com/sun/star/awt/FontStrikeout.hpp> |
58 | | #include <com/sun/star/awt/FontWeight.hpp> |
59 | | #include <com/sun/star/awt/FontUnderline.hpp> |
60 | | #include <com/sun/star/awt/Gradient.hpp> |
61 | | #include <com/sun/star/awt/Gradient2.hpp> |
62 | | #include <com/sun/star/beans/XPropertySet.hpp> |
63 | | #include <com/sun/star/beans/XPropertyState.hpp> |
64 | | #include <com/sun/star/beans/XPropertySetInfo.hpp> |
65 | | #include <com/sun/star/container/XEnumerationAccess.hpp> |
66 | | #include <com/sun/star/container/XIndexAccess.hpp> |
67 | | #include <com/sun/star/container/XNameAccess.hpp> |
68 | | #include <com/sun/star/drawing/BitmapMode.hpp> |
69 | | #include <com/sun/star/drawing/ColorMode.hpp> |
70 | | #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> |
71 | | #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> |
72 | | #include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp> |
73 | | #include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp> |
74 | | #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> |
75 | | #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> |
76 | | #include <com/sun/star/drawing/FillStyle.hpp> |
77 | | #include <com/sun/star/drawing/Hatch.hpp> |
78 | | #include <com/sun/star/drawing/LineDash.hpp> |
79 | | #include <com/sun/star/drawing/LineJoint.hpp> |
80 | | #include <com/sun/star/drawing/LineStyle.hpp> |
81 | | #include <com/sun/star/drawing/TextFitToSizeType.hpp> |
82 | | #include <com/sun/star/drawing/TextHorizontalAdjust.hpp> |
83 | | #include <com/sun/star/drawing/TextVerticalAdjust.hpp> |
84 | | #include <com/sun/star/drawing/XShape.hpp> |
85 | | #include <com/sun/star/drawing/XShapes.hpp> |
86 | | #include <com/sun/star/frame/XModel.hpp> |
87 | | #include <com/sun/star/graphic/XGraphic.hpp> |
88 | | #include <com/sun/star/i18n/ScriptType.hpp> |
89 | | #include <com/sun/star/i18n/BreakIterator.hpp> |
90 | | #include <com/sun/star/i18n/XBreakIterator.hpp> |
91 | | #include <com/sun/star/io/XOutputStream.hpp> |
92 | | #include <com/sun/star/lang/XMultiServiceFactory.hpp> |
93 | | #include <com/sun/star/style/LineSpacing.hpp> |
94 | | #include <com/sun/star/style/LineSpacingMode.hpp> |
95 | | #include <com/sun/star/text/WritingMode.hpp> |
96 | | #include <com/sun/star/text/WritingMode2.hpp> |
97 | | #include <com/sun/star/text/GraphicCrop.hpp> |
98 | | #include <com/sun/star/text/XText.hpp> |
99 | | #include <com/sun/star/text/XTextColumns.hpp> |
100 | | #include <com/sun/star/text/XTextContent.hpp> |
101 | | #include <com/sun/star/text/XTextField.hpp> |
102 | | #include <com/sun/star/text/XTextRange.hpp> |
103 | | #include <com/sun/star/text/XTextFrame.hpp> |
104 | | #include <com/sun/star/style/CaseMap.hpp> |
105 | | #include <com/sun/star/xml/dom/XNodeList.hpp> |
106 | | #include <com/sun/star/xml/sax/Writer.hpp> |
107 | | #include <com/sun/star/xml/sax/XSAXSerializable.hpp> |
108 | | #include <com/sun/star/container/XNamed.hpp> |
109 | | #include <com/sun/star/drawing/XDrawPages.hpp> |
110 | | #include <com/sun/star/drawing/XDrawPagesSupplier.hpp> |
111 | | #include <com/sun/star/drawing/RectanglePoint.hpp> |
112 | | |
113 | | #include <comphelper/propertyvalue.hxx> |
114 | | #include <comphelper/sequenceashashmap.hxx> |
115 | | #include <basegfx/units/Length.hxx> |
116 | | #include <comphelper/random.hxx> |
117 | | #include <comphelper/seqstream.hxx> |
118 | | #include <comphelper/storagehelper.hxx> |
119 | | #include <comphelper/xmltools.hxx> |
120 | | #include <o3tl/any.hxx> |
121 | | #include <o3tl/safeint.hxx> |
122 | | #include <o3tl/string_view.hxx> |
123 | | #include <tools/stream.hxx> |
124 | | #include <tools/UnitConversion.hxx> |
125 | | #include <unotools/fontdefs.hxx> |
126 | | #include <vcl/alpha.hxx> |
127 | | #include <vcl/cvtgrf.hxx> |
128 | | #include <vcl/gfxlink.hxx> |
129 | | #include <vcl/svapp.hxx> |
130 | | #include <vcl/outdev.hxx> |
131 | | #include <vcl/embeddedfontsmanager.hxx> |
132 | | #include <vcl/vectorgraphicdata.hxx> |
133 | | #include <rtl/strbuf.hxx> |
134 | | #include <filter/msfilter/escherex.hxx> |
135 | | #include <filter/msfilter/util.hxx> |
136 | | #include <editeng/outlobj.hxx> |
137 | | #include <editeng/svxenum.hxx> |
138 | | #include <editeng/unonames.hxx> |
139 | | #include <editeng/unoprnms.hxx> |
140 | | #include <editeng/flditem.hxx> |
141 | | #include <editeng/escapementitem.hxx> |
142 | | #include <editeng/unonrule.hxx> |
143 | | #include <docmodel/uno/UnoComplexColor.hxx> |
144 | | #include <svx/svdoashp.hxx> |
145 | | #include <svx/svdomedia.hxx> |
146 | | #include <svx/svdtrans.hxx> |
147 | | #include <svx/unoshape.hxx> |
148 | | #include <svx/EnhancedCustomShape2d.hxx> |
149 | | #include <drawingml/presetgeometrynames.hxx> |
150 | | #include <docmodel/uno/UnoGradientTools.hxx> |
151 | | #include <svx/svdpage.hxx> |
152 | | #include <svx/diagram/DiagramHelper_svx.hxx> |
153 | | |
154 | | using namespace ::css; |
155 | | using namespace ::css::beans; |
156 | | using namespace ::css::drawing; |
157 | | using namespace ::css::i18n; |
158 | | using namespace ::css::style; |
159 | | using namespace ::css::text; |
160 | | using namespace ::css::uno; |
161 | | using namespace ::css::container; |
162 | | using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand; |
163 | | |
164 | | using ::css::io::XOutputStream; |
165 | | using ::sax_fastparser::FSHelperPtr; |
166 | | using ::sax_fastparser::FastSerializerHelper; |
167 | | |
168 | | namespace |
169 | | { |
170 | | constexpr auto g_aSchemeColorNames = std::to_array<const char*>({ |
171 | | "dk1", "lt1", "dk2", "lt2", |
172 | | "accent1", "accent2", "accent3", |
173 | | "accent4", "accent5", "accent6", |
174 | | "hlink", "folHlink", |
175 | | }); |
176 | | |
177 | | constexpr auto g_aSchemeColorNamesText = std::to_array<const char*>({ |
178 | | "tx1", "bg1", "tx2", "bg2", |
179 | | "accent1", "accent2", "accent3", |
180 | | "accent4", "accent5", "accent6", |
181 | | "hlink", "folHlink", |
182 | | }); |
183 | | |
184 | | /** converts 1/100mm to the ST_TextSpacingPoint (1/100pt) */ |
185 | | sal_Int64 toTextSpacingPoint(sal_Int64 mm100) |
186 | 0 | { |
187 | 0 | constexpr auto mdToPt = o3tl::getConversionMulDiv(o3tl::Length::mm100, o3tl::Length::pt); |
188 | 0 | constexpr o3tl::detail::m_and_d md(mdToPt.first * 100, mdToPt.second); |
189 | 0 | return o3tl::convert(mm100, md.m, md.d); |
190 | 0 | } |
191 | | } |
192 | | |
193 | | namespace oox::drawingml { |
194 | | |
195 | | URLTransformer::~URLTransformer() |
196 | 0 | { |
197 | 0 | } |
198 | | |
199 | | OUString URLTransformer::getTransformedString(const OUString& rString) const |
200 | 0 | { |
201 | 0 | return rString; |
202 | 0 | } |
203 | | |
204 | | bool URLTransformer::isExternalURL(const OUString& rURL) const |
205 | 0 | { |
206 | 0 | bool bExternal = true; |
207 | 0 | if (rURL.startsWith("#")) |
208 | 0 | bExternal = false; |
209 | 0 | return bExternal; |
210 | 0 | } |
211 | | |
212 | | sal_Int32 URLTransformer::getExternalLinkIndex(const OUString& /*rUrl*/) const |
213 | 0 | { |
214 | 0 | return 0; |
215 | 0 | } |
216 | | |
217 | | GraphicExportCache& GraphicExportCache::get() |
218 | 0 | { |
219 | 0 | static GraphicExportCache staticGraphicExportCache; |
220 | 0 | return staticGraphicExportCache; |
221 | 0 | } |
222 | | |
223 | | static css::uno::Any getLineDash( const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rDashName ) |
224 | 0 | { |
225 | 0 | css::uno::Reference<css::lang::XMultiServiceFactory> xFact(xModel, css::uno::UNO_QUERY); |
226 | 0 | css::uno::Reference<css::container::XNameAccess> xNameAccess( |
227 | 0 | xFact->createInstance(u"com.sun.star.drawing.DashTable"_ustr), |
228 | 0 | css::uno::UNO_QUERY ); |
229 | 0 | if(xNameAccess.is()) |
230 | 0 | { |
231 | 0 | if (!xNameAccess->hasByName(rDashName)) |
232 | 0 | return css::uno::Any(); |
233 | | |
234 | 0 | return xNameAccess->getByName(rDashName); |
235 | 0 | } |
236 | | |
237 | 0 | return css::uno::Any(); |
238 | 0 | } |
239 | | |
240 | | namespace |
241 | | { |
242 | | void WriteGradientPath(const basegfx::BGradient& rBGradient, const FSHelperPtr& pFS, const bool bCircle) |
243 | 0 | { |
244 | 0 | pFS->startElementNS(XML_a, XML_path, XML_path, bCircle ? "circle" : "rect"); |
245 | | |
246 | | // Write the focus rectangle. Work with the focus point, and assume |
247 | | // that it extends 50% in all directions. The below |
248 | | // left/top/right/bottom values are percentages, where 0 means the |
249 | | // edge of the tile rectangle and 100% means the center of it. |
250 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList( |
251 | 0 | sax_fastparser::FastSerializerHelper::createAttrList()); |
252 | 0 | sal_Int32 nLeftPercent = rBGradient.GetXOffset(); |
253 | 0 | pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT)); |
254 | 0 | sal_Int32 nTopPercent = rBGradient.GetYOffset(); |
255 | 0 | pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT)); |
256 | 0 | sal_Int32 nRightPercent = 100 - rBGradient.GetXOffset(); |
257 | 0 | pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT)); |
258 | 0 | sal_Int32 nBottomPercent = 100 - rBGradient.GetYOffset(); |
259 | 0 | pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT)); |
260 | 0 | pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList); |
261 | |
|
262 | 0 | pFS->endElementNS(XML_a, XML_path); |
263 | 0 | } |
264 | | } |
265 | | |
266 | | DrawingML::DrawingML(::sax_fastparser::FSHelperPtr pFS, ::oox::core::XmlFilterBase* pFB, DocumentType eDocumentType, DMLTextExport* pTextExport) |
267 | 0 | : meDocumentType(eDocumentType) |
268 | 0 | , mpTextExport(pTextExport) |
269 | 0 | , mpFS(std::move(pFS)) |
270 | 0 | , mpFB(pFB) |
271 | 0 | , mbIsBackgroundDark(false) |
272 | 0 | , mbPlaceholder(false) |
273 | 0 | , mbDiagaramExport(false) |
274 | 0 | , mbDiagaramReplacementExport(false) |
275 | 0 | , mbDiagramModelTextExport(false) |
276 | 0 | , mnDiagramModelTextParagraph(-1) |
277 | 0 | { |
278 | 0 | uno::Reference<beans::XPropertySet> xSettings(pFB->getModelFactory()->createInstance(u"com.sun.star.document.Settings"_ustr), uno::UNO_QUERY); |
279 | 0 | if (xSettings.is()) |
280 | 0 | { |
281 | 0 | try |
282 | 0 | { |
283 | 0 | xSettings->getPropertyValue(u"EmbedFonts"_ustr) >>= mbEmbedFonts; |
284 | 0 | } |
285 | 0 | catch (Exception& ) |
286 | 0 | { |
287 | 0 | } |
288 | 0 | } |
289 | 0 | } |
290 | | |
291 | | sal_Int16 DrawingML::GetScriptType(const OUString& rStr) |
292 | 0 | { |
293 | 0 | if (rStr.getLength() > 0) |
294 | 0 | { |
295 | 0 | static Reference<css::i18n::XBreakIterator> xBreakIterator = |
296 | 0 | css::i18n::BreakIterator::create(comphelper::getProcessComponentContext()); |
297 | |
|
298 | 0 | sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0); |
299 | |
|
300 | 0 | if (nScriptType == css::i18n::ScriptType::WEAK) |
301 | 0 | { |
302 | 0 | sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType); |
303 | 0 | if (nPos < rStr.getLength()) |
304 | 0 | nScriptType = xBreakIterator->getScriptType(rStr, nPos); |
305 | |
|
306 | 0 | } |
307 | |
|
308 | 0 | if (nScriptType != css::i18n::ScriptType::WEAK) |
309 | 0 | return nScriptType; |
310 | 0 | } |
311 | | |
312 | 0 | return css::i18n::ScriptType::LATIN; |
313 | 0 | } |
314 | | |
315 | | bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName ) |
316 | 0 | { |
317 | 0 | try |
318 | 0 | { |
319 | 0 | mAny = rXPropertySet->getPropertyValue(aName); |
320 | 0 | if (mAny.hasValue()) |
321 | 0 | return true; |
322 | 0 | } |
323 | 0 | catch( const Exception& ) |
324 | 0 | { |
325 | | /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */ |
326 | 0 | } |
327 | 0 | return false; |
328 | 0 | } |
329 | | |
330 | | bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState ) |
331 | 0 | { |
332 | 0 | try |
333 | 0 | { |
334 | 0 | mAny = rXPropertySet->getPropertyValue(aName); |
335 | 0 | if (mAny.hasValue()) |
336 | 0 | { |
337 | 0 | eState = rXPropertyState->getPropertyState(aName); |
338 | 0 | return true; |
339 | 0 | } |
340 | 0 | } |
341 | 0 | catch( const Exception& ) |
342 | 0 | { |
343 | | /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */ |
344 | 0 | } |
345 | 0 | return false; |
346 | 0 | } |
347 | | |
348 | | bool DrawingML::GetDirectProperty( |
349 | | const css::uno::Reference<css::beans::XPropertySet>& rXPropSet, |
350 | | const css::uno::Reference<css::beans::XPropertyState>& rXPropState, const OUString& aName) |
351 | 0 | { |
352 | 0 | PropertyState state; |
353 | 0 | return GetPropertyAndState(rXPropSet, rXPropState, aName, state) |
354 | 0 | && state == beans::PropertyState_DIRECT_VALUE; |
355 | 0 | } |
356 | | |
357 | | namespace |
358 | | { |
359 | | /// Gets hexa value of color on string format. |
360 | | OString getColorStr(const ::Color nColor) |
361 | 0 | { |
362 | | // Transparency is a separate element. |
363 | 0 | return I32SHEX(sal_uInt32(nColor) & 0x00FFFFFF); |
364 | 0 | } |
365 | | } |
366 | | |
367 | | void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha ) |
368 | 0 | { |
369 | 0 | const auto sColor = getColorStr(nColor); |
370 | 0 | if( nAlpha < MAX_PERCENT ) |
371 | 0 | { |
372 | 0 | mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
373 | 0 | assert(nAlpha >= 0); |
374 | 0 | mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); |
375 | 0 | mpFS->endElementNS( XML_a, XML_srgbClr ); |
376 | |
|
377 | 0 | } |
378 | 0 | else |
379 | 0 | { |
380 | 0 | mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
381 | 0 | } |
382 | 0 | } |
383 | | |
384 | | void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
385 | 0 | { |
386 | | // prevent writing a tag with empty val attribute |
387 | 0 | if( sColorSchemeName.isEmpty() ) |
388 | 0 | return; |
389 | | |
390 | 0 | if( aTransformations.hasElements() ) |
391 | 0 | { |
392 | 0 | mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName); |
393 | 0 | WriteColorTransformations( aTransformations, nAlpha ); |
394 | 0 | mpFS->endElementNS( XML_a, XML_schemeClr ); |
395 | 0 | } |
396 | 0 | else if(nAlpha < MAX_PERCENT) |
397 | 0 | { |
398 | 0 | mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName); |
399 | 0 | mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); |
400 | 0 | mpFS->endElementNS( XML_a, XML_schemeClr ); |
401 | 0 | } |
402 | 0 | else |
403 | 0 | { |
404 | 0 | mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName); |
405 | 0 | } |
406 | 0 | } |
407 | | |
408 | | void DrawingML::WriteColor( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
409 | 0 | { |
410 | 0 | const auto sColor = getColorStr(nColor); |
411 | 0 | if( aTransformations.hasElements() ) |
412 | 0 | { |
413 | 0 | mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
414 | 0 | WriteColorTransformations(aTransformations, nAlpha); |
415 | 0 | mpFS->endElementNS(XML_a, XML_srgbClr); |
416 | 0 | } |
417 | 0 | else if(nAlpha < MAX_PERCENT) |
418 | 0 | { |
419 | 0 | mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
420 | 0 | assert(nAlpha >= 0); |
421 | 0 | mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); |
422 | 0 | mpFS->endElementNS(XML_a, XML_srgbClr); |
423 | 0 | } |
424 | 0 | else |
425 | 0 | { |
426 | 0 | mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
427 | 0 | } |
428 | 0 | } |
429 | | |
430 | | void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
431 | 0 | { |
432 | 0 | for( const auto& rTransformation : aTransformations ) |
433 | 0 | { |
434 | 0 | sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name ); |
435 | 0 | if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() ) |
436 | 0 | { |
437 | 0 | if(nToken == XML_alpha && nAlpha < MAX_PERCENT) |
438 | 0 | { |
439 | 0 | assert(nAlpha >= 0); |
440 | 0 | mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha)); |
441 | 0 | } |
442 | 0 | else |
443 | 0 | { |
444 | 0 | sal_Int32 nValue = rTransformation.Value.get<sal_Int32>(); |
445 | 0 | mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue)); |
446 | 0 | } |
447 | 0 | } |
448 | 0 | } |
449 | 0 | } |
450 | | |
451 | | void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha ) |
452 | 0 | { |
453 | 0 | mpFS->startElementNS(XML_a, XML_solidFill); |
454 | 0 | WriteColor( nColor, nAlpha ); |
455 | 0 | mpFS->endElementNS( XML_a, XML_solidFill ); |
456 | 0 | } |
457 | | |
458 | | void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
459 | 0 | { |
460 | 0 | mpFS->startElementNS(XML_a, XML_solidFill); |
461 | 0 | WriteColor( sSchemeName, aTransformations, nAlpha ); |
462 | 0 | mpFS->endElementNS( XML_a, XML_solidFill ); |
463 | 0 | } |
464 | | |
465 | | void DrawingML::WriteSolidFill( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
466 | 0 | { |
467 | 0 | mpFS->startElementNS(XML_a, XML_solidFill); |
468 | 0 | WriteColor(nColor, aTransformations, nAlpha); |
469 | 0 | mpFS->endElementNS(XML_a, XML_solidFill); |
470 | 0 | } |
471 | | |
472 | | void DrawingML::WriteSolidFill( const Reference< XPropertySet >& rXPropSet ) |
473 | 0 | { |
474 | | // get fill color |
475 | 0 | if ( !GetProperty( rXPropSet, u"FillColor"_ustr ) ) |
476 | 0 | return; |
477 | 0 | sal_uInt32 nFillColor = mAny.get<sal_uInt32>(); |
478 | | |
479 | | // get InteropGrabBag and search the relevant attributes |
480 | 0 | OUString sColorFillScheme; |
481 | 0 | sal_uInt32 nOriginalColor = 0; |
482 | 0 | Sequence< PropertyValue > aStyleProperties, aTransformations; |
483 | 0 | if ( GetProperty( rXPropSet, u"InteropGrabBag"_ustr ) ) |
484 | 0 | { |
485 | 0 | Sequence< PropertyValue > aGrabBag; |
486 | 0 | mAny >>= aGrabBag; |
487 | 0 | for (const auto& rProp : aGrabBag) |
488 | 0 | { |
489 | 0 | if( rProp.Name == "SpPrSolidFillSchemeClr" ) |
490 | 0 | rProp.Value >>= sColorFillScheme; |
491 | 0 | else if( rProp.Name == "OriginalSolidFillClr" ) |
492 | 0 | rProp.Value >>= nOriginalColor; |
493 | 0 | else if( rProp.Name == "StyleFillRef" ) |
494 | 0 | rProp.Value >>= aStyleProperties; |
495 | 0 | else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" ) |
496 | 0 | rProp.Value >>= aTransformations; |
497 | 0 | } |
498 | 0 | } |
499 | |
|
500 | 0 | sal_Int32 nAlpha = MAX_PERCENT; |
501 | 0 | if( GetProperty( rXPropSet, u"FillTransparence"_ustr ) ) |
502 | 0 | { |
503 | 0 | sal_Int32 nTransparency = 0; |
504 | 0 | mAny >>= nTransparency; |
505 | | // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) |
506 | 0 | nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); |
507 | 0 | } |
508 | | |
509 | | // OOXML has no separate transparence gradient but uses transparency in the gradient stops. |
510 | | // So we merge transparency and color and use gradient fill in such case. |
511 | 0 | basegfx::BGradient aTransparenceGradient; |
512 | 0 | OUString sFillTransparenceGradientName; |
513 | 0 | bool bNeedGradientFill(false); |
514 | |
|
515 | 0 | if (GetProperty(rXPropSet, u"FillTransparenceGradientName"_ustr) |
516 | 0 | && (mAny >>= sFillTransparenceGradientName) |
517 | 0 | && !sFillTransparenceGradientName.isEmpty() |
518 | 0 | && GetProperty(rXPropSet, u"FillTransparenceGradient"_ustr)) |
519 | 0 | { |
520 | 0 | aTransparenceGradient = model::gradient::getFromAny(mAny); |
521 | 0 | basegfx::BColor aSingleColor; |
522 | 0 | bNeedGradientFill = !aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor); |
523 | | |
524 | | // we no longer need to 'guess' if FillTransparenceGradient is used by |
525 | | // comparing its 1st color to COL_BLACK after having tested that the |
526 | | // FillTransparenceGradientName is set |
527 | 0 | if (!bNeedGradientFill) |
528 | 0 | { |
529 | | // Our alpha is a gray color value. |
530 | 0 | const sal_uInt8 nRed(aSingleColor.getRed() * 255.0); |
531 | | |
532 | | // drawingML alpha is a percentage on a 0..100000 scale. |
533 | 0 | nAlpha = (255 - nRed) * oox::drawingml::MAX_PERCENT / 255; |
534 | 0 | } |
535 | 0 | } |
536 | | |
537 | | // write XML |
538 | 0 | if (bNeedGradientFill) |
539 | 0 | { |
540 | | // no longer create copy/PseudoColorGradient, use new API of |
541 | | // WriteGradientFill to express fix fill color |
542 | 0 | mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); |
543 | 0 | WriteGradientFill(nullptr, nFillColor, &aTransparenceGradient); |
544 | 0 | mpFS->endElementNS( XML_a, XML_gradFill ); |
545 | 0 | } |
546 | 0 | else if ( nFillColor != nOriginalColor ) |
547 | 0 | { |
548 | | // the user has set a different color for the shape |
549 | 0 | if (!WriteSchemeColor(u"FillComplexColor"_ustr, rXPropSet)) |
550 | 0 | { |
551 | 0 | WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha); |
552 | 0 | } |
553 | 0 | } |
554 | | // tdf#91332 LO doesn't export the actual theme.xml in XLSX. |
555 | 0 | else if ( !sColorFillScheme.isEmpty() && GetDocumentType() != DOCUMENT_XLSX ) |
556 | 0 | { |
557 | | // the shape had a scheme color and the user didn't change it |
558 | 0 | WriteSolidFill( sColorFillScheme, aTransformations, nAlpha ); |
559 | 0 | } |
560 | 0 | else |
561 | 0 | { |
562 | | // the shape had a custom color and the user didn't change it |
563 | | // tdf#124013 |
564 | 0 | WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha ); |
565 | 0 | } |
566 | 0 | } |
567 | | |
568 | | bool DrawingML::WriteSchemeColor(OUString const& rPropertyName, const uno::Reference<beans::XPropertySet>& xPropertySet, bool bUseTextSchemeColors) |
569 | 0 | { |
570 | 0 | if (!xPropertySet->getPropertySetInfo()->hasPropertyByName(rPropertyName)) |
571 | 0 | return false; |
572 | | |
573 | 0 | uno::Reference<util::XComplexColor> xComplexColor; |
574 | 0 | xPropertySet->getPropertyValue(rPropertyName) >>= xComplexColor; |
575 | 0 | if (!xComplexColor.is()) |
576 | 0 | return false; |
577 | | |
578 | 0 | auto aComplexColor = model::color::getFromXComplexColor(xComplexColor); |
579 | 0 | if (aComplexColor.getThemeColorType() == model::ThemeColorType::Unknown) |
580 | 0 | return false; |
581 | | |
582 | 0 | auto nThemeColorIndex = sal_Int16(aComplexColor.getThemeColorType()); |
583 | 0 | OString aColorName; |
584 | 0 | if (bUseTextSchemeColors) |
585 | 0 | aColorName = g_aSchemeColorNamesText[nThemeColorIndex]; |
586 | 0 | else |
587 | 0 | aColorName = g_aSchemeColorNames[nThemeColorIndex]; |
588 | |
|
589 | 0 | mpFS->startElementNS(XML_a, XML_solidFill); |
590 | 0 | mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, aColorName); |
591 | |
|
592 | 0 | for (auto const& rTransform : aComplexColor.getTransformations()) |
593 | 0 | { |
594 | 0 | sal_Int32 nElement = XML_none; |
595 | 0 | switch (rTransform.meType) |
596 | 0 | { |
597 | 0 | case model::TransformationType::LumMod: |
598 | 0 | nElement = XML_lumMod; |
599 | 0 | break; |
600 | 0 | case model::TransformationType::LumOff: |
601 | 0 | nElement = XML_lumOff; |
602 | 0 | break; |
603 | 0 | case model::TransformationType::Tint: |
604 | 0 | nElement = XML_tint; |
605 | 0 | break; |
606 | 0 | case model::TransformationType::Shade: |
607 | 0 | nElement = XML_shade; |
608 | 0 | break; |
609 | 0 | default: |
610 | 0 | break; |
611 | 0 | } |
612 | | |
613 | 0 | if (nElement != XML_none) |
614 | 0 | mpFS->singleElementNS(XML_a, nElement, XML_val, OString::number(rTransform.mnValue * 10)); |
615 | 0 | } |
616 | | // Alpha is actually not contained in maTransformations although possible (as of Mar 2023). |
617 | 0 | sal_Int16 nAPITransparency(0); |
618 | 0 | if ((rPropertyName == u"FillComplexColor" && GetProperty(xPropertySet, u"FillTransparence"_ustr)) |
619 | 0 | || (rPropertyName == u"LineComplexColor" && GetProperty(xPropertySet, u"LineTransparence"_ustr)) |
620 | 0 | || (rPropertyName == u"CharComplexColor" && GetProperty(xPropertySet, u"CharTransparence"_ustr))) |
621 | 0 | { |
622 | 0 | mAny >>= nAPITransparency; |
623 | 0 | } |
624 | 0 | if (nAPITransparency != 0) |
625 | 0 | mpFS->singleElementNS(XML_a, XML_alpha, XML_val, |
626 | 0 | OString::number(MAX_PERCENT - (PER_PERCENT * nAPITransparency))); |
627 | |
|
628 | 0 | mpFS->endElementNS(XML_a, XML_schemeClr); |
629 | 0 | mpFS->endElementNS(XML_a, XML_solidFill); |
630 | |
|
631 | 0 | return true; |
632 | 0 | } |
633 | | |
634 | | void DrawingML::WriteGradientStop(double fOffset, const basegfx::BColor& rColor, const basegfx::BColor& rAlpha) |
635 | 0 | { |
636 | 0 | mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(basegfx::fround64(fOffset * 100000))); |
637 | 0 | WriteColor( |
638 | 0 | ::Color(rColor), |
639 | 0 | basegfx::fround((1.0 - rAlpha.luminance()) * oox::drawingml::MAX_PERCENT)); |
640 | 0 | mpFS->endElementNS( XML_a, XML_gs ); |
641 | 0 | } |
642 | | |
643 | | void DrawingML::WriteGradientFill( const Reference< XPropertySet >& rXPropSet ) |
644 | 0 | { |
645 | 0 | if (!GetProperty(rXPropSet, u"FillGradient"_ustr)) |
646 | 0 | return; |
647 | | |
648 | | // use BGradient constructor directly, it will take care of Gradient/Gradient2 |
649 | 0 | basegfx::BGradient aGradient = model::gradient::getFromAny(mAny); |
650 | | |
651 | | // get InteropGrabBag and search the relevant attributes |
652 | 0 | basegfx::BGradient aOriginalGradient; |
653 | 0 | Sequence< PropertyValue > aGradientStops; |
654 | 0 | if ( GetProperty( rXPropSet, u"InteropGrabBag"_ustr ) ) |
655 | 0 | { |
656 | 0 | Sequence< PropertyValue > aGrabBag; |
657 | 0 | mAny >>= aGrabBag; |
658 | 0 | for (const auto& rProp : aGrabBag) |
659 | 0 | if( rProp.Name == "GradFillDefinition" ) |
660 | 0 | rProp.Value >>= aGradientStops; |
661 | 0 | else if( rProp.Name == "OriginalGradFill" ) |
662 | 0 | aOriginalGradient = model::gradient::getFromAny(rProp.Value); |
663 | 0 | } |
664 | | |
665 | | // check if an ooxml gradient had been imported and if the user has modified it |
666 | | // Gradient grab-bag depends on theme grab-bag, which is implemented |
667 | | // only for DOCX. |
668 | 0 | if (aOriginalGradient == aGradient && GetDocumentType() == DOCUMENT_DOCX) |
669 | 0 | { |
670 | | // If we have no gradient stops that means original gradient was defined by a theme. |
671 | 0 | if( aGradientStops.hasElements() ) |
672 | 0 | { |
673 | 0 | mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); |
674 | 0 | WriteGrabBagGradientFill(aGradientStops, aGradient); |
675 | 0 | mpFS->endElementNS( XML_a, XML_gradFill ); |
676 | 0 | } |
677 | 0 | } |
678 | 0 | else |
679 | 0 | { |
680 | 0 | mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); |
681 | |
|
682 | 0 | basegfx::BGradient aTransparenceGradient; |
683 | 0 | basegfx::BGradient* pTransparenceGradient(nullptr); |
684 | 0 | double fTransparency(0.0); |
685 | 0 | OUString sFillTransparenceGradientName; |
686 | |
|
687 | 0 | if (GetProperty(rXPropSet, u"FillTransparenceGradientName"_ustr) |
688 | 0 | && (mAny >>= sFillTransparenceGradientName) |
689 | 0 | && !sFillTransparenceGradientName.isEmpty() |
690 | 0 | && GetProperty(rXPropSet, u"FillTransparenceGradient"_ustr)) |
691 | 0 | { |
692 | | // TransparenceGradient is only used when name is not empty |
693 | 0 | aTransparenceGradient = model::gradient::getFromAny(mAny); |
694 | 0 | pTransparenceGradient = &aTransparenceGradient; |
695 | 0 | } |
696 | 0 | else if (GetProperty(rXPropSet, u"FillTransparence"_ustr)) |
697 | 0 | { |
698 | | // no longer create PseudoTransparencyGradient, use new API of |
699 | | // WriteGradientFill to express fix transparency |
700 | 0 | sal_Int32 nTransparency(0); |
701 | 0 | mAny >>= nTransparency; |
702 | | // nTransparency is [0..100]% |
703 | 0 | fTransparency = nTransparency * 0.01; |
704 | 0 | } |
705 | | |
706 | | // tdf#155852 The gradient might wrongly have StepCount==0, as the draw:gradient-step-count |
707 | | // attribute in ODF does not belong to the gradient definition but is an attribute in |
708 | | // the graphic style of the shape. |
709 | 0 | if (GetProperty(rXPropSet, u"FillGradientStepCount"_ustr)) |
710 | 0 | { |
711 | 0 | sal_Int16 nStepCount = 0; |
712 | 0 | mAny >>= nStepCount; |
713 | 0 | aGradient.SetSteps(nStepCount); |
714 | 0 | } |
715 | |
|
716 | 0 | WriteGradientFill(&aGradient, 0, pTransparenceGradient, fTransparency); |
717 | |
|
718 | 0 | mpFS->endElementNS(XML_a, XML_gradFill); |
719 | 0 | } |
720 | 0 | } |
721 | | |
722 | | void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, const basegfx::BGradient& rBGradient ) |
723 | 0 | { |
724 | | // write back the original gradient |
725 | 0 | mpFS->startElementNS(XML_a, XML_gsLst); |
726 | | |
727 | | // get original stops and write them |
728 | 0 | for( const auto& rGradientStop : aGradientStops ) |
729 | 0 | { |
730 | 0 | Sequence< PropertyValue > aGradientStop; |
731 | 0 | rGradientStop.Value >>= aGradientStop; |
732 | | |
733 | | // get values |
734 | 0 | OUString sSchemeClr; |
735 | 0 | double nPos = 0; |
736 | 0 | sal_Int16 nTransparency = 0; |
737 | 0 | ::Color nRgbClr; |
738 | 0 | Sequence< PropertyValue > aTransformations; |
739 | 0 | for (const auto& rProp : aGradientStop) |
740 | 0 | { |
741 | 0 | if( rProp.Name == "SchemeClr" ) |
742 | 0 | rProp.Value >>= sSchemeClr; |
743 | 0 | else if( rProp.Name == "RgbClr" ) |
744 | 0 | rProp.Value >>= nRgbClr; |
745 | 0 | else if( rProp.Name == "Pos" ) |
746 | 0 | rProp.Value >>= nPos; |
747 | 0 | else if( rProp.Name == "Transparency" ) |
748 | 0 | rProp.Value >>= nTransparency; |
749 | 0 | else if( rProp.Name == "Transformations" ) |
750 | 0 | rProp.Value >>= aTransformations; |
751 | 0 | } |
752 | | // write stop |
753 | 0 | mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0)); |
754 | 0 | if( sSchemeClr.isEmpty() ) |
755 | 0 | { |
756 | | // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) |
757 | 0 | sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency ); |
758 | 0 | WriteColor( nRgbClr, nAlpha ); |
759 | 0 | } |
760 | 0 | else |
761 | 0 | { |
762 | 0 | WriteColor( sSchemeClr, aTransformations ); |
763 | 0 | } |
764 | 0 | mpFS->endElementNS( XML_a, XML_gs ); |
765 | 0 | } |
766 | 0 | mpFS->endElementNS( XML_a, XML_gsLst ); |
767 | |
|
768 | 0 | switch (rBGradient.GetGradientStyle()) |
769 | 0 | { |
770 | 0 | default: |
771 | 0 | { |
772 | 0 | const sal_Int16 nAngle(rBGradient.GetAngle()); |
773 | 0 | mpFS->singleElementNS( |
774 | 0 | XML_a, XML_lin, XML_ang, |
775 | 0 | OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000)); |
776 | 0 | break; |
777 | 0 | } |
778 | 0 | case awt::GradientStyle_RADIAL: |
779 | 0 | { |
780 | 0 | WriteGradientPath(rBGradient, mpFS, true); |
781 | 0 | break; |
782 | 0 | } |
783 | 0 | } |
784 | 0 | } |
785 | | |
786 | | void DrawingML::WriteGradientFill( |
787 | | const basegfx::BGradient* pColorGradient, sal_Int32 nFixColor, |
788 | | const basegfx::BGradient* pTransparenceGradient, double fFixTransparence) |
789 | 0 | { |
790 | 0 | basegfx::BColorStops aColorStops; |
791 | 0 | basegfx::BColorStops aAlphaStops; |
792 | 0 | basegfx::BColor aSingleColor(::Color(ColorTransparency, nFixColor).getBColor()); |
793 | 0 | basegfx::BColor aSingleAlpha(fFixTransparence); |
794 | 0 | const basegfx::BGradient* pGradient(pColorGradient); |
795 | |
|
796 | 0 | if (nullptr != pColorGradient) |
797 | 0 | { |
798 | | // extract and correct/process ColorStops |
799 | 0 | basegfx::utils::prepareColorStops(*pColorGradient, aColorStops, aSingleColor); |
800 | | |
801 | | // tdf#155827 Convert 'axial' to 'linear' before synchronize and for each gradient separate. |
802 | 0 | if (aColorStops.size() > 0 && awt::GradientStyle_AXIAL == pColorGradient->GetGradientStyle()) |
803 | 0 | aColorStops.doApplyAxial(); |
804 | 0 | } |
805 | 0 | if (nullptr != pTransparenceGradient) |
806 | 0 | { |
807 | | // remember basic Gradient definition to use |
808 | | // So we can get the gradient geometry in any case from pGradient. |
809 | 0 | if (nullptr == pGradient) |
810 | 0 | { |
811 | 0 | pGradient = pTransparenceGradient; |
812 | 0 | } |
813 | | |
814 | | // extract and correct/process AlphaStops |
815 | 0 | basegfx::utils::prepareColorStops(*pTransparenceGradient, aAlphaStops, aSingleAlpha); |
816 | 0 | if (aAlphaStops.size() > 0 |
817 | 0 | && awt::GradientStyle_AXIAL == pTransparenceGradient->GetGradientStyle()) |
818 | 0 | { |
819 | 0 | aAlphaStops.doApplyAxial(); |
820 | 0 | } |
821 | 0 | } |
822 | |
|
823 | 0 | if (nullptr == pGradient) |
824 | 0 | { |
825 | | // an error - see comment in header - is to give neither pColorGradient |
826 | | // nor pTransparenceGradient |
827 | 0 | assert(false && "pColorGradient or pTransparenceGradient should be set"); |
828 | 0 | return; |
829 | 0 | } |
830 | | |
831 | | // Apply steps if used. That increases the number of stops and thus needs to be done before |
832 | | // synchronize. |
833 | 0 | if (pGradient->GetSteps()) |
834 | 0 | { |
835 | 0 | aColorStops.doApplySteps(pGradient->GetSteps()); |
836 | | // transparency gradients are always automatic, so do not have steps. |
837 | 0 | } |
838 | | |
839 | | // synchronize ColorStops and AlphaStops as preparation to export |
840 | | // so also gradients 'coupled' indirectly using the 'FillTransparenceGradient' |
841 | | // method (at import time) will be exported again. |
842 | 0 | basegfx::utils::synchronizeColorStops(aColorStops, aAlphaStops, aSingleColor, aSingleAlpha); |
843 | |
|
844 | 0 | if (aColorStops.size() != aAlphaStops.size()) |
845 | 0 | { |
846 | | // this is an error - synchronizeColorStops above *has* to create that |
847 | | // state, see description there (!) |
848 | 0 | assert(false && "oox::WriteGradientFill: non-synchronized gradients (!)"); |
849 | 0 | return; |
850 | 0 | } |
851 | | |
852 | 0 | bool bLinearOrAxial(awt::GradientStyle_LINEAR == pGradient->GetGradientStyle() |
853 | 0 | || awt::GradientStyle_AXIAL == pGradient->GetGradientStyle()); |
854 | 0 | if (!bLinearOrAxial) |
855 | 0 | { |
856 | | // case awt::GradientStyle_RADIAL: |
857 | | // case awt::GradientStyle_ELLIPTICAL: |
858 | | // case awt::GradientStyle_RECT: |
859 | | // case awt::GradientStyle_SQUARE: |
860 | | // all these types need the gradients to be mirrored |
861 | 0 | aColorStops.reverseColorStops(); |
862 | 0 | aAlphaStops.reverseColorStops(); |
863 | 0 | } |
864 | | |
865 | | // If there were one stop, prepareColorStops() method would have cleared aColorStops, same for |
866 | | // aAlphaStops. In case of empty stops vectors synchronizeColorStops() method creates two stops |
867 | | // for each. So at this point we have at least two stops and can fulfill the requirement of |
868 | | // <gsLst> element to have at least two child elements. |
869 | | |
870 | | // export GradientStops (with alpha) |
871 | 0 | mpFS->startElementNS(XML_a, XML_gsLst); |
872 | |
|
873 | 0 | basegfx::BColorStops::const_iterator aCurrColor(aColorStops.begin()); |
874 | 0 | basegfx::BColorStops::const_iterator aCurrAlpha(aAlphaStops.begin()); |
875 | |
|
876 | 0 | while (aCurrColor != aColorStops.end() && aCurrAlpha != aAlphaStops.end()) |
877 | 0 | { |
878 | 0 | WriteGradientStop( |
879 | 0 | aCurrColor->getStopOffset(), |
880 | 0 | aCurrColor->getStopColor(), |
881 | 0 | aCurrAlpha->getStopColor()); |
882 | 0 | aCurrColor++; |
883 | 0 | aCurrAlpha++; |
884 | 0 | } |
885 | |
|
886 | 0 | mpFS->endElementNS( XML_a, XML_gsLst ); |
887 | |
|
888 | 0 | if (bLinearOrAxial) |
889 | 0 | { |
890 | | // CT_LinearShadeProperties, cases where gradient rotation has to be exported |
891 | | // 'scaled' does not exist in LO, so only 'ang'. |
892 | 0 | const sal_Int16 nAngle(pGradient->GetAngle()); |
893 | 0 | mpFS->singleElementNS( |
894 | 0 | XML_a, XML_lin, XML_ang, |
895 | 0 | OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000)); |
896 | 0 | } |
897 | 0 | else |
898 | 0 | { |
899 | | // CT_PathShadeProperties, cases where gradient path has to be exported |
900 | | // Concentric fill is not yet implemented, therefore no type 'shape', only 'circle' or 'rect' |
901 | 0 | const bool bCircle(pGradient->GetGradientStyle() == awt::GradientStyle_RADIAL || |
902 | 0 | pGradient->GetGradientStyle() == awt::GradientStyle_ELLIPTICAL); |
903 | 0 | WriteGradientPath(*pGradient, mpFS, bCircle); |
904 | 0 | } |
905 | 0 | } |
906 | | |
907 | | void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart ) |
908 | 0 | { |
909 | 0 | ESCHER_LineEnd eLineEnd; |
910 | 0 | sal_Int32 nArrowLength; |
911 | 0 | sal_Int32 nArrowWidth; |
912 | |
|
913 | 0 | if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) ) |
914 | 0 | return; |
915 | | |
916 | 0 | const char* len; |
917 | 0 | const char* type; |
918 | 0 | const char* width; |
919 | |
|
920 | 0 | switch( nArrowLength ) |
921 | 0 | { |
922 | 0 | case ESCHER_LineShortArrow: |
923 | 0 | len = "sm"; |
924 | 0 | break; |
925 | 0 | default: |
926 | 0 | case ESCHER_LineMediumLenArrow: |
927 | 0 | len = "med"; |
928 | 0 | break; |
929 | 0 | case ESCHER_LineLongArrow: |
930 | 0 | len = "lg"; |
931 | 0 | break; |
932 | 0 | } |
933 | | |
934 | 0 | switch( eLineEnd ) |
935 | 0 | { |
936 | 0 | default: |
937 | 0 | case ESCHER_LineNoEnd: |
938 | 0 | type = "none"; |
939 | 0 | break; |
940 | 0 | case ESCHER_LineArrowEnd: |
941 | 0 | type = "triangle"; |
942 | 0 | break; |
943 | 0 | case ESCHER_LineArrowStealthEnd: |
944 | 0 | type = "stealth"; |
945 | 0 | break; |
946 | 0 | case ESCHER_LineArrowDiamondEnd: |
947 | 0 | type = "diamond"; |
948 | 0 | break; |
949 | 0 | case ESCHER_LineArrowOvalEnd: |
950 | 0 | type = "oval"; |
951 | 0 | break; |
952 | 0 | case ESCHER_LineArrowOpenEnd: |
953 | 0 | type = "arrow"; |
954 | 0 | break; |
955 | 0 | } |
956 | | |
957 | 0 | switch( nArrowWidth ) |
958 | 0 | { |
959 | 0 | case ESCHER_LineNarrowArrow: |
960 | 0 | width = "sm"; |
961 | 0 | break; |
962 | 0 | default: |
963 | 0 | case ESCHER_LineMediumWidthArrow: |
964 | 0 | width = "med"; |
965 | 0 | break; |
966 | 0 | case ESCHER_LineWideArrow: |
967 | 0 | width = "lg"; |
968 | 0 | break; |
969 | 0 | } |
970 | | |
971 | 0 | mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd, |
972 | 0 | XML_len, len, |
973 | 0 | XML_type, type, |
974 | 0 | XML_w, width ); |
975 | 0 | } |
976 | | |
977 | | void DrawingML::WriteOutline( const Reference<XPropertySet>& rXPropSet, Reference< frame::XModel > const & xModel ) |
978 | 0 | { |
979 | 0 | drawing::LineStyle aLineStyle( drawing::LineStyle_NONE ); |
980 | 0 | if (GetProperty(rXPropSet, u"LineStyle"_ustr)) |
981 | 0 | mAny >>= aLineStyle; |
982 | |
|
983 | 0 | const LineCap aLineCap = GetProperty(rXPropSet, u"LineCap"_ustr) ? mAny.get<drawing::LineCap>() : LineCap_BUTT; |
984 | |
|
985 | 0 | sal_uInt32 nLineWidth = 0; |
986 | 0 | sal_uInt32 nEmuLineWidth = 0; |
987 | 0 | ::Color nColor; |
988 | 0 | sal_Int32 nColorAlpha = MAX_PERCENT; |
989 | 0 | bool bColorSet = false; |
990 | 0 | const char* cap = nullptr; |
991 | 0 | drawing::LineDash aLineDash; |
992 | 0 | bool bDashSet = false; |
993 | 0 | bool bNoFill = false; |
994 | | |
995 | | |
996 | | // get InteropGrabBag and search the relevant attributes |
997 | 0 | OUString sColorFillScheme; |
998 | 0 | ::Color aResolvedColorFillScheme; |
999 | |
|
1000 | 0 | ::Color nOriginalColor; |
1001 | 0 | ::Color nStyleColor; |
1002 | 0 | sal_uInt32 nStyleLineWidth = 0; |
1003 | |
|
1004 | 0 | Sequence<PropertyValue> aStyleProperties; |
1005 | 0 | Sequence<PropertyValue> aTransformations; |
1006 | |
|
1007 | 0 | drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE); |
1008 | 0 | drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE); |
1009 | |
|
1010 | 0 | if (GetProperty(rXPropSet, u"InteropGrabBag"_ustr)) |
1011 | 0 | { |
1012 | 0 | Sequence<PropertyValue> aGrabBag; |
1013 | 0 | mAny >>= aGrabBag; |
1014 | |
|
1015 | 0 | for (const auto& rProp : aGrabBag) |
1016 | 0 | { |
1017 | 0 | if( rProp.Name == "SpPrLnSolidFillSchemeClr" ) |
1018 | 0 | rProp.Value >>= sColorFillScheme; |
1019 | 0 | if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" ) |
1020 | 0 | rProp.Value >>= aResolvedColorFillScheme; |
1021 | 0 | else if( rProp.Name == "OriginalLnSolidFillClr" ) |
1022 | 0 | rProp.Value >>= nOriginalColor; |
1023 | 0 | else if( rProp.Name == "StyleLnRef" ) |
1024 | 0 | rProp.Value >>= aStyleProperties; |
1025 | 0 | else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" ) |
1026 | 0 | rProp.Value >>= aTransformations; |
1027 | 0 | else if( rProp.Name == "EmuLineWidth" ) |
1028 | 0 | rProp.Value >>= nEmuLineWidth; |
1029 | 0 | } |
1030 | 0 | for (const auto& rStyleProp : aStyleProperties) |
1031 | 0 | { |
1032 | 0 | if( rStyleProp.Name == "Color" ) |
1033 | 0 | rStyleProp.Value >>= nStyleColor; |
1034 | 0 | else if( rStyleProp.Name == "LineStyle" ) |
1035 | 0 | rStyleProp.Value >>= aStyleLineStyle; |
1036 | 0 | else if( rStyleProp.Name == "LineJoint" ) |
1037 | 0 | rStyleProp.Value >>= aStyleLineJoint; |
1038 | 0 | else if( rStyleProp.Name == "LineWidth" ) |
1039 | 0 | rStyleProp.Value >>= nStyleLineWidth; |
1040 | 0 | } |
1041 | 0 | } |
1042 | |
|
1043 | 0 | if (GetProperty(rXPropSet, u"LineWidth"_ustr)) |
1044 | 0 | mAny >>= nLineWidth; |
1045 | |
|
1046 | 0 | switch (aLineStyle) |
1047 | 0 | { |
1048 | 0 | case drawing::LineStyle_NONE: |
1049 | 0 | bNoFill = true; |
1050 | 0 | break; |
1051 | 0 | case drawing::LineStyle_DASH: |
1052 | 0 | if (GetProperty(rXPropSet, u"LineDash"_ustr)) |
1053 | 0 | { |
1054 | 0 | aLineDash = mAny.get<drawing::LineDash>(); |
1055 | | //this query is good for shapes, but in the case of charts it returns 0 values |
1056 | 0 | if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) { |
1057 | 0 | OUString aLineDashName; |
1058 | 0 | if (GetProperty(rXPropSet, u"LineDashName"_ustr)) |
1059 | 0 | mAny >>= aLineDashName; |
1060 | 0 | if (!aLineDashName.isEmpty() && xModel) { |
1061 | 0 | css::uno::Any aAny = getLineDash(xModel, aLineDashName); |
1062 | 0 | aAny >>= aLineDash; |
1063 | 0 | } |
1064 | 0 | } |
1065 | 0 | } |
1066 | 0 | else |
1067 | 0 | { |
1068 | | //export the linestyle of chart wall (plot area) and chart page |
1069 | 0 | OUString aLineDashName; |
1070 | 0 | if (GetProperty(rXPropSet, u"LineDashName"_ustr)) |
1071 | 0 | mAny >>= aLineDashName; |
1072 | 0 | if (!aLineDashName.isEmpty() && xModel) { |
1073 | 0 | css::uno::Any aAny = getLineDash(xModel, aLineDashName); |
1074 | 0 | aAny >>= aLineDash; |
1075 | 0 | } |
1076 | 0 | } |
1077 | 0 | bDashSet = true; |
1078 | 0 | if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE) |
1079 | 0 | { |
1080 | 0 | cap = "rnd"; |
1081 | 0 | } |
1082 | |
|
1083 | 0 | SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes |
1084 | 0 | << " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance); |
1085 | | |
1086 | 0 | [[fallthrough]]; |
1087 | 0 | case drawing::LineStyle_SOLID: |
1088 | 0 | default: |
1089 | 0 | if (GetProperty(rXPropSet, u"LineColor"_ustr)) |
1090 | 0 | { |
1091 | 0 | nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff); |
1092 | 0 | bColorSet = true; |
1093 | 0 | } |
1094 | 0 | if (GetProperty(rXPropSet, u"LineTransparence"_ustr)) |
1095 | 0 | { |
1096 | 0 | nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT); |
1097 | 0 | } |
1098 | 0 | if (aLineCap == LineCap_ROUND) |
1099 | 0 | cap = "rnd"; |
1100 | 0 | else if (aLineCap == LineCap_SQUARE) |
1101 | 0 | cap = "sq"; |
1102 | 0 | break; |
1103 | 0 | } |
1104 | | |
1105 | | // if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error |
1106 | 0 | if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth) |
1107 | 0 | nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth); |
1108 | 0 | mpFS->startElementNS( XML_a, XML_ln, |
1109 | 0 | XML_cap, cap, |
1110 | 0 | XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth), |
1111 | 0 | nLineWidth == 0 || GetDocumentType() == DOCUMENT_XLSX // tdf#119565 LO doesn't export the actual theme.xml in XLSX. |
1112 | 0 | || (nLineWidth > 1 && nStyleLineWidth != nLineWidth))); |
1113 | |
|
1114 | 0 | if( bColorSet ) |
1115 | 0 | { |
1116 | 0 | if( nColor != nOriginalColor ) |
1117 | 0 | { |
1118 | | // the user has set a different color for the line |
1119 | 0 | if (!WriteSchemeColor(u"LineComplexColor"_ustr, rXPropSet)) |
1120 | 0 | WriteSolidFill(nColor, nColorAlpha); |
1121 | 0 | } |
1122 | 0 | else if( !sColorFillScheme.isEmpty() ) |
1123 | 0 | { |
1124 | | // the line had a scheme color and the user didn't change it |
1125 | 0 | WriteSolidFill( aResolvedColorFillScheme, aTransformations ); |
1126 | 0 | } |
1127 | 0 | else |
1128 | 0 | { |
1129 | 0 | WriteSolidFill( nColor, nColorAlpha ); |
1130 | 0 | } |
1131 | 0 | } |
1132 | |
|
1133 | 0 | if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH ) |
1134 | 0 | { |
1135 | | // Try to detect if it might come from ms preset line style import. |
1136 | | // MS Office styles are always relative, both binary and OOXML. |
1137 | | // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles |
1138 | | // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49 |
1139 | | // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019). |
1140 | 0 | bool bIsConverted = false; |
1141 | |
|
1142 | 0 | bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE); |
1143 | 0 | if ( bIsRelative && aLineDash.Dots == 1) |
1144 | 0 | { // The lengths were tweaked on import in case of prstDash. Revert it here. |
1145 | 0 | sal_uInt32 nDotLen = aLineDash.DotLen; |
1146 | 0 | sal_uInt32 nDashLen = aLineDash.DashLen; |
1147 | 0 | sal_uInt32 nDistance = aLineDash.Distance; |
1148 | 0 | if (aLineCap != LineCap_BUTT && nDistance >= 99) |
1149 | 0 | { |
1150 | 0 | nDistance -= 99; |
1151 | 0 | nDotLen += 99; |
1152 | 0 | if (nDashLen > 0) |
1153 | 0 | nDashLen += 99; |
1154 | 0 | } |
1155 | | // LO uses length 0 for 100%, if the attribute is missing in ODF. |
1156 | | // Other applications might write 100%. Make it unique for the conditions. |
1157 | 0 | if (nDotLen == 0) |
1158 | 0 | nDotLen = 100; |
1159 | 0 | if (nDashLen == 0 && aLineDash.Dashes > 0) |
1160 | 0 | nDashLen = 100; |
1161 | 0 | bIsConverted = true; |
1162 | 0 | if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) |
1163 | 0 | { |
1164 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot"); |
1165 | 0 | } |
1166 | 0 | else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) |
1167 | 0 | { |
1168 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash"); |
1169 | 0 | } |
1170 | 0 | else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) |
1171 | 0 | { |
1172 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot"); |
1173 | 0 | } |
1174 | 0 | else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) |
1175 | 0 | { |
1176 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash"); |
1177 | 0 | } |
1178 | 0 | else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) |
1179 | 0 | { |
1180 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot"); |
1181 | 0 | } |
1182 | 0 | else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300) |
1183 | 0 | { |
1184 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot"); |
1185 | 0 | } |
1186 | 0 | else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) |
1187 | 0 | { |
1188 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot"); |
1189 | 0 | } |
1190 | 0 | else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) |
1191 | 0 | { |
1192 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash"); |
1193 | 0 | } |
1194 | 0 | else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100) |
1195 | 0 | { |
1196 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot"); |
1197 | 0 | } |
1198 | 0 | else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100) |
1199 | 0 | { |
1200 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot"); |
1201 | 0 | } |
1202 | 0 | else |
1203 | 0 | bIsConverted = false; |
1204 | 0 | } |
1205 | | // Do not map our own line styles to OOXML prstDash values, because custDash gives better results. |
1206 | 0 | if (!bIsConverted) |
1207 | 0 | { |
1208 | 0 | mpFS->startElementNS(XML_a, XML_custDash); |
1209 | | // In case of hairline we would need the current pixel size. Instead use a reasonable |
1210 | | // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx. |
1211 | | // (And it makes sure fLineWidth is not zero in below division.) |
1212 | 0 | double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95; |
1213 | 0 | int i; |
1214 | 0 | double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth; |
1215 | | // LO uses line width, in case Distance is zero. MS Office would use a space of zero length. |
1216 | | // So set 100% explicitly. |
1217 | 0 | if (aLineDash.Distance <= 0) |
1218 | 0 | fSp = 100.0; |
1219 | | // In case of custDash, round caps are included in dash length in MS Office. Square caps are added |
1220 | | // to dash length, same as in ODF. Change the length values accordingly. |
1221 | 0 | if (aLineCap == LineCap_ROUND && fSp > 99.0) |
1222 | 0 | fSp -= 99.0; |
1223 | |
|
1224 | 0 | if (aLineDash.Dots > 0) |
1225 | 0 | { |
1226 | 0 | double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth; |
1227 | | // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. |
1228 | 0 | if (aLineDash.DotLen == 0) |
1229 | 0 | fD = 100.0; |
1230 | | // Tweak dash length, see above. |
1231 | 0 | if (aLineCap == LineCap_ROUND && fSp > 99.0) |
1232 | 0 | fD += 99.0; |
1233 | |
|
1234 | 0 | for( i = 0; i < aLineDash.Dots; i ++ ) |
1235 | 0 | { |
1236 | 0 | mpFS->singleElementNS( XML_a, XML_ds, |
1237 | 0 | XML_d , write1000thOfAPercent(fD), |
1238 | 0 | XML_sp, write1000thOfAPercent(fSp) ); |
1239 | 0 | } |
1240 | 0 | } |
1241 | 0 | if ( aLineDash.Dashes > 0 ) |
1242 | 0 | { |
1243 | 0 | double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth; |
1244 | | // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. |
1245 | 0 | if (aLineDash.DashLen == 0) |
1246 | 0 | fD = 100.0; |
1247 | | // Tweak dash length, see above. |
1248 | 0 | if (aLineCap == LineCap_ROUND && fSp > 99.0) |
1249 | 0 | fD += 99.0; |
1250 | |
|
1251 | 0 | for( i = 0; i < aLineDash.Dashes; i ++ ) |
1252 | 0 | { |
1253 | 0 | mpFS->singleElementNS( XML_a , XML_ds, |
1254 | 0 | XML_d , write1000thOfAPercent(fD), |
1255 | 0 | XML_sp, write1000thOfAPercent(fSp) ); |
1256 | 0 | } |
1257 | 0 | } |
1258 | |
|
1259 | 0 | SAL_WARN_IF(nLineWidth <= 0, |
1260 | 0 | "oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth); |
1261 | 0 | SAL_WARN_IF(aLineDash.Dashes < 0, |
1262 | 0 | "oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes); |
1263 | 0 | SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0, |
1264 | 0 | "oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen); |
1265 | 0 | SAL_WARN_IF(aLineDash.Dots < 0, |
1266 | 0 | "oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots); |
1267 | 0 | SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0, |
1268 | 0 | "oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen); |
1269 | 0 | SAL_WARN_IF(aLineDash.Distance <= 0, |
1270 | 0 | "oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance); |
1271 | | |
1272 | 0 | mpFS->endElementNS( XML_a, XML_custDash ); |
1273 | 0 | } |
1274 | 0 | } |
1275 | | |
1276 | 0 | if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, u"LineJoint"_ustr)) |
1277 | 0 | { |
1278 | 0 | LineJoint eLineJoint = mAny.get<LineJoint>(); |
1279 | | |
1280 | | // tdf#119565 LO doesn't export the actual theme.xml in XLSX. |
1281 | 0 | if (aStyleLineJoint == LineJoint_NONE || GetDocumentType() == DOCUMENT_XLSX |
1282 | 0 | || aStyleLineJoint != eLineJoint) |
1283 | 0 | { |
1284 | | // style-defined line joint does not exist, or is different from the shape's joint |
1285 | 0 | switch( eLineJoint ) |
1286 | 0 | { |
1287 | 0 | case LineJoint_NONE: |
1288 | 0 | case LineJoint_BEVEL: |
1289 | 0 | mpFS->singleElementNS(XML_a, XML_bevel); |
1290 | 0 | break; |
1291 | 0 | default: |
1292 | 0 | case LineJoint_MIDDLE: |
1293 | 0 | case LineJoint_MITER: |
1294 | 0 | mpFS->singleElementNS(XML_a, XML_miter); |
1295 | 0 | break; |
1296 | 0 | case LineJoint_ROUND: |
1297 | 0 | mpFS->singleElementNS(XML_a, XML_round); |
1298 | 0 | break; |
1299 | 0 | } |
1300 | 0 | } |
1301 | 0 | } |
1302 | | |
1303 | 0 | if( !bNoFill ) |
1304 | 0 | { |
1305 | 0 | WriteLineArrow( rXPropSet, true ); |
1306 | 0 | WriteLineArrow( rXPropSet, false ); |
1307 | 0 | } |
1308 | 0 | else |
1309 | 0 | { |
1310 | 0 | mpFS->singleElementNS(XML_a, XML_noFill); |
1311 | 0 | } |
1312 | |
|
1313 | 0 | mpFS->endElementNS( XML_a, XML_ln ); |
1314 | 0 | } |
1315 | | |
1316 | | OUString DrawingML::GetComponentDir() const |
1317 | 0 | { |
1318 | 0 | return OUString(getComponentDir(meDocumentType)); |
1319 | 0 | } |
1320 | | |
1321 | | OUString DrawingML::GetRelationCompPrefix() const |
1322 | 0 | { |
1323 | 0 | return OUString(getRelationCompPrefix(meDocumentType)); |
1324 | 0 | } |
1325 | | |
1326 | | void GraphicExport::writeSvgExtension(OUString const& rSvgRelId) |
1327 | 0 | { |
1328 | 0 | if (rSvgRelId.isEmpty()) |
1329 | 0 | return; |
1330 | | |
1331 | 0 | mpFS->startElementNS(XML_a, XML_extLst); |
1332 | 0 | mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"); |
1333 | 0 | mpFS->singleElementNS(XML_asvg, XML_svgBlip, |
1334 | 0 | FSNS(XML_xmlns, XML_asvg), mpFilterBase->getNamespaceURL(OOX_NS(asvg)), |
1335 | 0 | FSNS(XML_r, XML_embed), rSvgRelId); |
1336 | 0 | mpFS->endElementNS(XML_a, XML_ext); |
1337 | 0 | mpFS->endElementNS( XML_a, XML_extLst); |
1338 | 0 | } |
1339 | | |
1340 | | void GraphicExport::writeBlip(Graphic const& rGraphic, std::vector<model::BlipEffect> const& rEffects) |
1341 | 0 | { |
1342 | 0 | OUString sRelId = writeToStorage(rGraphic, /*bRelPathToMedia*/false); |
1343 | |
|
1344 | 0 | mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId); |
1345 | |
|
1346 | 0 | auto const& rVectorGraphicDataPtr = rGraphic.getVectorGraphicData(); |
1347 | |
|
1348 | 0 | if (rVectorGraphicDataPtr && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg) |
1349 | 0 | { |
1350 | 0 | OUString sSvgRelId = writeToStorage(rGraphic, /*bRelPathToMedia*/false, TypeHint::SVG); |
1351 | 0 | writeSvgExtension(sSvgRelId); |
1352 | 0 | } |
1353 | |
|
1354 | 0 | for (auto const& rEffect : rEffects) |
1355 | 0 | { |
1356 | 0 | switch (rEffect.meType) |
1357 | 0 | { |
1358 | 0 | case model::BlipEffectType::AlphaBiLevel: |
1359 | 0 | { |
1360 | 0 | mpFS->singleElementNS(XML_a, XML_alphaBiLevel, XML_thresh, OString::number(rEffect.mnThreshold)); |
1361 | 0 | } |
1362 | 0 | break; |
1363 | 0 | case model::BlipEffectType::AlphaCeiling: |
1364 | 0 | { |
1365 | 0 | mpFS->singleElementNS(XML_a, XML_alphaCeiling); |
1366 | 0 | } |
1367 | 0 | break; |
1368 | 0 | case model::BlipEffectType::AlphaFloor: |
1369 | 0 | { |
1370 | 0 | mpFS->singleElementNS(XML_a, XML_alphaFloor); |
1371 | 0 | } |
1372 | 0 | break; |
1373 | 0 | case model::BlipEffectType::AlphaInverse: |
1374 | 0 | { |
1375 | 0 | mpFS->singleElementNS(XML_a, XML_alphaInv); |
1376 | | // TODO: export rEffect.maColor1 |
1377 | 0 | } |
1378 | 0 | break; |
1379 | 0 | case model::BlipEffectType::AlphaModulate: |
1380 | 0 | { |
1381 | 0 | mpFS->singleElementNS(XML_a, XML_alphaMod); |
1382 | | // TODO |
1383 | 0 | } |
1384 | 0 | break; |
1385 | 0 | case model::BlipEffectType::AlphaModulateFixed: |
1386 | 0 | { |
1387 | 0 | mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(rEffect.mnAmount)); |
1388 | 0 | } |
1389 | 0 | break; |
1390 | 0 | case model::BlipEffectType::AlphaReplace: |
1391 | 0 | { |
1392 | 0 | mpFS->singleElementNS(XML_a, XML_alphaRepl, XML_a, OString::number(rEffect.mnAlpha)); |
1393 | 0 | } |
1394 | 0 | break; |
1395 | 0 | case model::BlipEffectType::BiLevel: |
1396 | 0 | { |
1397 | 0 | mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(rEffect.mnThreshold)); |
1398 | 0 | } |
1399 | 0 | break; |
1400 | 0 | case model::BlipEffectType::Blur: |
1401 | 0 | { |
1402 | 0 | mpFS->singleElementNS(XML_a, XML_blur, |
1403 | 0 | XML_rad, OString::number(rEffect.mnRadius), |
1404 | 0 | XML_grow, rEffect.mbGrow ? "1" : "0"); |
1405 | 0 | } |
1406 | 0 | break; |
1407 | 0 | case model::BlipEffectType::ColorChange: |
1408 | 0 | { |
1409 | 0 | mpFS->startElementNS(XML_a, XML_clrChange, XML_useA, rEffect.mbUseAlpha ? "1" : "0"); |
1410 | 0 | mpFS->endElementNS(XML_a, XML_clrChange); |
1411 | 0 | } |
1412 | 0 | break; |
1413 | 0 | case model::BlipEffectType::ColorReplace: |
1414 | 0 | { |
1415 | 0 | mpFS->startElementNS(XML_a, XML_clrRepl); |
1416 | 0 | mpFS->endElementNS(XML_a, XML_clrRepl); |
1417 | 0 | } |
1418 | 0 | break; |
1419 | 0 | case model::BlipEffectType::DuoTone: |
1420 | 0 | { |
1421 | 0 | mpFS->startElementNS(XML_a, XML_duotone); |
1422 | 0 | mpFS->endElementNS(XML_a, XML_duotone); |
1423 | 0 | } |
1424 | 0 | break; |
1425 | 0 | case model::BlipEffectType::FillOverlay: |
1426 | 0 | { |
1427 | 0 | mpFS->singleElementNS(XML_a, XML_fillOverlay); |
1428 | 0 | } |
1429 | 0 | break; |
1430 | 0 | case model::BlipEffectType::Grayscale: |
1431 | 0 | { |
1432 | 0 | mpFS->singleElementNS(XML_a, XML_grayscl); |
1433 | 0 | } |
1434 | 0 | break; |
1435 | 0 | case model::BlipEffectType::HSL: |
1436 | 0 | { |
1437 | 0 | mpFS->singleElementNS(XML_a, XML_hsl, |
1438 | 0 | XML_hue, OString::number(rEffect.mnHue), |
1439 | 0 | XML_sat, OString::number(rEffect.mnSaturation), |
1440 | 0 | XML_lum, OString::number(rEffect.mnLuminance)); |
1441 | 0 | } |
1442 | 0 | break; |
1443 | 0 | case model::BlipEffectType::Luminance: |
1444 | 0 | { |
1445 | 0 | mpFS->singleElementNS(XML_a, XML_lum, |
1446 | 0 | XML_bright, OString::number(rEffect.mnBrightness), |
1447 | 0 | XML_contrast, OString::number(rEffect.mnContrast)); |
1448 | 0 | } |
1449 | 0 | break; |
1450 | 0 | case model::BlipEffectType::Tint: |
1451 | 0 | { |
1452 | 0 | mpFS->singleElementNS(XML_a, XML_tint, |
1453 | 0 | XML_hue, OString::number(rEffect.mnHue), |
1454 | 0 | XML_amt, OString::number(rEffect.mnAmount)); |
1455 | 0 | } |
1456 | 0 | break; |
1457 | | |
1458 | 0 | default: |
1459 | 0 | break; |
1460 | 0 | } |
1461 | 0 | } |
1462 | | |
1463 | 0 | mpFS->endElementNS(XML_a, XML_blip); |
1464 | 0 | } |
1465 | | |
1466 | | OUString GraphicExport::writeNewEntryToStorage(const Graphic& rGraphic, bool bRelPathToMedia) |
1467 | 0 | { |
1468 | 0 | GfxLink const aLink = rGraphic.GetGfxLink(); |
1469 | |
|
1470 | 0 | OUString sMediaType; |
1471 | 0 | OUString aExtension; |
1472 | |
|
1473 | 0 | SvMemoryStream aStream; |
1474 | 0 | const void* aData = aLink.GetData(); |
1475 | 0 | std::size_t nDataSize = aLink.GetDataSize(); |
1476 | |
|
1477 | 0 | switch (aLink.GetType()) |
1478 | 0 | { |
1479 | 0 | case GfxLinkType::NativeGif: |
1480 | 0 | sMediaType = u"image/gif"_ustr; |
1481 | 0 | aExtension = u"gif"_ustr; |
1482 | 0 | break; |
1483 | | |
1484 | | // #i15508# added BMP type for better exports |
1485 | | // export not yet active, so adding for reference (not checked) |
1486 | 0 | case GfxLinkType::NativeBmp: |
1487 | 0 | sMediaType = u"image/bmp"_ustr; |
1488 | 0 | aExtension = u"bmp"_ustr; |
1489 | 0 | break; |
1490 | | |
1491 | 0 | case GfxLinkType::NativeJpg: |
1492 | 0 | sMediaType = u"image/jpeg"_ustr; |
1493 | 0 | aExtension = u"jpeg"_ustr; |
1494 | 0 | break; |
1495 | 0 | case GfxLinkType::NativePng: |
1496 | 0 | sMediaType = u"image/png"_ustr; |
1497 | 0 | aExtension = u"png"_ustr; |
1498 | 0 | break; |
1499 | 0 | case GfxLinkType::NativeTif: |
1500 | 0 | sMediaType = u"image/tiff"_ustr; |
1501 | 0 | aExtension = u"tif"_ustr; |
1502 | 0 | break; |
1503 | 0 | case GfxLinkType::NativeWmf: |
1504 | 0 | if (aLink.IsEMF()) |
1505 | 0 | { |
1506 | 0 | sMediaType = u"image/x-emf"_ustr; |
1507 | 0 | aExtension = u"emf"_ustr; |
1508 | 0 | } |
1509 | 0 | else |
1510 | 0 | { |
1511 | 0 | sMediaType = u"image/x-wmf"_ustr; |
1512 | 0 | aExtension = u"wmf"_ustr; |
1513 | 0 | } |
1514 | 0 | break; |
1515 | 0 | case GfxLinkType::NativeMet: |
1516 | 0 | sMediaType = u"image/x-met"_ustr; |
1517 | 0 | aExtension = u"met"_ustr; |
1518 | 0 | break; |
1519 | 0 | case GfxLinkType::NativePct: |
1520 | 0 | sMediaType = u"image/x-pict"_ustr; |
1521 | 0 | aExtension = u"pct"_ustr; |
1522 | 0 | break; |
1523 | 0 | case GfxLinkType::NativeMov: |
1524 | 0 | sMediaType = u"application/movie"_ustr; |
1525 | 0 | aExtension = u"MOV"_ustr; |
1526 | 0 | break; |
1527 | 0 | default: |
1528 | 0 | { |
1529 | 0 | GraphicType aType = rGraphic.GetType(); |
1530 | 0 | if (aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile) |
1531 | 0 | { |
1532 | 0 | if (aType == GraphicType::Bitmap) |
1533 | 0 | { |
1534 | 0 | (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG); |
1535 | 0 | sMediaType = u"image/png"_ustr; |
1536 | 0 | aExtension = u"png"_ustr; |
1537 | 0 | } |
1538 | 0 | else |
1539 | 0 | { |
1540 | 0 | (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF); |
1541 | 0 | sMediaType = u"image/x-emf"_ustr; |
1542 | 0 | aExtension = u"emf"_ustr; |
1543 | 0 | } |
1544 | 0 | } |
1545 | 0 | else |
1546 | 0 | { |
1547 | 0 | SAL_WARN("oox.shape", "unhandled graphic type " << static_cast<int>(aType)); |
1548 | | |
1549 | | /*Earlier, even in case of unhandled graphic types we were |
1550 | | proceeding to write the image, which would eventually |
1551 | | write an empty image with a zero size, and return a valid |
1552 | | relationID, which is incorrect. |
1553 | | */ |
1554 | 0 | return OUString(); |
1555 | 0 | } |
1556 | | |
1557 | 0 | aData = aStream.GetData(); |
1558 | 0 | nDataSize = aStream.GetEndOfData(); |
1559 | 0 | } |
1560 | 0 | break; |
1561 | 0 | } |
1562 | | |
1563 | 0 | GraphicExportCache& rGraphicExportCache = GraphicExportCache::get(); |
1564 | 0 | auto sImageCountString = OUString::number(rGraphicExportCache.nextImageCount()); |
1565 | |
|
1566 | 0 | OUString sComponentDir(getComponentDir(meDocumentType)); |
1567 | |
|
1568 | 0 | OUString sImagePath = sComponentDir + u"/media/image"_ustr + sImageCountString + u"."_ustr + aExtension; |
1569 | |
|
1570 | 0 | Reference<XOutputStream> xOutStream = mpFilterBase->openFragmentStream(sImagePath, sMediaType); |
1571 | 0 | xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize)); |
1572 | 0 | xOutStream->closeOutput(); |
1573 | |
|
1574 | 0 | OUString sRelationCompPrefix; |
1575 | 0 | if (bRelPathToMedia) |
1576 | 0 | sRelationCompPrefix = u"../"_ustr; |
1577 | 0 | else |
1578 | 0 | sRelationCompPrefix = getRelationCompPrefix(meDocumentType); |
1579 | |
|
1580 | 0 | OUString sPath = sRelationCompPrefix + u"media/image"_ustr + sImageCountString + u"."_ustr + aExtension; |
1581 | |
|
1582 | 0 | rGraphicExportCache.addExportGraphics(rGraphic.GetChecksum(), sPath); |
1583 | |
|
1584 | 0 | return sPath; |
1585 | 0 | } |
1586 | | |
1587 | | namespace |
1588 | | { |
1589 | | BitmapChecksum makeChecksumUniqueForSVG(BitmapChecksum aChecksum) |
1590 | 0 | { |
1591 | | // need to modify the checksum so we know it's for SVG - just invert it |
1592 | 0 | return ~aChecksum; |
1593 | 0 | } |
1594 | | |
1595 | | } // end anonymous namespace |
1596 | | |
1597 | | OUString GraphicExport::writeNewSvgEntryToStorage(const Graphic& rGraphic, bool bRelPathToMedia) |
1598 | 0 | { |
1599 | 0 | OUString sMediaType = u"image/svg"_ustr; |
1600 | 0 | OUString aExtension = u"svg"_ustr; |
1601 | |
|
1602 | 0 | GfxLink const aLink = rGraphic.GetGfxLink(); |
1603 | 0 | if (aLink.GetType() != GfxLinkType::NativeSvg) |
1604 | 0 | return OUString(); |
1605 | | |
1606 | 0 | const void* aData = aLink.GetData(); |
1607 | 0 | std::size_t nDataSize = aLink.GetDataSize(); |
1608 | |
|
1609 | 0 | GraphicExportCache& rGraphicExportCache = GraphicExportCache::get(); |
1610 | 0 | auto sImageCountString = OUString::number(rGraphicExportCache.nextImageCount()); |
1611 | |
|
1612 | 0 | OUString sComponentDir(getComponentDir(meDocumentType)); |
1613 | |
|
1614 | 0 | OUString sImagePath = sComponentDir + u"/media/image"_ustr + sImageCountString + u"."_ustr + aExtension; |
1615 | |
|
1616 | 0 | Reference<XOutputStream> xOutStream = mpFilterBase->openFragmentStream(sImagePath, sMediaType); |
1617 | 0 | xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize)); |
1618 | 0 | xOutStream->closeOutput(); |
1619 | |
|
1620 | 0 | OUString sRelationCompPrefix; |
1621 | 0 | if (bRelPathToMedia) |
1622 | 0 | sRelationCompPrefix = u"../"_ustr; |
1623 | 0 | else |
1624 | 0 | sRelationCompPrefix = getRelationCompPrefix(meDocumentType); |
1625 | |
|
1626 | 0 | OUString sPath = sRelationCompPrefix + u"media/image"_ustr + sImageCountString + u"."_ustr + aExtension; |
1627 | |
|
1628 | 0 | rGraphicExportCache.addExportGraphics(makeChecksumUniqueForSVG(rGraphic.GetChecksum()), sPath); |
1629 | |
|
1630 | 0 | return sPath; |
1631 | 0 | } |
1632 | | |
1633 | | OUString GraphicExport::writeToStorage(const Graphic& rGraphic, bool bRelPathToMedia, TypeHint eHint, bool bReturnPath) |
1634 | 0 | { |
1635 | 0 | OUString sPath; |
1636 | |
|
1637 | 0 | auto aChecksum = rGraphic.GetChecksum(); |
1638 | 0 | if (eHint == TypeHint::SVG) |
1639 | 0 | aChecksum = makeChecksumUniqueForSVG(aChecksum); |
1640 | |
|
1641 | 0 | GraphicExportCache& rGraphicExportCache = GraphicExportCache::get(); |
1642 | 0 | sPath = rGraphicExportCache.findExportGraphics(aChecksum); |
1643 | |
|
1644 | 0 | if (sPath.isEmpty()) |
1645 | 0 | { |
1646 | 0 | if (eHint == TypeHint::SVG) |
1647 | 0 | sPath = writeNewSvgEntryToStorage(rGraphic, bRelPathToMedia); |
1648 | 0 | else |
1649 | 0 | sPath = writeNewEntryToStorage(rGraphic, bRelPathToMedia); |
1650 | |
|
1651 | 0 | if (sPath.isEmpty()) |
1652 | 0 | return OUString(); // couldn't store |
1653 | 0 | } |
1654 | | |
1655 | 0 | if (bReturnPath) |
1656 | 0 | return sPath; |
1657 | | |
1658 | 0 | OUString sRelId = mpFilterBase->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::IMAGE), sPath); |
1659 | 0 | return sRelId; |
1660 | 0 | } |
1661 | | |
1662 | | std::shared_ptr<GraphicExport> DrawingML::createGraphicExport() |
1663 | 0 | { |
1664 | 0 | return std::make_shared<GraphicExport>(mpFS, mpFB, meDocumentType); |
1665 | 0 | } |
1666 | | |
1667 | | OUString DrawingML::writeImageRelToStorage(const Graphic &rGraphic) |
1668 | 0 | { |
1669 | 0 | const auto& pVectorGraphicDataPtr = rGraphic.getVectorGraphicData(); |
1670 | 0 | const bool bIsSVG(pVectorGraphicDataPtr && pVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg); |
1671 | 0 | GraphicExport aExporter(mpFS, mpFB, meDocumentType); |
1672 | |
|
1673 | 0 | return aExporter.writeToStorage(rGraphic, isDiagaramExport(), bIsSVG ? GraphicExport::TypeHint::SVG : GraphicExport::TypeHint::Detect, true); |
1674 | 0 | } |
1675 | | |
1676 | | OUString DrawingML::writeGraphicToStorage(const Graphic& rGraphic , bool bRelPathToMedia, GraphicExport::TypeHint eHint) |
1677 | 0 | { |
1678 | 0 | GraphicExport aExporter(mpFS, mpFB, meDocumentType); |
1679 | 0 | return aExporter.writeToStorage(rGraphic, bRelPathToMedia, eHint); |
1680 | 0 | } |
1681 | | |
1682 | | void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape) |
1683 | 0 | { |
1684 | 0 | SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(xShape)); |
1685 | 0 | if (!pMediaObj) |
1686 | 0 | return; |
1687 | | |
1688 | | // extension |
1689 | 0 | OUString aExtension; |
1690 | 0 | const OUString& rURL(pMediaObj->getURL()); |
1691 | 0 | int nLastDot = rURL.lastIndexOf('.'); |
1692 | 0 | if (nLastDot >= 0) |
1693 | 0 | aExtension = rURL.copy(nLastDot).replace(':', '_'); // Colons are not allowed in Zip entry file names, see OStorageHelper::IsValidZipEntryFileName |
1694 | |
|
1695 | 0 | bool bEmbed = rURL.startsWith("vnd.sun.star.Package:"); |
1696 | 0 | Relationship eMediaType = Relationship::VIDEO; |
1697 | | |
1698 | | // mime type |
1699 | 0 | #if HAVE_FEATURE_AVMEDIA |
1700 | 0 | OUString aMimeType(pMediaObj->getMediaProperties().getMimeType()); |
1701 | | #else |
1702 | | OUString aMimeType("none"); |
1703 | | #endif |
1704 | 0 | if (aMimeType.startsWith("audio/")) |
1705 | 0 | { |
1706 | 0 | eMediaType = Relationship::AUDIO; |
1707 | 0 | } |
1708 | |
|
1709 | 0 | OUString aVideoFileRelId; |
1710 | 0 | OUString aMediaRelId; |
1711 | |
|
1712 | 0 | if (bEmbed) |
1713 | 0 | { |
1714 | 0 | sal_Int32 nImageCount = GraphicExportCache::get().nextImageCount(); |
1715 | |
|
1716 | 0 | OUString sFileName = GetComponentDir() + u"/media/media"_ustr + OUString::number(nImageCount) + aExtension; |
1717 | | |
1718 | | // copy the video stream |
1719 | 0 | Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(sFileName, aMimeType); |
1720 | |
|
1721 | 0 | uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream()); |
1722 | 0 | comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream); |
1723 | |
|
1724 | 0 | xOutStream->closeOutput(); |
1725 | | |
1726 | | // create the relation |
1727 | 0 | OUString aPath = GetRelationCompPrefix() + u"media/media"_ustr + OUString::number(nImageCount) + aExtension; |
1728 | |
|
1729 | 0 | aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath); |
1730 | 0 | aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath); |
1731 | 0 | } |
1732 | 0 | else |
1733 | 0 | { |
1734 | 0 | aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL, true); |
1735 | 0 | aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL, true); |
1736 | 0 | } |
1737 | |
|
1738 | 0 | GetFS()->startElementNS(XML_p, XML_nvPr); |
1739 | |
|
1740 | 0 | GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile, |
1741 | 0 | FSNS(XML_r, XML_link), aVideoFileRelId); |
1742 | |
|
1743 | 0 | GetFS()->startElementNS(XML_p, XML_extLst); |
1744 | | // media extensions; google this ID for details |
1745 | 0 | GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}"); |
1746 | |
|
1747 | 0 | GetFS()->singleElementNS(XML_p14, XML_media, |
1748 | 0 | bEmbed? FSNS(XML_r, XML_embed): FSNS(XML_r, XML_link), aMediaRelId); |
1749 | |
|
1750 | 0 | GetFS()->endElementNS(XML_p, XML_ext); |
1751 | 0 | GetFS()->endElementNS(XML_p, XML_extLst); |
1752 | |
|
1753 | 0 | GetFS()->endElementNS(XML_p, XML_nvPr); |
1754 | 0 | } |
1755 | | |
1756 | | void DrawingML::WriteImageBrightnessContrastTransparence(uno::Reference<beans::XPropertySet> const & rXPropSet) |
1757 | 0 | { |
1758 | 0 | sal_Int16 nBright = 0; |
1759 | 0 | sal_Int32 nContrast = 0; |
1760 | 0 | sal_Int32 nTransparence = 0; |
1761 | |
|
1762 | 0 | if (GetProperty(rXPropSet, u"AdjustLuminance"_ustr)) |
1763 | 0 | nBright = mAny.get<sal_Int16>(); |
1764 | 0 | if (GetProperty(rXPropSet, u"AdjustContrast"_ustr)) |
1765 | 0 | nContrast = mAny.get<sal_Int32>(); |
1766 | | // Used for shapes with picture fill |
1767 | 0 | if (GetProperty(rXPropSet, u"FillTransparence"_ustr)) |
1768 | 0 | nTransparence = mAny.get<sal_Int32>(); |
1769 | | // Used for pictures |
1770 | 0 | if (nTransparence == 0 && GetProperty(rXPropSet, u"Transparency"_ustr)) |
1771 | 0 | nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>()); |
1772 | |
|
1773 | 0 | if (GetProperty(rXPropSet, u"GraphicColorMode"_ustr)) |
1774 | 0 | { |
1775 | 0 | drawing::ColorMode aColorMode; |
1776 | 0 | mAny >>= aColorMode; |
1777 | 0 | if (aColorMode == drawing::ColorMode_GREYS) |
1778 | 0 | mpFS->singleElementNS(XML_a, XML_grayscl); |
1779 | 0 | else if (aColorMode == drawing::ColorMode_MONO) |
1780 | | //black/white has a 0,5 threshold in LibreOffice |
1781 | 0 | mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000)); |
1782 | 0 | else if (aColorMode == drawing::ColorMode_WATERMARK) |
1783 | 0 | { |
1784 | | //map watermark with mso washout |
1785 | 0 | nBright = 70; |
1786 | 0 | nContrast = -70; |
1787 | 0 | } |
1788 | 0 | } |
1789 | | |
1790 | |
|
1791 | 0 | if (nBright || nContrast) |
1792 | 0 | { |
1793 | 0 | mpFS->singleElementNS(XML_a, XML_lum, |
1794 | 0 | XML_bright, sax_fastparser::UseIf(OString::number(nBright * 1000), nBright != 0), |
1795 | 0 | XML_contrast, sax_fastparser::UseIf(OString::number(nContrast * 1000), nContrast != 0)); |
1796 | 0 | } |
1797 | |
|
1798 | 0 | if (nTransparence) |
1799 | 0 | { |
1800 | 0 | sal_Int32 nAlphaMod = (100 - nTransparence ) * PER_PERCENT; |
1801 | 0 | mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(nAlphaMod)); |
1802 | 0 | } |
1803 | 0 | } |
1804 | | |
1805 | | void DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rXPropSet, |
1806 | | uno::Reference<graphic::XGraphic> const & rxGraphic, |
1807 | | bool bRelPathToMedia) |
1808 | 0 | { |
1809 | 0 | OUString sRelId; |
1810 | |
|
1811 | 0 | if (!rxGraphic.is()) |
1812 | 0 | return; |
1813 | | |
1814 | 0 | Graphic aGraphic(rxGraphic); |
1815 | |
|
1816 | 0 | sRelId = writeGraphicToStorage(aGraphic, bRelPathToMedia); |
1817 | |
|
1818 | 0 | if (isDiagaramExport()) |
1819 | 0 | mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)), FSNS(XML_r, XML_embed), sRelId); |
1820 | 0 | else |
1821 | 0 | mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId); |
1822 | |
|
1823 | 0 | const auto& pVectorGraphicDataPtr = aGraphic.getVectorGraphicData(); |
1824 | |
|
1825 | 0 | if (pVectorGraphicDataPtr && pVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg) |
1826 | 0 | { |
1827 | 0 | GraphicExport aExporter(mpFS, mpFB, meDocumentType); |
1828 | 0 | OUString sSvgRelId = aExporter.writeToStorage(aGraphic, bRelPathToMedia, GraphicExport::TypeHint::SVG); |
1829 | 0 | if (!sSvgRelId.isEmpty()) |
1830 | 0 | aExporter.writeSvgExtension(sSvgRelId); |
1831 | 0 | } |
1832 | |
|
1833 | 0 | WriteImageBrightnessContrastTransparence(rXPropSet); |
1834 | |
|
1835 | 0 | WriteArtisticEffect(rXPropSet); |
1836 | |
|
1837 | 0 | mpFS->endElementNS(XML_a, XML_blip); |
1838 | 0 | } |
1839 | | |
1840 | | void DrawingML::WriteXGraphicBlipMode(uno::Reference<beans::XPropertySet> const & rXPropSet, |
1841 | | uno::Reference<graphic::XGraphic> const & rxGraphic, |
1842 | | css::awt::Size const& rSize) |
1843 | 0 | { |
1844 | 0 | BitmapMode eBitmapMode(BitmapMode_NO_REPEAT); |
1845 | 0 | if (GetProperty(rXPropSet, u"FillBitmapMode"_ustr)) |
1846 | 0 | mAny >>= eBitmapMode; |
1847 | |
|
1848 | 0 | SAL_INFO("oox.shape", "fill bitmap mode: " << int(eBitmapMode)); |
1849 | | |
1850 | 0 | switch (eBitmapMode) |
1851 | 0 | { |
1852 | 0 | case BitmapMode_REPEAT: |
1853 | 0 | WriteXGraphicTile(rXPropSet, rxGraphic, rSize); |
1854 | 0 | break; |
1855 | 0 | case BitmapMode_STRETCH: |
1856 | 0 | WriteXGraphicStretch(rXPropSet, rxGraphic); |
1857 | 0 | break; |
1858 | 0 | case BitmapMode_NO_REPEAT: |
1859 | 0 | WriteXGraphicCustomPosition(rXPropSet, rxGraphic, rSize); |
1860 | 0 | break; |
1861 | 0 | default: |
1862 | 0 | break; |
1863 | 0 | } |
1864 | 0 | } |
1865 | | |
1866 | | void DrawingML::WriteBlipOrNormalFill(const Reference<XPropertySet>& xPropSet, |
1867 | | const OUString& rURLPropName, const awt::Size& rSize) |
1868 | 0 | { |
1869 | | // check for blip and otherwise fall back to normal fill |
1870 | | // we always store normal fill properties but OOXML |
1871 | | // uses a choice between our fill props and BlipFill |
1872 | 0 | if (GetProperty ( xPropSet, rURLPropName )) |
1873 | 0 | WriteBlipFill( xPropSet, rURLPropName ); |
1874 | 0 | else |
1875 | 0 | WriteFill(xPropSet, rSize); |
1876 | 0 | } |
1877 | | |
1878 | | void DrawingML::WriteBlipFill(const Reference<XPropertySet>& rXPropSet, |
1879 | | const OUString& sURLPropName, const awt::Size& rSize) |
1880 | 0 | { |
1881 | 0 | WriteBlipFill( rXPropSet, rSize, sURLPropName, XML_a ); |
1882 | 0 | } |
1883 | | |
1884 | | void DrawingML::WriteBlipFill(const Reference<XPropertySet>& rXPropSet, const awt::Size& rSize, |
1885 | | const OUString& sURLPropName, sal_Int32 nXmlNamespace) |
1886 | 0 | { |
1887 | 0 | if ( !GetProperty( rXPropSet, sURLPropName ) ) |
1888 | 0 | return; |
1889 | | |
1890 | 0 | uno::Reference<graphic::XGraphic> xGraphic; |
1891 | 0 | if (mAny.has<uno::Reference<awt::XBitmap>>()) |
1892 | 0 | { |
1893 | 0 | uno::Reference<awt::XBitmap> xBitmap = mAny.get<uno::Reference<awt::XBitmap>>(); |
1894 | 0 | if (xBitmap.is()) |
1895 | 0 | xGraphic.set(xBitmap, uno::UNO_QUERY); |
1896 | 0 | } |
1897 | 0 | else if (mAny.has<uno::Reference<graphic::XGraphic>>()) |
1898 | 0 | { |
1899 | 0 | xGraphic = mAny.get<uno::Reference<graphic::XGraphic>>(); |
1900 | 0 | } |
1901 | |
|
1902 | 0 | if (xGraphic.is()) |
1903 | 0 | { |
1904 | 0 | bool bWriteMode = false; |
1905 | 0 | if (sURLPropName == "FillBitmap" || sURLPropName == "BackGraphic") |
1906 | 0 | bWriteMode = true; |
1907 | | |
1908 | | // we need to use bRelPathToMedia when DiagramExport and it contains media data |
1909 | | // or links |
1910 | 0 | WriteXGraphicBlipFill(rXPropSet, xGraphic, nXmlNamespace, bWriteMode, isDiagaramExport(), rSize); |
1911 | 0 | } |
1912 | 0 | } |
1913 | | |
1914 | | void DrawingML::WriteXGraphicBlipFill(uno::Reference<beans::XPropertySet> const & rXPropSet, |
1915 | | uno::Reference<graphic::XGraphic> const & rxGraphic, |
1916 | | sal_Int32 nXmlNamespace, bool bWriteMode, |
1917 | | bool bRelPathToMedia, css::awt::Size const& rSize) |
1918 | 0 | { |
1919 | 0 | if (!rxGraphic.is() ) |
1920 | 0 | return; |
1921 | | |
1922 | 0 | mpFS->startElementNS(nXmlNamespace , XML_blipFill, XML_rotWithShape, "0"); |
1923 | |
|
1924 | 0 | WriteXGraphicBlip(rXPropSet, rxGraphic, bRelPathToMedia); |
1925 | |
|
1926 | 0 | if (GetDocumentType() != DOCUMENT_DOCX) |
1927 | 0 | { |
1928 | | // Write the crop rectangle of Impress as a source rectangle. |
1929 | 0 | WriteSrcRectXGraphic(rXPropSet, rxGraphic); |
1930 | 0 | } |
1931 | |
|
1932 | 0 | if (bWriteMode) |
1933 | 0 | { |
1934 | 0 | WriteXGraphicBlipMode(rXPropSet, rxGraphic, rSize); |
1935 | 0 | } |
1936 | 0 | else if(GetProperty(rXPropSet, u"FillBitmapStretch"_ustr)) |
1937 | 0 | { |
1938 | 0 | bool bStretch = mAny.get<bool>(); |
1939 | |
|
1940 | 0 | if (bStretch) |
1941 | 0 | { |
1942 | 0 | WriteXGraphicStretch(rXPropSet, rxGraphic); |
1943 | 0 | } |
1944 | 0 | } |
1945 | 0 | mpFS->endElementNS(nXmlNamespace, XML_blipFill); |
1946 | 0 | } |
1947 | | |
1948 | | void DrawingML::WritePattFill( const Reference< XPropertySet >& rXPropSet ) |
1949 | 0 | { |
1950 | 0 | if ( GetProperty( rXPropSet, u"FillHatch"_ustr ) ) |
1951 | 0 | { |
1952 | 0 | drawing::Hatch aHatch; |
1953 | 0 | mAny >>= aHatch; |
1954 | 0 | WritePattFill(rXPropSet, aHatch); |
1955 | 0 | } |
1956 | 0 | } |
1957 | | |
1958 | | void DrawingML::WritePattFill(const Reference<XPropertySet>& rXPropSet, const css::drawing::Hatch& rHatch) |
1959 | 0 | { |
1960 | 0 | mpFS->startElementNS(XML_a, XML_pattFill, XML_prst, GetHatchPattern(rHatch)); |
1961 | |
|
1962 | 0 | sal_Int32 nAlpha = MAX_PERCENT; |
1963 | 0 | if (GetProperty(rXPropSet, u"FillTransparence"_ustr)) |
1964 | 0 | { |
1965 | 0 | sal_Int32 nTransparency = 0; |
1966 | 0 | mAny >>= nTransparency; |
1967 | 0 | nAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency)); |
1968 | 0 | } |
1969 | |
|
1970 | 0 | mpFS->startElementNS(XML_a, XML_fgClr); |
1971 | 0 | WriteColor(::Color(ColorTransparency, rHatch.Color), nAlpha); |
1972 | 0 | mpFS->endElementNS( XML_a , XML_fgClr ); |
1973 | |
|
1974 | 0 | ::Color nColor = COL_WHITE; |
1975 | |
|
1976 | 0 | if ( GetProperty( rXPropSet, u"FillBackground"_ustr ) ) |
1977 | 0 | { |
1978 | 0 | bool isBackgroundFilled = false; |
1979 | 0 | mAny >>= isBackgroundFilled; |
1980 | 0 | if( isBackgroundFilled ) |
1981 | 0 | { |
1982 | 0 | if( GetProperty( rXPropSet, u"FillColor"_ustr ) ) |
1983 | 0 | { |
1984 | 0 | mAny >>= nColor; |
1985 | 0 | } |
1986 | 0 | } |
1987 | 0 | else |
1988 | 0 | nAlpha = 0; |
1989 | 0 | } |
1990 | |
|
1991 | 0 | mpFS->startElementNS(XML_a, XML_bgClr); |
1992 | 0 | WriteColor(nColor, nAlpha); |
1993 | 0 | mpFS->endElementNS( XML_a , XML_bgClr ); |
1994 | |
|
1995 | 0 | mpFS->endElementNS( XML_a , XML_pattFill ); |
1996 | 0 | } |
1997 | | |
1998 | | void DrawingML::WriteGraphicCropProperties(uno::Reference<beans::XPropertySet> const & rXPropSet, |
1999 | | Size const & rOriginalSize, |
2000 | | MapMode const & rMapMode) |
2001 | 0 | { |
2002 | 0 | if (!GetProperty(rXPropSet, u"GraphicCrop"_ustr)) |
2003 | 0 | return; |
2004 | | |
2005 | 0 | css::text::GraphicCrop aGraphicCropStruct; |
2006 | 0 | mAny >>= aGraphicCropStruct; |
2007 | |
|
2008 | 0 | if(GetProperty(rXPropSet, u"CustomShapeGeometry"_ustr)) |
2009 | 0 | { |
2010 | | // tdf#134210 GraphicCrop property is handled in import filter because of LibreOffice has not core |
2011 | | // feature. We cropped the bitmap physically and MSO shouldn't crop bitmap one more time. When we |
2012 | | // have core feature for graphic cropping in custom shapes, we should uncomment the code anymore. |
2013 | |
|
2014 | 0 | mpFS->singleElementNS( XML_a, XML_srcRect); |
2015 | 0 | } |
2016 | 0 | else |
2017 | 0 | { |
2018 | 0 | Size aOriginalSize(rOriginalSize); |
2019 | | |
2020 | | // GraphicCrop is in mm100, so in case the original size is in pixels, convert it over. |
2021 | 0 | if (rMapMode.GetMapUnit() == MapUnit::MapPixel) |
2022 | 0 | aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM)); |
2023 | |
|
2024 | 0 | if (aOriginalSize.Width() != 0 && aOriginalSize.Height() != 0) |
2025 | 0 | if ( (0 != aGraphicCropStruct.Left) || (0 != aGraphicCropStruct.Top) || (0 != aGraphicCropStruct.Right) || (0 != aGraphicCropStruct.Bottom) ) |
2026 | 0 | { |
2027 | 0 | mpFS->singleElementNS( XML_a, XML_srcRect, |
2028 | 0 | XML_l, OString::number(rtl::math::round(aGraphicCropStruct.Left * 100000.0 / aOriginalSize.Width())), |
2029 | 0 | XML_t, OString::number(rtl::math::round(aGraphicCropStruct.Top * 100000.0 / aOriginalSize.Height())), |
2030 | 0 | XML_r, OString::number(rtl::math::round(aGraphicCropStruct.Right * 100000.0 / aOriginalSize.Width())), |
2031 | 0 | XML_b, OString::number(rtl::math::round(aGraphicCropStruct.Bottom * 100000.0 / aOriginalSize.Height())) ); |
2032 | 0 | } |
2033 | 0 | } |
2034 | 0 | } |
2035 | | |
2036 | | void DrawingML::WriteSrcRectXGraphic(uno::Reference<beans::XPropertySet> const & rxPropertySet, |
2037 | | uno::Reference<graphic::XGraphic> const & rxGraphic) |
2038 | 0 | { |
2039 | 0 | Graphic aGraphic(rxGraphic); |
2040 | 0 | Size aOriginalSize = aGraphic.GetPrefSize(); |
2041 | 0 | const MapMode aMapMode = aGraphic.GetPrefMapMode(); |
2042 | 0 | WriteGraphicCropProperties(rxPropertySet, aOriginalSize, aMapMode); |
2043 | 0 | } |
2044 | | |
2045 | | void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet, |
2046 | | uno::Reference<graphic::XGraphic> const & rxGraphic) |
2047 | 0 | { |
2048 | 0 | if (GetDocumentType() != DOCUMENT_DOCX) |
2049 | 0 | { |
2050 | | // Limiting the area used for stretching is not supported in Impress. |
2051 | 0 | mpFS->singleElementNS(XML_a, XML_stretch); |
2052 | 0 | return; |
2053 | 0 | } |
2054 | | |
2055 | 0 | mpFS->startElementNS(XML_a, XML_stretch); |
2056 | |
|
2057 | 0 | bool bCrop = false; |
2058 | 0 | if (GetProperty(rXPropSet, u"GraphicCrop"_ustr)) |
2059 | 0 | { |
2060 | 0 | css::text::GraphicCrop aGraphicCropStruct; |
2061 | 0 | mAny >>= aGraphicCropStruct; |
2062 | |
|
2063 | 0 | if ((0 != aGraphicCropStruct.Left) |
2064 | 0 | || (0 != aGraphicCropStruct.Top) |
2065 | 0 | || (0 != aGraphicCropStruct.Right) |
2066 | 0 | || (0 != aGraphicCropStruct.Bottom)) |
2067 | 0 | { |
2068 | 0 | Graphic aGraphic(rxGraphic); |
2069 | 0 | Size aOriginalSize(aGraphic.GetPrefSize()); |
2070 | 0 | mpFS->singleElementNS(XML_a, XML_fillRect, |
2071 | 0 | XML_l, OString::number(((aGraphicCropStruct.Left) * 100000) / aOriginalSize.Width()), |
2072 | 0 | XML_t, OString::number(((aGraphicCropStruct.Top) * 100000) / aOriginalSize.Height()), |
2073 | 0 | XML_r, OString::number(((aGraphicCropStruct.Right) * 100000) / aOriginalSize.Width()), |
2074 | 0 | XML_b, OString::number(((aGraphicCropStruct.Bottom) * 100000) / aOriginalSize.Height())); |
2075 | 0 | bCrop = true; |
2076 | 0 | } |
2077 | 0 | } |
2078 | |
|
2079 | 0 | if (!bCrop) |
2080 | 0 | { |
2081 | 0 | mpFS->singleElementNS(XML_a, XML_fillRect); |
2082 | 0 | } |
2083 | |
|
2084 | 0 | mpFS->endElementNS(XML_a, XML_stretch); |
2085 | 0 | } |
2086 | | |
2087 | | static OUString lclConvertRectanglePointToToken(RectanglePoint eRectanglePoint) |
2088 | 0 | { |
2089 | 0 | OUString sAlignment; |
2090 | 0 | switch (eRectanglePoint) |
2091 | 0 | { |
2092 | 0 | case RectanglePoint_LEFT_TOP: |
2093 | 0 | sAlignment = "tl"; |
2094 | 0 | break; |
2095 | 0 | case RectanglePoint_MIDDLE_TOP: |
2096 | 0 | sAlignment = "t"; |
2097 | 0 | break; |
2098 | 0 | case RectanglePoint_RIGHT_TOP: |
2099 | 0 | sAlignment = "tr"; |
2100 | 0 | break; |
2101 | 0 | case RectanglePoint_LEFT_MIDDLE: |
2102 | 0 | sAlignment = "l"; |
2103 | 0 | break; |
2104 | 0 | case RectanglePoint_MIDDLE_MIDDLE: |
2105 | 0 | sAlignment = "ctr"; |
2106 | 0 | break; |
2107 | 0 | case RectanglePoint_RIGHT_MIDDLE: |
2108 | 0 | sAlignment = "r"; |
2109 | 0 | break; |
2110 | 0 | case RectanglePoint_LEFT_BOTTOM: |
2111 | 0 | sAlignment = "bl"; |
2112 | 0 | break; |
2113 | 0 | case RectanglePoint_MIDDLE_BOTTOM: |
2114 | 0 | sAlignment = "b"; |
2115 | 0 | break; |
2116 | 0 | case RectanglePoint_RIGHT_BOTTOM: |
2117 | 0 | sAlignment = "br"; |
2118 | 0 | break; |
2119 | 0 | default: |
2120 | 0 | break; |
2121 | 0 | } |
2122 | 0 | return sAlignment; |
2123 | 0 | } |
2124 | | |
2125 | | void DrawingML::WriteXGraphicTile(uno::Reference<beans::XPropertySet> const& rXPropSet, |
2126 | | uno::Reference<graphic::XGraphic> const& rxGraphic, |
2127 | | css::awt::Size const& rSize) |
2128 | 0 | { |
2129 | 0 | Graphic aGraphic(rxGraphic); |
2130 | 0 | Size aOriginalSize(aGraphic.GetPrefSize()); |
2131 | 0 | const MapMode aMapMode = aGraphic.GetPrefMapMode(); |
2132 | | // if the original size is in pixel, convert it to mm100 |
2133 | 0 | if (aMapMode.GetMapUnit() == MapUnit::MapPixel) |
2134 | 0 | aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, |
2135 | 0 | MapMode(MapUnit::Map100thMM)); |
2136 | 0 | sal_Int32 nSizeX = 0; |
2137 | 0 | sal_Int32 nOffsetX = 0; |
2138 | 0 | if (GetProperty(rXPropSet, u"FillBitmapSizeX"_ustr)) |
2139 | 0 | { |
2140 | 0 | mAny >>= nSizeX; |
2141 | 0 | if (GetProperty(rXPropSet, u"FillBitmapPositionOffsetX"_ustr)) |
2142 | 0 | { |
2143 | 0 | sal_Int32 nX = (nSizeX != 0) ? nSizeX : aOriginalSize.Width(); |
2144 | 0 | if (nX < 0 && rSize.Width > 0) |
2145 | 0 | nX = rSize.Width * std::abs(nX) / 100; |
2146 | 0 | nOffsetX = (*o3tl::doAccess<sal_Int32>(mAny)) * nX * 3.6; |
2147 | 0 | } |
2148 | | |
2149 | | // convert the X size of bitmap to a percentage |
2150 | 0 | if (nSizeX > 0) |
2151 | 0 | nSizeX = double(nSizeX) / aOriginalSize.Width() * 100000; |
2152 | 0 | else if (nSizeX < 0) |
2153 | 0 | nSizeX *= 1000; |
2154 | 0 | else |
2155 | 0 | nSizeX = 100000; |
2156 | 0 | } |
2157 | |
|
2158 | 0 | sal_Int32 nSizeY = 0; |
2159 | 0 | sal_Int32 nOffsetY = 0; |
2160 | 0 | if (GetProperty(rXPropSet, u"FillBitmapSizeY"_ustr)) |
2161 | 0 | { |
2162 | 0 | mAny >>= nSizeY; |
2163 | 0 | if (GetProperty(rXPropSet, u"FillBitmapPositionOffsetY"_ustr)) |
2164 | 0 | { |
2165 | 0 | sal_Int32 nY = (nSizeY != 0) ? nSizeY : aOriginalSize.Height(); |
2166 | 0 | if (nY < 0 && rSize.Height > 0) |
2167 | 0 | nY = rSize.Height * std::abs(nY) / 100; |
2168 | 0 | nOffsetY = (*o3tl::doAccess<sal_Int32>(mAny)) * nY * 3.6; |
2169 | 0 | } |
2170 | | |
2171 | | // convert the Y size of bitmap to a percentage |
2172 | 0 | if (nSizeY > 0) |
2173 | 0 | nSizeY = double(nSizeY) / aOriginalSize.Height() * 100000; |
2174 | 0 | else if (nSizeY < 0) |
2175 | 0 | nSizeY *= 1000; |
2176 | 0 | else |
2177 | 0 | nSizeY = 100000; |
2178 | 0 | } |
2179 | | |
2180 | | // if the "Scale" setting is checked in the images settings dialog. |
2181 | 0 | if (nSizeX < 0 && nSizeY < 0) |
2182 | 0 | { |
2183 | 0 | if (rSize.Width != 0 && rSize.Height != 0) |
2184 | 0 | { |
2185 | 0 | nSizeX = rSize.Width / double(aOriginalSize.Width()) * std::abs(nSizeX); |
2186 | 0 | nSizeY = rSize.Height / double(aOriginalSize.Height()) * std::abs(nSizeY); |
2187 | 0 | } |
2188 | 0 | else |
2189 | 0 | { |
2190 | 0 | nSizeX = std::abs(nSizeX); |
2191 | 0 | nSizeY = std::abs(nSizeY); |
2192 | 0 | } |
2193 | 0 | } |
2194 | |
|
2195 | 0 | OUString sRectanglePoint; |
2196 | 0 | if (GetProperty(rXPropSet, u"FillBitmapRectanglePoint"_ustr)) |
2197 | 0 | sRectanglePoint = lclConvertRectanglePointToToken(*o3tl::doAccess<RectanglePoint>(mAny)); |
2198 | |
|
2199 | 0 | mpFS->singleElementNS(XML_a, XML_tile, XML_tx, OUString::number(nOffsetX), XML_ty, |
2200 | 0 | OUString::number(nOffsetY), XML_sx, OUString::number(nSizeX), XML_sy, |
2201 | 0 | OUString::number(nSizeY), XML_algn, sRectanglePoint); |
2202 | 0 | } |
2203 | | |
2204 | | void DrawingML::WriteXGraphicCustomPosition(uno::Reference<beans::XPropertySet> const& rXPropSet, |
2205 | | uno::Reference<graphic::XGraphic> const& rxGraphic, |
2206 | | css::awt::Size const& rSize) |
2207 | 0 | { |
2208 | 0 | Graphic aGraphic(rxGraphic); |
2209 | 0 | Size aOriginalSize(aGraphic.GetPrefSize()); |
2210 | 0 | const MapMode aMapMode = aGraphic.GetPrefMapMode(); |
2211 | | // if the original size is in pixel, convert it to mm100 |
2212 | 0 | if (aMapMode.GetMapUnit() == MapUnit::MapPixel) |
2213 | 0 | aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, |
2214 | 0 | MapMode(MapUnit::Map100thMM)); |
2215 | 0 | double nSizeX = 0; |
2216 | 0 | if (GetProperty(rXPropSet, u"FillBitmapSizeX"_ustr)) |
2217 | 0 | { |
2218 | 0 | mAny >>= nSizeX; |
2219 | 0 | if (nSizeX <= 0) |
2220 | 0 | { |
2221 | 0 | if (nSizeX == 0) |
2222 | 0 | nSizeX = aOriginalSize.Width(); |
2223 | 0 | else |
2224 | 0 | nSizeX /= 100; // percentage |
2225 | 0 | } |
2226 | 0 | } |
2227 | |
|
2228 | 0 | double nSizeY = 0; |
2229 | 0 | if (GetProperty(rXPropSet, u"FillBitmapSizeY"_ustr)) |
2230 | 0 | { |
2231 | 0 | mAny >>= nSizeY; |
2232 | 0 | if (nSizeY <= 0) |
2233 | 0 | { |
2234 | 0 | if (nSizeY == 0) |
2235 | 0 | nSizeY = aOriginalSize.Height(); |
2236 | 0 | else |
2237 | 0 | nSizeY /= 100; // percentage |
2238 | 0 | } |
2239 | 0 | } |
2240 | |
|
2241 | 0 | if (nSizeX < 0 && nSizeY < 0 && rSize.Width != 0 && rSize.Height != 0) |
2242 | 0 | { |
2243 | 0 | nSizeX = rSize.Width * std::abs(nSizeX); |
2244 | 0 | nSizeY = rSize.Height * std::abs(nSizeY); |
2245 | 0 | } |
2246 | |
|
2247 | 0 | sal_Int32 nL = 0, nT = 0, nR = 0, nB = 0; |
2248 | 0 | if (GetProperty(rXPropSet, u"FillBitmapRectanglePoint"_ustr) && rSize.Width != 0 && rSize.Height != 0) |
2249 | 0 | { |
2250 | 0 | sal_Int32 nWidth = (1 - (nSizeX / rSize.Width)) * 100000; |
2251 | 0 | sal_Int32 nHeight = (1 - (nSizeY / rSize.Height)) * 100000; |
2252 | |
|
2253 | 0 | switch (*o3tl::doAccess<RectanglePoint>(mAny)) |
2254 | 0 | { |
2255 | 0 | case RectanglePoint_LEFT_TOP: nR = nWidth; nB = nHeight; break; |
2256 | 0 | case RectanglePoint_RIGHT_TOP: nL = nWidth; nB = nHeight; break; |
2257 | 0 | case RectanglePoint_LEFT_BOTTOM: nR = nWidth; nT = nHeight; break; |
2258 | 0 | case RectanglePoint_RIGHT_BOTTOM: nL = nWidth; nT = nHeight; break; |
2259 | 0 | case RectanglePoint_LEFT_MIDDLE: nR = nWidth; nT = nB = nHeight / 2; break; |
2260 | 0 | case RectanglePoint_RIGHT_MIDDLE: nL = nWidth; nT = nB = nHeight / 2; break; |
2261 | 0 | case RectanglePoint_MIDDLE_TOP: nB = nHeight; nL = nR = nWidth / 2; break; |
2262 | 0 | case RectanglePoint_MIDDLE_BOTTOM: nT = nHeight; nL = nR = nWidth / 2; break; |
2263 | 0 | case RectanglePoint_MIDDLE_MIDDLE: nL = nR = nWidth / 2; nT = nB = nHeight / 2; break; |
2264 | 0 | default: break; |
2265 | 0 | } |
2266 | 0 | } |
2267 | | |
2268 | 0 | mpFS->startElementNS(XML_a, XML_stretch); |
2269 | |
|
2270 | 0 | mpFS->singleElementNS(XML_a, XML_fillRect, XML_l, |
2271 | 0 | sax_fastparser::UseIf(OString::number(nL), nL != 0), XML_t, |
2272 | 0 | sax_fastparser::UseIf(OString::number(nT), nT != 0), XML_r, |
2273 | 0 | sax_fastparser::UseIf(OString::number(nR), nR != 0), XML_b, |
2274 | 0 | sax_fastparser::UseIf(OString::number(nB), nB != 0)); |
2275 | |
|
2276 | 0 | mpFS->endElementNS(XML_a, XML_stretch); |
2277 | 0 | } |
2278 | | |
2279 | | namespace |
2280 | | { |
2281 | | bool IsTopGroupObj(const uno::Reference<drawing::XShape>& xShape) |
2282 | 0 | { |
2283 | 0 | SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape); |
2284 | 0 | if (!pObject) |
2285 | 0 | return false; |
2286 | | |
2287 | 0 | if (pObject->getParentSdrObjectFromSdrObject()) |
2288 | 0 | return false; |
2289 | | |
2290 | 0 | return pObject->IsGroupObject(); |
2291 | 0 | } |
2292 | | } |
2293 | | |
2294 | | void DrawingML::WriteTransformation(const Reference< XShape >& xShape, const tools::Rectangle& rRect, |
2295 | | sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape) |
2296 | 0 | { |
2297 | |
|
2298 | 0 | mpFS->startElementNS( nXmlNamespace, XML_xfrm, |
2299 | 0 | XML_flipH, sax_fastparser::UseIf("1", bFlipH), |
2300 | 0 | XML_flipV, sax_fastparser::UseIf("1", bFlipV), |
2301 | 0 | XML_rot, sax_fastparser::UseIf(OString::number(nRotation), nRotation % 21600000 != 0)); |
2302 | |
|
2303 | 0 | sal_Int32 nLeft = rRect.Left(); |
2304 | 0 | sal_Int32 nTop = rRect.Top(); |
2305 | | |
2306 | | // do *not* fallback to (0, 0) again when in DiagaramReplacementExport, that |
2307 | | // works more like a GraphicReplacement ObjectHierarchy snippet |
2308 | 0 | if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is() && !isDiagaramReplacementExport()) |
2309 | 0 | { |
2310 | 0 | nLeft = 0; |
2311 | 0 | nTop = 0; |
2312 | 0 | } |
2313 | 0 | sal_Int32 nChildLeft = nLeft; |
2314 | 0 | sal_Int32 nChildTop = nTop; |
2315 | |
|
2316 | 0 | const sal_Int64 MAX_SIZE = std::numeric_limits<sal_Int32>::max(); |
2317 | 0 | sal_Int32 nCx = std::min(oox::drawingml::convertHmmToEmu(rRect.GetWidth()), MAX_SIZE); |
2318 | 0 | sal_Int32 nCy = std::min(oox::drawingml::convertHmmToEmu(rRect.GetHeight()), MAX_SIZE); |
2319 | 0 | sal_Int32 nX = std::min(oox::drawingml::convertHmmToEmu(nLeft), MAX_SIZE); |
2320 | 0 | sal_Int32 nY = std::min(oox::drawingml::convertHmmToEmu(nTop), MAX_SIZE); |
2321 | |
|
2322 | 0 | mpFS->singleElementNS(XML_a, XML_off, XML_x, OString::number(nX), XML_y, OString::number(nY)); |
2323 | 0 | mpFS->singleElementNS(XML_a, XML_ext, XML_cx, OString::number(nCx), XML_cy, |
2324 | 0 | OString::number(nCy)); |
2325 | |
|
2326 | 0 | if (bIsGroupShape && (GetDocumentType() != DOCUMENT_DOCX || IsTopGroupObj(xShape))) |
2327 | 0 | { |
2328 | 0 | nX = std::min(oox::drawingml::convertHmmToEmu(nChildLeft), MAX_SIZE); |
2329 | 0 | nY = std::min(oox::drawingml::convertHmmToEmu(nChildTop), MAX_SIZE); |
2330 | 0 | mpFS->singleElementNS( |
2331 | 0 | XML_a, XML_chOff, XML_x, OString::number(nX), XML_y, OString::number(nY)); |
2332 | 0 | mpFS->singleElementNS( |
2333 | 0 | XML_a, XML_chExt, XML_cx, OString::number(nCx), XML_cy, OString::number(nCy)); |
2334 | 0 | } |
2335 | |
|
2336 | 0 | mpFS->endElementNS( nXmlNamespace, XML_xfrm ); |
2337 | 0 | } |
2338 | | |
2339 | | void DrawingML::WriteShapeTransformation( const Reference< XShape >& rXShape, sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, bool bSuppressRotation, bool bSuppressFlipping, bool bFlippedBeforeRotation ) |
2340 | 0 | { |
2341 | 0 | SAL_INFO("oox.shape", "write shape transformation"); |
2342 | | |
2343 | 0 | Degree100 nRotation; |
2344 | 0 | Degree100 nCameraRotation; |
2345 | | // The rotation from the imported file in 1/60000 deg (more precise than RotateAngle) |
2346 | 0 | std::optional<sal_Int32> oMSORotation; |
2347 | 0 | awt::Point aPos = rXShape->getPosition(); |
2348 | 0 | awt::Size aSize = rXShape->getSize(); |
2349 | |
|
2350 | 0 | bool bFlipHWrite = bFlipH && !bSuppressFlipping; |
2351 | 0 | bool bFlipVWrite = bFlipV && !bSuppressFlipping; |
2352 | 0 | bFlipH = bFlipH && !bFlippedBeforeRotation; |
2353 | 0 | bFlipV = bFlipV && !bFlippedBeforeRotation; |
2354 | |
|
2355 | 0 | if (isDiagaramExport()) |
2356 | 0 | { |
2357 | | // need to offset from top-left of Diagram's GroupObject |
2358 | 0 | SdrObject* pShape(SdrObject::getSdrObjectFromXShape(rXShape)); |
2359 | |
|
2360 | 0 | if (nullptr != pShape) |
2361 | 0 | { |
2362 | 0 | std::shared_ptr< svx::diagram::DiagramHelper_svx > pDiagramHelper(pShape->getDiagramHelper()); |
2363 | |
|
2364 | 0 | if (pDiagramHelper) |
2365 | 0 | { |
2366 | 0 | const Reference< XShape >& rRootShape(pDiagramHelper->getRootShape()); |
2367 | 0 | awt::Point aRootPos = rRootShape->getPosition(); |
2368 | 0 | aPos.X -= aRootPos.X; |
2369 | 0 | aPos.Y -= aRootPos.Y; |
2370 | 0 | } |
2371 | 0 | } |
2372 | 0 | } |
2373 | |
|
2374 | 0 | if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is()) |
2375 | 0 | { |
2376 | 0 | awt::Point aParentPos = m_xParent->getPosition(); |
2377 | 0 | aPos.X -= aParentPos.X; |
2378 | 0 | aPos.Y -= aParentPos.Y; |
2379 | 0 | } |
2380 | |
|
2381 | 0 | if ( aSize.Width < 0 ) |
2382 | 0 | aSize.Width = 1000; |
2383 | 0 | if ( aSize.Height < 0 ) |
2384 | 0 | aSize.Height = 1000; |
2385 | 0 | if (!bSuppressRotation) |
2386 | 0 | { |
2387 | 0 | SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rXShape); |
2388 | 0 | nRotation = pShape ? pShape->GetRotateAngle() : 0_deg100; |
2389 | 0 | if ( GetDocumentType() != DOCUMENT_DOCX ) |
2390 | 0 | { |
2391 | 0 | int faccos=bFlipV ? -1 : 1; |
2392 | 0 | int facsin=bFlipH ? -1 : 1; |
2393 | 0 | aPos.X-=(1-faccos*cos(toRadians(nRotation)))*aSize.Width/2-facsin*sin(toRadians(nRotation))*aSize.Height/2; |
2394 | 0 | aPos.Y-=(1-faccos*cos(toRadians(nRotation)))*aSize.Height/2+facsin*sin(toRadians(nRotation))*aSize.Width/2; |
2395 | 0 | } |
2396 | 0 | else if (m_xParent.is() && nRotation != 0_deg100) |
2397 | 0 | { |
2398 | | // Position for rotated shapes inside group is not set by DocxSdrExport. |
2399 | 0 | basegfx::B2DRange aRect(-aSize.Width / 2.0, -aSize.Height / 2.0, aSize.Width / 2.0, |
2400 | 0 | aSize.Height / 2.0); |
2401 | 0 | basegfx::B2DHomMatrix aRotateMatrix = |
2402 | 0 | basegfx::utils::createRotateB2DHomMatrix(toRadians(nRotation)); |
2403 | 0 | aRect.transform(aRotateMatrix); |
2404 | 0 | aPos.X += -aSize.Width / 2.0 - aRect.getMinX(); |
2405 | 0 | aPos.Y += -aSize.Height / 2.0 - aRect.getMinY(); |
2406 | 0 | } |
2407 | | |
2408 | | // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here. |
2409 | 0 | uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY); |
2410 | 0 | uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); |
2411 | 0 | if (xPropertySetInfo->hasPropertyByName(u"RotateAngle"_ustr)) |
2412 | 0 | { |
2413 | 0 | sal_Int32 nTmp; |
2414 | 0 | if (xPropertySet->getPropertyValue(u"RotateAngle"_ustr) >>= nTmp) |
2415 | 0 | nRotation = Degree100(nTmp); |
2416 | 0 | } |
2417 | | |
2418 | | // As long as the support of MS Office 3D-features is rudimentary, we restore the original |
2419 | | // values from InteropGrabBag. This affects images and custom shapes. |
2420 | 0 | if (xPropertySetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG)) |
2421 | 0 | { |
2422 | 0 | uno::Sequence<beans::PropertyValue> aGrabBagProps; |
2423 | 0 | xPropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBagProps; |
2424 | 0 | auto pMSORotation = std::find_if( |
2425 | 0 | std::cbegin(aGrabBagProps), std::cend(aGrabBagProps), |
2426 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "mso-rotation-angle"; }); |
2427 | 0 | sal_Int32 nMSORotation = 0; |
2428 | 0 | if (pMSORotation != std::cend(aGrabBagProps) && (pMSORotation->Value >>= nMSORotation)) |
2429 | 0 | oMSORotation = nMSORotation; |
2430 | 0 | auto p3DEffectProps = std::find_if( |
2431 | 0 | std::cbegin(aGrabBagProps), std::cend(aGrabBagProps), |
2432 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; }); |
2433 | 0 | if (p3DEffectProps != std::cend(aGrabBagProps)) |
2434 | 0 | { |
2435 | 0 | uno::Sequence<beans::PropertyValue> a3DEffectProps; |
2436 | 0 | p3DEffectProps->Value >>= a3DEffectProps; |
2437 | | // We have imported a scene3d. |
2438 | 0 | if (rXShape->getShapeType() == "com.sun.star.drawing.CustomShape") |
2439 | 0 | { |
2440 | 0 | WriteTransformation( |
2441 | 0 | rXShape, |
2442 | 0 | tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), |
2443 | 0 | nXmlNamespace, bFlipHWrite, bFlipVWrite, oMSORotation.value_or(0)); |
2444 | 0 | return; |
2445 | 0 | } |
2446 | 0 | if (rXShape->getShapeType() == "com.sun.star.drawing.GraphicObjectShape") |
2447 | 0 | { |
2448 | | // tdf#133037: restore original rotate angle of image before output |
2449 | 0 | auto pCameraProps = std::find_if( |
2450 | 0 | std::cbegin(a3DEffectProps), std::cend(a3DEffectProps), |
2451 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "Camera"; }); |
2452 | 0 | if (pCameraProps != std::cend(a3DEffectProps)) |
2453 | 0 | { |
2454 | 0 | uno::Sequence<beans::PropertyValue> aCameraProps; |
2455 | 0 | pCameraProps->Value >>= aCameraProps; |
2456 | 0 | auto pZRotationProp = std::find_if( |
2457 | 0 | std::cbegin(aCameraProps), std::cend(aCameraProps), |
2458 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "rotRev"; }); |
2459 | 0 | if (pZRotationProp != std::cend(aCameraProps)) |
2460 | 0 | { |
2461 | 0 | sal_Int32 nTmp = 0; |
2462 | 0 | pZRotationProp->Value >>= nTmp; |
2463 | 0 | nCameraRotation = MSORotateAngleToAPIAngle(nTmp); |
2464 | | // FixMe tdf#160327. Vertical flip will be false. |
2465 | 0 | } |
2466 | 0 | } |
2467 | 0 | } |
2468 | 0 | } |
2469 | 0 | } |
2470 | 0 | } // end if (!bSuppressRotation) |
2471 | | |
2472 | | // OOXML flips shapes before rotating them. |
2473 | 0 | if(bFlipH != bFlipV) |
2474 | 0 | nRotation = 36000_deg100 - nRotation; |
2475 | |
|
2476 | 0 | sal_Int32 nOOXRotation = ExportRotateClockwisify(nRotation + nCameraRotation); |
2477 | | // When the imported angle rounds to the angle that is about to be written, write |
2478 | | // the imported angle, so an unchanged shape keeps the full precision of the file |
2479 | 0 | if (oMSORotation && nCameraRotation == 0_deg100 |
2480 | 0 | && NormAngle36000(nRotation) == MSORotateAngleToAPIAngle(*oMSORotation)) |
2481 | 0 | nOOXRotation = *oMSORotation; |
2482 | |
|
2483 | 0 | WriteTransformation(rXShape, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace, |
2484 | 0 | bFlipHWrite, bFlipVWrite, nOOXRotation, IsGroupShape( rXShape )); |
2485 | 0 | } |
2486 | | |
2487 | | static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, std::u16string_view rURL) |
2488 | 0 | { |
2489 | 0 | Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW); |
2490 | 0 | Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW); |
2491 | 0 | sal_uInt32 nPageCount = xDrawPages->getCount(); |
2492 | 0 | OUString sTarget; |
2493 | |
|
2494 | 0 | for (sal_uInt32 i = 0; i < nPageCount; ++i) |
2495 | 0 | { |
2496 | 0 | Reference<XDrawPage> xDrawPage; |
2497 | 0 | xDrawPages->getByIndex(i) >>= xDrawPage; |
2498 | 0 | Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY); |
2499 | 0 | if (!xNamed) |
2500 | 0 | continue; |
2501 | 0 | OUString sSlideName = "#" + xNamed->getName(); |
2502 | 0 | if (rURL == sSlideName) |
2503 | 0 | { |
2504 | 0 | sTarget = "slide" + OUString::number(i + 1) + ".xml"; |
2505 | 0 | break; |
2506 | 0 | } |
2507 | 0 | else // If URL is linked to a shape, assign it's slide as target |
2508 | 0 | { |
2509 | 0 | Reference<XShapes> xShapes = xDrawPage; |
2510 | 0 | sal_uInt32 nShapes = xShapes->getCount(); |
2511 | 0 | for (sal_uInt32 j = 0; j < nShapes; j++) |
2512 | 0 | { |
2513 | 0 | Reference<XShape> xShape; |
2514 | 0 | xShapes->getByIndex(j) >>= xShape; |
2515 | 0 | Reference<container::XNamed> xName(xShape, UNO_QUERY); |
2516 | 0 | if (!xName) |
2517 | 0 | continue; |
2518 | 0 | OUString sShapeName = "#" + xName->getName(); |
2519 | 0 | if (rURL == sShapeName) |
2520 | 0 | { |
2521 | 0 | sTarget = "slide" + OUString::number(i + 1) + ".xml"; |
2522 | 0 | break; |
2523 | 0 | } |
2524 | 0 | } |
2525 | 0 | } |
2526 | 0 | } |
2527 | 0 | if (sTarget.isEmpty()) |
2528 | 0 | { |
2529 | 0 | size_t nSplit = rURL.rfind(' '); |
2530 | 0 | if (nSplit != std::u16string_view::npos) |
2531 | 0 | sTarget = OUString::Concat("slide") + rURL.substr(nSplit + 1) + ".xml"; |
2532 | 0 | } |
2533 | |
|
2534 | 0 | return sTarget; |
2535 | 0 | } |
2536 | | |
2537 | | void DrawingML::WriteRunProperties(const Reference<XPropertySet>& rRun, sal_Int32 nElement, WriteRunInput& rRunInput) |
2538 | 0 | { |
2539 | 0 | Reference< XPropertySet > rXPropSet = rRun; |
2540 | 0 | Reference< XPropertyState > rXPropState( rRun, UNO_QUERY ); |
2541 | 0 | OUString usLanguage; |
2542 | 0 | bool bComplex = (rRunInput.nScriptType == css::i18n::ScriptType::COMPLEX); |
2543 | 0 | const char* bold = "0"; |
2544 | 0 | const char* italic = nullptr; |
2545 | 0 | const char* underline = nullptr; |
2546 | 0 | const char* strikeout = nullptr; |
2547 | 0 | const char* cap = nullptr; |
2548 | 0 | sal_Int32 nSize = 1800; |
2549 | 0 | sal_Int32 nCharEscapement = 0; |
2550 | 0 | sal_Int32 nCharKerning = 0; |
2551 | 0 | sal_Int32 nCharEscapementHeight = 0; |
2552 | |
|
2553 | 0 | if (nElement == XML_endParaRPr && rRunInput.bOverridingCharHeight) |
2554 | 0 | { |
2555 | 0 | nSize = rRunInput.nCharHeight; |
2556 | 0 | } |
2557 | 0 | else if (GetProperty(rXPropSet, u"CharHeight"_ustr)) |
2558 | 0 | { |
2559 | 0 | nSize = static_cast<sal_Int32>(100*(*o3tl::doAccess<float>(mAny))); |
2560 | 0 | if ( nElement == XML_rPr || nElement == XML_defRPr ) |
2561 | 0 | { |
2562 | 0 | rRunInput.bOverridingCharHeight = true; |
2563 | 0 | rRunInput.nCharHeight = nSize; |
2564 | 0 | } |
2565 | 0 | } |
2566 | |
|
2567 | 0 | if (GetProperty(rXPropSet, u"CharKerning"_ustr)) |
2568 | 0 | nCharKerning = static_cast<sal_Int32>(*o3tl::doAccess<sal_Int16>(mAny)); |
2569 | | /** While setting values in propertymap, |
2570 | | * CharKerning converted using GetTextSpacingPoint |
2571 | | * i.e set @ https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/textcharacterproperties.cxx#129 |
2572 | | * therefore to get original value CharKerning need to be convert. |
2573 | | * https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/drawingmltypes.cxx#95 |
2574 | | **/ |
2575 | 0 | nCharKerning = toTextSpacingPoint(nCharKerning); |
2576 | |
|
2577 | 0 | if ((bComplex && GetProperty(rXPropSet, u"CharWeightComplex"_ustr)) |
2578 | 0 | || GetProperty(rXPropSet, u"CharWeight"_ustr)) |
2579 | 0 | { |
2580 | 0 | if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD ) |
2581 | 0 | bold = "1"; |
2582 | 0 | } |
2583 | |
|
2584 | 0 | if ((bComplex && GetProperty(rXPropSet, u"CharPostureComplex"_ustr)) |
2585 | 0 | || GetProperty(rXPropSet, u"CharPosture"_ustr)) |
2586 | 0 | switch ( *o3tl::doAccess<awt::FontSlant>(mAny) ) |
2587 | 0 | { |
2588 | 0 | case awt::FontSlant_OBLIQUE : |
2589 | 0 | case awt::FontSlant_ITALIC : |
2590 | 0 | italic = "1"; |
2591 | 0 | break; |
2592 | 0 | default: |
2593 | 0 | break; |
2594 | 0 | } |
2595 | | |
2596 | 0 | if (GetProperty(rXPropSet, u"CharUnderline"_ustr)) |
2597 | 0 | { |
2598 | 0 | switch ( *o3tl::doAccess<sal_Int16>(mAny) ) |
2599 | 0 | { |
2600 | 0 | case awt::FontUnderline::NONE : |
2601 | 0 | underline = "none"; |
2602 | 0 | break; |
2603 | 0 | case awt::FontUnderline::SINGLE : |
2604 | 0 | underline = "sng"; |
2605 | 0 | break; |
2606 | 0 | case awt::FontUnderline::DOUBLE : |
2607 | 0 | underline = "dbl"; |
2608 | 0 | break; |
2609 | 0 | case awt::FontUnderline::DOTTED : |
2610 | 0 | underline = "dotted"; |
2611 | 0 | break; |
2612 | 0 | case awt::FontUnderline::DASH : |
2613 | 0 | underline = "dash"; |
2614 | 0 | break; |
2615 | 0 | case awt::FontUnderline::LONGDASH : |
2616 | 0 | underline = "dashLong"; |
2617 | 0 | break; |
2618 | 0 | case awt::FontUnderline::DASHDOT : |
2619 | 0 | underline = "dotDash"; |
2620 | 0 | break; |
2621 | 0 | case awt::FontUnderline::DASHDOTDOT : |
2622 | 0 | underline = "dotDotDash"; |
2623 | 0 | break; |
2624 | 0 | case awt::FontUnderline::WAVE : |
2625 | 0 | underline = "wavy"; |
2626 | 0 | break; |
2627 | 0 | case awt::FontUnderline::DOUBLEWAVE : |
2628 | 0 | underline = "wavyDbl"; |
2629 | 0 | break; |
2630 | 0 | case awt::FontUnderline::BOLD : |
2631 | 0 | underline = "heavy"; |
2632 | 0 | break; |
2633 | 0 | case awt::FontUnderline::BOLDDOTTED : |
2634 | 0 | underline = "dottedHeavy"; |
2635 | 0 | break; |
2636 | 0 | case awt::FontUnderline::BOLDDASH : |
2637 | 0 | underline = "dashHeavy"; |
2638 | 0 | break; |
2639 | 0 | case awt::FontUnderline::BOLDLONGDASH : |
2640 | 0 | underline = "dashLongHeavy"; |
2641 | 0 | break; |
2642 | 0 | case awt::FontUnderline::BOLDDASHDOT : |
2643 | 0 | underline = "dotDashHeavy"; |
2644 | 0 | break; |
2645 | 0 | case awt::FontUnderline::BOLDDASHDOTDOT : |
2646 | 0 | underline = "dotDotDashHeavy"; |
2647 | 0 | break; |
2648 | 0 | case awt::FontUnderline::BOLDWAVE : |
2649 | 0 | underline = "wavyHeavy"; |
2650 | 0 | break; |
2651 | 0 | } |
2652 | 0 | } |
2653 | | |
2654 | 0 | if (GetProperty(rXPropSet, u"CharStrikeout"_ustr)) |
2655 | 0 | { |
2656 | 0 | switch ( *o3tl::doAccess<sal_Int16>(mAny) ) |
2657 | 0 | { |
2658 | 0 | case awt::FontStrikeout::NONE : |
2659 | 0 | strikeout = "noStrike"; |
2660 | 0 | break; |
2661 | 0 | case awt::FontStrikeout::SINGLE : |
2662 | | // LibO supports further values of character |
2663 | | // strikeout, OOXML standard (20.1.10.78, |
2664 | | // ST_TextStrikeType) however specifies only |
2665 | | // 3 - single, double and none. Approximate |
2666 | | // the remaining ones by single strike (better |
2667 | | // some strike than none at all). |
2668 | | // TODO: figure out how to do this better |
2669 | 0 | case awt::FontStrikeout::BOLD : |
2670 | 0 | case awt::FontStrikeout::SLASH : |
2671 | 0 | case awt::FontStrikeout::X : |
2672 | 0 | strikeout = "sngStrike"; |
2673 | 0 | break; |
2674 | 0 | case awt::FontStrikeout::DOUBLE : |
2675 | 0 | strikeout = "dblStrike"; |
2676 | 0 | break; |
2677 | 0 | } |
2678 | 0 | } |
2679 | | |
2680 | 0 | bool bLang = false; |
2681 | 0 | switch (rRunInput.nScriptType) |
2682 | 0 | { |
2683 | 0 | case css::i18n::ScriptType::ASIAN: |
2684 | 0 | bLang = GetProperty(rXPropSet, u"CharLocaleAsian"_ustr); break; |
2685 | 0 | case css::i18n::ScriptType::COMPLEX: |
2686 | 0 | bLang = GetProperty(rXPropSet, u"CharLocaleComplex"_ustr); break; |
2687 | 0 | default: |
2688 | 0 | bLang = GetProperty(rXPropSet, u"CharLocale"_ustr); break; |
2689 | 0 | } |
2690 | | |
2691 | 0 | if (bLang) |
2692 | 0 | { |
2693 | 0 | css::lang::Locale aLocale; |
2694 | 0 | mAny >>= aLocale; |
2695 | 0 | LanguageTag aLanguageTag( aLocale); |
2696 | 0 | if (!aLanguageTag.isSystemLocale()) |
2697 | 0 | usLanguage = aLanguageTag.getBcp47MS(); |
2698 | 0 | } |
2699 | |
|
2700 | 0 | if (rRunInput.bCheckDirect && GetDirectProperty(rXPropSet, rXPropState, u"CharEscapement"_ustr)) |
2701 | 0 | mAny >>= nCharEscapement; |
2702 | |
|
2703 | 0 | if (rRunInput.bCheckDirect && GetDirectProperty(rXPropSet, rXPropState, u"CharEscapementHeight"_ustr)) |
2704 | 0 | mAny >>= nCharEscapementHeight; |
2705 | |
|
2706 | 0 | if (DFLT_ESC_AUTO_SUPER == nCharEscapement) |
2707 | 0 | { |
2708 | | // Raised by the differences between the ascenders (ascent = baseline to top of highest letter). |
2709 | | // The ascent is generally about 80% of the total font height. |
2710 | | // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER) |
2711 | 0 | nCharEscapement = .8 * (100 - nCharEscapementHeight); |
2712 | 0 | } |
2713 | 0 | else if (DFLT_ESC_AUTO_SUB == nCharEscapement) |
2714 | 0 | { |
2715 | | // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter). |
2716 | | // The descent is generally about 20% of the total font height. |
2717 | | // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB) |
2718 | 0 | nCharEscapement = .2 * -(100 - nCharEscapementHeight); |
2719 | 0 | } |
2720 | |
|
2721 | 0 | if (nCharEscapement && nCharEscapementHeight) |
2722 | 0 | { |
2723 | 0 | nSize = (nSize * nCharEscapementHeight) / 100; |
2724 | | // MSO uses default ~58% size |
2725 | 0 | nSize = (nSize / 0.58); |
2726 | 0 | } |
2727 | |
|
2728 | 0 | if (GetProperty(rXPropSet, u"CharCaseMap"_ustr)) |
2729 | 0 | { |
2730 | 0 | switch ( *o3tl::doAccess<sal_Int16>(mAny) ) |
2731 | 0 | { |
2732 | 0 | case CaseMap::UPPERCASE : |
2733 | 0 | cap = "all"; |
2734 | 0 | break; |
2735 | 0 | case CaseMap::SMALLCAPS : |
2736 | 0 | cap = "small"; |
2737 | 0 | break; |
2738 | 0 | } |
2739 | 0 | } |
2740 | | |
2741 | 0 | assert(nSize >= 100 && "Minimum accepted value for fontsize(ST_TextFontSize) is 100"); |
2742 | | |
2743 | | // The data model of a Diagram defines the language, also holds no paragraph properties |
2744 | 0 | if (isDiagramModelTextExport()) |
2745 | 0 | { |
2746 | 0 | mpFS->singleElementNS(XML_a, nElement, XML_lang, |
2747 | 0 | sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty())); |
2748 | 0 | return; |
2749 | 0 | } |
2750 | | |
2751 | 0 | mpFS->startElementNS( XML_a, nElement, |
2752 | 0 | XML_lang, sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty()), |
2753 | 0 | XML_sz, OString::number(nSize), |
2754 | 0 | XML_b, bold, |
2755 | 0 | XML_i, italic, |
2756 | 0 | XML_u, underline, |
2757 | | // For Condensed character spacing spc value is negative. |
2758 | 0 | XML_spc, sax_fastparser::UseIf(OString::number(nCharKerning), nCharKerning != 0), |
2759 | 0 | XML_strike, strikeout, |
2760 | 0 | XML_baseline, sax_fastparser::UseIf(OString::number(nCharEscapement*1000), nCharEscapement != 0), |
2761 | 0 | XML_cap, cap ); |
2762 | | |
2763 | | // Fontwork-shapes in LO have text outline and fill from shape stroke and shape fill |
2764 | | // PowerPoint has this as run properties |
2765 | 0 | if (IsFontworkShape(rRunInput.xShapePropSet)) |
2766 | 0 | { |
2767 | 0 | WriteOutline(rRunInput.xShapePropSet); |
2768 | 0 | WriteBlipOrNormalFill(rRunInput.xShapePropSet, u"Graphic"_ustr); |
2769 | 0 | WriteShapeEffects(rRunInput.xShapePropSet); |
2770 | 0 | } |
2771 | 0 | else |
2772 | 0 | { |
2773 | | // mso doesn't like text color to be placed after typeface |
2774 | 0 | if (GetProperty(rXPropSet, u"CharColor"_ustr)) |
2775 | 0 | { |
2776 | 0 | ::Color color( ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny) ); |
2777 | 0 | SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO)); |
2778 | | |
2779 | | // WriteSolidFill() handles MAX_PERCENT as "no transparency". |
2780 | 0 | sal_Int32 nTransparency = MAX_PERCENT; |
2781 | 0 | if (rXPropSet->getPropertySetInfo()->hasPropertyByName(u"CharTransparence"_ustr)) |
2782 | 0 | { |
2783 | 0 | rXPropSet->getPropertyValue(u"CharTransparence"_ustr) >>= nTransparency; |
2784 | | // UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML |
2785 | | // tracks opacity. |
2786 | 0 | nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT); |
2787 | 0 | } |
2788 | |
|
2789 | 0 | bool bContoured = false; |
2790 | 0 | if (GetProperty(rXPropSet, u"CharContoured"_ustr)) |
2791 | 0 | bContoured = *o3tl::doAccess<bool>(mAny); |
2792 | | |
2793 | | // tdf#127696 If the CharContoured is true, then the text color is white and the outline color is the CharColor. |
2794 | 0 | if (bContoured) |
2795 | 0 | { |
2796 | 0 | mpFS->startElementNS(XML_a, XML_ln); |
2797 | 0 | if (color == COL_AUTO) |
2798 | 0 | { |
2799 | 0 | mbIsBackgroundDark ? WriteSolidFill(COL_WHITE) : WriteSolidFill(COL_BLACK); |
2800 | 0 | } |
2801 | 0 | else |
2802 | 0 | { |
2803 | 0 | color.SetAlpha(255); |
2804 | 0 | if (!WriteSchemeColor(u"CharComplexColor"_ustr, rXPropSet, rRunInput.bUseTextSchemeColors)) |
2805 | 0 | WriteSolidFill(color, nTransparency); |
2806 | 0 | } |
2807 | 0 | mpFS->endElementNS(XML_a, XML_ln); |
2808 | |
|
2809 | 0 | WriteSolidFill(COL_WHITE); |
2810 | 0 | } |
2811 | | // tdf#104219 In LibreOffice and MS Office, there are two types of colors: |
2812 | | // Automatic and Fixed. OOXML is setting automatic color, by not providing color. |
2813 | 0 | else if( color != COL_AUTO ) |
2814 | 0 | { |
2815 | 0 | color.SetAlpha(255); |
2816 | | // TODO: special handle embossed/engraved |
2817 | 0 | if (!WriteSchemeColor(u"CharComplexColor"_ustr, rXPropSet, rRunInput.bUseTextSchemeColors)) |
2818 | 0 | { |
2819 | 0 | WriteSolidFill(color, nTransparency); |
2820 | 0 | } |
2821 | 0 | } |
2822 | 0 | else if (GetDocumentType() == DOCUMENT_PPTX) |
2823 | 0 | { |
2824 | | // Resolve COL_AUTO for PPTX since MS Powerpoint doesn't have automatic colors. |
2825 | 0 | bool bIsTextBackgroundDark = mbIsBackgroundDark; |
2826 | 0 | if (rRunInput.xShapePropSet.is() && GetProperty(rRunInput.xShapePropSet, u"FillStyle"_ustr) |
2827 | 0 | && mAny.get<FillStyle>() != FillStyle_NONE |
2828 | 0 | && GetProperty(rRunInput.xShapePropSet, u"FillColor"_ustr)) |
2829 | 0 | { |
2830 | 0 | ::Color aShapeFillColor(ColorTransparency, mAny.get<sal_uInt32>()); |
2831 | 0 | bIsTextBackgroundDark = aShapeFillColor.IsDark(); |
2832 | 0 | } |
2833 | |
|
2834 | 0 | if (bIsTextBackgroundDark) |
2835 | 0 | WriteSolidFill(COL_WHITE); |
2836 | 0 | else |
2837 | 0 | WriteSolidFill(COL_BLACK); |
2838 | 0 | } |
2839 | |
|
2840 | 0 | if (rRunInput.xShapePropSet.is() && GetDocumentType() != DOCUMENT_DOCX) |
2841 | 0 | { |
2842 | 0 | mpFS->startElementNS(XML_a, XML_effectLst); |
2843 | 0 | WriteTextGlowEffect(rRunInput.xShapePropSet); |
2844 | 0 | mpFS->endElementNS(XML_a, XML_effectLst); |
2845 | 0 | } |
2846 | 0 | } |
2847 | 0 | } |
2848 | | |
2849 | | // tdf#128096, exporting XML_highlight to docx already works fine, |
2850 | | // so make sure this code is only run when exporting to pptx, just in case |
2851 | 0 | if (GetDocumentType() == DOCUMENT_PPTX) |
2852 | 0 | { |
2853 | 0 | if (GetProperty(rXPropSet, u"CharBackColor"_ustr)) |
2854 | 0 | { |
2855 | 0 | ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny)); |
2856 | 0 | if( color != COL_AUTO ) |
2857 | 0 | { |
2858 | 0 | mpFS->startElementNS(XML_a, XML_highlight); |
2859 | 0 | WriteColor( color ); |
2860 | 0 | mpFS->endElementNS( XML_a, XML_highlight ); |
2861 | 0 | } |
2862 | 0 | } |
2863 | 0 | } |
2864 | |
|
2865 | 0 | if (underline |
2866 | 0 | && (GetProperty(rXPropSet, u"CharUnderlineColor"_ustr))) |
2867 | 0 | { |
2868 | 0 | ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny)); |
2869 | | // if color is automatic, then we shouldn't write information about color but to take color from character |
2870 | 0 | if( color != COL_AUTO ) |
2871 | 0 | { |
2872 | 0 | mpFS->startElementNS(XML_a, XML_uFill); |
2873 | 0 | if (!WriteSchemeColor(u"CharUnderlineComplexColor"_ustr, rXPropSet, |
2874 | 0 | rRunInput.bUseTextSchemeColors)) |
2875 | 0 | WriteSolidFill( color ); |
2876 | 0 | mpFS->endElementNS( XML_a, XML_uFill ); |
2877 | 0 | } |
2878 | 0 | else |
2879 | 0 | { |
2880 | 0 | mpFS->singleElementNS(XML_a, XML_uFillTx); |
2881 | 0 | } |
2882 | 0 | } |
2883 | |
|
2884 | 0 | auto getExportFontName = [](const OUString& rTypeface, const OUString& rStyleName) -> OUString |
2885 | 0 | { |
2886 | 0 | if (rStyleName.isEmpty()) |
2887 | 0 | return rTypeface; |
2888 | 0 | if (OutputDevice* pDev = Application::GetDefaultDevice()) |
2889 | 0 | { |
2890 | 0 | OUString aLegacy; |
2891 | 0 | if (pDev->GetLegacyFontName(rTypeface, rStyleName, WEIGHT_DONTKNOW, ITALIC_DONTKNOW, aLegacy)) |
2892 | 0 | return aLegacy; |
2893 | 0 | } |
2894 | 0 | return rTypeface; |
2895 | 0 | }; |
2896 | |
|
2897 | 0 | if (GetProperty(rXPropSet, u"CharFontName"_ustr)) |
2898 | 0 | { |
2899 | 0 | const char* const pitch = nullptr; |
2900 | 0 | const char* const charset = nullptr; |
2901 | 0 | OUString usTypeface; |
2902 | |
|
2903 | 0 | mAny >>= usTypeface; |
2904 | |
|
2905 | 0 | OUString usStyleName; |
2906 | 0 | if (GetProperty(rXPropSet, u"CharFontStyleName"_ustr)) |
2907 | 0 | mAny >>= usStyleName; |
2908 | 0 | usTypeface = getExportFontName(usTypeface, usStyleName); |
2909 | |
|
2910 | 0 | if (!mbEmbedFonts || EmbeddedFontsManager::isCommonFont(usTypeface)) |
2911 | 0 | { |
2912 | 0 | OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) ); |
2913 | 0 | if (!aSubstName.isEmpty()) |
2914 | 0 | usTypeface = aSubstName; |
2915 | 0 | } |
2916 | |
|
2917 | 0 | mpFS->singleElementNS( XML_a, XML_latin, |
2918 | 0 | XML_typeface, usTypeface, |
2919 | 0 | XML_pitchFamily, pitch, |
2920 | 0 | XML_charset, charset ); |
2921 | 0 | } |
2922 | |
|
2923 | 0 | if (rRunInput.bCheckDirect |
2924 | 0 | && GetDirectProperty(rXPropSet, rXPropState, |
2925 | 0 | bComplex ? u"CharFontNameComplex"_ustr : u"CharFontNameAsian"_ustr)) |
2926 | 0 | { |
2927 | 0 | const char* const pitch = nullptr; |
2928 | 0 | const char* const charset = nullptr; |
2929 | 0 | OUString usTypeface; |
2930 | |
|
2931 | 0 | mAny >>= usTypeface; |
2932 | |
|
2933 | 0 | OUString usStyleName; |
2934 | 0 | if (GetDirectProperty(rXPropSet, rXPropState, |
2935 | 0 | bComplex ? u"CharFontStyleNameComplex"_ustr : u"CharFontStyleNameAsian"_ustr)) |
2936 | 0 | mAny >>= usStyleName; |
2937 | 0 | usTypeface = getExportFontName(usTypeface, usStyleName); |
2938 | |
|
2939 | 0 | if (!mbEmbedFonts || EmbeddedFontsManager::isCommonFont(usTypeface)) |
2940 | 0 | { |
2941 | 0 | OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) ); |
2942 | 0 | if (!aSubstName.isEmpty()) |
2943 | 0 | usTypeface = aSubstName; |
2944 | 0 | } |
2945 | 0 | mpFS->singleElementNS( XML_a, bComplex ? XML_cs : XML_ea, |
2946 | 0 | XML_typeface, usTypeface, |
2947 | 0 | XML_pitchFamily, pitch, |
2948 | 0 | XML_charset, charset ); |
2949 | 0 | } |
2950 | |
|
2951 | 0 | if (rRunInput.bIsField) |
2952 | 0 | { |
2953 | 0 | Reference< XTextField > rXTextField; |
2954 | 0 | if (GetProperty(rXPropSet, u"TextField"_ustr)) |
2955 | 0 | mAny >>= rXTextField; |
2956 | 0 | if( rXTextField.is() ) |
2957 | 0 | rXPropSet.set( rXTextField, UNO_QUERY ); |
2958 | 0 | } |
2959 | | |
2960 | | // field properties starts here |
2961 | 0 | if (GetProperty(rXPropSet, u"URL"_ustr)) |
2962 | 0 | { |
2963 | 0 | OUString sURL; |
2964 | |
|
2965 | 0 | mAny >>= sURL; |
2966 | 0 | if (!sURL.isEmpty()) |
2967 | 0 | { |
2968 | 0 | OUString sName; |
2969 | 0 | if (GetProperty(rXPropSet, u"Name"_ustr)) |
2970 | 0 | mAny >>= sName; |
2971 | | // OOXML keeps the accessible name in the tooltip attribute |
2972 | 0 | const auto oTooltip = sax_fastparser::UseIf(sName, !sName.isEmpty()); |
2973 | |
|
2974 | 0 | if (!sURL.match("#action?jump=")) |
2975 | 0 | { |
2976 | 0 | bool bExtURL = URLTransformer().isExternalURL(sURL); |
2977 | 0 | sURL = bExtURL ? sURL : lcl_GetTarget(GetFB()->getModel(), sURL); |
2978 | |
|
2979 | 0 | OUString sRelId; |
2980 | 0 | if (!sURL.isEmpty()) |
2981 | 0 | { |
2982 | 0 | sRelId |
2983 | 0 | = mpFB->addRelation(mpFS->getOutputStream(), |
2984 | 0 | bExtURL ? oox::getRelationship(Relationship::HYPERLINK) |
2985 | 0 | : oox::getRelationship(Relationship::SLIDE), |
2986 | 0 | sURL, bExtURL); |
2987 | 0 | } |
2988 | 0 | if (bExtURL) |
2989 | 0 | { |
2990 | 0 | if (isDiagaramExport()) |
2991 | 0 | mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)), FSNS(XML_r, XML_id), sRelId, XML_tooltip, oTooltip); |
2992 | 0 | else |
2993 | 0 | mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, XML_tooltip, oTooltip); |
2994 | 0 | } |
2995 | 0 | else |
2996 | 0 | { |
2997 | 0 | mpFS->singleElementNS( |
2998 | 0 | XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, XML_action, |
2999 | 0 | sURL.isEmpty() ? "ppaction://noaction" : "ppaction://hlinksldjump", |
3000 | 0 | XML_tooltip, oTooltip); |
3001 | 0 | } |
3002 | 0 | } |
3003 | 0 | else |
3004 | 0 | { |
3005 | 0 | sal_Int32 nIndex = sURL.indexOf('='); |
3006 | 0 | std::u16string_view aDestination(sURL.subView(nIndex + 1)); |
3007 | 0 | mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action, |
3008 | 0 | OUString::Concat("ppaction://hlinkshowjump?jump=") + aDestination, |
3009 | 0 | XML_tooltip, oTooltip); |
3010 | 0 | } |
3011 | 0 | } |
3012 | 0 | } |
3013 | 0 | mpFS->endElementNS( XML_a, nElement ); |
3014 | 0 | } |
3015 | | |
3016 | | OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField ) |
3017 | 0 | { |
3018 | 0 | Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY ); |
3019 | 0 | OUString aFieldType, aFieldValue; |
3020 | |
|
3021 | 0 | if (GetProperty(rXPropSet, u"TextPortionType"_ustr)) |
3022 | 0 | { |
3023 | 0 | aFieldType = *o3tl::doAccess<OUString>(mAny); |
3024 | 0 | SAL_INFO("oox.shape", "field type: " << aFieldType); |
3025 | 0 | } |
3026 | | |
3027 | 0 | if( aFieldType == "TextField" ) |
3028 | 0 | { |
3029 | 0 | Reference< XTextField > rXTextField; |
3030 | 0 | if (GetProperty(rXPropSet, u"TextField"_ustr)) |
3031 | 0 | mAny >>= rXTextField; |
3032 | 0 | if( rXTextField.is() ) |
3033 | 0 | { |
3034 | 0 | rXPropSet.set( rXTextField, UNO_QUERY ); |
3035 | 0 | if( rXPropSet.is() ) |
3036 | 0 | { |
3037 | 0 | OUString aFieldKind( rXTextField->getPresentation( true ) ); |
3038 | 0 | SAL_INFO("oox.shape", "field kind: " << aFieldKind); |
3039 | 0 | if( aFieldKind == "Page" ) |
3040 | 0 | { |
3041 | 0 | aFieldValue = "slidenum"; |
3042 | 0 | } |
3043 | 0 | else if( aFieldKind == "Pages" ) |
3044 | 0 | { |
3045 | 0 | aFieldValue = "slidecount"; |
3046 | 0 | } |
3047 | 0 | else if( aFieldKind == "PageName" ) |
3048 | 0 | { |
3049 | 0 | aFieldValue = "slidename"; |
3050 | 0 | } |
3051 | 0 | else if( aFieldKind == "URL" ) |
3052 | 0 | { |
3053 | 0 | bIsURLField = true; |
3054 | 0 | if (GetProperty(rXPropSet, u"Representation"_ustr)) |
3055 | 0 | mAny >>= aFieldValue; |
3056 | |
|
3057 | 0 | } |
3058 | 0 | else if(aFieldKind == "Date") |
3059 | 0 | { |
3060 | 0 | sal_Int32 nNumFmt = -1; |
3061 | 0 | rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; |
3062 | 0 | aFieldValue = GetDatetimeTypeFromDate(static_cast<SvxDateFormat>(nNumFmt)); |
3063 | 0 | } |
3064 | 0 | else if(aFieldKind == "DateTime") |
3065 | 0 | { |
3066 | | // The date/time field of a header/footer area carries no format of its own - |
3067 | | // the page holds it - and "datetime" seems a perfect fit (see MS-OI29500). |
3068 | 0 | aFieldValue = u"datetime"_ustr; |
3069 | 0 | } |
3070 | 0 | else if(aFieldKind == "ExtTime") |
3071 | 0 | { |
3072 | 0 | sal_Int32 nNumFmt = -1; |
3073 | 0 | rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; |
3074 | 0 | aFieldValue = GetDatetimeTypeFromTime(static_cast<SvxTimeFormat>(nNumFmt)); |
3075 | 0 | } |
3076 | 0 | else if(aFieldKind == "ExtFile") |
3077 | 0 | { |
3078 | 0 | sal_Int32 nNumFmt = -1; |
3079 | 0 | rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt; |
3080 | 0 | switch(nNumFmt) |
3081 | 0 | { |
3082 | 0 | case 0: aFieldValue = "file"; // Path/File name |
3083 | 0 | break; |
3084 | 0 | case 1: aFieldValue = "file1"; // Path |
3085 | 0 | break; |
3086 | 0 | case 2: aFieldValue = "file2"; // File name without extension |
3087 | 0 | break; |
3088 | 0 | case 3: aFieldValue = "file3"; // File name with extension |
3089 | 0 | } |
3090 | 0 | } |
3091 | 0 | else if(aFieldKind == "Author") |
3092 | 0 | { |
3093 | 0 | aFieldValue = "author"; |
3094 | 0 | } |
3095 | 0 | } |
3096 | 0 | } |
3097 | 0 | } |
3098 | 0 | return aFieldValue; |
3099 | 0 | } |
3100 | | |
3101 | | OUString DrawingML::GetDatetimeTypeFromDate(SvxDateFormat eDate) |
3102 | 0 | { |
3103 | 0 | return GetDatetimeTypeFromDateTime(eDate, SvxTimeFormat::AppDefault); |
3104 | 0 | } |
3105 | | |
3106 | | OUString DrawingML::GetDatetimeTypeFromTime(SvxTimeFormat eTime) |
3107 | 0 | { |
3108 | 0 | return GetDatetimeTypeFromDateTime(SvxDateFormat::AppDefault, eTime); |
3109 | 0 | } |
3110 | | |
3111 | | OUString DrawingML::GetDatetimeTypeFromDateTime(SvxDateFormat eDate, SvxTimeFormat eTime) |
3112 | 0 | { |
3113 | 0 | OUString aDateField; |
3114 | 0 | switch (eDate) |
3115 | 0 | { |
3116 | 0 | case SvxDateFormat::StdSmall: |
3117 | 0 | case SvxDateFormat::A: |
3118 | 0 | aDateField = "datetime"; |
3119 | 0 | break; |
3120 | 0 | case SvxDateFormat::B: |
3121 | 0 | aDateField = "datetime1"; // 13/02/1996 |
3122 | 0 | break; |
3123 | 0 | case SvxDateFormat::C: |
3124 | 0 | aDateField = "datetime5"; |
3125 | 0 | break; |
3126 | 0 | case SvxDateFormat::D: |
3127 | 0 | aDateField = "datetime3"; // 13 February 1996 |
3128 | 0 | break; |
3129 | 0 | case SvxDateFormat::StdBig: |
3130 | 0 | case SvxDateFormat::E: |
3131 | 0 | case SvxDateFormat::F: |
3132 | 0 | aDateField = "datetime2"; |
3133 | 0 | break; |
3134 | 0 | default: |
3135 | 0 | break; |
3136 | 0 | } |
3137 | | |
3138 | 0 | OUString aTimeField; |
3139 | 0 | switch (eTime) |
3140 | 0 | { |
3141 | 0 | case SvxTimeFormat::Standard: |
3142 | 0 | case SvxTimeFormat::HH24_MM_SS: |
3143 | 0 | case SvxTimeFormat::HH24_MM_SS_00: |
3144 | 0 | aTimeField = "datetime11"; // 13:49:38 |
3145 | 0 | break; |
3146 | 0 | case SvxTimeFormat::HH24_MM: |
3147 | 0 | aTimeField = "datetime10"; // 13:49 |
3148 | 0 | break; |
3149 | 0 | case SvxTimeFormat::HH12_MM: |
3150 | 0 | case SvxTimeFormat::HH12_MM_AMPM: |
3151 | 0 | aTimeField = "datetime12"; // 01:49 PM |
3152 | 0 | break; |
3153 | 0 | case SvxTimeFormat::HH12_MM_SS: |
3154 | 0 | case SvxTimeFormat::HH12_MM_SS_AMPM: |
3155 | 0 | case SvxTimeFormat::HH12_MM_SS_00: |
3156 | 0 | case SvxTimeFormat::HH12_MM_SS_00_AMPM: |
3157 | 0 | aTimeField = "datetime13"; // 01:49:38 PM |
3158 | 0 | break; |
3159 | 0 | default: |
3160 | 0 | break; |
3161 | 0 | } |
3162 | | |
3163 | 0 | if (!aDateField.isEmpty() && aTimeField.isEmpty()) |
3164 | 0 | return aDateField; |
3165 | 0 | else if (!aTimeField.isEmpty() && aDateField.isEmpty()) |
3166 | 0 | return aTimeField; |
3167 | 0 | else if (!aDateField.isEmpty() && !aTimeField.isEmpty()) |
3168 | 0 | { |
3169 | 0 | if (aTimeField == "datetime11" || aTimeField == "datetime13") |
3170 | | // only datetime format that has Date and HH:MM:SS |
3171 | 0 | return u"datetime9"_ustr; // dd/mm/yyyy H:MM:SS |
3172 | 0 | else |
3173 | | // only datetime format that has Date and HH:MM |
3174 | 0 | return u"datetime8"_ustr; // dd/mm/yyyy H:MM |
3175 | 0 | } |
3176 | 0 | else |
3177 | 0 | return u""_ustr; |
3178 | 0 | } |
3179 | | |
3180 | | void DrawingML::WriteRun( const Reference< XTextRange >& rRun, |
3181 | | bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, |
3182 | | const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet) |
3183 | 0 | { |
3184 | 0 | Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY ); |
3185 | 0 | sal_Int16 nLevel = -1; |
3186 | 0 | if (GetProperty(rXPropSet, u"NumberingLevel"_ustr)) |
3187 | 0 | mAny >>= nLevel; |
3188 | |
|
3189 | 0 | bool bNumberingIsNumber = true; |
3190 | 0 | if (GetProperty(rXPropSet, u"NumberingIsNumber"_ustr)) |
3191 | 0 | mAny >>= bNumberingIsNumber; |
3192 | |
|
3193 | 0 | float nFontSize = -1; |
3194 | 0 | if (GetProperty(rXPropSet, u"CharHeight"_ustr)) |
3195 | 0 | mAny >>= nFontSize; |
3196 | |
|
3197 | 0 | bool bIsURLField = false; |
3198 | 0 | OUString sFieldValue = GetFieldValue( rRun, bIsURLField ); |
3199 | 0 | bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField ); |
3200 | |
|
3201 | 0 | OUString sText = rRun->getString(); |
3202 | | |
3203 | | //if there is no text following the bullet, add a space after the bullet |
3204 | 0 | if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() ) |
3205 | 0 | sText=" "; |
3206 | |
|
3207 | 0 | if ( bIsURLField ) |
3208 | 0 | sText = sFieldValue; |
3209 | |
|
3210 | 0 | if( sText.isEmpty()) |
3211 | 0 | { |
3212 | 0 | Reference< XPropertySet > xPropSet( rRun, UNO_QUERY ); |
3213 | |
|
3214 | 0 | try |
3215 | 0 | { |
3216 | 0 | if( !xPropSet.is() || !( xPropSet->getPropertyValue( u"PlaceholderText"_ustr ) >>= sText ) ) |
3217 | 0 | return; |
3218 | 0 | if( sText.isEmpty() ) |
3219 | 0 | return; |
3220 | 0 | } |
3221 | 0 | catch (const Exception &) |
3222 | 0 | { |
3223 | 0 | return; |
3224 | 0 | } |
3225 | 0 | } |
3226 | | |
3227 | 0 | if (sText == "\n") |
3228 | 0 | { |
3229 | | // Empty run? Do not forget to write the font size in case of pptx: |
3230 | 0 | if ((GetDocumentType() == DOCUMENT_PPTX) && (nFontSize != -1)) |
3231 | 0 | { |
3232 | 0 | mpFS->startElementNS(XML_a, XML_br); |
3233 | 0 | mpFS->singleElementNS(XML_a, XML_rPr, XML_sz, |
3234 | 0 | OString::number(nFontSize * 100)); |
3235 | 0 | mpFS->endElementNS(XML_a, XML_br); |
3236 | 0 | } |
3237 | 0 | else |
3238 | 0 | mpFS->singleElementNS(XML_a, XML_br); |
3239 | 0 | } |
3240 | 0 | else |
3241 | 0 | { |
3242 | 0 | if( bWriteField ) |
3243 | 0 | { |
3244 | 0 | OString sUUID(comphelper::xml::generateGUIDString()); |
3245 | 0 | mpFS->startElementNS( XML_a, XML_fld, |
3246 | 0 | XML_id, sUUID.getStr(), |
3247 | 0 | XML_type, sFieldValue ); |
3248 | 0 | } |
3249 | 0 | else |
3250 | 0 | { |
3251 | 0 | mpFS->startElementNS(XML_a, XML_r); |
3252 | 0 | } |
3253 | |
|
3254 | 0 | Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY ); |
3255 | |
|
3256 | 0 | WriteRunInput aInput; |
3257 | 0 | aInput.bIsField = bIsURLField; |
3258 | 0 | aInput.bCheckDirect = true; |
3259 | 0 | aInput.nScriptType = GetScriptType(sText); |
3260 | 0 | aInput.xShapePropSet = rXShapePropSet; |
3261 | 0 | aInput.bOverridingCharHeight = rbOverridingCharHeight; |
3262 | 0 | aInput.nCharHeight = rnCharHeight; |
3263 | 0 | WriteRunProperties(xPropSet, XML_rPr, aInput); |
3264 | 0 | rbOverridingCharHeight = aInput.bOverridingCharHeight; |
3265 | 0 | rnCharHeight = aInput.nCharHeight; |
3266 | |
|
3267 | 0 | mpFS->startElementNS(XML_a, XML_t); |
3268 | 0 | mpFS->writeEscaped( sText ); |
3269 | 0 | mpFS->endElementNS( XML_a, XML_t ); |
3270 | |
|
3271 | 0 | if( bWriteField ) |
3272 | 0 | mpFS->endElementNS( XML_a, XML_fld ); |
3273 | 0 | else |
3274 | 0 | mpFS->endElementNS( XML_a, XML_r ); |
3275 | 0 | } |
3276 | 0 | } |
3277 | | |
3278 | | static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth) |
3279 | 0 | { |
3280 | 0 | OUString sPrefixSuffix; |
3281 | |
|
3282 | 0 | if (bPBoth) |
3283 | 0 | sPrefixSuffix = "ParenBoth"; |
3284 | 0 | else if (bPBehind) |
3285 | 0 | sPrefixSuffix = "ParenR"; |
3286 | 0 | else if (bSDot) |
3287 | 0 | sPrefixSuffix = "Period"; |
3288 | | |
3289 | | // For some of these, there is no corresponding valid value, so we use val+"Period" |
3290 | 0 | switch( nNumberingType ) |
3291 | 0 | { |
3292 | 0 | case SVX_NUM_CHARS_UPPER_LETTER_N : |
3293 | 0 | case SVX_NUM_CHARS_UPPER_LETTER : |
3294 | 0 | if (sPrefixSuffix.isEmpty()) |
3295 | 0 | return u"alphaUcPeriod"_ustr; |
3296 | 0 | else |
3297 | 0 | return "alphaUc" + sPrefixSuffix; |
3298 | | |
3299 | 0 | case SVX_NUM_CHARS_LOWER_LETTER_N : |
3300 | 0 | case SVX_NUM_CHARS_LOWER_LETTER : |
3301 | 0 | if (sPrefixSuffix.isEmpty()) |
3302 | 0 | return u"alphaLcPeriod"_ustr; |
3303 | 0 | else |
3304 | 0 | return "alphaLc" + sPrefixSuffix; |
3305 | | |
3306 | 0 | case SVX_NUM_ROMAN_UPPER : |
3307 | 0 | if (sPrefixSuffix.isEmpty()) |
3308 | 0 | return u"romanUcPeriod"_ustr; |
3309 | 0 | else |
3310 | 0 | return "romanUc" + sPrefixSuffix; |
3311 | | |
3312 | 0 | case SVX_NUM_ROMAN_LOWER : |
3313 | 0 | if (sPrefixSuffix.isEmpty()) |
3314 | 0 | return u"romanLcPeriod"_ustr; |
3315 | 0 | else |
3316 | 0 | return "romanLc" + sPrefixSuffix; |
3317 | | |
3318 | 0 | case SVX_NUM_ARABIC : |
3319 | 0 | { |
3320 | 0 | if (sPrefixSuffix.isEmpty()) |
3321 | 0 | return u"arabicPlain"_ustr; |
3322 | 0 | else |
3323 | 0 | return "arabic" + sPrefixSuffix; |
3324 | 0 | } |
3325 | 0 | default: |
3326 | 0 | break; |
3327 | 0 | } |
3328 | | |
3329 | 0 | return OUString(); |
3330 | 0 | } |
3331 | | |
3332 | | void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel ) |
3333 | 0 | { |
3334 | 0 | if (nLevel < 0 || !GetProperty(rXPropSet, u"NumberingRules"_ustr)) |
3335 | 0 | return; |
3336 | | |
3337 | 0 | Reference< XIndexAccess > rXIndexAccess; |
3338 | |
|
3339 | 0 | if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount()) |
3340 | 0 | return; |
3341 | | |
3342 | 0 | SAL_INFO("oox.shape", "numbering rules"); |
3343 | | |
3344 | 0 | Sequence<PropertyValue> aPropertySequence; |
3345 | 0 | rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence; |
3346 | |
|
3347 | 0 | if (!aPropertySequence.hasElements()) |
3348 | 0 | return; |
3349 | | |
3350 | 0 | SvxNumType nNumberingType = SVX_NUM_NUMBER_NONE; |
3351 | 0 | bool bSDot = false; |
3352 | 0 | bool bPBehind = false; |
3353 | 0 | bool bPBoth = false; |
3354 | 0 | sal_Unicode aBulletChar = 0x2022; // a bullet |
3355 | 0 | awt::FontDescriptor aFontDesc; |
3356 | 0 | bool bHasFontDesc = false; |
3357 | 0 | uno::Reference<graphic::XGraphic> xGraphic; |
3358 | 0 | sal_Int16 nBulletRelSize = 0; |
3359 | 0 | sal_Int16 nStartWith = 1; |
3360 | 0 | ::Color nBulletColor; |
3361 | 0 | bool bHasBulletColor = false; |
3362 | 0 | awt::Size aGraphicSize; |
3363 | |
|
3364 | 0 | for (const PropertyValue& rPropValue : aPropertySequence) |
3365 | 0 | { |
3366 | 0 | OUString aPropName( rPropValue.Name ); |
3367 | 0 | SAL_INFO("oox.shape", "pro name: " << aPropName); |
3368 | 0 | if ( aPropName == "NumberingType" ) |
3369 | 0 | { |
3370 | 0 | nNumberingType = static_cast<SvxNumType>(*o3tl::doAccess<sal_Int16>(rPropValue.Value)); |
3371 | 0 | } |
3372 | 0 | else if ( aPropName == "Prefix" ) |
3373 | 0 | { |
3374 | 0 | if( *o3tl::doAccess<OUString>(rPropValue.Value) == ")") |
3375 | 0 | bPBoth = true; |
3376 | 0 | } |
3377 | 0 | else if ( aPropName == "Suffix" ) |
3378 | 0 | { |
3379 | 0 | auto s = o3tl::doAccess<OUString>(rPropValue.Value); |
3380 | 0 | if( *s == ".") |
3381 | 0 | bSDot = true; |
3382 | 0 | else if( *s == ")") |
3383 | 0 | bPBehind = true; |
3384 | 0 | } |
3385 | 0 | else if(aPropName == "BulletColor") |
3386 | 0 | { |
3387 | 0 | nBulletColor = ::Color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(rPropValue.Value)); |
3388 | 0 | bHasBulletColor = true; |
3389 | 0 | } |
3390 | 0 | else if ( aPropName == "BulletChar" ) |
3391 | 0 | { |
3392 | 0 | aBulletChar = (*o3tl::doAccess<OUString>(rPropValue.Value))[ 0 ]; |
3393 | 0 | } |
3394 | 0 | else if ( aPropName == "BulletFont" ) |
3395 | 0 | { |
3396 | 0 | aFontDesc = *o3tl::doAccess<awt::FontDescriptor>(rPropValue.Value); |
3397 | 0 | bHasFontDesc = true; |
3398 | | |
3399 | | // Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font, |
3400 | | // instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used. |
3401 | | // Because there might exist a lot of damaged documents I added this two lines |
3402 | | // which fixes the bullet problem for the export. |
3403 | 0 | if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") ) |
3404 | 0 | aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252; |
3405 | |
|
3406 | 0 | } |
3407 | 0 | else if ( aPropName == "BulletRelSize" ) |
3408 | 0 | { |
3409 | 0 | nBulletRelSize = *o3tl::doAccess<sal_Int16>(rPropValue.Value); |
3410 | 0 | } |
3411 | 0 | else if ( aPropName == "StartWith" ) |
3412 | 0 | { |
3413 | 0 | nStartWith = *o3tl::doAccess<sal_Int16>(rPropValue.Value); |
3414 | 0 | } |
3415 | 0 | else if (aPropName == "GraphicBitmap") |
3416 | 0 | { |
3417 | 0 | auto xBitmap = rPropValue.Value.get<uno::Reference<awt::XBitmap>>(); |
3418 | 0 | xGraphic.set(xBitmap, uno::UNO_QUERY); |
3419 | 0 | } |
3420 | 0 | else if ( aPropName == "GraphicSize" ) |
3421 | 0 | { |
3422 | 0 | aGraphicSize = *o3tl::doAccess<awt::Size>(rPropValue.Value); |
3423 | 0 | SAL_INFO("oox.shape", "graphic size: " << aGraphicSize.Width << "x" << aGraphicSize.Height); |
3424 | 0 | } |
3425 | 0 | } |
3426 | | |
3427 | 0 | if (nNumberingType == SVX_NUM_NUMBER_NONE) |
3428 | 0 | return; |
3429 | | |
3430 | 0 | Graphic aGraphic(xGraphic); |
3431 | 0 | if (xGraphic.is() && aGraphic.GetType() != GraphicType::NONE) |
3432 | 0 | { |
3433 | 0 | tools::Long nFirstCharHeightMm = TransformMetric(fFirstCharHeight * 100.f, FieldUnit::POINT, FieldUnit::MM); |
3434 | 0 | float fBulletSizeRel = aGraphicSize.Height / static_cast<float>(nFirstCharHeightMm) / OOX_BULLET_LIST_SCALE_FACTOR; |
3435 | |
|
3436 | 0 | OUString sRelationId; |
3437 | |
|
3438 | 0 | if (fBulletSizeRel < 1.0f) |
3439 | 0 | { |
3440 | | // Add padding to get the bullet point centered in PPT |
3441 | 0 | Size aDestSize(64, 64); |
3442 | 0 | float fBulletSizeRelX = fBulletSizeRel / aGraphicSize.Height * aGraphicSize.Width; |
3443 | 0 | tools::Long nPaddingX = std::max<tools::Long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f)); |
3444 | 0 | tools::Long nPaddingY = std::lround((aDestSize.Height() - fBulletSizeRel * aDestSize.Height()) / 2.f); |
3445 | 0 | tools::Rectangle aDestRect(nPaddingX, nPaddingY, aDestSize.Width() - nPaddingX, aDestSize.Height() - nPaddingY); |
3446 | |
|
3447 | 0 | Bitmap aSourceBitmap(aGraphic.GetBitmap()); |
3448 | 0 | aSourceBitmap.Scale(aDestRect.GetSize()); |
3449 | 0 | tools::Rectangle aSourceRect(Point(0, 0), aDestRect.GetSize()); |
3450 | 0 | Bitmap aDestBitmap(aDestSize, vcl::PixelFormat::N32_BPP); |
3451 | 0 | aDestBitmap.CopyPixel(aDestRect, aSourceRect, aSourceBitmap); |
3452 | 0 | Graphic aDestGraphic(aDestBitmap); |
3453 | 0 | sRelationId = writeGraphicToStorage(aDestGraphic); |
3454 | 0 | fBulletSizeRel = 1.0f; |
3455 | 0 | } |
3456 | 0 | else |
3457 | 0 | { |
3458 | 0 | sRelationId = writeGraphicToStorage(aGraphic); |
3459 | 0 | } |
3460 | |
|
3461 | 0 | mpFS->singleElementNS( XML_a, XML_buSzPct, |
3462 | 0 | XML_val, OString::number(std::min<sal_Int32>(std::lround(100000.f * fBulletSizeRel), 400000))); |
3463 | 0 | mpFS->startElementNS(XML_a, XML_buBlip); |
3464 | 0 | mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelationId); |
3465 | 0 | mpFS->endElementNS( XML_a, XML_buBlip ); |
3466 | 0 | } |
3467 | 0 | else |
3468 | 0 | { |
3469 | 0 | if(bHasBulletColor) |
3470 | 0 | { |
3471 | 0 | if (nBulletColor == COL_AUTO ) |
3472 | 0 | { |
3473 | 0 | nBulletColor = ::Color(ColorTransparency, mbIsBackgroundDark ? 0xffffff : 0x000000); |
3474 | 0 | } |
3475 | 0 | mpFS->startElementNS(XML_a, XML_buClr); |
3476 | 0 | WriteColor( nBulletColor ); |
3477 | 0 | mpFS->endElementNS( XML_a, XML_buClr ); |
3478 | 0 | } |
3479 | |
|
3480 | 0 | if( nBulletRelSize && nBulletRelSize != 100 ) |
3481 | 0 | mpFS->singleElementNS( XML_a, XML_buSzPct, |
3482 | 0 | XML_val, OString::number(std::clamp<sal_Int32>(1000*nBulletRelSize, 25000, 400000))); |
3483 | 0 | if( bHasFontDesc ) |
3484 | 0 | { |
3485 | 0 | if ( SVX_NUM_CHAR_SPECIAL == nNumberingType ) |
3486 | 0 | aBulletChar = SubstituteBullet( aBulletChar, aFontDesc ); |
3487 | 0 | mpFS->singleElementNS( XML_a, XML_buFont, |
3488 | 0 | XML_typeface, aFontDesc.Name, |
3489 | 0 | XML_charset, sax_fastparser::UseIf("2", aFontDesc.CharSet == awt::CharSet::SYMBOL)); |
3490 | 0 | } |
3491 | |
|
3492 | 0 | OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth ); |
3493 | |
|
3494 | 0 | if (!aAutoNumType.isEmpty()) |
3495 | 0 | { |
3496 | 0 | mpFS->singleElementNS(XML_a, XML_buAutoNum, |
3497 | 0 | XML_type, aAutoNumType, |
3498 | 0 | XML_startAt, sax_fastparser::UseIf(OString::number(nStartWith), nStartWith > 1)); |
3499 | 0 | } |
3500 | 0 | else |
3501 | 0 | { |
3502 | 0 | mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar)); |
3503 | 0 | } |
3504 | 0 | } |
3505 | 0 | } |
3506 | | |
3507 | | void DrawingML::WriteParagraphTabStops(const Reference<XPropertySet>& rXPropSet) |
3508 | 0 | { |
3509 | 0 | css::uno::Sequence<css::style::TabStop> aTabStops; |
3510 | 0 | if (GetProperty(rXPropSet, u"ParaTabStops"_ustr)) |
3511 | 0 | aTabStops = *o3tl::doAccess<css::uno::Sequence<css::style::TabStop>>(mAny); |
3512 | |
|
3513 | 0 | if (aTabStops.getLength() > 0) |
3514 | 0 | mpFS->startElementNS(XML_a, XML_tabLst); |
3515 | |
|
3516 | 0 | const sal_uInt8 MAX_TAB_STOPS = 32; |
3517 | 0 | for (sal_uInt8 i = 0; i < aTabStops.getLength() && i < MAX_TAB_STOPS; i++) |
3518 | 0 | { |
3519 | 0 | const css::style::TabStop& rTabStop = aTabStops[i]; |
3520 | 0 | OString sPosition = OString::number(GetPointFromCoordinate(rTabStop.Position)); |
3521 | 0 | OString sAlignment; |
3522 | 0 | switch (rTabStop.Alignment) |
3523 | 0 | { |
3524 | 0 | case css::style::TabAlign_DECIMAL: |
3525 | 0 | sAlignment = "dec"_ostr; |
3526 | 0 | break; |
3527 | 0 | case css::style::TabAlign_RIGHT: |
3528 | 0 | sAlignment = "r"_ostr; |
3529 | 0 | break; |
3530 | 0 | case css::style::TabAlign_CENTER: |
3531 | 0 | sAlignment = "ctr"_ostr; |
3532 | 0 | break; |
3533 | 0 | case css::style::TabAlign_LEFT: |
3534 | 0 | default: |
3535 | 0 | sAlignment = "l"_ostr; |
3536 | 0 | } |
3537 | 0 | mpFS->singleElementNS(XML_a, XML_tab, XML_pos, sPosition, XML_algn, sAlignment); |
3538 | 0 | } |
3539 | 0 | if (aTabStops.getLength() > 0) |
3540 | 0 | mpFS->endElementNS(XML_a, XML_tabLst); |
3541 | 0 | } |
3542 | | |
3543 | | bool DrawingML::IsGroupShape( const Reference< XShape >& rXShape ) |
3544 | 0 | { |
3545 | 0 | bool bRet = false; |
3546 | 0 | if ( rXShape.is() ) |
3547 | 0 | { |
3548 | 0 | uno::Reference<lang::XServiceInfo> xServiceInfo(rXShape, uno::UNO_QUERY_THROW); |
3549 | 0 | bRet = xServiceInfo->supportsService(u"com.sun.star.drawing.GroupShape"_ustr); |
3550 | 0 | } |
3551 | 0 | return bRet; |
3552 | 0 | } |
3553 | | |
3554 | | sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, std::u16string_view propName) |
3555 | 0 | { |
3556 | 0 | if (nLevel < 0 || !GetProperty(rXPropSet, u"NumberingRules"_ustr)) |
3557 | 0 | return 0; |
3558 | | |
3559 | 0 | Reference< XIndexAccess > rXIndexAccess; |
3560 | |
|
3561 | 0 | if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount()) |
3562 | 0 | return 0; |
3563 | | |
3564 | 0 | SAL_INFO("oox.shape", "numbering rules"); |
3565 | | |
3566 | 0 | Sequence<PropertyValue> aPropertySequence; |
3567 | 0 | rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence; |
3568 | |
|
3569 | 0 | if (!aPropertySequence.hasElements()) |
3570 | 0 | return 0; |
3571 | | |
3572 | 0 | for (const PropertyValue& rPropValue : aPropertySequence) |
3573 | 0 | { |
3574 | 0 | OUString aPropName( rPropValue.Name ); |
3575 | 0 | SAL_INFO("oox.shape", "pro name: " << aPropName); |
3576 | 0 | if ( aPropName == propName ) |
3577 | 0 | return *o3tl::doAccess<sal_Int32>(rPropValue.Value); |
3578 | 0 | } |
3579 | | |
3580 | 0 | return 0; |
3581 | 0 | } |
3582 | | |
3583 | | const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment, bool bRTL, bool bPlaceHolder ) |
3584 | 0 | { |
3585 | 0 | const char* sAlignment = nullptr; |
3586 | |
|
3587 | 0 | switch( nAlignment ) |
3588 | 0 | { |
3589 | 0 | case style::ParagraphAdjust_START: |
3590 | 0 | if (bRTL) |
3591 | 0 | sAlignment = "r"; |
3592 | 0 | else if (bPlaceHolder) |
3593 | 0 | sAlignment = "l"; |
3594 | 0 | break; |
3595 | 0 | case style::ParagraphAdjust_END: |
3596 | 0 | if (bRTL && bPlaceHolder) |
3597 | 0 | sAlignment = "l"; |
3598 | 0 | else |
3599 | 0 | sAlignment = "r"; |
3600 | 0 | break; |
3601 | 0 | case style::ParagraphAdjust_CENTER: |
3602 | 0 | sAlignment = "ctr"; |
3603 | 0 | break; |
3604 | 0 | case style::ParagraphAdjust_RIGHT: |
3605 | 0 | sAlignment = "r"; |
3606 | 0 | break; |
3607 | 0 | case style::ParagraphAdjust_BLOCK: |
3608 | 0 | sAlignment = "just"; |
3609 | 0 | break; |
3610 | 0 | case style::ParagraphAdjust_LEFT: |
3611 | 0 | if (bPlaceHolder) // in case of PPTX placeholder objects, "l" is necessary for MSO |
3612 | 0 | sAlignment = "l"; |
3613 | 0 | break; |
3614 | 0 | default: |
3615 | 0 | ; |
3616 | 0 | } |
3617 | | |
3618 | 0 | return sAlignment; |
3619 | 0 | } |
3620 | | |
3621 | | void DrawingML::WriteLinespacing(const LineSpacing& rSpacing, float fFirstCharHeight) |
3622 | 0 | { |
3623 | 0 | if( rSpacing.Mode == LineSpacingMode::PROP ) |
3624 | 0 | { |
3625 | 0 | mpFS->singleElementNS( XML_a, XML_spcPct, |
3626 | 0 | XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000)); |
3627 | 0 | } |
3628 | 0 | else if (rSpacing.Mode == LineSpacingMode::MINIMUM |
3629 | 0 | && fFirstCharHeight > o3tl::convert(rSpacing.Height, o3tl::Length::mm100, o3tl::Length::pt)) |
3630 | 0 | { |
3631 | | // 100% proportional line spacing = single line spacing |
3632 | 0 | mpFS->singleElementNS(XML_a, XML_spcPct, XML_val, |
3633 | 0 | OString::number(static_cast<sal_Int32>(100000))); |
3634 | 0 | } |
3635 | 0 | else |
3636 | 0 | { |
3637 | 0 | mpFS->singleElementNS( XML_a, XML_spcPts, |
3638 | 0 | XML_val, OString::number(toTextSpacingPoint(rSpacing.Height))); |
3639 | 0 | } |
3640 | 0 | } |
3641 | | |
3642 | | bool DrawingML::WriteParagraphProperties(const Reference<XTextContent>& rParagraph, float fFirstCharHeight, sal_Int32 nElement) |
3643 | 0 | { |
3644 | | // The data model of a Diagram holds the text only. Attributes come from the layout, |
3645 | | // the style and the colours that the Diagram, so do not write here |
3646 | 0 | if (isDiagramModelTextExport()) |
3647 | 0 | return false; |
3648 | | |
3649 | 0 | Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); |
3650 | 0 | Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY ); |
3651 | 0 | PropertyState eState; |
3652 | |
|
3653 | 0 | if( !rXPropSet.is() || !rXPropState.is() ) |
3654 | 0 | return false; |
3655 | | |
3656 | 0 | sal_Int16 nLevel = -1; |
3657 | 0 | if (GetProperty(rXPropSet, u"NumberingLevel"_ustr)) |
3658 | 0 | mAny >>= nLevel; |
3659 | |
|
3660 | 0 | bool bWriteNumbering = true; |
3661 | 0 | bool bForceZeroIndent = false; |
3662 | 0 | if (mbPlaceholder) |
3663 | 0 | { |
3664 | 0 | Reference< text::XTextRange > xParaText(rParagraph, UNO_QUERY); |
3665 | 0 | if (xParaText) |
3666 | 0 | { |
3667 | 0 | bool bNumberingOnThisLevel = false; |
3668 | 0 | if (nLevel > -1) |
3669 | 0 | { |
3670 | 0 | Reference< XIndexAccess > xNumberingRules(rXPropSet->getPropertyValue(u"NumberingRules"_ustr), UNO_QUERY); |
3671 | 0 | const PropertyValues aNumRuleOfLevel = xNumberingRules->getByIndex(nLevel).get<PropertyValues>(); |
3672 | 0 | for (const PropertyValue& rRule : aNumRuleOfLevel) |
3673 | 0 | if (rRule.Name == "NumberingType" && rRule.Value.hasValue()) |
3674 | 0 | bNumberingOnThisLevel = rRule.Value.get<sal_uInt16>() != style::NumberingType::NUMBER_NONE; |
3675 | 0 | } |
3676 | |
|
3677 | 0 | const bool bIsNumberingVisible = rXPropSet->getPropertyValue(u"NumberingIsNumber"_ustr).get<bool>(); |
3678 | 0 | const bool bIsLineEmpty = !xParaText->getString().getLength(); |
3679 | |
|
3680 | 0 | bWriteNumbering = !bIsLineEmpty && bIsNumberingVisible && (nLevel != -1); |
3681 | 0 | bForceZeroIndent = (!bIsNumberingVisible || bIsLineEmpty || !bNumberingOnThisLevel); |
3682 | 0 | } |
3683 | |
|
3684 | 0 | } |
3685 | |
|
3686 | 0 | sal_Int16 nTmp = sal_Int16(style::ParagraphAdjust_LEFT); |
3687 | 0 | if (GetProperty(rXPropSet, u"ParaAdjust"_ustr)) |
3688 | 0 | mAny >>= nTmp; |
3689 | 0 | style::ParagraphAdjust nAlignment = static_cast<style::ParagraphAdjust>(nTmp); |
3690 | |
|
3691 | 0 | bool bHasLinespacing = false; |
3692 | 0 | LineSpacing aLineSpacing; |
3693 | 0 | if (GetPropertyAndState(rXPropSet, rXPropState, u"ParaLineSpacing"_ustr, eState) |
3694 | 0 | && (mAny >>= aLineSpacing) |
3695 | 0 | && (eState == beans::PropertyState_DIRECT_VALUE || |
3696 | | // only export if it differs from the default 100% line spacing |
3697 | 0 | aLineSpacing.Mode != LineSpacingMode::PROP || aLineSpacing.Height != 100)) |
3698 | 0 | bHasLinespacing = true; |
3699 | |
|
3700 | 0 | bool bRtl = false; |
3701 | 0 | if (GetProperty(rXPropSet, u"WritingMode"_ustr)) |
3702 | 0 | { |
3703 | 0 | sal_Int16 nWritingMode; |
3704 | 0 | if( ( mAny >>= nWritingMode ) && nWritingMode == text::WritingMode2::RL_TB ) |
3705 | 0 | { |
3706 | 0 | bRtl = true; |
3707 | 0 | } |
3708 | 0 | } |
3709 | |
|
3710 | 0 | sal_Int32 nParaLeftMargin = 0; |
3711 | 0 | sal_Int32 nParaFirstLineIndent = 0; |
3712 | |
|
3713 | 0 | if (GetProperty(rXPropSet, u"ParaLeftMargin"_ustr)) |
3714 | 0 | mAny >>= nParaLeftMargin; |
3715 | 0 | if (GetProperty(rXPropSet, u"ParaFirstLineIndent"_ustr)) |
3716 | 0 | mAny >>= nParaFirstLineIndent; |
3717 | |
|
3718 | 0 | sal_Int32 nParaTopMargin = 0; |
3719 | 0 | sal_Int32 nParaBottomMargin = 0; |
3720 | |
|
3721 | 0 | if (GetProperty(rXPropSet, u"ParaTopMargin"_ustr)) |
3722 | 0 | mAny >>= nParaTopMargin; |
3723 | 0 | if (GetProperty(rXPropSet, u"ParaBottomMargin"_ustr)) |
3724 | 0 | mAny >>= nParaBottomMargin; |
3725 | |
|
3726 | 0 | sal_Int32 nLeftMargin = getBulletMarginIndentation ( rXPropSet, nLevel,u"LeftMargin"); |
3727 | 0 | sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,u"FirstLineOffset"); |
3728 | |
|
3729 | 0 | if (bWriteNumbering && !bForceZeroIndent) |
3730 | 0 | { |
3731 | 0 | if (!(nLevel != -1 |
3732 | 0 | || nAlignment != style::ParagraphAdjust_LEFT |
3733 | 0 | || bHasLinespacing)) |
3734 | 0 | return false; |
3735 | 0 | } |
3736 | | |
3737 | 0 | sal_Int32 nParaDefaultTabSize = 0; |
3738 | 0 | if (GetProperty(rXPropSet, u"ParaTabStopDefaultDistance"_ustr)) |
3739 | 0 | mAny >>= nParaDefaultTabSize; |
3740 | | |
3741 | | // ST_TextIndentLevelType |
3742 | 0 | const sal_Int16 nOutLevel = std::min(nLevel, sal_Int16(8)); |
3743 | | |
3744 | | // ST_TextMargin: The margin value can't be greater than 51206400 Emu, which is 142240 MM100 ([ISO/IEC 29500-1] 20.1.10.72) |
3745 | 0 | sal_Int32 nMaxMarginVal = 142240; |
3746 | 0 | if (nParaLeftMargin > nMaxMarginVal) |
3747 | 0 | nParaLeftMargin = 0; |
3748 | 0 | if (nLeftMargin > nMaxMarginVal) |
3749 | 0 | nLeftMargin = 0; |
3750 | |
|
3751 | 0 | bool bIsListStyle = (nElement >= XML_lvl1pPr && nElement <= XML_lvl9pPr); |
3752 | |
|
3753 | 0 | if (nParaLeftMargin || bIsListStyle) // For Paragraph or List-Style elements |
3754 | 0 | mpFS->startElementNS( XML_a, nElement, |
3755 | 0 | XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)), nParaLeftMargin > 0), |
3756 | 0 | XML_lvl, sax_fastparser::UseIf(OString::number(nOutLevel), nOutLevel > 0), |
3757 | 0 | XML_indent, sax_fastparser::UseIf(OString::number((bForceZeroIndent && nParaFirstLineIndent == 0) ? 0 : oox::drawingml::convertHmmToEmu(nParaFirstLineIndent)), (bForceZeroIndent || nParaFirstLineIndent != 0)), |
3758 | 0 | XML_algn, GetAlignment( nAlignment, bRtl, mbPlaceholder ), |
3759 | 0 | XML_defTabSz, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaDefaultTabSize)), nParaDefaultTabSize > 0), |
3760 | 0 | XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl)); |
3761 | 0 | else |
3762 | 0 | mpFS->startElementNS( XML_a, nElement, |
3763 | 0 | XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0), |
3764 | 0 | XML_lvl, sax_fastparser::UseIf(OString::number(nOutLevel), nOutLevel > 0), |
3765 | 0 | XML_indent, sax_fastparser::UseIf(OString::number(!bForceZeroIndent ? oox::drawingml::convertHmmToEmu(nLineIndentation) : 0), (bForceZeroIndent || ( nLineIndentation != 0))), |
3766 | 0 | XML_algn, GetAlignment( nAlignment, bRtl, mbPlaceholder ), |
3767 | 0 | XML_defTabSz, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaDefaultTabSize)), nParaDefaultTabSize > 0), |
3768 | 0 | XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl)); |
3769 | | |
3770 | |
|
3771 | 0 | if( bHasLinespacing ) |
3772 | 0 | { |
3773 | 0 | mpFS->startElementNS(XML_a, XML_lnSpc); |
3774 | 0 | WriteLinespacing(aLineSpacing, fFirstCharHeight); |
3775 | 0 | mpFS->endElementNS( XML_a, XML_lnSpc ); |
3776 | 0 | } |
3777 | |
|
3778 | 0 | if( nParaTopMargin != 0 ) |
3779 | 0 | { |
3780 | 0 | mpFS->startElementNS(XML_a, XML_spcBef); |
3781 | 0 | { |
3782 | 0 | mpFS->singleElementNS( XML_a, XML_spcPts, |
3783 | 0 | XML_val, OString::number(toTextSpacingPoint(nParaTopMargin))); |
3784 | 0 | } |
3785 | 0 | mpFS->endElementNS( XML_a, XML_spcBef ); |
3786 | 0 | } |
3787 | |
|
3788 | 0 | if( nParaBottomMargin != 0 ) |
3789 | 0 | { |
3790 | 0 | mpFS->startElementNS(XML_a, XML_spcAft); |
3791 | 0 | { |
3792 | 0 | mpFS->singleElementNS( XML_a, XML_spcPts, |
3793 | 0 | XML_val, OString::number(toTextSpacingPoint(nParaBottomMargin))); |
3794 | 0 | } |
3795 | 0 | mpFS->endElementNS( XML_a, XML_spcAft ); |
3796 | 0 | } |
3797 | |
|
3798 | 0 | if (!bWriteNumbering) |
3799 | 0 | mpFS->singleElementNS(XML_a, XML_buNone); |
3800 | 0 | else |
3801 | 0 | WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel ); |
3802 | |
|
3803 | 0 | WriteParagraphTabStops( rXPropSet ); |
3804 | | |
3805 | | // do not end element for lstStyles since, defRPr should be stacked inside it |
3806 | 0 | bool bLstStyle = false; |
3807 | 0 | switch (nElement) |
3808 | 0 | { |
3809 | 0 | case XML_lvl1pPr: |
3810 | 0 | case XML_lvl2pPr: |
3811 | 0 | case XML_lvl3pPr: |
3812 | 0 | case XML_lvl4pPr: |
3813 | 0 | case XML_lvl5pPr: |
3814 | 0 | case XML_lvl6pPr: |
3815 | 0 | case XML_lvl7pPr: |
3816 | 0 | case XML_lvl8pPr: |
3817 | 0 | case XML_lvl9pPr: |
3818 | 0 | bLstStyle = true; |
3819 | 0 | break; |
3820 | 0 | } |
3821 | 0 | if( !bLstStyle ) |
3822 | 0 | mpFS->endElementNS( XML_a, nElement ); |
3823 | |
|
3824 | 0 | return true; |
3825 | 0 | } |
3826 | | |
3827 | | void DrawingML::WriteLstStyle(const css::uno::Reference<css::text::XTextContent>& rParagraph, |
3828 | | bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, |
3829 | | const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet, |
3830 | | sal_Int32 nElement) |
3831 | 0 | { |
3832 | 0 | Reference<XEnumerationAccess> xAccess(rParagraph, UNO_QUERY); |
3833 | 0 | if (!xAccess.is()) |
3834 | 0 | return; |
3835 | | |
3836 | 0 | Reference<XEnumeration> xEnumeration(xAccess->createEnumeration()); |
3837 | 0 | if (!xEnumeration.is()) |
3838 | 0 | return; |
3839 | | |
3840 | | |
3841 | 0 | Reference<XTextRange> rRun; |
3842 | |
|
3843 | 0 | if (!xEnumeration->hasMoreElements()) |
3844 | 0 | return; |
3845 | | |
3846 | 0 | Any aAny(xEnumeration->nextElement()); |
3847 | 0 | if (aAny >>= rRun) |
3848 | 0 | { |
3849 | 0 | float fFirstCharHeight = rnCharHeight / 1000.; |
3850 | 0 | Reference<XPropertySet> xFirstRunPropSet(rRun, UNO_QUERY); |
3851 | 0 | Reference<XPropertySetInfo> xFirstRunPropSetInfo |
3852 | 0 | = xFirstRunPropSet->getPropertySetInfo(); |
3853 | |
|
3854 | 0 | if (xFirstRunPropSetInfo->hasPropertyByName(u"CharHeight"_ustr)) |
3855 | 0 | fFirstCharHeight = xFirstRunPropSet->getPropertyValue(u"CharHeight"_ustr).get<float>(); |
3856 | |
|
3857 | 0 | if( !WriteParagraphProperties(rParagraph, fFirstCharHeight, nElement) ) |
3858 | 0 | mpFS->startElementNS(XML_a, nElement); |
3859 | |
|
3860 | 0 | WriteRunInput aInput; |
3861 | 0 | aInput.bCheckDirect = true; |
3862 | 0 | aInput.nScriptType = GetScriptType(rRun->getString()); |
3863 | 0 | aInput.xShapePropSet = rXShapePropSet; |
3864 | 0 | aInput.bOverridingCharHeight = rbOverridingCharHeight; |
3865 | 0 | aInput.nCharHeight = rnCharHeight; |
3866 | 0 | WriteRunProperties(xFirstRunPropSet, XML_defRPr, aInput); |
3867 | 0 | rbOverridingCharHeight = aInput.bOverridingCharHeight; |
3868 | 0 | rnCharHeight = aInput.nCharHeight; |
3869 | |
|
3870 | 0 | mpFS->endElementNS(XML_a, nElement); |
3871 | 0 | } |
3872 | 0 | } |
3873 | | |
3874 | | namespace |
3875 | | { |
3876 | | /// In case xShapeProps is an outliner shape, return a paragraph enumeration describing the outline |
3877 | | /// text format. |
3878 | | uno::Reference<container::XEnumeration> GetOutlinerTextFormatParaEnum(const uno::Reference<beans::XPropertySet>& xShapeProps) |
3879 | 0 | { |
3880 | 0 | SdrObject* pShape = SdrObject::getSdrObjectFromXShape(xShapeProps); |
3881 | 0 | if (pShape->GetObjIdentifier() != SdrObjKind::OutlineText) |
3882 | 0 | { |
3883 | | // Not an outliner shape. |
3884 | 0 | return {}; |
3885 | 0 | } |
3886 | | |
3887 | 0 | SdrPage* pPage = pShape->getSdrPageFromSdrObject(); |
3888 | 0 | if (pPage->TRG_HasMasterPage()) |
3889 | 0 | { |
3890 | | // Not a master page, the matching master page contains the shape that provides the outline |
3891 | | // text format. |
3892 | 0 | SdrPage& rMasterPage = pPage->TRG_GetMasterPage(); |
3893 | 0 | for (const rtl::Reference<SdrObject>& pObject : rMasterPage) |
3894 | 0 | { |
3895 | 0 | if (pObject->GetObjIdentifier() == SdrObjKind::OutlineText) |
3896 | 0 | { |
3897 | 0 | pShape = pObject.get(); |
3898 | 0 | break; |
3899 | 0 | } |
3900 | 0 | } |
3901 | 0 | } |
3902 | |
|
3903 | 0 | uno::Reference<text::XTextRange> xOutliner(pShape->getUnoShape(), uno::UNO_QUERY); |
3904 | 0 | uno::Reference<container::XEnumerationAccess> xText(xOutliner->getText(), uno::UNO_QUERY); |
3905 | 0 | return xText->createEnumeration(); |
3906 | 0 | } |
3907 | | } |
3908 | | |
3909 | | void DrawingML::WriteLstStyles(const css::uno::Reference<css::text::XTextContent>& rParagraph, |
3910 | | bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, |
3911 | | const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet) |
3912 | 0 | { |
3913 | 0 | mpFS->startElementNS(XML_a, XML_lstStyle); |
3914 | |
|
3915 | 0 | uno::Reference<container::XEnumeration> xMasterParagraphEnum = GetOutlinerTextFormatParaEnum(rXShapePropSet); |
3916 | 0 | if (xMasterParagraphEnum.is()) |
3917 | 0 | { |
3918 | | // Outliner shape, write the outline text format as multiple list levels, each with its |
3919 | | // paragraph properties. |
3920 | 0 | sal_Int32 nLevel = 0; |
3921 | 0 | while (xMasterParagraphEnum->hasMoreElements()) |
3922 | 0 | { |
3923 | 0 | uno::Reference<css::text::XTextContent> xParagraph(xMasterParagraphEnum->nextElement(), uno::UNO_QUERY); |
3924 | 0 | sal_Int32 nElement = 0; |
3925 | 0 | switch (nLevel) |
3926 | 0 | { |
3927 | 0 | case 0: |
3928 | 0 | nElement = XML_lvl1pPr; |
3929 | 0 | break; |
3930 | 0 | case 1: |
3931 | 0 | nElement = XML_lvl2pPr; |
3932 | 0 | break; |
3933 | 0 | case 2: |
3934 | 0 | nElement = XML_lvl3pPr; |
3935 | 0 | break; |
3936 | 0 | case 3: |
3937 | 0 | nElement = XML_lvl4pPr; |
3938 | 0 | break; |
3939 | 0 | case 4: |
3940 | 0 | nElement = XML_lvl5pPr; |
3941 | 0 | break; |
3942 | 0 | case 5: |
3943 | 0 | nElement = XML_lvl6pPr; |
3944 | 0 | break; |
3945 | 0 | case 6: |
3946 | 0 | nElement = XML_lvl7pPr; |
3947 | 0 | break; |
3948 | 0 | case 7: |
3949 | 0 | nElement = XML_lvl8pPr; |
3950 | 0 | break; |
3951 | 0 | case 8: |
3952 | 0 | nElement = XML_lvl9pPr; |
3953 | 0 | break; |
3954 | 0 | } |
3955 | 0 | if (!nElement) |
3956 | 0 | { |
3957 | 0 | break; |
3958 | 0 | } |
3959 | | |
3960 | 0 | WriteLstStyle(xParagraph, rbOverridingCharHeight, rnCharHeight, rXShapePropSet, nElement); |
3961 | 0 | ++nLevel; |
3962 | 0 | } |
3963 | 0 | } |
3964 | 0 | else |
3965 | 0 | { |
3966 | 0 | WriteLstStyle(rParagraph, rbOverridingCharHeight, rnCharHeight, rXShapePropSet, XML_lvl1pPr); |
3967 | 0 | } |
3968 | | |
3969 | 0 | mpFS->endElementNS(XML_a, XML_lstStyle); |
3970 | 0 | } |
3971 | | |
3972 | | void DrawingML::WriteParagraph( const Reference< XTextContent >& rParagraph, |
3973 | | bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, |
3974 | | const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet) |
3975 | 0 | { |
3976 | 0 | Reference< XEnumerationAccess > access( rParagraph, UNO_QUERY ); |
3977 | 0 | if( !access.is() ) |
3978 | 0 | return; |
3979 | | |
3980 | 0 | Reference< XEnumeration > enumeration( access->createEnumeration() ); |
3981 | 0 | if( !enumeration.is() ) |
3982 | 0 | return; |
3983 | | |
3984 | 0 | mpFS->startElementNS(XML_a, XML_p); |
3985 | |
|
3986 | 0 | bool bPropertiesWritten = false; |
3987 | 0 | while( enumeration->hasMoreElements() ) |
3988 | 0 | { |
3989 | 0 | Reference< XTextRange > run; |
3990 | 0 | Any any ( enumeration->nextElement() ); |
3991 | |
|
3992 | 0 | if (any >>= run) |
3993 | 0 | { |
3994 | 0 | if( !bPropertiesWritten ) |
3995 | 0 | { |
3996 | 0 | float fFirstCharHeight = rnCharHeight / 1000.; |
3997 | 0 | Reference< XPropertySet > xFirstRunPropSet (run, UNO_QUERY); |
3998 | 0 | Reference< XPropertySetInfo > xFirstRunPropSetInfo = xFirstRunPropSet->getPropertySetInfo(); |
3999 | 0 | if( xFirstRunPropSetInfo->hasPropertyByName(u"CharHeight"_ustr) ) |
4000 | 0 | { |
4001 | 0 | fFirstCharHeight = xFirstRunPropSet->getPropertyValue(u"CharHeight"_ustr).get<float>(); |
4002 | 0 | rnCharHeight = 100 * fFirstCharHeight; |
4003 | 0 | rbOverridingCharHeight = true; |
4004 | 0 | } |
4005 | 0 | WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_pPr); |
4006 | 0 | bPropertiesWritten = true; |
4007 | 0 | } |
4008 | 0 | WriteRun( run, rbOverridingCharHeight, rnCharHeight, rXShapePropSet); |
4009 | 0 | } |
4010 | 0 | } |
4011 | 0 | Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); |
4012 | |
|
4013 | 0 | WriteRunInput aInput; |
4014 | 0 | aInput.xShapePropSet = rXShapePropSet; |
4015 | 0 | aInput.bOverridingCharHeight = rbOverridingCharHeight; |
4016 | 0 | aInput.nCharHeight = rnCharHeight; |
4017 | 0 | WriteRunProperties(rXPropSet, XML_endParaRPr, aInput); |
4018 | 0 | rbOverridingCharHeight = aInput.bOverridingCharHeight; |
4019 | 0 | rnCharHeight = aInput.nCharHeight; |
4020 | |
|
4021 | 0 | mpFS->endElementNS( XML_a, XML_p ); |
4022 | 0 | } |
4023 | | |
4024 | | bool DrawingML::IsFontworkShape(const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet) |
4025 | 0 | { |
4026 | 0 | bool bResult(false); |
4027 | 0 | if (rXShapePropSet.is()) |
4028 | 0 | { |
4029 | 0 | Sequence<PropertyValue> aCustomShapeGeometryProps; |
4030 | 0 | if (GetProperty(rXShapePropSet, u"CustomShapeGeometry"_ustr)) |
4031 | 0 | { |
4032 | 0 | mAny >>= aCustomShapeGeometryProps; |
4033 | 0 | uno::Sequence<beans::PropertyValue> aTextPathSeq; |
4034 | 0 | for (const auto& rProp : aCustomShapeGeometryProps) |
4035 | 0 | { |
4036 | 0 | if (rProp.Name == "TextPath") |
4037 | 0 | { |
4038 | 0 | rProp.Value >>= aTextPathSeq; |
4039 | 0 | for (const auto& rTextPathItem : aTextPathSeq) |
4040 | 0 | { |
4041 | 0 | if (rTextPathItem.Name == "TextPath") |
4042 | 0 | { |
4043 | 0 | rTextPathItem.Value >>= bResult; |
4044 | 0 | break; |
4045 | 0 | } |
4046 | 0 | } |
4047 | 0 | break; |
4048 | 0 | } |
4049 | 0 | } |
4050 | 0 | } |
4051 | 0 | } |
4052 | 0 | return bResult; |
4053 | 0 | } |
4054 | | |
4055 | | // Output text body properties |
4056 | | void DrawingML::WriteBodyProps(const css::uno::Reference< css::uno::XInterface >& rXIface, |
4057 | | sal_Int32 nXmlNamespace, bool bIsFontworkShape, |
4058 | | sal_Int32 nTop, sal_Int32 nBottom, sal_Int32 nLeft, sal_Int32 nRight) |
4059 | 0 | { |
4060 | 0 | uno::Reference<drawing::XShape> xShape(rXIface, UNO_QUERY); |
4061 | 0 | uno::Reference<XPropertySet> rXPropSet(rXIface, UNO_QUERY); |
4062 | |
|
4063 | 0 | std::optional<OString> sWritingMode; |
4064 | 0 | if (GetProperty(rXPropSet, u"TextWritingMode"_ustr)) |
4065 | 0 | { |
4066 | 0 | WritingMode eMode; |
4067 | 0 | if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL ) |
4068 | 0 | sWritingMode = "eaVert"; |
4069 | 0 | } |
4070 | 0 | if (GetProperty(rXPropSet, u"WritingMode"_ustr)) |
4071 | 0 | { |
4072 | 0 | sal_Int16 nWritingMode; |
4073 | 0 | if (mAny >>= nWritingMode) |
4074 | 0 | { |
4075 | 0 | if (nWritingMode == text::WritingMode2::TB_RL) |
4076 | 0 | sWritingMode = "eaVert"; |
4077 | 0 | else if (nWritingMode == text::WritingMode2::BT_LR) |
4078 | 0 | sWritingMode = "vert270"; |
4079 | 0 | else if (nWritingMode == text::WritingMode2::TB_RL90) |
4080 | 0 | sWritingMode = "vert"; |
4081 | 0 | else if (nWritingMode == text::WritingMode2::TB_LR) |
4082 | 0 | sWritingMode = "mongolianVert"; |
4083 | 0 | else if (nWritingMode == text::WritingMode2::STACKED) |
4084 | 0 | sWritingMode = "wordArtVert"; |
4085 | 0 | } |
4086 | 0 | } |
4087 | | |
4088 | | // read values from CustomShapeGeometry |
4089 | 0 | Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq; |
4090 | 0 | uno::Sequence<beans::PropertyValue> aTextPathSeq; |
4091 | 0 | bool bScaleX(false); |
4092 | 0 | OUString sShapeType(u"non-primitive"_ustr); |
4093 | 0 | OUString sMSWordPresetTextWarp; |
4094 | 0 | sal_Int32 nTextPreRotateAngle = 0; // degree |
4095 | 0 | std::optional<Degree100> nTextRotateAngleDeg100; // text area rotation |
4096 | |
|
4097 | 0 | if (GetProperty(rXPropSet, u"CustomShapeGeometry"_ustr)) |
4098 | 0 | { |
4099 | 0 | Sequence< PropertyValue > aProps; |
4100 | 0 | if ( mAny >>= aProps ) |
4101 | 0 | { |
4102 | 0 | for (const auto& rProp : aProps) |
4103 | 0 | { |
4104 | 0 | if (rProp.Name == "TextPreRotateAngle") |
4105 | 0 | rProp.Value >>= nTextPreRotateAngle; |
4106 | 0 | else if (rProp.Name == "AdjustmentValues") |
4107 | 0 | rProp.Value >>= aAdjustmentSeq; |
4108 | 0 | else if (rProp.Name == "TextRotateAngle") |
4109 | 0 | { |
4110 | 0 | double fTextRotateAngle = 0; // degree |
4111 | 0 | rProp.Value >>= fTextRotateAngle; |
4112 | 0 | nTextRotateAngleDeg100 = Degree100(std::lround(fTextRotateAngle * 100.0)); |
4113 | 0 | } |
4114 | 0 | else if (rProp.Name == "Type") |
4115 | 0 | rProp.Value >>= sShapeType; |
4116 | 0 | else if (rProp.Name == "TextPath") |
4117 | 0 | { |
4118 | 0 | rProp.Value >>= aTextPathSeq; |
4119 | 0 | for (const auto& rTextPathItem : aTextPathSeq) |
4120 | 0 | { |
4121 | 0 | if (rTextPathItem.Name == "ScaleX") |
4122 | 0 | rTextPathItem.Value >>= bScaleX; |
4123 | 0 | } |
4124 | 0 | } |
4125 | 0 | else if (rProp.Name == "PresetTextWarp") |
4126 | 0 | rProp.Value >>= sMSWordPresetTextWarp; |
4127 | 0 | } |
4128 | 0 | } |
4129 | 0 | } |
4130 | 0 | else |
4131 | 0 | { |
4132 | 0 | if (mpTextExport) |
4133 | 0 | { |
4134 | 0 | if (xShape) |
4135 | 0 | { |
4136 | 0 | auto xTextFrame = mpTextExport->GetUnoTextFrame(xShape); |
4137 | 0 | if (xTextFrame) |
4138 | 0 | { |
4139 | 0 | uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY); |
4140 | 0 | auto aAny = xPropSet->getPropertyValue(u"WritingMode"_ustr); |
4141 | 0 | sal_Int16 nWritingMode; |
4142 | 0 | if (aAny >>= nWritingMode) |
4143 | 0 | { |
4144 | 0 | switch (nWritingMode) |
4145 | 0 | { |
4146 | 0 | case WritingMode2::TB_RL: |
4147 | 0 | sWritingMode = "eaVert"; |
4148 | 0 | break; |
4149 | 0 | case WritingMode2::BT_LR: |
4150 | 0 | sWritingMode = "vert270"; |
4151 | 0 | break; |
4152 | 0 | case WritingMode2::TB_RL90: |
4153 | 0 | sWritingMode = "vert"; |
4154 | 0 | break; |
4155 | 0 | case WritingMode2::TB_LR: |
4156 | 0 | sWritingMode = "mongolianVert"; |
4157 | 0 | break; |
4158 | 0 | default: |
4159 | 0 | break; |
4160 | 0 | } |
4161 | 0 | } |
4162 | 0 | } |
4163 | 0 | } |
4164 | 0 | } |
4165 | 0 | } |
4166 | | |
4167 | | // read InteropGrabBag if any |
4168 | 0 | std::optional<OUString> sHorzOverflow; |
4169 | 0 | std::optional<OUString> sVertOverflow; |
4170 | 0 | bool bUpright = false; |
4171 | 0 | std::optional<OString> isUpright; |
4172 | 0 | if (rXPropSet->getPropertySetInfo()->hasPropertyByName(u"InteropGrabBag"_ustr)) |
4173 | 0 | { |
4174 | 0 | uno::Sequence<beans::PropertyValue> aGrabBag; |
4175 | 0 | rXPropSet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag; |
4176 | 0 | for (const auto& aProp : aGrabBag) |
4177 | 0 | { |
4178 | 0 | if (aProp.Name == "Upright") |
4179 | 0 | { |
4180 | 0 | aProp.Value >>= bUpright; |
4181 | 0 | isUpright = OString(bUpright ? "1" : "0"); |
4182 | 0 | } |
4183 | 0 | else if (aProp.Name == "horzOverflow") |
4184 | 0 | { |
4185 | 0 | OUString sValue; |
4186 | 0 | aProp.Value >>= sValue; |
4187 | 0 | sHorzOverflow = sValue; |
4188 | 0 | } |
4189 | 0 | else if (aProp.Name == "vertOverflow") |
4190 | 0 | { |
4191 | 0 | OUString sValue; |
4192 | 0 | aProp.Value >>= sValue; |
4193 | 0 | sVertOverflow = sValue; |
4194 | 0 | } |
4195 | 0 | } |
4196 | 0 | } |
4197 | |
|
4198 | 0 | OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType)); |
4199 | | // ODF may have user defined TextPath, use "textPlain" as ersatz. |
4200 | 0 | if (sPresetWarp.isEmpty()) |
4201 | 0 | sPresetWarp = bIsFontworkShape ? std::u16string_view(u"textPlain") : std::u16string_view(u"textNoShape"); |
4202 | |
|
4203 | 0 | bool bFromWordArt = !bScaleX |
4204 | 0 | && ( sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp" |
4205 | 0 | || sPresetWarp == "textButton" || sPresetWarp == "textCircle"); |
4206 | |
|
4207 | 0 | if (bUpright) |
4208 | 0 | { |
4209 | 0 | Degree100 nShapeRotateAngleDeg100(0_deg100); |
4210 | 0 | if (GetProperty(rXPropSet, u"RotateAngle"_ustr)) |
4211 | 0 | nShapeRotateAngleDeg100 = Degree100(mAny.get<sal_Int32>()); |
4212 | | // Depending on shape rotation, the import has made 90deg changes to properties |
4213 | | // "TextPreRotateAngle" and "TextRotateAngle". Revert it. |
4214 | 0 | bool bWasAngleChanged |
4215 | 0 | = (nShapeRotateAngleDeg100 > 4500_deg100 && nShapeRotateAngleDeg100 <= 13500_deg100) |
4216 | 0 | || (nShapeRotateAngleDeg100 > 22500_deg100 |
4217 | 0 | && nShapeRotateAngleDeg100 <= 31500_deg100); |
4218 | 0 | if (bWasAngleChanged) |
4219 | 0 | { |
4220 | 0 | nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) + 9000_deg100; |
4221 | 0 | nTextPreRotateAngle -= 90; |
4222 | 0 | } |
4223 | | // If text is no longer upright, user has changed something. Do not write 'upright' then. |
4224 | | // This try to detect the case assumes, that the text area rotation was 0 in the original |
4225 | | // MS Office document. That is likely because MS Office has no UI to set it and the |
4226 | | // predefined SmartArt shapes, which use it, do not use 'upright'. |
4227 | 0 | Degree100 nAngleSum = nShapeRotateAngleDeg100 + nTextRotateAngleDeg100.value_or(0_deg100); |
4228 | 0 | if (abs(NormAngle18000(nAngleSum)) < 100_deg100) // consider inaccuracy from rounding |
4229 | 0 | { |
4230 | 0 | nTextRotateAngleDeg100.reset(); // 'upright' does not overrule text area rotation. |
4231 | 0 | } |
4232 | 0 | else |
4233 | 0 | { |
4234 | | // User changes. Keep current angles. |
4235 | 0 | isUpright.reset(); |
4236 | 0 | if (bWasAngleChanged) |
4237 | 0 | { |
4238 | 0 | nTextPreRotateAngle += 90; |
4239 | 0 | nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) - 9000_deg100; |
4240 | 0 | } |
4241 | 0 | } |
4242 | 0 | } |
4243 | | |
4244 | | // ToDo: Unsure about this. Need to investigate shapes from diagram import, especially diagrams |
4245 | | // with vertical text directions. |
4246 | 0 | if (nTextPreRotateAngle != 0 && !sWritingMode) |
4247 | 0 | { |
4248 | 0 | if (nTextPreRotateAngle == -90 || nTextPreRotateAngle == 270) |
4249 | 0 | sWritingMode = "vert"; |
4250 | 0 | else if (nTextPreRotateAngle == -270 || nTextPreRotateAngle == 90) |
4251 | 0 | sWritingMode = "vert270"; |
4252 | 0 | else if (nTextPreRotateAngle == -180 || nTextPreRotateAngle == 180) |
4253 | 0 | { |
4254 | 0 | nTextRotateAngleDeg100 |
4255 | 0 | = NormAngle18000(nTextRotateAngleDeg100.value_or(0_deg100) + 18000_deg100); |
4256 | | // ToDo: Examine insets. They might need rotation too. Check diagrams (SmartArt). |
4257 | 0 | } |
4258 | 0 | else |
4259 | 0 | SAL_WARN("oox", "unsuitable value for TextPreRotateAngle:" << nTextPreRotateAngle); |
4260 | 0 | } |
4261 | 0 | else if (nTextPreRotateAngle != 0 && sWritingMode && sWritingMode.value() == "eaVert") |
4262 | 0 | { |
4263 | | // ToDo: eaVert plus 270deg clockwise rotation has to be written with vert="horz" |
4264 | | // plus attribute 'normalEastAsianFlow="1"' on the <wps:wsp> element. |
4265 | 0 | } |
4266 | | // else nothing to do |
4267 | | |
4268 | | // Our WritingMode introduces text pre rotation which includes padding, MSO vert does not include |
4269 | | // padding. Therefore set padding so, that is looks the same in MSO as in LO. |
4270 | 0 | if (sWritingMode) |
4271 | 0 | { |
4272 | 0 | if (sWritingMode.value() == "vert" || sWritingMode.value() == "eaVert") |
4273 | 0 | { |
4274 | 0 | sal_Int32 nHelp = nLeft; |
4275 | 0 | nLeft = nBottom; |
4276 | 0 | nBottom = nRight; |
4277 | 0 | nRight = nTop; |
4278 | 0 | nTop = nHelp; |
4279 | 0 | } |
4280 | 0 | else if (sWritingMode.value() == "vert270") |
4281 | 0 | { |
4282 | 0 | sal_Int32 nHelp = nLeft; |
4283 | 0 | nLeft = nTop; |
4284 | 0 | nTop = nRight; |
4285 | 0 | nRight = nBottom; |
4286 | 0 | nBottom = nHelp; |
4287 | 0 | } |
4288 | 0 | else if (sWritingMode.value() == "mongolianVert") |
4289 | 0 | { |
4290 | | // ToDo: Examine padding |
4291 | 0 | } |
4292 | 0 | } |
4293 | |
|
4294 | 0 | std::optional<OString> sTextRotateAngleMSUnit; |
4295 | 0 | if (nTextRotateAngleDeg100.has_value()) |
4296 | 0 | sTextRotateAngleMSUnit |
4297 | 0 | = oox::drawingml::calcRotationValue(nTextRotateAngleDeg100.value().get()); |
4298 | | |
4299 | | // Prepare attributes 'anchor' and 'anchorCtr' |
4300 | | // LibreOffice has 12 value sets, MS Office only 6. We map them so, that it reverses the |
4301 | | // 6 mappings from import, and we assign the others approximately. |
4302 | 0 | TextVerticalAdjust eVerticalAlignment(TextVerticalAdjust_TOP); |
4303 | 0 | if (GetProperty(rXPropSet, u"TextVerticalAdjust"_ustr)) |
4304 | 0 | mAny >>= eVerticalAlignment; |
4305 | 0 | TextHorizontalAdjust eHorizontalAlignment(TextHorizontalAdjust_CENTER); |
4306 | 0 | if (GetProperty(rXPropSet, u"TextHorizontalAdjust"_ustr)) |
4307 | 0 | mAny >>= eHorizontalAlignment; |
4308 | |
|
4309 | 0 | const char* sAnchor = nullptr; |
4310 | 0 | bool bAnchorCtr = false; |
4311 | 0 | if (sWritingMode.has_value() |
4312 | 0 | && (sWritingMode.value() == "eaVert" || sWritingMode.value() == "mongolianVert")) |
4313 | 0 | { |
4314 | 0 | bAnchorCtr = eVerticalAlignment == TextVerticalAdjust_CENTER |
4315 | 0 | || eVerticalAlignment == TextVerticalAdjust_BOTTOM |
4316 | 0 | || eVerticalAlignment == TextVerticalAdjust_BLOCK; |
4317 | 0 | switch (eHorizontalAlignment) |
4318 | 0 | { |
4319 | 0 | case TextHorizontalAdjust_CENTER: |
4320 | 0 | sAnchor = "ctr"; |
4321 | 0 | break; |
4322 | 0 | case TextHorizontalAdjust_LEFT: |
4323 | 0 | sAnchor = sWritingMode.value() == "eaVert" ? "b" : "t"; |
4324 | 0 | break; |
4325 | 0 | case TextHorizontalAdjust_RIGHT: |
4326 | 0 | default: // TextHorizontalAdjust_BLOCK, should not happen |
4327 | 0 | sAnchor = sWritingMode.value() == "eaVert" ? "t" : "b"; |
4328 | 0 | break; |
4329 | 0 | } |
4330 | 0 | } |
4331 | 0 | else |
4332 | 0 | { |
4333 | 0 | bAnchorCtr = eHorizontalAlignment == TextHorizontalAdjust_CENTER |
4334 | 0 | || eHorizontalAlignment == TextHorizontalAdjust_RIGHT; |
4335 | 0 | sAnchor = GetTextVerticalAdjust(eVerticalAlignment); |
4336 | 0 | } |
4337 | | |
4338 | 0 | bool bHasWrap = false; |
4339 | 0 | bool bWrap = false; |
4340 | 0 | const char* pWrap = nullptr; |
4341 | |
|
4342 | 0 | if (xShape) { |
4343 | | // Only custom shapes obey the TextWordWrap option, normal text always wraps. |
4344 | 0 | if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, u"TextWordWrap"_ustr)) |
4345 | 0 | { |
4346 | 0 | mAny >>= bWrap; |
4347 | 0 | bHasWrap = true; |
4348 | 0 | } |
4349 | | |
4350 | | // tdf#134400: If AUTOGROWWIDTH and AUTOGROWHEIGHT are set, then export it as TextWordWrap |
4351 | 0 | if (SvxShapeText* pShpTxt = dynamic_cast<SvxShapeText*>(rXIface.get())) |
4352 | 0 | { |
4353 | 0 | const sdr::properties::BaseProperties& rProperties |
4354 | 0 | = pShpTxt->GetSdrObject()->GetProperties(); |
4355 | |
|
4356 | 0 | const SdrOnOffItem& rSdrTextFitWidth = rProperties.GetItem(SDRATTR_TEXT_AUTOGROWWIDTH); |
4357 | 0 | const SdrOnOffItem& rSdrTextFitHeight = rProperties.GetItem(SDRATTR_TEXT_AUTOGROWHEIGHT); |
4358 | |
|
4359 | 0 | if (rSdrTextFitWidth.GetValue() == true && rSdrTextFitHeight.GetValue() == true) |
4360 | 0 | { |
4361 | 0 | bHasWrap = true; |
4362 | 0 | bWrap = false; |
4363 | 0 | } |
4364 | 0 | } |
4365 | |
|
4366 | 0 | pWrap = (bHasWrap && !bWrap) || bIsFontworkShape ? "none" : nullptr; |
4367 | 0 | if (GetDocumentType() == DOCUMENT_DOCX) |
4368 | 0 | { |
4369 | | // In case of DOCX, if we want to have the same effect as |
4370 | | // TextShape's automatic word wrapping, then we need to set |
4371 | | // wrapping to square. |
4372 | 0 | uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY); |
4373 | 0 | if ((xServiceInfo.is() && xServiceInfo->supportsService(u"com.sun.star.drawing.TextShape"_ustr)) |
4374 | 0 | || bIsFontworkShape) |
4375 | 0 | pWrap = "square"; |
4376 | 0 | } |
4377 | 0 | } |
4378 | |
|
4379 | 0 | sal_Int16 nCols = 0; |
4380 | 0 | sal_Int32 nColSpacing = -1; |
4381 | 0 | if (GetProperty(rXPropSet, u"TextColumns"_ustr)) |
4382 | 0 | { |
4383 | 0 | if (css::uno::Reference<css::text::XTextColumns> xCols{ mAny, css::uno::UNO_QUERY }) |
4384 | 0 | { |
4385 | 0 | nCols = xCols->getColumnCount(); |
4386 | 0 | if (css::uno::Reference<css::beans::XPropertySet> xProps{ mAny, |
4387 | 0 | css::uno::UNO_QUERY }) |
4388 | 0 | { |
4389 | 0 | if (GetProperty(xProps, u"AutomaticDistance"_ustr)) |
4390 | 0 | mAny >>= nColSpacing; |
4391 | 0 | } |
4392 | 0 | } |
4393 | 0 | } |
4394 | |
|
4395 | 0 | if (!sVertOverflow && GetProperty(rXPropSet, u"TextClipVerticalOverflow"_ustr) && mAny.get<bool>()) |
4396 | 0 | { |
4397 | 0 | sVertOverflow = "clip"; |
4398 | 0 | } |
4399 | | |
4400 | | // tdf#151134 When writing placeholder shapes, inset must be explicitly specified |
4401 | 0 | bool bRequireInset = GetProperty(rXPropSet, u"IsPresentationObject"_ustr) && rXPropSet->getPropertyValue(u"IsPresentationObject"_ustr).get<bool>(); |
4402 | |
|
4403 | 0 | mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr, |
4404 | 0 | XML_rot, sTextRotateAngleMSUnit, |
4405 | 0 | XML_vertOverflow, sVertOverflow, |
4406 | 0 | XML_horzOverflow, sHorzOverflow, |
4407 | 0 | XML_vert, sWritingMode, |
4408 | 0 | XML_wrap, pWrap, |
4409 | 0 | XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), |
4410 | 0 | bRequireInset || nLeft != mconstDefaultLeftRightInset), |
4411 | 0 | XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), |
4412 | 0 | bRequireInset || nTop != mconstDefaultTopBottomInset), |
4413 | 0 | XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), |
4414 | 0 | bRequireInset || nRight != mconstDefaultLeftRightInset), |
4415 | 0 | XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), |
4416 | 0 | bRequireInset || nBottom != mconstDefaultTopBottomInset), |
4417 | 0 | XML_numCol, sax_fastparser::UseIf(OString::number(nCols), nCols > 0), |
4418 | 0 | XML_spcCol, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)), nCols > 0 && nColSpacing >= 0), |
4419 | 0 | XML_fromWordArt, sax_fastparser::UseIf("1", bFromWordArt), |
4420 | 0 | XML_anchor, sAnchor, |
4421 | 0 | XML_anchorCtr, sax_fastparser::UseIf("1", bAnchorCtr), |
4422 | 0 | XML_upright, isUpright); |
4423 | |
|
4424 | 0 | if (bIsFontworkShape) |
4425 | 0 | { |
4426 | 0 | if (aAdjustmentSeq.hasElements()) |
4427 | 0 | { |
4428 | 0 | mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp); |
4429 | 0 | mpFS->startElementNS(XML_a, XML_avLst); |
4430 | 0 | bool bHasTwoHandles( |
4431 | 0 | sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour" |
4432 | 0 | || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour" |
4433 | 0 | || sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1" |
4434 | 0 | || sPresetWarp == "textWave2" || sPresetWarp == "textWave4"); |
4435 | 0 | for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i ) |
4436 | 0 | { |
4437 | 0 | OString sName = "adj" + (bHasTwoHandles ? OString::number(i + 1) : OString()); |
4438 | 0 | double fValue(0.0); |
4439 | 0 | if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE) |
4440 | 0 | aAdjustmentSeq[i].Value >>= fValue; |
4441 | 0 | else |
4442 | 0 | { |
4443 | 0 | sal_Int32 nNumber(0); |
4444 | 0 | aAdjustmentSeq[i].Value >>= nNumber; |
4445 | 0 | fValue = static_cast<double>(nNumber); |
4446 | 0 | } |
4447 | | // Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree |
4448 | | // to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree. |
4449 | | // Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import. |
4450 | 0 | if (sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp" |
4451 | 0 | || sPresetWarp == "textButton" || sPresetWarp == "textCircle" |
4452 | 0 | || ((i == 0) |
4453 | 0 | && (sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour" |
4454 | 0 | || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour"))) |
4455 | 0 | { |
4456 | 0 | fValue *= 60000.0; |
4457 | 0 | if (fValue < 0) |
4458 | 0 | fValue += 21600000; |
4459 | 0 | } |
4460 | 0 | else if ((i == 1) |
4461 | 0 | && (sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1" |
4462 | 0 | || sPresetWarp == "textWave2" || sPresetWarp == "textWave4")) |
4463 | 0 | { |
4464 | 0 | fValue = fValue / 0.216 - 50000.0; |
4465 | 0 | } |
4466 | 0 | else if ((i == 1) |
4467 | 0 | && (sPresetWarp == "textArchDownPour" |
4468 | 0 | || sPresetWarp == "textArchUpPour" |
4469 | 0 | || sPresetWarp == "textButtonPour" |
4470 | 0 | || sPresetWarp == "textCirclePour")) |
4471 | 0 | { |
4472 | 0 | fValue /= 0.108; |
4473 | 0 | } |
4474 | 0 | else |
4475 | 0 | { |
4476 | 0 | fValue /= 0.216; |
4477 | 0 | } |
4478 | 0 | OString sFmla = "val " + OString::number(std::lround(fValue)); |
4479 | 0 | mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla); |
4480 | | // There exists faulty Favorite shapes with one handle but two adjustment values. |
4481 | 0 | if (!bHasTwoHandles) |
4482 | 0 | break; |
4483 | 0 | } |
4484 | 0 | mpFS->endElementNS(XML_a, XML_avLst); |
4485 | 0 | mpFS->endElementNS(XML_a, XML_prstTxWarp); |
4486 | 0 | } |
4487 | 0 | else |
4488 | 0 | { |
4489 | 0 | mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp); |
4490 | 0 | } |
4491 | 0 | } |
4492 | 0 | else if (GetDocumentType() == DOCUMENT_DOCX) |
4493 | 0 | { |
4494 | | // interim solution for fdo#80897, roundtrip DOCX > LO > DOCX |
4495 | 0 | if (!sMSWordPresetTextWarp.isEmpty()) |
4496 | 0 | mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sMSWordPresetTextWarp); |
4497 | 0 | } |
4498 | |
|
4499 | 0 | if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX) |
4500 | 0 | { |
4501 | | // tdf#112312: only custom shapes obey the TextAutoGrowHeight option |
4502 | 0 | bool bTextAutoGrowHeight = false; |
4503 | 0 | auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)) : nullptr; |
4504 | 0 | if (pSdrObjCustomShape && GetProperty(rXPropSet, u"TextAutoGrowHeight"_ustr)) |
4505 | 0 | { |
4506 | 0 | mAny >>= bTextAutoGrowHeight; |
4507 | 0 | } |
4508 | 0 | mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); |
4509 | 0 | } |
4510 | 0 | if (GetDocumentType() == DOCUMENT_PPTX) |
4511 | 0 | { |
4512 | 0 | TextFitToSizeType eFit = TextFitToSizeType_NONE; |
4513 | 0 | if (GetProperty(rXPropSet, u"TextFitToSize"_ustr)) |
4514 | 0 | mAny >>= eFit; |
4515 | |
|
4516 | 0 | if (eFit == TextFitToSizeType_AUTOFIT) |
4517 | 0 | { |
4518 | 0 | const sal_Int32 MAX_SCALE_VAL = 100000; |
4519 | 0 | sal_Int32 nFontScale = MAX_SCALE_VAL; |
4520 | 0 | sal_Int32 nSpacingReduction = 0; |
4521 | 0 | SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXIface.get()); |
4522 | 0 | if (pTextShape) |
4523 | 0 | { |
4524 | 0 | SdrTextObj* pTextObject = DynCastSdrTextObj(pTextShape->GetSdrObject()); |
4525 | 0 | if (pTextObject) |
4526 | 0 | { |
4527 | 0 | nFontScale = sal_Int32(pTextObject->GetFontScale() * 100000.0); |
4528 | 0 | nSpacingReduction = sal_Int32((1.0 - pTextObject->GetSpacingScale()) * 100000.0); |
4529 | 0 | } |
4530 | 0 | } |
4531 | |
|
4532 | 0 | bool bExportFontScale = false; |
4533 | 0 | if (nFontScale < MAX_SCALE_VAL && nFontScale > 0) |
4534 | 0 | bExportFontScale = true; |
4535 | |
|
4536 | 0 | bool bExportSpaceReduction = false; |
4537 | 0 | if (nSpacingReduction < MAX_SCALE_VAL && nSpacingReduction > 0) |
4538 | 0 | bExportSpaceReduction = true; |
4539 | |
|
4540 | 0 | mpFS->singleElementNS(XML_a, XML_normAutofit, |
4541 | 0 | XML_fontScale, sax_fastparser::UseIf(OString::number(nFontScale), bExportFontScale), |
4542 | 0 | XML_lnSpcReduction, sax_fastparser::UseIf(OString::number(nSpacingReduction), bExportSpaceReduction)); |
4543 | 0 | } |
4544 | 0 | else |
4545 | 0 | { |
4546 | 0 | bool bAutoGrowHeightEnabled = false; |
4547 | 0 | const SdrObject* pObj = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr; |
4548 | 0 | if (pObj) |
4549 | 0 | { |
4550 | 0 | switch (pObj->GetObjIdentifier()) |
4551 | 0 | { |
4552 | 0 | case SdrObjKind::NONE: |
4553 | 0 | case SdrObjKind::Text: |
4554 | 0 | case SdrObjKind::TitleText: |
4555 | 0 | case SdrObjKind::OutlineText: |
4556 | 0 | case SdrObjKind::Caption: |
4557 | 0 | case SdrObjKind::CustomShape: |
4558 | 0 | bAutoGrowHeightEnabled = true; |
4559 | 0 | break; |
4560 | 0 | default: |
4561 | 0 | bAutoGrowHeightEnabled = false; |
4562 | 0 | } |
4563 | 0 | } |
4564 | | |
4565 | 0 | bool bTextAutoGrowHeight = false; |
4566 | 0 | if (bAutoGrowHeightEnabled && GetProperty(rXPropSet, u"TextAutoGrowHeight"_ustr)) |
4567 | 0 | mAny >>= bTextAutoGrowHeight; |
4568 | 0 | mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); |
4569 | 0 | } |
4570 | 0 | } |
4571 | | |
4572 | 0 | Write3DEffects( rXPropSet, /*bIsText=*/true ); |
4573 | |
|
4574 | 0 | mpFS->endElementNS((nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr); |
4575 | 0 | } |
4576 | | |
4577 | | void DrawingML::WriteTextlessParagraph(const Reference<XInterface>& rXIface) |
4578 | 0 | { |
4579 | 0 | Reference<XTextContent> xParagraph; |
4580 | 0 | if (auto xAccess = rXIface.query<XEnumerationAccess>()) |
4581 | 0 | { |
4582 | 0 | if (auto xParagraphs = xAccess->createEnumeration(); |
4583 | 0 | xParagraphs && xParagraphs->hasMoreElements()) |
4584 | 0 | xParagraph = xParagraphs->nextElement().query<XTextContent>(); |
4585 | 0 | } |
4586 | |
|
4587 | 0 | if (!xParagraph) |
4588 | 0 | { |
4589 | 0 | mpFS->singleElementNS(XML_a, XML_p); |
4590 | 0 | return; |
4591 | 0 | } |
4592 | | |
4593 | | // tdf#144092: with no run to ask, the run properties come from the shape's propset |
4594 | 0 | auto xShapeProps = rXIface.query<XPropertySet>(); |
4595 | 0 | assert(xShapeProps); |
4596 | |
|
4597 | 0 | mpFS->startElementNS(XML_a, XML_p); |
4598 | 0 | WriteParagraphProperties(xParagraph, -1, XML_pPr); |
4599 | 0 | WriteRunInput aInput{ .xShapePropSet = xShapeProps }; |
4600 | 0 | WriteRunProperties(xShapeProps, XML_endParaRPr, aInput); |
4601 | 0 | mpFS->endElementNS(XML_a, XML_p); |
4602 | 0 | } |
4603 | | |
4604 | | void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bool bText, |
4605 | | sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles) |
4606 | 0 | { |
4607 | | // ToDo: Fontwork in DOCX |
4608 | 0 | uno::Reference<XText> xXText(rXIface, UNO_QUERY); |
4609 | 0 | if( !xXText.is() ) |
4610 | 0 | return; |
4611 | | |
4612 | 0 | uno::Reference<drawing::XShape> xShape(rXIface, UNO_QUERY); |
4613 | 0 | uno::Reference<XPropertySet> rXPropSet(rXIface, UNO_QUERY); |
4614 | |
|
4615 | 0 | bool bIsFontworkShape(IsFontworkShape(rXPropSet)); |
4616 | |
|
4617 | 0 | sal_Int32 nLeft = mconstDefaultLeftRightInset; |
4618 | 0 | sal_Int32 nRight = mconstDefaultLeftRightInset; |
4619 | 0 | sal_Int32 nTop = mconstDefaultTopBottomInset; |
4620 | 0 | sal_Int32 nBottom = mconstDefaultTopBottomInset; |
4621 | | |
4622 | | // top inset looks a bit different compared to ppt export |
4623 | | // check if something related doesn't work as expected |
4624 | 0 | if (GetProperty(rXPropSet, u"TextLeftDistance"_ustr)) |
4625 | 0 | mAny >>= nLeft; |
4626 | 0 | if (GetProperty(rXPropSet, u"TextRightDistance"_ustr)) |
4627 | 0 | mAny >>= nRight; |
4628 | 0 | if (GetProperty(rXPropSet, u"TextUpperDistance"_ustr)) |
4629 | 0 | mAny >>= nTop; |
4630 | 0 | if (GetProperty(rXPropSet, u"TextLowerDistance"_ustr)) |
4631 | 0 | mAny >>= nBottom; |
4632 | | |
4633 | | // Fontwork shapes in LO ignore insets in rendering, Word interprets them. |
4634 | 0 | if (GetDocumentType() == DOCUMENT_DOCX && bIsFontworkShape) |
4635 | 0 | { |
4636 | 0 | nLeft = 0; |
4637 | 0 | nRight = 0; |
4638 | 0 | nTop = 0; |
4639 | 0 | nBottom = 0; |
4640 | 0 | } |
4641 | | |
4642 | | // Transform the text distance values so they are compatible with OOXML insets |
4643 | 0 | if (xShape.is()) |
4644 | 0 | { |
4645 | 0 | sal_Int32 nTextHeight = xShape->getSize().Height; // Hmm, default |
4646 | | |
4647 | | // CustomShape can have text area different from shape rectangle |
4648 | 0 | auto* pCustomShape |
4649 | 0 | = dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)); |
4650 | 0 | if (pCustomShape) |
4651 | 0 | { |
4652 | 0 | const EnhancedCustomShape2d aCustomShape2d(*pCustomShape); |
4653 | 0 | nTextHeight = aCustomShape2d.GetTextRect().getOpenHeight(); |
4654 | 0 | if (DOCUMENT_DOCX == meDocumentType) |
4655 | 0 | nTextHeight = convertTwipToMm100(nTextHeight); |
4656 | 0 | } |
4657 | |
|
4658 | 0 | if (nTop + nBottom >= nTextHeight) |
4659 | 0 | { |
4660 | | // Effective bottom would be above effective top of text area. LO normalizes the |
4661 | | // effective text area in such case implicitly for rendering. MS needs indents so that |
4662 | | // the result is the normalized effective text area. |
4663 | 0 | std::swap(nTop, nBottom); |
4664 | 0 | nTop = nTextHeight - nTop; |
4665 | 0 | nBottom = nTextHeight - nBottom; |
4666 | 0 | } |
4667 | 0 | } |
4668 | |
|
4669 | 0 | if (bBodyPr) |
4670 | 0 | { |
4671 | 0 | WriteBodyProps(rXIface, nXmlNamespace, bIsFontworkShape, nTop, nBottom, nLeft, nRight); |
4672 | 0 | } |
4673 | 0 | else |
4674 | 0 | { |
4675 | 0 | mpFS->singleElementNS(XML_a, XML_bodyPr); |
4676 | 0 | } // end of bodyPr |
4677 | |
|
4678 | 0 | Reference< XEnumerationAccess > access( xXText, UNO_QUERY ); |
4679 | 0 | if( !access.is() || !bText ) |
4680 | 0 | return; |
4681 | | |
4682 | 0 | Reference< XEnumeration > enumeration( access->createEnumeration() ); |
4683 | 0 | if( !enumeration.is() ) |
4684 | 0 | return; |
4685 | | |
4686 | 0 | SdrObject* pSdrObject = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr; |
4687 | 0 | const SdrTextObj* pTxtObj = DynCastSdrTextObj( pSdrObject ); |
4688 | 0 | if (pTxtObj && mpTextExport) |
4689 | 0 | { |
4690 | 0 | std::vector<beans::PropertyValue> aOldCharFillPropVec; |
4691 | 0 | if (bIsFontworkShape) |
4692 | 0 | { |
4693 | | // Users may have set the character fill properties for more convenient editing. |
4694 | | // Save the properties before changing them for Fontwork export. |
4695 | 0 | FontworkHelpers::collectCharColorProps(xXText, aOldCharFillPropVec); |
4696 | | // Word has properties for abc-transform in the run properties of the text of the shape. |
4697 | | // Writer has the Fontwork properties as shape properties. Create the character fill |
4698 | | // properties needed for export from the shape fill properties |
4699 | | // and apply them to all runs. |
4700 | 0 | std::vector<beans::PropertyValue> aExportCharFillPropVec; |
4701 | 0 | FontworkHelpers::createCharFillPropsFromShape(rXPropSet, aExportCharFillPropVec); |
4702 | 0 | FontworkHelpers::applyPropsToRuns(aExportCharFillPropVec, xXText); |
4703 | | // Import has converted some items from CharInteropGrabBag to fill and line |
4704 | | // properties of the shape. For export we convert them back because users might have |
4705 | | // changed them. And we create them in case we come from an odt document. |
4706 | 0 | std::vector<beans::PropertyValue> aUpdatePropVec; |
4707 | 0 | FontworkHelpers::createCharInteropGrabBagUpdatesFromShapeProps(rXPropSet, aUpdatePropVec); |
4708 | 0 | FontworkHelpers::applyUpdatesToCharInteropGrabBag(aUpdatePropVec, xXText); |
4709 | 0 | } |
4710 | |
|
4711 | 0 | std::optional<OutlinerParaObject> pParaObj; |
4712 | | |
4713 | | /* |
4714 | | #i13885# |
4715 | | When the object is actively being edited, that text is not set into |
4716 | | the objects normal text object, but lives in a separate object. |
4717 | | */ |
4718 | 0 | if (pTxtObj->IsTextEditActive()) |
4719 | 0 | { |
4720 | 0 | pParaObj = pTxtObj->CreateEditOutlinerParaObject(); |
4721 | 0 | } |
4722 | 0 | else if (pTxtObj->GetOutlinerParaObject()) |
4723 | 0 | pParaObj = *pTxtObj->GetOutlinerParaObject(); |
4724 | |
|
4725 | 0 | if (pParaObj) |
4726 | 0 | { |
4727 | | // this is reached only in case some text is attached to the shape |
4728 | 0 | mpTextExport->WriteOutliner(*pParaObj); |
4729 | 0 | } |
4730 | |
|
4731 | 0 | if (bIsFontworkShape) |
4732 | 0 | FontworkHelpers::applyPropsToRuns(aOldCharFillPropVec, xXText); |
4733 | 0 | return; |
4734 | 0 | } |
4735 | | |
4736 | 0 | bool bOverridingCharHeight = false; |
4737 | 0 | sal_Int32 nCharHeight = -1; |
4738 | 0 | bool bFirstParagraph = true; |
4739 | |
|
4740 | 0 | if (xXText->getString().isEmpty()) |
4741 | 0 | { |
4742 | 0 | WriteTextlessParagraph(rXIface); |
4743 | 0 | return; |
4744 | 0 | } |
4745 | | |
4746 | 0 | sal_Int32 nParagraphIndex(0); |
4747 | |
|
4748 | 0 | while( enumeration->hasMoreElements() ) |
4749 | 0 | { |
4750 | 0 | Reference< XTextContent > paragraph; |
4751 | 0 | Any any ( enumeration->nextElement() ); |
4752 | |
|
4753 | 0 | if( any >>= paragraph) |
4754 | 0 | { |
4755 | | // A shape of a Diagram can represent several Points, one paragraph of it each. Only |
4756 | | // the paragraph that represents the Point being written belongs in it. |
4757 | 0 | const bool bOtherPoint(isDiagramModelTextExport() && mnDiagramModelTextParagraph >= 0 |
4758 | 0 | && nParagraphIndex != mnDiagramModelTextParagraph); |
4759 | 0 | nParagraphIndex++; |
4760 | |
|
4761 | 0 | if (bOtherPoint) |
4762 | 0 | continue; |
4763 | | |
4764 | 0 | if (bFirstParagraph && bWritePropertiesAsLstStyles) |
4765 | 0 | WriteLstStyles(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet); |
4766 | |
|
4767 | 0 | WriteParagraph(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet); |
4768 | 0 | bFirstParagraph = false; |
4769 | 0 | } |
4770 | 0 | } |
4771 | 0 | } |
4772 | | |
4773 | | void DrawingML::WritePresetShape( const OString& pShape , std::vector< std::pair<sal_Int32,sal_Int32>> & rAvList ) |
4774 | 0 | { |
4775 | 0 | mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); |
4776 | 0 | if ( !rAvList.empty() ) |
4777 | 0 | { |
4778 | |
|
4779 | 0 | mpFS->startElementNS(XML_a, XML_avLst); |
4780 | 0 | for (auto const& elem : rAvList) |
4781 | 0 | { |
4782 | 0 | OString sName = "adj" + ( ( elem.first > 0 ) ? OString::number(elem.first) : OString() ); |
4783 | 0 | OString sFmla = "val " + OString::number( elem.second ); |
4784 | |
|
4785 | 0 | mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla); |
4786 | 0 | } |
4787 | 0 | mpFS->endElementNS( XML_a, XML_avLst ); |
4788 | 0 | } |
4789 | 0 | else |
4790 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
4791 | |
|
4792 | 0 | mpFS->endElementNS( XML_a, XML_prstGeom ); |
4793 | 0 | } |
4794 | | |
4795 | | void DrawingML::WritePresetShape( const OString& pShape ) |
4796 | 0 | { |
4797 | 0 | mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); |
4798 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
4799 | 0 | mpFS->endElementNS( XML_a, XML_prstGeom ); |
4800 | 0 | } |
4801 | | |
4802 | | static std::map< OString, std::vector<OString> > lcl_getAdjNames() |
4803 | 0 | { |
4804 | 0 | std::map< OString, std::vector<OString> > aRet; |
4805 | |
|
4806 | 0 | OUString aPath(u"$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names"_ustr); |
4807 | 0 | rtl::Bootstrap::expandMacros(aPath); |
4808 | 0 | SvFileStream aStream(aPath, StreamMode::READ); |
4809 | 0 | if (aStream.GetError() != ERRCODE_NONE) |
4810 | 0 | SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names"); |
4811 | 0 | OStringBuffer aLine; |
4812 | 0 | while (aStream.ReadLine(aLine)) |
4813 | 0 | { |
4814 | 0 | sal_Int32 nIndex = 0; |
4815 | | // Each line is in a "key\tvalue" format: read the key, the rest is the value. |
4816 | 0 | OString aKey( o3tl::getToken(aLine, 0, '\t', nIndex) ); |
4817 | 0 | if (nIndex >= 0) |
4818 | 0 | { |
4819 | 0 | OString aValue( std::string_view(aLine).substr(nIndex) ); |
4820 | 0 | aRet[aKey].push_back(aValue); |
4821 | 0 | } |
4822 | 0 | else |
4823 | 0 | { |
4824 | 0 | SAL_WARN("oox.shape", "skipping invalid line: " << std::string_view(aLine)); |
4825 | 0 | } |
4826 | 0 | } |
4827 | 0 | return aRet; |
4828 | 0 | } |
4829 | | |
4830 | | void DrawingML::WritePresetShape( const OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp ) |
4831 | 0 | { |
4832 | 0 | static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames(); |
4833 | | // If there are predefined adj names for this shape type, look them up now. |
4834 | 0 | std::vector<OString> aAdjustments; |
4835 | 0 | auto it = aAdjMap.find(pShape); |
4836 | 0 | if (it != aAdjMap.end()) |
4837 | 0 | aAdjustments = it->second; |
4838 | |
|
4839 | 0 | mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); |
4840 | 0 | mpFS->startElementNS(XML_a, XML_avLst); |
4841 | |
|
4842 | 0 | Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq; |
4843 | 0 | if ( ( rProp.Value >>= aAdjustmentSeq ) |
4844 | 0 | && eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them |
4845 | 0 | && eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled |
4846 | 0 | && pShape != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name. |
4847 | 0 | ) |
4848 | 0 | { |
4849 | 0 | SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength()); |
4850 | 0 | sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0; |
4851 | 0 | if ( bPredefinedHandlesUsed ) |
4852 | 0 | EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted ); |
4853 | |
|
4854 | 0 | sal_Int32 nValue, nLength = aAdjustmentSeq.getLength(); |
4855 | | // aAdjustments will give info about the number of adj values for a particular geometry. For example for hexagon aAdjustments.size() will be 2 and for circular arrow it will be 5 as per lcl_getAdjNames. |
4856 | | // Sometimes there are more values than needed, so we ignore the excessive ones. |
4857 | 0 | if (aAdjustments.size() <= o3tl::make_unsigned(nLength)) |
4858 | 0 | { |
4859 | 0 | for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++) |
4860 | 0 | { |
4861 | 0 | if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) ) |
4862 | 0 | { |
4863 | | // If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list. |
4864 | 0 | OString aAdjName = aAdjustmentSeq[i].Name.isEmpty() |
4865 | 0 | ? aAdjustments[i] |
4866 | 0 | : aAdjustmentSeq[i].Name.toUtf8(); |
4867 | |
|
4868 | 0 | mpFS->singleElementNS( XML_a, XML_gd, |
4869 | 0 | XML_name, aAdjName, |
4870 | 0 | XML_fmla, "val " + OString::number(nValue)); |
4871 | 0 | } |
4872 | 0 | } |
4873 | 0 | } |
4874 | 0 | } |
4875 | | |
4876 | 0 | mpFS->endElementNS( XML_a, XML_avLst ); |
4877 | 0 | mpFS->endElementNS( XML_a, XML_prstGeom ); |
4878 | 0 | } |
4879 | | |
4880 | | namespace // helpers for DrawingML::WriteCustomGeometry |
4881 | | { |
4882 | | sal_Int32 |
4883 | | FindNextCommandEndSubpath(const sal_Int32 nStart, |
4884 | | const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) |
4885 | 0 | { |
4886 | 0 | sal_Int32 i = nStart < 0 ? 0 : nStart; |
4887 | 0 | while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH) |
4888 | 0 | i++; |
4889 | 0 | return i; |
4890 | 0 | } |
4891 | | |
4892 | | bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast, |
4893 | | const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) |
4894 | 0 | { |
4895 | 0 | for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++) |
4896 | 0 | { |
4897 | 0 | if (rSegments[i].Command == nCommand) |
4898 | 0 | return true; |
4899 | 0 | } |
4900 | 0 | return false; |
4901 | 0 | } |
4902 | | |
4903 | | // Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP |
4904 | | // intersects the ellipse in point S and this point S has angle fAngleDeg in degrees. |
4905 | | void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy, |
4906 | | const double fWR, const double fHR, const double fCx, |
4907 | | const double fCy, const double fRayPx, const double fRayPy) |
4908 | 0 | { |
4909 | 0 | if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) |
4910 | 0 | { |
4911 | 0 | rfSx = fCx; // needed for getting new 'current point' |
4912 | 0 | rfSy = fCy; |
4913 | 0 | } |
4914 | 0 | else |
4915 | 0 | { |
4916 | | // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation |
4917 | | // and get angle |
4918 | 0 | double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx); |
4919 | | // use angle for intersection point on circle and stretch back to ellipse |
4920 | 0 | double fPointMathEllipse_x = fWR * cos(fCircleMathAngle); |
4921 | 0 | double fPointMathEllipse_y = fHR * sin(fCircleMathAngle); |
4922 | | // get angle of intersection point on ellipse |
4923 | 0 | double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x); |
4924 | | // convert from Math to View orientation and shift ellipse back from origin |
4925 | 0 | rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle); |
4926 | 0 | rfSx = fPointMathEllipse_x + fCx; |
4927 | 0 | rfSy = -fPointMathEllipse_y + fCy; |
4928 | 0 | } |
4929 | 0 | } |
4930 | | |
4931 | | void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR, |
4932 | | const double fCx, const double fCy, const double fViewAngleDeg) |
4933 | 0 | { |
4934 | 0 | if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) |
4935 | 0 | { |
4936 | 0 | rfSx = fCx; // needed for getting new 'current point' |
4937 | 0 | rfSy = fCy; |
4938 | 0 | } |
4939 | 0 | else |
4940 | 0 | { |
4941 | 0 | double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR; |
4942 | 0 | double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR; |
4943 | 0 | double fRadius = 1.0 / std::hypot(fX, fY); |
4944 | 0 | rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg)); |
4945 | 0 | rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg)); |
4946 | 0 | } |
4947 | 0 | } |
4948 | | |
4949 | | sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam, |
4950 | | const EnhancedCustomShape2d& rCustomShape2d, |
4951 | | const bool bReplaceGeoWidth, const bool bReplaceGeoHeight) |
4952 | 0 | { |
4953 | 0 | double fValue = 0.0; |
4954 | 0 | rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight); |
4955 | 0 | sal_Int32 nValue(std::lround(fValue)); |
4956 | |
|
4957 | 0 | return nValue; |
4958 | 0 | } |
4959 | | |
4960 | | struct TextAreaRect |
4961 | | { |
4962 | | OString left; |
4963 | | OString top; |
4964 | | OString right; |
4965 | | OString bottom; |
4966 | | }; |
4967 | | |
4968 | | struct Guide |
4969 | | { |
4970 | | OString sName; |
4971 | | OString sFormula; |
4972 | | }; |
4973 | | |
4974 | | void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d, |
4975 | | std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect) |
4976 | 0 | { |
4977 | 0 | tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect()); |
4978 | 0 | tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect()); |
4979 | 0 | if (aTextAreaLO == aLogicRectLO) |
4980 | 0 | { |
4981 | 0 | rTextAreaRect.left = "l"_ostr; |
4982 | 0 | rTextAreaRect.top = "t"_ostr; |
4983 | 0 | rTextAreaRect.right = "r"_ostr; |
4984 | 0 | rTextAreaRect.bottom = "b"_ostr; |
4985 | 0 | return; |
4986 | 0 | } |
4987 | | // Flip aTextAreaLO if shape is flipped |
4988 | 0 | if (rEnhancedCustomShape2d.IsFlipHorz()) |
4989 | 0 | aTextAreaLO.Move((aLogicRectLO.Center().X() - aTextAreaLO.Center().X()) * 2, 0); |
4990 | 0 | if (rEnhancedCustomShape2d.IsFlipVert()) |
4991 | 0 | aTextAreaLO.Move(0, (aLogicRectLO.Center().Y() - aTextAreaLO.Center().Y()) * 2); |
4992 | |
|
4993 | 0 | Guide aGuide; |
4994 | | // horizontal |
4995 | 0 | const sal_Int32 nWidth = aLogicRectLO.Right() - aLogicRectLO.Left(); |
4996 | 0 | const OString sWidth = OString::number(oox::drawingml::convertHmmToEmu(nWidth)); |
4997 | | |
4998 | | // left |
4999 | 0 | aGuide.sName = "textAreaLeft"_ostr; |
5000 | 0 | sal_Int32 nHelp = aTextAreaLO.Left() - aLogicRectLO.Left(); |
5001 | 0 | const OString sLeft = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); |
5002 | 0 | aGuide.sFormula = "*/ " + sLeft + " w " + sWidth; |
5003 | 0 | rTextAreaRect.left = aGuide.sName; |
5004 | 0 | rGuideList.push_back(aGuide); |
5005 | | |
5006 | | // right |
5007 | 0 | aGuide.sName = "textAreaRight"_ostr; |
5008 | 0 | nHelp = aTextAreaLO.Right() - aLogicRectLO.Left(); |
5009 | 0 | const OString sRight = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); |
5010 | 0 | aGuide.sFormula = "*/ " + sRight + " w " + sWidth; |
5011 | 0 | rTextAreaRect.right = aGuide.sName; |
5012 | 0 | rGuideList.push_back(aGuide); |
5013 | | |
5014 | | // vertical |
5015 | 0 | const sal_Int32 nHeight = aLogicRectLO.Bottom() - aLogicRectLO.Top(); |
5016 | 0 | const OString sHeight = OString::number(oox::drawingml::convertHmmToEmu(nHeight)); |
5017 | | |
5018 | | // top |
5019 | 0 | aGuide.sName = "textAreaTop"_ostr; |
5020 | 0 | nHelp = aTextAreaLO.Top() - aLogicRectLO.Top(); |
5021 | 0 | const OString sTop = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); |
5022 | 0 | aGuide.sFormula = "*/ " + sTop + " h " + sHeight; |
5023 | 0 | rTextAreaRect.top = aGuide.sName; |
5024 | 0 | rGuideList.push_back(aGuide); |
5025 | | |
5026 | | // bottom |
5027 | 0 | aGuide.sName = "textAreaBottom"_ostr; |
5028 | 0 | nHelp = aTextAreaLO.Bottom() - aLogicRectLO.Top(); |
5029 | 0 | const OString sBottom = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); |
5030 | 0 | aGuide.sFormula = "*/ " + sBottom + " h " + sHeight; |
5031 | 0 | rTextAreaRect.bottom = aGuide.sName; |
5032 | 0 | rGuideList.push_back(aGuide); |
5033 | |
|
5034 | 0 | return; |
5035 | 0 | } |
5036 | | |
5037 | | bool IsValidOOXMLFormula(std::u16string_view sFormula) |
5038 | 0 | { |
5039 | | // NOTE: this list is not complete - just a first effort at a reasonable list, |
5040 | | // and most of these are not yet even generated by GetFormula()... |
5041 | | |
5042 | | // Accepted Formulas |
5043 | | // "val n1" |
5044 | | // "abs n1" |
5045 | | // "sqrt n1" |
5046 | | // "min n1 n2" |
5047 | | // "max n1 n2" |
5048 | | // "*/ n1 n2 n3" |
5049 | | // "+- n1 n2 n3" |
5050 | | // "+/ n1 n2 n3" |
5051 | | // "?: n1 n2 n3" // ifelse |
5052 | | |
5053 | | // Below vector contains validTokens for the 1st token based on the number of tokens in the formula. The order is: 2, 3, 4 |
5054 | 0 | const std::vector<std::set<std::u16string_view>> validTokens = |
5055 | 0 | { |
5056 | 0 | { u"val", u"abs", u"sqrt" }, |
5057 | 0 | { u"min", u"max" }, |
5058 | 0 | { u"*/", u"+-", u"+/", u"?:" } |
5059 | 0 | }; |
5060 | 0 | const std::set<std::u16string_view> builtInVariables = |
5061 | 0 | { |
5062 | 0 | u"w", |
5063 | 0 | u"h", |
5064 | 0 | u"t", |
5065 | 0 | u"b", |
5066 | 0 | u"l", |
5067 | 0 | u"r" |
5068 | 0 | }; |
5069 | 0 | const std::vector<OUString> strTokens = comphelper::string::split(sFormula, ' '); |
5070 | 0 | sal_uInt16 nSize = strTokens.size(); |
5071 | |
|
5072 | 0 | if (nSize < 2 || nSize > 4) |
5073 | 0 | return false; |
5074 | | |
5075 | 0 | auto aTokens = validTokens[nSize - 2]; |
5076 | | |
5077 | | // Check whether the 1st token is valid |
5078 | 0 | if (aTokens.find(strTokens[0]) == aTokens.end()) |
5079 | 0 | return false; |
5080 | | |
5081 | | // Check that the remaining tokens are either numbers or built-in variables |
5082 | 0 | for (sal_Int16 i = 1; i < nSize; i++) |
5083 | 0 | { |
5084 | 0 | OUString sVal = strTokens[i]; |
5085 | 0 | if (builtInVariables.contains(sVal)) |
5086 | 0 | continue; // valid built-in variable |
5087 | | |
5088 | | // TODO: recognize valid 'guide name' pointing to other equations or adjustments |
5089 | | |
5090 | 0 | if (OUString::number(sVal.toInt64()) != sVal) |
5091 | 0 | return false; // not a simple number |
5092 | 0 | } |
5093 | 0 | return true; |
5094 | 0 | } |
5095 | | |
5096 | | OUString GetFormula(const OUString& sEquation) |
5097 | 0 | { |
5098 | 0 | assert(!sEquation.isEmpty() && "surely an equation would never be empty..."); |
5099 | | // TODO: This needs to be completely re-written. It is extremely simplistic/minimal. |
5100 | | // What is needed here is the reverse of convertToOOEquation. |
5101 | | |
5102 | | // WARNING: Almost all of the current logic here was created simply to avoid 'corrupt document' |
5103 | | // notices from MS Office. The 'correct position' of the actual glue-points might be very wrong. |
5104 | | |
5105 | | // If the equation is numerical |
5106 | 0 | sal_Int64 nValue = sEquation.toInt64(); |
5107 | 0 | if (!sEquation.isEmpty() && OUString::number(nValue) == sEquation) |
5108 | 0 | return "val " + sEquation; |
5109 | | |
5110 | 0 | OUString sFormula = sEquation; |
5111 | | |
5112 | | /* replace LO native placeholders with OOXML placeholders |
5113 | | * #1: e.g. 'logwidth' |
5114 | | * #2: e.g. 'logwidth/2' |
5115 | | * #3: e.g. '1234*logwidth/5678' |
5116 | | */ |
5117 | |
|
5118 | 0 | if (sEquation == "logwidth") // #1 |
5119 | 0 | return "val w"; |
5120 | 0 | if (sEquation == "logheight") |
5121 | 0 | return "val h"; |
5122 | 0 | if (sEquation.startsWith("logwidth/")) // #2 |
5123 | 0 | sFormula = u"*/ 1 w "_ustr + sEquation.subView(9); |
5124 | 0 | else if (sEquation.startsWith("logheight/")) |
5125 | 0 | sFormula = u"*/ 1 h "_ustr + sEquation.subView(10); |
5126 | 0 | else |
5127 | 0 | { |
5128 | 0 | size_t nPos = sFormula.indexOf("*logwidth/"); //#3 |
5129 | 0 | if (nPos != std::string::npos) |
5130 | 0 | sFormula = "*/ " + sFormula.replaceAt(nPos, 10, " w "); |
5131 | 0 | else |
5132 | 0 | { |
5133 | 0 | nPos = sFormula.indexOf("*logheight/"); |
5134 | 0 | if (nPos != std::string::npos) |
5135 | 0 | sFormula = "*/ " + sFormula.replaceAt(nPos, 11, " h "); |
5136 | 0 | } |
5137 | 0 | } |
5138 | |
|
5139 | 0 | if (IsValidOOXMLFormula(sFormula)) |
5140 | 0 | return sFormula; |
5141 | 0 | else SAL_WARN("oox.shape","invalid OOXML formula["<<sFormula<<"]"); |
5142 | | |
5143 | 0 | return OUString(); |
5144 | 0 | } |
5145 | | |
5146 | | void prepareGluePoints(std::vector<Guide>& rGuideList, |
5147 | | const css::uno::Sequence<OUString>& aEquations, |
5148 | | const uno::Sequence<drawing::EnhancedCustomShapeParameterPair>& rGluePoints, |
5149 | | const sal_Int32 nWidth, const sal_Int32 nHeight) |
5150 | 0 | { |
5151 | 0 | if (rGluePoints.hasElements()) |
5152 | 0 | { |
5153 | 0 | sal_Int32 nIndex = 0; |
5154 | 0 | for (auto const& rGluePoint : rGluePoints) |
5155 | 0 | { |
5156 | 0 | ++nIndex; |
5157 | 0 | sal_Int32 nIdx1 = -1; |
5158 | 0 | sal_Int32 nIdx2 = -1; |
5159 | 0 | bool bValidIdx1 = false; |
5160 | 0 | bool bValidIdx2 = false; |
5161 | 0 | if (rGluePoint.First.Value >>= nIdx1) |
5162 | 0 | { |
5163 | 0 | bValidIdx1 = rGluePoint.First.Type == EnhancedCustomShapeParameterType::EQUATION; |
5164 | 0 | assert(!bValidIdx1 || nIdx1 >= 0); // It is always an index at least |
5165 | | // tdf#166335 |
5166 | | // Sadly, these equation indexes point to a (changed/enlarged) parsed collection, |
5167 | | // but aEquations is the un-parsed collection. |
5168 | | // For larger values, there is a good chance that the wrong equation is used. |
5169 | | // However, this is so complicated that we ignore that, |
5170 | | // and simply make sure we don't export corruption. Inaccuracy is fine... |
5171 | 0 | bValidIdx1 = bValidIdx1 && nIdx1 < aEquations.getLength(); |
5172 | 0 | } |
5173 | 0 | else |
5174 | 0 | continue; |
5175 | | |
5176 | 0 | if (rGluePoint.Second.Value >>= nIdx2) |
5177 | 0 | { |
5178 | 0 | bValidIdx2 = rGluePoint.Second.Type == EnhancedCustomShapeParameterType::EQUATION; |
5179 | 0 | assert(!bValidIdx2 || nIdx2 >= 0); |
5180 | 0 | bValidIdx2 = bValidIdx2 && nIdx2 < aEquations.getLength(); |
5181 | 0 | } |
5182 | 0 | else |
5183 | 0 | continue; |
5184 | | |
5185 | 0 | Guide aGuideX; |
5186 | 0 | Guide aGuideY; |
5187 | 0 | if (bValidIdx1) |
5188 | 0 | { |
5189 | 0 | aGuideX.sFormula = GetFormula(aEquations[nIdx1]).toUtf8(); |
5190 | 0 | if (aGuideX.sFormula.isEmpty()) // !IsValidOOXMLFormula |
5191 | 0 | continue; |
5192 | 0 | } |
5193 | 0 | else |
5194 | 0 | { |
5195 | | // nice TODO: avoid divide by zero, and simplify when multiplying by zero. |
5196 | | |
5197 | | // confirmation needed: does a simple gluepoint number always mean nIdx1 * w / width |
5198 | 0 | aGuideX.sFormula = "*/ " + OString::number(nIdx1) + " w " + OString::number(nWidth); |
5199 | 0 | } |
5200 | 0 | if (bValidIdx2) |
5201 | 0 | { |
5202 | 0 | aGuideY.sFormula = GetFormula(aEquations[nIdx2]).toUtf8(); |
5203 | 0 | if (aGuideY.sFormula.isEmpty()) // !IsValidOOXMLFormula |
5204 | 0 | continue; |
5205 | 0 | } |
5206 | 0 | else |
5207 | 0 | aGuideY.sFormula |
5208 | 0 | = "*/ " + OString::number(nIdx2) + " h " + OString::number(nHeight); |
5209 | | |
5210 | 0 | aGuideX.sName = "GluePoint"_ostr + OString::number(nIndex) + "X"; |
5211 | 0 | rGuideList.push_back(aGuideX); |
5212 | |
|
5213 | 0 | aGuideY.sName = "GluePoint"_ostr + OString::number(nIndex) + "Y"; |
5214 | 0 | rGuideList.push_back(aGuideY); |
5215 | 0 | } |
5216 | 0 | } |
5217 | 0 | } |
5218 | | } |
5219 | | |
5220 | | bool DrawingML::WriteCustomGeometry( |
5221 | | const Reference< XShape >& rXShape, |
5222 | | const SdrObjCustomShape& rSdrObjCustomShape) |
5223 | 0 | { |
5224 | 0 | uno::Reference< beans::XPropertySet > aXPropSet; |
5225 | 0 | uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get())); |
5226 | |
|
5227 | 0 | if ( ! (aAny >>= aXPropSet) ) |
5228 | 0 | return false; |
5229 | | |
5230 | 0 | try |
5231 | 0 | { |
5232 | 0 | aAny = aXPropSet->getPropertyValue( u"CustomShapeGeometry"_ustr ); |
5233 | 0 | if ( !aAny.hasValue() ) |
5234 | 0 | return false; |
5235 | 0 | } |
5236 | 0 | catch( const ::uno::Exception& ) |
5237 | 0 | { |
5238 | 0 | return false; |
5239 | 0 | } |
5240 | | |
5241 | 0 | auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny); |
5242 | 0 | if (!pGeometrySeq) |
5243 | 0 | return false; |
5244 | | |
5245 | 0 | auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), |
5246 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "Path"; }); |
5247 | 0 | if (pPathProp == std::cend(*pGeometrySeq)) |
5248 | 0 | return false; |
5249 | | |
5250 | 0 | uno::Sequence<beans::PropertyValue> aPathProp; |
5251 | 0 | pPathProp->Value >>= aPathProp; |
5252 | |
|
5253 | 0 | auto pEquationsProp |
5254 | 0 | = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), |
5255 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "Equations"; }); |
5256 | |
|
5257 | 0 | css::uno::Sequence<OUString> aEquationSeq; |
5258 | 0 | if (pEquationsProp != std::cend(*pGeometrySeq)) |
5259 | 0 | { |
5260 | 0 | pEquationsProp->Value >>= aEquationSeq; |
5261 | 0 | } |
5262 | |
|
5263 | 0 | uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aGluePoints; |
5264 | 0 | uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; |
5265 | 0 | uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; |
5266 | 0 | uno::Sequence<awt::Size> aPathSize; |
5267 | 0 | bool bReplaceGeoWidth = false; |
5268 | 0 | bool bReplaceGeoHeight = false; |
5269 | 0 | for (const beans::PropertyValue& rPathProp : aPathProp) |
5270 | 0 | { |
5271 | 0 | if (rPathProp.Name == "Coordinates") |
5272 | 0 | rPathProp.Value >>= aPairs; |
5273 | 0 | else if (rPathProp.Name == "Segments") |
5274 | 0 | rPathProp.Value >>= aSegments; |
5275 | 0 | else if (rPathProp.Name == "GluePoints") |
5276 | 0 | rPathProp.Value >>= aGluePoints; |
5277 | 0 | else if (rPathProp.Name == "SubViewSize") |
5278 | 0 | rPathProp.Value >>= aPathSize; |
5279 | 0 | else if (rPathProp.Name == "StretchX") |
5280 | 0 | bReplaceGeoWidth = true; |
5281 | 0 | else if (rPathProp.Name == "StretchY") |
5282 | 0 | bReplaceGeoHeight = true; |
5283 | 0 | } |
5284 | |
|
5285 | 0 | if ( !aPairs.hasElements() ) |
5286 | 0 | return false; |
5287 | | |
5288 | 0 | if ( !aSegments.hasElements() ) |
5289 | 0 | { |
5290 | 0 | aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment> |
5291 | 0 | { |
5292 | 0 | { MOVETO, 1 }, |
5293 | 0 | { LINETO, |
5294 | 0 | static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) }, |
5295 | 0 | { CLOSESUBPATH, 0 }, |
5296 | 0 | { ENDSUBPATH, 0 } |
5297 | 0 | }; |
5298 | 0 | }; |
5299 | |
|
5300 | 0 | int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0, |
5301 | 0 | [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; }); |
5302 | |
|
5303 | 0 | if ( nExpectedPairCount > aPairs.getLength() ) |
5304 | 0 | { |
5305 | 0 | SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs."); |
5306 | 0 | return false; |
5307 | 0 | } |
5308 | | |
5309 | | // A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the |
5310 | | // entire method. |
5311 | 0 | const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape)); |
5312 | | |
5313 | | // Prepare width and height for <a:path> |
5314 | 0 | bool bUseGlobalViewBox(false); |
5315 | | |
5316 | | // nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not |
5317 | | // triggered; same for height. |
5318 | 0 | sal_Int32 nViewBoxWidth(0); |
5319 | 0 | sal_Int32 nViewBoxHeight(0); |
5320 | 0 | if (!aPathSize.hasElements()) |
5321 | 0 | { |
5322 | 0 | bUseGlobalViewBox = true; |
5323 | | // If draw:viewBox is missing in draw:enhancedGeometry, then import sets |
5324 | | // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated |
5325 | | // current file via macro. Author of macro has to fix it. |
5326 | 0 | auto pProp = std::find_if( |
5327 | 0 | std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), |
5328 | 0 | [](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; }); |
5329 | 0 | if (pProp != std::cend(*pGeometrySeq)) |
5330 | 0 | { |
5331 | 0 | css::awt::Rectangle aViewBox; |
5332 | 0 | if (pProp->Value >>= aViewBox) |
5333 | 0 | { |
5334 | 0 | nViewBoxWidth = aViewBox.Width; |
5335 | 0 | nViewBoxHeight = aViewBox.Height; |
5336 | 0 | css::drawing::EnhancedCustomShapeParameter aECSP; |
5337 | 0 | aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
5338 | 0 | aECSP.Value <<= nViewBoxWidth; |
5339 | 0 | double fRetValue; |
5340 | 0 | aCustomShape2d.GetParameter(fRetValue, aECSP, true, false); |
5341 | 0 | nViewBoxWidth = basegfx::fround(fRetValue); |
5342 | 0 | aECSP.Value <<= nViewBoxHeight; |
5343 | 0 | aCustomShape2d.GetParameter(fRetValue, aECSP, false, true); |
5344 | 0 | nViewBoxHeight = basegfx::fround(fRetValue); |
5345 | 0 | } |
5346 | 0 | } |
5347 | | // Import from oox or documents, which are imported from oox and saved to strict ODF, might |
5348 | | // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those |
5349 | | // cases. Even if that is fixed, we need the substitute for old documents. |
5350 | 0 | if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq)) |
5351 | 0 | { |
5352 | | // Generate a substitute based on point coordinates |
5353 | 0 | sal_Int32 nXMin(0); |
5354 | 0 | aPairs[0].First.Value >>= nXMin; |
5355 | 0 | sal_Int32 nXMax = nXMin; |
5356 | 0 | sal_Int32 nYMin(0); |
5357 | 0 | aPairs[0].Second.Value >>= nYMin; |
5358 | 0 | sal_Int32 nYMax = nYMin; |
5359 | |
|
5360 | 0 | for (const auto& rPair : aPairs) |
5361 | 0 | { |
5362 | 0 | sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d, |
5363 | 0 | bReplaceGeoWidth, false); |
5364 | 0 | sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false, |
5365 | 0 | bReplaceGeoHeight); |
5366 | 0 | if (nX < nXMin) |
5367 | 0 | nXMin = nX; |
5368 | 0 | if (nY < nYMin) |
5369 | 0 | nYMin = nY; |
5370 | 0 | if (nX > nXMax) |
5371 | 0 | nXMax = nX; |
5372 | 0 | if (nY > nYMax) |
5373 | 0 | nYMax = nY; |
5374 | 0 | } |
5375 | 0 | nViewBoxWidth = std::max(nXMax, nXMax - nXMin); |
5376 | 0 | nViewBoxHeight = std::max(nYMax, nYMax - nYMin); |
5377 | 0 | } |
5378 | | // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a |
5379 | | // shift of the resulting path coordinates. |
5380 | 0 | } |
5381 | |
|
5382 | 0 | TextAreaRect aTextAreaRect; |
5383 | 0 | std::vector<Guide> aGuideList; // for now only for <a:rect> |
5384 | 0 | prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect); |
5385 | 0 | prepareGluePoints(aGuideList, aEquationSeq, aGluePoints, nViewBoxWidth, nViewBoxHeight); |
5386 | 0 | mpFS->startElementNS(XML_a, XML_custGeom); |
5387 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
5388 | 0 | if (aGuideList.empty()) |
5389 | 0 | { |
5390 | 0 | mpFS->singleElementNS(XML_a, XML_gdLst); |
5391 | 0 | } |
5392 | 0 | else |
5393 | 0 | { |
5394 | 0 | mpFS->startElementNS(XML_a, XML_gdLst); |
5395 | 0 | for (auto const& elem : aGuideList) |
5396 | 0 | { |
5397 | 0 | assert(IsValidOOXMLFormula(OUString::fromUtf8(elem.sFormula))); |
5398 | 0 | mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula); |
5399 | 0 | } |
5400 | 0 | mpFS->endElementNS(XML_a, XML_gdLst); |
5401 | 0 | } |
5402 | 0 | mpFS->singleElementNS(XML_a, XML_ahLst); |
5403 | |
|
5404 | 0 | if (!aGuideList.empty()) |
5405 | 0 | { |
5406 | 0 | mpFS->startElementNS(XML_a, XML_cxnLst); |
5407 | 0 | for (auto it = aGuideList.begin(); it != aGuideList.end(); ++it) |
5408 | 0 | { |
5409 | 0 | auto aNextIt = std::next(it); |
5410 | 0 | if (aNextIt != aGuideList.end() && it->sName.startsWith("GluePoint") |
5411 | 0 | && aNextIt->sName.startsWith("GluePoint")) |
5412 | 0 | { |
5413 | 0 | mpFS->startElementNS(XML_a, XML_cxn, XML_ang, "0"); |
5414 | 0 | mpFS->singleElementNS(XML_a, XML_pos, XML_x, it->sName, XML_y, aNextIt->sName); |
5415 | 0 | mpFS->endElementNS(XML_a, XML_cxn); |
5416 | |
|
5417 | 0 | ++it; |
5418 | 0 | } |
5419 | 0 | } |
5420 | 0 | mpFS->endElementNS(XML_a, XML_cxnLst); |
5421 | 0 | } |
5422 | |
|
5423 | 0 | mpFS->singleElementNS(XML_a, XML_rect, XML_l, aTextAreaRect.left, XML_t, aTextAreaRect.top, |
5424 | 0 | XML_r, aTextAreaRect.right, XML_b, aTextAreaRect.bottom); |
5425 | 0 | mpFS->startElementNS(XML_a, XML_pathLst); |
5426 | | |
5427 | | // Iterate over subpaths |
5428 | 0 | sal_Int32 nPairIndex = 0; // index over "Coordinates" |
5429 | 0 | sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize" |
5430 | 0 | sal_Int32 nSubpathStartIndex(0); // index over "Segments" |
5431 | 0 | sal_Int32 nSubPathIndex(0); // serial number of current subpath |
5432 | 0 | do |
5433 | 0 | { |
5434 | 0 | bool bOK(true); // catch faulty paths were commands do not correspond to points |
5435 | | // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment |
5436 | 0 | sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments); |
5437 | | |
5438 | | // Prepare attributes for a:path start element |
5439 | | // NOFILL or one of the LIGHTEN commands |
5440 | 0 | std::optional<OString> sFill; |
5441 | 0 | if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) |
5442 | 0 | sFill = "none"; |
5443 | 0 | else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) |
5444 | 0 | sFill = "darken"; |
5445 | 0 | else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, |
5446 | 0 | aSegments)) |
5447 | 0 | sFill = "darkenLess"; |
5448 | 0 | else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1, |
5449 | 0 | aSegments)) |
5450 | 0 | sFill = "lighten"; |
5451 | 0 | else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, |
5452 | 0 | aSegments)) |
5453 | 0 | sFill = "lightenLess"; |
5454 | 0 | else |
5455 | 0 | { |
5456 | | // shading info might be in object type, e.g. "Octagon Bevel". |
5457 | 0 | sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex)); |
5458 | 0 | if (nLuminanceChange <= -40) |
5459 | 0 | sFill = "darken"; |
5460 | 0 | else if (nLuminanceChange <= -10) |
5461 | 0 | sFill = "darkenLess"; |
5462 | 0 | else if (nLuminanceChange >= 40) |
5463 | 0 | sFill = "lighten"; |
5464 | 0 | else if (nLuminanceChange >= 10) |
5465 | 0 | sFill = "lightenLess"; |
5466 | 0 | } |
5467 | | // NOSTROKE |
5468 | 0 | std::optional<OString> sStroke; |
5469 | 0 | if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) |
5470 | 0 | sStroke = "0"; |
5471 | | |
5472 | | // Write a:path start element |
5473 | 0 | mpFS->startElementNS( |
5474 | 0 | XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w, |
5475 | 0 | OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width), |
5476 | 0 | XML_h, |
5477 | 0 | OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height)); |
5478 | | |
5479 | | // Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position |
5480 | | // of the target point in regard to the current point. Therefore we need to track the |
5481 | | // current point. A current point is not defined in the beginning. |
5482 | 0 | double fCurrentX(0.0); |
5483 | 0 | double fCurrentY(0.0); |
5484 | 0 | bool bCurrentValid(false); |
5485 | | // Actually write the subpath |
5486 | 0 | for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex; |
5487 | 0 | ++nSegmentIndex) |
5488 | 0 | { |
5489 | 0 | const auto& rSegment(aSegments[nSegmentIndex]); |
5490 | 0 | if (rSegment.Command == CLOSESUBPATH) |
5491 | 0 | { |
5492 | 0 | mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter |
5493 | | // ODF 1.4 specifies, that the start of the subpath becomes the current point. |
5494 | | // But that is not implemented yet. Currently LO keeps the last current point. |
5495 | 0 | } |
5496 | 0 | for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k) |
5497 | 0 | { |
5498 | 0 | bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX, |
5499 | 0 | fCurrentY, bCurrentValid, aCustomShape2d, |
5500 | 0 | bReplaceGeoWidth, bReplaceGeoHeight); |
5501 | 0 | } |
5502 | 0 | } // end loop over all commands of subpath |
5503 | | // finish this subpath in any case |
5504 | 0 | mpFS->endElementNS(XML_a, XML_path); |
5505 | |
|
5506 | 0 | if (!bOK) |
5507 | 0 | break; // exit loop if not enough values in aPairs |
5508 | | |
5509 | | // step forward to next subpath |
5510 | 0 | nSubpathStartIndex = nNextNcommandIndex + 1; |
5511 | 0 | nPathSizeIndex++; |
5512 | 0 | nSubPathIndex++; |
5513 | 0 | } while (nSubpathStartIndex < aSegments.getLength()); |
5514 | |
|
5515 | 0 | mpFS->endElementNS(XML_a, XML_pathLst); |
5516 | 0 | mpFS->endElementNS(XML_a, XML_custGeom); |
5517 | 0 | return true; // We have written custGeom even if path is poorly structured. |
5518 | 0 | } |
5519 | | |
5520 | | bool DrawingML::WriteCustomGeometrySegment( |
5521 | | const sal_Int16 eCommand, const sal_Int32 nCount, |
5522 | | const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs, |
5523 | | sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid, |
5524 | | const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth, |
5525 | | const bool bReplaceGeoHeight) |
5526 | 0 | { |
5527 | 0 | switch (eCommand) |
5528 | 0 | { |
5529 | 0 | case MOVETO: |
5530 | 0 | { |
5531 | 0 | if (rnPairIndex >= rPairs.getLength()) |
5532 | 0 | return false; |
5533 | | |
5534 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5535 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, |
5536 | 0 | bReplaceGeoHeight); |
5537 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5538 | 0 | rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, |
5539 | 0 | false); |
5540 | 0 | rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, |
5541 | 0 | bReplaceGeoHeight); |
5542 | 0 | rbCurrentValid = true; |
5543 | 0 | rnPairIndex++; |
5544 | 0 | break; |
5545 | 0 | } |
5546 | 0 | case LINETO: |
5547 | 0 | { |
5548 | 0 | if (rnPairIndex >= rPairs.getLength()) |
5549 | 0 | return false; |
5550 | | // LINETO without valid current point is a faulty path. LO is tolerant and makes a |
5551 | | // moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo, |
5552 | | // otherwise it shows nothing of the shape. |
5553 | 0 | if (rbCurrentValid) |
5554 | 0 | { |
5555 | 0 | mpFS->startElementNS(XML_a, XML_lnTo); |
5556 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, |
5557 | 0 | bReplaceGeoHeight); |
5558 | 0 | mpFS->endElementNS(XML_a, XML_lnTo); |
5559 | 0 | } |
5560 | 0 | else |
5561 | 0 | { |
5562 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5563 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, |
5564 | 0 | bReplaceGeoHeight); |
5565 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5566 | 0 | } |
5567 | 0 | rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, |
5568 | 0 | false); |
5569 | 0 | rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, |
5570 | 0 | bReplaceGeoHeight); |
5571 | 0 | rbCurrentValid = true; |
5572 | 0 | rnPairIndex++; |
5573 | 0 | break; |
5574 | 0 | } |
5575 | 0 | case CURVETO: |
5576 | 0 | { |
5577 | 0 | if (rnPairIndex + 2 >= rPairs.getLength()) |
5578 | 0 | return false; |
5579 | | |
5580 | 0 | mpFS->startElementNS(XML_a, XML_cubicBezTo); |
5581 | 0 | for (sal_uInt8 i = 0; i <= 2; ++i) |
5582 | 0 | { |
5583 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth, |
5584 | 0 | bReplaceGeoHeight); |
5585 | 0 | } |
5586 | 0 | mpFS->endElementNS(XML_a, XML_cubicBezTo); |
5587 | 0 | rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth, |
5588 | 0 | false); |
5589 | 0 | rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false, |
5590 | 0 | bReplaceGeoHeight); |
5591 | 0 | rbCurrentValid = true; |
5592 | 0 | rnPairIndex += 3; |
5593 | 0 | break; |
5594 | 0 | } |
5595 | 0 | case ANGLEELLIPSETO: |
5596 | 0 | case ANGLEELLIPSE: |
5597 | 0 | { |
5598 | 0 | if (rnPairIndex + 2 >= rPairs.getLength()) |
5599 | 0 | return false; |
5600 | | |
5601 | | // Read parameters |
5602 | 0 | double fCx = 0.0; |
5603 | 0 | rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); |
5604 | 0 | double fCy = 0.0; |
5605 | 0 | rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); |
5606 | 0 | double fWR = 0.0; |
5607 | 0 | rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false); |
5608 | 0 | double fHR = 0.0; |
5609 | 0 | rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex + 1].Second, false, false); |
5610 | 0 | double fStartAngle = 0.0; |
5611 | 0 | rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 2].First, false, false); |
5612 | 0 | double fEndAngle = 0.0; |
5613 | 0 | rCustomShape2d.GetParameter(fEndAngle, rPairs[rnPairIndex + 2].Second, false, false); |
5614 | | |
5615 | | // Prepare start and swing angle |
5616 | 0 | sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); |
5617 | 0 | sal_Int32 nSwingAng = 0; |
5618 | 0 | if (basegfx::fTools::equalZero(fStartAngle) |
5619 | 0 | && basegfx::fTools::equalZero(fEndAngle - 360.0)) |
5620 | 0 | nSwingAng = 360 * 60000; // special case full circle |
5621 | 0 | else |
5622 | 0 | { |
5623 | 0 | nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000); |
5624 | 0 | if (nSwingAng < 0) |
5625 | 0 | nSwingAng += 360 * 60000; |
5626 | 0 | } |
5627 | | |
5628 | | // calculate start point on ellipse |
5629 | 0 | double fSx = 0.0; |
5630 | 0 | double fSy = 0.0; |
5631 | 0 | getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle); |
5632 | | |
5633 | | // write markup for going to start point |
5634 | | // lnTo requires a valid current point |
5635 | 0 | if (eCommand == ANGLEELLIPSETO && rbCurrentValid) |
5636 | 0 | { |
5637 | 0 | mpFS->startElementNS(XML_a, XML_lnTo); |
5638 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)), |
5639 | 0 | XML_y, OString::number(std::lround(fSy))); |
5640 | 0 | mpFS->endElementNS(XML_a, XML_lnTo); |
5641 | 0 | } |
5642 | 0 | else |
5643 | 0 | { |
5644 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5645 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)), |
5646 | 0 | XML_y, OString::number(std::lround(fSy))); |
5647 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5648 | 0 | } |
5649 | | // write markup for arcTo |
5650 | 0 | if (!basegfx::fTools::equalZero(fWR) && !basegfx::fTools::equalZero(fHR)) |
5651 | 0 | mpFS->singleElement( |
5652 | 0 | FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR, |
5653 | 0 | OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng), |
5654 | 0 | XML_swAng, OString::number(nSwingAng)); |
5655 | |
|
5656 | 0 | getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, fEndAngle); |
5657 | 0 | rbCurrentValid = true; |
5658 | 0 | rnPairIndex += 3; |
5659 | 0 | break; |
5660 | 0 | } |
5661 | 0 | case ARCTO: |
5662 | 0 | case ARC: |
5663 | 0 | case CLOCKWISEARCTO: |
5664 | 0 | case CLOCKWISEARC: |
5665 | 0 | { |
5666 | 0 | if (rnPairIndex + 3 >= rPairs.getLength()) |
5667 | 0 | return false; |
5668 | | |
5669 | | // read parameters |
5670 | 0 | double fX1 = 0.0; |
5671 | 0 | rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); |
5672 | 0 | double fY1 = 0.0; |
5673 | 0 | rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); |
5674 | 0 | double fX2 = 0.0; |
5675 | 0 | rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth, |
5676 | 0 | false); |
5677 | 0 | double fY2 = 0.0; |
5678 | 0 | rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false, |
5679 | 0 | bReplaceGeoHeight); |
5680 | 0 | double fX3 = 0.0; |
5681 | 0 | rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth, |
5682 | 0 | false); |
5683 | 0 | double fY3 = 0.0; |
5684 | 0 | rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false, |
5685 | 0 | bReplaceGeoHeight); |
5686 | 0 | double fX4 = 0.0; |
5687 | 0 | rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth, |
5688 | 0 | false); |
5689 | 0 | double fY4 = 0.0; |
5690 | 0 | rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false, |
5691 | 0 | bReplaceGeoHeight); |
5692 | | // calculate ellipse parameter |
5693 | 0 | const double fWR = (std::max(fX1, fX2) - std::min(fX1, fX2)) / 2.0; |
5694 | 0 | const double fHR = (std::max(fY1, fY2) - std::min(fY1, fY2)) / 2.0; |
5695 | 0 | const double fCx = (fX1 + fX2) / 2.0; |
5696 | 0 | const double fCy = (fY1 + fY2) / 2.0; |
5697 | | // calculate start angle |
5698 | 0 | double fStartAngle = 0.0; |
5699 | 0 | double fPx = 0.0; |
5700 | 0 | double fPy = 0.0; |
5701 | 0 | getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX3, |
5702 | 0 | fY3); |
5703 | | // markup for going to start point |
5704 | | // lnTo requires a valid current point. |
5705 | 0 | if ((eCommand == ARCTO || eCommand == CLOCKWISEARCTO) && rbCurrentValid) |
5706 | 0 | { |
5707 | 0 | mpFS->startElementNS(XML_a, XML_lnTo); |
5708 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)), |
5709 | 0 | XML_y, OString::number(std::lround(fPy))); |
5710 | 0 | mpFS->endElementNS(XML_a, XML_lnTo); |
5711 | 0 | } |
5712 | 0 | else |
5713 | 0 | { |
5714 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5715 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)), |
5716 | 0 | XML_y, OString::number(std::lround(fPy))); |
5717 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5718 | 0 | } |
5719 | | // calculate swing angle |
5720 | 0 | double fEndAngle = 0.0; |
5721 | 0 | getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX4, fY4); |
5722 | 0 | double fSwingAngle(fEndAngle - fStartAngle); |
5723 | 0 | const bool bIsClockwise(eCommand == CLOCKWISEARCTO || eCommand == CLOCKWISEARC); |
5724 | 0 | if (bIsClockwise && fSwingAngle < 0) |
5725 | 0 | fSwingAngle += 360.0; |
5726 | 0 | else if (!bIsClockwise && fSwingAngle > 0) |
5727 | 0 | fSwingAngle -= 360.0; |
5728 | | // markup for arcTo |
5729 | | // ToDo: write markup for case zero width or height of ellipse |
5730 | 0 | const sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); |
5731 | 0 | const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); |
5732 | 0 | mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), |
5733 | 0 | XML_hR, OString::number(std::lround(fHR)), XML_stAng, |
5734 | 0 | OString::number(nStartAng), XML_swAng, OString::number(nSwingAng)); |
5735 | 0 | rfCurrentX = fPx; |
5736 | 0 | rfCurrentY = fPy; |
5737 | 0 | rbCurrentValid = true; |
5738 | 0 | rnPairIndex += 4; |
5739 | 0 | break; |
5740 | 0 | } |
5741 | 0 | case ELLIPTICALQUADRANTX: |
5742 | 0 | case ELLIPTICALQUADRANTY: |
5743 | 0 | { |
5744 | 0 | if (rnPairIndex >= rPairs.getLength()) |
5745 | 0 | return false; |
5746 | | |
5747 | | // read parameters |
5748 | 0 | double fX = 0.0; |
5749 | 0 | rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); |
5750 | 0 | double fY = 0.0; |
5751 | 0 | rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); |
5752 | | |
5753 | | // Prepare parameters for arcTo |
5754 | 0 | if (rbCurrentValid) |
5755 | 0 | { |
5756 | 0 | double fWR = std::abs(rfCurrentX - fX); |
5757 | 0 | double fHR = std::abs(rfCurrentY - fY); |
5758 | 0 | double fStartAngle(0.0); |
5759 | 0 | double fSwingAngle(0.0); |
5760 | | // The starting direction of the arc toggles between X and Y |
5761 | 0 | if ((eCommand == ELLIPTICALQUADRANTX && !(nCount % 2)) |
5762 | 0 | || (eCommand == ELLIPTICALQUADRANTY && (nCount % 2))) |
5763 | 0 | { |
5764 | | // arc starts horizontal |
5765 | 0 | fStartAngle = fY < rfCurrentY ? 90.0 : 270.0; |
5766 | 0 | const bool bClockwise = (fX < rfCurrentX && fY < rfCurrentY) |
5767 | 0 | || (fX > rfCurrentX && fY > rfCurrentY); |
5768 | 0 | fSwingAngle = bClockwise ? 90.0 : -90.0; |
5769 | 0 | } |
5770 | 0 | else |
5771 | 0 | { |
5772 | | // arc starts vertical |
5773 | 0 | fStartAngle = fX < rfCurrentX ? 0.0 : 180.0; |
5774 | 0 | const bool bClockwise = (fX < rfCurrentX && fY > rfCurrentY) |
5775 | 0 | || (fX > rfCurrentX && fY < rfCurrentY); |
5776 | 0 | fSwingAngle = bClockwise ? 90.0 : -90.0; |
5777 | 0 | } |
5778 | 0 | sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); |
5779 | 0 | sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); |
5780 | 0 | mpFS->singleElement( |
5781 | 0 | FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR, |
5782 | 0 | OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng), |
5783 | 0 | XML_swAng, OString::number(nSwingAng)); |
5784 | 0 | } |
5785 | 0 | else |
5786 | 0 | { |
5787 | | // faulty path, but we continue with the target point |
5788 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5789 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, |
5790 | 0 | bReplaceGeoHeight); |
5791 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5792 | 0 | } |
5793 | 0 | rfCurrentX = fX; |
5794 | 0 | rfCurrentY = fY; |
5795 | 0 | rbCurrentValid = true; |
5796 | 0 | rnPairIndex++; |
5797 | 0 | break; |
5798 | 0 | } |
5799 | 0 | case QUADRATICCURVETO: |
5800 | 0 | { |
5801 | 0 | if (rnPairIndex + 1 >= rPairs.getLength()) |
5802 | 0 | return false; |
5803 | | |
5804 | 0 | mpFS->startElementNS(XML_a, XML_quadBezTo); |
5805 | 0 | for (sal_uInt8 i = 0; i < 2; ++i) |
5806 | 0 | { |
5807 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth, |
5808 | 0 | bReplaceGeoHeight); |
5809 | 0 | } |
5810 | 0 | mpFS->endElementNS(XML_a, XML_quadBezTo); |
5811 | 0 | rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth, |
5812 | 0 | false); |
5813 | 0 | rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false, |
5814 | 0 | bReplaceGeoHeight); |
5815 | 0 | rbCurrentValid = true; |
5816 | 0 | rnPairIndex += 2; |
5817 | 0 | break; |
5818 | 0 | } |
5819 | 0 | case ARCANGLETO: |
5820 | 0 | { |
5821 | 0 | if (rnPairIndex + 1 >= rPairs.getLength()) |
5822 | 0 | return false; |
5823 | | |
5824 | 0 | double fWR = 0.0; |
5825 | 0 | rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex].First, false, false); |
5826 | 0 | double fHR = 0.0; |
5827 | 0 | rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex].Second, false, false); |
5828 | 0 | double fStartAngle = 0.0; |
5829 | 0 | rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 1].First, false, false); |
5830 | 0 | sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); |
5831 | 0 | double fSwingAng = 0.0; |
5832 | 0 | rCustomShape2d.GetParameter(fSwingAng, rPairs[rnPairIndex + 1].Second, false, false); |
5833 | 0 | sal_Int32 nSwingAng(std::lround(fSwingAng * 60000)); |
5834 | 0 | mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), |
5835 | 0 | XML_hR, OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng), |
5836 | 0 | XML_swAng, OString::number(nSwingAng)); |
5837 | 0 | double fPx = 0.0; |
5838 | 0 | double fPy = 0.0; |
5839 | 0 | getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle); |
5840 | 0 | double fCx = rfCurrentX - fPx; |
5841 | 0 | double fCy = rfCurrentY - fPy; |
5842 | 0 | getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, |
5843 | 0 | fStartAngle + fSwingAng); |
5844 | 0 | rbCurrentValid = true; |
5845 | 0 | rnPairIndex += 2; |
5846 | 0 | break; |
5847 | 0 | } |
5848 | 0 | default: |
5849 | | // do nothing |
5850 | 0 | break; |
5851 | 0 | } |
5852 | 0 | return true; |
5853 | 0 | } |
5854 | | |
5855 | | void DrawingML::WriteCustomGeometryPoint( |
5856 | | const drawing::EnhancedCustomShapeParameterPair& rParamPair, |
5857 | | const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth, |
5858 | | const bool bReplaceGeoHeight) |
5859 | 0 | { |
5860 | 0 | sal_Int32 nX |
5861 | 0 | = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false); |
5862 | 0 | sal_Int32 nY |
5863 | 0 | = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight); |
5864 | |
|
5865 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY)); |
5866 | 0 | } |
5867 | | |
5868 | | void DrawingML::WriteEmptyCustomGeometry() |
5869 | 0 | { |
5870 | | // This method is used for export to docx in case WriteCustomGeometry fails. |
5871 | 0 | mpFS->startElementNS(XML_a, XML_custGeom); |
5872 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
5873 | 0 | mpFS->singleElementNS(XML_a, XML_gdLst); |
5874 | 0 | mpFS->singleElementNS(XML_a, XML_ahLst); |
5875 | 0 | mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); |
5876 | 0 | mpFS->singleElementNS(XML_a, XML_pathLst); |
5877 | 0 | mpFS->endElementNS(XML_a, XML_custGeom); |
5878 | 0 | } |
5879 | | |
5880 | | bool DrawingML::WriteGraphicClipCustomGeometry(const Reference<XPropertySet>& rXPropSet, |
5881 | | const awt::Size& rSize) |
5882 | 0 | { |
5883 | 0 | if (!rXPropSet.is() |
5884 | 0 | || !rXPropSet->getPropertySetInfo()->hasPropertyByName( |
5885 | 0 | u"GraphicClipPolyPolygon"_ustr)) |
5886 | 0 | return false; |
5887 | | |
5888 | 0 | drawing::PolyPolygonBezierCoords aClip; |
5889 | 0 | if (!(rXPropSet->getPropertyValue(u"GraphicClipPolyPolygon"_ustr) >>= aClip) |
5890 | 0 | || !aClip.Coordinates.hasElements()) |
5891 | 0 | return false; |
5892 | | |
5893 | 0 | mpFS->startElementNS(XML_a, XML_custGeom); |
5894 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
5895 | 0 | mpFS->singleElementNS(XML_a, XML_gdLst); |
5896 | 0 | mpFS->singleElementNS(XML_a, XML_ahLst); |
5897 | 0 | mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); |
5898 | 0 | mpFS->startElementNS(XML_a, XML_pathLst); |
5899 | | |
5900 | | // The polygon is in the shape's own coordinates, so the path spans the shape and a point needs |
5901 | | // no conversion. All the contours share one path, which is what subtracts the areas they |
5902 | | // overlap - that is how a frame with a hole in it is described. |
5903 | 0 | mpFS->startElementNS(XML_a, XML_path, XML_w, OString::number(rSize.Width), XML_h, |
5904 | 0 | OString::number(rSize.Height)); |
5905 | |
|
5906 | 0 | auto writePoint = [this](const awt::Point& rPoint) { |
5907 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(rPoint.X), XML_y, |
5908 | 0 | OString::number(rPoint.Y)); |
5909 | 0 | }; |
5910 | |
|
5911 | 0 | for (sal_Int32 nContour = 0; nContour < aClip.Coordinates.getLength(); ++nContour) |
5912 | 0 | { |
5913 | 0 | const Sequence<awt::Point>& rPoints = aClip.Coordinates[nContour]; |
5914 | | // A contour may carry no flags at all; every point is then an ordinary one. |
5915 | 0 | const Sequence<drawing::PolygonFlags> aNoFlags; |
5916 | 0 | const Sequence<drawing::PolygonFlags>& rFlags |
5917 | 0 | = nContour < aClip.Flags.getLength() ? aClip.Flags[nContour] : aNoFlags; |
5918 | 0 | if (!rPoints.hasElements()) |
5919 | 0 | continue; |
5920 | | |
5921 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5922 | 0 | writePoint(rPoints[0]); |
5923 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5924 | |
|
5925 | 0 | for (sal_Int32 nPoint = 1; nPoint < rPoints.getLength(); ++nPoint) |
5926 | 0 | { |
5927 | 0 | const bool bControl = nPoint < rFlags.getLength() |
5928 | 0 | && rFlags[nPoint] == drawing::PolygonFlags_CONTROL; |
5929 | 0 | if (!bControl) |
5930 | 0 | { |
5931 | 0 | mpFS->startElementNS(XML_a, XML_lnTo); |
5932 | 0 | writePoint(rPoints[nPoint]); |
5933 | 0 | mpFS->endElementNS(XML_a, XML_lnTo); |
5934 | 0 | continue; |
5935 | 0 | } |
5936 | | |
5937 | | // A curve is two control points and the point they lead to. The one that closes a |
5938 | | // contour leads back to the point the contour started from and states no end point of |
5939 | | // its own, so the start point answers for it. |
5940 | 0 | if (nPoint + 1 >= rPoints.getLength()) |
5941 | 0 | break; |
5942 | 0 | const bool bClosing = nPoint + 2 >= rPoints.getLength(); |
5943 | 0 | mpFS->startElementNS(XML_a, XML_cubicBezTo); |
5944 | 0 | writePoint(rPoints[nPoint]); |
5945 | 0 | writePoint(rPoints[nPoint + 1]); |
5946 | 0 | writePoint(bClosing ? rPoints[0] : rPoints[nPoint + 2]); |
5947 | 0 | mpFS->endElementNS(XML_a, XML_cubicBezTo); |
5948 | 0 | nPoint += 2; |
5949 | 0 | } |
5950 | |
|
5951 | 0 | mpFS->singleElementNS(XML_a, XML_close); |
5952 | 0 | } |
5953 | |
|
5954 | 0 | mpFS->endElementNS(XML_a, XML_path); |
5955 | 0 | mpFS->endElementNS(XML_a, XML_pathLst); |
5956 | 0 | mpFS->endElementNS(XML_a, XML_custGeom); |
5957 | 0 | return true; |
5958 | 0 | } |
5959 | | |
5960 | | // version for SdrPathObj |
5961 | | void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, |
5962 | | const bool bClosed) |
5963 | 0 | { |
5964 | 0 | tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(rXShape); |
5965 | | // In case of Writer, the parent element is <wps:spPr>, and there the |
5966 | | // <a:custGeom> element is not optional. |
5967 | 0 | if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX) |
5968 | 0 | return; |
5969 | | |
5970 | 0 | mpFS->startElementNS(XML_a, XML_custGeom); |
5971 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
5972 | 0 | mpFS->singleElementNS(XML_a, XML_gdLst); |
5973 | 0 | mpFS->singleElementNS(XML_a, XML_ahLst); |
5974 | 0 | mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); |
5975 | |
|
5976 | 0 | mpFS->startElementNS(XML_a, XML_pathLst); |
5977 | |
|
5978 | 0 | awt::Size aSize = rXShape->getSize(); |
5979 | 0 | awt::Point aPos = rXShape->getPosition(); |
5980 | 0 | Reference<XPropertySet> xPropertySet(rXShape, UNO_QUERY); |
5981 | 0 | uno::Reference<XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); |
5982 | 0 | if (xPropertySetInfo->hasPropertyByName(u"AnchorPosition"_ustr)) |
5983 | 0 | { |
5984 | 0 | awt::Point aAnchorPosition; |
5985 | 0 | xPropertySet->getPropertyValue(u"AnchorPosition"_ustr) >>= aAnchorPosition; |
5986 | 0 | aPos.X += aAnchorPosition.X; |
5987 | 0 | aPos.Y += aAnchorPosition.Y; |
5988 | 0 | } |
5989 | | |
5990 | | // Only closed SdrPathObj can be filled |
5991 | 0 | std::optional<OString> sFill; |
5992 | 0 | if (!bClosed) |
5993 | 0 | sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard |
5994 | | |
5995 | | // Put all polygons of rPolyPolygon in the same path element |
5996 | | // to subtract the overlapped areas. |
5997 | 0 | mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width), |
5998 | 0 | XML_h, OString::number(aSize.Height)); |
5999 | |
|
6000 | 0 | for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++) |
6001 | 0 | { |
6002 | 0 | const tools::Polygon& aPoly = aPolyPolygon[i]; |
6003 | |
|
6004 | 0 | if (aPoly.GetSize() > 0) |
6005 | 0 | { |
6006 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
6007 | |
|
6008 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[0].X() - aPos.X), |
6009 | 0 | XML_y, OString::number(aPoly[0].Y() - aPos.Y)); |
6010 | |
|
6011 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
6012 | 0 | } |
6013 | |
|
6014 | 0 | for (sal_uInt16 j = 1; j < aPoly.GetSize(); j++) |
6015 | 0 | { |
6016 | 0 | PolyFlags flags = aPoly.GetFlags(j); |
6017 | 0 | if (flags == PolyFlags::Control) |
6018 | 0 | { |
6019 | | // a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this |
6020 | 0 | if (j + 2 < aPoly.GetSize() && aPoly.GetFlags(j + 1) == PolyFlags::Control |
6021 | 0 | && aPoly.GetFlags(j + 2) != PolyFlags::Control) |
6022 | 0 | { |
6023 | 0 | mpFS->startElementNS(XML_a, XML_cubicBezTo); |
6024 | 0 | for (sal_uInt32 k = 0; k <= 2; ++k) |
6025 | 0 | { |
6026 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, |
6027 | 0 | OString::number(aPoly[j + k].X() - aPos.X), XML_y, |
6028 | 0 | OString::number(aPoly[j + k].Y() - aPos.Y)); |
6029 | 0 | } |
6030 | 0 | mpFS->endElementNS(XML_a, XML_cubicBezTo); |
6031 | 0 | j += 2; |
6032 | 0 | } |
6033 | 0 | } |
6034 | 0 | else if (flags == PolyFlags::Normal) |
6035 | 0 | { |
6036 | 0 | mpFS->startElementNS(XML_a, XML_lnTo); |
6037 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[j].X() - aPos.X), |
6038 | 0 | XML_y, OString::number(aPoly[j].Y() - aPos.Y)); |
6039 | 0 | mpFS->endElementNS(XML_a, XML_lnTo); |
6040 | 0 | } |
6041 | 0 | } |
6042 | 0 | } |
6043 | 0 | if (bClosed) |
6044 | 0 | mpFS->singleElementNS(XML_a, XML_close); |
6045 | 0 | mpFS->endElementNS(XML_a, XML_path); |
6046 | |
|
6047 | 0 | mpFS->endElementNS(XML_a, XML_pathLst); |
6048 | |
|
6049 | 0 | mpFS->endElementNS(XML_a, XML_custGeom); |
6050 | 0 | } |
6051 | | |
6052 | | void DrawingML::WriteConnectorConnections( sal_Int32 nStartGlueId, sal_Int32 nEndGlueId, sal_Int32 nStartID, sal_Int32 nEndID ) |
6053 | 0 | { |
6054 | 0 | if( nStartID != -1 ) |
6055 | 0 | { |
6056 | 0 | mpFS->singleElementNS( XML_a, XML_stCxn, |
6057 | 0 | XML_id, OString::number(nStartID), |
6058 | 0 | XML_idx, OString::number(nStartGlueId) ); |
6059 | 0 | } |
6060 | 0 | if( nEndID != -1 ) |
6061 | 0 | { |
6062 | 0 | mpFS->singleElementNS( XML_a, XML_endCxn, |
6063 | 0 | XML_id, OString::number(nEndID), |
6064 | 0 | XML_idx, OString::number(nEndGlueId) ); |
6065 | 0 | } |
6066 | 0 | } |
6067 | | |
6068 | | sal_Unicode DrawingML::SubstituteBullet( sal_Unicode cBulletId, css::awt::FontDescriptor& rFontDesc ) |
6069 | 0 | { |
6070 | 0 | if ( IsOpenSymbol(rFontDesc.Name) ) |
6071 | 0 | { |
6072 | 0 | rtl_TextEncoding eCharSet = rFontDesc.CharSet; |
6073 | 0 | cBulletId = msfilter::util::bestFitOpenSymbolToMSFont(cBulletId, eCharSet, rFontDesc.Name); |
6074 | 0 | rFontDesc.CharSet = static_cast<sal_Int16>(eCharSet); |
6075 | 0 | } |
6076 | |
|
6077 | 0 | return cBulletId; |
6078 | 0 | } |
6079 | | |
6080 | | sax_fastparser::FSHelperPtr DrawingML::CreateOutputStream ( |
6081 | | const OUString& sFullStream, |
6082 | | std::u16string_view sRelativeStream, |
6083 | | const Reference< XOutputStream >& xParentRelation, |
6084 | | const OUString& sContentType, |
6085 | | const OUString& sRelationshipType, |
6086 | | OUString* pRelationshipId, |
6087 | | // if bNoHeader is true, don't create a header (<?xml... ) line |
6088 | | bool bNoHeader /* = false */ ) |
6089 | 0 | { |
6090 | 0 | OUString sRelationshipId; |
6091 | 0 | if (xParentRelation.is()) |
6092 | 0 | sRelationshipId = GetFB()->addRelation( xParentRelation, sRelationshipType, sRelativeStream ); |
6093 | 0 | else |
6094 | 0 | sRelationshipId = GetFB()->addRelation( sRelationshipType, sRelativeStream ); |
6095 | |
|
6096 | 0 | if( pRelationshipId ) |
6097 | 0 | *pRelationshipId = sRelationshipId; |
6098 | |
|
6099 | 0 | sax_fastparser::FSHelperPtr p = GetFB()->openFragmentStreamWithSerializer( |
6100 | 0 | sFullStream, sContentType, bNoHeader ); |
6101 | |
|
6102 | 0 | return p; |
6103 | 0 | } |
6104 | | |
6105 | | void DrawingML::WriteFill(const Reference<XPropertySet>& xPropSet, const awt::Size& rSize) |
6106 | 0 | { |
6107 | 0 | if ( !GetProperty( xPropSet, u"FillStyle"_ustr ) ) |
6108 | 0 | return; |
6109 | 0 | FillStyle aFillStyle( FillStyle_NONE ); |
6110 | 0 | xPropSet->getPropertyValue( u"FillStyle"_ustr ) >>= aFillStyle; |
6111 | | |
6112 | | // map full transparent background to no fill |
6113 | 0 | if (aFillStyle == FillStyle_SOLID) |
6114 | 0 | { |
6115 | 0 | OUString sFillTransparenceGradientName; |
6116 | |
|
6117 | 0 | if (GetProperty(xPropSet, u"FillTransparenceGradientName"_ustr) |
6118 | 0 | && (mAny >>= sFillTransparenceGradientName) |
6119 | 0 | && !sFillTransparenceGradientName.isEmpty() |
6120 | 0 | && GetProperty(xPropSet, u"FillTransparenceGradient"_ustr)) |
6121 | 0 | { |
6122 | | // check if a fully transparent TransparenceGradient is used |
6123 | | // use BGradient constructor & tooling here now |
6124 | 0 | const basegfx::BGradient aTransparenceGradient = model::gradient::getFromAny(mAny); |
6125 | 0 | basegfx::BColor aSingleColor; |
6126 | 0 | const bool bSingleColor(aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor)); |
6127 | 0 | const bool bCompletelyTransparent(bSingleColor && basegfx::fTools::equal(aSingleColor.luminance(), 1.0)); |
6128 | |
|
6129 | 0 | if (bCompletelyTransparent) |
6130 | 0 | { |
6131 | 0 | aFillStyle = FillStyle_NONE; |
6132 | 0 | } |
6133 | 0 | } |
6134 | 0 | else if ( GetProperty( xPropSet, u"FillTransparence"_ustr ) ) |
6135 | 0 | { |
6136 | | // check if a fully transparent FillTransparence is used |
6137 | 0 | sal_Int16 nVal = 0; |
6138 | 0 | xPropSet->getPropertyValue( u"FillTransparence"_ustr ) >>= nVal; |
6139 | 0 | if ( nVal == 100 ) |
6140 | 0 | aFillStyle = FillStyle_NONE; |
6141 | 0 | } |
6142 | 0 | } |
6143 | |
|
6144 | 0 | bool bUseBackground(false); |
6145 | 0 | if (GetProperty(xPropSet, u"FillUseSlideBackground"_ustr)) |
6146 | 0 | xPropSet->getPropertyValue(u"FillUseSlideBackground"_ustr) >>= bUseBackground; |
6147 | |
|
6148 | 0 | switch( aFillStyle ) |
6149 | 0 | { |
6150 | 0 | case FillStyle_SOLID : |
6151 | 0 | WriteSolidFill( xPropSet ); |
6152 | 0 | break; |
6153 | 0 | case FillStyle_GRADIENT : |
6154 | 0 | WriteGradientFill( xPropSet ); |
6155 | 0 | break; |
6156 | 0 | case FillStyle_BITMAP : |
6157 | 0 | WriteBlipFill( xPropSet, u"FillBitmap"_ustr, rSize ); |
6158 | 0 | break; |
6159 | 0 | case FillStyle_HATCH : |
6160 | 0 | WritePattFill( xPropSet ); |
6161 | 0 | break; |
6162 | 0 | case FillStyle_NONE: |
6163 | 0 | if (!bUseBackground) // attribute `useBgFill` will be written at parent p:sp shape |
6164 | 0 | mpFS->singleElementNS(XML_a, XML_noFill); |
6165 | 0 | break; |
6166 | 0 | default: |
6167 | 0 | ; |
6168 | 0 | } |
6169 | 0 | } |
6170 | | |
6171 | | void DrawingML::WriteStyleProperties( sal_Int32 nTokenId, const Sequence< PropertyValue >& aProperties ) |
6172 | 0 | { |
6173 | 0 | if( aProperties.hasElements() ) |
6174 | 0 | { |
6175 | 0 | OUString sSchemeClr; |
6176 | 0 | sal_uInt32 nIdx = 0; |
6177 | 0 | Sequence< PropertyValue > aTransformations; |
6178 | 0 | for( const auto& rProp : aProperties) |
6179 | 0 | { |
6180 | 0 | if( rProp.Name == "SchemeClr" ) |
6181 | 0 | rProp.Value >>= sSchemeClr; |
6182 | 0 | else if( rProp.Name == "Idx" ) |
6183 | 0 | rProp.Value >>= nIdx; |
6184 | 0 | else if( rProp.Name == "Transformations" ) |
6185 | 0 | rProp.Value >>= aTransformations; |
6186 | 0 | } |
6187 | 0 | mpFS->startElementNS(XML_a, nTokenId, XML_idx, OString::number(nIdx)); |
6188 | 0 | WriteColor(sSchemeClr, aTransformations); |
6189 | 0 | mpFS->endElementNS( XML_a, nTokenId ); |
6190 | 0 | } |
6191 | 0 | else |
6192 | 0 | { |
6193 | | // write mock <a:*Ref> tag |
6194 | 0 | mpFS->singleElementNS(XML_a, nTokenId, XML_idx, OString::number(0)); |
6195 | 0 | } |
6196 | 0 | } |
6197 | | |
6198 | | void DrawingML::WriteShapeStyle( const Reference< XPropertySet >& xPropSet ) |
6199 | 0 | { |
6200 | | // check existence of the grab bag |
6201 | 0 | if ( !GetProperty( xPropSet, u"InteropGrabBag"_ustr ) ) |
6202 | 0 | return; |
6203 | | |
6204 | | // extract the relevant properties from the grab bag |
6205 | 0 | Sequence< PropertyValue > aGrabBag; |
6206 | 0 | Sequence< PropertyValue > aFillRefProperties, aLnRefProperties, aEffectRefProperties; |
6207 | 0 | mAny >>= aGrabBag; |
6208 | 0 | for (const auto& rProp : aGrabBag) |
6209 | 0 | { |
6210 | 0 | if( rProp.Name == "StyleFillRef" ) |
6211 | 0 | rProp.Value >>= aFillRefProperties; |
6212 | 0 | else if( rProp.Name == "StyleLnRef" ) |
6213 | 0 | rProp.Value >>= aLnRefProperties; |
6214 | 0 | else if( rProp.Name == "StyleEffectRef" ) |
6215 | 0 | rProp.Value >>= aEffectRefProperties; |
6216 | 0 | } |
6217 | |
|
6218 | 0 | WriteStyleProperties( XML_lnRef, aLnRefProperties ); |
6219 | 0 | WriteStyleProperties( XML_fillRef, aFillRefProperties ); |
6220 | 0 | WriteStyleProperties( XML_effectRef, aEffectRefProperties ); |
6221 | | |
6222 | | // write mock <a:fontRef> |
6223 | 0 | mpFS->singleElementNS(XML_a, XML_fontRef, XML_idx, "minor"); |
6224 | 0 | } |
6225 | | |
6226 | | void DrawingML::WriteShapeEffect( std::u16string_view sName, const Sequence< PropertyValue >& aEffectProps ) |
6227 | 0 | { |
6228 | 0 | if( !aEffectProps.hasElements() ) |
6229 | 0 | return; |
6230 | | |
6231 | | // assign the proper tag and enable bContainsColor if necessary |
6232 | 0 | sal_Int32 nEffectToken = 0; |
6233 | 0 | bool bContainsColor = false; |
6234 | 0 | if( sName == u"outerShdw" ) |
6235 | 0 | { |
6236 | 0 | nEffectToken = FSNS( XML_a, XML_outerShdw ); |
6237 | 0 | bContainsColor = true; |
6238 | 0 | } |
6239 | 0 | else if( sName == u"innerShdw" ) |
6240 | 0 | { |
6241 | 0 | nEffectToken = FSNS( XML_a, XML_innerShdw ); |
6242 | 0 | bContainsColor = true; |
6243 | 0 | } |
6244 | 0 | else if( sName == u"glow" || sName == u"glowtext" ) |
6245 | 0 | { |
6246 | 0 | nEffectToken = FSNS( XML_a, XML_glow ); |
6247 | 0 | bContainsColor = true; |
6248 | 0 | } |
6249 | 0 | else if( sName == u"softEdge" ) |
6250 | 0 | nEffectToken = FSNS( XML_a, XML_softEdge ); |
6251 | 0 | else if( sName == u"reflection" ) |
6252 | 0 | nEffectToken = FSNS( XML_a, XML_reflection ); |
6253 | 0 | else if( sName == u"blur" ) |
6254 | 0 | nEffectToken = FSNS( XML_a, XML_blur ); |
6255 | |
|
6256 | 0 | OUString sSchemeClr; |
6257 | 0 | ::Color nRgbClr; |
6258 | 0 | sal_Int32 nAlpha = MAX_PERCENT; |
6259 | 0 | Sequence< PropertyValue > aTransformations; |
6260 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aOuterShdwAttrList = FastSerializerHelper::createAttrList(); |
6261 | 0 | for( const auto& rEffectProp : aEffectProps ) |
6262 | 0 | { |
6263 | 0 | if( rEffectProp.Name == "Attribs" ) |
6264 | 0 | { |
6265 | | // read tag attributes |
6266 | 0 | uno::Sequence< beans::PropertyValue > aOuterShdwProps; |
6267 | 0 | rEffectProp.Value >>= aOuterShdwProps; |
6268 | 0 | for (const auto& rOuterShdwProp : aOuterShdwProps) |
6269 | 0 | { |
6270 | 0 | if( rOuterShdwProp.Name == "algn" ) |
6271 | 0 | { |
6272 | 0 | OUString sVal; |
6273 | 0 | rOuterShdwProp.Value >>= sVal; |
6274 | 0 | aOuterShdwAttrList->add( XML_algn, sVal ); |
6275 | 0 | } |
6276 | 0 | else if( rOuterShdwProp.Name == "blurRad" ) |
6277 | 0 | { |
6278 | 0 | sal_Int64 nVal = 0; |
6279 | 0 | rOuterShdwProp.Value >>= nVal; |
6280 | 0 | if (nVal != 0) |
6281 | 0 | aOuterShdwAttrList->add( XML_blurRad, OString::number( nVal ) ); |
6282 | 0 | } |
6283 | 0 | else if( rOuterShdwProp.Name == "dir" ) |
6284 | 0 | { |
6285 | 0 | sal_Int32 nVal = 0; |
6286 | 0 | rOuterShdwProp.Value >>= nVal; |
6287 | 0 | aOuterShdwAttrList->add( XML_dir, OString::number( nVal ) ); |
6288 | 0 | } |
6289 | 0 | else if( rOuterShdwProp.Name == "dist" ) |
6290 | 0 | { |
6291 | 0 | sal_Int32 nVal = 0; |
6292 | 0 | rOuterShdwProp.Value >>= nVal; |
6293 | 0 | aOuterShdwAttrList->add( XML_dist, OString::number( nVal ) ); |
6294 | 0 | } |
6295 | 0 | else if( rOuterShdwProp.Name == "kx" ) |
6296 | 0 | { |
6297 | 0 | sal_Int32 nVal = 0; |
6298 | 0 | rOuterShdwProp.Value >>= nVal; |
6299 | 0 | aOuterShdwAttrList->add( XML_kx, OString::number( nVal ) ); |
6300 | 0 | } |
6301 | 0 | else if( rOuterShdwProp.Name == "ky" ) |
6302 | 0 | { |
6303 | 0 | sal_Int32 nVal = 0; |
6304 | 0 | rOuterShdwProp.Value >>= nVal; |
6305 | 0 | aOuterShdwAttrList->add( XML_ky, OString::number( nVal ) ); |
6306 | 0 | } |
6307 | 0 | else if( rOuterShdwProp.Name == "rotWithShape" ) |
6308 | 0 | { |
6309 | 0 | sal_Int32 nVal = 0; |
6310 | 0 | rOuterShdwProp.Value >>= nVal; |
6311 | 0 | aOuterShdwAttrList->add( XML_rotWithShape, OString::number( nVal ) ); |
6312 | 0 | } |
6313 | 0 | else if( rOuterShdwProp.Name == "sx" ) |
6314 | 0 | { |
6315 | 0 | sal_Int32 nVal = 0; |
6316 | 0 | rOuterShdwProp.Value >>= nVal; |
6317 | 0 | aOuterShdwAttrList->add( XML_sx, OString::number( nVal ) ); |
6318 | 0 | } |
6319 | 0 | else if( rOuterShdwProp.Name == "sy" ) |
6320 | 0 | { |
6321 | 0 | sal_Int32 nVal = 0; |
6322 | 0 | rOuterShdwProp.Value >>= nVal; |
6323 | 0 | aOuterShdwAttrList->add( XML_sy, OString::number( nVal ) ); |
6324 | 0 | } |
6325 | 0 | else if( rOuterShdwProp.Name == "rad" ) |
6326 | 0 | { |
6327 | 0 | sal_Int64 nVal = 0; |
6328 | 0 | rOuterShdwProp.Value >>= nVal; |
6329 | 0 | aOuterShdwAttrList->add( XML_rad, OString::number( nVal ) ); |
6330 | 0 | } |
6331 | 0 | else if( rOuterShdwProp.Name == "endA" ) |
6332 | 0 | { |
6333 | 0 | sal_Int32 nVal = 0; |
6334 | 0 | rOuterShdwProp.Value >>= nVal; |
6335 | 0 | aOuterShdwAttrList->add( XML_endA, OString::number( nVal ) ); |
6336 | 0 | } |
6337 | 0 | else if( rOuterShdwProp.Name == "endPos" ) |
6338 | 0 | { |
6339 | 0 | sal_Int32 nVal = 0; |
6340 | 0 | rOuterShdwProp.Value >>= nVal; |
6341 | 0 | aOuterShdwAttrList->add( XML_endPos, OString::number( nVal ) ); |
6342 | 0 | } |
6343 | 0 | else if( rOuterShdwProp.Name == "fadeDir" ) |
6344 | 0 | { |
6345 | 0 | sal_Int32 nVal = 0; |
6346 | 0 | rOuterShdwProp.Value >>= nVal; |
6347 | 0 | aOuterShdwAttrList->add( XML_fadeDir, OString::number( nVal ) ); |
6348 | 0 | } |
6349 | 0 | else if( rOuterShdwProp.Name == "stA" ) |
6350 | 0 | { |
6351 | 0 | sal_Int32 nVal = 0; |
6352 | 0 | rOuterShdwProp.Value >>= nVal; |
6353 | 0 | aOuterShdwAttrList->add( XML_stA, OString::number( nVal ) ); |
6354 | 0 | } |
6355 | 0 | else if( rOuterShdwProp.Name == "stPos" ) |
6356 | 0 | { |
6357 | 0 | sal_Int32 nVal = 0; |
6358 | 0 | rOuterShdwProp.Value >>= nVal; |
6359 | 0 | aOuterShdwAttrList->add( XML_stPos, OString::number( nVal ) ); |
6360 | 0 | } |
6361 | 0 | else if( rOuterShdwProp.Name == "grow" ) |
6362 | 0 | { |
6363 | 0 | sal_Int32 nVal = 0; |
6364 | 0 | rOuterShdwProp.Value >>= nVal; |
6365 | 0 | aOuterShdwAttrList->add( XML_grow, OString::number( nVal ) ); |
6366 | 0 | } |
6367 | 0 | } |
6368 | 0 | } |
6369 | 0 | else if(rEffectProp.Name == "RgbClr") |
6370 | 0 | { |
6371 | 0 | rEffectProp.Value >>= nRgbClr; |
6372 | 0 | } |
6373 | 0 | else if(rEffectProp.Name == "RgbClrTransparency") |
6374 | 0 | { |
6375 | 0 | sal_Int32 nTransparency; |
6376 | 0 | if (rEffectProp.Value >>= nTransparency) |
6377 | | // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) |
6378 | 0 | nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency ); |
6379 | 0 | } |
6380 | 0 | else if(rEffectProp.Name == "SchemeClr") |
6381 | 0 | { |
6382 | 0 | rEffectProp.Value >>= sSchemeClr; |
6383 | 0 | } |
6384 | 0 | else if(rEffectProp.Name == "SchemeClrTransformations") |
6385 | 0 | { |
6386 | 0 | rEffectProp.Value >>= aTransformations; |
6387 | 0 | } |
6388 | 0 | } |
6389 | |
|
6390 | 0 | if( nEffectToken <= 0 ) |
6391 | 0 | return; |
6392 | | |
6393 | 0 | mpFS->startElement( nEffectToken, aOuterShdwAttrList ); |
6394 | |
|
6395 | 0 | if( bContainsColor ) |
6396 | 0 | { |
6397 | 0 | if( sSchemeClr.isEmpty() ) |
6398 | 0 | WriteColor( nRgbClr, nAlpha ); |
6399 | 0 | else |
6400 | 0 | WriteColor( sSchemeClr, aTransformations ); |
6401 | 0 | } |
6402 | |
|
6403 | 0 | mpFS->endElement( nEffectToken ); |
6404 | 0 | } |
6405 | | |
6406 | | static sal_Int32 lcl_CalculateDist(const double dX, const double dY) |
6407 | 0 | { |
6408 | 0 | return static_cast< sal_Int32 >(std::hypot(dX, dY) * 360); |
6409 | 0 | } |
6410 | | |
6411 | | static sal_Int32 lcl_CalculateDir(const double dX, const double dY) |
6412 | 0 | { |
6413 | 0 | return (static_cast< sal_Int32 >(basegfx::rad2deg<60000>(atan2(dY,dX))) + 21600000) % 21600000; |
6414 | 0 | } |
6415 | | |
6416 | | void DrawingML::WriteShapeEffects( const Reference< XPropertySet >& rXPropSet ) |
6417 | 0 | { |
6418 | | // The effects without a shape property are written as they were imported. The others are |
6419 | | // written from their shape properties, and take only what the properties lack from the import. |
6420 | 0 | comphelper::SequenceAsHashMap aStoredEffects; |
6421 | 0 | if (GetProperty(rXPropSet, u"InteropGrabBag"_ustr)) |
6422 | 0 | { |
6423 | 0 | comphelper::SequenceAsHashMap aGrabBag(mAny); |
6424 | 0 | aStoredEffects << aGrabBag.getValue(u"EffectProperties"_ustr); |
6425 | 0 | } |
6426 | 0 | auto getStoredEffect = [&aStoredEffects](const OUString& rName) |
6427 | 0 | { return aStoredEffects.getUnpackedValueOrDefault(rName, Sequence<PropertyValue>()); }; |
6428 | |
|
6429 | 0 | bool bHasShadow = false; |
6430 | 0 | if (GetProperty(rXPropSet, u"Shadow"_ustr)) |
6431 | 0 | mAny >>= bHasShadow; |
6432 | 0 | sal_Int32 nGlowRadius = 0; |
6433 | 0 | if (GetProperty(rXPropSet, u"GlowEffectRadius"_ustr)) |
6434 | 0 | mAny >>= nGlowRadius; |
6435 | 0 | sal_Int32 nSoftEdgeRadius = 0; |
6436 | 0 | if (GetProperty(rXPropSet, u"SoftEdgeRadius"_ustr)) |
6437 | 0 | mAny >>= nSoftEdgeRadius; |
6438 | 0 | bool bHasReflection = false; |
6439 | 0 | if (GetProperty(rXPropSet, u"ReflectionEffect"_ustr)) |
6440 | 0 | mAny >>= bHasReflection; |
6441 | | |
6442 | | // An imported effect list is written even when all its effects are off, because an effect |
6443 | | // list replaces the effects that the style of the shape refers to. |
6444 | 0 | if (!bHasShadow && !nGlowRadius && !nSoftEdgeRadius && !bHasReflection |
6445 | 0 | && aStoredEffects.empty()) |
6446 | 0 | return; |
6447 | | |
6448 | | // tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376): |
6449 | | // blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge |
6450 | | // The import keeps no fillOverlay and no prstShdw. |
6451 | 0 | mpFS->startElementNS(XML_a, XML_effectLst); |
6452 | 0 | WriteShapeEffect(u"blur", getStoredEffect(u"blur"_ustr)); |
6453 | 0 | WriteGlowEffect(rXPropSet); |
6454 | 0 | WriteShapeEffect(u"innerShdw", getStoredEffect(u"innerShdw"_ustr)); |
6455 | 0 | if (bHasShadow) |
6456 | 0 | WriteShadowEffect(rXPropSet, getStoredEffect(u"outerShdw"_ustr)); |
6457 | 0 | WriteReflectionEffect(rXPropSet, getStoredEffect(u"reflection"_ustr)); |
6458 | 0 | WriteSoftEdgeEffect(rXPropSet); |
6459 | 0 | mpFS->endElementNS(XML_a, XML_effectLst); |
6460 | 0 | } |
6461 | | |
6462 | | void DrawingML::WriteShadowEffect(const Reference<XPropertySet>& rXPropSet, |
6463 | | const Sequence<PropertyValue>& rStoredEffect) |
6464 | 0 | { |
6465 | 0 | double dX = +0.0, dY = +0.0; |
6466 | 0 | sal_Int32 nBlur = 0; |
6467 | 0 | sal_Int32 nSizeX = MAX_PERCENT; |
6468 | 0 | sal_Int32 nSizeY = MAX_PERCENT; |
6469 | 0 | sal_Int32 nAlignment = static_cast<sal_Int32>(model::RectangleAlignment::Unset); |
6470 | 0 | rXPropSet->getPropertyValue(u"ShadowXDistance"_ustr) >>= dX; |
6471 | 0 | rXPropSet->getPropertyValue(u"ShadowYDistance"_ustr) >>= dY; |
6472 | 0 | rXPropSet->getPropertyValue(u"ShadowBlur"_ustr) >>= nBlur; |
6473 | 0 | if (GetProperty(rXPropSet, u"ShadowSizeX"_ustr)) |
6474 | 0 | mAny >>= nSizeX; |
6475 | 0 | if (GetProperty(rXPropSet, u"ShadowSizeY"_ustr)) |
6476 | 0 | mAny >>= nSizeY; |
6477 | 0 | if (GetProperty(rXPropSet, u"ShadowAlignment"_ustr)) |
6478 | 0 | mAny >>= nAlignment; |
6479 | | |
6480 | | // The stored attributes keep what has no shape property, like the skew. A shadow without |
6481 | | // them has rotWithShape="0", because the shadow distances of the shape are an offset on the |
6482 | | // page and the schema default is "1". |
6483 | 0 | comphelper::SequenceAsHashMap aStoredEffect(rStoredEffect); |
6484 | 0 | comphelper::SequenceAsHashMap aAttribs(aStoredEffect.getValue(u"Attribs"_ustr)); |
6485 | 0 | if (aAttribs.empty()) |
6486 | 0 | aAttribs[u"rotWithShape"_ustr] <<= sal_Int32(0); |
6487 | |
|
6488 | 0 | aAttribs[u"dist"_ustr] <<= lcl_CalculateDist(dX, dY); |
6489 | 0 | aAttribs[u"dir"_ustr] <<= lcl_CalculateDir(dX, dY); |
6490 | 0 | aAttribs[u"blurRad"_ustr] <<= oox::drawingml::convertHmmToEmu(nBlur); |
6491 | | |
6492 | | // A value that equals the schema default is written only when the imported shadow had it. |
6493 | 0 | auto setScale = [&aAttribs](const OUString& rName, sal_Int32 nScale) |
6494 | 0 | { |
6495 | 0 | if (nScale != MAX_PERCENT || aAttribs.contains(rName)) |
6496 | 0 | aAttribs[rName] <<= nScale; |
6497 | 0 | }; |
6498 | 0 | setScale(u"sx"_ustr, nSizeX); |
6499 | 0 | setScale(u"sy"_ustr, nSizeY); |
6500 | 0 | const OString aAlignment( |
6501 | 0 | convertRectangleAlignment(static_cast<model::RectangleAlignment>(nAlignment))); |
6502 | 0 | if (!aAlignment.isEmpty() && (aAlignment != "b" || aAttribs.contains(u"algn"_ustr))) |
6503 | 0 | aAttribs[u"algn"_ustr] <<= OUString::fromUtf8(aAlignment); |
6504 | 0 | aStoredEffect[u"Attribs"_ustr] <<= aAttribs.getAsConstPropertyValueList(); |
6505 | | |
6506 | | // A theme color is written as it was imported, because the shadow color property holds only |
6507 | | // the resolved color. |
6508 | 0 | if (!aStoredEffect.contains(u"SchemeClr"_ustr)) |
6509 | 0 | { |
6510 | 0 | aStoredEffect[u"RgbClr"_ustr] = rXPropSet->getPropertyValue(u"ShadowColor"_ustr); |
6511 | 0 | aStoredEffect[u"RgbClrTransparency"_ustr] |
6512 | 0 | = rXPropSet->getPropertyValue(u"ShadowTransparence"_ustr); |
6513 | 0 | } |
6514 | |
|
6515 | 0 | WriteShapeEffect(u"outerShdw", aStoredEffect.getAsConstPropertyValueList()); |
6516 | 0 | } |
6517 | | |
6518 | | void DrawingML::WriteGlowEffect(const Reference< XPropertySet >& rXPropSet) |
6519 | 0 | { |
6520 | 0 | if (!rXPropSet->getPropertySetInfo()->hasPropertyByName(u"GlowEffectRadius"_ustr)) |
6521 | 0 | { |
6522 | 0 | return; |
6523 | 0 | } |
6524 | | |
6525 | 0 | sal_Int32 nRad = 0; |
6526 | 0 | rXPropSet->getPropertyValue(u"GlowEffectRadius"_ustr) >>= nRad; |
6527 | 0 | if (!nRad) |
6528 | 0 | return; |
6529 | | |
6530 | 0 | Sequence< PropertyValue > aGlowAttribs{ comphelper::makePropertyValue( |
6531 | 0 | u"rad"_ustr, oox::drawingml::convertHmmToEmu(nRad)) }; |
6532 | 0 | Sequence< PropertyValue > aGlowProps{ |
6533 | 0 | comphelper::makePropertyValue(u"Attribs"_ustr, aGlowAttribs), |
6534 | 0 | comphelper::makePropertyValue(u"RgbClr"_ustr, rXPropSet->getPropertyValue(u"GlowEffectColor"_ustr)), |
6535 | 0 | comphelper::makePropertyValue(u"RgbClrTransparency"_ustr, rXPropSet->getPropertyValue(u"GlowEffectTransparency"_ustr)) |
6536 | 0 | }; |
6537 | | // TODO other stuff like saturation or luminance |
6538 | |
|
6539 | 0 | WriteShapeEffect(u"glow", aGlowProps); |
6540 | 0 | } |
6541 | | |
6542 | | void DrawingML::WriteTextGlowEffect(const Reference< XPropertySet >& rXPropSet) |
6543 | 0 | { |
6544 | 0 | if (!rXPropSet->getPropertySetInfo()->hasPropertyByName(u"GlowTextEffectRadius"_ustr)) |
6545 | 0 | { |
6546 | 0 | return; |
6547 | 0 | } |
6548 | | |
6549 | 0 | sal_Int32 nRad = 0; |
6550 | 0 | rXPropSet->getPropertyValue(u"GlowTextEffectRadius"_ustr) >>= nRad; |
6551 | 0 | if (!nRad) |
6552 | 0 | return; |
6553 | | |
6554 | 0 | Sequence< PropertyValue > aGlowAttribs{ comphelper::makePropertyValue( |
6555 | 0 | u"rad"_ustr, oox::drawingml::convertHmmToEmu(nRad)) }; |
6556 | 0 | Sequence< PropertyValue > aGlowProps{ |
6557 | 0 | comphelper::makePropertyValue(u"Attribs"_ustr, aGlowAttribs), |
6558 | 0 | comphelper::makePropertyValue(u"RgbClr"_ustr, rXPropSet->getPropertyValue(u"GlowTextEffectColor"_ustr)), |
6559 | 0 | comphelper::makePropertyValue(u"RgbClrTransparency"_ustr, rXPropSet->getPropertyValue(u"GlowTextEffectTransparency"_ustr)) |
6560 | 0 | }; |
6561 | | // TODO other stuff like saturation or luminance |
6562 | |
|
6563 | 0 | WriteShapeEffect(u"glowtext", aGlowProps); |
6564 | 0 | } |
6565 | | |
6566 | | void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet) |
6567 | 0 | { |
6568 | 0 | if (!rXPropSet->getPropertySetInfo()->hasPropertyByName(u"SoftEdgeRadius"_ustr)) |
6569 | 0 | { |
6570 | 0 | return; |
6571 | 0 | } |
6572 | | |
6573 | 0 | sal_Int32 nRad = 0; |
6574 | 0 | rXPropSet->getPropertyValue(u"SoftEdgeRadius"_ustr) >>= nRad; |
6575 | 0 | if (!nRad) |
6576 | 0 | return; |
6577 | | |
6578 | 0 | css::uno::Sequence<css::beans::PropertyValue> aAttribs{ comphelper::makePropertyValue( |
6579 | 0 | u"rad"_ustr, oox::drawingml::convertHmmToEmu(nRad)) }; |
6580 | 0 | css::uno::Sequence<css::beans::PropertyValue> aProps{ comphelper::makePropertyValue(u"Attribs"_ustr, |
6581 | 0 | aAttribs) }; |
6582 | |
|
6583 | 0 | WriteShapeEffect(u"softEdge", aProps); |
6584 | 0 | } |
6585 | | |
6586 | | namespace |
6587 | | { |
6588 | | // DrawingML has 1/1000 of a percent, the shape property has whole percent. |
6589 | | sal_Int32 toWholePercent(sal_Int32 nThousandthsOfPercent) |
6590 | 0 | { |
6591 | 0 | return static_cast<sal_Int32>(std::lround(nThousandthsOfPercent / 1000.0)); |
6592 | 0 | } |
6593 | | } |
6594 | | |
6595 | | void DrawingML::WriteReflectionEffect(const Reference<XPropertySet>& rXPropSet, |
6596 | | const Sequence<PropertyValue>& rStoredEffect) |
6597 | 0 | { |
6598 | 0 | if (!rXPropSet->getPropertySetInfo()->hasPropertyByName(u"ReflectionEffect"_ustr)) |
6599 | 0 | return; |
6600 | | |
6601 | 0 | bool bReflection = false; |
6602 | 0 | rXPropSet->getPropertyValue(u"ReflectionEffect"_ustr) >>= bReflection; |
6603 | 0 | if (!bReflection) |
6604 | 0 | return; |
6605 | | |
6606 | 0 | sal_Int32 nDistance = 0; |
6607 | 0 | sal_Int32 nBlurRadius = 0; |
6608 | 0 | sal_Int16 nStartTransparency = 0; |
6609 | 0 | sal_Int16 nStartPosition = 0; |
6610 | 0 | sal_Int16 nEndTransparency = 100; |
6611 | 0 | sal_Int16 nEndPosition = 100; |
6612 | 0 | rXPropSet->getPropertyValue(u"ReflectionEffectDistance"_ustr) >>= nDistance; |
6613 | 0 | rXPropSet->getPropertyValue(u"ReflectionEffectBlurRadius"_ustr) >>= nBlurRadius; |
6614 | 0 | rXPropSet->getPropertyValue(u"ReflectionEffectStartTransparency"_ustr) >>= nStartTransparency; |
6615 | 0 | rXPropSet->getPropertyValue(u"ReflectionEffectStartPosition"_ustr) >>= nStartPosition; |
6616 | 0 | rXPropSet->getPropertyValue(u"ReflectionEffectEndTransparency"_ustr) >>= nEndTransparency; |
6617 | 0 | rXPropSet->getPropertyValue(u"ReflectionEffectEndPosition"_ustr) >>= nEndPosition; |
6618 | | |
6619 | | // The stored attributes keep what has no shape property, like the direction and the scale. A |
6620 | | // reflection without them is mirrored straight down below the shape, like the presets. |
6621 | 0 | comphelper::SequenceAsHashMap aStoredEffect(rStoredEffect); |
6622 | 0 | comphelper::SequenceAsHashMap aAttribs(aStoredEffect.getValue(u"Attribs"_ustr)); |
6623 | 0 | if (aAttribs.empty()) |
6624 | 0 | { |
6625 | 0 | aAttribs[u"dir"_ustr] <<= sal_Int32(5400000); |
6626 | 0 | aAttribs[u"sy"_ustr] <<= sal_Int32(-100000); |
6627 | 0 | aAttribs[u"algn"_ustr] <<= u"bl"_ustr; |
6628 | 0 | aAttribs[u"rotWithShape"_ustr] <<= sal_Int32(0); |
6629 | 0 | } |
6630 | | |
6631 | | // A stored value stays when it still gives the value of the shape property, since the stored |
6632 | | // one can be more precise. A value that equals the schema default is left out. |
6633 | 0 | auto setLength = [&aAttribs](const OUString& rName, gfx::Length aLength) |
6634 | 0 | { |
6635 | 0 | sal_Int64 nStored = 0; |
6636 | 0 | if ((aAttribs.getValue(rName) >>= nStored) |
6637 | 0 | && gfx::Length::emu(nStored).as_hmm<sal_Int32>() == aLength.as_hmm<sal_Int32>()) |
6638 | 0 | return; |
6639 | 0 | if (aLength == gfx::Length()) |
6640 | 0 | aAttribs.erase(rName); |
6641 | 0 | else |
6642 | 0 | aAttribs[rName] <<= aLength.as_emu<sal_Int32>(); |
6643 | 0 | }; |
6644 | 0 | auto setPercent = [&aAttribs](const OUString& rName, sal_Int32 nPercent, sal_Int32 nDefault) |
6645 | 0 | { |
6646 | 0 | sal_Int32 nStored = 0; |
6647 | 0 | if ((aAttribs.getValue(rName) >>= nStored) && toWholePercent(nStored) == nPercent) |
6648 | 0 | return; |
6649 | 0 | if (nPercent * PER_PERCENT == nDefault) |
6650 | 0 | aAttribs.erase(rName); |
6651 | 0 | else |
6652 | 0 | aAttribs[rName] <<= nPercent * PER_PERCENT; |
6653 | 0 | }; |
6654 | 0 | setLength(u"dist"_ustr, gfx::Length::hmm(nDistance)); |
6655 | 0 | setLength(u"blurRad"_ustr, gfx::Length::hmm(nBlurRadius)); |
6656 | 0 | setPercent(u"stA"_ustr, 100 - nStartTransparency, MAX_PERCENT); |
6657 | 0 | setPercent(u"stPos"_ustr, nStartPosition, 0); |
6658 | 0 | setPercent(u"endA"_ustr, 100 - nEndTransparency, 0); |
6659 | 0 | setPercent(u"endPos"_ustr, nEndPosition, MAX_PERCENT); |
6660 | |
|
6661 | 0 | Sequence<PropertyValue> aProps{ comphelper::makePropertyValue( |
6662 | 0 | u"Attribs"_ustr, aAttribs.getAsConstPropertyValueList()) }; |
6663 | 0 | WriteShapeEffect(u"reflection", aProps); |
6664 | 0 | } |
6665 | | |
6666 | | void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText ) |
6667 | 0 | { |
6668 | | // check existence of the grab bag |
6669 | 0 | if( !GetProperty( xPropSet, u"InteropGrabBag"_ustr ) ) |
6670 | 0 | return; |
6671 | | |
6672 | | // extract the relevant properties from the grab bag |
6673 | 0 | Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps; |
6674 | 0 | mAny >>= aGrabBag; |
6675 | |
|
6676 | 0 | auto pShapeProp = std::find_if( std::cbegin(aGrabBag), std::cend(aGrabBag), |
6677 | 0 | [bIsText](const PropertyValue& rProp) |
6678 | 0 | { return rProp.Name == (bIsText ? u"Text3DEffectProperties" : u"3DEffectProperties"); }); |
6679 | 0 | if (pShapeProp != std::cend(aGrabBag)) |
6680 | 0 | { |
6681 | 0 | Sequence< PropertyValue > a3DEffectProps; |
6682 | 0 | pShapeProp->Value >>= a3DEffectProps; |
6683 | 0 | for (const auto& r3DEffectProp : a3DEffectProps) |
6684 | 0 | { |
6685 | 0 | if( r3DEffectProp.Name == "Camera" ) |
6686 | 0 | r3DEffectProp.Value >>= aEffectProps; |
6687 | 0 | else if( r3DEffectProp.Name == "LightRig" ) |
6688 | 0 | r3DEffectProp.Value >>= aLightRigProps; |
6689 | 0 | else if( r3DEffectProp.Name == "Shape3D" ) |
6690 | 0 | r3DEffectProp.Value >>= aShape3DProps; |
6691 | 0 | } |
6692 | 0 | } |
6693 | |
|
6694 | 0 | if( !aEffectProps.hasElements() && !aLightRigProps.hasElements() && !aShape3DProps.hasElements() ) |
6695 | 0 | return; |
6696 | | |
6697 | 0 | bool bCameraRotationPresent = false; |
6698 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aCameraAttrList = FastSerializerHelper::createAttrList(); |
6699 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aCameraRotationAttrList = FastSerializerHelper::createAttrList(); |
6700 | 0 | for (const auto& rEffectProp : aEffectProps) |
6701 | 0 | { |
6702 | 0 | if( rEffectProp.Name == "prst" ) |
6703 | 0 | { |
6704 | 0 | OUString sVal; |
6705 | 0 | rEffectProp.Value >>= sVal; |
6706 | 0 | aCameraAttrList->add(XML_prst, sVal); |
6707 | 0 | } |
6708 | 0 | else if( rEffectProp.Name == "fov" ) |
6709 | 0 | { |
6710 | 0 | float fVal = 0; |
6711 | 0 | rEffectProp.Value >>= fVal; |
6712 | 0 | aCameraAttrList->add( XML_fov, OString::number( fVal * 60000 ) ); |
6713 | 0 | } |
6714 | 0 | else if( rEffectProp.Name == "zoom" ) |
6715 | 0 | { |
6716 | 0 | float fVal = 1; |
6717 | 0 | rEffectProp.Value >>= fVal; |
6718 | 0 | aCameraAttrList->add( XML_zoom, OString::number( fVal * 100000 ) ); |
6719 | 0 | } |
6720 | 0 | else if( rEffectProp.Name == "rotLat" || |
6721 | 0 | rEffectProp.Name == "rotLon" || |
6722 | 0 | rEffectProp.Name == "rotRev" ) |
6723 | 0 | { |
6724 | 0 | sal_Int32 nVal = 0, nToken = XML_none; |
6725 | 0 | rEffectProp.Value >>= nVal; |
6726 | 0 | if( rEffectProp.Name == "rotLat" ) |
6727 | 0 | nToken = XML_lat; |
6728 | 0 | else if( rEffectProp.Name == "rotLon" ) |
6729 | 0 | nToken = XML_lon; |
6730 | 0 | else if( rEffectProp.Name == "rotRev" ) |
6731 | 0 | nToken = XML_rev; |
6732 | 0 | aCameraRotationAttrList->add( nToken, OString::number( nVal ) ); |
6733 | 0 | bCameraRotationPresent = true; |
6734 | 0 | } |
6735 | 0 | } |
6736 | |
|
6737 | 0 | bool bLightRigRotationPresent = false; |
6738 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aLightRigAttrList = FastSerializerHelper::createAttrList(); |
6739 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aLightRigRotationAttrList = FastSerializerHelper::createAttrList(); |
6740 | 0 | for (const auto& rLightRigProp : aLightRigProps) |
6741 | 0 | { |
6742 | 0 | if( rLightRigProp.Name == "rig" || rLightRigProp.Name == "dir" ) |
6743 | 0 | { |
6744 | 0 | OUString sVal; |
6745 | 0 | sal_Int32 nToken = XML_none; |
6746 | 0 | rLightRigProp.Value >>= sVal; |
6747 | 0 | if( rLightRigProp.Name == "rig" ) |
6748 | 0 | nToken = XML_rig; |
6749 | 0 | else if( rLightRigProp.Name == "dir" ) |
6750 | 0 | nToken = XML_dir; |
6751 | 0 | aLightRigAttrList->add(nToken, sVal); |
6752 | 0 | } |
6753 | 0 | else if( rLightRigProp.Name == "rotLat" || |
6754 | 0 | rLightRigProp.Name == "rotLon" || |
6755 | 0 | rLightRigProp.Name == "rotRev" ) |
6756 | 0 | { |
6757 | 0 | sal_Int32 nVal = 0, nToken = XML_none; |
6758 | 0 | rLightRigProp.Value >>= nVal; |
6759 | 0 | if( rLightRigProp.Name == "rotLat" ) |
6760 | 0 | nToken = XML_lat; |
6761 | 0 | else if( rLightRigProp.Name == "rotLon" ) |
6762 | 0 | nToken = XML_lon; |
6763 | 0 | else if( rLightRigProp.Name == "rotRev" ) |
6764 | 0 | nToken = XML_rev; |
6765 | 0 | aLightRigRotationAttrList->add( nToken, OString::number( nVal ) ); |
6766 | 0 | bLightRigRotationPresent = true; |
6767 | 0 | } |
6768 | 0 | } |
6769 | |
|
6770 | 0 | mpFS->startElementNS(XML_a, XML_scene3d); |
6771 | |
|
6772 | 0 | if( aEffectProps.hasElements() ) |
6773 | 0 | { |
6774 | 0 | mpFS->startElementNS( XML_a, XML_camera, aCameraAttrList ); |
6775 | 0 | if( bCameraRotationPresent ) |
6776 | 0 | { |
6777 | 0 | mpFS->singleElementNS( XML_a, XML_rot, aCameraRotationAttrList ); |
6778 | 0 | } |
6779 | 0 | mpFS->endElementNS( XML_a, XML_camera ); |
6780 | 0 | } |
6781 | 0 | else |
6782 | 0 | { |
6783 | | // a:camera with Word default values - Word won't open the document if this is not present |
6784 | 0 | mpFS->singleElementNS(XML_a, XML_camera, XML_prst, "orthographicFront"); |
6785 | 0 | } |
6786 | |
|
6787 | 0 | if( aEffectProps.hasElements() ) |
6788 | 0 | { |
6789 | 0 | mpFS->startElementNS( XML_a, XML_lightRig, aLightRigAttrList ); |
6790 | 0 | if( bLightRigRotationPresent ) |
6791 | 0 | { |
6792 | 0 | mpFS->singleElementNS( XML_a, XML_rot, aLightRigRotationAttrList ); |
6793 | 0 | } |
6794 | 0 | mpFS->endElementNS( XML_a, XML_lightRig ); |
6795 | 0 | } |
6796 | 0 | else |
6797 | 0 | { |
6798 | | // a:lightRig with Word default values - Word won't open the document if this is not present |
6799 | 0 | mpFS->singleElementNS(XML_a, XML_lightRig, XML_rig, "threePt", XML_dir, "t"); |
6800 | 0 | } |
6801 | |
|
6802 | 0 | mpFS->endElementNS( XML_a, XML_scene3d ); |
6803 | |
|
6804 | 0 | if( !aShape3DProps.hasElements() ) |
6805 | 0 | return; |
6806 | | |
6807 | 0 | bool bBevelTPresent = false, bBevelBPresent = false; |
6808 | 0 | Sequence< PropertyValue > aExtrusionColorProps, aContourColorProps; |
6809 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aBevelTAttrList = FastSerializerHelper::createAttrList(); |
6810 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aBevelBAttrList = FastSerializerHelper::createAttrList(); |
6811 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aShape3DAttrList = FastSerializerHelper::createAttrList(); |
6812 | 0 | for (const auto& rShape3DProp : aShape3DProps) |
6813 | 0 | { |
6814 | 0 | if( rShape3DProp.Name == "extrusionH" || rShape3DProp.Name == "contourW" || rShape3DProp.Name == "z" ) |
6815 | 0 | { |
6816 | 0 | sal_Int32 nVal = 0, nToken = XML_none; |
6817 | 0 | rShape3DProp.Value >>= nVal; |
6818 | 0 | if( rShape3DProp.Name == "extrusionH" ) |
6819 | 0 | nToken = XML_extrusionH; |
6820 | 0 | else if( rShape3DProp.Name == "contourW" ) |
6821 | 0 | nToken = XML_contourW; |
6822 | 0 | else if( rShape3DProp.Name == "z" ) |
6823 | 0 | nToken = XML_z; |
6824 | 0 | aShape3DAttrList->add( nToken, OString::number( nVal ) ); |
6825 | 0 | } |
6826 | 0 | else if( rShape3DProp.Name == "prstMaterial" ) |
6827 | 0 | { |
6828 | 0 | OUString sVal; |
6829 | 0 | rShape3DProp.Value >>= sVal; |
6830 | 0 | aShape3DAttrList->add(XML_prstMaterial, sVal); |
6831 | 0 | } |
6832 | 0 | else if( rShape3DProp.Name == "extrusionClr" ) |
6833 | 0 | { |
6834 | 0 | rShape3DProp.Value >>= aExtrusionColorProps; |
6835 | 0 | } |
6836 | 0 | else if( rShape3DProp.Name == "contourClr" ) |
6837 | 0 | { |
6838 | 0 | rShape3DProp.Value >>= aContourColorProps; |
6839 | 0 | } |
6840 | 0 | else if( rShape3DProp.Name == "bevelT" || rShape3DProp.Name == "bevelB" ) |
6841 | 0 | { |
6842 | 0 | Sequence< PropertyValue > aBevelProps; |
6843 | 0 | rShape3DProp.Value >>= aBevelProps; |
6844 | 0 | if ( !aBevelProps.hasElements() ) |
6845 | 0 | continue; |
6846 | | |
6847 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aBevelAttrList; |
6848 | 0 | if( rShape3DProp.Name == "bevelT" ) |
6849 | 0 | { |
6850 | 0 | bBevelTPresent = true; |
6851 | 0 | aBevelAttrList = aBevelTAttrList; |
6852 | 0 | } |
6853 | 0 | else |
6854 | 0 | { |
6855 | 0 | bBevelBPresent = true; |
6856 | 0 | aBevelAttrList = aBevelBAttrList; |
6857 | 0 | } |
6858 | 0 | for (const auto& rBevelProp : aBevelProps) |
6859 | 0 | { |
6860 | 0 | if( rBevelProp.Name == "w" || rBevelProp.Name == "h" ) |
6861 | 0 | { |
6862 | 0 | sal_Int32 nVal = 0, nToken = XML_none; |
6863 | 0 | rBevelProp.Value >>= nVal; |
6864 | 0 | if( rBevelProp.Name == "w" ) |
6865 | 0 | nToken = XML_w; |
6866 | 0 | else if( rBevelProp.Name == "h" ) |
6867 | 0 | nToken = XML_h; |
6868 | 0 | aBevelAttrList->add( nToken, OString::number( nVal ) ); |
6869 | 0 | } |
6870 | 0 | else if( rBevelProp.Name == "prst" ) |
6871 | 0 | { |
6872 | 0 | OUString sVal; |
6873 | 0 | rBevelProp.Value >>= sVal; |
6874 | 0 | aBevelAttrList->add(XML_prst, sVal); |
6875 | 0 | } |
6876 | 0 | } |
6877 | |
|
6878 | 0 | } |
6879 | 0 | } |
6880 | |
|
6881 | 0 | mpFS->startElementNS( XML_a, XML_sp3d, aShape3DAttrList ); |
6882 | 0 | if( bBevelTPresent ) |
6883 | 0 | { |
6884 | 0 | mpFS->singleElementNS( XML_a, XML_bevelT, aBevelTAttrList ); |
6885 | 0 | } |
6886 | 0 | if( bBevelBPresent ) |
6887 | 0 | { |
6888 | 0 | mpFS->singleElementNS( XML_a, XML_bevelB, aBevelBAttrList ); |
6889 | 0 | } |
6890 | 0 | if( aExtrusionColorProps.hasElements() ) |
6891 | 0 | { |
6892 | 0 | OUString sSchemeClr; |
6893 | 0 | ::Color nColor; |
6894 | 0 | sal_Int32 nTransparency(0); |
6895 | 0 | Sequence< PropertyValue > aColorTransformations; |
6896 | 0 | for (const auto& rExtrusionColorProp : aExtrusionColorProps) |
6897 | 0 | { |
6898 | 0 | if( rExtrusionColorProp.Name == "schemeClr" ) |
6899 | 0 | rExtrusionColorProp.Value >>= sSchemeClr; |
6900 | 0 | else if( rExtrusionColorProp.Name == "schemeClrTransformations" ) |
6901 | 0 | rExtrusionColorProp.Value >>= aColorTransformations; |
6902 | 0 | else if( rExtrusionColorProp.Name == "rgbClr" ) |
6903 | 0 | rExtrusionColorProp.Value >>= nColor; |
6904 | 0 | else if( rExtrusionColorProp.Name == "rgbClrTransparency" ) |
6905 | 0 | rExtrusionColorProp.Value >>= nTransparency; |
6906 | 0 | } |
6907 | 0 | mpFS->startElementNS(XML_a, XML_extrusionClr); |
6908 | |
|
6909 | 0 | if( sSchemeClr.isEmpty() ) |
6910 | 0 | WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); |
6911 | 0 | else |
6912 | 0 | WriteColor( sSchemeClr, aColorTransformations ); |
6913 | |
|
6914 | 0 | mpFS->endElementNS( XML_a, XML_extrusionClr ); |
6915 | 0 | } |
6916 | 0 | if( aContourColorProps.hasElements() ) |
6917 | 0 | { |
6918 | 0 | OUString sSchemeClr; |
6919 | 0 | ::Color nColor; |
6920 | 0 | sal_Int32 nTransparency(0); |
6921 | 0 | Sequence< PropertyValue > aColorTransformations; |
6922 | 0 | for (const auto& rContourColorProp : aContourColorProps) |
6923 | 0 | { |
6924 | 0 | if( rContourColorProp.Name == "schemeClr" ) |
6925 | 0 | rContourColorProp.Value >>= sSchemeClr; |
6926 | 0 | else if( rContourColorProp.Name == "schemeClrTransformations" ) |
6927 | 0 | rContourColorProp.Value >>= aColorTransformations; |
6928 | 0 | else if( rContourColorProp.Name == "rgbClr" ) |
6929 | 0 | rContourColorProp.Value >>= nColor; |
6930 | 0 | else if( rContourColorProp.Name == "rgbClrTransparency" ) |
6931 | 0 | rContourColorProp.Value >>= nTransparency; |
6932 | 0 | } |
6933 | 0 | mpFS->startElementNS(XML_a, XML_contourClr); |
6934 | |
|
6935 | 0 | if( sSchemeClr.isEmpty() ) |
6936 | 0 | WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); |
6937 | 0 | else |
6938 | 0 | WriteColor( sSchemeClr, aContourColorProps ); |
6939 | |
|
6940 | 0 | mpFS->endElementNS( XML_a, XML_contourClr ); |
6941 | 0 | } |
6942 | 0 | mpFS->endElementNS( XML_a, XML_sp3d ); |
6943 | 0 | } |
6944 | | |
6945 | | void DrawingML::WriteArtisticEffect( const Reference< XPropertySet >& rXPropSet ) |
6946 | 0 | { |
6947 | 0 | if( !GetProperty( rXPropSet, u"InteropGrabBag"_ustr ) ) |
6948 | 0 | return; |
6949 | | |
6950 | 0 | PropertyValue aEffect; |
6951 | 0 | Sequence< PropertyValue > aGrabBag; |
6952 | 0 | mAny >>= aGrabBag; |
6953 | 0 | auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), |
6954 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "ArtisticEffectProperties"; }); |
6955 | 0 | if (pProp != std::cend(aGrabBag)) |
6956 | 0 | pProp->Value >>= aEffect; |
6957 | 0 | sal_Int32 nEffectToken = ArtisticEffectProperties::getEffectToken( aEffect.Name ); |
6958 | 0 | if( nEffectToken == XML_none ) |
6959 | 0 | return; |
6960 | | |
6961 | 0 | Sequence< PropertyValue > aAttrs; |
6962 | 0 | aEffect.Value >>= aAttrs; |
6963 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aAttrList = FastSerializerHelper::createAttrList(); |
6964 | 0 | OString sRelId; |
6965 | 0 | for (const auto& rAttr : aAttrs) |
6966 | 0 | { |
6967 | 0 | sal_Int32 nToken = ArtisticEffectProperties::getEffectToken( rAttr.Name ); |
6968 | 0 | if( nToken != XML_none ) |
6969 | 0 | { |
6970 | 0 | sal_Int32 nVal = 0; |
6971 | 0 | rAttr.Value >>= nVal; |
6972 | 0 | aAttrList->add( nToken, OString::number( nVal ) ); |
6973 | 0 | } |
6974 | 0 | else if( rAttr.Name == "OriginalGraphic" ) |
6975 | 0 | { |
6976 | 0 | Sequence< PropertyValue > aGraphic; |
6977 | 0 | rAttr.Value >>= aGraphic; |
6978 | 0 | Sequence< sal_Int8 > aGraphicData; |
6979 | 0 | OUString sGraphicId; |
6980 | 0 | for (const auto& rProp : aGraphic) |
6981 | 0 | { |
6982 | 0 | if( rProp.Name == "Id" ) |
6983 | 0 | rProp.Value >>= sGraphicId; |
6984 | 0 | else if( rProp.Name == "Data" ) |
6985 | 0 | rProp.Value >>= aGraphicData; |
6986 | 0 | } |
6987 | 0 | sRelId = WriteWdpPicture( sGraphicId, aGraphicData ); |
6988 | 0 | } |
6989 | 0 | } |
6990 | |
|
6991 | 0 | mpFS->startElementNS(XML_a, XML_extLst); |
6992 | 0 | mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{BEBA8EAE-BF5A-486C-A8C5-ECC9F3942E4B}"); |
6993 | 0 | mpFS->startElementNS( XML_a14, XML_imgProps, |
6994 | 0 | FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)) ); |
6995 | 0 | mpFS->startElementNS(XML_a14, XML_imgLayer, FSNS(XML_r, XML_embed), sRelId); |
6996 | 0 | mpFS->startElementNS(XML_a14, XML_imgEffect); |
6997 | |
|
6998 | 0 | mpFS->singleElementNS( XML_a14, nEffectToken, aAttrList ); |
6999 | |
|
7000 | 0 | mpFS->endElementNS( XML_a14, XML_imgEffect ); |
7001 | 0 | mpFS->endElementNS( XML_a14, XML_imgLayer ); |
7002 | 0 | mpFS->endElementNS( XML_a14, XML_imgProps ); |
7003 | 0 | mpFS->endElementNS( XML_a, XML_ext ); |
7004 | 0 | mpFS->endElementNS( XML_a, XML_extLst ); |
7005 | 0 | } |
7006 | | |
7007 | | OString DrawingML::WriteWdpPicture( const OUString& rFileId, const Sequence< sal_Int8 >& rPictureData ) |
7008 | 0 | { |
7009 | 0 | auto& rGraphicExportCache = GraphicExportCache::get(); |
7010 | |
|
7011 | 0 | OUString aId = rGraphicExportCache.findWdpID(rFileId); |
7012 | 0 | if (!aId.isEmpty()) |
7013 | 0 | return OUStringToOString(aId, RTL_TEXTENCODING_UTF8); |
7014 | | |
7015 | 0 | sal_Int32 nWdpImageCount = rGraphicExportCache.nextWdpImageCount(); |
7016 | 0 | OUString sFileName = u"media/hdphoto"_ustr + OUString::number(nWdpImageCount) + u".wdp"_ustr; |
7017 | 0 | OUString sFragment = GetComponentDir() + u"/"_ustr + sFileName; |
7018 | 0 | Reference< XOutputStream > xOutStream = mpFB->openFragmentStream(sFragment, u"image/vnd.ms-photo"_ustr); |
7019 | 0 | xOutStream->writeBytes( rPictureData ); |
7020 | 0 | xOutStream->closeOutput(); |
7021 | |
|
7022 | 0 | aId = mpFB->addRelation(mpFS->getOutputStream(), |
7023 | 0 | oox::getRelationship(Relationship::HDPHOTO), |
7024 | 0 | Concat2View(GetRelationCompPrefix() + sFileName)); |
7025 | |
|
7026 | 0 | rGraphicExportCache.addToWdpCache(rFileId, aId); |
7027 | |
|
7028 | 0 | return OUStringToOString(aId, RTL_TEXTENCODING_UTF8); |
7029 | 0 | } |
7030 | | |
7031 | | void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rXShape, sal_Int32 nDiagramId, sal_Int32 nShapeId) |
7032 | 0 | { |
7033 | 0 | SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape); |
7034 | 0 | if (nullptr == pObj) |
7035 | 0 | return; |
7036 | | |
7037 | 0 | const std::shared_ptr< svx::diagram::DiagramHelper_svx >& rIDiagramHelper(pObj->getDiagramHelper()); |
7038 | 0 | if (!rIDiagramHelper) |
7039 | 0 | return; |
7040 | | |
7041 | 0 | const DiagramHelper_oox* pAdvancedDiagramHelper = static_cast<DiagramHelper_oox*>(rIDiagramHelper.get()); |
7042 | 0 | if (nullptr == pAdvancedDiagramHelper) |
7043 | 0 | return; |
7044 | | |
7045 | | // check mandatory and originally imported DataDom OOXLayout |
7046 | 0 | uno::Reference<xml::dom::XDocument> layoutDom; |
7047 | 0 | rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXLayout) >>= layoutDom; |
7048 | 0 | if (!layoutDom) |
7049 | 0 | return; |
7050 | | |
7051 | | // check mandatory and originally imported DataDom OOXStyle |
7052 | 0 | uno::Reference<xml::dom::XDocument> styleDom; |
7053 | 0 | rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXStyle) >>= styleDom; |
7054 | 0 | if (!styleDom) |
7055 | 0 | return; |
7056 | | |
7057 | | // check mandatory and originally imported DataDom OOXColor |
7058 | 0 | uno::Reference<xml::dom::XDocument> colorDom; |
7059 | 0 | rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXColor) >>= colorDom; |
7060 | 0 | if (!colorDom) |
7061 | 0 | return; |
7062 | | |
7063 | | // handle drawingDom OOXDrawing before OOXData. It may exist if Diagram is untouched |
7064 | | // from original import. it needs a drawingRelId that is referenced inside dataDom |
7065 | 0 | uno::Reference<xml::dom::XDocument> drawingDom; |
7066 | 0 | rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDrawing) >>= drawingDom; |
7067 | 0 | static bool bForceAlwaysReCreate(nullptr != std::getenv("FORCE_RECREATE_DIAGRAM_DATADOMS")); |
7068 | | |
7069 | | // check if re-creation is forced (for test purposes) |
7070 | 0 | if (drawingDom && bForceAlwaysReCreate) |
7071 | 0 | drawingDom.clear(); |
7072 | | |
7073 | | // add relation in any case. If we have a drawingDom the dataDom will be adapted, |
7074 | | // if drawingDom gets written it's needed to embed it there |
7075 | 0 | OUString drawingRelId; |
7076 | | |
7077 | | // CAUTION! We have stuff like testfdo79822 with file "fdo79822.docx" |
7078 | | // that do not originally have a drawingDom at all. The test mentions |
7079 | | // that this file was created in ms word 2007. In those cases we might |
7080 | | // try in the future to 'repair' stuff by writing a DrawingDom, but for |
7081 | | // now just do as version before did - write no DrawingDom at all if new |
7082 | | // features are not activated |
7083 | 0 | const OUString drawingFileName("diagrams/drawing" + OUString::number(nDiagramId) + ".xml"); |
7084 | 0 | const OUString sRelationCompPrefix(GetRelationCompPrefix()); |
7085 | 0 | const OUString sDir(GetComponentDir()); |
7086 | |
|
7087 | 0 | if (SdrObject::useAdvancedDiagramFeatures() || drawingDom.is()) |
7088 | 0 | { |
7089 | | // only add Relation if we write a DrawingDom |
7090 | 0 | drawingRelId = mpFB->addRelation( |
7091 | 0 | mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDRAWING), |
7092 | 0 | Concat2View(sRelationCompPrefix + drawingFileName)); |
7093 | 0 | } |
7094 | |
|
7095 | 0 | if (!drawingDom && SdrObject::useAdvancedDiagramFeatures()) |
7096 | 0 | { |
7097 | | // no drawingDom exists, so it was either not originally imported or the |
7098 | | // Diagram was changed, so it got deleted. write drawingDom directly as |
7099 | | // sub-content |
7100 | 0 | uno::Reference<io::XOutputStream> xOutputStream = mpFB->openFragmentStream( |
7101 | 0 | sDir + "/" + drawingFileName, |
7102 | 0 | u"application/vnd.ms-office.drawingml.diagramDrawing+xml"_ustr); |
7103 | |
|
7104 | 0 | if (xOutputStream) |
7105 | 0 | pAdvancedDiagramHelper->writeDiagramOOXDrawing(*this, xOutputStream); |
7106 | 0 | } |
7107 | | |
7108 | | // check DataDom OOXData |
7109 | 0 | uno::Reference<xml::dom::XDocument> dataDom; |
7110 | 0 | rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXData) >>= dataDom; |
7111 | | |
7112 | | // check if re-creation is forced (for test purposes) |
7113 | 0 | if (dataDom && bForceAlwaysReCreate) |
7114 | 0 | dataDom.clear(); |
7115 | | |
7116 | | // add data relation |
7117 | 0 | const OUString dataFileName = "diagrams/data" + OUString::number(nDiagramId) + ".xml"; |
7118 | 0 | const OUString dataRelId = |
7119 | 0 | mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDATA), |
7120 | 0 | Concat2View(sRelationCompPrefix + dataFileName)); |
7121 | |
|
7122 | 0 | if (!dataDom) |
7123 | 0 | { |
7124 | | // no dataDom exists, so the Diagram was changed, so it got deleted. |
7125 | | // write dataDom directly as sub-content |
7126 | | // OUString dataFileName = u"diagrams/data"_ustr + OUString::number(nDiagramId) + u".xml"_ustr; |
7127 | 0 | uno::Reference<io::XOutputStream> xOutputStream = mpFB->openFragmentStream( |
7128 | 0 | sDir + u"/"_ustr + dataFileName, |
7129 | 0 | u"application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"_ustr); |
7130 | |
|
7131 | 0 | if (xOutputStream) |
7132 | 0 | pAdvancedDiagramHelper->writeDiagramOOXData(*this, xOutputStream, drawingRelId); |
7133 | 0 | } |
7134 | | |
7135 | | // prepare AttributeList for export of this GroupObject representing the Diagram |
7136 | | // as reference to Diagram |
7137 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList |
7138 | 0 | = sax_fastparser::FastSerializerHelper::createAttrList(); |
7139 | | |
7140 | | // id in cNvPr under cNvGraphicFramePr must be unique among shapes, |
7141 | | // but can be different from diagram's own id |
7142 | 0 | const sal_Int32 nExpShapeId = nShapeId != -1 ? nShapeId : nDiagramId; |
7143 | 0 | pDocPrAttrList->add(XML_id, OString::number(nExpShapeId)); |
7144 | | // A diagram that was given a name keeps it. One that has none is named after its number, |
7145 | | // which is what a reader shows for it. |
7146 | 0 | OString sName = "Diagram" + OString::number(nDiagramId); |
7147 | 0 | if (uno::Reference<css::container::XNamed> xNamed{ rXShape, uno::UNO_QUERY }) |
7148 | 0 | { |
7149 | 0 | const OUString aShapeName = xNamed->getName(); |
7150 | 0 | if (!aShapeName.isEmpty()) |
7151 | 0 | sName = OUStringToOString(aShapeName, RTL_TEXTENCODING_UTF8); |
7152 | 0 | } |
7153 | 0 | pDocPrAttrList->add(XML_name, sName); |
7154 | |
|
7155 | 0 | if (GetDocumentType() == DOCUMENT_DOCX) |
7156 | 0 | { |
7157 | 0 | mpFS->singleElementNS(XML_wp, XML_docPr, pDocPrAttrList); |
7158 | 0 | mpFS->singleElementNS(XML_wp, XML_cNvGraphicFramePr); |
7159 | |
|
7160 | 0 | mpFS->startElementNS(XML_a, XML_graphic, FSNS(XML_xmlns, XML_a), |
7161 | 0 | mpFB->getNamespaceURL(OOX_NS(dml))); |
7162 | 0 | } |
7163 | 0 | else |
7164 | 0 | { |
7165 | | // use correct XmlNamespace, other DocumentType is DOCUMENT_XLSX |
7166 | 0 | const bool isPPTX(DOCUMENT_PPTX == GetDocumentType()); |
7167 | 0 | const sal_Int32 nXmlNamespace(isPPTX ? XML_p : XML_xdr); |
7168 | 0 | mpFS->startElementNS(nXmlNamespace, XML_nvGraphicFramePr); |
7169 | |
|
7170 | 0 | mpFS->singleElementNS(nXmlNamespace, XML_cNvPr, pDocPrAttrList); |
7171 | 0 | mpFS->singleElementNS(nXmlNamespace, XML_cNvGraphicFramePr); |
7172 | |
|
7173 | 0 | if (isPPTX) |
7174 | 0 | { |
7175 | 0 | mpFS->startElementNS(nXmlNamespace, XML_nvPr); |
7176 | 0 | mpFS->startElementNS(nXmlNamespace, XML_extLst); |
7177 | | // change tracking extension - required in PPTX |
7178 | 0 | mpFS->startElementNS(nXmlNamespace, XML_ext, XML_uri, "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}"); |
7179 | 0 | mpFS->singleElementNS(XML_p14, XML_modId, |
7180 | 0 | FSNS(XML_xmlns, XML_p14), mpFB->getNamespaceURL(OOX_NS(p14)), |
7181 | 0 | XML_val, |
7182 | 0 | OString::number(comphelper::rng::uniform_uint_distribution(1, SAL_MAX_UINT32))); |
7183 | 0 | mpFS->endElementNS(nXmlNamespace, XML_ext); |
7184 | 0 | mpFS->endElementNS(nXmlNamespace, XML_extLst); |
7185 | 0 | mpFS->endElementNS(nXmlNamespace, XML_nvPr); |
7186 | 0 | } |
7187 | |
|
7188 | 0 | mpFS->endElementNS(nXmlNamespace, XML_nvGraphicFramePr); |
7189 | | |
7190 | | // store size and position of background shape instead of group shape |
7191 | | // as some shapes may be outside |
7192 | 0 | css::uno::Reference<css::drawing::XShapes> xShapes(rXShape, uno::UNO_QUERY); |
7193 | 0 | if (xShapes.is() && xShapes->hasElements()) |
7194 | 0 | { |
7195 | 0 | css::uno::Reference<css::drawing::XShape> xShapeBg(xShapes->getByIndex(0), |
7196 | 0 | uno::UNO_QUERY); |
7197 | 0 | awt::Point aPos = xShapeBg->getPosition(); |
7198 | 0 | awt::Size aSize = xShapeBg->getSize(); |
7199 | 0 | WriteTransformation( |
7200 | 0 | xShapeBg, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), |
7201 | 0 | nXmlNamespace, false, false, 0, false); |
7202 | 0 | } |
7203 | |
|
7204 | 0 | mpFS->startElementNS(XML_a, XML_graphic); |
7205 | 0 | } |
7206 | |
|
7207 | 0 | mpFS->startElementNS(XML_a, XML_graphicData, XML_uri, |
7208 | 0 | "http://schemas.openxmlformats.org/drawingml/2006/diagram"); |
7209 | | |
7210 | | // add layout relation |
7211 | 0 | OUString layoutFileName = "diagrams/layout" + OUString::number(nDiagramId) + ".xml"; |
7212 | 0 | OUString layoutRelId = mpFB->addRelation(mpFS->getOutputStream(), |
7213 | 0 | oox::getRelationship(Relationship::DIAGRAMLAYOUT), |
7214 | 0 | Concat2View(sRelationCompPrefix + layoutFileName)); |
7215 | | |
7216 | | // add style relation |
7217 | 0 | OUString styleFileName = "diagrams/quickStyle" + OUString::number(nDiagramId) + ".xml"; |
7218 | 0 | OUString styleRelId = mpFB->addRelation(mpFS->getOutputStream(), |
7219 | 0 | oox::getRelationship(Relationship::DIAGRAMQUICKSTYLE), |
7220 | 0 | Concat2View(sRelationCompPrefix + styleFileName)); |
7221 | | |
7222 | | // add color relation |
7223 | 0 | OUString colorFileName = "diagrams/colors" + OUString::number(nDiagramId) + ".xml"; |
7224 | 0 | OUString colorRelId = mpFB->addRelation(mpFS->getOutputStream(), |
7225 | 0 | oox::getRelationship(Relationship::DIAGRAMCOLORS), |
7226 | 0 | Concat2View(sRelationCompPrefix + colorFileName)); |
7227 | |
|
7228 | 0 | mpFS->singleElementNS(XML_dgm, XML_relIds, |
7229 | 0 | FSNS(XML_xmlns, XML_dgm), mpFB->getNamespaceURL(OOX_NS(dmlDiagram)), |
7230 | 0 | FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)), |
7231 | 0 | FSNS(XML_r, XML_dm), dataRelId, FSNS(XML_r, XML_lo), layoutRelId, |
7232 | 0 | FSNS(XML_r, XML_qs), styleRelId, FSNS(XML_r, XML_cs), colorRelId); |
7233 | |
|
7234 | 0 | mpFS->endElementNS(XML_a, XML_graphicData); |
7235 | 0 | mpFS->endElementNS(XML_a, XML_graphic); |
7236 | |
|
7237 | 0 | uno::Reference<xml::sax::XSAXSerializable> serializer; |
7238 | 0 | uno::Reference<xml::sax::XWriter> writer |
7239 | 0 | = xml::sax::Writer::create(comphelper::getProcessComponentContext()); |
7240 | |
|
7241 | 0 | if (dataDom) |
7242 | 0 | { |
7243 | 0 | if (!drawingRelId.isEmpty()) |
7244 | 0 | { |
7245 | | // NOTE: drawingRelId may be empty if SdrObject::useAdvancedDiagramFeatures() |
7246 | | // is set, see comments above |
7247 | | // the original and unchanged dataDom contains a reference to the drawing |
7248 | | // relation. We need to update it with the new generated relation value |
7249 | | // before writing the dom to the file |
7250 | | |
7251 | | // Get the dsp:damaModelExt node from the dom |
7252 | 0 | uno::Reference<xml::dom::XNodeList> nodeList = dataDom->getElementsByTagNameNS( |
7253 | 0 | u"http://schemas.microsoft.com/office/drawing/2008/diagram"_ustr, u"dataModelExt"_ustr); |
7254 | | |
7255 | | // There must be one element only so get it |
7256 | 0 | uno::Reference<xml::dom::XNode> node = nodeList->item(0); |
7257 | | |
7258 | | // Get the list of attributes of the node |
7259 | 0 | uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes(); |
7260 | | |
7261 | | // Get the node with the relId attribute and set its new value |
7262 | 0 | uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem(u"relId"_ustr); |
7263 | 0 | relIdNode->setNodeValue(drawingRelId); |
7264 | 0 | } |
7265 | | |
7266 | | // write originally imported & untouched data file |
7267 | 0 | serializer.set(dataDom, uno::UNO_QUERY); |
7268 | 0 | uno::Reference<io::XOutputStream> xDataOutputStream = mpFB->openFragmentStream( |
7269 | 0 | sDir + "/" + dataFileName, |
7270 | 0 | u"application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"_ustr); |
7271 | 0 | writer->setOutputStream(xDataOutputStream); |
7272 | 0 | serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
7273 | 0 | uno::Sequence<beans::StringPair>()); |
7274 | | |
7275 | | // get originally imported OOXDataImageRels |
7276 | 0 | uno::Sequence<uno::Sequence<uno::Any>> xDataImageRelSeq; |
7277 | 0 | rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDataImageRels) |
7278 | 0 | >>= xDataImageRelSeq; |
7279 | | |
7280 | | // get originally imported OOXDataHlinkRels |
7281 | 0 | uno::Sequence<uno::Sequence<uno::Any>> xDataHlinkRelSeq; |
7282 | 0 | rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDataHlinkRels) |
7283 | 0 | >>= xDataHlinkRelSeq; |
7284 | | |
7285 | | // write the associated Images and rels for data file |
7286 | 0 | writeDiagramImageRels(xDataImageRelSeq, xDataOutputStream); |
7287 | 0 | writeDiagramHlinkRels(xDataHlinkRelSeq, xDataOutputStream); |
7288 | 0 | } |
7289 | | |
7290 | | // write layout file |
7291 | 0 | serializer.set(layoutDom, uno::UNO_QUERY); |
7292 | 0 | writer->setOutputStream(mpFB->openFragmentStream( |
7293 | 0 | sDir + "/" + layoutFileName, |
7294 | 0 | u"application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"_ustr)); |
7295 | 0 | serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
7296 | 0 | uno::Sequence<beans::StringPair>()); |
7297 | | |
7298 | | // write style file |
7299 | 0 | serializer.set(styleDom, uno::UNO_QUERY); |
7300 | 0 | writer->setOutputStream(mpFB->openFragmentStream( |
7301 | 0 | sDir + "/" + styleFileName, |
7302 | 0 | u"application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"_ustr)); |
7303 | 0 | serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
7304 | 0 | uno::Sequence<beans::StringPair>()); |
7305 | | |
7306 | | // write color file |
7307 | 0 | serializer.set(colorDom, uno::UNO_QUERY); |
7308 | 0 | writer->setOutputStream(mpFB->openFragmentStream( |
7309 | 0 | sDir + "/" + colorFileName, |
7310 | 0 | u"application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"_ustr)); |
7311 | 0 | serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
7312 | 0 | uno::Sequence<beans::StringPair>()); |
7313 | |
|
7314 | 0 | if (drawingDom.is()) |
7315 | 0 | { |
7316 | | // write original and untouched drawingDom |
7317 | 0 | serializer.set(drawingDom, uno::UNO_QUERY); |
7318 | 0 | uno::Reference<io::XOutputStream> xDrawingOutputStream = mpFB->openFragmentStream( |
7319 | 0 | sDir + "/" + drawingFileName, u"application/vnd.ms-office.drawingml.diagramDrawing+xml"_ustr); |
7320 | 0 | writer->setOutputStream(xDrawingOutputStream); |
7321 | 0 | serializer->serialize( |
7322 | 0 | uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
7323 | 0 | uno::Sequence<beans::StringPair>()); |
7324 | | |
7325 | | // get originally imported OOXDrawingImageRels |
7326 | 0 | uno::Sequence<uno::Sequence<uno::Any>> xDrawingImageRelSeq; |
7327 | 0 | rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDrawingImageRels) |
7328 | 0 | >>= xDrawingImageRelSeq; |
7329 | | |
7330 | | // get originally imported OOXDataHlinkRels |
7331 | 0 | uno::Sequence<uno::Sequence<uno::Any>> xDrawingHlinkRelSeq; |
7332 | 0 | rIDiagramHelper->getOOXDomValue(svx::diagram::DomMapFlag::OOXDrawingHlinkRels) |
7333 | 0 | >>= xDrawingHlinkRelSeq; |
7334 | | |
7335 | | // write the associated Images and rels for data file |
7336 | 0 | writeDiagramImageRels(xDrawingImageRelSeq, xDrawingOutputStream); |
7337 | 0 | writeDiagramHlinkRels(xDrawingHlinkRelSeq, xDrawingOutputStream); |
7338 | 0 | } |
7339 | 0 | } |
7340 | | |
7341 | | void DrawingML::writeDiagramImageRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq, |
7342 | | const uno::Reference<io::XOutputStream>& xOutStream) |
7343 | 0 | { |
7344 | | // add image relationships of OOXData, SmartArtDiagram |
7345 | 0 | OUString sType(oox::getRelationship(Relationship::IMAGE)); |
7346 | 0 | uno::Reference<xml::sax::XWriter> xWriter |
7347 | 0 | = xml::sax::Writer::create(comphelper::getProcessComponentContext()); |
7348 | 0 | xWriter->setOutputStream(xOutStream); |
7349 | | |
7350 | | // retrieve the relationships from Sequence |
7351 | 0 | for (sal_Int32 j = 0; j < xRelSeq.getLength(); j++) |
7352 | 0 | { |
7353 | | // diagramDataRelTuple[0] => RID, |
7354 | | // diagramDataRelTuple[1] => XGraphic |
7355 | 0 | const uno::Sequence<uno::Any>& diagramDataRelTuple = xRelSeq[j]; |
7356 | |
|
7357 | 0 | OUString sRelId; |
7358 | 0 | diagramDataRelTuple[0] >>= sRelId; |
7359 | 0 | sRelId = sRelId.copy(3); |
7360 | | |
7361 | | // get XGraphic |
7362 | 0 | uno::Reference<graphic::XGraphic> xGraphic; |
7363 | 0 | diagramDataRelTuple[1] >>= xGraphic; |
7364 | |
|
7365 | 0 | if (xGraphic) |
7366 | 0 | { |
7367 | | // write to Storage and get relative path back |
7368 | 0 | ::Graphic aGraphic(xGraphic); |
7369 | 0 | const OUString sFragment = writeImageRelToStorage(aGraphic); |
7370 | |
|
7371 | 0 | PropertySet aProps(xOutStream); |
7372 | 0 | aProps.setAnyProperty(PROP_RelId, uno::Any(sRelId.toInt32())); |
7373 | |
|
7374 | 0 | if (GetDocumentType() == DOCUMENT_DOCX) |
7375 | 0 | mpFB->addRelation(xOutStream, sType, Concat2View("../" + sFragment)); |
7376 | 0 | else |
7377 | 0 | mpFB->addRelation(xOutStream, sType, sFragment); |
7378 | 0 | } |
7379 | 0 | } |
7380 | 0 | } |
7381 | | |
7382 | | void DrawingML::writeDiagramHlinkRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq, |
7383 | | const uno::Reference<io::XOutputStream>& xOutStream) |
7384 | 0 | { |
7385 | 0 | uno::Reference<xml::sax::XWriter> xWriter |
7386 | 0 | = xml::sax::Writer::create(comphelper::getProcessComponentContext()); |
7387 | 0 | xWriter->setOutputStream(xOutStream); |
7388 | | |
7389 | | // retrieve the relationships from Sequence |
7390 | 0 | for (sal_Int32 j = 0; j < xRelSeq.getLength(); j++) |
7391 | 0 | { |
7392 | | // diagramDataRelTuple[0] => RID, |
7393 | | // diagramDataRelTuple[1] => Target |
7394 | | // diagramDataRelTuple[2] => Type |
7395 | 0 | const uno::Sequence<uno::Any>& diagramDataRelTuple = xRelSeq[j]; |
7396 | |
|
7397 | 0 | OUString sRelId; |
7398 | 0 | OUString sTarget; |
7399 | 0 | OUString sType; |
7400 | 0 | diagramDataRelTuple[0] >>= sRelId; |
7401 | 0 | sRelId = sRelId.copy(3); |
7402 | 0 | diagramDataRelTuple[1] >>= sTarget; |
7403 | 0 | diagramDataRelTuple[2] >>= sType; |
7404 | |
|
7405 | 0 | OUString sRelType; |
7406 | 0 | bool bExtURL = true; |
7407 | 0 | if (sType == u"slide") |
7408 | 0 | { |
7409 | 0 | sRelType = oox::getRelationship(Relationship::SLIDE); |
7410 | 0 | bExtURL = false; |
7411 | 0 | } |
7412 | 0 | else |
7413 | 0 | sRelType = oox::getRelationship(Relationship::HYPERLINK); |
7414 | |
|
7415 | 0 | PropertySet aProps(xOutStream); |
7416 | 0 | aProps.setAnyProperty(PROP_RelId, uno::Any(sRelId.toInt32())); |
7417 | 0 | mpFB->addRelation(xOutStream, sRelType, sTarget, bExtURL); |
7418 | 0 | } |
7419 | 0 | } |
7420 | | |
7421 | | void DrawingML::WriteFromTo(const uno::Reference<css::drawing::XShape>& rXShape, const awt::Size& aPageSize, |
7422 | | const FSHelperPtr& pDrawing) |
7423 | 0 | { |
7424 | 0 | awt::Point aTopLeft = rXShape->getPosition(); |
7425 | 0 | awt::Size aSize = rXShape->getSize(); |
7426 | |
|
7427 | 0 | SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape); |
7428 | 0 | if (pObj) |
7429 | 0 | { |
7430 | 0 | Degree100 nRotation = pObj->GetRotateAngle(); |
7431 | 0 | if (nRotation) |
7432 | 0 | { |
7433 | 0 | sal_Int16 nHalfWidth = aSize.Width / 2; |
7434 | 0 | sal_Int16 nHalfHeight = aSize.Height / 2; |
7435 | | // aTopLeft needs correction for rotated customshapes |
7436 | 0 | if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape) |
7437 | 0 | { |
7438 | | // Center of bounding box of the rotated shape |
7439 | 0 | const auto aSnapRectCenter(pObj->GetSnapRect().Center()); |
7440 | 0 | aTopLeft.X = aSnapRectCenter.X() - nHalfWidth; |
7441 | 0 | aTopLeft.Y = aSnapRectCenter.Y() - nHalfHeight; |
7442 | 0 | } |
7443 | | |
7444 | | // MSO changes the anchor positions at these angles and that does an extra 90 degrees |
7445 | | // rotation on our shapes, so we output it in such position that MSO |
7446 | | // can draw this shape correctly. |
7447 | 0 | if ((nRotation >= 4500_deg100 && nRotation < 13500_deg100) || (nRotation >= 22500_deg100 && nRotation < 31500_deg100)) |
7448 | 0 | { |
7449 | 0 | aTopLeft.X = aTopLeft.X - nHalfHeight + nHalfWidth; |
7450 | 0 | aTopLeft.Y = aTopLeft.Y - nHalfWidth + nHalfHeight; |
7451 | |
|
7452 | 0 | std::swap(aSize.Width, aSize.Height); |
7453 | 0 | } |
7454 | 0 | } |
7455 | 0 | } |
7456 | |
|
7457 | 0 | tools::Rectangle aLocation(aTopLeft.X, aTopLeft.Y, aTopLeft.X + aSize.Width, aTopLeft.Y + aSize.Height); |
7458 | 0 | double nXpos = static_cast<double>(aLocation.TopLeft().getX()) / static_cast<double>(aPageSize.Width); |
7459 | 0 | double nYpos = static_cast<double>(aLocation.TopLeft().getY()) / static_cast<double>(aPageSize.Height); |
7460 | |
|
7461 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_from)); |
7462 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_x)); |
7463 | 0 | pDrawing->write(nXpos); |
7464 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_x)); |
7465 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_y)); |
7466 | 0 | pDrawing->write(nYpos); |
7467 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_y)); |
7468 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_from)); |
7469 | |
|
7470 | 0 | nXpos = static_cast<double>(aLocation.BottomRight().getX()) / static_cast<double>(aPageSize.Width); |
7471 | 0 | nYpos = static_cast<double>(aLocation.BottomRight().getY()) / static_cast<double>(aPageSize.Height); |
7472 | |
|
7473 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_to)); |
7474 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_x)); |
7475 | 0 | pDrawing->write(nXpos); |
7476 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_x)); |
7477 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_y)); |
7478 | 0 | pDrawing->write(nYpos); |
7479 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_y)); |
7480 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_to)); |
7481 | 0 | } |
7482 | | |
7483 | | } |
7484 | | |
7485 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |