1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: writertreevisiting.cxx,v $
13 * This file is part of OpenOffice.org.
15 * OpenOffice.org is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU Lesser General Public License version 3
17 * only, as published by the Free Software Foundation.
19 * OpenOffice.org is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Lesser General Public License version 3 for more details
23 * (a copy is included in the LICENSE file that accompanied this code).
25 * You should have received a copy of the GNU Lesser General Public License
26 * version 3 along with OpenOffice.org. If not, see
27 * <http://www.openoffice.org/license.html>
28 * for a copy of the LGPLv3 License.
30 ************************************************************************/
32 // MARKER(update_precomp.py): autogen include statement, do not remove
33 #include "precompiled_sdext.hxx"
35 #include "pdfiprocessor.hxx"
36 #include "xmlemitter.hxx"
37 #include "pdfihelper.hxx"
38 #include "imagecontainer.hxx"
40 #include "writertreevisiting.hxx"
41 #include "genericelements.hxx"
43 #include <basegfx/polygon/b2dpolypolygontools.hxx>
44 #include <basegfx/range/b2drange.hxx>
50 void WriterXmlEmitter::visit( HyperlinkElement
& elem
, const std::list
< Element
* >::const_iterator
& )
52 if( elem
.Children
.empty() )
55 const char* pType
= dynamic_cast<DrawElement
*>(elem
.Children
.front()) ? "draw:a" : "text:a";
58 aProps
[ USTR( "xlink:type" ) ] = USTR( "simple" );
59 aProps
[ USTR( "xlink:href" ) ] = elem
.URI
;
60 aProps
[ USTR( "office:target-frame-name" ) ] = USTR( "_blank" );
61 aProps
[ USTR( "xlink:show" ) ] = USTR( "new" );
63 m_rEmitContext
.rEmitter
.beginTag( pType
, aProps
);
64 std::list
< Element
* >::iterator this_it
= elem
.Children
.begin();
65 while( this_it
!=elem
.Children
.end() && *this_it
!= &elem
)
67 (*this_it
)->visitedBy( *this, this_it
);
70 m_rEmitContext
.rEmitter
.endTag( pType
);
73 void WriterXmlEmitter::visit( TextElement
& elem
, const std::list
< Element
* >::const_iterator
& )
75 if( ! elem
.Text
.getLength() )
79 if( elem
.StyleId
!= -1 )
81 aProps
[ rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "text:style-name" ) ) ] =
82 m_rEmitContext
.rStyles
.getStyleName( elem
.StyleId
);
85 m_rEmitContext
.rEmitter
.beginTag( "text:span", aProps
);
86 m_rEmitContext
.rEmitter
.write( elem
.Text
.makeStringAndClear() );
87 std::list
< Element
* >::iterator this_it
= elem
.Children
.begin();
88 while( this_it
!=elem
.Children
.end() && *this_it
!= &elem
)
90 (*this_it
)->visitedBy( *this, this_it
);
94 m_rEmitContext
.rEmitter
.endTag( "text:span" );
97 void WriterXmlEmitter::visit( ParagraphElement
& elem
, const std::list
< Element
* >::const_iterator
& )
100 if( elem
.StyleId
!= -1 )
102 aProps
[ USTR( "text:style-name" ) ] = m_rEmitContext
.rStyles
.getStyleName( elem
.StyleId
);
104 const char* pTagType
= "text:p";
105 if( elem
.Type
== elem
.Headline
)
107 m_rEmitContext
.rEmitter
.beginTag( pTagType
, aProps
);
109 std::list
< Element
* >::iterator this_it
= elem
.Children
.begin();
110 while( this_it
!=elem
.Children
.end() && *this_it
!= &elem
)
112 (*this_it
)->visitedBy( *this, this_it
);
116 m_rEmitContext
.rEmitter
.endTag( pTagType
);
119 void WriterXmlEmitter::fillFrameProps( DrawElement
& rElem
,
121 const EmitContext
& rEmitContext
)
123 double rel_x
= rElem
.x
, rel_y
= rElem
.y
;
125 // find anchor type by recursing though parents
126 Element
* pAnchor
= rElem
.Parent
;
128 ! dynamic_cast<ParagraphElement
*>(pAnchor
) &&
129 ! dynamic_cast<PageElement
*>(pAnchor
) )
131 pAnchor
= pAnchor
->Parent
;
135 if( dynamic_cast<ParagraphElement
*>(pAnchor
) )
137 rProps
[ USTR( "text:anchor-type" ) ] =
138 rElem
.isCharacter
? USTR( "character" ) : USTR( "paragraph" );
142 PageElement
* pPage
= dynamic_cast<PageElement
*>(pAnchor
);
143 rProps
[ USTR( "text:anchor-type" ) ] = USTR( "page" );
144 rProps
[ USTR( "text:anchor-page-number" ) ] = rtl::OUString::valueOf(pPage
->PageNumber
);
150 rProps
[ USTR( "draw:z-index" ) ] = rtl::OUString::valueOf( rElem
.ZOrder
);
151 rProps
[ USTR( "draw:style-name" )] = rEmitContext
.rStyles
.getStyleName( rElem
.StyleId
);
152 rProps
[ USTR( "svg:width" ) ] = convertPixelToUnitString( rElem
.w
);
153 rProps
[ USTR( "svg:height" ) ] = convertPixelToUnitString( rElem
.h
);
155 const GraphicsContext
& rGC
=
156 rEmitContext
.rProcessor
.getGraphicsContext( rElem
.GCId
);
157 if( rGC
.Transformation
.isIdentity() )
159 if( !rElem
.isCharacter
)
161 rProps
[ USTR( "svg:x" ) ] = convertPixelToUnitString( rel_x
);
162 rProps
[ USTR( "svg:y" ) ] = convertPixelToUnitString( rel_y
);
167 basegfx::B2DTuple aScale
, aTranslation
;
168 double fRotate
, fShearX
;
170 rGC
.Transformation
.decompose( aScale
, aTranslation
, fRotate
, fShearX
);
172 rtl::OUStringBuffer
aBuf( 256 );
174 // TODO(F2): general transformation case missing; if implemented, note
175 // that ODF rotation is oriented the other way
177 // build transformation string
180 aBuf
.appendAscii( "skewX( " );
181 aBuf
.append( fShearX
);
182 aBuf
.appendAscii( " )" );
186 if( aBuf
.getLength() > 0 )
187 aBuf
.append( sal_Unicode(' ') );
188 aBuf
.appendAscii( "rotate( " );
189 aBuf
.append( -fRotate
);
190 aBuf
.appendAscii( " )" );
193 if( ! rElem
.isCharacter
)
195 if( aBuf
.getLength() > 0 )
196 aBuf
.append( sal_Unicode(' ') );
197 aBuf
.appendAscii( "translate( " );
198 aBuf
.append( convertPixelToUnitString( rel_x
) );
199 aBuf
.append( sal_Unicode(' ') );
200 aBuf
.append( convertPixelToUnitString( rel_y
) );
201 aBuf
.appendAscii( " )" );
204 rProps
[ USTR( "draw:transform" ) ] = aBuf
.makeStringAndClear();
208 void WriterXmlEmitter::visit( FrameElement
& elem
, const std::list
< Element
* >::const_iterator
& )
210 if( elem
.Children
.empty() )
213 bool bTextBox
= (dynamic_cast<ParagraphElement
*>(elem
.Children
.front()) != NULL
);
214 PropertyMap aFrameProps
;
215 fillFrameProps( elem
, aFrameProps
, m_rEmitContext
);
216 m_rEmitContext
.rEmitter
.beginTag( "draw:frame", aFrameProps
);
218 m_rEmitContext
.rEmitter
.beginTag( "draw:text-box", PropertyMap() );
220 std::list
< Element
* >::iterator this_it
= elem
.Children
.begin();
221 while( this_it
!=elem
.Children
.end() && *this_it
!= &elem
)
223 (*this_it
)->visitedBy( *this, this_it
);
228 m_rEmitContext
.rEmitter
.endTag( "draw:text-box" );
229 m_rEmitContext
.rEmitter
.endTag( "draw:frame" );
232 void WriterXmlEmitter::visit( PolyPolyElement
& elem
, const std::list
< Element
* >::const_iterator
& )
234 elem
.updateGeometry();
236 * aw recommends using 100dth of mm in all respects since the xml import
237 * (a) is buggy (see issue 37213)
238 * (b) is optimized for 100dth of mm and does not scale itself then,
239 * this does not gain us speed but makes for smaller rounding errors since
240 * the xml importer coordinates are integer based
242 for (sal_uInt32 i
= 0; i
< elem
.PolyPoly
.count(); i
++)
244 basegfx::B2DPolygon b2dPolygon
;
245 b2dPolygon
= elem
.PolyPoly
.getB2DPolygon( i
);
247 for ( sal_uInt32 j
= 0; j
< b2dPolygon
.count(); j
++ )
249 basegfx::B2DPoint point
;
250 basegfx::B2DPoint nextPoint
;
251 point
= b2dPolygon
.getB2DPoint( j
);
253 basegfx::B2DPoint prevPoint
;
254 prevPoint
= b2dPolygon
.getPrevControlPoint( j
) ;
256 point
.setX( convPx2mmPrec2( point
.getX() )*100.0 );
257 point
.setY( convPx2mmPrec2( point
.getY() )*100.0 );
259 if ( b2dPolygon
.isPrevControlPointUsed( j
) )
261 prevPoint
.setX( convPx2mmPrec2( prevPoint
.getX() )*100.0 );
262 prevPoint
.setY( convPx2mmPrec2( prevPoint
.getY() )*100.0 );
265 if ( b2dPolygon
.isNextControlPointUsed( j
) )
267 nextPoint
= b2dPolygon
.getNextControlPoint( j
) ;
268 nextPoint
.setX( convPx2mmPrec2( nextPoint
.getX() )*100.0 );
269 nextPoint
.setY( convPx2mmPrec2( nextPoint
.getY() )*100.0 );
272 b2dPolygon
.setB2DPoint( j
, point
);
274 if ( b2dPolygon
.isPrevControlPointUsed( j
) )
275 b2dPolygon
.setPrevControlPoint( j
, prevPoint
) ;
277 if ( b2dPolygon
.isNextControlPointUsed( j
) )
278 b2dPolygon
.setNextControlPoint( j
, nextPoint
) ;
281 elem
.PolyPoly
.setB2DPolygon( i
, b2dPolygon
);
285 fillFrameProps( elem
, aProps
, m_rEmitContext
);
286 rtl::OUStringBuffer
aBuf( 64 );
287 aBuf
.appendAscii( "0 0 " );
288 aBuf
.append( convPx2mmPrec2(elem
.w
)*100.0 );
289 aBuf
.append( sal_Unicode(' ') );
290 aBuf
.append( convPx2mmPrec2(elem
.h
)*100.0 );
291 aProps
[ USTR( "svg:viewBox" ) ] = aBuf
.makeStringAndClear();
292 aProps
[ USTR( "svg:d" ) ] = basegfx::tools::exportToSvgD( elem
.PolyPoly
);
294 m_rEmitContext
.rEmitter
.beginTag( "draw:path", aProps
);
295 m_rEmitContext
.rEmitter
.endTag( "draw:path" );
298 void WriterXmlEmitter::visit( ImageElement
& elem
, const std::list
< Element
* >::const_iterator
& )
300 PropertyMap aImageProps
;
301 m_rEmitContext
.rEmitter
.beginTag( "draw:image", aImageProps
);
302 m_rEmitContext
.rEmitter
.beginTag( "office:binary-data", PropertyMap() );
303 m_rEmitContext
.rImages
.writeBase64EncodedStream( elem
.Image
, m_rEmitContext
);
304 m_rEmitContext
.rEmitter
.endTag( "office:binary-data" );
305 m_rEmitContext
.rEmitter
.endTag( "draw:image" );
308 void WriterXmlEmitter::visit( PageElement
& elem
, const std::list
< Element
* >::const_iterator
& )
310 if( m_rEmitContext
.xStatusIndicator
.is() )
311 m_rEmitContext
.xStatusIndicator
->setValue( elem
.PageNumber
);
313 std::list
< Element
* >::iterator this_it
= elem
.Children
.begin();
314 while( this_it
!=elem
.Children
.end() && *this_it
!= &elem
)
316 (*this_it
)->visitedBy( *this, this_it
);
321 void WriterXmlEmitter::visit( DocumentElement
& elem
, const std::list
< Element
* >::const_iterator
&)
323 m_rEmitContext
.rEmitter
.beginTag( "office:body", PropertyMap() );
324 m_rEmitContext
.rEmitter
.beginTag( "office:text", PropertyMap() );
326 for( std::list
< Element
* >::iterator it
= elem
.Children
.begin(); it
!= elem
.Children
.end(); ++it
)
328 PageElement
* pPage
= dynamic_cast<PageElement
*>(*it
);
331 // emit only page anchored objects
332 // currently these are only DrawElement types
333 for( std::list
< Element
* >::iterator child_it
= pPage
->Children
.begin(); child_it
!= pPage
->Children
.end(); ++child_it
)
335 if( dynamic_cast<DrawElement
*>(*child_it
) != NULL
)
336 (*child_it
)->visitedBy( *this, child_it
);
341 // do not emit page anchored objects, they are emitted before
342 // (must precede all pages in writer document) currently these are
343 // only DrawElement types
344 for( std::list
< Element
* >::iterator it
= elem
.Children
.begin(); it
!= elem
.Children
.end(); ++it
)
346 if( dynamic_cast<DrawElement
*>(*it
) == NULL
)
347 (*it
)->visitedBy( *this, it
);
350 m_rEmitContext
.rEmitter
.endTag( "office:text" );
351 m_rEmitContext
.rEmitter
.endTag( "office:body" );
354 /////////////////////////////////////////////////////////////////
356 void WriterXmlOptimizer::visit( HyperlinkElement
&, const std::list
< Element
* >::const_iterator
& )
360 void WriterXmlOptimizer::visit( TextElement
&, const std::list
< Element
* >::const_iterator
&)
364 void WriterXmlOptimizer::visit( FrameElement
& elem
, const std::list
< Element
* >::const_iterator
& )
366 elem
.applyToChildren(*this);
369 void WriterXmlOptimizer::visit( ImageElement
&, const std::list
< Element
* >::const_iterator
& )
373 void WriterXmlOptimizer::visit( PolyPolyElement
& elem
, const std::list
< Element
* >::const_iterator
& )
375 /* note: optimize two consecutive PolyPolyElements that
376 * have the same path but one of which is a stroke while
377 * the other is a fill
381 // find following PolyPolyElement in parent's children list
382 std::list
< Element
* >::iterator this_it
= elem
.Parent
->Children
.begin();
383 while( this_it
!= elem
.Parent
->Children
.end() && *this_it
!= &elem
)
386 if( this_it
!= elem
.Parent
->Children
.end() )
388 std::list
< Element
* >::iterator next_it
= this_it
;
389 if( ++next_it
!= elem
.Parent
->Children
.end() )
391 PolyPolyElement
* pNext
= dynamic_cast<PolyPolyElement
*>(*next_it
);
392 if( pNext
&& pNext
->PolyPoly
== elem
.PolyPoly
)
394 const GraphicsContext
& rNextGC
=
395 m_rProcessor
.getGraphicsContext( pNext
->GCId
);
396 const GraphicsContext
& rThisGC
=
397 m_rProcessor
.getGraphicsContext( elem
.GCId
);
399 if( rThisGC
.BlendMode
== rNextGC
.BlendMode
&&
400 rThisGC
.Flatness
== rNextGC
.Flatness
&&
401 rThisGC
.Transformation
== rNextGC
.Transformation
&&
402 rThisGC
.Clip
== rNextGC
.Clip
&&
403 pNext
->Action
== PATH_STROKE
&&
404 (elem
.Action
== PATH_FILL
|| elem
.Action
== PATH_EOFILL
) )
406 GraphicsContext aGC
= rThisGC
;
407 aGC
.LineJoin
= rNextGC
.LineJoin
;
408 aGC
.LineCap
= rNextGC
.LineCap
;
409 aGC
.LineWidth
= rNextGC
.LineWidth
;
410 aGC
.MiterLimit
= rNextGC
.MiterLimit
;
411 aGC
.DashArray
= rNextGC
.DashArray
;
412 aGC
.LineColor
= rNextGC
.LineColor
;
413 elem
.GCId
= m_rProcessor
.getGCId( aGC
);
415 elem
.Action
|= pNext
->Action
;
417 elem
.Children
.splice( elem
.Children
.end(), pNext
->Children
);
418 elem
.Parent
->Children
.erase( next_it
);
427 void WriterXmlOptimizer::visit( ParagraphElement
& elem
, const std::list
< Element
* >::const_iterator
& rParentIt
)
429 optimizeTextElements( elem
);
431 elem
.applyToChildren(*this);
433 if( elem
.Parent
&& rParentIt
!= elem
.Parent
->Children
.end() )
435 // find if there is a previous paragraph that might be a heading for this one
436 std::list
<Element
*>::const_iterator prev
= rParentIt
;
437 ParagraphElement
* pPrevPara
= NULL
;
438 while( prev
!= elem
.Parent
->Children
.begin() )
441 pPrevPara
= dynamic_cast< ParagraphElement
* >(*prev
);
444 /* What constitutes a heading ? current hints are:
446 * - not too far away from this paragraph (two heading height max ?)
447 * - font larger or bold
448 * this is of course incomplete
449 * FIXME: improve hints for heading
451 // check for single line
452 if( pPrevPara
->isSingleLined( m_rProcessor
) )
454 double head_line_height
= pPrevPara
->getLineHeight( m_rProcessor
);
455 if( pPrevPara
->y
+ pPrevPara
->h
+ 2*head_line_height
> elem
.y
)
457 // check for larger font
458 if( head_line_height
> elem
.getLineHeight( m_rProcessor
) )
460 pPrevPara
->Type
= elem
.Headline
;
464 // check whether text of pPrevPara is bold (at least first text element)
465 // and this para is not bold (dito)
466 TextElement
* pPrevText
= pPrevPara
->getFirstTextChild();
467 TextElement
* pThisText
= elem
.getFirstTextChild();
468 if( pPrevText
&& pThisText
)
470 const FontAttributes
& rPrevFont
= m_rProcessor
.getFont( pPrevText
->FontId
);
471 const FontAttributes
& rThisFont
= m_rProcessor
.getFont( pThisText
->FontId
);
472 if( rPrevFont
.isBold
&& ! rThisFont
.isBold
)
473 pPrevPara
->Type
= elem
.Headline
;
484 void WriterXmlOptimizer::visit( PageElement
& elem
, const std::list
< Element
* >::const_iterator
& )
486 if( m_rProcessor
.getStatusIndicator().is() )
487 m_rProcessor
.getStatusIndicator()->setValue( elem
.PageNumber
);
489 // resolve hyperlinks
490 elem
.resolveHyperlinks();
492 elem
.resolveFontStyles( m_rProcessor
); // underlines and such
494 // FIXME: until hyperlinks and font effects are adjusted for
495 // geometrical search handle them before sorting
496 m_rProcessor
.sortElements( &elem
);
498 // find paragraphs in text
499 ParagraphElement
* pCurPara
= NULL
;
500 std::list
< Element
* >::iterator page_element
, next_page_element
;
501 next_page_element
= elem
.Children
.begin();
502 double fCurLineHeight
= 0.0; // average height of text items in current para
503 int nCurLineElements
= 0; // number of line contributing elements in current para
504 double line_left
= elem
.w
, line_right
= 0.0;
505 double column_width
= elem
.w
*0.75; // estimate text width
506 // TODO: guess columns
507 while( next_page_element
!= elem
.Children
.end() )
509 page_element
= next_page_element
++;
510 ParagraphElement
* pPagePara
= dynamic_cast<ParagraphElement
*>(*page_element
);
513 pCurPara
= pPagePara
;
514 // adjust line height and text items
515 fCurLineHeight
= 0.0;
516 nCurLineElements
= 0;
517 for( std::list
< Element
* >::iterator it
= pCurPara
->Children
.begin();
518 it
!= pCurPara
->Children
.end(); ++it
)
520 TextElement
* pTestText
= dynamic_cast<TextElement
*>(*it
);
523 fCurLineHeight
= (fCurLineHeight
*double(nCurLineElements
) + pTestText
->h
)/double(nCurLineElements
+1);
530 HyperlinkElement
* pLink
= dynamic_cast<HyperlinkElement
*>(*page_element
);
531 DrawElement
* pDraw
= dynamic_cast<DrawElement
*>(*page_element
);
532 if( ! pDraw
&& pLink
&& ! pLink
->Children
.empty() )
533 pDraw
= dynamic_cast<DrawElement
*>(pLink
->Children
.front() );
536 // insert small drawing objects as character, else leave them page bound
538 bool bInsertToParagraph
= false;
539 // first check if this is either inside the paragraph
540 if( pCurPara
&& pDraw
->y
< pCurPara
->y
+ pCurPara
->h
)
542 if( pDraw
->h
< fCurLineHeight
* 1.5 )
544 bInsertToParagraph
= true;
545 fCurLineHeight
= (fCurLineHeight
*double(nCurLineElements
) + pDraw
->h
)/double(nCurLineElements
+1);
547 // mark draw element as character
548 pDraw
->isCharacter
= true;
551 // or perhaps the draw element begins a new paragraph
552 else if( next_page_element
!= elem
.Children
.end() )
554 TextElement
* pText
= dynamic_cast<TextElement
*>(*next_page_element
);
557 ParagraphElement
* pPara
= dynamic_cast<ParagraphElement
*>(*next_page_element
);
558 if( pPara
&& ! pPara
->Children
.empty() )
559 pText
= dynamic_cast<TextElement
*>(pPara
->Children
.front());
561 if( pText
&& // check there is a text
562 pDraw
->h
< pText
->h
*1.5 && // and it is approx the same height
563 // and either upper or lower edge of pDraw is inside text's vertical range
564 ( ( pDraw
->y
>= pText
->y
&& pDraw
->y
<= pText
->y
+pText
->h
) ||
565 ( pDraw
->y
+pDraw
->h
>= pText
->y
&& pDraw
->y
+pDraw
->h
<= pText
->y
+pText
->h
)
569 bInsertToParagraph
= true;
570 fCurLineHeight
= pDraw
->h
;
571 nCurLineElements
= 1;
572 line_left
= pDraw
->x
;
573 line_right
= pDraw
->x
+ pDraw
->w
;
574 // begin a new paragraph
576 // mark draw element as character
577 pDraw
->isCharacter
= true;
581 if( ! bInsertToParagraph
)
588 TextElement
* pText
= dynamic_cast<TextElement
*>(*page_element
);
589 if( ! pText
&& pLink
&& ! pLink
->Children
.empty() )
590 pText
= dynamic_cast<TextElement
*>(pLink
->Children
.front());
593 Element
* pGeo
= pLink
? static_cast<Element
*>(pLink
) :
594 static_cast<Element
*>(pText
);
597 // there was already a text element, check for a new paragraph
598 if( nCurLineElements
> 0 )
600 // if the new text is significantly distant from the paragraph
601 // begin a new paragraph
602 if( pGeo
->y
> pCurPara
->y
+pCurPara
->h
+ fCurLineHeight
*0.5 )
603 pCurPara
= NULL
; // insert new paragraph
604 else if( pGeo
->y
> (pCurPara
->y
+pCurPara
->h
- fCurLineHeight
*0.05) )
606 // new paragraph if either the last line of the paragraph
607 // was significantly shorter than the paragraph as a whole
608 if( (line_right
- line_left
) < pCurPara
->w
*0.75 )
610 // or the last line was significantly smaller than the column width
611 else if( (line_right
- line_left
) < column_width
*0.75 )
616 // update line height/width
619 fCurLineHeight
= (fCurLineHeight
*double(nCurLineElements
) + pGeo
->h
)/double(nCurLineElements
+1);
621 if( pGeo
->x
< line_left
)
623 if( pGeo
->x
+pGeo
->w
> line_right
)
624 line_right
= pGeo
->x
+pGeo
->w
;
628 fCurLineHeight
= pGeo
->h
;
629 nCurLineElements
= 1;
631 line_right
= pGeo
->x
+ pGeo
->w
;
635 // move element to current paragraph
636 if( ! pCurPara
) // new paragraph, insert one
638 pCurPara
= m_rProcessor
.getElementFactory()->createParagraphElement( NULL
);
640 pCurPara
->Parent
= &elem
;
641 //insert new paragraph before current element
642 page_element
= elem
.Children
.insert( page_element
, pCurPara
);
643 // forward iterator to current element again
645 // update next_element which is now invalid
646 next_page_element
= page_element
;
647 ++ next_page_element
;
649 Element
* pCurEle
= *page_element
;
650 pCurEle
->setParent( page_element
, pCurPara
);
651 OSL_ENSURE( !pText
|| pCurEle
== pText
|| pCurEle
== pLink
, "paragraph child list in disorder" );
653 pCurPara
->updateGeometryWith( pCurEle
);
657 elem
.applyToChildren(*this);
659 // find possible header and footer
660 checkHeaderAndFooter( elem
);
663 void WriterXmlOptimizer::checkHeaderAndFooter( PageElement
& rElem
)
665 /* indicators for a header:
666 * - single line paragrah at top of page ( inside 15% page height)
667 * - at least linheight above the next paragr aph
669 * indicators for a footer likewise:
670 * - single line paragraph at bottom of page (inside 15% page height)
671 * - at least lineheight below the previous paragraph
675 // Note: the following assumes that the pages' chiuldren have been
676 // sorted geometrically
677 std::list
< Element
* >::iterator it
= rElem
.Children
.begin();
678 while( it
!= rElem
.Children
.end() )
680 ParagraphElement
* pPara
= dynamic_cast<ParagraphElement
*>(*it
);
683 if( pPara
->y
+pPara
->h
< rElem
.h
*0.15 && pPara
->isSingleLined( m_rProcessor
) )
685 std::list
< Element
* >::iterator next_it
= it
;
686 ParagraphElement
* pNextPara
= NULL
;
687 while( ++next_it
!= rElem
.Children
.end() && pNextPara
== NULL
)
689 pNextPara
= dynamic_cast<ParagraphElement
*>(*next_it
);
691 if( pNextPara
&& pNextPara
->y
> pPara
->y
+pPara
->h
*2 )
693 rElem
.HeaderElement
= pPara
;
694 pPara
->Parent
= NULL
;
695 rElem
.Children
.remove( pPara
);
704 std::list
< Element
* >::reverse_iterator rit
= rElem
.Children
.rbegin();
705 while( rit
!= rElem
.Children
.rend() )
707 ParagraphElement
* pPara
= dynamic_cast<ParagraphElement
*>(*rit
);
710 if( pPara
->y
> rElem
.h
*0.85 && pPara
->isSingleLined( m_rProcessor
) )
712 std::list
< Element
* >::reverse_iterator next_it
= rit
;
713 ParagraphElement
* pNextPara
= NULL
;
714 while( ++next_it
!= rElem
.Children
.rend() && pNextPara
== NULL
)
716 pNextPara
= dynamic_cast<ParagraphElement
*>(*next_it
);
718 if( pNextPara
&& pNextPara
->y
< pPara
->y
-pPara
->h
*2 )
720 rElem
.FooterElement
= pPara
;
721 pPara
->Parent
= NULL
;
722 rElem
.Children
.remove( pPara
);
731 void WriterXmlOptimizer::optimizeTextElements(Element
& rParent
)
733 if( rParent
.Children
.empty() ) // this should not happen
735 OSL_ENSURE( 0, "empty paragraph optimized" );
739 // concatenate child elements with same font id
740 std::list
< Element
* >::iterator next
= rParent
.Children
.begin();
741 std::list
< Element
* >::iterator it
= next
++;
742 FrameElement
* pFrame
= dynamic_cast<FrameElement
*>(rParent
.Parent
);
743 bool bRotatedFrame
= false;
746 const GraphicsContext
& rFrameGC
= m_rProcessor
.getGraphicsContext( pFrame
->GCId
);
747 if( rFrameGC
.isRotatedOrSkewed() )
748 bRotatedFrame
= true;
750 while( next
!= rParent
.Children
.end() )
752 bool bConcat
= false;
753 TextElement
* pCur
= dynamic_cast<TextElement
*>(*it
);
756 TextElement
* pNext
= dynamic_cast<TextElement
*>(*next
);
759 const GraphicsContext
& rCurGC
= m_rProcessor
.getGraphicsContext( pCur
->GCId
);
760 const GraphicsContext
& rNextGC
= m_rProcessor
.getGraphicsContext( pNext
->GCId
);
762 // line and space optimization; works only in strictly horizontal mode
765 && ! rCurGC
.isRotatedOrSkewed()
766 && ! rNextGC
.isRotatedOrSkewed()
767 && pNext
->Text
.charAt( 0 ) != sal_Unicode(' ')
768 && pCur
->Text
.getLength() > 0
769 && pCur
->Text
.charAt( pCur
->Text
.getLength()-1 ) != sal_Unicode(' ')
772 // check for new line in paragraph
773 if( pNext
->y
> pCur
->y
+pCur
->h
)
776 // check whether a space would should be inserted or a hyphen removed
777 sal_Unicode aLastCode
= pCur
->Text
.charAt( pCur
->Text
.getLength()-1 );
779 || aLastCode
== 0x2010
780 || (aLastCode
>= 0x2012 && aLastCode
<= 0x2015)
781 || aLastCode
== 0xff0d
785 pCur
->Text
.setLength( pCur
->Text
.getLength()-1 );
787 // append a space unless there is a non breaking hyphen
788 else if( aLastCode
!= 0x2011 )
790 pCur
->Text
.append( sal_Unicode( ' ' ) );
793 else // we're continuing the same line
795 // check whether a space would should be inserted
796 // check for a small horizontal offset
797 if( pCur
->x
+ pCur
->w
+ pNext
->h
*0.15 < pNext
->x
)
799 pCur
->Text
.append( sal_Unicode(' ') );
803 // concatenate consecutive text elements unless there is a
804 // font or text color or matrix change, leave a new span in that case
805 if( pCur
->FontId
== pNext
->FontId
&&
806 rCurGC
.FillColor
.Red
== rNextGC
.FillColor
.Red
&&
807 rCurGC
.FillColor
.Green
== rNextGC
.FillColor
.Green
&&
808 rCurGC
.FillColor
.Blue
== rNextGC
.FillColor
.Blue
&&
809 rCurGC
.FillColor
.Alpha
== rNextGC
.FillColor
.Alpha
&&
810 rCurGC
.Transformation
== rNextGC
.Transformation
813 pCur
->updateGeometryWith( pNext
);
814 // append text to current element
815 pCur
->Text
.append( pNext
->Text
.getStr(), pNext
->Text
.getLength() );
816 // append eventual children to current element
817 // and clear children (else the children just
818 // appended to pCur would be destroyed)
819 pCur
->Children
.splice( pCur
->Children
.end(), pNext
->Children
);
820 // get rid of the now useless element
821 rParent
.Children
.erase( next
);
827 else if( dynamic_cast<HyperlinkElement
*>(*it
) )
828 optimizeTextElements( **it
);
842 void WriterXmlOptimizer::visit( DocumentElement
& elem
, const std::list
< Element
* >::const_iterator
&)
844 elem
.applyToChildren(*this);
847 //////////////////////////////////////////////////////////////////////////////////
850 void WriterXmlFinalizer::visit( PolyPolyElement
& elem
, const std::list
< Element
* >::const_iterator
& )
852 // xxx TODO copied from DrawElement
853 const GraphicsContext
& rGC
= m_rProcessor
.getGraphicsContext(elem
.GCId
);
855 aProps
[ USTR( "style:family" ) ] = USTR( "graphic" );
857 PropertyMap aGCProps
;
859 // TODO(F3): proper dash emulation
860 if( elem
.Action
& PATH_STROKE
)
862 aGCProps
[ USTR("draw:stroke") ] = rGC
.DashArray
.empty() ? USTR("solid") : USTR("dash");
863 aGCProps
[ USTR("svg:stroke-color") ] = getColorString( rGC
.LineColor
);
864 if( rGC
.LineWidth
!= 0.0 )
866 ::basegfx::B2DVector
aVec(rGC
.LineWidth
,0);
867 aVec
*= rGC
.Transformation
;
869 aVec
.setX ( convPx2mmPrec2( aVec
.getX() )*100.0 );
870 aVec
.setY ( convPx2mmPrec2( aVec
.getY() )*100.0 );
872 aGCProps
[ USTR("svg:stroke-width") ] = rtl::OUString::valueOf( aVec
.getLength() );
877 aGCProps
[ USTR("draw:stroke") ] = USTR("none");
880 // TODO(F1): check whether stuff could be emulated by gradient/bitmap/hatch
881 if( elem
.Action
& (PATH_FILL
| PATH_EOFILL
) )
883 aGCProps
[ USTR("draw:fill") ] = USTR("solid");
884 aGCProps
[ USTR("draw:fill-color") ] = getColorString( rGC
.FillColor
);
888 aGCProps
[ USTR("draw:fill") ] = USTR("none");
891 StyleContainer::Style
aStyle( "style:style", aProps
);
892 StyleContainer::Style
aSubStyle( "style:graphic-properties", aGCProps
);
893 aStyle
.SubStyles
.push_back( &aSubStyle
);
895 elem
.StyleId
= m_rStyleContainer
.getStyleId( aStyle
);
898 void WriterXmlFinalizer::visit( HyperlinkElement
&, const std::list
< Element
* >::const_iterator
& )
902 void WriterXmlFinalizer::visit( TextElement
& elem
, const std::list
< Element
* >::const_iterator
& )
904 const FontAttributes
& rFont
= m_rProcessor
.getFont( elem
.FontId
);
906 aProps
[ USTR( "style:family" ) ] = USTR( "text" );
908 PropertyMap aFontProps
;
911 aFontProps
[ USTR( "fo:font-family" ) ] = rFont
.familyName
;
915 aFontProps
[ USTR( "fo:font-weight" ) ] = USTR( "bold" );
916 aFontProps
[ USTR( "fo:font-weight-asian" ) ] = USTR( "bold" );
917 aFontProps
[ USTR( "fo:font-weight-complex" ) ] = USTR( "bold" );
922 aFontProps
[ USTR( "fo:font-style" ) ] = USTR( "italic" );
923 aFontProps
[ USTR( "fo:font-style-asian" ) ] = USTR( "italic" );
924 aFontProps
[ USTR( "fo:font-style-complex" ) ] = USTR( "italic" );
927 if( rFont
.isUnderline
)
929 aFontProps
[ USTR( "style:text-underline-style" ) ] = USTR( "solid" );
930 aFontProps
[ USTR( "style:text-underline-width" ) ] = USTR( "auto" );
931 aFontProps
[ USTR( "style:text-underline-color" ) ] = USTR( "font-color" );
934 if( rFont
.isOutline
)
936 aFontProps
[ USTR( "style:text-outline" ) ] = USTR( "true" );
939 rtl::OUStringBuffer
aBuf( 32 );
940 aBuf
.append( rFont
.size
*72/PDFI_OUTDEV_RESOLUTION
);
941 aBuf
.appendAscii( "pt" );
942 rtl::OUString aFSize
= aBuf
.makeStringAndClear();
943 aFontProps
[ USTR( "fo:font-size" ) ] = aFSize
;
944 aFontProps
[ USTR( "style:font-size-asian" ) ] = aFSize
;
945 aFontProps
[ USTR( "style:font-size-complex" ) ] = aFSize
;
947 const GraphicsContext
& rGC
= m_rProcessor
.getGraphicsContext( elem
.GCId
);
948 aFontProps
[ USTR( "fo:color" ) ] = getColorString( rFont
.isOutline
? rGC
.LineColor
: rGC
.FillColor
);
950 StyleContainer::Style
aStyle( "style:style", aProps
);
951 StyleContainer::Style
aSubStyle( "style:text-properties", aFontProps
);
952 aStyle
.SubStyles
.push_back( &aSubStyle
);
953 elem
.StyleId
= m_rStyleContainer
.getStyleId( aStyle
);
956 void WriterXmlFinalizer::visit( ParagraphElement
& elem
, const std::list
< Element
* >::const_iterator
& rParentIt
)
958 PropertyMap aParaProps
;
962 // check for center alignement
963 // criterion: paragraph is small relative to parent and distributed around its center
964 double p_x
= elem
.Parent
->x
;
965 double p_y
= elem
.Parent
->y
;
966 double p_w
= elem
.Parent
->w
;
967 double p_h
= elem
.Parent
->h
;
969 PageElement
* pPage
= dynamic_cast<PageElement
*>(elem
.Parent
);
972 p_x
+= pPage
->LeftMargin
;
973 p_y
+= pPage
->TopMargin
;
974 p_w
-= pPage
->LeftMargin
+pPage
->RightMargin
;
975 p_h
-= pPage
->TopMargin
+pPage
->BottomMargin
;
977 bool bIsCenter
= false;
978 if( elem
.w
< ( p_w
/2) )
980 double delta
= elem
.w
/4;
981 // allow very small paragraphs to deviate a little more
982 // relative to parent's center
985 if( fabs( elem
.x
+elem
.w
/2 - ( p_x
+ p_w
/2) ) < delta
||
986 (pPage
&& fabs( elem
.x
+elem
.w
/2 - (pPage
->x
+ pPage
->w
/2) ) < delta
) )
989 aParaProps
[ USTR( "fo:text-align" ) ] = USTR( "center" );
992 if( ! bIsCenter
&& elem
.x
> p_x
+ p_w
/10 )
995 rtl::OUStringBuffer
aBuf( 32 );
996 aBuf
.append( convPx2mm( elem
.x
- p_x
) );
997 aBuf
.appendAscii( "mm" );
998 aParaProps
[ USTR( "fo:margin-left" ) ] = aBuf
.makeStringAndClear();
1001 // check whether to leave some space to next paragraph
1002 // find wether there is a next paragraph
1003 std::list
< Element
* >::const_iterator it
= rParentIt
;
1004 const ParagraphElement
* pNextPara
= NULL
;
1005 while( ++it
!= elem
.Parent
->Children
.end() && ! pNextPara
)
1006 pNextPara
= dynamic_cast< const ParagraphElement
* >(*it
);
1009 if( pNextPara
->y
- (elem
.y
+elem
.h
) > convmm2Px( 10 ) )
1011 rtl::OUStringBuffer
aBuf( 32 );
1012 aBuf
.append( convPx2mm( pNextPara
->y
- (elem
.y
+elem
.h
) ) );
1013 aBuf
.appendAscii( "mm" );
1014 aParaProps
[ USTR( "fo:margin-bottom" ) ] = aBuf
.makeStringAndClear();
1019 if( ! aParaProps
.empty() )
1022 aProps
[ USTR( "style:family" ) ] = USTR( "paragraph" );
1023 StyleContainer::Style
aStyle( "style:style", aProps
);
1024 StyleContainer::Style
aSubStyle( "style:paragraph-properties", aParaProps
);
1025 aStyle
.SubStyles
.push_back( &aSubStyle
);
1026 elem
.StyleId
= m_rStyleContainer
.getStyleId( aStyle
);
1029 elem
.applyToChildren(*this);
1032 void WriterXmlFinalizer::visit( FrameElement
& elem
, const std::list
< Element
* >::const_iterator
&)
1035 aProps
[ USTR( "style:family" ) ] = USTR( "graphic" );
1037 PropertyMap aGCProps
;
1039 aGCProps
[ USTR("draw:stroke") ] = USTR("none");
1040 aGCProps
[ USTR("draw:fill") ] = USTR("none");
1042 StyleContainer::Style
aStyle( "style:style", aProps
);
1043 StyleContainer::Style
aSubStyle( "style:graphic-properties", aGCProps
);
1044 aStyle
.SubStyles
.push_back( &aSubStyle
);
1046 elem
.StyleId
= m_rStyleContainer
.getStyleId( aStyle
);
1047 elem
.applyToChildren(*this);
1050 void WriterXmlFinalizer::visit( ImageElement
&, const std::list
< Element
* >::const_iterator
& )
1054 void WriterXmlFinalizer::setFirstOnPage( ParagraphElement
& rElem
,
1055 StyleContainer
& rStyles
,
1056 const rtl::OUString
& rMasterPageName
)
1059 if( rElem
.StyleId
!= -1 )
1061 const PropertyMap
* pProps
= rStyles
.getProperties( rElem
.StyleId
);
1066 aProps
[ USTR( "style:family" ) ] = USTR( "paragraph" );
1067 aProps
[ USTR( "style:master-page-name" ) ] = rMasterPageName
;
1069 if( rElem
.StyleId
!= -1 )
1070 rElem
.StyleId
= rStyles
.setProperties( rElem
.StyleId
, aProps
);
1073 StyleContainer::Style
aStyle( "style:style", aProps
);
1074 rElem
.StyleId
= rStyles
.getStyleId( aStyle
);
1078 void WriterXmlFinalizer::visit( PageElement
& elem
, const std::list
< Element
* >::const_iterator
& )
1080 if( m_rProcessor
.getStatusIndicator().is() )
1081 m_rProcessor
.getStatusIndicator()->setValue( elem
.PageNumber
);
1083 // transform from pixel to mm
1084 double page_width
= convPx2mm( elem
.w
), page_height
= convPx2mm( elem
.h
);
1086 // calculate page margins out of the relevant children (paragraphs)
1087 elem
.TopMargin
= elem
.h
, elem
.BottomMargin
= 0, elem
.LeftMargin
= elem
.w
, elem
.RightMargin
= 0;
1088 // first element should be a paragraphy
1089 ParagraphElement
* pFirstPara
= NULL
;
1090 for( std::list
< Element
* >::const_iterator it
= elem
.Children
.begin(); it
!= elem
.Children
.end(); ++it
)
1092 if( dynamic_cast<ParagraphElement
*>( *it
) )
1094 if( (*it
)->x
< elem
.LeftMargin
)
1095 elem
.LeftMargin
= (*it
)->x
;
1096 if( (*it
)->y
< elem
.TopMargin
)
1097 elem
.TopMargin
= (*it
)->y
;
1098 if( (*it
)->x
+ (*it
)->w
> elem
.w
- elem
.RightMargin
)
1099 elem
.RightMargin
= elem
.w
- ((*it
)->x
+ (*it
)->w
);
1100 if( (*it
)->y
+ (*it
)->h
> elem
.h
- elem
.BottomMargin
)
1101 elem
.BottomMargin
= elem
.h
- ((*it
)->y
+ (*it
)->h
);
1103 pFirstPara
= dynamic_cast<ParagraphElement
*>( *it
);
1106 if( elem
.HeaderElement
&& elem
.HeaderElement
->y
< elem
.TopMargin
)
1107 elem
.TopMargin
= elem
.HeaderElement
->y
;
1108 if( elem
.FooterElement
&& elem
.FooterElement
->y
+elem
.FooterElement
->h
> elem
.h
- elem
.BottomMargin
)
1109 elem
.BottomMargin
= elem
.h
- (elem
.FooterElement
->y
+ elem
.FooterElement
->h
);
1111 // transform margins to mm
1112 double left_margin
= convPx2mm( elem
.LeftMargin
);
1113 double right_margin
= convPx2mm( elem
.RightMargin
);
1114 double top_margin
= convPx2mm( elem
.TopMargin
);
1115 double bottom_margin
= convPx2mm( elem
.BottomMargin
);
1118 // use default page margins
1125 // round left/top margin to nearest mm
1126 left_margin
= rtl_math_round( left_margin
, 0, rtl_math_RoundingMode_Floor
);
1127 top_margin
= rtl_math_round( top_margin
, 0, rtl_math_RoundingMode_Floor
);
1128 // round (fuzzy) right/bottom margin to nearest cm
1129 right_margin
= rtl_math_round( right_margin
, right_margin
>= 10 ? -1 : 0, rtl_math_RoundingMode_Floor
);
1130 bottom_margin
= rtl_math_round( bottom_margin
, bottom_margin
>= 10 ? -1 : 0, rtl_math_RoundingMode_Floor
);
1132 // set reasonable default in case of way too large margins
1133 // e.g. no paragraph case
1134 if( left_margin
> page_width
/2.0 - 10 )
1136 if( right_margin
> page_width
/2.0 - 10 )
1138 if( top_margin
> page_height
/2.0 - 10 )
1140 if( bottom_margin
> page_height
/2.0 - 10 )
1143 // catch the weird cases
1144 if( left_margin
< 0 )
1146 if( right_margin
< 0 )
1148 if( top_margin
< 0 )
1150 if( bottom_margin
< 0 )
1153 // widely differing margins are unlikely to be correct
1154 if( right_margin
> left_margin
*1.5 )
1155 right_margin
= left_margin
;
1157 elem
.LeftMargin
= convmm2Px( left_margin
);
1158 elem
.RightMargin
= convmm2Px( right_margin
);
1159 elem
.TopMargin
= convmm2Px( top_margin
);
1160 elem
.BottomMargin
= convmm2Px( bottom_margin
);
1162 // get styles for paragraphs
1163 PropertyMap aPageProps
;
1164 PropertyMap aPageLayoutProps
;
1165 rtl::OUStringBuffer
aBuf( 64 );
1166 aPageLayoutProps
[ USTR( "fo:page-width" ) ] = unitMMString( page_width
);
1167 aPageLayoutProps
[ USTR( "fo:page-height" ) ] = unitMMString( page_height
);
1168 aPageLayoutProps
[ USTR( "style:print-orientation" ) ]
1169 = elem
.w
< elem
.h
? USTR( "portrait" ) : USTR( "landscape" );
1170 aPageLayoutProps
[ USTR( "fo:margin-top" ) ] = unitMMString( top_margin
);
1171 aPageLayoutProps
[ USTR( "fo:margin-bottom" ) ] = unitMMString( bottom_margin
);
1172 aPageLayoutProps
[ USTR( "fo:margin-left" ) ] = unitMMString( left_margin
);
1173 aPageLayoutProps
[ USTR( "fo:margin-right" ) ] = unitMMString( right_margin
);
1174 aPageLayoutProps
[ USTR( "style:writing-mode" ) ]= USTR( "lr-tb" );
1176 StyleContainer::Style
aStyle( "style:page-layout", aPageProps
);
1177 StyleContainer::Style
aSubStyle( "style:page-layout-properties", aPageLayoutProps
);
1178 aStyle
.SubStyles
.push_back(&aSubStyle
);
1179 sal_Int32 nPageStyle
= m_rStyleContainer
.impl_getStyleId( aStyle
, false );
1181 // create master page
1182 rtl::OUString aMasterPageLayoutName
= m_rStyleContainer
.getStyleName( nPageStyle
);
1183 aPageProps
[ USTR( "style:page-layout-name" ) ] = aMasterPageLayoutName
;
1184 StyleContainer::Style
aMPStyle( "style:master-page", aPageProps
);
1185 StyleContainer::Style
aHeaderStyle( "style:header", PropertyMap() );
1186 StyleContainer::Style
aFooterStyle( "style:footer", PropertyMap() );
1187 if( elem
.HeaderElement
)
1189 elem
.HeaderElement
->visitedBy( *this, std::list
<Element
*>::iterator() );
1190 aHeaderStyle
.ContainedElement
= elem
.HeaderElement
;
1191 aMPStyle
.SubStyles
.push_back( &aHeaderStyle
);
1193 if( elem
.FooterElement
)
1195 elem
.FooterElement
->visitedBy( *this, std::list
<Element
*>::iterator() );
1196 aFooterStyle
.ContainedElement
= elem
.FooterElement
;
1197 aMPStyle
.SubStyles
.push_back( &aFooterStyle
);
1199 elem
.StyleId
= m_rStyleContainer
.impl_getStyleId( aMPStyle
,false );
1202 rtl::OUString aMasterPageName
= m_rStyleContainer
.getStyleName( elem
.StyleId
);
1204 // create styles for children
1205 elem
.applyToChildren(*this);
1207 // no paragraph or other elements before the first paragraph
1210 pFirstPara
= m_rProcessor
.getElementFactory()->createParagraphElement( NULL
);
1211 pFirstPara
->Parent
= &elem
;
1212 elem
.Children
.push_front( pFirstPara
);
1214 setFirstOnPage(*pFirstPara
, m_rStyleContainer
, aMasterPageName
);
1217 void WriterXmlFinalizer::visit( DocumentElement
& elem
, const std::list
< Element
* >::const_iterator
& )
1219 elem
.applyToChildren(*this);