mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	Signature validation
This commit is contained in:
		@@ -19,6 +19,7 @@
 | 
			
		||||
#include "pdfdocument.h"
 | 
			
		||||
#include "pdfencoding.h"
 | 
			
		||||
#include "pdfform.h"
 | 
			
		||||
#include "pdfutils.h"
 | 
			
		||||
#include "pdfsignaturehandler_impl.h"
 | 
			
		||||
 | 
			
		||||
#include <openssl/err.h>
 | 
			
		||||
@@ -105,7 +106,7 @@ PDFSignature PDFSignature::parse(const PDFObjectStorage* storage, PDFObject obje
 | 
			
		||||
        result.m_byteRanges.reserve(byteRangeCount);
 | 
			
		||||
        for (size_t i = 0; i < byteRangeCount; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            ByteRange byteRange = { byteRangesArray[i], byteRangesArray[i + 1] };
 | 
			
		||||
            ByteRange byteRange = { byteRangesArray[2 * i], byteRangesArray[2 * i + 1] };
 | 
			
		||||
            result.m_byteRanges.push_back(byteRange);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -287,6 +288,18 @@ void PDFSignatureVerificationResult::addSignatureDataOtherError()
 | 
			
		||||
    m_errors << PDFTranslationContext::tr("Signed data are invalid.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PDFSignatureVerificationResult::addSignatureDataCoveredBySignatureMissingError()
 | 
			
		||||
{
 | 
			
		||||
    m_flags.setFlag(Error_Signature_DataCoveredBySignatureMissing);
 | 
			
		||||
    m_errors << PDFTranslationContext::tr("Data covered by signature are not present.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PDFSignatureVerificationResult::addSignatureNotCoveredBytesWarning(PDFInteger count)
 | 
			
		||||
{
 | 
			
		||||
    m_flags.setFlag(Warning_Signature_NotCoveredBytes);
 | 
			
		||||
    m_warnings << PDFTranslationContext::tr("%1 bytes are not covered by signature.").arg(count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PDFSignatureVerificationResult::setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName)
 | 
			
		||||
{
 | 
			
		||||
    m_signatureFieldQualifiedName = signatureFieldQualifiedName;
 | 
			
		||||
@@ -297,6 +310,14 @@ void PDFSignatureVerificationResult::setSignatureFieldReference(PDFObjectReferen
 | 
			
		||||
    m_signatureFieldReference = signatureFieldReference;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PDFSignatureVerificationResult::validate()
 | 
			
		||||
{
 | 
			
		||||
    if (isCertificateValid() && isSignatureValid())
 | 
			
		||||
    {
 | 
			
		||||
        m_flags.setFlag(OK);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PDFPublicKeySignatureHandler::initializeResult(PDFSignatureVerificationResult& result) const
 | 
			
		||||
{
 | 
			
		||||
    PDFObjectReference signatureFieldReference = m_signatureField->getSelfReference();
 | 
			
		||||
@@ -457,6 +478,86 @@ void PDFPublicKeySignatureHandler::verifyCertificate(PDFSignatureVerificationRes
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BIO* PDFPublicKeySignatureHandler::getSignedDataBuffer(pdf::PDFSignatureVerificationResult& result, QByteArray& outputBuffer) const
 | 
			
		||||
{
 | 
			
		||||
    const PDFSignature& signature = m_signatureField->getSignature();
 | 
			
		||||
    const QByteArray& contents = signature.getContents();
 | 
			
		||||
    const QByteArray& sourceData = m_sourceData;
 | 
			
		||||
 | 
			
		||||
    PDFInteger size = 0;
 | 
			
		||||
    const PDFSignature::ByteRanges& byteRanges = signature.getByteRanges();
 | 
			
		||||
    for (const PDFSignature::ByteRange& byteRange : byteRanges)
 | 
			
		||||
    {
 | 
			
		||||
        size += byteRange.size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sanity checks
 | 
			
		||||
    if (size > sourceData.size())
 | 
			
		||||
    {
 | 
			
		||||
        result.addSignatureDataCoveredBySignatureMissingError();
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PDFClosedIntervalSet bytesCoveredBySignature;
 | 
			
		||||
 | 
			
		||||
    outputBuffer.reserve(size);
 | 
			
		||||
    for (const PDFSignature::ByteRange& byteRange : byteRanges)
 | 
			
		||||
    {
 | 
			
		||||
        PDFInteger startOffset = byteRange.offset; // Offset to the first data byte
 | 
			
		||||
        PDFInteger endOffset = byteRange.offset + byteRange.size; // Offset to the byte following last data byte
 | 
			
		||||
 | 
			
		||||
        if (startOffset == endOffset)
 | 
			
		||||
        {
 | 
			
		||||
            // This means byte range is zero
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (startOffset > endOffset || startOffset < 0 || endOffset < 0 || startOffset >= m_sourceData.size() || endOffset > m_sourceData.size())
 | 
			
		||||
        {
 | 
			
		||||
            result.addSignatureDataCoveredBySignatureMissingError();
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const int length = endOffset - startOffset;
 | 
			
		||||
        outputBuffer.append(sourceData.constData() + startOffset, length);
 | 
			
		||||
        bytesCoveredBySignature.addInterval(startOffset, endOffset - 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Jakub Melka: We must find byte string, which corresponds to signature.
 | 
			
		||||
    // We find only first occurence, because second one should not exist - because
 | 
			
		||||
    // it will mean that signature must be covered by itself.
 | 
			
		||||
    QByteArray hexContents = contents.toHex();
 | 
			
		||||
    int index = sourceData.indexOf(hexContents);
 | 
			
		||||
    if (index == -1)
 | 
			
		||||
    {
 | 
			
		||||
        index = sourceData.indexOf(hexContents.toUpper());
 | 
			
		||||
    }
 | 
			
		||||
    if (index != -1)
 | 
			
		||||
    {
 | 
			
		||||
        int firstByteIndex = index;
 | 
			
		||||
        int lastByteIndex = index + hexContents.size() - 1;
 | 
			
		||||
 | 
			
		||||
        if (firstByteIndex > 0 && sourceData[firstByteIndex - 1] == '<')
 | 
			
		||||
        {
 | 
			
		||||
            --firstByteIndex;
 | 
			
		||||
        }
 | 
			
		||||
        if (lastByteIndex + 1 < sourceData.size() && sourceData[lastByteIndex + 1] == '>')
 | 
			
		||||
        {
 | 
			
		||||
            ++lastByteIndex;
 | 
			
		||||
        }
 | 
			
		||||
        bytesCoveredBySignature.addInterval(firstByteIndex, lastByteIndex);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We add a warning, that this signature doesn't cover whole source byte range
 | 
			
		||||
    if (!bytesCoveredBySignature.isCovered(0, sourceData.size() - 1))
 | 
			
		||||
    {
 | 
			
		||||
        const PDFInteger notCoveredBytes = sourceData.size() - int(bytesCoveredBySignature.getTotalLength());
 | 
			
		||||
        result.addSignatureNotCoveredBytesWarning(notCoveredBytes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return BIO_new_mem_buf(outputBuffer.data(), outputBuffer.length());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PDFPublicKeySignatureHandler::verifySignature(PDFSignatureVerificationResult& result) const
 | 
			
		||||
{
 | 
			
		||||
    PDFOpenSSLGlobalLock lock;
 | 
			
		||||
@@ -471,7 +572,8 @@ void PDFPublicKeySignatureHandler::verifySignature(PDFSignatureVerificationResul
 | 
			
		||||
    const unsigned char* data = reinterpret_cast<const unsigned char*>(content.data());
 | 
			
		||||
    if (PKCS7* pkcs7 = d2i_PKCS7(nullptr, &data, content.size()))
 | 
			
		||||
    {
 | 
			
		||||
        if (BIO* inputBuffer = getSignedDataBuffer(result))
 | 
			
		||||
        QByteArray buffer;
 | 
			
		||||
        if (BIO* inputBuffer = getSignedDataBuffer(result, buffer))
 | 
			
		||||
        {
 | 
			
		||||
            if (BIO* dataBio = PKCS7_dataInit(pkcs7, inputBuffer))
 | 
			
		||||
            {
 | 
			
		||||
@@ -534,6 +636,10 @@ void PDFPublicKeySignatureHandler::verifySignature(PDFSignatureVerificationResul
 | 
			
		||||
 | 
			
		||||
            BIO_free(inputBuffer);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            // There is no need for adding error, error is in this case added by getSignedDataBuffer function
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        PKCS7_free(pkcs7);
 | 
			
		||||
    }
 | 
			
		||||
@@ -554,6 +660,7 @@ PDFSignatureVerificationResult PDFSignatureHandler_adbe_pkcs7_detached::verify()
 | 
			
		||||
    initializeResult(result);
 | 
			
		||||
    verifyCertificate(result);
 | 
			
		||||
    verifySignature(result);
 | 
			
		||||
    result.validate();
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -247,36 +247,39 @@ public:
 | 
			
		||||
 | 
			
		||||
    enum VerificationFlag
 | 
			
		||||
    {
 | 
			
		||||
        None                                        = 0x00000,  ///< Used only for initialization
 | 
			
		||||
        OK                                          = 0x00001,  ///< Both certificate and signature is OK
 | 
			
		||||
        Certificate_OK                              = 0x00002,  ///< Certificate is OK
 | 
			
		||||
        Signature_OK                                = 0x00004,  ///< Signature is OK
 | 
			
		||||
        Error_NoHandler                             = 0x00008,  ///< No signature handler for given signature
 | 
			
		||||
        Error_Generic                               = 0x00010,  ///< Generic error (uknown general error)
 | 
			
		||||
        None                                            = 0x00000000,  ///< Used only for initialization
 | 
			
		||||
        OK                                              = 0x00000001,  ///< Both certificate and signature is OK
 | 
			
		||||
        Certificate_OK                                  = 0x00000002,  ///< Certificate is OK
 | 
			
		||||
        Signature_OK                                    = 0x00000004,  ///< Signature is OK
 | 
			
		||||
        Error_NoHandler                                 = 0x00000008,  ///< No signature handler for given signature
 | 
			
		||||
        Error_Generic                                   = 0x00000010,  ///< Generic error (uknown general error)
 | 
			
		||||
 | 
			
		||||
        Error_Certificate_Invalid                   = 0x00020,  ///< Certificate is invalid
 | 
			
		||||
        Error_Certificate_NoSignatures              = 0x00040,  ///< No signature found in certificate data
 | 
			
		||||
        Error_Certificate_Missing                   = 0x00080,  ///< Certificate is missing
 | 
			
		||||
        Error_Certificate_Generic                   = 0x00100,  ///< Generic error during certificate verification
 | 
			
		||||
        Error_Certificate_Expired                   = 0x00200,  ///< Certificate has expired
 | 
			
		||||
        Error_Certificate_SelfSigned                = 0x00400,  ///< Self signed certificate
 | 
			
		||||
        Error_Certificate_SelfSignedChain           = 0x00800,  ///< Self signed certificate in chain
 | 
			
		||||
        Error_Certificate_TrustedNotFound           = 0x01000,  ///< No trusted certificate was found
 | 
			
		||||
        Error_Certificate_Revoked                   = 0x02000,  ///< Certificate has been revoked
 | 
			
		||||
        Error_Certificate_Other                     = 0x04000,  ///< Other certificate error. See OpenSSL code for details.
 | 
			
		||||
        Error_Certificate_Invalid                       = 0x00000020,  ///< Certificate is invalid
 | 
			
		||||
        Error_Certificate_NoSignatures                  = 0x00000040,  ///< No signature found in certificate data
 | 
			
		||||
        Error_Certificate_Missing                       = 0x00000080,  ///< Certificate is missing
 | 
			
		||||
        Error_Certificate_Generic                       = 0x00000100,  ///< Generic error during certificate verification
 | 
			
		||||
        Error_Certificate_Expired                       = 0x00000200,  ///< Certificate has expired
 | 
			
		||||
        Error_Certificate_SelfSigned                    = 0x00000400,  ///< Self signed certificate
 | 
			
		||||
        Error_Certificate_SelfSignedChain               = 0x00000800,  ///< Self signed certificate in chain
 | 
			
		||||
        Error_Certificate_TrustedNotFound               = 0x00001000,  ///< No trusted certificate was found
 | 
			
		||||
        Error_Certificate_Revoked                       = 0x00002000,  ///< Certificate has been revoked
 | 
			
		||||
        Error_Certificate_Other                         = 0x00004000,  ///< Other certificate error. See OpenSSL code for details.
 | 
			
		||||
 | 
			
		||||
        Error_Signature_Invalid                     = 0x08000,  ///< Signature is invalid for some reason
 | 
			
		||||
        Error_Signature_SourceCertificateMissing    = 0x10000,  ///< Source certificate of signature is missing
 | 
			
		||||
        Error_Signature_NoSignaturesFound           = 0x20000,  ///< No signatures found
 | 
			
		||||
        Error_Signature_DigestFailure               = 0x40000,  ///< Digest failure
 | 
			
		||||
        Error_Signature_DataOther                   = 0x80000,  ///< Signed data were not verified
 | 
			
		||||
        Error_Signature_Invalid                         = 0x00008000,  ///< Signature is invalid for some reason
 | 
			
		||||
        Error_Signature_SourceCertificateMissing        = 0x00010000,  ///< Source certificate of signature is missing
 | 
			
		||||
        Error_Signature_NoSignaturesFound               = 0x00020000,  ///< No signatures found
 | 
			
		||||
        Error_Signature_DigestFailure                   = 0x00040000,  ///< Digest failure
 | 
			
		||||
        Error_Signature_DataOther                       = 0x00080000,  ///< Signed data were not verified
 | 
			
		||||
        Error_Signature_DataCoveredBySignatureMissing   = 0x00100000,  ///< Data covered by signature are not present
 | 
			
		||||
 | 
			
		||||
        Warning_Signature_NotCoveredBytes               = 0x00200000,  ///< Some bytes in source data are not covered by signature
 | 
			
		||||
 | 
			
		||||
        Error_Certificates_Mask = Error_Certificate_Invalid | Error_Certificate_NoSignatures | Error_Certificate_Missing | Error_Certificate_Generic |
 | 
			
		||||
                                  Error_Certificate_Expired | Error_Certificate_SelfSigned | Error_Certificate_SelfSignedChain | Error_Certificate_TrustedNotFound |
 | 
			
		||||
                                  Error_Certificate_Revoked | Error_Certificate_Other,
 | 
			
		||||
 | 
			
		||||
        Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound |
 | 
			
		||||
                                Error_Signature_DigestFailure | Error_Signature_DataOther,
 | 
			
		||||
                                Error_Signature_DigestFailure | Error_Signature_DataOther | Error_Signature_DataCoveredBySignatureMissing,
 | 
			
		||||
    };
 | 
			
		||||
    Q_DECLARE_FLAGS(VerificationFlags, VerificationFlag)
 | 
			
		||||
 | 
			
		||||
@@ -299,6 +302,8 @@ public:
 | 
			
		||||
    void addSignatureCertificateMissingError();
 | 
			
		||||
    void addSignatureDigestFailureError();
 | 
			
		||||
    void addSignatureDataOtherError();
 | 
			
		||||
    void addSignatureDataCoveredBySignatureMissingError();
 | 
			
		||||
    void addSignatureNotCoveredBytesWarning(PDFInteger count);
 | 
			
		||||
 | 
			
		||||
    bool isValid() const { return hasFlag(OK); }
 | 
			
		||||
    bool isCertificateValid() const { return hasFlag(Certificate_OK); }
 | 
			
		||||
@@ -312,17 +317,22 @@ public:
 | 
			
		||||
    PDFObjectReference getSignatureFieldReference() const { return m_signatureFieldReference; }
 | 
			
		||||
    const QString& getSignatureFieldQualifiedName() const { return m_signatureFieldQualifiedName; }
 | 
			
		||||
    const QStringList& getErrors() const { return m_errors; }
 | 
			
		||||
    const QStringList& getWarnings() const { return m_warnings; }
 | 
			
		||||
 | 
			
		||||
    void setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName);
 | 
			
		||||
    void setSignatureFieldReference(PDFObjectReference signatureFieldReference);
 | 
			
		||||
 | 
			
		||||
    void addCertificateInfo(PDFCertificateInfo info) { m_certificateInfos.emplace_back(qMove(info)); }
 | 
			
		||||
 | 
			
		||||
    /// Adds OK flag, if both certificate and signature are valid
 | 
			
		||||
    void validate();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    VerificationFlags m_flags = None;
 | 
			
		||||
    PDFObjectReference m_signatureFieldReference;
 | 
			
		||||
    QString m_signatureFieldQualifiedName;
 | 
			
		||||
    QStringList m_errors;
 | 
			
		||||
    QStringList m_warnings;
 | 
			
		||||
    PDFCertificateInfos m_certificateInfos;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,8 @@ protected:
 | 
			
		||||
    void verifySignature(PDFSignatureVerificationResult& result) const;
 | 
			
		||||
    void addTrustedCertificates(X509_STORE* store) const;
 | 
			
		||||
 | 
			
		||||
    BIO* getSignedDataBuffer(PDFSignatureVerificationResult& result, QByteArray& outputBuffer) const;
 | 
			
		||||
 | 
			
		||||
    /// Return a list of certificates from PKCS7 object
 | 
			
		||||
    static STACK_OF(X509)* getCertificates(PKCS7* pkcs7);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -278,4 +278,75 @@ std::vector<PDFDependentLibraryInfo> PDFDependentLibraryInfo::getLibraryInfo()
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PDFClosedIntervalSet::addInterval(PDFInteger low, PDFInteger high)
 | 
			
		||||
{
 | 
			
		||||
    m_intervals.emplace_back(low, high);
 | 
			
		||||
    normalize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PDFClosedIntervalSet::isCovered(PDFInteger low, PDFInteger high)
 | 
			
		||||
{
 | 
			
		||||
    PDFClosedIntervalSet temporary;
 | 
			
		||||
    temporary.addInterval(low, high);
 | 
			
		||||
    return *this == temporary;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PDFInteger PDFClosedIntervalSet::getTotalLength() const
 | 
			
		||||
{
 | 
			
		||||
    return std::accumulate(m_intervals.cbegin(), m_intervals.cend(), 0, [](PDFInteger count, const auto& b) { return count + b.second - b.first + 1; });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PDFClosedIntervalSet::normalize()
 | 
			
		||||
{
 | 
			
		||||
    // Algorithm:
 | 
			
		||||
    //   1) sort all ranges
 | 
			
		||||
    //   2) merge adjacent ones
 | 
			
		||||
 | 
			
		||||
    qSort(m_intervals);
 | 
			
		||||
 | 
			
		||||
    std::vector<ClosedInterval> intervals;
 | 
			
		||||
    auto it = m_intervals.cbegin();
 | 
			
		||||
    auto itEnd = m_intervals.cend();
 | 
			
		||||
 | 
			
		||||
    while (it != itEnd)
 | 
			
		||||
    {
 | 
			
		||||
        ClosedInterval interval = *it++;
 | 
			
		||||
 | 
			
		||||
        while (it != itEnd && overlapsOrAdjacent(interval, *it))
 | 
			
		||||
        {
 | 
			
		||||
            interval = std::make_pair(qMin(interval.first, it->first), qMax(interval.second, it->second));
 | 
			
		||||
            ++it;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        intervals.push_back(interval);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_intervals = qMove(intervals);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PDFClosedIntervalSet::overlapsOrAdjacent(ClosedInterval a, ClosedInterval b)
 | 
			
		||||
{
 | 
			
		||||
    if (a.first > b.first)
 | 
			
		||||
    {
 | 
			
		||||
        std::swap(a, b);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // There are 3 cases
 | 
			
		||||
    //   a      b
 | 
			
		||||
    // |---|  |---|      1)
 | 
			
		||||
    //
 | 
			
		||||
    //   a    b
 | 
			
		||||
    // |---||---|        2)
 | 
			
		||||
    //
 | 
			
		||||
    //   a   b
 | 
			
		||||
    // |---|             3)
 | 
			
		||||
    //    |---|
 | 
			
		||||
    //
 | 
			
		||||
    // We cover cases 2) and 3) with following condition:
 | 
			
		||||
    // [a1, a2], [b1, b2]
 | 
			
		||||
    // b1 <= a2 + 1, because we can have intervals [1,2], [3,4] - these should be merged
 | 
			
		||||
 | 
			
		||||
    return b.first <= a.second + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}   // namespace pdf
 | 
			
		||||
 
 | 
			
		||||
@@ -526,6 +526,47 @@ private:
 | 
			
		||||
    QString m_errorMessage;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Set of closed intervals
 | 
			
		||||
class PDFClosedIntervalSet
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    explicit inline PDFClosedIntervalSet() = default;
 | 
			
		||||
 | 
			
		||||
    bool operator ==(const PDFClosedIntervalSet&) const = default;
 | 
			
		||||
 | 
			
		||||
    using ClosedInterval = std::pair<PDFInteger, PDFInteger>;
 | 
			
		||||
 | 
			
		||||
    /// Adds closed interval, where \p low is lower bound
 | 
			
		||||
    /// of the closed interval, and high is upper bound
 | 
			
		||||
    /// of closed interval.
 | 
			
		||||
    /// \param low Lower bound of interval
 | 
			
		||||
    /// \param high Upper bound of interval
 | 
			
		||||
    void addInterval(PDFInteger low, PDFInteger high);
 | 
			
		||||
 | 
			
		||||
    /// Adds a single value to the interval (closed interval
 | 
			
		||||
    /// of single value)
 | 
			
		||||
    /// \param value Value
 | 
			
		||||
    void addValue(PDFInteger value) { addInterval(value, value); }
 | 
			
		||||
 | 
			
		||||
    /// Returns true, if given closed range is subset of
 | 
			
		||||
    /// this interval set.
 | 
			
		||||
    bool isCovered(PDFInteger low, PDFInteger high);
 | 
			
		||||
 | 
			
		||||
    /// Returns sum of interval lengths
 | 
			
		||||
    PDFInteger getTotalLength() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /// Normalizes interval ranges - merges adjacent intervals
 | 
			
		||||
    void normalize();
 | 
			
		||||
 | 
			
		||||
    /// Returns true, if interval overlaps, or is adjacent to the other one
 | 
			
		||||
    /// \param a First interval
 | 
			
		||||
    /// \param b Second interval
 | 
			
		||||
    static bool overlapsOrAdjacent(ClosedInterval a, ClosedInterval b);
 | 
			
		||||
 | 
			
		||||
    std::vector<ClosedInterval> m_intervals;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}   // namespace pdf
 | 
			
		||||
 | 
			
		||||
#endif // PDFUTILS_H
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user