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