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