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