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 <genericelements.hxx>
22 #include <pdfiprocessor.hxx>
23 #include <pdfihelper.hxx>
24 
25 #include <com/sun/star/i18n/BreakIterator.hpp>
26 #include <com/sun/star/i18n/ScriptType.hpp>
27 #include <basegfx/polygon/b2dpolypolygontools.hxx>
28 #include <basegfx/range/b2drange.hxx>
29 #include <sal/log.hxx>
30 
31 namespace pdfi
32 {
33 
~Element()34 Element::~Element()
35 {
36 }
37 
applyToChildren(ElementTreeVisitor & rVisitor)38 void Element::applyToChildren( ElementTreeVisitor& rVisitor )
39 {
40     for( auto it = Children.begin(); it != Children.end(); ++it )
41         (*it)->visitedBy( rVisitor, it );
42 }
43 
setParent(std::list<std::unique_ptr<Element>>::iterator const & el,Element * pNewParent)44 void Element::setParent( std::list<std::unique_ptr<Element>>::iterator const & el, Element* pNewParent )
45 {
46     if( pNewParent )
47     {
48         pNewParent->Children.splice( pNewParent->Children.end(), (*el)->Parent->Children, el );
49         (*el)->Parent = pNewParent;
50     }
51 }
52 
updateGeometryWith(const Element * pMergeFrom)53 void Element::updateGeometryWith( const Element* pMergeFrom )
54 {
55     if( w == 0 && h == 0 )
56     {
57         x = pMergeFrom->x;
58         y = pMergeFrom->y;
59         w = pMergeFrom->w;
60         h = pMergeFrom->h;
61     }
62     else
63     {
64         if( pMergeFrom->x < x )
65         {
66             w += x - pMergeFrom->x;
67             x = pMergeFrom->x;
68         }
69         if( pMergeFrom->x+pMergeFrom->w > x+w )
70             w = pMergeFrom->w+pMergeFrom->x - x;
71         if( pMergeFrom->y < y )
72         {
73             h += y - pMergeFrom->y;
74             y = pMergeFrom->y;
75         }
76         if( pMergeFrom->y+pMergeFrom->h > y+h )
77             h = pMergeFrom->h+pMergeFrom->y - y;
78     }
79 }
80 
81 
82 #if OSL_DEBUG_LEVEL > 0
83 #include <typeinfo>
emitStructure(int nLevel)84 void Element::emitStructure( int nLevel)
85 {
86     SAL_INFO( "sdext", std::string(nLevel, ' ') << "<" << typeid( *this ).name() << " " << this << "> ("
87                 << std::setprecision(1) << x << "," << y << ")+(" << w << "x" << h << ")" );
88     for (auto const& child : Children)
89         child->emitStructure(nLevel+1);
90     SAL_INFO( "sdext", std::string(nLevel, ' ') << "</" << typeid( *this ).name() << ">"  );
91 }
92 #endif
93 
visitedBy(ElementTreeVisitor & visitor,const std::list<std::unique_ptr<Element>>::const_iterator &)94 void ListElement::visitedBy( ElementTreeVisitor& visitor, const std::list< std::unique_ptr<Element> >::const_iterator& )
95 {
96     // this is only an inner node
97     applyToChildren(visitor);
98 }
99 
visitedBy(ElementTreeVisitor & rVisitor,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)100 void HyperlinkElement::visitedBy( ElementTreeVisitor&                          rVisitor,
101                                   const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
102 {
103     rVisitor.visit(*this,rParentIt);
104 }
105 
visitedBy(ElementTreeVisitor & rVisitor,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)106 void TextElement::visitedBy( ElementTreeVisitor&                          rVisitor,
107                              const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
108 {
109     rVisitor.visit(*this,rParentIt);
110 }
111 
visitedBy(ElementTreeVisitor & rVisitor,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)112 void FrameElement::visitedBy( ElementTreeVisitor&                          rVisitor,
113                               const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
114 {
115     rVisitor.visit(*this,rParentIt);
116 }
117 
visitedBy(ElementTreeVisitor & rVisitor,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)118 void ImageElement::visitedBy( ElementTreeVisitor&                          rVisitor,
119                               const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt)
120 {
121     rVisitor.visit( *this, rParentIt);
122 }
123 
PolyPolyElement(Element * pParent,sal_Int32 nGCId,const basegfx::B2DPolyPolygon & rPolyPoly,sal_Int8 nAction,ImageId nFillImage,double nTileWidth,double nTileHeight)124 PolyPolyElement::PolyPolyElement( Element*                       pParent,
125                                   sal_Int32                      nGCId,
126                                   const basegfx::B2DPolyPolygon& rPolyPoly,
127                                   sal_Int8                       nAction,
128                                   ImageId                        nFillImage,
129                                   double                         nTileWidth,
130                                   double                         nTileHeight )
131     : DrawElement( pParent, nGCId ),
132       PolyPoly( rPolyPoly ),
133       Action( nAction ),
134       FillImage( nFillImage ),
135       TileWidth( nTileWidth ),
136       TileHeight( nTileHeight )
137 {
138 }
139 
updateGeometry()140 void PolyPolyElement::updateGeometry()
141 {
142     basegfx::B2DRange aRange;
143     if( PolyPoly.areControlPointsUsed() )
144         aRange = basegfx::utils::getRange( basegfx::utils::adaptiveSubdivideByAngle( PolyPoly ) );
145     else
146         aRange = basegfx::utils::getRange( PolyPoly );
147     x = aRange.getMinX();
148     y = aRange.getMinY();
149     w = aRange.getWidth();
150     h = aRange.getHeight();
151 
152     // fdo#32330 - non-closed paths will not show up filled in LibO
153     if( Action & (PATH_FILL | PATH_EOFILL) )
154         PolyPoly.setClosed(true);
155 }
156 
visitedBy(ElementTreeVisitor & rVisitor,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)157 void PolyPolyElement::visitedBy( ElementTreeVisitor&                          rVisitor,
158                                  const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt)
159 {
160     rVisitor.visit( *this, rParentIt);
161 }
162 
163 #if OSL_DEBUG_LEVEL > 0
emitStructure(int nLevel)164 void PolyPolyElement::emitStructure( int nLevel)
165 {
166     SAL_INFO( "sdext", std::string(nLevel, ' ') << "<" << typeid( *this ).name() << " " << this << ">" );
167     SAL_INFO( "sdext", "path=" );
168     int nPoly = PolyPoly.count();
169     for( int i = 0; i < nPoly; i++ )
170     {
171         OUStringBuffer buff;
172         basegfx::B2DPolygon aPoly = PolyPoly.getB2DPolygon( i );
173         int nPoints = aPoly.count();
174         for( int n = 0; n < nPoints; n++ )
175         {
176             basegfx::B2DPoint aPoint = aPoly.getB2DPoint( n );
177             buff.append( " (" + OUString::number(aPoint.getX()) + "," + OUString::number(aPoint.getY()) + ")");
178         }
179         SAL_INFO( "sdext", "    " << buff.makeStringAndClear() );
180     }
181     for (auto const& child : Children)
182         child->emitStructure( nLevel+1 );
183     SAL_INFO( "sdext", std::string(nLevel, ' ') << "</" << typeid( *this ).name() << ">");
184 }
185 #endif
186 
visitedBy(ElementTreeVisitor & rVisitor,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)187 void ParagraphElement::visitedBy( ElementTreeVisitor&                          rVisitor,
188                                   const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
189 {
190     rVisitor.visit(*this,rParentIt);
191 }
192 
isSingleLined(PDFIProcessor const & rProc) const193 bool ParagraphElement::isSingleLined( PDFIProcessor const & rProc ) const
194 {
195     TextElement* pText = nullptr, *pLastText = nullptr;
196     for( auto& rxChild : Children )
197     {
198         // a paragraph containing subparagraphs cannot be single lined
199         if( dynamic_cast< ParagraphElement* >(rxChild.get()) != nullptr )
200             return false;
201 
202         pText = rxChild->dynCastAsTextElement();
203         if( pText )
204         {
205             const FontAttributes& rFont = rProc.getFont( pText->FontId );
206             if( pText->h > rFont.size*1.5 )
207                 return  false;
208             if( pLastText )
209             {
210                 if( pText->y > pLastText->y+pLastText->h ||
211                     pLastText->y > pText->y+pText->h )
212                     return false;
213             }
214             else
215                 pLastText = pText;
216         }
217     }
218 
219     // a paragraph without a single text is not considered single lined
220     return pLastText != nullptr;
221 }
222 
getLineHeight(PDFIProcessor & rProc) const223 double ParagraphElement::getLineHeight( PDFIProcessor& rProc ) const
224 {
225     double line_h = 0;
226     for( auto& rxChild : Children )
227     {
228         ParagraphElement* pPara = dynamic_cast< ParagraphElement* >(rxChild.get());
229         TextElement* pText = nullptr;
230         if( pPara )
231         {
232             double lh = pPara->getLineHeight( rProc );
233             if( lh > line_h )
234                 line_h = lh;
235         }
236         else if( (pText = rxChild->dynCastAsTextElement()) != nullptr )
237         {
238             const FontAttributes& rFont = rProc.getFont( pText->FontId );
239             double lh = pText->h;
240             if( pText->h > rFont.size*1.5 )
241                 lh = rFont.size;
242             if( lh > line_h )
243                 line_h = lh;
244         }
245     }
246     return line_h;
247 }
248 
getFirstTextChild() const249 TextElement* ParagraphElement::getFirstTextChild() const
250 {
251     TextElement* pText = nullptr;
252     auto it = std::find_if(Children.begin(), Children.end(),
253         [](const std::unique_ptr<Element>& rxElem) { return rxElem->dynCastAsTextElement() != nullptr; });
254     if (it != Children.end())
255         pText = (*it)->dynCastAsTextElement();
256     return pText;
257 }
258 
~PageElement()259 PageElement::~PageElement()
260 {
261 }
262 
visitedBy(ElementTreeVisitor & rVisitor,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)263 void PageElement::visitedBy( ElementTreeVisitor&                          rVisitor,
264                              const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
265 {
266      rVisitor.visit(*this, rParentIt);
267 }
268 
resolveHyperlink(const std::list<std::unique_ptr<Element>>::iterator & link_it,std::list<std::unique_ptr<Element>> & rElements)269 bool PageElement::resolveHyperlink( const std::list<std::unique_ptr<Element>>::iterator& link_it, std::list<std::unique_ptr<Element>>& rElements )
270 {
271     HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(link_it->get());
272     if( ! pLink ) // sanity check
273         return false;
274 
275     for( auto it = rElements.begin(); it != rElements.end(); ++it )
276     {
277         if( (*it)->x >= pLink->x && (*it)->x + (*it)->w <= pLink->x + pLink->w &&
278             (*it)->y >= pLink->y && (*it)->y + (*it)->h <= pLink->y + pLink->h )
279         {
280             TextElement* pText = (*it)->dynCastAsTextElement();
281             if( pText )
282             {
283                 if( pLink->Children.empty() )
284                 {
285                     // insert the hyperlink before the frame
286                     rElements.splice( it, Hyperlinks.Children, link_it );
287                     pLink->Parent = (*it)->Parent;
288                 }
289                 // move text element into hyperlink
290                 auto next = it;
291                 ++next;
292                 Element::setParent( it, pLink );
293                 it = next;
294                 --it;
295                 continue;
296             }
297             // a link can contain multiple text elements or a single frame
298             if( ! pLink->Children.empty() )
299                 continue;
300             if( dynamic_cast<ParagraphElement*>(it->get())  )
301             {
302                 if( resolveHyperlink( link_it, (*it)->Children ) )
303                     break;
304                 continue;
305             }
306             FrameElement* pFrame = dynamic_cast<FrameElement*>(it->get());
307             if( pFrame )
308             {
309                 // insert the hyperlink before the frame
310                 rElements.splice( it, Hyperlinks.Children, link_it );
311                 pLink->Parent = (*it)->Parent;
312                 // move frame into hyperlink
313                 Element::setParent( it, pLink );
314                 break;
315             }
316         }
317     }
318     return ! pLink->Children.empty();
319 }
320 
resolveHyperlinks()321 void PageElement::resolveHyperlinks()
322 {
323     while( ! Hyperlinks.Children.empty() )
324     {
325         if( ! resolveHyperlink( Hyperlinks.Children.begin(), Children ) )
326         {
327             Hyperlinks.Children.pop_front();
328         }
329     }
330 }
331 
resolveFontStyles(PDFIProcessor const & rProc)332 void PageElement::resolveFontStyles( PDFIProcessor const & rProc )
333 {
334     resolveUnderlines(rProc);
335 }
336 
resolveUnderlines(PDFIProcessor const & rProc)337 void PageElement::resolveUnderlines( PDFIProcessor const & rProc )
338 {
339     // FIXME: currently the algorithm used is quadratic
340     // this could be solved by some sorting beforehand
341 
342     std::vector<Element*> textAndHypers;
343     textAndHypers.reserve(Children.size());
344     for (auto const & p : Children)
345     {
346         if (p->dynCastAsTextElement() || dynamic_cast<HyperlinkElement*>(p.get()))
347             textAndHypers.push_back(p.get());
348     }
349 
350     auto poly_it = Children.begin();
351     while( poly_it != Children.end() )
352     {
353         PolyPolyElement* pPoly = dynamic_cast< PolyPolyElement* >(poly_it->get());
354         if( ! pPoly || ! pPoly->Children.empty() )
355         {
356             ++poly_it;
357             continue;
358         }
359         /* check for: no filling
360         *             only two points (FIXME: handle small rectangles, too)
361         *             y coordinates of points are equal
362         */
363         if( pPoly->Action != PATH_STROKE )
364         {
365             ++poly_it;
366             continue;
367         }
368         if( pPoly->PolyPoly.count() != 1 )
369         {
370             ++poly_it;
371             continue;
372         }
373 
374         bool bRemovePoly = false;
375         basegfx::B2DPolygon aPoly = pPoly->PolyPoly.getB2DPolygon(0);
376         if( aPoly.count() != 2 ||
377             aPoly.getB2DPoint(0).getY() != aPoly.getB2DPoint(1).getY() )
378         {
379             ++poly_it;
380             continue;
381         }
382         double l_x = aPoly.getB2DPoint(0).getX();
383         double r_x = aPoly.getB2DPoint(1).getX();
384         double u_y;
385         if( r_x < l_x )
386         {
387             u_y = r_x; r_x = l_x; l_x = u_y;
388         }
389         u_y = aPoly.getB2DPoint(0).getY();
390         for( Element* pEle : textAndHypers )
391         {
392             if( pEle->y <= u_y && pEle->y + pEle->h*1.1 >= u_y )
393             {
394                 // first: is the element underlined completely ?
395                 if( pEle->x + pEle->w*0.1 >= l_x &&
396                     pEle->x + pEle->w*0.9 <= r_x )
397                 {
398                     TextElement* pText = pEle->dynCastAsTextElement();
399                     if( pText )
400                     {
401                         const GraphicsContext& rTextGC = rProc.getGraphicsContext( pText->GCId );
402                         if( ! rTextGC.isRotatedOrSkewed() )
403                         {
404                             bRemovePoly = true;
405                             // retrieve ID for modified font
406                             FontAttributes aAttr = rProc.getFont( pText->FontId );
407                             aAttr.isUnderline = true;
408                             pText->FontId = rProc.getFontId( aAttr );
409                         }
410                     }
411                     else // must be HyperlinkElement
412                         bRemovePoly = true;
413                 }
414                 // second: hyperlinks may be larger than their underline
415                 // since they are just arbitrary rectangles in the action definition
416                 else if( l_x >= pEle->x && r_x <= pEle->x+pEle->w &&
417                         dynamic_cast< HyperlinkElement* >(pEle) != nullptr )
418                 {
419                     bRemovePoly = true;
420                 }
421             }
422         }
423         if( bRemovePoly )
424             poly_it = Children.erase( poly_it );
425         else
426             ++poly_it;
427     }
428 }
429 
~DocumentElement()430 DocumentElement::~DocumentElement()
431 {
432 }
433 
visitedBy(ElementTreeVisitor & rVisitor,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)434 void DocumentElement::visitedBy( ElementTreeVisitor&                          rVisitor,
435                                  const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt)
436 {
437     rVisitor.visit(*this, rParentIt);
438 }
439 
isComplex(const css::uno::Reference<css::i18n::XBreakIterator> & rBreakIterator,TextElement * const pTextElem)440 bool isComplex(const css::uno::Reference<css::i18n::XBreakIterator>& rBreakIterator, TextElement* const pTextElem) {
441     OUString str(pTextElem->Text.toString());
442     for(int i=0; i< str.getLength(); i++)
443     {
444         sal_Int16 nType = rBreakIterator->getScriptType(str, i);
445         if (nType == css::i18n::ScriptType::COMPLEX)
446         {
447             return true;
448         }
449     }
450     return false;
451 }
452 
453 }
454 
455 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
456