diff options
Diffstat (limited to 'sdext/source/pdfimport/wrapper/wrapper.cxx')
-rw-r--r-- | sdext/source/pdfimport/wrapper/wrapper.cxx | 1253 |
1 files changed, 1253 insertions, 0 deletions
diff --git a/sdext/source/pdfimport/wrapper/wrapper.cxx b/sdext/source/pdfimport/wrapper/wrapper.cxx new file mode 100644 index 000000000..5d2a2c0b6 --- /dev/null +++ b/sdext/source/pdfimport/wrapper/wrapper.cxx @@ -0,0 +1,1253 @@ +/* -*- 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_folders.h> + +#include <contentsink.hxx> +#include <pdfparse.hxx> +#include <pdfihelper.hxx> +#include <wrapper.hxx> + +#include <o3tl/string_view.hxx> +#include <osl/file.h> +#include <osl/file.hxx> +#include <osl/thread.h> +#include <osl/process.h> +#include <osl/diagnose.h> +#include <rtl/bootstrap.hxx> +#include <rtl/ustring.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> + +#include <comphelper/propertysequence.hxx> +#include <comphelper/string.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> +#include <com/sun/star/geometry/AffineMatrix2D.hpp> +#include <com/sun/star/geometry/RealRectangle2D.hpp> +#include <com/sun/star/geometry/RealSize2D.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/utils/unopolypolygon.hxx> + +#include <vcl/metric.hxx> +#include <vcl/font.hxx> +#include <vcl/virdev.hxx> + +#include <cstddef> +#include <memory> +#include <string_view> +#include <unordered_map> +#include <string.h> + +using namespace com::sun::star; + +namespace pdfi +{ + +namespace +{ + +// identifier of the strings coming from the out-of-process xpdf +// converter +enum parseKey { + CLIPPATH, + DRAWCHAR, + DRAWIMAGE, + DRAWLINK, + DRAWMASK, + DRAWMASKEDIMAGE, + DRAWSOFTMASKEDIMAGE, + ENDPAGE, + ENDTEXTOBJECT, + EOCLIPPATH, + EOFILLPATH, + FILLPATH, + HYPERLINK, + INTERSECTCLIP, + INTERSECTEOCLIP, + POPSTATE, + PUSHSTATE, + RESTORESTATE, + SAVESTATE, + SETBLENDMODE, + SETFILLCOLOR, + SETFONT, + SETLINECAP, + SETLINEDASH, + SETLINEJOIN, + SETLINEWIDTH, + SETMITERLIMIT, + SETPAGENUM, + SETSTROKECOLOR, + SETTEXTRENDERMODE, + SETTRANSFORMATION, + STARTPAGE, + STROKEPATH, + UPDATEBLENDMODE, + UPDATECTM, + UPDATEFILLCOLOR, + UPDATEFILLOPACITY, + UPDATEFLATNESS, + UPDATEFONT, + UPDATELINECAP, + UPDATELINEDASH, + UPDATELINEJOIN, + UPDATELINEWIDTH, + UPDATEMITERLIMIT, + UPDATESTROKECOLOR, + UPDATESTROKEOPACITY, + NONE +}; + +#if defined _MSC_VER && defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-register" +#pragma clang diagnostic ignored "-Wextra-tokens" +#endif +#include <hash.cxx> +#if defined _MSC_VER && defined __clang__ +#pragma clang diagnostic pop +#endif + +class Parser +{ + friend class LineParser; + + typedef std::unordered_map< sal_Int64, + FontAttributes > FontMapType; + + ScopedVclPtr<VirtualDevice> m_xDev; + const uno::Reference<uno::XComponentContext> m_xContext; + const ContentSinkSharedPtr m_pSink; + const oslFileHandle m_pErr; + FontMapType m_aFontMap; + +public: + Parser( const ContentSinkSharedPtr& rSink, + oslFileHandle pErr, + const uno::Reference<uno::XComponentContext>& xContext ) : + m_xContext(xContext), + m_pSink(rSink), + m_pErr(pErr), + m_aFontMap(101) + {} + + void parseLine( const OString& rLine ); +}; + +class LineParser { + Parser & m_parser; + OString m_aLine; + + static void parseFontFamilyName( FontAttributes& aResult ); + void readInt32( sal_Int32& o_Value ); + void readInt64( sal_Int64& o_Value ); + void readDouble( double& o_Value ); + void readBinaryData( uno::Sequence<sal_Int8>& rBuf ); + + uno::Sequence<beans::PropertyValue> readImageImpl(); + +public: + std::size_t m_nCharIndex = 0; + + LineParser(Parser & parser, OString const & line): m_parser(parser), m_aLine(line) {} + + std::string_view readNextToken(); + sal_Int32 readInt32(); + double readDouble(); + + uno::Reference<rendering::XPolyPolygon2D> readPath(); + + void readChar(); + void readLineCap(); + void readLineDash(); + void readLineJoin(); + void readTransformation(); + rendering::ARGBColor readColor(); + void readFont(); + + void readImage(); + void readMask(); + void readLink(); + void readMaskedImage(); + void readSoftMaskedImage(); +}; + +/** Unescapes line-ending characters in input string. These + characters are encoded as pairs of characters: '\\' 'n', resp. + '\\' 'r'. This function converts them back to '\n', resp. '\r'. + */ +OString lcl_unescapeLineFeeds(std::string_view i_rStr) +{ + const size_t nOrigLen(i_rStr.size()); + const char* const pOrig(i_rStr.data()); + std::unique_ptr<char[]> pBuffer(new char[nOrigLen + 1]); + + const char* pRead(pOrig); + char* pWrite(pBuffer.get()); + const char* pCur(pOrig); + while ((pCur = strchr(pCur, '\\')) != nullptr) + { + const char cNext(pCur[1]); + if (cNext == 'n' || cNext == 'r' || cNext == '\\') + { + const size_t nLen(pCur - pRead); + strncpy(pWrite, pRead, nLen); + pWrite += nLen; + *pWrite = cNext == 'n' ? '\n' : (cNext == 'r' ? '\r' : '\\'); + ++pWrite; + pCur = pRead = pCur + 2; + } + else + { + // Just continue on the next character. The current + // block will be copied the next time it goes through the + // 'if' branch. + ++pCur; + } + } + // maybe there are some data to copy yet + if (sal::static_int_cast<size_t>(pRead - pOrig) < nOrigLen) + { + const size_t nLen(nOrigLen - (pRead - pOrig)); + strncpy(pWrite, pRead, nLen); + pWrite += nLen; + } + *pWrite = '\0'; + + OString aResult(pBuffer.get()); + return aResult; +} + +std::string_view LineParser::readNextToken() +{ + if (m_nCharIndex == std::string_view::npos) { + SAL_WARN("sdext.pdfimport", "insufficient input"); + return {}; + } + return o3tl::getToken(m_aLine,' ',m_nCharIndex); +} + +void LineParser::readInt32( sal_Int32& o_Value ) +{ + std::string_view tok = readNextToken(); + o_Value = o3tl::toInt32(tok); +} + +sal_Int32 LineParser::readInt32() +{ + std::string_view tok = readNextToken(); + return o3tl::toInt32(tok); +} + +void LineParser::readInt64( sal_Int64& o_Value ) +{ + std::string_view tok = readNextToken(); + o_Value = o3tl::toInt64(tok); +} + +void LineParser::readDouble( double& o_Value ) +{ + std::string_view tok = readNextToken(); + o_Value = rtl_math_stringToDouble(tok.data(), tok.data() + tok.size(), '.', 0, + nullptr, nullptr); +} + +double LineParser::readDouble() +{ + std::string_view tok = readNextToken(); + return rtl_math_stringToDouble(tok.data(), tok.data() + tok.size(), '.', 0, + nullptr, nullptr); +} + +void LineParser::readBinaryData( uno::Sequence<sal_Int8>& rBuf ) +{ + sal_Int32 nFileLen( rBuf.getLength() ); + sal_Int8* pBuf( rBuf.getArray() ); + sal_uInt64 nBytesRead(0); + oslFileError nRes=osl_File_E_None; + while( nFileLen ) + { + nRes = osl_readFile( m_parser.m_pErr, pBuf, nFileLen, &nBytesRead ); + if (osl_File_E_None != nRes ) + break; + pBuf += nBytesRead; + nFileLen -= sal::static_int_cast<sal_Int32>(nBytesRead); + } + + OSL_PRECOND(nRes==osl_File_E_None, "inconsistent data"); +} + +uno::Reference<rendering::XPolyPolygon2D> LineParser::readPath() +{ + static const std::string_view aSubPathMarker( "subpath" ); + + if( readNextToken() != aSubPathMarker ) + OSL_PRECOND(false, "broken path"); + + basegfx::B2DPolyPolygon aResult; + while( m_nCharIndex != std::string_view::npos ) + { + basegfx::B2DPolygon aSubPath; + + sal_Int32 nClosedFlag; + readInt32( nClosedFlag ); + aSubPath.setClosed( nClosedFlag != 0 ); + + sal_Int32 nContiguousControlPoints(0); + + while( m_nCharIndex != std::string_view::npos ) + { + std::size_t nDummy=m_nCharIndex; + if (o3tl::getToken(m_aLine,' ',nDummy) == aSubPathMarker) { + break; + } + + sal_Int32 nCurveFlag; + double nX, nY; + readDouble( nX ); + readDouble( nY ); + readInt32( nCurveFlag ); + + aSubPath.append(basegfx::B2DPoint(nX,nY)); + if( nCurveFlag ) + { + ++nContiguousControlPoints; + } + else if( nContiguousControlPoints ) + { + OSL_PRECOND(nContiguousControlPoints==2,"broken bezier path"); + + // have two control points before us. the current one + // is a normal point - thus, convert previous points + // into bezier segment + const sal_uInt32 nPoints( aSubPath.count() ); + const basegfx::B2DPoint aCtrlA( aSubPath.getB2DPoint(nPoints-3) ); + const basegfx::B2DPoint aCtrlB( aSubPath.getB2DPoint(nPoints-2) ); + const basegfx::B2DPoint aEnd( aSubPath.getB2DPoint(nPoints-1) ); + aSubPath.remove(nPoints-3, 3); + aSubPath.appendBezierSegment(aCtrlA, aCtrlB, aEnd); + + nContiguousControlPoints=0; + } + } + + aResult.append( aSubPath ); + if( m_nCharIndex != std::string_view::npos ) + readNextToken(); + } + + return static_cast<rendering::XLinePolyPolygon2D*>( + new basegfx::unotools::UnoPolyPolygon(std::move(aResult))); +} + +void LineParser::readChar() +{ + double fontSize; + geometry::Matrix2D aUnoMatrix; + geometry::RealRectangle2D aRect; + + readDouble(aRect.X1); + readDouble(aRect.Y1); + readDouble(aRect.X2); + readDouble(aRect.Y2); + readDouble(aUnoMatrix.m00); + readDouble(aUnoMatrix.m01); + readDouble(aUnoMatrix.m10); + readDouble(aUnoMatrix.m11); + readDouble(fontSize); + + OString aChars; + + if (m_nCharIndex != std::string_view::npos) + aChars = lcl_unescapeLineFeeds( m_aLine.subView( m_nCharIndex ) ); + + // chars gobble up rest of line + m_nCharIndex = std::string_view::npos; + + m_parser.m_pSink->drawGlyphs(OStringToOUString(aChars, RTL_TEXTENCODING_UTF8), + aRect, aUnoMatrix, fontSize); +} + +void LineParser::readLineCap() +{ + sal_Int8 nCap(rendering::PathCapType::BUTT); + switch( readInt32() ) + { + default: + case 0: nCap = rendering::PathCapType::BUTT; break; + case 1: nCap = rendering::PathCapType::ROUND; break; + case 2: nCap = rendering::PathCapType::SQUARE; break; + } + m_parser.m_pSink->setLineCap(nCap); +} + +void LineParser::readLineDash() +{ + if( m_nCharIndex == std::string_view::npos ) + { + m_parser.m_pSink->setLineDash( uno::Sequence<double>(), 0.0 ); + return; + } + + const double nOffset(readDouble()); + const sal_Int32 nLen(readInt32()); + + uno::Sequence<double> aDashArray(nLen); + double* pArray=aDashArray.getArray(); + for( sal_Int32 i=0; i<nLen; ++i ) + *pArray++ = readDouble(); + + m_parser.m_pSink->setLineDash( aDashArray, nOffset ); +} + +void LineParser::readLineJoin() +{ + sal_Int8 nJoin(rendering::PathJoinType::MITER); + switch( readInt32() ) + { + default: + case 0: nJoin = rendering::PathJoinType::MITER; break; + case 1: nJoin = rendering::PathJoinType::ROUND; break; + case 2: nJoin = rendering::PathJoinType::BEVEL; break; + } + m_parser.m_pSink->setLineJoin(nJoin); +} + +void LineParser::readTransformation() +{ + geometry::AffineMatrix2D aMat; + readDouble(aMat.m00); + readDouble(aMat.m10); + readDouble(aMat.m01); + readDouble(aMat.m11); + readDouble(aMat.m02); + readDouble(aMat.m12); + m_parser.m_pSink->setTransformation( aMat ); +} + +rendering::ARGBColor LineParser::readColor() +{ + rendering::ARGBColor aRes; + readDouble(aRes.Red); + readDouble(aRes.Green); + readDouble(aRes.Blue); + readDouble(aRes.Alpha); + return aRes; +} + +/* Parse and convert the font family name (passed from xpdfimport) to correct font names +e.g. TimesNewRomanPSMT -> TimesNewRoman + TimesNewRomanPS-BoldMT -> TimesNewRoman + TimesNewRomanPS-BoldItalicMT -> TimesNewRoman +During the conversion, also apply the font features (bold italic etc) to the result. + +TODO: Further convert the font names to real font names in the system rather than the PS names. +e.g., TimesNewRoman -> Times New Roman +*/ +void LineParser::parseFontFamilyName( FontAttributes& rResult ) +{ + SAL_INFO("sdext.pdfimport", "Processing " << rResult.familyName << " ---"); + rResult.familyName = rResult.familyName.trim(); + for (const OUString& fontAttributesSuffix: fontAttributesSuffixes) + { + if ( rResult.familyName.endsWith(fontAttributesSuffix) ) + { + rResult.familyName = rResult.familyName.replaceAll(fontAttributesSuffix, ""); + SAL_INFO("sdext.pdfimport", rResult.familyName); + if (fontAttributesSuffix == u"Heavy" || fontAttributesSuffix == u"Black") + { + rResult.fontWeight = u"900"; + } + else if (fontAttributesSuffix == u"ExtraBold" || fontAttributesSuffix == u"UltraBold") + { + rResult.fontWeight = u"800"; + } + else if (fontAttributesSuffix == u"Bold") + { + rResult.fontWeight = u"bold"; + } + else if (fontAttributesSuffix == u"Semibold") + { + rResult.fontWeight = u"600"; + } + else if (fontAttributesSuffix == u"Medium") + { + rResult.fontWeight = u"500"; + } + else if (fontAttributesSuffix == u"Normal" || fontAttributesSuffix == u"Regular" || fontAttributesSuffix == u"Book") + { + rResult.fontWeight = u"400"; + } + else if (fontAttributesSuffix == u"Light") + { + rResult.fontWeight = u"300"; + } + else if (fontAttributesSuffix == u"ExtraLight" || fontAttributesSuffix == u"UltraLight") + { + rResult.fontWeight = u"200"; + } + else if (fontAttributesSuffix == u"Thin") + { + rResult.fontWeight = u"100"; + } + + if ( (fontAttributesSuffix == "Italic") or (fontAttributesSuffix == "Oblique") ) + { + rResult.isItalic = true; + } + } + } +} + +void LineParser::readFont() +{ + /* + xpdf line is like (separated by space): + updateFont <FontID> <isEmbedded> <maFontWeight> <isItalic> <isUnderline> <TransformedFontSize> <nEmbedSize> <FontName> + updateFont 14 1 4 0 0 1200.000000 23068 TimesNewRomanPSMT + + If nEmbedSize > 0, then a fontFile is followed as a stream. + */ + sal_Int64 nFontID; + sal_Int32 nIsEmbedded; + sal_Int32 nFontWeight; + sal_Int32 nIsItalic; + sal_Int32 nIsUnderline; + double nSize; + sal_Int32 nFileLen; + OString aFontName; + + readInt64(nFontID); // read FontID + readInt32(nIsEmbedded); // read isEmbedded + readInt32(nFontWeight); // read maFontWeight, see GfxFont enum Weight + readInt32(nIsItalic); // read isItalic + readInt32(nIsUnderline);// read isUnderline + readDouble(nSize); // read TransformedFontSize + readInt32(nFileLen); // read nEmbedSize + + nSize = nSize < 0.0 ? -nSize : nSize; + // Read FontName. From the current position to the end (any white spaces will be included). + aFontName = lcl_unescapeLineFeeds(m_aLine.subView(m_nCharIndex)); + + // name gobbles up rest of line + m_nCharIndex = std::string_view::npos; + + // Check if this font is already in our font map list. + // If yes, update the font size and skip. + Parser::FontMapType::const_iterator pFont( m_parser.m_aFontMap.find(nFontID) ); + if( pFont != m_parser.m_aFontMap.end() ) + { + OSL_PRECOND(nFileLen==0,"font data for known font"); + FontAttributes aRes(pFont->second); + aRes.size = nSize; + m_parser.m_pSink->setFont( aRes ); + + return; + } + + // The font is not yet in the map list - get info and add to map + OUString sFontWeight; // font weight name per ODF specifications + if (nFontWeight == 0 or nFontWeight == 4) // WeightNotDefined or W400, map to normal font + sFontWeight = u"normal"; + else if (nFontWeight == 1) // W100, Thin + sFontWeight = u"100"; + else if (nFontWeight == 2) // W200, Extra-Light + sFontWeight = u"200"; + else if (nFontWeight == 3) // W300, Light + sFontWeight = u"300"; + else if (nFontWeight == 5) // W500, Medium. Is this supported by ODF? + sFontWeight = u"500"; + else if (nFontWeight == 6) // W600, Semi-Bold + sFontWeight = u"600"; + else if (nFontWeight == 7) // W700, Bold + sFontWeight = u"bold"; + else if (nFontWeight == 8) // W800, Extra-Bold + sFontWeight = u"800"; + else if (nFontWeight == 9) // W900, Black + sFontWeight = u"900"; + SAL_INFO("sdext.pdfimport", "Font weight passed from xpdfimport is: " << sFontWeight); + + FontAttributes aResult( OStringToOUString( aFontName, RTL_TEXTENCODING_UTF8 ), + sFontWeight, + nIsItalic != 0, + nIsUnderline != 0, + nSize, + 1.0); + + /* The above font attributes (fontName, fontWeight, italic) are based on + xpdf line output and may not be reliable. To get correct attributes, + we do the following: + 1. Read the embedded font file and determine the attributes based on the + font file. + 2. If we failed to read the font file, or empty result is returned, then + determine the font attributes from the font name. + 3. If all these attempts have failed, then use a fallback font. + */ + if (nFileLen > 0) + { + uno::Sequence<sal_Int8> aFontFile(nFileLen); + readBinaryData(aFontFile); // Read fontFile. + + vcl::Font aFontReadResult = vcl::Font::identifyFont(aFontFile.getArray(), nFileLen); + SAL_INFO("sdext.pdfimport", "familyName: " << aFontReadResult.GetFamilyName()); + + if (!aFontReadResult.GetFamilyName().isEmpty()) // font detection successful + { + // Family name + aResult.familyName = aFontReadResult.GetFamilyName(); + SAL_INFO("sdext.pdfimport", aResult.familyName); + // tdf#143959: there are cases when the family name returned by font descriptor + // is like "AAAAAA+TimesNewRoman,Bold". In this case, use the font name + // determined by parseFontFamilyName instead, but still determine the font + // attributes (bold italic etc) from the font descriptor. + if (aResult.familyName.getLength() > 7 and aResult.familyName.indexOf(u"+", 6) == 6) + { + aResult.familyName = aResult.familyName.copy(7, aResult.familyName.getLength() - 7); + parseFontFamilyName(aResult); + } + if (aResult.familyName.endsWithIgnoreAsciiCase("-VKana")) + { + parseFontFamilyName(aResult); + } + + // Font weight + if (aFontReadResult.GetWeight() == WEIGHT_THIN) + aResult.fontWeight = u"100"; + else if (aFontReadResult.GetWeight() == WEIGHT_ULTRALIGHT) + aResult.fontWeight = u"200"; + else if (aFontReadResult.GetWeight() == WEIGHT_LIGHT) + aResult.fontWeight = u"300"; + else if (aFontReadResult.GetWeight() == WEIGHT_SEMILIGHT) + aResult.fontWeight = u"350"; + // no need to check "normal" here as this is default in nFontWeight above + else if (aFontReadResult.GetWeight() == WEIGHT_SEMIBOLD) + aResult.fontWeight = u"600"; + else if (aFontReadResult.GetWeight() == WEIGHT_BOLD) + aResult.fontWeight = u"bold"; + else if (aFontReadResult.GetWeight() == WEIGHT_ULTRABOLD) + aResult.fontWeight = u"800"; + else if (aFontReadResult.GetWeight() == WEIGHT_BLACK) + aResult.fontWeight = u"900"; + SAL_INFO("sdext.pdfimport", aResult.fontWeight); + + // Italic + aResult.isItalic = (aFontReadResult.GetItalic() == ITALIC_OBLIQUE || + aFontReadResult.GetItalic() == ITALIC_NORMAL); + } else // font detection failed + { + SAL_WARN("sdext.pdfimport", + "Font detection from fontFile returned empty result. Guessing font info from font name."); + parseFontFamilyName(aResult); + } + + } else // no embedded font file - guess font attributes from font name + { + parseFontFamilyName(aResult); + } + + // last fallback + if (aResult.familyName.isEmpty()) + { + SAL_WARN("sdext.pdfimport", "Failed to determine the font, using a fallback font Arial."); + aResult.familyName = "Arial"; + } + + if (!m_parser.m_xDev) + m_parser.m_xDev.disposeAndReset(VclPtr<VirtualDevice>::Create()); + + vcl::Font font(aResult.familyName, Size(0, 1000)); + m_parser.m_xDev->SetFont(font); + FontMetric metric(m_parser.m_xDev->GetFontMetric()); + aResult.ascent = metric.GetAscent() / 1000.0; + + m_parser.m_aFontMap[nFontID] = aResult; + + aResult.size = nSize; + m_parser.m_pSink->setFont(aResult); +} + +uno::Sequence<beans::PropertyValue> LineParser::readImageImpl() +{ + std::string_view aToken = readNextToken(); + const sal_Int32 nImageSize( readInt32() ); + + OUString aFileName; + if( aToken == "PNG" ) + aFileName = "DUMMY.PNG"; + else if( aToken == "JPEG" ) + aFileName = "DUMMY.JPEG"; + else if( aToken == "PBM" ) + aFileName = "DUMMY.PBM"; + else + { + SAL_WARN_IF(aToken != "PPM","sdext.pdfimport","Invalid bitmap format"); + aFileName = "DUMMY.PPM"; + } + + uno::Sequence<sal_Int8> aDataSequence(nImageSize); + readBinaryData( aDataSequence ); + + uno::Sequence< uno::Any > aStreamCreationArgs{ uno::Any(aDataSequence) }; + + uno::Reference< uno::XComponentContext > xContext( m_parser.m_xContext, uno::UNO_SET_THROW ); + uno::Reference< lang::XMultiComponentFactory > xFactory( xContext->getServiceManager(), uno::UNO_SET_THROW ); + uno::Reference< io::XInputStream > xDataStream( + xFactory->createInstanceWithArgumentsAndContext( "com.sun.star.io.SequenceInputStream", aStreamCreationArgs, m_parser.m_xContext ), + uno::UNO_QUERY_THROW ); + + uno::Sequence<beans::PropertyValue> aSequence( comphelper::InitPropertySequence({ + { "URL", uno::Any(aFileName) }, + { "InputStream", uno::Any( xDataStream ) }, + { "InputSequence", uno::Any(aDataSequence) } + })); + + return aSequence; +} + +void LineParser::readImage() +{ + sal_Int32 nWidth, nHeight,nMaskColors; + readInt32(nWidth); + readInt32(nHeight); + readInt32(nMaskColors); + + uno::Sequence<beans::PropertyValue> aImg( readImageImpl() ); + + if( nMaskColors ) + { + uno::Sequence<sal_Int8> aDataSequence(nMaskColors); + readBinaryData( aDataSequence ); + + uno::Sequence<double> aMinRange(nMaskColors/2); + auto pMinRange = aMinRange.getArray(); + uno::Sequence<double> aMaxRange(nMaskColors/2); + auto pMaxRange = aMaxRange.getArray(); + for( sal_Int32 i=0; i<nMaskColors/2; ++i ) + { + pMinRange[i] = aDataSequence[i] / 255.0; + pMaxRange[i] = aDataSequence[i+nMaskColors/2] / 255.0; + } + + uno::Sequence<uno::Any> aMaskRanges{ uno::Any(aMinRange), uno::Any(aMaxRange) }; + m_parser.m_pSink->drawColorMaskedImage( aImg, aMaskRanges ); + } + else + m_parser.m_pSink->drawImage( aImg ); +} + +void LineParser::readMask() +{ + sal_Int32 nWidth, nHeight, nInvert; + readInt32(nWidth); + readInt32(nHeight); + readInt32(nInvert); + + m_parser.m_pSink->drawMask( readImageImpl(), nInvert != 0); +} + +void LineParser::readLink() +{ + geometry::RealRectangle2D aBounds; + readDouble(aBounds.X1); + readDouble(aBounds.Y1); + readDouble(aBounds.X2); + readDouble(aBounds.Y2); + + m_parser.m_pSink->hyperLink( aBounds, + OStringToOUString( lcl_unescapeLineFeeds( + m_aLine.subView(m_nCharIndex) ), + RTL_TEXTENCODING_UTF8 ) ); + // name gobbles up rest of line + m_nCharIndex = std::string_view::npos; +} + +void LineParser::readMaskedImage() +{ + sal_Int32 nWidth, nHeight, nMaskWidth, nMaskHeight, nMaskInvert; + readInt32(nWidth); + readInt32(nHeight); + readInt32(nMaskWidth); + readInt32(nMaskHeight); + readInt32(nMaskInvert); + + const uno::Sequence<beans::PropertyValue> aImage( readImageImpl() ); + const uno::Sequence<beans::PropertyValue> aMask ( readImageImpl() ); + m_parser.m_pSink->drawMaskedImage( aImage, aMask, nMaskInvert != 0 ); +} + +void LineParser::readSoftMaskedImage() +{ + sal_Int32 nWidth, nHeight, nMaskWidth, nMaskHeight; + readInt32(nWidth); + readInt32(nHeight); + readInt32(nMaskWidth); + readInt32(nMaskHeight); + + const uno::Sequence<beans::PropertyValue> aImage( readImageImpl() ); + const uno::Sequence<beans::PropertyValue> aMask ( readImageImpl() ); + m_parser.m_pSink->drawAlphaMaskedImage( aImage, aMask ); +} + +void Parser::parseLine( const OString& rLine ) +{ + OSL_PRECOND( m_pSink, "Invalid sink" ); + OSL_PRECOND( m_pErr, "Invalid filehandle" ); + OSL_PRECOND( m_xContext.is(), "Invalid service factory" ); + + LineParser lp(*this, rLine); + const std::string_view rCmd = lp.readNextToken(); + const hash_entry* pEntry = PdfKeywordHash::in_word_set( rCmd.data(), + rCmd.size() ); + OSL_ASSERT(pEntry); + switch( pEntry->eKey ) + { + case CLIPPATH: + m_pSink->intersectClip(lp.readPath()); break; + case DRAWCHAR: + lp.readChar(); break; + case DRAWIMAGE: + lp.readImage(); break; + case DRAWLINK: + lp.readLink(); break; + case DRAWMASK: + lp.readMask(); break; + case DRAWMASKEDIMAGE: + lp.readMaskedImage(); break; + case DRAWSOFTMASKEDIMAGE: + lp.readSoftMaskedImage(); break; + case ENDPAGE: + m_pSink->endPage(); break; + case ENDTEXTOBJECT: + m_pSink->endText(); break; + case EOCLIPPATH: + m_pSink->intersectEoClip(lp.readPath()); break; + case EOFILLPATH: + m_pSink->eoFillPath(lp.readPath()); break; + case FILLPATH: + m_pSink->fillPath(lp.readPath()); break; + case RESTORESTATE: + m_pSink->popState(); break; + case SAVESTATE: + m_pSink->pushState(); break; + case SETPAGENUM: + m_pSink->setPageNum( lp.readInt32() ); break; + case STARTPAGE: + { + const double nWidth ( lp.readDouble() ); + const double nHeight( lp.readDouble() ); + m_pSink->startPage( geometry::RealSize2D( nWidth, nHeight ) ); + break; + } + case STROKEPATH: + m_pSink->strokePath(lp.readPath()); break; + case UPDATECTM: + lp.readTransformation(); break; + case UPDATEFILLCOLOR: + m_pSink->setFillColor( lp.readColor() ); break; + case UPDATEFLATNESS: + m_pSink->setFlatness( lp.readDouble( ) ); break; + case UPDATEFONT: + lp.readFont(); break; + case UPDATELINECAP: + lp.readLineCap(); break; + case UPDATELINEDASH: + lp.readLineDash(); break; + case UPDATELINEJOIN: + lp.readLineJoin(); break; + case UPDATELINEWIDTH: + m_pSink->setLineWidth( lp.readDouble() );break; + case UPDATEMITERLIMIT: + m_pSink->setMiterLimit( lp.readDouble() ); break; + case UPDATESTROKECOLOR: + m_pSink->setStrokeColor( lp.readColor() ); break; + case UPDATESTROKEOPACITY: + break; + case SETTEXTRENDERMODE: + m_pSink->setTextRenderMode( lp.readInt32() ); break; + + case NONE: + default: + OSL_PRECOND(false,"Unknown input"); + break; + } + + // all consumed? + SAL_WARN_IF( + lp.m_nCharIndex!=std::string_view::npos, "sdext.pdfimport", "leftover scanner input"); +} + +} // namespace + +static bool checkEncryption( std::u16string_view i_rPath, + const uno::Reference< task::XInteractionHandler >& i_xIHdl, + OUString& io_rPwd, + bool& o_rIsEncrypted, + const OUString& i_rDocName + ) +{ + bool bSuccess = false; + OString aPDFFile = OUStringToOString( i_rPath, osl_getThreadTextEncoding() ); + + std::unique_ptr<pdfparse::PDFEntry> pEntry( pdfparse::PDFReader::read( aPDFFile.getStr() )); + if( pEntry ) + { + pdfparse::PDFFile* pPDFFile = dynamic_cast<pdfparse::PDFFile*>(pEntry.get()); + if( pPDFFile ) + { + o_rIsEncrypted = pPDFFile->isEncrypted(); + if( o_rIsEncrypted ) + { + if( pPDFFile->usesSupportedEncryptionFormat() ) + { + bool bAuthenticated = false; + if( !io_rPwd.isEmpty() ) + { + OString aIsoPwd = OUStringToOString( io_rPwd, + RTL_TEXTENCODING_ISO_8859_1 ); + bAuthenticated = pPDFFile->setupDecryptionData( aIsoPwd.getStr() ); + } + if( bAuthenticated ) + bSuccess = true; + else + { + if( i_xIHdl.is() ) + { + bool bEntered = false; + do + { + bEntered = getPassword( i_xIHdl, io_rPwd, ! bEntered, i_rDocName ); + OString aIsoPwd = OUStringToOString( io_rPwd, + RTL_TEXTENCODING_ISO_8859_1 ); + bAuthenticated = pPDFFile->setupDecryptionData( aIsoPwd.getStr() ); + } while( bEntered && ! bAuthenticated ); + } + + bSuccess = bAuthenticated; + } + } + else if( i_xIHdl.is() ) + { + reportUnsupportedEncryptionFormat( i_xIHdl ); + //TODO: this should either be handled further down the + // call stack, or else information that this has already + // been handled should be passed down the call stack, so + // that SfxBaseModel::load does not show an additional + // "General Error" message box + } + } + else + bSuccess = true; + } + } + return bSuccess; +} + +namespace { + +class Buffering +{ + static const int SIZE = 64*1024; + std::unique_ptr<char[]> aBuffer; + oslFileHandle& pOut; + size_t pos; + sal_uInt64 left; + +public: + explicit Buffering(oslFileHandle& out) : aBuffer(new char[SIZE]), pOut(out), pos(0), left(0) {} + + oslFileError read(char *pChar, short count, sal_uInt64* pBytesRead) + { + oslFileError nRes = osl_File_E_None; + sal_uInt64 nBytesRead = 0; + while (count > 0) + { + if (left == 0) + { + nRes = osl_readFile(pOut, aBuffer.get(), SIZE, &left); + if (nRes != osl_File_E_None || left == 0) + { + *pBytesRead = nBytesRead; + return nRes; + } + pos = 0; + } + *pChar = aBuffer.get()[pos]; + --count; + ++pos; + --left; + ++pChar; + ++nBytesRead; + } + *pBytesRead = nBytesRead; + return osl_File_E_None; + } +}; + +} + +bool xpdf_ImportFromFile(const OUString& rURL, + const ContentSinkSharedPtr& rSink, + const uno::Reference<task::XInteractionHandler>& xIHdl, + const OUString& rPwd, + const uno::Reference<uno::XComponentContext>& xContext, + const OUString& rFilterOptions) +{ + OSL_ASSERT(rSink); + + OUString aSysUPath; + if( osl_getSystemPathFromFileURL( rURL.pData, &aSysUPath.pData ) != osl_File_E_None ) + { + SAL_WARN( + "sdext.pdfimport", + "getSystemPathFromFileURL(" << rURL << ") failed"); + return false; + } + OUString aDocName( rURL.copy( rURL.lastIndexOf( '/' )+1 ) ); + + // check for encryption, if necessary get password + OUString aPwd( rPwd ); + bool bIsEncrypted = false; + if( !checkEncryption( aSysUPath, xIHdl, aPwd, bIsEncrypted, aDocName ) ) + { + SAL_INFO( + "sdext.pdfimport", + "checkEncryption(" << aSysUPath << ") failed"); + return false; + } + + // Determine xpdfimport executable URL: + OUString converterURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/xpdfimport"); + rtl::Bootstrap::expandMacros(converterURL); //TODO: detect failure + + // Determine pathname of xpdfimport_err.pdf: + OUString errPathname("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/xpdfimport/xpdfimport_err.pdf"); + rtl::Bootstrap::expandMacros(errPathname); //TODO: detect failure + if (osl::FileBase::getSystemPathFromFileURL(errPathname, errPathname) + != osl::FileBase::E_None) + { + SAL_WARN( + "sdext.pdfimport", + "getSystemPathFromFileURL(" << errPathname << ") failed"); + return false; + } + + // spawn separate process to keep LGPL/GPL code apart. + + OUString aOptFlag("-o"); + rtl_uString* args[] = { aSysUPath.pData, errPathname.pData, + aOptFlag.pData, rFilterOptions.pData }; + sal_Int32 nArgs = rFilterOptions.isEmpty() ? 2 : 4; + + oslProcess aProcess; + oslFileHandle pIn = nullptr; + oslFileHandle pOut = nullptr; + oslFileHandle pErr = nullptr; + oslSecurity pSecurity = osl_getCurrentSecurity (); + oslProcessError eErr = + osl_executeProcess_WithRedirectedIO(converterURL.pData, + args, + nArgs, + osl_Process_SEARCHPATH|osl_Process_HIDDEN, + pSecurity, + nullptr, nullptr, 0, + &aProcess, &pIn, &pOut, &pErr); + osl_freeSecurityHandle(pSecurity); + + bool bRet=true; + try + { + if( eErr!=osl_Process_E_None ) + { + SAL_WARN( + "sdext.pdfimport", + "executeProcess of " << converterURL << " failed with " + << +eErr); + return false; + } + + if( pIn ) + { + OStringBuffer aBuf(256); + if( bIsEncrypted ) + aBuf.append( OUStringToOString( aPwd, RTL_TEXTENCODING_ISO_8859_1 ) ); + aBuf.append( '\n' ); + + sal_uInt64 nWritten = 0; + osl_writeFile( pIn, aBuf.getStr(), sal_uInt64(aBuf.getLength()), &nWritten ); + } + + if( pOut && pErr ) + { + // read results of PDF parser. One line - one call to + // OutputDev. stderr is used for alternate streams, like + // embedded fonts and bitmaps + Parser aParser(rSink,pErr,xContext); + Buffering aBuffering(pOut); + OStringBuffer line; + for( ;; ) + { + char aChar('\n'); + sal_uInt64 nBytesRead; + oslFileError nRes; + + // skip garbage \r \n at start of line + for (;;) + { + nRes = aBuffering.read(&aChar, 1, &nBytesRead); + if (osl_File_E_None != nRes || nBytesRead != 1 || (aChar != '\n' && aChar != '\r') ) + break; + } + if ( osl_File_E_None != nRes ) + break; + + if( aChar != '\n' && aChar != '\r' ) + line.append( aChar ); + + for (;;) + { + nRes = aBuffering.read(&aChar, 1, &nBytesRead); + if ( osl_File_E_None != nRes || nBytesRead != 1 || aChar == '\n' || aChar == '\r' ) + break; + line.append( aChar ); + } + if ( osl_File_E_None != nRes ) + break; + if ( line.isEmpty() ) + break; + + aParser.parseLine(line.makeStringAndClear()); + } + } + } + catch( uno::Exception& ) + { + // crappy C file interface. need manual resource dealloc + bRet = false; + } + + if( pIn ) + osl_closeFile(pIn); + if( pOut ) + osl_closeFile(pOut); + if( pErr ) + osl_closeFile(pErr); + eErr = osl_joinProcess(aProcess); + if (eErr == osl_Process_E_None) + { + oslProcessInfo info; + info.Size = sizeof info; + eErr = osl_getProcessInfo(aProcess, osl_Process_EXITCODE, &info); + if (eErr == osl_Process_E_None) + { + if (info.Code != 0) + { + SAL_WARN( + "sdext.pdfimport", + "getProcessInfo of " << converterURL + << " failed with exit code " << info.Code); + bRet = false; + } + } + else + { + SAL_WARN( + "sdext.pdfimport", + "getProcessInfo of " << converterURL << " failed with " + << +eErr); + bRet = false; + } + } + else + { + SAL_WARN( + "sdext.pdfimport", + "joinProcess of " << converterURL << " failed with " << +eErr); + bRet = false; + } + osl_freeProcessHandle(aProcess); + return bRet; +} + + +bool xpdf_ImportFromStream( const uno::Reference< io::XInputStream >& xInput, + const ContentSinkSharedPtr& rSink, + const uno::Reference<task::XInteractionHandler >& xIHdl, + const OUString& rPwd, + const uno::Reference< uno::XComponentContext >& xContext, + const OUString& rFilterOptions ) +{ + OSL_ASSERT(xInput.is()); + OSL_ASSERT(rSink); + + // convert XInputStream to local temp file + oslFileHandle aFile = nullptr; + OUString aURL; + if( osl_createTempFile( nullptr, &aFile, &aURL.pData ) != osl_File_E_None ) + return false; + + // copy content, buffered... + const sal_uInt32 nBufSize = 4096; + uno::Sequence<sal_Int8> aBuf( nBufSize ); + sal_uInt64 nBytes = 0; + sal_uInt64 nWritten = 0; + bool bSuccess = true; + do + { + try + { + nBytes = xInput->readBytes( aBuf, nBufSize ); + } + catch( css::uno::Exception& ) + { + osl_closeFile( aFile ); + throw; + } + if( nBytes > 0 ) + { + osl_writeFile( aFile, aBuf.getConstArray(), nBytes, &nWritten ); + if( nWritten != nBytes ) + { + bSuccess = false; + break; + } + } + } + while( nBytes == nBufSize ); + + osl_closeFile( aFile ); + + if ( bSuccess ) + bSuccess = xpdf_ImportFromFile( aURL, rSink, xIHdl, rPwd, xContext, rFilterOptions ); + osl_removeFile( aURL.pData ); + + return bSuccess; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |