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