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