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 <sal/config.h>
21 #include <sal/log.hxx>
22 #include <string_view>
23 
24 #include <pdfiprocessor.hxx>
25 #include <xmlemitter.hxx>
26 #include <pdfihelper.hxx>
27 #include <imagecontainer.hxx>
28 #include "style.hxx"
29 #include "writertreevisiting.hxx"
30 #include <genericelements.hxx>
31 
32 #include <basegfx/polygon/b2dpolypolygontools.hxx>
33 #include <osl/diagnose.h>
34 #include <com/sun/star/i18n/CharacterClassification.hpp>
35 #include <com/sun/star/i18n/DirectionProperty.hpp>
36 #include <comphelper/string.hxx>
37 
38 using namespace ::com::sun::star;
39 using namespace ::com::sun::star::lang;
40 using namespace ::com::sun::star::i18n;
41 using namespace ::com::sun::star::uno;
42 
43 namespace pdfi
44 {
45 
GetBreakIterator()46 const Reference<XBreakIterator>& WriterXmlOptimizer::GetBreakIterator()
47 {
48     if (!mxBreakIter.is())
49     {
50         mxBreakIter = BreakIterator::create(m_rProcessor.m_xContext);
51     }
52     return mxBreakIter;
53 }
54 
GetCharacterClassification()55 const Reference< XCharacterClassification >& WriterXmlEmitter::GetCharacterClassification()
56 {
57     if ( !mxCharClass.is() )
58     {
59         Reference< XComponentContext > xContext( m_rEmitContext.m_xContext, uno::UNO_SET_THROW );
60         mxCharClass = CharacterClassification::create(xContext);
61     }
62     return mxCharClass;
63 }
64 
visit(HyperlinkElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)65 void WriterXmlEmitter::visit( HyperlinkElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&   )
66 {
67     if( elem.Children.empty() )
68         return;
69 
70     const char* pType = dynamic_cast<DrawElement*>(elem.Children.front().get()) ? "draw:a" : "text:a";
71 
72     PropertyMap aProps;
73     aProps[ u"xlink:type"_ustr ] = "simple";
74     aProps[ u"xlink:href"_ustr ] = elem.URI;
75     aProps[ u"office:target-frame-name"_ustr ] = "_blank";
76     aProps[ u"xlink:show"_ustr ] = "new";
77 
78     m_rEmitContext.rEmitter.beginTag( pType, aProps );
79     auto this_it = elem.Children.begin();
80     while( this_it != elem.Children.end() && this_it->get() != &elem )
81     {
82         (*this_it)->visitedBy( *this, this_it );
83         ++this_it;
84     }
85     m_rEmitContext.rEmitter.endTag( pType );
86 }
87 
visit(TextElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)88 void WriterXmlEmitter::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&   )
89 {
90     if( elem.Text.isEmpty() )
91         return;
92 
93     PropertyMap aProps = {};
94     const sal_Unicode strSpace = 0x0020;
95     const sal_Unicode strNbSpace = 0x00A0;
96     const sal_Unicode tabSpace = 0x0009;
97 
98     if( elem.StyleId != -1 )
99     {
100         aProps[ u"text:style-name"_ustr ] =
101             m_rEmitContext.rStyles.getStyleName( elem.StyleId );
102     }
103 
104     OUString str(elem.Text.toString());
105 
106     // Check for RTL
107     bool isRTL = false;
108     Reference< i18n::XCharacterClassification > xCC( GetCharacterClassification() );
109     if( xCC.is() )
110     {
111         for(int i=1; i< elem.Text.getLength(); i++)
112         {
113             i18n::DirectionProperty nType = static_cast<i18n::DirectionProperty>(xCC->getCharacterDirection( str, i ));
114             if ( nType == i18n::DirectionProperty_RIGHT_TO_LEFT           ||
115                  nType == i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC    ||
116                  nType == i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING ||
117                  nType == i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE
118                 )
119                 isRTL = true;
120         }
121     }
122 
123     if (isRTL)  // If so, reverse string
124     {
125         // First, produce mirrored-image for each code point which has the Bidi_Mirrored property.
126         str = PDFIProcessor::SubstituteBidiMirrored(str);
127         // Then, reverse the code points in the string, in backward order.
128         str = ::comphelper::string::reverseCodePoints(str);
129     }
130 
131     m_rEmitContext.rEmitter.beginTag( "text:span", aProps );
132 
133     sal_Unicode strToken;
134     for (int i = 0; i < elem.Text.getLength(); i++)
135     {
136         strToken = str[i];
137         if (strToken == strSpace || strToken == strNbSpace)
138         {
139             aProps[u"text:c"_ustr] = "1";
140             m_rEmitContext.rEmitter.beginTag("text:s", aProps);
141             m_rEmitContext.rEmitter.endTag("text:s");
142         }
143         else if (strToken == tabSpace)
144         {
145             m_rEmitContext.rEmitter.beginTag("text:tab", aProps);
146             m_rEmitContext.rEmitter.endTag("text:tab");
147         }
148         else
149             m_rEmitContext.rEmitter.write(OUString(strToken));
150     }
151 
152     auto this_it = elem.Children.begin();
153     while( this_it != elem.Children.end() && this_it->get() != &elem )
154     {
155         (*this_it)->visitedBy( *this, this_it );
156         ++this_it;
157     }
158 
159     m_rEmitContext.rEmitter.endTag( "text:span" );
160 }
161 
visit(ParagraphElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)162 void WriterXmlEmitter::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&   )
163 {
164     PropertyMap aProps;
165     if( elem.StyleId != -1 )
166     {
167         aProps[ u"text:style-name"_ustr ] = m_rEmitContext.rStyles.getStyleName( elem.StyleId );
168     }
169     const char* pTagType = "text:p";
170     if( elem.Type == ParagraphElement::Headline )
171         pTagType = "text:h";
172     m_rEmitContext.rEmitter.beginTag( pTagType, aProps );
173 
174     auto this_it = elem.Children.begin();
175     while( this_it != elem.Children.end() && this_it->get() != &elem )
176     {
177         (*this_it)->visitedBy( *this, this_it );
178         ++this_it;
179     }
180 
181     m_rEmitContext.rEmitter.endTag( pTagType );
182 }
183 
fillFrameProps(DrawElement & rElem,PropertyMap & rProps,const EmitContext & rEmitContext)184 void WriterXmlEmitter::fillFrameProps( DrawElement&       rElem,
185                                        PropertyMap&       rProps,
186                                        const EmitContext& rEmitContext )
187 {
188     double rel_x = rElem.x, rel_y = rElem.y;
189 
190     // find anchor type by recursing though parents
191     Element* pAnchor = &rElem;
192     ParagraphElement* pParaElt = nullptr;
193     PageElement* pPage = nullptr;
194     while ((pAnchor = pAnchor->Parent))
195     {
196         if ((pParaElt = dynamic_cast<ParagraphElement*>(pAnchor)))
197             break;
198         if ((pPage = dynamic_cast<PageElement*>(pAnchor)))
199             break;
200     }
201     if( pAnchor )
202     {
203         if (pParaElt)
204         {
205             rProps[ u"text:anchor-type"_ustr ] = rElem.isCharacter
206                 ? std::u16string_view(u"character") : std::u16string_view(u"paragraph");
207         }
208         else
209         {
210             assert(pPage); // guaranteed by the while loop above
211             rProps[ u"text:anchor-type"_ustr ] = "page";
212             rProps[ u"text:anchor-page-number"_ustr ] = OUString::number(pPage->PageNumber);
213         }
214         rel_x -= pAnchor->x;
215         rel_y -= pAnchor->y;
216     }
217 
218     rProps[ u"draw:z-index"_ustr ] = OUString::number( rElem.ZOrder );
219     rProps[ u"draw:style-name"_ustr] = rEmitContext.rStyles.getStyleName( rElem.StyleId );
220     rProps[ u"svg:width"_ustr ]   = convertPixelToUnitString( rElem.w );
221     rProps[ u"svg:height"_ustr ]  = convertPixelToUnitString( rElem.h );
222 
223     const GraphicsContext& rGC =
224         rEmitContext.rProcessor.getGraphicsContext( rElem.GCId );
225     if( rGC.Transformation.isIdentity() )
226     {
227         if( !rElem.isCharacter )
228         {
229             rProps[ u"svg:x"_ustr ]       = convertPixelToUnitString( rel_x );
230             rProps[ u"svg:y"_ustr ]       = convertPixelToUnitString( rel_y );
231         }
232     }
233     else
234     {
235         basegfx::B2DTuple aScale, aTranslation;
236         double fRotate, fShearX;
237 
238         rGC.Transformation.decompose( aScale, aTranslation, fRotate, fShearX );
239 
240         OUStringBuffer aBuf( 256 );
241 
242         // TODO(F2): general transformation case missing; if implemented, note
243         // that ODF rotation is oriented the other way
244 
245         // build transformation string
246         if (rElem.MirrorVertical)
247         {
248             // At some point, rElem.h may start arriving positive,
249             // so use robust adjusting math
250             rel_y -= std::abs(rElem.h);
251             if (!aBuf.isEmpty())
252                 aBuf.append(' ');
253             aBuf.append("scale( 1.0 -1.0 )");
254         }
255         if( fShearX != 0.0 )
256         {
257             aBuf.append( "skewX( " + OUString::number(fShearX) + " )" );
258         }
259         if( fRotate != 0.0 )
260         {
261             if( !aBuf.isEmpty() )
262                 aBuf.append( ' ' );
263             aBuf.append( "rotate( " + OUString::number(-fRotate) + " )" );
264 
265         }
266         if( ! rElem.isCharacter )
267         {
268             if( !aBuf.isEmpty() )
269                 aBuf.append( ' ' );
270             aBuf.append( "translate( "
271                 + convertPixelToUnitString( rel_x )
272                 + " "
273                 + convertPixelToUnitString( rel_y )
274                 + " )" );
275         }
276 
277         rProps[ u"draw:transform"_ustr ] = aBuf.makeStringAndClear();
278     }
279 }
280 
visit(FrameElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)281 void WriterXmlEmitter::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&   )
282 {
283     if( elem.Children.empty() )
284         return;
285 
286     bool bTextBox = (dynamic_cast<ParagraphElement*>(elem.Children.front().get()) != nullptr);
287     PropertyMap aFrameProps;
288     fillFrameProps( elem, aFrameProps, m_rEmitContext );
289     m_rEmitContext.rEmitter.beginTag( "draw:frame", aFrameProps );
290     if( bTextBox )
291         m_rEmitContext.rEmitter.beginTag( "draw:text-box", PropertyMap() );
292 
293     auto this_it = elem.Children.begin();
294     while( this_it != elem.Children.end() && this_it->get() != &elem )
295     {
296         (*this_it)->visitedBy( *this, this_it );
297         ++this_it;
298     }
299 
300     if( bTextBox )
301         m_rEmitContext.rEmitter.endTag( "draw:text-box" );
302     m_rEmitContext.rEmitter.endTag( "draw:frame" );
303 }
304 
visit(PolyPolyElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)305 void WriterXmlEmitter::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
306 {
307     elem.updateGeometry();
308     /* note:
309      *   aw recommends using 100dth of mm in all respects since the xml import
310      *   (a) is buggy (see issue 37213)
311      *   (b) is optimized for 100dth of mm and does not scale itself then,
312      *       this does not gain us speed but makes for smaller rounding errors since
313      *       the xml importer coordinates are integer based
314      */
315     for (sal_uInt32 i = 0; i< elem.PolyPoly.count(); i++)
316     {
317         basegfx::B2DPolygon b2dPolygon =  elem.PolyPoly.getB2DPolygon( i );
318 
319         for ( sal_uInt32 j = 0; j< b2dPolygon.count(); j++ )
320         {
321             basegfx::B2DPoint point;
322             basegfx::B2DPoint nextPoint;
323             point = b2dPolygon.getB2DPoint( j );
324 
325             basegfx::B2DPoint prevPoint = b2dPolygon.getPrevControlPoint( j ) ;
326 
327             point.setX( convPx2mmPrec2( point.getX() )*100.0 );
328             point.setY( convPx2mmPrec2( point.getY() )*100.0 );
329 
330             if ( b2dPolygon.isPrevControlPointUsed( j ) )
331             {
332                 prevPoint.setX( convPx2mmPrec2( prevPoint.getX() )*100.0 );
333                 prevPoint.setY( convPx2mmPrec2( prevPoint.getY() )*100.0 );
334             }
335 
336             if ( b2dPolygon.isNextControlPointUsed( j ) )
337             {
338                 nextPoint = b2dPolygon.getNextControlPoint( j ) ;
339                 nextPoint.setX( convPx2mmPrec2( nextPoint.getX() )*100.0 );
340                 nextPoint.setY( convPx2mmPrec2( nextPoint.getY() )*100.0 );
341             }
342 
343             b2dPolygon.setB2DPoint( j, point );
344 
345             if ( b2dPolygon.isPrevControlPointUsed( j ) )
346                 b2dPolygon.setPrevControlPoint( j , prevPoint ) ;
347 
348             if ( b2dPolygon.isNextControlPointUsed( j ) )
349                 b2dPolygon.setNextControlPoint( j , nextPoint ) ;
350         }
351 
352         elem.PolyPoly.setB2DPolygon( i, b2dPolygon );
353     }
354 
355     PropertyMap aProps;
356     fillFrameProps( elem, aProps, m_rEmitContext );
357     aProps[ u"svg:viewBox"_ustr ] =
358         "0 0 "
359         + OUString::number(convPx2mmPrec2(elem.w)*100.0)
360         + " "
361         + OUString::number( convPx2mmPrec2(elem.h)*100.0 );
362     aProps[ u"svg:d"_ustr ]       = basegfx::utils::exportToSvgD( elem.PolyPoly, true, true, false );
363 
364     m_rEmitContext.rEmitter.beginTag( "draw:path", aProps );
365     m_rEmitContext.rEmitter.endTag( "draw:path" );
366 }
367 
visit(ImageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)368 void WriterXmlEmitter::visit( ImageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
369 {
370     PropertyMap aImageProps;
371     m_rEmitContext.rEmitter.beginTag( "draw:image", aImageProps );
372     m_rEmitContext.rEmitter.beginTag( "office:binary-data", PropertyMap() );
373     m_rEmitContext.rImages.writeBase64EncodedStream( elem.Image, m_rEmitContext);
374     m_rEmitContext.rEmitter.endTag( "office:binary-data" );
375     m_rEmitContext.rEmitter.endTag( "draw:image" );
376 }
377 
visit(PageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)378 void WriterXmlEmitter::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&   )
379 {
380     if( m_rEmitContext.xStatusIndicator.is() )
381         m_rEmitContext.xStatusIndicator->setValue( elem.PageNumber );
382 
383     auto this_it =  elem.Children.begin();
384     while( this_it != elem.Children.end() && this_it->get() != &elem )
385     {
386         (*this_it)->visitedBy( *this, this_it );
387         ++this_it;
388     }
389 }
390 
visit(DocumentElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)391 void WriterXmlEmitter::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
392 {
393     m_rEmitContext.rEmitter.beginTag( "office:body", PropertyMap() );
394     m_rEmitContext.rEmitter.beginTag( "office:text", PropertyMap() );
395 
396     for( const auto& rxChild : elem.Children )
397     {
398         PageElement* pPage = dynamic_cast<PageElement*>(rxChild.get());
399         if( pPage )
400         {
401             // emit only page anchored objects
402             // currently these are only DrawElement types
403             for( auto child_it = pPage->Children.begin(); child_it != pPage->Children.end(); ++child_it )
404             {
405                 if( dynamic_cast<DrawElement*>(child_it->get()) != nullptr )
406                     (*child_it)->visitedBy( *this, child_it );
407             }
408         }
409     }
410 
411     // do not emit page anchored objects, they are emitted before
412     // (must precede all pages in writer document) currently these are
413     // only DrawElement types
414     for( auto it = elem.Children.begin(); it != elem.Children.end(); ++it )
415     {
416         if( dynamic_cast<DrawElement*>(it->get()) == nullptr )
417             (*it)->visitedBy( *this, it );
418     }
419 
420     m_rEmitContext.rEmitter.endTag( "office:text" );
421     m_rEmitContext.rEmitter.endTag( "office:body" );
422 }
423 
424 
visit(HyperlinkElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)425 void WriterXmlOptimizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
426 {
427 }
428 
visit(TextElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)429 void WriterXmlOptimizer::visit( TextElement&, const std::list< std::unique_ptr<Element> >::const_iterator&)
430 {
431 }
432 
visit(FrameElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)433 void WriterXmlOptimizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
434 {
435     elem.applyToChildren(*this);
436 }
437 
visit(ImageElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)438 void WriterXmlOptimizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
439 {
440 }
441 
visit(PolyPolyElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator & elemIt)442 void WriterXmlOptimizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& elemIt )
443 {
444     /* note: optimize two consecutive PolyPolyElements that
445      *  have the same path but one of which is a stroke while
446      *     the other is a fill
447      */
448     if( !elem.Parent )
449         return;
450     // find following PolyPolyElement in parent's children list
451     if( elemIt == elem.Parent->Children.end() )
452         return;
453     auto next_it = elemIt;
454     ++next_it;
455     if( next_it == elem.Parent->Children.end() )
456         return;
457 
458     PolyPolyElement* pNext = dynamic_cast<PolyPolyElement*>(next_it->get());
459     if( !pNext || pNext->PolyPoly != elem.PolyPoly )
460         return;
461 
462     const GraphicsContext& rNextGC =
463                   m_rProcessor.getGraphicsContext( pNext->GCId );
464     const GraphicsContext& rThisGC =
465                   m_rProcessor.getGraphicsContext( elem.GCId );
466 
467     if( !(rThisGC.BlendMode      == rNextGC.BlendMode &&
468         rThisGC.Flatness       == rNextGC.Flatness &&
469         rThisGC.Transformation == rNextGC.Transformation &&
470         rThisGC.Clip           == rNextGC.Clip &&
471         pNext->Action          == PATH_STROKE &&
472         (elem.Action == PATH_FILL || elem.Action == PATH_EOFILL)) )
473         return;
474 
475     GraphicsContext aGC = rThisGC;
476     aGC.LineJoin  = rNextGC.LineJoin;
477     aGC.LineCap   = rNextGC.LineCap;
478     aGC.LineWidth = rNextGC.LineWidth;
479     aGC.MiterLimit= rNextGC.MiterLimit;
480     aGC.DashArray = rNextGC.DashArray;
481     aGC.LineColor = rNextGC.LineColor;
482     elem.GCId = m_rProcessor.getGCId( aGC );
483 
484     elem.Action |= pNext->Action;
485 
486     elem.Children.splice( elem.Children.end(), pNext->Children );
487     elem.Parent->Children.erase(next_it);
488 }
489 
visit(ParagraphElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)490 void WriterXmlOptimizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt)
491 {
492     optimizeTextElements( elem );
493 
494     elem.applyToChildren(*this);
495 
496     if( !(elem.Parent && rParentIt != elem.Parent->Children.end()) )
497         return;
498 
499     // find if there is a previous paragraph that might be a heading for this one
500     auto prev = rParentIt;
501     ParagraphElement* pPrevPara = nullptr;
502     while( prev != elem.Parent->Children.begin() )
503     {
504         --prev;
505         pPrevPara = dynamic_cast< ParagraphElement* >(prev->get());
506         if( pPrevPara )
507         {
508             /* What constitutes a heading ? current hints are:
509              * - one line only
510              * - not too far away from this paragraph (two heading height max ?)
511              * - font larger or bold
512              * this is of course incomplete
513              * FIXME: improve hints for heading
514              */
515             // check for single line
516             if( pPrevPara->isSingleLined( m_rProcessor ) )
517             {
518                 double head_line_height = pPrevPara->getLineHeight( m_rProcessor );
519                 if( pPrevPara->y + pPrevPara->h + 2*head_line_height > elem.y )
520                 {
521                     // check for larger font
522                     if( head_line_height > elem.getLineHeight( m_rProcessor ) )
523                     {
524                         pPrevPara->Type = ParagraphElement::Headline;
525                     }
526                     else
527                     {
528                         // check whether text of pPrevPara is bold (at least first text element)
529                         // and this para is not bold (ditto)
530                         TextElement* pPrevText = pPrevPara->getFirstTextChild();
531                         TextElement* pThisText = elem.getFirstTextChild();
532                         if( pPrevText && pThisText )
533                         {
534                             const FontAttributes& rPrevFont = m_rProcessor.getFont( pPrevText->FontId );
535                             const FontAttributes& rThisFont = m_rProcessor.getFont( pThisText->FontId );
536                             if ( (rPrevFont.fontWeight ==  u"600" ||
537                                   rPrevFont.fontWeight ==  u"bold" ||
538                                   rPrevFont.fontWeight ==  u"800" ||
539                                   rPrevFont.fontWeight ==  u"900" )  &&
540                                  (rThisFont.fontWeight ==  u"600" ||
541                                   rThisFont.fontWeight ==  u"bold" ||
542                                   rThisFont.fontWeight ==  u"800" ||
543                                   rThisFont.fontWeight ==  u"900" ) )
544                             {
545                                 pPrevPara->Type = ParagraphElement::Headline;
546                             }
547                         }
548                     }
549                 }
550             }
551             break;
552         }
553     }
554 }
555 
visit(PageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)556 void WriterXmlOptimizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
557 {
558     if( m_rProcessor.getStatusIndicator().is() )
559         m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
560 
561     // resolve hyperlinks
562     elem.resolveHyperlinks();
563 
564     elem.resolveFontStyles( m_rProcessor ); // underlines and such
565 
566     // FIXME: until hyperlinks and font effects are adjusted for
567     // geometrical search handle them before sorting
568     PDFIProcessor::sortElements( &elem );
569 
570     // find paragraphs in text
571     ParagraphElement* pCurPara = nullptr;
572     std::list< std::unique_ptr<Element> >::iterator page_element, next_page_element;
573     next_page_element = elem.Children.begin();
574     double fCurLineHeight = 0.0; // average height of text items in current para
575     int nCurLineElements = 0; // number of line contributing elements in current para
576     double line_left = elem.w, line_right = 0.0;
577     double column_width = elem.w*0.75; // estimate text width
578     // TODO: guess columns
579     while( next_page_element != elem.Children.end() )
580     {
581         page_element = next_page_element++;
582         ParagraphElement* pPagePara = dynamic_cast<ParagraphElement*>(page_element->get());
583         if( pPagePara )
584         {
585             pCurPara = pPagePara;
586             // adjust line height and text items
587             fCurLineHeight = 0.0;
588             nCurLineElements = 0;
589             for( const auto& rxChild : pCurPara->Children )
590             {
591                 TextElement* pTestText = rxChild->dynCastAsTextElement();
592                 if( pTestText )
593                 {
594                     fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pTestText->h)/double(nCurLineElements+1);
595                     nCurLineElements++;
596                 }
597             }
598             continue;
599         }
600 
601         HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(page_element->get());
602         DrawElement* pDraw = dynamic_cast<DrawElement*>(page_element->get());
603         if( ! pDraw && pLink && ! pLink->Children.empty() )
604             pDraw = dynamic_cast<DrawElement*>(pLink->Children.front().get() );
605         if( pDraw )
606         {
607             // insert small drawing objects as character, else leave them page bound
608 
609             bool bInsertToParagraph = false;
610             // first check if this is either inside the paragraph
611             if( pCurPara && pDraw->y < pCurPara->y + pCurPara->h )
612             {
613                 if( pDraw->h < fCurLineHeight * 1.5 )
614                 {
615                     bInsertToParagraph = true;
616                     fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pDraw->h)/double(nCurLineElements+1);
617                     nCurLineElements++;
618                     // mark draw element as character
619                     pDraw->isCharacter = true;
620                 }
621             }
622             // or perhaps the draw element begins a new paragraph
623             else if( next_page_element != elem.Children.end() )
624             {
625                 TextElement* pText = (*next_page_element)->dynCastAsTextElement();
626                 if( ! pText )
627                 {
628                     ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(next_page_element->get());
629                     if( pPara && ! pPara->Children.empty() )
630                         pText = pPara->Children.front()->dynCastAsTextElement();
631                 }
632                 if( pText && // check there is a text
633                     pDraw->h < pText->h*1.5 && // and it is approx the same height
634                     // and either upper or lower edge of pDraw is inside text's vertical range
635                     ( ( pDraw->y >= pText->y && pDraw->y <= pText->y+pText->h ) ||
636                       ( pDraw->y+pDraw->h >= pText->y && pDraw->y+pDraw->h <= pText->y+pText->h )
637                       )
638                     )
639                 {
640                     bInsertToParagraph = true;
641                     fCurLineHeight = pDraw->h;
642                     nCurLineElements = 1;
643                     line_left = pDraw->x;
644                     line_right = pDraw->x + pDraw->w;
645                     // begin a new paragraph
646                     pCurPara = nullptr;
647                     // mark draw element as character
648                     pDraw->isCharacter = true;
649                 }
650             }
651 
652             if( ! bInsertToParagraph )
653             {
654                 pCurPara = nullptr;
655                 continue;
656             }
657         }
658 
659         TextElement* pText = (*page_element)->dynCastAsTextElement();
660         if( ! pText && pLink && ! pLink->Children.empty() )
661             pText = pLink->Children.front()->dynCastAsTextElement();
662         if( pText )
663         {
664             Element* pGeo = pLink ? static_cast<Element*>(pLink) :
665                                     static_cast<Element*>(pText);
666             if( pCurPara )
667             {
668                 // there was already a text element, check for a new paragraph
669                 if( nCurLineElements > 0 )
670                 {
671                     // if the new text is significantly distant from the paragraph
672                     // begin a new paragraph
673                     if( pGeo->y > pCurPara->y+pCurPara->h + fCurLineHeight*0.5 )
674                         pCurPara = nullptr; // insert new paragraph
675                     else if( pGeo->y > (pCurPara->y+pCurPara->h - fCurLineHeight*0.05) )
676                     {
677                         // new paragraph if either the last line of the paragraph
678                         // was significantly shorter than the paragraph as a whole
679                         if( (line_right - line_left) < pCurPara->w*0.75 )
680                             pCurPara = nullptr;
681                         // or the last line was significantly smaller than the column width
682                         else if( (line_right - line_left) < column_width*0.75 )
683                             pCurPara = nullptr;
684                     }
685                 }
686             }
687             // update line height/width
688             if( pCurPara )
689             {
690                 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pGeo->h)/double(nCurLineElements+1);
691                 nCurLineElements++;
692                 if( pGeo->x < line_left )
693                     line_left = pGeo->x;
694                 if( pGeo->x+pGeo->w > line_right )
695                     line_right = pGeo->x+pGeo->w;
696             }
697             else
698             {
699                 fCurLineHeight = pGeo->h;
700                 nCurLineElements = 1;
701                 line_left = pGeo->x;
702                 line_right = pGeo->x + pGeo->w;
703             }
704         }
705 
706         // move element to current paragraph
707         if( ! pCurPara ) // new paragraph, insert one
708         {
709             pCurPara = ElementFactory::createParagraphElement( nullptr );
710             assert(pCurPara);
711             // set parent
712             pCurPara->Parent = &elem;
713             //insert new paragraph before current element
714             page_element = elem.Children.insert( page_element, std::unique_ptr<Element>(pCurPara) );
715             // forward iterator to current element again
716             ++ page_element;
717             // update next_element which is now invalid
718             next_page_element = page_element;
719             ++ next_page_element;
720         }
721         Element* pCurEle = page_element->get();
722         Element::setParent( page_element, pCurPara );
723         OSL_ENSURE( !pText || pCurEle == pText || pCurEle == pLink, "paragraph child list in disorder" );
724         if( pText || pDraw )
725             pCurPara->updateGeometryWith( pCurEle );
726     }
727 
728     // process children
729     elem.applyToChildren(*this);
730 
731     // find possible header and footer
732     checkHeaderAndFooter( elem );
733 }
734 
checkHeaderAndFooter(PageElement & rElem)735 void WriterXmlOptimizer::checkHeaderAndFooter( PageElement& rElem )
736 {
737     /* indicators for a header:
738      *  - single line paragraph at top of page (inside 15% page height)
739      *  - at least lineheight above the next paragraph
740      *
741      *  indicators for a footer likewise:
742      *  - single line paragraph at bottom of page (inside 15% page height)
743      *  - at least lineheight below the previous paragraph
744      */
745 
746     auto isParagraphElement = [](std::unique_ptr<Element>& rxChild) -> bool {
747         return dynamic_cast<ParagraphElement*>(rxChild.get()) != nullptr;
748     };
749 
750     // detect header
751     // Note: the following assumes that the pages' children have been
752     // sorted geometrically
753     auto it = std::find_if(rElem.Children.begin(), rElem.Children.end(), isParagraphElement);
754     if (it != rElem.Children.end())
755     {
756         ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**it);
757         if( rPara.y+rPara.h < rElem.h*0.15 && rPara.isSingleLined( m_rProcessor ) )
758         {
759             auto next_it = it;
760             ParagraphElement* pNextPara = nullptr;
761             while( ++next_it != rElem.Children.end() && pNextPara == nullptr )
762             {
763                 pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
764             }
765             if( pNextPara && pNextPara->y > rPara.y+rPara.h*2 )
766             {
767                 rElem.HeaderElement = std::move(*it);
768                 rPara.Parent = nullptr;
769                 rElem.Children.erase( it );
770             }
771         }
772     }
773 
774     // detect footer
775     auto rit = std::find_if(rElem.Children.rbegin(), rElem.Children.rend(), isParagraphElement);
776     if (rit == rElem.Children.rend())
777         return;
778 
779     ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**rit);
780     if( !(rPara.y > rElem.h*0.85 && rPara.isSingleLined( m_rProcessor )) )
781         return;
782 
783     std::list< std::unique_ptr<Element> >::reverse_iterator next_it = rit;
784     ParagraphElement* pNextPara = nullptr;
785     while( ++next_it != rElem.Children.rend() && pNextPara == nullptr )
786     {
787         pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
788     }
789     if( pNextPara && pNextPara->y < rPara.y-rPara.h*2 )
790     {
791         rElem.FooterElement = std::move(*rit);
792         rPara.Parent = nullptr;
793         rElem.Children.erase( std::next(rit).base() );
794     }
795 }
796 
optimizeTextElements(Element & rParent)797 void WriterXmlOptimizer::optimizeTextElements(Element& rParent)
798 {
799     if( rParent.Children.empty() ) // this should not happen
800     {
801         OSL_FAIL( "empty paragraph optimized" );
802         return;
803     }
804 
805     // concatenate child elements with same font id
806     auto next = rParent.Children.begin();
807     auto it = next++;
808     FrameElement* pFrame = dynamic_cast<FrameElement*>(rParent.Parent);
809     bool bRotatedFrame = false;
810     if( pFrame )
811     {
812         const GraphicsContext& rFrameGC = m_rProcessor.getGraphicsContext( pFrame->GCId );
813         if( rFrameGC.isRotatedOrSkewed() )
814             bRotatedFrame = true;
815     }
816     while( next != rParent.Children.end() )
817     {
818         bool bConcat = false;
819         TextElement* pCur = (*it)->dynCastAsTextElement();
820         if( pCur )
821         {
822             TextElement* pNext = dynamic_cast<TextElement*>(next->get());
823             OUString str;
824             bool bPara = strspn("ParagraphElement", typeid(rParent).name());
825             ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(&rParent);
826             if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
827                 pPara->bRtl = true;
828             if( pNext )
829             {
830                 const GraphicsContext& rCurGC = m_rProcessor.getGraphicsContext( pCur->GCId );
831                 const GraphicsContext& rNextGC = m_rProcessor.getGraphicsContext( pNext->GCId );
832 
833                 // line and space optimization; works only in strictly horizontal mode
834 
835                 if( !bRotatedFrame
836                     && ! rCurGC.isRotatedOrSkewed()
837                     && ! rNextGC.isRotatedOrSkewed()
838                     && ! pNext->Text.isEmpty()
839                     && pNext->Text[0] != ' '
840                     && ! pCur->Text.isEmpty()
841                     && pCur->Text[pCur->Text.getLength() - 1] != ' '
842                     )
843                 {
844                     // check for new line in paragraph
845                     if( pNext->y > pCur->y+pCur->h )
846                     {
847                         // new line begins
848                         // check whether a space would should be inserted or a hyphen removed
849                         sal_Unicode aLastCode = pCur->Text[pCur->Text.getLength() - 1];
850                         if( aLastCode == '-'
851                             || aLastCode == 0x2010
852                             || (aLastCode >= 0x2012 && aLastCode <= 0x2015)
853                             || aLastCode == 0xff0d
854                         )
855                         {
856                             // cut a hyphen
857                             pCur->Text.setLength( pCur->Text.getLength()-1 );
858                         }
859                         // append a space unless there is a non breaking hyphen
860                         else if( aLastCode != 0x2011 )
861                         {
862                             pCur->Text.append( ' ' );
863                         }
864                     }
865                     else // we're continuing the same line
866                     {
867                         // check whether a space would should be inserted
868                         // check for a small horizontal offset
869                         if( pCur->x + pCur->w + pNext->h*0.15 < pNext->x )
870                         {
871                             pCur->Text.append( ' ' );
872                         }
873                     }
874                 }
875                 // concatenate consecutive text elements unless there is a
876                 // font or text color change, leave a new span in that case
877                 if( pCur->FontId == pNext->FontId &&
878                     rCurGC.FillColor.Red == rNextGC.FillColor.Red &&
879                     rCurGC.FillColor.Green == rNextGC.FillColor.Green &&
880                     rCurGC.FillColor.Blue == rNextGC.FillColor.Blue &&
881                     rCurGC.FillColor.Alpha == rNextGC.FillColor.Alpha
882                     )
883                 {
884                     pCur->updateGeometryWith( pNext );
885                     if (pPara && pPara->bRtl)
886                     {
887                         // Tdf#152083: If RTL, reverse the text in pNext so that its correct order is
888                         // restored when the combined text is reversed in WriterXmlEmitter::visit.
889                         OUString tempStr;
890                         bool bNeedReverse=false;
891                         str = pNext->Text.toString();
892                         for (sal_Int32 i=0; i < str.getLength(); i++)
893                         {
894                             if (str[i] == u' ')
895                             {   // Space char (e.g. the space as in " م") needs special treatment.
896                                 //   First, append the space char to pCur.
897                                 pCur->Text.append(OUStringChar(str[i]));
898                                 //   Then, check whether the tmpStr needs reverse, if so then reverse and append.
899                                 if (bNeedReverse)
900                                 {
901                                     tempStr = ::comphelper::string::reverseCodePoints(tempStr);
902                                     pCur->Text.append(tempStr);
903                                     tempStr = u""_ustr;
904                                 }
905                                 bNeedReverse = false;
906                             }
907                             else
908                             {
909                                 tempStr += OUStringChar(str[i]);
910                                 bNeedReverse = true;
911                             }
912                         }
913                         // Do the last append
914                         if (bNeedReverse)
915                         {
916                             tempStr = ::comphelper::string::reverseCodePoints(tempStr);
917                             pCur->Text.append(tempStr);
918                         }
919                         else
920                         {
921                             pCur->Text.append(tempStr);
922                         }
923                     }
924                     else
925                     {
926                         // append text to current element directly without reverse
927                         pCur->Text.append(pNext->Text);
928                     }
929                     if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
930                         pPara->bRtl = true;
931                     // append eventual children to current element
932                     // and clear children (else the children just
933                     // appended to pCur would be destroyed)
934                     pCur->Children.splice( pCur->Children.end(), pNext->Children );
935                     // get rid of the now useless element
936                     rParent.Children.erase( next );
937                     bConcat = true;
938                 }
939             }
940         }
941         else if( dynamic_cast<HyperlinkElement*>(it->get()) )
942             optimizeTextElements( **it );
943         if( bConcat )
944         {
945             next = it;
946             ++next;
947         }
948         else
949         {
950             ++it;
951             ++next;
952         }
953     }
954 }
955 
visit(DocumentElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)956 void WriterXmlOptimizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
957 {
958     elem.applyToChildren(*this);
959 }
960 
961 
visit(PolyPolyElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)962 void WriterXmlFinalizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
963 {
964     // xxx TODO copied from DrawElement
965     const GraphicsContext& rGC = m_rProcessor.getGraphicsContext(elem.GCId );
966     PropertyMap aProps;
967     aProps[ u"style:family"_ustr ] = "graphic";
968 
969     PropertyMap aGCProps;
970     if (elem.Action & PATH_STROKE)
971     {
972         double scale = GetAverageTransformationScale(rGC.Transformation);
973         if (rGC.DashArray.size() < 2)
974         {
975             aGCProps[ u"draw:stroke"_ustr ] = "solid";
976         }
977         else
978         {
979             PropertyMap props;
980             FillDashStyleProps(props, rGC.DashArray, scale);
981             StyleContainer::Style style("draw:stroke-dash"_ostr, std::move(props));
982 
983             aGCProps[ u"draw:stroke"_ustr ] = "dash";
984             aGCProps[ u"draw:stroke-dash"_ustr ] =
985                 m_rStyleContainer.getStyleName(
986                 m_rStyleContainer.getStyleId(style));
987         }
988 
989         aGCProps[ u"svg:stroke-color"_ustr ] = getColorString(rGC.LineColor);
990         aGCProps[ u"svg:stroke-width"_ustr ] = convertPixelToUnitString(rGC.LineWidth * scale);
991         aGCProps[ u"draw:stroke-linejoin"_ustr ] = rGC.GetLineJoinString();
992         aGCProps[ u"svg:stroke-linecap"_ustr ] = rGC.GetLineCapString();
993     }
994     else
995     {
996         aGCProps[ u"draw:stroke"_ustr ] = "none";
997     }
998 
999     if (elem.FillImage != -1)
1000     {
1001         PropertyMap props;
1002         // The image isn't actually in a prop, it's in an extra chunk inside.
1003         StyleContainer::Style style("draw:fill-image"_ostr, std::move(props));
1004         style.Contents = m_rProcessor.getImages().asBase64EncodedString(elem.FillImage);
1005         aGCProps[ u"draw:fill-image-name"_ustr ] =
1006             m_rStyleContainer.getStyleName(
1007             m_rStyleContainer.getStyleId(style));
1008         aGCProps[ u"draw:fill-image-width"_ustr ] = unitMMString(convPx2mm(elem.TileWidth));
1009         aGCProps[ u"draw:fill-image-height"_ustr ] = unitMMString(convPx2mm(elem.TileHeight));
1010     }
1011 
1012     // TODO(F1): check whether stuff could be emulated by gradient/bitmap/hatch
1013     if( elem.Action & (PATH_FILL | PATH_EOFILL) )
1014     {
1015         if (elem.FillImage == -1)
1016         {
1017             aGCProps[ u"draw:fill"_ustr ]   = "solid";
1018         }
1019         else
1020         {
1021             aGCProps[ u"draw:fill"_ustr ]   = "bitmap";
1022         }
1023         aGCProps[ u"draw:fill-color"_ustr ] = getColorString( rGC.FillColor );
1024     }
1025     else
1026     {
1027         aGCProps[ u"draw:fill"_ustr ] = "none";
1028     }
1029 
1030     StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1031     StyleContainer::Style aSubStyle( "style:graphic-properties"_ostr, std::move(aGCProps) );
1032     aStyle.SubStyles.push_back( &aSubStyle );
1033 
1034     elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1035 }
1036 
visit(HyperlinkElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)1037 void WriterXmlFinalizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1038 {
1039 }
1040 
visit(TextElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1041 void WriterXmlFinalizer::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1042 {
1043     const FontAttributes& rFont = m_rProcessor.getFont( elem.FontId );
1044     PropertyMap aProps;
1045     aProps[ u"style:family"_ustr ] = "text";
1046 
1047     PropertyMap aFontProps;
1048 
1049     // family name
1050     // TODO: tdf#143095: use system font name rather than PSName
1051     SAL_INFO("sdext.pdfimport", "The font used in xml is: " << rFont.familyName);
1052     aFontProps[ u"fo:font-family"_ustr ] = rFont.familyName;
1053     aFontProps[ u"style:font-family-asian"_ustr ] = rFont.familyName;
1054     aFontProps[ u"style:font-family-complex"_ustr ] = rFont.familyName;
1055 
1056     // bold
1057     aFontProps[ u"fo:font-weight"_ustr ]            = rFont.fontWeight;
1058     aFontProps[ u"style:font-weight-asian"_ustr ]   = rFont.fontWeight;
1059     aFontProps[ u"style:font-weight-complex"_ustr ] = rFont.fontWeight;
1060 
1061     // italic
1062     if( rFont.isItalic )
1063     {
1064         aFontProps[ u"fo:font-style"_ustr ]            = "italic";
1065         aFontProps[ u"style:font-style-asian"_ustr ]   = "italic";
1066         aFontProps[ u"style:font-style-complex"_ustr ] = "italic";
1067     }
1068 
1069     // underline
1070     if( rFont.isUnderline )
1071     {
1072         aFontProps[ u"style:text-underline-style"_ustr ]  = "solid";
1073         aFontProps[ u"style:text-underline-width"_ustr ]  = "auto";
1074         aFontProps[ u"style:text-underline-color"_ustr ]  = "font-color";
1075     }
1076 
1077     // outline
1078     if( rFont.isOutline )
1079         aFontProps[ u"style:text-outline"_ustr ]  = "true";
1080 
1081     // size
1082     OUString aFSize = OUString::number( rFont.size*72/PDFI_OUTDEV_RESOLUTION ) + "pt";
1083     aFontProps[ u"fo:font-size"_ustr ]            = aFSize;
1084     aFontProps[ u"style:font-size-asian"_ustr ]   = aFSize;
1085     aFontProps[ u"style:font-size-complex"_ustr ] = aFSize;
1086 
1087     // color
1088     const GraphicsContext& rGC = m_rProcessor.getGraphicsContext( elem.GCId );
1089     aFontProps[ u"fo:color"_ustr ] = getColorString( rFont.isOutline ? rGC.LineColor : rGC.FillColor );
1090 
1091     StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1092     StyleContainer::Style aSubStyle( "style:text-properties"_ostr, std::move(aFontProps) );
1093     aStyle.SubStyles.push_back( &aSubStyle );
1094     elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1095 }
1096 
visit(ParagraphElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)1097 void WriterXmlFinalizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
1098 {
1099     PropertyMap aParaProps;
1100 
1101     if( elem.Parent )
1102     {
1103         // check for center alignment
1104         // criterion: paragraph is small relative to parent and distributed around its center
1105         double p_x = elem.Parent->x;
1106         double p_w = elem.Parent->w;
1107 
1108         PageElement* pPage = dynamic_cast<PageElement*>(elem.Parent);
1109         if( pPage )
1110         {
1111             p_x += pPage->LeftMargin;
1112             p_w -= pPage->LeftMargin+pPage->RightMargin;
1113         }
1114         bool bIsCenter = false;
1115         if( elem.w < ( p_w/2) )
1116         {
1117             double delta = elem.w/4;
1118             // allow very small paragraphs to deviate a little more
1119             // relative to parent's center
1120             if( elem.w <  p_w/8 )
1121                 delta = elem.w;
1122             if( fabs( elem.x+elem.w/2 - ( p_x+ p_w/2) ) <  delta ||
1123                 (pPage && fabs( elem.x+elem.w/2 - (pPage->x + pPage->w/2) ) <  delta) )
1124             {
1125                 bIsCenter = true;
1126                 aParaProps[ u"fo:text-align"_ustr ] = "center";
1127             }
1128         }
1129         if( ! bIsCenter && elem.x > p_x + p_w/10 )
1130         {
1131             // indent
1132             aParaProps[ u"fo:margin-left"_ustr ] = OUString::number(convPx2mm( elem.x - p_x )) + "mm";
1133         }
1134 
1135         // check whether to leave some space to next paragraph
1136         // find whether there is a next paragraph
1137         auto it = rParentIt;
1138         const ParagraphElement* pNextPara = nullptr;
1139         while( ++it != elem.Parent->Children.end() && ! pNextPara )
1140             pNextPara = dynamic_cast< const ParagraphElement* >(it->get());
1141         if( pNextPara )
1142         {
1143             if( pNextPara->y - (elem.y+elem.h) > convmm2Px( 10 ) )
1144             {
1145                 aParaProps[ u"fo:margin-bottom"_ustr ] =
1146                     OUString::number( convPx2mm( pNextPara->y - (elem.y+elem.h) ) ) + "mm";
1147             }
1148         }
1149     }
1150 
1151     if( ! aParaProps.empty() )
1152     {
1153         PropertyMap aProps;
1154         aProps[ u"style:family"_ustr ] = "paragraph";
1155         StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1156         StyleContainer::Style aSubStyle( "style:paragraph-properties"_ostr, std::move(aParaProps) );
1157         aStyle.SubStyles.push_back( &aSubStyle );
1158         elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1159     }
1160 
1161     elem.applyToChildren(*this);
1162 }
1163 
visit(FrameElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1164 void WriterXmlFinalizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
1165 {
1166     PropertyMap aProps;
1167     aProps[ u"style:family"_ustr ] = "graphic";
1168 
1169     PropertyMap aGCProps;
1170 
1171     aGCProps[ u"draw:stroke"_ustr ]                    = "none";
1172     aGCProps[ u"draw:fill"_ustr ]                      = "none";
1173     aGCProps[ u"draw:auto-grow-height"_ustr ]          = "true";
1174     aGCProps[ u"draw:auto-grow-width"_ustr ]           = "true";
1175     aGCProps[ u"draw:textarea-horizontal-align"_ustr ] = "left";
1176     aGCProps[ u"draw:textarea-vertical-align"_ustr ]   = "top";
1177     aGCProps[ u"fo:min-height"_ustr]                   = "0cm";
1178     aGCProps[ u"fo:min-width"_ustr]                    = "0cm";
1179     aGCProps[ u"fo:padding-top"_ustr ]                 = "0cm";
1180     aGCProps[ u"fo:padding-left"_ustr ]                = "0cm";
1181     aGCProps[ u"fo:padding-right"_ustr ]               = "0cm";
1182     aGCProps[ u"fo:padding-bottom"_ustr ]              = "0cm";
1183 
1184     StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1185     StyleContainer::Style aSubStyle( "style:graphic-properties"_ostr, std::move(aGCProps) );
1186     aStyle.SubStyles.push_back( &aSubStyle );
1187 
1188     elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1189     elem.applyToChildren(*this);
1190 }
1191 
visit(ImageElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)1192 void WriterXmlFinalizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1193 {
1194 }
1195 
setFirstOnPage(ParagraphElement & rElem,StyleContainer & rStyles,const OUString & rMasterPageName)1196 void WriterXmlFinalizer::setFirstOnPage( ParagraphElement&    rElem,
1197                                          StyleContainer&      rStyles,
1198                                          const OUString& rMasterPageName )
1199 {
1200     PropertyMap aProps;
1201     if( rElem.StyleId != -1 )
1202     {
1203         const PropertyMap* pProps = rStyles.getProperties( rElem.StyleId );
1204         if( pProps )
1205             aProps = *pProps;
1206     }
1207 
1208     aProps[ u"style:family"_ustr ] = "paragraph";
1209     aProps[ u"style:master-page-name"_ustr ] = rMasterPageName;
1210 
1211     if( rElem.StyleId != -1 )
1212         rElem.StyleId = rStyles.setProperties( rElem.StyleId, std::move(aProps) );
1213     else
1214     {
1215         StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1216         rElem.StyleId = rStyles.getStyleId( aStyle );
1217     }
1218 }
1219 
visit(PageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1220 void WriterXmlFinalizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1221 {
1222     if( m_rProcessor.getStatusIndicator().is() )
1223         m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
1224 
1225     // transform from pixel to mm
1226     double page_width = convPx2mm( elem.w ), page_height = convPx2mm( elem.h );
1227 
1228     // calculate page margins out of the relevant children (paragraphs)
1229     elem.TopMargin = elem.h;
1230     elem.BottomMargin = 0;
1231     elem.LeftMargin = elem.w;
1232     elem.RightMargin = 0;
1233     // first element should be a paragraph
1234     ParagraphElement* pFirstPara = nullptr;
1235     for( const auto& rxChild : elem.Children )
1236     {
1237         if( dynamic_cast<ParagraphElement*>( rxChild.get() ) )
1238         {
1239             if( rxChild->x < elem.LeftMargin )
1240                 elem.LeftMargin = rxChild->x;
1241             if( rxChild->y < elem.TopMargin )
1242                 elem.TopMargin = rxChild->y;
1243             if( rxChild->x + rxChild->w > elem.w - elem.RightMargin )
1244                 elem.RightMargin = elem.w - (rxChild->x + rxChild->w);
1245             if( rxChild->y + rxChild->h > elem.h - elem.BottomMargin )
1246                 elem.BottomMargin = elem.h - (rxChild->y + rxChild->h);
1247             if( ! pFirstPara )
1248                 pFirstPara = dynamic_cast<ParagraphElement*>( rxChild.get() );
1249         }
1250     }
1251     if( elem.HeaderElement && elem.HeaderElement->y < elem.TopMargin )
1252         elem.TopMargin = elem.HeaderElement->y;
1253     if( elem.FooterElement && elem.FooterElement->y+elem.FooterElement->h > elem.h - elem.BottomMargin )
1254         elem.BottomMargin = elem.h - (elem.FooterElement->y + elem.FooterElement->h);
1255 
1256     // transform margins to mm
1257     double left_margin     = convPx2mm( elem.LeftMargin );
1258     double right_margin    = convPx2mm( elem.RightMargin );
1259     double top_margin      = convPx2mm( elem.TopMargin );
1260     double bottom_margin   = convPx2mm( elem.BottomMargin );
1261     if( ! pFirstPara )
1262     {
1263         // use default page margins
1264         left_margin     = 10;
1265         right_margin    = 10;
1266         top_margin      = 10;
1267         bottom_margin   = 10;
1268     }
1269 
1270     // round left/top margin to nearest mm
1271     left_margin     = rtl_math_round( left_margin, 0, rtl_math_RoundingMode_Floor );
1272     top_margin      = rtl_math_round( top_margin, 0, rtl_math_RoundingMode_Floor );
1273     // round (fuzzy) right/bottom margin to nearest cm
1274     right_margin    = rtl_math_round( right_margin, right_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1275     bottom_margin   = rtl_math_round( bottom_margin, bottom_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1276 
1277     // set reasonable default in case of way too large margins
1278     // e.g. no paragraph case
1279     if( left_margin > page_width/2.0 - 10 )
1280         left_margin = 10;
1281     if( right_margin > page_width/2.0 - 10 )
1282         right_margin = 10;
1283     if( top_margin > page_height/2.0 - 10 )
1284         top_margin = 10;
1285     if( bottom_margin > page_height/2.0 - 10 )
1286         bottom_margin = 10;
1287 
1288     // catch the weird cases
1289     if( left_margin < 0 )
1290         left_margin = 0;
1291     if( right_margin < 0 )
1292         right_margin = 0;
1293     if( top_margin < 0 )
1294         top_margin = 0;
1295     if( bottom_margin < 0 )
1296         bottom_margin = 0;
1297 
1298     // widely differing margins are unlikely to be correct
1299     if( right_margin > left_margin*1.5 )
1300         right_margin = left_margin;
1301 
1302     elem.LeftMargin      = convmm2Px( left_margin );
1303     elem.RightMargin     = convmm2Px( right_margin );
1304     elem.TopMargin       = convmm2Px( top_margin );
1305     elem.BottomMargin    = convmm2Px( bottom_margin );
1306 
1307     // get styles for paragraphs
1308     PropertyMap aPageProps;
1309     PropertyMap aPageLayoutProps;
1310     aPageLayoutProps[ u"fo:page-width"_ustr ]     = unitMMString( page_width );
1311     aPageLayoutProps[ u"fo:page-height"_ustr ]    = unitMMString( page_height );
1312     aPageLayoutProps[ u"style:print-orientation"_ustr ]
1313         = elem.w < elem.h ? std::u16string_view(u"portrait") : std::u16string_view(u"landscape");
1314     aPageLayoutProps[ u"fo:margin-top"_ustr ]     = unitMMString( top_margin );
1315     aPageLayoutProps[ u"fo:margin-bottom"_ustr ]  = unitMMString( bottom_margin );
1316     aPageLayoutProps[ u"fo:margin-left"_ustr ]    = unitMMString( left_margin );
1317     aPageLayoutProps[ u"fo:margin-right"_ustr ]   = unitMMString( right_margin );
1318     aPageLayoutProps[ u"style:writing-mode"_ustr ]= "lr-tb";
1319 
1320     StyleContainer::Style aStyle( "style:page-layout"_ostr, std::move(aPageProps));
1321     StyleContainer::Style aSubStyle( "style:page-layout-properties"_ostr, std::move(aPageLayoutProps));
1322     aStyle.SubStyles.push_back(&aSubStyle);
1323     sal_Int32 nPageStyle = m_rStyleContainer.impl_getStyleId( aStyle, false );
1324 
1325     // create master page
1326     OUString aMasterPageLayoutName = m_rStyleContainer.getStyleName( nPageStyle );
1327     aPageProps[ u"style:page-layout-name"_ustr ] = aMasterPageLayoutName;
1328     StyleContainer::Style aMPStyle( "style:master-page"_ostr, std::move(aPageProps) );
1329     StyleContainer::Style aHeaderStyle( "style:header"_ostr, PropertyMap() );
1330     StyleContainer::Style aFooterStyle( "style:footer"_ostr, PropertyMap() );
1331     if( elem.HeaderElement )
1332     {
1333         elem.HeaderElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1334         aHeaderStyle.ContainedElement = elem.HeaderElement.get();
1335         aMPStyle.SubStyles.push_back( &aHeaderStyle );
1336     }
1337     if( elem.FooterElement )
1338     {
1339         elem.FooterElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1340         aFooterStyle.ContainedElement = elem.FooterElement.get();
1341         aMPStyle.SubStyles.push_back( &aFooterStyle );
1342     }
1343     elem.StyleId = m_rStyleContainer.impl_getStyleId( aMPStyle,false );
1344 
1345 
1346     OUString aMasterPageName = m_rStyleContainer.getStyleName( elem.StyleId );
1347 
1348     // create styles for children
1349     elem.applyToChildren(*this);
1350 
1351     // no paragraph or other elements before the first paragraph
1352     if( ! pFirstPara )
1353     {
1354         pFirstPara = ElementFactory::createParagraphElement( nullptr );
1355         pFirstPara->Parent = &elem;
1356         elem.Children.push_front( std::unique_ptr<Element>(pFirstPara) );
1357     }
1358     setFirstOnPage(*pFirstPara, m_rStyleContainer, aMasterPageName);
1359 }
1360 
visit(DocumentElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1361 void WriterXmlFinalizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1362 {
1363     elem.applyToChildren(*this);
1364 }
1365 
1366 }
1367 
1368 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1369