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 #include <sal/config.h> 21 #include <sal/log.hxx> 22 #include <string_view> 23 24 #include <pdfiprocessor.hxx> 25 #include <xmlemitter.hxx> 26 #include <pdfihelper.hxx> 27 #include <imagecontainer.hxx> 28 #include "style.hxx" 29 #include "writertreevisiting.hxx" 30 #include <genericelements.hxx> 31 32 #include <basegfx/polygon/b2dpolypolygontools.hxx> 33 #include <osl/diagnose.h> 34 35 using namespace ::com::sun::star; 36 37 namespace pdfi 38 { 39 40 void WriterXmlEmitter::visit( HyperlinkElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 41 { 42 if( elem.Children.empty() ) 43 return; 44 45 const char* pType = dynamic_cast<DrawElement*>(elem.Children.front().get()) ? "draw:a" : "text:a"; 46 47 PropertyMap aProps; 48 aProps[ "xlink:type" ] = "simple"; 49 aProps[ "xlink:href" ] = elem.URI; 50 aProps[ "office:target-frame-name" ] = "_blank"; 51 aProps[ "xlink:show" ] = "new"; 52 53 m_rEmitContext.rEmitter.beginTag( pType, aProps ); 54 auto this_it = elem.Children.begin(); 55 while( this_it != elem.Children.end() && this_it->get() != &elem ) 56 { 57 (*this_it)->visitedBy( *this, this_it ); 58 ++this_it; 59 } 60 m_rEmitContext.rEmitter.endTag( pType ); 61 } 62 63 void WriterXmlEmitter::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 64 { 65 if( elem.Text.isEmpty() ) 66 return; 67 68 PropertyMap aProps; 69 if( elem.StyleId != -1 ) 70 { 71 aProps[ OUString( "text:style-name" ) ] = 72 m_rEmitContext.rStyles.getStyleName( elem.StyleId ); 73 } 74 75 m_rEmitContext.rEmitter.beginTag( "text:span", aProps ); 76 m_rEmitContext.rEmitter.write( elem.Text.makeStringAndClear() ); 77 auto this_it = elem.Children.begin(); 78 while( this_it != elem.Children.end() && this_it->get() != &elem ) 79 { 80 (*this_it)->visitedBy( *this, this_it ); 81 ++this_it; 82 } 83 84 m_rEmitContext.rEmitter.endTag( "text:span" ); 85 } 86 87 void WriterXmlEmitter::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 88 { 89 PropertyMap aProps; 90 if( elem.StyleId != -1 ) 91 { 92 aProps[ "text:style-name" ] = m_rEmitContext.rStyles.getStyleName( elem.StyleId ); 93 } 94 const char* pTagType = "text:p"; 95 if( elem.Type == ParagraphElement::Headline ) 96 pTagType = "text:h"; 97 m_rEmitContext.rEmitter.beginTag( pTagType, aProps ); 98 99 auto this_it = elem.Children.begin(); 100 while( this_it != elem.Children.end() && this_it->get() != &elem ) 101 { 102 (*this_it)->visitedBy( *this, this_it ); 103 ++this_it; 104 } 105 106 m_rEmitContext.rEmitter.endTag( pTagType ); 107 } 108 109 void WriterXmlEmitter::fillFrameProps( DrawElement& rElem, 110 PropertyMap& rProps, 111 const EmitContext& rEmitContext ) 112 { 113 double rel_x = rElem.x, rel_y = rElem.y; 114 115 // find anchor type by recursing though parents 116 Element* pAnchor = &rElem; 117 ParagraphElement* pParaElt = nullptr; 118 PageElement* pPage = nullptr; 119 while ((pAnchor = pAnchor->Parent)) 120 { 121 if ((pParaElt = dynamic_cast<ParagraphElement*>(pAnchor))) 122 break; 123 if ((pPage = dynamic_cast<PageElement*>(pAnchor))) 124 break; 125 } 126 if( pAnchor ) 127 { 128 if (pParaElt) 129 { 130 rProps[ "text:anchor-type" ] = rElem.isCharacter 131 ? std::u16string_view(u"character") : std::u16string_view(u"paragraph"); 132 } 133 else 134 { 135 assert(pPage); // guaranteed by the while loop above 136 rProps[ "text:anchor-type" ] = "page"; 137 rProps[ "text:anchor-page-number" ] = OUString::number(pPage->PageNumber); 138 } 139 rel_x -= pAnchor->x; 140 rel_y -= pAnchor->y; 141 } 142 143 rProps[ "draw:z-index" ] = OUString::number( rElem.ZOrder ); 144 rProps[ "draw:style-name"] = rEmitContext.rStyles.getStyleName( rElem.StyleId ); 145 rProps[ "svg:width" ] = convertPixelToUnitString( rElem.w ); 146 rProps[ "svg:height" ] = convertPixelToUnitString( rElem.h ); 147 148 const GraphicsContext& rGC = 149 rEmitContext.rProcessor.getGraphicsContext( rElem.GCId ); 150 if( rGC.Transformation.isIdentity() ) 151 { 152 if( !rElem.isCharacter ) 153 { 154 rProps[ "svg:x" ] = convertPixelToUnitString( rel_x ); 155 rProps[ "svg:y" ] = convertPixelToUnitString( rel_y ); 156 } 157 } 158 else 159 { 160 basegfx::B2DTuple aScale, aTranslation; 161 double fRotate, fShearX; 162 163 rGC.Transformation.decompose( aScale, aTranslation, fRotate, fShearX ); 164 165 OUStringBuffer aBuf( 256 ); 166 167 // TODO(F2): general transformation case missing; if implemented, note 168 // that ODF rotation is oriented the other way 169 170 // build transformation string 171 if (rElem.MirrorVertical) 172 { 173 // At some point, rElem.h may start arriving positive, 174 // so use robust adjusting math 175 rel_y -= std::abs(rElem.h); 176 if (!aBuf.isEmpty()) 177 aBuf.append(' '); 178 aBuf.append("scale( 1.0 -1.0 )"); 179 } 180 if( fShearX != 0.0 ) 181 { 182 aBuf.append( "skewX( " ); 183 aBuf.append( fShearX ); 184 aBuf.append( " )" ); 185 } 186 if( fRotate != 0.0 ) 187 { 188 if( !aBuf.isEmpty() ) 189 aBuf.append( ' ' ); 190 aBuf.append( "rotate( " ); 191 aBuf.append( -fRotate ); 192 aBuf.append( " )" ); 193 194 } 195 if( ! rElem.isCharacter ) 196 { 197 if( !aBuf.isEmpty() ) 198 aBuf.append( ' ' ); 199 aBuf.append( "translate( " ); 200 aBuf.append( convertPixelToUnitString( rel_x ) ); 201 aBuf.append( ' ' ); 202 aBuf.append( convertPixelToUnitString( rel_y ) ); 203 aBuf.append( " )" ); 204 } 205 206 rProps[ "draw:transform" ] = aBuf.makeStringAndClear(); 207 } 208 } 209 210 void WriterXmlEmitter::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 211 { 212 if( elem.Children.empty() ) 213 return; 214 215 bool bTextBox = (dynamic_cast<ParagraphElement*>(elem.Children.front().get()) != nullptr); 216 PropertyMap aFrameProps; 217 fillFrameProps( elem, aFrameProps, m_rEmitContext ); 218 m_rEmitContext.rEmitter.beginTag( "draw:frame", aFrameProps ); 219 if( bTextBox ) 220 m_rEmitContext.rEmitter.beginTag( "draw:text-box", PropertyMap() ); 221 222 auto this_it = elem.Children.begin(); 223 while( this_it != elem.Children.end() && this_it->get() != &elem ) 224 { 225 (*this_it)->visitedBy( *this, this_it ); 226 ++this_it; 227 } 228 229 if( bTextBox ) 230 m_rEmitContext.rEmitter.endTag( "draw:text-box" ); 231 m_rEmitContext.rEmitter.endTag( "draw:frame" ); 232 } 233 234 void WriterXmlEmitter::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 235 { 236 elem.updateGeometry(); 237 /* note: 238 * aw recommends using 100dth of mm in all respects since the xml import 239 * (a) is buggy (see issue 37213) 240 * (b) is optimized for 100dth of mm and does not scale itself then, 241 * this does not gain us speed but makes for smaller rounding errors since 242 * the xml importer coordinates are integer based 243 */ 244 for (sal_uInt32 i = 0; i< elem.PolyPoly.count(); i++) 245 { 246 basegfx::B2DPolygon b2dPolygon = elem.PolyPoly.getB2DPolygon( i ); 247 248 for ( sal_uInt32 j = 0; j< b2dPolygon.count(); j++ ) 249 { 250 basegfx::B2DPoint point; 251 basegfx::B2DPoint nextPoint; 252 point = b2dPolygon.getB2DPoint( j ); 253 254 basegfx::B2DPoint prevPoint = b2dPolygon.getPrevControlPoint( j ) ; 255 256 point.setX( convPx2mmPrec2( point.getX() )*100.0 ); 257 point.setY( convPx2mmPrec2( point.getY() )*100.0 ); 258 259 if ( b2dPolygon.isPrevControlPointUsed( j ) ) 260 { 261 prevPoint.setX( convPx2mmPrec2( prevPoint.getX() )*100.0 ); 262 prevPoint.setY( convPx2mmPrec2( prevPoint.getY() )*100.0 ); 263 } 264 265 if ( b2dPolygon.isNextControlPointUsed( j ) ) 266 { 267 nextPoint = b2dPolygon.getNextControlPoint( j ) ; 268 nextPoint.setX( convPx2mmPrec2( nextPoint.getX() )*100.0 ); 269 nextPoint.setY( convPx2mmPrec2( nextPoint.getY() )*100.0 ); 270 } 271 272 b2dPolygon.setB2DPoint( j, point ); 273 274 if ( b2dPolygon.isPrevControlPointUsed( j ) ) 275 b2dPolygon.setPrevControlPoint( j , prevPoint ) ; 276 277 if ( b2dPolygon.isNextControlPointUsed( j ) ) 278 b2dPolygon.setNextControlPoint( j , nextPoint ) ; 279 } 280 281 elem.PolyPoly.setB2DPolygon( i, b2dPolygon ); 282 } 283 284 PropertyMap aProps; 285 fillFrameProps( elem, aProps, m_rEmitContext ); 286 OUStringBuffer aBuf( 64 ); 287 aBuf.append( "0 0 " ); 288 aBuf.append( convPx2mmPrec2(elem.w)*100.0 ); 289 aBuf.append( ' ' ); 290 aBuf.append( convPx2mmPrec2(elem.h)*100.0 ); 291 aProps[ "svg:viewBox" ] = aBuf.makeStringAndClear(); 292 aProps[ "svg:d" ] = basegfx::utils::exportToSvgD( elem.PolyPoly, true, true, false ); 293 294 m_rEmitContext.rEmitter.beginTag( "draw:path", aProps ); 295 m_rEmitContext.rEmitter.endTag( "draw:path" ); 296 } 297 298 void WriterXmlEmitter::visit( ImageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 299 { 300 PropertyMap aImageProps; 301 m_rEmitContext.rEmitter.beginTag( "draw:image", aImageProps ); 302 m_rEmitContext.rEmitter.beginTag( "office:binary-data", PropertyMap() ); 303 m_rEmitContext.rImages.writeBase64EncodedStream( elem.Image, m_rEmitContext); 304 m_rEmitContext.rEmitter.endTag( "office:binary-data" ); 305 m_rEmitContext.rEmitter.endTag( "draw:image" ); 306 } 307 308 void WriterXmlEmitter::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 309 { 310 if( m_rEmitContext.xStatusIndicator.is() ) 311 m_rEmitContext.xStatusIndicator->setValue( elem.PageNumber ); 312 313 auto this_it = elem.Children.begin(); 314 while( this_it != elem.Children.end() && this_it->get() != &elem ) 315 { 316 (*this_it)->visitedBy( *this, this_it ); 317 ++this_it; 318 } 319 } 320 321 void WriterXmlEmitter::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&) 322 { 323 m_rEmitContext.rEmitter.beginTag( "office:body", PropertyMap() ); 324 m_rEmitContext.rEmitter.beginTag( "office:text", PropertyMap() ); 325 326 for( const auto& rxChild : elem.Children ) 327 { 328 PageElement* pPage = dynamic_cast<PageElement*>(rxChild.get()); 329 if( pPage ) 330 { 331 // emit only page anchored objects 332 // currently these are only DrawElement types 333 for( auto child_it = pPage->Children.begin(); child_it != pPage->Children.end(); ++child_it ) 334 { 335 if( dynamic_cast<DrawElement*>(child_it->get()) != nullptr ) 336 (*child_it)->visitedBy( *this, child_it ); 337 } 338 } 339 } 340 341 // do not emit page anchored objects, they are emitted before 342 // (must precede all pages in writer document) currently these are 343 // only DrawElement types 344 for( auto it = elem.Children.begin(); it != elem.Children.end(); ++it ) 345 { 346 if( dynamic_cast<DrawElement*>(it->get()) == nullptr ) 347 (*it)->visitedBy( *this, it ); 348 } 349 350 m_rEmitContext.rEmitter.endTag( "office:text" ); 351 m_rEmitContext.rEmitter.endTag( "office:body" ); 352 } 353 354 355 void WriterXmlOptimizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& ) 356 { 357 } 358 359 void WriterXmlOptimizer::visit( TextElement&, const std::list< std::unique_ptr<Element> >::const_iterator&) 360 { 361 } 362 363 void WriterXmlOptimizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 364 { 365 elem.applyToChildren(*this); 366 } 367 368 void WriterXmlOptimizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& ) 369 { 370 } 371 372 void WriterXmlOptimizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& elemIt ) 373 { 374 /* note: optimize two consecutive PolyPolyElements that 375 * have the same path but one of which is a stroke while 376 * the other is a fill 377 */ 378 if( !elem.Parent ) 379 return; 380 // find following PolyPolyElement in parent's children list 381 if( elemIt == elem.Parent->Children.end() ) 382 return; 383 auto next_it = elemIt; 384 ++next_it; 385 if( next_it == elem.Parent->Children.end() ) 386 return; 387 388 PolyPolyElement* pNext = dynamic_cast<PolyPolyElement*>(next_it->get()); 389 if( !pNext || pNext->PolyPoly != elem.PolyPoly ) 390 return; 391 392 const GraphicsContext& rNextGC = 393 m_rProcessor.getGraphicsContext( pNext->GCId ); 394 const GraphicsContext& rThisGC = 395 m_rProcessor.getGraphicsContext( elem.GCId ); 396 397 if( !(rThisGC.BlendMode == rNextGC.BlendMode && 398 rThisGC.Flatness == rNextGC.Flatness && 399 rThisGC.Transformation == rNextGC.Transformation && 400 rThisGC.Clip == rNextGC.Clip && 401 pNext->Action == PATH_STROKE && 402 (elem.Action == PATH_FILL || elem.Action == PATH_EOFILL)) ) 403 return; 404 405 GraphicsContext aGC = rThisGC; 406 aGC.LineJoin = rNextGC.LineJoin; 407 aGC.LineCap = rNextGC.LineCap; 408 aGC.LineWidth = rNextGC.LineWidth; 409 aGC.MiterLimit= rNextGC.MiterLimit; 410 aGC.DashArray = rNextGC.DashArray; 411 aGC.LineColor = rNextGC.LineColor; 412 elem.GCId = m_rProcessor.getGCId( aGC ); 413 414 elem.Action |= pNext->Action; 415 416 elem.Children.splice( elem.Children.end(), pNext->Children ); 417 elem.Parent->Children.erase(next_it); 418 } 419 420 void WriterXmlOptimizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt) 421 { 422 optimizeTextElements( elem ); 423 424 elem.applyToChildren(*this); 425 426 if( !(elem.Parent && rParentIt != elem.Parent->Children.end()) ) 427 return; 428 429 // find if there is a previous paragraph that might be a heading for this one 430 auto prev = rParentIt; 431 ParagraphElement* pPrevPara = nullptr; 432 while( prev != elem.Parent->Children.begin() ) 433 { 434 --prev; 435 pPrevPara = dynamic_cast< ParagraphElement* >(prev->get()); 436 if( pPrevPara ) 437 { 438 /* What constitutes a heading ? current hints are: 439 * - one line only 440 * - not too far away from this paragraph (two heading height max ?) 441 * - font larger or bold 442 * this is of course incomplete 443 * FIXME: improve hints for heading 444 */ 445 // check for single line 446 if( pPrevPara->isSingleLined( m_rProcessor ) ) 447 { 448 double head_line_height = pPrevPara->getLineHeight( m_rProcessor ); 449 if( pPrevPara->y + pPrevPara->h + 2*head_line_height > elem.y ) 450 { 451 // check for larger font 452 if( head_line_height > elem.getLineHeight( m_rProcessor ) ) 453 { 454 pPrevPara->Type = ParagraphElement::Headline; 455 } 456 else 457 { 458 // check whether text of pPrevPara is bold (at least first text element) 459 // and this para is not bold (ditto) 460 TextElement* pPrevText = pPrevPara->getFirstTextChild(); 461 TextElement* pThisText = elem.getFirstTextChild(); 462 if( pPrevText && pThisText ) 463 { 464 const FontAttributes& rPrevFont = m_rProcessor.getFont( pPrevText->FontId ); 465 const FontAttributes& rThisFont = m_rProcessor.getFont( pThisText->FontId ); 466 if ( (rPrevFont.fontWeight == u"600" || 467 rPrevFont.fontWeight == u"bold" || 468 rPrevFont.fontWeight == u"800" || 469 rPrevFont.fontWeight == u"900" ) && 470 (rThisFont.fontWeight == u"600" || 471 rThisFont.fontWeight == u"bold" || 472 rThisFont.fontWeight == u"800" || 473 rThisFont.fontWeight == u"900" ) ) 474 { 475 pPrevPara->Type = ParagraphElement::Headline; 476 } 477 } 478 } 479 } 480 } 481 break; 482 } 483 } 484 } 485 486 void WriterXmlOptimizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 487 { 488 if( m_rProcessor.getStatusIndicator().is() ) 489 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber ); 490 491 // resolve hyperlinks 492 elem.resolveHyperlinks(); 493 494 elem.resolveFontStyles( m_rProcessor ); // underlines and such 495 496 // FIXME: until hyperlinks and font effects are adjusted for 497 // geometrical search handle them before sorting 498 PDFIProcessor::sortElements( &elem ); 499 500 // find paragraphs in text 501 ParagraphElement* pCurPara = nullptr; 502 std::list< std::unique_ptr<Element> >::iterator page_element, next_page_element; 503 next_page_element = elem.Children.begin(); 504 double fCurLineHeight = 0.0; // average height of text items in current para 505 int nCurLineElements = 0; // number of line contributing elements in current para 506 double line_left = elem.w, line_right = 0.0; 507 double column_width = elem.w*0.75; // estimate text width 508 // TODO: guess columns 509 while( next_page_element != elem.Children.end() ) 510 { 511 page_element = next_page_element++; 512 ParagraphElement* pPagePara = dynamic_cast<ParagraphElement*>(page_element->get()); 513 if( pPagePara ) 514 { 515 pCurPara = pPagePara; 516 // adjust line height and text items 517 fCurLineHeight = 0.0; 518 nCurLineElements = 0; 519 for( const auto& rxChild : pCurPara->Children ) 520 { 521 TextElement* pTestText = rxChild->dynCastAsTextElement(); 522 if( pTestText ) 523 { 524 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pTestText->h)/double(nCurLineElements+1); 525 nCurLineElements++; 526 } 527 } 528 continue; 529 } 530 531 HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(page_element->get()); 532 DrawElement* pDraw = dynamic_cast<DrawElement*>(page_element->get()); 533 if( ! pDraw && pLink && ! pLink->Children.empty() ) 534 pDraw = dynamic_cast<DrawElement*>(pLink->Children.front().get() ); 535 if( pDraw ) 536 { 537 // insert small drawing objects as character, else leave them page bound 538 539 bool bInsertToParagraph = false; 540 // first check if this is either inside the paragraph 541 if( pCurPara && pDraw->y < pCurPara->y + pCurPara->h ) 542 { 543 if( pDraw->h < fCurLineHeight * 1.5 ) 544 { 545 bInsertToParagraph = true; 546 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pDraw->h)/double(nCurLineElements+1); 547 nCurLineElements++; 548 // mark draw element as character 549 pDraw->isCharacter = true; 550 } 551 } 552 // or perhaps the draw element begins a new paragraph 553 else if( next_page_element != elem.Children.end() ) 554 { 555 TextElement* pText = (*next_page_element)->dynCastAsTextElement(); 556 if( ! pText ) 557 { 558 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(next_page_element->get()); 559 if( pPara && ! pPara->Children.empty() ) 560 pText = pPara->Children.front()->dynCastAsTextElement(); 561 } 562 if( pText && // check there is a text 563 pDraw->h < pText->h*1.5 && // and it is approx the same height 564 // and either upper or lower edge of pDraw is inside text's vertical range 565 ( ( pDraw->y >= pText->y && pDraw->y <= pText->y+pText->h ) || 566 ( pDraw->y+pDraw->h >= pText->y && pDraw->y+pDraw->h <= pText->y+pText->h ) 567 ) 568 ) 569 { 570 bInsertToParagraph = true; 571 fCurLineHeight = pDraw->h; 572 nCurLineElements = 1; 573 line_left = pDraw->x; 574 line_right = pDraw->x + pDraw->w; 575 // begin a new paragraph 576 pCurPara = nullptr; 577 // mark draw element as character 578 pDraw->isCharacter = true; 579 } 580 } 581 582 if( ! bInsertToParagraph ) 583 { 584 pCurPara = nullptr; 585 continue; 586 } 587 } 588 589 TextElement* pText = (*page_element)->dynCastAsTextElement(); 590 if( ! pText && pLink && ! pLink->Children.empty() ) 591 pText = pLink->Children.front()->dynCastAsTextElement(); 592 if( pText ) 593 { 594 Element* pGeo = pLink ? static_cast<Element*>(pLink) : 595 static_cast<Element*>(pText); 596 if( pCurPara ) 597 { 598 // there was already a text element, check for a new paragraph 599 if( nCurLineElements > 0 ) 600 { 601 // if the new text is significantly distant from the paragraph 602 // begin a new paragraph 603 if( pGeo->y > pCurPara->y+pCurPara->h + fCurLineHeight*0.5 ) 604 pCurPara = nullptr; // insert new paragraph 605 else if( pGeo->y > (pCurPara->y+pCurPara->h - fCurLineHeight*0.05) ) 606 { 607 // new paragraph if either the last line of the paragraph 608 // was significantly shorter than the paragraph as a whole 609 if( (line_right - line_left) < pCurPara->w*0.75 ) 610 pCurPara = nullptr; 611 // or the last line was significantly smaller than the column width 612 else if( (line_right - line_left) < column_width*0.75 ) 613 pCurPara = nullptr; 614 } 615 } 616 } 617 // update line height/width 618 if( pCurPara ) 619 { 620 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pGeo->h)/double(nCurLineElements+1); 621 nCurLineElements++; 622 if( pGeo->x < line_left ) 623 line_left = pGeo->x; 624 if( pGeo->x+pGeo->w > line_right ) 625 line_right = pGeo->x+pGeo->w; 626 } 627 else 628 { 629 fCurLineHeight = pGeo->h; 630 nCurLineElements = 1; 631 line_left = pGeo->x; 632 line_right = pGeo->x + pGeo->w; 633 } 634 } 635 636 // move element to current paragraph 637 if( ! pCurPara ) // new paragraph, insert one 638 { 639 pCurPara = ElementFactory::createParagraphElement( nullptr ); 640 // set parent 641 pCurPara->Parent = &elem; 642 //insert new paragraph before current element 643 page_element = elem.Children.insert( page_element, std::unique_ptr<Element>(pCurPara) ); 644 // forward iterator to current element again 645 ++ page_element; 646 // update next_element which is now invalid 647 next_page_element = page_element; 648 ++ next_page_element; 649 } 650 Element* pCurEle = page_element->get(); 651 Element::setParent( page_element, pCurPara ); 652 OSL_ENSURE( !pText || pCurEle == pText || pCurEle == pLink, "paragraph child list in disorder" ); 653 if( pText || pDraw ) 654 pCurPara->updateGeometryWith( pCurEle ); 655 } 656 657 // process children 658 elem.applyToChildren(*this); 659 660 // find possible header and footer 661 checkHeaderAndFooter( elem ); 662 } 663 664 void WriterXmlOptimizer::checkHeaderAndFooter( PageElement& rElem ) 665 { 666 /* indicators for a header: 667 * - single line paragraph at top of page (inside 15% page height) 668 * - at least lineheight above the next paragraph 669 * 670 * indicators for a footer likewise: 671 * - single line paragraph at bottom of page (inside 15% page height) 672 * - at least lineheight below the previous paragraph 673 */ 674 675 auto isParagraphElement = [](std::unique_ptr<Element>& rxChild) -> bool { 676 return dynamic_cast<ParagraphElement*>(rxChild.get()) != nullptr; 677 }; 678 679 // detect header 680 // Note: the following assumes that the pages' children have been 681 // sorted geometrically 682 auto it = std::find_if(rElem.Children.begin(), rElem.Children.end(), isParagraphElement); 683 if (it != rElem.Children.end()) 684 { 685 ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**it); 686 if( rPara.y+rPara.h < rElem.h*0.15 && rPara.isSingleLined( m_rProcessor ) ) 687 { 688 auto next_it = it; 689 ParagraphElement* pNextPara = nullptr; 690 while( ++next_it != rElem.Children.end() && pNextPara == nullptr ) 691 { 692 pNextPara = dynamic_cast<ParagraphElement*>(next_it->get()); 693 } 694 if( pNextPara && pNextPara->y > rPara.y+rPara.h*2 ) 695 { 696 rElem.HeaderElement = std::move(*it); 697 rPara.Parent = nullptr; 698 rElem.Children.erase( it ); 699 } 700 } 701 } 702 703 // detect footer 704 auto rit = std::find_if(rElem.Children.rbegin(), rElem.Children.rend(), isParagraphElement); 705 if (rit == rElem.Children.rend()) 706 return; 707 708 ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**rit); 709 if( !(rPara.y > rElem.h*0.85 && rPara.isSingleLined( m_rProcessor )) ) 710 return; 711 712 std::list< std::unique_ptr<Element> >::reverse_iterator next_it = rit; 713 ParagraphElement* pNextPara = nullptr; 714 while( ++next_it != rElem.Children.rend() && pNextPara == nullptr ) 715 { 716 pNextPara = dynamic_cast<ParagraphElement*>(next_it->get()); 717 } 718 if( pNextPara && pNextPara->y < rPara.y-rPara.h*2 ) 719 { 720 rElem.FooterElement = std::move(*rit); 721 rPara.Parent = nullptr; 722 rElem.Children.erase( std::next(rit).base() ); 723 } 724 } 725 726 void WriterXmlOptimizer::optimizeTextElements(Element& rParent) 727 { 728 if( rParent.Children.empty() ) // this should not happen 729 { 730 OSL_FAIL( "empty paragraph optimized" ); 731 return; 732 } 733 734 // concatenate child elements with same font id 735 auto next = rParent.Children.begin(); 736 auto it = next++; 737 FrameElement* pFrame = dynamic_cast<FrameElement*>(rParent.Parent); 738 bool bRotatedFrame = false; 739 if( pFrame ) 740 { 741 const GraphicsContext& rFrameGC = m_rProcessor.getGraphicsContext( pFrame->GCId ); 742 if( rFrameGC.isRotatedOrSkewed() ) 743 bRotatedFrame = true; 744 } 745 while( next != rParent.Children.end() ) 746 { 747 bool bConcat = false; 748 TextElement* pCur = (*it)->dynCastAsTextElement(); 749 if( pCur ) 750 { 751 TextElement* pNext = dynamic_cast<TextElement*>(next->get()); 752 if( pNext ) 753 { 754 const GraphicsContext& rCurGC = m_rProcessor.getGraphicsContext( pCur->GCId ); 755 const GraphicsContext& rNextGC = m_rProcessor.getGraphicsContext( pNext->GCId ); 756 757 // line and space optimization; works only in strictly horizontal mode 758 759 if( !bRotatedFrame 760 && ! rCurGC.isRotatedOrSkewed() 761 && ! rNextGC.isRotatedOrSkewed() 762 && ! pNext->Text.isEmpty() 763 && pNext->Text[0] != ' ' 764 && ! pCur->Text.isEmpty() 765 && pCur->Text[pCur->Text.getLength() - 1] != ' ' 766 ) 767 { 768 // check for new line in paragraph 769 if( pNext->y > pCur->y+pCur->h ) 770 { 771 // new line begins 772 // check whether a space would should be inserted or a hyphen removed 773 sal_Unicode aLastCode = pCur->Text[pCur->Text.getLength() - 1]; 774 if( aLastCode == '-' 775 || aLastCode == 0x2010 776 || (aLastCode >= 0x2012 && aLastCode <= 0x2015) 777 || aLastCode == 0xff0d 778 ) 779 { 780 // cut a hyphen 781 pCur->Text.setLength( pCur->Text.getLength()-1 ); 782 } 783 // append a space unless there is a non breaking hyphen 784 else if( aLastCode != 0x2011 ) 785 { 786 pCur->Text.append( ' ' ); 787 } 788 } 789 else // we're continuing the same line 790 { 791 // check whether a space would should be inserted 792 // check for a small horizontal offset 793 if( pCur->x + pCur->w + pNext->h*0.15 < pNext->x ) 794 { 795 pCur->Text.append( ' ' ); 796 } 797 } 798 } 799 // concatenate consecutive text elements unless there is a 800 // font or text color or matrix change, leave a new span in that case 801 if( pCur->FontId == pNext->FontId && 802 rCurGC.FillColor.Red == rNextGC.FillColor.Red && 803 rCurGC.FillColor.Green == rNextGC.FillColor.Green && 804 rCurGC.FillColor.Blue == rNextGC.FillColor.Blue && 805 rCurGC.FillColor.Alpha == rNextGC.FillColor.Alpha && 806 rCurGC.Transformation == rNextGC.Transformation 807 ) 808 { 809 pCur->updateGeometryWith( pNext ); 810 // append text to current element 811 pCur->Text.append( pNext->Text ); 812 // append eventual children to current element 813 // and clear children (else the children just 814 // appended to pCur would be destroyed) 815 pCur->Children.splice( pCur->Children.end(), pNext->Children ); 816 // get rid of the now useless element 817 rParent.Children.erase( next ); 818 bConcat = true; 819 } 820 } 821 } 822 else if( dynamic_cast<HyperlinkElement*>(it->get()) ) 823 optimizeTextElements( **it ); 824 if( bConcat ) 825 { 826 next = it; 827 ++next; 828 } 829 else 830 { 831 ++it; 832 ++next; 833 } 834 } 835 } 836 837 void WriterXmlOptimizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&) 838 { 839 elem.applyToChildren(*this); 840 } 841 842 843 void WriterXmlFinalizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 844 { 845 // xxx TODO copied from DrawElement 846 const GraphicsContext& rGC = m_rProcessor.getGraphicsContext(elem.GCId ); 847 PropertyMap aProps; 848 aProps[ "style:family" ] = "graphic"; 849 850 PropertyMap aGCProps; 851 if (elem.Action & PATH_STROKE) 852 { 853 double scale = GetAverageTransformationScale(rGC.Transformation); 854 if (rGC.DashArray.size() < 2) 855 { 856 aGCProps[ "draw:stroke" ] = "solid"; 857 } 858 else 859 { 860 PropertyMap props; 861 FillDashStyleProps(props, rGC.DashArray, scale); 862 StyleContainer::Style style("draw:stroke-dash", std::move(props)); 863 864 aGCProps[ "draw:stroke" ] = "dash"; 865 aGCProps[ "draw:stroke-dash" ] = 866 m_rStyleContainer.getStyleName( 867 m_rStyleContainer.getStyleId(style)); 868 } 869 870 aGCProps[ "svg:stroke-color" ] = getColorString(rGC.LineColor); 871 aGCProps[ "svg:stroke-width" ] = convertPixelToUnitString(rGC.LineWidth * scale); 872 aGCProps[ "draw:stroke-linejoin" ] = rGC.GetLineJoinString(); 873 aGCProps[ "svg:stroke-linecap" ] = rGC.GetLineCapString(); 874 } 875 else 876 { 877 aGCProps[ "draw:stroke" ] = "none"; 878 } 879 880 // TODO(F1): check whether stuff could be emulated by gradient/bitmap/hatch 881 if( elem.Action & (PATH_FILL | PATH_EOFILL) ) 882 { 883 aGCProps[ "draw:fill" ] = "solid"; 884 aGCProps[ "draw:fill-color" ] = getColorString( rGC.FillColor ); 885 } 886 else 887 { 888 aGCProps[ "draw:fill" ] = "none"; 889 } 890 891 StyleContainer::Style aStyle( "style:style", std::move(aProps) ); 892 StyleContainer::Style aSubStyle( "style:graphic-properties", std::move(aGCProps) ); 893 aStyle.SubStyles.push_back( &aSubStyle ); 894 895 elem.StyleId = m_rStyleContainer.getStyleId( aStyle ); 896 } 897 898 void WriterXmlFinalizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& ) 899 { 900 } 901 902 void WriterXmlFinalizer::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 903 { 904 const FontAttributes& rFont = m_rProcessor.getFont( elem.FontId ); 905 PropertyMap aProps; 906 aProps[ "style:family" ] = "text"; 907 908 PropertyMap aFontProps; 909 910 // family name 911 // TODO: tdf#143095: use system font name rather than PSName 912 SAL_INFO("sdext.pdfimport", "The font used in xml is: " << rFont.familyName); 913 aFontProps[ "fo:font-family" ] = rFont.familyName; 914 aFontProps[ "style:font-family-asia" ] = rFont.familyName; 915 aFontProps[ "style:font-family-complex" ] = rFont.familyName; 916 917 // bold 918 aFontProps[ "fo:font-weight" ] = rFont.fontWeight; 919 aFontProps[ "style:font-weight-asian" ] = rFont.fontWeight; 920 aFontProps[ "style:font-weight-complex" ] = rFont.fontWeight; 921 922 // italic 923 if( rFont.isItalic ) 924 { 925 aFontProps[ "fo:font-style" ] = "italic"; 926 aFontProps[ "style:font-style-asian" ] = "italic"; 927 aFontProps[ "style:font-style-complex" ] = "italic"; 928 } 929 930 // underline 931 if( rFont.isUnderline ) 932 { 933 aFontProps[ "style:text-underline-style" ] = "solid"; 934 aFontProps[ "style:text-underline-width" ] = "auto"; 935 aFontProps[ "style:text-underline-color" ] = "font-color"; 936 } 937 938 // outline 939 if( rFont.isOutline ) 940 aFontProps[ "style:text-outline" ] = "true"; 941 942 // size 943 OUString aFSize = OUString::number( rFont.size*72/PDFI_OUTDEV_RESOLUTION ) + "pt"; 944 aFontProps[ "fo:font-size" ] = aFSize; 945 aFontProps[ "style:font-size-asian" ] = aFSize; 946 aFontProps[ "style:font-size-complex" ] = aFSize; 947 948 // color 949 const GraphicsContext& rGC = m_rProcessor.getGraphicsContext( elem.GCId ); 950 aFontProps[ "fo:color" ] = getColorString( rFont.isOutline ? rGC.LineColor : rGC.FillColor ); 951 952 StyleContainer::Style aStyle( "style:style", std::move(aProps) ); 953 StyleContainer::Style aSubStyle( "style:text-properties", std::move(aFontProps) ); 954 aStyle.SubStyles.push_back( &aSubStyle ); 955 elem.StyleId = m_rStyleContainer.getStyleId( aStyle ); 956 } 957 958 void WriterXmlFinalizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt ) 959 { 960 PropertyMap aParaProps; 961 962 if( elem.Parent ) 963 { 964 // check for center alignment 965 // criterion: paragraph is small relative to parent and distributed around its center 966 double p_x = elem.Parent->x; 967 double p_w = elem.Parent->w; 968 969 PageElement* pPage = dynamic_cast<PageElement*>(elem.Parent); 970 if( pPage ) 971 { 972 p_x += pPage->LeftMargin; 973 p_w -= pPage->LeftMargin+pPage->RightMargin; 974 } 975 bool bIsCenter = false; 976 if( elem.w < ( p_w/2) ) 977 { 978 double delta = elem.w/4; 979 // allow very small paragraphs to deviate a little more 980 // relative to parent's center 981 if( elem.w < p_w/8 ) 982 delta = elem.w; 983 if( fabs( elem.x+elem.w/2 - ( p_x+ p_w/2) ) < delta || 984 (pPage && fabs( elem.x+elem.w/2 - (pPage->x + pPage->w/2) ) < delta) ) 985 { 986 bIsCenter = true; 987 aParaProps[ "fo:text-align" ] = "center"; 988 } 989 } 990 if( ! bIsCenter && elem.x > p_x + p_w/10 ) 991 { 992 // indent 993 OUStringBuffer aBuf( 32 ); 994 aBuf.append( convPx2mm( elem.x - p_x ) ); 995 aBuf.append( "mm" ); 996 aParaProps[ "fo:margin-left" ] = aBuf.makeStringAndClear(); 997 } 998 999 // check whether to leave some space to next paragraph 1000 // find whether there is a next paragraph 1001 auto it = rParentIt; 1002 const ParagraphElement* pNextPara = nullptr; 1003 while( ++it != elem.Parent->Children.end() && ! pNextPara ) 1004 pNextPara = dynamic_cast< const ParagraphElement* >(it->get()); 1005 if( pNextPara ) 1006 { 1007 if( pNextPara->y - (elem.y+elem.h) > convmm2Px( 10 ) ) 1008 { 1009 OUStringBuffer aBuf( 32 ); 1010 aBuf.append( convPx2mm( pNextPara->y - (elem.y+elem.h) ) ); 1011 aBuf.append( "mm" ); 1012 aParaProps[ "fo:margin-bottom" ] = aBuf.makeStringAndClear(); 1013 } 1014 } 1015 } 1016 1017 if( ! aParaProps.empty() ) 1018 { 1019 PropertyMap aProps; 1020 aProps[ "style:family" ] = "paragraph"; 1021 StyleContainer::Style aStyle( "style:style", std::move(aProps) ); 1022 StyleContainer::Style aSubStyle( "style:paragraph-properties", std::move(aParaProps) ); 1023 aStyle.SubStyles.push_back( &aSubStyle ); 1024 elem.StyleId = m_rStyleContainer.getStyleId( aStyle ); 1025 } 1026 1027 elem.applyToChildren(*this); 1028 } 1029 1030 void WriterXmlFinalizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&) 1031 { 1032 PropertyMap aProps; 1033 aProps[ "style:family" ] = "graphic"; 1034 1035 PropertyMap aGCProps; 1036 1037 aGCProps[ "draw:stroke" ] = "none"; 1038 aGCProps[ "draw:fill" ] = "none"; 1039 aGCProps[ "draw:auto-grow-height" ] = "true"; 1040 aGCProps[ "draw:auto-grow-width" ] = "true"; 1041 aGCProps[ "draw:textarea-horizontal-align" ] = "left"; 1042 aGCProps[ "draw:textarea-vertical-align" ] = "top"; 1043 aGCProps[ "fo:min-height"] = "0cm"; 1044 aGCProps[ "fo:min-width"] = "0cm"; 1045 aGCProps[ "fo:padding-top" ] = "0cm"; 1046 aGCProps[ "fo:padding-left" ] = "0cm"; 1047 aGCProps[ "fo:padding-right" ] = "0cm"; 1048 aGCProps[ "fo:padding-bottom" ] = "0cm"; 1049 1050 StyleContainer::Style aStyle( "style:style", std::move(aProps) ); 1051 StyleContainer::Style aSubStyle( "style:graphic-properties", std::move(aGCProps) ); 1052 aStyle.SubStyles.push_back( &aSubStyle ); 1053 1054 elem.StyleId = m_rStyleContainer.getStyleId( aStyle ); 1055 elem.applyToChildren(*this); 1056 } 1057 1058 void WriterXmlFinalizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& ) 1059 { 1060 } 1061 1062 void WriterXmlFinalizer::setFirstOnPage( ParagraphElement& rElem, 1063 StyleContainer& rStyles, 1064 const OUString& rMasterPageName ) 1065 { 1066 PropertyMap aProps; 1067 if( rElem.StyleId != -1 ) 1068 { 1069 const PropertyMap* pProps = rStyles.getProperties( rElem.StyleId ); 1070 if( pProps ) 1071 aProps = *pProps; 1072 } 1073 1074 aProps[ "style:family" ] = "paragraph"; 1075 aProps[ "style:master-page-name" ] = rMasterPageName; 1076 1077 if( rElem.StyleId != -1 ) 1078 rElem.StyleId = rStyles.setProperties( rElem.StyleId, std::move(aProps) ); 1079 else 1080 { 1081 StyleContainer::Style aStyle( "style:style", std::move(aProps) ); 1082 rElem.StyleId = rStyles.getStyleId( aStyle ); 1083 } 1084 } 1085 1086 void WriterXmlFinalizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 1087 { 1088 if( m_rProcessor.getStatusIndicator().is() ) 1089 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber ); 1090 1091 // transform from pixel to mm 1092 double page_width = convPx2mm( elem.w ), page_height = convPx2mm( elem.h ); 1093 1094 // calculate page margins out of the relevant children (paragraphs) 1095 elem.TopMargin = elem.h; 1096 elem.BottomMargin = 0; 1097 elem.LeftMargin = elem.w; 1098 elem.RightMargin = 0; 1099 // first element should be a paragraph 1100 ParagraphElement* pFirstPara = nullptr; 1101 for( const auto& rxChild : elem.Children ) 1102 { 1103 if( dynamic_cast<ParagraphElement*>( rxChild.get() ) ) 1104 { 1105 if( rxChild->x < elem.LeftMargin ) 1106 elem.LeftMargin = rxChild->x; 1107 if( rxChild->y < elem.TopMargin ) 1108 elem.TopMargin = rxChild->y; 1109 if( rxChild->x + rxChild->w > elem.w - elem.RightMargin ) 1110 elem.RightMargin = elem.w - (rxChild->x + rxChild->w); 1111 if( rxChild->y + rxChild->h > elem.h - elem.BottomMargin ) 1112 elem.BottomMargin = elem.h - (rxChild->y + rxChild->h); 1113 if( ! pFirstPara ) 1114 pFirstPara = dynamic_cast<ParagraphElement*>( rxChild.get() ); 1115 } 1116 } 1117 if( elem.HeaderElement && elem.HeaderElement->y < elem.TopMargin ) 1118 elem.TopMargin = elem.HeaderElement->y; 1119 if( elem.FooterElement && elem.FooterElement->y+elem.FooterElement->h > elem.h - elem.BottomMargin ) 1120 elem.BottomMargin = elem.h - (elem.FooterElement->y + elem.FooterElement->h); 1121 1122 // transform margins to mm 1123 double left_margin = convPx2mm( elem.LeftMargin ); 1124 double right_margin = convPx2mm( elem.RightMargin ); 1125 double top_margin = convPx2mm( elem.TopMargin ); 1126 double bottom_margin = convPx2mm( elem.BottomMargin ); 1127 if( ! pFirstPara ) 1128 { 1129 // use default page margins 1130 left_margin = 10; 1131 right_margin = 10; 1132 top_margin = 10; 1133 bottom_margin = 10; 1134 } 1135 1136 // round left/top margin to nearest mm 1137 left_margin = rtl_math_round( left_margin, 0, rtl_math_RoundingMode_Floor ); 1138 top_margin = rtl_math_round( top_margin, 0, rtl_math_RoundingMode_Floor ); 1139 // round (fuzzy) right/bottom margin to nearest cm 1140 right_margin = rtl_math_round( right_margin, right_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor ); 1141 bottom_margin = rtl_math_round( bottom_margin, bottom_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor ); 1142 1143 // set reasonable default in case of way too large margins 1144 // e.g. no paragraph case 1145 if( left_margin > page_width/2.0 - 10 ) 1146 left_margin = 10; 1147 if( right_margin > page_width/2.0 - 10 ) 1148 right_margin = 10; 1149 if( top_margin > page_height/2.0 - 10 ) 1150 top_margin = 10; 1151 if( bottom_margin > page_height/2.0 - 10 ) 1152 bottom_margin = 10; 1153 1154 // catch the weird cases 1155 if( left_margin < 0 ) 1156 left_margin = 0; 1157 if( right_margin < 0 ) 1158 right_margin = 0; 1159 if( top_margin < 0 ) 1160 top_margin = 0; 1161 if( bottom_margin < 0 ) 1162 bottom_margin = 0; 1163 1164 // widely differing margins are unlikely to be correct 1165 if( right_margin > left_margin*1.5 ) 1166 right_margin = left_margin; 1167 1168 elem.LeftMargin = convmm2Px( left_margin ); 1169 elem.RightMargin = convmm2Px( right_margin ); 1170 elem.TopMargin = convmm2Px( top_margin ); 1171 elem.BottomMargin = convmm2Px( bottom_margin ); 1172 1173 // get styles for paragraphs 1174 PropertyMap aPageProps; 1175 PropertyMap aPageLayoutProps; 1176 aPageLayoutProps[ "fo:page-width" ] = unitMMString( page_width ); 1177 aPageLayoutProps[ "fo:page-height" ] = unitMMString( page_height ); 1178 aPageLayoutProps[ "style:print-orientation" ] 1179 = elem.w < elem.h ? std::u16string_view(u"portrait") : std::u16string_view(u"landscape"); 1180 aPageLayoutProps[ "fo:margin-top" ] = unitMMString( top_margin ); 1181 aPageLayoutProps[ "fo:margin-bottom" ] = unitMMString( bottom_margin ); 1182 aPageLayoutProps[ "fo:margin-left" ] = unitMMString( left_margin ); 1183 aPageLayoutProps[ "fo:margin-right" ] = unitMMString( right_margin ); 1184 aPageLayoutProps[ "style:writing-mode" ]= "lr-tb"; 1185 1186 StyleContainer::Style aStyle( "style:page-layout", std::move(aPageProps)); 1187 StyleContainer::Style aSubStyle( "style:page-layout-properties", std::move(aPageLayoutProps)); 1188 aStyle.SubStyles.push_back(&aSubStyle); 1189 sal_Int32 nPageStyle = m_rStyleContainer.impl_getStyleId( aStyle, false ); 1190 1191 // create master page 1192 OUString aMasterPageLayoutName = m_rStyleContainer.getStyleName( nPageStyle ); 1193 aPageProps[ "style:page-layout-name" ] = aMasterPageLayoutName; 1194 StyleContainer::Style aMPStyle( "style:master-page", std::move(aPageProps) ); 1195 StyleContainer::Style aHeaderStyle( "style:header", PropertyMap() ); 1196 StyleContainer::Style aFooterStyle( "style:footer", PropertyMap() ); 1197 if( elem.HeaderElement ) 1198 { 1199 elem.HeaderElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() ); 1200 aHeaderStyle.ContainedElement = elem.HeaderElement.get(); 1201 aMPStyle.SubStyles.push_back( &aHeaderStyle ); 1202 } 1203 if( elem.FooterElement ) 1204 { 1205 elem.FooterElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() ); 1206 aFooterStyle.ContainedElement = elem.FooterElement.get(); 1207 aMPStyle.SubStyles.push_back( &aFooterStyle ); 1208 } 1209 elem.StyleId = m_rStyleContainer.impl_getStyleId( aMPStyle,false ); 1210 1211 1212 OUString aMasterPageName = m_rStyleContainer.getStyleName( elem.StyleId ); 1213 1214 // create styles for children 1215 elem.applyToChildren(*this); 1216 1217 // no paragraph or other elements before the first paragraph 1218 if( ! pFirstPara ) 1219 { 1220 pFirstPara = ElementFactory::createParagraphElement( nullptr ); 1221 pFirstPara->Parent = &elem; 1222 elem.Children.push_front( std::unique_ptr<Element>(pFirstPara) ); 1223 } 1224 setFirstOnPage(*pFirstPara, m_rStyleContainer, aMasterPageName); 1225 } 1226 1227 void WriterXmlFinalizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& ) 1228 { 1229 elem.applyToChildren(*this); 1230 } 1231 1232 } 1233 1234 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 1235
