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