File: main.cpp

package info (click to toggle)
comparepdf 1.0.1-1.1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 192 kB
  • sloc: cpp: 1,035; makefile: 4
file content (196 lines) | stat: -rw-r--r-- 6,904 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/*
    Copyright (c) 2011 Qtrac Ltd. All rights reserved.

    This program or module is free software: you can redistribute it
    and/or modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation, either version 2 of
    the License, or (at your option) any later version. It is provided
    for educational purposes and 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 General Public License for more details.
*/

#if QT_VERSION >= 0x040600
#include <QSharedPointer>
#else
#include <tr1/memory>
#endif
#include <poppler-qt5.h>
#include <QCoreApplication>
#include <QImage>
#include <QTextStream>
#include "option_parser.hpp"

#if QT_VERSION >= 0x040600
typedef QSharedPointer<Poppler::Document> PdfDocument;
typedef QSharedPointer<Poppler::Page> PdfPage;
typedef QSharedPointer<Poppler::TextBox> PdfTextBox;
#else
typedef std::tr1::shared_ptr<Poppler::Document> PdfDocument;
typedef std::tr1::shared_ptr<Poppler::Page> PdfPage;
typedef std::tr1::shared_ptr<Poppler::TextBox> PdfTextBox;
#endif
typedef QList<PdfTextBox> TextBoxList;

enum Difference{
    Same, DifferentPageCount, DifferentTexts, DifferentAppearance,
    Error};

PdfDocument getPdf(const QString &filename, QString *error);
TextBoxList getTextBoxes(PdfPage page);
Difference compareFiles(const QString &file1, const QString &file2,
        bool compareText, QString *error);


const QString Version("1.0.1");


int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    AQP::OptionParser parser(app.arguments(),
            QObject::tr("usage: {program} [options] <pdf1> <pdf2>\n"
            "\nA program to compare two PDF files.\n"),
            QObject::tr("\nA return value of 0 means no differences "
            "detected; 1 or 2 signifies an error; 10 means they differ "
            "visually, 13 means they differ textually, and 15 means "
            "they have different page counts.\n"
            "\nVersion %1 Copyright (c) 2011-12 Qtrac Ltd. "
            "All rights reserved.\nTo see detailed page by page "
            "differences in a GUI application, use DiffPDF: see "
            "http://www.qtrac.eu").arg(Version));
    AQP::StringOptionPtr comparisonModeOpt = parser.addStringOption(
            QObject::tr("c"), QObject::tr("compare"));
    comparisonModeOpt->setDefaultValue("text");
    comparisonModeOpt->setAcceptableValues(QStringList()
            << QObject::tr("appearance") << QObject::tr("a")
            << QObject::tr("text") << QObject::tr("t"));
    comparisonModeOpt->setHelp(QObject::tr("comparison mode"));
    AQP::IntegerOptionPtr verboseOpt = parser.addIntegerOption(
            QObject::tr("v"), QObject::tr("verbose"));
    verboseOpt->setDefaultValue(1);
    verboseOpt->setAcceptableValues(QSet<int>() << 0 << 1 << 2);
    verboseOpt->setHelp(QObject::tr("0 = quiet; 1 = report differences; "
                "2 = report same and different"));
    AQP::BooleanOptionPtr versionOpt = parser.addBooleanOption("",
            QObject::tr("version"));
    versionOpt->setHelp(QObject::tr("show version information and "
                "terminate"));
    if (!parser.parse())
        return 2;
    QTextStream out(stdout);
    if (versionOpt->boolean()) {
        out << QObject::tr("comparepdf ") << Version << "\n"
            << QObject::tr("poppler") << "\n";
        return 0;
    }
    QStringList files = parser.remainder();
    if (files.count() != 2 || !files[0].toLower().endsWith(".pdf") ||
            !files[1].toLower().endsWith(".pdf")) {
        out << QObject::tr("Two PDF files must be specified\n");
        return 1;
    }

    bool compareText = comparisonModeOpt->value()[0] == 't';
    QString error;
    Difference difference = compareFiles(files[0], files[1], compareText,
            &error);
    if (!error.isEmpty()) {
        out << error << "\n";
        return 1;
    }
    int result = Same; // 0
    if (difference == DifferentPageCount)
        result = 15;
    else if (difference == DifferentTexts)
        result = 13;
    else if (difference == DifferentAppearance)
        result = 10;
    if (!result && verboseOpt->value() == 2) {
        out << QObject::tr("No differences detected.\n");
    } else if (result && verboseOpt->value() > 0) {
        if (comparisonModeOpt->value()[0] == 't')
            out << QObject::tr("Files have different texts.\n");
        else
            out << QObject::tr("Files look different.\n");
    }
    return result;
}


Difference compareFiles(const QString &filename1, const QString &filename2,
        bool compareText, QString *error)
{
    PdfDocument pdf1 = getPdf(filename1, error);
    if (!error->isEmpty())
        return Error;
    PdfDocument pdf2 = getPdf(filename2, error);
    if (!error->isEmpty())
        return Error;
    int count = pdf1->numPages();
    if (count != pdf2->numPages())
        return DifferentPageCount;
    for (int page = 0; page < count; ++page) {
        PdfPage page1(pdf1->page(page));
        if (!page1) {
            *error = QObject::tr("Failed to read page %1 from '%2'.")
                          .arg(page + 1).arg(filename1);
            return Error;
        }
        PdfPage page2(pdf2->page(page));
        if (!page2) {
            *error = QObject::tr("Failed to read page %1 from '%2'.")
                          .arg(page + 1).arg(filename2);
            return Error;
        }
        if (compareText) {
            TextBoxList list1 = getTextBoxes(page1);
            TextBoxList list2 = getTextBoxes(page2);
            QStringList words1;
            QStringList words2;
            foreach (const PdfTextBox &box, list1)
                words1 << box->text();
            foreach (const PdfTextBox &box, list2)
                words2 << box->text();
            if (words1.join("") != words2.join(""))
                return DifferentTexts;
        } else {
            QImage image1 = page1->renderToImage();
            QImage image2 = page2->renderToImage();
            if (image1 != image2)
                return DifferentAppearance;
        }
    }
    return Same;
}


PdfDocument getPdf(const QString &filename, QString *error)
{
    PdfDocument pdf(Poppler::Document::load(filename));
    if (!pdf) {
        *error = QObject::tr("Cannot load '%1'.").arg(filename);
    }
    else if (pdf->isLocked()) {
        *error = QObject::tr("Cannot read a locked PDF ('%1').").arg(
                filename);
#if QT_VERSION >= 0x040600
        pdf.clear();
#else
        pdf.reset();
#endif
    }
    return pdf;
}


TextBoxList getTextBoxes(PdfPage page)
{
    TextBoxList boxes;
    foreach (Poppler::TextBox *box, page->textList()) {
        PdfTextBox box_ptr(box);
        boxes.append(box_ptr);
    }
    return boxes;
}