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