Spaces:
Running
Running
class TestAnnotations : public QObject | |
{ | |
Q_OBJECT | |
public: | |
explicit TestAnnotations(QObject *parent = nullptr) : QObject(parent) { } | |
void saveAndCheck(const std::unique_ptr<Poppler::Document> &doc, const std::function<void(Poppler::Annotation *a)> &checkFunction); | |
private slots: | |
void checkQColorPrecision(); | |
void checkFontSizeAndColor(); | |
void checkHighlightFromAndToQuads(); | |
void checkUTF16LEAnnot(); | |
void checkModificationCreationDate(); | |
void checkNonMarkupAnnotations(); | |
void checkDefaultAppearance(); | |
}; | |
/* Is .5f sufficient for 16 bit color channel roundtrip trough save and load on all architectures? */ | |
void TestAnnotations::checkQColorPrecision() | |
{ | |
bool precisionOk = true; | |
for (int i = std::numeric_limits<uint16_t>::min(); i <= std::numeric_limits<uint16_t>::max(); i++) { | |
double normalized = static_cast<uint16_t>(i) / static_cast<double>(std::numeric_limits<uint16_t>::max()); | |
const std::unique_ptr<GooString> serialized = GooString::format("{0:.5f}", normalized); | |
double deserialized = gatof(serialized->c_str()); | |
uint16_t denormalized = std::round(deserialized * std::numeric_limits<uint16_t>::max()); | |
if (static_cast<uint16_t>(i) != denormalized) { | |
precisionOk = false; | |
break; | |
} | |
} | |
QVERIFY(precisionOk); | |
} | |
void TestAnnotations::checkFontSizeAndColor() | |
{ | |
const QString contents = QStringLiteral("foobar"); | |
const std::vector<QColor> testColors { QColor::fromRgb(0xAB, 0xCD, 0xEF), QColor::fromCmyk(0xAB, 0xBC, 0xCD, 0xDE) }; | |
const QFont testFont(QStringLiteral("Helvetica"), 20); | |
QTemporaryFile tempFile; | |
QVERIFY(tempFile.open()); | |
tempFile.close(); | |
{ | |
std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(TESTDATADIR "/unittestcases/UseNone.pdf") }; | |
QVERIFY(doc.get()); | |
std::unique_ptr<Poppler::Page> page { doc->page(0) }; | |
QVERIFY(page.get()); | |
for (const auto &color : testColors) { | |
auto annot = std::make_unique<Poppler::TextAnnotation>(Poppler::TextAnnotation::InPlace); | |
annot->setBoundary(QRectF(0.0, 0.0, 1.0, 1.0)); | |
annot->setContents(contents); | |
annot->setTextFont(testFont); | |
annot->setTextColor(color); | |
page->addAnnotation(annot.get()); | |
} | |
std::unique_ptr<Poppler::PDFConverter> conv(doc->pdfConverter()); | |
QVERIFY(conv.get()); | |
conv->setOutputFileName(tempFile.fileName()); | |
conv->setPDFOptions(Poppler::PDFConverter::WithChanges); | |
QVERIFY(conv->convert()); | |
} | |
{ | |
std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(tempFile.fileName()) }; | |
QVERIFY(doc.get()); | |
std::unique_ptr<Poppler::Page> page { doc->page(0) }; | |
QVERIFY(page.get()); | |
auto annots = page->annotations(); | |
QCOMPARE(annots.size(), static_cast<int>(testColors.size())); | |
auto &&annot = annots.cbegin(); | |
for (const auto &color : testColors) { | |
QCOMPARE((*annot)->subType(), Poppler::Annotation::AText); | |
auto textAnnot = static_cast<Poppler::TextAnnotation *>(annot->get()); | |
QCOMPARE(textAnnot->contents(), contents); | |
QCOMPARE(textAnnot->textFont().pointSize(), testFont.pointSize()); | |
QCOMPARE(static_cast<int>(textAnnot->textColor().spec()), static_cast<int>(color.spec())); | |
QCOMPARE(textAnnot->textColor(), color); | |
if (annot != annots.cend()) { | |
++annot; | |
} | |
} | |
} | |
} | |
namespace Poppler { | |
static bool operator==(const Poppler::HighlightAnnotation::Quad &a, const Poppler::HighlightAnnotation::Quad &b) | |
{ | |
// FIXME We do not compare capStart, capEnd and feather since AnnotQuadrilaterals doesn't contain that info and thus | |
// HighlightAnnotationPrivate::fromQuadrilaterals uses default values | |
return a.points[0] == b.points[0] && a.points[1] == b.points[1] && a.points[2] == b.points[2] && a.points[3] == b.points[3]; | |
} | |
} | |
void TestAnnotations::checkHighlightFromAndToQuads() | |
{ | |
std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(TESTDATADIR "/unittestcases/UseNone.pdf") }; | |
std::unique_ptr<Poppler::Page> page { doc->page(0) }; | |
auto ha = std::make_unique<Poppler::HighlightAnnotation>(); | |
page->addAnnotation(ha.get()); | |
const QList<Poppler::HighlightAnnotation::Quad> quads = { { { { 0, 0.1 }, { 0.2, 0.3 }, { 0.4, 0.5 }, { 0.6, 0.7 } }, false, false, 0 }, { { { 0.8, 0.9 }, { 0.1, 0.2 }, { 0.3, 0.4 }, { 0.5, 0.6 } }, true, false, 0.4 } }; | |
ha->setHighlightQuads(quads); | |
QCOMPARE(ha->highlightQuads(), quads); | |
} | |
void TestAnnotations::checkUTF16LEAnnot() | |
{ | |
std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(TESTDATADIR "/unittestcases/utf16le-annot.pdf") }; | |
QVERIFY(doc.get()); | |
std::unique_ptr<Poppler::Page> page { doc->page(0) }; | |
QVERIFY(page.get()); | |
auto annots = page->annotations(); | |
QCOMPARE(annots.size(), 2); | |
const auto &annot = annots[1]; | |
QCOMPARE(annot->contents(), QString::fromUtf8("Únîcödé豰")); // clazy:exclude=qstring-allocations | |
} | |
void TestAnnotations::saveAndCheck(const std::unique_ptr<Poppler::Document> &doc, const std::function<void(Poppler::Annotation *a)> &checkFunction) | |
{ | |
// also check that saving yields the same output | |
QTemporaryFile tempFile; | |
QVERIFY(tempFile.open()); | |
tempFile.close(); | |
std::unique_ptr<Poppler::PDFConverter> conv(doc->pdfConverter()); | |
conv->setOutputFileName(tempFile.fileName()); | |
conv->setPDFOptions(Poppler::PDFConverter::WithChanges); | |
conv->convert(); | |
std::unique_ptr<Poppler::Document> savedDoc { Poppler::Document::load(tempFile.fileName()) }; | |
std::unique_ptr<Poppler::Page> page { doc->page(0) }; | |
auto annots = page->annotations(); | |
checkFunction(annots.at(1).get()); | |
} | |
void TestAnnotations::checkModificationCreationDate() | |
{ | |
std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(TESTDATADIR "/unittestcases/utf16le-annot.pdf") }; | |
QVERIFY(doc.get()); | |
std::unique_ptr<Poppler::Page> page { doc->page(0) }; | |
auto annots = page->annotations(); | |
const auto &annot = annots.at(1); | |
QCOMPARE(annot->creationDate(), QDateTime()); | |
QCOMPARE(annot->modificationDate(), QDateTime()); | |
const QDateTime dt1(QDate(2020, 8, 7), QTime(18, 34, 56)); | |
annot->setCreationDate(dt1); | |
auto checkFunction1 = [dt1](Poppler::Annotation *a) { | |
QCOMPARE(a->creationDate(), dt1); | |
// setting the creation date updates the modification date | |
QVERIFY(std::abs(a->modificationDate().secsTo(QDateTime::currentDateTime())) < 2); | |
}; | |
checkFunction1(annot.get()); | |
saveAndCheck(doc, checkFunction1); | |
const QDateTime dt2(QDate(2020, 8, 30), QTime(8, 14, 52)); | |
annot->setModificationDate(dt2); | |
auto checkFunction2 = [dt2](Poppler::Annotation *a) { QCOMPARE(a->modificationDate(), dt2); }; | |
checkFunction2(annot.get()); | |
saveAndCheck(doc, checkFunction2); | |
// setting the creation date to empty means "use the modification date" and also updates the modification date | |
// so both creation date and modification date are the same and are now | |
annot->setCreationDate(QDateTime()); | |
auto checkFunction3 = [](Poppler::Annotation *a) { | |
QVERIFY(std::abs(a->creationDate().secsTo(QDateTime::currentDateTime())) < 2); | |
QCOMPARE(a->creationDate(), a->modificationDate()); | |
}; | |
checkFunction3(annot.get()); | |
saveAndCheck(doc, checkFunction3); | |
annot->setModificationDate(QDateTime()); | |
auto checkFunction4 = [](Poppler::Annotation *a) { | |
QCOMPARE(a->creationDate(), QDateTime()); | |
QCOMPARE(a->modificationDate(), QDateTime()); | |
}; | |
checkFunction4(annot.get()); | |
saveAndCheck(doc, checkFunction4); | |
} | |
void TestAnnotations::checkNonMarkupAnnotations() | |
{ | |
std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(TESTDATADIR "/unittestcases/checkbox_issue_159.pdf") }; | |
QVERIFY(doc.get()); | |
std::unique_ptr<Poppler::Page> page { doc->page(0) }; | |
QVERIFY(page.get()); | |
auto annots = page->annotations(); | |
QCOMPARE(annots.size(), 17); | |
} | |
void TestAnnotations::checkDefaultAppearance() | |
{ | |
std::unique_ptr<GooString> roundtripString; | |
{ | |
GooString daString { "/Helv 10 Tf 0.1 0.2 0.3 rg" }; | |
const DefaultAppearance da { &daString }; | |
QCOMPARE(da.getFontPtSize(), 10.); | |
QVERIFY(da.getFontName().isName()); | |
QCOMPARE(da.getFontName().getName(), "Helv"); | |
const AnnotColor *color = da.getFontColor(); | |
QVERIFY(color); | |
QCOMPARE(color->getSpace(), AnnotColor::colorRGB); | |
QCOMPARE(color->getValues()[0], 0.1); | |
QCOMPARE(color->getValues()[1], 0.2); | |
QCOMPARE(color->getValues()[2], 0.3); | |
roundtripString = std::make_unique<GooString>(da.toAppearanceString()); | |
} | |
{ | |
/* roundtrip through parse/generate/parse shall preserve values */ | |
const DefaultAppearance da { roundtripString.get() }; | |
QCOMPARE(da.getFontPtSize(), 10.); | |
QVERIFY(da.getFontName().isName()); | |
QCOMPARE(da.getFontName().getName(), "Helv"); | |
const AnnotColor *color = da.getFontColor(); | |
QVERIFY(color); | |
QCOMPARE(color->getSpace(), AnnotColor::colorRGB); | |
QCOMPARE(color->getValues()[0], 0.1); | |
QCOMPARE(color->getValues()[1], 0.2); | |
QCOMPARE(color->getValues()[2], 0.3); | |
} | |
{ | |
/* parsing bad DA strings must not cause crash */ | |
GooString daString { "/ % Tf 1 2 rg" }; | |
const DefaultAppearance da { &daString }; | |
QVERIFY(!da.getFontName().isName()); | |
} | |
} | |
QTEST_GUILESS_MAIN(TestAnnotations) | |