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 #include <sal/log.hxx>
20 #include <pdfiprocessor.hxx>
21 #include <xmlemitter.hxx>
22 #include <pdfihelper.hxx>
23 #include <imagecontainer.hxx>
24 #include "style.hxx"
25 #include "drawtreevisiting.hxx"
26 #include <genericelements.hxx>
27
28 #include <basegfx/polygon/b2dpolypolygontools.hxx>
29 #include <osl/diagnose.h>
30 #include <rtl/math.hxx>
31 #include <com/sun/star/i18n/BreakIterator.hpp>
32 #include <com/sun/star/i18n/CharacterClassification.hpp>
33 #include <com/sun/star/i18n/ScriptType.hpp>
34 #include <com/sun/star/i18n/DirectionProperty.hpp>
35 #include <comphelper/string.hxx>
36
37 #include <string.h>
38 #include <string_view>
39
40 using namespace ::com::sun::star;
41 using namespace ::com::sun::star::lang;
42 using namespace ::com::sun::star::i18n;
43 using namespace ::com::sun::star::uno;
44
45 namespace pdfi
46 {
47
GetBreakIterator()48 const Reference< XBreakIterator >& DrawXmlOptimizer::GetBreakIterator()
49 {
50 if ( !mxBreakIter.is() )
51 {
52 Reference< XComponentContext > xContext( m_rProcessor.m_xContext, uno::UNO_SET_THROW );
53 mxBreakIter = BreakIterator::create(xContext);
54 }
55 return mxBreakIter;
56 }
57
GetCharacterClassification()58 const Reference< XCharacterClassification >& DrawXmlEmitter::GetCharacterClassification()
59 {
60 if ( !mxCharClass.is() )
61 {
62 Reference< XComponentContext > xContext( m_rEmitContext.m_xContext, uno::UNO_SET_THROW );
63 mxCharClass = CharacterClassification::create(xContext);
64 }
65 return mxCharClass;
66 }
67
visit(HyperlinkElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)68 void DrawXmlEmitter::visit( HyperlinkElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
69 {
70 if( elem.Children.empty() )
71 return;
72
73 const char* pType = dynamic_cast<DrawElement*>(elem.Children.front().get()) ? "draw:a" : "text:a";
74
75 PropertyMap aProps;
76 aProps[ u"xlink:type"_ustr ] = "simple";
77 aProps[ u"xlink:href"_ustr ] = elem.URI;
78 aProps[ u"office:target-frame-name"_ustr ] = "_blank";
79 aProps[ u"xlink:show"_ustr ] = "new";
80
81 m_rEmitContext.rEmitter.beginTag( pType, aProps );
82 auto this_it = elem.Children.begin();
83 while( this_it != elem.Children.end() && this_it->get() != &elem )
84 {
85 (*this_it)->visitedBy( *this, this_it );
86 ++this_it;
87 }
88 m_rEmitContext.rEmitter.endTag( pType );
89 }
90
visit(TextElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)91 void DrawXmlEmitter::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
92 {
93 if( elem.Text.isEmpty() )
94 return;
95
96 OUString strSpace(u' ');
97 OUString strNbSpace(u'\x00A0');
98 OUString tabSpace(u'\x0009');
99 PropertyMap aProps;
100 if( elem.StyleId != -1 )
101 {
102 aProps[ u"text:style-name"_ustr ] =
103 m_rEmitContext.rStyles.getStyleName( elem.StyleId );
104 }
105
106 OUString str(elem.Text.toString());
107
108 // Check for RTL
109 bool isRTL = false;
110 Reference< i18n::XCharacterClassification > xCC( GetCharacterClassification() );
111 if( xCC.is() )
112 {
113 for(int i=1; i< elem.Text.getLength(); i++)
114 {
115 css::i18n::DirectionProperty nType = static_cast<css::i18n::DirectionProperty>(xCC->getCharacterDirection( str, i ));
116 if ( nType == css::i18n::DirectionProperty_RIGHT_TO_LEFT ||
117 nType == css::i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC ||
118 nType == css::i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING ||
119 nType == css::i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE
120 )
121 isRTL = true;
122 }
123 }
124
125 if (isRTL) // If so, reverse string
126 {
127 // First, produce mirrored-image for each code point which has the Bidi_Mirrored property.
128 str = PDFIProcessor::SubstituteBidiMirrored(str);
129 // Then, reverse the code points in the string, in backward order.
130 str = ::comphelper::string::reverseCodePoints(str);
131 }
132
133 m_rEmitContext.rEmitter.beginTag( "text:span", aProps );
134
135 aProps = {};
136 for(int i=0; i< elem.Text.getLength(); i++)
137 {
138 OUString strToken= str.copy(i,1) ;
139 if( strSpace == strToken || strNbSpace == strToken )
140 {
141 aProps[ u"text:c"_ustr ] = "1";
142 m_rEmitContext.rEmitter.beginTag( "text:s", aProps );
143 m_rEmitContext.rEmitter.endTag( "text:s");
144 }
145 else
146 {
147 if( tabSpace == strToken )
148 {
149 m_rEmitContext.rEmitter.beginTag( "text:tab", aProps );
150 m_rEmitContext.rEmitter.endTag( "text:tab");
151 }
152 else
153 {
154 m_rEmitContext.rEmitter.write( strToken );
155 }
156 }
157 }
158
159 auto this_it = elem.Children.begin();
160 while( this_it != elem.Children.end() && this_it->get() != &elem )
161 {
162 (*this_it)->visitedBy( *this, this_it );
163 ++this_it;
164 }
165
166 m_rEmitContext.rEmitter.endTag( "text:span" );
167 }
168
visit(ParagraphElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)169 void DrawXmlEmitter::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
170 {
171 PropertyMap aProps;
172 if( elem.StyleId != -1 )
173 {
174 aProps[ u"text:style-name"_ustr ] = m_rEmitContext.rStyles.getStyleName( elem.StyleId );
175 }
176 const char* pTagType = "text:p";
177 if( elem.Type == ParagraphElement::Headline )
178 pTagType = "text:h";
179 m_rEmitContext.rEmitter.beginTag( pTagType, aProps );
180
181 auto this_it = elem.Children.begin();
182 while( this_it != elem.Children.end() && this_it->get() != &elem )
183 {
184 (*this_it)->visitedBy( *this, this_it );
185 ++this_it;
186 }
187
188 m_rEmitContext.rEmitter.endTag( pTagType );
189 }
190
fillFrameProps(DrawElement & rElem,PropertyMap & rProps,const EmitContext & rEmitContext,bool bWasTransformed)191 void DrawXmlEmitter::fillFrameProps( DrawElement& rElem,
192 PropertyMap& rProps,
193 const EmitContext& rEmitContext,
194 bool bWasTransformed
195 )
196 {
197 static constexpr OUStringLiteral sDrawZIndex = u"draw:z-index";
198 static constexpr OUStringLiteral sDrawStyleName = u"draw:style-name";
199 static constexpr OUStringLiteral sDrawTextStyleName = u"draw:text-style-name";
200 static constexpr OUStringLiteral sSvgX = u"svg:x";
201 static constexpr OUStringLiteral sSvgY = u"svg:y";
202 static constexpr OUStringLiteral sSvgWidth = u"svg:width";
203 static constexpr OUStringLiteral sSvgHeight = u"svg:height";
204 static constexpr OUStringLiteral sDrawTransform = u"draw:transform";
205
206 rProps[ sDrawZIndex ] = OUString::number( rElem.ZOrder );
207 rProps[ sDrawStyleName ] = rEmitContext.rStyles.getStyleName( rElem.StyleId );
208
209 if (rElem.IsForText)
210 rProps[ sDrawTextStyleName ] = rEmitContext.rStyles.getStyleName(rElem.TextStyleId);
211
212 const GraphicsContext& rGC =
213 rEmitContext.rProcessor.getGraphicsContext( rElem.GCId );
214
215 if (bWasTransformed)
216 {
217 rProps[ sSvgX ] = convertPixelToUnitString(rElem.x);
218 rProps[ sSvgY ] = convertPixelToUnitString(rElem.y);
219 rProps[ sSvgWidth ] = convertPixelToUnitString(rElem.w);
220 rProps[ sSvgHeight ] = convertPixelToUnitString(rElem.h);
221 }
222 else
223 {
224 basegfx::B2DHomMatrix mat(rGC.Transformation);
225
226 if (rElem.MirrorVertical)
227 {
228 basegfx::B2DHomMatrix mat2;
229 mat2.translate(0, -0.5);
230 mat2.scale(1, -1);
231 mat2.translate(0, 0.5);
232 mat = mat * mat2;
233 }
234
235 double scale = convPx2mm(100);
236 mat.scale(scale, scale);
237
238 rProps[ sDrawTransform ] =
239 OUString::Concat("matrix(")
240 + OUString::number(mat.get(0, 0))
241 + " "
242 + OUString::number(mat.get(1, 0))
243 + " "
244 + OUString::number(mat.get(0, 1))
245 + " "
246 + OUString::number(mat.get(1, 1))
247 + " "
248 + OUString::number(mat.get(0, 2))
249 + " "
250 + OUString::number(mat.get(1, 2))
251 + ")";
252
253 }
254 }
255
visit(FrameElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)256 void DrawXmlEmitter::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
257 {
258 if( elem.Children.empty() )
259 return;
260
261 bool bTextBox = (dynamic_cast<ParagraphElement*>(elem.Children.front().get()) != nullptr);
262 PropertyMap aFrameProps;
263 fillFrameProps( elem, aFrameProps, m_rEmitContext, false );
264 m_rEmitContext.rEmitter.beginTag( "draw:frame", aFrameProps );
265 if( bTextBox )
266 m_rEmitContext.rEmitter.beginTag( "draw:text-box", PropertyMap() );
267
268 auto this_it = elem.Children.begin();
269 while( this_it != elem.Children.end() && this_it->get() != &elem )
270 {
271 (*this_it)->visitedBy( *this, this_it );
272 ++this_it;
273 }
274
275 if( bTextBox )
276 m_rEmitContext.rEmitter.endTag( "draw:text-box" );
277 m_rEmitContext.rEmitter.endTag( "draw:frame" );
278 }
279
visit(PolyPolyElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)280 void DrawXmlEmitter::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
281 {
282 elem.updateGeometry();
283 /* note:
284 * aw recommends using 100dth of mm in all respects since the xml import
285 * (a) is buggy (see issue 37213)
286 * (b) is optimized for 100dth of mm and does not scale itself then,
287 * this does not gain us speed but makes for smaller rounding errors since
288 * the xml importer coordinates are integer based
289 */
290 for (sal_uInt32 i = 0; i< elem.PolyPoly.count(); i++)
291 {
292 basegfx::B2DPolygon b2dPolygon = elem.PolyPoly.getB2DPolygon( i );
293
294 for ( sal_uInt32 j = 0; j< b2dPolygon.count(); j++ )
295 {
296 basegfx::B2DPoint point;
297 basegfx::B2DPoint nextPoint;
298 point = b2dPolygon.getB2DPoint( j );
299
300 basegfx::B2DPoint prevPoint = b2dPolygon.getPrevControlPoint( j ) ;
301
302 point.setX( convPx2mmPrec2( point.getX() )*100.0 );
303 point.setY( convPx2mmPrec2( point.getY() )*100.0 );
304
305 if ( b2dPolygon.isPrevControlPointUsed( j ) )
306 {
307 prevPoint.setX( convPx2mmPrec2( prevPoint.getX() )*100.0 );
308 prevPoint.setY( convPx2mmPrec2( prevPoint.getY() )*100.0 );
309 }
310
311 if ( b2dPolygon.isNextControlPointUsed( j ) )
312 {
313 nextPoint = b2dPolygon.getNextControlPoint( j ) ;
314 nextPoint.setX( convPx2mmPrec2( nextPoint.getX() )*100.0 );
315 nextPoint.setY( convPx2mmPrec2( nextPoint.getY() )*100.0 );
316 }
317
318 b2dPolygon.setB2DPoint( j, point );
319
320 if ( b2dPolygon.isPrevControlPointUsed( j ) )
321 b2dPolygon.setPrevControlPoint( j , prevPoint ) ;
322
323 if ( b2dPolygon.isNextControlPointUsed( j ) )
324 b2dPolygon.setNextControlPoint( j , nextPoint ) ;
325 }
326
327 elem.PolyPoly.setB2DPolygon( i, b2dPolygon );
328 }
329
330 PropertyMap aProps;
331 // PDFIProcessor transforms geometrical objects, not images and text
332 // so we need to tell fillFrameProps here that the transformation for
333 // a PolyPolyElement was already applied (aside from translation)
334 fillFrameProps( elem, aProps, m_rEmitContext, true );
335 aProps[ u"svg:viewBox"_ustr ] =
336 "0 0 "
337 + OUString::number( convPx2mmPrec2(elem.w)*100.0 )
338 + " "
339 + OUString::number( convPx2mmPrec2(elem.h)*100.0 );
340 aProps[ u"svg:d"_ustr ] = basegfx::utils::exportToSvgD( elem.PolyPoly, false, true, false );
341
342 m_rEmitContext.rEmitter.beginTag( "draw:path", aProps );
343 m_rEmitContext.rEmitter.endTag( "draw:path" );
344 }
345
visit(ImageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)346 void DrawXmlEmitter::visit( ImageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
347 {
348 PropertyMap aImageProps;
349 m_rEmitContext.rEmitter.beginTag( "draw:image", aImageProps );
350 m_rEmitContext.rEmitter.beginTag( "office:binary-data", PropertyMap() );
351 m_rEmitContext.rImages.writeBase64EncodedStream( elem.Image, m_rEmitContext);
352 m_rEmitContext.rEmitter.endTag( "office:binary-data" );
353 m_rEmitContext.rEmitter.endTag( "draw:image" );
354 }
355
visit(PageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)356 void DrawXmlEmitter::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
357 {
358 PropertyMap aPageProps;
359 aPageProps[ u"draw:master-page-name"_ustr ] = m_rEmitContext.rStyles.getStyleName( elem.StyleId );
360
361 m_rEmitContext.rEmitter.beginTag("draw:page", aPageProps);
362
363 if( m_rEmitContext.xStatusIndicator.is() )
364 m_rEmitContext.xStatusIndicator->setValue( elem.PageNumber );
365
366 auto this_it = elem.Children.begin();
367 while( this_it != elem.Children.end() && this_it->get() != &elem )
368 {
369 (*this_it)->visitedBy( *this, this_it );
370 ++this_it;
371 }
372
373 m_rEmitContext.rEmitter.endTag("draw:page");
374 }
375
visit(DocumentElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)376 void DrawXmlEmitter::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
377 {
378 m_rEmitContext.rEmitter.beginTag( "office:body", PropertyMap() );
379 m_rEmitContext.rEmitter.beginTag( m_bWriteDrawDocument ? "office:drawing" : "office:presentation",
380 PropertyMap() );
381
382 auto this_it = elem.Children.begin();
383 while( this_it != elem.Children.end() && this_it->get() != &elem )
384 {
385 (*this_it)->visitedBy( *this, this_it );
386 ++this_it;
387 }
388
389 m_rEmitContext.rEmitter.endTag( m_bWriteDrawDocument ? "office:drawing" : "office:presentation" );
390 m_rEmitContext.rEmitter.endTag( "office:body" );
391 }
392
393
visit(HyperlinkElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)394 void DrawXmlOptimizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
395 {
396 }
397
visit(TextElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)398 void DrawXmlOptimizer::visit( TextElement&, const std::list< std::unique_ptr<Element> >::const_iterator&)
399 {
400 }
401
visit(FrameElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)402 void DrawXmlOptimizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
403 {
404 elem.applyToChildren(*this);
405 }
406
visit(ImageElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)407 void DrawXmlOptimizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
408 {
409 }
410
visit(PolyPolyElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator & elemIt)411 void DrawXmlOptimizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& elemIt )
412 {
413 /* note: optimize two consecutive PolyPolyElements that
414 * have the same path but one of which is a stroke while
415 * the other is a fill
416 */
417 if( !elem.Parent )
418 return;
419
420 // find following PolyPolyElement in parent's children list
421 if( elemIt == elem.Parent->Children.end() )
422 return;
423 auto next_it = elemIt;
424 ++next_it;
425 if( next_it == elem.Parent->Children.end() )
426 return;
427
428 PolyPolyElement* pNext = dynamic_cast<PolyPolyElement*>(next_it->get());
429 // TODO(F2): this comparison fails for OOo-generated polygons with beziers.
430 if( !pNext || pNext->PolyPoly != elem.PolyPoly )
431 return;
432
433 const GraphicsContext& rNextGC =
434 m_rProcessor.getGraphicsContext( pNext->GCId );
435 const GraphicsContext& rThisGC =
436 m_rProcessor.getGraphicsContext( elem.GCId );
437
438 if( !(rThisGC.BlendMode == rNextGC.BlendMode &&
439 rThisGC.Flatness == rNextGC.Flatness &&
440 rThisGC.Transformation == rNextGC.Transformation &&
441 rThisGC.Clip == rNextGC.Clip &&
442 rThisGC.FillColor.Red == rNextGC.FillColor.Red &&
443 rThisGC.FillColor.Green== rNextGC.FillColor.Green &&
444 rThisGC.FillColor.Blue == rNextGC.FillColor.Blue &&
445 rThisGC.FillColor.Alpha== rNextGC.FillColor.Alpha &&
446 pNext->Action == PATH_STROKE &&
447 (elem.Action == PATH_FILL || elem.Action == PATH_EOFILL)) )
448 return;
449
450 GraphicsContext aGC = rThisGC;
451 aGC.LineJoin = rNextGC.LineJoin;
452 aGC.LineCap = rNextGC.LineCap;
453 aGC.LineWidth = rNextGC.LineWidth;
454 aGC.MiterLimit= rNextGC.MiterLimit;
455 aGC.DashArray = rNextGC.DashArray;
456 aGC.LineColor = rNextGC.LineColor;
457 elem.GCId = m_rProcessor.getGCId( aGC );
458
459 elem.Action |= pNext->Action;
460
461 elem.Children.splice( elem.Children.end(), pNext->Children );
462 elem.Parent->Children.erase(next_it);
463 }
464
visit(ParagraphElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)465 void DrawXmlOptimizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
466 {
467 optimizeTextElements( elem );
468
469 elem.applyToChildren(*this);
470 }
471
visit(PageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)472 void DrawXmlOptimizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
473 {
474 if( m_rProcessor.getStatusIndicator().is() )
475 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
476
477 // resolve hyperlinks
478 elem.resolveHyperlinks();
479
480 elem.resolveFontStyles( m_rProcessor ); // underlines and such
481
482 // FIXME: until hyperlinks and font effects are adjusted for
483 // geometrical search handle them before sorting
484 PDFIProcessor::sortElements( &elem );
485
486 // find paragraphs in text
487 ParagraphElement* pCurPara = nullptr;
488 std::list< std::unique_ptr<Element> >::iterator page_element, next_page_element;
489 next_page_element = elem.Children.begin();
490 double fCurLineHeight = 0.0; // average height of text items in current para
491 int nCurLineElements = 0; // number of line contributing elements in current para
492 double line_left = elem.w, line_right = 0.0;
493 double column_width = elem.w*0.75; // estimate text width
494 // TODO: guess columns
495 while( next_page_element != elem.Children.end() )
496 {
497 page_element = next_page_element++;
498 ParagraphElement* pPagePara = dynamic_cast<ParagraphElement*>(page_element->get());
499 if( pPagePara )
500 {
501 pCurPara = pPagePara;
502 // adjust line height and text items
503 fCurLineHeight = 0.0;
504 nCurLineElements = 0;
505 for( const auto& rxChild : pCurPara->Children )
506 {
507 TextElement* pTestText = rxChild->dynCastAsTextElement();
508 if( pTestText )
509 {
510 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pTestText->h)/double(nCurLineElements+1);
511 nCurLineElements++;
512 }
513 }
514 continue;
515 }
516
517 HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(page_element->get());
518 DrawElement* pDraw = dynamic_cast<DrawElement*>(page_element->get());
519 if( ! pDraw && pLink && ! pLink->Children.empty() )
520 pDraw = dynamic_cast<DrawElement*>(pLink->Children.front().get() );
521 if( pDraw )
522 {
523 // insert small drawing objects as character, else leave them page bound
524
525 bool bInsertToParagraph = false;
526 // first check if this is either inside the paragraph
527 if( pCurPara && pDraw->y < pCurPara->y + pCurPara->h )
528 {
529 if( pDraw->h < fCurLineHeight * 1.5 )
530 {
531 bInsertToParagraph = true;
532 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pDraw->h)/double(nCurLineElements+1);
533 nCurLineElements++;
534 // mark draw element as character
535 pDraw->isCharacter = true;
536 }
537 }
538 // or perhaps the draw element begins a new paragraph
539 else if( next_page_element != elem.Children.end() )
540 {
541 TextElement* pText = (*next_page_element)->dynCastAsTextElement();
542 if( ! pText )
543 {
544 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(next_page_element->get());
545 if( pPara && ! pPara->Children.empty() )
546 pText = pPara->Children.front()->dynCastAsTextElement();
547 }
548 if( pText && // check there is a text
549 pDraw->h < pText->h*1.5 && // and it is approx the same height
550 // and either upper or lower edge of pDraw is inside text's vertical range
551 ( ( pDraw->y >= pText->y && pDraw->y <= pText->y+pText->h ) ||
552 ( pDraw->y+pDraw->h >= pText->y && pDraw->y+pDraw->h <= pText->y+pText->h )
553 )
554 )
555 {
556 bInsertToParagraph = true;
557 fCurLineHeight = pDraw->h;
558 nCurLineElements = 1;
559 line_left = pDraw->x;
560 line_right = pDraw->x + pDraw->w;
561 // begin a new paragraph
562 pCurPara = nullptr;
563 // mark draw element as character
564 pDraw->isCharacter = true;
565 }
566 }
567
568 if( ! bInsertToParagraph )
569 {
570 pCurPara = nullptr;
571 continue;
572 }
573 }
574
575 TextElement* pText = (*page_element)->dynCastAsTextElement();
576 if( ! pText && pLink && ! pLink->Children.empty() )
577 pText = pLink->Children.front()->dynCastAsTextElement();
578 if( pText )
579 {
580 Element* pGeo = pLink ? static_cast<Element*>(pLink) :
581 static_cast<Element*>(pText);
582 if( pCurPara )
583 {
584 // there was already a text element, check for a new paragraph
585 if( nCurLineElements > 0 )
586 {
587 // if the new text is significantly distant from the paragraph
588 // begin a new paragraph
589 if( pGeo->y > pCurPara->y + pCurPara->h + fCurLineHeight*0.5 )
590 pCurPara = nullptr; // insert new paragraph
591 else if( pGeo->y > (pCurPara->y+pCurPara->h - fCurLineHeight*0.05) )
592 {
593 // new paragraph if either the last line of the paragraph
594 // was significantly shorter than the paragraph as a whole
595 if( (line_right - line_left) < pCurPara->w*0.75 )
596 pCurPara = nullptr;
597 // or the last line was significantly smaller than the column width
598 else if( (line_right - line_left) < column_width*0.75 )
599 pCurPara = nullptr;
600 }
601 }
602
603
604 }
605
606
607 // update line height/width
608 if( pCurPara )
609 {
610 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pGeo->h)/double(nCurLineElements+1);
611 nCurLineElements++;
612 if( pGeo->x < line_left )
613 line_left = pGeo->x;
614 if( pGeo->x+pGeo->w > line_right )
615 line_right = pGeo->x+pGeo->w;
616 }
617 else
618 {
619 fCurLineHeight = pGeo->h;
620 nCurLineElements = 1;
621 line_left = pGeo->x;
622 line_right = pGeo->x + pGeo->w;
623 }
624 }
625
626
627 // move element to current paragraph
628 if (! pCurPara ) // new paragraph, insert one
629 {
630 pCurPara = ElementFactory::createParagraphElement( nullptr );
631 assert(pCurPara);
632 // set parent
633 pCurPara->Parent = &elem;
634 //insert new paragraph before current element
635 page_element = elem.Children.insert( page_element, std::unique_ptr<Element>(pCurPara) );
636 // forward iterator to current element again
637 ++ page_element;
638 // update next_element which is now invalid
639 next_page_element = page_element;
640 ++ next_page_element;
641 }
642 Element* pCurEle = page_element->get();
643 Element::setParent( page_element, pCurPara );
644 OSL_ENSURE( !pText || pCurEle == pText || pCurEle == pLink, "paragraph child list in disorder" );
645 if( pText || pDraw )
646 pCurPara->updateGeometryWith( pCurEle );
647 }
648
649 // process children
650 elem.applyToChildren(*this);
651 }
652
isSpaces(TextElement * pTextElem)653 static bool isSpaces(TextElement* pTextElem)
654 {
655 for (sal_Int32 i = 0; i != pTextElem->Text.getLength(); ++i) {
656 if (pTextElem->Text[i] != ' ') {
657 return false;
658 }
659 }
660 return true;
661 }
662
optimizeTextElements(Element & rParent)663 void DrawXmlOptimizer::optimizeTextElements(Element& rParent)
664 {
665 if( rParent.Children.empty() ) // this should not happen
666 {
667 OSL_FAIL( "empty paragraph optimized" );
668 return;
669 }
670
671 // concatenate child elements with same font id
672 auto next = rParent.Children.begin();
673 auto it = next++;
674
675 while( next != rParent.Children.end() )
676 {
677 bool bConcat = false;
678 TextElement* pCur = (*it)->dynCastAsTextElement();
679
680 if( pCur )
681 {
682 TextElement* pNext = (*next)->dynCastAsTextElement();
683 OUString str;
684 bool bPara = strspn("ParagraphElement", typeid(rParent).name());
685 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(&rParent);
686 if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
687 pPara->bRtl = true;
688 if( pNext )
689 {
690 const GraphicsContext& rCurGC = m_rProcessor.getGraphicsContext( pCur->GCId );
691 const GraphicsContext& rNextGC = m_rProcessor.getGraphicsContext( pNext->GCId );
692
693 // line and space optimization; works only in strictly horizontal mode
694
695 // concatenate consecutive text elements unless there is a
696 // font or text color change, leave a new span in that case
697 if( (pCur->FontId == pNext->FontId || isSpaces(pNext)) &&
698 rCurGC.FillColor.Red == rNextGC.FillColor.Red &&
699 rCurGC.FillColor.Green == rNextGC.FillColor.Green &&
700 rCurGC.FillColor.Blue == rNextGC.FillColor.Blue &&
701 rCurGC.FillColor.Alpha == rNextGC.FillColor.Alpha
702 )
703 {
704 pCur->updateGeometryWith( pNext );
705 if (pPara && pPara->bRtl)
706 {
707 // Tdf#152083: If RTL, reverse the text in pNext so that its correct order is
708 // restored when the combined text is reversed in DrawXmlEmitter::visit.
709 OUString tempStr;
710 bool bNeedReverse=false;
711 str = pNext->Text.toString();
712 for (sal_Int32 i=0; i < str.getLength(); i++)
713 {
714 if (str[i] == u' ')
715 { // Space char (e.g. the space as in " م") needs special treatment.
716 // First, append the space char to pCur.
717 pCur->Text.append(OUStringChar(str[i]));
718 // Then, check whether the tmpStr needs reverse, if so then reverse and append.
719 if (bNeedReverse)
720 {
721 tempStr = ::comphelper::string::reverseCodePoints(tempStr);
722 pCur->Text.append(tempStr);
723 tempStr = u""_ustr;
724 }
725 bNeedReverse = false;
726 }
727 else
728 {
729 tempStr += OUStringChar(str[i]);
730 bNeedReverse = true;
731 }
732 }
733 // Do the last append
734 if (bNeedReverse)
735 {
736 tempStr = ::comphelper::string::reverseCodePoints(tempStr);
737 pCur->Text.append(tempStr);
738 }
739 else
740 {
741 pCur->Text.append(tempStr);
742 }
743 }
744 else
745 {
746 // append text to current element directly without reverse
747 pCur->Text.append( pNext->Text );
748 }
749
750 if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
751 pPara->bRtl = true;
752 // append eventual children to current element
753 // and clear children (else the children just
754 // appended to pCur would be destroyed)
755 pCur->Children.splice( pCur->Children.end(), pNext->Children );
756 // get rid of the now useless element
757 rParent.Children.erase( next );
758 bConcat = true;
759 }
760 }
761 }
762 else if( dynamic_cast<HyperlinkElement*>(it->get()) )
763 optimizeTextElements( **it );
764 if ( bConcat )
765 next = it;
766 else
767 ++it;
768 ++next;
769 }
770 }
771
visit(DocumentElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)772 void DrawXmlOptimizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
773 {
774 elem.applyToChildren(*this);
775 }
776
777
visit(PolyPolyElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)778 void DrawXmlFinalizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
779 {
780 // xxx TODO copied from DrawElement
781 const GraphicsContext& rGC = m_rProcessor.getGraphicsContext(elem.GCId );
782
783 PropertyMap aProps;
784 aProps[ u"style:family"_ustr ] = "graphic";
785 aProps[ u"style:parent-style-name"_ustr ] = "standard";
786 // generate standard graphic style if necessary
787 m_rStyleContainer.getStandardStyleId( "graphic" );
788
789 PropertyMap aGCProps;
790 if (elem.Action & PATH_STROKE)
791 {
792 double scale = GetAverageTransformationScale(rGC.Transformation);
793 if (rGC.DashArray.size() < 2)
794 {
795 aGCProps[ u"draw:stroke"_ustr ] = "solid";
796 }
797 else
798 {
799 PropertyMap props;
800 FillDashStyleProps(props, rGC.DashArray, scale);
801 StyleContainer::Style style("draw:stroke-dash"_ostr, std::move(props));
802
803 aGCProps[ u"draw:stroke"_ustr ] = "dash";
804 aGCProps[ u"draw:stroke-dash"_ustr ] =
805 m_rStyleContainer.getStyleName(
806 m_rStyleContainer.getStyleId(style));
807 }
808
809 aGCProps[ u"svg:stroke-color"_ustr ] = getColorString(rGC.LineColor);
810 if (rGC.LineColor.Alpha != 1.0)
811 aGCProps[u"svg:stroke-opacity"_ustr] = getPercentString(rGC.LineColor.Alpha * 100.0);
812 aGCProps[ u"svg:stroke-width"_ustr ] = convertPixelToUnitString(rGC.LineWidth * scale);
813 aGCProps[ u"draw:stroke-linejoin"_ustr ] = rGC.GetLineJoinString();
814 aGCProps[ u"svg:stroke-linecap"_ustr ] = rGC.GetLineCapString();
815 }
816 else
817 {
818 aGCProps[ u"draw:stroke"_ustr ] = "none";
819 }
820
821 if (elem.FillImage != -1)
822 {
823 PropertyMap props;
824 // The image isn't actually in a prop, it's in an extra chunk inside.
825 StyleContainer::Style style("draw:fill-image"_ostr, std::move(props));
826 style.Contents = m_rProcessor.getImages().asBase64EncodedString(elem.FillImage);
827 aGCProps[ u"draw:fill-image-name"_ustr ] =
828 m_rStyleContainer.getStyleName(
829 m_rStyleContainer.getStyleId(style));
830 aGCProps[ u"draw:fill-image-width"_ustr ] = unitMMString(convPx2mm(elem.TileWidth));
831 aGCProps[ u"draw:fill-image-height"_ustr ] = unitMMString(convPx2mm(elem.TileHeight));
832
833 }
834
835 // TODO(F1): check whether stuff could be emulated by gradient/bitmap/hatch
836 if( elem.Action & (PATH_FILL | PATH_EOFILL) )
837 {
838 if (elem.FillImage == -1)
839 {
840 aGCProps[ u"draw:fill"_ustr ] = "solid";
841 }
842 else
843 {
844 aGCProps[ u"draw:fill"_ustr ] = "bitmap";
845 }
846 aGCProps[ u"draw:fill-color"_ustr ] = getColorString(rGC.FillColor);
847 if (rGC.FillColor.Alpha != 1.0)
848 aGCProps[u"draw:opacity"_ustr] = getPercentString(rGC.FillColor.Alpha * 100.0);
849 }
850 else
851 {
852 aGCProps[ u"draw:fill"_ustr ] = "none";
853 }
854
855 StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
856 StyleContainer::Style aSubStyle( "style:graphic-properties"_ostr, std::move(aGCProps) );
857 aStyle.SubStyles.push_back( &aSubStyle );
858
859 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
860 }
861
visit(HyperlinkElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)862 void DrawXmlFinalizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
863 {
864 }
865
SetFontsizeProperties(PropertyMap & props,double fontSize)866 static void SetFontsizeProperties(PropertyMap& props, double fontSize)
867 {
868 OUString aFSize = OUString::number(fontSize * 72 / PDFI_OUTDEV_RESOLUTION) + "pt";
869 props[u"fo:font-size"_ustr] = aFSize;
870 props[u"style:font-size-asian"_ustr] = aFSize;
871 props[u"style:font-size-complex"_ustr] = aFSize;
872 }
873
visit(TextElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)874 void DrawXmlFinalizer::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
875 {
876 const FontAttributes& rFont = m_rProcessor.getFont( elem.FontId );
877 PropertyMap aProps;
878 aProps[ u"style:family"_ustr ] = "text";
879
880 PropertyMap aFontProps;
881
882 // family name
883 // TODO: tdf#143095: use system font name rather than PSName
884 SAL_INFO("sdext.pdfimport", "The font used in xml is: " << rFont.familyName);
885 aFontProps[ u"fo:font-family"_ustr ] = rFont.familyName;
886 aFontProps[ u"style:font-family-asian"_ustr ] = rFont.familyName;
887 aFontProps[ u"style:font-family-complex"_ustr ] = rFont.familyName;
888
889 // bold
890 aFontProps[ u"fo:font-weight"_ustr ] = rFont.fontWeight;
891 aFontProps[ u"style:font-weight-asian"_ustr ] = rFont.fontWeight;
892 aFontProps[ u"style:font-weight-complex"_ustr ] = rFont.fontWeight;
893
894 // italic
895 if( rFont.isItalic )
896 {
897 aFontProps[ u"fo:font-style"_ustr ] = "italic";
898 aFontProps[ u"style:font-style-asian"_ustr ] = "italic";
899 aFontProps[ u"style:font-style-complex"_ustr ] = "italic";
900 }
901
902 // underline
903 if( rFont.isUnderline )
904 {
905 aFontProps[ u"style:text-underline-style"_ustr ] = "solid";
906 aFontProps[ u"style:text-underline-width"_ustr ] = "auto";
907 aFontProps[ u"style:text-underline-color"_ustr ] = "font-color";
908 }
909
910 // outline
911 if( rFont.isOutline )
912 aFontProps[ u"style:text-outline"_ustr ] = "true";
913
914 // size
915 SetFontsizeProperties(aFontProps, rFont.size);
916
917 // color
918 const GraphicsContext& rGC = m_rProcessor.getGraphicsContext( elem.GCId );
919 aFontProps[ u"fo:color"_ustr ] = getColorString( rFont.isOutline ? rGC.LineColor : rGC.FillColor );
920
921 // scale
922 double fRotate, fShearX;
923 basegfx::B2DTuple aScale, aTranslation;
924 rGC.Transformation.decompose(aScale, aTranslation, fRotate, fShearX);
925 double textScale = 100 * aScale.getX() / aScale.getY();
926 if (((textScale >= 1) && (textScale <= 99)) ||
927 ((textScale >= 101) && (textScale <= 999)))
928 {
929 aFontProps[ u"style:text-scale"_ustr ] = getPercentString(textScale);
930 }
931
932 StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
933 StyleContainer::Style aSubStyle( "style:text-properties"_ostr, std::move(aFontProps) );
934 aStyle.SubStyles.push_back( &aSubStyle );
935 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
936 }
937
visit(ParagraphElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)938 void DrawXmlFinalizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
939 {
940
941 PropertyMap aProps;
942 aProps[ u"style:family"_ustr ] = "paragraph";
943 // generate standard paragraph style if necessary
944 m_rStyleContainer.getStandardStyleId( "paragraph" );
945
946 PropertyMap aParProps;
947
948 aParProps[ u"fo:text-align"_ustr] = "start";
949 if (elem.bRtl)
950 aParProps[ u"style:writing-mode"_ustr] = "rl-tb";
951 else
952 aParProps[ u"style:writing-mode"_ustr] = "lr-tb";
953
954 StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
955 StyleContainer::Style aSubStyle( "style:paragraph-properties"_ostr, std::move(aParProps) );
956 aStyle.SubStyles.push_back( &aSubStyle );
957
958 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
959
960 elem.applyToChildren(*this);
961 }
962
visit(FrameElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)963 void DrawXmlFinalizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
964 {
965 PropertyMap props1;
966 props1[ u"style:family"_ustr ] = "graphic";
967 props1[ u"style:parent-style-name"_ustr ] = "standard";
968 // generate standard graphic style if necessary
969 m_rStyleContainer.getStandardStyleId( "graphic" );
970
971 PropertyMap aGCProps;
972
973 aGCProps[ u"draw:stroke"_ustr ] = "none";
974 aGCProps[ u"draw:fill"_ustr ] = "none";
975 aGCProps[ u"draw:auto-grow-height"_ustr ] = "true";
976 aGCProps[ u"draw:auto-grow-width"_ustr ] = "true";
977 aGCProps[ u"draw:textarea-horizontal-align"_ustr ] = "left";
978 aGCProps[ u"draw:textarea-vertical-align"_ustr ] = "top";
979 aGCProps[ u"fo:min-height"_ustr] = "0cm";
980 aGCProps[ u"fo:min-width"_ustr] = "0cm";
981 aGCProps[ u"fo:padding-top"_ustr ] = "0cm";
982 aGCProps[ u"fo:padding-left"_ustr ] = "0cm";
983 aGCProps[ u"fo:padding-right"_ustr ] = "0cm";
984 aGCProps[ u"fo:padding-bottom"_ustr ] = "0cm";
985
986 StyleContainer::Style style1( "style:style"_ostr, std::move(props1) );
987 StyleContainer::Style subStyle1( "style:graphic-properties"_ostr, std::move(aGCProps) );
988 style1.SubStyles.push_back(&subStyle1);
989
990 elem.StyleId = m_rStyleContainer.getStyleId(style1);
991
992 if (elem.IsForText)
993 {
994 PropertyMap props2;
995 props2[u"style:family"_ustr] = "paragraph";
996
997 PropertyMap textProps;
998 SetFontsizeProperties(textProps, elem.FontSize);
999
1000 StyleContainer::Style style2("style:style"_ostr, std::move(props2));
1001 StyleContainer::Style subStyle2("style:text-properties"_ostr, std::move(textProps));
1002 style2.SubStyles.push_back(&subStyle2);
1003 elem.TextStyleId = m_rStyleContainer.getStyleId(style2);
1004 }
1005
1006 elem.applyToChildren(*this);
1007 }
1008
visit(ImageElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)1009 void DrawXmlFinalizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1010 {
1011 }
1012
visit(PageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1013 void DrawXmlFinalizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1014 {
1015 if( m_rProcessor.getStatusIndicator().is() )
1016 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
1017
1018 // transform from pixel to mm
1019 double page_width = convPx2mm( elem.w ), page_height = convPx2mm( elem.h );
1020
1021 // calculate page margins out of the relevant children (paragraphs)
1022 elem.TopMargin = elem.h;
1023 elem.BottomMargin = 0;
1024 elem.LeftMargin = elem.w;
1025 elem.RightMargin = 0;
1026
1027 for( const auto& rxChild : elem.Children )
1028 {
1029 if( rxChild->x < elem.LeftMargin )
1030 elem.LeftMargin = rxChild->x;
1031 if( rxChild->y < elem.TopMargin )
1032 elem.TopMargin = rxChild->y;
1033 if( rxChild->x + rxChild->w > elem.RightMargin )
1034 elem.RightMargin = (rxChild->x + rxChild->w);
1035 if( rxChild->y + rxChild->h > elem.BottomMargin )
1036 elem.BottomMargin = (rxChild->y + rxChild->h);
1037 }
1038
1039 // transform margins to mm
1040 double left_margin = convPx2mm( elem.LeftMargin );
1041 double right_margin = convPx2mm( elem.RightMargin );
1042 double top_margin = convPx2mm( elem.TopMargin );
1043 double bottom_margin = convPx2mm( elem.BottomMargin );
1044
1045 // round left/top margin to nearest mm
1046 left_margin = rtl_math_round( left_margin, 0, rtl_math_RoundingMode_Floor );
1047 top_margin = rtl_math_round( top_margin, 0, rtl_math_RoundingMode_Floor );
1048 // round (fuzzy) right/bottom margin to nearest cm
1049 right_margin = rtl_math_round( right_margin, right_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1050 bottom_margin = rtl_math_round( bottom_margin, bottom_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1051
1052 // set reasonable default in case of way too large margins
1053 // e.g. no paragraph case
1054 if( left_margin > page_width/2.0 - 10 )
1055 left_margin = 10;
1056 if( right_margin > page_width/2.0 - 10 )
1057 right_margin = 10;
1058 if( top_margin > page_height/2.0 - 10 )
1059 top_margin = 10;
1060 if( bottom_margin > page_height/2.0 - 10 )
1061 bottom_margin = 10;
1062
1063 // catch the weird cases
1064 if( left_margin < 0 )
1065 left_margin = 0;
1066 if( right_margin < 0 )
1067 right_margin = 0;
1068 if( top_margin < 0 )
1069 top_margin = 0;
1070 if( bottom_margin < 0 )
1071 bottom_margin = 0;
1072
1073 // widely differing margins are unlikely to be correct
1074 if( right_margin > left_margin*1.5 )
1075 right_margin = left_margin;
1076
1077 elem.LeftMargin = convmm2Px( left_margin );
1078 elem.RightMargin = convmm2Px( right_margin );
1079 elem.TopMargin = convmm2Px( top_margin );
1080 elem.BottomMargin = convmm2Px( bottom_margin );
1081
1082 // get styles for paragraphs
1083 PropertyMap aPageProps;
1084 PropertyMap aPageLayoutProps;
1085 aPageLayoutProps[ u"fo:margin-top"_ustr ] = unitMMString( top_margin );
1086 aPageLayoutProps[ u"fo:margin-bottom"_ustr ] = unitMMString( bottom_margin );
1087 aPageLayoutProps[ u"fo:margin-left"_ustr ] = unitMMString( left_margin );
1088 aPageLayoutProps[ u"fo:margin-right"_ustr ] = unitMMString( right_margin );
1089 aPageLayoutProps[ u"fo:page-width"_ustr ] = unitMMString( page_width );
1090 aPageLayoutProps[ u"fo:page-height"_ustr ] = unitMMString( page_height );
1091 aPageLayoutProps[ u"style:print-orientation"_ustr ]= elem.w < elem.h ? std::u16string_view(u"portrait") : std::u16string_view(u"landscape");
1092 aPageLayoutProps[ u"style:writing-mode"_ustr ]= "lr-tb";
1093
1094 StyleContainer::Style aStyle( "style:page-layout"_ostr, std::move(aPageProps));
1095 StyleContainer::Style aSubStyle( "style:page-layout-properties"_ostr, std::move(aPageLayoutProps));
1096 aStyle.SubStyles.push_back(&aSubStyle);
1097 sal_Int32 nPageStyle = m_rStyleContainer.impl_getStyleId( aStyle, false );
1098
1099 // create master page
1100 OUString aMasterPageLayoutName = m_rStyleContainer.getStyleName( nPageStyle );
1101 aPageProps[ u"style:page-layout-name"_ustr ] = aMasterPageLayoutName;
1102
1103 StyleContainer::Style aMPStyle( "style:master-page"_ostr, std::move(aPageProps));
1104
1105 elem.StyleId = m_rStyleContainer.impl_getStyleId( aMPStyle,false );
1106
1107 // create styles for children
1108 elem.applyToChildren(*this);
1109 }
1110
visit(DocumentElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1111 void DrawXmlFinalizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1112 {
1113 elem.applyToChildren(*this);
1114 }
1115
1116 }
1117
1118 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1119