1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_sdext.hxx" 26 27 #define BASEGFX_STATICLIBRARY 28 29 #ifdef SYSTEM_ZLIB 30 #include "zlib.h" 31 #else 32 #include <zlib/zlib.h> 33 #endif 34 35 #include "outputwrap.hxx" 36 #include "contentsink.hxx" 37 #include "pdfihelper.hxx" 38 #include "wrapper.hxx" 39 #include "pdfparse.hxx" 40 #include "../pdfiadaptor.hxx" 41 42 #include <rtl/math.hxx> 43 #include <osl/file.hxx> 44 #include <osl/process.h> 45 #include <gtest/gtest.h> 46 #include <cppuhelper/compbase1.hxx> 47 #include <cppuhelper/bootstrap.hxx> 48 #include <cppuhelper/basemutex.hxx> 49 #include <comphelper/sequence.hxx> 50 51 52 #include <com/sun/star/rendering/XCanvas.hpp> 53 #include <com/sun/star/rendering/XColorSpace.hpp> 54 #include <com/sun/star/rendering/PathJoinType.hpp> 55 #include <com/sun/star/rendering/PathCapType.hpp> 56 #include <com/sun/star/rendering/BlendMode.hpp> 57 #include <com/sun/star/lang/XMultiServiceFactory.hpp> 58 #include <com/sun/star/lang/XInitialization.hpp> 59 #include <com/sun/star/registry/XSimpleRegistry.hpp> 60 61 #include <basegfx/matrix/b2dhommatrix.hxx> 62 #include <basegfx/tools/canvastools.hxx> 63 #include <basegfx/polygon/b2dpolygon.hxx> 64 #include <basegfx/polygon/b2dpolypolygon.hxx> 65 #include <basegfx/polygon/b2dpolypolygontools.hxx> 66 #include <basegfx/polygon/b2dpolygonclipper.hxx> 67 68 #include <vector> 69 #include <hash_map> 70 71 72 using namespace ::pdfparse; 73 using namespace ::pdfi; 74 using namespace ::com::sun::star; 75 76 namespace 77 { 78 class TestSink : public ContentSink 79 { 80 public: 81 TestSink() : 82 m_nNextFontId( 1 ), 83 m_aIdToFont(), 84 m_aFontToId(), 85 m_aGCStack(1), 86 m_aPageSize(), 87 m_aHyperlinkBounds(), 88 m_aURI(), 89 m_aTextOut(), 90 m_nNumPages(0), 91 m_bPageEnded(false), 92 m_bRedCircleSeen(false), 93 m_bGreenStrokeSeen(false), 94 m_bDashedLineSeen(false) 95 {} 96 97 ~TestSink() 98 { 99 ASSERT_TRUE(m_aPageSize.Width == 79400 && m_aPageSize.Height == 59500) << "A4 page size (in 100th of points)"; 100 ASSERT_TRUE(m_bPageEnded) << "endPage() called"; 101 ASSERT_TRUE(m_nNumPages == 1) << "Num pages equal one"; 102 ASSERT_TRUE(rtl::math::approxEqual(m_aHyperlinkBounds.X1,34.7 ) && 103 rtl::math::approxEqual(m_aHyperlinkBounds.Y1,386.0) && 104 rtl::math::approxEqual(m_aHyperlinkBounds.X2,166.7) && 105 rtl::math::approxEqual(m_aHyperlinkBounds.Y2,406.2)) << "Correct hyperlink bounding box"; 106 ASSERT_TRUE(m_aURI == ::rtl::OUString::createFromAscii( "http://download.openoffice.org/" )) << "Correct hyperlink URI"; 107 108 const char* sText = " \n \nThis is a testtext\nNew paragraph,\nnew line\n" 109 "Hyperlink, this is\n?\nThis is more text\noutline mode\n?\nNew paragraph\n"; 110 ::rtl::OString aTmp; 111 m_aTextOut.makeStringAndClear().convertToString( &aTmp, 112 RTL_TEXTENCODING_ASCII_US, 113 OUSTRING_TO_OSTRING_CVTFLAGS ); 114 ASSERT_TRUE( sText == aTmp ) << "Imported text is \"This is a testtext New paragraph, new line" 115 " Hyperlink, this is * This is more text outline mode * New paragraph\""; 116 117 ASSERT_TRUE(m_bRedCircleSeen) << "red circle seen in input"; 118 ASSERT_TRUE(m_bGreenStrokeSeen) << "green stroke seen in input"; 119 ASSERT_TRUE(m_bDashedLineSeen) << "dashed line seen in input"; 120 } 121 122 private: 123 GraphicsContext& getCurrentContext() { return m_aGCStack.back(); } 124 125 // ContentSink interface implementation 126 virtual void setPageNum( sal_Int32 nNumPages ) 127 { 128 m_nNumPages = nNumPages; 129 } 130 131 virtual void startPage( const geometry::RealSize2D& rSize ) 132 { 133 m_aPageSize = rSize; 134 } 135 136 virtual void endPage() 137 { 138 m_bPageEnded = true; 139 } 140 141 virtual void hyperLink( const geometry::RealRectangle2D& rBounds, 142 const ::rtl::OUString& rURI ) 143 { 144 m_aHyperlinkBounds = rBounds; 145 m_aURI = rURI; 146 } 147 148 virtual void pushState() 149 { 150 m_aGCStack.push_back( m_aGCStack.back() ); 151 } 152 153 virtual void popState() 154 { 155 m_aGCStack.pop_back(); 156 } 157 158 virtual void setTransformation( const geometry::AffineMatrix2D& rMatrix ) 159 { 160 basegfx::unotools::homMatrixFromAffineMatrix( 161 getCurrentContext().Transformation, 162 rMatrix ); 163 } 164 165 virtual void setLineDash( const uno::Sequence<double>& dashes, 166 double start ) 167 { 168 GraphicsContext& rContext( getCurrentContext() ); 169 if( dashes.getLength() ) 170 comphelper::sequenceToContainer(rContext.DashArray,dashes); 171 ASSERT_TRUE(start == 0.0) << "line dashing start offset"; 172 } 173 174 virtual void setFlatness( double nFlatness ) 175 { 176 getCurrentContext().Flatness = nFlatness; 177 } 178 179 virtual void setLineJoin(sal_Int8 nJoin) 180 { 181 getCurrentContext().LineJoin = nJoin; 182 } 183 184 virtual void setLineCap(sal_Int8 nCap) 185 { 186 getCurrentContext().LineCap = nCap; 187 } 188 189 virtual void setMiterLimit(double nVal) 190 { 191 getCurrentContext().MiterLimit = nVal; 192 } 193 194 virtual void setLineWidth(double nVal) 195 { 196 getCurrentContext().LineWidth = nVal; 197 } 198 199 virtual void setFillColor( const rendering::ARGBColor& rColor ) 200 { 201 getCurrentContext().FillColor = rColor; 202 } 203 204 virtual void setStrokeColor( const rendering::ARGBColor& rColor ) 205 { 206 getCurrentContext().LineColor = rColor; 207 } 208 209 virtual void setBlendMode(sal_Int8 nMode) 210 { 211 getCurrentContext().BlendMode = nMode; 212 } 213 214 virtual void setFont( const FontAttributes& rFont ) 215 { 216 FontToIdMap::const_iterator it = m_aFontToId.find( rFont ); 217 if( it != m_aFontToId.end() ) 218 getCurrentContext().FontId = it->second; 219 else 220 { 221 m_aFontToId[ rFont ] = m_nNextFontId; 222 m_aIdToFont[ m_nNextFontId ] = rFont; 223 getCurrentContext().FontId = m_nNextFontId; 224 m_nNextFontId++; 225 } 226 } 227 228 virtual void strokePath( const uno::Reference<rendering::XPolyPolygon2D>& rPath ) 229 { 230 GraphicsContext& rContext( getCurrentContext() ); 231 basegfx::B2DPolyPolygon aPath = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath); 232 aPath.transform( rContext.Transformation ); 233 234 if( rContext.DashArray.empty() ) 235 { 236 ASSERT_TRUE(rContext.LineColor.Alpha == 1.0 && 237 rContext.LineColor.Red == 0.0 && 238 rContext.LineColor.Green == 1.0 && 239 rContext.LineColor.Blue == 0.0) << "Line color is green"; 240 241 ASSERT_TRUE(rtl::math::approxEqual(rContext.LineWidth, 28.3)) << "Line width is 0"; 242 243 const char* sExportString = "m53570 7650-35430 24100"; 244 ASSERT_TRUE(basegfx::tools::exportToSvgD( aPath, true, true, false ).compareToAscii(sExportString) == 0) << "Stroke is m535.7 518.5-354.3-241"; 245 246 m_bGreenStrokeSeen = true; 247 } 248 else 249 { 250 ASSERT_TRUE(rContext.DashArray.size() == 4 && 251 rtl::math::approxEqual(rContext.DashArray[0],14.3764) && 252 rContext.DashArray[0] == rContext.DashArray[1] && 253 rContext.DashArray[1] == rContext.DashArray[2] && 254 rContext.DashArray[2] == rContext.DashArray[3]) << "Dash array cons ists of four entries"; 255 256 ASSERT_TRUE(rContext.LineColor.Alpha == 1.0 && 257 rContext.LineColor.Red == 0.0 && 258 rContext.LineColor.Green == 0.0 && 259 rContext.LineColor.Blue == 0.0) << "Line color is black"; 260 261 ASSERT_TRUE(rContext.LineWidth == 0) << "Line width is 0"; 262 263 const char* sExportString = "m49890 5670.00000000001-35430 24090"; 264 ASSERT_TRUE(basegfx::tools::exportToSvgD( aPath, true, true, false ).compareToAscii(sExportString) == 0) << "Stroke is m49890 5670.00000000001-35430 24090"; 265 266 m_bDashedLineSeen = true; 267 } 268 ASSERT_TRUE(rContext.BlendMode == rendering::BlendMode::NORMAL) << "Blend mode is normal"; 269 ASSERT_TRUE(rContext.LineJoin == rendering::PathJoinType::ROUND) << "Join type is round"; 270 ASSERT_TRUE(rContext.LineCap == rendering::PathCapType::BUTT) << "Cap type is butt"; 271 ASSERT_TRUE(rContext.MiterLimit == 10) << "Line miter limit is 10"; 272 ASSERT_TRUE(rContext.Flatness == 1) << "Flatness is 0"; 273 ASSERT_TRUE(rContext.FontId == 0) << "Font id is 0"; 274 } 275 276 virtual void fillPath( const uno::Reference<rendering::XPolyPolygon2D>& rPath ) 277 { 278 GraphicsContext& rContext( getCurrentContext() ); 279 basegfx::B2DPolyPolygon aPath = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath); 280 aPath.transform( rContext.Transformation ); 281 282 ASSERT_TRUE(rContext.FillColor.Alpha == 1.0 && 283 rContext.FillColor.Red == 0.0 && 284 rContext.FillColor.Green == 0.0 && 285 rContext.FillColor.Blue == 0.0) << "Fill color is black"; 286 ASSERT_TRUE(rContext.BlendMode == rendering::BlendMode::NORMAL) << "Blend mode is normal"; 287 ASSERT_TRUE(rContext.Flatness == 10) << "Flatness is 10"; 288 ASSERT_TRUE(rContext.FontId == 0) << "Font id is 0"; 289 } 290 291 virtual void eoFillPath( const uno::Reference<rendering::XPolyPolygon2D>& rPath ) 292 { 293 GraphicsContext& rContext( getCurrentContext() ); 294 basegfx::B2DPolyPolygon aPath = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath); 295 aPath.transform( rContext.Transformation ); 296 297 ASSERT_TRUE(rContext.FillColor.Alpha == 1.0 && 298 rContext.FillColor.Red == 1.0 && 299 rContext.FillColor.Green == 0.0 && 300 rContext.FillColor.Blue == 0.0) << "Fill color is black"; 301 ASSERT_TRUE(rContext.BlendMode == rendering::BlendMode::NORMAL) << "Blend mode is normal"; 302 ASSERT_TRUE(rContext.Flatness == 1) << "Flatness is 0"; 303 ASSERT_TRUE(rContext.FontId == 0) << "Font id is 0"; 304 305 const char* sExportString = "m12050 49610c-4310 0-7800-3490-7800-7800 0-4300 " 306 "3490-7790 7800-7790 4300 0 7790 3490 7790 7790 0 4310-3490 7800-7790 7800z"; 307 ASSERT_TRUE(basegfx::tools::exportToSvgD( aPath, true, true, false ).compareToAscii(sExportString) == 0) << "Stroke is a 4-bezier circle"; 308 309 m_bRedCircleSeen = true; 310 } 311 312 virtual void intersectClip(const uno::Reference<rendering::XPolyPolygon2D>& rPath) 313 { 314 basegfx::B2DPolyPolygon aNewClip = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath); 315 basegfx::B2DPolyPolygon aCurClip = getCurrentContext().Clip; 316 317 if( aCurClip.count() ) // #i92985# adapted API from (..., false, false) to (..., true, false) 318 aNewClip = basegfx::tools::clipPolyPolygonOnPolyPolygon( aCurClip, aNewClip, true, false ); 319 320 getCurrentContext().Clip = aNewClip; 321 } 322 323 virtual void intersectEoClip(const uno::Reference<rendering::XPolyPolygon2D>& rPath) 324 { 325 basegfx::B2DPolyPolygon aNewClip = basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(rPath); 326 basegfx::B2DPolyPolygon aCurClip = getCurrentContext().Clip; 327 328 if( aCurClip.count() ) // #i92985# adapted API from (..., false, false) to (..., true, false) 329 aNewClip = basegfx::tools::clipPolyPolygonOnPolyPolygon( aCurClip, aNewClip, true, false ); 330 331 getCurrentContext().Clip = aNewClip; 332 } 333 334 virtual void drawGlyphs( const rtl::OUString& rGlyphs, 335 const geometry::RealRectangle2D& /*rRect*/, 336 const geometry::Matrix2D& /*rFontMatrix*/ ) 337 { 338 m_aTextOut.append(rGlyphs); 339 } 340 341 virtual void endText() 342 { 343 m_aTextOut.append( ::rtl::OUString::createFromAscii("\n") ); 344 } 345 346 virtual void drawMask(const uno::Sequence<beans::PropertyValue>& xBitmap, 347 bool /*bInvert*/ ) 348 { 349 ASSERT_TRUE(xBitmap.getLength()==3) << "drawMask received two properties"; 350 ASSERT_TRUE(xBitmap[0].Name.compareToAscii( "URL" ) == 0) << "drawMask got URL param"; 351 ASSERT_TRUE(xBitmap[1].Name.compareToAscii( "InputStream" ) == 0) << "drawMask got InputStream param"; 352 } 353 354 virtual void drawImage(const uno::Sequence<beans::PropertyValue>& xBitmap ) 355 { 356 ASSERT_TRUE(xBitmap.getLength()==3) << "drawImage received two properties"; 357 ASSERT_TRUE(xBitmap[0].Name.compareToAscii( "URL" ) == 0) << "drawImage got URL param"; 358 ASSERT_TRUE(xBitmap[1].Name.compareToAscii( "InputStream" ) == 0) << "drawImage got InputStream param"; 359 } 360 361 virtual void drawColorMaskedImage(const uno::Sequence<beans::PropertyValue>& xBitmap, 362 const uno::Sequence<uno::Any>& /*xMaskColors*/ ) 363 { 364 ASSERT_TRUE(xBitmap.getLength()==3) << "drawColorMaskedImage received two properties"; 365 ASSERT_TRUE(xBitmap[0].Name.compareToAscii( "URL" ) == 0) << "drawColorMaskedImage got URL param"; 366 ASSERT_TRUE(xBitmap[1].Name.compareToAscii( "InputStream" ) == 0) << "drawColorMaskedImage got InputStream param"; 367 } 368 369 virtual void drawMaskedImage(const uno::Sequence<beans::PropertyValue>& xBitmap, 370 const uno::Sequence<beans::PropertyValue>& xMask, 371 bool /*bInvertMask*/) 372 { 373 ASSERT_TRUE(xBitmap.getLength()==3) << "drawMaskedImage received two properties #1"; 374 ASSERT_TRUE(xBitmap[0].Name.compareToAscii( "URL" ) == 0) << "drawMaskedImage got URL param #1"; 375 ASSERT_TRUE(xBitmap[1].Name.compareToAscii( "InputStream" ) == 0) << "drawMaskedImage got InputStream param #1"; 376 377 ASSERT_TRUE(xMask.getLength()==3) << "drawMaskedImage received two properties #2"; 378 ASSERT_TRUE(xMask[0].Name.compareToAscii( "URL" ) == 0) << "drawMaskedImage got URL param #2"; 379 ASSERT_TRUE(xMask[1].Name.compareToAscii( "InputStream" ) == 0) << "drawMaskedImage got InputStream param #2"; 380 } 381 382 virtual void drawAlphaMaskedImage(const uno::Sequence<beans::PropertyValue>& xBitmap, 383 const uno::Sequence<beans::PropertyValue>& xMask) 384 { 385 ASSERT_TRUE(xBitmap.getLength()==3) << "drawAlphaMaskedImage received two properties #1"; 386 ASSERT_TRUE(xBitmap[0].Name.compareToAscii( "URL" ) == 0) << "drawAlphaMaskedImage got URL param #1"; 387 ASSERT_TRUE(xBitmap[1].Name.compareToAscii( "InputStream" ) == 0) << "drawAlphaMaskedImage got InputStream param #1"; 388 389 ASSERT_TRUE(xMask.getLength()==3) << "drawAlphaMaskedImage received two properties #2"; 390 ASSERT_TRUE(xMask[0].Name.compareToAscii( "URL" ) == 0) << "drawAlphaMaskedImage got URL param #2"; 391 ASSERT_TRUE(xMask[1].Name.compareToAscii( "InputStream" ) == 0) << "drawAlphaMaskedImage got InputStream param #2"; 392 } 393 394 virtual void setTextRenderMode( sal_Int32 ) 395 { 396 } 397 398 typedef std::hash_map<sal_Int32,FontAttributes> IdToFontMap; 399 typedef std::hash_map<FontAttributes,sal_Int32,FontAttrHash> FontToIdMap; 400 401 typedef std::hash_map<sal_Int32,GraphicsContext> IdToGCMap; 402 typedef std::hash_map<GraphicsContext,sal_Int32,GraphicsContextHash> GCToIdMap; 403 404 typedef std::vector<GraphicsContext> GraphicsContextStack; 405 406 sal_Int32 m_nNextFontId; 407 IdToFontMap m_aIdToFont; 408 FontToIdMap m_aFontToId; 409 410 GraphicsContextStack m_aGCStack; 411 geometry::RealSize2D m_aPageSize; 412 geometry::RealRectangle2D m_aHyperlinkBounds; 413 ::rtl::OUString m_aURI; 414 ::rtl::OUStringBuffer m_aTextOut; 415 sal_Int32 m_nNumPages; 416 bool m_bPageEnded; 417 bool m_bRedCircleSeen; 418 bool m_bGreenStrokeSeen; 419 bool m_bDashedLineSeen; 420 }; 421 422 class PDFITest : public ::testing::Test 423 { 424 protected: 425 uno::Reference<uno::XComponentContext> mxCtx; 426 rtl::OUString msBaseDir; 427 bool mbUnoInitialized; 428 429 public: 430 PDFITest() : mxCtx(),msBaseDir(),mbUnoInitialized(false) 431 {} 432 433 void SetUp() 434 { 435 if( !mbUnoInitialized ) 436 { 437 const char* pArgs( getenv("TESTS_FORWARD_STRING") ); 438 ASSERT_TRUE(pArgs) << "Test file parameter"; 439 440 msBaseDir = rtl::OUString::createFromAscii(pArgs); 441 442 // bootstrap UNO 443 try 444 { 445 ::rtl::OUString aIniUrl; 446 ASSERT_TRUE( 447 osl_getFileURLFromSystemPath( 448 (msBaseDir+rtl::OUString::createFromAscii("pdfi_unittest_test.ini")).pData, 449 &aIniUrl.pData ) == osl_File_E_None ) 450 << "Converting ini file to URL"; 451 452 mxCtx = ::cppu::defaultBootstrap_InitialComponentContext(aIniUrl); 453 ASSERT_TRUE(mxCtx.is()) << "Getting component context"; 454 } 455 catch( uno::Exception& ) 456 { 457 FAIL() << "Bootstrapping UNO"; 458 } 459 460 mbUnoInitialized = true; 461 } 462 } 463 void TearDown() 464 { 465 } 466 }; 467 468 TEST_F(PDFITest, testXPDFParser) 469 { 470 pdfi::ContentSinkSharedPtr pSink( new TestSink() ); 471 pdfi::xpdf_ImportFromFile( msBaseDir + rtl::OUString::createFromAscii("pdfi_unittest_test.pdf"), 472 pSink, 473 uno::Reference< task::XInteractionHandler >(), 474 rtl::OUString(), 475 mxCtx ); 476 477 // make destruction explicit, a bunch of things are 478 // checked in the destructor 479 pSink.reset(); 480 } 481 482 TEST_F(PDFITest, testOdfDrawExport) 483 { 484 pdfi::PDFIRawAdaptor aAdaptor( mxCtx ); 485 aAdaptor.setTreeVisitorFactory( createDrawTreeVisitorFactory() ); 486 487 ::rtl::OUString aURL, aAbsURL, aBaseURL; 488 osl_getFileURLFromSystemPath( (msBaseDir + rtl::OUString::createFromAscii("pdfi_unittest_draw.xml")).pData, 489 &aURL.pData ); 490 osl_getProcessWorkingDir(&aBaseURL.pData); 491 osl_getAbsoluteFileURL(aBaseURL.pData,aURL.pData,&aAbsURL.pData); 492 ASSERT_TRUE(aAdaptor.odfConvert( msBaseDir + rtl::OUString::createFromAscii("pdfi_unittest_test.pdf"), 493 new OutputWrap(aAbsURL), 494 NULL )) << "Exporting to ODF"; 495 } 496 497 TEST_F(PDFITest, testOdfWriterExport) 498 { 499 pdfi::PDFIRawAdaptor aAdaptor( mxCtx ); 500 aAdaptor.setTreeVisitorFactory( createWriterTreeVisitorFactory() ); 501 502 ::rtl::OUString aURL, aAbsURL, aBaseURL; 503 osl_getFileURLFromSystemPath( (msBaseDir + rtl::OUString::createFromAscii("pdfi_unittest_writer.xml")).pData, 504 &aURL.pData ); 505 osl_getProcessWorkingDir(&aBaseURL.pData); 506 osl_getAbsoluteFileURL(aBaseURL.pData,aURL.pData,&aAbsURL.pData); 507 ASSERT_TRUE(aAdaptor.odfConvert( msBaseDir + rtl::OUString::createFromAscii("pdfi_unittest_test.pdf"), 508 new OutputWrap(aAbsURL), 509 NULL )) << "Exporting to ODF"; 510 } 511 512 } 513 514 int main(int argc, char **argv) 515 { 516 ::testing::InitGoogleTest(&argc, argv); 517 return RUN_ALL_TESTS(); 518 } 519