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