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