LibreOffice Module sdext (master)  1
pdfiprocessor.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 
21 #include <pdfiprocessor.hxx>
22 #include <xmlemitter.hxx>
23 #include <pdfihelper.hxx>
24 #include <imagecontainer.hxx>
25 #include <genericelements.hxx>
26 #include "style.hxx"
27 #include <treevisiting.hxx>
28 
29 #include <sal/log.hxx>
30 
31 #include <comphelper/sequence.hxx>
36 #include <vcl/svapp.hxx>
37 
38 using namespace com::sun::star;
39 
40 
41 namespace pdfi
42 {
43 
44  PDFIProcessor::PDFIProcessor( const uno::Reference< task::XStatusIndicator >& xStat ,
45  css::uno::Reference< css::uno::XComponentContext > const & xContext) :
46 
47  m_xContext(xContext),
48  prevCharWidth(0),
49  m_pDocument( ElementFactory::createDocumentElement() ),
50  m_pCurPage(nullptr),
51  m_pCurElement(nullptr),
52  m_nNextFontId( 1 ),
53  m_aIdToFont(),
54  m_aFontToId(),
55  m_aGCStack(),
56  m_nNextGCId( 1 ),
57  m_aIdToGC(),
58  m_aGCToId(),
59  m_aImages(),
60  m_nPages(0),
61  m_nNextZOrder( 1 ),
62  m_xStatusIndicator( xStat )
63 {
64  FontAttributes aDefFont;
65  aDefFont.familyName = "Helvetica";
66  aDefFont.isBold = false;
67  aDefFont.isItalic = false;
68  aDefFont.size = 10*PDFI_OUTDEV_RESOLUTION/72;
69  m_aIdToFont.insert({0, aDefFont});
70  m_aFontToId.insert({aDefFont, 0});
71 
72  GraphicsContext aDefGC;
73  m_aGCStack.push_back( aDefGC );
74  m_aGCToId.insert({aDefGC, 0});
75  m_aIdToGC.insert({0, aDefGC});
76 }
77 
78 void PDFIProcessor::setPageNum( sal_Int32 nPages )
79 {
80  m_nPages = nPages;
81 }
82 
83 
85 {
86  GraphicsContextStack::value_type const a(m_aGCStack.back());
87  m_aGCStack.push_back(a);
88 }
89 
91 {
92  m_aGCStack.pop_back();
93 }
94 
95 void PDFIProcessor::setFlatness( double value )
96 {
97  getCurrentContext().Flatness = value;
98 }
99 
100 void PDFIProcessor::setTransformation( const geometry::AffineMatrix2D& rMatrix )
101 {
104  rMatrix );
105 }
106 
107 void PDFIProcessor::setLineDash( const uno::Sequence<double>& dashes,
108  double /*start*/ )
109 {
110  // TODO(F2): factor in start offset
111  GraphicsContext& rContext( getCurrentContext() );
113 }
114 
116 {
117  getCurrentContext().LineJoin = nJoin;
118 }
119 
121 {
122  getCurrentContext().LineCap = nCap;
123 }
124 
126 {
127  SAL_WARN("sdext.pdfimport", "PDFIProcessor::setMiterLimit(): not supported by ODF");
128 }
129 
130 void PDFIProcessor::setLineWidth(double nWidth)
131 {
132  getCurrentContext().LineWidth = nWidth;
133 }
134 
135 void PDFIProcessor::setFillColor( const rendering::ARGBColor& rColor )
136 {
137  getCurrentContext().FillColor = rColor;
138 }
139 
140 void PDFIProcessor::setStrokeColor( const rendering::ARGBColor& rColor )
141 {
142  getCurrentContext().LineColor = rColor;
143 }
144 
146 {
147  FontAttributes aChangedFont( i_rFont );
149  // for text render modes, please see PDF reference manual
150  if (rGC.TextRenderMode == 1)
151  {
152  aChangedFont.isOutline = true;
153  }
154  else if (rGC.TextRenderMode == 2)
155  {
156  // tdf#81484: faux bold is represented as "stroke+fill" (while using the same color for both stroke and fill) in pdf.
157  // Convert to bold instead if the stroke color is the same as the fill color,
158  // otherwise it should be outline.
160  aChangedFont.isBold = true;
161  else
162  aChangedFont.isOutline = true;
163  }
164  FontToIdMap::const_iterator it = m_aFontToId.find( aChangedFont );
165  if( it != m_aFontToId.end() )
166  rGC.FontId = it->second;
167  else
168  {
169  m_aFontToId[ aChangedFont ] = m_nNextFontId;
170  m_aIdToFont[ m_nNextFontId ] = aChangedFont;
171  rGC.FontId = m_nNextFontId;
172  m_nNextFontId++;
173  }
174 }
175 
176 void PDFIProcessor::setTextRenderMode( sal_Int32 i_nMode )
177 {
179  rGC.TextRenderMode = i_nMode;
180  IdToFontMap::iterator it = m_aIdToFont.find( rGC.FontId );
181  if( it != m_aIdToFont.end() )
182  setFont( it->second );
183 }
184 
185 sal_Int32 PDFIProcessor::getFontId( const FontAttributes& rAttr ) const
186 {
187  const sal_Int32 nCurFont = getCurrentContext().FontId;
188  const_cast<PDFIProcessor*>(this)->setFont( rAttr );
189  const sal_Int32 nFont = getCurrentContext().FontId;
190  const_cast<PDFIProcessor*>(this)->getCurrentContext().FontId = nCurFont;
191 
192  return nFont;
193 }
194 
195 // line diagnose block - start
196 void PDFIProcessor::processGlyphLine()
197 {
198  if (m_GlyphsList.empty())
199  return;
200 
201  double spaceDetectBoundary = 0.0;
202 
203  // Try to find space glyph and its width
204  for (CharGlyph & i : m_GlyphsList)
205  {
206  OUString& glyph = i.getGlyph();
207 
208  sal_Unicode ch = '\0';
209  if (!glyph.isEmpty())
210  ch = glyph[0];
211 
212  if ((ch == 0x20) || (ch == 0xa0))
213  {
214  double spaceWidth = i.getWidth();
215  spaceDetectBoundary = spaceWidth * 0.5;
216  break;
217  }
218  }
219 
220  // If space glyph is not found, use average glyph width instead
221  if (spaceDetectBoundary == 0.0)
222  {
223  double avgGlyphWidth = 0.0;
224  for (const CharGlyph & i : m_GlyphsList)
225  avgGlyphWidth += i.getWidth();
226  avgGlyphWidth /= m_GlyphsList.size();
227  spaceDetectBoundary = avgGlyphWidth * 0.2;
228  }
229 
230  FrameElement* frame = ElementFactory::createFrameElement(
231  m_GlyphsList[0].getCurElement(),
232  getGCId(m_GlyphsList[0].getGC()));
233  frame->ZOrder = m_nNextZOrder++;
234  frame->IsForText = true;
235  frame->FontSize = getFont(m_GlyphsList[0].getGC().FontId).size;
236  ParagraphElement* para = ElementFactory::createParagraphElement(frame);
237 
238  for (size_t i = 0; i < m_GlyphsList.size(); i++)
239  {
240  bool prependSpace = false;
241  TextElement* text = ElementFactory::createTextElement(
242  para,
243  getGCId(m_GlyphsList[i].getGC()),
244  m_GlyphsList[i].getGC().FontId);
245  if (i == 0)
246  {
247  text->x = m_GlyphsList[0].getGC().Transformation.get(0, 2);
248  text->y = m_GlyphsList[0].getGC().Transformation.get(1, 2);
249  text->w = 0;
250  text->h = 0;
251  para->updateGeometryWith(text);
252  frame->updateGeometryWith(para);
253  }
254  else
255  {
256  double spaceSize = m_GlyphsList[i].getPrevSpaceWidth();
257  prependSpace = spaceSize > spaceDetectBoundary;
258  }
259  if (prependSpace)
260  text->Text.append(" ");
261  text->Text.append(m_GlyphsList[i].getGlyph());
262  }
263 
264  m_GlyphsList.clear();
265 }
266 
267 void PDFIProcessor::drawGlyphs( const OUString& rGlyphs,
268  const geometry::RealRectangle2D& rRect,
269  const geometry::Matrix2D& rFontMatrix,
270  double fontSize)
271 {
272  double ascent = getFont(getCurrentContext().FontId).ascent;
273 
274  basegfx::B2DHomMatrix fontMatrix(
275  rFontMatrix.m00, rFontMatrix.m01, 0.0,
276  rFontMatrix.m10, rFontMatrix.m11, 0.0);
277  fontMatrix.scale(fontSize, fontSize);
278 
279  basegfx::B2DHomMatrix totalTextMatrix1(fontMatrix);
280  basegfx::B2DHomMatrix totalTextMatrix2(fontMatrix);
281  totalTextMatrix1.translate(rRect.X1, rRect.Y1);
282  totalTextMatrix2.translate(rRect.X2, rRect.Y2);
283 
284  basegfx::B2DHomMatrix corrMatrix;
285  corrMatrix.scale(1.0, -1.0);
286  corrMatrix.translate(0.0, ascent);
287  totalTextMatrix1 = totalTextMatrix1 * corrMatrix;
288  totalTextMatrix2 = totalTextMatrix2 * corrMatrix;
289 
290  totalTextMatrix1 *= getCurrentContext().Transformation;
291  totalTextMatrix2 *= getCurrentContext().Transformation;
292 
293  basegfx::B2DHomMatrix invMatrix(totalTextMatrix1);
294  basegfx::B2DHomMatrix invPrevMatrix(prevTextMatrix);
295  invMatrix.invert();
296  invPrevMatrix.invert();
297  basegfx::B2DHomMatrix offsetMatrix1(totalTextMatrix1);
298  basegfx::B2DHomMatrix offsetMatrix2(totalTextMatrix2);
299  offsetMatrix1 *= invPrevMatrix;
300  offsetMatrix2 *= invMatrix;
301 
302  double charWidth = offsetMatrix2.get(0, 2);
303  double prevSpaceWidth = offsetMatrix1.get(0, 2) - prevCharWidth;
304 
305  if ((totalTextMatrix1.get(0, 0) != prevTextMatrix.get(0, 0)) ||
306  (totalTextMatrix1.get(0, 1) != prevTextMatrix.get(0, 1)) ||
307  (totalTextMatrix1.get(1, 0) != prevTextMatrix.get(1, 0)) ||
308  (totalTextMatrix1.get(1, 1) != prevTextMatrix.get(1, 1)) ||
309  (offsetMatrix1.get(0, 2) < 0.0) ||
310  (prevSpaceWidth > prevCharWidth * 1.3) ||
311  (!basegfx::fTools::equalZero(offsetMatrix1.get(1, 2), 0.0001)))
312  {
313  processGlyphLine();
314  }
315 
316  CharGlyph aGlyph(m_pCurElement, getCurrentContext(), charWidth, prevSpaceWidth, rGlyphs);
317  aGlyph.getGC().Transformation = totalTextMatrix1;
318  m_GlyphsList.push_back(aGlyph);
319 
320  prevCharWidth = charWidth;
321  prevTextMatrix = totalTextMatrix1;
322 }
323 
324 void PDFIProcessor::endText()
325 {
326  TextElement* pText = dynamic_cast<TextElement*>(m_pCurElement);
327  if( pText )
328  m_pCurElement = pText->Parent;
329 }
330 
331 void PDFIProcessor::setupImage(ImageId nImage)
332 {
333  const GraphicsContext& rGC(getCurrentContext());
334 
335  basegfx::B2DTuple aScale, aTranslation;
336  double fRotate, fShearX;
337  rGC.Transformation.decompose(aScale, aTranslation, fRotate, fShearX);
338 
339  const sal_Int32 nGCId = getGCId(rGC);
340  FrameElement* pFrame = ElementFactory::createFrameElement( m_pCurElement, nGCId );
341  ImageElement* pImageElement = ElementFactory::createImageElement( pFrame, nGCId, nImage );
342  pFrame->x = pImageElement->x = aTranslation.getX();
343  pFrame->y = pImageElement->y = aTranslation.getY();
344  pFrame->w = pImageElement->w = aScale.getX();
345  pFrame->h = pImageElement->h = aScale.getY();
346  pFrame->ZOrder = m_nNextZOrder++;
347 
348  // Poppler wrapper takes into account that vertical axes of PDF and ODF are opposite,
349  // and it flips matrix vertically (see poppler's GfxState::GfxState()).
350  // But image internal vertical axis is independent of PDF vertical axis direction,
351  // so arriving matrix is extra-flipped relative to image.
352  // We force vertical flip here to compensate that.
353  pFrame->MirrorVertical = true;
354 }
355 
356 void PDFIProcessor::drawMask(const uno::Sequence<beans::PropertyValue>& xBitmap,
357  bool /*bInvert*/ )
358 {
359  // TODO(F3): Handle mask and inversion
360  setupImage( m_aImages.addImage(xBitmap) );
361 }
362 
363 void PDFIProcessor::drawImage(const uno::Sequence<beans::PropertyValue>& xBitmap )
364 {
365  setupImage( m_aImages.addImage(xBitmap) );
366 }
367 
368 void PDFIProcessor::drawColorMaskedImage(const uno::Sequence<beans::PropertyValue>& xBitmap,
369  const uno::Sequence<uno::Any>& /*xMaskColors*/ )
370 {
371  // TODO(F3): Handle mask colors
372  setupImage( m_aImages.addImage(xBitmap) );
373 }
374 
375 void PDFIProcessor::drawMaskedImage(const uno::Sequence<beans::PropertyValue>& xBitmap,
376  const uno::Sequence<beans::PropertyValue>& /*xMask*/,
377  bool /*bInvertMask*/)
378 {
379  // TODO(F3): Handle mask and inversion
380  setupImage( m_aImages.addImage(xBitmap) );
381 }
382 
383 void PDFIProcessor::drawAlphaMaskedImage(const uno::Sequence<beans::PropertyValue>& xBitmap,
384  const uno::Sequence<beans::PropertyValue>& /*xMask*/)
385 {
386  // TODO(F3): Handle mask
387 
388  setupImage( m_aImages.addImage(xBitmap) );
389 
390 }
391 
392 void PDFIProcessor::strokePath( const uno::Reference< rendering::XPolyPolygon2D >& rPath )
393 {
396 
397  PolyPolyElement* pPoly = ElementFactory::createPolyPolyElement(
398  m_pCurElement,
399  getGCId(getCurrentContext()),
400  aPoly,
401  PATH_STROKE );
402  pPoly->updateGeometry();
403  pPoly->ZOrder = m_nNextZOrder++;
404 }
405 
406 void PDFIProcessor::fillPath( const uno::Reference< rendering::XPolyPolygon2D >& rPath )
407 {
410 
411  PolyPolyElement* pPoly = ElementFactory::createPolyPolyElement(
412  m_pCurElement,
413  getGCId(getCurrentContext()),
414  aPoly,
415  PATH_FILL );
416  pPoly->updateGeometry();
417  pPoly->ZOrder = m_nNextZOrder++;
418 }
419 
420 void PDFIProcessor::eoFillPath( const uno::Reference< rendering::XPolyPolygon2D >& rPath )
421 {
424 
425  PolyPolyElement* pPoly = ElementFactory::createPolyPolyElement(
426  m_pCurElement,
427  getGCId(getCurrentContext()),
428  aPoly,
429  PATH_EOFILL );
430  pPoly->updateGeometry();
431  pPoly->ZOrder = m_nNextZOrder++;
432 }
433 
434 void PDFIProcessor::intersectClip(const uno::Reference< rendering::XPolyPolygon2D >& rPath)
435 {
436  // TODO(F3): interpret fill mode
439  basegfx::B2DPolyPolygon aCurClip = getCurrentContext().Clip;
440 
441  if( aCurClip.count() ) // #i92985# adapted API from (..., false, false) to (..., true, false)
442  aNewClip = basegfx::utils::clipPolyPolygonOnPolyPolygon( aCurClip, aNewClip, true, false );
443 
444  getCurrentContext().Clip = aNewClip;
445 }
446 
447 void PDFIProcessor::intersectEoClip(const uno::Reference< rendering::XPolyPolygon2D >& rPath)
448 {
449  // TODO(F3): interpret fill mode
452  basegfx::B2DPolyPolygon aCurClip = getCurrentContext().Clip;
453 
454  if( aCurClip.count() ) // #i92985# adapted API from (..., false, false) to (..., true, false)
455  aNewClip = basegfx::utils::clipPolyPolygonOnPolyPolygon( aCurClip, aNewClip, true, false );
456 
457  getCurrentContext().Clip = aNewClip;
458 }
459 
460 void PDFIProcessor::hyperLink( const geometry::RealRectangle2D& rBounds,
461  const OUString& rURI )
462 {
463  if( !rURI.isEmpty() )
464  {
465  HyperlinkElement* pLink = ElementFactory::createHyperlinkElement(
466  &m_pCurPage->Hyperlinks,
467  rURI );
468  pLink->x = rBounds.X1;
469  pLink->y = rBounds.Y1;
470  pLink->w = rBounds.X2-rBounds.X1;
471  pLink->h = rBounds.Y2-rBounds.Y1;
472  }
473 }
474 
475 const FontAttributes& PDFIProcessor::getFont( sal_Int32 nFontId ) const
476 {
477  IdToFontMap::const_iterator it = m_aIdToFont.find( nFontId );
478  if( it == m_aIdToFont.end() )
479  it = m_aIdToFont.find( 0 );
480  return it->second;
481 }
482 
483 sal_Int32 PDFIProcessor::getGCId( const GraphicsContext& rGC )
484 {
485  sal_Int32 nGCId = 0;
486  auto it = m_aGCToId.find( rGC );
487  if( it != m_aGCToId.end() )
488  nGCId = it->second;
489  else
490  {
491  m_aGCToId.insert({rGC, m_nNextGCId});
492  m_aIdToGC.insert({m_nNextGCId, rGC});
493  nGCId = m_nNextGCId;
494  m_nNextGCId++;
495  }
496 
497  return nGCId;
498 }
499 
500 const GraphicsContext& PDFIProcessor::getGraphicsContext( sal_Int32 nGCId ) const
501 {
502  auto it = m_aIdToGC.find( nGCId );
503  if( it == m_aIdToGC.end() )
504  it = m_aIdToGC.find( 0 );
505  return it->second;
506 }
507 
508 void PDFIProcessor::endPage()
509 {
510  processGlyphLine(); // draw last line
511  if( m_xStatusIndicator.is()
512  && m_pCurPage
513  && m_pCurPage->PageNumber == m_nPages
514  )
515  m_xStatusIndicator->end();
516 }
517 
518 void PDFIProcessor::startPage( const geometry::RealSize2D& rSize )
519 {
520  // initial clip is to page bounds
523  basegfx::B2DRange( 0, 0, rSize.Width, rSize.Height )));
524 
525  sal_Int32 nNextPageNr = m_pCurPage ? m_pCurPage->PageNumber+1 : 1;
526  if( m_xStatusIndicator.is() )
527  {
528  if( nNextPageNr == 1 )
529  startIndicator( " " );
530  m_xStatusIndicator->setValue( nNextPageNr );
531  }
532  m_pCurPage = ElementFactory::createPageElement(m_pDocument.get(), nNextPageNr);
533  m_pCurElement = m_pCurPage;
534  m_pCurPage->w = rSize.Width;
535  m_pCurPage->h = rSize.Height;
536  m_nNextZOrder = 1;
537 
538 
539 }
540 
541 void PDFIProcessor::emit( XmlEmitter& rEmitter,
542  const TreeVisitorFactory& rVisitorFactory )
543 {
544 #if OSL_DEBUG_LEVEL > 0
545  m_pDocument->emitStructure( 0 );
546 #endif
547 
548  ElementTreeVisitorSharedPtr optimizingVisitor(
549  rVisitorFactory.createOptimizingVisitor(*this));
550  // FIXME: localization
551  startIndicator( " " );
552  m_pDocument->visitedBy( *optimizingVisitor, std::list<std::unique_ptr<Element>>::const_iterator());
553 
554 #if OSL_DEBUG_LEVEL > 0
555  m_pDocument->emitStructure( 0 );
556 #endif
557 
558  // get styles
559  StyleContainer aStyles;
560  ElementTreeVisitorSharedPtr finalizingVisitor(
561  rVisitorFactory.createStyleCollectingVisitor(aStyles,*this));
562  // FIXME: localization
563 
564  m_pDocument->visitedBy( *finalizingVisitor, std::list<std::unique_ptr<Element>>::const_iterator() );
565 
566  EmitContext aContext( rEmitter, aStyles, m_aImages, *this, m_xStatusIndicator, m_xContext );
567  ElementTreeVisitorSharedPtr aEmittingVisitor(
568  rVisitorFactory.createEmittingVisitor(aContext));
569 
570  PropertyMap aProps;
571  // document prolog
572  #define OASIS_STR "urn:oasis:names:tc:opendocument:xmlns:"
573  aProps[ "xmlns:office" ] = OASIS_STR "office:1.0" ;
574  aProps[ "xmlns:style" ] = OASIS_STR "style:1.0" ;
575  aProps[ "xmlns:text" ] = OASIS_STR "text:1.0" ;
576  aProps[ "xmlns:svg" ] = OASIS_STR "svg-compatible:1.0" ;
577  aProps[ "xmlns:table" ] = OASIS_STR "table:1.0" ;
578  aProps[ "xmlns:draw" ] = OASIS_STR "drawing:1.0" ;
579  aProps[ "xmlns:fo" ] = OASIS_STR "xsl-fo-compatible:1.0" ;
580  aProps[ "xmlns:xlink"] = "http://www.w3.org/1999/xlink";
581  aProps[ "xmlns:dc"] = "http://purl.org/dc/elements/1.1/";
582  aProps[ "xmlns:number"] = OASIS_STR "datastyle:1.0" ;
583  aProps[ "xmlns:presentation"] = OASIS_STR "presentation:1.0" ;
584  aProps[ "xmlns:math"] = "http://www.w3.org/1998/Math/MathML";
585  aProps[ "xmlns:form"] = OASIS_STR "form:1.0" ;
586  aProps[ "xmlns:script"] = OASIS_STR "script:1.0" ;
587  aProps[ "xmlns:dom"] = "http://www.w3.org/2001/xml-events";
588  aProps[ "xmlns:xforms"] = "http://www.w3.org/2002/xforms";
589  aProps[ "xmlns:xsd"] = "http://www.w3.org/2001/XMLSchema";
590  aProps[ "xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance";
591  aProps[ "office:version" ] = "1.0";
592 
593  aContext.rEmitter.beginTag( "office:document", aProps );
594 
595  // emit style list
596  aStyles.emit( aContext, *aEmittingVisitor );
597 
598  m_pDocument->visitedBy( *aEmittingVisitor, std::list<std::unique_ptr<Element>>::const_iterator() );
599  aContext.rEmitter.endTag( "office:document" );
600  endIndicator();
601 }
602 
603 void PDFIProcessor::startIndicator( const OUString& rText )
604 {
605  sal_Int32 nElements = m_nPages;
606  if( !m_xStatusIndicator.is() )
607  return;
608 
609  sal_Int32 nLength = rText.getLength();
610  OUStringBuffer aStr( nLength*2 );
611  const sal_Unicode* pText = rText.getStr();
612  for( int i = 0; i < nLength; i++ )
613  {
614  if( nLength-i > 1&&
615  pText[i] == '%' &&
616  pText[i+1] == 'd'
617  )
618  {
619  aStr.append( nElements );
620  i++;
621  }
622  else
623  aStr.append( pText[i] );
624  }
625  m_xStatusIndicator->start( aStr.makeStringAndClear(), nElements );
626 }
627 
628 void PDFIProcessor::endIndicator()
629 {
630  if( m_xStatusIndicator.is() )
631  m_xStatusIndicator->end();
632 }
633 
634 static bool lr_tb_sort( std::unique_ptr<Element> const & pLeft, std::unique_ptr<Element> const & pRight )
635 {
636  // Ensure irreflexivity (which could be compromised if h or w is negative):
637  if (pLeft == pRight)
638  return false;
639 
640  // first: top-bottom sorting
641 
642  // Note: allow for 10% overlap on text lines since text lines are usually
643  // of the same order as font height whereas the real paint area
644  // of text is usually smaller
645  double fudge_factor_left = 0.0, fudge_factor_right = 0.0;
646  if( dynamic_cast< TextElement* >(pLeft.get()) )
647  fudge_factor_left = 0.1;
648  if (dynamic_cast< TextElement* >(pRight.get()))
649  fudge_factor_right = 0.1;
650 
651  // Allow negative height
652  double lower_boundary_left = pLeft->y + std::max(pLeft->h, 0.0) - fabs(pLeft->h) * fudge_factor_left;
653  double lower_boundary_right = pRight->y + std::max(pRight->h, 0.0) - fabs(pRight->h) * fudge_factor_right;
654  double upper_boundary_left = pLeft->y + std::min(pLeft->h, 0.0);
655  double upper_boundary_right = pRight->y + std::min(pRight->h, 0.0);
656  // if left's lower boundary is above right's upper boundary
657  // then left is smaller
658  if( lower_boundary_left < upper_boundary_right )
659  return true;
660  // if right's lower boundary is above left's upper boundary
661  // then left is definitely not smaller
662  if( lower_boundary_right < upper_boundary_left )
663  return false;
664 
665  // Allow negative width
666  double left_boundary_left = pLeft->y + std::min(pLeft->w, 0.0);
667  double left_boundary_right = pRight->y + std::min(pRight->w, 0.0);
668  double right_boundary_left = pLeft->y + std::max(pLeft->w, 0.0);
669  double right_boundary_right = pRight->y + std::max(pRight->w, 0.0);
670  // by now we have established that left and right are inside
671  // a "line", that is they have vertical overlap
672  // second: left-right sorting
673  // if left's right boundary is left to right's left boundary
674  // then left is smaller
675  if( right_boundary_left < left_boundary_right )
676  return true;
677  // if right's right boundary is left to left's left boundary
678  // then left is definitely not smaller
679  if( right_boundary_right < left_boundary_left )
680  return false;
681 
682  // here we have established vertical and horizontal overlap
683  // so sort left first, top second
684  if( pLeft->x < pRight->x )
685  return true;
686  if( pRight->x < pLeft->x )
687  return false;
688  if( pLeft->y < pRight->y )
689  return true;
690 
691  return false;
692 }
693 
694 void PDFIProcessor::sortElements(Element* pEle)
695 {
696  if( pEle->Children.empty() )
697  return;
698 
699  // sort method from std::list is equivalent to stable_sort
700  // See S Meyers, Effective STL
701  pEle->Children.sort(lr_tb_sort);
702 }
703 
704 // helper method: get a mirrored string
705 OUString PDFIProcessor::mirrorString( const OUString& i_rString )
706 {
707  const sal_Int32 nLen = i_rString.getLength();
708  OUStringBuffer aMirror( nLen );
709 
710  sal_Int32 i = 0;
711  while(i < nLen)
712  {
713  // read one code point
714  const sal_uInt32 nCodePoint = i_rString.iterateCodePoints( &i );
715 
716  // and append it mirrored
717  aMirror.appendUtf32( GetMirroredChar(nCodePoint) );
718  }
719  return aMirror.makeStringAndClear();
720 }
721 
722 }
723 
724 /* 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
bool equalZero(const T &rfVal)
signed char sal_Int8
#define PDFI_OUTDEV_RESOLUTION
Definition: pdfihelper.hxx:38
virtual void beginTag(const char *pTag, const PropertyMap &rProperties)=0
Open up a tag with the given properties.
Main entry from the parser.
sal_UCS4 GetMirroredChar(sal_UCS4 nChar)
virtual void setFillColor(const css::rendering::ARGBColor &rColor) override
double get(sal_uInt16 nRow, sal_uInt16 nColumn) const
B2DPolyPolygon clipPolyPolygonOnPolyPolygon(const B2DPolyPolygon &rCandidate, const B2DPolyPolygon &rClip, bool bInside, bool bStroke)
virtual void pushState() override
sal_uInt16 sal_Unicode
DstType sequenceToContainer(const css::uno::Sequence< SrcType > &i_Sequence)
exports com.sun.star. frame
css::rendering::ARGBColor FillColor
Definition: pdfihelper.hxx:97
sal_Int32 nElements
std::vector< double > DashArray
Definition: pdfihelper.hxx:104
virtual void setLineWidth(double) override
sal_Int32 ImageId
Definition: pdfihelper.hxx:45
def getCurrentContext()
exports com.sun.star. text
virtual std::shared_ptr< ElementTreeVisitor > createOptimizingVisitor(PDFIProcessor &) const =0
Create visitor that combines tree nodes.
std::list< std::unique_ptr< Element > > Children
int i
virtual void setStrokeColor(const css::rendering::ARGBColor &rColor) override
uno_Any a
bool decompose(B2DTuple &rScale, B2DTuple &rTranslate, double &rRotate, double &rShearX) const
basegfx::B2DHomMatrix Transformation
Definition: pdfihelper.hxx:107
virtual void setPageNum(sal_Int32 nNumPages) override
Total number of pages for upcoming document.
B2DPolygon createPolygonFromRect(const B2DRectangle &rRect, double fRadiusX, double fRadiusY)
Output interface to ODF.
Definition: xmlemitter.hxx:32
void scale(double fX, double fY)
void transform(const basegfx::B2DHomMatrix &rMatrix)
::basegfx::B2DPolyPolygon b2DPolyPolygonFromXPolyPolygon2D(const uno::Reference< rendering::XPolyPolygon2D > &xPoly)
virtual void setMiterLimit(double) override
Tree manipulation factory.
virtual void setLineJoin(sal_Int8) override
virtual void setLineCap(sal_Int8) override
std::shared_ptr< ElementTreeVisitor > ElementTreeVisitorSharedPtr
GraphicsContext & getGC()
void updateGeometryWith(const Element *pMergeFrom)
Union element geometry with given element.
sal_uInt32 count() const
virtual void setTransformation(const css::geometry::AffineMatrix2D &rMatrix) override
SvBaseLink * pLink
GraphicsContext & getCurrentContext()
#define OASIS_STR
virtual void setFlatness(double) override
void emit(EmitContext &rContext, ElementTreeVisitor &rContainedElemVisitor)
Definition: style.cxx:209
void translate(double fX, double fY)
XmlEmitter & rEmitter
virtual std::shared_ptr< ElementTreeVisitor > createStyleCollectingVisitor(StyleContainer &, PDFIProcessor &) const =0
Create visitor that prepares style info.
GraphicsContextStack m_aGCStack
virtual std::shared_ptr< ElementTreeVisitor > createEmittingVisitor(EmitContext &) const =0
Create visitor that emits tree to an output target.
virtual void popState() override
#define SAL_WARN(area, stream)
double getX() const
virtual void setLineDash(const css::uno::Sequence< double > &dashes, double start) override
::basegfx::B2DHomMatrix & homMatrixFromAffineMatrix(::basegfx::B2DHomMatrix &output, const geometry::AffineMatrix2D &input)
sal_Int32 nLength
virtual void setFont(const FontAttributes &rFont) override
static bool lr_tb_sort(std::unique_ptr< Element > const &pLeft, std::unique_ptr< Element > const &pRight)
aStr
OUStringBuffer Text
virtual void endTag(const char *pTag)=0
Close previously opened tag.
const uno::Reference< uno::XComponentContext > m_xContext
Definition: wrapper.cxx:150