/* -*- 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/types.h>
#include <sal/log.hxx>
#include <osl/file.hxx>
#include <rtl/strbuf.hxx>
#include <rtl/string.hxx>
#include <tools/gen.hxx>
#include <tools/long.hxx>
#include <tools/poly.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/vector/b2enums.hxx>
#include <vcl/lineinfo.hxx>
#include <vcl/pdfwriter.hxx>
#include <pdf/Matrix3.hxx>
#include <pdf/pdfwriter_impl.hxx>
#include "pdfwriter_utils.hxx"
#include <vector>
using namespace::com::sun::star;
static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
namespace vcl
{
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<</Length "
+ 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"
"<</Type/Page/Parent "
+ 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<</D " );
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( 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
{
const Point aLL = convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter,
rRect.BottomLeft() + Point( 0, 1 )
);
const Size aSize = 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
{
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( 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( 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( 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;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.