LibreOffice Module sdext (master) 1
writertreevisiting.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <sal/config.h>
21#include <sal/log.hxx>
22#include <string_view>
23
24#include <pdfiprocessor.hxx>
25#include <xmlemitter.hxx>
26#include <pdfihelper.hxx>
27#include <imagecontainer.hxx>
28#include "style.hxx"
30#include <genericelements.hxx>
31
33#include <osl/diagnose.h>
34#include <com/sun/star/i18n/CharacterClassification.hpp>
35#include <com/sun/star/i18n/DirectionProperty.hpp>
36#include <comphelper/string.hxx>
37
38using namespace ::com::sun::star;
39using namespace ::com::sun::star::lang;
40using namespace ::com::sun::star::i18n;
41using namespace ::com::sun::star::uno;
42
43namespace pdfi
44{
45
46const Reference<XBreakIterator>& WriterXmlOptimizer::GetBreakIterator()
47{
48 if (!mxBreakIter.is())
49 {
50 mxBreakIter = BreakIterator::create(m_rProcessor.m_xContext);
51 }
52 return mxBreakIter;
53}
54
55const Reference< XCharacterClassification >& WriterXmlEmitter::GetCharacterClassification()
56{
57 if ( !mxCharClass.is() )
58 {
59 Reference< XComponentContext > xContext( m_rEmitContext.m_xContext, uno::UNO_SET_THROW );
60 mxCharClass = CharacterClassification::create(xContext);
61 }
62 return mxCharClass;
63}
64
65void WriterXmlEmitter::visit( HyperlinkElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
66{
67 if( elem.Children.empty() )
68 return;
69
70 const char* pType = dynamic_cast<DrawElement*>(elem.Children.front().get()) ? "draw:a" : "text:a";
71
72 PropertyMap aProps;
73 aProps[ "xlink:type" ] = "simple";
74 aProps[ "xlink:href" ] = elem.URI;
75 aProps[ "office:target-frame-name" ] = "_blank";
76 aProps[ "xlink:show" ] = "new";
77
78 m_rEmitContext.rEmitter.beginTag( pType, aProps );
79 auto this_it = elem.Children.begin();
80 while( this_it != elem.Children.end() && this_it->get() != &elem )
81 {
82 (*this_it)->visitedBy( *this, this_it );
83 ++this_it;
84 }
86}
87
88void WriterXmlEmitter::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
89{
90 if( elem.Text.isEmpty() )
91 return;
92
93 PropertyMap aProps = {};
94 const sal_Unicode strSpace = 0x0020;
95 const sal_Unicode strNbSpace = 0x00A0;
96 const sal_Unicode tabSpace = 0x0009;
97
98 if( elem.StyleId != -1 )
99 {
100 aProps[ OUString( "text:style-name" ) ] =
102 }
103
104 OUString str(elem.Text.toString());
105
106 // Check for RTL
107 bool isRTL = false;
108 Reference< i18n::XCharacterClassification > xCC( GetCharacterClassification() );
109 if( xCC.is() )
110 {
111 for(int i=1; i< elem.Text.getLength(); i++)
112 {
113 i18n::DirectionProperty nType = static_cast<i18n::DirectionProperty>(xCC->getCharacterDirection( str, i ));
114 if ( nType == i18n::DirectionProperty_RIGHT_TO_LEFT ||
115 nType == i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC ||
116 nType == i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING ||
117 nType == i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE
118 )
119 isRTL = true;
120 }
121 }
122
123 if (isRTL) // If so, reverse string
124 {
125 // First, produce mirrored-image for each code point which has the Bidi_Mirrored property.
127 // Then, reverse the code points in the string, in backward order.
128 str = ::comphelper::string::reverseCodePoints(str);
129 }
130
131 m_rEmitContext.rEmitter.beginTag( "text:span", aProps );
132
133 sal_Unicode strToken;
134 for (int i = 0; i < elem.Text.getLength(); i++)
135 {
136 strToken = str[i];
137 if (strToken == strSpace || strToken == strNbSpace)
138 {
139 aProps["text:c"] = "1";
140 m_rEmitContext.rEmitter.beginTag("text:s", aProps);
142 }
143 else if (strToken == tabSpace)
144 {
145 m_rEmitContext.rEmitter.beginTag("text:tab", aProps);
146 m_rEmitContext.rEmitter.endTag("text:tab");
147 }
148 else
149 m_rEmitContext.rEmitter.write(OUString(strToken));
150 }
151
152 auto this_it = elem.Children.begin();
153 while( this_it != elem.Children.end() && this_it->get() != &elem )
154 {
155 (*this_it)->visitedBy( *this, this_it );
156 ++this_it;
157 }
158
159 m_rEmitContext.rEmitter.endTag( "text:span" );
160}
161
162void WriterXmlEmitter::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
163{
164 PropertyMap aProps;
165 if( elem.StyleId != -1 )
166 {
167 aProps[ "text:style-name" ] = m_rEmitContext.rStyles.getStyleName( elem.StyleId );
168 }
169 const char* pTagType = "text:p";
170 if( elem.Type == ParagraphElement::Headline )
171 pTagType = "text:h";
172 m_rEmitContext.rEmitter.beginTag( pTagType, aProps );
173
174 auto this_it = elem.Children.begin();
175 while( this_it != elem.Children.end() && this_it->get() != &elem )
176 {
177 (*this_it)->visitedBy( *this, this_it );
178 ++this_it;
179 }
180
181 m_rEmitContext.rEmitter.endTag( pTagType );
182}
183
185 PropertyMap& rProps,
186 const EmitContext& rEmitContext )
187{
188 double rel_x = rElem.x, rel_y = rElem.y;
189
190 // find anchor type by recursing though parents
191 Element* pAnchor = &rElem;
192 ParagraphElement* pParaElt = nullptr;
193 PageElement* pPage = nullptr;
194 while ((pAnchor = pAnchor->Parent))
195 {
196 if ((pParaElt = dynamic_cast<ParagraphElement*>(pAnchor)))
197 break;
198 if ((pPage = dynamic_cast<PageElement*>(pAnchor)))
199 break;
200 }
201 if( pAnchor )
202 {
203 if (pParaElt)
204 {
205 rProps[ "text:anchor-type" ] = rElem.isCharacter
206 ? std::u16string_view(u"character") : std::u16string_view(u"paragraph");
207 }
208 else
209 {
210 assert(pPage); // guaranteed by the while loop above
211 rProps[ "text:anchor-type" ] = "page";
212 rProps[ "text:anchor-page-number" ] = OUString::number(pPage->PageNumber);
213 }
214 rel_x -= pAnchor->x;
215 rel_y -= pAnchor->y;
216 }
217
218 rProps[ "draw:z-index" ] = OUString::number( rElem.ZOrder );
219 rProps[ "draw:style-name"] = rEmitContext.rStyles.getStyleName( rElem.StyleId );
220 rProps[ "svg:width" ] = convertPixelToUnitString( rElem.w );
221 rProps[ "svg:height" ] = convertPixelToUnitString( rElem.h );
222
223 const GraphicsContext& rGC =
224 rEmitContext.rProcessor.getGraphicsContext( rElem.GCId );
225 if( rGC.Transformation.isIdentity() )
226 {
227 if( !rElem.isCharacter )
228 {
229 rProps[ "svg:x" ] = convertPixelToUnitString( rel_x );
230 rProps[ "svg:y" ] = convertPixelToUnitString( rel_y );
231 }
232 }
233 else
234 {
235 basegfx::B2DTuple aScale, aTranslation;
236 double fRotate, fShearX;
237
238 rGC.Transformation.decompose( aScale, aTranslation, fRotate, fShearX );
239
240 OUStringBuffer aBuf( 256 );
241
242 // TODO(F2): general transformation case missing; if implemented, note
243 // that ODF rotation is oriented the other way
244
245 // build transformation string
246 if (rElem.MirrorVertical)
247 {
248 // At some point, rElem.h may start arriving positive,
249 // so use robust adjusting math
250 rel_y -= std::abs(rElem.h);
251 if (!aBuf.isEmpty())
252 aBuf.append(' ');
253 aBuf.append("scale( 1.0 -1.0 )");
254 }
255 if( fShearX != 0.0 )
256 {
257 aBuf.append( "skewX( " );
258 aBuf.append( fShearX );
259 aBuf.append( " )" );
260 }
261 if( fRotate != 0.0 )
262 {
263 if( !aBuf.isEmpty() )
264 aBuf.append( ' ' );
265 aBuf.append( "rotate( " );
266 aBuf.append( -fRotate );
267 aBuf.append( " )" );
268
269 }
270 if( ! rElem.isCharacter )
271 {
272 if( !aBuf.isEmpty() )
273 aBuf.append( ' ' );
274 aBuf.append( "translate( " );
275 aBuf.append( convertPixelToUnitString( rel_x ) );
276 aBuf.append( ' ' );
277 aBuf.append( convertPixelToUnitString( rel_y ) );
278 aBuf.append( " )" );
279 }
280
281 rProps[ "draw:transform" ] = aBuf.makeStringAndClear();
282 }
283}
284
285void WriterXmlEmitter::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
286{
287 if( elem.Children.empty() )
288 return;
289
290 bool bTextBox = (dynamic_cast<ParagraphElement*>(elem.Children.front().get()) != nullptr);
291 PropertyMap aFrameProps;
292 fillFrameProps( elem, aFrameProps, m_rEmitContext );
293 m_rEmitContext.rEmitter.beginTag( "draw:frame", aFrameProps );
294 if( bTextBox )
295 m_rEmitContext.rEmitter.beginTag( "draw:text-box", PropertyMap() );
296
297 auto this_it = elem.Children.begin();
298 while( this_it != elem.Children.end() && this_it->get() != &elem )
299 {
300 (*this_it)->visitedBy( *this, this_it );
301 ++this_it;
302 }
303
304 if( bTextBox )
305 m_rEmitContext.rEmitter.endTag( "draw:text-box" );
306 m_rEmitContext.rEmitter.endTag( "draw:frame" );
307}
308
309void WriterXmlEmitter::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
310{
311 elem.updateGeometry();
312 /* note:
313 * aw recommends using 100dth of mm in all respects since the xml import
314 * (a) is buggy (see issue 37213)
315 * (b) is optimized for 100dth of mm and does not scale itself then,
316 * this does not gain us speed but makes for smaller rounding errors since
317 * the xml importer coordinates are integer based
318 */
319 for (sal_uInt32 i = 0; i< elem.PolyPoly.count(); i++)
320 {
321 basegfx::B2DPolygon b2dPolygon = elem.PolyPoly.getB2DPolygon( i );
322
323 for ( sal_uInt32 j = 0; j< b2dPolygon.count(); j++ )
324 {
326 basegfx::B2DPoint nextPoint;
327 point = b2dPolygon.getB2DPoint( j );
328
329 basegfx::B2DPoint prevPoint = b2dPolygon.getPrevControlPoint( j ) ;
330
331 point.setX( convPx2mmPrec2( point.getX() )*100.0 );
332 point.setY( convPx2mmPrec2( point.getY() )*100.0 );
333
334 if ( b2dPolygon.isPrevControlPointUsed( j ) )
335 {
336 prevPoint.setX( convPx2mmPrec2( prevPoint.getX() )*100.0 );
337 prevPoint.setY( convPx2mmPrec2( prevPoint.getY() )*100.0 );
338 }
339
340 if ( b2dPolygon.isNextControlPointUsed( j ) )
341 {
342 nextPoint = b2dPolygon.getNextControlPoint( j ) ;
343 nextPoint.setX( convPx2mmPrec2( nextPoint.getX() )*100.0 );
344 nextPoint.setY( convPx2mmPrec2( nextPoint.getY() )*100.0 );
345 }
346
347 b2dPolygon.setB2DPoint( j, point );
348
349 if ( b2dPolygon.isPrevControlPointUsed( j ) )
350 b2dPolygon.setPrevControlPoint( j , prevPoint ) ;
351
352 if ( b2dPolygon.isNextControlPointUsed( j ) )
353 b2dPolygon.setNextControlPoint( j , nextPoint ) ;
354 }
355
356 elem.PolyPoly.setB2DPolygon( i, b2dPolygon );
357 }
358
359 PropertyMap aProps;
360 fillFrameProps( elem, aProps, m_rEmitContext );
361 OUStringBuffer aBuf( 64 );
362 aBuf.append( "0 0 " );
363 aBuf.append( convPx2mmPrec2(elem.w)*100.0 );
364 aBuf.append( ' ' );
365 aBuf.append( convPx2mmPrec2(elem.h)*100.0 );
366 aProps[ "svg:viewBox" ] = aBuf.makeStringAndClear();
367 aProps[ "svg:d" ] = basegfx::utils::exportToSvgD( elem.PolyPoly, true, true, false );
368
369 m_rEmitContext.rEmitter.beginTag( "draw:path", aProps );
370 m_rEmitContext.rEmitter.endTag( "draw:path" );
371}
372
373void WriterXmlEmitter::visit( ImageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
374{
375 PropertyMap aImageProps;
376 m_rEmitContext.rEmitter.beginTag( "draw:image", aImageProps );
377 m_rEmitContext.rEmitter.beginTag( "office:binary-data", PropertyMap() );
379 m_rEmitContext.rEmitter.endTag( "office:binary-data" );
380 m_rEmitContext.rEmitter.endTag( "draw:image" );
381}
382
383void WriterXmlEmitter::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
384{
387
388 auto this_it = elem.Children.begin();
389 while( this_it != elem.Children.end() && this_it->get() != &elem )
390 {
391 (*this_it)->visitedBy( *this, this_it );
392 ++this_it;
393 }
394}
395
396void WriterXmlEmitter::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
397{
398 m_rEmitContext.rEmitter.beginTag( "office:body", PropertyMap() );
399 m_rEmitContext.rEmitter.beginTag( "office:text", PropertyMap() );
400
401 for( const auto& rxChild : elem.Children )
402 {
403 PageElement* pPage = dynamic_cast<PageElement*>(rxChild.get());
404 if( pPage )
405 {
406 // emit only page anchored objects
407 // currently these are only DrawElement types
408 for( auto child_it = pPage->Children.begin(); child_it != pPage->Children.end(); ++child_it )
409 {
410 if( dynamic_cast<DrawElement*>(child_it->get()) != nullptr )
411 (*child_it)->visitedBy( *this, child_it );
412 }
413 }
414 }
415
416 // do not emit page anchored objects, they are emitted before
417 // (must precede all pages in writer document) currently these are
418 // only DrawElement types
419 for( auto it = elem.Children.begin(); it != elem.Children.end(); ++it )
420 {
421 if( dynamic_cast<DrawElement*>(it->get()) != nullptr )
422 (*it)->visitedBy( *this, it );
423 }
424
425 m_rEmitContext.rEmitter.endTag( "office:text" );
426 m_rEmitContext.rEmitter.endTag( "office:body" );
427}
428
429
430void WriterXmlOptimizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
431{
432}
433
434void WriterXmlOptimizer::visit( TextElement&, const std::list< std::unique_ptr<Element> >::const_iterator&)
435{
436}
437
438void WriterXmlOptimizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
439{
440 elem.applyToChildren(*this);
441}
442
443void WriterXmlOptimizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
444{
445}
446
447void WriterXmlOptimizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& elemIt )
448{
449 /* note: optimize two consecutive PolyPolyElements that
450 * have the same path but one of which is a stroke while
451 * the other is a fill
452 */
453 if( !elem.Parent )
454 return;
455 // find following PolyPolyElement in parent's children list
456 if( elemIt == elem.Parent->Children.end() )
457 return;
458 auto next_it = elemIt;
459 ++next_it;
460 if( next_it == elem.Parent->Children.end() )
461 return;
462
463 PolyPolyElement* pNext = dynamic_cast<PolyPolyElement*>(next_it->get());
464 if( !pNext || pNext->PolyPoly != elem.PolyPoly )
465 return;
466
467 const GraphicsContext& rNextGC =
469 const GraphicsContext& rThisGC =
471
472 if( !(rThisGC.BlendMode == rNextGC.BlendMode &&
473 rThisGC.Flatness == rNextGC.Flatness &&
474 rThisGC.Transformation == rNextGC.Transformation &&
475 rThisGC.Clip == rNextGC.Clip &&
476 pNext->Action == PATH_STROKE &&
477 (elem.Action == PATH_FILL || elem.Action == PATH_EOFILL)) )
478 return;
479
480 GraphicsContext aGC = rThisGC;
481 aGC.LineJoin = rNextGC.LineJoin;
482 aGC.LineCap = rNextGC.LineCap;
483 aGC.LineWidth = rNextGC.LineWidth;
484 aGC.MiterLimit= rNextGC.MiterLimit;
485 aGC.DashArray = rNextGC.DashArray;
486 aGC.LineColor = rNextGC.LineColor;
487 elem.GCId = m_rProcessor.getGCId( aGC );
488
489 elem.Action |= pNext->Action;
490
491 elem.Children.splice( elem.Children.end(), pNext->Children );
492 elem.Parent->Children.erase(next_it);
493}
494
495void WriterXmlOptimizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt)
496{
497 optimizeTextElements( elem );
498
499 elem.applyToChildren(*this);
500
501 if( !(elem.Parent && rParentIt != elem.Parent->Children.end()) )
502 return;
503
504 // find if there is a previous paragraph that might be a heading for this one
505 auto prev = rParentIt;
506 ParagraphElement* pPrevPara = nullptr;
507 while( prev != elem.Parent->Children.begin() )
508 {
509 --prev;
510 pPrevPara = dynamic_cast< ParagraphElement* >(prev->get());
511 if( pPrevPara )
512 {
513 /* What constitutes a heading ? current hints are:
514 * - one line only
515 * - not too far away from this paragraph (two heading height max ?)
516 * - font larger or bold
517 * this is of course incomplete
518 * FIXME: improve hints for heading
519 */
520 // check for single line
521 if( pPrevPara->isSingleLined( m_rProcessor ) )
522 {
523 double head_line_height = pPrevPara->getLineHeight( m_rProcessor );
524 if( pPrevPara->y + pPrevPara->h + 2*head_line_height > elem.y )
525 {
526 // check for larger font
527 if( head_line_height > elem.getLineHeight( m_rProcessor ) )
528 {
529 pPrevPara->Type = ParagraphElement::Headline;
530 }
531 else
532 {
533 // check whether text of pPrevPara is bold (at least first text element)
534 // and this para is not bold (ditto)
535 TextElement* pPrevText = pPrevPara->getFirstTextChild();
536 TextElement* pThisText = elem.getFirstTextChild();
537 if( pPrevText && pThisText )
538 {
539 const FontAttributes& rPrevFont = m_rProcessor.getFont( pPrevText->FontId );
540 const FontAttributes& rThisFont = m_rProcessor.getFont( pThisText->FontId );
541 if ( (rPrevFont.fontWeight == u"600" ||
542 rPrevFont.fontWeight == u"bold" ||
543 rPrevFont.fontWeight == u"800" ||
544 rPrevFont.fontWeight == u"900" ) &&
545 (rThisFont.fontWeight == u"600" ||
546 rThisFont.fontWeight == u"bold" ||
547 rThisFont.fontWeight == u"800" ||
548 rThisFont.fontWeight == u"900" ) )
549 {
550 pPrevPara->Type = ParagraphElement::Headline;
551 }
552 }
553 }
554 }
555 }
556 break;
557 }
558 }
559}
560
561void WriterXmlOptimizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
562{
564 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
565
566 // resolve hyperlinks
567 elem.resolveHyperlinks();
568
569 elem.resolveFontStyles( m_rProcessor ); // underlines and such
570
571 // FIXME: until hyperlinks and font effects are adjusted for
572 // geometrical search handle them before sorting
574
575 // find paragraphs in text
576 ParagraphElement* pCurPara = nullptr;
577 std::list< std::unique_ptr<Element> >::iterator page_element, next_page_element;
578 next_page_element = elem.Children.begin();
579 double fCurLineHeight = 0.0; // average height of text items in current para
580 int nCurLineElements = 0; // number of line contributing elements in current para
581 double line_left = elem.w, line_right = 0.0;
582 double column_width = elem.w*0.75; // estimate text width
583 // TODO: guess columns
584 while( next_page_element != elem.Children.end() )
585 {
586 page_element = next_page_element++;
587 ParagraphElement* pPagePara = dynamic_cast<ParagraphElement*>(page_element->get());
588 if( pPagePara )
589 {
590 pCurPara = pPagePara;
591 // adjust line height and text items
592 fCurLineHeight = 0.0;
593 nCurLineElements = 0;
594 for( const auto& rxChild : pCurPara->Children )
595 {
596 TextElement* pTestText = rxChild->dynCastAsTextElement();
597 if( pTestText )
598 {
599 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pTestText->h)/double(nCurLineElements+1);
600 nCurLineElements++;
601 }
602 }
603 continue;
604 }
605
606 HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(page_element->get());
607 DrawElement* pDraw = dynamic_cast<DrawElement*>(page_element->get());
608 if( ! pDraw && pLink && ! pLink->Children.empty() )
609 pDraw = dynamic_cast<DrawElement*>(pLink->Children.front().get() );
610 if( pDraw )
611 {
612 // insert small drawing objects as character, else leave them page bound
613
614 bool bInsertToParagraph = false;
615 // first check if this is either inside the paragraph
616 if( pCurPara && pDraw->y < pCurPara->y + pCurPara->h )
617 {
618 if( pDraw->h < fCurLineHeight * 1.5 )
619 {
620 bInsertToParagraph = true;
621 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pDraw->h)/double(nCurLineElements+1);
622 nCurLineElements++;
623 // mark draw element as character
624 pDraw->isCharacter = true;
625 }
626 }
627 // or perhaps the draw element begins a new paragraph
628 else if( next_page_element != elem.Children.end() )
629 {
630 TextElement* pText = (*next_page_element)->dynCastAsTextElement();
631 if( ! pText )
632 {
633 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(next_page_element->get());
634 if( pPara && ! pPara->Children.empty() )
635 pText = pPara->Children.front()->dynCastAsTextElement();
636 }
637 if( pText && // check there is a text
638 pDraw->h < pText->h*1.5 && // and it is approx the same height
639 // and either upper or lower edge of pDraw is inside text's vertical range
640 ( ( pDraw->y >= pText->y && pDraw->y <= pText->y+pText->h ) ||
641 ( pDraw->y+pDraw->h >= pText->y && pDraw->y+pDraw->h <= pText->y+pText->h )
642 )
643 )
644 {
645 bInsertToParagraph = true;
646 fCurLineHeight = pDraw->h;
647 nCurLineElements = 1;
648 line_left = pDraw->x;
649 line_right = pDraw->x + pDraw->w;
650 // begin a new paragraph
651 pCurPara = nullptr;
652 // mark draw element as character
653 pDraw->isCharacter = true;
654 }
655 }
656
657 if( ! bInsertToParagraph )
658 {
659 pCurPara = nullptr;
660 continue;
661 }
662 }
663
664 TextElement* pText = (*page_element)->dynCastAsTextElement();
665 if( ! pText && pLink && ! pLink->Children.empty() )
666 pText = pLink->Children.front()->dynCastAsTextElement();
667 if( pText )
668 {
669 Element* pGeo = pLink ? static_cast<Element*>(pLink) :
670 static_cast<Element*>(pText);
671 if( pCurPara )
672 {
673 // there was already a text element, check for a new paragraph
674 if( nCurLineElements > 0 )
675 {
676 // if the new text is significantly distant from the paragraph
677 // begin a new paragraph
678 if( pGeo->y > pCurPara->y+pCurPara->h + fCurLineHeight*0.5 )
679 pCurPara = nullptr; // insert new paragraph
680 else if( pGeo->y > (pCurPara->y+pCurPara->h - fCurLineHeight*0.05) )
681 {
682 // new paragraph if either the last line of the paragraph
683 // was significantly shorter than the paragraph as a whole
684 if( (line_right - line_left) < pCurPara->w*0.75 )
685 pCurPara = nullptr;
686 // or the last line was significantly smaller than the column width
687 else if( (line_right - line_left) < column_width*0.75 )
688 pCurPara = nullptr;
689 }
690 }
691 }
692 // update line height/width
693 if( pCurPara )
694 {
695 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pGeo->h)/double(nCurLineElements+1);
696 nCurLineElements++;
697 if( pGeo->x < line_left )
698 line_left = pGeo->x;
699 if( pGeo->x+pGeo->w > line_right )
700 line_right = pGeo->x+pGeo->w;
701 }
702 else
703 {
704 fCurLineHeight = pGeo->h;
705 nCurLineElements = 1;
706 line_left = pGeo->x;
707 line_right = pGeo->x + pGeo->w;
708 }
709 }
710
711 // move element to current paragraph
712 if( ! pCurPara ) // new paragraph, insert one
713 {
714 pCurPara = ElementFactory::createParagraphElement( nullptr );
715 // set parent
716 pCurPara->Parent = &elem;
717 //insert new paragraph before current element
718 page_element = elem.Children.insert( page_element, std::unique_ptr<Element>(pCurPara) );
719 // forward iterator to current element again
720 ++ page_element;
721 // update next_element which is now invalid
722 next_page_element = page_element;
723 ++ next_page_element;
724 }
725 Element* pCurEle = page_element->get();
726 Element::setParent( page_element, pCurPara );
727 OSL_ENSURE( !pText || pCurEle == pText || pCurEle == pLink, "paragraph child list in disorder" );
728 if( pText || pDraw )
729 pCurPara->updateGeometryWith( pCurEle );
730 }
731
732 // process children
733 elem.applyToChildren(*this);
734
735 // find possible header and footer
736 checkHeaderAndFooter( elem );
737}
738
740{
741 /* indicators for a header:
742 * - single line paragraph at top of page (inside 15% page height)
743 * - at least lineheight above the next paragraph
744 *
745 * indicators for a footer likewise:
746 * - single line paragraph at bottom of page (inside 15% page height)
747 * - at least lineheight below the previous paragraph
748 */
749
750 auto isParagraphElement = [](std::unique_ptr<Element>& rxChild) -> bool {
751 return dynamic_cast<ParagraphElement*>(rxChild.get()) != nullptr;
752 };
753
754 // detect header
755 // Note: the following assumes that the pages' children have been
756 // sorted geometrically
757 auto it = std::find_if(rElem.Children.begin(), rElem.Children.end(), isParagraphElement);
758 if (it != rElem.Children.end())
759 {
760 ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**it);
761 if( rPara.y+rPara.h < rElem.h*0.15 && rPara.isSingleLined( m_rProcessor ) )
762 {
763 auto next_it = it;
764 ParagraphElement* pNextPara = nullptr;
765 while( ++next_it != rElem.Children.end() && pNextPara == nullptr )
766 {
767 pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
768 }
769 if( pNextPara && pNextPara->y > rPara.y+rPara.h*2 )
770 {
771 rElem.HeaderElement = std::move(*it);
772 rPara.Parent = nullptr;
773 rElem.Children.erase( it );
774 }
775 }
776 }
777
778 // detect footer
779 auto rit = std::find_if(rElem.Children.rbegin(), rElem.Children.rend(), isParagraphElement);
780 if (rit == rElem.Children.rend())
781 return;
782
783 ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**rit);
784 if( !(rPara.y > rElem.h*0.85 && rPara.isSingleLined( m_rProcessor )) )
785 return;
786
787 std::list< std::unique_ptr<Element> >::reverse_iterator next_it = rit;
788 ParagraphElement* pNextPara = nullptr;
789 while( ++next_it != rElem.Children.rend() && pNextPara == nullptr )
790 {
791 pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
792 }
793 if( pNextPara && pNextPara->y < rPara.y-rPara.h*2 )
794 {
795 rElem.FooterElement = std::move(*rit);
796 rPara.Parent = nullptr;
797 rElem.Children.erase( std::next(rit).base() );
798 }
799}
800
802{
803 if( rParent.Children.empty() ) // this should not happen
804 {
805 OSL_FAIL( "empty paragraph optimized" );
806 return;
807 }
808
809 // concatenate child elements with same font id
810 auto next = rParent.Children.begin();
811 auto it = next++;
812 FrameElement* pFrame = dynamic_cast<FrameElement*>(rParent.Parent);
813 bool bRotatedFrame = false;
814 if( pFrame )
815 {
816 const GraphicsContext& rFrameGC = m_rProcessor.getGraphicsContext( pFrame->GCId );
817 if( rFrameGC.isRotatedOrSkewed() )
818 bRotatedFrame = true;
819 }
820 while( next != rParent.Children.end() )
821 {
822 bool bConcat = false;
823 TextElement* pCur = (*it)->dynCastAsTextElement();
824 if( pCur )
825 {
826 TextElement* pNext = dynamic_cast<TextElement*>(next->get());
827 OUString str;
828 bool bPara = strspn("ParagraphElement", typeid(rParent).name());
829 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(&rParent);
830 if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
831 pPara->bRtl = true;
832 if( pNext )
833 {
834 const GraphicsContext& rCurGC = m_rProcessor.getGraphicsContext( pCur->GCId );
835 const GraphicsContext& rNextGC = m_rProcessor.getGraphicsContext( pNext->GCId );
836
837 // line and space optimization; works only in strictly horizontal mode
838
839 if( !bRotatedFrame
840 && ! rCurGC.isRotatedOrSkewed()
841 && ! rNextGC.isRotatedOrSkewed()
842 && ! pNext->Text.isEmpty()
843 && pNext->Text[0] != ' '
844 && ! pCur->Text.isEmpty()
845 && pCur->Text[pCur->Text.getLength() - 1] != ' '
846 )
847 {
848 // check for new line in paragraph
849 if( pNext->y > pCur->y+pCur->h )
850 {
851 // new line begins
852 // check whether a space would should be inserted or a hyphen removed
853 sal_Unicode aLastCode = pCur->Text[pCur->Text.getLength() - 1];
854 if( aLastCode == '-'
855 || aLastCode == 0x2010
856 || (aLastCode >= 0x2012 && aLastCode <= 0x2015)
857 || aLastCode == 0xff0d
858 )
859 {
860 // cut a hyphen
861 pCur->Text.setLength( pCur->Text.getLength()-1 );
862 }
863 // append a space unless there is a non breaking hyphen
864 else if( aLastCode != 0x2011 )
865 {
866 pCur->Text.append( ' ' );
867 }
868 }
869 else // we're continuing the same line
870 {
871 // check whether a space would should be inserted
872 // check for a small horizontal offset
873 if( pCur->x + pCur->w + pNext->h*0.15 < pNext->x )
874 {
875 pCur->Text.append( ' ' );
876 }
877 }
878 }
879 // concatenate consecutive text elements unless there is a
880 // font or text color change, leave a new span in that case
881 if( pCur->FontId == pNext->FontId &&
882 rCurGC.FillColor.Red == rNextGC.FillColor.Red &&
883 rCurGC.FillColor.Green == rNextGC.FillColor.Green &&
884 rCurGC.FillColor.Blue == rNextGC.FillColor.Blue &&
885 rCurGC.FillColor.Alpha == rNextGC.FillColor.Alpha
886 )
887 {
888 pCur->updateGeometryWith( pNext );
889 if (pPara && pPara->bRtl)
890 {
891 // Tdf#152083: If RTL, reverse the text in pNext so that its correct order is
892 // restored when the combined text is reversed in WriterXmlEmitter::visit.
893 OUString tempStr;
894 bool bNeedReverse=false;
895 str = pNext->Text.toString();
896 for (sal_Int32 i=0; i < str.getLength(); i++)
897 {
898 if (str[i] == u' ')
899 { // Space char (e.g. the space as in " م") needs special treatment.
900 // First, append the space char to pCur.
901 pCur->Text.append(OUStringChar(str[i]));
902 // Then, check whether the tmpStr needs reverse, if so then reverse and append.
903 if (bNeedReverse)
904 {
905 tempStr = ::comphelper::string::reverseCodePoints(tempStr);
906 pCur->Text.append(tempStr);
907 tempStr = u"";
908 }
909 bNeedReverse = false;
910 }
911 else
912 {
913 tempStr += OUStringChar(str[i]);
914 bNeedReverse = true;
915 }
916 }
917 // Do the last append
918 if (bNeedReverse)
919 {
920 tempStr = ::comphelper::string::reverseCodePoints(tempStr);
921 pCur->Text.append(tempStr);
922 }
923 else
924 {
925 pCur->Text.append(tempStr);
926 }
927 }
928 else
929 {
930 // append text to current element directly without reverse
931 pCur->Text.append(pNext->Text);
932 }
933 if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
934 pPara->bRtl = true;
935 // append eventual children to current element
936 // and clear children (else the children just
937 // appended to pCur would be destroyed)
938 pCur->Children.splice( pCur->Children.end(), pNext->Children );
939 // get rid of the now useless element
940 rParent.Children.erase( next );
941 bConcat = true;
942 }
943 }
944 }
945 else if( dynamic_cast<HyperlinkElement*>(it->get()) )
946 optimizeTextElements( **it );
947 if( bConcat )
948 {
949 next = it;
950 ++next;
951 }
952 else
953 {
954 ++it;
955 ++next;
956 }
957 }
958}
959
960void WriterXmlOptimizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
961{
962 elem.applyToChildren(*this);
963}
964
965
966void WriterXmlFinalizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
967{
968 // xxx TODO copied from DrawElement
970 PropertyMap aProps;
971 aProps[ "style:family" ] = "graphic";
972
973 PropertyMap aGCProps;
974 if (elem.Action & PATH_STROKE)
975 {
977 if (rGC.DashArray.size() < 2)
978 {
979 aGCProps[ "draw:stroke" ] = "solid";
980 }
981 else
982 {
985 StyleContainer::Style style("draw:stroke-dash", std::move(props));
986
987 aGCProps[ "draw:stroke" ] = "dash";
988 aGCProps[ "draw:stroke-dash" ] =
991 }
992
993 aGCProps[ "svg:stroke-color" ] = getColorString(rGC.LineColor);
994 aGCProps[ "svg:stroke-width" ] = convertPixelToUnitString(rGC.LineWidth * scale);
995 aGCProps[ "draw:stroke-linejoin" ] = rGC.GetLineJoinString();
996 aGCProps[ "svg:stroke-linecap" ] = rGC.GetLineCapString();
997 }
998 else
999 {
1000 aGCProps[ "draw:stroke" ] = "none";
1001 }
1002
1003 // TODO(F1): check whether stuff could be emulated by gradient/bitmap/hatch
1004 if( elem.Action & (PATH_FILL | PATH_EOFILL) )
1005 {
1006 aGCProps[ "draw:fill" ] = "solid";
1007 aGCProps[ "draw:fill-color" ] = getColorString( rGC.FillColor );
1008 }
1009 else
1010 {
1011 aGCProps[ "draw:fill" ] = "none";
1012 }
1013
1014 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1015 StyleContainer::Style aSubStyle( "style:graphic-properties", std::move(aGCProps) );
1016 aStyle.SubStyles.push_back( &aSubStyle );
1017
1018 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1019}
1020
1021void WriterXmlFinalizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1022{
1023}
1024
1025void WriterXmlFinalizer::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1026{
1027 const FontAttributes& rFont = m_rProcessor.getFont( elem.FontId );
1028 PropertyMap aProps;
1029 aProps[ "style:family" ] = "text";
1030
1031 PropertyMap aFontProps;
1032
1033 // family name
1034 // TODO: tdf#143095: use system font name rather than PSName
1035 SAL_INFO("sdext.pdfimport", "The font used in xml is: " << rFont.familyName);
1036 aFontProps[ "fo:font-family" ] = rFont.familyName;
1037 aFontProps[ "style:font-family-asian" ] = rFont.familyName;
1038 aFontProps[ "style:font-family-complex" ] = rFont.familyName;
1039
1040 // bold
1041 aFontProps[ "fo:font-weight" ] = rFont.fontWeight;
1042 aFontProps[ "style:font-weight-asian" ] = rFont.fontWeight;
1043 aFontProps[ "style:font-weight-complex" ] = rFont.fontWeight;
1044
1045 // italic
1046 if( rFont.isItalic )
1047 {
1048 aFontProps[ "fo:font-style" ] = "italic";
1049 aFontProps[ "style:font-style-asian" ] = "italic";
1050 aFontProps[ "style:font-style-complex" ] = "italic";
1051 }
1052
1053 // underline
1054 if( rFont.isUnderline )
1055 {
1056 aFontProps[ "style:text-underline-style" ] = "solid";
1057 aFontProps[ "style:text-underline-width" ] = "auto";
1058 aFontProps[ "style:text-underline-color" ] = "font-color";
1059 }
1060
1061 // outline
1062 if( rFont.isOutline )
1063 aFontProps[ "style:text-outline" ] = "true";
1064
1065 // size
1066 OUString aFSize = OUString::number( rFont.size*72/PDFI_OUTDEV_RESOLUTION ) + "pt";
1067 aFontProps[ "fo:font-size" ] = aFSize;
1068 aFontProps[ "style:font-size-asian" ] = aFSize;
1069 aFontProps[ "style:font-size-complex" ] = aFSize;
1070
1071 // color
1073 aFontProps[ "fo:color" ] = getColorString( rFont.isOutline ? rGC.LineColor : rGC.FillColor );
1074
1075 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1076 StyleContainer::Style aSubStyle( "style:text-properties", std::move(aFontProps) );
1077 aStyle.SubStyles.push_back( &aSubStyle );
1078 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1079}
1080
1081void WriterXmlFinalizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
1082{
1083 PropertyMap aParaProps;
1084
1085 if( elem.Parent )
1086 {
1087 // check for center alignment
1088 // criterion: paragraph is small relative to parent and distributed around its center
1089 double p_x = elem.Parent->x;
1090 double p_w = elem.Parent->w;
1091
1092 PageElement* pPage = dynamic_cast<PageElement*>(elem.Parent);
1093 if( pPage )
1094 {
1095 p_x += pPage->LeftMargin;
1096 p_w -= pPage->LeftMargin+pPage->RightMargin;
1097 }
1098 bool bIsCenter = false;
1099 if( elem.w < ( p_w/2) )
1100 {
1101 double delta = elem.w/4;
1102 // allow very small paragraphs to deviate a little more
1103 // relative to parent's center
1104 if( elem.w < p_w/8 )
1105 delta = elem.w;
1106 if( fabs( elem.x+elem.w/2 - ( p_x+ p_w/2) ) < delta ||
1107 (pPage && fabs( elem.x+elem.w/2 - (pPage->x + pPage->w/2) ) < delta) )
1108 {
1109 bIsCenter = true;
1110 aParaProps[ "fo:text-align" ] = "center";
1111 }
1112 }
1113 if( ! bIsCenter && elem.x > p_x + p_w/10 )
1114 {
1115 // indent
1116 OUStringBuffer aBuf( 32 );
1117 aBuf.append( convPx2mm( elem.x - p_x ) );
1118 aBuf.append( "mm" );
1119 aParaProps[ "fo:margin-left" ] = aBuf.makeStringAndClear();
1120 }
1121
1122 // check whether to leave some space to next paragraph
1123 // find whether there is a next paragraph
1124 auto it = rParentIt;
1125 const ParagraphElement* pNextPara = nullptr;
1126 while( ++it != elem.Parent->Children.end() && ! pNextPara )
1127 pNextPara = dynamic_cast< const ParagraphElement* >(it->get());
1128 if( pNextPara )
1129 {
1130 if( pNextPara->y - (elem.y+elem.h) > convmm2Px( 10 ) )
1131 {
1132 OUStringBuffer aBuf( 32 );
1133 aBuf.append( convPx2mm( pNextPara->y - (elem.y+elem.h) ) );
1134 aBuf.append( "mm" );
1135 aParaProps[ "fo:margin-bottom" ] = aBuf.makeStringAndClear();
1136 }
1137 }
1138 }
1139
1140 if( ! aParaProps.empty() )
1141 {
1142 PropertyMap aProps;
1143 aProps[ "style:family" ] = "paragraph";
1144 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1145 StyleContainer::Style aSubStyle( "style:paragraph-properties", std::move(aParaProps) );
1146 aStyle.SubStyles.push_back( &aSubStyle );
1147 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1148 }
1149
1150 elem.applyToChildren(*this);
1151}
1152
1153void WriterXmlFinalizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
1154{
1155 PropertyMap aProps;
1156 aProps[ "style:family" ] = "graphic";
1157
1158 PropertyMap aGCProps;
1159
1160 aGCProps[ "draw:stroke" ] = "none";
1161 aGCProps[ "draw:fill" ] = "none";
1162 aGCProps[ "draw:auto-grow-height" ] = "true";
1163 aGCProps[ "draw:auto-grow-width" ] = "true";
1164 aGCProps[ "draw:textarea-horizontal-align" ] = "left";
1165 aGCProps[ "draw:textarea-vertical-align" ] = "top";
1166 aGCProps[ "fo:min-height"] = "0cm";
1167 aGCProps[ "fo:min-width"] = "0cm";
1168 aGCProps[ "fo:padding-top" ] = "0cm";
1169 aGCProps[ "fo:padding-left" ] = "0cm";
1170 aGCProps[ "fo:padding-right" ] = "0cm";
1171 aGCProps[ "fo:padding-bottom" ] = "0cm";
1172
1173 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1174 StyleContainer::Style aSubStyle( "style:graphic-properties", std::move(aGCProps) );
1175 aStyle.SubStyles.push_back( &aSubStyle );
1176
1177 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1178 elem.applyToChildren(*this);
1179}
1180
1181void WriterXmlFinalizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1182{
1183}
1184
1186 StyleContainer& rStyles,
1187 const OUString& rMasterPageName )
1188{
1189 PropertyMap aProps;
1190 if( rElem.StyleId != -1 )
1191 {
1192 const PropertyMap* pProps = rStyles.getProperties( rElem.StyleId );
1193 if( pProps )
1194 aProps = *pProps;
1195 }
1196
1197 aProps[ "style:family" ] = "paragraph";
1198 aProps[ "style:master-page-name" ] = rMasterPageName;
1199
1200 if( rElem.StyleId != -1 )
1201 rElem.StyleId = rStyles.setProperties( rElem.StyleId, std::move(aProps) );
1202 else
1203 {
1204 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1205 rElem.StyleId = rStyles.getStyleId( aStyle );
1206 }
1207}
1208
1209void WriterXmlFinalizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1210{
1211 if( m_rProcessor.getStatusIndicator().is() )
1212 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
1213
1214 // transform from pixel to mm
1215 double page_width = convPx2mm( elem.w ), page_height = convPx2mm( elem.h );
1216
1217 // calculate page margins out of the relevant children (paragraphs)
1218 elem.TopMargin = elem.h;
1219 elem.BottomMargin = 0;
1220 elem.LeftMargin = elem.w;
1221 elem.RightMargin = 0;
1222 // first element should be a paragraph
1223 ParagraphElement* pFirstPara = nullptr;
1224 for( const auto& rxChild : elem.Children )
1225 {
1226 if( dynamic_cast<ParagraphElement*>( rxChild.get() ) )
1227 {
1228 if( rxChild->x < elem.LeftMargin )
1229 elem.LeftMargin = rxChild->x;
1230 if( rxChild->y < elem.TopMargin )
1231 elem.TopMargin = rxChild->y;
1232 if( rxChild->x + rxChild->w > elem.w - elem.RightMargin )
1233 elem.RightMargin = elem.w - (rxChild->x + rxChild->w);
1234 if( rxChild->y + rxChild->h > elem.h - elem.BottomMargin )
1235 elem.BottomMargin = elem.h - (rxChild->y + rxChild->h);
1236 if( ! pFirstPara )
1237 pFirstPara = dynamic_cast<ParagraphElement*>( rxChild.get() );
1238 }
1239 }
1240 if( elem.HeaderElement && elem.HeaderElement->y < elem.TopMargin )
1241 elem.TopMargin = elem.HeaderElement->y;
1242 if( elem.FooterElement && elem.FooterElement->y+elem.FooterElement->h > elem.h - elem.BottomMargin )
1243 elem.BottomMargin = elem.h - (elem.FooterElement->y + elem.FooterElement->h);
1244
1245 // transform margins to mm
1246 double left_margin = convPx2mm( elem.LeftMargin );
1247 double right_margin = convPx2mm( elem.RightMargin );
1248 double top_margin = convPx2mm( elem.TopMargin );
1249 double bottom_margin = convPx2mm( elem.BottomMargin );
1250 if( ! pFirstPara )
1251 {
1252 // use default page margins
1253 left_margin = 10;
1254 right_margin = 10;
1255 top_margin = 10;
1256 bottom_margin = 10;
1257 }
1258
1259 // round left/top margin to nearest mm
1260 left_margin = rtl_math_round( left_margin, 0, rtl_math_RoundingMode_Floor );
1261 top_margin = rtl_math_round( top_margin, 0, rtl_math_RoundingMode_Floor );
1262 // round (fuzzy) right/bottom margin to nearest cm
1263 right_margin = rtl_math_round( right_margin, right_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1264 bottom_margin = rtl_math_round( bottom_margin, bottom_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1265
1266 // set reasonable default in case of way too large margins
1267 // e.g. no paragraph case
1268 if( left_margin > page_width/2.0 - 10 )
1269 left_margin = 10;
1270 if( right_margin > page_width/2.0 - 10 )
1271 right_margin = 10;
1272 if( top_margin > page_height/2.0 - 10 )
1273 top_margin = 10;
1274 if( bottom_margin > page_height/2.0 - 10 )
1275 bottom_margin = 10;
1276
1277 // catch the weird cases
1278 if( left_margin < 0 )
1279 left_margin = 0;
1280 if( right_margin < 0 )
1281 right_margin = 0;
1282 if( top_margin < 0 )
1283 top_margin = 0;
1284 if( bottom_margin < 0 )
1285 bottom_margin = 0;
1286
1287 // widely differing margins are unlikely to be correct
1288 if( right_margin > left_margin*1.5 )
1289 right_margin = left_margin;
1290
1291 elem.LeftMargin = convmm2Px( left_margin );
1292 elem.RightMargin = convmm2Px( right_margin );
1293 elem.TopMargin = convmm2Px( top_margin );
1294 elem.BottomMargin = convmm2Px( bottom_margin );
1295
1296 // get styles for paragraphs
1297 PropertyMap aPageProps;
1298 PropertyMap aPageLayoutProps;
1299 aPageLayoutProps[ "fo:page-width" ] = unitMMString( page_width );
1300 aPageLayoutProps[ "fo:page-height" ] = unitMMString( page_height );
1301 aPageLayoutProps[ "style:print-orientation" ]
1302 = elem.w < elem.h ? std::u16string_view(u"portrait") : std::u16string_view(u"landscape");
1303 aPageLayoutProps[ "fo:margin-top" ] = unitMMString( top_margin );
1304 aPageLayoutProps[ "fo:margin-bottom" ] = unitMMString( bottom_margin );
1305 aPageLayoutProps[ "fo:margin-left" ] = unitMMString( left_margin );
1306 aPageLayoutProps[ "fo:margin-right" ] = unitMMString( right_margin );
1307 aPageLayoutProps[ "style:writing-mode" ]= "lr-tb";
1308
1309 StyleContainer::Style aStyle( "style:page-layout", std::move(aPageProps));
1310 StyleContainer::Style aSubStyle( "style:page-layout-properties", std::move(aPageLayoutProps));
1311 aStyle.SubStyles.push_back(&aSubStyle);
1312 sal_Int32 nPageStyle = m_rStyleContainer.impl_getStyleId( aStyle, false );
1313
1314 // create master page
1315 OUString aMasterPageLayoutName = m_rStyleContainer.getStyleName( nPageStyle );
1316 aPageProps[ "style:page-layout-name" ] = aMasterPageLayoutName;
1317 StyleContainer::Style aMPStyle( "style:master-page", std::move(aPageProps) );
1318 StyleContainer::Style aHeaderStyle( "style:header", PropertyMap() );
1319 StyleContainer::Style aFooterStyle( "style:footer", PropertyMap() );
1320 if( elem.HeaderElement )
1321 {
1322 elem.HeaderElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1323 aHeaderStyle.ContainedElement = elem.HeaderElement.get();
1324 aMPStyle.SubStyles.push_back( &aHeaderStyle );
1325 }
1326 if( elem.FooterElement )
1327 {
1328 elem.FooterElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1329 aFooterStyle.ContainedElement = elem.FooterElement.get();
1330 aMPStyle.SubStyles.push_back( &aFooterStyle );
1331 }
1332 elem.StyleId = m_rStyleContainer.impl_getStyleId( aMPStyle,false );
1333
1334
1335 OUString aMasterPageName = m_rStyleContainer.getStyleName( elem.StyleId );
1336
1337 // create styles for children
1338 elem.applyToChildren(*this);
1339
1340 // no paragraph or other elements before the first paragraph
1341 if( ! pFirstPara )
1342 {
1343 pFirstPara = ElementFactory::createParagraphElement( nullptr );
1344 pFirstPara->Parent = &elem;
1345 elem.Children.push_front( std::unique_ptr<Element>(pFirstPara) );
1346 }
1347 setFirstOnPage(*pFirstPara, m_rStyleContainer, aMasterPageName);
1348}
1349
1350void WriterXmlFinalizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1351{
1352 elem.applyToChildren(*this);
1353}
1354
1355}
1356
1357/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
bool decompose(B2DTuple &rScale, B2DTuple &rTranslate, double &rRotate, double &rShearX) const
bool isIdentity() const
B2DPolygon const & getB2DPolygon(sal_uInt32 nIndex) const
void setB2DPolygon(sal_uInt32 nIndex, const B2DPolygon &rPolygon)
sal_uInt32 count() const
bool isPrevControlPointUsed(sal_uInt32 nIndex) const
void setB2DPoint(sal_uInt32 nIndex, const basegfx::B2DPoint &rValue)
bool isNextControlPointUsed(sal_uInt32 nIndex) const
void setPrevControlPoint(sal_uInt32 nIndex, const basegfx::B2DPoint &rValue)
basegfx::B2DPoint const & getB2DPoint(sal_uInt32 nIndex) const
void setNextControlPoint(sal_uInt32 nIndex, const basegfx::B2DPoint &rValue)
basegfx::B2DPoint getPrevControlPoint(sal_uInt32 nIndex) const
sal_uInt32 count() const
basegfx::B2DPoint getNextControlPoint(sal_uInt32 nIndex) const
TYPE getX() const
void setY(TYPE fY)
TYPE getY() const
void setX(TYPE fX)
static ParagraphElement * createParagraphElement(Element *pParent)
void writeBase64EncodedStream(ImageId nImageId, EmitContext &rContext)
css::uno::Reference< css::uno::XComponentContext > m_xContext
const GraphicsContext & getGraphicsContext(sal_Int32 nGCId) const
const FontAttributes & getFont(sal_Int32 nFontId) const
const css::uno::Reference< css::task::XStatusIndicator > & getStatusIndicator() const
static void sortElements(Element *pElement)
static OUString SubstituteBidiMirrored(const OUString &rString)
sal_Int32 getGCId(const GraphicsContext &rGC)
sal_Int32 setProperties(sal_Int32 nStyleId, PropertyMap &&rNewProps)
Definition: style.cxx:93
OUString getStyleName(sal_Int32 nStyle) const
Definition: style.cxx:146
sal_Int32 impl_getStyleId(const Style &rStyle, bool bSubStyle)
Definition: style.cxx:37
const PropertyMap * getProperties(sal_Int32 nStyleId) const
Definition: style.cxx:86
sal_Int32 getStyleId(const Style &rStyle)
Definition: style.hxx:153
static void fillFrameProps(DrawElement &rElem, PropertyMap &rProps, const EmitContext &rEmitContext)
virtual void visit(HyperlinkElement &, const std::list< std::unique_ptr< Element > >::const_iterator &) override
const css::uno::Reference< css::i18n::XCharacterClassification > & GetCharacterClassification()
css::uno::Reference< css::i18n::XCharacterClassification > mxCharClass
static void setFirstOnPage(ParagraphElement &rElem, StyleContainer &rStyles, const OUString &rMasterPageName)
virtual void visit(HyperlinkElement &, const std::list< std::unique_ptr< Element > >::const_iterator &) override
StyleContainer & m_rStyleContainer
void optimizeTextElements(Element &rParent)
const css::uno::Reference< css::i18n::XBreakIterator > & GetBreakIterator()
virtual void visit(HyperlinkElement &, const std::list< std::unique_ptr< Element > >::const_iterator &) override
void checkHeaderAndFooter(PageElement &rElem)
css::uno::Reference< css::i18n::XBreakIterator > mxBreakIter
virtual void write(const OUString &rString)=0
Write PCTEXT as-is to output.
virtual void endTag(const char *pTag)=0
Close previously opened tag.
virtual void beginTag(const char *pTag, const PropertyMap &rProperties)=0
Open up a tag with the given properties.
float u
void const * base
const char * name
SvBaseLink * pLink
#define SAL_INFO(area, stream)
aBuf
def point()
OUString exportToSvgD(const B2DPolyPolygon &rPolyPoly, bool bUseRelativeCoordinates, bool bDetectQuadraticBeziers, bool bHandleRelativeNextPointCompatible, bool bOOXMLMotionPath=false)
int i
OUString convertPixelToUnitString(double fPix)
Definition: pdfihelper.cxx:113
OUString getColorString(const css::rendering::ARGBColor &)
Convert color to "#FEFEFE" color notation.
double convmm2Px(double fMM)
Definition: pdfihelper.hxx:60
double convPx2mm(double fPix)
Definition: pdfihelper.hxx:53
double GetAverageTransformationScale(const basegfx::B2DHomMatrix &matrix)
Definition: pdfihelper.cxx:32
OUString unitMMString(double fMM)
Definition: pdfihelper.cxx:108
std::unordered_map< OUString, OUString > PropertyMap
Definition: pdfihelper.hxx:44
double convPx2mmPrec2(double fPix)
round to 2 decimal places
Definition: pdfihelper.hxx:68
@ PATH_EOFILL
Definition: pdfihelper.hxx:48
@ PATH_STROKE
Definition: pdfihelper.hxx:48
@ PATH_FILL
Definition: pdfihelper.hxx:48
bool isComplex(const css::uno::Reference< css::i18n::XBreakIterator > &rBreakIterator, TextElement *const pTextElem)
void FillDashStyleProps(PropertyMap &props, const std::vector< double > &dashArray, double scale)
Definition: pdfihelper.cxx:40
dictionary props
#define PDFI_OUTDEV_RESOLUTION
Definition: pdfihelper.hxx:38
sal_Int32 scale
QPRO_FUNC_TYPE nType
static void setParent(std::list< std::unique_ptr< Element > >::iterator const &el, Element *pNewParent)
el must be a valid dereferenceable iterator of el->Parent->Children pNewParent must not be NULL
std::list< std::unique_ptr< Element > > Children
void applyToChildren(ElementTreeVisitor &)
Apply visitor to all children.
void updateGeometryWith(const Element *pMergeFrom)
Union element geometry with given element.
ImageContainer & rImages
css::uno::Reference< css::uno::XComponentContext > m_xContext
XmlEmitter & rEmitter
PDFIProcessor & rProcessor
css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator
StyleContainer & rStyles
css::rendering::ARGBColor FillColor
Definition: pdfihelper.hxx:100
css::rendering::ARGBColor LineColor
Definition: pdfihelper.hxx:99
bool isRotatedOrSkewed() const
Definition: pdfihelper.hxx:180
basegfx::B2DHomMatrix Transformation
Definition: pdfihelper.hxx:110
OUString GetLineCapString() const
Definition: pdfihelper.hxx:166
OUString GetLineJoinString() const
Definition: pdfihelper.hxx:152
std::vector< double > DashArray
Definition: pdfihelper.hxx:107
basegfx::B2DPolyPolygon Clip
Definition: pdfihelper.hxx:111
std::unique_ptr< Element > FooterElement
std::unique_ptr< Element > HeaderElement
virtual void visitedBy(ElementTreeVisitor &, const std::list< std::unique_ptr< Element > >::const_iterator &rParentIt) override
To be implemented by every tree node that needs to be visitable.
void resolveFontStyles(PDFIProcessor const &rProc)
TextElement * getFirstTextChild() const
double getLineHeight(PDFIProcessor &rProc) const
bool isSingleLined(PDFIProcessor const &rProc) const
basegfx::B2DPolyPolygon PolyPoly
std::vector< Style * > SubStyles
Definition: style.hxx:46
Element * ContainedElement
Definition: style.hxx:45
virtual const TextElement * dynCastAsTextElement() const override
To avoid some dynamic_cast cost.
OUStringBuffer Text
sal_uInt16 sal_Unicode