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