/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sal/config.h>
#include <config_crypto.h>
#include <sal/types.h>
#include <math.h>
#include <algorithm>
#include <string_view>
#include <lcms2.h>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <memory>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/util/URL.hpp>
#include <com/sun/star/util/URLTransformer.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <comphelper/xmlencode.hxx>
#include <cppuhelper/implbase.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <o3tl/numeric.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/temporary.hxx>
#include <officecfg/Office/Common.hxx>
#include <osl/diagnose.h>
#include <osl/file.hxx>
#include <osl/thread.h>
#include <rtl/crc.h>
#include <rtl/digest.h>
#include <rtl/uri.hxx>
#include <rtl/ustrbuf.hxx>
#include <svl/cryptosign.hxx>
#include <sal/log.hxx>
#include <svl/urihelper.hxx>
#include <tools/fract.hxx>
#include <tools/stream.hxx>
#include <tools/helpers.hxx>
#include <tools/urlobj.hxx>
#include <tools/UnitConversion.hxx>
#include <tools/zcodec.hxx>
#include <unotools/configmgr.hxx>
#include <vcl/bitmapex.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/cvtgrf.hxx>
#include <vcl/fontcharmap.hxx>
#include <vcl/glyphitemcache.hxx>
#include <vcl/kernarray.hxx>
#include <vcl/lineinfo.hxx>
#include <vcl/metric.hxx>
#include <vcl/mnemonic.hxx>
#include <vcl/pdfread.hxx>
#include <vcl/settings.hxx>
#include <strhelper.hxx>
#include <vcl/svapp.hxx>
#include <vcl/virdev.hxx>
#include <vcl/filter/pdfdocument.hxx>
#include <vcl/filter/PngImageReader.hxx>
#include <comphelper/hash.hxx>
#include <vcl/pdf/PDFNote.hxx>
#include <svdata.hxx>
#include <vcl/BitmapWriteAccess.hxx>
#include <pdf/COSWriter.hxx>
#include <fontsubset.hxx>
#include <font/EmphasisMark.hxx>
#include <font/PhysicalFontFace.hxx>
#include <salgdi.hxx>
#include <textlayout.hxx>
#include <textlineinfo.hxx>
#include <impglyphitem.hxx>
#include <pdf/XmpMetadata.hxx>
#include <pdf/objectcopier.hxx>
#include <pdf/pdfwriter_impl.hxx>
#include <pdf/PdfConfig.hxx>
#include <pdf/PDFEncryptorR6.hxx>
#include <pdf/PDFEncryptor.hxx>
#include <o3tl/sorted_vector.hxx>
#include <frozen/bits/defines.h>
#include <frozen/bits/elsa_std.h>
#include <frozen/unordered_map.h>
using namespace ::com::sun::star;
static bool g_bDebugDisableCompression = getenv(
"VCL_DEBUG_DISABLE_PDFCOMPRESSION" );
namespace
{
constexpr std::string_view constNamespacePDF2(
"http://iso.org/pdf2/ssn ");
constexpr sal_Int32 nLog10Divisor = 3;
constexpr
double fDivisor = 1000.0;
constexpr
double pixelToPoint(
double px)
{
return px / fDivisor;
}
constexpr sal_Int32 pointToPixel(
double pt)
{
return sal_Int32(pt * fDivisor);
}
void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
{
aLine.append(nObjectID);
aLine.append(
" 0 obj\n" );
}
void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
{
aLine.append(nObjectID);
aLine.append(
" 0 R " );
}
/*
* Convert a string before using it.
*
* This string conversion function is needed because the destination name
* in a PDF file seen through an Internet browser should be
* specially crafted, in order to be used directly by the browser.
* In this way the fragment part of a hyperlink to a PDF file (e.g. something
* as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
* PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
* from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
* and go to named destination thefragment using default zoom'.
* The conversion is needed because in case of a fragment in the form: Slide%201
* (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
* using this conversion, in both the generated named destinations, fragment and GoToR
* destination.
*
* The names for destinations are name objects and so they don't need to be encrypted
* even though they expose the content of PDF file (e.g. guessing the PDF content from the
* destination name).
*
* Further limitation: it is advisable to use standard ASCII characters for
* OOo bookmarks.
*/
void appendDestinationName(
const OUString& rString, OStringBuffer& rBuffer )
{
const sal_Unicode* pStr = rString.getStr();
sal_Int32 nLen = rString.getLength();
for (
int i = 0; i < nLen; i++ )
{
sal_Unicode aChar = pStr[i];
if ( (aChar >=
'0' && aChar <=
'9' ) ||
(aChar >=
'a' && aChar <=
'z' ) ||
(aChar >=
'A' && aChar <=
'Z' ) ||
aChar ==
'-' )
{
rBuffer.append(
static_cast <
char >(aChar));
}
else
{
sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
if (aValueHigh > 0)
vcl::COSWriter::appendHex(aValueHigh, rBuffer);
vcl::COSWriter::appendHex(
static_cast <sal_Int8>(aChar & 255 ), rBuffer);
}
}
}
}
// end anonymous namespace
namespace vcl
{
namespace
{
template <
class GEOMETRY >
GEOMETRY lcl_convert(
const MapMode& _rSource,
const MapMode& _rDest, OutputDevi
ce* _pPixelConversion, const GEOMETRY& _rObject )
{
GEOMETRY aPoint;
if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
{
aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
}
else
{
aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
}
return aPoint;
}
void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle);
} // end anonymous namespace
void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
{
/* #i80258# previously we use appendName here
however we need a slightly different coding scheme than the normal
name encoding for field names
*/
const OUString& rName = i_rControl.Name;
OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
int nLen = aStr.getLength();
OStringBuffer aBuffer( rName.getLength()+64 );
for ( int i = 0; i < nLen; i++ )
{
/* #i16920# PDF recommendation: output UTF8, any byte
* outside the interval [32(=ASCII' ');126(=ASCII'~')]
* should be escaped hexadecimal
*/
if ( aStr[i] >= 32 && aStr[i] <= 126 )
aBuffer.append( aStr[i] );
else
{
aBuffer.append( '#' );
COSWriter::appendHex(static_cast <sal_Int8>(aStr[i]), aBuffer);
}
}
OString aFullName( aBuffer.makeStringAndClear() );
/* #i82785# create hierarchical fields down to the for each dot in i_rName */
sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
OString aPartialName;
OString aDomain;
do
{
nLastTokenIndex = nTokenIndex;
aPartialName = aFullName.getToken( 0, '.' , nTokenIndex );
if ( nTokenIndex != -1 )
{
// find or create a hierarchical field
// first find the fully qualified name up to this field
aDomain = aFullName.copy( 0, nTokenIndex-1 );
std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
if ( it == m_aFieldNameMap.end() )
{
// create new hierarchy field
sal_Int32 nNewWidget = m_aWidgets.size();
m_aWidgets.emplace_back( );
m_aWidgets[nNewWidget].m_nObject = createObject();
m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
m_aWidgets[nNewWidget].m_aName = aPartialName;
m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
m_aFieldNameMap[aDomain] = nNewWidget;
m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
if ( nLastTokenIndex > 0 )
{
// this field is not a root field and
// needs to be inserted to its parent
OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
it = m_aFieldNameMap.find( aParentDomain );
OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
if ( it != m_aFieldNameMap.end() )
{
OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
if ( it->second < sal_Int32(m_aWidgets.size()) )
{
PDFWidget& rParentField( m_aWidgets[it->second] );
rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
rParentField.m_aKidsIndex.push_back( nNewWidget );
m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
}
}
}
}
else if ( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
{
// this is invalid, someone tries to have a terminal field as parent
// example: a button with the name foo.bar exists and
// another button is named foo.bar.no
// workaround: put the second terminal field as much up in the hierarchy as
// necessary to have a non-terminal field as parent (or none at all)
// since it->second already is terminal, we just need to use its parent
aDomain.clear();
aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
if ( nLastTokenIndex > 0 )
{
aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
aFullName = aDomain + "." + aPartialName;
}
else
aFullName = aPartialName;
break ;
}
}
} while ( nTokenIndex != -1 );
// insert widget into its hierarchy field
if ( !aDomain.isEmpty() )
{
std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
if ( it != m_aFieldNameMap.end() )
{
OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" );
if ( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() )
{
m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
}
}
}
if ( aPartialName.isEmpty() )
{
// how funny, an empty field name
if ( i_rControl.getType() == PDFWriter::RadioButton )
{
aPartialName = "RadioGroup" +
OString::number( static_cast <const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
}
else
aPartialName = "Widget" _ostr;
}
if ( ! m_aContext.AllowDuplicateFieldNames )
{
std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
if ( it != m_aFieldNameMap.end() ) // not unique
{
std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
OString aTry;
sal_Int32 nTry = 2;
do
{
aTry = aFullName + "_" + OString::number(nTry++);
check_it = m_aFieldNameMap.find( aTry );
} while ( check_it != m_aFieldNameMap.end() );
aFullName = aTry;
m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
}
else
m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
}
// finally
m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
}
namespace
{
void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
{
if ( nValue < 0 )
{
rBuffer.append( '-' );
nValue = -nValue;
}
sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
while ( nDiv-- )
nFactor *= 10;
sal_Int32 nInt = nValue / nFactor;
rBuffer.append( nInt );
if (nFactor > 1 && nValue % nFactor)
{
rBuffer.append( '.' );
do
{
nFactor /= 10;
rBuffer.append((nValue / nFactor) % 10);
}
while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
}
}
// appends a double. PDF does not accept exponential format, only fixed point
void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 10 )
{
bool bNeg = false ;
if ( fValue < 0.0 )
{
bNeg = true ;
fValue=-fValue;
}
sal_Int64 nInt = static_cast <sal_Int64>(fValue);
fValue -= static_cast <double >(nInt);
// optimizing hardware may lead to a value of 1.0 after the subtraction
if ( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
{
nInt++;
fValue = 0.0;
}
sal_Int64 nFrac = 0;
if ( fValue )
{
fValue *= pow( 10.0, static_cast <double >(nPrecision) );
nFrac = static_cast <sal_Int64>(fValue);
}
if ( bNeg && ( nInt || nFrac ) )
rBuffer.append( '-' );
rBuffer.append( nInt );
if ( !nFrac )
return ;
int i;
rBuffer.append( '.' );
sal_Int64 nBound = static_cast <sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
{
sal_Int64 nNumb = nFrac / nBound;
nFrac -= nNumb * nBound;
rBuffer.append( nNumb );
nBound /= 10;
}
}
void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
{
if ( rColor == COL_TRANSPARENT )
return ;
if ( bConvertToGrey )
{
sal_uInt8 cByte = rColor.GetLuminance();
appendDouble( static_cast <double >(cByte) / 255.0, rBuffer );
}
else
{
appendDouble( static_cast <double >(rColor.GetRed()) / 255.0, rBuffer );
rBuffer.append( ' ' );
appendDouble( static_cast <double >(rColor.GetGreen()) / 255.0, rBuffer );
rBuffer.append( ' ' );
appendDouble( static_cast <double >(rColor.GetBlue()) / 255.0, rBuffer );
}
}
} // end anonymous namespace
void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
{
if ( rColor != COL_TRANSPARENT )
{
bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
appendColor( rColor, rBuffer, bGrey );
rBuffer.append( bGrey ? " G" : " RG" );
}
}
void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
{
if ( rColor != COL_TRANSPARENT )
{
bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
appendColor( rColor, rBuffer, bGrey );
rBuffer.append( bGrey ? " g" : " rg" );
}
}
namespace
{
void appendPdfTimeDate(OStringBuffer & rBuffer,
sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta)
{
rBuffer.append("D:" );
rBuffer.append(char ('0' + ((year / 1000) % 10)));
rBuffer.append(char ('0' + ((year / 100) % 10)));
rBuffer.append(char ('0' + ((year / 10) % 10)));
rBuffer.append(char ('0' + (year % 10)));
rBuffer.append(char ('0' + ((month / 10) % 10)));
rBuffer.append(char ('0' + (month % 10)));
rBuffer.append(char ('0' + ((day / 10) % 10)));
rBuffer.append(char ('0' + (day % 10)));
rBuffer.append(char ('0' + ((hours / 10) % 10)));
rBuffer.append(char ('0' + (hours % 10)));
rBuffer.append(char ('0' + ((minutes / 10) % 10)));
rBuffer.append(char ('0' + (minutes % 10)));
rBuffer.append(char ('0' + ((seconds / 10) % 10)));
rBuffer.append(char ('0' + (seconds % 10)));
if (tzDelta == 0)
{
rBuffer.append("Z" );
}
else
{
if (tzDelta > 0 )
rBuffer.append("+" );
else
{
rBuffer.append("-" );
tzDelta = -tzDelta;
}
rBuffer.append(char ('0' + ((tzDelta / 36000) % 10)));
rBuffer.append(char ('0' + ((tzDelta / 3600) % 10)));
rBuffer.append("'" );
rBuffer.append(char ('0' + ((tzDelta / 600) % 6)));
rBuffer.append(char ('0' + ((tzDelta / 60) % 10)));
}
}
const char * getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion)
{
switch (ePDFVersion)
{
case PDFWriter::PDFVersion::PDF_A_1:
case PDFWriter::PDFVersion::PDF_1_4:
return "1.4" ;
case PDFWriter::PDFVersion::PDF_1_5:
return "1.5" ;
case PDFWriter::PDFVersion::PDF_1_6:
return "1.6" ;
default :
case PDFWriter::PDFVersion::PDF_A_2:
case PDFWriter::PDFVersion::PDF_A_3:
case PDFWriter::PDFVersion::PDF_1_7:
return "1.7" ;
// PDF 2.0
case PDFWriter::PDFVersion::PDF_A_4:
case PDFWriter::PDFVersion::PDF_2_0:
return "2.0" ;
}
}
void computeDocumentIdentifier(std::vector<sal_uInt8>& o_rIdentifier,
const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
const OString& i_rCString1,
const css::util::DateTime& rCreationMetaDate, OString& o_rCString2)
{
o_rIdentifier.clear();
//build the document id
OString aInfoValuesOut;
OStringBuffer aID(1024);
if (!i_rDocInfo.Title.isEmpty())
COSWriter::appendUnicodeTextString(i_rDocInfo.Title, aID);
if (!i_rDocInfo.Author.isEmpty())
COSWriter::appendUnicodeTextString(i_rDocInfo.Author, aID);
if (!i_rDocInfo.Subject.isEmpty())
COSWriter::appendUnicodeTextString(i_rDocInfo.Subject, aID);
if (!i_rDocInfo.Keywords.isEmpty())
COSWriter::appendUnicodeTextString(i_rDocInfo.Keywords, aID);
if (!i_rDocInfo.Creator.isEmpty())
COSWriter::appendUnicodeTextString(i_rDocInfo.Creator, aID);
if (!i_rDocInfo.Producer.isEmpty())
COSWriter::appendUnicodeTextString(i_rDocInfo.Producer, aID);
TimeValue aTVal, aGMT;
oslDateTime aDT;
aDT.NanoSeconds = rCreationMetaDate.NanoSeconds;
aDT.Seconds = rCreationMetaDate.Seconds;
aDT.Minutes = rCreationMetaDate.Minutes;
aDT.Hours = rCreationMetaDate.Hours;
aDT.Day = rCreationMetaDate.Day;
aDT.Month = rCreationMetaDate.Month;
aDT.Year = rCreationMetaDate.Year;
osl_getSystemTime(&aGMT);
osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
OStringBuffer aCreationMetaDateString(64);
// i59651: we fill the Metadata date string as well, if PDF/A is requested
// according to ISO 19005-1:2005 6.7.3 the date is corrected for
// local time zone offset UTC only, whereas Acrobat 8 seems
// to use the localtime notation only
// according to a recommendation in XMP Specification (Jan 2004, page 75)
// the Acrobat way seems the right approach
aCreationMetaDateString.append(char ('0' + ((aDT.Year / 1000) % 10)));
aCreationMetaDateString.append(char ('0' + ((aDT.Year / 100) % 10)));
aCreationMetaDateString.append(char ('0' + ((aDT.Year / 10) % 10)));
aCreationMetaDateString.append(char ('0' + ((aDT.Year) % 10)));
aCreationMetaDateString.append("-" );
aCreationMetaDateString.append(char ('0' + ((aDT.Month / 10) % 10)));
aCreationMetaDateString.append(char ('0' + ((aDT.Month) % 10)));
aCreationMetaDateString.append("-" );
aCreationMetaDateString.append(char ('0' + ((aDT.Day / 10) % 10)));
aCreationMetaDateString.append(char ('0' + ((aDT.Day) % 10)));
aCreationMetaDateString.append("T" );
aCreationMetaDateString.append(char ('0' + ((aDT.Hours / 10) % 10)));
aCreationMetaDateString.append(char ('0' + ((aDT.Hours) % 10)));
aCreationMetaDateString.append(":" );
aCreationMetaDateString.append(char ('0' + ((aDT.Minutes / 10) % 10)));
aCreationMetaDateString.append(char ('0' + ((aDT.Minutes) % 10)));
aCreationMetaDateString.append(":" );
aCreationMetaDateString.append(char ('0' + ((aDT.Seconds / 10) % 10)));
aCreationMetaDateString.append(char ('0' + ((aDT.Seconds) % 10)));
sal_uInt32 nDelta = 0;
if (aGMT.Seconds > aTVal.Seconds)
{
nDelta = aGMT.Seconds - aTVal.Seconds;
aCreationMetaDateString.append("-" );
}
else if (aGMT.Seconds < aTVal.Seconds)
{
nDelta = aTVal.Seconds - aGMT.Seconds;
aCreationMetaDateString.append("+" );
}
else
{
aCreationMetaDateString.append("Z" );
}
if (nDelta)
{
aCreationMetaDateString.append(char ('0' + ((nDelta / 36000) % 10)));
aCreationMetaDateString.append(char ('0' + ((nDelta / 3600) % 10)));
aCreationMetaDateString.append(":" );
aCreationMetaDateString.append(char ('0' + ((nDelta / 600) % 6)));
aCreationMetaDateString.append(char ('0' + ((nDelta / 60) % 10)));
}
aID.append(i_rCString1.getStr(), i_rCString1.getLength());
aInfoValuesOut = aID.makeStringAndClear();
o_rCString2 = aCreationMetaDateString.makeStringAndClear();
::comphelper::Hash aDigest(::comphelper::HashType::MD5);
aDigest.update(reinterpret_cast <unsigned char const *>(&aGMT), sizeof (aGMT));
aDigest.update(reinterpret_cast <unsigned char const *>(aInfoValuesOut.getStr()),
aInfoValuesOut.getLength());
//the binary form of the doc id is needed for encryption stuff
o_rIdentifier = aDigest.finalize();
}
} // end anonymous namespace
PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
:
m_pWriter( pWriter ),
m_nPageWidth( nPageWidth ),
m_nPageHeight( nPageHeight ),
m_nUserUnit( 1 ),
m_eOrientation( eOrientation ),
m_nPageObject( 0 ), // invalid object number
m_nStreamLengthObject( 0 ),
m_nBeginStreamPos( 0 ),
m_eTransition( PDFWriter::PageTransition::Regular ),
m_nTransTime( 0 )
{
// object ref must be only ever updated in emit()
m_nPageObject = m_pWriter->createObject();
switch (m_pWriter->m_aContext.Version)
{
// 1.6 or later
default :
m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
break ;
case PDFWriter::PDFVersion::PDF_1_4:
case PDFWriter::PDFVersion::PDF_1_5:
case PDFWriter::PDFVersion::PDF_A_1:
break ;
}
}
void PDFPage::beginStream()
{
if (g_bDebugDisableCompression)
{
m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +" );
}
m_aStreamObjects.push_back(m_pWriter->createObject());
if ( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
return ;
m_nStreamLengthObject = m_pWriter->createObject();
// write content stream header
OStringBuffer aLine(
OString::number(m_aStreamObjects.back())
+ " 0 obj\n<
+ OString::number( m_nStreamLengthObject )
+ " 0 R" );
if (!g_bDebugDisableCompression)
aLine.append( "/Filter/FlateDecode" );
aLine.append( ">>\nstream\n" );
if ( ! m_pWriter->writeBuffer( aLine ) )
return ;
if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
{
m_pWriter->m_aFile.close();
m_pWriter->m_bOpen = false ;
}
if (!g_bDebugDisableCompression)
m_pWriter->beginCompression();
m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
}
void PDFPage::endStream()
{
if (!g_bDebugDisableCompression)
m_pWriter->endCompression();
sal_uInt64 nEndStreamPos;
if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
{
m_pWriter->m_aFile.close();
m_pWriter->m_bOpen = false ;
return ;
}
m_pWriter->disableStreamEncryption();
if ( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n" ) )
return ;
// emit stream length object
if ( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
return ;
OString aLine =
OString::number( m_nStreamLengthObject ) +
" 0 obj\n" +
OString::number( static_cast <sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
"\nendobj\n\n" ;
m_pWriter->writeBuffer( aLine );
}
bool PDFPage::emit(sal_Int32 nParentObject )
{
m_pWriter->MARK("PDFPage::emit" );
// emit page object
if ( ! m_pWriter->updateObject( m_nPageObject ) )
return false ;
OStringBuffer aLine(
OString::number(m_nPageObject)
+ " 0 obj\n"
"<
+ OString::number(nParentObject)
+ " 0 R"
"/Resources "
+ OString::number(m_pWriter->getResourceDictObj())
+ " 0 R" );
if ( m_nPageWidth && m_nPageHeight )
{
aLine.append( "/MediaBox[0 0 "
+ OString::number(m_nPageWidth / m_nUserUnit)
+ " "
+ OString::number(m_nPageHeight / m_nUserUnit)
+ "]" );
if (m_nUserUnit > 1)
{
aLine.append("\n/UserUnit " + OString::number(m_nUserUnit));
}
}
switch ( m_eOrientation )
{
case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break ;
case PDFWriter::Orientation::Inherit: break ;
}
int nAnnots = m_aAnnotations.size();
if ( nAnnots > 0 )
{
aLine.append( "/Annots[\n" );
for ( int i = 0; i < nAnnots; i++ )
{
aLine.append( OString::number(m_aAnnotations[i])
+ " 0 R" );
aLine.append( ((i+1)%15) ? " " : "\n" );
}
aLine.append( "]\n" );
}
if (PDFWriter::PDFVersion::PDF_1_5 <= m_pWriter->m_aContext.Version)
{
// ISO 14289-1:2014, Clause: 7.18.3 requires it if there are annotations
// but Adobe Acrobat Pro complains if it is ever missing so just
// write it always.
aLine.append( "/Tabs/S\n" );
}
if ( !m_aMCIDParents.empty() )
{
OStringBuffer aStructParents( 1024 );
aStructParents.append( "[ " );
int nParents = m_aMCIDParents.size();
for ( int i = 0; i < nParents; i++ )
{
aStructParents.append( OString::number(m_aMCIDParents[i])
+ " 0 R" );
aStructParents.append( ((i%10) == 9) ? "\n" : " " );
}
aStructParents.append( "]" );
m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
aLine.append( "/StructParents "
+ OString::number( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) )
+ "\n" );
}
if ( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
{
// transition duration
aLine.append( "/Trans< );
appendDouble( static_cast <double >(m_nTransTime)/1000.0, aLine, 3 );
aLine.append( "\n" );
const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
switch ( m_eTransition )
{
case PDFWriter::PageTransition::SplitHorizontalInward:
pStyle = "Split" ; pDm = "H" ; pM = "I" ; break ;
case PDFWriter::PageTransition::SplitHorizontalOutward:
pStyle = "Split" ; pDm = "H" ; pM = "O" ; break ;
case PDFWriter::PageTransition::SplitVerticalInward:
pStyle = "Split" ; pDm = "V" ; pM = "I" ; break ;
case PDFWriter::PageTransition::SplitVerticalOutward:
pStyle = "Split" ; pDm = "V" ; pM = "O" ; break ;
case PDFWriter::PageTransition::BlindsHorizontal:
pStyle = "Blinds" ; pDm = "H" ; break ;
case PDFWriter::PageTransition::BlindsVertical:
pStyle = "Blinds" ; pDm = "V" ; break ;
case PDFWriter::PageTransition::BoxInward:
pStyle = "Box" ; pM = "I" ; break ;
case PDFWriter::PageTransition::BoxOutward:
pStyle = "Box" ; pM = "O" ; break ;
case PDFWriter::PageTransition::WipeLeftToRight:
pStyle = "Wipe" ; pDi = "0" ; break ;
case PDFWriter::PageTransition::WipeBottomToTop:
pStyle = "Wipe" ; pDi = "90" ; break ;
case PDFWriter::PageTransition::WipeRightToLeft:
pStyle = "Wipe" ; pDi = "180" ; break ;
case PDFWriter::PageTransition::WipeTopToBottom:
pStyle = "Wipe" ; pDi = "270" ; break ;
case PDFWriter::PageTransition::Dissolve:
pStyle = "Dissolve" ; break ;
case PDFWriter::PageTransition::Regular:
break ;
}
// transition style
if ( pStyle )
{
aLine.append( OString::Concat("/S/" ) + pStyle + "\n" );
}
if ( pDm )
{
aLine.append( OString::Concat("/Dm/" ) + pDm + "\n" );
}
if ( pM )
{
aLine.append( OString::Concat("/M/" ) + pM + "\n" );
}
if ( pDi )
{
aLine.append( OString::Concat("/Di " ) + pDi + "\n" );
}
aLine.append( ">>\n" );
}
aLine.append( "/Contents" );
unsigned int nStreamObjects = m_aStreamObjects.size();
if ( nStreamObjects > 1 )
aLine.append( '[' );
for (sal_Int32 i : m_aStreamObjects)
{
aLine.append( " " + OString::number( i ) + " 0 R" );
}
if ( nStreamObjects > 1 )
aLine.append( ']' );
aLine.append( ">>\nendobj\n\n" );
return m_pWriter->writeBuffer( aLine );
}
void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
{
Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter,
rPoint ) );
sal_Int32 nValue = aPoint.X();
appendFixedInt( nValue, rBuffer );
rBuffer.append( ' ' );
nValue = pointToPixel(getHeight()) - aPoint.Y();
appendFixedInt( nValue, rBuffer );
}
void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
{
double fValue = pixelToPoint(rPoint.getX());
appendDouble( fValue, rBuffer, nLog10Divisor );
rBuffer.append( ' ' );
fValue = getHeight() - pixelToPoint(rPoint.getY());
appendDouble( fValue, rBuffer, nLog10Divisor );
}
void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
{
appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
rBuffer.append( ' ' );
appendMappedLength( static_cast <sal_Int32>(rRect.GetWidth()), rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( static_cast <sal_Int32>(rRect.GetHeight()), rBuffer );
rBuffer.append( " re" );
}
void PDFPage::convertRect( tools::Rectangle& rRect ) const
{
Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter,
rRect.BottomLeft() + Point( 0, 1 )
);
Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter,
rRect.GetSize() );
rRect.SetLeft( aLL.X() );
rRect.SetRight( aLL.X() + aSize.Width() );
rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
rRect.SetBottom( rRect.Top() + aSize.Height() );
}
void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
{
sal_uInt16 nPoints = rPoly.GetSize();
/*
* #108582# applications do weird things
*/
sal_uInt32 nBufLen = rBuffer.getLength();
if ( nPoints <= 0 )
return ;
const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
appendPoint( rPoly[0], rBuffer );
rBuffer.append( " m\n" );
for ( sal_uInt16 i = 1; i < nPoints; i++ )
{
if ( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
{
// bezier
SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter" , "unexpected sequence of control points" );
appendPoint( rPoly[i], rBuffer );
rBuffer.append( " " );
appendPoint( rPoly[i+1], rBuffer );
rBuffer.append( " " );
appendPoint( rPoly[i+2], rBuffer );
rBuffer.append( " c" );
i += 2; // add additionally consumed points
}
else
{
// line
appendPoint( rPoly[i], rBuffer );
rBuffer.append( " l" );
}
if ( (rBuffer.getLength() - nBufLen) > 65 )
{
rBuffer.append( "\n" );
nBufLen = rBuffer.getLength();
}
else
rBuffer.append( " " );
}
if ( bClose )
rBuffer.append( "h\n" );
}
void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
{
basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter,
rPoly ) );
if ( basegfx::utils::isRectangle( aPoly ) )
{
basegfx::B2DRange aRange( aPoly.getB2DRange() );
basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
appendPixelPoint( aBL, rBuffer );
rBuffer.append( ' ' );
appendMappedLength( aRange.getWidth(), rBuffer, false , nLog10Divisor );
rBuffer.append( ' ' );
appendMappedLength( aRange.getHeight(), rBuffer, true , nLog10Divisor );
rBuffer.append( " re\n" );
return ;
}
sal_uInt32 nPoints = aPoly.count();
if ( nPoints <= 0 )
return ;
sal_uInt32 nBufLen = rBuffer.getLength();
basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
appendPixelPoint( aLastPoint, rBuffer );
rBuffer.append( " m\n" );
for ( sal_uInt32 i = 1; i <= nPoints; i++ )
{
if ( i != nPoints || aPoly.isClosed() )
{
sal_uInt32 nCurPoint = i % nPoints;
sal_uInt32 nLastPoint = i-1;
basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
if ( aPoly.isNextControlPointUsed( nLastPoint ) &&
aPoly.isPrevControlPointUsed( nCurPoint ) )
{
appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
rBuffer.append( ' ' );
appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
rBuffer.append( ' ' );
appendPixelPoint( aPoint, rBuffer );
rBuffer.append( " c" );
}
else if ( aPoly.isNextControlPointUsed( nLastPoint ) )
{
appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
rBuffer.append( ' ' );
appendPixelPoint( aPoint, rBuffer );
rBuffer.append( " y" );
}
else if ( aPoly.isPrevControlPointUsed( nCurPoint ) )
{
appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
rBuffer.append( ' ' );
appendPixelPoint( aPoint, rBuffer );
rBuffer.append( " v" );
}
else
{
appendPixelPoint( aPoint, rBuffer );
rBuffer.append( " l" );
}
if ( (rBuffer.getLength() - nBufLen) > 65 )
{
rBuffer.append( "\n" );
nBufLen = rBuffer.getLength();
}
else
rBuffer.append( " " );
}
}
rBuffer.append( "h\n" );
}
void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
{
sal_uInt16 nPolygons = rPolyPoly.Count();
for ( sal_uInt16 n = 0; n < nPolygons; n++ )
appendPolygon( rPolyPoly[n], rBuffer );
}
void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
{
for (auto const & rPolygon : rPolyPoly)
appendPolygon( rPolygon, rBuffer );
}
void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
{
sal_Int32 nValue = nLength;
if ( nLength < 0 )
{
rBuffer.append( '-' );
nValue = -nLength;
}
Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter,
Size( nValue, nValue ) ) );
nValue = bVertical ? aSize.Height() : aSize.Width();
if ( pOutLength )
*pOutLength = ((nLength < 0 ) ? -nValue : nValue);
appendFixedInt( nValue, rBuffer );
}
void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
{
Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter,
Size( 1000, 1000 ) ) );
fLength *= pixelToPoint(static_cast <double >(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
appendDouble( fLength, rBuffer, nPrecision );
}
bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
{
if (LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
{
// dashed and non-degraded case, check for implementation limits of dash array
// in PDF reader apps (e.g. acroread)
if (2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
{
return false ;
}
}
if (basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
{
// LineJoin used, ExtLineInfo required
return false ;
}
if (css::drawing::LineCap_BUTT != rInfo.GetLineCap())
{
// LineCap used, ExtLineInfo required
return false ;
}
if ( rInfo.GetStyle() == LineStyle::Dash )
{
rBuffer.append( "[ " );
if ( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
{
appendMappedLength( rInfo.GetDashLen(), rBuffer );
rBuffer.append( ' ' );
appendMappedLength( rInfo.GetDistance(), rBuffer );
rBuffer.append( ' ' );
}
else
{
for ( int n = 0; n < rInfo.GetDashCount(); n++ )
{
appendMappedLength( rInfo.GetDashLen(), rBuffer );
rBuffer.append( ' ' );
appendMappedLength( rInfo.GetDistance(), rBuffer );
rBuffer.append( ' ' );
}
for ( int m = 0; m < rInfo.GetDotCount(); m++ )
{
appendMappedLength( rInfo.GetDotLen(), rBuffer );
rBuffer.append( ' ' );
appendMappedLength( rInfo.GetDistance(), rBuffer );
rBuffer.append( ' ' );
}
}
rBuffer.append( "] 0 d\n" );
}
if ( rInfo.GetWidth() > 1 )
{
appendMappedLength( rInfo.GetWidth(), rBuffer );
rBuffer.append( " w\n" );
}
else if ( rInfo.GetWidth() == 0 )
{
// "pixel" line
appendDouble( 72.0/double (m_pWriter->GetDPIX()), rBuffer );
rBuffer.append( " w\n" );
}
return true ;
}
void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
{
if ( nWidth <= 0 )
return ;
if ( nDelta < 1 )
nDelta = 1;
rBuffer.append( "0 " );
appendMappedLength( nY, rBuffer );
rBuffer.append( " m\n" );
for ( sal_Int32 n = 0; n < nWidth; )
{
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nDelta+nY, rBuffer );
rBuffer.append( ' ' );
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nY, rBuffer );
rBuffer.append( " v " );
if ( n < nWidth )
{
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nY-nDelta, rBuffer );
rBuffer.append( ' ' );
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nY, rBuffer );
rBuffer.append( " v\n" );
}
}
rBuffer.append( "S\n" );
}
void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer)
{
appendDouble(rMatrix.get(0), rBuffer);
rBuffer.append(' ' );
appendDouble(rMatrix.get(1), rBuffer);
rBuffer.append(' ' );
appendDouble(rMatrix.get(2), rBuffer);
rBuffer.append(' ' );
appendDouble(rMatrix.get(3), rBuffer);
rBuffer.append(' ' );
appendPoint(Point(tools::Long (rMatrix.get(4)), tools::Long (rMatrix.get(5))), rBuffer);
}
double PDFPage::getHeight() const
{
double fRet = m_nPageHeight ? m_nPageHeight : 842; // default A4 height in inch/72, OK to use hardcoded value here?
if (m_nUserUnit > 1)
{
fRet /= m_nUserUnit;
}
return fRet;
}
PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
const css::uno::Reference< css::beans::XMaterialHolder >& xEncryptionMaterialHolder,
PDFWriter& i_rOuterFace)
: VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF),
m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
m_nCurrentStructElement( 0 ),
m_bEmitStructure( true ),
m_nNextFID( 1 ),
m_aPDFBmpCache(comphelper::IsFuzzing() ? 15 :
officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()),
m_nCurrentPage( -1 ),
m_nCatalogObject(0),
m_nSignatureObject( -1 ),
m_nSignatureContentOffset( 0 ),
m_nSignatureLastByteRangeNoOffset( 0 ),
m_nResourceDict( -1 ),
m_nFontDictObject( -1 ),
m_aContext(rContext),
m_aFile(m_aContext.URL),
m_bOpen(false ),
m_DocDigest(::comphelper::HashType::MD5),
m_rOuterFace( i_rOuterFace )
{
m_aStructure.emplace_back( );
m_aStructure[0].m_nOwnElement = 0;
m_aStructure[0].m_nParentElement = 0;
//m_StructElementStack.push(0);
// tdf#150786 use the same settings for widgets regardless of theme
m_aWidgetStyleSettings.SetStandardStyles();
GraphicsState aState;
aState.m_aMapMode = m_aMapMode;
aState.m_aFont.SetFamilyName( u"Times" _ustr );
aState.m_aFont.SetFontSize( Size( 0, 12 ) );
m_aGraphicsStack.push_front( aState );
osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
if (aError != osl::File::E_None)
{
if (aError == osl::File::E_EXIST)
{
aError = m_aFile.open(osl_File_OpenFlag_Write);
if (aError == osl::File::E_None)
aError = m_aFile.setSize(0);
}
}
if (aError != osl::File::E_None)
return ;
m_bOpen = true ;
// setup DocInfo
setupDocInfo();
if (xEncryptionMaterialHolder.is())
{
if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0 || m_aContext.Version == PDFWriter::PDFVersion::PDF_A_4)
m_pPDFEncryptor.reset(new PDFEncryptorR6);
else
m_pPDFEncryptor.reset(new PDFEncryptor);
m_pPDFEncryptor->prepareEncryption(xEncryptionMaterialHolder, m_aContext.Encryption);
}
if (m_pPDFEncryptor && m_aContext.Encryption.canEncrypt())
{
m_pPDFEncryptor->setupKeysAndCheck(m_aContext.Encryption);
}
// write header
OStringBuffer aBuffer( 20 );
aBuffer.append( "%PDF-" );
aBuffer.append(getPDFVersionStr(m_aContext.Version));
// append something binary as comment (suggested in PDF Reference)
aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
if ( !writeBuffer( aBuffer ) )
{
m_aFile.close();
m_bOpen = false ;
return ;
}
// insert outline root
m_aOutline.emplace_back( );
switch (m_aContext.Version)
{
case PDFWriter::PDFVersion::PDF_A_1:
m_nPDFA_Version = 1;
m_bIsPDF_A1 = true ;
m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
break ;
case PDFWriter::PDFVersion::PDF_A_2:
m_nPDFA_Version = 2;
m_bIsPDF_A2 = true ;
m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
break ;
case PDFWriter::PDFVersion::PDF_A_3:
m_nPDFA_Version = 3;
m_bIsPDF_A3 = true ;
m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
break ;
case PDFWriter::PDFVersion::PDF_A_4:
m_nPDFA_Version = 4;
m_bIsPDF_A4 = true ;
m_aContext.Version = PDFWriter::PDFVersion::PDF_2_0;
break ;
default :
break ;
}
// PDF/UA can only be enabled if PDF version is 1.7 (PDF/UA-1) and 2.0 (PDF/UA-2)
if (m_aContext.UniversalAccessibilityCompliance && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_7)
{
m_bIsPDF_UA = true ;
m_aContext.Tagged = true ;
}
// Add common PDF 2.0 namespace when we are using PDF 2.0
if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0)
{
m_aNamespacesMap.emplace(constNamespacePDF2, createObject());
}
if ( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
else
SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
SetOutputSizePixel( Size( 640, 480 ) );
SetMapMode(MapMode(MapUnit::MapMM));
}
PDFWriterImpl::~PDFWriterImpl()
{
disposeOnce();
}
void PDFWriterImpl::dispose()
{
m_aPages.clear();
VirtualDevice::dispose();
}
bool PDFWriterImpl::ImplNewFont() const
{
const ImplSVData* pSVData = ImplGetSVData();
if ( mxFontCollection == pSVData->maGDIData.mxScreenFontList
|| mxFontCache == pSVData->maGDIData.mxScreenFontCache )
{
const_cast <vcl::PDFWriterImpl&>(*this ).ImplUpdateFontData();
}
return OutputDevice::ImplNewFont();
}
void PDFWriterImpl::setupDocInfo()
{
std::vector< sal_uInt8 > aId;
m_aCreationDateString = PDFWriter::GetDateTime();
computeDocumentIdentifier(aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString);
if ( m_aContext.Encryption.DocumentIdentifier.empty() )
m_aContext.Encryption.DocumentIdentifier = std::move(aId);
}
OString PDFWriter::GetDateTime(svl::crypto::SigningContext* pSigningContext)
{
OStringBuffer aRet;
TimeValue aTVal, aGMT;
oslDateTime aDT;
osl_getSystemTime(&aGMT);
if (pSigningContext)
{
// The context unit is milliseconds, TimeValue is seconds + nanoseconds.
if (pSigningContext->m_nSignatureTime)
{
aGMT = std::chrono::milliseconds(pSigningContext->m_nSignatureTime);
}
else
{
pSigningContext->m_nSignatureTime = static_cast <sal_Int64>(aGMT.Seconds) * 1000 + aGMT.Nanosec / 1000000;
}
}
osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
osl_getDateTimeFromTimeValue(&aTVal, &aDT);
sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
aRet.append("'" );
return aRet.makeStringAndClear();
}
void PDFWriterImpl::emitComment( const char * pComment )
{
OString aLine = OString::Concat("% " ) + pComment + "\n" ;
writeBuffer( aLine );
}
bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
{
if (!g_bDebugDisableCompression)
{
sal_uInt64 nEndPos = pStream->TellEnd();
pStream->Seek( STREAM_SEEK_TO_BEGIN );
ZCodec aCodec( 0x4000, 0x4000 );
SvMemoryStream aStream;
aCodec.BeginCompression();
aCodec.Write( aStream, static_cast <const sal_uInt8*>(pStream->GetData()), nEndPos );
aCodec.EndCompression();
nEndPos = aStream.Tell();
pStream->Seek( STREAM_SEEK_TO_BEGIN );
aStream.Seek( STREAM_SEEK_TO_BEGIN );
pStream->SetStreamSize( nEndPos );
pStream->WriteBytes( aStream.GetData(), nEndPos );
return true ;
}
else
return false ;
}
void PDFWriterImpl::beginCompression()
{
if (!g_bDebugDisableCompression)
{
m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
m_pMemStream = std::make_unique<SvMemoryStream>();
m_pCodec->BeginCompression();
}
}
void PDFWriterImpl::endCompression()
{
if (!g_bDebugDisableCompression && m_pCodec)
{
m_pCodec->EndCompression();
m_pCodec.reset();
sal_uInt64 nLen = m_pMemStream->Tell();
m_pMemStream->Seek( 0 );
(void )writeBufferBytes( m_pMemStream->GetData(), nLen );
m_pMemStream.reset();
}
}
bool PDFWriterImpl::writeBufferBytes( const void * pBuffer, sal_uInt64 nBytes )
{
if ( ! m_bOpen ) // we are already down the drain
return false ;
if ( ! nBytes ) // huh ?
return true ;
if ( !m_aOutputStreams.empty() )
{
m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
m_aOutputStreams.front().m_pStream->WriteBytes(
pBuffer, sal::static_int_cast<std::size_t>(nBytes));
return true ;
}
sal_uInt64 nWritten;
sal_uInt64 nActualSize = nBytes;
// we are compressing the stream
if (m_pCodec)
{
m_pCodec->Write( *m_pMemStream, static_cast <const sal_uInt8*>(pBuffer), static_cast <sal_uLong>(nBytes) );
nWritten = nBytes;
}
else
{
// is it encrypted?
bool bStreamEncryption = m_pPDFEncryptor && m_pPDFEncryptor->isStreamEncryptionEnabled();
if (bStreamEncryption)
{
nActualSize = m_pPDFEncryptor->calculateSizeIncludingHeader(nActualSize);
m_vEncryptionBuffer.resize(nActualSize);
m_pPDFEncryptor->encrypt(pBuffer, nBytes, m_vEncryptionBuffer, nActualSize);
}
const void * pWriteBuffer = bStreamEncryption ? m_vEncryptionBuffer.data() : pBuffer;
m_DocDigest.update(static_cast <unsigned char const *>(pWriteBuffer), sal_uInt32(nActualSize));
if (m_aFile.write(pWriteBuffer, nActualSize, nWritten) != osl::File::E_None)
nWritten = 0;
if (nWritten != nActualSize)
{
m_aFile.close();
m_bOpen = false ;
return false ;
}
return true ;
}
return nWritten == nActualSize;
}
void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
{
endPage();
m_nCurrentPage = m_aPages.size();
m_aPages.emplace_back(this , nPageWidth, nPageHeight, eOrientation );
const Fraction frac(m_aPages.back().m_nUserUnit, pointToPixel(1));
m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac);
m_aPages.back().beginStream();
// setup global graphics state
// linewidth is "1 pixel" by default
OStringBuffer aBuf( 16 );
appendDouble( 72.0/double (GetDPIX()), aBuf );
aBuf.append( " w\n" );
writeBuffer( aBuf );
}
void PDFWriterImpl::endPage()
{
if ( m_aPages.empty() )
return ;
// close eventual MC sequence
endStructureElementMCSeq();
// sanity check
if ( !m_aOutputStreams.empty() )
{
OSL_FAIL( "redirection across pages !!!" );
m_aOutputStreams.clear(); // leak !
m_aMapMode.SetOrigin( Point() );
}
m_aGraphicsStack.clear();
m_aGraphicsStack.emplace_back( );
// this should pop the PDF graphics stack if necessary
updateGraphicsState();
m_aPages.back().endStream();
// reset the default font
Font aFont;
aFont.SetFamilyName( u"Times" _ustr );
aFont.SetFontSize( Size( 0, 12 ) );
m_aCurrentPDFState = m_aGraphicsStack.front();
m_aGraphicsStack.front().m_aFont = std::move(aFont);
for (auto & bitmap : m_aBitmaps)
{
if ( ! bitmap.m_aBitmap.IsEmpty() )
{
writeBitmapObject(bitmap);
bitmap.m_aBitmap = BitmapEx();
}
}
for (auto & jpeg : m_aJPGs)
{
if ( jpeg.m_pStream )
{
writeJPG( jpeg );
jpeg.m_pStream.reset();
jpeg.m_aAlphaMask = AlphaMask();
}
}
for (auto & item : m_aTransparentObjects)
{
if ( item.m_pContentStream )
{
writeTransparentObject(item);
item.m_pContentStream.reset();
}
}
}
sal_Int32 PDFWriterImpl::createObject()
{
m_aObjects.push_back( ~0U );
return m_aObjects.size();
}
bool PDFWriterImpl::updateObject( sal_Int32 n )
{
if ( ! m_bOpen )
return false ;
sal_uInt64 nOffset = ~0U;
osl::File::RC aError = m_aFile.getPos(nOffset);
SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter" , "could not register object" );
if (aError != osl::File::E_None)
{
m_aFile.close();
m_bOpen = false ;
}
m_aObjects[ n-1 ] = nOffset;
return aError == osl::File::E_None;
}
sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
{
if ( nObject > 0 )
{
OStringBuffer aLine( 1024 );
aLine.append( OString::number(nObject)
+ " 0 obj\n"
"< );
sal_Int32 nTreeItems = m_aStructParentTree.size();
for ( sal_Int32 n = 0; n < nTreeItems; n++ )
{
aLine.append( OString::number(n) + " "
+ m_aStructParentTree[n]
+ "\n" );
}
aLine.append( "]>>\nendobj\n\n" );
if (!updateObject(nObject))
return 0;
if (!writeBuffer(aLine))
return 0;
}
return nObject;
}
// every structure element already has a unique object id - just use it for ID
static OString GenerateID(sal_Int32 const nObjectId)
{
return "id" + OString::number(nObjectId);
}
sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject)
{
// loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 10.15
if (nObject < 0)
{
return nObject;
}
// the name tree entries must be sorted lexicographically.
std::map<OString, sal_Int32> ids;
for (auto n : m_StructElemObjsWithID)
{
ids.emplace(GenerateID(n), n);
}
OStringBuffer buf;
COSWriter aWriter(buf, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
appendObjectID(nObject, buf);
buf.append("<);
for (auto const & it : ids)
{
aWriter.writeLiteralEncrypt(it.first, nObject);
buf.append(" " );
appendObjectReference(it.second, buf);
buf.append("\n" );
}
buf.append("] >>\nendobj\n\n" );
if (!updateObject(nObject)) return 0;
if (!writeBuffer(buf)) return 0;
return nObject;
}
const char * PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
{
static constexpr auto aAttributeStrings = frozen::make_unordered_map<PDFWriter::StructAttribute, const char *>({
{ PDFWriter::Placement, "Placement" },
{ PDFWriter::WritingMode, "WritingMode" },
{ PDFWriter::SpaceBefore, "SpaceBefore" },
{ PDFWriter::SpaceAfter, "SpaceAfter" },
{ PDFWriter::StartIndent, "StartIndent" },
{ PDFWriter::EndIndent, "EndIndent" },
{ PDFWriter::TextIndent, "TextIndent" },
{ PDFWriter::TextAlign, "TextAlign" },
{ PDFWriter::Width, "Width" },
{ PDFWriter::Height, "Height" },
{ PDFWriter::BlockAlign, "BlockAlign" },
{ PDFWriter::InlineAlign, "InlineAlign" },
{ PDFWriter::LineHeight, "LineHeight" },
{ PDFWriter::BaselineShift, "BaselineShift" },
{ PDFWriter::TextDecorationType,"TextDecorationType" },
{ PDFWriter::ListNumbering, "ListNumbering" },
{ PDFWriter::RowSpan, "RowSpan" },
{ PDFWriter::ColSpan, "ColSpan" },
{ PDFWriter::Scope, "Scope" },
{ PDFWriter::Role, "Role" },
{ PDFWriter::RubyAlign, "RubyAlign" },
{ PDFWriter::RubyPosition, "RubyPosition" },
{ PDFWriter::Type, "Type" },
{ PDFWriter::Subtype, "Subtype" },
{ PDFWriter::LinkAnnotation, "LinkAnnotation" },
{ PDFWriter::NoteAnnotation, "NoteAnnotation" }
});
auto it = aAttributeStrings.find( eAttr );
if ( it == aAttributeStrings.end() )
SAL_INFO("vcl.pdfwriter" , "invalid PDFWriter::StructAttribute " << eAttr);
return it != aAttributeStrings.end() ? it->second : "" ;
}
const char * PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
{
static constexpr auto aValueStrings = frozen::make_unordered_map<PDFWriter::StructAttributeValue, const char *>({
{ PDFWriter::NONE, "None" },
{ PDFWriter::Block, "Block" },
{ PDFWriter::Inline , "Inline" },
{ PDFWriter::Before, "Before" },
{ PDFWriter::After, "After" },
{ PDFWriter::Start, "Start" },
{ PDFWriter::End, "End" },
{ PDFWriter::LrTb, "LrTb" },
{ PDFWriter::RlTb, "RlTb" },
{ PDFWriter::TbRl, "TbRl" },
{ PDFWriter::Center, "Center" },
{ PDFWriter::Justify, "Justify" },
{ PDFWriter::Auto , "Auto" },
{ PDFWriter::Middle, "Middle" },
{ PDFWriter::Normal, "Normal" },
{ PDFWriter::Underline, "Underline" },
{ PDFWriter::Overline, "Overline" },
{ PDFWriter::LineThrough,"LineThrough" },
{ PDFWriter::Row, "Row" },
{ PDFWriter::Column, "Column" },
{ PDFWriter::Both, "Both" },
{ PDFWriter::Pagination, "Pagination" },
{ PDFWriter::Layout, "Layout" },
{ PDFWriter::Page, "Page" },
{ PDFWriter::Background, "Background" },
{ PDFWriter::Header, "Header" },
{ PDFWriter::Footer, "Footer" },
{ PDFWriter::Watermark, "Watermark" },
{ PDFWriter::Rb, "rb" },
{ PDFWriter::Cb, "cb" },
{ PDFWriter::Pb, "pb" },
{ PDFWriter::Tv, "tv" },
{ PDFWriter::RStart, "Start" },
{ PDFWriter::RCenter, "Center" },
{ PDFWriter::REnd, "End" },
{ PDFWriter::RJustify, "Justify" },
{ PDFWriter::RDistribute,"Distribute" },
{ PDFWriter::RBefore, "Before" },
{ PDFWriter::RAfter, "After" },
{ PDFWriter::RWarichu, "Warichu" },
{ PDFWriter::RInline, "Inline" },
{ PDFWriter::Disc, "Disc" },
{ PDFWriter::Circle, "Circle" },
{ PDFWriter::Square, "Square" },
{ PDFWriter::Decimal, "Decimal" },
{ PDFWriter::UpperRoman, "UpperRoman" },
{ PDFWriter::LowerRoman, "LowerRoman" },
{ PDFWriter::UpperAlpha, "UpperAlpha" },
{ PDFWriter::LowerAlpha, "LowerAlpha" }
});
auto it = aValueStrings.find( eVal );
if ( it == aValueStrings.end() )
SAL_INFO("vcl.pdfwriter" , "invalid PDFWriter::StructAttributeValue " << eVal);
return it != aValueStrings.end() ? it->second : "" ;
}
static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
{
o_rLine.append( "/" );
o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
if ( i_rVal.eValue != PDFWriter::Invalid )
{
o_rLine.append( "/" );
o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
}
else
{
// numerical value
o_rLine.append( " " );
if ( i_bIsFixedInt )
appendFixedInt( i_rVal.nValue, o_rLine );
else
o_rLine.append( i_rVal.nValue );
}
o_rLine.append( "\n" );
}
template <typename T>
void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot)
{
// update struct parent of link
OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R" );
m_aStructParentTree.push_back( aStructParentEntry );
rAnnot.m_nStructParent = m_aStructParentTree.size()-1;
sal_Int32 const nAnnotObj(rAnnot.m_nObject);
i_rEle.m_aKids.emplace_back(ObjReferenceObj{nAnnotObj});
}
OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
{
// create layout, list and table attribute sets
OStringBuffer aLayout(256), aList(64), aTable(64);
OStringBuffer aPrintField;
for (auto const & attribute : i_rEle.m_aAttributes)
{
if ( attribute.first == PDFWriter::ListNumbering )
appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
else if (attribute.first == PDFWriter::Role)
{
appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, true );
}
else if ( attribute.first == PDFWriter::RowSpan ||
attribute.first == PDFWriter::ColSpan ||
attribute.first == PDFWriter::Scope)
{
appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
}
else if ( attribute.first == PDFWriter::LinkAnnotation )
{
sal_Int32 nLink = attribute.second.nValue;
std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
m_aLinkPropertyMap.find( nLink );
if ( link_it != m_aLinkPropertyMap.end() )
nLink = link_it->second;
if ( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() )
{
AppendAnnotKid(i_rEle, m_aLinks[nLink]);
}
else
{
OSL_FAIL( "unresolved link id for Link structure" );
SAL_INFO("vcl.pdfwriter" , "unresolved link id " << nLink << " for Link structure" );
if (g_bDebugDisableCompression)
{
OString aLine = "unresolved link id " +
OString::number( nLink ) +
" for Link structure" ;
emitComment( aLine.getStr() );
}
}
}
else if (attribute.first == PDFWriter::NoteAnnotation)
{
sal_Int32 nNote = attribute.second.nValue;
std::map<sal_Int32, sal_Int32>::const_iterator link_it = m_aLinkPropertyMap.find(nNote);
if (link_it != m_aLinkPropertyMap.end())
nNote = link_it->second;
if (nNote >= 0 && o3tl::make_unsigned(nNote) < m_aNotes.size())
{
AppendAnnotKid(i_rEle, m_aNotes[nNote]);
}
else
{
OSL_FAIL("unresolved note id for Note structure" );
SAL_INFO("vcl.pdfwriter" , "unresolved note id " << nNote << " for Note structure" );
if (g_bDebugDisableCompression)
{
OString aLine
= "unresolved note id " + OString::number(nNote) + " for Note structure" ;
emitComment(aLine.getStr());
}
}
}
else
appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
}
if ( ! i_rEle.m_aBBox.IsEmpty() )
{
aLayout.append( "/BBox[" );
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=96 H=92 G=93
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland