// Copyright (C) 2018-2021 Jakub Melka // // This file is part of PDF4QT. // // PDF4QT is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // with the written consent of the copyright owner, any later version. // // PDF4QT is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with PDF4QT. If not, see . #include "pdfstreamfilters.h" #include "pdfexception.h" #include "pdfconstants.h" #include "pdfparser.h" #include "pdfsecurityhandler.h" #include "pdfutils.h" #include #include namespace pdf { QByteArray PDFAsciiHexDecodeFilter::apply(const QByteArray& data, const PDFObjectFetcher& objectFetcher, const PDFObject& parameters, const PDFSecurityHandler* securityHandler) const { Q_UNUSED(objectFetcher); Q_UNUSED(parameters); Q_UNUSED(securityHandler); const int indexOfEnd = data.indexOf('>'); const int size = (indexOfEnd == -1) ? data.size() : indexOfEnd; if (size % 2 == 1) { // We must add trailing zero to the buffer QByteArray temporaryData(data.constData(), size); temporaryData.push_back('0'); return QByteArray::fromHex(temporaryData); } else if (size == data.size()) { // We do this, because we do not want to allocate unnecessary buffer for this case. // This case should be common. return QByteArray::fromHex(data); } return QByteArray::fromHex(QByteArray::fromRawData(data.constData(), size)); } QByteArray PDFAscii85DecodeFilter::apply(const QByteArray& data, const PDFObjectFetcher& objectFetcher, const PDFObject& parameters, const PDFSecurityHandler* securityHandler) const { Q_UNUSED(objectFetcher); Q_UNUSED(parameters); Q_UNUSED(securityHandler); const unsigned char* dataBegin = reinterpret_cast(data.constData()); const unsigned char* dataEnd = reinterpret_cast(data.constData() + data.size()); const unsigned char* it = dataBegin; const constexpr uint32_t STREAM_END = 0xFFFFFFFF; auto getChar = [&it, dataEnd]() -> uint32_t { // Skip whitespace characters while (it != dataEnd && PDFLexicalAnalyzer::isWhitespace(*it)) { ++it; } if (it == dataEnd || (*it == '~')) { return STREAM_END; } return *it++; }; QByteArray result; result.reserve(data.size() * 4 / 5); while (true) { const uint32_t scannedChar = getChar(); if (scannedChar == STREAM_END) { break; } else if (scannedChar == 'z') { result.append(4, static_cast(0)); } else { // Scan all 5 characters, some of then can be equal to STREAM_END constant. We will // treat all these characters as last character. std::array scannedChars; scannedChars.fill(84); scannedChars[0] = scannedChar - 33; int validBytes = 0; for (auto it = std::next(scannedChars.begin()); it != scannedChars.end(); ++it) { uint32_t character = getChar(); if (character == STREAM_END) { break; } *it = character - 33; ++validBytes; } // Decode bytes using 85 base uint32_t decodedBytesPacked = 0; for (const uint32_t value : scannedChars) { decodedBytesPacked = decodedBytesPacked * 85 + value; } // Decode bytes into byte array std::array decodedBytesUnpacked; decodedBytesUnpacked.fill(0); for (auto byteIt = decodedBytesUnpacked.rbegin(); byteIt != decodedBytesUnpacked.rend(); ++byteIt) { *byteIt = static_cast(decodedBytesPacked & 0xFF); decodedBytesPacked = decodedBytesPacked >> 8; } Q_ASSERT(validBytes <= decodedBytesUnpacked.size()); for (int i = 0; i < validBytes; ++i) { result.push_back(decodedBytesUnpacked[i]); } } } return result; } class PDFLzwStreamDecoder { public: explicit PDFLzwStreamDecoder(const QByteArray& inputByteArray, uint32_t early); QByteArray decompress(); private: static constexpr const uint32_t CODE_TABLE_RESET = 256; static constexpr const uint32_t CODE_END_OF_STREAM = 257; // Maximal code size is 12 bits. so we can have 2^12 = 4096 items // in the table (some items are unused, for example 256, 257). We also // need to initialize items under code 256, because we treat them specially, // they are not initialized in the decompress. static constexpr const uint32_t TABLE_SIZE = 4096; /// Clears the input data table void clearTable(); /// Returns a newly scanned code uint32_t getCode(); struct TableItem { uint32_t previous = TABLE_SIZE; char character = 0; }; std::array m_table; std::array m_sequence; uint32_t m_nextCode; ///< Next code value (to be written into the table) uint32_t m_nextBits; ///< Number of bits of the next code uint32_t m_early; ///< Early (see PDF 1.7 Specification, this constant is 0 or 1, based on the dictionary value) uint32_t m_inputBuffer; ///< Input buffer, containing bits, which were read from the input byte array uint32_t m_inputBits; ///< Number of bits in the input buffer. std::array::iterator m_currentSequenceEnd; bool m_first; ///< Are we reading from stream for first time after the reset char m_newCharacter; ///< New character to be written int m_position; ///< Position in the input array const QByteArray& m_inputByteArray; }; PDFLzwStreamDecoder::PDFLzwStreamDecoder(const QByteArray& inputByteArray, uint32_t early) : m_table(), m_sequence(), m_nextCode(0), m_nextBits(0), m_early(early), m_inputBuffer(0), m_inputBits(0), m_currentSequenceEnd(m_sequence.begin()), m_first(false), m_newCharacter(0), m_position(0), m_inputByteArray(inputByteArray) { for (size_t i = 0; i < 256; ++i) { m_table[i].character = static_cast(i); m_table[i].previous = TABLE_SIZE; } clearTable(); } QByteArray PDFLzwStreamDecoder::decompress() { QByteArray result; // Guess output byte array size - assume compress ratio is 2:1 result.reserve(m_inputByteArray.size() * 2); uint32_t previousCode = TABLE_SIZE; while (true) { const uint32_t code = getCode(); if (code == CODE_END_OF_STREAM) { // We are at end of stream break; } else if (code == CODE_TABLE_RESET) { // Just reset the table clearTable(); continue; } // Normal operation code if (code < m_nextCode) { m_currentSequenceEnd = m_sequence.begin(); for (uint32_t currentCode = code; currentCode != TABLE_SIZE; currentCode = m_table[currentCode].previous) { *m_currentSequenceEnd++ = m_table[currentCode].character; } // We must reverse the sequence, because we stored it in the // linked list, which we traversed from last to first item. std::reverse(m_sequence.begin(), m_currentSequenceEnd); } else if (code == m_nextCode) { // We use the buffer from previous run, just add a new // character to the end. *m_currentSequenceEnd++ = m_newCharacter; } else { // Unknown code throw PDFException(PDFTranslationContext::tr("Invalid code in the LZW stream.")); } m_newCharacter = m_sequence.front(); if (m_first) { m_first = false; } else { // Add a new word in the dictionary, if we have it if (m_nextCode < TABLE_SIZE) { m_table[m_nextCode].character = m_newCharacter; m_table[m_nextCode].previous = previousCode; ++m_nextCode; } // Change bit size of the code, if it is neccessary switch (m_nextCode + m_early) { case 512: m_nextBits = 10; break; case 1024: m_nextBits = 11; break; case 2048: m_nextBits = 12; break; default: break; } } previousCode = code; // Copy the input array to the buffer std::copy(m_sequence.begin(), m_currentSequenceEnd, std::back_inserter(result)); } result.shrink_to_fit(); return result; } void PDFLzwStreamDecoder::clearTable() { // We do not clear the m_table array here. It is for performance reasons, we assume // the input is correct. We also do not clear the sequence buffer here. m_nextCode = 258; m_nextBits = 9; m_first = true; m_newCharacter = 0; } uint32_t PDFLzwStreamDecoder::getCode() { while (m_inputBits < m_nextBits) { // Did we reach end of array? if (m_position == m_inputByteArray.size()) { return CODE_END_OF_STREAM; } m_inputBuffer = (m_inputBuffer << 8) | static_cast(m_inputByteArray[m_position++]); m_inputBits += 8; } // We must omit bits from left (old ones) and right (newly scanned ones) and // read just m_nextBits bits. Mask should omit the old ones and shift (m_inputBits - m_nextBits) // should omit the new ones. const uint32_t mask = ((1 << m_nextBits) - 1); const uint32_t code = (m_inputBuffer >> (m_inputBits - m_nextBits)) & mask; m_inputBits -= m_nextBits; return code; } QByteArray PDFLzwDecodeFilter::apply(const QByteArray& data, const PDFObjectFetcher& objectFetcher, const PDFObject& parameters, const PDFSecurityHandler* securityHandler) const { Q_UNUSED(securityHandler); uint32_t early = 1; const PDFObject& dereferencedParameters = objectFetcher(parameters); if (dereferencedParameters.isDictionary()) { const PDFDictionary* dictionary = dereferencedParameters.getDictionary(); PDFInteger predictor = 1; const PDFObject& predictorObject = objectFetcher(dictionary->get("Predictor")); if (predictorObject.isInt()) { predictor = predictorObject.getInteger(); } const PDFObject& earlyChangeObject = objectFetcher(dictionary->get("EarlyChange")); if (earlyChangeObject.isInt()) { early = earlyChangeObject.getInteger(); } } PDFStreamPredictor predictor = PDFStreamPredictor::createPredictor(objectFetcher, parameters); PDFLzwStreamDecoder decoder(data, early); return predictor.apply(decoder.decompress()); } QByteArray PDFFlateDecodeFilter::apply(const QByteArray& data, const PDFObjectFetcher& objectFetcher, const PDFObject& parameters, const PDFSecurityHandler* securityHandler) const { Q_UNUSED(securityHandler); const PDFObject& dereferencedParameters = objectFetcher(parameters); if (dereferencedParameters.isDictionary()) { const PDFDictionary* dictionary = dereferencedParameters.getDictionary(); PDFInteger predictor = 1; const PDFObject& predictorObject = objectFetcher(dictionary->get("Predictor")); if (predictorObject.isInt()) { predictor = predictorObject.getInteger(); } } PDFStreamPredictor predictor = PDFStreamPredictor::createPredictor(objectFetcher, parameters); return predictor.apply(uncompress(data)); } QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data) { QByteArray result; QByteArray decompressedData = uncompress(data); z_stream stream = { }; stream.next_in = const_cast(convertByteArrayToUcharPtr(decompressedData)); stream.avail_in = decompressedData.size(); std::array outputBuffer = { }; int error = deflateInit(&stream, Z_BEST_COMPRESSION); if (error != Z_OK) { throw PDFException(PDFTranslationContext::tr("Failed to initialize flate compression stream.")); } do { stream.next_out = outputBuffer.data(); stream.avail_out = static_cast(outputBuffer.size()); error = deflate(&stream, Z_FINISH); int bytesWritten = int(outputBuffer.size()) - stream.avail_out; result.append(reinterpret_cast(outputBuffer.data()), bytesWritten); } while (error == Z_OK); QString errorMessage; if (stream.msg) { errorMessage = QString::fromLatin1(stream.msg); } deflateEnd(&stream); switch (error) { case Z_STREAM_END: break; // No error, normal behaviour default: { if (errorMessage.isEmpty()) { errorMessage = PDFTranslationContext::tr("zlib code: %1").arg(error); } throw PDFException(PDFTranslationContext::tr("Error decompressing by flate method: %1").arg(errorMessage)); } } return result; } PDFInteger PDFFlateDecodeFilter::getStreamDataLength(const QByteArray& data, PDFInteger offset) const { if (offset < 0 || offset >= data.size()) { return -1; } z_stream stream = { }; stream.next_in = const_cast(convertByteArrayToUcharPtr(data) + offset); stream.avail_in = data.size() - offset; std::array outputBuffer = { }; int error = inflateInit(&stream); if (error != Z_OK) { return -1; } do { stream.next_out = outputBuffer.data(); stream.avail_out = static_cast(outputBuffer.size()); error = inflate(&stream, Z_NO_FLUSH); } while (error == Z_OK); PDFInteger dataLength = stream.total_in; inflateEnd(&stream); if (error == Z_STREAM_END) { return dataLength; } return -1; } QByteArray PDFFlateDecodeFilter::uncompress(const QByteArray& data) { QByteArray result; z_stream stream = { }; stream.next_in = const_cast(convertByteArrayToUcharPtr(data)); stream.avail_in = data.size(); std::array outputBuffer = { }; int error = inflateInit(&stream); if (error != Z_OK) { throw PDFException(PDFTranslationContext::tr("Failed to initialize flate decompression stream.")); } do { stream.next_out = outputBuffer.data(); stream.avail_out = static_cast(outputBuffer.size()); error = inflate(&stream, Z_NO_FLUSH); int bytesWritten = int(outputBuffer.size()) - stream.avail_out; result.append(reinterpret_cast(outputBuffer.data()), bytesWritten); } while (error == Z_OK); QString errorMessage; if (stream.msg) { errorMessage = QString::fromLatin1(stream.msg); } inflateEnd(&stream); switch (error) { case Z_STREAM_END: break; // No error, normal behaviour default: { if (errorMessage.isEmpty()) { errorMessage = PDFTranslationContext::tr("zlib code: %1").arg(error); } throw PDFException(PDFTranslationContext::tr("Error decompressing by flate method: %1").arg(errorMessage)); } } return result; } QByteArray PDFRunLengthDecodeFilter::apply(const QByteArray& data, const PDFObjectFetcher& objectFetcher, const PDFObject& parameters, const PDFSecurityHandler* securityHandler) const { Q_UNUSED(objectFetcher); Q_UNUSED(parameters); Q_UNUSED(securityHandler); QByteArray result; result.reserve(data.size() * 2); auto itEnd = data.cend(); for (auto it = data.cbegin(); it != itEnd;) { const unsigned char current = *it++; if (current == 128) { // End of stream marker break; } else if (current < 128) { // Copy n + 1 characters from the input array literally (and advance iterators) const int count = static_cast(current) + 1; std::copy(it, std::next(it, count), std::back_inserter(result)); std::advance(it, count); } else if (current > 128) { // Copy 257 - n copies of single character const int count = 257 - current; const char toBeCopied = *it++; std::fill_n(std::back_inserter(result), count, toBeCopied); } } return result; } const PDFStreamFilter* PDFStreamFilterStorage::getFilter(const QByteArray& filterName) { const PDFStreamFilterStorage* instance = getInstance(); auto it = instance->m_filters.find(filterName); if (it != instance->m_filters.cend()) { return it->second.get(); } auto itNameDecoded = instance->m_abbreviations.find(filterName); if (itNameDecoded != instance->m_abbreviations.cend()) { return getFilter(itNameDecoded->second); } return nullptr; } PDFStreamFilterStorage::StreamFilters PDFStreamFilterStorage::getStreamFilters(const PDFStream* stream, const PDFObjectFetcher& objectFetcher) { StreamFilters result; const PDFDictionary* dictionary = stream->getDictionary(); // Retrieve filters PDFObject filters; if (dictionary->hasKey(PDF_STREAM_DICT_FILTER)) { filters = objectFetcher(dictionary->get(PDF_STREAM_DICT_FILTER)); } else if (dictionary->hasKey(PDF_STREAM_DICT_FILE_FILTER)) { filters = objectFetcher(dictionary->get(PDF_STREAM_DICT_FILE_FILTER)); } // Retrieve filter parameters PDFObject filterParameters; if (dictionary->hasKey(PDF_STREAM_DICT_DECODE_PARMS)) { filterParameters = objectFetcher(dictionary->get(PDF_STREAM_DICT_DECODE_PARMS)); } else if (dictionary->hasKey(PDF_STREAM_DICT_FDECODE_PARMS)) { filterParameters = objectFetcher(dictionary->get(PDF_STREAM_DICT_FDECODE_PARMS)); } if (filters.isName()) { result.filterObjects.push_back(PDFStreamFilterStorage::getFilter(filters.getString())); } else if (filters.isArray()) { const PDFArray* filterArray = filters.getArray(); const size_t filterCount = filterArray->getCount(); for (size_t i = 0; i < filterCount; ++i) { const PDFObject& object = objectFetcher(filterArray->getItem(i)); if (object.isName()) { result.filterObjects.push_back(PDFStreamFilterStorage::getFilter(object.getString())); } else { result.valid = false; return result; } } } else if (!filters.isNull()) { result.valid = false; return result; } if (filterParameters.isArray()) { const PDFArray* filterParameterArray = filterParameters.getArray(); const size_t filterParameterCount = filterParameterArray->getCount(); for (size_t i = 0; i < filterParameterCount; ++i) { const PDFObject& object = objectFetcher(filterParameterArray->getItem(i)); result.filterParameterObjects.push_back(object); } } else { result.filterParameterObjects.push_back(filterParameters); } result.filterParameterObjects.resize(result.filterObjects.size()); std::reverse(result.filterObjects.begin(), result.filterObjects.end()); std::reverse(result.filterParameterObjects.begin(), result.filterParameterObjects.end()); return result; } QByteArray PDFStreamFilterStorage::getDecodedStream(const PDFStream* stream, const PDFObjectFetcher& objectFetcher, const PDFSecurityHandler* securityHandler) { StreamFilters streamFilters = getStreamFilters(stream, objectFetcher); QByteArray result = *stream->getContent(); if (!streamFilters.valid) { // Stream filters are invalid return QByteArray(); } for (size_t i = 0, count = streamFilters.filterObjects.size(); i < count; ++i) { const PDFStreamFilter* streamFilter = streamFilters.filterObjects[i]; const PDFObject& streamFilterParameters = streamFilters.filterParameterObjects[i]; if (streamFilter) { result = streamFilter->apply(result, objectFetcher, streamFilterParameters, securityHandler); } } return result; } QByteArray PDFStreamFilterStorage::getDecodedStream(const PDFStream* stream, const PDFSecurityHandler* securityHandler) { return getDecodedStream(stream, [](const PDFObject& object) -> const PDFObject& { return object; }, securityHandler); } PDFInteger PDFStreamFilterStorage::getStreamDataLength(const QByteArray& data, const QByteArray& filterName, PDFInteger offset) { if (const PDFStreamFilter* filter = getFilter(filterName)) { return filter->getStreamDataLength(data, offset); } return -1; } PDFStreamFilterStorage::PDFStreamFilterStorage() { // Initialize map with the filters m_filters["ASCIIHexDecode"] = std::make_unique(); m_filters["ASCII85Decode"] = std::make_unique(); m_filters["LZWDecode"] = std::make_unique(); m_filters["FlateDecode"] = std::make_unique(); m_filters["RunLengthDecode"] = std::make_unique(); m_filters["Crypt"] = std::make_unique(); m_abbreviations["AHx"] = "ASCIIHexDecode"; m_abbreviations["A85"] = "ASCII85Decode"; m_abbreviations["LZW"] = "LZWDecode"; m_abbreviations["Fl"] = "FlateDecode"; m_abbreviations["RL"] = "RunLengthDecode"; m_abbreviations["CCF"] = "CCITFaxDecode"; m_abbreviations["DCT"] = "DCTDecode"; } const PDFStreamFilterStorage* PDFStreamFilterStorage::getInstance() { static PDFStreamFilterStorage instance; return &instance; } PDFStreamPredictor PDFStreamPredictor::createPredictor(const PDFObjectFetcher& objectFetcher, const PDFObject& parameters) { const PDFObject& dereferencedParameters = objectFetcher(parameters); if (dereferencedParameters.isDictionary()) { const PDFDictionary* dictionary = dereferencedParameters.getDictionary(); auto getInteger = [dictionary, &objectFetcher](const char* key, int min, int max, int defaultValue) -> int { const PDFObject& object = objectFetcher(dictionary->get(key)); if (object.isInt()) { PDFInteger value = object.getInteger(); if (value < min || value > max) { throw PDFException(PDFTranslationContext::tr("Property '%1' should be in range from %2 to %3.").arg(QString::fromLatin1(key)).arg(min).arg(max)); } return value; } else if (object.isNull()) { return defaultValue; } throw PDFException(PDFTranslationContext::tr("Invalid property '%1' of the stream predictor parameters.").arg(QString::fromLatin1(key))); return 0; }; int predictor = getInteger("Predictor", 1, 15, 1); int components = getInteger("Colors", 1, PDF_MAX_COLOR_COMPONENTS, 1); int bitsPerComponent = getInteger("BitsPerComponent", 1, 16, 8); int columns = getInteger("Columns", 1, std::numeric_limits::max(), 1); return PDFStreamPredictor(static_cast(predictor), components, bitsPerComponent, columns); } return PDFStreamPredictor(); } QByteArray PDFStreamPredictor::apply(const QByteArray& data) const { switch (m_predictor) { case NoPredictor: return data; case TIFF: return applyTIFFPredictor(data); default: { if (m_predictor >= 10) { return applyPNGPredictor(data); } break; } } throw PDFException(PDFTranslationContext::tr("Invalid predictor algorithm.")); return QByteArray(); } QByteArray PDFStreamPredictor::applyPNGPredictor(const QByteArray& data) const { QByteArray outputData; outputData.reserve(data.size()); auto it = data.cbegin(); auto itEnd = data.cend(); int pixelBytes = (m_components * m_bitsPerComponent + 7) / 8; auto readByte = [&it, &itEnd]() -> uint8_t { if (it != itEnd) { return static_cast(*it++); } // According to the PDF specification, incomplete line is completed. For this // reason, we behave as we have zero data in the buffer. return 0; }; // Idea: to avoid using if for many cases, we use larger buffer filled with zeros const int totalBytes = m_stride + pixelBytes; std::vector line(totalBytes, 0); std::vector lineOld(totalBytes, 0); Predictor currentPredictor = m_predictor; while (it != itEnd) { // First, read the predictor data for current line currentPredictor = static_cast(readByte() + 10); for (int i = 0; i < m_stride; ++i) { uint8_t currentByte = readByte(); int lineIndex = i + pixelBytes; switch (currentPredictor) { case PNG_Sub: { line[lineIndex] = line[i] + currentByte; break; } case PNG_Up: { line[lineIndex] = lineOld[lineIndex] + currentByte; break; } case PNG_Average: { line[lineIndex] = (lineOld[lineIndex] + line[i]) / 2 + currentByte; break; } case PNG_Paeth: { // a = left, // b = upper, // c = upper left const int a = line[i]; const int b = lineOld[lineIndex]; const int c = lineOld[i]; const int p = a + b - c; const int pa = std::abs(p - a); const int pb = std::abs(p - b); const int pc = std::abs(p - c); if (pa <= pb && pa <= pc) { line[lineIndex] = a + currentByte; } else if (pb <= pc) { line[lineIndex] = b + currentByte; } else { line[lineIndex] = c + currentByte; } break; } case PNG_None: default: { line[lineIndex] = currentByte; break; } } // Fill the output buffer outputData.push_back(static_cast(line[lineIndex])); } // Swap the buffers std::swap(line, lineOld); } return outputData; } QByteArray PDFStreamPredictor::applyTIFFPredictor(const QByteArray& data) const { Q_UNUSED(data); PDFBitWriter writer(m_bitsPerComponent); PDFBitReader reader(&data, m_bitsPerComponent); writer.reserve(data.size()); std::vector leftValues(m_components, 0); while (!reader.isAtEnd()) { for (int i = 0; i < m_columns; ++i) { for (int componentIndex = 0; componentIndex < m_components; ++componentIndex) { leftValues[componentIndex] = (leftValues[componentIndex] + reader.read()) & reader.max(); writer.write(leftValues[componentIndex]); } } std::fill(leftValues.begin(), leftValues.end(), 0); reader.alignToBytes(); writer.finishLine(); } return writer.takeByteArray(); } QByteArray PDFCryptFilter::apply(const QByteArray& data, const PDFObjectFetcher& objectFetcher, const PDFObject& parameters, const PDFSecurityHandler* securityHandler) const { if (!securityHandler) { throw PDFException(PDFTranslationContext::tr("Security handler required, but not provided.")); } PDFObjectReference objectReference; QByteArray cryptFilterName = PDFSecurityHandler::IDENTITY_FILTER_NAME; const PDFObject& dereferencedParameters = objectFetcher(parameters); if (dereferencedParameters.isDictionary()) { const PDFDictionary* dictionary = dereferencedParameters.getDictionary(); const PDFObject& cryptFilterNameObject = objectFetcher(dictionary->get("Name")); if (cryptFilterNameObject.isName()) { cryptFilterName = cryptFilterNameObject.getString(); } const PDFObject& objectReferenceObject = dictionary->get(PDFSecurityHandler::OBJECT_REFERENCE_DICTIONARY_NAME); if (objectReferenceObject.isReference()) { objectReference = objectReferenceObject.getReference(); } } return securityHandler->decryptByFilter(data, cryptFilterName, objectReference); } PDFInteger PDFStreamFilter::getStreamDataLength(const QByteArray& data, PDFInteger offset) const { Q_UNUSED(data); Q_UNUSED(offset); return -1; } } // namespace pdf