/* -*- 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 <config_features.h>
#include <outputwrap.hxx>
#include <contentsink.hxx>
#include <pdfihelper.hxx>
#include <wrapper.hxx>
#include <pdfparse.hxx>
#include <../pdfiadaptor.hxx>
#include <rtl/math.hxx>
#include <osl/file.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/string.hxx>
#include <cppunit/TestAssert.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/plugin/TestPlugIn.h>
#include <test/bootstrapfixture.hxx>
#include <test/xmltesttools.hxx>
#include <com/sun/star/geometry/RealRectangle2D.hpp>
#include <com/sun/star/geometry/RealSize2D.hpp>
#include <com/sun/star/rendering/PathJoinType.hpp>
#include <com/sun/star/rendering/PathCapType.hpp>
#include <com/sun/star/rendering/BlendMode.hpp>
#include <basegfx/utils/canvastools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolygonclipper.hxx>
#include <unordered_map>
#include <vector>
#include <rtl/ustring.hxx>
#include <rtl/ref.hxx>
using namespace ::pdfi;
using namespace ::com::sun::star;
namespace
{
class TestSink : public ContentSink
{
public:
TestSink() :
m_nNextFontId( 1 ),
m_aIdToFont(),
m_aFontToId(),
m_aGCStack(1),
m_aPageSize(),
m_aHyperlinkBounds(),
m_aURI(),
m_aTextOut(),
m_nNumPages(0),
m_bPageEnded(false),
m_bRedCircleSeen(false),
m_bGreenStrokeSeen(false),
m_bDashedLineSeen(false),
m_bImageSeen(false)
{}
void check()
{
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "A4 page size (in 100th of points): Width", 79400, m_aPageSize.Width, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "A4 page size (in 100th of points): Height", 59500, m_aPageSize.Height, 0.0000001 );
CPPUNIT_ASSERT_MESSAGE( "endPage() called", m_bPageEnded );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Num pages equal one", sal_Int32(1), m_nNumPages );
CPPUNIT_ASSERT_MESSAGE( "Correct hyperlink bounding box",
rtl::math::approxEqual(m_aHyperlinkBounds.X1,34.7 ) );
CPPUNIT_ASSERT_MESSAGE( "Correct hyperlink bounding box",
rtl::math::approxEqual(m_aHyperlinkBounds.Y1,386.0) );
CPPUNIT_ASSERT_MESSAGE( "Correct hyperlink bounding box",
rtl::math::approxEqual(m_aHyperlinkBounds.X2,166.7) );
CPPUNIT_ASSERT_MESSAGE( "Correct hyperlink bounding box",
rtl::math::approxEqual(m_aHyperlinkBounds.Y2,406.2) );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Correct hyperlink URI", u"http://download.openoffice.org/"_ustr, m_aURI );
const char* const sText = " \n \nThis is a testtext\nNew paragraph,\nnew line\n"
"Hyperlink, this is\n?\nThis is more text\noutline mode\n?\nNew paragraph\n";
OString aTmp;
m_aTextOut.makeStringAndClear().convertToString( &aTmp,
RTL_TEXTENCODING_ASCII_US,
OUSTRING_TO_OSTRING_CVTFLAGS );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Imported text is \"This is a testtext New paragraph, new line"
" Hyperlink, this is * This is more text outline mode * New paragraph\"",
aTmp, OString(sText) );
CPPUNIT_ASSERT_MESSAGE( "red circle seen in input", m_bRedCircleSeen );
CPPUNIT_ASSERT_MESSAGE( "green stroke seen in input", m_bGreenStrokeSeen );
CPPUNIT_ASSERT_MESSAGE( "dashed line seen in input", m_bDashedLineSeen );
CPPUNIT_ASSERT_MESSAGE( "image seen in input", m_bImageSeen );
}
private:
GraphicsContext& getCurrentContext() { return m_aGCStack.back(); }
// ContentSink interface implementation
virtual void setPageNum( sal_Int32 nNumPages ) override
{
m_nNumPages = nNumPages;
}
virtual void startPage( const geometry::RealSize2D& rSize ) override
{
m_aPageSize = rSize;
}
virtual void endPage() override
{
m_bPageEnded = true;
}
virtual void hyperLink( const geometry::RealRectangle2D& rBounds,
const OUString& rURI ) override
{
m_aHyperlinkBounds = rBounds;
m_aURI = rURI;
}
virtual void pushState() override
{
GraphicsContextStack::value_type const a(m_aGCStack.back());
m_aGCStack.push_back(a);
}
virtual void popState() override
{
m_aGCStack.pop_back();
}
virtual void setTransformation( const geometry::AffineMatrix2D& rMatrix ) override
{
basegfx::unotools::homMatrixFromAffineMatrix(
getCurrentContext().Transformation,
rMatrix );
}
virtual void setLineDash( const uno::Sequence<double>& dashes,
double start ) override
{
GraphicsContext& rContext( getCurrentContext() );
if( dashes.hasElements() )
comphelper::sequenceToContainer(rContext.DashArray,dashes);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "line dashing start offset", 0.0, start, 0.000000001 );
}
virtual void setFlatness( double nFlatness ) override
{
getCurrentContext().Flatness = nFlatness;
}
virtual void setLineJoin(basegfx::B2DLineJoin nJoin) override
{
getCurrentContext().LineJoin = nJoin;
}
virtual void setLineCap(sal_Int8 nCap) override
{
getCurrentContext().LineCap = nCap;
}
virtual void setMiterLimit(double nVal) override
{
getCurrentContext().MiterLimit = nVal;
}
virtual void setLineWidth(double nVal) override
{
getCurrentContext().LineWidth = nVal;
}
virtual void setFillColor( const rendering::ARGBColor& rColor ) override
{
getCurrentContext().FillColor = rColor;
}
virtual void setStrokeColor( const rendering::ARGBColor& rColor ) override
{
getCurrentContext().LineColor = rColor;
}
virtual void setFont( const FontAttributes& rFont ) override
{
FontToIdMap::const_iterator it = m_aFontToId.find( rFont );
if( it != m_aFontToId.end() )
getCurrentContext().FontId = it->second;
else
{
m_aFontToId[ rFont ] = m_nNextFontId;
m_aIdToFont[ m_nNextFontId ] = rFont;
getCurrentContext().FontId = m_nNextFontId;
m_nNextFontId++;
}
}
virtual void strokePath( const uno::Reference<rendering::XPolyPolygon2D>& rPath ) override
{
GraphicsContext& rContext( getCurrentContext() );
basegfx::B2DPolyPolygon aPath = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath);
aPath.transform( rContext.Transformation );
if( rContext.DashArray.empty() )
{
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is green", 1.0, rContext.LineColor.Alpha, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is green", 0.0, rContext.LineColor.Blue, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is green", 1.0, rContext.LineColor.Green, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is green", 0.0, rContext.LineColor.Red, 0.00000001);
CPPUNIT_ASSERT_MESSAGE( "Line width is 0",
rtl::math::approxEqual(rContext.LineWidth, 28.3) );
static constexpr OUString sExportString = u"m53570 7650-35430 24100"_ustr;
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Stroke is m535.7 518.5-354.3-241",
sExportString, basegfx::utils::exportToSvgD( aPath, true, true, false ) );
m_bGreenStrokeSeen = true;
}
else
{
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Dash array consists of four entries", std::vector<double>::size_type(4), rContext.DashArray.size());
CPPUNIT_ASSERT_DOUBLES_EQUAL( 14.3764, rContext.DashArray[0], 1E-12 );
CPPUNIT_ASSERT_DOUBLES_EQUAL( rContext.DashArray[0], rContext.DashArray[1], 1E-12 );
CPPUNIT_ASSERT_DOUBLES_EQUAL( rContext.DashArray[1], rContext.DashArray[2], 1E-12 );
CPPUNIT_ASSERT_DOUBLES_EQUAL( rContext.DashArray[2], rContext.DashArray[3], 1E-12 );
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 1.0, rContext.LineColor.Alpha, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 0.0, rContext.LineColor.Blue, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 0.0, rContext.LineColor.Green, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 0.0, rContext.LineColor.Red, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line width is 0",
0, rContext.LineWidth, 0.0000001 );
static constexpr OUString sExportString = u"m49890 5670.00000000001-35430 24090"_ustr;
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Stroke is m49890 5670.00000000001-35430 24090",
sExportString, basegfx::utils::exportToSvgD( aPath, true, true, false ) );
m_bDashedLineSeen = true;
}
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Blend mode is normal",
rendering::BlendMode::NORMAL, rContext.BlendMode );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Join type is round",
basegfx::B2DLineJoin::Round, rContext.LineJoin );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Cap type is butt",
rendering::PathCapType::BUTT, rContext.LineCap );
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line miter limit is 10",
10, rContext.MiterLimit, 0.0000001 );
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Flatness is 0",
1, rContext.Flatness, 0.00000001 );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Font id is 0",
sal_Int32(0), rContext.FontId );
}
virtual void fillPath( const uno::Reference<rendering::XPolyPolygon2D>& rPath ) override
{
GraphicsContext& rContext( getCurrentContext() );
basegfx::B2DPolyPolygon aPath = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath);
aPath.transform( rContext.Transformation );
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 1.0, rContext.LineColor.Alpha, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 0.0, rContext.LineColor.Blue, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 0.0, rContext.LineColor.Green, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 0.0, rContext.LineColor.Red, 0.00000001);
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Blend mode is normal",
rendering::BlendMode::NORMAL, rContext.BlendMode );
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Flatness is 10",
10, rContext.Flatness, 0.00000001 );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Font id is 0",
sal_Int32(0), rContext.FontId );
}
virtual void eoFillPath( const uno::Reference<rendering::XPolyPolygon2D>& rPath ) override
{
GraphicsContext& rContext( getCurrentContext() );
basegfx::B2DPolyPolygon aPath = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath);
aPath.transform( rContext.Transformation );
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 1.0, rContext.LineColor.Alpha, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 0.0, rContext.LineColor.Blue, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 0.0, rContext.LineColor.Green, 0.00000001);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Line color is black", 0.0, rContext.LineColor.Red, 0.00000001);
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Blend mode is normal",
rendering::BlendMode::NORMAL, rContext.BlendMode );
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Flatness is 0",
1, rContext.Flatness, 0.00000001 );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Font id is 0",
sal_Int32(0), rContext.FontId );
static constexpr OUString sExportString
= u"m12050 49610c-4310 0-7800-3490-7800-7800 0-4300 "
"3490-7790 7800-7790 4300 0 7790 3490 7790 7790 0 4310-3490 7800-7790 7800z"_ustr;
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Stroke is a 4-bezier circle",
sExportString, basegfx::utils::exportToSvgD( aPath, true, true, false ) );
m_bRedCircleSeen = true;
}
virtual void intersectClip(const uno::Reference<rendering::XPolyPolygon2D>& rPath) override
{
basegfx::B2DPolyPolygon aNewClip = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath);
basegfx::B2DPolyPolygon aCurClip = getCurrentContext().Clip;
if( aCurClip.count() ) // #i92985# adapted API from (..., false, false) to (..., true, false)
aNewClip = basegfx::utils::clipPolyPolygonOnPolyPolygon( aCurClip, aNewClip, true, false );
getCurrentContext().Clip = aNewClip;
}
virtual void intersectEoClip(const uno::Reference<rendering::XPolyPolygon2D>& rPath) override
{
basegfx::B2DPolyPolygon aNewClip = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath);
basegfx::B2DPolyPolygon aCurClip = getCurrentContext().Clip;
if( aCurClip.count() ) // #i92985# adapted API from (..., false, false) to (..., true, false)
aNewClip = basegfx::utils::clipPolyPolygonOnPolyPolygon( aCurClip, aNewClip, true, false );
getCurrentContext().Clip = aNewClip;
}
virtual void intersectClipToStroke(const uno::Reference<rendering::XPolyPolygon2D>& /* rPath */) override
{
// Not copying the contents of this, unlike the other clip functions above
// it's too complex to copy in, and I don't think the clip is actually used in the test
}
virtual void drawGlyphs( const OUString& rGlyphs,
const geometry::RealRectangle2D& /*rRect*/,
const geometry::Matrix2D& /*rFontMatrix*/,
double /*fontSize*/) override
{
m_aTextOut.append(rGlyphs);
}
virtual void endText() override
{
m_aTextOut.append( "\n" );
}
virtual void drawMask(const uno::Sequence<beans::PropertyValue>& xBitmap,
bool /*bInvert*/ ) override
{
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawMask received two properties",
sal_Int32(3), xBitmap.getLength() );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawMask got URL param",
u"URL"_ustr, xBitmap[0].Name );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawMask got InputStream param",
u"InputStream"_ustr, xBitmap[1].Name );
}
virtual void drawImage(const uno::Sequence<beans::PropertyValue>& xBitmap ) override
{
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawImage received two properties",
sal_Int32(3), xBitmap.getLength() );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawImage got URL param",
u"URL"_ustr, xBitmap[0].Name );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawImage got InputStream param",
u"InputStream"_ustr, xBitmap[1].Name );
m_bImageSeen = true;
}
virtual void drawColorMaskedImage(const uno::Sequence<beans::PropertyValue>& xBitmap,
const uno::Sequence<uno::Any>& /*xMaskColors*/ ) override
{
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawColorMaskedImage received two properties",
sal_Int32(3), xBitmap.getLength() );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawColorMaskedImage got URL param",
u"URL"_ustr, xBitmap[0].Name );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawColorMaskedImage got InputStream param",
u"InputStream"_ustr, xBitmap[1].Name );
}
virtual void drawMaskedImage(const uno::Sequence<beans::PropertyValue>& xBitmap,
const uno::Sequence<beans::PropertyValue>& xMask,
bool /*bInvertMask*/) override
{
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawMaskedImage received two properties #1",
sal_Int32(3), xBitmap.getLength() );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawMaskedImage got URL param #1",
u"URL"_ustr, xBitmap[0].Name );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawMaskedImage got InputStream param #1",
u"InputStream"_ustr, xBitmap[1].Name );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawMaskedImage received two properties #2",
sal_Int32(3), xMask.getLength() );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawMaskedImage got URL param #2",
u"URL"_ustr, xMask[0].Name );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawMaskedImage got InputStream param #2",
u"InputStream"_ustr, xMask[1].Name );
}
virtual void drawAlphaMaskedImage(const uno::Sequence<beans::PropertyValue>& xBitmap,
const uno::Sequence<beans::PropertyValue>& xMask) override
{
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawAlphaMaskedImage received two properties #1",
sal_Int32(3), xBitmap.getLength() );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawAlphaMaskedImage got URL param #1",
u"URL"_ustr, xBitmap[0].Name );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawAlphaMaskedImage got InputStream param #1",
u"InputStream"_ustr, xBitmap[1].Name );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawAlphaMaskedImage received two properties #2",
sal_Int32(3), xMask.getLength() );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawAlphaMaskedImage got URL param #2",
u"URL"_ustr, xMask[0].Name );
CPPUNIT_ASSERT_EQUAL_MESSAGE( "drawAlphaMaskedImage got InputStream param #2",
u"InputStream"_ustr, xMask[1].Name );
}
virtual void setTextRenderMode( sal_Int32 ) override
{
}
virtual void tilingPatternFill(int, int, int, int,
double, double,
int,
css::geometry::AffineMatrix2D&,
const css::uno::Sequence<css::beans::PropertyValue>&) override
{
}
typedef std::unordered_map<sal_Int32,FontAttributes> IdToFontMap;
typedef std::unordered_map<FontAttributes,sal_Int32,FontAttrHash> FontToIdMap;
typedef std::vector<GraphicsContext> GraphicsContextStack;
sal_Int32 m_nNextFontId;
IdToFontMap m_aIdToFont;
FontToIdMap m_aFontToId;
GraphicsContextStack m_aGCStack;
geometry::RealSize2D m_aPageSize;
geometry::RealRectangle2D m_aHyperlinkBounds;
OUString m_aURI;
OUStringBuffer m_aTextOut;
sal_Int32 m_nNumPages;
bool m_bPageEnded;
bool m_bRedCircleSeen;
bool m_bGreenStrokeSeen;
bool m_bDashedLineSeen;
bool m_bImageSeen;
};
class PDFITest : public test::BootstrapFixture, public XmlTestTools
{
public:
void testXPDFParser()
{
#if HAVE_FEATURE_POPPLER
auto pSink = std::make_shared<TestSink>();
CPPUNIT_ASSERT(
pdfi::xpdf_ImportFromFile(
m_directories.getURLFromSrc(u"/sdext/source/pdfimport/test/testinput.pdf"),
pSink,
uno::Reference< task::XInteractionHandler >(),
OUString(),
getComponentContext(), u""_ustr ) );
pSink->check();
#endif
}
void testOdfDrawExport()
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor( new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()) );
xAdaptor->setTreeVisitorFactory( createDrawTreeVisitorFactory() );
OUString tempFileURL;
CPPUNIT_ASSERT_EQUAL( osl::File::E_None, osl::File::createTempFile( nullptr, nullptr, &tempFileURL ) );
osl::File::remove( tempFileURL ); // FIXME the below apparently fails silently if the file already exists
CPPUNIT_ASSERT_MESSAGE("Exporting to ODF",
xAdaptor->odfConvert( m_directories.getURLFromSrc(u"/sdext/source/pdfimport/test/testinput.pdf"),
new OutputWrap(tempFileURL),
nullptr ));
osl::File::remove( tempFileURL );
#endif
}
void testOdfWriterExport()
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor( new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()) );
xAdaptor->setTreeVisitorFactory( createWriterTreeVisitorFactory() );
OUString tempFileURL;
CPPUNIT_ASSERT_EQUAL( osl::File::E_None, osl::File::createTempFile( nullptr, nullptr, &tempFileURL ) );
osl::File::remove( tempFileURL ); // FIXME the below apparently fails silently if the file already exists
CPPUNIT_ASSERT_MESSAGE("Exporting to ODF",
xAdaptor->odfConvert( m_directories.getURLFromSrc(u"/sdext/source/pdfimport/test/testinput.pdf"),
new OutputWrap(tempFileURL),
nullptr ));
osl::File::remove( tempFileURL );
#endif
}
void testTdf96993()
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor(new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()));
xAdaptor->setTreeVisitorFactory(createDrawTreeVisitorFactory());
OString aOutput;
CPPUNIT_ASSERT_MESSAGE("Exporting to ODF",
xAdaptor->odfConvert(m_directories.getURLFromSrc(u"/sdext/source/pdfimport/test/testTdf96993.pdf"),
new OutputWrapString(aOutput),
nullptr));
// This ensures that the imported image arrives properly flipped
CPPUNIT_ASSERT(aOutput.indexOf("draw:transform=\"matrix(18520.8333333333 0 0 26281.9444444444 0 0)\"") != -1);
#endif
}
void testTdf98421()
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor(new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()));
xAdaptor->setTreeVisitorFactory(createWriterTreeVisitorFactory());
OString aOutput;
CPPUNIT_ASSERT_MESSAGE("Exporting to ODF",
xAdaptor->odfConvert(m_directories.getURLFromSrc(u"/sdext/source/pdfimport/test/testTdf96993.pdf"),
new OutputWrapString(aOutput),
nullptr));
// This ensures that the imported image arrives properly flipped
CPPUNIT_ASSERT(aOutput.indexOf("draw:transform=\"scale( 1.0 -1.0 ) translate( 0mm 0mm )\"") != -1);
CPPUNIT_ASSERT(aOutput.indexOf("svg:height=\"-262.82mm\"") != -1);
#endif
}
void testTdf105536()
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor(new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()));
xAdaptor->setTreeVisitorFactory(createDrawTreeVisitorFactory());
OString aOutput;
CPPUNIT_ASSERT_MESSAGE("Exporting to ODF",
xAdaptor->odfConvert(m_directories.getURLFromSrc(u"/sdext/source/pdfimport/test/testTdf105536.pdf"),
new OutputWrapString(aOutput),
nullptr));
// This ensures that the imported image arrives properly flipped
CPPUNIT_ASSERT(aOutput.indexOf("draw:transform=\"matrix(-21488.4 0 0 -27978.1 21488.4 27978.1)\"") != -1);
#endif
}
void testTdf141709_chinesechar()
{
// this test crashes on the windows jenkins boxes, but no-one can catch it locally
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor(new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()));
xAdaptor->setTreeVisitorFactory(createDrawTreeVisitorFactory());
OString aOutput;
CPPUNIT_ASSERT_MESSAGE("Exporting to ODF",
xAdaptor->odfConvert(m_directories.getURLFromSrc(u"/sdext/qa/unit/data/testTdf141709_chinesechar.pdf"),
new OutputWrapString(aOutput),
nullptr));
xmlDocUniquePtr pXmlDoc(xmlParseDoc(reinterpret_cast<xmlChar const *>(aOutput.getStr())));
// This ensures that the imported text contains all of the characters
OString xpath = "//draw:frame[@draw:z-index='3'][1]/draw:text-box/text:p/text:span[1]"_ostr;
OUString sContent = getXPathContent(pXmlDoc, xpath).replaceAll("\n", "");
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"敏捷的狐狸跨过慵懒的"_ustr, sContent);
xpath = "//draw:frame[@draw:z-index='4'][1]/draw:text-box/text:p/text:span[1]"_ostr;
sContent = getXPathContent(pXmlDoc, xpath).replaceAll("\n", "");
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"狗。"_ustr, sContent);
#endif
}
void testTdf78427_FontFeatures()
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor(new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()));
xAdaptor->setTreeVisitorFactory(createDrawTreeVisitorFactory());
OString aOutput;
CPPUNIT_ASSERT_MESSAGE("Converting PDF to ODF XML",
xAdaptor->odfConvert( m_directories.getURLFromSrc(
u"/sdext/qa/unit/data/tdf78427-testFontFeatures.pdf"),
new OutputWrapString(aOutput),
nullptr ));
// Un-comment the following debug line to see the content of generated XML content in
// workdir/CppunitTest/sdext_pdfimport.test.log after running "make CppunitTest_sdext_pdfimport".
//std::cout << aOutput << std::endl;
xmlDocUniquePtr pXmlDoc(xmlParseDoc(reinterpret_cast<xmlChar const *>(aOutput.getStr())));
//CPPUNIT_ASSERT(pXmlDoc);
/* Test for the 1st paragraph */
OUString styleName = getXPath(pXmlDoc, "//draw:frame[1]//text:span[1]", "style-name");
OString xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font-weight and font-style should be normal
assertXPath(pXmlDoc, xpath, "font-weight", u"normal");
assertXPathNoAttribute(pXmlDoc, xpath, "font-style");
/* Test for the 2nd paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[2]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// there should be a font-weight="bold", but no font-style italic
assertXPath(pXmlDoc, xpath, "font-weight", u"bold");
assertXPathNoAttribute(pXmlDoc, xpath, "font-style");
/* Test for the 3rd paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[3]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// there should be a font-style="italic", but no font-weight bold
assertXPath(pXmlDoc, xpath, "font-weight", u"normal");
assertXPath(pXmlDoc, xpath, "font-style", u"italic");
/* Test for the 4th paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[4]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// there should be both font-style="italic" and font-weight="bold"
assertXPath(pXmlDoc, xpath, "font-weight", u"bold");
assertXPath(pXmlDoc, xpath, "font-style", u"italic");
/* Test for the 5th paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[5]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font should be Arial and font-weight="bold", no font-style
assertXPath(pXmlDoc, xpath, "font-family", u"Arial");
assertXPath(pXmlDoc, xpath, "font-weight", u"bold");
assertXPathNoAttribute(pXmlDoc, xpath, "font-style");
/* Test for the 6th paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[6]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font should be Arial without font-weight and font-style
assertXPath(pXmlDoc, xpath, "font-family", u"Arial");
assertXPath(pXmlDoc, xpath, "font-weight", u"normal");
assertXPathNoAttribute(pXmlDoc, xpath, "font-style");
/* Test for the 7th paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[7]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font should be SimSun without font-weight and font-style
assertXPath(pXmlDoc, xpath, "font-family", u"SimSun"); // TODO: tdf#143095 use localized font name rather than PS name
assertXPath(pXmlDoc, xpath, "font-weight", u"normal");
assertXPathNoAttribute(pXmlDoc, xpath, "font-style");
/* Test for the 8th paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[8]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font should be SimSun and font-weight="bold", no font-style italic
assertXPath(pXmlDoc, xpath, "font-family", u"SimSun");
assertXPath(pXmlDoc, xpath, "font-weight", u"bold");
assertXPathNoAttribute(pXmlDoc, xpath, "font-style");
/* Test for the 9th paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[9]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font should be SimSun, font-weight should be "normal", font-style="italic"
assertXPath(pXmlDoc, xpath, "font-family", u"SimSun");
assertXPath(pXmlDoc, xpath, "font-weight", u"normal");
// FIXME and remove the below comment:
// the chinese chars are shown in pdf as faux italic (fake italic). It is currencly imported wrongly as normal font style.
// See tdf#78427 for how the faux bold problem was handled. Faux italic may be handled using the transformation pattern.
// assertXPath(pXmlDoc, xpath, "font-style", "italic");
/* Test for the 10th paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[10]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font should be SimSun font-weight="bold" and font-style="italic"
assertXPath(pXmlDoc, xpath, "font-family", u"SimSun");
assertXPath(pXmlDoc, xpath, "font-weight", u"bold");
// FIXME: faux italic, see above
// assertXPath(pXmlDoc, xpath, "font-style", "italic");
/* Test for the 11th paragraph */
styleName = getXPath(pXmlDoc, "//draw:frame[11]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font should be SimSun and there should be style:text-outline="true"
// (i.e., the real "outline" font rather than faux bold / fake bold)
assertXPath(pXmlDoc, xpath, "font-family", u"SimSun");
assertXPath(pXmlDoc, xpath, "font-weight", u"normal");
assertXPathNoAttribute(pXmlDoc, xpath, "font-style");
assertXPath(pXmlDoc, xpath, "text-outline", u"true");
#endif
}
void testTdf78427_FontWeight_MyraidProSemibold() // Related to attachment 155937.
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor(new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()));
xAdaptor->setTreeVisitorFactory(createDrawTreeVisitorFactory());
OString aOutput;
CPPUNIT_ASSERT_MESSAGE("Converting PDF to ODF XML",
xAdaptor->odfConvert( m_directories.getURLFromSrc(
u"/sdext/qa/unit/data/tdf78427-MyraidPro-Semibold-Light.pdf"),
new OutputWrapString(aOutput),
nullptr ));
//std::cout << aOutput << std::endl;
xmlDocUniquePtr pXmlDoc(xmlParseDoc(reinterpret_cast<xmlChar const *>(aOutput.getStr())));
//CPPUNIT_ASSERT(pXmlDoc);
// The for the 1st frame */
OUString styleName = getXPath(pXmlDoc, "//draw:frame[1]//text:span[1]", "style-name");
OString xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font-weight and font-style should be 600 (Semibold)
assertXPath(pXmlDoc, xpath, "font-weight", u"600");
// The for the 2nd frame */
styleName = getXPath(pXmlDoc, "//draw:frame[2]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
// the font-weight and font-style should be 300 (Light)
assertXPath(pXmlDoc, xpath, "font-weight", u"300");
#endif
}
void testTdf143959_nameFromFontFile()
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor(new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()));
xAdaptor->setTreeVisitorFactory(createDrawTreeVisitorFactory());
OString aOutput;
CPPUNIT_ASSERT_MESSAGE("Converting PDF to ODF XML",
xAdaptor->odfConvert( m_directories.getURLFromSrc(u"/sdext/qa/unit/data/testTdf143959.pdf"),
new OutputWrapString(aOutput),
nullptr ));
//std::cout << aOutput << std::endl;
xmlDocUniquePtr pXmlDoc(xmlParseDoc(reinterpret_cast<xmlChar const *>(aOutput.getStr())));
/* Test for the 1st text paragraph */
OUString styleName = getXPath(pXmlDoc, "//draw:frame[2]//text:span[1]", "style-name");
OString xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
CPPUNIT_ASSERT_EQUAL(u"TimesNewRoman"_ustr,
getXPath(pXmlDoc, xpath, "font-family").replaceAll(u" ", u""));
/* Test for the "TOTAL ESTA HOJA USD" paragraph" */
styleName = getXPath(pXmlDoc, "//draw:frame[last()-1]//text:span[1]", "style-name");
xpath = "//office:automatic-styles/style:style[@style:name=\"" +
OUStringToOString(styleName, RTL_TEXTENCODING_UTF8) +
"\"]/style:text-properties";
CPPUNIT_ASSERT_EQUAL(u"TimesNewRoman"_ustr,
getXPath(pXmlDoc, xpath, "font-family").replaceAll(u" ", u""));
CPPUNIT_ASSERT_EQUAL(u"bold"_ustr,
getXPath(pXmlDoc, xpath, "font-weight"));
#endif
}
void testTdf104597_textrun()
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor(new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()));
xAdaptor->setTreeVisitorFactory(createDrawTreeVisitorFactory());
OString aOutput;
CPPUNIT_ASSERT_MESSAGE("Converting PDF to ODF XML",
xAdaptor->odfConvert(m_directories.getURLFromSrc(u"/sdext/qa/unit/data/tdf104597_textrun.pdf"),
new OutputWrapString(aOutput),
nullptr));
xmlDocUniquePtr pXmlDoc(xmlParseDoc(reinterpret_cast<xmlChar const *>(aOutput.getStr())));
// Test for امُ عَلَيْكَ
OString xpath = "string(//draw:frame[@draw:transform='matrix(917.222222222222 0 0 917.222222222222 14821.9583333333 2159.23861112778)']/draw:text-box/text:p/text:span)"_ostr;
OUString sContent = getXPathContent(pXmlDoc, xpath);
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"امُ عَلَيَْك"_ustr, sContent.replaceAll("\n\n", " ").replaceAll("\n", ""));
// Test for ٱلسََّل . It appears in the 3rd frame, i.e. after the امُ عَلَيَْك which is in the 2nd frame (from left to right)
// thus these two frames together appear as ٱلسََّل امُ عَلَيْكَ in Draw.
// FIXME: Should be ٱلسَّلَامُ عَلَيْكَ (i.e. the two text frames should be merged into one so that the ل and the ا will show as لَا rather than ل ا)
xpath = "string(//draw:frame[@draw:transform='matrix(917.222222222222 0 0 917.222222222222 17420.1666666667 2159.23861112778)']/draw:text-box/text:p/text:span)"_ostr;
sContent = getXPathContent(pXmlDoc, xpath);
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"ٱلسََّل"_ustr, sContent.replaceAll("\n\n", " ").replaceAll("\n", ""));
// Test for "LibreOffice RTL"
xpath = "string(//draw:frame[@draw:transform='matrix(917.222222222222 0 0 917.222222222222 12779.375 5121.79583335)']/draw:text-box/text:p/text:span)"_ostr;
sContent = getXPathContent(pXmlDoc, xpath);
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"LibreOffice RTL"_ustr, sContent.replaceAll("\n\n", " ").replaceAll("\n", ""));
// Test for "LibreOffice LTR (test)"
xpath = "string(//draw:frame[last()-1]/draw:text-box/text:p/text:span[last()])"_ostr;
sContent = getXPathContent(pXmlDoc, xpath);
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"LibreOffice LTR (test)"_ustr, sContent.replaceAll("\n\n", " ").replaceAll("\n", ""));
/* Test for Chinese characters */
// Use last() instead of matrix below, because the matrix may be different on different OS due to fallback of Chinese fonts.
xpath = "string(//draw:frame[last()]/draw:text-box/text:p/text:span)"_ostr;
sContent = getXPathContent(pXmlDoc, xpath);
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"中文测试,中文"_ustr, sContent.replaceAll("\n\n", " ").replaceAll("\n", ""));
// Test pdf text run in the Writer PDF import filter
xAdaptor->setTreeVisitorFactory(createWriterTreeVisitorFactory());
OString aOutput2;
xAdaptor->odfConvert(m_directories.getURLFromSrc(u"/sdext/qa/unit/data/tdf104597_textrun.pdf"),
new OutputWrapString(aOutput2),
nullptr);
xmlDocUniquePtr pXmlDoc2(xmlParseDoc(reinterpret_cast<xmlChar const *>(aOutput2.getStr())));
xpath = "string(//draw:frame[@draw:z-index='3'][1]/draw:text-box/text:p/text:span)"_ostr;
sContent = getXPathContent(pXmlDoc2, xpath).replaceAll("\n\n", " ").replaceAll("\n", "");
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput2.getStr(), u"ٱلسََّل"_ustr, sContent);
xpath = "string(//draw:frame[@draw:z-index='2'][1]/draw:text-box/text:p/text:span)"_ostr;
sContent = getXPathContent(pXmlDoc2, xpath).replaceAll("\n\n", " ").replaceAll("\n", "");
CPPUNIT_ASSERT_EQUAL(u"امُ عَلَيَْك"_ustr, sContent);
xpath = "string(//draw:frame[last()]/draw:text-box/text:p/text:span)"_ostr;
sContent = getXPathContent(pXmlDoc2, xpath).replaceAll("\n\n", " ").replaceAll("\n", "");
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput2.getStr(), u"中文测试,中文"_ustr, sContent);
#endif
}
void testSpaces()
{
#if HAVE_FEATURE_POPPLER
rtl::Reference<pdfi::PDFIRawAdaptor> xAdaptor(new pdfi::PDFIRawAdaptor(OUString(), getComponentContext()));
xAdaptor->setTreeVisitorFactory(createWriterTreeVisitorFactory());
OString aOutput;
xAdaptor->odfConvert(m_directories.getURLFromSrc(u"/sdext/qa/unit/data/testSpace.pdf"),
new OutputWrapString(aOutput),
nullptr);
xmlDocUniquePtr pXmlDoc(xmlParseDoc(reinterpret_cast<xmlChar const *>(aOutput.getStr())));
// Space test: there are 10 spaces, each space is expressed as a <text:s text:c="1" ...>,
// thus the 10th text:s should exist and the attribute "text:c" should be "1".
OString xpath = "//draw:frame[@draw:z-index='1'][1]/draw:text-box/text:p/text:span/text:s[10]"_ostr;
OUString sContent = getXPath(pXmlDoc, xpath, "c");
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"1"_ustr, sContent);
// Tab test: there are 10 tabs. Text before and after the tabs are shown in different draw frames.
// With the Liberation Serif font, the horizontal position of the first frame is 20.03mm and the
// second frame is 94.12mm.
xpath = "//draw:frame[@draw:z-index='2'][1]"_ostr;
sContent = getXPath(pXmlDoc, xpath, "transform");
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"translate( 20.03mm 25.05mm )"_ustr, sContent);
xpath = "//draw:frame[@draw:z-index='3'][1]"_ostr;
sContent = getXPath(pXmlDoc, xpath, "transform");
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"translate( 94.12mm 25.05mm )"_ustr, sContent);
// Non-breaking space test: there are 10 NBSpaces, which are treated as the same as normal space in PDF,
// thus each is expressed as a <text:s text:c="1" ...>.
// The 10th text:s should exist and the attribute "text:c" should be "1".
xpath = "//draw:frame[@draw:z-index='4'][1]/draw:text-box/text:p/text:span/text:s[10]"_ostr;
sContent = getXPath(pXmlDoc, xpath, "c");
CPPUNIT_ASSERT_EQUAL_MESSAGE(aOutput.getStr(), u"1"_ustr, sContent);
#endif
}
CPPUNIT_TEST_SUITE(PDFITest);
CPPUNIT_TEST(testXPDFParser);
CPPUNIT_TEST(testOdfWriterExport);
CPPUNIT_TEST(testOdfDrawExport);
CPPUNIT_TEST(testTdf96993);
CPPUNIT_TEST(testTdf98421);
CPPUNIT_TEST(testTdf105536);
CPPUNIT_TEST(testTdf141709_chinesechar);
CPPUNIT_TEST(testTdf78427_FontFeatures);
CPPUNIT_TEST(testTdf78427_FontWeight_MyraidProSemibold);
CPPUNIT_TEST(testTdf143959_nameFromFontFile);
CPPUNIT_TEST(testTdf104597_textrun);
CPPUNIT_TEST(testSpaces);
CPPUNIT_TEST_SUITE_END();
};
}
CPPUNIT_TEST_SUITE_REGISTRATION(PDFITest);
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'homMatrixFromAffineMatrix' is required to be utilized.
↑ V1076 Code contains invisible characters that may alter its logic. Consider enabling the display of invisible characters in the code editor.