Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/vcl/source/gdi/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 443 kB image not shown  

Quelle  pdfwriter_impl.cxx   Sprache: C

 
/* -*- 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();
    forint 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, OutputDevice* _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 );
    forint 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&&nbsp;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" );
        forint 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();
        forint 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&&nbsp;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
        {
            forint n = 0; n < rInfo.GetDashCount(); n++ )
            {
                appendMappedLength( rInfo.GetDashLen(), rBuffer );
                rBuffer.append( ' ' );
                appendMappedLength( rInfo.GetDistance(), rBuffer );
                rBuffer.append( ' ' );
            }
            forint 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






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.