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 #include <sal/log.hxx> 20 #include <pdfiprocessor.hxx> 21 #include <xmlemitter.hxx> 22 #include <pdfihelper.hxx> 23 #include <imagecontainer.hxx> 24 #include "style.hxx" 25 #include "drawtreevisiting.hxx" 26 #include <genericelements.hxx> 27 28 #include <basegfx/polygon/b2dpolypolygontools.hxx> 29 #include <osl/diagnose.h> 30 #include <rtl/math.hxx> 31 #include <com/sun/star/i18n/BreakIterator.hpp> 32 #include <com/sun/star/i18n/CharacterClassification.hpp> 33 #include <com/sun/star/i18n/ScriptType.hpp> 34 #include <com/sun/star/i18n/DirectionProperty.hpp> 35 #include <comphelper/string.hxx> 36 37 #include <string.h> 38 #include <string_view> 39 40 using namespace ::com::sun::star; 41 using namespace ::com::sun::star::lang; 42 using namespace ::com::sun::star::i18n; 43 using namespace ::com::sun::star::uno; 44 45 namespace pdfi 46 { 47 48 const Reference< XBreakIterator >& DrawXmlOptimizer::GetBreakIterator() 49 { 50 if ( !mxBreakIter.is() ) 51 { 52 Reference< XComponentContext > xContext( m_rProcessor.m_xContext, uno::UNO_SET_THROW ); 53 mxBreakIter = BreakIterator::create(xContext); 54 } 55 return mxBreakIter; 56 } 57 58 const Reference< XCharacterClassification >& DrawXmlEmitter::GetCharacterClassification() 59 { 60 if ( !mxCharClass.is() ) 61 { 62 Reference< XComponentContext > xContext( m_rEmitContext.m_xContext, uno::UNO_SET_THROW ); 63 mxCharClass = CharacterClassification::create(xContext); 64 } 65 return mxCharClass; 66 } 67 68 void DrawXmlEmitter::visit( HyperlinkElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 69 { 70 if( elem.Children.empty() ) 71 return; 72 73 const char* pType = dynamic_cast<DrawElement*>(elem.Children.front().get()) ? "draw:a" : "text:a"; 74 75 PropertyMap aProps; 76 aProps[ "xlink:type" ] = "simple"; 77 aProps[ "xlink:href" ] = elem.URI; 78 aProps[ "office:target-frame-name" ] = "_blank"; 79 aProps[ "xlink:show" ] = "new"; 80 81 m_rEmitContext.rEmitter.beginTag( pType, aProps ); 82 auto this_it = elem.Children.begin(); 83 while( this_it != elem.Children.end() && this_it->get() != &elem ) 84 { 85 (*this_it)->visitedBy( *this, this_it ); 86 ++this_it; 87 } 88 m_rEmitContext.rEmitter.endTag( pType ); 89 } 90 91 void DrawXmlEmitter::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 92 { 93 if( elem.Text.isEmpty() ) 94 return; 95 96 OUString strSpace(u' '); 97 OUString strNbSpace(u'\x00A0'); 98 OUString tabSpace(u'\x0009'); 99 PropertyMap aProps; 100 if( elem.StyleId != -1 ) 101 { 102 aProps[ OUString( "text:style-name" ) ] = 103 m_rEmitContext.rStyles.getStyleName( elem.StyleId ); 104 } 105 106 OUString str(elem.Text.toString()); 107 108 // Check for RTL 109 bool isRTL = false; 110 Reference< i18n::XCharacterClassification > xCC( GetCharacterClassification() ); 111 if( xCC.is() ) 112 { 113 for(int i=1; i< elem.Text.getLength(); i++) 114 { 115 css::i18n::DirectionProperty nType = static_cast<css::i18n::DirectionProperty>(xCC->getCharacterDirection( str, i )); 116 if ( nType == css::i18n::DirectionProperty_RIGHT_TO_LEFT || 117 nType == css::i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC || 118 nType == css::i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING || 119 nType == css::i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE 120 ) 121 isRTL = true; 122 } 123 } 124 125 if (isRTL) // If so, reverse string 126 str = ::comphelper::string::reverseString(str); 127 128 m_rEmitContext.rEmitter.beginTag( "text:span", aProps ); 129 130 aProps = {}; 131 for(int i=0; i< elem.Text.getLength(); i++) 132 { 133 OUString strToken= str.copy(i,1) ; 134 if( strSpace == strToken || strNbSpace == strToken ) 135 { 136 aProps[ "text:c" ] = "1"; 137 m_rEmitContext.rEmitter.beginTag( "text:s", aProps ); 138 m_rEmitContext.rEmitter.endTag( "text:s"); 139 } 140 else 141 { 142 if( tabSpace == strToken ) 143 { 144 m_rEmitContext.rEmitter.beginTag( "text:tab", aProps ); 145 m_rEmitContext.rEmitter.endTag( "text:tab"); 146 } 147 else 148 { 149 m_rEmitContext.rEmitter.write( strToken ); 150 } 151 } 152 } 153 154 auto this_it = elem.Children.begin(); 155 while( this_it != elem.Children.end() && this_it->get() != &elem ) 156 { 157 (*this_it)->visitedBy( *this, this_it ); 158 ++this_it; 159 } 160 161 m_rEmitContext.rEmitter.endTag( "text:span" ); 162 } 163 164 void DrawXmlEmitter::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 165 { 166 PropertyMap aProps; 167 if( elem.StyleId != -1 ) 168 { 169 aProps[ "text:style-name" ] = m_rEmitContext.rStyles.getStyleName( elem.StyleId ); 170 } 171 const char* pTagType = "text:p"; 172 if( elem.Type == ParagraphElement::Headline ) 173 pTagType = "text:h"; 174 m_rEmitContext.rEmitter.beginTag( pTagType, aProps ); 175 176 auto this_it = elem.Children.begin(); 177 while( this_it != elem.Children.end() && this_it->get() != &elem ) 178 { 179 (*this_it)->visitedBy( *this, this_it ); 180 ++this_it; 181 } 182 183 m_rEmitContext.rEmitter.endTag( pTagType ); 184 } 185 186 void DrawXmlEmitter::fillFrameProps( DrawElement& rElem, 187 PropertyMap& rProps, 188 const EmitContext& rEmitContext, 189 bool bWasTransformed 190 ) 191 { 192 static constexpr OUStringLiteral sDrawZIndex = u"draw:z-index"; 193 static constexpr OUStringLiteral sDrawStyleName = u"draw:style-name"; 194 static constexpr OUStringLiteral sDrawTextStyleName = u"draw:text-style-name"; 195 static constexpr OUStringLiteral sSvgX = u"svg:x"; 196 static constexpr OUStringLiteral sSvgY = u"svg:y"; 197 static constexpr OUStringLiteral sSvgWidth = u"svg:width"; 198 static constexpr OUStringLiteral sSvgHeight = u"svg:height"; 199 static constexpr OUStringLiteral sDrawTransform = u"draw:transform"; 200 201 rProps[ sDrawZIndex ] = OUString::number( rElem.ZOrder ); 202 rProps[ sDrawStyleName ] = rEmitContext.rStyles.getStyleName( rElem.StyleId ); 203 204 if (rElem.IsForText) 205 rProps[ sDrawTextStyleName ] = rEmitContext.rStyles.getStyleName(rElem.TextStyleId); 206 207 const GraphicsContext& rGC = 208 rEmitContext.rProcessor.getGraphicsContext( rElem.GCId ); 209 210 if (bWasTransformed) 211 { 212 rProps[ sSvgX ] = convertPixelToUnitString(rElem.x); 213 rProps[ sSvgY ] = convertPixelToUnitString(rElem.y); 214 rProps[ sSvgWidth ] = convertPixelToUnitString(rElem.w); 215 rProps[ sSvgHeight ] = convertPixelToUnitString(rElem.h); 216 } 217 else 218 { 219 OUStringBuffer aBuf(256); 220 221 basegfx::B2DHomMatrix mat(rGC.Transformation); 222 223 if (rElem.MirrorVertical) 224 { 225 basegfx::B2DHomMatrix mat2; 226 mat2.translate(0, -0.5); 227 mat2.scale(1, -1); 228 mat2.translate(0, 0.5); 229 mat = mat * mat2; 230 } 231 232 double scale = convPx2mm(100); 233 mat.scale(scale, scale); 234 235 aBuf.append("matrix("); 236 aBuf.append(mat.get(0, 0)); 237 aBuf.append(' '); 238 aBuf.append(mat.get(1, 0)); 239 aBuf.append(' '); 240 aBuf.append(mat.get(0, 1)); 241 aBuf.append(' '); 242 aBuf.append(mat.get(1, 1)); 243 aBuf.append(' '); 244 aBuf.append(mat.get(0, 2)); 245 aBuf.append(' '); 246 aBuf.append(mat.get(1, 2)); 247 aBuf.append(")"); 248 249 rProps[ sDrawTransform ] = aBuf.makeStringAndClear(); 250 } 251 } 252 253 void DrawXmlEmitter::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 254 { 255 if( elem.Children.empty() ) 256 return; 257 258 bool bTextBox = (dynamic_cast<ParagraphElement*>(elem.Children.front().get()) != nullptr); 259 PropertyMap aFrameProps; 260 fillFrameProps( elem, aFrameProps, m_rEmitContext, false ); 261 m_rEmitContext.rEmitter.beginTag( "draw:frame", aFrameProps ); 262 if( bTextBox ) 263 m_rEmitContext.rEmitter.beginTag( "draw:text-box", PropertyMap() ); 264 265 auto this_it = elem.Children.begin(); 266 while( this_it != elem.Children.end() && this_it->get() != &elem ) 267 { 268 (*this_it)->visitedBy( *this, this_it ); 269 ++this_it; 270 } 271 272 if( bTextBox ) 273 m_rEmitContext.rEmitter.endTag( "draw:text-box" ); 274 m_rEmitContext.rEmitter.endTag( "draw:frame" ); 275 } 276 277 void DrawXmlEmitter::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 278 { 279 elem.updateGeometry(); 280 /* note: 281 * aw recommends using 100dth of mm in all respects since the xml import 282 * (a) is buggy (see issue 37213) 283 * (b) is optimized for 100dth of mm and does not scale itself then, 284 * this does not gain us speed but makes for smaller rounding errors since 285 * the xml importer coordinates are integer based 286 */ 287 for (sal_uInt32 i = 0; i< elem.PolyPoly.count(); i++) 288 { 289 basegfx::B2DPolygon b2dPolygon = elem.PolyPoly.getB2DPolygon( i ); 290 291 for ( sal_uInt32 j = 0; j< b2dPolygon.count(); j++ ) 292 { 293 basegfx::B2DPoint point; 294 basegfx::B2DPoint nextPoint; 295 point = b2dPolygon.getB2DPoint( j ); 296 297 basegfx::B2DPoint prevPoint = b2dPolygon.getPrevControlPoint( j ) ; 298 299 point.setX( convPx2mmPrec2( point.getX() )*100.0 ); 300 point.setY( convPx2mmPrec2( point.getY() )*100.0 ); 301 302 if ( b2dPolygon.isPrevControlPointUsed( j ) ) 303 { 304 prevPoint.setX( convPx2mmPrec2( prevPoint.getX() )*100.0 ); 305 prevPoint.setY( convPx2mmPrec2( prevPoint.getY() )*100.0 ); 306 } 307 308 if ( b2dPolygon.isNextControlPointUsed( j ) ) 309 { 310 nextPoint = b2dPolygon.getNextControlPoint( j ) ; 311 nextPoint.setX( convPx2mmPrec2( nextPoint.getX() )*100.0 ); 312 nextPoint.setY( convPx2mmPrec2( nextPoint.getY() )*100.0 ); 313 } 314 315 b2dPolygon.setB2DPoint( j, point ); 316 317 if ( b2dPolygon.isPrevControlPointUsed( j ) ) 318 b2dPolygon.setPrevControlPoint( j , prevPoint ) ; 319 320 if ( b2dPolygon.isNextControlPointUsed( j ) ) 321 b2dPolygon.setNextControlPoint( j , nextPoint ) ; 322 } 323 324 elem.PolyPoly.setB2DPolygon( i, b2dPolygon ); 325 } 326 327 PropertyMap aProps; 328 // PDFIProcessor transforms geometrical objects, not images and text 329 // so we need to tell fillFrameProps here that the transformation for 330 // a PolyPolyElement was already applied (aside from translation) 331 fillFrameProps( elem, aProps, m_rEmitContext, true ); 332 OUStringBuffer aBuf( 64 ); 333 aBuf.append( "0 0 " ); 334 aBuf.append( convPx2mmPrec2(elem.w)*100.0 ); 335 aBuf.append( ' ' ); 336 aBuf.append( convPx2mmPrec2(elem.h)*100.0 ); 337 aProps[ "svg:viewBox" ] = aBuf.makeStringAndClear(); 338 aProps[ "svg:d" ] = basegfx::utils::exportToSvgD( elem.PolyPoly, false, true, false ); 339 340 m_rEmitContext.rEmitter.beginTag( "draw:path", aProps ); 341 m_rEmitContext.rEmitter.endTag( "draw:path" ); 342 } 343 344 void DrawXmlEmitter::visit( ImageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 345 { 346 PropertyMap aImageProps; 347 m_rEmitContext.rEmitter.beginTag( "draw:image", aImageProps ); 348 m_rEmitContext.rEmitter.beginTag( "office:binary-data", PropertyMap() ); 349 m_rEmitContext.rImages.writeBase64EncodedStream( elem.Image, m_rEmitContext); 350 m_rEmitContext.rEmitter.endTag( "office:binary-data" ); 351 m_rEmitContext.rEmitter.endTag( "draw:image" ); 352 } 353 354 void DrawXmlEmitter::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 355 { 356 PropertyMap aPageProps; 357 aPageProps[ "draw:master-page-name" ] = m_rEmitContext.rStyles.getStyleName( elem.StyleId ); 358 359 m_rEmitContext.rEmitter.beginTag("draw:page", aPageProps); 360 361 if( m_rEmitContext.xStatusIndicator.is() ) 362 m_rEmitContext.xStatusIndicator->setValue( elem.PageNumber ); 363 364 auto this_it = elem.Children.begin(); 365 while( this_it != elem.Children.end() && this_it->get() != &elem ) 366 { 367 (*this_it)->visitedBy( *this, this_it ); 368 ++this_it; 369 } 370 371 m_rEmitContext.rEmitter.endTag("draw:page"); 372 } 373 374 void DrawXmlEmitter::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&) 375 { 376 m_rEmitContext.rEmitter.beginTag( "office:body", PropertyMap() ); 377 m_rEmitContext.rEmitter.beginTag( m_bWriteDrawDocument ? "office:drawing" : "office:presentation", 378 PropertyMap() ); 379 380 auto this_it = elem.Children.begin(); 381 while( this_it != elem.Children.end() && this_it->get() != &elem ) 382 { 383 (*this_it)->visitedBy( *this, this_it ); 384 ++this_it; 385 } 386 387 m_rEmitContext.rEmitter.endTag( m_bWriteDrawDocument ? "office:drawing" : "office:presentation" ); 388 m_rEmitContext.rEmitter.endTag( "office:body" ); 389 } 390 391 392 void DrawXmlOptimizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& ) 393 { 394 } 395 396 void DrawXmlOptimizer::visit( TextElement&, const std::list< std::unique_ptr<Element> >::const_iterator&) 397 { 398 } 399 400 void DrawXmlOptimizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 401 { 402 elem.applyToChildren(*this); 403 } 404 405 void DrawXmlOptimizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& ) 406 { 407 } 408 409 void DrawXmlOptimizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& elemIt ) 410 { 411 /* note: optimize two consecutive PolyPolyElements that 412 * have the same path but one of which is a stroke while 413 * the other is a fill 414 */ 415 if( !elem.Parent ) 416 return; 417 418 // find following PolyPolyElement in parent's children list 419 if( elemIt == elem.Parent->Children.end() ) 420 return; 421 auto next_it = elemIt; 422 ++next_it; 423 if( next_it == elem.Parent->Children.end() ) 424 return; 425 426 PolyPolyElement* pNext = dynamic_cast<PolyPolyElement*>(next_it->get()); 427 // TODO(F2): this comparison fails for OOo-generated polygons with beziers. 428 if( !pNext || pNext->PolyPoly != elem.PolyPoly ) 429 return; 430 431 const GraphicsContext& rNextGC = 432 m_rProcessor.getGraphicsContext( pNext->GCId ); 433 const GraphicsContext& rThisGC = 434 m_rProcessor.getGraphicsContext( elem.GCId ); 435 436 if( !(rThisGC.BlendMode == rNextGC.BlendMode && 437 rThisGC.Flatness == rNextGC.Flatness && 438 rThisGC.Transformation == rNextGC.Transformation && 439 rThisGC.Clip == rNextGC.Clip && 440 rThisGC.FillColor.Red == rNextGC.FillColor.Red && 441 rThisGC.FillColor.Green== rNextGC.FillColor.Green && 442 rThisGC.FillColor.Blue == rNextGC.FillColor.Blue && 443 rThisGC.FillColor.Alpha== rNextGC.FillColor.Alpha && 444 pNext->Action == PATH_STROKE && 445 (elem.Action == PATH_FILL || elem.Action == PATH_EOFILL)) ) 446 return; 447 448 GraphicsContext aGC = rThisGC; 449 aGC.LineJoin = rNextGC.LineJoin; 450 aGC.LineCap = rNextGC.LineCap; 451 aGC.LineWidth = rNextGC.LineWidth; 452 aGC.MiterLimit= rNextGC.MiterLimit; 453 aGC.DashArray = rNextGC.DashArray; 454 aGC.LineColor = rNextGC.LineColor; 455 elem.GCId = m_rProcessor.getGCId( aGC ); 456 457 elem.Action |= pNext->Action; 458 459 elem.Children.splice( elem.Children.end(), pNext->Children ); 460 elem.Parent->Children.erase(next_it); 461 } 462 463 void DrawXmlOptimizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 464 { 465 optimizeTextElements( elem ); 466 467 elem.applyToChildren(*this); 468 } 469 470 void DrawXmlOptimizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 471 { 472 if( m_rProcessor.getStatusIndicator().is() ) 473 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber ); 474 475 // resolve hyperlinks 476 elem.resolveHyperlinks(); 477 478 elem.resolveFontStyles( m_rProcessor ); // underlines and such 479 480 // FIXME: until hyperlinks and font effects are adjusted for 481 // geometrical search handle them before sorting 482 PDFIProcessor::sortElements( &elem ); 483 484 // find paragraphs in text 485 ParagraphElement* pCurPara = nullptr; 486 std::list< std::unique_ptr<Element> >::iterator page_element, next_page_element; 487 next_page_element = elem.Children.begin(); 488 double fCurLineHeight = 0.0; // average height of text items in current para 489 int nCurLineElements = 0; // number of line contributing elements in current para 490 double line_left = elem.w, line_right = 0.0; 491 double column_width = elem.w*0.75; // estimate text width 492 // TODO: guess columns 493 while( next_page_element != elem.Children.end() ) 494 { 495 page_element = next_page_element++; 496 ParagraphElement* pPagePara = dynamic_cast<ParagraphElement*>(page_element->get()); 497 if( pPagePara ) 498 { 499 pCurPara = pPagePara; 500 // adjust line height and text items 501 fCurLineHeight = 0.0; 502 nCurLineElements = 0; 503 for( const auto& rxChild : pCurPara->Children ) 504 { 505 TextElement* pTestText = rxChild->dynCastAsTextElement(); 506 if( pTestText ) 507 { 508 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pTestText->h)/double(nCurLineElements+1); 509 nCurLineElements++; 510 } 511 } 512 continue; 513 } 514 515 HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(page_element->get()); 516 DrawElement* pDraw = dynamic_cast<DrawElement*>(page_element->get()); 517 if( ! pDraw && pLink && ! pLink->Children.empty() ) 518 pDraw = dynamic_cast<DrawElement*>(pLink->Children.front().get() ); 519 if( pDraw ) 520 { 521 // insert small drawing objects as character, else leave them page bound 522 523 bool bInsertToParagraph = false; 524 // first check if this is either inside the paragraph 525 if( pCurPara && pDraw->y < pCurPara->y + pCurPara->h ) 526 { 527 if( pDraw->h < fCurLineHeight * 1.5 ) 528 { 529 bInsertToParagraph = true; 530 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pDraw->h)/double(nCurLineElements+1); 531 nCurLineElements++; 532 // mark draw element as character 533 pDraw->isCharacter = true; 534 } 535 } 536 // or perhaps the draw element begins a new paragraph 537 else if( next_page_element != elem.Children.end() ) 538 { 539 TextElement* pText = (*next_page_element)->dynCastAsTextElement(); 540 if( ! pText ) 541 { 542 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(next_page_element->get()); 543 if( pPara && ! pPara->Children.empty() ) 544 pText = pPara->Children.front()->dynCastAsTextElement(); 545 } 546 if( pText && // check there is a text 547 pDraw->h < pText->h*1.5 && // and it is approx the same height 548 // and either upper or lower edge of pDraw is inside text's vertical range 549 ( ( pDraw->y >= pText->y && pDraw->y <= pText->y+pText->h ) || 550 ( pDraw->y+pDraw->h >= pText->y && pDraw->y+pDraw->h <= pText->y+pText->h ) 551 ) 552 ) 553 { 554 bInsertToParagraph = true; 555 fCurLineHeight = pDraw->h; 556 nCurLineElements = 1; 557 line_left = pDraw->x; 558 line_right = pDraw->x + pDraw->w; 559 // begin a new paragraph 560 pCurPara = nullptr; 561 // mark draw element as character 562 pDraw->isCharacter = true; 563 } 564 } 565 566 if( ! bInsertToParagraph ) 567 { 568 pCurPara = nullptr; 569 continue; 570 } 571 } 572 573 TextElement* pText = (*page_element)->dynCastAsTextElement(); 574 if( ! pText && pLink && ! pLink->Children.empty() ) 575 pText = pLink->Children.front()->dynCastAsTextElement(); 576 if( pText ) 577 { 578 Element* pGeo = pLink ? static_cast<Element*>(pLink) : 579 static_cast<Element*>(pText); 580 if( pCurPara ) 581 { 582 // there was already a text element, check for a new paragraph 583 if( nCurLineElements > 0 ) 584 { 585 // if the new text is significantly distant from the paragraph 586 // begin a new paragraph 587 if( pGeo->y > pCurPara->y + pCurPara->h + fCurLineHeight*0.5 ) 588 pCurPara = nullptr; // insert new paragraph 589 else if( pGeo->y > (pCurPara->y+pCurPara->h - fCurLineHeight*0.05) ) 590 { 591 // new paragraph if either the last line of the paragraph 592 // was significantly shorter than the paragraph as a whole 593 if( (line_right - line_left) < pCurPara->w*0.75 ) 594 pCurPara = nullptr; 595 // or the last line was significantly smaller than the column width 596 else if( (line_right - line_left) < column_width*0.75 ) 597 pCurPara = nullptr; 598 } 599 } 600 601 602 } 603 604 605 // update line height/width 606 if( pCurPara ) 607 { 608 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pGeo->h)/double(nCurLineElements+1); 609 nCurLineElements++; 610 if( pGeo->x < line_left ) 611 line_left = pGeo->x; 612 if( pGeo->x+pGeo->w > line_right ) 613 line_right = pGeo->x+pGeo->w; 614 } 615 else 616 { 617 fCurLineHeight = pGeo->h; 618 nCurLineElements = 1; 619 line_left = pGeo->x; 620 line_right = pGeo->x + pGeo->w; 621 } 622 } 623 624 625 // move element to current paragraph 626 if (! pCurPara ) // new paragraph, insert one 627 { 628 pCurPara = ElementFactory::createParagraphElement( nullptr ); 629 // set parent 630 pCurPara->Parent = &elem; 631 //insert new paragraph before current element 632 page_element = elem.Children.insert( page_element, std::unique_ptr<Element>(pCurPara) ); 633 // forward iterator to current element again 634 ++ page_element; 635 // update next_element which is now invalid 636 next_page_element = page_element; 637 ++ next_page_element; 638 } 639 Element* pCurEle = page_element->get(); 640 Element::setParent( page_element, pCurPara ); 641 OSL_ENSURE( !pText || pCurEle == pText || pCurEle == pLink, "paragraph child list in disorder" ); 642 if( pText || pDraw ) 643 pCurPara->updateGeometryWith( pCurEle ); 644 } 645 646 // process children 647 elem.applyToChildren(*this); 648 } 649 650 static bool isSpaces(TextElement* pTextElem) 651 { 652 for (sal_Int32 i = 0; i != pTextElem->Text.getLength(); ++i) { 653 if (pTextElem->Text[i] != ' ') { 654 return false; 655 } 656 } 657 return true; 658 } 659 660 void DrawXmlOptimizer::optimizeTextElements(Element& rParent) 661 { 662 if( rParent.Children.empty() ) // this should not happen 663 { 664 OSL_FAIL( "empty paragraph optimized" ); 665 return; 666 } 667 668 // concatenate child elements with same font id 669 auto next = rParent.Children.begin(); 670 auto it = next++; 671 672 while( next != rParent.Children.end() ) 673 { 674 bool bConcat = false; 675 TextElement* pCur = (*it)->dynCastAsTextElement(); 676 677 if( pCur ) 678 { 679 TextElement* pNext = (*next)->dynCastAsTextElement(); 680 bool isComplex = false; 681 OUString str(pCur->Text.toString()); 682 for(int i=0; i< str.getLength(); i++) 683 { 684 sal_Int16 nType = GetBreakIterator()->getScriptType( str, i ); 685 if (nType == css::i18n::ScriptType::COMPLEX) 686 isComplex = true; 687 } 688 bool bPara = strspn("ParagraphElement", typeid(rParent).name()); 689 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(&rParent); 690 if (bPara && pPara && isComplex) 691 pPara->bRtl = true; 692 if( pNext ) 693 { 694 const GraphicsContext& rCurGC = m_rProcessor.getGraphicsContext( pCur->GCId ); 695 const GraphicsContext& rNextGC = m_rProcessor.getGraphicsContext( pNext->GCId ); 696 697 // line and space optimization; works only in strictly horizontal mode 698 699 // concatenate consecutive text elements unless there is a 700 // font or text color change, leave a new span in that case 701 if( (pCur->FontId == pNext->FontId || isSpaces(pNext)) && 702 rCurGC.FillColor.Red == rNextGC.FillColor.Red && 703 rCurGC.FillColor.Green == rNextGC.FillColor.Green && 704 rCurGC.FillColor.Blue == rNextGC.FillColor.Blue && 705 rCurGC.FillColor.Alpha == rNextGC.FillColor.Alpha 706 ) 707 { 708 pCur->updateGeometryWith( pNext ); 709 // append text to current element 710 pCur->Text.append( pNext->Text ); 711 712 str = pCur->Text.toString(); 713 for(int i=0; i< str.getLength(); i++) 714 { 715 sal_Int16 nType = GetBreakIterator()->getScriptType( str, i ); 716 if (nType == css::i18n::ScriptType::COMPLEX) 717 isComplex = true; 718 } 719 if (bPara && pPara && isComplex) 720 pPara->bRtl = true; 721 // append eventual children to current element 722 // and clear children (else the children just 723 // appended to pCur would be destroyed) 724 pCur->Children.splice( pCur->Children.end(), pNext->Children ); 725 // get rid of the now useless element 726 rParent.Children.erase( next ); 727 bConcat = true; 728 } 729 } 730 } 731 else if( dynamic_cast<HyperlinkElement*>(it->get()) ) 732 optimizeTextElements( **it ); 733 if ( bConcat ) 734 next = it; 735 else 736 ++it; 737 ++next; 738 } 739 } 740 741 void DrawXmlOptimizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&) 742 { 743 elem.applyToChildren(*this); 744 } 745 746 747 void DrawXmlFinalizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 748 { 749 // xxx TODO copied from DrawElement 750 const GraphicsContext& rGC = m_rProcessor.getGraphicsContext(elem.GCId ); 751 752 PropertyMap aProps; 753 aProps[ "style:family" ] = "graphic"; 754 aProps[ "style:parent-style-name" ] = "standard"; 755 // generate standard graphic style if necessary 756 m_rStyleContainer.getStandardStyleId( "graphic" ); 757 758 PropertyMap aGCProps; 759 if (elem.Action & PATH_STROKE) 760 { 761 double scale = GetAverageTransformationScale(rGC.Transformation); 762 if (rGC.DashArray.size() < 2) 763 { 764 aGCProps[ "draw:stroke" ] = "solid"; 765 } 766 else 767 { 768 PropertyMap props; 769 FillDashStyleProps(props, rGC.DashArray, scale); 770 StyleContainer::Style style("draw:stroke-dash", std::move(props)); 771 772 aGCProps[ "draw:stroke" ] = "dash"; 773 aGCProps[ "draw:stroke-dash" ] = 774 m_rStyleContainer.getStyleName( 775 m_rStyleContainer.getStyleId(style)); 776 } 777 778 aGCProps[ "svg:stroke-color" ] = getColorString(rGC.LineColor); 779 if (rGC.LineColor.Alpha != 1.0) 780 aGCProps["svg:stroke-opacity"] = getPercentString(rGC.LineColor.Alpha * 100.0); 781 aGCProps[ "svg:stroke-width" ] = convertPixelToUnitString(rGC.LineWidth * scale); 782 aGCProps[ "draw:stroke-linejoin" ] = rGC.GetLineJoinString(); 783 aGCProps[ "svg:stroke-linecap" ] = rGC.GetLineCapString(); 784 } 785 else 786 { 787 aGCProps[ "draw:stroke" ] = "none"; 788 } 789 790 // TODO(F1): check whether stuff could be emulated by gradient/bitmap/hatch 791 if( elem.Action & (PATH_FILL | PATH_EOFILL) ) 792 { 793 aGCProps[ "draw:fill" ] = "solid"; 794 aGCProps[ "draw:fill-color" ] = getColorString(rGC.FillColor); 795 if (rGC.FillColor.Alpha != 1.0) 796 aGCProps["draw:opacity"] = getPercentString(rGC.FillColor.Alpha * 100.0); 797 } 798 else 799 { 800 aGCProps[ "draw:fill" ] = "none"; 801 } 802 803 StyleContainer::Style aStyle( "style:style", std::move(aProps) ); 804 StyleContainer::Style aSubStyle( "style:graphic-properties", std::move(aGCProps) ); 805 aStyle.SubStyles.push_back( &aSubStyle ); 806 807 elem.StyleId = m_rStyleContainer.getStyleId( aStyle ); 808 } 809 810 void DrawXmlFinalizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& ) 811 { 812 } 813 814 static void SetFontsizeProperties(PropertyMap& props, double fontSize) 815 { 816 OUString aFSize = OUString::number(fontSize * 72 / PDFI_OUTDEV_RESOLUTION) + "pt"; 817 props["fo:font-size"] = aFSize; 818 props["style:font-size-asian"] = aFSize; 819 props["style:font-size-complex"] = aFSize; 820 } 821 822 void DrawXmlFinalizer::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 823 { 824 const FontAttributes& rFont = m_rProcessor.getFont( elem.FontId ); 825 PropertyMap aProps; 826 aProps[ "style:family" ] = "text"; 827 828 PropertyMap aFontProps; 829 830 // family name 831 // TODO: tdf#143095: use system font name rather than PSName 832 SAL_INFO("sdext.pdfimport", "The font used in xml is: " << rFont.familyName); 833 aFontProps[ "fo:font-family" ] = rFont.familyName; 834 aFontProps[ "style:font-family-asia" ] = rFont.familyName; 835 aFontProps[ "style:font-family-complex" ] = rFont.familyName; 836 837 // bold 838 aFontProps[ "fo:font-weight" ] = rFont.fontWeight; 839 aFontProps[ "style:font-weight-asian" ] = rFont.fontWeight; 840 aFontProps[ "style:font-weight-complex" ] = rFont.fontWeight; 841 842 // italic 843 if( rFont.isItalic ) 844 { 845 aFontProps[ "fo:font-style" ] = "italic"; 846 aFontProps[ "style:font-style-asian" ] = "italic"; 847 aFontProps[ "style:font-style-complex" ] = "italic"; 848 } 849 850 // underline 851 if( rFont.isUnderline ) 852 { 853 aFontProps[ "style:text-underline-style" ] = "solid"; 854 aFontProps[ "style:text-underline-width" ] = "auto"; 855 aFontProps[ "style:text-underline-color" ] = "font-color"; 856 } 857 858 // outline 859 if( rFont.isOutline ) 860 aFontProps[ "style:text-outline" ] = "true"; 861 862 // size 863 SetFontsizeProperties(aFontProps, rFont.size); 864 865 // color 866 const GraphicsContext& rGC = m_rProcessor.getGraphicsContext( elem.GCId ); 867 aFontProps[ "fo:color" ] = getColorString( rFont.isOutline ? rGC.LineColor : rGC.FillColor ); 868 869 // scale 870 double fRotate, fShearX; 871 basegfx::B2DTuple aScale, aTranslation; 872 rGC.Transformation.decompose(aScale, aTranslation, fRotate, fShearX); 873 double textScale = 100 * aScale.getX() / aScale.getY(); 874 if (((textScale >= 1) && (textScale <= 99)) || 875 ((textScale >= 101) && (textScale <= 999))) 876 { 877 aFontProps[ "style:text-scale" ] = getPercentString(textScale); 878 } 879 880 StyleContainer::Style aStyle( "style:style", std::move(aProps) ); 881 StyleContainer::Style aSubStyle( "style:text-properties", std::move(aFontProps) ); 882 aStyle.SubStyles.push_back( &aSubStyle ); 883 elem.StyleId = m_rStyleContainer.getStyleId( aStyle ); 884 } 885 886 void DrawXmlFinalizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 887 { 888 889 PropertyMap aProps; 890 aProps[ "style:family" ] = "paragraph"; 891 // generate standard paragraph style if necessary 892 m_rStyleContainer.getStandardStyleId( "paragraph" ); 893 894 PropertyMap aParProps; 895 896 aParProps[ "fo:text-align"] = "start"; 897 if (elem.bRtl) 898 aParProps[ "style:writing-mode"] = "rl-tb"; 899 else 900 aParProps[ "style:writing-mode"] = "lr-tb"; 901 902 StyleContainer::Style aStyle( "style:style", std::move(aProps) ); 903 StyleContainer::Style aSubStyle( "style:paragraph-properties", std::move(aParProps) ); 904 aStyle.SubStyles.push_back( &aSubStyle ); 905 906 elem.StyleId = m_rStyleContainer.getStyleId( aStyle ); 907 908 elem.applyToChildren(*this); 909 } 910 911 void DrawXmlFinalizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&) 912 { 913 PropertyMap props1; 914 props1[ "style:family" ] = "graphic"; 915 props1[ "style:parent-style-name" ] = "standard"; 916 // generate standard graphic style if necessary 917 m_rStyleContainer.getStandardStyleId( "graphic" ); 918 919 PropertyMap aGCProps; 920 921 aGCProps[ "draw:stroke" ] = "none"; 922 aGCProps[ "draw:fill" ] = "none"; 923 aGCProps[ "draw:auto-grow-height" ] = "true"; 924 aGCProps[ "draw:auto-grow-width" ] = "true"; 925 aGCProps[ "draw:textarea-horizontal-align" ] = "left"; 926 aGCProps[ "draw:textarea-vertical-align" ] = "top"; 927 aGCProps[ "fo:min-height"] = "0cm"; 928 aGCProps[ "fo:min-width"] = "0cm"; 929 aGCProps[ "fo:padding-top" ] = "0cm"; 930 aGCProps[ "fo:padding-left" ] = "0cm"; 931 aGCProps[ "fo:padding-right" ] = "0cm"; 932 aGCProps[ "fo:padding-bottom" ] = "0cm"; 933 934 StyleContainer::Style style1( "style:style", std::move(props1) ); 935 StyleContainer::Style subStyle1( "style:graphic-properties", std::move(aGCProps) ); 936 style1.SubStyles.push_back(&subStyle1); 937 938 elem.StyleId = m_rStyleContainer.getStyleId(style1); 939 940 if (elem.IsForText) 941 { 942 PropertyMap props2; 943 props2["style:family"] = "paragraph"; 944 945 PropertyMap textProps; 946 SetFontsizeProperties(textProps, elem.FontSize); 947 948 StyleContainer::Style style2("style:style", std::move(props2)); 949 StyleContainer::Style subStyle2("style:text-properties", std::move(textProps)); 950 style2.SubStyles.push_back(&subStyle2); 951 elem.TextStyleId = m_rStyleContainer.getStyleId(style2); 952 } 953 954 elem.applyToChildren(*this); 955 } 956 957 void DrawXmlFinalizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& ) 958 { 959 } 960 961 void DrawXmlFinalizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 962 { 963 if( m_rProcessor.getStatusIndicator().is() ) 964 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber ); 965 966 // transform from pixel to mm 967 double page_width = convPx2mm( elem.w ), page_height = convPx2mm( elem.h ); 968 969 // calculate page margins out of the relevant children (paragraphs) 970 elem.TopMargin = elem.h; 971 elem.BottomMargin = 0; 972 elem.LeftMargin = elem.w; 973 elem.RightMargin = 0; 974 975 for( const auto& rxChild : elem.Children ) 976 { 977 if( rxChild->x < elem.LeftMargin ) 978 elem.LeftMargin = rxChild->x; 979 if( rxChild->y < elem.TopMargin ) 980 elem.TopMargin = rxChild->y; 981 if( rxChild->x + rxChild->w > elem.RightMargin ) 982 elem.RightMargin = (rxChild->x + rxChild->w); 983 if( rxChild->y + rxChild->h > elem.BottomMargin ) 984 elem.BottomMargin = (rxChild->y + rxChild->h); 985 } 986 987 // transform margins to mm 988 double left_margin = convPx2mm( elem.LeftMargin ); 989 double right_margin = convPx2mm( elem.RightMargin ); 990 double top_margin = convPx2mm( elem.TopMargin ); 991 double bottom_margin = convPx2mm( elem.BottomMargin ); 992 993 // round left/top margin to nearest mm 994 left_margin = rtl_math_round( left_margin, 0, rtl_math_RoundingMode_Floor ); 995 top_margin = rtl_math_round( top_margin, 0, rtl_math_RoundingMode_Floor ); 996 // round (fuzzy) right/bottom margin to nearest cm 997 right_margin = rtl_math_round( right_margin, right_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor ); 998 bottom_margin = rtl_math_round( bottom_margin, bottom_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor ); 999 1000 // set reasonable default in case of way too large margins 1001 // e.g. no paragraph case 1002 if( left_margin > page_width/2.0 - 10 ) 1003 left_margin = 10; 1004 if( right_margin > page_width/2.0 - 10 ) 1005 right_margin = 10; 1006 if( top_margin > page_height/2.0 - 10 ) 1007 top_margin = 10; 1008 if( bottom_margin > page_height/2.0 - 10 ) 1009 bottom_margin = 10; 1010 1011 // catch the weird cases 1012 if( left_margin < 0 ) 1013 left_margin = 0; 1014 if( right_margin < 0 ) 1015 right_margin = 0; 1016 if( top_margin < 0 ) 1017 top_margin = 0; 1018 if( bottom_margin < 0 ) 1019 bottom_margin = 0; 1020 1021 // widely differing margins are unlikely to be correct 1022 if( right_margin > left_margin*1.5 ) 1023 right_margin = left_margin; 1024 1025 elem.LeftMargin = convmm2Px( left_margin ); 1026 elem.RightMargin = convmm2Px( right_margin ); 1027 elem.TopMargin = convmm2Px( top_margin ); 1028 elem.BottomMargin = convmm2Px( bottom_margin ); 1029 1030 // get styles for paragraphs 1031 PropertyMap aPageProps; 1032 PropertyMap aPageLayoutProps; 1033 aPageLayoutProps[ "fo:margin-top" ] = unitMMString( top_margin ); 1034 aPageLayoutProps[ "fo:margin-bottom" ] = unitMMString( bottom_margin ); 1035 aPageLayoutProps[ "fo:margin-left" ] = unitMMString( left_margin ); 1036 aPageLayoutProps[ "fo:margin-right" ] = unitMMString( right_margin ); 1037 aPageLayoutProps[ "fo:page-width" ] = unitMMString( page_width ); 1038 aPageLayoutProps[ "fo:page-height" ] = unitMMString( page_height ); 1039 aPageLayoutProps[ "style:print-orientation" ]= elem.w < elem.h ? std::u16string_view(u"portrait") : std::u16string_view(u"landscape"); 1040 aPageLayoutProps[ "style:writing-mode" ]= "lr-tb"; 1041 1042 StyleContainer::Style aStyle( "style:page-layout", std::move(aPageProps)); 1043 StyleContainer::Style aSubStyle( "style:page-layout-properties", std::move(aPageLayoutProps)); 1044 aStyle.SubStyles.push_back(&aSubStyle); 1045 sal_Int32 nPageStyle = m_rStyleContainer.impl_getStyleId( aStyle, false ); 1046 1047 // create master page 1048 OUString aMasterPageLayoutName = m_rStyleContainer.getStyleName( nPageStyle ); 1049 aPageProps[ "style:page-layout-name" ] = aMasterPageLayoutName; 1050 1051 StyleContainer::Style aMPStyle( "style:master-page", std::move(aPageProps)); 1052 1053 elem.StyleId = m_rStyleContainer.impl_getStyleId( aMPStyle,false ); 1054 1055 // create styles for children 1056 elem.applyToChildren(*this); 1057 } 1058 1059 void DrawXmlFinalizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 1060 { 1061 elem.applyToChildren(*this); 1062 } 1063 1064 } 1065 1066 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 1067
