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 <config_crypto.h> 22 23 #include <sal/types.h> 24 25 #include <math.h> 26 #include <algorithm> 27 #include <string_view> 28 29 #include <lcms2.h> 30 31 #include <basegfx/matrix/b2dhommatrix.hxx> 32 #include <basegfx/polygon/b2dpolygon.hxx> 33 #include <basegfx/polygon/b2dpolygontools.hxx> 34 #include <basegfx/polygon/b2dpolypolygon.hxx> 35 #include <basegfx/polygon/b2dpolypolygoncutter.hxx> 36 #include <basegfx/polygon/b2dpolypolygontools.hxx> 37 #include <memory> 38 #include <com/sun/star/io/XOutputStream.hpp> 39 #include <com/sun/star/util/URL.hpp> 40 #include <com/sun/star/util/URLTransformer.hpp> 41 #include <comphelper/processfactory.hxx> 42 #include <comphelper/string.hxx> 43 #include <cppuhelper/implbase.hxx> 44 #include <i18nlangtag/languagetag.hxx> 45 #include <o3tl/numeric.hxx> 46 #include <o3tl/safeint.hxx> 47 #include <o3tl/temporary.hxx> 48 #include <officecfg/Office/Common.hxx> 49 #include <osl/file.hxx> 50 #include <osl/thread.h> 51 #include <rtl/digest.h> 52 #include <rtl/uri.hxx> 53 #include <rtl/ustrbuf.hxx> 54 #include <sal/log.hxx> 55 #include <svl/urihelper.hxx> 56 #include <tools/fract.hxx> 57 #include <tools/stream.hxx> 58 #include <tools/helpers.hxx> 59 #include <tools/urlobj.hxx> 60 #include <tools/zcodec.hxx> 61 #include <svl/cryptosign.hxx> 62 #include <vcl/bitmapex.hxx> 63 #include <vcl/canvastools.hxx> 64 #include <vcl/cvtgrf.hxx> 65 #include <vcl/fontcharmap.hxx> 66 #include <vcl/glyphitemcache.hxx> 67 #include <vcl/kernarray.hxx> 68 #include <vcl/lineinfo.hxx> 69 #include <vcl/metric.hxx> 70 #include <vcl/mnemonic.hxx> 71 #include <vcl/settings.hxx> 72 #include <strhelper.hxx> 73 #include <vcl/svapp.hxx> 74 #include <vcl/virdev.hxx> 75 #include <vcl/filter/pdfdocument.hxx> 76 #include <vcl/filter/PngImageReader.hxx> 77 #include <comphelper/hash.hxx> 78 79 #include <svdata.hxx> 80 #include <bitmap/BitmapWriteAccess.hxx> 81 #include <fontsubset.hxx> 82 #include <font/EmphasisMark.hxx> 83 #include <font/PhysicalFontFace.hxx> 84 #include <salgdi.hxx> 85 #include <textlayout.hxx> 86 #include <textlineinfo.hxx> 87 #include <impglyphitem.hxx> 88 #include <pdf/XmpMetadata.hxx> 89 #include <pdf/objectcopier.hxx> 90 #include <pdf/pdfwriter_impl.hxx> 91 #include <pdf/PdfConfig.hxx> 92 #include <o3tl/sorted_vector.hxx> 93 94 using namespace::com::sun::star; 95 96 static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION"); 97 98 namespace 99 { 100 101 constexpr sal_Int32 nLog10Divisor = 3; 102 constexpr double fDivisor = 1000.0; 103 104 constexpr double pixelToPoint(double px) 105 { 106 return px / fDivisor; 107 } 108 109 constexpr sal_Int32 pointToPixel(double pt) 110 { 111 return sal_Int32(pt * fDivisor); 112 } 113 114 void appendHex(sal_Int8 nInt, OStringBuffer& rBuffer) 115 { 116 static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', 117 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 118 rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] ); 119 rBuffer.append( pHexDigits[ nInt & 15 ] ); 120 } 121 122 void appendName( std::u16string_view rStr, OStringBuffer& rBuffer ) 123 { 124 // FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1 125 // I guess than when reading the #xx sequence it will count for a single character. 126 OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) ); 127 int nLen = aStr.getLength(); 128 for( int i = 0; i < nLen; i++ ) 129 { 130 /* #i16920# PDF recommendation: output UTF8, any byte 131 * outside the interval [33(=ASCII'!');126(=ASCII'~')] 132 * should be escaped hexadecimal 133 * for the sake of ghostscript which also reads PDF 134 * but has a narrower acceptance rate we only pass 135 * alphanumerics and '-' literally. 136 */ 137 if( (aStr[i] >= 'A' && aStr[i] <= 'Z' ) || 138 (aStr[i] >= 'a' && aStr[i] <= 'z' ) || 139 (aStr[i] >= '0' && aStr[i] <= '9' ) || 140 aStr[i] == '-' ) 141 { 142 rBuffer.append( aStr[i] ); 143 } 144 else 145 { 146 rBuffer.append( '#' ); 147 appendHex( static_cast<sal_Int8>(aStr[i]), rBuffer ); 148 } 149 } 150 } 151 152 void appendName( const char* pStr, OStringBuffer& rBuffer ) 153 { 154 // FIXME i59651 see above 155 while( pStr && *pStr ) 156 { 157 if( (*pStr >= 'A' && *pStr <= 'Z' ) || 158 (*pStr >= 'a' && *pStr <= 'z' ) || 159 (*pStr >= '0' && *pStr <= '9' ) || 160 *pStr == '-' ) 161 { 162 rBuffer.append( *pStr ); 163 } 164 else 165 { 166 rBuffer.append( '#' ); 167 appendHex( static_cast<sal_Int8>(*pStr), rBuffer ); 168 } 169 pStr++; 170 } 171 } 172 173 //used only to emit encoded passwords 174 void appendLiteralString( const char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer ) 175 { 176 while( nLength ) 177 { 178 switch( *pStr ) 179 { 180 case '\n' : 181 rBuffer.append( "\\n" ); 182 break; 183 case '\r' : 184 rBuffer.append( "\\r" ); 185 break; 186 case '\t' : 187 rBuffer.append( "\\t" ); 188 break; 189 case '\b' : 190 rBuffer.append( "\\b" ); 191 break; 192 case '\f' : 193 rBuffer.append( "\\f" ); 194 break; 195 case '(' : 196 case ')' : 197 case '\\' : 198 rBuffer.append( "\\" ); 199 rBuffer.append( static_cast<char>(*pStr) ); 200 break; 201 default: 202 rBuffer.append( static_cast<char>(*pStr) ); 203 break; 204 } 205 pStr++; 206 nLength--; 207 } 208 } 209 210 /* 211 * Convert a string before using it. 212 * 213 * This string conversion function is needed because the destination name 214 * in a PDF file seen through an Internet browser should be 215 * specially crafted, in order to be used directly by the browser. 216 * In this way the fragment part of a hyperlink to a PDF file (e.g. something 217 * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the 218 * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called 219 * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf 220 * and go to named destination thefragment using default zoom'. 221 * The conversion is needed because in case of a fragment in the form: Slide%201 222 * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201 223 * using this conversion, in both the generated named destinations, fragment and GoToR 224 * destination. 225 * 226 * The names for destinations are name objects and so they don't need to be encrypted 227 * even though they expose the content of PDF file (e.g. guessing the PDF content from the 228 * destination name). 229 * 230 * Further limitation: it is advisable to use standard ASCII characters for 231 * OOo bookmarks. 232 */ 233 void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer ) 234 { 235 const sal_Unicode* pStr = rString.getStr(); 236 sal_Int32 nLen = rString.getLength(); 237 for( int i = 0; i < nLen; i++ ) 238 { 239 sal_Unicode aChar = pStr[i]; 240 if( (aChar >= '0' && aChar <= '9' ) || 241 (aChar >= 'a' && aChar <= 'z' ) || 242 (aChar >= 'A' && aChar <= 'Z' ) || 243 aChar == '-' ) 244 { 245 rBuffer.append(static_cast<char>(aChar)); 246 } 247 else 248 { 249 sal_Int8 aValueHigh = sal_Int8(aChar >> 8); 250 if(aValueHigh > 0) 251 appendHex( aValueHigh, rBuffer ); 252 appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer ); 253 } 254 } 255 } 256 257 } // end anonymous namespace 258 259 namespace vcl 260 { 261 const sal_uInt8 PDFWriterImpl::s_nPadString[32] = 262 { 263 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, 264 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A 265 }; 266 267 namespace 268 { 269 270 template < class GEOMETRY > 271 GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject ) 272 { 273 GEOMETRY aPoint; 274 if ( MapUnit::MapPixel == _rSource.GetMapUnit() ) 275 { 276 aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest ); 277 } 278 else 279 { 280 aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest ); 281 } 282 return aPoint; 283 } 284 285 } // end anonymous namespace 286 287 void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer) 288 { 289 rBuffer.append( "FEFF" ); 290 const sal_Unicode* pStr = rString.getStr(); 291 sal_Int32 nLen = rString.getLength(); 292 for( int i = 0; i < nLen; i++ ) 293 { 294 sal_Unicode aChar = pStr[i]; 295 appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer ); 296 appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer ); 297 } 298 } 299 300 void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl ) 301 { 302 /* #i80258# previously we use appendName here 303 however we need a slightly different coding scheme than the normal 304 name encoding for field names 305 */ 306 const OUString& rName = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text; 307 OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) ); 308 int nLen = aStr.getLength(); 309 310 OStringBuffer aBuffer( rName.getLength()+64 ); 311 for( int i = 0; i < nLen; i++ ) 312 { 313 /* #i16920# PDF recommendation: output UTF8, any byte 314 * outside the interval [32(=ASCII' ');126(=ASCII'~')] 315 * should be escaped hexadecimal 316 */ 317 if( aStr[i] >= 32 && aStr[i] <= 126 ) 318 aBuffer.append( aStr[i] ); 319 else 320 { 321 aBuffer.append( '#' ); 322 appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer ); 323 } 324 } 325 326 OString aFullName( aBuffer.makeStringAndClear() ); 327 328 /* #i82785# create hierarchical fields down to the for each dot in i_rName */ 329 sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0; 330 OString aPartialName; 331 OString aDomain; 332 do 333 { 334 nLastTokenIndex = nTokenIndex; 335 aPartialName = aFullName.getToken( 0, '.', nTokenIndex ); 336 if( nTokenIndex != -1 ) 337 { 338 // find or create a hierarchical field 339 // first find the fully qualified name up to this field 340 aDomain = aFullName.copy( 0, nTokenIndex-1 ); 341 std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain ); 342 if( it == m_aFieldNameMap.end() ) 343 { 344 // create new hierarchy field 345 sal_Int32 nNewWidget = m_aWidgets.size(); 346 m_aWidgets.emplace_back( ); 347 m_aWidgets[nNewWidget].m_nObject = createObject(); 348 m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy; 349 m_aWidgets[nNewWidget].m_aName = aPartialName; 350 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; 351 m_aFieldNameMap[aDomain] = nNewWidget; 352 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; 353 if( nLastTokenIndex > 0 ) 354 { 355 // this field is not a root field and 356 // needs to be inserted to its parent 357 OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) ); 358 it = m_aFieldNameMap.find( aParentDomain ); 359 OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" ); 360 if( it != m_aFieldNameMap.end() ) 361 { 362 OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" ); 363 if( it->second < sal_Int32(m_aWidgets.size()) ) 364 { 365 PDFWidget& rParentField( m_aWidgets[it->second] ); 366 rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject ); 367 rParentField.m_aKidsIndex.push_back( nNewWidget ); 368 m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject; 369 } 370 } 371 } 372 } 373 else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy ) 374 { 375 // this is invalid, someone tries to have a terminal field as parent 376 // example: a button with the name foo.bar exists and 377 // another button is named foo.bar.no 378 // workaround: put the second terminal field as much up in the hierarchy as 379 // necessary to have a non-terminal field as parent (or none at all) 380 // since it->second already is terminal, we just need to use its parent 381 aDomain.clear(); 382 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); 383 if( nLastTokenIndex > 0 ) 384 { 385 aDomain = aFullName.copy( 0, nLastTokenIndex-1 ); 386 aFullName = aDomain + "." + aPartialName; 387 } 388 else 389 aFullName = aPartialName; 390 break; 391 } 392 } 393 } while( nTokenIndex != -1 ); 394 395 // insert widget into its hierarchy field 396 if( !aDomain.isEmpty() ) 397 { 398 std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain ); 399 if( it != m_aFieldNameMap.end() ) 400 { 401 OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" ); 402 if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() ) 403 { 404 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject; 405 m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject); 406 m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex ); 407 } 408 } 409 } 410 411 if( aPartialName.isEmpty() ) 412 { 413 // how funny, an empty field name 414 if( i_rControl.getType() == PDFWriter::RadioButton ) 415 { 416 aPartialName = "RadioGroup" + 417 OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup ); 418 } 419 else 420 aPartialName = OString( "Widget" ); 421 } 422 423 if( ! m_aContext.AllowDuplicateFieldNames ) 424 { 425 std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName ); 426 427 if( it != m_aFieldNameMap.end() ) // not unique 428 { 429 std::unordered_map< OString, sal_Int32 >::const_iterator check_it; 430 OString aTry; 431 sal_Int32 nTry = 2; 432 do 433 { 434 OStringBuffer aUnique( aFullName.getLength() + 16 ); 435 aUnique.append( aFullName ); 436 aUnique.append( '_' ); 437 aUnique.append( nTry++ ); 438 aTry = aUnique.makeStringAndClear(); 439 check_it = m_aFieldNameMap.find( aTry ); 440 } while( check_it != m_aFieldNameMap.end() ); 441 aFullName = aTry; 442 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; 443 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); 444 } 445 else 446 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; 447 } 448 449 // finally 450 m_aWidgets[i_nWidgetIndex].m_aName = aPartialName; 451 } 452 453 namespace 454 { 455 456 void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer ) 457 { 458 if( nValue < 0 ) 459 { 460 rBuffer.append( '-' ); 461 nValue = -nValue; 462 } 463 sal_Int32 nFactor = 1, nDiv = nLog10Divisor; 464 while( nDiv-- ) 465 nFactor *= 10; 466 467 sal_Int32 nInt = nValue / nFactor; 468 rBuffer.append( nInt ); 469 if (nFactor > 1 && nValue % nFactor) 470 { 471 rBuffer.append( '.' ); 472 do 473 { 474 nFactor /= 10; 475 rBuffer.append((nValue / nFactor) % 10); 476 } 477 while (nFactor > 1 && nValue % nFactor); // omit trailing zeros 478 } 479 } 480 481 // appends a double. PDF does not accept exponential format, only fixed point 482 void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 10 ) 483 { 484 bool bNeg = false; 485 if( fValue < 0.0 ) 486 { 487 bNeg = true; 488 fValue=-fValue; 489 } 490 491 sal_Int64 nInt = static_cast<sal_Int64>(fValue); 492 fValue -= static_cast<double>(nInt); 493 // optimizing hardware may lead to a value of 1.0 after the subtraction 494 if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision ) 495 { 496 nInt++; 497 fValue = 0.0; 498 } 499 sal_Int64 nFrac = 0; 500 if( fValue ) 501 { 502 fValue *= pow( 10.0, static_cast<double>(nPrecision) ); 503 nFrac = static_cast<sal_Int64>(fValue); 504 } 505 if( bNeg && ( nInt || nFrac ) ) 506 rBuffer.append( '-' ); 507 rBuffer.append( nInt ); 508 if( !nFrac ) 509 return; 510 511 int i; 512 rBuffer.append( '.' ); 513 sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5); 514 for ( i = 0; ( i < nPrecision ) && nFrac; i++ ) 515 { 516 sal_Int64 nNumb = nFrac / nBound; 517 nFrac -= nNumb * nBound; 518 rBuffer.append( nNumb ); 519 nBound /= 10; 520 } 521 } 522 523 void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey ) 524 { 525 526 if( rColor == COL_TRANSPARENT ) 527 return; 528 529 if( bConvertToGrey ) 530 { 531 sal_uInt8 cByte = rColor.GetLuminance(); 532 appendDouble( static_cast<double>(cByte) / 255.0, rBuffer ); 533 } 534 else 535 { 536 appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer ); 537 rBuffer.append( ' ' ); 538 appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer ); 539 rBuffer.append( ' ' ); 540 appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer ); 541 } 542 } 543 544 } // end anonymous namespace 545 546 void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) 547 { 548 if( rColor != COL_TRANSPARENT ) 549 { 550 bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale; 551 appendColor( rColor, rBuffer, bGrey ); 552 rBuffer.append( bGrey ? " G" : " RG" ); 553 } 554 } 555 556 void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) 557 { 558 if( rColor != COL_TRANSPARENT ) 559 { 560 bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale; 561 appendColor( rColor, rBuffer, bGrey ); 562 rBuffer.append( bGrey ? " g" : " rg" ); 563 } 564 } 565 566 namespace 567 { 568 569 void appendPdfTimeDate(OStringBuffer & rBuffer, 570 sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta) 571 { 572 rBuffer.append("D:"); 573 rBuffer.append(char('0' + ((year / 1000) % 10))); 574 rBuffer.append(char('0' + ((year / 100) % 10))); 575 rBuffer.append(char('0' + ((year / 10) % 10))); 576 rBuffer.append(char('0' + (year % 10))); 577 rBuffer.append(char('0' + ((month / 10) % 10))); 578 rBuffer.append(char('0' + (month % 10))); 579 rBuffer.append(char('0' + ((day / 10) % 10))); 580 rBuffer.append(char('0' + (day % 10))); 581 rBuffer.append(char('0' + ((hours / 10) % 10))); 582 rBuffer.append(char('0' + (hours % 10))); 583 rBuffer.append(char('0' + ((minutes / 10) % 10))); 584 rBuffer.append(char('0' + (minutes % 10))); 585 rBuffer.append(char('0' + ((seconds / 10) % 10))); 586 rBuffer.append(char('0' + (seconds % 10))); 587 588 if (tzDelta == 0) 589 { 590 rBuffer.append("Z"); 591 } 592 else 593 { 594 if (tzDelta > 0 ) 595 rBuffer.append("+"); 596 else 597 { 598 rBuffer.append("-"); 599 tzDelta = -tzDelta; 600 } 601 602 rBuffer.append(char('0' + ((tzDelta / 36000) % 10))); 603 rBuffer.append(char('0' + ((tzDelta / 3600) % 10))); 604 rBuffer.append("'"); 605 rBuffer.append(char('0' + ((tzDelta / 600) % 6))); 606 rBuffer.append(char('0' + ((tzDelta / 60) % 10))); 607 } 608 } 609 610 } // end namespace 611 612 PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation ) 613 : 614 m_pWriter( pWriter ), 615 m_nPageWidth( nPageWidth ), 616 m_nPageHeight( nPageHeight ), 617 m_nUserUnit( 1 ), 618 m_eOrientation( eOrientation ), 619 m_nPageObject( 0 ), // invalid object number 620 m_nStreamLengthObject( 0 ), 621 m_nBeginStreamPos( 0 ), 622 m_eTransition( PDFWriter::PageTransition::Regular ), 623 m_nTransTime( 0 ) 624 { 625 // object ref must be only ever updated in emit() 626 m_nPageObject = m_pWriter->createObject(); 627 628 switch (m_pWriter->m_aContext.Version) 629 { 630 case PDFWriter::PDFVersion::PDF_1_6: 631 m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0); 632 break; 633 default: 634 // 1.2 -> 1.5 635 break; 636 } 637 } 638 639 void PDFPage::beginStream() 640 { 641 if (g_bDebugDisableCompression) 642 { 643 m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +"); 644 } 645 m_aStreamObjects.push_back(m_pWriter->createObject()); 646 if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) ) 647 return; 648 649 m_nStreamLengthObject = m_pWriter->createObject(); 650 // write content stream header 651 OStringBuffer aLine; 652 aLine.append( m_aStreamObjects.back() ); 653 aLine.append( " 0 obj\n<</Length " ); 654 aLine.append( m_nStreamLengthObject ); 655 aLine.append( " 0 R" ); 656 if (!g_bDebugDisableCompression) 657 aLine.append( "/Filter/FlateDecode" ); 658 aLine.append( ">>\nstream\n" ); 659 if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) ) 660 return; 661 if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos)) 662 { 663 m_pWriter->m_aFile.close(); 664 m_pWriter->m_bOpen = false; 665 } 666 if (!g_bDebugDisableCompression) 667 m_pWriter->beginCompression(); 668 m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() ); 669 } 670 671 void PDFPage::endStream() 672 { 673 if (!g_bDebugDisableCompression) 674 m_pWriter->endCompression(); 675 sal_uInt64 nEndStreamPos; 676 if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos)) 677 { 678 m_pWriter->m_aFile.close(); 679 m_pWriter->m_bOpen = false; 680 return; 681 } 682 m_pWriter->disableStreamEncryption(); 683 if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) ) 684 return; 685 // emit stream length object 686 if( ! m_pWriter->updateObject( m_nStreamLengthObject ) ) 687 return; 688 OString aLine = 689 OString::number( m_nStreamLengthObject ) + 690 " 0 obj\n" + 691 OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) + 692 "\nendobj\n\n"; 693 m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ); 694 } 695 696 bool PDFPage::emit(sal_Int32 nParentObject ) 697 { 698 m_pWriter->MARK("PDFPage::emit"); 699 // emit page object 700 if( ! m_pWriter->updateObject( m_nPageObject ) ) 701 return false; 702 OStringBuffer aLine; 703 704 aLine.append( m_nPageObject ); 705 aLine.append( " 0 obj\n" 706 "<</Type/Page/Parent " ); 707 aLine.append( nParentObject ); 708 aLine.append( " 0 R" ); 709 aLine.append( "/Resources " ); 710 aLine.append( m_pWriter->getResourceDictObj() ); 711 aLine.append( " 0 R" ); 712 if( m_nPageWidth && m_nPageHeight ) 713 { 714 aLine.append( "/MediaBox[0 0 " ); 715 aLine.append(m_nPageWidth / m_nUserUnit); 716 aLine.append( ' ' ); 717 aLine.append(m_nPageHeight / m_nUserUnit); 718 aLine.append( "]" ); 719 if (m_nUserUnit > 1) 720 { 721 aLine.append("\n/UserUnit "); 722 aLine.append(m_nUserUnit); 723 } 724 } 725 switch( m_eOrientation ) 726 { 727 case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break; 728 case PDFWriter::Orientation::Inherit: break; 729 } 730 int nAnnots = m_aAnnotations.size(); 731 if( nAnnots > 0 ) 732 { 733 aLine.append( "/Annots[\n" ); 734 for( int i = 0; i < nAnnots; i++ ) 735 { 736 aLine.append( m_aAnnotations[i] ); 737 aLine.append( " 0 R" ); 738 aLine.append( ((i+1)%15) ? " " : "\n" ); 739 } 740 aLine.append( "]\n" ); 741 if (m_pWriter->m_aContext.Version != PDFWriter::PDFVersion::PDF_A_1 742 && PDFWriter::PDFVersion::PDF_1_5 <= m_pWriter->m_aContext.Version) 743 { 744 // ISO 14289-1:2014, Clause: 7.18.3 745 aLine.append( "/Tabs(S)\n" ); 746 } 747 } 748 if( !m_aMCIDParents.empty() ) 749 { 750 OStringBuffer aStructParents( 1024 ); 751 aStructParents.append( "[ " ); 752 int nParents = m_aMCIDParents.size(); 753 for( int i = 0; i < nParents; i++ ) 754 { 755 aStructParents.append( m_aMCIDParents[i] ); 756 aStructParents.append( " 0 R" ); 757 aStructParents.append( ((i%10) == 9) ? "\n" : " " ); 758 } 759 aStructParents.append( "]" ); 760 m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() ); 761 762 aLine.append( "/StructParents " ); 763 aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) ); 764 aLine.append( "\n" ); 765 } 766 if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 ) 767 { 768 // transition duration 769 aLine.append( "/Trans<</D " ); 770 appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 ); 771 aLine.append( "\n" ); 772 const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr; 773 switch( m_eTransition ) 774 { 775 case PDFWriter::PageTransition::SplitHorizontalInward: 776 pStyle = "Split"; pDm = "H"; pM = "I"; break; 777 case PDFWriter::PageTransition::SplitHorizontalOutward: 778 pStyle = "Split"; pDm = "H"; pM = "O"; break; 779 case PDFWriter::PageTransition::SplitVerticalInward: 780 pStyle = "Split"; pDm = "V"; pM = "I"; break; 781 case PDFWriter::PageTransition::SplitVerticalOutward: 782 pStyle = "Split"; pDm = "V"; pM = "O"; break; 783 case PDFWriter::PageTransition::BlindsHorizontal: 784 pStyle = "Blinds"; pDm = "H"; break; 785 case PDFWriter::PageTransition::BlindsVertical: 786 pStyle = "Blinds"; pDm = "V"; break; 787 case PDFWriter::PageTransition::BoxInward: 788 pStyle = "Box"; pM = "I"; break; 789 case PDFWriter::PageTransition::BoxOutward: 790 pStyle = "Box"; pM = "O"; break; 791 case PDFWriter::PageTransition::WipeLeftToRight: 792 pStyle = "Wipe"; pDi = "0"; break; 793 case PDFWriter::PageTransition::WipeBottomToTop: 794 pStyle = "Wipe"; pDi = "90"; break; 795 case PDFWriter::PageTransition::WipeRightToLeft: 796 pStyle = "Wipe"; pDi = "180"; break; 797 case PDFWriter::PageTransition::WipeTopToBottom: 798 pStyle = "Wipe"; pDi = "270"; break; 799 case PDFWriter::PageTransition::Dissolve: 800 pStyle = "Dissolve"; break; 801 case PDFWriter::PageTransition::Regular: 802 break; 803 } 804 // transition style 805 if( pStyle ) 806 { 807 aLine.append( "/S/" ); 808 aLine.append( pStyle ); 809 aLine.append( "\n" ); 810 } 811 if( pDm ) 812 { 813 aLine.append( "/Dm/" ); 814 aLine.append( pDm ); 815 aLine.append( "\n" ); 816 } 817 if( pM ) 818 { 819 aLine.append( "/M/" ); 820 aLine.append( pM ); 821 aLine.append( "\n" ); 822 } 823 if( pDi ) 824 { 825 aLine.append( "/Di " ); 826 aLine.append( pDi ); 827 aLine.append( "\n" ); 828 } 829 aLine.append( ">>\n" ); 830 } 831 832 aLine.append( "/Contents" ); 833 unsigned int nStreamObjects = m_aStreamObjects.size(); 834 if( nStreamObjects > 1 ) 835 aLine.append( '[' ); 836 for(sal_Int32 i : m_aStreamObjects) 837 { 838 aLine.append( ' ' ); 839 aLine.append( i ); 840 aLine.append( " 0 R" ); 841 } 842 if( nStreamObjects > 1 ) 843 aLine.append( ']' ); 844 aLine.append( ">>\nendobj\n\n" ); 845 return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ); 846 } 847 848 void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const 849 { 850 Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, 851 m_pWriter->m_aMapMode, 852 m_pWriter, 853 rPoint ) ); 854 855 sal_Int32 nValue = aPoint.X(); 856 857 appendFixedInt( nValue, rBuffer ); 858 859 rBuffer.append( ' ' ); 860 861 nValue = pointToPixel(getHeight()) - aPoint.Y(); 862 863 appendFixedInt( nValue, rBuffer ); 864 } 865 866 void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const 867 { 868 double fValue = pixelToPoint(rPoint.getX()); 869 870 appendDouble( fValue, rBuffer, nLog10Divisor ); 871 rBuffer.append( ' ' ); 872 fValue = getHeight() - pixelToPoint(rPoint.getY()); 873 appendDouble( fValue, rBuffer, nLog10Divisor ); 874 } 875 876 void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const 877 { 878 appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer ); 879 rBuffer.append( ' ' ); 880 appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false ); 881 rBuffer.append( ' ' ); 882 appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer ); 883 rBuffer.append( " re" ); 884 } 885 886 void PDFPage::convertRect( tools::Rectangle& rRect ) const 887 { 888 Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, 889 m_pWriter->m_aMapMode, 890 m_pWriter, 891 rRect.BottomLeft() + Point( 0, 1 ) 892 ); 893 Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, 894 m_pWriter->m_aMapMode, 895 m_pWriter, 896 rRect.GetSize() ); 897 rRect.SetLeft( aLL.X() ); 898 rRect.SetRight( aLL.X() + aSize.Width() ); 899 rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() ); 900 rRect.SetBottom( rRect.Top() + aSize.Height() ); 901 } 902 903 void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const 904 { 905 sal_uInt16 nPoints = rPoly.GetSize(); 906 /* 907 * #108582# applications do weird things 908 */ 909 sal_uInt32 nBufLen = rBuffer.getLength(); 910 if( nPoints <= 0 ) 911 return; 912 913 const PolyFlags* pFlagArray = rPoly.GetConstFlagAry(); 914 appendPoint( rPoly[0], rBuffer ); 915 rBuffer.append( " m\n" ); 916 for( sal_uInt16 i = 1; i < nPoints; i++ ) 917 { 918 if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 ) 919 { 920 // bezier 921 SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" ); 922 appendPoint( rPoly[i], rBuffer ); 923 rBuffer.append( " " ); 924 appendPoint( rPoly[i+1], rBuffer ); 925 rBuffer.append( " " ); 926 appendPoint( rPoly[i+2], rBuffer ); 927 rBuffer.append( " c" ); 928 i += 2; // add additionally consumed points 929 } 930 else 931 { 932 // line 933 appendPoint( rPoly[i], rBuffer ); 934 rBuffer.append( " l" ); 935 } 936 if( (rBuffer.getLength() - nBufLen) > 65 ) 937 { 938 rBuffer.append( "\n" ); 939 nBufLen = rBuffer.getLength(); 940 } 941 else 942 rBuffer.append( " " ); 943 } 944 if( bClose ) 945 rBuffer.append( "h\n" ); 946 } 947 948 void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const 949 { 950 basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, 951 m_pWriter->m_aMapMode, 952 m_pWriter, 953 rPoly ) ); 954 955 if( basegfx::utils::isRectangle( aPoly ) ) 956 { 957 basegfx::B2DRange aRange( aPoly.getB2DRange() ); 958 basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() ); 959 appendPixelPoint( aBL, rBuffer ); 960 rBuffer.append( ' ' ); 961 appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor ); 962 rBuffer.append( ' ' ); 963 appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor ); 964 rBuffer.append( " re\n" ); 965 return; 966 } 967 sal_uInt32 nPoints = aPoly.count(); 968 if( nPoints <= 0 ) 969 return; 970 971 sal_uInt32 nBufLen = rBuffer.getLength(); 972 basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) ); 973 appendPixelPoint( aLastPoint, rBuffer ); 974 rBuffer.append( " m\n" ); 975 for( sal_uInt32 i = 1; i <= nPoints; i++ ) 976 { 977 if( i != nPoints || aPoly.isClosed() ) 978 { 979 sal_uInt32 nCurPoint = i % nPoints; 980 sal_uInt32 nLastPoint = i-1; 981 basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) ); 982 if( aPoly.isNextControlPointUsed( nLastPoint ) && 983 aPoly.isPrevControlPointUsed( nCurPoint ) ) 984 { 985 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer ); 986 rBuffer.append( ' ' ); 987 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer ); 988 rBuffer.append( ' ' ); 989 appendPixelPoint( aPoint, rBuffer ); 990 rBuffer.append( " c" ); 991 } 992 else if( aPoly.isNextControlPointUsed( nLastPoint ) ) 993 { 994 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer ); 995 rBuffer.append( ' ' ); 996 appendPixelPoint( aPoint, rBuffer ); 997 rBuffer.append( " y" ); 998 } 999 else if( aPoly.isPrevControlPointUsed( nCurPoint ) ) 1000 { 1001 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer ); 1002 rBuffer.append( ' ' ); 1003 appendPixelPoint( aPoint, rBuffer ); 1004 rBuffer.append( " v" ); 1005 } 1006 else 1007 { 1008 appendPixelPoint( aPoint, rBuffer ); 1009 rBuffer.append( " l" ); 1010 } 1011 if( (rBuffer.getLength() - nBufLen) > 65 ) 1012 { 1013 rBuffer.append( "\n" ); 1014 nBufLen = rBuffer.getLength(); 1015 } 1016 else 1017 rBuffer.append( " " ); 1018 } 1019 } 1020 rBuffer.append( "h\n" ); 1021 } 1022 1023 void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const 1024 { 1025 sal_uInt16 nPolygons = rPolyPoly.Count(); 1026 for( sal_uInt16 n = 0; n < nPolygons; n++ ) 1027 appendPolygon( rPolyPoly[n], rBuffer ); 1028 } 1029 1030 void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const 1031 { 1032 for(auto const& rPolygon : rPolyPoly) 1033 appendPolygon( rPolygon, rBuffer ); 1034 } 1035 1036 void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const 1037 { 1038 sal_Int32 nValue = nLength; 1039 if ( nLength < 0 ) 1040 { 1041 rBuffer.append( '-' ); 1042 nValue = -nLength; 1043 } 1044 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, 1045 m_pWriter->m_aMapMode, 1046 m_pWriter, 1047 Size( nValue, nValue ) ) ); 1048 nValue = bVertical ? aSize.Height() : aSize.Width(); 1049 if( pOutLength ) 1050 *pOutLength = ((nLength < 0 ) ? -nValue : nValue); 1051 1052 appendFixedInt( nValue, rBuffer ); 1053 } 1054 1055 void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const 1056 { 1057 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, 1058 m_pWriter->m_aMapMode, 1059 m_pWriter, 1060 Size( 1000, 1000 ) ) ); 1061 fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0); 1062 appendDouble( fLength, rBuffer, nPrecision ); 1063 } 1064 1065 bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const 1066 { 1067 if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen()) 1068 { 1069 // dashed and non-degraded case, check for implementation limits of dash array 1070 // in PDF reader apps (e.g. acroread) 1071 if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10) 1072 { 1073 return false; 1074 } 1075 } 1076 1077 if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin()) 1078 { 1079 // LineJoin used, ExtLineInfo required 1080 return false; 1081 } 1082 1083 if(css::drawing::LineCap_BUTT != rInfo.GetLineCap()) 1084 { 1085 // LineCap used, ExtLineInfo required 1086 return false; 1087 } 1088 1089 if( rInfo.GetStyle() == LineStyle::Dash ) 1090 { 1091 rBuffer.append( "[ " ); 1092 if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case 1093 { 1094 appendMappedLength( rInfo.GetDashLen(), rBuffer ); 1095 rBuffer.append( ' ' ); 1096 appendMappedLength( rInfo.GetDistance(), rBuffer ); 1097 rBuffer.append( ' ' ); 1098 } 1099 else 1100 { 1101 for( int n = 0; n < rInfo.GetDashCount(); n++ ) 1102 { 1103 appendMappedLength( rInfo.GetDashLen(), rBuffer ); 1104 rBuffer.append( ' ' ); 1105 appendMappedLength( rInfo.GetDistance(), rBuffer ); 1106 rBuffer.append( ' ' ); 1107 } 1108 for( int m = 0; m < rInfo.GetDotCount(); m++ ) 1109 { 1110 appendMappedLength( rInfo.GetDotLen(), rBuffer ); 1111 rBuffer.append( ' ' ); 1112 appendMappedLength( rInfo.GetDistance(), rBuffer ); 1113 rBuffer.append( ' ' ); 1114 } 1115 } 1116 rBuffer.append( "] 0 d\n" ); 1117 } 1118 1119 if( rInfo.GetWidth() > 1 ) 1120 { 1121 appendMappedLength( rInfo.GetWidth(), rBuffer ); 1122 rBuffer.append( " w\n" ); 1123 } 1124 else if( rInfo.GetWidth() == 0 ) 1125 { 1126 // "pixel" line 1127 appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer ); 1128 rBuffer.append( " w\n" ); 1129 } 1130 1131 return true; 1132 } 1133 1134 void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const 1135 { 1136 if( nWidth <= 0 ) 1137 return; 1138 if( nDelta < 1 ) 1139 nDelta = 1; 1140 1141 rBuffer.append( "0 " ); 1142 appendMappedLength( nY, rBuffer ); 1143 rBuffer.append( " m\n" ); 1144 for( sal_Int32 n = 0; n < nWidth; ) 1145 { 1146 n += nDelta; 1147 appendMappedLength( n, rBuffer, false ); 1148 rBuffer.append( ' ' ); 1149 appendMappedLength( nDelta+nY, rBuffer ); 1150 rBuffer.append( ' ' ); 1151 n += nDelta; 1152 appendMappedLength( n, rBuffer, false ); 1153 rBuffer.append( ' ' ); 1154 appendMappedLength( nY, rBuffer ); 1155 rBuffer.append( " v " ); 1156 if( n < nWidth ) 1157 { 1158 n += nDelta; 1159 appendMappedLength( n, rBuffer, false ); 1160 rBuffer.append( ' ' ); 1161 appendMappedLength( nY-nDelta, rBuffer ); 1162 rBuffer.append( ' ' ); 1163 n += nDelta; 1164 appendMappedLength( n, rBuffer, false ); 1165 rBuffer.append( ' ' ); 1166 appendMappedLength( nY, rBuffer ); 1167 rBuffer.append( " v\n" ); 1168 } 1169 } 1170 rBuffer.append( "S\n" ); 1171 } 1172 1173 void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer) 1174 { 1175 appendDouble(rMatrix.get(0), rBuffer); 1176 rBuffer.append(' '); 1177 appendDouble(rMatrix.get(1), rBuffer); 1178 rBuffer.append(' '); 1179 appendDouble(rMatrix.get(2), rBuffer); 1180 rBuffer.append(' '); 1181 appendDouble(rMatrix.get(3), rBuffer); 1182 rBuffer.append(' '); 1183 appendPoint(Point(tools::Long(rMatrix.get(4)), tools::Long(rMatrix.get(5))), rBuffer); 1184 } 1185 1186 double PDFPage::getHeight() const 1187 { 1188 double fRet = m_nPageHeight ? m_nPageHeight : vcl::pdf::g_nInheritedPageHeight; 1189 1190 if (m_nUserUnit > 1) 1191 { 1192 fRet /= m_nUserUnit; 1193 } 1194 1195 return fRet; 1196 } 1197 1198 PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, 1199 const css::uno::Reference< css::beans::XMaterialHolder >& xEnc, 1200 PDFWriter& i_rOuterFace) 1201 : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF), 1202 m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ), 1203 m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()), 1204 m_nCurrentStructElement( 0 ), 1205 m_bEmitStructure( true ), 1206 m_nNextFID( 1 ), 1207 m_aPDFBmpCache( 1208 officecfg::Office::Common::VCL::PDFExportImageCacheSize::get() ), 1209 m_nCurrentPage( -1 ), 1210 m_nCatalogObject(0), 1211 m_nSignatureObject( -1 ), 1212 m_nSignatureContentOffset( 0 ), 1213 m_nSignatureLastByteRangeNoOffset( 0 ), 1214 m_nResourceDict( -1 ), 1215 m_nFontDictObject( -1 ), 1216 m_aContext(rContext), 1217 m_aFile(m_aContext.URL), 1218 m_bOpen(false), 1219 m_DocDigest(::comphelper::HashType::MD5), 1220 m_aCipher( nullptr ), 1221 m_nKeyLength(0), 1222 m_nRC4KeyLength(0), 1223 m_bEncryptThisStream( false ), 1224 m_nAccessPermissions(0), 1225 m_bIsPDF_A1( false ), 1226 m_bIsPDF_A2( false ), 1227 m_bIsPDF_UA( false ), 1228 m_bIsPDF_A3( false ), 1229 m_rOuterFace( i_rOuterFace ) 1230 { 1231 m_aStructure.emplace_back( ); 1232 m_aStructure[0].m_nOwnElement = 0; 1233 m_aStructure[0].m_nParentElement = 0; 1234 1235 Font aFont; 1236 aFont.SetFamilyName( "Times" ); 1237 aFont.SetFontSize( Size( 0, 12 ) ); 1238 1239 // tdf#150786 use the same settings for widgets regardless of theme 1240 m_aWidgetStyleSettings.SetStandardStyles(); 1241 1242 GraphicsState aState; 1243 aState.m_aMapMode = m_aMapMode; 1244 aState.m_aFont = aFont; 1245 m_aGraphicsStack.push_front( aState ); 1246 1247 osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create); 1248 if (aError != osl::File::E_None) 1249 { 1250 if (aError == osl::File::E_EXIST) 1251 { 1252 aError = m_aFile.open(osl_File_OpenFlag_Write); 1253 if (aError == osl::File::E_None) 1254 aError = m_aFile.setSize(0); 1255 } 1256 } 1257 if (aError != osl::File::E_None) 1258 return; 1259 1260 m_bOpen = true; 1261 1262 // setup DocInfo 1263 setupDocInfo(); 1264 1265 /* prepare the cypher engine, can be done in CTOR, free in DTOR */ 1266 m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); 1267 1268 if( xEnc.is() ) 1269 prepareEncryption( xEnc ); 1270 1271 if( m_aContext.Encryption.Encrypt() ) 1272 { 1273 // sanity check 1274 if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE || 1275 m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE || 1276 m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH 1277 ) 1278 { 1279 // the field lengths are invalid ? This was not setup by initEncryption. 1280 // do not encrypt after all 1281 m_aContext.Encryption.OValue.clear(); 1282 m_aContext.Encryption.UValue.clear(); 1283 OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" ); 1284 } 1285 else // setup key lengths 1286 m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength ); 1287 } 1288 1289 // write header 1290 OStringBuffer aBuffer( 20 ); 1291 aBuffer.append( "%PDF-" ); 1292 switch( m_aContext.Version ) 1293 { 1294 case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break; 1295 case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break; 1296 case PDFWriter::PDFVersion::PDF_A_1: 1297 case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break; 1298 case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break; 1299 default: 1300 case PDFWriter::PDFVersion::PDF_1_6: aBuffer.append( "1.6" );break; 1301 } 1302 // append something binary as comment (suggested in PDF Reference) 1303 aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" ); 1304 if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) ) 1305 { 1306 m_aFile.close(); 1307 m_bOpen = false; 1308 return; 1309 } 1310 1311 // insert outline root 1312 m_aOutline.emplace_back( ); 1313 1314 m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1); 1315 if( m_bIsPDF_A1 ) 1316 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour 1317 1318 m_bIsPDF_A2 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_2); 1319 if( m_bIsPDF_A2 ) 1320 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features 1321 1322 m_bIsPDF_A3 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_3); 1323 if( m_bIsPDF_A3 ) 1324 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features 1325 1326 if (m_aContext.UniversalAccessibilityCompliance) 1327 { 1328 m_bIsPDF_UA = true; 1329 m_aContext.Tagged = true; 1330 } 1331 1332 if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 ) 1333 SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 ); 1334 else 1335 SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy ); 1336 1337 SetOutputSizePixel( Size( 640, 480 ) ); 1338 SetMapMode(MapMode(MapUnit::MapMM)); 1339 } 1340 1341 PDFWriterImpl::~PDFWriterImpl() 1342 { 1343 disposeOnce(); 1344 } 1345 1346 void PDFWriterImpl::dispose() 1347 { 1348 if( m_aCipher ) 1349 rtl_cipher_destroyARCFOUR( m_aCipher ); 1350 m_aPages.clear(); 1351 VirtualDevice::dispose(); 1352 } 1353 1354 bool PDFWriterImpl::ImplNewFont() const 1355 { 1356 const ImplSVData* pSVData = ImplGetSVData(); 1357 1358 if( mxFontCollection == pSVData->maGDIData.mxScreenFontList 1359 || mxFontCache == pSVData->maGDIData.mxScreenFontCache ) 1360 { 1361 const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData(); 1362 } 1363 1364 return OutputDevice::ImplNewFont(); 1365 } 1366 1367 void PDFWriterImpl::setupDocInfo() 1368 { 1369 std::vector< sal_uInt8 > aId; 1370 m_aCreationDateString = PDFWriter::GetDateTime(); 1371 computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString ); 1372 if( m_aContext.Encryption.DocumentIdentifier.empty() ) 1373 m_aContext.Encryption.DocumentIdentifier = aId; 1374 } 1375 1376 OString PDFWriter::GetDateTime() 1377 { 1378 OStringBuffer aRet; 1379 1380 TimeValue aTVal, aGMT; 1381 oslDateTime aDT; 1382 osl_getSystemTime(&aGMT); 1383 osl_getLocalTimeFromSystemTime(&aGMT, &aTVal); 1384 osl_getDateTimeFromTimeValue(&aTVal, &aDT); 1385 1386 sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds; 1387 1388 appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta); 1389 1390 aRet.append("'"); 1391 return aRet.makeStringAndClear(); 1392 } 1393 1394 void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier, 1395 const vcl::PDFWriter::PDFDocInfo& i_rDocInfo, 1396 const OString& i_rCString1, 1397 OString& o_rCString2 1398 ) 1399 { 1400 o_rIdentifier.clear(); 1401 1402 //build the document id 1403 OString aInfoValuesOut; 1404 OStringBuffer aID( 1024 ); 1405 if( !i_rDocInfo.Title.isEmpty() ) 1406 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID); 1407 if( !i_rDocInfo.Author.isEmpty() ) 1408 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID); 1409 if( !i_rDocInfo.Subject.isEmpty() ) 1410 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID); 1411 if( !i_rDocInfo.Keywords.isEmpty() ) 1412 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID); 1413 if( !i_rDocInfo.Creator.isEmpty() ) 1414 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID); 1415 if( !i_rDocInfo.Producer.isEmpty() ) 1416 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID); 1417 1418 TimeValue aTVal, aGMT; 1419 oslDateTime aDT; 1420 osl_getSystemTime( &aGMT ); 1421 osl_getLocalTimeFromSystemTime( &aGMT, &aTVal ); 1422 osl_getDateTimeFromTimeValue( &aTVal, &aDT ); 1423 OStringBuffer aCreationMetaDateString(64); 1424 1425 // i59651: we fill the Metadata date string as well, if PDF/A is requested 1426 // according to ISO 19005-1:2005 6.7.3 the date is corrected for 1427 // local time zone offset UTC only, whereas Acrobat 8 seems 1428 // to use the localtime notation only 1429 // according to a recommendation in XMP Specification (Jan 2004, page 75) 1430 // the Acrobat way seems the right approach 1431 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/1000)%10)) ); 1432 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/100)%10)) ); 1433 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/10)%10)) ); 1434 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year)%10)) ); 1435 aCreationMetaDateString.append( "-" ); 1436 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month/10)%10)) ); 1437 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month)%10)) ); 1438 aCreationMetaDateString.append( "-" ); 1439 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day/10)%10)) ); 1440 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day)%10)) ); 1441 aCreationMetaDateString.append( "T" ); 1442 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours/10)%10)) ); 1443 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours)%10)) ); 1444 aCreationMetaDateString.append( ":" ); 1445 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes/10)%10)) ); 1446 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes)%10)) ); 1447 aCreationMetaDateString.append( ":" ); 1448 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds/10)%10)) ); 1449 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds)%10)) ); 1450 1451 sal_uInt32 nDelta = 0; 1452 if( aGMT.Seconds > aTVal.Seconds ) 1453 { 1454 nDelta = aGMT.Seconds-aTVal.Seconds; 1455 aCreationMetaDateString.append( "-" ); 1456 } 1457 else if( aGMT.Seconds < aTVal.Seconds ) 1458 { 1459 nDelta = aTVal.Seconds-aGMT.Seconds; 1460 aCreationMetaDateString.append( "+" ); 1461 } 1462 else 1463 { 1464 aCreationMetaDateString.append( "Z" ); 1465 1466 } 1467 if( nDelta ) 1468 { 1469 aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/36000)%10)) ); 1470 aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/3600)%10)) ); 1471 aCreationMetaDateString.append( ":" ); 1472 aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/600)%6)) ); 1473 aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/60)%10)) ); 1474 } 1475 aID.append( i_rCString1.getStr(), i_rCString1.getLength() ); 1476 1477 aInfoValuesOut = aID.makeStringAndClear(); 1478 o_rCString2 = aCreationMetaDateString.makeStringAndClear(); 1479 1480 ::comphelper::Hash aDigest(::comphelper::HashType::MD5); 1481 aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT)); 1482 aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength()); 1483 //the binary form of the doc id is needed for encryption stuff 1484 o_rIdentifier = aDigest.finalize(); 1485 } 1486 1487 /* i12626 methods */ 1488 /* 1489 check if the Unicode string must be encrypted or not, perform the requested task, 1490 append the string as unicode hex, encrypted if needed 1491 */ 1492 inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer ) 1493 { 1494 rOutBuffer.append( "<" ); 1495 if( m_aContext.Encryption.Encrypt() ) 1496 { 1497 const sal_Unicode* pStr = rInString.getStr(); 1498 sal_Int32 nLen = rInString.getLength(); 1499 //prepare a unicode string, encrypt it 1500 enableStringEncryption( nInObjectNumber ); 1501 sal_uInt8 *pCopy = m_vEncryptionBuffer.data(); 1502 sal_Int32 nChars = 2 + (nLen * 2); 1503 m_vEncryptionBuffer.resize(nChars); 1504 *pCopy++ = 0xFE; 1505 *pCopy++ = 0xFF; 1506 // we need to prepare a byte stream from the unicode string buffer 1507 for( int i = 0; i < nLen; i++ ) 1508 { 1509 sal_Unicode aUnChar = pStr[i]; 1510 *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 ); 1511 *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 ); 1512 } 1513 //encrypt in place 1514 rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars ); 1515 //now append, hexadecimal (appendHex), the encrypted result 1516 for(int i = 0; i < nChars; i++) 1517 appendHex( m_vEncryptionBuffer[i], rOutBuffer ); 1518 } 1519 else 1520 PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer); 1521 rOutBuffer.append( ">" ); 1522 } 1523 1524 inline void PDFWriterImpl::appendLiteralStringEncrypt( std::string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer ) 1525 { 1526 rOutBuffer.append( "(" ); 1527 sal_Int32 nChars = rInString.size(); 1528 //check for encryption, if ok, encrypt the string, then convert with appndLiteralString 1529 if( m_aContext.Encryption.Encrypt() ) 1530 { 1531 m_vEncryptionBuffer.resize(nChars); 1532 //encrypt the string in a buffer, then append it 1533 enableStringEncryption( nInObjectNumber ); 1534 rtl_cipher_encodeARCFOUR( m_aCipher, rInString.data(), nChars, m_vEncryptionBuffer.data(), nChars ); 1535 appendLiteralString( reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer ); 1536 } 1537 else 1538 appendLiteralString( rInString.data(), nChars , rOutBuffer ); 1539 rOutBuffer.append( ")" ); 1540 } 1541 1542 void PDFWriterImpl::appendLiteralStringEncrypt( std::u16string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc ) 1543 { 1544 OString aBufferString( OUStringToOString( rInString, nEnc ) ); 1545 sal_Int32 nLen = aBufferString.getLength(); 1546 OStringBuffer aBuf( nLen ); 1547 const char* pT = aBufferString.getStr(); 1548 1549 for( sal_Int32 i = 0; i < nLen; i++, pT++ ) 1550 { 1551 if( (*pT & 0x80) == 0 ) 1552 aBuf.append( *pT ); 1553 else 1554 { 1555 aBuf.append( '<' ); 1556 appendHex( *pT, aBuf ); 1557 aBuf.append( '>' ); 1558 } 1559 } 1560 aBufferString = aBuf.makeStringAndClear(); 1561 appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer); 1562 } 1563 1564 /* end i12626 methods */ 1565 1566 void PDFWriterImpl::emitComment( const char* pComment ) 1567 { 1568 OString aLine = OString::Concat("% ") + pComment + "\n"; 1569 writeBuffer( aLine.getStr(), aLine.getLength() ); 1570 } 1571 1572 bool PDFWriterImpl::compressStream( SvMemoryStream* pStream ) 1573 { 1574 if (!g_bDebugDisableCompression) 1575 { 1576 sal_uInt64 nEndPos = pStream->TellEnd(); 1577 pStream->Seek( STREAM_SEEK_TO_BEGIN ); 1578 ZCodec aCodec( 0x4000, 0x4000 ); 1579 SvMemoryStream aStream; 1580 aCodec.BeginCompression(); 1581 aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos ); 1582 aCodec.EndCompression(); 1583 nEndPos = aStream.Tell(); 1584 pStream->Seek( STREAM_SEEK_TO_BEGIN ); 1585 aStream.Seek( STREAM_SEEK_TO_BEGIN ); 1586 pStream->SetStreamSize( nEndPos ); 1587 pStream->WriteBytes( aStream.GetData(), nEndPos ); 1588 return true; 1589 } 1590 else 1591 return false; 1592 } 1593 1594 void PDFWriterImpl::beginCompression() 1595 { 1596 if (!g_bDebugDisableCompression) 1597 { 1598 m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 ); 1599 m_pMemStream = std::make_unique<SvMemoryStream>(); 1600 m_pCodec->BeginCompression(); 1601 } 1602 } 1603 1604 void PDFWriterImpl::endCompression() 1605 { 1606 if (!g_bDebugDisableCompression && m_pCodec) 1607 { 1608 m_pCodec->EndCompression(); 1609 m_pCodec.reset(); 1610 sal_uInt64 nLen = m_pMemStream->Tell(); 1611 m_pMemStream->Seek( 0 ); 1612 writeBuffer( m_pMemStream->GetData(), nLen ); 1613 m_pMemStream.reset(); 1614 } 1615 } 1616 1617 bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes ) 1618 { 1619 if( ! m_bOpen ) // we are already down the drain 1620 return false; 1621 1622 if( ! nBytes ) // huh ? 1623 return true; 1624 1625 if( !m_aOutputStreams.empty() ) 1626 { 1627 m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END ); 1628 m_aOutputStreams.front().m_pStream->WriteBytes( 1629 pBuffer, sal::static_int_cast<std::size_t>(nBytes)); 1630 return true; 1631 } 1632 1633 sal_uInt64 nWritten; 1634 if( m_pCodec ) 1635 { 1636 m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) ); 1637 nWritten = nBytes; 1638 } 1639 else 1640 { 1641 bool buffOK = true; 1642 if( m_bEncryptThisStream ) 1643 { 1644 /* implement the encryption part of the PDF spec encryption algorithm 3.1 */ 1645 m_vEncryptionBuffer.resize(nBytes); 1646 if( buffOK ) 1647 rtl_cipher_encodeARCFOUR( m_aCipher, 1648 pBuffer, static_cast<sal_Size>(nBytes), 1649 m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) ); 1650 } 1651 1652 const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data() : pBuffer; 1653 m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes)); 1654 1655 if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None) 1656 nWritten = 0; 1657 1658 if( nWritten != nBytes ) 1659 { 1660 m_aFile.close(); 1661 m_bOpen = false; 1662 } 1663 } 1664 1665 return nWritten == nBytes; 1666 } 1667 1668 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation ) 1669 { 1670 endPage(); 1671 m_nCurrentPage = m_aPages.size(); 1672 m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation ); 1673 1674 sal_Int32 nUserUnit = m_aPages.back().m_nUserUnit; 1675 if (nUserUnit > 1) 1676 { 1677 m_aMapMode = MapMode(MapUnit::MapPoint, Point(), Fraction(nUserUnit, pointToPixel(1)), 1678 Fraction(nUserUnit, pointToPixel(1))); 1679 } 1680 1681 m_aPages.back().beginStream(); 1682 1683 // setup global graphics state 1684 // linewidth is "1 pixel" by default 1685 OStringBuffer aBuf( 16 ); 1686 appendDouble( 72.0/double(GetDPIX()), aBuf ); 1687 aBuf.append( " w\n" ); 1688 writeBuffer( aBuf.getStr(), aBuf.getLength() ); 1689 } 1690 1691 void PDFWriterImpl::endPage() 1692 { 1693 if( m_aPages.empty() ) 1694 return; 1695 1696 // close eventual MC sequence 1697 endStructureElementMCSeq(); 1698 1699 // sanity check 1700 if( !m_aOutputStreams.empty() ) 1701 { 1702 OSL_FAIL( "redirection across pages !!!" ); 1703 m_aOutputStreams.clear(); // leak ! 1704 m_aMapMode.SetOrigin( Point() ); 1705 } 1706 1707 m_aGraphicsStack.clear(); 1708 m_aGraphicsStack.emplace_back( ); 1709 1710 // this should pop the PDF graphics stack if necessary 1711 updateGraphicsState(); 1712 1713 m_aPages.back().endStream(); 1714 1715 // reset the default font 1716 Font aFont; 1717 aFont.SetFamilyName( "Times" ); 1718 aFont.SetFontSize( Size( 0, 12 ) ); 1719 1720 m_aCurrentPDFState = m_aGraphicsStack.front(); 1721 m_aGraphicsStack.front().m_aFont = aFont; 1722 1723 for (auto & bitmap : m_aBitmaps) 1724 { 1725 if( ! bitmap.m_aBitmap.IsEmpty() ) 1726 { 1727 writeBitmapObject(bitmap); 1728 bitmap.m_aBitmap = BitmapEx(); 1729 } 1730 } 1731 for (auto & jpeg : m_aJPGs) 1732 { 1733 if( jpeg.m_pStream ) 1734 { 1735 writeJPG( jpeg ); 1736 jpeg.m_pStream.reset(); 1737 jpeg.m_aAlphaMask = AlphaMask(); 1738 } 1739 } 1740 for (auto & item : m_aTransparentObjects) 1741 { 1742 if( item.m_pContentStream ) 1743 { 1744 writeTransparentObject(item); 1745 item.m_pContentStream.reset(); 1746 } 1747 } 1748 1749 } 1750 1751 sal_Int32 PDFWriterImpl::createObject() 1752 { 1753 m_aObjects.push_back( ~0U ); 1754 return m_aObjects.size(); 1755 } 1756 1757 bool PDFWriterImpl::updateObject( sal_Int32 n ) 1758 { 1759 if( ! m_bOpen ) 1760 return false; 1761 1762 sal_uInt64 nOffset = ~0U; 1763 osl::File::RC aError = m_aFile.getPos(nOffset); 1764 SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" ); 1765 if (aError != osl::File::E_None) 1766 { 1767 m_aFile.close(); 1768 m_bOpen = false; 1769 } 1770 m_aObjects[ n-1 ] = nOffset; 1771 return aError == osl::File::E_None; 1772 } 1773 1774 #define CHECK_RETURN( x ) if( !(x) ) return 0 1775 #define CHECK_RETURN2( x ) if( !(x) ) return 1776 1777 sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject ) 1778 { 1779 if( nObject > 0 ) 1780 { 1781 OStringBuffer aLine( 1024 ); 1782 1783 aLine.append( nObject ); 1784 aLine.append( " 0 obj\n" 1785 "<</Nums[\n" ); 1786 sal_Int32 nTreeItems = m_aStructParentTree.size(); 1787 for( sal_Int32 n = 0; n < nTreeItems; n++ ) 1788 { 1789 aLine.append( n ); 1790 aLine.append( ' ' ); 1791 aLine.append( m_aStructParentTree[n] ); 1792 aLine.append( "\n" ); 1793 } 1794 aLine.append( "]>>\nendobj\n\n" ); 1795 CHECK_RETURN( updateObject( nObject ) ); 1796 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 1797 } 1798 return nObject; 1799 } 1800 1801 const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr ) 1802 { 1803 static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings; 1804 // fill maps once 1805 if( aAttributeStrings.empty() ) 1806 { 1807 aAttributeStrings[ PDFWriter::Placement ] = "Placement"; 1808 aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode"; 1809 aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore"; 1810 aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter"; 1811 aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent"; 1812 aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent"; 1813 aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent"; 1814 aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign"; 1815 aAttributeStrings[ PDFWriter::Width ] = "Width"; 1816 aAttributeStrings[ PDFWriter::Height ] = "Height"; 1817 aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign"; 1818 aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign"; 1819 aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight"; 1820 aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift"; 1821 aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType"; 1822 aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering"; 1823 aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan"; 1824 aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan"; 1825 aAttributeStrings[ PDFWriter::Scope ] = "Scope"; 1826 aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation"; 1827 } 1828 1829 std::map< PDFWriter::StructAttribute, const char* >::const_iterator it = 1830 aAttributeStrings.find( eAttr ); 1831 1832 if( it == aAttributeStrings.end() ) 1833 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr); 1834 1835 return it != aAttributeStrings.end() ? it->second : ""; 1836 } 1837 1838 const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal ) 1839 { 1840 static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings; 1841 1842 if( aValueStrings.empty() ) 1843 { 1844 aValueStrings[ PDFWriter::NONE ] = "None"; 1845 aValueStrings[ PDFWriter::Block ] = "Block"; 1846 aValueStrings[ PDFWriter::Inline ] = "Inline"; 1847 aValueStrings[ PDFWriter::Before ] = "Before"; 1848 aValueStrings[ PDFWriter::After ] = "After"; 1849 aValueStrings[ PDFWriter::Start ] = "Start"; 1850 aValueStrings[ PDFWriter::End ] = "End"; 1851 aValueStrings[ PDFWriter::LrTb ] = "LrTb"; 1852 aValueStrings[ PDFWriter::RlTb ] = "RlTb"; 1853 aValueStrings[ PDFWriter::TbRl ] = "TbRl"; 1854 aValueStrings[ PDFWriter::Center ] = "Center"; 1855 aValueStrings[ PDFWriter::Justify ] = "Justify"; 1856 aValueStrings[ PDFWriter::Auto ] = "Auto"; 1857 aValueStrings[ PDFWriter::Middle ] = "Middle"; 1858 aValueStrings[ PDFWriter::Normal ] = "Normal"; 1859 aValueStrings[ PDFWriter::Underline ] = "Underline"; 1860 aValueStrings[ PDFWriter::Overline ] = "Overline"; 1861 aValueStrings[ PDFWriter::LineThrough ] = "LineThrough"; 1862 aValueStrings[ PDFWriter::Row ] = "Row"; 1863 aValueStrings[ PDFWriter::Column ] = "Column"; 1864 aValueStrings[ PDFWriter::Both ] = "Both"; 1865 aValueStrings[ PDFWriter::Disc ] = "Disc"; 1866 aValueStrings[ PDFWriter::Circle ] = "Circle"; 1867 aValueStrings[ PDFWriter::Square ] = "Square"; 1868 aValueStrings[ PDFWriter::Decimal ] = "Decimal"; 1869 aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman"; 1870 aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman"; 1871 aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha"; 1872 aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha"; 1873 } 1874 1875 std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it = 1876 aValueStrings.find( eVal ); 1877 1878 if( it == aValueStrings.end() ) 1879 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal); 1880 1881 return it != aValueStrings.end() ? it->second : ""; 1882 } 1883 1884 static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt ) 1885 { 1886 o_rLine.append( "/" ); 1887 o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) ); 1888 1889 if( i_rVal.eValue != PDFWriter::Invalid ) 1890 { 1891 o_rLine.append( "/" ); 1892 o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) ); 1893 } 1894 else 1895 { 1896 // numerical value 1897 o_rLine.append( " " ); 1898 if( i_bIsFixedInt ) 1899 appendFixedInt( i_rVal.nValue, o_rLine ); 1900 else 1901 o_rLine.append( i_rVal.nValue ); 1902 } 1903 o_rLine.append( "\n" ); 1904 } 1905 1906 OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) 1907 { 1908 // create layout, list and table attribute sets 1909 OStringBuffer aLayout(256), aList(64), aTable(64); 1910 for (auto const& attribute : i_rEle.m_aAttributes) 1911 { 1912 if( attribute.first == PDFWriter::ListNumbering ) 1913 appendStructureAttributeLine( attribute.first, attribute.second, aList, true ); 1914 else if( attribute.first == PDFWriter::RowSpan || 1915 attribute.first == PDFWriter::ColSpan || 1916 attribute.first == PDFWriter::Scope) 1917 { 1918 appendStructureAttributeLine( attribute.first, attribute.second, aTable, false ); 1919 } 1920 else if( attribute.first == PDFWriter::LinkAnnotation ) 1921 { 1922 sal_Int32 nLink = attribute.second.nValue; 1923 std::map< sal_Int32, sal_Int32 >::const_iterator link_it = 1924 m_aLinkPropertyMap.find( nLink ); 1925 if( link_it != m_aLinkPropertyMap.end() ) 1926 nLink = link_it->second; 1927 if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() ) 1928 { 1929 // update struct parent of link 1930 OString aStructParentEntry = 1931 OString::number( i_rEle.m_nObject ) + 1932 " 0 R"; 1933 m_aStructParentTree.push_back( aStructParentEntry ); 1934 m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1; 1935 1936 sal_Int32 nRefObject = createObject(); 1937 if (updateObject(nRefObject)) 1938 { 1939 OString aRef = 1940 OString::number( nRefObject ) + 1941 " 0 obj\n" 1942 "<</Type/OBJR/Obj " + 1943 OString::number( m_aLinks[ nLink ].m_nObject ) + 1944 " 0 R>>\n" 1945 "endobj\n\n"; 1946 writeBuffer( aRef.getStr(), aRef.getLength() ); 1947 } 1948 1949 i_rEle.m_aKids.emplace_back( nRefObject ); 1950 } 1951 else 1952 { 1953 OSL_FAIL( "unresolved link id for Link structure" ); 1954 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure"); 1955 if (g_bDebugDisableCompression) 1956 { 1957 OString aLine = "unresolved link id " + 1958 OString::number( nLink ) + 1959 " for Link structure"; 1960 emitComment( aLine.getStr() ); 1961 } 1962 } 1963 } 1964 else 1965 appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true ); 1966 } 1967 if( ! i_rEle.m_aBBox.IsEmpty() ) 1968 { 1969 aLayout.append( "/BBox[" ); 1970 appendFixedInt( i_rEle.m_aBBox.Left(), aLayout ); 1971 aLayout.append( " " ); 1972 appendFixedInt( i_rEle.m_aBBox.Top(), aLayout ); 1973 aLayout.append( " " ); 1974 appendFixedInt( i_rEle.m_aBBox.Right(), aLayout ); 1975 aLayout.append( " " ); 1976 appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout ); 1977 aLayout.append( "]\n" ); 1978 } 1979 1980 std::vector< sal_Int32 > aAttribObjects; 1981 if( !aLayout.isEmpty() ) 1982 { 1983 aAttribObjects.push_back( createObject() ); 1984 if (updateObject( aAttribObjects.back() )) 1985 { 1986 OStringBuffer aObj( 64 ); 1987 aObj.append( aAttribObjects.back() ); 1988 aObj.append( " 0 obj\n" 1989 "<</O/Layout\n" ); 1990 aLayout.append( ">>\nendobj\n\n" ); 1991 writeBuffer( aObj.getStr(), aObj.getLength() ); 1992 writeBuffer( aLayout.getStr(), aLayout.getLength() ); 1993 } 1994 } 1995 if( !aList.isEmpty() ) 1996 { 1997 aAttribObjects.push_back( createObject() ); 1998 if (updateObject( aAttribObjects.back() )) 1999 { 2000 OStringBuffer aObj( 64 ); 2001 aObj.append( aAttribObjects.back() ); 2002 aObj.append( " 0 obj\n" 2003 "<</O/List\n" ); 2004 aList.append( ">>\nendobj\n\n" ); 2005 writeBuffer( aObj.getStr(), aObj.getLength() ); 2006 writeBuffer( aList.getStr(), aList.getLength() ); 2007 } 2008 } 2009 if( !aTable.isEmpty() ) 2010 { 2011 aAttribObjects.push_back( createObject() ); 2012 if (updateObject( aAttribObjects.back() )) 2013 { 2014 OStringBuffer aObj( 64 ); 2015 aObj.append( aAttribObjects.back() ); 2016 aObj.append( " 0 obj\n" 2017 "<</O/Table\n" ); 2018 aTable.append( ">>\nendobj\n\n" ); 2019 writeBuffer( aObj.getStr(), aObj.getLength() ); 2020 writeBuffer( aTable.getStr(), aTable.getLength() ); 2021 } 2022 } 2023 2024 OStringBuffer aRet( 64 ); 2025 if( aAttribObjects.size() > 1 ) 2026 aRet.append( " [" ); 2027 for (auto const& attrib : aAttribObjects) 2028 { 2029 aRet.append( " " ); 2030 aRet.append( attrib ); 2031 aRet.append( " 0 R" ); 2032 } 2033 if( aAttribObjects.size() > 1 ) 2034 aRet.append( " ]" ); 2035 return aRet.makeStringAndClear(); 2036 } 2037 2038 sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) 2039 { 2040 if( 2041 // do not emit NonStruct and its children 2042 rEle.m_eType == PDFWriter::NonStructElement && 2043 rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root 2044 ) 2045 return 0; 2046 2047 for (auto const& child : rEle.m_aChildren) 2048 { 2049 if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() ) 2050 { 2051 PDFStructureElement& rChild = m_aStructure[ child ]; 2052 if( rChild.m_eType != PDFWriter::NonStructElement ) 2053 { 2054 if( rChild.m_nParentElement == rEle.m_nOwnElement ) 2055 emitStructure( rChild ); 2056 else 2057 { 2058 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" ); 2059 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child); 2060 } 2061 } 2062 } 2063 else 2064 { 2065 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" ); 2066 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child); 2067 } 2068 } 2069 2070 OStringBuffer aLine( 512 ); 2071 aLine.append( rEle.m_nObject ); 2072 aLine.append( " 0 obj\n" 2073 "<</Type" ); 2074 sal_Int32 nParentTree = -1; 2075 if( rEle.m_nOwnElement == rEle.m_nParentElement ) 2076 { 2077 nParentTree = createObject(); 2078 CHECK_RETURN( nParentTree ); 2079 aLine.append( "/StructTreeRoot\n" ); 2080 aLine.append( "/ParentTree " ); 2081 aLine.append( nParentTree ); 2082 aLine.append( " 0 R\n" ); 2083 if( ! m_aRoleMap.empty() ) 2084 { 2085 aLine.append( "/RoleMap<<" ); 2086 for (auto const& role : m_aRoleMap) 2087 { 2088 aLine.append( '/' ); 2089 aLine.append(role.first); 2090 aLine.append( '/' ); 2091 aLine.append( role.second ); 2092 aLine.append( '\n' ); 2093 } 2094 aLine.append( ">>\n" ); 2095 } 2096 } 2097 else 2098 { 2099 aLine.append( "/StructElem\n" 2100 "/S/" ); 2101 if( !rEle.m_aAlias.isEmpty() ) 2102 aLine.append( rEle.m_aAlias ); 2103 else 2104 aLine.append( getStructureTag( rEle.m_eType ) ); 2105 aLine.append( "\n" 2106 "/P " ); 2107 aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject ); 2108 aLine.append( " 0 R\n" 2109 "/Pg " ); 2110 aLine.append( rEle.m_nFirstPageObject ); 2111 aLine.append( " 0 R\n" ); 2112 if( !rEle.m_aActualText.isEmpty() ) 2113 { 2114 aLine.append( "/ActualText" ); 2115 appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine ); 2116 aLine.append( "\n" ); 2117 } 2118 if( !rEle.m_aAltText.isEmpty() ) 2119 { 2120 aLine.append( "/Alt" ); 2121 appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine ); 2122 aLine.append( "\n" ); 2123 } 2124 } 2125 if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) ) 2126 { 2127 OString aAttribs = emitStructureAttributes( rEle ); 2128 if( !aAttribs.isEmpty() ) 2129 { 2130 aLine.append( "/A" ); 2131 aLine.append( aAttribs ); 2132 aLine.append( "\n" ); 2133 } 2134 } 2135 if( !rEle.m_aLocale.Language.isEmpty() ) 2136 { 2137 /* PDF allows only RFC 3066, which is only partly BCP 47 and does not 2138 * include script tags and others. 2139 * http://pdf.editme.com/pdfua-naturalLanguageSpecification 2140 * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886 2141 * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification 2142 * */ 2143 LanguageTag aLanguageTag( rEle.m_aLocale); 2144 OUString aLanguage, aScript, aCountry; 2145 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry); 2146 if (!aLanguage.isEmpty()) 2147 { 2148 OUStringBuffer aLocBuf( 16 ); 2149 aLocBuf.append( aLanguage ); 2150 if( !aCountry.isEmpty() ) 2151 { 2152 aLocBuf.append( '-' ); 2153 aLocBuf.append( aCountry ); 2154 } 2155 aLine.append( "/Lang" ); 2156 appendLiteralStringEncrypt( aLocBuf, rEle.m_nObject, aLine ); 2157 aLine.append( "\n" ); 2158 } 2159 } 2160 if( ! rEle.m_aKids.empty() ) 2161 { 2162 unsigned int i = 0; 2163 aLine.append( "/K[" ); 2164 for (auto const& kid : rEle.m_aKids) 2165 { 2166 if( kid.nMCID == -1 ) 2167 { 2168 aLine.append( kid.nObject ); 2169 aLine.append( " 0 R" ); 2170 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " ); 2171 } 2172 else 2173 { 2174 if( kid.nObject == rEle.m_nFirstPageObject ) 2175 { 2176 aLine.append( kid.nMCID ); 2177 aLine.append( " " ); 2178 } 2179 else 2180 { 2181 aLine.append( "<</Type/MCR/Pg " ); 2182 aLine.append( kid.nObject ); 2183 aLine.append( " 0 R /MCID " ); 2184 aLine.append( kid.nMCID ); 2185 aLine.append( ">>\n" ); 2186 } 2187 } 2188 ++i; 2189 } 2190 aLine.append( "]\n" ); 2191 } 2192 aLine.append( ">>\nendobj\n\n" ); 2193 2194 CHECK_RETURN( updateObject( rEle.m_nObject ) ); 2195 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 2196 2197 CHECK_RETURN( emitStructParentTree( nParentTree ) ); 2198 2199 return rEle.m_nObject; 2200 } 2201 2202 bool PDFWriterImpl::emitGradients() 2203 { 2204 for (auto const& gradient : m_aGradients) 2205 { 2206 if ( !writeGradientFunction( gradient ) ) return false; 2207 } 2208 return true; 2209 } 2210 2211 bool PDFWriterImpl::emitTilings() 2212 { 2213 OStringBuffer aTilingObj( 1024 ); 2214 2215 for (auto & tiling : m_aTilings) 2216 { 2217 SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" ); 2218 if( ! tiling.m_pTilingStream ) 2219 continue; 2220 2221 aTilingObj.setLength( 0 ); 2222 2223 if (g_bDebugDisableCompression) 2224 { 2225 emitComment( "PDFWriterImpl::emitTilings" ); 2226 } 2227 2228 sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left()); 2229 sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top()); 2230 sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth()); 2231 sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight()); 2232 if( tiling.m_aCellSize.Width() == 0 ) 2233 tiling.m_aCellSize.setWidth( nW ); 2234 if( tiling.m_aCellSize.Height() == 0 ) 2235 tiling.m_aCellSize.setHeight( nH ); 2236 2237 bool bDeflate = compressStream( tiling.m_pTilingStream.get() ); 2238 sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd(); 2239 tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN ); 2240 2241 // write pattern object 2242 aTilingObj.append( tiling.m_nObject ); 2243 aTilingObj.append( " 0 obj\n" ); 2244 aTilingObj.append( "<</Type/Pattern/PatternType 1\n" 2245 "/PaintType 1\n" 2246 "/TilingType 2\n" 2247 "/BBox[" ); 2248 appendFixedInt( nX, aTilingObj ); 2249 aTilingObj.append( ' ' ); 2250 appendFixedInt( nY, aTilingObj ); 2251 aTilingObj.append( ' ' ); 2252 appendFixedInt( nX+nW, aTilingObj ); 2253 aTilingObj.append( ' ' ); 2254 appendFixedInt( nY+nH, aTilingObj ); 2255 aTilingObj.append( "]\n" 2256 "/XStep " ); 2257 appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj ); 2258 aTilingObj.append( "\n" 2259 "/YStep " ); 2260 appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj ); 2261 aTilingObj.append( "\n" ); 2262 if( tiling.m_aTransform.matrix[0] != 1.0 || 2263 tiling.m_aTransform.matrix[1] != 0.0 || 2264 tiling.m_aTransform.matrix[3] != 0.0 || 2265 tiling.m_aTransform.matrix[4] != 1.0 || 2266 tiling.m_aTransform.matrix[2] != 0.0 || 2267 tiling.m_aTransform.matrix[5] != 0.0 ) 2268 { 2269 aTilingObj.append( "/Matrix [" ); 2270 // TODO: scaling, mirroring on y, etc 2271 appendDouble( tiling.m_aTransform.matrix[0], aTilingObj ); 2272 aTilingObj.append( ' ' ); 2273 appendDouble( tiling.m_aTransform.matrix[1], aTilingObj ); 2274 aTilingObj.append( ' ' ); 2275 appendDouble( tiling.m_aTransform.matrix[3], aTilingObj ); 2276 aTilingObj.append( ' ' ); 2277 appendDouble( tiling.m_aTransform.matrix[4], aTilingObj ); 2278 aTilingObj.append( ' ' ); 2279 appendDouble( tiling.m_aTransform.matrix[2], aTilingObj ); 2280 aTilingObj.append( ' ' ); 2281 appendDouble( tiling.m_aTransform.matrix[5], aTilingObj ); 2282 aTilingObj.append( "]\n" ); 2283 } 2284 aTilingObj.append( "/Resources" ); 2285 tiling.m_aResources.append( aTilingObj, getFontDictObject() ); 2286 if( bDeflate ) 2287 aTilingObj.append( "/Filter/FlateDecode" ); 2288 aTilingObj.append( "/Length " ); 2289 aTilingObj.append( static_cast<sal_Int32>(nTilingStreamSize) ); 2290 aTilingObj.append( ">>\nstream\n" ); 2291 if ( !updateObject( tiling.m_nObject ) ) return false; 2292 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false; 2293 checkAndEnableStreamEncryption( tiling.m_nObject ); 2294 bool written = writeBuffer( tiling.m_pTilingStream->GetData(), nTilingStreamSize ); 2295 tiling.m_pTilingStream.reset(); 2296 if( !written ) 2297 return false; 2298 disableStreamEncryption(); 2299 aTilingObj.setLength( 0 ); 2300 aTilingObj.append( "\nendstream\nendobj\n\n" ); 2301 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false; 2302 } 2303 return true; 2304 } 2305 2306 sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject) 2307 { 2308 if( !pFD ) 2309 return 0; 2310 const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont(); 2311 2312 OStringBuffer aLine( 1024 ); 2313 2314 if( nFontObject <= 0 ) 2315 nFontObject = createObject(); 2316 CHECK_RETURN( updateObject( nFontObject ) ); 2317 aLine.append( nFontObject ); 2318 aLine.append( " 0 obj\n" 2319 "<</Type/Font/Subtype/Type1/BaseFont/" ); 2320 appendName( rBuildinFont.m_pPSName, aLine ); 2321 aLine.append( "\n" ); 2322 if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 ) 2323 aLine.append( "/Encoding/WinAnsiEncoding\n" ); 2324 aLine.append( ">>\nendobj\n\n" ); 2325 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 2326 return nFontObject; 2327 } 2328 2329 namespace 2330 { 2331 // Translate units from TT to PS (standard 1/1000) 2332 int XUnits(int nUPEM, int n) { return (n * 1000) / nUPEM; } 2333 } 2334 2335 std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFace, EmbedFont const & rEmbed ) 2336 { 2337 std::map< sal_Int32, sal_Int32 > aRet; 2338 2339 if (g_bDebugDisableCompression) 2340 emitComment("PDFWriterImpl::emitSystemFont"); 2341 2342 FontSubsetInfo aInfo; 2343 // fill in dummy values 2344 aInfo.m_nAscent = 1000; 2345 aInfo.m_nDescent = 200; 2346 aInfo.m_nCapHeight = 1000; 2347 aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) ); 2348 aInfo.m_aPSName = pFace->GetFamilyName(); 2349 2350 sal_Int32 pWidths[256] = { 0 }; 2351 const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance; 2352 auto nUPEM = pFace->UnitsPerEm(); 2353 for( sal_Ucs c = 32; c < 256; c++ ) 2354 { 2355 sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c); 2356 pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false)); 2357 } 2358 2359 // We are interested only in filling aInfo 2360 sal_GlyphId aGlyphIds[] = { 0 }; 2361 sal_uInt8 pEncoding[] = { 0 }; 2362 std::vector<sal_uInt8> aBuffer; 2363 pFace->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo); 2364 2365 // write font descriptor 2366 sal_Int32 nFontDescriptor = emitFontDescriptor( pFace, aInfo, 0, 0 ); 2367 if( nFontDescriptor ) 2368 { 2369 // write font object 2370 sal_Int32 nObject = createObject(); 2371 if( updateObject( nObject ) ) 2372 { 2373 OStringBuffer aLine( 1024 ); 2374 aLine.append( nObject ); 2375 aLine.append( " 0 obj\n" 2376 "<</Type/Font/Subtype/TrueType" ); 2377 aLine.append( "/BaseFont/" ); 2378 appendName( aInfo.m_aPSName, aLine ); 2379 aLine.append( "\n" ); 2380 if (!pFace->IsMicrosoftSymbolEncoded()) 2381 aLine.append( "/Encoding/WinAnsiEncoding\n" ); 2382 aLine.append( "/FirstChar 32 /LastChar 255\n" 2383 "/Widths[" ); 2384 for( int i = 32; i < 256; i++ ) 2385 { 2386 aLine.append( pWidths[i] ); 2387 aLine.append( ((i&15) == 15) ? "\n" : " " ); 2388 } 2389 aLine.append( "]\n" 2390 "/FontDescriptor " ); 2391 aLine.append( nFontDescriptor ); 2392 aLine.append( " 0 R>>\n" 2393 "endobj\n\n" ); 2394 writeBuffer( aLine.getStr(), aLine.getLength() ); 2395 2396 aRet[ rEmbed.m_nNormalFontID ] = nObject; 2397 } 2398 } 2399 2400 return aRet; 2401 } 2402 2403 namespace 2404 { 2405 uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths, 2406 sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex, 2407 sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits, 2408 sal_Int32& nToUnicodeStream) 2409 { 2410 rCodeUnits.reserve(256); 2411 2412 // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine 2413 pWidths[0] = 0; 2414 2415 uint32_t nGlyphs = 1; 2416 for (auto const& item : rSubset.m_aMapping) 2417 { 2418 sal_uInt8 nEnc = item.second.getGlyphId(); 2419 2420 SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", 2421 "duplicate glyph"); 2422 SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding"); 2423 2424 pGlyphIds[nEnc] = item.first; 2425 pEncoding[nEnc] = nEnc; 2426 pEncToUnicodeIndex[nEnc] = static_cast<sal_Int32>(rCodeUnits.size()); 2427 pCodeUnitsPerGlyph[nEnc] = item.second.countCodes(); 2428 pWidths[nEnc] = item.second.getGlyphWidth(); 2429 for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++) 2430 rCodeUnits.push_back(item.second.getCode(n)); 2431 if (item.second.getCode(0)) 2432 nToUnicodeStream = 1; 2433 if (nGlyphs < 256) 2434 nGlyphs++; 2435 else 2436 OSL_FAIL("too many glyphs for subset"); 2437 } 2438 2439 return nGlyphs; 2440 } 2441 } 2442 2443 bool PDFWriterImpl::emitType3Font(const vcl::font::PhysicalFontFace* pFace, 2444 const FontSubset& rType3Font, 2445 std::map<sal_Int32, sal_Int32>& rFontIDToObject) 2446 { 2447 if (g_bDebugDisableCompression) 2448 emitComment("PDFWriterImpl::emitType3Font"); 2449 2450 const auto& rColorPalettes = pFace->GetColorPalettes(); 2451 2452 FontSubsetInfo aSubsetInfo; 2453 sal_GlyphId pTempGlyphIds[] = { 0 }; 2454 sal_uInt8 pTempEncoding[] = { 0 }; 2455 std::vector<sal_uInt8> aBuffer; 2456 pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo); 2457 2458 for (auto& rSubset : rType3Font.m_aSubsets) 2459 { 2460 sal_GlyphId pGlyphIds[256] = {}; 2461 sal_Int32 pWidths[256]; 2462 sal_uInt8 pEncoding[256] = {}; 2463 sal_Int32 pEncToUnicodeIndex[256] = {}; 2464 sal_Int32 pCodeUnitsPerGlyph[256] = {}; 2465 std::vector<sal_Ucs> aCodeUnits; 2466 sal_Int32 nToUnicodeStream = 0; 2467 2468 // fill arrays and prepare encoding index map 2469 auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex, 2470 pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream); 2471 2472 // write font descriptor 2473 sal_Int32 nFontDescriptor = 0; 2474 if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4) 2475 nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0); 2476 2477 if (nToUnicodeStream) 2478 nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph, 2479 pEncToUnicodeIndex, nGlyphs); 2480 2481 // write font object 2482 sal_Int32 nFontObject = createObject(); 2483 if (!updateObject(nFontObject)) 2484 return false; 2485 2486 OStringBuffer aLine(1024); 2487 aLine.append(nFontObject); 2488 aLine.append(" 0 obj\n" 2489 "<</Type/Font/Subtype/Type3/Name/"); 2490 appendName(aSubsetInfo.m_aPSName, aLine); 2491 2492 aLine.append("\n/FontBBox["); 2493 // note: Top and Bottom are reversed in VCL and PDF rectangles 2494 aLine.append(OString::number(aSubsetInfo.m_aFontBBox.Left())); 2495 aLine.append(' '); 2496 aLine.append(OString::number(aSubsetInfo.m_aFontBBox.Top())); 2497 aLine.append(' '); 2498 aLine.append(OString::number(aSubsetInfo.m_aFontBBox.Right())); 2499 aLine.append(' '); 2500 aLine.append(OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1)); 2501 aLine.append("]\n"); 2502 2503 auto nScale = 1. / pFace->UnitsPerEm(); 2504 aLine.append("/FontMatrix["); 2505 aLine.append(nScale); 2506 aLine.append(" 0 0 "); 2507 aLine.append(nScale); 2508 aLine.append(" 0 0]\n"); 2509 2510 sal_Int32 pGlyphStreams[256] = {}; 2511 aLine.append("/CharProcs<<\n"); 2512 for (auto i = 1u; i < nGlyphs; i++) 2513 { 2514 auto nStream = createObject(); 2515 aLine.append("/"); 2516 aLine.append(pFace->GetGlyphName(pGlyphIds[i], true)); 2517 aLine.append(" "); 2518 aLine.append(nStream); 2519 aLine.append(" 0 R\n"); 2520 pGlyphStreams[i] = nStream; 2521 } 2522 aLine.append(">>\n"); 2523 2524 aLine.append("/Encoding<</Type/Encoding/Differences[1"); 2525 for (auto i = 1u; i < nGlyphs; i++) 2526 aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true)); 2527 aLine.append("]>>\n"); 2528 2529 aLine.append("/FirstChar 0\n" 2530 "/LastChar "); 2531 aLine.append(OString::number(nGlyphs)); 2532 aLine.append("\n"); 2533 2534 aLine.append("/Widths["); 2535 for (auto i = 0u; i < nGlyphs; i++) 2536 { 2537 aLine.append(pWidths[i]); 2538 aLine.append(" "); 2539 } 2540 aLine.append("]\n"); 2541 2542 if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4) 2543 { 2544 aLine.append("/FontDescriptor "); 2545 aLine.append(nFontDescriptor); 2546 aLine.append(" 0 R\n"); 2547 } 2548 2549 auto nResources = createObject(); 2550 aLine.append("/Resources "); 2551 aLine.append(nResources); 2552 aLine.append(" 0 R\n"); 2553 2554 if (nToUnicodeStream) 2555 { 2556 aLine.append("/ToUnicode "); 2557 aLine.append(nToUnicodeStream); 2558 aLine.append(" 0 R\n"); 2559 } 2560 2561 aLine.append(">>\n" 2562 "endobj\n\n"); 2563 2564 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 2565 return false; 2566 2567 std::set<sal_Int32> aUsedFonts; 2568 std::list<BitmapEmit> aUsedBitmaps; 2569 std::map<sal_uInt8, sal_Int32> aUsedAlpha; 2570 ResourceDict aResourceDict; 2571 std::list<StreamRedirect> aOutputStreams; 2572 2573 for (auto i = 1u; i < nGlyphs; i++) 2574 { 2575 auto nStream = pGlyphStreams[i]; 2576 if (!updateObject(nStream)) 2577 return false; 2578 OStringBuffer aContents(1024); 2579 aContents.append(pWidths[i]); 2580 aContents.append(" 0 d0\n"); 2581 2582 const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second; 2583 const auto& rLayers = rGlyph.getColorLayers(); 2584 for (const auto& rLayer : rLayers) 2585 { 2586 aUsedFonts.insert(rLayer.m_nFontID); 2587 2588 aContents.append("q "); 2589 // 0xFFFF is a special value means foreground color. 2590 if (rLayer.m_nColorIndex != 0xFFFF) 2591 { 2592 auto& rPalette = rColorPalettes[0]; 2593 auto aColor(rPalette[rLayer.m_nColorIndex]); 2594 appendNonStrokingColor(aColor, aContents); 2595 aContents.append(" "); 2596 if (aColor.GetAlpha() != 0xFF 2597 && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4) 2598 { 2599 auto nAlpha = aColor.GetAlpha(); 2600 OStringBuffer aName(16); 2601 aName.append("GS"); 2602 appendHex(nAlpha, aName); 2603 2604 aContents.append("/" + aName + " gs "); 2605 2606 if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end()) 2607 { 2608 auto nObject = createObject(); 2609 aUsedAlpha[nAlpha] = nObject; 2610 pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(), 2611 nObject, aResourceDict, aOutputStreams); 2612 } 2613 } 2614 } 2615 aContents.append("BT "); 2616 aContents.append("/F" + OString::number(rLayer.m_nFontID) + " "); 2617 aContents.append(OString::number(pFace->UnitsPerEm()) + " Tf "); 2618 aContents.append("<"); 2619 appendHex(rLayer.m_nSubsetGlyphID, aContents); 2620 aContents.append(">Tj "); 2621 aContents.append("ET "); 2622 aContents.append("Q\n"); 2623 } 2624 2625 tools::Rectangle aRect; 2626 const auto& rBitmapData = rGlyph.getColorBitmap(aRect); 2627 if (!rBitmapData.empty()) 2628 { 2629 SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(), 2630 StreamMode::READ); 2631 vcl::PngImageReader aReader(aStream); 2632 auto aBitmapEmit = createBitmapEmit(std::move(aReader.read()), Graphic(), 2633 aUsedBitmaps, aResourceDict, aOutputStreams); 2634 2635 auto nObject = aBitmapEmit.m_aReferenceXObject.getObject(); 2636 aContents.append("q "); 2637 aContents.append(aRect.GetWidth()); 2638 aContents.append(" 0 0 "); 2639 aContents.append(aRect.GetHeight()); 2640 aContents.append(" "); 2641 aContents.append(aRect.getX()); 2642 aContents.append(" "); 2643 aContents.append(aRect.getY()); 2644 aContents.append(" cm "); 2645 aContents.append("/Im"); 2646 aContents.append(nObject); 2647 aContents.append(" Do Q\n"); 2648 } 2649 2650 const auto& rOutline = rGlyph.getOutline(); 2651 if (rOutline.count()) 2652 { 2653 // XXX I have no idea why this transformation matrix is needed. 2654 aContents.append("q 10 0 0 10 0 "); 2655 appendDouble(m_aPages.back().getHeight() * -10, aContents, 3); 2656 aContents.append(" cm\n"); 2657 m_aPages.back().appendPolyPolygon(rOutline, aContents); 2658 aContents.append("f\n"); 2659 aContents.append("Q\n"); 2660 } 2661 2662 aLine.setLength(0); 2663 aLine.append(nStream); 2664 aLine.append(" 0 obj\n<</Length "); 2665 aLine.append(aContents.getLength()); 2666 aLine.append(">>\nstream\n"); 2667 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 2668 return false; 2669 if (!writeBuffer(aContents.getStr(), aContents.getLength())) 2670 return false; 2671 aLine.setLength(0); 2672 aLine.append("endstream\nendobj\n\n"); 2673 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 2674 return false; 2675 } 2676 2677 // write font dict 2678 auto nFontDict = createObject(); 2679 aLine.setLength(0); 2680 aLine.append(nFontDict); 2681 aLine.append(" 0 obj\n<<"); 2682 for (auto nFontID : aUsedFonts) 2683 { 2684 aLine.append("/F"); 2685 aLine.append(nFontID); 2686 aLine.append(" "); 2687 aLine.append(rFontIDToObject[nFontID]); 2688 aLine.append(" 0 R"); 2689 } 2690 aLine.append(">>\nendobj\n\n"); 2691 if (!updateObject(nFontDict)) 2692 return false; 2693 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 2694 return false; 2695 2696 // write ExtGState objects 2697 if (!aUsedAlpha.empty()) 2698 { 2699 for (const auto & [ nAlpha, nObject ] : aUsedAlpha) 2700 { 2701 aLine.setLength(0); 2702 aLine.append(nObject); 2703 aLine.append(" 0 obj\n<<"); 2704 if (m_bIsPDF_A1) 2705 { 2706 aLine.append("/CA 1.0/ca 1.0"); 2707 m_aErrors.insert(PDFWriter::Warning_Transparency_Omitted_PDFA); 2708 } 2709 else 2710 { 2711 aLine.append("/CA "); 2712 appendDouble(nAlpha / 255., aLine); 2713 aLine.append("/ca "); 2714 appendDouble(nAlpha / 255., aLine); 2715 } 2716 aLine.append(">>\nendobj\n\n"); 2717 if (!updateObject(nObject)) 2718 return false; 2719 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 2720 return false; 2721 } 2722 } 2723 2724 // write bitmap objects 2725 for (auto& aBitmap : aUsedBitmaps) 2726 writeBitmapObject(aBitmap); 2727 2728 // write resources dict 2729 aLine.setLength(0); 2730 aLine.append(nResources); 2731 aLine.append(" 0 obj\n"); 2732 aResourceDict.append(aLine, nFontDict); 2733 aLine.append("endobj\n\n"); 2734 if (!updateObject(nResources)) 2735 return false; 2736 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 2737 return false; 2738 2739 rFontIDToObject[rSubset.m_nFontID] = nFontObject; 2740 } 2741 2742 return true; 2743 } 2744 2745 typedef int ThreeInts[3]; 2746 static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen, 2747 ThreeInts& rSegmentLengths ) 2748 { 2749 if( !pFontBytes || (nByteLen < 0) ) 2750 return false; 2751 const unsigned char* pPtr = pFontBytes; 2752 const unsigned char* pEnd = pFontBytes + nByteLen; 2753 2754 for(int & rSegmentLength : rSegmentLengths) { 2755 // read segment1 header 2756 if( pPtr+6 >= pEnd ) 2757 return false; 2758 if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) ) 2759 return false; 2760 const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2]; 2761 if( nLen <= 0) 2762 return false; 2763 rSegmentLength = nLen; 2764 pPtr += nLen + 6; 2765 } 2766 2767 // read segment-end header 2768 if( pPtr+2 >= pEnd ) 2769 return false; 2770 if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) ) 2771 return false; 2772 2773 return true; 2774 } 2775 2776 static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer ) 2777 { 2778 if( nSubsetID ) 2779 { 2780 for( int i = 0; i < 6; i++ ) 2781 { 2782 int nOffset = nSubsetID % 26; 2783 nSubsetID /= 26; 2784 rBuffer.append( static_cast<char>('A'+nOffset) ); 2785 } 2786 rBuffer.append( '+' ); 2787 } 2788 appendName( rPSName, rBuffer ); 2789 } 2790 2791 sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding, 2792 const sal_Ucs* pCodeUnits, 2793 const sal_Int32* pCodeUnitsPerGlyph, 2794 const sal_Int32* pEncToUnicodeIndex, 2795 uint32_t nGlyphs ) 2796 { 2797 int nMapped = 0; 2798 for (auto n = 0u; n < nGlyphs; ++n) 2799 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] ) 2800 nMapped++; 2801 2802 if( nMapped == 0 ) 2803 return 0; 2804 2805 sal_Int32 nStream = createObject(); 2806 CHECK_RETURN( updateObject( nStream ) ); 2807 2808 OStringBuffer aContents( 1024 ); 2809 aContents.append( 2810 "/CIDInit/ProcSet findresource begin\n" 2811 "12 dict begin\n" 2812 "begincmap\n" 2813 "/CIDSystemInfo<<\n" 2814 "/Registry (Adobe)\n" 2815 "/Ordering (UCS)\n" 2816 "/Supplement 0\n" 2817 ">> def\n" 2818 "/CMapName/Adobe-Identity-UCS def\n" 2819 "/CMapType 2 def\n" 2820 "1 begincodespacerange\n" 2821 "<00> <FF>\n" 2822 "endcodespacerange\n" 2823 ); 2824 int nCount = 0; 2825 for (auto n = 0u; n < nGlyphs; ++n) 2826 { 2827 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] ) 2828 { 2829 if( (nCount % 100) == 0 ) 2830 { 2831 if( nCount ) 2832 aContents.append( "endbfchar\n" ); 2833 aContents.append( static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) ); 2834 aContents.append( " beginbfchar\n" ); 2835 } 2836 aContents.append( '<' ); 2837 appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents ); 2838 aContents.append( "> <" ); 2839 // TODO: handle code points>U+FFFF 2840 sal_Int32 nIndex = pEncToUnicodeIndex[n]; 2841 for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ ) 2842 { 2843 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] / 256), aContents ); 2844 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] & 255), aContents ); 2845 } 2846 aContents.append( ">\n" ); 2847 nCount++; 2848 } 2849 } 2850 aContents.append( "endbfchar\n" 2851 "endcmap\n" 2852 "CMapName currentdict /CMap defineresource pop\n" 2853 "end\n" 2854 "end\n" ); 2855 SvMemoryStream aStream; 2856 if (!g_bDebugDisableCompression) 2857 { 2858 ZCodec aCodec( 0x4000, 0x4000 ); 2859 aCodec.BeginCompression(); 2860 aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() ); 2861 aCodec.EndCompression(); 2862 } 2863 2864 if (g_bDebugDisableCompression) 2865 { 2866 emitComment( "PDFWriterImpl::createToUnicodeCMap" ); 2867 } 2868 OStringBuffer aLine( 40 ); 2869 2870 aLine.append( nStream ); 2871 aLine.append( " 0 obj\n<</Length " ); 2872 sal_Int32 nLen = 0; 2873 if (!g_bDebugDisableCompression) 2874 { 2875 nLen = static_cast<sal_Int32>(aStream.Tell()); 2876 aStream.Seek( 0 ); 2877 aLine.append( nLen ); 2878 aLine.append( "/Filter/FlateDecode" ); 2879 } 2880 else 2881 aLine.append( aContents.getLength() ); 2882 aLine.append( ">>\nstream\n" ); 2883 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 2884 checkAndEnableStreamEncryption( nStream ); 2885 if (!g_bDebugDisableCompression) 2886 { 2887 CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) ); 2888 } 2889 else 2890 { 2891 CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) ); 2892 } 2893 disableStreamEncryption(); 2894 aLine.setLength( 0 ); 2895 aLine.append( "\nendstream\n" 2896 "endobj\n\n" ); 2897 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 2898 return nStream; 2899 } 2900 2901 sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFace, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream ) 2902 { 2903 OStringBuffer aLine( 1024 ); 2904 // get font flags, see PDF reference 1.4 p. 358 2905 // possibly characters outside Adobe standard encoding 2906 // so set Symbolic flag 2907 sal_Int32 nFontFlags = (1<<2); 2908 if( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE ) 2909 nFontFlags |= (1 << 6); 2910 if( pFace->GetPitch() == PITCH_FIXED ) 2911 nFontFlags |= 1; 2912 if( pFace->GetFamilyType() == FAMILY_SCRIPT ) 2913 nFontFlags |= (1 << 3); 2914 else if( pFace->GetFamilyType() == FAMILY_ROMAN ) 2915 nFontFlags |= (1 << 1); 2916 2917 sal_Int32 nFontDescriptor = createObject(); 2918 CHECK_RETURN( updateObject( nFontDescriptor ) ); 2919 aLine.setLength( 0 ); 2920 aLine.append( nFontDescriptor ); 2921 aLine.append( " 0 obj\n" 2922 "<</Type/FontDescriptor/FontName/" ); 2923 appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine ); 2924 aLine.append( "\n" 2925 "/Flags " ); 2926 aLine.append( nFontFlags ); 2927 aLine.append( "\n" 2928 "/FontBBox[" ); 2929 // note: Top and Bottom are reversed in VCL and PDF rectangles 2930 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Left()) ); 2931 aLine.append( ' ' ); 2932 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Top()) ); 2933 aLine.append( ' ' ); 2934 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Right()) ); 2935 aLine.append( ' ' ); 2936 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Bottom()+1) ); 2937 aLine.append( "]/ItalicAngle " ); 2938 if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL ) 2939 aLine.append( "-30" ); 2940 else 2941 aLine.append( "0" ); 2942 aLine.append( "\n" 2943 "/Ascent " ); 2944 aLine.append( static_cast<sal_Int32>(rInfo.m_nAscent) ); 2945 aLine.append( "\n" 2946 "/Descent " ); 2947 aLine.append( static_cast<sal_Int32>(-rInfo.m_nDescent) ); 2948 aLine.append( "\n" 2949 "/CapHeight " ); 2950 aLine.append( static_cast<sal_Int32>(rInfo.m_nCapHeight) ); 2951 // According to PDF reference 1.4 StemV is required 2952 // seems a tad strange to me, but well ... 2953 aLine.append( "\n" 2954 "/StemV 80\n" ); 2955 if( nFontStream ) 2956 { 2957 aLine.append( "/FontFile" ); 2958 switch( rInfo.m_nFontType ) 2959 { 2960 case FontType::SFNT_TTF: 2961 aLine.append( '2' ); 2962 break; 2963 case FontType::TYPE1_PFA: 2964 case FontType::TYPE1_PFB: 2965 case FontType::ANY_TYPE1: 2966 break; 2967 default: 2968 OSL_FAIL( "unknown fonttype in PDF font descriptor" ); 2969 return 0; 2970 } 2971 aLine.append( ' ' ); 2972 aLine.append( nFontStream ); 2973 aLine.append( " 0 R\n" ); 2974 } 2975 aLine.append( ">>\n" 2976 "endobj\n\n" ); 2977 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 2978 2979 return nFontDescriptor; 2980 } 2981 2982 void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const 2983 { 2984 for (auto const& item : m_aBuildinFontToObjectMap) 2985 { 2986 rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() ); 2987 rDict.append( ' ' ); 2988 rDict.append( item.second ); 2989 rDict.append( " 0 R" ); 2990 } 2991 } 2992 2993 bool PDFWriterImpl::emitFonts() 2994 { 2995 OStringBuffer aLine( 1024 ); 2996 2997 std::map< sal_Int32, sal_Int32 > aFontIDToObject; 2998 2999 for (const auto & subset : m_aSubsets) 3000 { 3001 for (auto & s_subset :subset.second.m_aSubsets) 3002 { 3003 sal_GlyphId pGlyphIds[ 256 ] = {}; 3004 sal_Int32 pWidths[ 256 ]; 3005 sal_uInt8 pEncoding[ 256 ] = {}; 3006 sal_Int32 pEncToUnicodeIndex[ 256 ] = {}; 3007 sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {}; 3008 std::vector<sal_Ucs> aCodeUnits; 3009 sal_Int32 nToUnicodeStream = 0; 3010 3011 // fill arrays and prepare encoding index map 3012 auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex, 3013 pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream); 3014 3015 std::vector<sal_uInt8> aBuffer; 3016 FontSubsetInfo aSubsetInfo; 3017 const auto* pFace = subset.first; 3018 if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo)) 3019 { 3020 // create font stream 3021 if (g_bDebugDisableCompression) 3022 { 3023 emitComment( "PDFWriterImpl::emitFonts" ); 3024 } 3025 sal_Int32 nFontStream = createObject(); 3026 sal_Int32 nStreamLengthObject = createObject(); 3027 if ( !updateObject( nFontStream ) ) return false; 3028 aLine.setLength( 0 ); 3029 aLine.append( nFontStream ); 3030 aLine.append( " 0 obj\n" 3031 "<</Length " ); 3032 aLine.append( nStreamLengthObject ); 3033 if (!g_bDebugDisableCompression) 3034 aLine.append( " 0 R" 3035 "/Filter/FlateDecode" 3036 "/Length1 " ); 3037 else 3038 aLine.append( " 0 R" 3039 "/Length1 " ); 3040 3041 sal_uInt64 nStartPos = 0; 3042 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF ) 3043 { 3044 aLine.append( static_cast<sal_Int32>(aBuffer.size()) ); 3045 3046 aLine.append( ">>\n" 3047 "stream\n" ); 3048 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false; 3049 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false; 3050 3051 // copy font file 3052 beginCompression(); 3053 checkAndEnableStreamEncryption( nFontStream ); 3054 if (!writeBuffer(aBuffer.data(), aBuffer.size())) 3055 return false; 3056 } 3057 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT) 3058 { 3059 // TODO: implement 3060 OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" ); 3061 } 3062 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA? 3063 { 3064 // get the PFB-segment lengths 3065 ThreeInts aSegmentLengths = {0,0,0}; 3066 getPfbSegmentLengths(aBuffer.data(), static_cast<int>(aBuffer.size()), aSegmentLengths); 3067 // the lengths below are mandatory for PDF-exported Type1 fonts 3068 // because the PFB segment headers get stripped! WhyOhWhy. 3069 aLine.append( static_cast<sal_Int32>(aSegmentLengths[0]) ); 3070 aLine.append( "/Length2 " ); 3071 aLine.append( static_cast<sal_Int32>(aSegmentLengths[1]) ); 3072 aLine.append( "/Length3 " ); 3073 aLine.append( static_cast<sal_Int32>(aSegmentLengths[2]) ); 3074 3075 aLine.append( ">>\n" 3076 "stream\n" ); 3077 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false; 3078 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false; 3079 3080 // emit PFB-sections without section headers 3081 beginCompression(); 3082 checkAndEnableStreamEncryption( nFontStream ); 3083 if ( !writeBuffer( &aBuffer[6], aSegmentLengths[0] ) ) return false; 3084 if ( !writeBuffer( &aBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false; 3085 if ( !writeBuffer( &aBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false; 3086 } 3087 else 3088 { 3089 SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType)); 3090 aLine.append( "0 >>\nstream\n" ); 3091 } 3092 3093 endCompression(); 3094 disableStreamEncryption(); 3095 3096 sal_uInt64 nEndPos = 0; 3097 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false; 3098 // end the stream 3099 aLine.setLength( 0 ); 3100 aLine.append( "\nendstream\nendobj\n\n" ); 3101 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false; 3102 3103 // emit stream length object 3104 if ( !updateObject( nStreamLengthObject ) ) return false; 3105 aLine.setLength( 0 ); 3106 aLine.append( nStreamLengthObject ); 3107 aLine.append( " 0 obj\n" ); 3108 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) ); 3109 aLine.append( "\nendobj\n\n" ); 3110 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false; 3111 3112 // write font descriptor 3113 sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream ); 3114 3115 if( nToUnicodeStream ) 3116 nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs ); 3117 3118 sal_Int32 nFontObject = createObject(); 3119 if ( !updateObject( nFontObject ) ) return false; 3120 aLine.setLength( 0 ); 3121 aLine.append( nFontObject ); 3122 3123 aLine.append( " 0 obj\n" ); 3124 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ? 3125 "<</Type/Font/Subtype/Type1/BaseFont/" : 3126 "<</Type/Font/Subtype/TrueType/BaseFont/" ); 3127 appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine ); 3128 aLine.append( "\n" 3129 "/FirstChar 0\n" 3130 "/LastChar " ); 3131 aLine.append( static_cast<sal_Int32>(nGlyphs-1) ); 3132 aLine.append( "\n" 3133 "/Widths[" ); 3134 for (auto i = 0u; i < nGlyphs; i++) 3135 { 3136 aLine.append( pWidths[ i ] ); 3137 aLine.append( ((i & 15) == 15) ? "\n" : " " ); 3138 } 3139 aLine.append( "]\n" 3140 "/FontDescriptor " ); 3141 aLine.append( nFontDescriptor ); 3142 aLine.append( " 0 R\n" ); 3143 if( nToUnicodeStream ) 3144 { 3145 aLine.append( "/ToUnicode " ); 3146 aLine.append( nToUnicodeStream ); 3147 aLine.append( " 0 R\n" ); 3148 } 3149 aLine.append( ">>\n" 3150 "endobj\n\n" ); 3151 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false; 3152 3153 aFontIDToObject[ s_subset.m_nFontID ] = nFontObject; 3154 } 3155 else 3156 { 3157 OStringBuffer aErrorComment( 256 ); 3158 aErrorComment.append( "CreateFontSubset failed for font \"" ); 3159 aErrorComment.append( OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) ); 3160 aErrorComment.append( '\"' ); 3161 if( pFace->GetItalic() == ITALIC_NORMAL ) 3162 aErrorComment.append( " italic" ); 3163 else if( pFace->GetItalic() == ITALIC_OBLIQUE ) 3164 aErrorComment.append( " oblique" ); 3165 aErrorComment.append( " weight=" ); 3166 aErrorComment.append( sal_Int32(pFace->GetWeight()) ); 3167 emitComment( aErrorComment.getStr() ); 3168 } 3169 } 3170 } 3171 3172 // emit system fonts 3173 for (auto const& systemFont : m_aSystemFonts) 3174 { 3175 std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second ); 3176 for (auto const& item : aObjects) 3177 { 3178 if ( !item.second ) return false; 3179 aFontIDToObject[ item.first ] = item.second; 3180 } 3181 } 3182 3183 // emit Type3 fonts 3184 for (auto const& it : m_aType3Fonts) 3185 { 3186 if (!emitType3Font(it.first, it.second, aFontIDToObject)) 3187 return false; 3188 } 3189 3190 OStringBuffer aFontDict( 1024 ); 3191 aFontDict.append( getFontDictObject() ); 3192 aFontDict.append( " 0 obj\n" 3193 "<<" ); 3194 int ni = 0; 3195 for (auto const& itemMap : aFontIDToObject) 3196 { 3197 aFontDict.append( "/F" ); 3198 aFontDict.append( itemMap.first ); 3199 aFontDict.append( ' ' ); 3200 aFontDict.append( itemMap.second ); 3201 aFontDict.append( " 0 R" ); 3202 if( ((++ni) & 7) == 0 ) 3203 aFontDict.append( '\n' ); 3204 } 3205 // emit builtin font for widget appearances / variable text 3206 for (auto & item : m_aBuildinFontToObjectMap) 3207 { 3208 rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first)); 3209 item.second = emitBuildinFont( aData.get(), item.second ); 3210 } 3211 3212 appendBuildinFontsToDict(aFontDict); 3213 aFontDict.append( "\n>>\nendobj\n\n" ); 3214 3215 if ( !updateObject( getFontDictObject() ) ) return false; 3216 if ( !writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ) return false; 3217 return true; 3218 } 3219 3220 sal_Int32 PDFWriterImpl::emitResources() 3221 { 3222 // emit shadings 3223 if( ! m_aGradients.empty() ) 3224 CHECK_RETURN( emitGradients() ); 3225 // emit tilings 3226 if( ! m_aTilings.empty() ) 3227 CHECK_RETURN( emitTilings() ); 3228 3229 // emit font dict 3230 CHECK_RETURN( emitFonts() ); 3231 3232 // emit Resource dict 3233 OStringBuffer aLine( 512 ); 3234 sal_Int32 nResourceDict = getResourceDictObj(); 3235 CHECK_RETURN( updateObject( nResourceDict ) ); 3236 aLine.setLength( 0 ); 3237 aLine.append( nResourceDict ); 3238 aLine.append( " 0 obj\n" ); 3239 m_aGlobalResourceDict.append( aLine, getFontDictObject() ); 3240 aLine.append( "endobj\n\n" ); 3241 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 3242 return nResourceDict; 3243 } 3244 3245 sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts, 3246 sal_Int32 nItemLevel, 3247 sal_Int32 nCurrentItemId ) 3248 { 3249 /* The /Count number of an item is 3250 positive: the number of visible subitems 3251 negative: the negative number of subitems that will become visible if 3252 the item gets opened 3253 see PDF ref 1.4 p 478 3254 */ 3255 3256 sal_Int32 nCount = 0; 3257 3258 if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible 3259 m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible 3260 ) 3261 { 3262 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; 3263 sal_Int32 nChildren = rItem.m_aChildren.size(); 3264 for( sal_Int32 i = 0; i < nChildren; i++ ) 3265 nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); 3266 rCounts[nCurrentItemId] = nCount; 3267 // return 1 (this item) + visible sub items 3268 if( nCount < 0 ) 3269 nCount = 0; 3270 nCount++; 3271 } 3272 else 3273 { 3274 // this bookmark level is invisible 3275 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; 3276 sal_Int32 nChildren = rItem.m_aChildren.size(); 3277 rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size()); 3278 for( sal_Int32 i = 0; i < nChildren; i++ ) 3279 updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); 3280 nCount = -1; 3281 } 3282 3283 return nCount; 3284 } 3285 3286 sal_Int32 PDFWriterImpl::emitOutline() 3287 { 3288 int i, nItems = m_aOutline.size(); 3289 3290 // do we have an outline at all ? 3291 if( nItems < 2 ) 3292 return 0; 3293 3294 // reserve object numbers for all outline items 3295 for( i = 0; i < nItems; ++i ) 3296 m_aOutline[i].m_nObject = createObject(); 3297 3298 // update all parent, next and prev object ids 3299 for( i = 0; i < nItems; ++i ) 3300 { 3301 PDFOutlineEntry& rItem = m_aOutline[i]; 3302 int nChildren = rItem.m_aChildren.size(); 3303 3304 if( nChildren ) 3305 { 3306 for( int n = 0; n < nChildren; ++n ) 3307 { 3308 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ]; 3309 3310 rChild.m_nParentObject = rItem.m_nObject; 3311 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0; 3312 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0; 3313 } 3314 3315 } 3316 } 3317 3318 // calculate Count entries for all items 3319 std::vector< sal_Int32 > aCounts( nItems ); 3320 updateOutlineItemCount( aCounts, 0, 0 ); 3321 3322 // emit hierarchy 3323 for( i = 0; i < nItems; ++i ) 3324 { 3325 PDFOutlineEntry& rItem = m_aOutline[i]; 3326 OStringBuffer aLine( 1024 ); 3327 3328 CHECK_RETURN( updateObject( rItem.m_nObject ) ); 3329 aLine.append( rItem.m_nObject ); 3330 aLine.append( " 0 obj\n" ); 3331 aLine.append( "<<" ); 3332 // number of visible children (all levels) 3333 if( i > 0 || aCounts[0] > 0 ) 3334 { 3335 aLine.append( "/Count " ); 3336 aLine.append( aCounts[i] ); 3337 } 3338 if( ! rItem.m_aChildren.empty() ) 3339 { 3340 // children list: First, Last 3341 aLine.append( "/First " ); 3342 aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject ); 3343 aLine.append( " 0 R/Last " ); 3344 aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject ); 3345 aLine.append( " 0 R\n" ); 3346 } 3347 if( i > 0 ) 3348 { 3349 // Title, Dest, Parent, Prev, Next 3350 aLine.append( "/Title" ); 3351 appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine ); 3352 aLine.append( "\n" ); 3353 // Dest is not required 3354 if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() ) 3355 { 3356 aLine.append( "/Dest" ); 3357 appendDest( rItem.m_nDestID, aLine ); 3358 } 3359 aLine.append( "/Parent " ); 3360 aLine.append( rItem.m_nParentObject ); 3361 aLine.append( " 0 R" ); 3362 if( rItem.m_nPrevObject ) 3363 { 3364 aLine.append( "/Prev " ); 3365 aLine.append( rItem.m_nPrevObject ); 3366 aLine.append( " 0 R" ); 3367 } 3368 if( rItem.m_nNextObject ) 3369 { 3370 aLine.append( "/Next " ); 3371 aLine.append( rItem.m_nNextObject ); 3372 aLine.append( " 0 R" ); 3373 } 3374 } 3375 aLine.append( ">>\nendobj\n\n" ); 3376 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 3377 } 3378 3379 return m_aOutline[0].m_nObject; 3380 } 3381 3382 #undef CHECK_RETURN 3383 #define CHECK_RETURN( x ) if( !x ) return false 3384 3385 bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer ) 3386 { 3387 if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) 3388 { 3389 SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested"); 3390 return false; 3391 } 3392 3393 const PDFDest& rDest = m_aDests[ nDestID ]; 3394 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; 3395 3396 rBuffer.append( '[' ); 3397 rBuffer.append( rDestPage.m_nPageObject ); 3398 rBuffer.append( " 0 R" ); 3399 3400 switch( rDest.m_eType ) 3401 { 3402 case PDFWriter::DestAreaType::XYZ: 3403 default: 3404 rBuffer.append( "/XYZ " ); 3405 appendFixedInt( rDest.m_aRect.Left(), rBuffer ); 3406 rBuffer.append( ' ' ); 3407 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); 3408 rBuffer.append( " 0" ); 3409 break; 3410 case PDFWriter::DestAreaType::FitRectangle: 3411 rBuffer.append( "/FitR " ); 3412 appendFixedInt( rDest.m_aRect.Left(), rBuffer ); 3413 rBuffer.append( ' ' ); 3414 appendFixedInt( rDest.m_aRect.Top(), rBuffer ); 3415 rBuffer.append( ' ' ); 3416 appendFixedInt( rDest.m_aRect.Right(), rBuffer ); 3417 rBuffer.append( ' ' ); 3418 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); 3419 break; 3420 } 3421 rBuffer.append( ']' ); 3422 3423 return true; 3424 } 3425 3426 void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream) 3427 { 3428 sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType); 3429 auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back(); 3430 rAttachedFile.maFilename = rFileName; 3431 rAttachedFile.maMimeType = rMimeType; 3432 rAttachedFile.maDescription = rDescription; 3433 rAttachedFile.mnEmbeddedFileObjectId = nObjectID; 3434 rAttachedFile.mnObjectId = createObject(); 3435 } 3436 3437 sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType) 3438 { 3439 sal_Int32 aObjectID = createObject(); 3440 auto& rEmbedded = m_aEmbeddedFiles.emplace_back(); 3441 rEmbedded.m_nObject = aObjectID; 3442 rEmbedded.m_aSubType = rMimeType; 3443 rEmbedded.m_pStream = std::move(rStream); 3444 return aObjectID; 3445 } 3446 3447 sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer) 3448 { 3449 sal_Int32 aObjectID = createObject(); 3450 m_aEmbeddedFiles.emplace_back(); 3451 m_aEmbeddedFiles.back().m_nObject = aObjectID; 3452 m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer; 3453 return aObjectID; 3454 } 3455 3456 bool PDFWriterImpl::emitScreenAnnotations() 3457 { 3458 int nAnnots = m_aScreens.size(); 3459 for (int i = 0; i < nAnnots; i++) 3460 { 3461 const PDFScreen& rScreen = m_aScreens[i]; 3462 3463 OStringBuffer aLine; 3464 bool bEmbed = false; 3465 if (!rScreen.m_aTempFileURL.isEmpty()) 3466 { 3467 bEmbed = true; 3468 if (!updateObject(rScreen.m_nTempFileObject)) 3469 continue; 3470 3471 SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ); 3472 SvMemoryStream aMemoryStream; 3473 aMemoryStream.WriteStream(aFileStream); 3474 3475 aLine.append(rScreen.m_nTempFileObject); 3476 aLine.append(" 0 obj\n"); 3477 aLine.append("<< /Type /EmbeddedFile /Length "); 3478 aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize())); 3479 aLine.append(" >>\nstream\n"); 3480 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength())); 3481 aLine.setLength(0); 3482 3483 CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize())); 3484 3485 aLine.append("\nendstream\nendobj\n\n"); 3486 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength())); 3487 aLine.setLength(0); 3488 } 3489 3490 if (!updateObject(rScreen.m_nObject)) 3491 continue; 3492 3493 // Annot dictionary. 3494 aLine.append(rScreen.m_nObject); 3495 aLine.append(" 0 obj\n"); 3496 aLine.append("<</Type/Annot"); 3497 aLine.append("/Subtype/Screen/Rect["); 3498 appendFixedInt(rScreen.m_aRect.Left(), aLine); 3499 aLine.append(' '); 3500 appendFixedInt(rScreen.m_aRect.Top(), aLine); 3501 aLine.append(' '); 3502 appendFixedInt(rScreen.m_aRect.Right(), aLine); 3503 aLine.append(' '); 3504 appendFixedInt(rScreen.m_aRect.Bottom(), aLine); 3505 aLine.append("]"); 3506 3507 // Action dictionary. 3508 aLine.append("/A<</Type/Action /S/Rendition /AN "); 3509 aLine.append(rScreen.m_nObject); 3510 aLine.append(" 0 R "); 3511 3512 // Rendition dictionary. 3513 aLine.append("/R<</Type/Rendition /S/MR "); 3514 3515 // MediaClip dictionary. 3516 aLine.append("/C<</Type/MediaClip /S/MCD "); 3517 if (bEmbed) 3518 { 3519 aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F "); 3520 aLine.append(rScreen.m_nTempFileObject); 3521 aLine.append(" 0 R >> >>"); 3522 } 3523 else 3524 { 3525 // Linked. 3526 aLine.append("/D << /Type /Filespec /FS /URL /F "); 3527 appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding()); 3528 aLine.append(" >>"); 3529 } 3530 // Allow playing the video via a tempfile. 3531 aLine.append("/P <</TF (TEMPACCESS)>>"); 3532 // Until the real MIME type (instead of application/vnd.sun.star.media) is available here. 3533 aLine.append("/CT (video/mp4)"); 3534 aLine.append(">>"); 3535 3536 // End Rendition dictionary by requesting play/pause/stop controls. 3537 aLine.append("/P<</BE<</C true >>>>"); 3538 aLine.append(">>"); 3539 3540 // End Action dictionary. 3541 aLine.append("/OP 0 >>"); 3542 3543 // End Annot dictionary. 3544 aLine.append("/P "); 3545 aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject); 3546 aLine.append(" 0 R\n>>\nendobj\n\n"); 3547 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength())); 3548 } 3549 3550 return true; 3551 } 3552 3553 bool PDFWriterImpl::emitLinkAnnotations() 3554 { 3555 MARK("PDFWriterImpl::emitLinkAnnotations"); 3556 int nAnnots = m_aLinks.size(); 3557 for( int i = 0; i < nAnnots; i++ ) 3558 { 3559 const PDFLink& rLink = m_aLinks[i]; 3560 if( ! updateObject( rLink.m_nObject ) ) 3561 continue; 3562 3563 OStringBuffer aLine( 1024 ); 3564 aLine.append( rLink.m_nObject ); 3565 aLine.append( " 0 obj\n" ); 3566 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' 3567 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 3568 aLine.append( "<</Type/Annot" ); 3569 if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3) 3570 aLine.append( "/F 4" ); 3571 aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" ); 3572 3573 appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle 3574 aLine.append( ' ' ); 3575 appendFixedInt( rLink.m_aRect.Top(), aLine ); 3576 aLine.append( ' ' ); 3577 appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle 3578 aLine.append( ' ' ); 3579 appendFixedInt( rLink.m_aRect.Bottom(), aLine ); 3580 aLine.append( "]" ); 3581 // ISO 14289-1:2014, Clause: 7.18.5 3582 aLine.append("/Contents"); 3583 appendUnicodeTextStringEncrypt(rLink.m_AltText, rLink.m_nObject, aLine); 3584 if( rLink.m_nDest >= 0 ) 3585 { 3586 aLine.append( "/Dest" ); 3587 appendDest( rLink.m_nDest, aLine ); 3588 } 3589 else 3590 { 3591 /* 3592 destination is external to the document, so 3593 we check in the following sequence: 3594 3595 if target type is neither .pdf, nor .od[tpgs], then 3596 check if relative or absolute and act accordingly (use URI or 'launch application' as requested) 3597 end processing 3598 else if target is .od[tpgs]: then 3599 if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file 3600 processing continue 3601 3602 if (new)target is .pdf : then 3603 if GotToR is requested, then 3604 convert the target in GoToR where the fragment of the URI is 3605 considered the named destination in the target file, set relative or absolute as requested 3606 else strip the fragment from URL and then set URI or 'launch application' as requested 3607 */ 3608 3609 // FIXME: check if the decode mechanisms for URL processing throughout this implementation 3610 // are the correct one!! 3611 3612 // extract target file type 3613 auto url(URIHelper::resolveIdnaHost(rLink.m_aURL)); 3614 3615 INetURLObject aDocumentURL( m_aContext.BaseURL ); 3616 INetURLObject aTargetURL( url ); 3617 bool bSetGoToRMode = false; 3618 bool bTargetHasPDFExtension = false; 3619 INetProtocol eTargetProtocol = aTargetURL.GetProtocol(); 3620 bool bIsUNCPath = false; 3621 bool bUnparsedURI = false; 3622 3623 // check if the protocol is a known one, or if there is no protocol at all (on target only) 3624 // if there is no protocol, make the target relative to the current document directory 3625 // getting the needed URL information from the current document path 3626 if( eTargetProtocol == INetProtocol::NotValid ) 3627 { 3628 if( url.getLength() > 4 && url.startsWith("\\\\\\\\")) 3629 { 3630 bIsUNCPath = true; 3631 } 3632 else 3633 { 3634 INetURLObject aNewURL(rtl::Uri::convertRelToAbs(m_aContext.BaseURL, url)); 3635 aTargetURL = aNewURL; //reassign the new target URL 3636 3637 //recompute the target protocol, with the new URL 3638 //normal URL processing resumes 3639 eTargetProtocol = aTargetURL.GetProtocol(); 3640 3641 bUnparsedURI = eTargetProtocol == INetProtocol::NotValid; 3642 } 3643 } 3644 3645 OUString aFileExtension = aTargetURL.GetFileExtension(); 3646 3647 // Check if the URL ends in '/': if yes it's a directory, 3648 // it will be forced to a URI link. 3649 // possibly a malformed URI, leave it as it is, force as URI 3650 if( aTargetURL.hasFinalSlash() ) 3651 m_aContext.DefaultLinkAction = PDFWriter::URIAction; 3652 3653 if( !aFileExtension.isEmpty() ) 3654 { 3655 if( m_aContext.ConvertOOoTargetToPDFTarget ) 3656 { 3657 bool bChangeFileExtensionToPDF = false; 3658 //examine the file type (.odm .odt. .odp, odg, ods) 3659 if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) ) 3660 bChangeFileExtensionToPDF = true; 3661 if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) ) 3662 bChangeFileExtensionToPDF = true; 3663 else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) ) 3664 bChangeFileExtensionToPDF = true; 3665 else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) ) 3666 bChangeFileExtensionToPDF = true; 3667 else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) ) 3668 bChangeFileExtensionToPDF = true; 3669 if( bChangeFileExtensionToPDF ) 3670 aTargetURL.setExtension(u"pdf" ); 3671 } 3672 //check if extension is pdf, see if GoToR should be forced 3673 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" ); 3674 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension ) 3675 bSetGoToRMode = true; 3676 } 3677 //prepare the URL, if relative or not 3678 INetProtocol eBaseProtocol = aDocumentURL.GetProtocol(); 3679 //queue the string common to all types of actions 3680 aLine.append( "/A<</Type/Action/S"); 3681 if( bIsUNCPath ) // handle Win UNC paths 3682 { 3683 aLine.append( "/Launch/Win<</F" ); 3684 // INetURLObject is not good with UNC paths, use original path 3685 appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); 3686 aLine.append( ">>" ); 3687 } 3688 else 3689 { 3690 bool bSetRelative = false; 3691 bool bFileSpec = false; 3692 //check if relative file link is requested and if the protocol is 'file://' 3693 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File ) 3694 bSetRelative = true; 3695 3696 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is, 3697 if( !bSetGoToRMode ) 3698 { 3699 switch( m_aContext.DefaultLinkAction ) 3700 { 3701 default: 3702 case PDFWriter::URIAction : 3703 case PDFWriter::URIActionDestination : 3704 aLine.append( "/URI/URI" ); 3705 break; 3706 case PDFWriter::LaunchAction: 3707 // now: 3708 // if a launch action is requested and the hyperlink target has a fragment 3709 // and the target file does not have a pdf extension, or it's not a 'file:://' 3710 // protocol then force the uri action on it 3711 // This code will permit the correct opening of application on web pages, 3712 // the one that normally have fragments (but I may be wrong...) 3713 // and will force the use of URI when the protocol is not file: 3714 if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) || 3715 eTargetProtocol != INetProtocol::File ) 3716 { 3717 aLine.append( "/URI/URI" ); 3718 } 3719 else 3720 { 3721 aLine.append( "/Launch/F" ); 3722 bFileSpec = true; 3723 } 3724 break; 3725 } 3726 } 3727 3728 //fragment are encoded in the same way as in the named destination processing 3729 if( bSetGoToRMode ) 3730 { 3731 //add the fragment 3732 OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset ); 3733 aLine.append("/GoToR"); 3734 aLine.append("/F"); 3735 appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark, 3736 INetURLObject::EncodeMechanism::WasEncoded, 3737 INetURLObject::DecodeMechanism::WithCharset ) : 3738 aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); 3739 if( !aFragment.isEmpty() ) 3740 { 3741 aLine.append("/D/"); 3742 appendDestinationName( aFragment , aLine ); 3743 } 3744 } 3745 else 3746 { 3747 // change the fragment to accommodate the bookmark (only if the file extension 3748 // is PDF and the requested action is of the correct type) 3749 if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination && 3750 bTargetHasPDFExtension && !aFragment.isEmpty() ) 3751 { 3752 OStringBuffer aLineLoc( 1024 ); 3753 appendDestinationName( aFragment , aLineLoc ); 3754 //substitute the fragment 3755 aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) ); 3756 } 3757 OUString aURL = bUnparsedURI ? url : 3758 aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : 3759 INetURLObject::DecodeMechanism::NONE ); 3760 appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL, 3761 INetURLObject::EncodeMechanism::WasEncoded, 3762 bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE 3763 ) : 3764 aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); 3765 } 3766 } 3767 aLine.append( ">>\n" ); 3768 } 3769 if( rLink.m_nStructParent > 0 ) 3770 { 3771 aLine.append( "/StructParent " ); 3772 aLine.append( rLink.m_nStructParent ); 3773 } 3774 aLine.append( ">>\nendobj\n\n" ); 3775 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 3776 } 3777 3778 return true; 3779 } 3780 3781 namespace 3782 { 3783 3784 void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine) 3785 { 3786 aLine.append("/Rect["); 3787 appendFixedInt(rRectangle.Left(), aLine); 3788 aLine.append(' '); 3789 appendFixedInt(rRectangle.Top(), aLine); 3790 aLine.append(' '); 3791 appendFixedInt(rRectangle.Right(), aLine); 3792 aLine.append(' '); 3793 appendFixedInt(rRectangle.Bottom(), aLine); 3794 aLine.append("] "); 3795 } 3796 3797 void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine) 3798 { 3799 aLine.append(nObjectID); 3800 aLine.append(" 0 obj\n"); 3801 } 3802 3803 void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine) 3804 { 3805 aLine.append(nObjectID); 3806 aLine.append(" 0 R "); 3807 } 3808 3809 } // end anonymous namespace 3810 3811 void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote) 3812 { 3813 appendObjectID(rNote.m_nObject, aLine); 3814 3815 aLine.append("<</Type /Annot /Subtype /Text "); 3816 3817 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' 3818 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 3819 if (m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3) 3820 aLine.append("/F 4 "); 3821 3822 appendAnnotationRect(rNote.m_aRect, aLine); 3823 3824 aLine.append("/Popup "); 3825 appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine); 3826 3827 auto & rDateTime = rNote.m_aContents.maModificationDate; 3828 3829 aLine.append("/M ("); 3830 appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0); 3831 aLine.append(") "); 3832 3833 // contents of the note (type text string) 3834 aLine.append("/Contents "); 3835 appendUnicodeTextStringEncrypt(rNote.m_aContents.Contents, rNote.m_nObject, aLine); 3836 aLine.append("\n"); 3837 3838 // optional title 3839 if (!rNote.m_aContents.Title.isEmpty()) 3840 { 3841 aLine.append("/T "); 3842 appendUnicodeTextStringEncrypt(rNote.m_aContents.Title, rNote.m_nObject, aLine); 3843 aLine.append("\n"); 3844 } 3845 aLine.append(">>\n"); 3846 aLine.append("endobj\n\n"); 3847 } 3848 3849 void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp) 3850 { 3851 appendObjectID(rPopUp.m_nObject, aLine); 3852 aLine.append("<</Type /Annot /Subtype /Popup "); 3853 aLine.append("/Parent "); 3854 appendObjectReference(rPopUp.m_nParentObject, aLine); 3855 aLine.append(">>\n"); 3856 aLine.append("endobj\n\n"); 3857 } 3858 3859 bool PDFWriterImpl::emitNoteAnnotations() 3860 { 3861 // emit note annotations 3862 int nAnnots = m_aNotes.size(); 3863 for( int i = 0; i < nAnnots; i++ ) 3864 { 3865 const PDFNoteEntry& rNote = m_aNotes[i]; 3866 const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation; 3867 3868 { 3869 if (!updateObject(rNote.m_nObject)) 3870 return false; 3871 3872 OStringBuffer aLine(1024); 3873 3874 emitTextAnnotationLine(aLine, rNote); 3875 3876 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 3877 return false; 3878 } 3879 3880 { 3881 3882 if (!updateObject(rPopUp.m_nObject)) 3883 return false; 3884 3885 OStringBuffer aLine(1024); 3886 3887 emitPopupAnnotationLine(aLine, rPopUp); 3888 3889 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 3890 return false; 3891 } 3892 } 3893 return true; 3894 } 3895 3896 Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont ) 3897 { 3898 bool bAdjustSize = false; 3899 3900 Font aFont( rControlFont ); 3901 if( aFont.GetFamilyName().isEmpty() ) 3902 { 3903 aFont = rAppSetFont; 3904 if( rControlFont.GetFontHeight() ) 3905 aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) ); 3906 else 3907 bAdjustSize = true; 3908 if( rControlFont.GetItalic() != ITALIC_DONTKNOW ) 3909 aFont.SetItalic( rControlFont.GetItalic() ); 3910 if( rControlFont.GetWeight() != WEIGHT_DONTKNOW ) 3911 aFont.SetWeight( rControlFont.GetWeight() ); 3912 } 3913 else if( ! aFont.GetFontHeight() ) 3914 { 3915 aFont.SetFontSize( rAppSetFont.GetFontSize() ); 3916 bAdjustSize = true; 3917 } 3918 if( bAdjustSize ) 3919 { 3920 Size aFontSize = aFont.GetFontSize(); 3921 OutputDevice* pDefDev = Application::GetDefaultDevice(); 3922 aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() ); 3923 aFont.SetFontSize( aFontSize ); 3924 } 3925 return aFont; 3926 } 3927 3928 sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont ) 3929 { 3930 sal_Int32 nBest = 4; // default to Helvetica 3931 3932 if (rFont.GetFamilyType() == FAMILY_ROMAN) 3933 { 3934 // Serif: default to Times-Roman. 3935 nBest = 8; 3936 } 3937 3938 OUString aFontName( rFont.GetFamilyName() ); 3939 aFontName = aFontName.toAsciiLowerCase(); 3940 3941 if( aFontName.indexOf( "times" ) != -1 ) 3942 nBest = 8; 3943 else if( aFontName.indexOf( "courier" ) != -1 ) 3944 nBest = 0; 3945 else if( aFontName.indexOf( "dingbats" ) != -1 ) 3946 nBest = 13; 3947 else if( aFontName.indexOf( "symbol" ) != -1 ) 3948 nBest = 12; 3949 if( nBest < 12 ) 3950 { 3951 if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL ) 3952 nBest += 1; 3953 if( rFont.GetWeight() > WEIGHT_MEDIUM ) 3954 nBest += 2; 3955 } 3956 3957 if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() ) 3958 m_aBuildinFontToObjectMap[ nBest ] = createObject(); 3959 3960 return nBest; 3961 } 3962 3963 static const Color& replaceColor( const Color& rCol1, const Color& rCol2 ) 3964 { 3965 return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1; 3966 } 3967 3968 void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget ) 3969 { 3970 const StyleSettings& rSettings = m_aWidgetStyleSettings; 3971 3972 // save graphics state 3973 push( PushFlags::ALL ); 3974 3975 // transform relative to control's coordinates since an 3976 // appearance stream is a form XObject 3977 // this relies on the m_aRect member of rButton NOT already being transformed 3978 // to default user space 3979 if( rWidget.Background || rWidget.Border ) 3980 { 3981 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT ); 3982 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT ); 3983 drawRectangle( rWidget.Location ); 3984 } 3985 // prepare font to use 3986 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() ); 3987 setFont( aFont ); 3988 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) ); 3989 3990 drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle ); 3991 3992 // create DA string while local mapmode is still in place 3993 // (that is before endRedirect()) 3994 OStringBuffer aDA( 256 ); 3995 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA ); 3996 Font aDummyFont( "Helvetica", aFont.GetFontSize() ); 3997 sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont ); 3998 aDA.append( ' ' ); 3999 aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject()); 4000 aDA.append( ' ' ); 4001 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA ); 4002 aDA.append( " Tf" ); 4003 rButton.m_aDAString = aDA.makeStringAndClear(); 4004 4005 pop(); 4006 4007 rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream(); 4008 4009 /* seems like a bad hack but at least works in both AR5 and 6: 4010 we draw the button ourselves and tell AR 4011 the button would be totally transparent with no text 4012 4013 One would expect that simply setting a normal appearance 4014 should suffice, but no, as soon as the user actually presses 4015 the button and an action is tied to it (gasp! a button that 4016 does something) the appearance gets replaced by some crap that AR 4017 creates on the fly even if no DA or MK is given. On AR6 at least 4018 the DA and MK work as expected, but on AR5 this creates a region 4019 filled with the background color but nor text. Urgh. 4020 */ 4021 rButton.m_aMKDict = "/BC [] /BG [] /CA"; 4022 rButton.m_aMKDictCAString = ""; 4023 } 4024 4025 Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern, 4026 const PDFWriter::AnyWidget& rWidget, 4027 const StyleSettings& rSettings ) 4028 { 4029 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() ); 4030 4031 if( rWidget.Background || rWidget.Border ) 4032 { 4033 if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT ) 4034 { 4035 sal_Int32 nDelta = GetDPIX() / 500; 4036 if( nDelta < 1 ) 4037 nDelta = 1; 4038 setLineColor( COL_TRANSPARENT ); 4039 tools::Rectangle aRect = rIntern.m_aRect; 4040 setFillColor( rSettings.GetLightBorderColor() ); 4041 drawRectangle( aRect ); 4042 aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta ); 4043 aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta ); 4044 setFillColor( rSettings.GetFieldColor() ); 4045 drawRectangle( aRect ); 4046 setFillColor( rSettings.GetLightColor() ); 4047 drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) ); 4048 drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) ); 4049 setFillColor( rSettings.GetDarkShadowColor() ); 4050 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) ); 4051 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) ); 4052 } 4053 else 4054 { 4055 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT ); 4056 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT ); 4057 drawRectangle( rIntern.m_aRect ); 4058 } 4059 4060 if( rWidget.Border ) 4061 { 4062 // adjust edit area accounting for border 4063 sal_Int32 nDelta = aFont.GetFontHeight()/4; 4064 if( nDelta < 1 ) 4065 nDelta = 1; 4066 rIntern.m_aRect.AdjustLeft(nDelta ); 4067 rIntern.m_aRect.AdjustTop(nDelta ); 4068 rIntern.m_aRect.AdjustRight( -nDelta ); 4069 rIntern.m_aRect.AdjustBottom( -nDelta ); 4070 } 4071 } 4072 return aFont; 4073 } 4074 4075 void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget ) 4076 { 4077 const StyleSettings& rSettings = m_aWidgetStyleSettings; 4078 SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 ); 4079 4080 push( PushFlags::ALL ); 4081 4082 // prepare font to use, draw field border 4083 Font aFont = drawFieldBorder( rEdit, rWidget, rSettings ); 4084 // Get the built-in font which is closest to aFont. 4085 sal_Int32 nBest = getBestBuildinFont(aFont); 4086 4087 // prepare DA string 4088 OStringBuffer aDA( 32 ); 4089 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); 4090 aDA.append( ' ' ); 4091 aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject()); 4092 4093 OStringBuffer aDR( 32 ); 4094 aDR.append( "/Font " ); 4095 aDR.append( getFontDictObject() ); 4096 aDR.append( " 0 R" ); 4097 rEdit.m_aDRDict = aDR.makeStringAndClear(); 4098 aDA.append( ' ' ); 4099 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA ); 4100 aDA.append( " Tf" ); 4101 4102 /* create an empty appearance stream, let the viewer create 4103 the appearance at runtime. This is because AR5 seems to 4104 paint the widget appearance always, and a dynamically created 4105 appearance on top of it. AR6 is well behaved in that regard, so 4106 that behaviour seems to be a bug. Anyway this empty appearance 4107 relies on /NeedAppearances in the AcroForm dictionary set to "true" 4108 */ 4109 beginRedirect( pEditStream, rEdit.m_aRect ); 4110 OString aAppearance = "/Tx BMC\nEMC\n"; 4111 writeBuffer( aAppearance.getStr(), aAppearance.getLength() ); 4112 4113 endRedirect(); 4114 pop(); 4115 4116 rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream; 4117 4118 rEdit.m_aDAString = aDA.makeStringAndClear(); 4119 } 4120 4121 void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget ) 4122 { 4123 const StyleSettings& rSettings = m_aWidgetStyleSettings; 4124 SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 ); 4125 4126 push( PushFlags::ALL ); 4127 4128 // prepare font to use, draw field border 4129 Font aFont = drawFieldBorder( rBox, rWidget, rSettings ); 4130 sal_Int32 nBest = getSystemFont( aFont ); 4131 4132 beginRedirect( pListBoxStream, rBox.m_aRect ); 4133 4134 setLineColor( COL_TRANSPARENT ); 4135 setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) ); 4136 drawRectangle( rBox.m_aRect ); 4137 4138 // empty appearance, see createDefaultEditAppearance for reference 4139 OString aAppearance = "/Tx BMC\nEMC\n"; 4140 writeBuffer( aAppearance.getStr(), aAppearance.getLength() ); 4141 4142 endRedirect(); 4143 pop(); 4144 4145 rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream; 4146 4147 // prepare DA string 4148 OStringBuffer aDA( 256 ); 4149 // prepare DA string 4150 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); 4151 aDA.append( ' ' ); 4152 aDA.append( "/F" ); 4153 aDA.append( nBest ); 4154 4155 OStringBuffer aDR( 32 ); 4156 aDR.append( "/Font " ); 4157 aDR.append( getFontDictObject() ); 4158 aDR.append( " 0 R" ); 4159 rBox.m_aDRDict = aDR.makeStringAndClear(); 4160 aDA.append( ' ' ); 4161 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA ); 4162 aDA.append( " Tf" ); 4163 rBox.m_aDAString = aDA.makeStringAndClear(); 4164 } 4165 4166 void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget ) 4167 { 4168 const StyleSettings& rSettings = m_aWidgetStyleSettings; 4169 4170 // save graphics state 4171 push( PushFlags::ALL ); 4172 4173 if( rWidget.Background || rWidget.Border ) 4174 { 4175 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT ); 4176 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT ); 4177 drawRectangle( rBox.m_aRect ); 4178 } 4179 4180 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); 4181 setFont( aFont ); 4182 Size aFontSize = aFont.GetFontSize(); 4183 if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) 4184 aFontSize.setHeight( rBox.m_aRect.GetHeight() ); 4185 sal_Int32 nDelta = aFontSize.Height()/10; 4186 if( nDelta < 1 ) 4187 nDelta = 1; 4188 4189 tools::Rectangle aCheckRect, aTextRect; 4190 { 4191 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta ); 4192 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 ); 4193 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() ); 4194 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() ); 4195 4196 // #i74206# handle small controls without text area 4197 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) 4198 { 4199 aCheckRect.AdjustRight( -nDelta ); 4200 aCheckRect.AdjustTop(nDelta/2 ); 4201 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) ); 4202 } 4203 4204 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta ); 4205 aTextRect.SetTop( rBox.m_aRect.Top() ); 4206 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta ); 4207 aTextRect.SetBottom( rBox.m_aRect.Bottom() ); 4208 } 4209 setLineColor( COL_BLACK ); 4210 setFillColor( COL_TRANSPARENT ); 4211 OStringBuffer aLW( 32 ); 4212 aLW.append( "q " ); 4213 m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW ); 4214 aLW.append( " w " ); 4215 writeBuffer( aLW.getStr(), aLW.getLength() ); 4216 drawRectangle( aCheckRect ); 4217 writeBuffer( " Q\n", 3 ); 4218 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); 4219 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); 4220 4221 pop(); 4222 4223 OStringBuffer aDA( 256 ); 4224 4225 // tdf#93853 don't rely on Zapf (or any other 'standard' font) 4226 // being present, but our own OpenSymbol - N.B. PDF/A for good 4227 // reasons require even the standard PS fonts to be embedded! 4228 Push(); 4229 SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) ); 4230 const LogicalFontInstance* pFontInstance = GetFontInstance(); 4231 const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace(); 4232 Pop(); 4233 4234 // make sure OpenSymbol is embedded, and includes our checkmark 4235 const sal_Unicode cMark=0x2713; 4236 const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark); 4237 const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false); 4238 4239 sal_uInt8 nMappedGlyph; 4240 sal_Int32 nMappedFontObject; 4241 registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject); 4242 4243 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); 4244 aDA.append( ' ' ); 4245 aDA.append( "/F" ); 4246 aDA.append( nMappedFontObject ); 4247 aDA.append( " 0 Tf" ); 4248 4249 OStringBuffer aDR( 32 ); 4250 aDR.append( "/Font " ); 4251 aDR.append( getFontDictObject() ); 4252 aDR.append( " 0 R" ); 4253 rBox.m_aDRDict = aDR.makeStringAndClear(); 4254 rBox.m_aDAString = aDA.makeStringAndClear(); 4255 rBox.m_aMKDict = "/CA"; 4256 rBox.m_aMKDictCAString = "8"; 4257 rBox.m_aRect = aCheckRect; 4258 4259 // create appearance streams 4260 sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol 4261 nCharXOffset *= aCheckRect.GetHeight(); 4262 nCharXOffset /= 2000; 4263 sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf 4264 nCharYOffset *= aCheckRect.GetHeight(); 4265 nCharYOffset /= 2000; 4266 4267 // write 'checked' appearance stream 4268 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); 4269 beginRedirect( pCheckStream, aCheckRect ); 4270 aDA.append( "/Tx BMC\nq BT\n" ); 4271 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); 4272 aDA.append( ' ' ); 4273 aDA.append( "/F" ); 4274 aDA.append( nMappedFontObject ); 4275 aDA.append( ' ' ); 4276 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); 4277 aDA.append( " Tf\n" ); 4278 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA ); 4279 aDA.append( " " ); 4280 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA ); 4281 aDA.append( " Td <" ); 4282 appendHex( nMappedGlyph, aDA ); 4283 aDA.append( "> Tj\nET\nQ\nEMC\n" ); 4284 writeBuffer( aDA.getStr(), aDA.getLength() ); 4285 endRedirect(); 4286 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream; 4287 4288 // write 'unchecked' appearance stream 4289 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); 4290 beginRedirect( pUncheckStream, aCheckRect ); 4291 writeBuffer( "/Tx BMC\nEMC\n", 12 ); 4292 endRedirect(); 4293 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream; 4294 } 4295 4296 void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget ) 4297 { 4298 const StyleSettings& rSettings = m_aWidgetStyleSettings; 4299 4300 // save graphics state 4301 push( PushFlags::ALL ); 4302 4303 if( rWidget.Background || rWidget.Border ) 4304 { 4305 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT ); 4306 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT ); 4307 drawRectangle( rBox.m_aRect ); 4308 } 4309 4310 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); 4311 setFont( aFont ); 4312 Size aFontSize = aFont.GetFontSize(); 4313 if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) 4314 aFontSize.setHeight( rBox.m_aRect.GetHeight() ); 4315 sal_Int32 nDelta = aFontSize.Height()/10; 4316 if( nDelta < 1 ) 4317 nDelta = 1; 4318 4319 tools::Rectangle aCheckRect, aTextRect; 4320 { 4321 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta ); 4322 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 ); 4323 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() ); 4324 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() ); 4325 4326 // #i74206# handle small controls without text area 4327 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) 4328 { 4329 aCheckRect.AdjustRight( -nDelta ); 4330 aCheckRect.AdjustTop(nDelta/2 ); 4331 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) ); 4332 } 4333 4334 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta ); 4335 aTextRect.SetTop( rBox.m_aRect.Top() ); 4336 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta ); 4337 aTextRect.SetBottom( rBox.m_aRect.Bottom() ); 4338 } 4339 setLineColor( COL_BLACK ); 4340 setFillColor( COL_TRANSPARENT ); 4341 OStringBuffer aLW( 32 ); 4342 aLW.append( "q " ); 4343 m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW ); 4344 aLW.append( " w " ); 4345 writeBuffer( aLW.getStr(), aLW.getLength() ); 4346 drawEllipse( aCheckRect ); 4347 writeBuffer( " Q\n", 3 ); 4348 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); 4349 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); 4350 4351 pop(); 4352 4353 //to encrypt this (el) 4354 rBox.m_aMKDict = "/CA"; 4355 //after this assignment, to m_aMKDic cannot be added anything 4356 rBox.m_aMKDictCAString = "l"; 4357 4358 rBox.m_aRect = aCheckRect; 4359 4360 // create appearance streams 4361 push( PushFlags::ALL); 4362 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); 4363 4364 beginRedirect( pCheckStream, aCheckRect ); 4365 OStringBuffer aDA( 256 ); 4366 aDA.append( "/Tx BMC\nq BT\n" ); 4367 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); 4368 aDA.append( ' ' ); 4369 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); 4370 aDA.append( " 0 0 Td\nET\nQ\n" ); 4371 writeBuffer( aDA.getStr(), aDA.getLength() ); 4372 setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); 4373 setLineColor( COL_TRANSPARENT ); 4374 aCheckRect.AdjustLeft(3*nDelta ); 4375 aCheckRect.AdjustTop(3*nDelta ); 4376 aCheckRect.AdjustBottom( -(3*nDelta) ); 4377 aCheckRect.AdjustRight( -(3*nDelta) ); 4378 drawEllipse( aCheckRect ); 4379 writeBuffer( "\nEMC\n", 5 ); 4380 endRedirect(); 4381 4382 pop(); 4383 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream; 4384 4385 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); 4386 beginRedirect( pUncheckStream, aCheckRect ); 4387 writeBuffer( "/Tx BMC\nEMC\n", 12 ); 4388 endRedirect(); 4389 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream; 4390 } 4391 4392 bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict ) 4393 { 4394 // TODO: check and insert default streams 4395 OString aStandardAppearance; 4396 switch( rWidget.m_eType ) 4397 { 4398 case PDFWriter::CheckBox: 4399 aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US ); 4400 break; 4401 default: 4402 break; 4403 } 4404 4405 if( !rWidget.m_aAppearances.empty() ) 4406 { 4407 rAnnotDict.append( "/AP<<\n" ); 4408 for (auto & dict_item : rWidget.m_aAppearances) 4409 { 4410 rAnnotDict.append( "/" ); 4411 rAnnotDict.append( dict_item.first ); 4412 bool bUseSubDict = (dict_item.second.size() > 1); 4413 4414 // PDF/A requires sub-dicts for /FT/Btn objects (clause 4415 // 6.3.3) 4416 if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3) 4417 { 4418 if( rWidget.m_eType == PDFWriter::RadioButton || 4419 rWidget.m_eType == PDFWriter::CheckBox || 4420 rWidget.m_eType == PDFWriter::PushButton ) 4421 { 4422 bUseSubDict = true; 4423 } 4424 } 4425 4426 rAnnotDict.append( bUseSubDict ? "<<" : " " ); 4427 4428 for (auto const& stream_item : dict_item.second) 4429 { 4430 SvMemoryStream* pAppearanceStream = stream_item.second; 4431 dict_item.second[ stream_item.first ] = nullptr; 4432 4433 bool bDeflate = compressStream( pAppearanceStream ); 4434 4435 sal_Int64 nStreamLen = pAppearanceStream->TellEnd(); 4436 pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN ); 4437 sal_Int32 nObject = createObject(); 4438 CHECK_RETURN( updateObject( nObject ) ); 4439 if (g_bDebugDisableCompression) 4440 { 4441 emitComment( "PDFWriterImpl::emitAppearances" ); 4442 } 4443 OStringBuffer aLine; 4444 aLine.append( nObject ); 4445 4446 aLine.append( " 0 obj\n" 4447 "<</Type/XObject\n" 4448 "/Subtype/Form\n" 4449 "/BBox[0 0 " ); 4450 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine ); 4451 aLine.append( " " ); 4452 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine ); 4453 aLine.append( "]\n" 4454 "/Resources " ); 4455 aLine.append( getResourceDictObj() ); 4456 aLine.append( " 0 R\n" 4457 "/Length " ); 4458 aLine.append( nStreamLen ); 4459 aLine.append( "\n" ); 4460 if( bDeflate ) 4461 aLine.append( "/Filter/FlateDecode\n" ); 4462 aLine.append( ">>\nstream\n" ); 4463 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 4464 checkAndEnableStreamEncryption( nObject ); 4465 CHECK_RETURN( writeBuffer( pAppearanceStream->GetData(), nStreamLen ) ); 4466 disableStreamEncryption(); 4467 CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) ); 4468 4469 if( bUseSubDict ) 4470 { 4471 rAnnotDict.append( " /" ); 4472 rAnnotDict.append( stream_item.first ); 4473 rAnnotDict.append( " " ); 4474 } 4475 rAnnotDict.append( nObject ); 4476 rAnnotDict.append( " 0 R" ); 4477 4478 delete pAppearanceStream; 4479 } 4480 4481 rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" ); 4482 } 4483 rAnnotDict.append( ">>\n" ); 4484 if( !aStandardAppearance.isEmpty() ) 4485 { 4486 rAnnotDict.append( "/AS /" ); 4487 rAnnotDict.append( aStandardAppearance ); 4488 rAnnotDict.append( "\n" ); 4489 } 4490 } 4491 4492 return true; 4493 } 4494 4495 bool PDFWriterImpl::emitWidgetAnnotations() 4496 { 4497 ensureUniqueRadioOnValues(); 4498 4499 int nAnnots = m_aWidgets.size(); 4500 for( int a = 0; a < nAnnots; a++ ) 4501 { 4502 PDFWidget& rWidget = m_aWidgets[a]; 4503 4504 if( rWidget.m_eType == PDFWriter::CheckBox ) 4505 { 4506 if ( !rWidget.m_aOnValue.isEmpty() ) 4507 { 4508 auto app_it = rWidget.m_aAppearances.find( "N" ); 4509 if( app_it != rWidget.m_aAppearances.end() ) 4510 { 4511 auto stream_it = app_it->second.find( "Yes" ); 4512 if( stream_it != app_it->second.end() ) 4513 { 4514 SvMemoryStream* pStream = stream_it->second; 4515 app_it->second.erase( stream_it ); 4516 OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 ); 4517 appendName( rWidget.m_aOnValue, aBuf ); 4518 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; 4519 } 4520 else 4521 SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" ); 4522 } 4523 } 4524 4525 if ( !rWidget.m_aOffValue.isEmpty() ) 4526 { 4527 auto app_it = rWidget.m_aAppearances.find( "N" ); 4528 if( app_it != rWidget.m_aAppearances.end() ) 4529 { 4530 auto stream_it = app_it->second.find( "Off" ); 4531 if( stream_it != app_it->second.end() ) 4532 { 4533 SvMemoryStream* pStream = stream_it->second; 4534 app_it->second.erase( stream_it ); 4535 OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 ); 4536 appendName( rWidget.m_aOffValue, aBuf ); 4537 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; 4538 } 4539 else 4540 SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" ); 4541 } 4542 } 4543 } 4544 4545 OStringBuffer aLine( 1024 ); 4546 OStringBuffer aValue( 256 ); 4547 aLine.append( rWidget.m_nObject ); 4548 aLine.append( " 0 obj\n" 4549 "<<" ); 4550 if( rWidget.m_eType != PDFWriter::Hierarchy ) 4551 { 4552 // emit widget annotation only for terminal fields 4553 if( rWidget.m_aKids.empty() ) 4554 { 4555 int iRectMargin; 4556 4557 aLine.append( "/Type/Annot/Subtype/Widget/F " ); 4558 4559 if (rWidget.m_eType == PDFWriter::Signature) 4560 { 4561 aLine.append( "132\n" ); // Print & Locked 4562 iRectMargin = 0; 4563 } 4564 else 4565 { 4566 aLine.append( "4\n" ); 4567 iRectMargin = 1; 4568 } 4569 4570 aLine.append("/Rect[" ); 4571 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine ); 4572 aLine.append( ' ' ); 4573 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine ); 4574 aLine.append( ' ' ); 4575 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine ); 4576 aLine.append( ' ' ); 4577 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine ); 4578 aLine.append( "]\n" ); 4579 } 4580 aLine.append( "/FT/" ); 4581 switch( rWidget.m_eType ) 4582 { 4583 case PDFWriter::RadioButton: 4584 case PDFWriter::CheckBox: 4585 // for radio buttons only the RadioButton field, not the 4586 // CheckBox children should have a value, else acrobat reader 4587 // does not always check the right button 4588 // of course real check boxes (not belonging to a radio group) 4589 // need their values, too 4590 if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 ) 4591 { 4592 aValue.append( "/" ); 4593 // check for radio group with all buttons unpressed 4594 if( rWidget.m_aValue.isEmpty() ) 4595 aValue.append( "Off" ); 4596 else 4597 appendName( rWidget.m_aValue, aValue ); 4598 } 4599 [[fallthrough]]; 4600 case PDFWriter::PushButton: 4601 aLine.append( "Btn" ); 4602 break; 4603 case PDFWriter::ListBox: 4604 if( rWidget.m_nFlags & 0x200000 ) // multiselect 4605 { 4606 aValue.append( "[" ); 4607 for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ ) 4608 { 4609 sal_Int32 nEntry = rWidget.m_aSelectedEntries[i]; 4610 if( nEntry >= 0 4611 && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() ) 4612 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue ); 4613 } 4614 aValue.append( "]" ); 4615 } 4616 else if( !rWidget.m_aSelectedEntries.empty() && 4617 rWidget.m_aSelectedEntries[0] >= 0 && 4618 o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() ) 4619 { 4620 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue ); 4621 } 4622 else 4623 appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue ); 4624 aLine.append( "Ch" ); 4625 break; 4626 case PDFWriter::ComboBox: 4627 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue ); 4628 aLine.append( "Ch" ); 4629 break; 4630 case PDFWriter::Edit: 4631 aLine.append( "Tx" ); 4632 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue ); 4633 break; 4634 case PDFWriter::Signature: 4635 aLine.append( "Sig" ); 4636 aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US)); 4637 break; 4638 case PDFWriter::Hierarchy: // make the compiler happy 4639 break; 4640 } 4641 aLine.append( "\n" ); 4642 aLine.append( "/P " ); 4643 aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject ); 4644 aLine.append( " 0 R\n" ); 4645 } 4646 if( rWidget.m_nParent ) 4647 { 4648 aLine.append( "/Parent " ); 4649 aLine.append( rWidget.m_nParent ); 4650 aLine.append( " 0 R\n" ); 4651 } 4652 if( !rWidget.m_aKids.empty() ) 4653 { 4654 aLine.append( "/Kids[" ); 4655 for( size_t i = 0; i < rWidget.m_aKids.size(); i++ ) 4656 { 4657 aLine.append( rWidget.m_aKids[i] ); 4658 aLine.append( " 0 R" ); 4659 aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); 4660 } 4661 aLine.append( "]\n" ); 4662 } 4663 if( !rWidget.m_aName.isEmpty() ) 4664 { 4665 aLine.append( "/T" ); 4666 appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine ); 4667 aLine.append( "\n" ); 4668 } 4669 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !rWidget.m_aDescription.isEmpty() ) 4670 { 4671 // the alternate field name should be unicode able since it is 4672 // supposed to be used in UI 4673 aLine.append( "/TU" ); 4674 appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine ); 4675 aLine.append( "\n" ); 4676 } 4677 4678 if( rWidget.m_nFlags ) 4679 { 4680 aLine.append( "/Ff " ); 4681 aLine.append( rWidget.m_nFlags ); 4682 aLine.append( "\n" ); 4683 } 4684 if( !aValue.isEmpty() ) 4685 { 4686 OString aVal = aValue.makeStringAndClear(); 4687 aLine.append( "/V " ); 4688 aLine.append( aVal ); 4689 aLine.append( "\n" 4690 "/DV " ); 4691 aLine.append( aVal ); 4692 aLine.append( "\n" ); 4693 } 4694 if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox ) 4695 { 4696 sal_Int32 nTI = -1; 4697 aLine.append( "/Opt[\n" ); 4698 sal_Int32 i = 0; 4699 for (auto const& entry : rWidget.m_aListEntries) 4700 { 4701 appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine ); 4702 aLine.append( "\n" ); 4703 if( entry == rWidget.m_aValue ) 4704 nTI = i; 4705 ++i; 4706 } 4707 aLine.append( "]\n" ); 4708 if( nTI > 0 ) 4709 { 4710 aLine.append( "/TI " ); 4711 aLine.append( nTI ); 4712 aLine.append( "\n" ); 4713 if( rWidget.m_nFlags & 0x200000 ) // Multiselect 4714 { 4715 aLine.append( "/I [" ); 4716 aLine.append( nTI ); 4717 aLine.append( "]\n" ); 4718 } 4719 } 4720 } 4721 if( rWidget.m_eType == PDFWriter::Edit ) 4722 { 4723 if ( rWidget.m_nMaxLen > 0 ) 4724 { 4725 aLine.append( "/MaxLen " ); 4726 aLine.append( rWidget.m_nMaxLen ); 4727 aLine.append( "\n" ); 4728 } 4729 4730 if ( rWidget.m_nFormat == PDFWriter::Number ) 4731 { 4732 OString aHexText; 4733 4734 if ( !rWidget.m_aCurrencySymbol.isEmpty() ) 4735 { 4736 // Get the hexadecimal code 4737 sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1); 4738 aHexText = "\\\\u" + OString::number(cChar, 16); 4739 } 4740 4741 aLine.append("/AA<<\n"); 4742 aLine.append("/F<</JS(AFNumber_Format\\("); 4743 aLine.append(OString::number(rWidget.m_nDecimalAccuracy)); 4744 aLine.append(", 0, 0, 0, \""); 4745 aLine.append( aHexText ); 4746 aLine.append("\","); 4747 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol)); 4748 aLine.append("\\);)"); 4749 aLine.append("/S/JavaScript>>\n"); 4750 aLine.append("/K<</JS(AFNumber_Keystroke\\("); 4751 aLine.append(OString::number(rWidget.m_nDecimalAccuracy)); 4752 aLine.append(", 0, 0, 0, \""); 4753 aLine.append( aHexText ); 4754 aLine.append("\","); 4755 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol)); 4756 aLine.append("\\);)"); 4757 aLine.append("/S/JavaScript>>\n"); 4758 aLine.append(">>\n"); 4759 } 4760 else if ( rWidget.m_nFormat == PDFWriter::Time ) 4761 { 4762 aLine.append("/AA<<\n"); 4763 aLine.append("/F<</JS(AFTime_FormatEx\\(\""); 4764 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US)); 4765 aLine.append("\"\\);)"); 4766 aLine.append("/S/JavaScript>>\n"); 4767 aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\""); 4768 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US)); 4769 aLine.append("\"\\);)"); 4770 aLine.append("/S/JavaScript>>\n"); 4771 aLine.append(">>\n"); 4772 } 4773 else if ( rWidget.m_nFormat == PDFWriter::Date ) 4774 { 4775 aLine.append("/AA<<\n"); 4776 aLine.append("/F<</JS(AFDate_FormatEx\\(\""); 4777 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US)); 4778 aLine.append("\"\\);)"); 4779 aLine.append("/S/JavaScript>>\n"); 4780 aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\""); 4781 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US)); 4782 aLine.append("\"\\);)"); 4783 aLine.append("/S/JavaScript>>\n"); 4784 aLine.append(">>\n"); 4785 } 4786 } 4787 if( rWidget.m_eType == PDFWriter::PushButton ) 4788 { 4789 if(!m_bIsPDF_A1) 4790 { 4791 OStringBuffer aDest; 4792 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) ) 4793 { 4794 aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " ); 4795 aLine.append( aDest ); 4796 aLine.append( ">>>>\n" ); 4797 } 4798 else if( rWidget.m_aListEntries.empty() ) 4799 { 4800 if( !m_bIsPDF_A2 && !m_bIsPDF_A3 ) 4801 { 4802 // create a reset form action 4803 aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" ); 4804 } 4805 } 4806 else if( rWidget.m_bSubmit ) 4807 { 4808 // create a submit form action 4809 aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" ); 4810 appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() ); 4811 aLine.append( "/Flags " ); 4812 4813 sal_Int32 nFlags = 0; 4814 switch( m_aContext.SubmitFormat ) 4815 { 4816 case PDFWriter::HTML: 4817 nFlags |= 4; 4818 break; 4819 case PDFWriter::XML: 4820 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 ) 4821 nFlags |= 32; 4822 break; 4823 case PDFWriter::PDF: 4824 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 ) 4825 nFlags |= 256; 4826 break; 4827 case PDFWriter::FDF: 4828 default: 4829 break; 4830 } 4831 if( rWidget.m_bSubmitGet ) 4832 nFlags |= 8; 4833 aLine.append( nFlags ); 4834 aLine.append( ">>>>\n" ); 4835 } 4836 else 4837 { 4838 // create a URI action 4839 aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" ); 4840 aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) ); 4841 aLine.append( ")>>>>\n" ); 4842 } 4843 } 4844 else 4845 m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA ); 4846 } 4847 if( !rWidget.m_aDAString.isEmpty() ) 4848 { 4849 if( !rWidget.m_aDRDict.isEmpty() ) 4850 { 4851 aLine.append( "/DR<<" ); 4852 aLine.append( rWidget.m_aDRDict ); 4853 aLine.append( ">>\n" ); 4854 } 4855 else 4856 { 4857 aLine.append( "/DR<</Font<<" ); 4858 appendBuildinFontsToDict( aLine ); 4859 aLine.append( ">>>>\n" ); 4860 } 4861 aLine.append( "/DA" ); 4862 appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine ); 4863 aLine.append( "\n" ); 4864 if( rWidget.m_nTextStyle & DrawTextFlags::Center ) 4865 aLine.append( "/Q 1\n" ); 4866 else if( rWidget.m_nTextStyle & DrawTextFlags::Right ) 4867 aLine.append( "/Q 2\n" ); 4868 } 4869 // appearance characteristics for terminal fields 4870 // which are supposed to have an appearance constructed 4871 // by the viewer application 4872 if( !rWidget.m_aMKDict.isEmpty() ) 4873 { 4874 aLine.append( "/MK<<" ); 4875 aLine.append( rWidget.m_aMKDict ); 4876 //add the CA string, encrypting it 4877 appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine); 4878 aLine.append( ">>\n" ); 4879 } 4880 4881 CHECK_RETURN( emitAppearances( rWidget, aLine ) ); 4882 4883 aLine.append( ">>\n" 4884 "endobj\n\n" ); 4885 CHECK_RETURN( updateObject( rWidget.m_nObject ) ); 4886 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 4887 } 4888 return true; 4889 } 4890 4891 bool PDFWriterImpl::emitAnnotations() 4892 { 4893 if( m_aPages.empty() ) 4894 return false; 4895 4896 CHECK_RETURN( emitLinkAnnotations() ); 4897 CHECK_RETURN(emitScreenAnnotations()); 4898 CHECK_RETURN( emitNoteAnnotations() ); 4899 CHECK_RETURN( emitWidgetAnnotations() ); 4900 4901 return true; 4902 } 4903 4904 class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream > 4905 { 4906 VclPtr<PDFWriterImpl> m_pWriter; 4907 bool m_bWrite; 4908 public: 4909 explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {} 4910 4911 virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override 4912 { 4913 if( m_bWrite && aData.hasElements() ) 4914 { 4915 sal_Int32 nBytes = aData.getLength(); 4916 m_pWriter->writeBuffer( aData.getConstArray(), nBytes ); 4917 } 4918 } 4919 virtual void SAL_CALL flush() override {} 4920 virtual void SAL_CALL closeOutput() override 4921 { 4922 m_bWrite = false; 4923 } 4924 }; 4925 4926 bool PDFWriterImpl::emitEmbeddedFiles() 4927 { 4928 for (auto& rEmbeddedFile : m_aEmbeddedFiles) 4929 { 4930 if (!updateObject(rEmbeddedFile.m_nObject)) 4931 continue; 4932 4933 sal_Int32 nSizeObject = createObject(); 4934 sal_Int32 nParamsObject = createObject(); 4935 4936 OStringBuffer aLine; 4937 aLine.append(rEmbeddedFile.m_nObject); 4938 aLine.append(" 0 obj\n"); 4939 aLine.append("<< /Type /EmbeddedFile"); 4940 if (!rEmbeddedFile.m_aSubType.isEmpty()) 4941 { 4942 aLine.append("/Subtype /"); 4943 appendName(rEmbeddedFile.m_aSubType, aLine); 4944 } 4945 aLine.append(" /Length "); 4946 appendObjectReference(nSizeObject, aLine); 4947 aLine.append(" /Params "); 4948 appendObjectReference(nParamsObject, aLine); 4949 aLine.append(">>\nstream\n"); 4950 checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject); 4951 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength())); 4952 disableStreamEncryption(); 4953 aLine.setLength(0); 4954 4955 sal_Int64 nSize{}; 4956 if (!rEmbeddedFile.m_aDataContainer.isEmpty()) 4957 { 4958 nSize = rEmbeddedFile.m_aDataContainer.getSize(); 4959 CHECK_RETURN(writeBuffer(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize())); 4960 } 4961 else if (rEmbeddedFile.m_pStream) 4962 { 4963 sal_uInt64 nBegin = getCurrentFilePosition(); 4964 css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this)); 4965 rEmbeddedFile.m_pStream->write(xStream); 4966 rEmbeddedFile.m_pStream.reset(); 4967 xStream.clear(); 4968 nSize = sal_Int64(getCurrentFilePosition() - nBegin); 4969 } 4970 aLine.append("\nendstream\nendobj\n\n"); 4971 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength())); 4972 aLine.setLength(0); 4973 4974 if (!updateObject(nSizeObject)) 4975 return false; 4976 aLine.append(nSizeObject); 4977 aLine.append(" 0 obj\n"); 4978 aLine.append(nSize); 4979 aLine.append("\nendobj\n\n"); 4980 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 4981 return false; 4982 aLine.setLength(0); 4983 4984 if (!updateObject(nParamsObject)) 4985 return false; 4986 aLine.append(nParamsObject); 4987 aLine.append(" 0 obj\n"); 4988 aLine.append("<<"); 4989 aLine.append("/Size "); 4990 aLine.append(nSize); 4991 aLine.append(">>"); 4992 aLine.append("\nendobj\n\n"); 4993 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 4994 return false; 4995 } 4996 return true; 4997 } 4998 4999 #undef CHECK_RETURN 5000 #define CHECK_RETURN( x ) if( !x ) return false 5001 5002 bool PDFWriterImpl::emitCatalog() 5003 { 5004 // build page tree 5005 // currently there is only one node that contains all leaves 5006 5007 // first create a page tree node id 5008 sal_Int32 nTreeNode = createObject(); 5009 5010 // emit global resource dictionary (page emit needs it) 5011 CHECK_RETURN( emitResources() ); 5012 5013 // emit all pages 5014 for (auto & page : m_aPages) 5015 if( ! page.emit( nTreeNode ) ) 5016 return false; 5017 5018 sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations(); 5019 5020 sal_Int32 nOutlineDict = emitOutline(); 5021 5022 // emit Output intent 5023 sal_Int32 nOutputIntentObject = emitOutputIntent(); 5024 5025 // emit metadata 5026 sal_Int32 nMetadataObject = emitDocumentMetadata(); 5027 5028 sal_Int32 nStructureDict = 0; 5029 if(m_aStructure.size() > 1) 5030 { 5031 // check if dummy structure containers are needed 5032 addInternalStructureContainer(m_aStructure[0]); 5033 nStructureDict = m_aStructure[0].m_nObject = createObject(); 5034 emitStructure( m_aStructure[ 0 ] ); 5035 } 5036 5037 // adjust tree node file offset 5038 if( ! updateObject( nTreeNode ) ) 5039 return false; 5040 5041 // emit tree node 5042 OStringBuffer aLine( 2048 ); 5043 aLine.append( nTreeNode ); 5044 aLine.append( " 0 obj\n" ); 5045 aLine.append( "<</Type/Pages\n" ); 5046 aLine.append( "/Resources " ); 5047 aLine.append( getResourceDictObj() ); 5048 aLine.append( " 0 R\n" ); 5049 5050 double nMediaBoxWidth = 0; 5051 double nMediaBoxHeight = 0; 5052 sal_Int32 nUserUnit = 1; 5053 if( m_aPages.empty() ) // sanity check, this should not happen 5054 { 5055 nMediaBoxWidth = g_nInheritedPageWidth; 5056 nMediaBoxHeight = g_nInheritedPageHeight; 5057 } 5058 else 5059 { 5060 for (auto const& page : m_aPages) 5061 { 5062 if( page.m_nPageWidth > nMediaBoxWidth ) 5063 { 5064 nMediaBoxWidth = page.m_nPageWidth; 5065 nUserUnit = page.m_nUserUnit; 5066 } 5067 if( page.m_nPageHeight > nMediaBoxHeight ) 5068 { 5069 nMediaBoxHeight = page.m_nPageHeight; 5070 nUserUnit = page.m_nUserUnit; 5071 } 5072 } 5073 } 5074 aLine.append( "/MediaBox[ 0 0 " ); 5075 aLine.append(nMediaBoxWidth / nUserUnit); 5076 aLine.append( ' ' ); 5077 aLine.append(nMediaBoxHeight / nUserUnit); 5078 aLine.append(" ]\n"); 5079 if (nUserUnit > 1) 5080 { 5081 aLine.append("/UserUnit "); 5082 aLine.append(nUserUnit); 5083 aLine.append("\n"); 5084 } 5085 aLine.append("/Kids[ "); 5086 unsigned int i = 0; 5087 for (const auto & page : m_aPages) 5088 { 5089 aLine.append( page.m_nPageObject ); 5090 aLine.append( " 0 R" ); 5091 aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); 5092 ++i; 5093 } 5094 aLine.append( "]\n" 5095 "/Count " ); 5096 aLine.append( static_cast<sal_Int32>(m_aPages.size()) ); 5097 aLine.append( ">>\n" 5098 "endobj\n\n" ); 5099 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 5100 5101 // emit annotation objects 5102 CHECK_RETURN( emitAnnotations() ); 5103 CHECK_RETURN( emitEmbeddedFiles() ); 5104 5105 // emit attached files 5106 for (auto & rAttachedFile : m_aDocumentAttachedFiles) 5107 { 5108 if (!updateObject(rAttachedFile.mnObjectId)) 5109 return false; 5110 aLine.setLength( 0 ); 5111 5112 appendObjectID(rAttachedFile.mnObjectId, aLine); 5113 aLine.append("<</Type /Filespec"); 5114 aLine.append("/F<"); 5115 PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine); 5116 aLine.append("> "); 5117 aLine.append("/UF<"); 5118 PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine); 5119 aLine.append("> "); 5120 if (!rAttachedFile.maDescription.isEmpty()) 5121 { 5122 aLine.append("/Desc <"); 5123 PDFWriter::AppendUnicodeTextString(rAttachedFile.maDescription, aLine); 5124 aLine.append("> "); 5125 } 5126 aLine.append("/EF <</F "); 5127 appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine); 5128 aLine.append(">>"); 5129 aLine.append(">>\nendobj\n\n"); 5130 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 5131 } 5132 5133 // emit Catalog 5134 m_nCatalogObject = createObject(); 5135 if( ! updateObject( m_nCatalogObject ) ) 5136 return false; 5137 aLine.setLength( 0 ); 5138 aLine.append( m_nCatalogObject ); 5139 aLine.append( " 0 obj\n" 5140 "<</Type/Catalog/Pages " ); 5141 aLine.append( nTreeNode ); 5142 aLine.append( " 0 R\n" ); 5143 5144 // check if there are named destinations to emit (root must be inside the catalog) 5145 if( nNamedDestinationsDictionary ) 5146 { 5147 aLine.append("/Dests "); 5148 aLine.append( nNamedDestinationsDictionary ); 5149 aLine.append( " 0 R\n" ); 5150 } 5151 5152 if (!m_aDocumentAttachedFiles.empty()) 5153 { 5154 aLine.append("/Names "); 5155 aLine.append("<</EmbeddedFiles <</Names ["); 5156 for (auto & rAttachedFile : m_aDocumentAttachedFiles) 5157 { 5158 aLine.append('<'); 5159 PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine); 5160 aLine.append('>'); 5161 aLine.append(' '); 5162 appendObjectReference(rAttachedFile.mnObjectId, aLine); 5163 } 5164 aLine.append("]>>>>"); 5165 aLine.append("\n" ); 5166 } 5167 5168 if( m_aContext.PageLayout != PDFWriter::DefaultLayout ) 5169 switch( m_aContext.PageLayout ) 5170 { 5171 default : 5172 case PDFWriter::SinglePage : 5173 aLine.append( "/PageLayout/SinglePage\n" ); 5174 break; 5175 case PDFWriter::Continuous : 5176 aLine.append( "/PageLayout/OneColumn\n" ); 5177 break; 5178 case PDFWriter::ContinuousFacing : 5179 // the flag m_aContext.FirstPageLeft below is used to set the page on the left side 5180 aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side 5181 break; 5182 } 5183 if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode ) 5184 switch( m_aContext.PDFDocumentMode ) 5185 { 5186 default : 5187 aLine.append( "/PageMode/UseNone\n" ); 5188 break; 5189 case PDFWriter::UseOutlines : 5190 aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open 5191 break; 5192 case PDFWriter::UseThumbs : 5193 aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open 5194 break; 5195 } 5196 else if( m_aContext.OpenInFullScreenMode ) 5197 aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen 5198 5199 OStringBuffer aInitPageRef; 5200 if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() ) 5201 { 5202 aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject ); 5203 aInitPageRef.append( " 0 R" ); 5204 } 5205 else 5206 aInitPageRef.append( "0" ); 5207 5208 switch( m_aContext.PDFDocumentAction ) 5209 { 5210 case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default 5211 default: 5212 if( aInitPageRef.getLength() > 1 ) 5213 { 5214 aLine.append( "/OpenAction[" ); 5215 aLine.append( aInitPageRef ); 5216 aLine.append( " /XYZ null null 0]\n" ); 5217 } 5218 break; 5219 case PDFWriter::FitInWindow : 5220 aLine.append( "/OpenAction[" ); 5221 aLine.append( aInitPageRef ); 5222 aLine.append( " /Fit]\n" ); //Open fit page 5223 break; 5224 case PDFWriter::FitWidth : 5225 aLine.append( "/OpenAction[" ); 5226 aLine.append( aInitPageRef ); 5227 aLine.append( " /FitH " ); 5228 aLine.append( g_nInheritedPageHeight );//Open fit width 5229 aLine.append( "]\n" ); 5230 break; 5231 case PDFWriter::FitVisible : 5232 aLine.append( "/OpenAction[" ); 5233 aLine.append( aInitPageRef ); 5234 aLine.append( " /FitBH " ); 5235 aLine.append( g_nInheritedPageHeight );//Open fit visible 5236 aLine.append( "]\n" ); 5237 break; 5238 case PDFWriter::ActionZoom : 5239 aLine.append( "/OpenAction[" ); 5240 aLine.append( aInitPageRef ); 5241 aLine.append( " /XYZ null null " ); 5242 if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 ) 5243 aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 ); 5244 else 5245 aLine.append( "0" ); 5246 aLine.append( "]\n" ); 5247 break; 5248 } 5249 5250 // viewer preferences, if we had some, then emit 5251 if( m_aContext.HideViewerToolbar || 5252 ( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle ) || 5253 m_aContext.HideViewerMenubar || 5254 m_aContext.HideViewerWindowControls || m_aContext.FitWindow || 5255 m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) || 5256 m_aContext.OpenInFullScreenMode ) 5257 { 5258 aLine.append( "/ViewerPreferences<<" ); 5259 if( m_aContext.HideViewerToolbar ) 5260 aLine.append( "/HideToolbar true\n" ); 5261 if( m_aContext.HideViewerMenubar ) 5262 aLine.append( "/HideMenubar true\n" ); 5263 if( m_aContext.HideViewerWindowControls ) 5264 aLine.append( "/HideWindowUI true\n" ); 5265 if( m_aContext.FitWindow ) 5266 aLine.append( "/FitWindow true\n" ); 5267 if( m_aContext.CenterWindow ) 5268 aLine.append( "/CenterWindow true\n" ); 5269 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle ) 5270 aLine.append( "/DisplayDocTitle true\n" ); 5271 if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) 5272 aLine.append( "/Direction/R2L\n" ); 5273 if( m_aContext.OpenInFullScreenMode ) 5274 switch( m_aContext.PDFDocumentMode ) 5275 { 5276 default : 5277 case PDFWriter::ModeDefault : 5278 aLine.append( "/NonFullScreenPageMode/UseNone\n" ); 5279 break; 5280 case PDFWriter::UseOutlines : 5281 aLine.append( "/NonFullScreenPageMode/UseOutlines\n" ); 5282 break; 5283 case PDFWriter::UseThumbs : 5284 aLine.append( "/NonFullScreenPageMode/UseThumbs\n" ); 5285 break; 5286 } 5287 aLine.append( ">>\n" ); 5288 } 5289 5290 if( nOutlineDict ) 5291 { 5292 aLine.append( "/Outlines " ); 5293 aLine.append( nOutlineDict ); 5294 aLine.append( " 0 R\n" ); 5295 } 5296 if( nStructureDict ) 5297 { 5298 aLine.append( "/StructTreeRoot " ); 5299 aLine.append( nStructureDict ); 5300 aLine.append( " 0 R\n" ); 5301 } 5302 if( !m_aContext.DocumentLocale.Language.isEmpty() ) 5303 { 5304 /* PDF allows only RFC 3066, see above in emitStructure(). */ 5305 LanguageTag aLanguageTag( m_aContext.DocumentLocale); 5306 OUString aLanguage, aScript, aCountry; 5307 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry); 5308 if (!aLanguage.isEmpty()) 5309 { 5310 OUStringBuffer aLocBuf( 16 ); 5311 aLocBuf.append( aLanguage ); 5312 if( !aCountry.isEmpty() ) 5313 { 5314 aLocBuf.append( '-' ); 5315 aLocBuf.append( aCountry ); 5316 } 5317 aLine.append( "/Lang" ); 5318 appendLiteralStringEncrypt( aLocBuf, m_nCatalogObject, aLine ); 5319 aLine.append( "\n" ); 5320 } 5321 } 5322 if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 ) 5323 { 5324 aLine.append( "/MarkInfo<</Marked true>>\n" ); 5325 } 5326 if( !m_aWidgets.empty() ) 5327 { 5328 aLine.append( "/AcroForm<</Fields[\n" ); 5329 int nWidgets = m_aWidgets.size(); 5330 int nOut = 0; 5331 for( int j = 0; j < nWidgets; j++ ) 5332 { 5333 // output only root fields 5334 if( m_aWidgets[j].m_nParent < 1 ) 5335 { 5336 aLine.append( m_aWidgets[j].m_nObject ); 5337 aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " ); 5338 } 5339 } 5340 aLine.append( "\n]" ); 5341 5342 #if HAVE_FEATURE_NSS 5343 if (m_nSignatureObject != -1) 5344 aLine.append( "/SigFlags 3"); 5345 #endif 5346 5347 aLine.append( "/DR " ); 5348 aLine.append( getResourceDictObj() ); 5349 aLine.append( " 0 R" ); 5350 // NeedAppearances must not be used if PDF is signed 5351 if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3 5352 #if HAVE_FEATURE_NSS 5353 || ( m_nSignatureObject != -1 ) 5354 #endif 5355 ) 5356 aLine.append( ">>\n" ); 5357 else 5358 aLine.append( "/NeedAppearances true>>\n" ); 5359 } 5360 5361 //check if there is a Metadata object 5362 if( nOutputIntentObject ) 5363 { 5364 aLine.append("/OutputIntents["); 5365 aLine.append( nOutputIntentObject ); 5366 aLine.append( " 0 R]" ); 5367 } 5368 5369 if( nMetadataObject ) 5370 { 5371 aLine.append("/Metadata "); 5372 aLine.append( nMetadataObject ); 5373 aLine.append( " 0 R" ); 5374 } 5375 5376 aLine.append( ">>\n" 5377 "endobj\n\n" ); 5378 return writeBuffer( aLine.getStr(), aLine.getLength() ); 5379 } 5380 5381 #if HAVE_FEATURE_NSS 5382 5383 bool PDFWriterImpl::emitSignature() 5384 { 5385 if( !updateObject( m_nSignatureObject ) ) 5386 return false; 5387 5388 OStringBuffer aLine( 0x5000 ); 5389 aLine.append( m_nSignatureObject ); 5390 aLine.append( " 0 obj\n" ); 5391 aLine.append("<</Contents <" ); 5392 5393 sal_uInt64 nOffset = ~0U; 5394 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) ); 5395 5396 m_nSignatureContentOffset = nOffset + aLine.getLength(); 5397 5398 // reserve some space for the PKCS#7 object 5399 OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH ); 5400 comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0'); 5401 aLine.append( aContentFiller ); 5402 aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached"); 5403 5404 if( !m_aContext.DocumentInfo.Author.isEmpty() ) 5405 { 5406 aLine.append( "/Name" ); 5407 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine ); 5408 } 5409 5410 aLine.append( " /M "); 5411 appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine ); 5412 5413 aLine.append( " /ByteRange [ 0 "); 5414 aLine.append( m_nSignatureContentOffset - 1 ); 5415 aLine.append( " " ); 5416 aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 ); 5417 aLine.append( " " ); 5418 5419 m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength(); 5420 5421 // mark the last ByteRange no and add some space. Now, we don't know 5422 // how many bytes we need for this ByteRange value 5423 // The real value will be overwritten in the finalizeSignature method 5424 OStringBuffer aByteRangeFiller( 100 ); 5425 comphelper::string::padToLength(aByteRangeFiller, 100, ' '); 5426 aLine.append( aByteRangeFiller ); 5427 aLine.append(" /Filter/Adobe.PPKMS"); 5428 5429 //emit reason, location and contactinfo 5430 if ( !m_aContext.SignReason.isEmpty() ) 5431 { 5432 aLine.append("/Reason"); 5433 appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine ); 5434 } 5435 5436 if ( !m_aContext.SignLocation.isEmpty() ) 5437 { 5438 aLine.append("/Location"); 5439 appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine ); 5440 } 5441 5442 if ( !m_aContext.SignContact.isEmpty() ) 5443 { 5444 aLine.append("/ContactInfo"); 5445 appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine ); 5446 } 5447 5448 aLine.append(" >>\nendobj\n\n" ); 5449 5450 return writeBuffer( aLine.getStr(), aLine.getLength() ); 5451 } 5452 5453 bool PDFWriterImpl::finalizeSignature() 5454 { 5455 if (!m_aContext.SignCertificate.is()) 5456 return false; 5457 5458 // 1- calculate last ByteRange value 5459 sal_uInt64 nOffset = ~0U; 5460 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) ); 5461 5462 sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); 5463 5464 // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position 5465 sal_uInt64 nWritten = 0; 5466 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) ); 5467 OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]"; 5468 5469 if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None) 5470 { 5471 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) ); 5472 return false; 5473 } 5474 5475 // 3- create the PKCS#7 object using NSS 5476 5477 // Prepare buffer and calculate PDF file digest 5478 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) ); 5479 5480 std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]); 5481 sal_uInt64 bytesRead1; 5482 5483 //FIXME: Check if hash is calculated from the correct byterange 5484 if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) || 5485 bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1) 5486 { 5487 SAL_WARN("vcl.pdfwriter", "First buffer read failed"); 5488 return false; 5489 } 5490 5491 std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]); 5492 sal_uInt64 bytesRead2; 5493 5494 if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) || 5495 osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) || 5496 bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo)) 5497 { 5498 SAL_WARN("vcl.pdfwriter", "Second buffer read failed"); 5499 return false; 5500 } 5501 5502 OStringBuffer aCMSHexBuffer; 5503 svl::crypto::Signing aSigning(m_aContext.SignCertificate); 5504 aSigning.AddDataRange(buffer1.get(), bytesRead1); 5505 aSigning.AddDataRange(buffer2.get(), bytesRead2); 5506 aSigning.SetSignTSA(m_aContext.SignTSA); 5507 aSigning.SetSignPassword(m_aContext.SignPassword); 5508 if (!aSigning.Sign(aCMSHexBuffer)) 5509 { 5510 SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed"); 5511 return false; 5512 } 5513 5514 assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH); 5515 5516 // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object 5517 nWritten = 0; 5518 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) ); 5519 m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten); 5520 5521 return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset); 5522 } 5523 5524 #endif //HAVE_FEATURE_NSS 5525 5526 sal_Int32 PDFWriterImpl::emitInfoDict( ) 5527 { 5528 sal_Int32 nObject = createObject(); 5529 5530 if( updateObject( nObject ) ) 5531 { 5532 OStringBuffer aLine( 1024 ); 5533 aLine.append( nObject ); 5534 aLine.append( " 0 obj\n" 5535 "<<" ); 5536 if( !m_aContext.DocumentInfo.Title.isEmpty() ) 5537 { 5538 aLine.append( "/Title" ); 5539 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine ); 5540 aLine.append( "\n" ); 5541 } 5542 if( !m_aContext.DocumentInfo.Author.isEmpty() ) 5543 { 5544 aLine.append( "/Author" ); 5545 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine ); 5546 aLine.append( "\n" ); 5547 } 5548 if( !m_aContext.DocumentInfo.Subject.isEmpty() ) 5549 { 5550 aLine.append( "/Subject" ); 5551 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine ); 5552 aLine.append( "\n" ); 5553 } 5554 if( !m_aContext.DocumentInfo.Keywords.isEmpty() ) 5555 { 5556 aLine.append( "/Keywords" ); 5557 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine ); 5558 aLine.append( "\n" ); 5559 } 5560 if( !m_aContext.DocumentInfo.Creator.isEmpty() ) 5561 { 5562 aLine.append( "/Creator" ); 5563 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine ); 5564 aLine.append( "\n" ); 5565 } 5566 if( !m_aContext.DocumentInfo.Producer.isEmpty() ) 5567 { 5568 aLine.append( "/Producer" ); 5569 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine ); 5570 aLine.append( "\n" ); 5571 } 5572 5573 aLine.append( "/CreationDate" ); 5574 appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine ); 5575 aLine.append( ">>\nendobj\n\n" ); 5576 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) 5577 nObject = 0; 5578 } 5579 else 5580 nObject = 0; 5581 5582 return nObject; 5583 } 5584 5585 // Part of this function may be shared with method appendDest. 5586 sal_Int32 PDFWriterImpl::emitNamedDestinations() 5587 { 5588 sal_Int32 nCount = m_aNamedDests.size(); 5589 if( nCount <= 0 ) 5590 return 0;//define internal error 5591 5592 //get the object number for all the destinations 5593 sal_Int32 nObject = createObject(); 5594 5595 if( updateObject( nObject ) ) 5596 { 5597 //emit the dictionary 5598 OStringBuffer aLine( 1024 ); 5599 aLine.append( nObject ); 5600 aLine.append( " 0 obj\n" 5601 "<<" ); 5602 5603 sal_Int32 nDestID; 5604 for( nDestID = 0; nDestID < nCount; nDestID++ ) 5605 { 5606 const PDFNamedDest& rDest = m_aNamedDests[ nDestID ]; 5607 // In order to correctly function both under an Internet browser and 5608 // directly with a reader (provided the reader has the feature) we 5609 // need to set the name of the destination the same way it will be encoded 5610 // in an Internet link 5611 INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used 5612 aLocalURL.SetMark( rDest.m_aDestName ); 5613 5614 const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as 5615 // in link creation ( see PDFWriterImpl::emitLinkAnnotations ) 5616 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; 5617 5618 aLine.append( '/' ); 5619 appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog ) 5620 aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function 5621 //maps the preceding character properly 5622 aLine.append( rDestPage.m_nPageObject ); 5623 aLine.append( " 0 R" ); 5624 5625 switch( rDest.m_eType ) 5626 { 5627 case PDFWriter::DestAreaType::XYZ: 5628 default: 5629 aLine.append( "/XYZ " ); 5630 appendFixedInt( rDest.m_aRect.Left(), aLine ); 5631 aLine.append( ' ' ); 5632 appendFixedInt( rDest.m_aRect.Bottom(), aLine ); 5633 aLine.append( " 0" ); 5634 break; 5635 case PDFWriter::DestAreaType::FitRectangle: 5636 aLine.append( "/FitR " ); 5637 appendFixedInt( rDest.m_aRect.Left(), aLine ); 5638 aLine.append( ' ' ); 5639 appendFixedInt( rDest.m_aRect.Top(), aLine ); 5640 aLine.append( ' ' ); 5641 appendFixedInt( rDest.m_aRect.Right(), aLine ); 5642 aLine.append( ' ' ); 5643 appendFixedInt( rDest.m_aRect.Bottom(), aLine ); 5644 break; 5645 } 5646 aLine.append( "]\n" ); 5647 } 5648 5649 //close 5650 aLine.append( ">>\nendobj\n\n" ); 5651 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) 5652 nObject = 0; 5653 } 5654 else 5655 nObject = 0; 5656 5657 return nObject; 5658 } 5659 5660 // emits the output intent dictionary 5661 sal_Int32 PDFWriterImpl::emitOutputIntent() 5662 { 5663 if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 ) 5664 return 0; 5665 5666 //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1 5667 5668 OStringBuffer aLine( 1024 ); 5669 sal_Int32 nICCObject = createObject(); 5670 sal_Int32 nStreamLengthObject = createObject(); 5671 5672 aLine.append( nICCObject ); 5673 // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16) 5674 aLine.append( " 0 obj\n<</N 3/Length " ); 5675 aLine.append( nStreamLengthObject ); 5676 aLine.append( " 0 R" ); 5677 if (!g_bDebugDisableCompression) 5678 aLine.append( "/Filter/FlateDecode" ); 5679 aLine.append( ">>\nstream\n" ); 5680 if ( !updateObject( nICCObject ) ) return 0; 5681 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0; 5682 //get file position 5683 sal_uInt64 nBeginStreamPos = 0; 5684 if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos)) 5685 return 0; 5686 beginCompression(); 5687 checkAndEnableStreamEncryption( nICCObject ); 5688 cmsHPROFILE hProfile = cmsCreate_sRGBProfile(); 5689 //force ICC profile version 2.1 5690 cmsSetProfileVersion(hProfile, 2.1); 5691 cmsUInt32Number nBytesNeeded = 0; 5692 cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded); 5693 if (!nBytesNeeded) 5694 return 0; 5695 std::vector<unsigned char> aBuffer(nBytesNeeded); 5696 cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded); 5697 cmsCloseProfile(hProfile); 5698 bool written = writeBuffer( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) ); 5699 disableStreamEncryption(); 5700 endCompression(); 5701 5702 sal_uInt64 nEndStreamPos = 0; 5703 if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None) 5704 return 0; 5705 5706 if( !written ) 5707 return 0; 5708 if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) ) 5709 return 0 ; 5710 aLine.setLength( 0 ); 5711 5712 //emit the stream length object 5713 if ( !updateObject( nStreamLengthObject ) ) return 0; 5714 aLine.setLength( 0 ); 5715 aLine.append( nStreamLengthObject ); 5716 aLine.append( " 0 obj\n" ); 5717 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) ); 5718 aLine.append( "\nendobj\n\n" ); 5719 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0; 5720 aLine.setLength( 0 ); 5721 5722 //emit the OutputIntent dictionary 5723 sal_Int32 nOIObject = createObject(); 5724 if ( !updateObject( nOIObject ) ) return 0; 5725 aLine.append( nOIObject ); 5726 aLine.append( " 0 obj\n" 5727 "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier"); 5728 5729 appendLiteralStringEncrypt( std::string_view("sRGB IEC61966-2.1") ,nOIObject, aLine ); 5730 aLine.append("/DestOutputProfile "); 5731 aLine.append( nICCObject ); 5732 aLine.append( " 0 R>>\nendobj\n\n" ); 5733 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0; 5734 5735 return nOIObject; 5736 } 5737 5738 // formats the string for the XML stream 5739 void escapeStringXML(const OUString& rStr, OUString &rValue) 5740 { 5741 const sal_Unicode* pUni = rStr.getStr(); 5742 int nLen = rStr.getLength(); 5743 for( ; nLen; nLen--, pUni++ ) 5744 { 5745 switch( *pUni ) 5746 { 5747 case u'&': 5748 rValue += "&"; 5749 break; 5750 case u'<': 5751 rValue += "<"; 5752 break; 5753 case u'>': 5754 rValue += ">"; 5755 break; 5756 case u'\'': 5757 rValue += "'"; 5758 break; 5759 case u'"': 5760 rValue += """; 5761 break; 5762 default: 5763 rValue += OUStringChar( *pUni ); 5764 break; 5765 } 5766 } 5767 } 5768 5769 static void lcl_assignMeta(const OUString& aValue, OString& aMeta) 5770 { 5771 if (!aValue.isEmpty()) 5772 { 5773 OUString aTempString; 5774 escapeStringXML(aValue, aTempString); 5775 aMeta = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8); 5776 } 5777 } 5778 5779 // emits the document metadata 5780 sal_Int32 PDFWriterImpl::emitDocumentMetadata() 5781 { 5782 if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 && !m_bIsPDF_UA) 5783 return 0; 5784 5785 //get the object number for all the destinations 5786 sal_Int32 nObject = createObject(); 5787 5788 if( updateObject( nObject ) ) 5789 { 5790 pdf::XmpMetadata aMetadata; 5791 5792 if (m_bIsPDF_A1) 5793 aMetadata.mnPDF_A = 1; 5794 else if (m_bIsPDF_A2) 5795 aMetadata.mnPDF_A = 2; 5796 else if (m_bIsPDF_A3) 5797 aMetadata.mnPDF_A = 3; 5798 5799 aMetadata.mbPDF_UA = m_bIsPDF_UA; 5800 5801 lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle); 5802 lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor); 5803 lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject); 5804 lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer); 5805 lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords); 5806 lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool); 5807 aMetadata.m_sCreateDate = m_aCreationMetaDateString; 5808 5809 OStringBuffer aMetadataObj( 1024 ); 5810 5811 aMetadataObj.append( nObject ); 5812 aMetadataObj.append( " 0 obj\n" ); 5813 5814 aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " ); 5815 5816 aMetadataObj.append( sal_Int32(aMetadata.getSize()) ); 5817 aMetadataObj.append( ">>\nstream\n" ); 5818 if ( !writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) ) 5819 return 0; 5820 //emit the stream 5821 if ( !writeBuffer( aMetadata.getData(), aMetadata.getSize() ) ) 5822 return 0; 5823 5824 aMetadataObj.setLength( 0 ); 5825 aMetadataObj.append( "\nendstream\nendobj\n\n" ); 5826 if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) ) 5827 nObject = 0; 5828 } 5829 else 5830 nObject = 0; 5831 5832 return nObject; 5833 } 5834 5835 bool PDFWriterImpl::emitTrailer() 5836 { 5837 // emit doc info 5838 sal_Int32 nDocInfoObject = emitInfoDict( ); 5839 5840 sal_Int32 nSecObject = 0; 5841 5842 if( m_aContext.Encryption.Encrypt() ) 5843 { 5844 //emit the security information 5845 //must be emitted as indirect dictionary object, since 5846 //Acrobat Reader 5 works only with this kind of implementation 5847 nSecObject = createObject(); 5848 5849 if( updateObject( nSecObject ) ) 5850 { 5851 OStringBuffer aLineS( 1024 ); 5852 aLineS.append( nSecObject ); 5853 aLineS.append( " 0 obj\n" 5854 "<</Filter/Standard/V " ); 5855 // check the version 5856 aLineS.append( "2/Length 128/R 3" ); 5857 5858 // emit the owner password, must not be encrypted 5859 aLineS.append( "/O(" ); 5860 appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS ); 5861 aLineS.append( ")/U(" ); 5862 appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS ); 5863 aLineS.append( ")/P " );// the permission set 5864 aLineS.append( m_nAccessPermissions ); 5865 aLineS.append( ">>\nendobj\n\n" ); 5866 if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) ) 5867 nSecObject = 0; 5868 } 5869 else 5870 nSecObject = 0; 5871 } 5872 // emit xref table 5873 // remember start 5874 sal_uInt64 nXRefOffset = 0; 5875 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) ); 5876 CHECK_RETURN( writeBuffer( "xref\n", 5 ) ); 5877 5878 sal_Int32 nObjects = m_aObjects.size(); 5879 OStringBuffer aLine; 5880 aLine.append( "0 " ); 5881 aLine.append( static_cast<sal_Int32>(nObjects+1) ); 5882 aLine.append( "\n" ); 5883 aLine.append( "0000000000 65535 f \n" ); 5884 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 5885 5886 for( sal_Int32 i = 0; i < nObjects; i++ ) 5887 { 5888 aLine.setLength( 0 ); 5889 OString aOffset = OString::number( m_aObjects[i] ); 5890 for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ ) 5891 aLine.append( '0' ); 5892 aLine.append( aOffset ); 5893 aLine.append( " 00000 n \n" ); 5894 SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" ); 5895 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 5896 } 5897 5898 // prepare document checksum 5899 OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 ); 5900 ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize()); 5901 for (sal_uInt8 i : nMD5Sum) 5902 appendHex( i, aDocChecksum ); 5903 // document id set in setDocInfo method 5904 // emit trailer 5905 aLine.setLength( 0 ); 5906 aLine.append( "trailer\n" 5907 "<</Size " ); 5908 aLine.append( static_cast<sal_Int32>(nObjects+1) ); 5909 aLine.append( "/Root " ); 5910 aLine.append( m_nCatalogObject ); 5911 aLine.append( " 0 R\n" ); 5912 if( nSecObject ) 5913 { 5914 aLine.append( "/Encrypt "); 5915 aLine.append( nSecObject ); 5916 aLine.append( " 0 R\n" ); 5917 } 5918 if( nDocInfoObject ) 5919 { 5920 aLine.append( "/Info " ); 5921 aLine.append( nDocInfoObject ); 5922 aLine.append( " 0 R\n" ); 5923 } 5924 if( ! m_aContext.Encryption.DocumentIdentifier.empty() ) 5925 { 5926 aLine.append( "/ID [ <" ); 5927 for (auto const& item : m_aContext.Encryption.DocumentIdentifier) 5928 { 5929 appendHex( sal_Int8(item), aLine ); 5930 } 5931 aLine.append( ">\n" 5932 "<" ); 5933 for (auto const& item : m_aContext.Encryption.DocumentIdentifier) 5934 { 5935 appendHex( sal_Int8(item), aLine ); 5936 } 5937 aLine.append( "> ]\n" ); 5938 } 5939 if( !aDocChecksum.isEmpty() ) 5940 { 5941 aLine.append( "/DocChecksum /" ); 5942 aLine.append( aDocChecksum ); 5943 aLine.append( "\n" ); 5944 } 5945 5946 if (!m_aDocumentAttachedFiles.empty()) 5947 { 5948 aLine.append( "/AdditionalStreams [" ); 5949 for (auto const& rAttachedFile : m_aDocumentAttachedFiles) 5950 { 5951 aLine.append( "/" ); 5952 appendName(rAttachedFile.maMimeType, aLine); 5953 aLine.append(" "); 5954 appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine); 5955 aLine.append("\n"); 5956 } 5957 aLine.append( "]\n" ); 5958 } 5959 5960 aLine.append( ">>\n" 5961 "startxref\n" ); 5962 aLine.append( static_cast<sal_Int64>(nXRefOffset) ); 5963 aLine.append( "\n" 5964 "%%EOF\n" ); 5965 return writeBuffer( aLine.getStr(), aLine.getLength() ); 5966 } 5967 5968 namespace { 5969 5970 struct AnnotationSortEntry 5971 { 5972 sal_Int32 nTabOrder; 5973 sal_Int32 nObject; 5974 sal_Int32 nWidgetIndex; 5975 5976 AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) : 5977 nTabOrder( nTab ), 5978 nObject( nObj ), 5979 nWidgetIndex( nI ) 5980 {} 5981 }; 5982 5983 struct AnnotSortContainer 5984 { 5985 o3tl::sorted_vector< sal_Int32 > aObjects; 5986 std::vector< AnnotationSortEntry > aSortedAnnots; 5987 }; 5988 5989 struct AnnotSorterLess 5990 { 5991 std::vector<PDFWidget>& m_rWidgets; 5992 5993 explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {} 5994 5995 bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight ) 5996 { 5997 if( rLeft.nTabOrder < rRight.nTabOrder ) 5998 return true; 5999 if( rRight.nTabOrder < rLeft.nTabOrder ) 6000 return false; 6001 if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 ) 6002 return false; 6003 if( rRight.nWidgetIndex < 0 ) 6004 return true; 6005 if( rLeft.nWidgetIndex < 0 ) 6006 return false; 6007 // remember: widget rects are in PDF coordinates, so they are ordered down up 6008 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() > 6009 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() ) 6010 return true; 6011 if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() > 6012 m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() ) 6013 return false; 6014 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() < 6015 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() ) 6016 return true; 6017 return false; 6018 } 6019 }; 6020 6021 } 6022 6023 void PDFWriterImpl::sortWidgets() 6024 { 6025 // sort widget annotations on each page as per their 6026 // TabOrder attribute 6027 std::unordered_map< sal_Int32, AnnotSortContainer > sorted; 6028 int nWidgets = m_aWidgets.size(); 6029 for( int nW = 0; nW < nWidgets; nW++ ) 6030 { 6031 const PDFWidget& rWidget = m_aWidgets[nW]; 6032 if( rWidget.m_nPage >= 0 ) 6033 { 6034 AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ]; 6035 // optimize vector allocation 6036 if( rCont.aSortedAnnots.empty() ) 6037 rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() ); 6038 // insert widget to tab sorter 6039 // RadioButtons are not page annotations, only their individual check boxes are 6040 if( rWidget.m_eType != PDFWriter::RadioButton ) 6041 { 6042 rCont.aObjects.insert( rWidget.m_nObject ); 6043 rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW ); 6044 } 6045 } 6046 } 6047 for (auto & item : sorted) 6048 { 6049 // append entries for non widget annotations 6050 PDFPage& rPage = m_aPages[ item.first ]; 6051 unsigned int nAnnots = rPage.m_aAnnotations.size(); 6052 for( unsigned int nA = 0; nA < nAnnots; nA++ ) 6053 if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end()) 6054 item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 ); 6055 6056 AnnotSorterLess aLess( m_aWidgets ); 6057 std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess ); 6058 // sanity check 6059 if( item.second.aSortedAnnots.size() == nAnnots) 6060 { 6061 for( unsigned int nA = 0; nA < nAnnots; nA++ ) 6062 rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject; 6063 } 6064 else 6065 { 6066 SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" ); 6067 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions " 6068 "on page nr " << item.first << ", " << 6069 static_cast<tools::Long>(item.second.aSortedAnnots.size()) << " sorted and " << 6070 static_cast<tools::Long>(nAnnots) << " unsorted"); 6071 } 6072 } 6073 6074 // FIXME: implement tab order in structure tree for PDF 1.5 6075 } 6076 6077 bool PDFWriterImpl::emit() 6078 { 6079 endPage(); 6080 6081 // resort structure tree and annotations if necessary 6082 // needed for widget tab order 6083 sortWidgets(); 6084 6085 #if HAVE_FEATURE_NSS 6086 if( m_aContext.SignPDF ) 6087 { 6088 // sign the document 6089 PDFWriter::SignatureWidget aSignature; 6090 aSignature.Name = "Signature1"; 6091 createControl( aSignature, 0 ); 6092 } 6093 #endif 6094 6095 // emit catalog 6096 CHECK_RETURN( emitCatalog() ); 6097 6098 #if HAVE_FEATURE_NSS 6099 if (m_nSignatureObject != -1) // if document is signed, emit sigdict 6100 { 6101 if( !emitSignature() ) 6102 { 6103 m_aErrors.insert( PDFWriter::Error_Signature_Failed ); 6104 return false; 6105 } 6106 } 6107 #endif 6108 6109 // emit trailer 6110 CHECK_RETURN( emitTrailer() ); 6111 6112 #if HAVE_FEATURE_NSS 6113 if (m_nSignatureObject != -1) // finalize the signature 6114 { 6115 if( !finalizeSignature() ) 6116 { 6117 m_aErrors.insert( PDFWriter::Error_Signature_Failed ); 6118 return false; 6119 } 6120 } 6121 #endif 6122 6123 m_aFile.close(); 6124 m_bOpen = false; 6125 6126 return true; 6127 } 6128 6129 6130 sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont ) 6131 { 6132 Push(); 6133 6134 SetFont( i_rFont ); 6135 6136 const LogicalFontInstance* pFontInstance = GetFontInstance(); 6137 const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace(); 6138 sal_Int32 nFontID = 0; 6139 auto it = m_aSystemFonts.find( pFace ); 6140 if( it != m_aSystemFonts.end() ) 6141 nFontID = it->second.m_nNormalFontID; 6142 else 6143 { 6144 nFontID = m_nNextFID++; 6145 m_aSystemFonts[ pFace ] = EmbedFont(); 6146 m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance); 6147 m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID; 6148 } 6149 6150 Pop(); 6151 return nFontID; 6152 } 6153 6154 void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId, 6155 const vcl::font::PhysicalFontFace* pFace, 6156 const std::vector<sal_Ucs>& rCodeUnits, 6157 sal_Int32 nGlyphWidth, 6158 sal_uInt8& nMappedGlyph, 6159 sal_Int32& nMappedFontObject) 6160 { 6161 FontSubset& rSubset = m_aSubsets[ pFace ]; 6162 // search for font specific glyphID 6163 auto it = rSubset.m_aMapping.find( nFontGlyphId ); 6164 if( it != rSubset.m_aMapping.end() ) 6165 { 6166 nMappedFontObject = it->second.m_nFontID; 6167 nMappedGlyph = it->second.m_nSubsetGlyphID; 6168 } 6169 else 6170 { 6171 // create new subset if necessary 6172 if( rSubset.m_aSubsets.empty() 6173 || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) ) 6174 { 6175 rSubset.m_aSubsets.emplace_back( m_nNextFID++ ); 6176 } 6177 6178 // copy font id 6179 nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID; 6180 // create new glyph in subset 6181 sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1); 6182 nMappedGlyph = nNewId; 6183 6184 // add new glyph to emitted font subset 6185 GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ]; 6186 rNewGlyphEmit.setGlyphId( nNewId ); 6187 rNewGlyphEmit.setGlyphWidth(XUnits(pFace->UnitsPerEm(), nGlyphWidth)); 6188 for (const auto nCode : rCodeUnits) 6189 rNewGlyphEmit.addCode(nCode); 6190 6191 // add new glyph to font mapping 6192 Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ]; 6193 rNewGlyph.m_nFontID = nMappedFontObject; 6194 rNewGlyph.m_nSubsetGlyphID = nNewId; 6195 } 6196 } 6197 6198 void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId, 6199 const vcl::font::PhysicalFontFace* pFace, 6200 const LogicalFontInstance* pFont, 6201 const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth, 6202 sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject) 6203 { 6204 auto bVariations = !pFace->GetVariations(*pFont).empty(); 6205 if (pFace->IsColorFont() || bVariations) 6206 { 6207 // Font has colors, check if this glyph has color layers or bitmap. 6208 tools::Rectangle aRect; 6209 auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId); 6210 auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect); 6211 if (!aLayers.empty() || !aBitmap.empty() || bVariations) 6212 { 6213 auto& rSubset = m_aType3Fonts[pFace]; 6214 auto it = rSubset.m_aMapping.find(nFontGlyphId); 6215 if (it != rSubset.m_aMapping.end()) 6216 { 6217 nMappedFontObject = it->second.m_nFontID; 6218 nMappedGlyph = it->second.m_nSubsetGlyphID; 6219 } 6220 else 6221 { 6222 // create new subset if necessary 6223 if (rSubset.m_aSubsets.empty() 6224 || (rSubset.m_aSubsets.back().m_aMapping.size() > 254)) 6225 { 6226 rSubset.m_aSubsets.emplace_back(m_nNextFID++); 6227 } 6228 6229 // copy font id 6230 nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID; 6231 // create new glyph in subset 6232 sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>( 6233 rSubset.m_aSubsets.back().m_aMapping.size() + 1); 6234 nMappedGlyph = nNewId; 6235 6236 // add new glyph to emitted font subset 6237 auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId]; 6238 rNewGlyphEmit.setGlyphId(nNewId); 6239 rNewGlyphEmit.setGlyphWidth(nGlyphWidth); 6240 for (const auto nCode : rCodeUnits) 6241 rNewGlyphEmit.addCode(nCode); 6242 6243 // add color layers to the glyphs 6244 if (!aLayers.empty()) 6245 { 6246 for (const auto& aLayer : aLayers) 6247 { 6248 sal_uInt8 nLayerGlyph; 6249 sal_Int32 nLayerFontID; 6250 registerSimpleGlyph(aLayer.nGlyphIndex, pFace, rCodeUnits, nGlyphWidth, 6251 nLayerGlyph, nLayerFontID); 6252 6253 rNewGlyphEmit.addColorLayer( 6254 { nLayerFontID, nLayerGlyph, aLayer.nColorIndex }); 6255 } 6256 } 6257 else if (!aBitmap.empty()) 6258 rNewGlyphEmit.setColorBitmap(aBitmap, aRect); 6259 else if (bVariations) 6260 rNewGlyphEmit.setOutline(pFont->GetGlyphOutlineUntransformed(nFontGlyphId)); 6261 6262 // add new glyph to font mapping 6263 Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId]; 6264 rNewGlyph.m_nFontID = nMappedFontObject; 6265 rNewGlyph.m_nSubsetGlyphID = nNewId; 6266 } 6267 return; 6268 } 6269 } 6270 6271 // If we reach here then the glyph has no color layers. 6272 registerSimpleGlyph(nFontGlyphId, pFace, rCodeUnits, nGlyphWidth, nMappedGlyph, 6273 nMappedFontObject); 6274 } 6275 6276 void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines ) 6277 { 6278 push( PushFlags::ALL ); 6279 6280 FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief(); 6281 6282 Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor(); 6283 Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor; 6284 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; 6285 Color aReliefColor( COL_LIGHTGRAY ); 6286 if( aTextColor == COL_BLACK ) 6287 aTextColor = COL_WHITE; 6288 if( aTextLineColor == COL_BLACK ) 6289 aTextLineColor = COL_WHITE; 6290 if( aOverlineColor == COL_BLACK ) 6291 aOverlineColor = COL_WHITE; 6292 // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct 6293 if( aTextColor == COL_WHITE ) 6294 aReliefColor = COL_BLACK; 6295 6296 Font aSetFont = m_aCurrentPDFState.m_aFont; 6297 aSetFont.SetRelief( FontRelief::NONE ); 6298 aSetFont.SetShadow( false ); 6299 6300 aSetFont.SetColor( aReliefColor ); 6301 setTextLineColor( aReliefColor ); 6302 setOverlineColor( aReliefColor ); 6303 setFont( aSetFont ); 6304 tools::Long nOff = 1 + GetDPIX()/300; 6305 if( eRelief == FontRelief::Engraved ) 6306 nOff = -nOff; 6307 6308 rLayout.DrawOffset() += Point( nOff, nOff ); 6309 updateGraphicsState(); 6310 drawLayout( rLayout, rText, bTextLines ); 6311 6312 rLayout.DrawOffset() -= Point( nOff, nOff ); 6313 setTextLineColor( aTextLineColor ); 6314 setOverlineColor( aOverlineColor ); 6315 aSetFont.SetColor( aTextColor ); 6316 setFont( aSetFont ); 6317 updateGraphicsState(); 6318 drawLayout( rLayout, rText, bTextLines ); 6319 6320 // clean up the mess 6321 pop(); 6322 } 6323 6324 void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines ) 6325 { 6326 Font aSaveFont = m_aCurrentPDFState.m_aFont; 6327 Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor; 6328 Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor; 6329 6330 Font& rFont = m_aCurrentPDFState.m_aFont; 6331 if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 ) 6332 rFont.SetColor( COL_LIGHTGRAY ); 6333 else 6334 rFont.SetColor( COL_BLACK ); 6335 rFont.SetShadow( false ); 6336 rFont.SetOutline( false ); 6337 setFont( rFont ); 6338 setTextLineColor( rFont.GetColor() ); 6339 setOverlineColor( rFont.GetColor() ); 6340 updateGraphicsState(); 6341 6342 tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24); 6343 if( rFont.IsOutline() ) 6344 nOff++; 6345 rLayout.DrawBase() += DevicePoint(nOff, nOff); 6346 drawLayout( rLayout, rText, bTextLines ); 6347 rLayout.DrawBase() -= DevicePoint(nOff, nOff); 6348 6349 setFont( aSaveFont ); 6350 setTextLineColor( aSaveTextLineColor ); 6351 setOverlineColor( aSaveOverlineColor ); 6352 updateGraphicsState(); 6353 } 6354 6355 void PDFWriterImpl::drawVerticalGlyphs( 6356 const std::vector<PDFGlyph>& rGlyphs, 6357 OStringBuffer& rLine, 6358 const Point& rAlignOffset, 6359 const Matrix3& rRotScale, 6360 double fAngle, 6361 double fXScale, 6362 sal_Int32 nFontHeight) 6363 { 6364 double nXOffset = 0; 6365 Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos)); 6366 aCurPos += rAlignOffset; 6367 for( size_t i = 0; i < rGlyphs.size(); i++ ) 6368 { 6369 // have to emit each glyph on its own 6370 double fDeltaAngle = 0.0; 6371 double fYScale = 1.0; 6372 double fTempXScale = fXScale; 6373 6374 // perform artificial italics if necessary 6375 double fSkew = 0.0; 6376 if (rGlyphs[i].m_pFont->NeedsArtificialItalic()) 6377 fSkew = ARTIFICIAL_ITALIC_SKEW; 6378 6379 double fSkewB = fSkew; 6380 double fSkewA = 0.0; 6381 6382 Point aDeltaPos; 6383 if (rGlyphs[i].m_pGlyph->IsVertical()) 6384 { 6385 fDeltaAngle = M_PI/2.0; 6386 fYScale = fXScale; 6387 fTempXScale = 1.0; 6388 fSkewA = -fSkewB; 6389 fSkewB = 0.0; 6390 } 6391 aDeltaPos += SubPixelToLogic(DevicePoint(nXOffset / fXScale, 0)) - SubPixelToLogic(DevicePoint()); 6392 if( i < rGlyphs.size()-1 ) 6393 // #i120627# the text on the Y axis is reversed when export ppt file to PDF format 6394 { 6395 double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX(); 6396 double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY(); 6397 nXOffset += std::hypot(nOffsetX, nOffsetY); 6398 } 6399 if (!rGlyphs[i].m_pGlyph->glyphId()) 6400 continue; 6401 6402 aDeltaPos = rRotScale.transform( aDeltaPos ); 6403 6404 Matrix3 aMat; 6405 if( fSkewB != 0.0 || fSkewA != 0.0 ) 6406 aMat.skew( fSkewA, fSkewB ); 6407 aMat.scale( fTempXScale, fYScale ); 6408 aMat.rotate( fAngle+fDeltaAngle ); 6409 aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() ); 6410 m_aPages.back().appendMatrix3(aMat, rLine); 6411 rLine.append( " Tm" ); 6412 if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId ) 6413 { 6414 rLine.append( " /F" ); 6415 rLine.append( rGlyphs[i].m_nMappedFontId ); 6416 rLine.append( ' ' ); 6417 m_aPages.back().appendMappedLength( nFontHeight, rLine ); 6418 rLine.append( " Tf" ); 6419 } 6420 rLine.append( "<" ); 6421 appendHex( rGlyphs[i].m_nMappedGlyphId, rLine ); 6422 rLine.append( ">Tj\n" ); 6423 } 6424 } 6425 6426 void PDFWriterImpl::drawHorizontalGlyphs( 6427 const std::vector<PDFGlyph>& rGlyphs, 6428 OStringBuffer& rLine, 6429 const Point& rAlignOffset, 6430 bool bFirst, 6431 double fAngle, 6432 double fXScale, 6433 sal_Int32 nFontHeight, 6434 sal_Int32 nPixelFontHeight) 6435 { 6436 // horizontal (= normal) case 6437 6438 // fill in run end indices 6439 // end is marked by index of the first glyph of the next run 6440 // a run is marked by same mapped font id and same Y position 6441 std::vector< sal_uInt32 > aRunEnds; 6442 aRunEnds.reserve( rGlyphs.size() ); 6443 for( size_t i = 1; i < rGlyphs.size(); i++ ) 6444 { 6445 if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId || 6446 rGlyphs[i].m_pFont != rGlyphs[i-1].m_pFont || 6447 rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() ) 6448 { 6449 aRunEnds.push_back(i); 6450 } 6451 } 6452 // last run ends at last glyph 6453 aRunEnds.push_back( rGlyphs.size() ); 6454 6455 // loop over runs of the same font 6456 sal_uInt32 nBeginRun = 0; 6457 for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ ) 6458 { 6459 // setup text matrix back transformed to current coordinate system 6460 Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos)); 6461 aCurPos += rAlignOffset; 6462 6463 // perform artificial italics if necessary 6464 double fSkew = 0.0; 6465 if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic()) 6466 fSkew = ARTIFICIAL_ITALIC_SKEW; 6467 6468 // the first run can be set with "Td" operator 6469 // subsequent use of that operator would move 6470 // the textline matrix relative to what was set before 6471 // making use of that would drive us into rounding issues 6472 Matrix3 aMat; 6473 if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 ) 6474 { 6475 m_aPages.back().appendPoint( aCurPos, rLine ); 6476 rLine.append( " Td " ); 6477 } 6478 else 6479 { 6480 if( fSkew != 0.0 ) 6481 aMat.skew( 0.0, fSkew ); 6482 aMat.scale( fXScale, 1.0 ); 6483 aMat.rotate( fAngle ); 6484 aMat.translate( aCurPos.X(), aCurPos.Y() ); 6485 m_aPages.back().appendMatrix3(aMat, rLine); 6486 rLine.append( " Tm\n" ); 6487 } 6488 // set up correct font 6489 rLine.append( "/F" ); 6490 rLine.append( rGlyphs[nBeginRun].m_nMappedFontId ); 6491 rLine.append( ' ' ); 6492 m_aPages.back().appendMappedLength( nFontHeight, rLine ); 6493 rLine.append( " Tf" ); 6494 6495 // output glyphs using Tj or TJ 6496 OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 ); 6497 aKernedLine.append( "[<" ); 6498 aUnkernedLine.append( '<' ); 6499 appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine ); 6500 appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine ); 6501 6502 aMat.invert(); 6503 bool bNeedKern = false; 6504 for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ ) 6505 { 6506 appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine ); 6507 // check if default glyph positioning is sufficient 6508 const DevicePoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos ); 6509 const DevicePoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos ); 6510 double fAdvance = aThisPos.getX() - aPrevPos.getX(); 6511 fAdvance *= 1000.0 / nPixelFontHeight; 6512 const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5; 6513 SAL_WARN_IF( 6514 fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter", 6515 "adjustment " << fAdjustment << " outside 32-bit int"); 6516 const sal_Int32 nAdjustment = static_cast<sal_Int32>( 6517 std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32))); 6518 if( nAdjustment != 0 ) 6519 { 6520 // apply individual glyph positioning 6521 bNeedKern = true; 6522 aKernedLine.append( ">" ); 6523 aKernedLine.append( nAdjustment ); 6524 aKernedLine.append( "<" ); 6525 } 6526 appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine ); 6527 } 6528 aKernedLine.append( ">]TJ\n" ); 6529 aUnkernedLine.append( ">Tj\n" ); 6530 rLine.append( bNeedKern ? aKernedLine : aUnkernedLine ); 6531 6532 // set beginning of next run 6533 nBeginRun = aRunEnds[nRun]; 6534 } 6535 } 6536 6537 void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines ) 6538 { 6539 // relief takes precedence over shadow (see outdev3.cxx) 6540 if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE ) 6541 { 6542 drawRelief( rLayout, rText, bTextLines ); 6543 return; 6544 } 6545 else if( m_aCurrentPDFState.m_aFont.IsShadow() ) 6546 drawShadow( rLayout, rText, bTextLines ); 6547 6548 OStringBuffer aLine( 512 ); 6549 6550 const int nMaxGlyphs = 256; 6551 6552 std::vector<sal_Ucs> aCodeUnits; 6553 bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical(); 6554 int nIndex = 0; 6555 double fXScale = 1.0; 6556 sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight; 6557 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment(); 6558 6559 // transform font height back to current units 6560 // note: the layout calculates in outdevs device pixel !! 6561 sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight ); 6562 if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() ) 6563 { 6564 Font aFont( m_aCurrentPDFState.m_aFont ); 6565 aFont.SetAverageFontWidth( 0 ); 6566 FontMetric aMetric = GetFontMetric( aFont ); 6567 if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() ) 6568 { 6569 fXScale = 6570 static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) / 6571 static_cast<double>(aMetric.GetAverageFontWidth()); 6572 } 6573 } 6574 6575 // if the mapmode is distorted we need to adjust for that also 6576 if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() ) 6577 { 6578 fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY()); 6579 } 6580 6581 Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation(); 6582 // normalize angles 6583 while( nAngle < 0_deg10 ) 6584 nAngle += 3600_deg10; 6585 nAngle = nAngle % 3600_deg10; 6586 double fAngle = toRadians(nAngle); 6587 6588 Matrix3 aRotScale; 6589 aRotScale.scale( fXScale, 1.0 ); 6590 if( fAngle != 0.0 ) 6591 aRotScale.rotate( -fAngle ); 6592 6593 bool bPop = false; 6594 bool bABold = false; 6595 // artificial bold necessary ? 6596 if (GetFontInstance()->NeedsArtificialBold()) 6597 { 6598 aLine.append("q "); 6599 bPop = true; 6600 bABold = true; 6601 } 6602 // setup text colors (if necessary) 6603 Color aStrokeColor( COL_TRANSPARENT ); 6604 Color aNonStrokeColor( COL_TRANSPARENT ); 6605 6606 if( m_aCurrentPDFState.m_aFont.IsOutline() ) 6607 { 6608 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); 6609 aNonStrokeColor = COL_WHITE; 6610 } 6611 else 6612 aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); 6613 if( bABold ) 6614 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); 6615 6616 if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor ) 6617 { 6618 if( ! bPop ) 6619 aLine.append( "q " ); 6620 bPop = true; 6621 appendStrokingColor( aStrokeColor, aLine ); 6622 aLine.append( "\n" ); 6623 } 6624 if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor ) 6625 { 6626 if( ! bPop ) 6627 aLine.append( "q " ); 6628 bPop = true; 6629 appendNonStrokingColor( aNonStrokeColor, aLine ); 6630 aLine.append( "\n" ); 6631 } 6632 6633 // begin text object 6634 aLine.append( "BT\n" ); 6635 // outline attribute ? 6636 if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold ) 6637 { 6638 // set correct text mode, set stroke width 6639 aLine.append( "2 Tr " ); // fill, then stroke 6640 6641 if( m_aCurrentPDFState.m_aFont.IsOutline() ) 6642 { 6643 // unclear what to do in case of outline and artificial bold 6644 // for the time being outline wins 6645 aLine.append( "0.25 w \n" ); 6646 } 6647 else 6648 { 6649 double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0; 6650 m_aPages.back().appendMappedLength( fW, aLine ); 6651 aLine.append ( " w\n" ); 6652 } 6653 } 6654 6655 FontMetric aRefDevFontMetric = GetFontMetric(); 6656 const GlyphItem* pGlyph = nullptr; 6657 const LogicalFontInstance* pGlyphFont = nullptr; 6658 6659 // collect the glyphs into a single array 6660 std::vector< PDFGlyph > aGlyphs; 6661 aGlyphs.reserve( nMaxGlyphs ); 6662 // first get all the glyphs and register them; coordinates still in Pixel 6663 DevicePoint aPos; 6664 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont)) 6665 { 6666 const auto* pFace = pGlyphFont->GetFontFace(); 6667 6668 aCodeUnits.clear(); 6669 6670 // tdf#66597, tdf#115117 6671 // 6672 // Here is how we embed textual content in PDF files, to allow for 6673 // better text extraction for complex and typography-rich text. 6674 // 6675 // * If there is many to one or many to many mapping, use an 6676 // ActualText span embedding the original string, since ToUnicode 6677 // can't handle these. 6678 // * If the one glyph is used for several Unicode code points, also 6679 // use ActualText since ToUnicode can map each glyph in the font 6680 // only once. 6681 // * Limit ActualText to single cluster at a time, since using it 6682 // for whole words or sentences breaks text selection and 6683 // highlighting in PDF viewers (there will be no way to tell 6684 // which glyphs belong to which characters). 6685 // * Keep generating (now) redundant ToUnicode entries for 6686 // compatibility with old tools not supporting ActualText. 6687 6688 assert(pGlyph->charCount() >= 0); 6689 for (int n = 0; n < pGlyph->charCount(); n++) 6690 aCodeUnits.push_back(rText[pGlyph->charPos() + n]); 6691 6692 bool bUseActualText = false; 6693 6694 // If this is a start of complex cluster, use ActualText. 6695 if (pGlyph->IsClusterStart()) 6696 bUseActualText = true; 6697 6698 const auto nGlyphId = pGlyph->glyphId(); 6699 6700 // A glyph can't have more than one ToUnicode entry, use ActualText 6701 // instead. 6702 if (!aCodeUnits.empty() && !bUseActualText) 6703 { 6704 for (const auto& rSubset : m_aSubsets[pFace].m_aSubsets) 6705 { 6706 const auto& it = rSubset.m_aMapping.find(nGlyphId); 6707 if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits) 6708 { 6709 bUseActualText = true; 6710 aCodeUnits.clear(); 6711 } 6712 } 6713 } 6714 6715 auto nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, pGlyph->IsVertical(), false); 6716 6717 sal_uInt8 nMappedGlyph; 6718 sal_Int32 nMappedFontObject; 6719 registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nGlyphWidth, nMappedGlyph, nMappedFontObject); 6720 6721 int nCharPos = -1; 6722 if (bUseActualText || pGlyph->IsInCluster()) 6723 nCharPos = pGlyph->charPos(); 6724 6725 aGlyphs.emplace_back(aPos, 6726 pGlyph, 6727 pGlyphFont, 6728 XUnits(pFace->UnitsPerEm(), nGlyphWidth), 6729 nMappedFontObject, 6730 nMappedGlyph, 6731 nCharPos); 6732 } 6733 6734 // Avoid fill color when map mode is in pixels, the below code assumes 6735 // logic map mode. 6736 bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel; 6737 if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel) 6738 { 6739 // PDF doesn't have a text fill color, so draw a rectangle before 6740 // drawing the actual text. 6741 push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR); 6742 setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor()); 6743 // Avoid border around the rectangle for Writer shape text. 6744 setLineColor(COL_TRANSPARENT); 6745 6746 // The rectangle is the bounding box of the text, but also includes 6747 // ascent / descent to match the on-screen rendering. 6748 // This is the top left of the text without ascent / descent. 6749 DevicePoint aDrawPosition(rLayout.GetDrawPosition()); 6750 tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition), 6751 Size(ImplDevicePixelToLogicWidth(rLayout.GetTextWidth()), 0)); 6752 aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent()); 6753 // This includes ascent / descent. 6754 aRectangle.setHeight(aRefDevFontMetric.GetLineHeight()); 6755 6756 const LogicalFontInstance* pFontInstance = GetFontInstance(); 6757 if (pFontInstance->mnOrientation) 6758 { 6759 // Adapt rectangle for rotated text. 6760 tools::Polygon aPolygon(aRectangle); 6761 aPolygon.Rotate(SubPixelToLogic(aDrawPosition), pFontInstance->mnOrientation); 6762 drawPolygon(aPolygon); 6763 } 6764 else 6765 drawRectangle(aRectangle); 6766 6767 pop(); 6768 } 6769 6770 Point aAlignOffset; 6771 if ( eAlign == ALIGN_BOTTOM ) 6772 aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) ); 6773 else if ( eAlign == ALIGN_TOP ) 6774 aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() ); 6775 if( aAlignOffset.X() || aAlignOffset.Y() ) 6776 aAlignOffset = aRotScale.transform( aAlignOffset ); 6777 6778 /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original 6779 string contained only one of the UTF16 BOMs 6780 */ 6781 if( ! aGlyphs.empty() ) 6782 { 6783 size_t nStart = 0; 6784 size_t nEnd = 0; 6785 while (nStart < aGlyphs.size()) 6786 { 6787 while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos) 6788 nEnd++; 6789 6790 std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd); 6791 6792 int nCharPos, nCharCount; 6793 if (!aRun.front().m_pGlyph->IsRTLGlyph()) 6794 { 6795 nCharPos = aRun.front().m_nCharPos; 6796 nCharCount = aRun.front().m_pGlyph->charCount(); 6797 } 6798 else 6799 { 6800 nCharPos = aRun.back().m_nCharPos; 6801 nCharCount = aRun.back().m_pGlyph->charCount(); 6802 } 6803 6804 if (nCharPos >= 0 && nCharCount) 6805 { 6806 aLine.append("/Span<</ActualText<FEFF"); 6807 for (int i = 0; i < nCharCount; i++) 6808 { 6809 sal_Unicode aChar = rText[nCharPos + i]; 6810 appendHex(static_cast<sal_Int8>(aChar >> 8), aLine); 6811 appendHex(static_cast<sal_Int8>(aChar & 255), aLine); 6812 } 6813 aLine.append( ">>>\nBDC\n" ); 6814 } 6815 6816 if (bVertical) 6817 drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, nFontHeight); 6818 else 6819 drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, nFontHeight, nPixelFontHeight); 6820 6821 if (nCharPos >= 0 && nCharCount) 6822 aLine.append( "EMC\n" ); 6823 6824 nStart = nEnd; 6825 } 6826 } 6827 6828 // end textobject 6829 aLine.append( "ET\n" ); 6830 if( bPop ) 6831 aLine.append( "Q\n" ); 6832 6833 writeBuffer( aLine.getStr(), aLine.getLength() ); 6834 6835 // draw eventual textlines 6836 FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout(); 6837 FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline(); 6838 FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline(); 6839 if( bTextLines && 6840 ( 6841 ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) || 6842 ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) || 6843 ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW ) 6844 ) 6845 ) 6846 { 6847 bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove(); 6848 if( m_aCurrentPDFState.m_aFont.IsWordLineMode() ) 6849 { 6850 DevicePoint aStartPt; 6851 DeviceCoordinate nWidth = 0; 6852 nIndex = 0; 6853 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex)) 6854 { 6855 if (!pGlyph->IsSpacing()) 6856 { 6857 if( !nWidth ) 6858 aStartPt = aPos; 6859 6860 nWidth += pGlyph->newWidth(); 6861 } 6862 else if( nWidth > 0 ) 6863 { 6864 drawTextLine( SubPixelToLogic(aStartPt), 6865 ImplDevicePixelToLogicWidth( nWidth ), 6866 eStrikeout, eUnderline, eOverline, bUnderlineAbove ); 6867 nWidth = 0; 6868 } 6869 } 6870 6871 if( nWidth > 0 ) 6872 { 6873 drawTextLine( SubPixelToLogic(aStartPt), 6874 ImplDevicePixelToLogicWidth( nWidth ), 6875 eStrikeout, eUnderline, eOverline, bUnderlineAbove ); 6876 } 6877 } 6878 else 6879 { 6880 DevicePoint aStartPt = rLayout.GetDrawPosition(); 6881 int nWidth = rLayout.GetTextWidth(); 6882 drawTextLine( SubPixelToLogic(aStartPt), 6883 ImplDevicePixelToLogicWidth( nWidth ), 6884 eStrikeout, eUnderline, eOverline, bUnderlineAbove ); 6885 } 6886 } 6887 6888 // write eventual emphasis marks 6889 if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) ) 6890 return; 6891 6892 push( PushFlags::ALL ); 6893 6894 aLine.setLength( 0 ); 6895 aLine.append( "q\n" ); 6896 6897 FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle(); 6898 6899 tools::Long nEmphHeight; 6900 if ( nEmphMark & FontEmphasisMark::PosBelow ) 6901 nEmphHeight = GetEmphasisDescent(); 6902 else 6903 nEmphHeight = GetEmphasisAscent(); 6904 6905 vcl::font::EmphasisMark aEmphasisMark(nEmphMark, ImplDevicePixelToLogicWidth(nEmphHeight), GetDPIY()); 6906 if ( aEmphasisMark.IsShapePolyLine() ) 6907 { 6908 setLineColor( m_aCurrentPDFState.m_aFont.GetColor() ); 6909 setFillColor( COL_TRANSPARENT ); 6910 } 6911 else 6912 { 6913 setFillColor( m_aCurrentPDFState.m_aFont.GetColor() ); 6914 setLineColor( COL_TRANSPARENT ); 6915 } 6916 6917 writeBuffer( aLine.getStr(), aLine.getLength() ); 6918 6919 Point aOffset(0,0); 6920 6921 if ( nEmphMark & FontEmphasisMark::PosBelow ) 6922 aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() ); 6923 else 6924 aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) ); 6925 6926 tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 2; 6927 tools::Long nEmphHeight2 = nEmphHeight / 2; 6928 aOffset += Point( nEmphWidth2, nEmphHeight2 ); 6929 6930 if ( eAlign == ALIGN_BOTTOM ) 6931 aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) ); 6932 else if ( eAlign == ALIGN_TOP ) 6933 aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() ); 6934 6935 nIndex = 0; 6936 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex)) 6937 { 6938 if (pGlyph->IsSpacing()) 6939 { 6940 DevicePoint aAdjOffset(aOffset.X(), aOffset.Y()); 6941 aAdjOffset.adjustX((pGlyph->newWidth() - aEmphasisMark.GetWidth()) / 2); 6942 aAdjOffset = aRotScale.transform( aAdjOffset ); 6943 6944 aAdjOffset -= DevicePoint(nEmphWidth2, nEmphHeight2); 6945 6946 DevicePoint aMarkDevPos(aPos); 6947 aMarkDevPos += aAdjOffset; 6948 Point aMarkPos = SubPixelToLogic(aMarkDevPos); 6949 drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(), 6950 aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(), 6951 aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() ); 6952 } 6953 } 6954 6955 writeBuffer( "Q\n", 2 ); 6956 pop(); 6957 6958 } 6959 6960 void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY, 6961 const tools::PolyPolygon& rPolyPoly, bool bPolyLine, 6962 const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 ) 6963 { 6964 // TODO: pass nWidth as width of this mark 6965 // long nWidth = 0; 6966 6967 if ( rPolyPoly.Count() ) 6968 { 6969 if ( bPolyLine ) 6970 { 6971 tools::Polygon aPoly = rPolyPoly.GetObject( 0 ); 6972 aPoly.Move( nX, nY ); 6973 drawPolyLine( aPoly ); 6974 } 6975 else 6976 { 6977 tools::PolyPolygon aPolyPoly = rPolyPoly; 6978 aPolyPoly.Move( nX, nY ); 6979 drawPolyPolygon( aPolyPoly ); 6980 } 6981 } 6982 6983 if ( !rRect1.IsEmpty() ) 6984 { 6985 tools::Rectangle aRect( Point( nX+rRect1.Left(), 6986 nY+rRect1.Top() ), rRect1.GetSize() ); 6987 drawRectangle( aRect ); 6988 } 6989 6990 if ( !rRect2.IsEmpty() ) 6991 { 6992 tools::Rectangle aRect( Point( nX+rRect2.Left(), 6993 nY+rRect2.Top() ), rRect2.GetSize() ); 6994 6995 drawRectangle( aRect ); 6996 } 6997 } 6998 6999 void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines ) 7000 { 7001 MARK( "drawText" ); 7002 7003 updateGraphicsState(); 7004 7005 // get a layout from the OutputDevice's SalGraphics 7006 // this also enforces font substitution and sets the font on SalGraphics 7007 const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()-> 7008 GetLayoutGlyphs( this, rText, nIndex, nLen ); 7009 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 7010 0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs ); 7011 if( pLayout ) 7012 { 7013 drawLayout( *pLayout, rText, bTextLines ); 7014 } 7015 } 7016 7017 void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, o3tl::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen ) 7018 { 7019 MARK( "drawText with array" ); 7020 7021 updateGraphicsState(); 7022 7023 // get a layout from the OutputDevice's SalGraphics 7024 // this also enforces font substitution and sets the font on SalGraphics 7025 const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()-> 7026 GetLayoutGlyphs( this, rText, nIndex, nLen ); 7027 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray, 7028 SalLayoutFlags::NONE, nullptr, layoutGlyphs ); 7029 if( pLayout ) 7030 { 7031 drawLayout( *pLayout, rText, true ); 7032 } 7033 } 7034 7035 void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen ) 7036 { 7037 MARK( "drawStretchText" ); 7038 7039 updateGraphicsState(); 7040 7041 // get a layout from the OutputDevice's SalGraphics 7042 // this also enforces font substitution and sets the font on SalGraphics 7043 const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()-> 7044 GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth ); 7045 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth, 7046 {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs ); 7047 if( pLayout ) 7048 { 7049 drawLayout( *pLayout, rText, true ); 7050 } 7051 } 7052 7053 void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle ) 7054 { 7055 tools::Long nWidth = rRect.GetWidth(); 7056 tools::Long nHeight = rRect.GetHeight(); 7057 7058 if ( nWidth <= 0 || nHeight <= 0 ) 7059 return; 7060 7061 MARK( "drawText with rectangle" ); 7062 7063 updateGraphicsState(); 7064 7065 // clip with rectangle 7066 OStringBuffer aLine; 7067 aLine.append( "q " ); 7068 m_aPages.back().appendRect( rRect, aLine ); 7069 aLine.append( " W* n\n" ); 7070 writeBuffer( aLine.getStr(), aLine.getLength() ); 7071 7072 // if disabled text is needed, put in here 7073 7074 Point aPos = rRect.TopLeft(); 7075 7076 tools::Long nTextHeight = GetTextHeight(); 7077 sal_Int32 nMnemonicPos = -1; 7078 7079 OUString aStr = rOrigStr; 7080 if ( nStyle & DrawTextFlags::Mnemonic ) 7081 aStr = removeMnemonicFromString( aStr, nMnemonicPos ); 7082 7083 // multiline text 7084 if ( nStyle & DrawTextFlags::MultiLine ) 7085 { 7086 ImplMultiTextLineInfo aMultiLineInfo; 7087 sal_Int32 i; 7088 sal_Int32 nFormatLines; 7089 7090 if ( nTextHeight ) 7091 { 7092 vcl::DefaultTextLayout aLayout( *this ); 7093 OUString aLastLine; 7094 OutputDevice::ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, aLayout ); 7095 sal_Int32 nLines = nHeight/nTextHeight; 7096 nFormatLines = aMultiLineInfo.Count(); 7097 if ( !nLines ) 7098 nLines = 1; 7099 if ( nFormatLines > nLines ) 7100 { 7101 if ( nStyle & DrawTextFlags::EndEllipsis ) 7102 { 7103 // handle last line 7104 nFormatLines = nLines-1; 7105 7106 ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines ); 7107 aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF); 7108 // replace line feed by space 7109 aLastLine = aLastLine.replace('\n', ' '); 7110 aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle ); 7111 nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom); 7112 nStyle |= DrawTextFlags::Top; 7113 } 7114 } 7115 7116 // vertical alignment 7117 if ( nStyle & DrawTextFlags::Bottom ) 7118 aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) ); 7119 else if ( nStyle & DrawTextFlags::VCenter ) 7120 aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 ); 7121 7122 // draw all lines excluding the last 7123 for ( i = 0; i < nFormatLines; i++ ) 7124 { 7125 ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i ); 7126 if ( nStyle & DrawTextFlags::Right ) 7127 aPos.AdjustX(nWidth-rLineInfo.GetWidth() ); 7128 else if ( nStyle & DrawTextFlags::Center ) 7129 aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 ); 7130 sal_Int32 nIndex = rLineInfo.GetIndex(); 7131 sal_Int32 nLineLen = rLineInfo.GetLen(); 7132 drawText( aPos, aStr, nIndex, nLineLen ); 7133 // mnemonics should not appear in documents, 7134 // if the need arises, put them in here 7135 aPos.AdjustY(nTextHeight ); 7136 aPos.setX( rRect.Left() ); 7137 } 7138 7139 // output last line left adjusted since it was shortened 7140 if (!aLastLine.isEmpty()) 7141 drawText( aPos, aLastLine, 0, aLastLine.getLength() ); 7142 } 7143 } 7144 else 7145 { 7146 tools::Long nTextWidth = GetTextWidth( aStr ); 7147 7148 // Evt. Text kuerzen 7149 if ( nTextWidth > nWidth ) 7150 { 7151 if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) ) 7152 { 7153 aStr = GetEllipsisString( aStr, nWidth, nStyle ); 7154 nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right); 7155 nStyle |= DrawTextFlags::Left; 7156 nTextWidth = GetTextWidth( aStr ); 7157 } 7158 } 7159 7160 // vertical alignment 7161 if ( nStyle & DrawTextFlags::Right ) 7162 aPos.AdjustX(nWidth-nTextWidth ); 7163 else if ( nStyle & DrawTextFlags::Center ) 7164 aPos.AdjustX((nWidth-nTextWidth)/2 ); 7165 7166 if ( nStyle & DrawTextFlags::Bottom ) 7167 aPos.AdjustY(nHeight-nTextHeight ); 7168 else if ( nStyle & DrawTextFlags::VCenter ) 7169 aPos.AdjustY((nHeight-nTextHeight)/2 ); 7170 7171 // mnemonics should be inserted here if the need arises 7172 7173 // draw the actual text 7174 drawText( aPos, aStr, 0, aStr.getLength() ); 7175 } 7176 7177 // reset clip region to original value 7178 aLine.setLength( 0 ); 7179 aLine.append( "Q\n" ); 7180 writeBuffer( aLine.getStr(), aLine.getLength() ); 7181 } 7182 7183 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop ) 7184 { 7185 MARK( "drawLine" ); 7186 7187 updateGraphicsState(); 7188 7189 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) 7190 return; 7191 7192 OStringBuffer aLine; 7193 m_aPages.back().appendPoint( rStart, aLine ); 7194 aLine.append( " m " ); 7195 m_aPages.back().appendPoint( rStop, aLine ); 7196 aLine.append( " l S\n" ); 7197 7198 writeBuffer( aLine.getStr(), aLine.getLength() ); 7199 } 7200 7201 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo ) 7202 { 7203 MARK( "drawLine with LineInfo" ); 7204 updateGraphicsState(); 7205 7206 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) 7207 return; 7208 7209 if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 ) 7210 { 7211 drawLine( rStart, rStop ); 7212 return; 7213 } 7214 7215 OStringBuffer aLine; 7216 7217 aLine.append( "q " ); 7218 if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) 7219 { 7220 m_aPages.back().appendPoint( rStart, aLine ); 7221 aLine.append( " m " ); 7222 m_aPages.back().appendPoint( rStop, aLine ); 7223 aLine.append( " l S Q\n" ); 7224 7225 writeBuffer( aLine.getStr(), aLine.getLength() ); 7226 } 7227 else 7228 { 7229 PDFWriter::ExtLineInfo aInfo; 7230 convertLineInfoToExtLineInfo( rInfo, aInfo ); 7231 Point aPolyPoints[2] = { rStart, rStop }; 7232 tools::Polygon aPoly( 2, aPolyPoints ); 7233 drawPolyLine( aPoly, aInfo ); 7234 } 7235 } 7236 7237 #define HCONV( x ) ImplDevicePixelToLogicHeight( x ) 7238 7239 void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove ) 7240 { 7241 // note: units in pFontInstance are ref device pixel 7242 const LogicalFontInstance* pFontInstance = GetFontInstance(); 7243 tools::Long nLineHeight = 0; 7244 tools::Long nLinePos = 0; 7245 7246 appendStrokingColor( aColor, aLine ); 7247 aLine.append( "\n" ); 7248 7249 if ( bIsAbove ) 7250 { 7251 if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() ) 7252 ImplInitAboveTextLineSize(); 7253 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() ); 7254 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() ); 7255 } 7256 else 7257 { 7258 if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() ) 7259 ImplInitTextLineSize(); 7260 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() ); 7261 nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() ); 7262 } 7263 if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) ) 7264 nLineHeight = 3; 7265 7266 tools::Long nLineWidth = GetDPIX()/450; 7267 if ( ! nLineWidth ) 7268 nLineWidth = 1; 7269 7270 if ( eTextLine == LINESTYLE_BOLDWAVE ) 7271 nLineWidth = 3*nLineWidth; 7272 7273 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine ); 7274 aLine.append( " w " ); 7275 7276 if ( eTextLine == LINESTYLE_DOUBLEWAVE ) 7277 { 7278 tools::Long nOrgLineHeight = nLineHeight; 7279 nLineHeight /= 3; 7280 if ( nLineHeight < 2 ) 7281 { 7282 if ( nOrgLineHeight > 1 ) 7283 nLineHeight = 2; 7284 else 7285 nLineHeight = 1; 7286 } 7287 tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2); 7288 if ( nLineDY < nLineWidth ) 7289 nLineDY = nLineWidth; 7290 tools::Long nLineDY2 = nLineDY/2; 7291 if ( !nLineDY2 ) 7292 nLineDY2 = 1; 7293 7294 nLinePos -= nLineWidth-nLineDY2; 7295 7296 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); 7297 7298 nLinePos += nLineWidth+nLineDY; 7299 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); 7300 } 7301 else 7302 { 7303 if ( eTextLine != LINESTYLE_BOLDWAVE ) 7304 nLinePos -= nLineWidth/2; 7305 m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine ); 7306 } 7307 } 7308 7309 void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove ) 7310 { 7311 // note: units in pFontInstance are ref device pixel 7312 const LogicalFontInstance* pFontInstance = GetFontInstance(); 7313 tools::Long nLineHeight = 0; 7314 tools::Long nLinePos = 0; 7315 tools::Long nLinePos2 = 0; 7316 7317 if ( eTextLine > LINESTYLE_BOLDWAVE ) 7318 eTextLine = LINESTYLE_SINGLE; 7319 7320 switch ( eTextLine ) 7321 { 7322 case LINESTYLE_SINGLE: 7323 case LINESTYLE_DOTTED: 7324 case LINESTYLE_DASH: 7325 case LINESTYLE_LONGDASH: 7326 case LINESTYLE_DASHDOT: 7327 case LINESTYLE_DASHDOTDOT: 7328 if ( bIsAbove ) 7329 { 7330 if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() ) 7331 ImplInitAboveTextLineSize(); 7332 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineSize() ); 7333 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineOffset() ); 7334 } 7335 else 7336 { 7337 if ( !pFontInstance->mxFontMetric->GetUnderlineSize() ) 7338 ImplInitTextLineSize(); 7339 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetUnderlineSize() ); 7340 nLinePos = HCONV( pFontInstance->mxFontMetric->GetUnderlineOffset() ); 7341 } 7342 break; 7343 case LINESTYLE_BOLD: 7344 case LINESTYLE_BOLDDOTTED: 7345 case LINESTYLE_BOLDDASH: 7346 case LINESTYLE_BOLDLONGDASH: 7347 case LINESTYLE_BOLDDASHDOT: 7348 case LINESTYLE_BOLDDASHDOTDOT: 7349 if ( bIsAbove ) 7350 { 7351 if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() ) 7352 ImplInitAboveTextLineSize(); 7353 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() ); 7354 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset() ); 7355 } 7356 else 7357 { 7358 if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() ) 7359 ImplInitTextLineSize(); 7360 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineSize() ); 7361 nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineOffset() ); 7362 nLinePos += nLineHeight/2; 7363 } 7364 break; 7365 case LINESTYLE_DOUBLE: 7366 if ( bIsAbove ) 7367 { 7368 if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() ) 7369 ImplInitAboveTextLineSize(); 7370 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() ); 7371 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1() ); 7372 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2() ); 7373 } 7374 else 7375 { 7376 if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() ) 7377 ImplInitTextLineSize(); 7378 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineSize() ); 7379 nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1() ); 7380 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2() ); 7381 } 7382 break; 7383 default: 7384 break; 7385 } 7386 7387 if ( !nLineHeight ) 7388 return; 7389 7390 // outline attribute ? 7391 if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE) 7392 { 7393 appendStrokingColor(aColor, aLine); // stroke with text color 7394 aLine.append( " " ); 7395 appendNonStrokingColor(COL_WHITE, aLine); // fill with white 7396 aLine.append( "\n" ); 7397 aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout 7398 7399 // draw rectangle instead 7400 aLine.append( "0 " ); 7401 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos * 1.5), aLine ); 7402 aLine.append( " " ); 7403 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false ); 7404 aLine.append( ' ' ); 7405 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine ); 7406 aLine.append( " re h B\n" ); 7407 return; 7408 } 7409 7410 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine ); 7411 aLine.append( " w " ); 7412 appendStrokingColor( aColor, aLine ); 7413 aLine.append( "\n" ); 7414 7415 switch ( eTextLine ) 7416 { 7417 case LINESTYLE_DOTTED: 7418 case LINESTYLE_BOLDDOTTED: 7419 aLine.append( "[ " ); 7420 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); 7421 aLine.append( " ] 0 d\n" ); 7422 break; 7423 case LINESTYLE_DASH: 7424 case LINESTYLE_LONGDASH: 7425 case LINESTYLE_BOLDDASH: 7426 case LINESTYLE_BOLDLONGDASH: 7427 { 7428 sal_Int32 nDashLength = 4*nLineHeight; 7429 sal_Int32 nVoidLength = 2*nLineHeight; 7430 if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) ) 7431 nDashLength = 8*nLineHeight; 7432 7433 aLine.append( "[ " ); 7434 m_aPages.back().appendMappedLength( nDashLength, aLine, false ); 7435 aLine.append( ' ' ); 7436 m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); 7437 aLine.append( " ] 0 d\n" ); 7438 } 7439 break; 7440 case LINESTYLE_DASHDOT: 7441 case LINESTYLE_BOLDDASHDOT: 7442 { 7443 sal_Int32 nDashLength = 4*nLineHeight; 7444 sal_Int32 nVoidLength = 2*nLineHeight; 7445 aLine.append( "[ " ); 7446 m_aPages.back().appendMappedLength( nDashLength, aLine, false ); 7447 aLine.append( ' ' ); 7448 m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); 7449 aLine.append( ' ' ); 7450 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); 7451 aLine.append( ' ' ); 7452 m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); 7453 aLine.append( " ] 0 d\n" ); 7454 } 7455 break; 7456 case LINESTYLE_DASHDOTDOT: 7457 case LINESTYLE_BOLDDASHDOTDOT: 7458 { 7459 sal_Int32 nDashLength = 4*nLineHeight; 7460 sal_Int32 nVoidLength = 2*nLineHeight; 7461 aLine.append( "[ " ); 7462 m_aPages.back().appendMappedLength( nDashLength, aLine, false ); 7463 aLine.append( ' ' ); 7464 m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); 7465 aLine.append( ' ' ); 7466 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); 7467 aLine.append( ' ' ); 7468 m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); 7469 aLine.append( ' ' ); 7470 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); 7471 aLine.append( ' ' ); 7472 m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); 7473 aLine.append( " ] 0 d\n" ); 7474 } 7475 break; 7476 default: 7477 break; 7478 } 7479 7480 aLine.append( "0 " ); 7481 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); 7482 aLine.append( " m " ); 7483 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false ); 7484 aLine.append( ' ' ); 7485 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); 7486 aLine.append( " l S\n" ); 7487 if ( eTextLine == LINESTYLE_DOUBLE ) 7488 { 7489 aLine.append( "0 " ); 7490 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); 7491 aLine.append( " m " ); 7492 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false ); 7493 aLine.append( ' ' ); 7494 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); 7495 aLine.append( " l S\n" ); 7496 } 7497 7498 } 7499 7500 void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor ) 7501 { 7502 // note: units in pFontInstance are ref device pixel 7503 const LogicalFontInstance* pFontInstance = GetFontInstance(); 7504 tools::Long nLineHeight = 0; 7505 tools::Long nLinePos = 0; 7506 tools::Long nLinePos2 = 0; 7507 7508 if ( eStrikeout > STRIKEOUT_X ) 7509 eStrikeout = STRIKEOUT_SINGLE; 7510 7511 switch ( eStrikeout ) 7512 { 7513 case STRIKEOUT_SINGLE: 7514 if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() ) 7515 ImplInitTextLineSize(); 7516 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetStrikeoutSize() ); 7517 nLinePos = HCONV( pFontInstance->mxFontMetric->GetStrikeoutOffset() ); 7518 break; 7519 case STRIKEOUT_BOLD: 7520 if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() ) 7521 ImplInitTextLineSize(); 7522 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutSize() ); 7523 nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutOffset() ); 7524 break; 7525 case STRIKEOUT_DOUBLE: 7526 if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() ) 7527 ImplInitTextLineSize(); 7528 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() ); 7529 nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1() ); 7530 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2() ); 7531 break; 7532 default: 7533 break; 7534 } 7535 7536 if ( !nLineHeight ) 7537 return; 7538 7539 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine ); 7540 aLine.append( " w " ); 7541 appendStrokingColor( aColor, aLine ); 7542 aLine.append( "\n" ); 7543 7544 aLine.append( "0 " ); 7545 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); 7546 aLine.append( " m " ); 7547 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine ); 7548 aLine.append( ' ' ); 7549 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); 7550 aLine.append( " l S\n" ); 7551 7552 if ( eStrikeout == STRIKEOUT_DOUBLE ) 7553 { 7554 aLine.append( "0 " ); 7555 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); 7556 aLine.append( " m " ); 7557 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine ); 7558 aLine.append( ' ' ); 7559 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); 7560 aLine.append( " l S\n" ); 7561 } 7562 7563 } 7564 7565 void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout ) 7566 { 7567 //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need 7568 //to tweak this 7569 7570 OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" ); 7571 OUString aStrikeout = aStrikeoutChar; 7572 while( GetTextWidth( aStrikeout ) < nWidth ) 7573 aStrikeout += aStrikeout; 7574 7575 // do not get broader than nWidth modulo 1 character 7576 while( GetTextWidth( aStrikeout ) >= nWidth ) 7577 aStrikeout = aStrikeout.copy(1); 7578 aStrikeout += aStrikeoutChar; 7579 bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow(); 7580 if ( bShadow ) 7581 { 7582 Font aFont = m_aCurrentPDFState.m_aFont; 7583 aFont.SetShadow( false ); 7584 setFont( aFont ); 7585 updateGraphicsState(); 7586 } 7587 7588 // strikeout string is left aligned non-CTL text 7589 vcl::text::ComplexTextLayoutFlags nOrigTLM = GetLayoutMode(); 7590 SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong); 7591 7592 push( PushFlags::CLIPREGION ); 7593 FontMetric aRefDevFontMetric = GetFontMetric(); 7594 tools::Rectangle aRect; 7595 aRect.SetLeft( rPos.X() ); 7596 aRect.SetRight( aRect.Left()+nWidth ); 7597 aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() ); 7598 aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() ); 7599 7600 const LogicalFontInstance* pFontInstance = GetFontInstance(); 7601 if (pFontInstance->mnOrientation) 7602 { 7603 tools::Polygon aPoly( aRect ); 7604 aPoly.Rotate( rPos, pFontInstance->mnOrientation); 7605 aRect = aPoly.GetBoundRect(); 7606 } 7607 7608 intersectClipRegion( aRect ); 7609 drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false ); 7610 pop(); 7611 7612 SetLayoutMode( nOrigTLM ); 7613 7614 if ( bShadow ) 7615 { 7616 Font aFont = m_aCurrentPDFState.m_aFont; 7617 aFont.SetShadow( true ); 7618 setFont( aFont ); 7619 updateGraphicsState(); 7620 } 7621 } 7622 7623 void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove ) 7624 { 7625 if ( !nWidth || 7626 ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) && 7627 ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) && 7628 ((eOverline == LINESTYLE_NONE)||(eOverline == LINESTYLE_DONTKNOW)) ) ) 7629 return; 7630 7631 MARK( "drawTextLine" ); 7632 updateGraphicsState(); 7633 7634 // note: units in pFontInstance are ref device pixel 7635 const LogicalFontInstance* pFontInstance = GetFontInstance(); 7636 Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor; 7637 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; 7638 Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor(); 7639 bool bStrikeoutDone = false; 7640 bool bUnderlineDone = false; 7641 bool bOverlineDone = false; 7642 7643 if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) ) 7644 { 7645 drawStrikeoutChar( rPos, nWidth, eStrikeout ); 7646 bStrikeoutDone = true; 7647 } 7648 7649 Point aPos( rPos ); 7650 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment(); 7651 if( eAlign == ALIGN_TOP ) 7652 aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() )); 7653 else if( eAlign == ALIGN_BOTTOM ) 7654 aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) ); 7655 7656 OStringBuffer aLine( 512 ); 7657 // save GS 7658 aLine.append( "q " ); 7659 7660 // rotate and translate matrix 7661 double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation()); 7662 Matrix3 aMat; 7663 aMat.rotate( fAngle ); 7664 aMat.translate( aPos.X(), aPos.Y() ); 7665 m_aPages.back().appendMatrix3(aMat, aLine); 7666 aLine.append( " cm\n" ); 7667 7668 if ( aUnderlineColor.IsTransparent() ) 7669 aUnderlineColor = aStrikeoutColor; 7670 7671 if ( (eUnderline == LINESTYLE_SMALLWAVE) || 7672 (eUnderline == LINESTYLE_WAVE) || 7673 (eUnderline == LINESTYLE_DOUBLEWAVE) || 7674 (eUnderline == LINESTYLE_BOLDWAVE) ) 7675 { 7676 drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); 7677 bUnderlineDone = true; 7678 } 7679 7680 if ( (eOverline == LINESTYLE_SMALLWAVE) || 7681 (eOverline == LINESTYLE_WAVE) || 7682 (eOverline == LINESTYLE_DOUBLEWAVE) || 7683 (eOverline == LINESTYLE_BOLDWAVE) ) 7684 { 7685 drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); 7686 bOverlineDone = true; 7687 } 7688 7689 if ( !bUnderlineDone ) 7690 { 7691 drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); 7692 } 7693 7694 if ( !bOverlineDone ) 7695 { 7696 drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); 7697 } 7698 7699 if ( !bStrikeoutDone ) 7700 { 7701 drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor ); 7702 } 7703 7704 aLine.append( "Q\n" ); 7705 writeBuffer( aLine.getStr(), aLine.getLength() ); 7706 } 7707 7708 void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly ) 7709 { 7710 MARK( "drawPolygon" ); 7711 7712 updateGraphicsState(); 7713 7714 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && 7715 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) 7716 return; 7717 7718 int nPoints = rPoly.GetSize(); 7719 OStringBuffer aLine( 20 * nPoints ); 7720 m_aPages.back().appendPolygon( rPoly, aLine ); 7721 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && 7722 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) 7723 aLine.append( "B*\n" ); 7724 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) 7725 aLine.append( "S\n" ); 7726 else 7727 aLine.append( "f*\n" ); 7728 7729 writeBuffer( aLine.getStr(), aLine.getLength() ); 7730 } 7731 7732 void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly ) 7733 { 7734 MARK( "drawPolyPolygon" ); 7735 7736 updateGraphicsState(); 7737 7738 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && 7739 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) 7740 return; 7741 7742 int nPolygons = rPolyPoly.Count(); 7743 7744 OStringBuffer aLine( 40 * nPolygons ); 7745 m_aPages.back().appendPolyPolygon( rPolyPoly, aLine ); 7746 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && 7747 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) 7748 aLine.append( "B*\n" ); 7749 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) 7750 aLine.append( "S\n" ); 7751 else 7752 aLine.append( "f*\n" ); 7753 7754 writeBuffer( aLine.getStr(), aLine.getLength() ); 7755 } 7756 7757 void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent ) 7758 { 7759 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" ); 7760 nTransparentPercent = nTransparentPercent % 100; 7761 7762 MARK( "drawTransparent" ); 7763 7764 updateGraphicsState(); 7765 7766 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && 7767 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) 7768 return; 7769 7770 if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) 7771 { 7772 m_aErrors.insert( m_bIsPDF_A1 ? 7773 PDFWriter::Warning_Transparency_Omitted_PDFA : 7774 PDFWriter::Warning_Transparency_Omitted_PDF13 ); 7775 7776 drawPolyPolygon( rPolyPoly ); 7777 return; 7778 } 7779 7780 // create XObject 7781 m_aTransparentObjects.emplace_back( ); 7782 // FIXME: polygons with beziers may yield incorrect bound rect 7783 m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect(); 7784 // convert rectangle to default user space 7785 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); 7786 m_aTransparentObjects.back().m_nObject = createObject(); 7787 m_aTransparentObjects.back().m_nExtGStateObject = createObject(); 7788 m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0; 7789 m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 )); 7790 // create XObject's content stream 7791 OStringBuffer aContent( 256 ); 7792 m_aPages.back().appendPolyPolygon( rPolyPoly, aContent ); 7793 if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT && 7794 m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT ) 7795 aContent.append( " B*\n" ); 7796 else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT ) 7797 aContent.append( " S\n" ); 7798 else 7799 aContent.append( " f*\n" ); 7800 m_aTransparentObjects.back().m_pContentStream->WriteBytes( 7801 aContent.getStr(), aContent.getLength() ); 7802 7803 OStringBuffer aObjName( 16 ); 7804 aObjName.append( "Tr" ); 7805 aObjName.append( m_aTransparentObjects.back().m_nObject ); 7806 OString aTrName( aObjName.makeStringAndClear() ); 7807 aObjName.append( "EGS" ); 7808 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); 7809 OString aExtName( aObjName.makeStringAndClear() ); 7810 7811 OString aLine = 7812 // insert XObject 7813 "q /" + 7814 aExtName + 7815 " gs /" + 7816 aTrName + 7817 " Do Q\n"; 7818 writeBuffer( aLine.getStr(), aLine.getLength() ); 7819 7820 pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject ); 7821 pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); 7822 } 7823 7824 void PDFWriterImpl::pushResource(ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams) 7825 { 7826 if( nObject < 0 ) 7827 return; 7828 7829 switch( eKind ) 7830 { 7831 case ResourceKind::XObject: 7832 rResourceDict.m_aXObjects[rResource] = nObject; 7833 if (!rOutputStreams.empty()) 7834 rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject; 7835 break; 7836 case ResourceKind::ExtGState: 7837 rResourceDict.m_aExtGStates[rResource] = nObject; 7838 if (!rOutputStreams.empty()) 7839 rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject; 7840 break; 7841 case ResourceKind::Shading: 7842 rResourceDict.m_aShadings[rResource] = nObject; 7843 if (!rOutputStreams.empty()) 7844 rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject; 7845 break; 7846 case ResourceKind::Pattern: 7847 rResourceDict.m_aPatterns[rResource] = nObject; 7848 if (!rOutputStreams.empty()) 7849 rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject; 7850 break; 7851 } 7852 } 7853 7854 void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject ) 7855 { 7856 pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams); 7857 } 7858 7859 void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect ) 7860 { 7861 push( PushFlags::ALL ); 7862 7863 // force reemitting clip region inside the new stream, and 7864 // prevent emitting an unbalanced "Q" at the start 7865 clearClipRegion(); 7866 // this is needed to point m_aCurrentPDFState at the pushed state 7867 // ... but it's pointless to actually write into the "outer" stream here! 7868 updateGraphicsState(Mode::NOWRITE); 7869 7870 m_aOutputStreams.push_front( StreamRedirect() ); 7871 m_aOutputStreams.front().m_pStream = pStream; 7872 m_aOutputStreams.front().m_aMapMode = m_aMapMode; 7873 7874 if( !rTargetRect.IsEmpty() ) 7875 { 7876 m_aOutputStreams.front().m_aTargetRect = 7877 lcl_convert( m_aGraphicsStack.front().m_aMapMode, 7878 m_aMapMode, 7879 this, 7880 rTargetRect ); 7881 Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft(); 7882 tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight()); 7883 aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) ); 7884 m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta ); 7885 } 7886 7887 // setup graphics state for independent object stream 7888 7889 // force reemitting colors 7890 m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT; 7891 m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT; 7892 } 7893 7894 SvStream* PDFWriterImpl::endRedirect() 7895 { 7896 SvStream* pStream = nullptr; 7897 if( ! m_aOutputStreams.empty() ) 7898 { 7899 pStream = m_aOutputStreams.front().m_pStream; 7900 m_aMapMode = m_aOutputStreams.front().m_aMapMode; 7901 m_aOutputStreams.pop_front(); 7902 } 7903 7904 pop(); 7905 7906 m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT; 7907 m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT; 7908 7909 // needed after pop() to set m_aCurrentPDFState 7910 updateGraphicsState(Mode::NOWRITE); 7911 7912 return pStream; 7913 } 7914 7915 void PDFWriterImpl::beginTransparencyGroup() 7916 { 7917 updateGraphicsState(); 7918 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 ) 7919 beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() ); 7920 } 7921 7922 void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent ) 7923 { 7924 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" ); 7925 nTransparentPercent = nTransparentPercent % 100; 7926 7927 if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) 7928 return; 7929 7930 // create XObject 7931 m_aTransparentObjects.emplace_back( ); 7932 m_aTransparentObjects.back().m_aBoundRect = rBoundingBox; 7933 // convert rectangle to default user space 7934 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); 7935 m_aTransparentObjects.back().m_nObject = createObject(); 7936 m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0; 7937 // get XObject's content stream 7938 m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) ); 7939 m_aTransparentObjects.back().m_nExtGStateObject = createObject(); 7940 7941 OStringBuffer aObjName( 16 ); 7942 aObjName.append( "Tr" ); 7943 aObjName.append( m_aTransparentObjects.back().m_nObject ); 7944 OString aTrName( aObjName.makeStringAndClear() ); 7945 aObjName.append( "EGS" ); 7946 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); 7947 OString aExtName( aObjName.makeStringAndClear() ); 7948 7949 OString aLine = 7950 // insert XObject 7951 "q /" + 7952 aExtName + 7953 " gs /" + 7954 aTrName + 7955 " Do Q\n"; 7956 writeBuffer( aLine.getStr(), aLine.getLength() ); 7957 7958 pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject ); 7959 pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); 7960 7961 } 7962 7963 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect ) 7964 { 7965 MARK( "drawRectangle" ); 7966 7967 updateGraphicsState(); 7968 7969 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && 7970 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) 7971 return; 7972 7973 OStringBuffer aLine( 40 ); 7974 m_aPages.back().appendRect( rRect, aLine ); 7975 7976 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && 7977 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) 7978 aLine.append( " B*\n" ); 7979 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) 7980 aLine.append( " S\n" ); 7981 else 7982 aLine.append( " f*\n" ); 7983 7984 writeBuffer( aLine.getStr(), aLine.getLength() ); 7985 } 7986 7987 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) 7988 { 7989 MARK( "drawRectangle with rounded edges" ); 7990 7991 if( !nHorzRound && !nVertRound ) 7992 drawRectangle( rRect ); 7993 7994 updateGraphicsState(); 7995 7996 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && 7997 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) 7998 return; 7999 8000 if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 ) 8001 nHorzRound = rRect.GetWidth()/2; 8002 if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 ) 8003 nVertRound = rRect.GetHeight()/2; 8004 8005 Point aPoints[16]; 8006 const double kappa = 0.5522847498; 8007 const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5); 8008 const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5); 8009 8010 aPoints[1] = Point( rRect.Left() + nHorzRound, rRect.Top() ); 8011 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); 8012 aPoints[2] = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() ); 8013 aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() ); 8014 8015 aPoints[5] = Point( rRect.Right()+1, rRect.Top()+nVertRound ); 8016 aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky ); 8017 aPoints[6] = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound ); 8018 aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky ); 8019 8020 aPoints[9] = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 ); 8021 aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() ); 8022 aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() ); 8023 aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() ); 8024 8025 aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound ); 8026 aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky ); 8027 aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound ); 8028 aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky ); 8029 8030 OStringBuffer aLine( 80 ); 8031 m_aPages.back().appendPoint( aPoints[1], aLine ); 8032 aLine.append( " m " ); 8033 m_aPages.back().appendPoint( aPoints[2], aLine ); 8034 aLine.append( " l " ); 8035 m_aPages.back().appendPoint( aPoints[3], aLine ); 8036 aLine.append( ' ' ); 8037 m_aPages.back().appendPoint( aPoints[4], aLine ); 8038 aLine.append( ' ' ); 8039 m_aPages.back().appendPoint( aPoints[5], aLine ); 8040 aLine.append( " c\n" ); 8041 m_aPages.back().appendPoint( aPoints[6], aLine ); 8042 aLine.append( " l " ); 8043 m_aPages.back().appendPoint( aPoints[7], aLine ); 8044 aLine.append( ' ' ); 8045 m_aPages.back().appendPoint( aPoints[8], aLine ); 8046 aLine.append( ' ' ); 8047 m_aPages.back().appendPoint( aPoints[9], aLine ); 8048 aLine.append( " c\n" ); 8049 m_aPages.back().appendPoint( aPoints[10], aLine ); 8050 aLine.append( " l " ); 8051 m_aPages.back().appendPoint( aPoints[11], aLine ); 8052 aLine.append( ' ' ); 8053 m_aPages.back().appendPoint( aPoints[12], aLine ); 8054 aLine.append( ' ' ); 8055 m_aPages.back().appendPoint( aPoints[13], aLine ); 8056 aLine.append( " c\n" ); 8057 m_aPages.back().appendPoint( aPoints[14], aLine ); 8058 aLine.append( " l " ); 8059 m_aPages.back().appendPoint( aPoints[15], aLine ); 8060 aLine.append( ' ' ); 8061 m_aPages.back().appendPoint( aPoints[0], aLine ); 8062 aLine.append( ' ' ); 8063 m_aPages.back().appendPoint( aPoints[1], aLine ); 8064 aLine.append( " c " ); 8065 8066 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && 8067 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) 8068 aLine.append( "b*\n" ); 8069 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) 8070 aLine.append( "s\n" ); 8071 else 8072 aLine.append( "f*\n" ); 8073 8074 writeBuffer( aLine.getStr(), aLine.getLength() ); 8075 } 8076 8077 void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect ) 8078 { 8079 MARK( "drawEllipse" ); 8080 8081 updateGraphicsState(); 8082 8083 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && 8084 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) 8085 return; 8086 8087 Point aPoints[12]; 8088 const double kappa = 0.5522847498; 8089 const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5); 8090 const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5); 8091 8092 aPoints[1] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() ); 8093 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); 8094 aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() ); 8095 8096 aPoints[4] = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 ); 8097 aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky ); 8098 aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky ); 8099 8100 aPoints[7] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 ); 8101 aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() ); 8102 aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() ); 8103 8104 aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 ); 8105 aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky ); 8106 aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky ); 8107 8108 OStringBuffer aLine( 80 ); 8109 m_aPages.back().appendPoint( aPoints[1], aLine ); 8110 aLine.append( " m " ); 8111 m_aPages.back().appendPoint( aPoints[2], aLine ); 8112 aLine.append( ' ' ); 8113 m_aPages.back().appendPoint( aPoints[3], aLine ); 8114 aLine.append( ' ' ); 8115 m_aPages.back().appendPoint( aPoints[4], aLine ); 8116 aLine.append( " c\n" ); 8117 m_aPages.back().appendPoint( aPoints[5], aLine ); 8118 aLine.append( ' ' ); 8119 m_aPages.back().appendPoint( aPoints[6], aLine ); 8120 aLine.append( ' ' ); 8121 m_aPages.back().appendPoint( aPoints[7], aLine ); 8122 aLine.append( " c\n" ); 8123 m_aPages.back().appendPoint( aPoints[8], aLine ); 8124 aLine.append( ' ' ); 8125 m_aPages.back().appendPoint( aPoints[9], aLine ); 8126 aLine.append( ' ' ); 8127 m_aPages.back().appendPoint( aPoints[10], aLine ); 8128 aLine.append( " c\n" ); 8129 m_aPages.back().appendPoint( aPoints[11], aLine ); 8130 aLine.append( ' ' ); 8131 m_aPages.back().appendPoint( aPoints[0], aLine ); 8132 aLine.append( ' ' ); 8133 m_aPages.back().appendPoint( aPoints[1], aLine ); 8134 aLine.append( " c " ); 8135 8136 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && 8137 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) 8138 aLine.append( "b*\n" ); 8139 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) 8140 aLine.append( "s\n" ); 8141 else 8142 aLine.append( "f*\n" ); 8143 8144 writeBuffer( aLine.getStr(), aLine.getLength() ); 8145 } 8146 8147 static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint ) 8148 { 8149 Point aOrigin((rRect.Left()+rRect.Right()+1)/2, 8150 (rRect.Top()+rRect.Bottom()+1)/2); 8151 Point aPoint = rPoint - aOrigin; 8152 8153 double fX = static_cast<double>(aPoint.X()); 8154 double fY = static_cast<double>(-aPoint.Y()); 8155 8156 if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0)) 8157 throw o3tl::divide_by_zero(); 8158 8159 if( rRect.GetWidth() > rRect.GetHeight() ) 8160 fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight())); 8161 else if( rRect.GetHeight() > rRect.GetWidth() ) 8162 fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth())); 8163 return atan2( fY, fX ); 8164 } 8165 8166 void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord ) 8167 { 8168 MARK( "drawArc" ); 8169 8170 updateGraphicsState(); 8171 8172 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && 8173 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) 8174 return; 8175 8176 // calculate start and stop angles 8177 const double fStartAngle = calcAngle( rRect, rStart ); 8178 double fStopAngle = calcAngle( rRect, rStop ); 8179 while( fStopAngle < fStartAngle ) 8180 fStopAngle += 2.0*M_PI; 8181 const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1; 8182 const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments); 8183 const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0); 8184 const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0; 8185 const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0; 8186 8187 const Point aCenter( (rRect.Left()+rRect.Right()+1)/2, 8188 (rRect.Top()+rRect.Bottom()+1)/2 ); 8189 8190 OStringBuffer aLine( 30*nFragments ); 8191 Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ), 8192 -static_cast<int>(halfHeight * sin(fStartAngle) ) ); 8193 aPoint += aCenter; 8194 m_aPages.back().appendPoint( aPoint, aLine ); 8195 aLine.append( " m " ); 8196 if( !basegfx::fTools::equal(fStartAngle, fStopAngle) ) 8197 { 8198 for( int i = 0; i < nFragments; i++ ) 8199 { 8200 const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta; 8201 const double fStopFragment = fStartFragment + fFragmentDelta; 8202 aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ), 8203 -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) ); 8204 aPoint += aCenter; 8205 m_aPages.back().appendPoint( aPoint, aLine ); 8206 aLine.append( ' ' ); 8207 8208 aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ), 8209 -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) ); 8210 aPoint += aCenter; 8211 m_aPages.back().appendPoint( aPoint, aLine ); 8212 aLine.append( ' ' ); 8213 8214 aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ), 8215 -static_cast<int>(halfHeight * sin(fStopFragment) ) ); 8216 aPoint += aCenter; 8217 m_aPages.back().appendPoint( aPoint, aLine ); 8218 aLine.append( " c\n" ); 8219 } 8220 } 8221 if( bWithChord || bWithPie ) 8222 { 8223 if( bWithPie ) 8224 { 8225 m_aPages.back().appendPoint( aCenter, aLine ); 8226 aLine.append( " l " ); 8227 } 8228 aLine.append( "h " ); 8229 } 8230 if( ! bWithChord && ! bWithPie ) 8231 aLine.append( "S\n" ); 8232 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && 8233 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) 8234 aLine.append( "B*\n" ); 8235 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) 8236 aLine.append( "S\n" ); 8237 else 8238 aLine.append( "f*\n" ); 8239 8240 writeBuffer( aLine.getStr(), aLine.getLength() ); 8241 } 8242 8243 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly ) 8244 { 8245 MARK( "drawPolyLine" ); 8246 8247 sal_uInt16 nPoints = rPoly.GetSize(); 8248 if( nPoints < 2 ) 8249 return; 8250 8251 updateGraphicsState(); 8252 8253 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) 8254 return; 8255 8256 OStringBuffer aLine( 20 * nPoints ); 8257 m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] ); 8258 aLine.append( "S\n" ); 8259 8260 writeBuffer( aLine.getStr(), aLine.getLength() ); 8261 } 8262 8263 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo ) 8264 { 8265 MARK( "drawPolyLine with LineInfo" ); 8266 8267 updateGraphicsState(); 8268 8269 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) 8270 return; 8271 8272 OStringBuffer aLine; 8273 aLine.append( "q " ); 8274 if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) 8275 { 8276 writeBuffer( aLine.getStr(), aLine.getLength() ); 8277 drawPolyLine( rPoly ); 8278 writeBuffer( "Q\n", 2 ); 8279 } 8280 else 8281 { 8282 PDFWriter::ExtLineInfo aInfo; 8283 convertLineInfoToExtLineInfo( rInfo, aInfo ); 8284 drawPolyLine( rPoly, aInfo ); 8285 } 8286 } 8287 8288 void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut ) 8289 { 8290 SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" ); 8291 rOut.m_fLineWidth = rIn.GetWidth(); 8292 rOut.m_fTransparency = 0.0; 8293 rOut.m_eCap = PDFWriter::capButt; 8294 rOut.m_eJoin = PDFWriter::joinMiter; 8295 rOut.m_fMiterLimit = 10; 8296 rOut.m_aDashArray = rIn.GetDotDashArray(); 8297 8298 // add LineJoin 8299 switch(rIn.GetLineJoin()) 8300 { 8301 case basegfx::B2DLineJoin::Bevel : 8302 { 8303 rOut.m_eJoin = PDFWriter::joinBevel; 8304 break; 8305 } 8306 // Pdf has no 'none' lineJoin, default is miter 8307 case basegfx::B2DLineJoin::NONE : 8308 case basegfx::B2DLineJoin::Miter : 8309 { 8310 rOut.m_eJoin = PDFWriter::joinMiter; 8311 break; 8312 } 8313 case basegfx::B2DLineJoin::Round : 8314 { 8315 rOut.m_eJoin = PDFWriter::joinRound; 8316 break; 8317 } 8318 } 8319 8320 // add LineCap 8321 switch(rIn.GetLineCap()) 8322 { 8323 default: /* css::drawing::LineCap_BUTT */ 8324 { 8325 rOut.m_eCap = PDFWriter::capButt; 8326 break; 8327 } 8328 case css::drawing::LineCap_ROUND: 8329 { 8330 rOut.m_eCap = PDFWriter::capRound; 8331 break; 8332 } 8333 case css::drawing::LineCap_SQUARE: 8334 { 8335 rOut.m_eCap = PDFWriter::capSquare; 8336 break; 8337 } 8338 } 8339 } 8340 8341 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo ) 8342 { 8343 MARK( "drawPolyLine with ExtLineInfo" ); 8344 8345 updateGraphicsState(); 8346 8347 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) 8348 return; 8349 8350 if( rInfo.m_fTransparency >= 1.0 ) 8351 return; 8352 8353 if( rInfo.m_fTransparency != 0.0 ) 8354 beginTransparencyGroup(); 8355 8356 OStringBuffer aLine; 8357 aLine.append( "q " ); 8358 m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine ); 8359 aLine.append( " w" ); 8360 if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader 8361 { 8362 switch( rInfo.m_eCap ) 8363 { 8364 default: 8365 case PDFWriter::capButt: aLine.append( " 0 J" );break; 8366 case PDFWriter::capRound: aLine.append( " 1 J" );break; 8367 case PDFWriter::capSquare: aLine.append( " 2 J" );break; 8368 } 8369 switch( rInfo.m_eJoin ) 8370 { 8371 default: 8372 case PDFWriter::joinMiter: 8373 { 8374 double fLimit = rInfo.m_fMiterLimit; 8375 if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit ) 8376 fLimit = fLimit / rInfo.m_fLineWidth; 8377 if( fLimit < 1.0 ) 8378 fLimit = 1.0; 8379 aLine.append( " 0 j " ); 8380 appendDouble( fLimit, aLine ); 8381 aLine.append( " M" ); 8382 } 8383 break; 8384 case PDFWriter::joinRound: aLine.append( " 1 j" );break; 8385 case PDFWriter::joinBevel: aLine.append( " 2 j" );break; 8386 } 8387 if( !rInfo.m_aDashArray.empty() ) 8388 { 8389 aLine.append( " [ " ); 8390 for (auto const& dash : rInfo.m_aDashArray) 8391 { 8392 m_aPages.back().appendMappedLength( dash, aLine ); 8393 aLine.append( ' ' ); 8394 } 8395 aLine.append( "] 0 d" ); 8396 } 8397 aLine.append( "\n" ); 8398 writeBuffer( aLine.getStr(), aLine.getLength() ); 8399 drawPolyLine( rPoly ); 8400 } 8401 else 8402 { 8403 basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon()); 8404 basegfx::B2DPolyPolygon aPolyPoly; 8405 8406 basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly); 8407 8408 // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments. 8409 // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality) 8410 // this line needs to be removed and the loop below adapted accordingly 8411 aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly); 8412 8413 const sal_uInt32 nPolygonCount(aPolyPoly.count()); 8414 8415 for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ ) 8416 { 8417 aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " ); 8418 aPoly = aPolyPoly.getB2DPolygon( nPoly ); 8419 const sal_uInt32 nPointCount(aPoly.count()); 8420 8421 if(nPointCount) 8422 { 8423 const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1); 8424 basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0)); 8425 8426 for(sal_uInt32 a(0); a < nEdgeCount; a++) 8427 { 8428 if( a > 0 ) 8429 aLine.append( " " ); 8430 const sal_uInt32 nNextIndex((a + 1) % nPointCount); 8431 const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex)); 8432 8433 m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()), 8434 FRound(aCurrent.getY()) ), 8435 aLine ); 8436 aLine.append( " m " ); 8437 m_aPages.back().appendPoint( Point( FRound(aNext.getX()), 8438 FRound(aNext.getY()) ), 8439 aLine ); 8440 aLine.append( " l" ); 8441 8442 // prepare next edge 8443 aCurrent = aNext; 8444 } 8445 } 8446 } 8447 aLine.append( " S " ); 8448 writeBuffer( aLine.getStr(), aLine.getLength() ); 8449 } 8450 writeBuffer( "Q\n", 2 ); 8451 8452 if( rInfo.m_fTransparency == 0.0 ) 8453 return; 8454 8455 // FIXME: actually this may be incorrect with bezier polygons 8456 tools::Rectangle aBoundRect( rPoly.GetBoundRect() ); 8457 // avoid clipping with thick lines 8458 if( rInfo.m_fLineWidth > 0.0 ) 8459 { 8460 sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth); 8461 aBoundRect.AdjustTop( -nLW ); 8462 aBoundRect.AdjustLeft( -nLW ); 8463 aBoundRect.AdjustRight(nLW ); 8464 aBoundRect.AdjustBottom(nLW ); 8465 } 8466 endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) ); 8467 } 8468 8469 void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor ) 8470 { 8471 MARK( "drawPixel" ); 8472 8473 Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor ); 8474 8475 if( aColor == COL_TRANSPARENT ) 8476 return; 8477 8478 // pixels are drawn in line color, so have to set 8479 // the nonstroking color to line color 8480 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; 8481 setFillColor( aColor ); 8482 8483 updateGraphicsState(); 8484 8485 OStringBuffer aLine( 20 ); 8486 m_aPages.back().appendPoint( rPoint, aLine ); 8487 aLine.append( ' ' ); 8488 appendDouble( 1.0/double(GetDPIX()), aLine ); 8489 aLine.append( ' ' ); 8490 appendDouble( 1.0/double(GetDPIY()), aLine ); 8491 aLine.append( " re f\n" ); 8492 writeBuffer( aLine.getStr(), aLine.getLength() ); 8493 8494 setFillColor( aOldFillColor ); 8495 } 8496 8497 void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject ) 8498 { 8499 CHECK_RETURN2( updateObject( rObject.m_nObject ) ); 8500 8501 bool bFlateFilter = compressStream( rObject.m_pContentStream.get() ); 8502 sal_uInt64 nSize = rObject.m_pContentStream->TellEnd(); 8503 rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN ); 8504 if (g_bDebugDisableCompression) 8505 { 8506 emitComment( "PDFWriterImpl::writeTransparentObject" ); 8507 } 8508 OStringBuffer aLine( 512 ); 8509 CHECK_RETURN2( updateObject( rObject.m_nObject ) ); 8510 aLine.append( rObject.m_nObject ); 8511 aLine.append( " 0 obj\n" 8512 "<</Type/XObject\n" 8513 "/Subtype/Form\n" 8514 "/BBox[ " ); 8515 appendFixedInt( rObject.m_aBoundRect.Left(), aLine ); 8516 aLine.append( ' ' ); 8517 appendFixedInt( rObject.m_aBoundRect.Top(), aLine ); 8518 aLine.append( ' ' ); 8519 appendFixedInt( rObject.m_aBoundRect.Right(), aLine ); 8520 aLine.append( ' ' ); 8521 appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine ); 8522 aLine.append( " ]\n" ); 8523 if( ! m_bIsPDF_A1 ) 8524 { 8525 // 7.8.3 Resource dicts are required for content streams 8526 aLine.append( "/Resources " ); 8527 aLine.append( getResourceDictObj() ); 8528 aLine.append( " 0 R\n" ); 8529 8530 aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" ); 8531 } 8532 8533 aLine.append( "/Length " ); 8534 aLine.append( static_cast<sal_Int32>(nSize) ); 8535 aLine.append( "\n" ); 8536 if( bFlateFilter ) 8537 aLine.append( "/Filter/FlateDecode\n" ); 8538 aLine.append( ">>\n" 8539 "stream\n" ); 8540 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 8541 checkAndEnableStreamEncryption( rObject.m_nObject ); 8542 CHECK_RETURN2( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) ); 8543 disableStreamEncryption(); 8544 aLine.setLength( 0 ); 8545 aLine.append( "\n" 8546 "endstream\n" 8547 "endobj\n\n" ); 8548 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 8549 8550 // write ExtGState dict for this XObject 8551 aLine.setLength( 0 ); 8552 aLine.append( rObject.m_nExtGStateObject ); 8553 aLine.append( " 0 obj\n" 8554 "<<" ); 8555 8556 if( m_bIsPDF_A1 ) 8557 { 8558 aLine.append( "/CA 1.0/ca 1.0" ); 8559 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); 8560 } 8561 else 8562 { 8563 aLine.append( "/CA " ); 8564 appendDouble( rObject.m_fAlpha, aLine ); 8565 aLine.append( "\n" 8566 " /ca " ); 8567 appendDouble( rObject.m_fAlpha, aLine ); 8568 } 8569 aLine.append( "\n" ); 8570 8571 aLine.append( ">>\n" 8572 "endobj\n\n" ); 8573 CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) ); 8574 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 8575 } 8576 8577 bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject ) 8578 { 8579 // LO internal gradient -> PDF shading type: 8580 // * GradientStyle::Linear: axial shading, using sampled-function with 2 samples 8581 // [t=0:colorStart, t=1:colorEnd] 8582 // * GradientStyle::Axial: axial shading, using sampled-function with 3 samples 8583 // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd] 8584 // * other styles: function shading with aSize.Width() * aSize.Height() samples 8585 sal_Int32 nFunctionObject = createObject(); 8586 CHECK_RETURN( updateObject( nFunctionObject ) ); 8587 8588 ScopedVclPtrInstance< VirtualDevice > aDev; 8589 aDev->SetOutputSizePixel( rObject.m_aSize ); 8590 aDev->SetMapMode( MapMode( MapUnit::MapPixel ) ); 8591 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) 8592 aDev->SetDrawMode( aDev->GetDrawMode() | 8593 ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText | 8594 DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) ); 8595 aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient ); 8596 8597 Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize ); 8598 Bitmap::ScopedReadAccess pAccess(aSample); 8599 8600 Size aSize = aSample.GetSizePixel(); 8601 8602 sal_Int32 nStreamLengthObject = createObject(); 8603 if (g_bDebugDisableCompression) 8604 { 8605 emitComment( "PDFWriterImpl::writeGradientFunction" ); 8606 } 8607 OStringBuffer aLine( 120 ); 8608 aLine.append( nFunctionObject ); 8609 aLine.append( " 0 obj\n" 8610 "<</FunctionType 0\n"); 8611 switch (rObject.m_aGradient.GetStyle()) 8612 { 8613 case GradientStyle::Linear: 8614 case GradientStyle::Axial: 8615 aLine.append("/Domain[ 0 1]\n"); 8616 break; 8617 default: 8618 aLine.append("/Domain[ 0 1 0 1]\n"); 8619 } 8620 aLine.append("/Size[ " ); 8621 switch (rObject.m_aGradient.GetStyle()) 8622 { 8623 case GradientStyle::Linear: 8624 aLine.append('2'); 8625 break; 8626 case GradientStyle::Axial: 8627 aLine.append('3'); 8628 break; 8629 default: 8630 aLine.append( static_cast<sal_Int32>(aSize.Width()) ); 8631 aLine.append( ' ' ); 8632 aLine.append( static_cast<sal_Int32>(aSize.Height()) ); 8633 } 8634 aLine.append( " ]\n" 8635 "/BitsPerSample 8\n" 8636 "/Range[ 0 1 0 1 0 1 ]\n" 8637 "/Order 3\n" 8638 "/Length " ); 8639 aLine.append( nStreamLengthObject ); 8640 if (!g_bDebugDisableCompression) 8641 aLine.append( " 0 R\n" 8642 "/Filter/FlateDecode" 8643 ">>\n" 8644 "stream\n" ); 8645 else 8646 aLine.append( " 0 R\n" 8647 ">>\n" 8648 "stream\n" ); 8649 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 8650 8651 sal_uInt64 nStartStreamPos = 0; 8652 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) ); 8653 8654 checkAndEnableStreamEncryption( nFunctionObject ); 8655 beginCompression(); 8656 sal_uInt8 aCol[3]; 8657 switch (rObject.m_aGradient.GetStyle()) 8658 { 8659 case GradientStyle::Axial: 8660 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed(); 8661 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen(); 8662 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue(); 8663 CHECK_RETURN( writeBuffer( aCol, 3 ) ); 8664 [[fallthrough]]; 8665 case GradientStyle::Linear: 8666 { 8667 aCol[0] = rObject.m_aGradient.GetStartColor().GetRed(); 8668 aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen(); 8669 aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue(); 8670 CHECK_RETURN( writeBuffer( aCol, 3 ) ); 8671 8672 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed(); 8673 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen(); 8674 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue(); 8675 CHECK_RETURN( writeBuffer( aCol, 3 ) ); 8676 break; 8677 } 8678 default: 8679 for( int y = aSize.Height()-1; y >= 0; y-- ) 8680 { 8681 for( tools::Long x = 0; x < aSize.Width(); x++ ) 8682 { 8683 BitmapColor aColor = pAccess->GetColor( y, x ); 8684 aCol[0] = aColor.GetRed(); 8685 aCol[1] = aColor.GetGreen(); 8686 aCol[2] = aColor.GetBlue(); 8687 CHECK_RETURN( writeBuffer( aCol, 3 ) ); 8688 } 8689 } 8690 } 8691 endCompression(); 8692 disableStreamEncryption(); 8693 8694 sal_uInt64 nEndStreamPos = 0; 8695 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) ); 8696 8697 aLine.setLength( 0 ); 8698 aLine.append( "\nendstream\nendobj\n\n" ); 8699 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 8700 8701 // write stream length 8702 CHECK_RETURN( updateObject( nStreamLengthObject ) ); 8703 aLine.setLength( 0 ); 8704 aLine.append( nStreamLengthObject ); 8705 aLine.append( " 0 obj\n" ); 8706 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) ); 8707 aLine.append( "\nendobj\n\n" ); 8708 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 8709 8710 CHECK_RETURN( updateObject( rObject.m_nObject ) ); 8711 aLine.setLength( 0 ); 8712 aLine.append( rObject.m_nObject ); 8713 aLine.append( " 0 obj\n"); 8714 switch (rObject.m_aGradient.GetStyle()) 8715 { 8716 case GradientStyle::Linear: 8717 case GradientStyle::Axial: 8718 aLine.append("<</ShadingType 2\n"); 8719 break; 8720 default: 8721 aLine.append("<</ShadingType 1\n"); 8722 } 8723 aLine.append("/ColorSpace/DeviceRGB\n" 8724 "/AntiAlias true\n"); 8725 8726 // Determination of shading axis 8727 // See: OutputDevice::ImplDrawLinearGradient for reference 8728 tools::Rectangle aRect; 8729 aRect.SetLeft(0); 8730 aRect.SetTop(0); 8731 aRect.SetRight( aSize.Width() ); 8732 aRect.SetBottom( aSize.Height() ); 8733 8734 tools::Rectangle aBoundRect; 8735 Point aCenter; 8736 Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10; 8737 rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter ); 8738 8739 const bool bLinear = (rObject.m_aGradient.GetStyle() == GradientStyle::Linear); 8740 double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0; 8741 if ( !bLinear ) 8742 { 8743 fBorder /= 2.0; 8744 } 8745 8746 aBoundRect.AdjustBottom( -fBorder ); 8747 if (!bLinear) 8748 { 8749 aBoundRect.AdjustTop(fBorder ); 8750 } 8751 8752 switch (rObject.m_aGradient.GetStyle()) 8753 { 8754 case GradientStyle::Linear: 8755 case GradientStyle::Axial: 8756 { 8757 aLine.append("/Domain[ 0 1 ]\n" 8758 "/Coords[ " ); 8759 tools::Polygon aPoly( 2 ); 8760 aPoly[0] = aBoundRect.BottomCenter(); 8761 aPoly[1] = aBoundRect.TopCenter(); 8762 aPoly.Rotate( aCenter, 3600_deg10 - nAngle ); 8763 8764 aLine.append( static_cast<sal_Int32>(aPoly[0].X()) ); 8765 aLine.append( " " ); 8766 aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) ); 8767 aLine.append( " " ); 8768 aLine.append( static_cast<sal_Int32>(aPoly[1].X())); 8769 aLine.append( " "); 8770 aLine.append( static_cast<sal_Int32>(aPoly[1].Y())); 8771 aLine.append( " ]\n"); 8772 aLine.append("/Extend [true true]\n"); 8773 break; 8774 } 8775 default: 8776 aLine.append("/Domain[ 0 1 0 1 ]\n" 8777 "/Matrix[ " ); 8778 aLine.append( static_cast<sal_Int32>(aSize.Width()) ); 8779 aLine.append( " 0 0 " ); 8780 aLine.append( static_cast<sal_Int32>(aSize.Height()) ); 8781 aLine.append( " 0 0 ]\n"); 8782 } 8783 aLine.append("/Function " ); 8784 aLine.append( nFunctionObject ); 8785 aLine.append( " 0 R\n" 8786 ">>\n" 8787 "endobj\n\n" ); 8788 return writeBuffer( aLine.getStr(), aLine.getLength() ); 8789 } 8790 8791 void PDFWriterImpl::writeJPG( const JPGEmit& rObject ) 8792 { 8793 if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject) 8794 { 8795 writeReferenceXObject(rObject.m_aReferenceXObject); 8796 return; 8797 } 8798 8799 CHECK_RETURN2( rObject.m_pStream ); 8800 CHECK_RETURN2( updateObject( rObject.m_nObject ) ); 8801 8802 sal_Int32 nLength = rObject.m_pStream->TellEnd(); 8803 rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN ); 8804 8805 sal_Int32 nMaskObject = 0; 8806 if( !rObject.m_aAlphaMask.IsEmpty() ) 8807 { 8808 if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 8809 && !m_bIsPDF_A1) 8810 { 8811 nMaskObject = createObject(); 8812 } 8813 else if( m_bIsPDF_A1 ) 8814 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); 8815 else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) 8816 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 ); 8817 8818 } 8819 if (g_bDebugDisableCompression) 8820 { 8821 emitComment( "PDFWriterImpl::writeJPG" ); 8822 } 8823 8824 OStringBuffer aLine(200); 8825 aLine.append( rObject.m_nObject ); 8826 aLine.append( " 0 obj\n" 8827 "<</Type/XObject/Subtype/Image/Width " ); 8828 aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) ); 8829 aLine.append( " /Height " ); 8830 aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) ); 8831 aLine.append( " /BitsPerComponent 8 " ); 8832 if( rObject.m_bTrueColor ) 8833 aLine.append( "/ColorSpace/DeviceRGB" ); 8834 else 8835 aLine.append( "/ColorSpace/DeviceGray" ); 8836 aLine.append( "/Filter/DCTDecode/Length " ); 8837 aLine.append( nLength ); 8838 if( nMaskObject ) 8839 { 8840 aLine.append(" /SMask "); 8841 aLine.append( nMaskObject ); 8842 aLine.append( " 0 R " ); 8843 } 8844 aLine.append( ">>\nstream\n" ); 8845 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 8846 8847 checkAndEnableStreamEncryption( rObject.m_nObject ); 8848 CHECK_RETURN2( writeBuffer( rObject.m_pStream->GetData(), nLength ) ); 8849 disableStreamEncryption(); 8850 8851 aLine.setLength( 0 ); 8852 aLine.append( "\nendstream\nendobj\n\n" ); 8853 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 8854 8855 if( nMaskObject ) 8856 { 8857 BitmapEmit aEmit; 8858 aEmit.m_nObject = nMaskObject; 8859 aEmit.m_aBitmap = BitmapEx( rObject.m_aAlphaMask, rObject.m_aAlphaMask ); 8860 writeBitmapObject( aEmit, true ); 8861 } 8862 8863 writeReferenceXObject(rObject.m_aReferenceXObject); 8864 } 8865 8866 void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit) 8867 { 8868 if (rEmit.m_nFormObject <= 0) 8869 return; 8870 8871 // Count /Matrix and /BBox. 8872 // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired 8873 // rendering DPI so we have to take into account that here too. 8874 static const double fResolutionDPI = vcl::pdf::getDefaultPdfResolutionDpi(); 8875 8876 sal_Int32 nOldDPIX = GetDPIX(); 8877 sal_Int32 nOldDPIY = GetDPIY(); 8878 SetDPIX(fResolutionDPI); 8879 SetDPIY(fResolutionDPI); 8880 Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit())); 8881 SetDPIX(nOldDPIX); 8882 SetDPIY(nOldDPIY); 8883 double fScaleX = 1.0 / aSize.Width(); 8884 double fScaleY = 1.0 / aSize.Height(); 8885 8886 sal_Int32 nWrappedFormObject = 0; 8887 if (!m_aContext.UseReferenceXObject) 8888 { 8889 // Parse the PDF data, we need that to write the PDF dictionary of our 8890 // object. 8891 if (rEmit.m_nExternalPDFDataIndex < 0) 8892 return; 8893 auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex); 8894 auto& pPDFDocument = rExternalPDFStream.getPDFDocument(); 8895 if (!pPDFDocument) 8896 { 8897 // Couldn't parse the document and can't continue 8898 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document"); 8899 return; 8900 } 8901 8902 std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages(); 8903 if (aPages.empty()) 8904 { 8905 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages"); 8906 return; 8907 } 8908 8909 size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0; 8910 8911 filter::PDFObjectElement* pPage = aPages[nPageIndex]; 8912 if (!pPage) 8913 { 8914 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page"); 8915 return; 8916 } 8917 8918 double aOrigin[2] = { 0.0, 0.0 }; 8919 if (auto* pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox"))) 8920 { 8921 const auto& rElements = pArray->GetElements(); 8922 if (rElements.size() >= 4) 8923 { 8924 // get x1, y1 of the rectangle. 8925 for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx) 8926 { 8927 if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx])) 8928 aOrigin[nIdx] = pNumElement->GetValue(); 8929 } 8930 } 8931 } 8932 8933 std::vector<filter::PDFObjectElement*> aContentStreams; 8934 if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents")) 8935 aContentStreams.push_back(pContentStream); 8936 else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"))) 8937 { 8938 for (const auto pElement : pArray->GetElements()) 8939 { 8940 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); 8941 if (!pReference) 8942 continue; 8943 8944 filter::PDFObjectElement* pObject = pReference->LookupObject(); 8945 if (!pObject) 8946 continue; 8947 8948 aContentStreams.push_back(pObject); 8949 } 8950 } 8951 8952 if (aContentStreams.empty()) 8953 { 8954 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream"); 8955 return; 8956 } 8957 8958 // Merge link annotations from pPage to our page. 8959 std::vector<filter::PDFObjectElement*> aAnnots; 8960 if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Annots"))) 8961 { 8962 for (const auto pElement : pArray->GetElements()) 8963 { 8964 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); 8965 if (!pReference) 8966 { 8967 continue; 8968 } 8969 8970 filter::PDFObjectElement* pObject = pReference->LookupObject(); 8971 if (!pObject) 8972 { 8973 continue; 8974 } 8975 8976 auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type")); 8977 if (!pType || pType->GetValue() != "Annot") 8978 { 8979 continue; 8980 } 8981 8982 auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype")); 8983 if (!pSubtype || pSubtype->GetValue() != "Link") 8984 { 8985 continue; 8986 } 8987 8988 // Reference to a link annotation object, remember it. 8989 aAnnots.push_back(pObject); 8990 } 8991 } 8992 if (!aAnnots.empty()) 8993 { 8994 PDFObjectCopier aCopier(*this); 8995 SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer(); 8996 std::map<sal_Int32, sal_Int32> aMap; 8997 for (const auto& pAnnot : aAnnots) 8998 { 8999 // Copy over the annotation and refer to its new id. 9000 sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pAnnot, aMap); 9001 m_aPages.back().m_aAnnotations.push_back(nNewId); 9002 } 9003 } 9004 9005 nWrappedFormObject = createObject(); 9006 // Write the form XObject wrapped below. This is a separate object from 9007 // the wrapper, this way there is no need to alter the stream contents. 9008 9009 OStringBuffer aLine; 9010 aLine.append(nWrappedFormObject); 9011 aLine.append(" 0 obj\n"); 9012 aLine.append("<< /Type /XObject"); 9013 aLine.append(" /Subtype /Form"); 9014 9015 tools::Long nWidth = aSize.Width(); 9016 tools::Long nHeight = aSize.Height(); 9017 basegfx::B2DRange aBBox(0, 0, aSize.Width(), aSize.Height()); 9018 if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate"))) 9019 { 9020 // The original page was rotated, then construct a transformation matrix which does the 9021 // same with our form object. 9022 sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360; 9023 // /Rotate is clockwise, matrix rotate is counter-clockwise. 9024 sal_Int32 nAngle = -1 * nRotAngle; 9025 9026 // The bounding box just rotates. 9027 basegfx::B2DHomMatrix aBBoxMat; 9028 aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue())); 9029 aBBox.transform(aBBoxMat); 9030 9031 // Now transform the object: rotate around the center and make sure that the rotation 9032 // doesn't affect the aspect ratio. 9033 basegfx::B2DHomMatrix aMat; 9034 aMat.translate(-0.5 * aBBox.getWidth() - aOrigin[0], -0.5 * aBBox.getHeight() - aOrigin[1]); 9035 aMat.rotate(basegfx::deg2rad(nAngle)); 9036 aMat.translate(0.5 * nWidth, 0.5 * nHeight); 9037 9038 aLine.append(" /Matrix [ "); 9039 aLine.append(aMat.a()); 9040 aLine.append(" "); 9041 aLine.append(aMat.b()); 9042 aLine.append(" "); 9043 aLine.append(aMat.c()); 9044 aLine.append(" "); 9045 aLine.append(aMat.d()); 9046 aLine.append(" "); 9047 aLine.append(aMat.e()); 9048 aLine.append(" "); 9049 aLine.append(aMat.f()); 9050 aLine.append(" ] "); 9051 } 9052 9053 PDFObjectCopier aCopier(*this); 9054 auto & rResources = rExternalPDFStream.getCopiedResources(); 9055 aCopier.copyPageResources(pPage, aLine, rResources); 9056 9057 aLine.append(" /BBox [ "); 9058 aLine.append(aOrigin[0]); 9059 aLine.append(' '); 9060 aLine.append(aOrigin[1]); 9061 aLine.append(' '); 9062 aLine.append(aBBox.getWidth() + aOrigin[0]); 9063 aLine.append(' '); 9064 aLine.append(aBBox.getHeight() + aOrigin[1]); 9065 aLine.append(" ]"); 9066 9067 if (!g_bDebugDisableCompression) 9068 aLine.append(" /Filter/FlateDecode"); 9069 aLine.append(" /Length "); 9070 9071 SvMemoryStream aStream; 9072 bool bCompressed = false; 9073 sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed); 9074 aLine.append(nLength); 9075 9076 aLine.append(">>\nstream\n"); 9077 if (g_bDebugDisableCompression) 9078 { 9079 emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject"); 9080 } 9081 if (!updateObject(nWrappedFormObject)) 9082 return; 9083 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 9084 return; 9085 aLine.setLength(0); 9086 9087 checkAndEnableStreamEncryption(nWrappedFormObject); 9088 // Copy the original page streams to the form XObject stream. 9089 aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize()); 9090 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 9091 return; 9092 aLine.setLength(0); 9093 disableStreamEncryption(); 9094 9095 aLine.append("\nendstream\nendobj\n\n"); 9096 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 9097 return; 9098 } 9099 9100 OStringBuffer aLine; 9101 if (g_bDebugDisableCompression) 9102 { 9103 emitComment("PDFWriterImpl::writeReferenceXObject, FormObject"); 9104 } 9105 if (!updateObject(rEmit.m_nFormObject)) 9106 return; 9107 9108 // Now have all the info to write the form XObject. 9109 aLine.append(rEmit.m_nFormObject); 9110 aLine.append(" 0 obj\n"); 9111 aLine.append("<< /Type /XObject"); 9112 aLine.append(" /Subtype /Form"); 9113 aLine.append(" /Resources << /XObject<<"); 9114 9115 sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject; 9116 aLine.append(" /Im"); 9117 aLine.append(nObject); 9118 aLine.append(" "); 9119 aLine.append(nObject); 9120 aLine.append(" 0 R"); 9121 9122 aLine.append(">> >>"); 9123 aLine.append(" /Matrix [ "); 9124 appendDouble(fScaleX, aLine); 9125 aLine.append(" 0 0 "); 9126 appendDouble(fScaleY, aLine); 9127 aLine.append(" 0 0 ]"); 9128 aLine.append(" /BBox [ 0 0 "); 9129 aLine.append(aSize.Width()); 9130 aLine.append(" "); 9131 aLine.append(aSize.Height()); 9132 aLine.append(" ]\n"); 9133 9134 if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0) 9135 { 9136 // Write the reference dictionary. 9137 aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F "); 9138 aLine.append(rEmit.m_nEmbeddedObject); 9139 aLine.append(" 0 R >> >> /Page 0 >>\n"); 9140 } 9141 9142 aLine.append("/Length "); 9143 9144 OStringBuffer aStream; 9145 aStream.append("q "); 9146 if (m_aContext.UseReferenceXObject) 9147 { 9148 // Reference XObject markup is used, just refer to the fallback bitmap 9149 // here. 9150 aStream.append(aSize.Width()); 9151 aStream.append(" 0 0 "); 9152 aStream.append(aSize.Height()); 9153 aStream.append(" 0 0 cm\n"); 9154 aStream.append("/Im"); 9155 aStream.append(rEmit.m_nBitmapObject); 9156 aStream.append(" Do\n"); 9157 } 9158 else 9159 { 9160 // Reset line width to the default. 9161 aStream.append(" 1 w\n"); 9162 9163 // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be 9164 // consistent with that. 9165 aStream.append("1 1 1 rg\n"); 9166 aStream.append("0 0 "); 9167 aStream.append(aSize.Width()); 9168 aStream.append(" "); 9169 aStream.append(aSize.Height()); 9170 aStream.append(" re\n"); 9171 aStream.append("f*\n"); 9172 9173 // No reference XObject, draw the form XObject containing the original 9174 // page streams. 9175 aStream.append("/Im"); 9176 aStream.append(nWrappedFormObject); 9177 aStream.append(" Do\n"); 9178 } 9179 aStream.append("Q"); 9180 aLine.append(aStream.getLength()); 9181 9182 aLine.append(">>\nstream\n"); 9183 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 9184 return; 9185 aLine.setLength(0); 9186 9187 checkAndEnableStreamEncryption(rEmit.m_nFormObject); 9188 aLine.append(aStream.getStr()); 9189 if (!writeBuffer(aLine.getStr(), aLine.getLength())) 9190 return; 9191 aLine.setLength(0); 9192 disableStreamEncryption(); 9193 9194 aLine.append("\nendstream\nendobj\n\n"); 9195 CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength())); 9196 } 9197 9198 namespace 9199 { 9200 unsigned char reverseByte(unsigned char b) 9201 { 9202 b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; 9203 b = (b & 0xCC) >> 2 | (b & 0x33) << 2; 9204 b = (b & 0xAA) >> 1 | (b & 0x55) << 1; 9205 return b; 9206 } 9207 9208 //tdf#103051 convert any N1BitLsbPal to N1BitMsbPal 9209 Bitmap getExportBitmap(const Bitmap &rBitmap) 9210 { 9211 Bitmap::ScopedReadAccess pAccess(const_cast<Bitmap&>(rBitmap)); 9212 const ScanlineFormat eFormat = pAccess->GetScanlineFormat(); 9213 if (eFormat != ScanlineFormat::N1BitLsbPal) 9214 return rBitmap; 9215 Bitmap aNewBmp(rBitmap); 9216 BitmapScopedWriteAccess xWriteAcc(aNewBmp); 9217 const int nScanLineBytes = (pAccess->Width() + 7U) / 8U; 9218 for (tools::Long nY = 0L; nY < xWriteAcc->Height(); ++nY) 9219 { 9220 Scanline pBitSwap = xWriteAcc->GetScanline(nY); 9221 for (int x = 0; x < nScanLineBytes; ++x) 9222 pBitSwap[x] = reverseByte(pBitSwap[x]); 9223 } 9224 return aNewBmp; 9225 } 9226 } 9227 9228 bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject, bool bMask ) 9229 { 9230 if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject) 9231 { 9232 writeReferenceXObject(rObject.m_aReferenceXObject); 9233 return true; 9234 } 9235 9236 CHECK_RETURN( updateObject( rObject.m_nObject ) ); 9237 9238 Bitmap aBitmap; 9239 bool bWriteMask = false; 9240 if( ! bMask ) 9241 { 9242 aBitmap = getExportBitmap(rObject.m_aBitmap.GetBitmap()); 9243 if( rObject.m_aBitmap.IsAlpha() ) 9244 { 9245 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 ) 9246 bWriteMask = true; 9247 // else draw without alpha channel 9248 } 9249 } 9250 else 9251 { 9252 if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() ) 9253 { 9254 if( rObject.m_aBitmap.IsAlpha() ) 9255 { 9256 aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlphaMask()); 9257 aBitmap.Convert( BmpConversion::N1BitThreshold ); 9258 SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "mask conversion failed" ); 9259 } 9260 } 9261 else if (aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP) 9262 { 9263 aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlphaMask().GetBitmap()); 9264 aBitmap.Convert( BmpConversion::N8BitGreys ); 9265 SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "alpha mask conversion failed" ); 9266 } 9267 } 9268 9269 Bitmap::ScopedReadAccess pAccess(aBitmap); 9270 9271 bool bTrueColor = true; 9272 sal_Int32 nBitsPerComponent = 0; 9273 auto const ePixelFormat = aBitmap.getPixelFormat(); 9274 switch (ePixelFormat) 9275 { 9276 case vcl::PixelFormat::N8_BPP: 9277 bTrueColor = false; 9278 nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat); 9279 break; 9280 case vcl::PixelFormat::N24_BPP: 9281 case vcl::PixelFormat::N32_BPP: 9282 bTrueColor = true; 9283 nBitsPerComponent = 8; 9284 break; 9285 case vcl::PixelFormat::INVALID: 9286 return false; 9287 } 9288 9289 sal_Int32 nStreamLengthObject = createObject(); 9290 sal_Int32 nMaskObject = 0; 9291 9292 if (g_bDebugDisableCompression) 9293 { 9294 emitComment( "PDFWriterImpl::writeBitmapObject" ); 9295 } 9296 OStringBuffer aLine(1024); 9297 aLine.append( rObject.m_nObject ); 9298 aLine.append( " 0 obj\n" 9299 "<</Type/XObject/Subtype/Image/Width " ); 9300 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) ); 9301 aLine.append( "/Height " ); 9302 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) ); 9303 aLine.append( "/BitsPerComponent " ); 9304 aLine.append( nBitsPerComponent ); 9305 aLine.append( "/Length " ); 9306 aLine.append( nStreamLengthObject ); 9307 aLine.append( " 0 R\n" ); 9308 if (!g_bDebugDisableCompression) 9309 { 9310 if( nBitsPerComponent != 1 ) 9311 { 9312 aLine.append( "/Filter/FlateDecode" ); 9313 } 9314 else 9315 { 9316 aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " ); 9317 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) ); 9318 aLine.append( ">>\n" ); 9319 } 9320 } 9321 if( ! bMask ) 9322 { 9323 aLine.append( "/ColorSpace" ); 9324 if( bTrueColor ) 9325 aLine.append( "/DeviceRGB\n" ); 9326 else if( aBitmap.HasGreyPaletteAny() ) 9327 { 9328 aLine.append( "/DeviceGray\n" ); 9329 if (aBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP) 9330 { 9331 // #i47395# 1 bit bitmaps occasionally have an inverted grey palette 9332 sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) ); 9333 assert( nBlackIndex == 0 || nBlackIndex == 1); 9334 sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) ); 9335 if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) && 9336 pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) ) 9337 { 9338 // It is black and white 9339 if( nBlackIndex == 1 ) 9340 aLine.append( "/Decode[1 0]\n" ); 9341 } 9342 else 9343 { 9344 // It is two levels of grey 9345 aLine.append( "/Decode[" ); 9346 assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() && 9347 pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() && 9348 pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() && 9349 pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() ); 9350 aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 ); 9351 aLine.append( " " ); 9352 aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 ); 9353 aLine.append( "]\n" ); 9354 } 9355 } 9356 } 9357 else 9358 { 9359 aLine.append( "[ /Indexed/DeviceRGB " ); 9360 aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) ); 9361 aLine.append( "\n<" ); 9362 if( m_aContext.Encryption.Encrypt() ) 9363 { 9364 enableStringEncryption( rObject.m_nObject ); 9365 //check encryption buffer size 9366 m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3); 9367 int nChar = 0; 9368 //fill the encryption buffer 9369 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) 9370 { 9371 const BitmapColor& rColor = pAccess->GetPaletteColor( i ); 9372 m_vEncryptionBuffer[nChar++] = rColor.GetRed(); 9373 m_vEncryptionBuffer[nChar++] = rColor.GetGreen(); 9374 m_vEncryptionBuffer[nChar++] = rColor.GetBlue(); 9375 } 9376 //encrypt the colorspace lookup table 9377 rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChar, m_vEncryptionBuffer.data(), nChar ); 9378 //now queue the data for output 9379 nChar = 0; 9380 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) 9381 { 9382 appendHex(m_vEncryptionBuffer[nChar++], aLine ); 9383 appendHex(m_vEncryptionBuffer[nChar++], aLine ); 9384 appendHex(m_vEncryptionBuffer[nChar++], aLine ); 9385 } 9386 } 9387 else //no encryption requested (PDF/A-1a program flow drops here) 9388 { 9389 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) 9390 { 9391 const BitmapColor& rColor = pAccess->GetPaletteColor( i ); 9392 appendHex( rColor.GetRed(), aLine ); 9393 appendHex( rColor.GetGreen(), aLine ); 9394 appendHex( rColor.GetBlue(), aLine ); 9395 } 9396 } 9397 aLine.append( ">\n]\n" ); 9398 } 9399 } 9400 else 9401 { 9402 aLine.append( "/ColorSpace/DeviceGray\n" 9403 "/Decode [ 1 0 ]\n" ); 9404 } 9405 9406 if( ! bMask && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !m_bIsPDF_A1 ) 9407 { 9408 if( bWriteMask ) 9409 { 9410 nMaskObject = createObject(); 9411 if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 ) 9412 aLine.append( "/SMask " ); 9413 else 9414 aLine.append( "/Mask " ); 9415 aLine.append( nMaskObject ); 9416 aLine.append( " 0 R\n" ); 9417 } 9418 } 9419 else if( m_bIsPDF_A1 && bWriteMask ) 9420 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); 9421 9422 aLine.append( ">>\n" 9423 "stream\n" ); 9424 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 9425 sal_uInt64 nStartPos = 0; 9426 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) ); 9427 9428 checkAndEnableStreamEncryption( rObject.m_nObject ); 9429 if (!g_bDebugDisableCompression && nBitsPerComponent == 1) 9430 { 9431 writeG4Stream(pAccess.get()); 9432 } 9433 else 9434 { 9435 beginCompression(); 9436 if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb ) 9437 { 9438 //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits). 9439 const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U; 9440 9441 for( tools::Long i = 0; i < pAccess->Height(); i++ ) 9442 { 9443 CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) ); 9444 } 9445 } 9446 else 9447 { 9448 const int nScanLineBytes = pAccess->Width()*3; 9449 std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]); 9450 for( tools::Long y = 0; y < pAccess->Height(); y++ ) 9451 { 9452 for( tools::Long x = 0; x < pAccess->Width(); x++ ) 9453 { 9454 BitmapColor aColor = pAccess->GetColor( y, x ); 9455 xCol[3*x+0] = aColor.GetRed(); 9456 xCol[3*x+1] = aColor.GetGreen(); 9457 xCol[3*x+2] = aColor.GetBlue(); 9458 } 9459 CHECK_RETURN(writeBuffer(xCol.get(), nScanLineBytes)); 9460 } 9461 } 9462 endCompression(); 9463 } 9464 disableStreamEncryption(); 9465 9466 sal_uInt64 nEndPos = 0; 9467 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) ); 9468 aLine.setLength( 0 ); 9469 aLine.append( "\nendstream\nendobj\n\n" ); 9470 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 9471 CHECK_RETURN( updateObject( nStreamLengthObject ) ); 9472 aLine.setLength( 0 ); 9473 aLine.append( nStreamLengthObject ); 9474 aLine.append( " 0 obj\n" ); 9475 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) ); 9476 aLine.append( "\nendobj\n\n" ); 9477 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); 9478 9479 if( nMaskObject ) 9480 { 9481 BitmapEmit aEmit; 9482 aEmit.m_nObject = nMaskObject; 9483 aEmit.m_aBitmap = rObject.m_aBitmap; 9484 return writeBitmapObject( aEmit, true ); 9485 } 9486 9487 writeReferenceXObject(rObject.m_aReferenceXObject); 9488 9489 return true; 9490 } 9491 9492 void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject) 9493 { 9494 // The bitmap object is always a valid identifier, even if the graphic has 9495 // no pdf data. 9496 rEmit.m_nBitmapObject = nBitmapObject; 9497 9498 if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf) 9499 return; 9500 9501 BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer(); 9502 9503 if (m_aContext.UseReferenceXObject) 9504 { 9505 // Store the original PDF data as an embedded file. 9506 auto nObjectID = addEmbeddedFile(rDataContainer); 9507 rEmit.m_nEmbeddedObject = nObjectID; 9508 } 9509 else 9510 { 9511 sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer); 9512 rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex(); 9513 rEmit.m_nExternalPDFDataIndex = aIndex; 9514 } 9515 9516 rEmit.m_nFormObject = createObject(); 9517 rEmit.m_aPixelSize = rGraphic.GetPrefSize(); 9518 } 9519 9520 void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic ) 9521 { 9522 MARK( "drawJPGBitmap" ); 9523 9524 OStringBuffer aLine( 80 ); 9525 updateGraphicsState(); 9526 9527 // #i40055# sanity check 9528 if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) ) 9529 return; 9530 if( ! (rSizePixel.Width() && rSizePixel.Height()) ) 9531 return; 9532 9533 rDCTData.Seek( 0 ); 9534 if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale ) 9535 { 9536 // need to convert to grayscale; 9537 // load stream to bitmap and draw the bitmap instead 9538 Graphic aGraphic; 9539 GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG ); 9540 if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() ) 9541 { 9542 Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() ); 9543 BitmapEx aBmpEx( aBmp, rAlphaMask ); 9544 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx ); 9545 } 9546 else 9547 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() ); 9548 return; 9549 } 9550 9551 std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream); 9552 pStream->WriteStream( rDCTData ); 9553 pStream->Seek( STREAM_SEEK_TO_END ); 9554 9555 BitmapID aID; 9556 aID.m_aPixelSize = rSizePixel; 9557 aID.m_nSize = pStream->Tell(); 9558 pStream->Seek( STREAM_SEEK_TO_BEGIN ); 9559 aID.m_nChecksum = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize ); 9560 if( ! rAlphaMask.IsEmpty() ) 9561 aID.m_nMaskChecksum = rAlphaMask.GetChecksum(); 9562 9563 std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(), 9564 [&](const JPGEmit& arg) { return aID == arg.m_aID; }); 9565 if( it == m_aJPGs.end() ) 9566 { 9567 m_aJPGs.emplace( m_aJPGs.begin() ); 9568 JPGEmit& rEmit = m_aJPGs.front(); 9569 if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject) 9570 rEmit.m_nObject = createObject(); 9571 rEmit.m_aID = aID; 9572 rEmit.m_pStream = std::move( pStream ); 9573 rEmit.m_bTrueColor = bIsTrueColor; 9574 if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel ) 9575 rEmit.m_aAlphaMask = rAlphaMask; 9576 createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject); 9577 9578 it = m_aJPGs.begin(); 9579 } 9580 9581 aLine.append( "q " ); 9582 sal_Int32 nCheckWidth = 0; 9583 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth ); 9584 aLine.append( " 0 0 " ); 9585 sal_Int32 nCheckHeight = 0; 9586 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight ); 9587 aLine.append( ' ' ); 9588 m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine ); 9589 aLine.append( " cm\n/Im" ); 9590 sal_Int32 nObject = it->m_aReferenceXObject.getObject(); 9591 aLine.append(nObject); 9592 aLine.append( " Do Q\n" ); 9593 if( nCheckWidth == 0 || nCheckHeight == 0 ) 9594 { 9595 // #i97512# avoid invalid current matrix 9596 aLine.setLength( 0 ); 9597 aLine.append( "\n%jpeg image /Im" ); 9598 aLine.append( it->m_nObject ); 9599 aLine.append( " scaled to zero size, omitted\n" ); 9600 } 9601 writeBuffer( aLine.getStr(), aLine.getLength() ); 9602 9603 OString aObjName = "Im" + OString::number(nObject); 9604 pushResource( ResourceKind::XObject, aObjName, nObject ); 9605 9606 } 9607 9608 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor ) 9609 { 9610 OStringBuffer aLine( 80 ); 9611 updateGraphicsState(); 9612 9613 aLine.append( "q " ); 9614 if( rFillColor != COL_TRANSPARENT ) 9615 { 9616 appendNonStrokingColor( rFillColor, aLine ); 9617 aLine.append( ' ' ); 9618 } 9619 sal_Int32 nCheckWidth = 0; 9620 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth ); 9621 aLine.append( " 0 0 " ); 9622 sal_Int32 nCheckHeight = 0; 9623 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight ); 9624 aLine.append( ' ' ); 9625 m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine ); 9626 aLine.append( " cm\n/Im" ); 9627 sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject(); 9628 aLine.append(nObject); 9629 aLine.append( " Do Q\n" ); 9630 if( nCheckWidth == 0 || nCheckHeight == 0 ) 9631 { 9632 // #i97512# avoid invalid current matrix 9633 aLine.setLength( 0 ); 9634 aLine.append( "\n%bitmap image /Im" ); 9635 aLine.append( rBitmap.m_nObject ); 9636 aLine.append( " scaled to zero size, omitted\n" ); 9637 } 9638 writeBuffer( aLine.getStr(), aLine.getLength() ); 9639 } 9640 9641 const BitmapEmit& PDFWriterImpl::createBitmapEmit(const BitmapEx& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams) 9642 { 9643 BitmapEx aBitmap( i_rBitmap ); 9644 auto ePixelFormat = aBitmap.GetBitmap().getPixelFormat(); 9645 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) 9646 aBitmap.Convert(BmpConversion::N8BitGreys); 9647 BitmapID aID; 9648 aID.m_aPixelSize = aBitmap.GetSizePixel(); 9649 aID.m_nSize = vcl::pixelFormatBitCount(ePixelFormat); 9650 aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum(); 9651 aID.m_nMaskChecksum = 0; 9652 if( aBitmap.IsAlpha() ) 9653 aID.m_nMaskChecksum = aBitmap.GetAlphaMask().GetChecksum(); 9654 std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(), 9655 [&](const BitmapEmit& arg) { return aID == arg.m_aID; }); 9656 if (it == rBitmaps.end()) 9657 { 9658 rBitmaps.push_front(BitmapEmit()); 9659 rBitmaps.front().m_aID = aID; 9660 rBitmaps.front().m_aBitmap = aBitmap; 9661 if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject) 9662 rBitmaps.front().m_nObject = createObject(); 9663 createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject); 9664 it = rBitmaps.begin(); 9665 } 9666 9667 sal_Int32 nObject = it->m_aReferenceXObject.getObject(); 9668 OString aObjName = "Im" + OString::number(nObject); 9669 pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams); 9670 9671 return *it; 9672 } 9673 9674 const BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic ) 9675 { 9676 return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams); 9677 } 9678 9679 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic ) 9680 { 9681 MARK( "drawBitmap (Bitmap)" ); 9682 9683 // #i40055# sanity check 9684 if( ! (rDestSize.Width() && rDestSize.Height()) ) 9685 return; 9686 9687 const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic ); 9688 drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT ); 9689 } 9690 9691 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap ) 9692 { 9693 MARK( "drawBitmap (BitmapEx)" ); 9694 9695 // #i40055# sanity check 9696 if( ! (rDestSize.Width() && rDestSize.Height()) ) 9697 return; 9698 9699 const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() ); 9700 drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT ); 9701 } 9702 9703 sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize ) 9704 { 9705 Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode, 9706 MapMode( MapUnit::MapPoint ), 9707 this, 9708 rSize ) ); 9709 // check if we already have this gradient 9710 // rounding to point will generally lose some pixels 9711 // round up to point boundary 9712 aPtSize.AdjustWidth( 1 ); 9713 aPtSize.AdjustHeight( 1 ); 9714 std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(), 9715 [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); }); 9716 9717 if( it == m_aGradients.end() ) 9718 { 9719 m_aGradients.push_front( GradientEmit() ); 9720 m_aGradients.front().m_aGradient = rGradient; 9721 m_aGradients.front().m_nObject = createObject(); 9722 m_aGradients.front().m_aSize = aPtSize; 9723 it = m_aGradients.begin(); 9724 } 9725 9726 OStringBuffer aObjName( 16 ); 9727 aObjName.append( 'P' ); 9728 aObjName.append( it->m_nObject ); 9729 pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject ); 9730 9731 return it->m_nObject; 9732 } 9733 9734 void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient ) 9735 { 9736 MARK( "drawGradient (Rectangle)" ); 9737 9738 if( m_aContext.Version == PDFWriter::PDFVersion::PDF_1_2 ) 9739 { 9740 drawRectangle( rRect ); 9741 return; 9742 } 9743 9744 sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() ); 9745 9746 Point aTranslate( rRect.BottomLeft() ); 9747 aTranslate += Point( 0, 1 ); 9748 9749 updateGraphicsState(); 9750 9751 OStringBuffer aLine( 80 ); 9752 aLine.append( "q 1 0 0 1 " ); 9753 m_aPages.back().appendPoint( aTranslate, aLine ); 9754 aLine.append( " cm " ); 9755 // if a stroke is appended reset the clip region before stroke 9756 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) 9757 aLine.append( "q " ); 9758 aLine.append( "0 0 " ); 9759 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false ); 9760 aLine.append( ' ' ); 9761 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine ); 9762 aLine.append( " re W n\n" ); 9763 9764 aLine.append( "/P" ); 9765 aLine.append( nGradient ); 9766 aLine.append( " sh " ); 9767 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) 9768 { 9769 aLine.append( "Q 0 0 " ); 9770 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false ); 9771 aLine.append( ' ' ); 9772 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine ); 9773 aLine.append( " re S " ); 9774 } 9775 aLine.append( "Q\n" ); 9776 writeBuffer( aLine.getStr(), aLine.getLength() ); 9777 } 9778 9779 void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch ) 9780 { 9781 MARK( "drawHatch" ); 9782 9783 updateGraphicsState(); 9784 9785 if( rPolyPoly.Count() ) 9786 { 9787 tools::PolyPolygon aPolyPoly( rPolyPoly ); 9788 9789 aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME ); 9790 push( PushFlags::LINECOLOR ); 9791 setLineColor( rHatch.GetColor() ); 9792 DrawHatch( aPolyPoly, rHatch, false ); 9793 pop(); 9794 } 9795 } 9796 9797 void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall ) 9798 { 9799 MARK( "drawWallpaper" ); 9800 9801 bool bDrawColor = false; 9802 bool bDrawGradient = false; 9803 bool bDrawBitmap = false; 9804 9805 BitmapEx aBitmap; 9806 Point aBmpPos = rRect.TopLeft(); 9807 Size aBmpSize; 9808 if( rWall.IsBitmap() ) 9809 { 9810 aBitmap = rWall.GetBitmap(); 9811 aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(), 9812 getMapMode(), 9813 this, 9814 aBitmap.GetPrefSize() ); 9815 tools::Rectangle aRect( rRect ); 9816 if( rWall.IsRect() ) 9817 { 9818 aRect = rWall.GetRect(); 9819 aBmpPos = aRect.TopLeft(); 9820 aBmpSize = aRect.GetSize(); 9821 } 9822 if( rWall.GetStyle() != WallpaperStyle::Scale ) 9823 { 9824 if( rWall.GetStyle() != WallpaperStyle::Tile ) 9825 { 9826 bDrawBitmap = true; 9827 if( rWall.IsGradient() ) 9828 bDrawGradient = true; 9829 else 9830 bDrawColor = true; 9831 switch( rWall.GetStyle() ) 9832 { 9833 case WallpaperStyle::TopLeft: 9834 break; 9835 case WallpaperStyle::Top: 9836 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 ); 9837 break; 9838 case WallpaperStyle::Left: 9839 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 ); 9840 break; 9841 case WallpaperStyle::TopRight: 9842 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() ); 9843 break; 9844 case WallpaperStyle::Center: 9845 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 ); 9846 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 ); 9847 break; 9848 case WallpaperStyle::Right: 9849 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() ); 9850 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 ); 9851 break; 9852 case WallpaperStyle::BottomLeft: 9853 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() ); 9854 break; 9855 case WallpaperStyle::Bottom: 9856 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 ); 9857 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() ); 9858 break; 9859 case WallpaperStyle::BottomRight: 9860 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() ); 9861 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() ); 9862 break; 9863 default: ; 9864 } 9865 } 9866 else 9867 { 9868 // push the bitmap 9869 const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() ); 9870 9871 // convert to page coordinates; this needs to be done here 9872 // since the emit does not know the page anymore 9873 tools::Rectangle aConvertRect( aBmpPos, aBmpSize ); 9874 m_aPages.back().convertRect( aConvertRect ); 9875 9876 OString aImageName = "Im" + OString::number( rEmit.m_nObject ); 9877 9878 // push the pattern 9879 OStringBuffer aTilingStream( 32 ); 9880 appendFixedInt( aConvertRect.GetWidth(), aTilingStream ); 9881 aTilingStream.append( " 0 0 " ); 9882 appendFixedInt( aConvertRect.GetHeight(), aTilingStream ); 9883 aTilingStream.append( " 0 0 cm\n/" ); 9884 aTilingStream.append( aImageName ); 9885 aTilingStream.append( " Do\n" ); 9886 9887 m_aTilings.emplace_back( ); 9888 m_aTilings.back().m_nObject = createObject(); 9889 m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() ); 9890 m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream()); 9891 m_aTilings.back().m_pTilingStream->WriteBytes( 9892 aTilingStream.getStr(), aTilingStream.getLength() ); 9893 // phase the tiling so wallpaper begins on upper left 9894 if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0)) 9895 throw o3tl::divide_by_zero(); 9896 m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor; 9897 m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor; 9898 m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject; 9899 9900 updateGraphicsState(); 9901 9902 OStringBuffer aObjName( 16 ); 9903 aObjName.append( 'P' ); 9904 aObjName.append( m_aTilings.back().m_nObject ); 9905 OString aPatternName( aObjName.makeStringAndClear() ); 9906 pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject ); 9907 9908 // fill a rRect with the pattern 9909 OStringBuffer aLine( 100 ); 9910 aLine.append( "q /Pattern cs /" ); 9911 aLine.append( aPatternName ); 9912 aLine.append( " scn " ); 9913 m_aPages.back().appendRect( rRect, aLine ); 9914 aLine.append( " f Q\n" ); 9915 writeBuffer( aLine.getStr(), aLine.getLength() ); 9916 } 9917 } 9918 else 9919 { 9920 aBmpPos = aRect.TopLeft(); 9921 aBmpSize = aRect.GetSize(); 9922 bDrawBitmap = true; 9923 } 9924 9925 if( aBitmap.IsAlpha() ) 9926 { 9927 if( rWall.IsGradient() ) 9928 bDrawGradient = true; 9929 else 9930 bDrawColor = true; 9931 } 9932 } 9933 else if( rWall.IsGradient() ) 9934 bDrawGradient = true; 9935 else 9936 bDrawColor = true; 9937 9938 if( bDrawGradient ) 9939 { 9940 drawGradient( rRect, rWall.GetGradient() ); 9941 } 9942 if( bDrawColor ) 9943 { 9944 Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor; 9945 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; 9946 setLineColor( COL_TRANSPARENT ); 9947 setFillColor( rWall.GetColor() ); 9948 drawRectangle( rRect ); 9949 setLineColor( aOldLineColor ); 9950 setFillColor( aOldFillColor ); 9951 } 9952 if( bDrawBitmap ) 9953 { 9954 // set temporary clip region since aBmpPos and aBmpSize 9955 // may be outside rRect 9956 OStringBuffer aLine( 20 ); 9957 aLine.append( "q " ); 9958 m_aPages.back().appendRect( rRect, aLine ); 9959 aLine.append( " W n\n" ); 9960 writeBuffer( aLine.getStr(), aLine.getLength() ); 9961 drawBitmap( aBmpPos, aBmpSize, aBitmap ); 9962 writeBuffer( "Q\n", 2 ); 9963 } 9964 } 9965 9966 void PDFWriterImpl::updateGraphicsState(Mode const mode) 9967 { 9968 OStringBuffer aLine( 256 ); 9969 GraphicsState& rNewState = m_aGraphicsStack.front(); 9970 // first set clip region since it might invalidate everything else 9971 9972 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion ) 9973 { 9974 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion; 9975 9976 if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion || 9977 ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) ) 9978 { 9979 if( m_aCurrentPDFState.m_bClipRegion ) 9980 { 9981 aLine.append( "Q " ); 9982 // invalidate everything but the clip region 9983 m_aCurrentPDFState = GraphicsState(); 9984 rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion; 9985 } 9986 if( rNewState.m_bClipRegion ) 9987 { 9988 // clip region is always stored in private PDF mapmode 9989 MapMode aNewMapMode = rNewState.m_aMapMode; 9990 rNewState.m_aMapMode = m_aMapMode; 9991 SetMapMode( rNewState.m_aMapMode ); 9992 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; 9993 9994 aLine.append("q "); 9995 if ( rNewState.m_aClipRegion.count() ) 9996 { 9997 m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine ); 9998 } 9999 else 10000 { 10001 // tdf#130150 Need to revert tdf#99680, that breaks the 10002 // rule that an set but empty clip-region clips everything 10003 // aka draws nothing -> nothing is in an empty clip-region 10004 aLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible 10005 } 10006 aLine.append( "W* n\n" ); 10007 10008 rNewState.m_aMapMode = aNewMapMode; 10009 SetMapMode( rNewState.m_aMapMode ); 10010 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; 10011 } 10012 } 10013 } 10014 10015 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode ) 10016 { 10017 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode; 10018 SetMapMode( rNewState.m_aMapMode ); 10019 } 10020 10021 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font ) 10022 { 10023 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font; 10024 SetFont( rNewState.m_aFont ); 10025 } 10026 10027 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode ) 10028 { 10029 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode; 10030 SetLayoutMode( rNewState.m_nLayoutMode ); 10031 } 10032 10033 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage ) 10034 { 10035 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage; 10036 SetDigitLanguage( rNewState.m_aDigitLanguage ); 10037 } 10038 10039 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor ) 10040 { 10041 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor; 10042 if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor && 10043 rNewState.m_aLineColor != COL_TRANSPARENT ) 10044 { 10045 appendStrokingColor( rNewState.m_aLineColor, aLine ); 10046 aLine.append( "\n" ); 10047 } 10048 } 10049 10050 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor ) 10051 { 10052 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor; 10053 if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor && 10054 rNewState.m_aFillColor != COL_TRANSPARENT ) 10055 { 10056 appendNonStrokingColor( rNewState.m_aFillColor, aLine ); 10057 aLine.append( "\n" ); 10058 } 10059 } 10060 10061 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent ) 10062 { 10063 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent; 10064 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 ) 10065 { 10066 // TODO: switch extended graphicsstate 10067 } 10068 } 10069 10070 // everything is up to date now 10071 m_aCurrentPDFState = m_aGraphicsStack.front(); 10072 if ((mode != Mode::NOWRITE) && !aLine.isEmpty()) 10073 writeBuffer( aLine.getStr(), aLine.getLength() ); 10074 } 10075 10076 /* #i47544# imitate OutputDevice behaviour: 10077 * if a font with a nontransparent color is set, it overwrites the current 10078 * text color. OTOH setting the text color will overwrite the color of the font. 10079 */ 10080 void PDFWriterImpl::setFont( const vcl::Font& rFont ) 10081 { 10082 Color aColor = rFont.GetColor(); 10083 if( aColor == COL_TRANSPARENT ) 10084 aColor = m_aGraphicsStack.front().m_aFont.GetColor(); 10085 m_aGraphicsStack.front().m_aFont = rFont; 10086 m_aGraphicsStack.front().m_aFont.SetColor( aColor ); 10087 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font; 10088 } 10089 10090 void PDFWriterImpl::push( PushFlags nFlags ) 10091 { 10092 OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" ); 10093 m_aGraphicsStack.push_front( m_aGraphicsStack.front() ); 10094 m_aGraphicsStack.front().m_nFlags = nFlags; 10095 } 10096 10097 void PDFWriterImpl::pop() 10098 { 10099 OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" ); 10100 if( m_aGraphicsStack.size() < 2 ) 10101 return; 10102 10103 GraphicsState aState = m_aGraphicsStack.front(); 10104 m_aGraphicsStack.pop_front(); 10105 GraphicsState& rOld = m_aGraphicsStack.front(); 10106 10107 // move those parameters back that were not pushed 10108 // in the first place 10109 if( ! (aState.m_nFlags & PushFlags::LINECOLOR) ) 10110 setLineColor( aState.m_aLineColor ); 10111 if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) ) 10112 setFillColor( aState.m_aFillColor ); 10113 if( ! (aState.m_nFlags & PushFlags::FONT) ) 10114 setFont( aState.m_aFont ); 10115 if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) ) 10116 setTextColor( aState.m_aFont.GetColor() ); 10117 if( ! (aState.m_nFlags & PushFlags::MAPMODE) ) 10118 setMapMode( aState.m_aMapMode ); 10119 if( ! (aState.m_nFlags & PushFlags::CLIPREGION) ) 10120 { 10121 // do not use setClipRegion here 10122 // it would convert again assuming the current mapmode 10123 rOld.m_aClipRegion = aState.m_aClipRegion; 10124 rOld.m_bClipRegion = aState.m_bClipRegion; 10125 } 10126 if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) ) 10127 setTextLineColor( aState.m_aTextLineColor ); 10128 if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) ) 10129 setOverlineColor( aState.m_aOverlineColor ); 10130 if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) ) 10131 setTextAlign( aState.m_aFont.GetAlignment() ); 10132 if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) ) 10133 setTextFillColor( aState.m_aFont.GetFillColor() ); 10134 if( ! (aState.m_nFlags & PushFlags::REFPOINT) ) 10135 { 10136 // what ? 10137 } 10138 // invalidate graphics state 10139 m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All; 10140 } 10141 10142 void PDFWriterImpl::setMapMode( const MapMode& rMapMode ) 10143 { 10144 m_aGraphicsStack.front().m_aMapMode = rMapMode; 10145 SetMapMode( rMapMode ); 10146 m_aCurrentPDFState.m_aMapMode = rMapMode; 10147 } 10148 10149 void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion ) 10150 { 10151 // tdf#130150 improve coordinate manipulations to double precision transformations 10152 const basegfx::B2DHomMatrix aCurrentTransform( 10153 GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode)); 10154 basegfx::B2DPolyPolygon aRegion(rRegion); 10155 10156 aRegion.transform(aCurrentTransform); 10157 m_aGraphicsStack.front().m_aClipRegion = aRegion; 10158 m_aGraphicsStack.front().m_bClipRegion = true; 10159 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion; 10160 } 10161 10162 void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY ) 10163 { 10164 if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) ) 10165 return; 10166 10167 // tdf#130150 improve coordinate manipulations to double precision transformations 10168 basegfx::B2DHomMatrix aConvertA; 10169 10170 if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit()) 10171 { 10172 aConvertA = GetInverseViewTransformation(m_aMapMode); 10173 } 10174 else 10175 { 10176 aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode); 10177 } 10178 10179 basegfx::B2DPoint aB2DPointA(nX, nY); 10180 basegfx::B2DPoint aB2DPointB(0.0, 0.0); 10181 aB2DPointA *= aConvertA; 10182 aB2DPointB *= aConvertA; 10183 aB2DPointA -= aB2DPointB; 10184 basegfx::B2DHomMatrix aMat; 10185 10186 aMat.translate(aB2DPointA.getX(), aB2DPointA.getY()); 10187 m_aGraphicsStack.front().m_aClipRegion.transform( aMat ); 10188 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion; 10189 } 10190 10191 void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect ) 10192 { 10193 basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect( 10194 vcl::unotools::b2DRectangleFromRectangle(rRect) ) ); 10195 intersectClipRegion( aRect ); 10196 } 10197 10198 void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion ) 10199 { 10200 // tdf#130150 improve coordinate manipulations to double precision transformations 10201 const basegfx::B2DHomMatrix aCurrentTransform( 10202 GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode)); 10203 basegfx::B2DPolyPolygon aRegion(rRegion); 10204 10205 aRegion.transform(aCurrentTransform); 10206 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion; 10207 10208 if( m_aGraphicsStack.front().m_bClipRegion ) 10209 { 10210 basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) ); 10211 aRegion = basegfx::utils::prepareForPolygonOperation( aRegion ); 10212 m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion ); 10213 } 10214 else 10215 { 10216 m_aGraphicsStack.front().m_aClipRegion = aRegion; 10217 m_aGraphicsStack.front().m_bClipRegion = true; 10218 } 10219 } 10220 10221 void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr ) 10222 { 10223 if (nPageNr < 0) 10224 nPageNr = m_nCurrentPage; 10225 10226 if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size()) 10227 return; 10228 10229 m_aNotes.emplace_back(); 10230 auto & rNoteEntry = m_aNotes.back(); 10231 rNoteEntry.m_nObject = createObject(); 10232 rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject(); 10233 rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject; 10234 rNoteEntry.m_aContents = rNote; 10235 rNoteEntry.m_aRect = rRect; 10236 // convert to default user space now, since the mapmode may change 10237 m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect); 10238 10239 // insert note to page's annotation list 10240 m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject); 10241 m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject); 10242 } 10243 10244 sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText) 10245 { 10246 if( nPageNr < 0 ) 10247 nPageNr = m_nCurrentPage; 10248 10249 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) 10250 return -1; 10251 10252 sal_Int32 nRet = m_aLinks.size(); 10253 10254 m_aLinks.emplace_back(rAltText); 10255 m_aLinks.back().m_nObject = createObject(); 10256 m_aLinks.back().m_nPage = nPageNr; 10257 m_aLinks.back().m_aRect = rRect; 10258 // convert to default user space now, since the mapmode may change 10259 m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect ); 10260 10261 // insert link to page's annotation list 10262 m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject ); 10263 10264 return nRet; 10265 } 10266 10267 sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr) 10268 { 10269 if (nPageNr < 0) 10270 nPageNr = m_nCurrentPage; 10271 10272 if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size()) 10273 return -1; 10274 10275 sal_Int32 nRet = m_aScreens.size(); 10276 10277 m_aScreens.emplace_back(); 10278 m_aScreens.back().m_nObject = createObject(); 10279 m_aScreens.back().m_nPage = nPageNr; 10280 m_aScreens.back().m_aRect = rRect; 10281 // Convert to default user space now, since the mapmode may change. 10282 m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect); 10283 10284 // Insert link to page's annotation list. 10285 m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject); 10286 10287 return nRet; 10288 } 10289 10290 sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) 10291 { 10292 if( nPageNr < 0 ) 10293 nPageNr = m_nCurrentPage; 10294 10295 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) 10296 return -1; 10297 10298 sal_Int32 nRet = m_aNamedDests.size(); 10299 10300 m_aNamedDests.emplace_back( ); 10301 m_aNamedDests.back().m_aDestName = sDestName; 10302 m_aNamedDests.back().m_nPage = nPageNr; 10303 m_aNamedDests.back().m_eType = eType; 10304 m_aNamedDests.back().m_aRect = rRect; 10305 // convert to default user space now, since the mapmode may change 10306 m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect ); 10307 10308 return nRet; 10309 } 10310 10311 sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) 10312 { 10313 if( nPageNr < 0 ) 10314 nPageNr = m_nCurrentPage; 10315 10316 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) 10317 return -1; 10318 10319 sal_Int32 nRet = m_aDests.size(); 10320 10321 m_aDests.emplace_back( ); 10322 m_aDests.back().m_nPage = nPageNr; 10323 m_aDests.back().m_eType = eType; 10324 m_aDests.back().m_aRect = rRect; 10325 // convert to default user space now, since the mapmode may change 10326 m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect ); 10327 10328 return nRet; 10329 } 10330 10331 sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) 10332 { 10333 m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType ); 10334 return m_aDestinationIdTranslation[ nDestId ]; 10335 } 10336 10337 void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) 10338 { 10339 if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() ) 10340 return; 10341 if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() ) 10342 return; 10343 10344 m_aLinks[ nLinkId ].m_nDest = nDestId; 10345 } 10346 10347 void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL ) 10348 { 10349 if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() ) 10350 return; 10351 10352 m_aLinks[ nLinkId ].m_nDest = -1; 10353 10354 using namespace ::com::sun::star; 10355 10356 if (!m_xTrans.is()) 10357 { 10358 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() ); 10359 m_xTrans = util::URLTransformer::create(xContext); 10360 } 10361 10362 util::URL aURL; 10363 aURL.Complete = rURL; 10364 10365 m_xTrans->parseStrict( aURL ); 10366 10367 m_aLinks[ nLinkId ].m_aURL = aURL.Complete; 10368 } 10369 10370 void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL) 10371 { 10372 if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size()) 10373 return; 10374 10375 m_aScreens[nScreenId].m_aURL = rURL; 10376 } 10377 10378 void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL) 10379 { 10380 if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size()) 10381 return; 10382 10383 m_aScreens[nScreenId].m_aTempFileURL = rURL; 10384 m_aScreens[nScreenId].m_nTempFileObject = createObject(); 10385 } 10386 10387 void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId ) 10388 { 10389 m_aLinkPropertyMap[ nPropertyId ] = nLinkId; 10390 } 10391 10392 sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID ) 10393 { 10394 // create new item 10395 sal_Int32 nNewItem = m_aOutline.size(); 10396 m_aOutline.emplace_back( ); 10397 10398 // set item attributes 10399 setOutlineItemParent( nNewItem, nParent ); 10400 setOutlineItemText( nNewItem, rText ); 10401 setOutlineItemDest( nNewItem, nDestID ); 10402 10403 return nNewItem; 10404 } 10405 10406 void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent ) 10407 { 10408 if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) 10409 return; 10410 10411 if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem ) 10412 { 10413 nNewParent = 0; 10414 } 10415 // insert item to new parent's list of children 10416 m_aOutline[ nNewParent ].m_aChildren.push_back( nItem ); 10417 } 10418 10419 void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText ) 10420 { 10421 if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) 10422 return; 10423 10424 m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText ); 10425 } 10426 10427 void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID ) 10428 { 10429 if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist 10430 return; 10431 if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist 10432 return; 10433 m_aOutline[nItem].m_nDestID = nDestID; 10434 } 10435 10436 const char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType ) 10437 { 10438 static std::map< PDFWriter::StructElement, const char* > aTagStrings; 10439 if( aTagStrings.empty() ) 10440 { 10441 aTagStrings[ PDFWriter::NonStructElement] = "NonStruct"; 10442 aTagStrings[ PDFWriter::Document ] = "Document"; 10443 aTagStrings[ PDFWriter::Part ] = "Part"; 10444 aTagStrings[ PDFWriter::Article ] = "Art"; 10445 aTagStrings[ PDFWriter::Section ] = "Sect"; 10446 aTagStrings[ PDFWriter::Division ] = "Div"; 10447 aTagStrings[ PDFWriter::BlockQuote ] = "BlockQuote"; 10448 aTagStrings[ PDFWriter::Caption ] = "Caption"; 10449 aTagStrings[ PDFWriter::TOC ] = "TOC"; 10450 aTagStrings[ PDFWriter::TOCI ] = "TOCI"; 10451 aTagStrings[ PDFWriter::Index ] = "Index"; 10452 aTagStrings[ PDFWriter::Paragraph ] = "P"; 10453 aTagStrings[ PDFWriter::Heading ] = "H"; 10454 aTagStrings[ PDFWriter::H1 ] = "H1"; 10455 aTagStrings[ PDFWriter::H2 ] = "H2"; 10456 aTagStrings[ PDFWriter::H3 ] = "H3"; 10457 aTagStrings[ PDFWriter::H4 ] = "H4"; 10458 aTagStrings[ PDFWriter::H5 ] = "H5"; 10459 aTagStrings[ PDFWriter::H6 ] = "H6"; 10460 aTagStrings[ PDFWriter::List ] = "L"; 10461 aTagStrings[ PDFWriter::ListItem ] = "LI"; 10462 aTagStrings[ PDFWriter::LILabel ] = "Lbl"; 10463 aTagStrings[ PDFWriter::LIBody ] = "LBody"; 10464 aTagStrings[ PDFWriter::Table ] = "Table"; 10465 aTagStrings[ PDFWriter::TableRow ] = "TR"; 10466 aTagStrings[ PDFWriter::TableHeader ] = "TH"; 10467 aTagStrings[ PDFWriter::TableData ] = "TD"; 10468 aTagStrings[ PDFWriter::Span ] = "Span"; 10469 aTagStrings[ PDFWriter::Quote ] = "Quote"; 10470 aTagStrings[ PDFWriter::Note ] = "Note"; 10471 aTagStrings[ PDFWriter::Reference ] = "Reference"; 10472 aTagStrings[ PDFWriter::BibEntry ] = "BibEntry"; 10473 aTagStrings[ PDFWriter::Code ] = "Code"; 10474 aTagStrings[ PDFWriter::Link ] = "Link"; 10475 aTagStrings[ PDFWriter::Figure ] = "Figure"; 10476 aTagStrings[ PDFWriter::Formula ] = "Formula"; 10477 aTagStrings[ PDFWriter::Form ] = "Form"; 10478 } 10479 10480 std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType ); 10481 10482 return it != aTagStrings.end() ? it->second : "Div"; 10483 } 10484 10485 void PDFWriterImpl::addRoleMap(OString aAlias, PDFWriter::StructElement eType) 10486 { 10487 OString aTag = getStructureTag(eType); 10488 // For PDF/UA it's not allowed to map an alias with the same name. 10489 // Not sure if this allowed, necessary or recommended otherwise, so 10490 // only enable filtering when PDF/UA is enabled. 10491 if (!m_bIsPDF_UA || aAlias != aTag) 10492 m_aRoleMap[aAlias] = aTag; 10493 } 10494 10495 void PDFWriterImpl::beginStructureElementMCSeq() 10496 { 10497 if( m_bEmitStructure && 10498 m_nCurrentStructElement > 0 && // StructTreeRoot 10499 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence 10500 ) 10501 { 10502 PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ]; 10503 OStringBuffer aLine( 128 ); 10504 sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size(); 10505 aLine.append( "/" ); 10506 if( !rEle.m_aAlias.isEmpty() ) 10507 aLine.append( rEle.m_aAlias ); 10508 else 10509 aLine.append( getStructureTag( rEle.m_eType ) ); 10510 aLine.append( "<</MCID " ); 10511 aLine.append( nMCID ); 10512 aLine.append( ">>BDC\n" ); 10513 writeBuffer( aLine.getStr(), aLine.getLength() ); 10514 10515 // update the element's content list 10516 SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object " 10517 << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = " 10518 << rEle.m_nFirstPageObject); 10519 rEle.m_aKids.emplace_back( nMCID, m_aPages[m_nCurrentPage].m_nPageObject ); 10520 // update the page's mcid parent list 10521 m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject ); 10522 // mark element MC sequence as open 10523 rEle.m_bOpenMCSeq = true; 10524 } 10525 // handle artifacts 10526 else if( ! m_bEmitStructure && m_aContext.Tagged && 10527 m_nCurrentStructElement > 0 && 10528 m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement && 10529 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence 10530 ) 10531 { 10532 OString aLine = "/Artifact BMC\n"; 10533 writeBuffer( aLine.getStr(), aLine.getLength() ); 10534 // mark element MC sequence as open 10535 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true; 10536 } 10537 } 10538 10539 void PDFWriterImpl::endStructureElementMCSeq() 10540 { 10541 if( m_nCurrentStructElement > 0 && // StructTreeRoot 10542 ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) && 10543 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence 10544 ) 10545 { 10546 writeBuffer( "EMC\n", 4 ); 10547 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false; 10548 } 10549 } 10550 10551 bool PDFWriterImpl::checkEmitStructure() 10552 { 10553 bool bEmit = false; 10554 if( m_aContext.Tagged ) 10555 { 10556 bEmit = true; 10557 sal_Int32 nEle = m_nCurrentStructElement; 10558 while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() ) 10559 { 10560 if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement ) 10561 { 10562 bEmit = false; 10563 break; 10564 } 10565 nEle = m_aStructure[ nEle ].m_nParentElement; 10566 } 10567 } 10568 return bEmit; 10569 } 10570 10571 sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, std::u16string_view rAlias ) 10572 { 10573 if( m_nCurrentPage < 0 ) 10574 return -1; 10575 10576 if( ! m_aContext.Tagged ) 10577 return -1; 10578 10579 // close eventual current MC sequence 10580 endStructureElementMCSeq(); 10581 10582 if( m_nCurrentStructElement == 0 && 10583 eType != PDFWriter::Document && eType != PDFWriter::NonStructElement ) 10584 { 10585 // struct tree root hit, but not beginning document 10586 // this might happen with setCurrentStructureElement 10587 // silently insert structure into document again if one properly exists 10588 if( ! m_aStructure[ 0 ].m_aChildren.empty() ) 10589 { 10590 const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren; 10591 auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(), 10592 [&](sal_Int32 nElement) { return m_aStructure[ nElement ].m_eType == PDFWriter::Document; }); 10593 if( it != rRootChildren.end() ) 10594 { 10595 m_nCurrentStructElement = *it; 10596 SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" ); 10597 } 10598 else { 10599 OSL_FAIL( "document structure in disorder !" ); 10600 } 10601 } 10602 else { 10603 OSL_FAIL( "PDF document structure MUST be contained in a Document element" ); 10604 } 10605 } 10606 10607 sal_Int32 nNewId = sal_Int32(m_aStructure.size()); 10608 m_aStructure.emplace_back( ); 10609 PDFStructureElement& rEle = m_aStructure.back(); 10610 rEle.m_eType = eType; 10611 rEle.m_nOwnElement = nNewId; 10612 rEle.m_nParentElement = m_nCurrentStructElement; 10613 rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject; 10614 m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId ); 10615 m_nCurrentStructElement = nNewId; 10616 10617 // handle alias names 10618 if( !rAlias.empty() && eType != PDFWriter::NonStructElement ) 10619 { 10620 OStringBuffer aNameBuf( rAlias.size() ); 10621 appendName( rAlias, aNameBuf ); 10622 OString aAliasName( aNameBuf.makeStringAndClear() ); 10623 rEle.m_aAlias = aAliasName; 10624 addRoleMap(aAliasName, eType); 10625 } 10626 10627 if (g_bDebugDisableCompression) 10628 { 10629 OStringBuffer aLine( "beginStructureElement " ); 10630 aLine.append( m_nCurrentStructElement ); 10631 aLine.append( ": " ); 10632 aLine.append( getStructureTag( eType ) ); 10633 if( !rEle.m_aAlias.isEmpty() ) 10634 { 10635 aLine.append( " aliased as \"" ); 10636 aLine.append( rEle.m_aAlias ); 10637 aLine.append( '\"' ); 10638 } 10639 emitComment( aLine.getStr() ); 10640 } 10641 10642 // check whether to emit structure henceforth 10643 m_bEmitStructure = checkEmitStructure(); 10644 10645 if( m_bEmitStructure ) // don't create nonexistent objects 10646 { 10647 rEle.m_nObject = createObject(); 10648 // update parent's kids list 10649 m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject); 10650 } 10651 return nNewId; 10652 } 10653 10654 void PDFWriterImpl::endStructureElement() 10655 { 10656 if( m_nCurrentPage < 0 ) 10657 return; 10658 10659 if( ! m_aContext.Tagged ) 10660 return; 10661 10662 if( m_nCurrentStructElement == 0 ) 10663 { 10664 // hit the struct tree root, that means there is an endStructureElement 10665 // without corresponding beginStructureElement 10666 return; 10667 } 10668 10669 // end the marked content sequence 10670 endStructureElementMCSeq(); 10671 10672 OStringBuffer aLine; 10673 if (g_bDebugDisableCompression) 10674 { 10675 aLine.append( "endStructureElement " ); 10676 aLine.append( m_nCurrentStructElement ); 10677 aLine.append( ": " ); 10678 aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) ); 10679 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() ) 10680 { 10681 aLine.append( " aliased as \"" ); 10682 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); 10683 aLine.append( '\"' ); 10684 } 10685 } 10686 10687 // "end" the structure element, the parent becomes current element 10688 m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement; 10689 10690 // check whether to emit structure henceforth 10691 m_bEmitStructure = checkEmitStructure(); 10692 10693 if (g_bDebugDisableCompression && m_bEmitStructure) 10694 { 10695 emitComment( aLine.getStr() ); 10696 } 10697 } 10698 10699 /* 10700 * This function adds an internal structure list container to overcome the 8191 elements array limitation 10701 * in kids element emission. 10702 * Recursive function 10703 * 10704 */ 10705 void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle ) 10706 { 10707 if( rEle.m_eType == PDFWriter::NonStructElement && 10708 rEle.m_nOwnElement != rEle.m_nParentElement ) 10709 return; 10710 10711 for (auto const& child : rEle.m_aChildren) 10712 { 10713 if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() ) 10714 { 10715 PDFStructureElement& rChild = m_aStructure[ child ]; 10716 if( rChild.m_eType != PDFWriter::NonStructElement ) 10717 { 10718 //triggered when a child of the rEle element is found 10719 if( rChild.m_nParentElement == rEle.m_nOwnElement ) 10720 addInternalStructureContainer( rChild );//examine the child 10721 else 10722 { 10723 OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" ); 10724 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child ); 10725 } 10726 } 10727 } 10728 else 10729 { 10730 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" ); 10731 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child ); 10732 } 10733 } 10734 10735 if( rEle.m_nOwnElement == rEle.m_nParentElement ) 10736 return; 10737 10738 if( rEle.m_aKids.empty() ) 10739 return; 10740 10741 if( rEle.m_aKids.size() <= ncMaxPDFArraySize ) return; 10742 10743 //then we need to add the containers for the kids elements 10744 // a list to be used for the new kid element 10745 std::list< PDFStructureElementKid > aNewKids; 10746 std::list< sal_Int32 > aNewChildren; 10747 10748 // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?) 10749 OString aAliasName("Div"); 10750 addRoleMap(aAliasName, PDFWriter::Division); 10751 10752 while( rEle.m_aKids.size() > ncMaxPDFArraySize ) 10753 { 10754 sal_Int32 nCurrentStructElement = rEle.m_nOwnElement; 10755 sal_Int32 nNewId = sal_Int32(m_aStructure.size()); 10756 m_aStructure.emplace_back( ); 10757 PDFStructureElement& rEleNew = m_aStructure.back(); 10758 rEleNew.m_aAlias = aAliasName; 10759 rEleNew.m_eType = PDFWriter::Division; // a new Div type container 10760 rEleNew.m_nOwnElement = nNewId; 10761 rEleNew.m_nParentElement = nCurrentStructElement; 10762 //inherit the same page as the first child to be reparented 10763 rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject; 10764 rEleNew.m_nObject = createObject();//assign a PDF object number 10765 //add the object to the kid list of the parent 10766 aNewKids.emplace_back( rEleNew.m_nObject ); 10767 aNewChildren.push_back( nNewId ); 10768 10769 std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() ); 10770 std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() ); 10771 advance( aChildEndIt, ncMaxPDFArraySize ); 10772 advance( aKidEndIt, ncMaxPDFArraySize ); 10773 10774 rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(), 10775 rEle.m_aKids, 10776 rEle.m_aKids.begin(), 10777 aKidEndIt ); 10778 rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(), 10779 rEle.m_aChildren, 10780 rEle.m_aChildren.begin(), 10781 aChildEndIt ); 10782 // set the kid's new parent 10783 for (auto const& child : rEleNew.m_aChildren) 10784 { 10785 m_aStructure[ child ].m_nParentElement = nNewId; 10786 } 10787 } 10788 //finally add the new kids resulting from the container added 10789 rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() ); 10790 rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() ); 10791 } 10792 10793 bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle ) 10794 { 10795 bool bSuccess = false; 10796 10797 if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() ) 10798 { 10799 // end eventual previous marked content sequence 10800 endStructureElementMCSeq(); 10801 10802 m_nCurrentStructElement = nEle; 10803 m_bEmitStructure = checkEmitStructure(); 10804 if (g_bDebugDisableCompression) 10805 { 10806 OStringBuffer aLine( "setCurrentStructureElement " ); 10807 aLine.append( m_nCurrentStructElement ); 10808 aLine.append( ": " ); 10809 aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) ); 10810 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() ) 10811 { 10812 aLine.append( " aliased as \"" ); 10813 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); 10814 aLine.append( '\"' ); 10815 } 10816 if( ! m_bEmitStructure ) 10817 aLine.append( " (inside NonStruct)" ); 10818 emitComment( aLine.getStr() ); 10819 } 10820 bSuccess = true; 10821 } 10822 10823 return bSuccess; 10824 } 10825 10826 bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal ) 10827 { 10828 if( !m_aContext.Tagged ) 10829 return false; 10830 10831 bool bInsert = false; 10832 if( m_nCurrentStructElement > 0 && m_bEmitStructure ) 10833 { 10834 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; 10835 switch( eAttr ) 10836 { 10837 case PDFWriter::Placement: 10838 if( eVal == PDFWriter::Block || 10839 eVal == PDFWriter::Inline || 10840 eVal == PDFWriter::Before || 10841 eVal == PDFWriter::Start || 10842 eVal == PDFWriter::End ) 10843 bInsert = true; 10844 break; 10845 case PDFWriter::WritingMode: 10846 if( eVal == PDFWriter::LrTb || 10847 eVal == PDFWriter::RlTb || 10848 eVal == PDFWriter::TbRl ) 10849 { 10850 bInsert = true; 10851 } 10852 break; 10853 case PDFWriter::TextAlign: 10854 if( eVal == PDFWriter::Start || 10855 eVal == PDFWriter::Center || 10856 eVal == PDFWriter::End || 10857 eVal == PDFWriter::Justify ) 10858 { 10859 if( eType == PDFWriter::Paragraph || 10860 eType == PDFWriter::Heading || 10861 eType == PDFWriter::H1 || 10862 eType == PDFWriter::H2 || 10863 eType == PDFWriter::H3 || 10864 eType == PDFWriter::H4 || 10865 eType == PDFWriter::H5 || 10866 eType == PDFWriter::H6 || 10867 eType == PDFWriter::List || 10868 eType == PDFWriter::ListItem || 10869 eType == PDFWriter::LILabel || 10870 eType == PDFWriter::LIBody || 10871 eType == PDFWriter::Table || 10872 eType == PDFWriter::TableRow || 10873 eType == PDFWriter::TableHeader || 10874 eType == PDFWriter::TableData ) 10875 { 10876 bInsert = true; 10877 } 10878 } 10879 break; 10880 case PDFWriter::Width: 10881 case PDFWriter::Height: 10882 if( eVal == PDFWriter::Auto ) 10883 { 10884 if( eType == PDFWriter::Figure || 10885 eType == PDFWriter::Formula || 10886 eType == PDFWriter::Form || 10887 eType == PDFWriter::Table || 10888 eType == PDFWriter::TableHeader || 10889 eType == PDFWriter::TableData ) 10890 { 10891 bInsert = true; 10892 } 10893 } 10894 break; 10895 case PDFWriter::BlockAlign: 10896 if( eVal == PDFWriter::Before || 10897 eVal == PDFWriter::Middle || 10898 eVal == PDFWriter::After || 10899 eVal == PDFWriter::Justify ) 10900 { 10901 if( eType == PDFWriter::TableHeader || 10902 eType == PDFWriter::TableData ) 10903 { 10904 bInsert = true; 10905 } 10906 } 10907 break; 10908 case PDFWriter::InlineAlign: 10909 if( eVal == PDFWriter::Start || 10910 eVal == PDFWriter::Center || 10911 eVal == PDFWriter::End ) 10912 { 10913 if( eType == PDFWriter::TableHeader || 10914 eType == PDFWriter::TableData ) 10915 { 10916 bInsert = true; 10917 } 10918 } 10919 break; 10920 case PDFWriter::LineHeight: 10921 if( eVal == PDFWriter::Normal || 10922 eVal == PDFWriter::Auto ) 10923 { 10924 // only for ILSE and BLSE 10925 if( eType == PDFWriter::Paragraph || 10926 eType == PDFWriter::Heading || 10927 eType == PDFWriter::H1 || 10928 eType == PDFWriter::H2 || 10929 eType == PDFWriter::H3 || 10930 eType == PDFWriter::H4 || 10931 eType == PDFWriter::H5 || 10932 eType == PDFWriter::H6 || 10933 eType == PDFWriter::List || 10934 eType == PDFWriter::ListItem || 10935 eType == PDFWriter::LILabel || 10936 eType == PDFWriter::LIBody || 10937 eType == PDFWriter::Table || 10938 eType == PDFWriter::TableRow || 10939 eType == PDFWriter::TableHeader || 10940 eType == PDFWriter::TableData || 10941 eType == PDFWriter::Span || 10942 eType == PDFWriter::Quote || 10943 eType == PDFWriter::Note || 10944 eType == PDFWriter::Reference || 10945 eType == PDFWriter::BibEntry || 10946 eType == PDFWriter::Code || 10947 eType == PDFWriter::Link ) 10948 { 10949 bInsert = true; 10950 } 10951 } 10952 break; 10953 case PDFWriter::TextDecorationType: 10954 if( eVal == PDFWriter::NONE || 10955 eVal == PDFWriter::Underline || 10956 eVal == PDFWriter::Overline || 10957 eVal == PDFWriter::LineThrough ) 10958 { 10959 // only for ILSE and BLSE 10960 if( eType == PDFWriter::Paragraph || 10961 eType == PDFWriter::Heading || 10962 eType == PDFWriter::H1 || 10963 eType == PDFWriter::H2 || 10964 eType == PDFWriter::H3 || 10965 eType == PDFWriter::H4 || 10966 eType == PDFWriter::H5 || 10967 eType == PDFWriter::H6 || 10968 eType == PDFWriter::List || 10969 eType == PDFWriter::ListItem || 10970 eType == PDFWriter::LILabel || 10971 eType == PDFWriter::LIBody || 10972 eType == PDFWriter::Table || 10973 eType == PDFWriter::TableRow || 10974 eType == PDFWriter::TableHeader || 10975 eType == PDFWriter::TableData || 10976 eType == PDFWriter::Span || 10977 eType == PDFWriter::Quote || 10978 eType == PDFWriter::Note || 10979 eType == PDFWriter::Reference || 10980 eType == PDFWriter::BibEntry || 10981 eType == PDFWriter::Code || 10982 eType == PDFWriter::Link ) 10983 { 10984 bInsert = true; 10985 } 10986 } 10987 break; 10988 case PDFWriter::Scope: 10989 if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both) 10990 { 10991 if (eType == PDFWriter::TableHeader 10992 && m_aContext.Version != PDFWriter::PDFVersion::PDF_A_1 10993 && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version) 10994 { 10995 bInsert = true; 10996 } 10997 } 10998 break; 10999 case PDFWriter::ListNumbering: 11000 if( eVal == PDFWriter::NONE || 11001 eVal == PDFWriter::Disc || 11002 eVal == PDFWriter::Circle || 11003 eVal == PDFWriter::Square || 11004 eVal == PDFWriter::Decimal || 11005 eVal == PDFWriter::UpperRoman || 11006 eVal == PDFWriter::LowerRoman || 11007 eVal == PDFWriter::UpperAlpha || 11008 eVal == PDFWriter::LowerAlpha ) 11009 { 11010 if( eType == PDFWriter::List ) 11011 bInsert = true; 11012 } 11013 break; 11014 default: break; 11015 } 11016 } 11017 11018 if( bInsert ) 11019 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal ); 11020 else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) 11021 SAL_INFO("vcl.pdfwriter", 11022 "rejecting setStructureAttribute( " << getAttributeTag( eAttr ) 11023 << ", " << getAttributeValueTag( eVal ) 11024 << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) 11025 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias 11026 << ") element"); 11027 11028 return bInsert; 11029 } 11030 11031 bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue ) 11032 { 11033 if( ! m_aContext.Tagged ) 11034 return false; 11035 11036 bool bInsert = false; 11037 if( m_nCurrentStructElement > 0 && m_bEmitStructure ) 11038 { 11039 if( eAttr == PDFWriter::Language ) 11040 { 11041 m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale(); 11042 return true; 11043 } 11044 11045 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; 11046 switch( eAttr ) 11047 { 11048 case PDFWriter::SpaceBefore: 11049 case PDFWriter::SpaceAfter: 11050 case PDFWriter::StartIndent: 11051 case PDFWriter::EndIndent: 11052 // just for BLSE 11053 if( eType == PDFWriter::Paragraph || 11054 eType == PDFWriter::Heading || 11055 eType == PDFWriter::H1 || 11056 eType == PDFWriter::H2 || 11057 eType == PDFWriter::H3 || 11058 eType == PDFWriter::H4 || 11059 eType == PDFWriter::H5 || 11060 eType == PDFWriter::H6 || 11061 eType == PDFWriter::List || 11062 eType == PDFWriter::ListItem || 11063 eType == PDFWriter::LILabel || 11064 eType == PDFWriter::LIBody || 11065 eType == PDFWriter::Table || 11066 eType == PDFWriter::TableRow || 11067 eType == PDFWriter::TableHeader || 11068 eType == PDFWriter::TableData ) 11069 { 11070 bInsert = true; 11071 } 11072 break; 11073 case PDFWriter::TextIndent: 11074 // paragraph like BLSE and additional elements 11075 if( eType == PDFWriter::Paragraph || 11076 eType == PDFWriter::Heading || 11077 eType == PDFWriter::H1 || 11078 eType == PDFWriter::H2 || 11079 eType == PDFWriter::H3 || 11080 eType == PDFWriter::H4 || 11081 eType == PDFWriter::H5 || 11082 eType == PDFWriter::H6 || 11083 eType == PDFWriter::LILabel || 11084 eType == PDFWriter::LIBody || 11085 eType == PDFWriter::TableHeader || 11086 eType == PDFWriter::TableData ) 11087 { 11088 bInsert = true; 11089 } 11090 break; 11091 case PDFWriter::Width: 11092 case PDFWriter::Height: 11093 if( eType == PDFWriter::Figure || 11094 eType == PDFWriter::Formula || 11095 eType == PDFWriter::Form || 11096 eType == PDFWriter::Table || 11097 eType == PDFWriter::TableHeader || 11098 eType == PDFWriter::TableData ) 11099 { 11100 bInsert = true; 11101 } 11102 break; 11103 case PDFWriter::LineHeight: 11104 case PDFWriter::BaselineShift: 11105 // only for ILSE and BLSE 11106 if( eType == PDFWriter::Paragraph || 11107 eType == PDFWriter::Heading || 11108 eType == PDFWriter::H1 || 11109 eType == PDFWriter::H2 || 11110 eType == PDFWriter::H3 || 11111 eType == PDFWriter::H4 || 11112 eType == PDFWriter::H5 || 11113 eType == PDFWriter::H6 || 11114 eType == PDFWriter::List || 11115 eType == PDFWriter::ListItem || 11116 eType == PDFWriter::LILabel || 11117 eType == PDFWriter::LIBody || 11118 eType == PDFWriter::Table || 11119 eType == PDFWriter::TableRow || 11120 eType == PDFWriter::TableHeader || 11121 eType == PDFWriter::TableData || 11122 eType == PDFWriter::Span || 11123 eType == PDFWriter::Quote || 11124 eType == PDFWriter::Note || 11125 eType == PDFWriter::Reference || 11126 eType == PDFWriter::BibEntry || 11127 eType == PDFWriter::Code || 11128 eType == PDFWriter::Link ) 11129 { 11130 bInsert = true; 11131 } 11132 break; 11133 case PDFWriter::RowSpan: 11134 case PDFWriter::ColSpan: 11135 // only for table cells 11136 if( eType == PDFWriter::TableHeader || 11137 eType == PDFWriter::TableData ) 11138 { 11139 bInsert = true; 11140 } 11141 break; 11142 case PDFWriter::LinkAnnotation: 11143 if( eType == PDFWriter::Link ) 11144 bInsert = true; 11145 break; 11146 default: break; 11147 } 11148 } 11149 11150 if( bInsert ) 11151 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue ); 11152 else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) 11153 SAL_INFO("vcl.pdfwriter", 11154 "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr ) 11155 << ", " << static_cast<int>(nValue) 11156 << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) 11157 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias 11158 << ") element"); 11159 11160 return bInsert; 11161 } 11162 11163 void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect ) 11164 { 11165 sal_Int32 nPageNr = m_nCurrentPage; 11166 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged ) 11167 return; 11168 11169 if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) ) 11170 return; 11171 11172 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; 11173 if( eType == PDFWriter::Figure || 11174 eType == PDFWriter::Formula || 11175 eType == PDFWriter::Form || 11176 eType == PDFWriter::Division || 11177 eType == PDFWriter::Table ) 11178 { 11179 m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect; 11180 // convert to default user space now, since the mapmode may change 11181 m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox ); 11182 } 11183 } 11184 11185 void PDFWriterImpl::setActualText( const OUString& rText ) 11186 { 11187 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) 11188 { 11189 m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText; 11190 } 11191 } 11192 11193 void PDFWriterImpl::setAlternateText( const OUString& rText ) 11194 { 11195 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) 11196 { 11197 m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText; 11198 } 11199 } 11200 11201 void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr ) 11202 { 11203 if( nPageNr < 0 ) 11204 nPageNr = m_nCurrentPage; 11205 11206 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) 11207 return; 11208 11209 m_aPages[ nPageNr ].m_eTransition = eType; 11210 m_aPages[ nPageNr ].m_nTransTime = nMilliSec; 11211 } 11212 11213 void PDFWriterImpl::ensureUniqueRadioOnValues() 11214 { 11215 // loop over radio groups 11216 for (auto const& group : m_aRadioGroupWidgets) 11217 { 11218 PDFWidget& rGroupWidget = m_aWidgets[ group.second ]; 11219 // check whether all kids have a unique OnValue 11220 std::unordered_map< OUString, sal_Int32 > aOnValues; 11221 bool bIsUnique = true; 11222 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex) 11223 { 11224 const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue; 11225 SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal); 11226 if( aOnValues.find( rVal ) == aOnValues.end() ) 11227 { 11228 aOnValues[ rVal ] = 1; 11229 } 11230 else 11231 { 11232 bIsUnique = false; 11233 break; 11234 } 11235 } 11236 if( ! bIsUnique ) 11237 { 11238 SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" ); 11239 // make unique by using ascending OnValues 11240 int nKid = 0; 11241 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex) 11242 { 11243 PDFWidget& rKid = m_aWidgets[nKidIndex]; 11244 rKid.m_aOnValue = OUString::number( nKid+1 ); 11245 if( rKid.m_aValue != "Off" ) 11246 rKid.m_aValue = rKid.m_aOnValue; 11247 ++nKid; 11248 } 11249 } 11250 // finally move the "Yes" appearance to the OnValue appearance 11251 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex) 11252 { 11253 PDFWidget& rKid = m_aWidgets[nKidIndex]; 11254 if ( !rKid.m_aOnValue.isEmpty() ) 11255 { 11256 auto app_it = rKid.m_aAppearances.find( "N" ); 11257 if( app_it != rKid.m_aAppearances.end() ) 11258 { 11259 auto stream_it = app_it->second.find( "Yes" ); 11260 if( stream_it != app_it->second.end() ) 11261 { 11262 SvMemoryStream* pStream = stream_it->second; 11263 app_it->second.erase( stream_it ); 11264 OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 ); 11265 appendName( rKid.m_aOnValue, aBuf ); 11266 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; 11267 } 11268 else 11269 SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" ); 11270 } 11271 } 11272 11273 if ( !rKid.m_aOffValue.isEmpty() ) 11274 { 11275 auto app_it = rKid.m_aAppearances.find( "N" ); 11276 if( app_it != rKid.m_aAppearances.end() ) 11277 { 11278 auto stream_it = app_it->second.find( "Off" ); 11279 if( stream_it != app_it->second.end() ) 11280 { 11281 SvMemoryStream* pStream = stream_it->second; 11282 app_it->second.erase( stream_it ); 11283 OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 ); 11284 appendName( rKid.m_aOffValue, aBuf ); 11285 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; 11286 } 11287 else 11288 SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" ); 11289 } 11290 } 11291 11292 // update selected radio button 11293 if( rKid.m_aValue != "Off" ) 11294 { 11295 rGroupWidget.m_aValue = rKid.m_aValue; 11296 } 11297 } 11298 } 11299 } 11300 11301 sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn ) 11302 { 11303 sal_Int32 nRadioGroupWidget = -1; 11304 11305 std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup ); 11306 11307 if( it == m_aRadioGroupWidgets.end() ) 11308 { 11309 m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget = 11310 sal_Int32(m_aWidgets.size()); 11311 11312 // new group, insert the radiobutton 11313 m_aWidgets.emplace_back( ); 11314 m_aWidgets.back().m_nObject = createObject(); 11315 m_aWidgets.back().m_nPage = m_nCurrentPage; 11316 m_aWidgets.back().m_eType = PDFWriter::RadioButton; 11317 m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup; 11318 m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits 11319 11320 createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn ); 11321 } 11322 else 11323 nRadioGroupWidget = it->second; 11324 11325 return nRadioGroupWidget; 11326 } 11327 11328 sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr ) 11329 { 11330 if( nPageNr < 0 ) 11331 nPageNr = m_nCurrentPage; 11332 11333 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) 11334 return -1; 11335 11336 bool sigHidden(true); 11337 sal_Int32 nNewWidget = m_aWidgets.size(); 11338 m_aWidgets.emplace_back( ); 11339 11340 m_aWidgets.back().m_nObject = createObject(); 11341 m_aWidgets.back().m_aRect = rControl.Location; 11342 m_aWidgets.back().m_nPage = nPageNr; 11343 m_aWidgets.back().m_eType = rControl.getType(); 11344 11345 sal_Int32 nRadioGroupWidget = -1; 11346 // for unknown reasons the radio buttons of a radio group must not have a 11347 // field name, else the buttons are in fact check boxes - 11348 // that is multiple buttons of the radio group can be selected 11349 if( rControl.getType() == PDFWriter::RadioButton ) 11350 nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) ); 11351 else 11352 { 11353 createWidgetFieldName( nNewWidget, rControl ); 11354 } 11355 11356 // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid 11357 PDFWidget& rNewWidget = m_aWidgets[nNewWidget]; 11358 rNewWidget.m_aDescription = rControl.Description; 11359 rNewWidget.m_aText = rControl.Text; 11360 rNewWidget.m_nTextStyle = rControl.TextStyle & 11361 ( DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top | 11362 DrawTextFlags::VCenter | DrawTextFlags::Bottom | 11363 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); 11364 rNewWidget.m_nTabOrder = rControl.TabOrder; 11365 11366 // various properties are set via the flags (/Ff) property of the field dict 11367 if( rControl.ReadOnly ) 11368 rNewWidget.m_nFlags |= 1; 11369 if( rControl.getType() == PDFWriter::PushButton ) 11370 { 11371 const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl); 11372 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) 11373 rNewWidget.m_nTextStyle = 11374 DrawTextFlags::Center | DrawTextFlags::VCenter | 11375 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; 11376 11377 rNewWidget.m_nFlags |= 0x00010000; 11378 if( !rBtn.URL.isEmpty() ) 11379 rNewWidget.m_aListEntries.push_back( rBtn.URL ); 11380 rNewWidget.m_bSubmit = rBtn.Submit; 11381 rNewWidget.m_bSubmitGet = rBtn.SubmitGet; 11382 rNewWidget.m_nDest = rBtn.Dest; 11383 createDefaultPushButtonAppearance( rNewWidget, rBtn ); 11384 } 11385 else if( rControl.getType() == PDFWriter::RadioButton ) 11386 { 11387 const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl); 11388 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) 11389 rNewWidget.m_nTextStyle = 11390 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; 11391 /* PDF sees a RadioButton group as one radio button with 11392 * children which are in turn check boxes 11393 * 11394 * so we need to create a radio button on demand for a new group 11395 * and insert a checkbox for each RadioButtonWidget as its child 11396 */ 11397 rNewWidget.m_eType = PDFWriter::CheckBox; 11398 rNewWidget.m_nRadioGroup = rBtn.RadioGroup; 11399 11400 SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" ); 11401 11402 PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget]; 11403 rRadioButton.m_aKids.push_back( rNewWidget.m_nObject ); 11404 rRadioButton.m_aKidsIndex.push_back( nNewWidget ); 11405 rNewWidget.m_nParent = rRadioButton.m_nObject; 11406 11407 rNewWidget.m_aValue = "Off"; 11408 rNewWidget.m_aOnValue = rBtn.OnValue; 11409 rNewWidget.m_aOffValue = rBtn.OffValue; 11410 if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected ) 11411 { 11412 rNewWidget.m_aValue = rNewWidget.m_aOnValue; 11413 rRadioButton.m_aValue = rNewWidget.m_aOnValue; 11414 } 11415 createDefaultRadioButtonAppearance( rNewWidget, rBtn ); 11416 11417 // union rect of radio group 11418 tools::Rectangle aRect = rNewWidget.m_aRect; 11419 m_aPages[ nPageNr ].convertRect( aRect ); 11420 rRadioButton.m_aRect.Union( aRect ); 11421 } 11422 else if( rControl.getType() == PDFWriter::CheckBox ) 11423 { 11424 const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl); 11425 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) 11426 rNewWidget.m_nTextStyle = 11427 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; 11428 11429 rNewWidget.m_aValue 11430 = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" ); 11431 rNewWidget.m_aOnValue = rBox.OnValue; 11432 rNewWidget.m_aOffValue = rBox.OffValue; 11433 // create default appearance before m_aRect gets transformed 11434 createDefaultCheckBoxAppearance( rNewWidget, rBox ); 11435 } 11436 else if( rControl.getType() == PDFWriter::ListBox ) 11437 { 11438 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) 11439 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter; 11440 11441 const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl); 11442 rNewWidget.m_aListEntries = rLstBox.Entries; 11443 rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries; 11444 rNewWidget.m_aValue = rLstBox.Text; 11445 if( rLstBox.DropDown ) 11446 rNewWidget.m_nFlags |= 0x00020000; 11447 if( rLstBox.MultiSelect && !rLstBox.DropDown && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 ) 11448 rNewWidget.m_nFlags |= 0x00200000; 11449 11450 createDefaultListBoxAppearance( rNewWidget, rLstBox ); 11451 } 11452 else if( rControl.getType() == PDFWriter::ComboBox ) 11453 { 11454 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) 11455 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter; 11456 11457 const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl); 11458 rNewWidget.m_aValue = rBox.Text; 11459 rNewWidget.m_aListEntries = rBox.Entries; 11460 rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag 11461 11462 PDFWriter::ListBoxWidget aLBox; 11463 aLBox.Name = rBox.Name; 11464 aLBox.Description = rBox.Description; 11465 aLBox.Text = rBox.Text; 11466 aLBox.TextStyle = rBox.TextStyle; 11467 aLBox.ReadOnly = rBox.ReadOnly; 11468 aLBox.Border = rBox.Border; 11469 aLBox.BorderColor = rBox.BorderColor; 11470 aLBox.Background = rBox.Background; 11471 aLBox.BackgroundColor = rBox.BackgroundColor; 11472 aLBox.TextFont = rBox.TextFont; 11473 aLBox.TextColor = rBox.TextColor; 11474 aLBox.DropDown = true; 11475 aLBox.MultiSelect = false; 11476 aLBox.Entries = rBox.Entries; 11477 11478 createDefaultListBoxAppearance( rNewWidget, aLBox ); 11479 } 11480 else if( rControl.getType() == PDFWriter::Edit ) 11481 { 11482 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) 11483 rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter; 11484 11485 const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl); 11486 if( rEdit.MultiLine ) 11487 { 11488 rNewWidget.m_nFlags |= 0x00001000; 11489 rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; 11490 } 11491 if( rEdit.Password ) 11492 rNewWidget.m_nFlags |= 0x00002000; 11493 if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 ) 11494 rNewWidget.m_nFlags |= 0x00100000; 11495 rNewWidget.m_nMaxLen = rEdit.MaxLen; 11496 rNewWidget.m_nFormat = rEdit.Format; 11497 rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol; 11498 rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy; 11499 rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol; 11500 rNewWidget.m_aTimeFormat = rEdit.TimeFormat; 11501 rNewWidget.m_aDateFormat = rEdit.DateFormat; 11502 rNewWidget.m_aValue = rEdit.Text; 11503 11504 createDefaultEditAppearance( rNewWidget, rEdit ); 11505 } 11506 #if HAVE_FEATURE_NSS 11507 else if( rControl.getType() == PDFWriter::Signature) 11508 { 11509 sigHidden = true; 11510 11511 rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0); 11512 11513 m_nSignatureObject = createObject(); 11514 rNewWidget.m_aValue = OUString::number( m_nSignatureObject ); 11515 rNewWidget.m_aValue += " 0 R"; 11516 // let's add a fake appearance 11517 rNewWidget.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream(); 11518 } 11519 #endif 11520 11521 // if control is a hidden signature, do not convert coordinates since we 11522 // need /Rect [ 0 0 0 0 ] 11523 if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) ) 11524 { 11525 // convert to default user space now, since the mapmode may change 11526 // note: create default appearances before m_aRect gets transformed 11527 m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect ); 11528 } 11529 11530 // insert widget to page's annotation list 11531 m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject ); 11532 11533 return nNewWidget; 11534 } 11535 11536 void PDFWriterImpl::MARK( const char* pString ) 11537 { 11538 beginStructureElementMCSeq(); 11539 if (g_bDebugDisableCompression) 11540 emitComment( pString ); 11541 } 11542 11543 sal_Int32 ReferenceXObjectEmit::getObject() const 11544 { 11545 if (m_nFormObject > 0) 11546 return m_nFormObject; 11547 else 11548 return m_nBitmapObject; 11549 } 11550 } 11551 11552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 11553
