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( " + OUString::number(fShearX) + " )" );
258 }
259 if( fRotate != 0.0 )
260 {
261 if( !aBuf.isEmpty() )
262 aBuf.append( ' ' );
263 aBuf.append( "rotate( " + OUString::number(-fRotate) + " )" );
264
265 }
266 if( ! rElem.isCharacter )
267 {
268 if( !aBuf.isEmpty() )
269 aBuf.append( ' ' );
270 aBuf.append( "translate( "
271 + convertPixelToUnitString( rel_x )
272 + " "
273 + convertPixelToUnitString( rel_y )
274 + " )" );
275 }
276
277 rProps[ "draw:transform" ] = aBuf.makeStringAndClear();
278 }
279}
280
281void WriterXmlEmitter::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
282{
283 if( elem.Children.empty() )
284 return;
285
286 bool bTextBox = (dynamic_cast<ParagraphElement*>(elem.Children.front().get()) != nullptr);
287 PropertyMap aFrameProps;
288 fillFrameProps( elem, aFrameProps, m_rEmitContext );
289 m_rEmitContext.rEmitter.beginTag( "draw:frame", aFrameProps );
290 if( bTextBox )
291 m_rEmitContext.rEmitter.beginTag( "draw:text-box", PropertyMap() );
292
293 auto this_it = elem.Children.begin();
294 while( this_it != elem.Children.end() && this_it->get() != &elem )
295 {
296 (*this_it)->visitedBy( *this, this_it );
297 ++this_it;
298 }
299
300 if( bTextBox )
301 m_rEmitContext.rEmitter.endTag( "draw:text-box" );
302 m_rEmitContext.rEmitter.endTag( "draw:frame" );
303}
304
305void WriterXmlEmitter::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
306{
307 elem.updateGeometry();
308 /* note:
309 * aw recommends using 100dth of mm in all respects since the xml import
310 * (a) is buggy (see issue 37213)
311 * (b) is optimized for 100dth of mm and does not scale itself then,
312 * this does not gain us speed but makes for smaller rounding errors since
313 * the xml importer coordinates are integer based
314 */
315 for (sal_uInt32 i = 0; i< elem.PolyPoly.count(); i++)
316 {
317 basegfx::B2DPolygon b2dPolygon = elem.PolyPoly.getB2DPolygon( i );
318
319 for ( sal_uInt32 j = 0; j< b2dPolygon.count(); j++ )
320 {
322 basegfx::B2DPoint nextPoint;
323 point = b2dPolygon.getB2DPoint( j );
324
325 basegfx::B2DPoint prevPoint = b2dPolygon.getPrevControlPoint( j ) ;
326
327 point.setX( convPx2mmPrec2( point.getX() )*100.0 );
328 point.setY( convPx2mmPrec2( point.getY() )*100.0 );
329
330 if ( b2dPolygon.isPrevControlPointUsed( j ) )
331 {
332 prevPoint.setX( convPx2mmPrec2( prevPoint.getX() )*100.0 );
333 prevPoint.setY( convPx2mmPrec2( prevPoint.getY() )*100.0 );
334 }
335
336 if ( b2dPolygon.isNextControlPointUsed( j ) )
337 {
338 nextPoint = b2dPolygon.getNextControlPoint( j ) ;
339 nextPoint.setX( convPx2mmPrec2( nextPoint.getX() )*100.0 );
340 nextPoint.setY( convPx2mmPrec2( nextPoint.getY() )*100.0 );
341 }
342
343 b2dPolygon.setB2DPoint( j, point );
344
345 if ( b2dPolygon.isPrevControlPointUsed( j ) )
346 b2dPolygon.setPrevControlPoint( j , prevPoint ) ;
347
348 if ( b2dPolygon.isNextControlPointUsed( j ) )
349 b2dPolygon.setNextControlPoint( j , nextPoint ) ;
350 }
351
352 elem.PolyPoly.setB2DPolygon( i, b2dPolygon );
353 }
354
355 PropertyMap aProps;
356 fillFrameProps( elem, aProps, m_rEmitContext );
357 aProps[ "svg:viewBox" ] =
358 "0 0 "
359 + OUString::number(convPx2mmPrec2(elem.w)*100.0)
360 + " "
361 + OUString::number( convPx2mmPrec2(elem.h)*100.0 );
362 aProps[ "svg:d" ] = basegfx::utils::exportToSvgD( elem.PolyPoly, true, true, false );
363
364 m_rEmitContext.rEmitter.beginTag( "draw:path", aProps );
365 m_rEmitContext.rEmitter.endTag( "draw:path" );
366}
367
368void WriterXmlEmitter::visit( ImageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
369{
370 PropertyMap aImageProps;
371 m_rEmitContext.rEmitter.beginTag( "draw:image", aImageProps );
372 m_rEmitContext.rEmitter.beginTag( "office:binary-data", PropertyMap() );
374 m_rEmitContext.rEmitter.endTag( "office:binary-data" );
375 m_rEmitContext.rEmitter.endTag( "draw:image" );
376}
377
378void WriterXmlEmitter::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
379{
382
383 auto this_it = elem.Children.begin();
384 while( this_it != elem.Children.end() && this_it->get() != &elem )
385 {
386 (*this_it)->visitedBy( *this, this_it );
387 ++this_it;
388 }
389}
390
391void WriterXmlEmitter::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
392{
393 m_rEmitContext.rEmitter.beginTag( "office:body", PropertyMap() );
394 m_rEmitContext.rEmitter.beginTag( "office:text", PropertyMap() );
395
396 for( const auto& rxChild : elem.Children )
397 {
398 PageElement* pPage = dynamic_cast<PageElement*>(rxChild.get());
399 if( pPage )
400 {
401 // emit only page anchored objects
402 // currently these are only DrawElement types
403 for( auto child_it = pPage->Children.begin(); child_it != pPage->Children.end(); ++child_it )
404 {
405 if( dynamic_cast<DrawElement*>(child_it->get()) != nullptr )
406 (*child_it)->visitedBy( *this, child_it );
407 }
408 }
409 }
410
411 // do not emit page anchored objects, they are emitted before
412 // (must precede all pages in writer document) currently these are
413 // only DrawElement types
414 for( auto it = elem.Children.begin(); it != elem.Children.end(); ++it )
415 {
416 if( dynamic_cast<DrawElement*>(it->get()) != nullptr )
417 (*it)->visitedBy( *this, it );
418 }
419
420 m_rEmitContext.rEmitter.endTag( "office:text" );
421 m_rEmitContext.rEmitter.endTag( "office:body" );
422}
423
424
425void WriterXmlOptimizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
426{
427}
428
429void WriterXmlOptimizer::visit( TextElement&, const std::list< std::unique_ptr<Element> >::const_iterator&)
430{
431}
432
433void WriterXmlOptimizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
434{
435 elem.applyToChildren(*this);
436}
437
438void WriterXmlOptimizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
439{
440}
441
442void WriterXmlOptimizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& elemIt )
443{
444 /* note: optimize two consecutive PolyPolyElements that
445 * have the same path but one of which is a stroke while
446 * the other is a fill
447 */
448 if( !elem.Parent )
449 return;
450 // find following PolyPolyElement in parent's children list
451 if( elemIt == elem.Parent->Children.end() )
452 return;
453 auto next_it = elemIt;
454 ++next_it;
455 if( next_it == elem.Parent->Children.end() )
456 return;
457
458 PolyPolyElement* pNext = dynamic_cast<PolyPolyElement*>(next_it->get());
459 if( !pNext || pNext->PolyPoly != elem.PolyPoly )
460 return;
461
462 const GraphicsContext& rNextGC =
464 const GraphicsContext& rThisGC =
466
467 if( !(rThisGC.BlendMode == rNextGC.BlendMode &&
468 rThisGC.Flatness == rNextGC.Flatness &&
469 rThisGC.Transformation == rNextGC.Transformation &&
470 rThisGC.Clip == rNextGC.Clip &&
471 pNext->Action == PATH_STROKE &&
472 (elem.Action == PATH_FILL || elem.Action == PATH_EOFILL)) )
473 return;
474
475 GraphicsContext aGC = rThisGC;
476 aGC.LineJoin = rNextGC.LineJoin;
477 aGC.LineCap = rNextGC.LineCap;
478 aGC.LineWidth = rNextGC.LineWidth;
479 aGC.MiterLimit= rNextGC.MiterLimit;
480 aGC.DashArray = rNextGC.DashArray;
481 aGC.LineColor = rNextGC.LineColor;
482 elem.GCId = m_rProcessor.getGCId( aGC );
483
484 elem.Action |= pNext->Action;
485
486 elem.Children.splice( elem.Children.end(), pNext->Children );
487 elem.Parent->Children.erase(next_it);
488}
489
490void WriterXmlOptimizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt)
491{
492 optimizeTextElements( elem );
493
494 elem.applyToChildren(*this);
495
496 if( !(elem.Parent && rParentIt != elem.Parent->Children.end()) )
497 return;
498
499 // find if there is a previous paragraph that might be a heading for this one
500 auto prev = rParentIt;
501 ParagraphElement* pPrevPara = nullptr;
502 while( prev != elem.Parent->Children.begin() )
503 {
504 --prev;
505 pPrevPara = dynamic_cast< ParagraphElement* >(prev->get());
506 if( pPrevPara )
507 {
508 /* What constitutes a heading ? current hints are:
509 * - one line only
510 * - not too far away from this paragraph (two heading height max ?)
511 * - font larger or bold
512 * this is of course incomplete
513 * FIXME: improve hints for heading
514 */
515 // check for single line
516 if( pPrevPara->isSingleLined( m_rProcessor ) )
517 {
518 double head_line_height = pPrevPara->getLineHeight( m_rProcessor );
519 if( pPrevPara->y + pPrevPara->h + 2*head_line_height > elem.y )
520 {
521 // check for larger font
522 if( head_line_height > elem.getLineHeight( m_rProcessor ) )
523 {
524 pPrevPara->Type = ParagraphElement::Headline;
525 }
526 else
527 {
528 // check whether text of pPrevPara is bold (at least first text element)
529 // and this para is not bold (ditto)
530 TextElement* pPrevText = pPrevPara->getFirstTextChild();
531 TextElement* pThisText = elem.getFirstTextChild();
532 if( pPrevText && pThisText )
533 {
534 const FontAttributes& rPrevFont = m_rProcessor.getFont( pPrevText->FontId );
535 const FontAttributes& rThisFont = m_rProcessor.getFont( pThisText->FontId );
536 if ( (rPrevFont.fontWeight == u"600" ||
537 rPrevFont.fontWeight == u"bold" ||
538 rPrevFont.fontWeight == u"800" ||
539 rPrevFont.fontWeight == u"900" ) &&
540 (rThisFont.fontWeight == u"600" ||
541 rThisFont.fontWeight == u"bold" ||
542 rThisFont.fontWeight == u"800" ||
543 rThisFont.fontWeight == u"900" ) )
544 {
545 pPrevPara->Type = ParagraphElement::Headline;
546 }
547 }
548 }
549 }
550 }
551 break;
552 }
553 }
554}
555
556void WriterXmlOptimizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
557{
559 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
560
561 // resolve hyperlinks
562 elem.resolveHyperlinks();
563
564 elem.resolveFontStyles( m_rProcessor ); // underlines and such
565
566 // FIXME: until hyperlinks and font effects are adjusted for
567 // geometrical search handle them before sorting
569
570 // find paragraphs in text
571 ParagraphElement* pCurPara = nullptr;
572 std::list< std::unique_ptr<Element> >::iterator page_element, next_page_element;
573 next_page_element = elem.Children.begin();
574 double fCurLineHeight = 0.0; // average height of text items in current para
575 int nCurLineElements = 0; // number of line contributing elements in current para
576 double line_left = elem.w, line_right = 0.0;
577 double column_width = elem.w*0.75; // estimate text width
578 // TODO: guess columns
579 while( next_page_element != elem.Children.end() )
580 {
581 page_element = next_page_element++;
582 ParagraphElement* pPagePara = dynamic_cast<ParagraphElement*>(page_element->get());
583 if( pPagePara )
584 {
585 pCurPara = pPagePara;
586 // adjust line height and text items
587 fCurLineHeight = 0.0;
588 nCurLineElements = 0;
589 for( const auto& rxChild : pCurPara->Children )
590 {
591 TextElement* pTestText = rxChild->dynCastAsTextElement();
592 if( pTestText )
593 {
594 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pTestText->h)/double(nCurLineElements+1);
595 nCurLineElements++;
596 }
597 }
598 continue;
599 }
600
601 HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(page_element->get());
602 DrawElement* pDraw = dynamic_cast<DrawElement*>(page_element->get());
603 if( ! pDraw && pLink && ! pLink->Children.empty() )
604 pDraw = dynamic_cast<DrawElement*>(pLink->Children.front().get() );
605 if( pDraw )
606 {
607 // insert small drawing objects as character, else leave them page bound
608
609 bool bInsertToParagraph = false;
610 // first check if this is either inside the paragraph
611 if( pCurPara && pDraw->y < pCurPara->y + pCurPara->h )
612 {
613 if( pDraw->h < fCurLineHeight * 1.5 )
614 {
615 bInsertToParagraph = true;
616 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pDraw->h)/double(nCurLineElements+1);
617 nCurLineElements++;
618 // mark draw element as character
619 pDraw->isCharacter = true;
620 }
621 }
622 // or perhaps the draw element begins a new paragraph
623 else if( next_page_element != elem.Children.end() )
624 {
625 TextElement* pText = (*next_page_element)->dynCastAsTextElement();
626 if( ! pText )
627 {
628 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(next_page_element->get());
629 if( pPara && ! pPara->Children.empty() )
630 pText = pPara->Children.front()->dynCastAsTextElement();
631 }
632 if( pText && // check there is a text
633 pDraw->h < pText->h*1.5 && // and it is approx the same height
634 // and either upper or lower edge of pDraw is inside text's vertical range
635 ( ( pDraw->y >= pText->y && pDraw->y <= pText->y+pText->h ) ||
636 ( pDraw->y+pDraw->h >= pText->y && pDraw->y+pDraw->h <= pText->y+pText->h )
637 )
638 )
639 {
640 bInsertToParagraph = true;
641 fCurLineHeight = pDraw->h;
642 nCurLineElements = 1;
643 line_left = pDraw->x;
644 line_right = pDraw->x + pDraw->w;
645 // begin a new paragraph
646 pCurPara = nullptr;
647 // mark draw element as character
648 pDraw->isCharacter = true;
649 }
650 }
651
652 if( ! bInsertToParagraph )
653 {
654 pCurPara = nullptr;
655 continue;
656 }
657 }
658
659 TextElement* pText = (*page_element)->dynCastAsTextElement();
660 if( ! pText && pLink && ! pLink->Children.empty() )
661 pText = pLink->Children.front()->dynCastAsTextElement();
662 if( pText )
663 {
664 Element* pGeo = pLink ? static_cast<Element*>(pLink) :
665 static_cast<Element*>(pText);
666 if( pCurPara )
667 {
668 // there was already a text element, check for a new paragraph
669 if( nCurLineElements > 0 )
670 {
671 // if the new text is significantly distant from the paragraph
672 // begin a new paragraph
673 if( pGeo->y > pCurPara->y+pCurPara->h + fCurLineHeight*0.5 )
674 pCurPara = nullptr; // insert new paragraph
675 else if( pGeo->y > (pCurPara->y+pCurPara->h - fCurLineHeight*0.05) )
676 {
677 // new paragraph if either the last line of the paragraph
678 // was significantly shorter than the paragraph as a whole
679 if( (line_right - line_left) < pCurPara->w*0.75 )
680 pCurPara = nullptr;
681 // or the last line was significantly smaller than the column width
682 else if( (line_right - line_left) < column_width*0.75 )
683 pCurPara = nullptr;
684 }
685 }
686 }
687 // update line height/width
688 if( pCurPara )
689 {
690 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pGeo->h)/double(nCurLineElements+1);
691 nCurLineElements++;
692 if( pGeo->x < line_left )
693 line_left = pGeo->x;
694 if( pGeo->x+pGeo->w > line_right )
695 line_right = pGeo->x+pGeo->w;
696 }
697 else
698 {
699 fCurLineHeight = pGeo->h;
700 nCurLineElements = 1;
701 line_left = pGeo->x;
702 line_right = pGeo->x + pGeo->w;
703 }
704 }
705
706 // move element to current paragraph
707 if( ! pCurPara ) // new paragraph, insert one
708 {
709 pCurPara = ElementFactory::createParagraphElement( nullptr );
710 // set parent
711 pCurPara->Parent = &elem;
712 //insert new paragraph before current element
713 page_element = elem.Children.insert( page_element, std::unique_ptr<Element>(pCurPara) );
714 // forward iterator to current element again
715 ++ page_element;
716 // update next_element which is now invalid
717 next_page_element = page_element;
718 ++ next_page_element;
719 }
720 Element* pCurEle = page_element->get();
721 Element::setParent( page_element, pCurPara );
722 OSL_ENSURE( !pText || pCurEle == pText || pCurEle == pLink, "paragraph child list in disorder" );
723 if( pText || pDraw )
724 pCurPara->updateGeometryWith( pCurEle );
725 }
726
727 // process children
728 elem.applyToChildren(*this);
729
730 // find possible header and footer
731 checkHeaderAndFooter( elem );
732}
733
735{
736 /* indicators for a header:
737 * - single line paragraph at top of page (inside 15% page height)
738 * - at least lineheight above the next paragraph
739 *
740 * indicators for a footer likewise:
741 * - single line paragraph at bottom of page (inside 15% page height)
742 * - at least lineheight below the previous paragraph
743 */
744
745 auto isParagraphElement = [](std::unique_ptr<Element>& rxChild) -> bool {
746 return dynamic_cast<ParagraphElement*>(rxChild.get()) != nullptr;
747 };
748
749 // detect header
750 // Note: the following assumes that the pages' children have been
751 // sorted geometrically
752 auto it = std::find_if(rElem.Children.begin(), rElem.Children.end(), isParagraphElement);
753 if (it != rElem.Children.end())
754 {
755 ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**it);
756 if( rPara.y+rPara.h < rElem.h*0.15 && rPara.isSingleLined( m_rProcessor ) )
757 {
758 auto next_it = it;
759 ParagraphElement* pNextPara = nullptr;
760 while( ++next_it != rElem.Children.end() && pNextPara == nullptr )
761 {
762 pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
763 }
764 if( pNextPara && pNextPara->y > rPara.y+rPara.h*2 )
765 {
766 rElem.HeaderElement = std::move(*it);
767 rPara.Parent = nullptr;
768 rElem.Children.erase( it );
769 }
770 }
771 }
772
773 // detect footer
774 auto rit = std::find_if(rElem.Children.rbegin(), rElem.Children.rend(), isParagraphElement);
775 if (rit == rElem.Children.rend())
776 return;
777
778 ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**rit);
779 if( !(rPara.y > rElem.h*0.85 && rPara.isSingleLined( m_rProcessor )) )
780 return;
781
782 std::list< std::unique_ptr<Element> >::reverse_iterator next_it = rit;
783 ParagraphElement* pNextPara = nullptr;
784 while( ++next_it != rElem.Children.rend() && pNextPara == nullptr )
785 {
786 pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
787 }
788 if( pNextPara && pNextPara->y < rPara.y-rPara.h*2 )
789 {
790 rElem.FooterElement = std::move(*rit);
791 rPara.Parent = nullptr;
792 rElem.Children.erase( std::next(rit).base() );
793 }
794}
795
797{
798 if( rParent.Children.empty() ) // this should not happen
799 {
800 OSL_FAIL( "empty paragraph optimized" );
801 return;
802 }
803
804 // concatenate child elements with same font id
805 auto next = rParent.Children.begin();
806 auto it = next++;
807 FrameElement* pFrame = dynamic_cast<FrameElement*>(rParent.Parent);
808 bool bRotatedFrame = false;
809 if( pFrame )
810 {
811 const GraphicsContext& rFrameGC = m_rProcessor.getGraphicsContext( pFrame->GCId );
812 if( rFrameGC.isRotatedOrSkewed() )
813 bRotatedFrame = true;
814 }
815 while( next != rParent.Children.end() )
816 {
817 bool bConcat = false;
818 TextElement* pCur = (*it)->dynCastAsTextElement();
819 if( pCur )
820 {
821 TextElement* pNext = dynamic_cast<TextElement*>(next->get());
822 OUString str;
823 bool bPara = strspn("ParagraphElement", typeid(rParent).name());
824 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(&rParent);
825 if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
826 pPara->bRtl = true;
827 if( pNext )
828 {
829 const GraphicsContext& rCurGC = m_rProcessor.getGraphicsContext( pCur->GCId );
830 const GraphicsContext& rNextGC = m_rProcessor.getGraphicsContext( pNext->GCId );
831
832 // line and space optimization; works only in strictly horizontal mode
833
834 if( !bRotatedFrame
835 && ! rCurGC.isRotatedOrSkewed()
836 && ! rNextGC.isRotatedOrSkewed()
837 && ! pNext->Text.isEmpty()
838 && pNext->Text[0] != ' '
839 && ! pCur->Text.isEmpty()
840 && pCur->Text[pCur->Text.getLength() - 1] != ' '
841 )
842 {
843 // check for new line in paragraph
844 if( pNext->y > pCur->y+pCur->h )
845 {
846 // new line begins
847 // check whether a space would should be inserted or a hyphen removed
848 sal_Unicode aLastCode = pCur->Text[pCur->Text.getLength() - 1];
849 if( aLastCode == '-'
850 || aLastCode == 0x2010
851 || (aLastCode >= 0x2012 && aLastCode <= 0x2015)
852 || aLastCode == 0xff0d
853 )
854 {
855 // cut a hyphen
856 pCur->Text.setLength( pCur->Text.getLength()-1 );
857 }
858 // append a space unless there is a non breaking hyphen
859 else if( aLastCode != 0x2011 )
860 {
861 pCur->Text.append( ' ' );
862 }
863 }
864 else // we're continuing the same line
865 {
866 // check whether a space would should be inserted
867 // check for a small horizontal offset
868 if( pCur->x + pCur->w + pNext->h*0.15 < pNext->x )
869 {
870 pCur->Text.append( ' ' );
871 }
872 }
873 }
874 // concatenate consecutive text elements unless there is a
875 // font or text color change, leave a new span in that case
876 if( pCur->FontId == pNext->FontId &&
877 rCurGC.FillColor.Red == rNextGC.FillColor.Red &&
878 rCurGC.FillColor.Green == rNextGC.FillColor.Green &&
879 rCurGC.FillColor.Blue == rNextGC.FillColor.Blue &&
880 rCurGC.FillColor.Alpha == rNextGC.FillColor.Alpha
881 )
882 {
883 pCur->updateGeometryWith( pNext );
884 if (pPara && pPara->bRtl)
885 {
886 // Tdf#152083: If RTL, reverse the text in pNext so that its correct order is
887 // restored when the combined text is reversed in WriterXmlEmitter::visit.
888 OUString tempStr;
889 bool bNeedReverse=false;
890 str = pNext->Text.toString();
891 for (sal_Int32 i=0; i < str.getLength(); i++)
892 {
893 if (str[i] == u' ')
894 { // Space char (e.g. the space as in " م") needs special treatment.
895 // First, append the space char to pCur.
896 pCur->Text.append(OUStringChar(str[i]));
897 // Then, check whether the tmpStr needs reverse, if so then reverse and append.
898 if (bNeedReverse)
899 {
900 tempStr = ::comphelper::string::reverseCodePoints(tempStr);
901 pCur->Text.append(tempStr);
902 tempStr = u"";
903 }
904 bNeedReverse = false;
905 }
906 else
907 {
908 tempStr += OUStringChar(str[i]);
909 bNeedReverse = true;
910 }
911 }
912 // Do the last append
913 if (bNeedReverse)
914 {
915 tempStr = ::comphelper::string::reverseCodePoints(tempStr);
916 pCur->Text.append(tempStr);
917 }
918 else
919 {
920 pCur->Text.append(tempStr);
921 }
922 }
923 else
924 {
925 // append text to current element directly without reverse
926 pCur->Text.append(pNext->Text);
927 }
928 if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
929 pPara->bRtl = true;
930 // append eventual children to current element
931 // and clear children (else the children just
932 // appended to pCur would be destroyed)
933 pCur->Children.splice( pCur->Children.end(), pNext->Children );
934 // get rid of the now useless element
935 rParent.Children.erase( next );
936 bConcat = true;
937 }
938 }
939 }
940 else if( dynamic_cast<HyperlinkElement*>(it->get()) )
941 optimizeTextElements( **it );
942 if( bConcat )
943 {
944 next = it;
945 ++next;
946 }
947 else
948 {
949 ++it;
950 ++next;
951 }
952 }
953}
954
955void WriterXmlOptimizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
956{
957 elem.applyToChildren(*this);
958}
959
960
961void WriterXmlFinalizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
962{
963 // xxx TODO copied from DrawElement
965 PropertyMap aProps;
966 aProps[ "style:family" ] = "graphic";
967
968 PropertyMap aGCProps;
969 if (elem.Action & PATH_STROKE)
970 {
972 if (rGC.DashArray.size() < 2)
973 {
974 aGCProps[ "draw:stroke" ] = "solid";
975 }
976 else
977 {
980 StyleContainer::Style style("draw:stroke-dash", std::move(props));
981
982 aGCProps[ "draw:stroke" ] = "dash";
983 aGCProps[ "draw:stroke-dash" ] =
986 }
987
988 aGCProps[ "svg:stroke-color" ] = getColorString(rGC.LineColor);
989 aGCProps[ "svg:stroke-width" ] = convertPixelToUnitString(rGC.LineWidth * scale);
990 aGCProps[ "draw:stroke-linejoin" ] = rGC.GetLineJoinString();
991 aGCProps[ "svg:stroke-linecap" ] = rGC.GetLineCapString();
992 }
993 else
994 {
995 aGCProps[ "draw:stroke" ] = "none";
996 }
997
998 // TODO(F1): check whether stuff could be emulated by gradient/bitmap/hatch
999 if( elem.Action & (PATH_FILL | PATH_EOFILL) )
1000 {
1001 aGCProps[ "draw:fill" ] = "solid";
1002 aGCProps[ "draw:fill-color" ] = getColorString( rGC.FillColor );
1003 }
1004 else
1005 {
1006 aGCProps[ "draw:fill" ] = "none";
1007 }
1008
1009 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1010 StyleContainer::Style aSubStyle( "style:graphic-properties", std::move(aGCProps) );
1011 aStyle.SubStyles.push_back( &aSubStyle );
1012
1013 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1014}
1015
1016void WriterXmlFinalizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1017{
1018}
1019
1020void WriterXmlFinalizer::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1021{
1022 const FontAttributes& rFont = m_rProcessor.getFont( elem.FontId );
1023 PropertyMap aProps;
1024 aProps[ "style:family" ] = "text";
1025
1026 PropertyMap aFontProps;
1027
1028 // family name
1029 // TODO: tdf#143095: use system font name rather than PSName
1030 SAL_INFO("sdext.pdfimport", "The font used in xml is: " << rFont.familyName);
1031 aFontProps[ "fo:font-family" ] = rFont.familyName;
1032 aFontProps[ "style:font-family-asian" ] = rFont.familyName;
1033 aFontProps[ "style:font-family-complex" ] = rFont.familyName;
1034
1035 // bold
1036 aFontProps[ "fo:font-weight" ] = rFont.fontWeight;
1037 aFontProps[ "style:font-weight-asian" ] = rFont.fontWeight;
1038 aFontProps[ "style:font-weight-complex" ] = rFont.fontWeight;
1039
1040 // italic
1041 if( rFont.isItalic )
1042 {
1043 aFontProps[ "fo:font-style" ] = "italic";
1044 aFontProps[ "style:font-style-asian" ] = "italic";
1045 aFontProps[ "style:font-style-complex" ] = "italic";
1046 }
1047
1048 // underline
1049 if( rFont.isUnderline )
1050 {
1051 aFontProps[ "style:text-underline-style" ] = "solid";
1052 aFontProps[ "style:text-underline-width" ] = "auto";
1053 aFontProps[ "style:text-underline-color" ] = "font-color";
1054 }
1055
1056 // outline
1057 if( rFont.isOutline )
1058 aFontProps[ "style:text-outline" ] = "true";
1059
1060 // size
1061 OUString aFSize = OUString::number( rFont.size*72/PDFI_OUTDEV_RESOLUTION ) + "pt";
1062 aFontProps[ "fo:font-size" ] = aFSize;
1063 aFontProps[ "style:font-size-asian" ] = aFSize;
1064 aFontProps[ "style:font-size-complex" ] = aFSize;
1065
1066 // color
1068 aFontProps[ "fo:color" ] = getColorString( rFont.isOutline ? rGC.LineColor : rGC.FillColor );
1069
1070 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1071 StyleContainer::Style aSubStyle( "style:text-properties", std::move(aFontProps) );
1072 aStyle.SubStyles.push_back( &aSubStyle );
1073 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1074}
1075
1076void WriterXmlFinalizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
1077{
1078 PropertyMap aParaProps;
1079
1080 if( elem.Parent )
1081 {
1082 // check for center alignment
1083 // criterion: paragraph is small relative to parent and distributed around its center
1084 double p_x = elem.Parent->x;
1085 double p_w = elem.Parent->w;
1086
1087 PageElement* pPage = dynamic_cast<PageElement*>(elem.Parent);
1088 if( pPage )
1089 {
1090 p_x += pPage->LeftMargin;
1091 p_w -= pPage->LeftMargin+pPage->RightMargin;
1092 }
1093 bool bIsCenter = false;
1094 if( elem.w < ( p_w/2) )
1095 {
1096 double delta = elem.w/4;
1097 // allow very small paragraphs to deviate a little more
1098 // relative to parent's center
1099 if( elem.w < p_w/8 )
1100 delta = elem.w;
1101 if( fabs( elem.x+elem.w/2 - ( p_x+ p_w/2) ) < delta ||
1102 (pPage && fabs( elem.x+elem.w/2 - (pPage->x + pPage->w/2) ) < delta) )
1103 {
1104 bIsCenter = true;
1105 aParaProps[ "fo:text-align" ] = "center";
1106 }
1107 }
1108 if( ! bIsCenter && elem.x > p_x + p_w/10 )
1109 {
1110 // indent
1111 aParaProps[ "fo:margin-left" ] = OUString::number(convPx2mm( elem.x - p_x )) + "mm";
1112 }
1113
1114 // check whether to leave some space to next paragraph
1115 // find whether there is a next paragraph
1116 auto it = rParentIt;
1117 const ParagraphElement* pNextPara = nullptr;
1118 while( ++it != elem.Parent->Children.end() && ! pNextPara )
1119 pNextPara = dynamic_cast< const ParagraphElement* >(it->get());
1120 if( pNextPara )
1121 {
1122 if( pNextPara->y - (elem.y+elem.h) > convmm2Px( 10 ) )
1123 {
1124 aParaProps[ "fo:margin-bottom" ] =
1125 OUString::number( convPx2mm( pNextPara->y - (elem.y+elem.h) ) ) + "mm";
1126 }
1127 }
1128 }
1129
1130 if( ! aParaProps.empty() )
1131 {
1132 PropertyMap aProps;
1133 aProps[ "style:family" ] = "paragraph";
1134 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1135 StyleContainer::Style aSubStyle( "style:paragraph-properties", std::move(aParaProps) );
1136 aStyle.SubStyles.push_back( &aSubStyle );
1137 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1138 }
1139
1140 elem.applyToChildren(*this);
1141}
1142
1143void WriterXmlFinalizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
1144{
1145 PropertyMap aProps;
1146 aProps[ "style:family" ] = "graphic";
1147
1148 PropertyMap aGCProps;
1149
1150 aGCProps[ "draw:stroke" ] = "none";
1151 aGCProps[ "draw:fill" ] = "none";
1152 aGCProps[ "draw:auto-grow-height" ] = "true";
1153 aGCProps[ "draw:auto-grow-width" ] = "true";
1154 aGCProps[ "draw:textarea-horizontal-align" ] = "left";
1155 aGCProps[ "draw:textarea-vertical-align" ] = "top";
1156 aGCProps[ "fo:min-height"] = "0cm";
1157 aGCProps[ "fo:min-width"] = "0cm";
1158 aGCProps[ "fo:padding-top" ] = "0cm";
1159 aGCProps[ "fo:padding-left" ] = "0cm";
1160 aGCProps[ "fo:padding-right" ] = "0cm";
1161 aGCProps[ "fo:padding-bottom" ] = "0cm";
1162
1163 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1164 StyleContainer::Style aSubStyle( "style:graphic-properties", std::move(aGCProps) );
1165 aStyle.SubStyles.push_back( &aSubStyle );
1166
1167 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1168 elem.applyToChildren(*this);
1169}
1170
1171void WriterXmlFinalizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1172{
1173}
1174
1176 StyleContainer& rStyles,
1177 const OUString& rMasterPageName )
1178{
1179 PropertyMap aProps;
1180 if( rElem.StyleId != -1 )
1181 {
1182 const PropertyMap* pProps = rStyles.getProperties( rElem.StyleId );
1183 if( pProps )
1184 aProps = *pProps;
1185 }
1186
1187 aProps[ "style:family" ] = "paragraph";
1188 aProps[ "style:master-page-name" ] = rMasterPageName;
1189
1190 if( rElem.StyleId != -1 )
1191 rElem.StyleId = rStyles.setProperties( rElem.StyleId, std::move(aProps) );
1192 else
1193 {
1194 StyleContainer::Style aStyle( "style:style", std::move(aProps) );
1195 rElem.StyleId = rStyles.getStyleId( aStyle );
1196 }
1197}
1198
1199void WriterXmlFinalizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1200{
1201 if( m_rProcessor.getStatusIndicator().is() )
1202 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
1203
1204 // transform from pixel to mm
1205 double page_width = convPx2mm( elem.w ), page_height = convPx2mm( elem.h );
1206
1207 // calculate page margins out of the relevant children (paragraphs)
1208 elem.TopMargin = elem.h;
1209 elem.BottomMargin = 0;
1210 elem.LeftMargin = elem.w;
1211 elem.RightMargin = 0;
1212 // first element should be a paragraph
1213 ParagraphElement* pFirstPara = nullptr;
1214 for( const auto& rxChild : elem.Children )
1215 {
1216 if( dynamic_cast<ParagraphElement*>( rxChild.get() ) )
1217 {
1218 if( rxChild->x < elem.LeftMargin )
1219 elem.LeftMargin = rxChild->x;
1220 if( rxChild->y < elem.TopMargin )
1221 elem.TopMargin = rxChild->y;
1222 if( rxChild->x + rxChild->w > elem.w - elem.RightMargin )
1223 elem.RightMargin = elem.w - (rxChild->x + rxChild->w);
1224 if( rxChild->y + rxChild->h > elem.h - elem.BottomMargin )
1225 elem.BottomMargin = elem.h - (rxChild->y + rxChild->h);
1226 if( ! pFirstPara )
1227 pFirstPara = dynamic_cast<ParagraphElement*>( rxChild.get() );
1228 }
1229 }
1230 if( elem.HeaderElement && elem.HeaderElement->y < elem.TopMargin )
1231 elem.TopMargin = elem.HeaderElement->y;
1232 if( elem.FooterElement && elem.FooterElement->y+elem.FooterElement->h > elem.h - elem.BottomMargin )
1233 elem.BottomMargin = elem.h - (elem.FooterElement->y + elem.FooterElement->h);
1234
1235 // transform margins to mm
1236 double left_margin = convPx2mm( elem.LeftMargin );
1237 double right_margin = convPx2mm( elem.RightMargin );
1238 double top_margin = convPx2mm( elem.TopMargin );
1239 double bottom_margin = convPx2mm( elem.BottomMargin );
1240 if( ! pFirstPara )
1241 {
1242 // use default page margins
1243 left_margin = 10;
1244 right_margin = 10;
1245 top_margin = 10;
1246 bottom_margin = 10;
1247 }
1248
1249 // round left/top margin to nearest mm
1250 left_margin = rtl_math_round( left_margin, 0, rtl_math_RoundingMode_Floor );
1251 top_margin = rtl_math_round( top_margin, 0, rtl_math_RoundingMode_Floor );
1252 // round (fuzzy) right/bottom margin to nearest cm
1253 right_margin = rtl_math_round( right_margin, right_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1254 bottom_margin = rtl_math_round( bottom_margin, bottom_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1255
1256 // set reasonable default in case of way too large margins
1257 // e.g. no paragraph case
1258 if( left_margin > page_width/2.0 - 10 )
1259 left_margin = 10;
1260 if( right_margin > page_width/2.0 - 10 )
1261 right_margin = 10;
1262 if( top_margin > page_height/2.0 - 10 )
1263 top_margin = 10;
1264 if( bottom_margin > page_height/2.0 - 10 )
1265 bottom_margin = 10;
1266
1267 // catch the weird cases
1268 if( left_margin < 0 )
1269 left_margin = 0;
1270 if( right_margin < 0 )
1271 right_margin = 0;
1272 if( top_margin < 0 )
1273 top_margin = 0;
1274 if( bottom_margin < 0 )
1275 bottom_margin = 0;
1276
1277 // widely differing margins are unlikely to be correct
1278 if( right_margin > left_margin*1.5 )
1279 right_margin = left_margin;
1280
1281 elem.LeftMargin = convmm2Px( left_margin );
1282 elem.RightMargin = convmm2Px( right_margin );
1283 elem.TopMargin = convmm2Px( top_margin );
1284 elem.BottomMargin = convmm2Px( bottom_margin );
1285
1286 // get styles for paragraphs
1287 PropertyMap aPageProps;
1288 PropertyMap aPageLayoutProps;
1289 aPageLayoutProps[ "fo:page-width" ] = unitMMString( page_width );
1290 aPageLayoutProps[ "fo:page-height" ] = unitMMString( page_height );
1291 aPageLayoutProps[ "style:print-orientation" ]
1292 = elem.w < elem.h ? std::u16string_view(u"portrait") : std::u16string_view(u"landscape");
1293 aPageLayoutProps[ "fo:margin-top" ] = unitMMString( top_margin );
1294 aPageLayoutProps[ "fo:margin-bottom" ] = unitMMString( bottom_margin );
1295 aPageLayoutProps[ "fo:margin-left" ] = unitMMString( left_margin );
1296 aPageLayoutProps[ "fo:margin-right" ] = unitMMString( right_margin );
1297 aPageLayoutProps[ "style:writing-mode" ]= "lr-tb";
1298
1299 StyleContainer::Style aStyle( "style:page-layout", std::move(aPageProps));
1300 StyleContainer::Style aSubStyle( "style:page-layout-properties", std::move(aPageLayoutProps));
1301 aStyle.SubStyles.push_back(&aSubStyle);
1302 sal_Int32 nPageStyle = m_rStyleContainer.impl_getStyleId( aStyle, false );
1303
1304 // create master page
1305 OUString aMasterPageLayoutName = m_rStyleContainer.getStyleName( nPageStyle );
1306 aPageProps[ "style:page-layout-name" ] = aMasterPageLayoutName;
1307 StyleContainer::Style aMPStyle( "style:master-page", std::move(aPageProps) );
1308 StyleContainer::Style aHeaderStyle( "style:header", PropertyMap() );
1309 StyleContainer::Style aFooterStyle( "style:footer", PropertyMap() );
1310 if( elem.HeaderElement )
1311 {
1312 elem.HeaderElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1313 aHeaderStyle.ContainedElement = elem.HeaderElement.get();
1314 aMPStyle.SubStyles.push_back( &aHeaderStyle );
1315 }
1316 if( elem.FooterElement )
1317 {
1318 elem.FooterElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1319 aFooterStyle.ContainedElement = elem.FooterElement.get();
1320 aMPStyle.SubStyles.push_back( &aFooterStyle );
1321 }
1322 elem.StyleId = m_rStyleContainer.impl_getStyleId( aMPStyle,false );
1323
1324
1325 OUString aMasterPageName = m_rStyleContainer.getStyleName( elem.StyleId );
1326
1327 // create styles for children
1328 elem.applyToChildren(*this);
1329
1330 // no paragraph or other elements before the first paragraph
1331 if( ! pFirstPara )
1332 {
1333 pFirstPara = ElementFactory::createParagraphElement( nullptr );
1334 pFirstPara->Parent = &elem;
1335 elem.Children.push_front( std::unique_ptr<Element>(pFirstPara) );
1336 }
1337 setFirstOnPage(*pFirstPara, m_rStyleContainer, aMasterPageName);
1338}
1339
1340void WriterXmlFinalizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1341{
1342 elem.applyToChildren(*this);
1343}
1344
1345}
1346
1347/* 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