Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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: */