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