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