| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_sdext.hxx" |
| |
| #include "xmlemitter.hxx" |
| #include "genericelements.hxx" |
| #include "pdfiprocessor.hxx" |
| #include "pdfihelper.hxx" |
| #include "style.hxx" |
| |
| |
| #include <basegfx/polygon/b2dpolypolygontools.hxx> |
| #include <basegfx/range/b2drange.hxx> |
| |
| namespace pdfi |
| { |
| |
| ElementFactory::~ElementFactory() |
| { |
| } |
| |
| Element::~Element() |
| { |
| while( !Children.empty() ) |
| { |
| Element* pCurr( Children.front() ); |
| delete pCurr; |
| Children.pop_front(); |
| } |
| } |
| |
| void Element::applyToChildren( ElementTreeVisitor& rVisitor ) |
| { |
| for( std::list< Element* >::iterator it = Children.begin(); it != Children.end(); ++it ) |
| (*it)->visitedBy( rVisitor, it ); |
| } |
| |
| void Element::setParent( std::list<Element*>::iterator& el, Element* pNewParent ) |
| { |
| if( pNewParent ) |
| { |
| pNewParent->Children.splice( pNewParent->Children.end(), (*el)->Parent->Children, el ); |
| (*el)->Parent = pNewParent; |
| } |
| } |
| |
| void Element::updateGeometryWith( const Element* pMergeFrom ) |
| { |
| if( w == 0 && h == 0 ) |
| { |
| x = pMergeFrom->x; |
| y = pMergeFrom->y; |
| w = pMergeFrom->w; |
| h = pMergeFrom->h; |
| } |
| else |
| { |
| if( pMergeFrom->x < x ) |
| { |
| w += x - pMergeFrom->x; |
| x = pMergeFrom->x; |
| } |
| if( pMergeFrom->x+pMergeFrom->w > x+w ) |
| w = pMergeFrom->w+pMergeFrom->x - x; |
| if( pMergeFrom->y < y ) |
| { |
| h += y - pMergeFrom->y; |
| y = pMergeFrom->y; |
| } |
| if( pMergeFrom->y+pMergeFrom->h > y+h ) |
| h = pMergeFrom->h+pMergeFrom->y - y; |
| } |
| } |
| |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| #include <typeinfo> |
| void Element::emitStructure( int nLevel) |
| { |
| OSL_TRACE( "%*s<%s %p> (%.1f,%.1f)+(%.1fx%.1f)\n", |
| nLevel, "", typeid( *this ).name(), this, |
| x, y, w, h ); |
| for( std::list< Element* >::iterator it = Children.begin(); it != Children.end(); ++it ) |
| (*it)->emitStructure(nLevel+1 ); |
| OSL_TRACE( "%*s</%s>\n", nLevel, "", typeid( *this ).name() ); |
| } |
| #endif |
| |
| void ListElement::visitedBy( ElementTreeVisitor& visitor, const std::list< Element* >::const_iterator& ) |
| { |
| // this is only an inner node |
| applyToChildren(visitor); |
| } |
| |
| void HyperlinkElement::visitedBy( ElementTreeVisitor& rVisitor, |
| const std::list< Element* >::const_iterator& rParentIt ) |
| { |
| rVisitor.visit(*this,rParentIt); |
| } |
| |
| void TextElement::visitedBy( ElementTreeVisitor& rVisitor, |
| const std::list< Element* >::const_iterator& rParentIt ) |
| { |
| rVisitor.visit(*this,rParentIt); |
| } |
| |
| void FrameElement::visitedBy( ElementTreeVisitor& rVisitor, |
| const std::list< Element* >::const_iterator& rParentIt ) |
| { |
| rVisitor.visit(*this,rParentIt); |
| } |
| |
| void ImageElement::visitedBy( ElementTreeVisitor& rVisitor, |
| const std::list< Element* >::const_iterator& rParentIt) |
| { |
| rVisitor.visit( *this, rParentIt); |
| } |
| |
| PolyPolyElement::PolyPolyElement( Element* pParent, |
| sal_Int32 nGCId, |
| const basegfx::B2DPolyPolygon& rPolyPoly, |
| sal_Int8 nAction ) |
| : DrawElement( pParent, nGCId ), |
| PolyPoly( rPolyPoly ), |
| Action( nAction ) |
| { |
| } |
| |
| void PolyPolyElement::updateGeometry() |
| { |
| basegfx::B2DRange aRange; |
| if( PolyPoly.areControlPointsUsed() ) |
| aRange = basegfx::tools::getRange( basegfx::tools::adaptiveSubdivideByAngle( PolyPoly ) ); |
| else |
| aRange = basegfx::tools::getRange( PolyPoly ); |
| x = aRange.getMinX(); |
| y = aRange.getMinY(); |
| w = aRange.getWidth(); |
| h = aRange.getHeight(); |
| } |
| |
| void PolyPolyElement::visitedBy( ElementTreeVisitor& rVisitor, |
| const std::list< Element* >::const_iterator& rParentIt) |
| { |
| rVisitor.visit( *this, rParentIt); |
| } |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| void PolyPolyElement::emitStructure( int nLevel) |
| { |
| OSL_TRACE( "%*s<%s %p>\n", nLevel, "", typeid( *this ).name(), this ); |
| OSL_TRACE( "path=" ); |
| int nPoly = PolyPoly.count(); |
| for( int i = 0; i < nPoly; i++ ) |
| { |
| basegfx::B2DPolygon aPoly = PolyPoly.getB2DPolygon( i ); |
| int nPoints = aPoly.count(); |
| for( int n = 0; n < nPoints; n++ ) |
| { |
| basegfx::B2DPoint aPoint = aPoly.getB2DPoint( n ); |
| OSL_TRACE( " (%g,%g)", aPoint.getX(), aPoint.getY() ); |
| } |
| OSL_TRACE( "\n" ); |
| } |
| for( std::list< Element* >::iterator it = Children.begin(); it != Children.end(); ++it ) |
| (*it)->emitStructure( nLevel+1 ); |
| OSL_TRACE( "%*s</%s>\n", nLevel, "", typeid( *this ).name() ); |
| } |
| #endif |
| |
| void ParagraphElement::visitedBy( ElementTreeVisitor& rVisitor, |
| const std::list< Element* >::const_iterator& rParentIt ) |
| { |
| rVisitor.visit(*this,rParentIt); |
| } |
| |
| bool ParagraphElement::isSingleLined( PDFIProcessor& rProc ) const |
| { |
| std::list< Element* >::const_iterator it = Children.begin(); |
| TextElement* pText = NULL, *pLastText = NULL; |
| while( it != Children.end() ) |
| { |
| // a paragraph containing subparagraphs cannot be single lined |
| if( dynamic_cast< ParagraphElement* >(*it) != NULL ) |
| return false; |
| |
| pText = dynamic_cast< TextElement* >(*it); |
| if( pText ) |
| { |
| const FontAttributes& rFont = rProc.getFont( pText->FontId ); |
| if( pText->h > rFont.size*1.5 ) |
| return false; |
| if( pLastText ) |
| { |
| if( pText->y > pLastText->y+pLastText->h || |
| pLastText->y > pText->y+pText->h ) |
| return false; |
| } |
| else |
| pLastText = pText; |
| } |
| ++it; |
| } |
| |
| // a paragraph without a single text is not considered single lined |
| return pLastText != NULL; |
| } |
| |
| double ParagraphElement::getLineHeight( PDFIProcessor& rProc ) const |
| { |
| double line_h = 0; |
| for( std::list< Element* >::const_iterator it = Children.begin(); it != Children.end(); ++it ) |
| { |
| ParagraphElement* pPara = dynamic_cast< ParagraphElement* >(*it); |
| TextElement* pText = NULL; |
| if( pPara ) |
| { |
| double lh = pPara->getLineHeight( rProc ); |
| if( lh > line_h ) |
| line_h = lh; |
| } |
| else if( (pText = dynamic_cast< TextElement* >( *it )) != NULL ) |
| { |
| const FontAttributes& rFont = rProc.getFont( pText->FontId ); |
| double lh = pText->h; |
| if( pText->h > rFont.size*1.5 ) |
| lh = rFont.size; |
| if( lh > line_h ) |
| line_h = lh; |
| } |
| } |
| return line_h; |
| } |
| |
| TextElement* ParagraphElement::getFirstTextChild() const |
| { |
| TextElement* pText = NULL; |
| for( std::list< Element* >::const_iterator it = Children.begin(); |
| it != Children.end() && ! pText; ++it ) |
| { |
| pText = dynamic_cast<TextElement*>(*it); |
| } |
| return pText; |
| } |
| |
| PageElement::~PageElement() |
| { |
| if( HeaderElement ) |
| delete HeaderElement; |
| if( FooterElement ) |
| delete FooterElement; |
| } |
| |
| void PageElement::visitedBy( ElementTreeVisitor& rVisitor, |
| const std::list< Element* >::const_iterator& rParentIt ) |
| { |
| rVisitor.visit(*this, rParentIt); |
| } |
| |
| void PageElement::updateParagraphGeometry( Element* pEle ) |
| { |
| // update geometry of children |
| for( std::list< Element* >::iterator it = pEle->Children.begin(); |
| it != pEle->Children.end(); ++it ) |
| { |
| updateParagraphGeometry( *it ); |
| } |
| // if this is a paragraph itself, then update according to children geometry |
| if( dynamic_cast<ParagraphElement*>(pEle) ) |
| { |
| for( std::list< Element* >::iterator it = pEle->Children.begin(); |
| it != pEle->Children.end(); ++it ) |
| { |
| Element* pChild = NULL; |
| TextElement* pText = dynamic_cast<TextElement*>(*it); |
| if( pText ) |
| pChild = pText; |
| else |
| { |
| ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(*it); |
| if( pPara ) |
| pChild = pPara; |
| } |
| if( pChild ) |
| pEle->updateGeometryWith( pChild ); |
| } |
| } |
| } |
| |
| bool PageElement::resolveHyperlink( std::list<Element*>::iterator link_it, std::list<Element*>& rElements ) |
| { |
| HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(*link_it); |
| if( ! pLink ) // sanity check |
| return false; |
| |
| for( std::list<Element*>::iterator it = rElements.begin(); it != rElements.end(); ++it ) |
| { |
| if( (*it)->x >= pLink->x && (*it)->x + (*it)->w <= pLink->x + pLink->w && |
| (*it)->y >= pLink->y && (*it)->y + (*it)->h <= pLink->y + pLink->h ) |
| { |
| TextElement* pText = dynamic_cast<TextElement*>(*it); |
| if( pText ) |
| { |
| if( pLink->Children.empty() ) |
| { |
| // insert the hyperlink before the frame |
| rElements.splice( it, Hyperlinks.Children, link_it ); |
| pLink->Parent = (*it)->Parent; |
| } |
| // move text element into hyperlink |
| std::list<Element*>::iterator next = it; |
| ++next; |
| Element::setParent( it, pLink ); |
| it = next; |
| --it; |
| continue; |
| } |
| // a link can contain multiple text elements or a single frame |
| if( ! pLink->Children.empty() ) |
| continue; |
| if( dynamic_cast<ParagraphElement*>(*it) ) |
| { |
| if( resolveHyperlink( link_it, (*it)->Children ) ) |
| break; |
| continue; |
| } |
| FrameElement* pFrame = dynamic_cast<FrameElement*>(*it); |
| if( pFrame ) |
| { |
| // insert the hyperlink before the frame |
| rElements.splice( it, Hyperlinks.Children, link_it ); |
| pLink->Parent = (*it)->Parent; |
| // move frame into hyperlink |
| Element::setParent( it, pLink ); |
| break; |
| } |
| } |
| } |
| return ! pLink->Children.empty(); |
| } |
| |
| void PageElement::resolveHyperlinks() |
| { |
| while( ! Hyperlinks.Children.empty() ) |
| { |
| if( ! resolveHyperlink( Hyperlinks.Children.begin(), Children ) ) |
| { |
| delete Hyperlinks.Children.front(); |
| Hyperlinks.Children.pop_front(); |
| } |
| } |
| } |
| |
| void PageElement::resolveFontStyles( PDFIProcessor& rProc ) |
| { |
| resolveUnderlines(rProc); |
| } |
| |
| void PageElement::resolveUnderlines( PDFIProcessor& rProc ) |
| { |
| // FIXME: currently the algorithm used is quadratic |
| // this could be solved by some sorting beforehand |
| |
| std::list< Element* >::iterator poly_it = Children.begin(); |
| while( poly_it != Children.end() ) |
| { |
| PolyPolyElement* pPoly = dynamic_cast< PolyPolyElement* >(*poly_it); |
| if( ! pPoly || ! pPoly->Children.empty() ) |
| { |
| ++poly_it; |
| continue; |
| } |
| /* check for: no filling |
| * only two points (FIXME: handle small rectangles, too) |
| * y coordinates of points are equal |
| */ |
| if( pPoly->Action != PATH_STROKE ) |
| { |
| ++poly_it; |
| continue; |
| } |
| if( pPoly->PolyPoly.count() != 1 ) |
| { |
| ++poly_it; |
| continue; |
| } |
| |
| bool bRemovePoly = false; |
| basegfx::B2DPolygon aPoly = pPoly->PolyPoly.getB2DPolygon(0); |
| if( aPoly.count() != 2 || |
| aPoly.getB2DPoint(0).getY() != aPoly.getB2DPoint(1).getY() ) |
| { |
| ++poly_it; |
| continue; |
| } |
| double l_x = aPoly.getB2DPoint(0).getX(); |
| double r_x = aPoly.getB2DPoint(1).getX(); |
| double u_y; |
| if( r_x < l_x ) |
| { |
| u_y = r_x; r_x = l_x; l_x = u_y; |
| } |
| u_y = aPoly.getB2DPoint(0).getY(); |
| for( std::list< Element*>::iterator it = Children.begin(); |
| it != Children.end(); ++it ) |
| { |
| Element* pEle = *it; |
| if( pEle->y <= u_y && pEle->y + pEle->h*1.1 >= u_y ) |
| { |
| // first: is the element underlined completely ? |
| if( pEle->x + pEle->w*0.1 >= l_x && |
| pEle->x + pEle->w*0.9 <= r_x ) |
| { |
| TextElement* pText = dynamic_cast< TextElement* >(pEle); |
| if( pText ) |
| { |
| const GraphicsContext& rTextGC = rProc.getGraphicsContext( pText->GCId ); |
| if( ! rTextGC.isRotatedOrSkewed() ) |
| { |
| bRemovePoly = true; |
| // retrieve ID for modified font |
| FontAttributes aAttr = rProc.getFont( pText->FontId ); |
| aAttr.isUnderline = true; |
| pText->FontId = rProc.getFontId( aAttr ); |
| } |
| } |
| else if( dynamic_cast< HyperlinkElement* >(pEle) ) |
| bRemovePoly = true; |
| } |
| // second: hyperlinks may be larger than their underline |
| // since they are just arbitrary rectangles in the action definition |
| else if( dynamic_cast< HyperlinkElement* >(pEle) != NULL && |
| l_x >= pEle->x && r_x <= pEle->x+pEle->w ) |
| { |
| bRemovePoly = true; |
| } |
| } |
| } |
| if( bRemovePoly ) |
| { |
| std::list< Element* >::iterator next_it = poly_it; |
| ++next_it; |
| Children.erase( poly_it ); |
| delete pPoly; |
| poly_it = next_it; |
| } |
| else |
| ++poly_it; |
| } |
| } |
| |
| DocumentElement::~DocumentElement() |
| { |
| } |
| |
| void DocumentElement::visitedBy( ElementTreeVisitor& rVisitor, |
| const std::list< Element* >::const_iterator& rParentIt) |
| { |
| rVisitor.visit(*this, rParentIt); |
| } |
| |
| |
| } |