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  ? OUStringLiteral(u"character") : OUStringLiteral(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.isBold && ! rThisFont.isBold )
467  pPrevPara->Type = ParagraphElement::Headline;
468  }
469  }
470  }
471  }
472  break;
473  }
474  }
475 }
476 
477 void WriterXmlOptimizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
478 {
479  if( m_rProcessor.getStatusIndicator().is() )
480  m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
481 
482  // resolve hyperlinks
483  elem.resolveHyperlinks();
484 
485  elem.resolveFontStyles( m_rProcessor ); // underlines and such
486 
487  // FIXME: until hyperlinks and font effects are adjusted for
488  // geometrical search handle them before sorting
490 
491  // find paragraphs in text
492  ParagraphElement* pCurPara = nullptr;
493  std::list< std::unique_ptr<Element> >::iterator page_element, next_page_element;
494  next_page_element = elem.Children.begin();
495  double fCurLineHeight = 0.0; // average height of text items in current para
496  int nCurLineElements = 0; // number of line contributing elements in current para
497  double line_left = elem.w, line_right = 0.0;
498  double column_width = elem.w*0.75; // estimate text width
499  // TODO: guess columns
500  while( next_page_element != elem.Children.end() )
501  {
502  page_element = next_page_element++;
503  ParagraphElement* pPagePara = dynamic_cast<ParagraphElement*>(page_element->get());
504  if( pPagePara )
505  {
506  pCurPara = pPagePara;
507  // adjust line height and text items
508  fCurLineHeight = 0.0;
509  nCurLineElements = 0;
510  for( const auto& rxChild : pCurPara->Children )
511  {
512  TextElement* pTestText = dynamic_cast<TextElement*>(rxChild.get());
513  if( pTestText )
514  {
515  fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pTestText->h)/double(nCurLineElements+1);
516  nCurLineElements++;
517  }
518  }
519  continue;
520  }
521 
522  HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(page_element->get());
523  DrawElement* pDraw = dynamic_cast<DrawElement*>(page_element->get());
524  if( ! pDraw && pLink && ! pLink->Children.empty() )
525  pDraw = dynamic_cast<DrawElement*>(pLink->Children.front().get() );
526  if( pDraw )
527  {
528  // insert small drawing objects as character, else leave them page bound
529 
530  bool bInsertToParagraph = false;
531  // first check if this is either inside the paragraph
532  if( pCurPara && pDraw->y < pCurPara->y + pCurPara->h )
533  {
534  if( pDraw->h < fCurLineHeight * 1.5 )
535  {
536  bInsertToParagraph = true;
537  fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pDraw->h)/double(nCurLineElements+1);
538  nCurLineElements++;
539  // mark draw element as character
540  pDraw->isCharacter = true;
541  }
542  }
543  // or perhaps the draw element begins a new paragraph
544  else if( next_page_element != elem.Children.end() )
545  {
546  TextElement* pText = dynamic_cast<TextElement*>(next_page_element->get());
547  if( ! pText )
548  {
549  ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(next_page_element->get());
550  if( pPara && ! pPara->Children.empty() )
551  pText = dynamic_cast<TextElement*>(pPara->Children.front().get());
552  }
553  if( pText && // check there is a text
554  pDraw->h < pText->h*1.5 && // and it is approx the same height
555  // and either upper or lower edge of pDraw is inside text's vertical range
556  ( ( pDraw->y >= pText->y && pDraw->y <= pText->y+pText->h ) ||
557  ( pDraw->y+pDraw->h >= pText->y && pDraw->y+pDraw->h <= pText->y+pText->h )
558  )
559  )
560  {
561  bInsertToParagraph = true;
562  fCurLineHeight = pDraw->h;
563  nCurLineElements = 1;
564  line_left = pDraw->x;
565  line_right = pDraw->x + pDraw->w;
566  // begin a new paragraph
567  pCurPara = nullptr;
568  // mark draw element as character
569  pDraw->isCharacter = true;
570  }
571  }
572 
573  if( ! bInsertToParagraph )
574  {
575  pCurPara = nullptr;
576  continue;
577  }
578  }
579 
580  TextElement* pText = dynamic_cast<TextElement*>(page_element->get());
581  if( ! pText && pLink && ! pLink->Children.empty() )
582  pText = dynamic_cast<TextElement*>(pLink->Children.front().get());
583  if( pText )
584  {
585  Element* pGeo = pLink ? static_cast<Element*>(pLink) :
586  static_cast<Element*>(pText);
587  if( pCurPara )
588  {
589  // there was already a text element, check for a new paragraph
590  if( nCurLineElements > 0 )
591  {
592  // if the new text is significantly distant from the paragraph
593  // begin a new paragraph
594  if( pGeo->y > pCurPara->y+pCurPara->h + fCurLineHeight*0.5 )
595  pCurPara = nullptr; // insert new paragraph
596  else if( pGeo->y > (pCurPara->y+pCurPara->h - fCurLineHeight*0.05) )
597  {
598  // new paragraph if either the last line of the paragraph
599  // was significantly shorter than the paragraph as a whole
600  if( (line_right - line_left) < pCurPara->w*0.75 )
601  pCurPara = nullptr;
602  // or the last line was significantly smaller than the column width
603  else if( (line_right - line_left) < column_width*0.75 )
604  pCurPara = nullptr;
605  }
606  }
607  }
608  // update line height/width
609  if( pCurPara )
610  {
611  fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pGeo->h)/double(nCurLineElements+1);
612  nCurLineElements++;
613  if( pGeo->x < line_left )
614  line_left = pGeo->x;
615  if( pGeo->x+pGeo->w > line_right )
616  line_right = pGeo->x+pGeo->w;
617  }
618  else
619  {
620  fCurLineHeight = pGeo->h;
621  nCurLineElements = 1;
622  line_left = pGeo->x;
623  line_right = pGeo->x + pGeo->w;
624  }
625  }
626 
627  // move element to current paragraph
628  if( ! pCurPara ) // new paragraph, insert one
629  {
630  pCurPara = ElementFactory::createParagraphElement( nullptr );
631  // set parent
632  pCurPara->Parent = &elem;
633  //insert new paragraph before current element
634  page_element = elem.Children.insert( page_element, std::unique_ptr<Element>(pCurPara) );
635  // forward iterator to current element again
636  ++ page_element;
637  // update next_element which is now invalid
638  next_page_element = page_element;
639  ++ next_page_element;
640  }
641  Element* pCurEle = page_element->get();
642  Element::setParent( page_element, pCurPara );
643  OSL_ENSURE( !pText || pCurEle == pText || pCurEle == pLink, "paragraph child list in disorder" );
644  if( pText || pDraw )
645  pCurPara->updateGeometryWith( pCurEle );
646  }
647 
648  // process children
649  elem.applyToChildren(*this);
650 
651  // find possible header and footer
652  checkHeaderAndFooter( elem );
653 }
654 
656 {
657  /* indicators for a header:
658  * - single line paragraph at top of page (inside 15% page height)
659  * - at least lineheight above the next paragraph
660  *
661  * indicators for a footer likewise:
662  * - single line paragraph at bottom of page (inside 15% page height)
663  * - at least lineheight below the previous paragraph
664  */
665 
666  auto isParagraphElement = [](std::unique_ptr<Element>& rxChild) -> bool {
667  return dynamic_cast<ParagraphElement*>(rxChild.get()) != nullptr;
668  };
669 
670  // detect header
671  // Note: the following assumes that the pages' children have been
672  // sorted geometrically
673  auto it = std::find_if(rElem.Children.begin(), rElem.Children.end(), isParagraphElement);
674  if (it != rElem.Children.end())
675  {
676  ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**it);
677  if( rPara.y+rPara.h < rElem.h*0.15 && rPara.isSingleLined( m_rProcessor ) )
678  {
679  auto next_it = it;
680  ParagraphElement* pNextPara = nullptr;
681  while( ++next_it != rElem.Children.end() && pNextPara == nullptr )
682  {
683  pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
684  }
685  if( pNextPara && pNextPara->y > rPara.y+rPara.h*2 )
686  {
687  rElem.HeaderElement = std::move(*it);
688  rPara.Parent = nullptr;
689  rElem.Children.erase( it );
690  }
691  }
692  }
693 
694  // detect footer
695  auto rit = std::find_if(rElem.Children.rbegin(), rElem.Children.rend(), isParagraphElement);
696  if (rit == rElem.Children.rend())
697  return;
698 
699  ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**rit);
700  if( !(rPara.y > rElem.h*0.85 && rPara.isSingleLined( m_rProcessor )) )
701  return;
702 
703  std::list< std::unique_ptr<Element> >::reverse_iterator next_it = rit;
704  ParagraphElement* pNextPara = nullptr;
705  while( ++next_it != rElem.Children.rend() && pNextPara == nullptr )
706  {
707  pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
708  }
709  if( pNextPara && pNextPara->y < rPara.y-rPara.h*2 )
710  {
711  rElem.FooterElement = std::move(*rit);
712  rPara.Parent = nullptr;
713  rElem.Children.erase( std::next(rit).base() );
714  }
715 }
716 
718 {
719  if( rParent.Children.empty() ) // this should not happen
720  {
721  OSL_FAIL( "empty paragraph optimized" );
722  return;
723  }
724 
725  // concatenate child elements with same font id
726  auto next = rParent.Children.begin();
727  auto it = next++;
728  FrameElement* pFrame = dynamic_cast<FrameElement*>(rParent.Parent);
729  bool bRotatedFrame = false;
730  if( pFrame )
731  {
732  const GraphicsContext& rFrameGC = m_rProcessor.getGraphicsContext( pFrame->GCId );
733  if( rFrameGC.isRotatedOrSkewed() )
734  bRotatedFrame = true;
735  }
736  while( next != rParent.Children.end() )
737  {
738  bool bConcat = false;
739  TextElement* pCur = dynamic_cast<TextElement*>(it->get());
740  if( pCur )
741  {
742  TextElement* pNext = dynamic_cast<TextElement*>(next->get());
743  if( pNext )
744  {
745  const GraphicsContext& rCurGC = m_rProcessor.getGraphicsContext( pCur->GCId );
746  const GraphicsContext& rNextGC = m_rProcessor.getGraphicsContext( pNext->GCId );
747 
748  // line and space optimization; works only in strictly horizontal mode
749 
750  if( !bRotatedFrame
751  && ! rCurGC.isRotatedOrSkewed()
752  && ! rNextGC.isRotatedOrSkewed()
753  && ! pNext->Text.isEmpty()
754  && pNext->Text[0] != ' '
755  && ! pCur->Text.isEmpty()
756  && pCur->Text[pCur->Text.getLength() - 1] != ' '
757  )
758  {
759  // check for new line in paragraph
760  if( pNext->y > pCur->y+pCur->h )
761  {
762  // new line begins
763  // check whether a space would should be inserted or a hyphen removed
764  sal_Unicode aLastCode = pCur->Text[pCur->Text.getLength() - 1];
765  if( aLastCode == '-'
766  || aLastCode == 0x2010
767  || (aLastCode >= 0x2012 && aLastCode <= 0x2015)
768  || aLastCode == 0xff0d
769  )
770  {
771  // cut a hyphen
772  pCur->Text.setLength( pCur->Text.getLength()-1 );
773  }
774  // append a space unless there is a non breaking hyphen
775  else if( aLastCode != 0x2011 )
776  {
777  pCur->Text.append( ' ' );
778  }
779  }
780  else // we're continuing the same line
781  {
782  // check whether a space would should be inserted
783  // check for a small horizontal offset
784  if( pCur->x + pCur->w + pNext->h*0.15 < pNext->x )
785  {
786  pCur->Text.append( ' ' );
787  }
788  }
789  }
790  // concatenate consecutive text elements unless there is a
791  // font or text color or matrix change, leave a new span in that case
792  if( pCur->FontId == pNext->FontId &&
793  rCurGC.FillColor.Red == rNextGC.FillColor.Red &&
794  rCurGC.FillColor.Green == rNextGC.FillColor.Green &&
795  rCurGC.FillColor.Blue == rNextGC.FillColor.Blue &&
796  rCurGC.FillColor.Alpha == rNextGC.FillColor.Alpha &&
797  rCurGC.Transformation == rNextGC.Transformation
798  )
799  {
800  pCur->updateGeometryWith( pNext );
801  // append text to current element
802  pCur->Text.append( pNext->Text );
803  // append eventual children to current element
804  // and clear children (else the children just
805  // appended to pCur would be destroyed)
806  pCur->Children.splice( pCur->Children.end(), pNext->Children );
807  // get rid of the now useless element
808  rParent.Children.erase( next );
809  bConcat = true;
810  }
811  }
812  }
813  else if( dynamic_cast<HyperlinkElement*>(it->get()) )
814  optimizeTextElements( **it );
815  if( bConcat )
816  {
817  next = it;
818  ++next;
819  }
820  else
821  {
822  ++it;
823  ++next;
824  }
825  }
826 }
827 
828 void WriterXmlOptimizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
829 {
830  elem.applyToChildren(*this);
831 }
832 
833 
834 void WriterXmlFinalizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
835 {
836  // xxx TODO copied from DrawElement
838  PropertyMap aProps;
839  aProps[ "style:family" ] = "graphic";
840 
841  PropertyMap aGCProps;
842  if (elem.Action & PATH_STROKE)
843  {
845  if (rGC.DashArray.size() < 2)
846  {
847  aGCProps[ "draw:stroke" ] = "solid";
848  }
849  else
850  {
852  FillDashStyleProps(props, rGC.DashArray, scale);
853  StyleContainer::Style style("draw:stroke-dash", props);
854 
855  aGCProps[ "draw:stroke" ] = "dash";
856  aGCProps[ "draw:stroke-dash" ] =
859  }
860 
861  aGCProps[ "svg:stroke-color" ] = getColorString(rGC.LineColor);
862  aGCProps[ "svg:stroke-width" ] = convertPixelToUnitString(rGC.LineWidth * scale);
863  aGCProps[ "draw:stroke-linejoin" ] = rGC.GetLineJoinString();
864  aGCProps[ "svg:stroke-linecap" ] = rGC.GetLineCapString();
865  }
866  else
867  {
868  aGCProps[ "draw:stroke" ] = "none";
869  }
870 
871  // TODO(F1): check whether stuff could be emulated by gradient/bitmap/hatch
872  if( elem.Action & (PATH_FILL | PATH_EOFILL) )
873  {
874  aGCProps[ "draw:fill" ] = "solid";
875  aGCProps[ "draw:fill-color" ] = getColorString( rGC.FillColor );
876  }
877  else
878  {
879  aGCProps[ "draw:fill" ] = "none";
880  }
881 
882  StyleContainer::Style aStyle( "style:style", aProps );
883  StyleContainer::Style aSubStyle( "style:graphic-properties", aGCProps );
884  aStyle.SubStyles.push_back( &aSubStyle );
885 
886  elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
887 }
888 
889 void WriterXmlFinalizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
890 {
891 }
892 
893 void WriterXmlFinalizer::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
894 {
895  const FontAttributes& rFont = m_rProcessor.getFont( elem.FontId );
896  PropertyMap aProps;
897  aProps[ "style:family" ] = "text";
898 
899  PropertyMap aFontProps;
900 
901  // family name
902  // TODO: tdf#143095: use system font name rather than PSName
903  SAL_INFO("sdext.pdfimport", "The font used in xml is: " << rFont.familyName);
904  aFontProps[ "fo:font-family" ] = rFont.familyName;
905  aFontProps[ "style:font-family-asia" ] = rFont.familyName;
906  aFontProps[ "style:font-family-complex" ] = rFont.familyName;
907 
908  // bold
909  if( rFont.isBold )
910  {
911  aFontProps[ "fo:font-weight" ] = "bold";
912  aFontProps[ "style:font-weight-asian" ] = "bold";
913  aFontProps[ "style:font-weight-complex" ] = "bold";
914  }
915 
916  // italic
917  if( rFont.isItalic )
918  {
919  aFontProps[ "fo:font-style" ] = "italic";
920  aFontProps[ "style:font-style-asian" ] = "italic";
921  aFontProps[ "style:font-style-complex" ] = "italic";
922  }
923 
924  // underline
925  if( rFont.isUnderline )
926  {
927  aFontProps[ "style:text-underline-style" ] = "solid";
928  aFontProps[ "style:text-underline-width" ] = "auto";
929  aFontProps[ "style:text-underline-color" ] = "font-color";
930  }
931 
932  // outline
933  if( rFont.isOutline )
934  aFontProps[ "style:text-outline" ] = "true";
935 
936  // size
937  OUString aFSize = OUString::number( rFont.size*72/PDFI_OUTDEV_RESOLUTION ) + "pt";
938  aFontProps[ "fo:font-size" ] = aFSize;
939  aFontProps[ "style:font-size-asian" ] = aFSize;
940  aFontProps[ "style:font-size-complex" ] = aFSize;
941 
942  // color
944  aFontProps[ "fo:color" ] = getColorString( rFont.isOutline ? rGC.LineColor : rGC.FillColor );
945 
946  StyleContainer::Style aStyle( "style:style", aProps );
947  StyleContainer::Style aSubStyle( "style:text-properties", aFontProps );
948  aStyle.SubStyles.push_back( &aSubStyle );
949  elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
950 }
951 
952 void WriterXmlFinalizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
953 {
954  PropertyMap aParaProps;
955 
956  if( elem.Parent )
957  {
958  // check for center alignment
959  // criterion: paragraph is small relative to parent and distributed around its center
960  double p_x = elem.Parent->x;
961  double p_w = elem.Parent->w;
962 
963  PageElement* pPage = dynamic_cast<PageElement*>(elem.Parent);
964  if( pPage )
965  {
966  p_x += pPage->LeftMargin;
967  p_w -= pPage->LeftMargin+pPage->RightMargin;
968  }
969  bool bIsCenter = false;
970  if( elem.w < ( p_w/2) )
971  {
972  double delta = elem.w/4;
973  // allow very small paragraphs to deviate a little more
974  // relative to parent's center
975  if( elem.w < p_w/8 )
976  delta = elem.w;
977  if( fabs( elem.x+elem.w/2 - ( p_x+ p_w/2) ) < delta ||
978  (pPage && fabs( elem.x+elem.w/2 - (pPage->x + pPage->w/2) ) < delta) )
979  {
980  bIsCenter = true;
981  aParaProps[ "fo:text-align" ] = "center";
982  }
983  }
984  if( ! bIsCenter && elem.x > p_x + p_w/10 )
985  {
986  // indent
987  OUStringBuffer aBuf( 32 );
988  aBuf.append( convPx2mm( elem.x - p_x ) );
989  aBuf.append( "mm" );
990  aParaProps[ "fo:margin-left" ] = aBuf.makeStringAndClear();
991  }
992 
993  // check whether to leave some space to next paragraph
994  // find whether there is a next paragraph
995  auto it = rParentIt;
996  const ParagraphElement* pNextPara = nullptr;
997  while( ++it != elem.Parent->Children.end() && ! pNextPara )
998  pNextPara = dynamic_cast< const ParagraphElement* >(it->get());
999  if( pNextPara )
1000  {
1001  if( pNextPara->y - (elem.y+elem.h) > convmm2Px( 10 ) )
1002  {
1003  OUStringBuffer aBuf( 32 );
1004  aBuf.append( convPx2mm( pNextPara->y - (elem.y+elem.h) ) );
1005  aBuf.append( "mm" );
1006  aParaProps[ "fo:margin-bottom" ] = aBuf.makeStringAndClear();
1007  }
1008  }
1009  }
1010 
1011  if( ! aParaProps.empty() )
1012  {
1013  PropertyMap aProps;
1014  aProps[ "style:family" ] = "paragraph";
1015  StyleContainer::Style aStyle( "style:style", aProps );
1016  StyleContainer::Style aSubStyle( "style:paragraph-properties", aParaProps );
1017  aStyle.SubStyles.push_back( &aSubStyle );
1018  elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1019  }
1020 
1021  elem.applyToChildren(*this);
1022 }
1023 
1024 void WriterXmlFinalizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
1025 {
1026  PropertyMap aProps;
1027  aProps[ "style:family" ] = "graphic";
1028 
1029  PropertyMap aGCProps;
1030 
1031  aGCProps[ "draw:stroke" ] = "none";
1032  aGCProps[ "draw:fill" ] = "none";
1033  aGCProps[ "draw:auto-grow-height" ] = "true";
1034  aGCProps[ "draw:auto-grow-width" ] = "true";
1035  aGCProps[ "draw:textarea-horizontal-align" ] = "left";
1036  aGCProps[ "draw:textarea-vertical-align" ] = "top";
1037  aGCProps[ "fo:min-height"] = "0cm";
1038  aGCProps[ "fo:min-width"] = "0cm";
1039  aGCProps[ "fo:padding-top" ] = "0cm";
1040  aGCProps[ "fo:padding-left" ] = "0cm";
1041  aGCProps[ "fo:padding-right" ] = "0cm";
1042  aGCProps[ "fo:padding-bottom" ] = "0cm";
1043 
1044  StyleContainer::Style aStyle( "style:style", aProps );
1045  StyleContainer::Style aSubStyle( "style:graphic-properties", aGCProps );
1046  aStyle.SubStyles.push_back( &aSubStyle );
1047 
1048  elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1049  elem.applyToChildren(*this);
1050 }
1051 
1052 void WriterXmlFinalizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1053 {
1054 }
1055 
1057  StyleContainer& rStyles,
1058  const OUString& rMasterPageName )
1059 {
1060  PropertyMap aProps;
1061  if( rElem.StyleId != -1 )
1062  {
1063  const PropertyMap* pProps = rStyles.getProperties( rElem.StyleId );
1064  if( pProps )
1065  aProps = *pProps;
1066  }
1067 
1068  aProps[ "style:family" ] = "paragraph";
1069  aProps[ "style:master-page-name" ] = rMasterPageName;
1070 
1071  if( rElem.StyleId != -1 )
1072  rElem.StyleId = rStyles.setProperties( rElem.StyleId, aProps );
1073  else
1074  {
1075  StyleContainer::Style aStyle( "style:style", aProps );
1076  rElem.StyleId = rStyles.getStyleId( aStyle );
1077  }
1078 }
1079 
1080 void WriterXmlFinalizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1081 {
1082  if( m_rProcessor.getStatusIndicator().is() )
1083  m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
1084 
1085  // transform from pixel to mm
1086  double page_width = convPx2mm( elem.w ), page_height = convPx2mm( elem.h );
1087 
1088  // calculate page margins out of the relevant children (paragraphs)
1089  elem.TopMargin = elem.h;
1090  elem.BottomMargin = 0;
1091  elem.LeftMargin = elem.w;
1092  elem.RightMargin = 0;
1093  // first element should be a paragraph
1094  ParagraphElement* pFirstPara = nullptr;
1095  for( const auto& rxChild : elem.Children )
1096  {
1097  if( dynamic_cast<ParagraphElement*>( rxChild.get() ) )
1098  {
1099  if( rxChild->x < elem.LeftMargin )
1100  elem.LeftMargin = rxChild->x;
1101  if( rxChild->y < elem.TopMargin )
1102  elem.TopMargin = rxChild->y;
1103  if( rxChild->x + rxChild->w > elem.w - elem.RightMargin )
1104  elem.RightMargin = elem.w - (rxChild->x + rxChild->w);
1105  if( rxChild->y + rxChild->h > elem.h - elem.BottomMargin )
1106  elem.BottomMargin = elem.h - (rxChild->y + rxChild->h);
1107  if( ! pFirstPara )
1108  pFirstPara = dynamic_cast<ParagraphElement*>( rxChild.get() );
1109  }
1110  }
1111  if( elem.HeaderElement && elem.HeaderElement->y < elem.TopMargin )
1112  elem.TopMargin = elem.HeaderElement->y;
1113  if( elem.FooterElement && elem.FooterElement->y+elem.FooterElement->h > elem.h - elem.BottomMargin )
1114  elem.BottomMargin = elem.h - (elem.FooterElement->y + elem.FooterElement->h);
1115 
1116  // transform margins to mm
1117  double left_margin = convPx2mm( elem.LeftMargin );
1118  double right_margin = convPx2mm( elem.RightMargin );
1119  double top_margin = convPx2mm( elem.TopMargin );
1120  double bottom_margin = convPx2mm( elem.BottomMargin );
1121  if( ! pFirstPara )
1122  {
1123  // use default page margins
1124  left_margin = 10;
1125  right_margin = 10;
1126  top_margin = 10;
1127  bottom_margin = 10;
1128  }
1129 
1130  // round left/top margin to nearest mm
1131  left_margin = rtl_math_round( left_margin, 0, rtl_math_RoundingMode_Floor );
1132  top_margin = rtl_math_round( top_margin, 0, rtl_math_RoundingMode_Floor );
1133  // round (fuzzy) right/bottom margin to nearest cm
1134  right_margin = rtl_math_round( right_margin, right_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1135  bottom_margin = rtl_math_round( bottom_margin, bottom_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1136 
1137  // set reasonable default in case of way too large margins
1138  // e.g. no paragraph case
1139  if( left_margin > page_width/2.0 - 10 )
1140  left_margin = 10;
1141  if( right_margin > page_width/2.0 - 10 )
1142  right_margin = 10;
1143  if( top_margin > page_height/2.0 - 10 )
1144  top_margin = 10;
1145  if( bottom_margin > page_height/2.0 - 10 )
1146  bottom_margin = 10;
1147 
1148  // catch the weird cases
1149  if( left_margin < 0 )
1150  left_margin = 0;
1151  if( right_margin < 0 )
1152  right_margin = 0;
1153  if( top_margin < 0 )
1154  top_margin = 0;
1155  if( bottom_margin < 0 )
1156  bottom_margin = 0;
1157 
1158  // widely differing margins are unlikely to be correct
1159  if( right_margin > left_margin*1.5 )
1160  right_margin = left_margin;
1161 
1162  elem.LeftMargin = convmm2Px( left_margin );
1163  elem.RightMargin = convmm2Px( right_margin );
1164  elem.TopMargin = convmm2Px( top_margin );
1165  elem.BottomMargin = convmm2Px( bottom_margin );
1166 
1167  // get styles for paragraphs
1168  PropertyMap aPageProps;
1169  PropertyMap aPageLayoutProps;
1170  aPageLayoutProps[ "fo:page-width" ] = unitMMString( page_width );
1171  aPageLayoutProps[ "fo:page-height" ] = unitMMString( page_height );
1172  aPageLayoutProps[ "style:print-orientation" ]
1173  = elem.w < elem.h ? std::u16string_view(u"portrait") : std::u16string_view(u"landscape");
1174  aPageLayoutProps[ "fo:margin-top" ] = unitMMString( top_margin );
1175  aPageLayoutProps[ "fo:margin-bottom" ] = unitMMString( bottom_margin );
1176  aPageLayoutProps[ "fo:margin-left" ] = unitMMString( left_margin );
1177  aPageLayoutProps[ "fo:margin-right" ] = unitMMString( right_margin );
1178  aPageLayoutProps[ "style:writing-mode" ]= "lr-tb";
1179 
1180  StyleContainer::Style aStyle( "style:page-layout", aPageProps);
1181  StyleContainer::Style aSubStyle( "style:page-layout-properties", aPageLayoutProps);
1182  aStyle.SubStyles.push_back(&aSubStyle);
1183  sal_Int32 nPageStyle = m_rStyleContainer.impl_getStyleId( aStyle, false );
1184 
1185  // create master page
1186  OUString aMasterPageLayoutName = m_rStyleContainer.getStyleName( nPageStyle );
1187  aPageProps[ "style:page-layout-name" ] = aMasterPageLayoutName;
1188  StyleContainer::Style aMPStyle( "style:master-page", aPageProps );
1189  StyleContainer::Style aHeaderStyle( "style:header", PropertyMap() );
1190  StyleContainer::Style aFooterStyle( "style:footer", PropertyMap() );
1191  if( elem.HeaderElement )
1192  {
1193  elem.HeaderElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1194  aHeaderStyle.ContainedElement = elem.HeaderElement.get();
1195  aMPStyle.SubStyles.push_back( &aHeaderStyle );
1196  }
1197  if( elem.FooterElement )
1198  {
1199  elem.FooterElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1200  aFooterStyle.ContainedElement = elem.FooterElement.get();
1201  aMPStyle.SubStyles.push_back( &aFooterStyle );
1202  }
1203  elem.StyleId = m_rStyleContainer.impl_getStyleId( aMPStyle,false );
1204 
1205 
1206  OUString aMasterPageName = m_rStyleContainer.getStyleName( elem.StyleId );
1207 
1208  // create styles for children
1209  elem.applyToChildren(*this);
1210 
1211  // no paragraph or other elements before the first paragraph
1212  if( ! pFirstPara )
1213  {
1214  pFirstPara = ElementFactory::createParagraphElement( nullptr );
1215  pFirstPara->Parent = &elem;
1216  elem.Children.push_front( std::unique_ptr<Element>(pFirstPara) );
1217  }
1218  setFirstOnPage(*pFirstPara, m_rStyleContainer, aMasterPageName);
1219 }
1220 
1221 void WriterXmlFinalizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1222 {
1223  elem.applyToChildren(*this);
1224 }
1225 
1226 }
1227 
1228 /* 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
sal_Int32 setProperties(sal_Int32 nStyleId, const PropertyMap &rNewProps)
Definition: style.cxx:93
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
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