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 
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  aFontProps[ "fo:font-family" ] = rFont.familyName;
903  // bold
904  if( rFont.isBold )
905  {
906  aFontProps[ "fo:font-weight" ] = "bold";
907  aFontProps[ "fo:font-weight-asian" ] = "bold";
908  aFontProps[ "fo:font-weight-complex" ] = "bold";
909  }
910  // italic
911  if( rFont.isItalic )
912  {
913  aFontProps[ "fo:font-style" ] = "italic";
914  aFontProps[ "fo:font-style-asian" ] = "italic";
915  aFontProps[ "fo:font-style-complex" ] = "italic";
916  }
917  // underline
918  if( rFont.isUnderline )
919  {
920  aFontProps[ "style:text-underline-style" ] = "solid";
921  aFontProps[ "style:text-underline-width" ] = "auto";
922  aFontProps[ "style:text-underline-color" ] = "font-color";
923  }
924  // outline
925  if( rFont.isOutline )
926  {
927  aFontProps[ "style:text-outline" ] = "true";
928  }
929  // size
930  OUString aFSize = OUString::number( rFont.size*72/PDFI_OUTDEV_RESOLUTION ) + "pt";
931  aFontProps[ "fo:font-size" ] = aFSize;
932  aFontProps[ "style:font-size-asian" ] = aFSize;
933  aFontProps[ "style:font-size-complex" ] = aFSize;
934  // color
936  aFontProps[ "fo:color" ] = getColorString( rFont.isOutline ? rGC.LineColor : rGC.FillColor );
937 
938  StyleContainer::Style aStyle( "style:style", aProps );
939  StyleContainer::Style aSubStyle( "style:text-properties", aFontProps );
940  aStyle.SubStyles.push_back( &aSubStyle );
941  elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
942 }
943 
944 void WriterXmlFinalizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
945 {
946  PropertyMap aParaProps;
947 
948  if( elem.Parent )
949  {
950  // check for center alignment
951  // criterion: paragraph is small relative to parent and distributed around its center
952  double p_x = elem.Parent->x;
953  double p_w = elem.Parent->w;
954 
955  PageElement* pPage = dynamic_cast<PageElement*>(elem.Parent);
956  if( pPage )
957  {
958  p_x += pPage->LeftMargin;
959  p_w -= pPage->LeftMargin+pPage->RightMargin;
960  }
961  bool bIsCenter = false;
962  if( elem.w < ( p_w/2) )
963  {
964  double delta = elem.w/4;
965  // allow very small paragraphs to deviate a little more
966  // relative to parent's center
967  if( elem.w < p_w/8 )
968  delta = elem.w;
969  if( fabs( elem.x+elem.w/2 - ( p_x+ p_w/2) ) < delta ||
970  (pPage && fabs( elem.x+elem.w/2 - (pPage->x + pPage->w/2) ) < delta) )
971  {
972  bIsCenter = true;
973  aParaProps[ "fo:text-align" ] = "center";
974  }
975  }
976  if( ! bIsCenter && elem.x > p_x + p_w/10 )
977  {
978  // indent
979  OUStringBuffer aBuf( 32 );
980  aBuf.append( convPx2mm( elem.x - p_x ) );
981  aBuf.append( "mm" );
982  aParaProps[ "fo:margin-left" ] = aBuf.makeStringAndClear();
983  }
984 
985  // check whether to leave some space to next paragraph
986  // find whether there is a next paragraph
987  auto it = rParentIt;
988  const ParagraphElement* pNextPara = nullptr;
989  while( ++it != elem.Parent->Children.end() && ! pNextPara )
990  pNextPara = dynamic_cast< const ParagraphElement* >(it->get());
991  if( pNextPara )
992  {
993  if( pNextPara->y - (elem.y+elem.h) > convmm2Px( 10 ) )
994  {
995  OUStringBuffer aBuf( 32 );
996  aBuf.append( convPx2mm( pNextPara->y - (elem.y+elem.h) ) );
997  aBuf.append( "mm" );
998  aParaProps[ "fo:margin-bottom" ] = aBuf.makeStringAndClear();
999  }
1000  }
1001  }
1002 
1003  if( ! aParaProps.empty() )
1004  {
1005  PropertyMap aProps;
1006  aProps[ "style:family" ] = "paragraph";
1007  StyleContainer::Style aStyle( "style:style", aProps );
1008  StyleContainer::Style aSubStyle( "style:paragraph-properties", aParaProps );
1009  aStyle.SubStyles.push_back( &aSubStyle );
1010  elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1011  }
1012 
1013  elem.applyToChildren(*this);
1014 }
1015 
1016 void WriterXmlFinalizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
1017 {
1018  PropertyMap aProps;
1019  aProps[ "style:family" ] = "graphic";
1020 
1021  PropertyMap aGCProps;
1022 
1023  aGCProps[ "draw:stroke" ] = "none";
1024  aGCProps[ "draw:fill" ] = "none";
1025  aGCProps[ "draw:auto-grow-height" ] = "true";
1026  aGCProps[ "draw:auto-grow-width" ] = "true";
1027  aGCProps[ "draw:textarea-horizontal-align" ] = "left";
1028  aGCProps[ "draw:textarea-vertical-align" ] = "top";
1029  aGCProps[ "fo:min-height"] = "0cm";
1030  aGCProps[ "fo:min-width"] = "0cm";
1031  aGCProps[ "fo:padding-top" ] = "0cm";
1032  aGCProps[ "fo:padding-left" ] = "0cm";
1033  aGCProps[ "fo:padding-right" ] = "0cm";
1034  aGCProps[ "fo:padding-bottom" ] = "0cm";
1035 
1036  StyleContainer::Style aStyle( "style:style", aProps );
1037  StyleContainer::Style aSubStyle( "style:graphic-properties", aGCProps );
1038  aStyle.SubStyles.push_back( &aSubStyle );
1039 
1040  elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1041  elem.applyToChildren(*this);
1042 }
1043 
1044 void WriterXmlFinalizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1045 {
1046 }
1047 
1049  StyleContainer& rStyles,
1050  const OUString& rMasterPageName )
1051 {
1052  PropertyMap aProps;
1053  if( rElem.StyleId != -1 )
1054  {
1055  const PropertyMap* pProps = rStyles.getProperties( rElem.StyleId );
1056  if( pProps )
1057  aProps = *pProps;
1058  }
1059 
1060  aProps[ "style:family" ] = "paragraph";
1061  aProps[ "style:master-page-name" ] = rMasterPageName;
1062 
1063  if( rElem.StyleId != -1 )
1064  rElem.StyleId = rStyles.setProperties( rElem.StyleId, aProps );
1065  else
1066  {
1067  StyleContainer::Style aStyle( "style:style", aProps );
1068  rElem.StyleId = rStyles.getStyleId( aStyle );
1069  }
1070 }
1071 
1072 void WriterXmlFinalizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1073 {
1074  if( m_rProcessor.getStatusIndicator().is() )
1075  m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
1076 
1077  // transform from pixel to mm
1078  double page_width = convPx2mm( elem.w ), page_height = convPx2mm( elem.h );
1079 
1080  // calculate page margins out of the relevant children (paragraphs)
1081  elem.TopMargin = elem.h;
1082  elem.BottomMargin = 0;
1083  elem.LeftMargin = elem.w;
1084  elem.RightMargin = 0;
1085  // first element should be a paragraph
1086  ParagraphElement* pFirstPara = nullptr;
1087  for( const auto& rxChild : elem.Children )
1088  {
1089  if( dynamic_cast<ParagraphElement*>( rxChild.get() ) )
1090  {
1091  if( rxChild->x < elem.LeftMargin )
1092  elem.LeftMargin = rxChild->x;
1093  if( rxChild->y < elem.TopMargin )
1094  elem.TopMargin = rxChild->y;
1095  if( rxChild->x + rxChild->w > elem.w - elem.RightMargin )
1096  elem.RightMargin = elem.w - (rxChild->x + rxChild->w);
1097  if( rxChild->y + rxChild->h > elem.h - elem.BottomMargin )
1098  elem.BottomMargin = elem.h - (rxChild->y + rxChild->h);
1099  if( ! pFirstPara )
1100  pFirstPara = dynamic_cast<ParagraphElement*>( rxChild.get() );
1101  }
1102  }
1103  if( elem.HeaderElement && elem.HeaderElement->y < elem.TopMargin )
1104  elem.TopMargin = elem.HeaderElement->y;
1105  if( elem.FooterElement && elem.FooterElement->y+elem.FooterElement->h > elem.h - elem.BottomMargin )
1106  elem.BottomMargin = elem.h - (elem.FooterElement->y + elem.FooterElement->h);
1107 
1108  // transform margins to mm
1109  double left_margin = convPx2mm( elem.LeftMargin );
1110  double right_margin = convPx2mm( elem.RightMargin );
1111  double top_margin = convPx2mm( elem.TopMargin );
1112  double bottom_margin = convPx2mm( elem.BottomMargin );
1113  if( ! pFirstPara )
1114  {
1115  // use default page margins
1116  left_margin = 10;
1117  right_margin = 10;
1118  top_margin = 10;
1119  bottom_margin = 10;
1120  }
1121 
1122  // round left/top margin to nearest mm
1123  left_margin = rtl_math_round( left_margin, 0, rtl_math_RoundingMode_Floor );
1124  top_margin = rtl_math_round( top_margin, 0, rtl_math_RoundingMode_Floor );
1125  // round (fuzzy) right/bottom margin to nearest cm
1126  right_margin = rtl_math_round( right_margin, right_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1127  bottom_margin = rtl_math_round( bottom_margin, bottom_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1128 
1129  // set reasonable default in case of way too large margins
1130  // e.g. no paragraph case
1131  if( left_margin > page_width/2.0 - 10 )
1132  left_margin = 10;
1133  if( right_margin > page_width/2.0 - 10 )
1134  right_margin = 10;
1135  if( top_margin > page_height/2.0 - 10 )
1136  top_margin = 10;
1137  if( bottom_margin > page_height/2.0 - 10 )
1138  bottom_margin = 10;
1139 
1140  // catch the weird cases
1141  if( left_margin < 0 )
1142  left_margin = 0;
1143  if( right_margin < 0 )
1144  right_margin = 0;
1145  if( top_margin < 0 )
1146  top_margin = 0;
1147  if( bottom_margin < 0 )
1148  bottom_margin = 0;
1149 
1150  // widely differing margins are unlikely to be correct
1151  if( right_margin > left_margin*1.5 )
1152  right_margin = left_margin;
1153 
1154  elem.LeftMargin = convmm2Px( left_margin );
1155  elem.RightMargin = convmm2Px( right_margin );
1156  elem.TopMargin = convmm2Px( top_margin );
1157  elem.BottomMargin = convmm2Px( bottom_margin );
1158 
1159  // get styles for paragraphs
1160  PropertyMap aPageProps;
1161  PropertyMap aPageLayoutProps;
1162  aPageLayoutProps[ "fo:page-width" ] = unitMMString( page_width );
1163  aPageLayoutProps[ "fo:page-height" ] = unitMMString( page_height );
1164  aPageLayoutProps[ "style:print-orientation" ]
1165  = elem.w < elem.h ? std::u16string_view(u"portrait") : std::u16string_view(u"landscape");
1166  aPageLayoutProps[ "fo:margin-top" ] = unitMMString( top_margin );
1167  aPageLayoutProps[ "fo:margin-bottom" ] = unitMMString( bottom_margin );
1168  aPageLayoutProps[ "fo:margin-left" ] = unitMMString( left_margin );
1169  aPageLayoutProps[ "fo:margin-right" ] = unitMMString( right_margin );
1170  aPageLayoutProps[ "style:writing-mode" ]= "lr-tb";
1171 
1172  StyleContainer::Style aStyle( "style:page-layout", aPageProps);
1173  StyleContainer::Style aSubStyle( "style:page-layout-properties", aPageLayoutProps);
1174  aStyle.SubStyles.push_back(&aSubStyle);
1175  sal_Int32 nPageStyle = m_rStyleContainer.impl_getStyleId( aStyle, false );
1176 
1177  // create master page
1178  OUString aMasterPageLayoutName = m_rStyleContainer.getStyleName( nPageStyle );
1179  aPageProps[ "style:page-layout-name" ] = aMasterPageLayoutName;
1180  StyleContainer::Style aMPStyle( "style:master-page", aPageProps );
1181  StyleContainer::Style aHeaderStyle( "style:header", PropertyMap() );
1182  StyleContainer::Style aFooterStyle( "style:footer", PropertyMap() );
1183  if( elem.HeaderElement )
1184  {
1185  elem.HeaderElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1186  aHeaderStyle.ContainedElement = elem.HeaderElement.get();
1187  aMPStyle.SubStyles.push_back( &aHeaderStyle );
1188  }
1189  if( elem.FooterElement )
1190  {
1191  elem.FooterElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1192  aFooterStyle.ContainedElement = elem.FooterElement.get();
1193  aMPStyle.SubStyles.push_back( &aFooterStyle );
1194  }
1195  elem.StyleId = m_rStyleContainer.impl_getStyleId( aMPStyle,false );
1196 
1197 
1198  OUString aMasterPageName = m_rStyleContainer.getStyleName( elem.StyleId );
1199 
1200  // create styles for children
1201  elem.applyToChildren(*this);
1202 
1203  // no paragraph or other elements before the first paragraph
1204  if( ! pFirstPara )
1205  {
1206  pFirstPara = ElementFactory::createParagraphElement( nullptr );
1207  pFirstPara->Parent = &elem;
1208  elem.Children.push_front( std::unique_ptr<Element>(pFirstPara) );
1209  }
1210  setFirstOnPage(*pFirstPara, m_rStyleContainer, aMasterPageName);
1211 }
1212 
1213 void WriterXmlFinalizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1214 {
1215  elem.applyToChildren(*this);
1216 }
1217 
1218 }
1219 
1220 /* 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
StyleContainer & m_rStyleContainer
void setX(double fX)
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:45
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
double getX() const
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:117
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:44
double getY() const
def point()
const FontAttributes & getFont(sal_Int32 nFontId) const
sal_uInt16 sal_Unicode
B2DPolygon const & getB2DPolygon(sal_uInt32 nIndex) const
basegfx::B2DPolyPolygon Clip
Definition: pdfihelper.hxx:108
const BorderLinePrimitive2D *pCandidateB assert(pCandidateA)
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)
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
void setY(double fY)
SvBaseLink * pLink
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:152
static void sortElements(Element *pElement)
XmlEmitter & rEmitter
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