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