14.18 round 1: a starved PDF throws, an early zoom centres, inches round to even, tests tightened
This commit is contained in:
13 files changed
+210
-15
No files matched your search
@@ -30,6 +30,7 @@ private slots:
|
||||
void eachFormatShowsItsRowsAndWritesItsBytes();
|
||||
void theSizeKeepsTheProportionsWithinTheLimits();
|
||||
void aClearPNGShowsTheCheckerboard();
|
||||
void thePageRoundsAsSwiftsFormatter();
|
||||
};
|
||||
|
||||
namespace {
|
||||
@@ -382,5 +383,20 @@ void ExportAsSheetTests::aClearPNGShowsTheCheckerboard()
|
||||
QCOMPARE(shown.previewAt({100, 165}), QColor(31, 31, 31));
|
||||
}
|
||||
|
||||
// Swift 6.0.3's formatter, measured: halves of its digits to even.
|
||||
void ExportAsSheetTests::thePageRoundsAsSwiftsFormatter()
|
||||
{
|
||||
const auto page = [](int width, int height, double dpi) {
|
||||
Sheet shown({halfRed(width, height).image, dpi}, ExportFormat::pdf);
|
||||
return shown.find<QLabel>("exportPage").text();
|
||||
};
|
||||
QCOMPARE(page(45, 9, 72), QString("0.62 × 0.12 in at 72 DPI"));
|
||||
QCOMPARE(page(27, 1, 72), QString("0.38 × 0.01 in at 72 DPI"));
|
||||
QCOMPARE(page(1, 3, 200), QString("0 × 0.02 in at 200 DPI"));
|
||||
QCOMPARE(page(27, 29, 200), QString("0.14 × 0.14 in at 200 DPI"));
|
||||
QCOMPARE(page(100, 2999, 300), QString("0.33 × 10 in at 300 DPI"));
|
||||
QCOMPARE(page(30000, 1, 1), QString("30,000 × 1 in at 1 DPI"));
|
||||
}
|
||||
|
||||
QTEST_MAIN(ExportAsSheetTests)
|
||||
#include "ExportAsSheetTests.moc"
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "AddressSpaceLimit.h"
|
||||
#include "IO/ImageExporter.h"
|
||||
#include <QThreadPool>
|
||||
#include <QtEndian>
|
||||
#include <QtTest>
|
||||
#include <malloc.h>
|
||||
|
||||
// A starved PDF throws; it never comes back broken.
|
||||
class ExportPDFFailureTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void aStarvedPDFThrowsOrIsWhole();
|
||||
};
|
||||
|
||||
namespace {
|
||||
// Each Flate stream's bytes, inflated.
|
||||
QList<QByteArray> inflated(const QByteArray &pdf)
|
||||
{
|
||||
QList<QByteArray> streams;
|
||||
for (qsizetype at = pdf.indexOf("/FlateDecode"); at >= 0; at = pdf.indexOf("/FlateDecode", at + 1)) {
|
||||
const qsizetype begin = pdf.indexOf(">>\nstream\n", at) + 10, end = pdf.indexOf("\nendstream", begin);
|
||||
QByteArray sized(4, 0);
|
||||
qToBigEndian(quint32(64 << 20), sized.data());
|
||||
streams << qUncompress(sized + pdf.mid(begin, end - begin));
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
}
|
||||
|
||||
void ExportPDFFailureTests::aStarvedPDFThrowsOrIsWhole()
|
||||
{
|
||||
// Noise, so the compressed streams stay near their size.
|
||||
QImage noise(1000, 1000, QImage::Format_RGBA8888_Premultiplied);
|
||||
quint32 seed = 7;
|
||||
for (int y = 0; y < noise.height(); ++y) {
|
||||
auto *row = reinterpret_cast<quint32 *>(noise.scanLine(y));
|
||||
for (int x = 0; x < noise.width(); ++x)
|
||||
row[x] = (seed = seed * 1664525u + 1013904223u) | 0xff000000u;
|
||||
}
|
||||
const ExportRaster raster{noise, 72};
|
||||
// Qt converts on pool threads: start them before any limit.
|
||||
QThreadPool::globalInstance()->setExpiryTimeout(-1);
|
||||
ImageExporter::pdfData(raster);
|
||||
int refused = 0, whole = 0;
|
||||
for (qint64 megabytes = 2; megabytes <= 48; ++megabytes) {
|
||||
QByteArray pdf;
|
||||
try {
|
||||
const AddressSpaceLimit limit(megabytes << 20);
|
||||
pdf = ImageExporter::pdfData(raster);
|
||||
} catch (const ExportError &error) {
|
||||
QCOMPARE(error.kind, ExportError::Kind::render);
|
||||
++refused;
|
||||
continue;
|
||||
}
|
||||
const QList<QByteArray> streams = inflated(pdf);
|
||||
QCOMPARE(streams.size(), qsizetype(2));
|
||||
QVERIFY2(streams[0].size() == 1000 * 1000 * 3 && streams[1].size() == 1000 * 1000, qPrintable(QString::number(megabytes)));
|
||||
++whole;
|
||||
}
|
||||
// The sweep reaches both sides of the limit.
|
||||
QVERIFY(refused > 0 && whole > 0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Large asks map memory the limit counts.
|
||||
mallopt(M_MMAP_THRESHOLD, 64 * 1024);
|
||||
QCoreApplication app(argc, argv);
|
||||
ExportPDFFailureTests tests;
|
||||
return QTest::qExec(&tests, argc, argv);
|
||||
}
|
||||
|
||||
#include "ExportPDFFailureTests.moc"
|
||||
@@ -79,6 +79,14 @@ void ExportPDFTests::pdfIsOnePageAtThePrintedSizeWithLosslessPixels()
|
||||
QCOMPARE(at(0, 0)[3], 0);
|
||||
// The page draws the image over it, nothing beneath.
|
||||
QCOMPARE(streamOf(object(pdf, 4)), QByteArray("q\n2.8800 0 0 2.8800 0 0 cm /Im0 Do\nQ"));
|
||||
// Rows top first, each channel its own, as drawn.
|
||||
QImage grid = BrushRaster::context(2, 3, false);
|
||||
for (int y = 0; y < 3; ++y)
|
||||
for (int x = 0; x < 2; ++x)
|
||||
grid.setPixel(x, y, qRgba(10 + 40 * y + 20 * x, 100 + 30 * y - 10 * x, 200 - 50 * y + 5 * x, 255));
|
||||
const QByteArray rows = ImageExporter::pdfData(ExportRaster{grid, 72});
|
||||
QCOMPARE(streamOf(object(rows, 5)), QByteArray("\x0a\x64\xc8\x1e\x5a\xcd\x32\x82\x96\x46\x78\x9b\x5a\xa0\x64\x6e\x96\x69", 18));
|
||||
QVERIFY(object(rows, 5).contains("/Width 2 /Height 3"));
|
||||
// A soft edge goes straight: colour whole, alpha apart.
|
||||
QImage soft = BrushRaster::context(1, 1, false);
|
||||
soft.setPixelColor(0, 0, QColor(255, 0, 0, 128));
|
||||
|
||||
@@ -21,6 +21,7 @@ private slots:
|
||||
void theUpdatingPlateSitsInTheMiddle();
|
||||
void zoomKeepsTheMiddleAndDragsMoveIt();
|
||||
void theSheetsButtonsAndKeysZoomIt();
|
||||
void aZoomBeforeTheFirstImageCentresIt();
|
||||
};
|
||||
|
||||
namespace {
|
||||
@@ -303,5 +304,34 @@ void JPEGPreviewTests::theSheetsButtonsAndKeysZoomIt()
|
||||
ShortcutSettings::shared().reload();
|
||||
}
|
||||
|
||||
// Swift makes the preview with its first result, centred.
|
||||
void JPEGPreviewTests::aZoomBeforeTheFirstImageCentresIt()
|
||||
{
|
||||
EditorSession session;
|
||||
QImage red(1200, 800, QImage::Format_RGBA8888_Premultiplied);
|
||||
red.fill(Qt::red);
|
||||
const auto offsets = [&](bool early) {
|
||||
ExportAsSheet sheet({red, 72}, session, ExportFormat::png, [](std::optional<ExportAsSheet::Chosen>) {});
|
||||
sheet.show();
|
||||
auto &exporting = *sheet.findChild<QPushButton *>(QStringLiteral("exportExport"));
|
||||
if (early) {
|
||||
if (!QTest::qWaitForWindowActive(&sheet) || exporting.isEnabled())
|
||||
throw std::runtime_error("ready before the key");
|
||||
QTest::keyClick(&sheet, Qt::Key_1, Qt::ControlModifier);
|
||||
}
|
||||
if (!QTest::qWaitFor([&] { return exporting.isEnabled(); }))
|
||||
throw std::runtime_error("never ready");
|
||||
auto &preview = *sheet.findChild<JPEGPreview *>();
|
||||
if (!early)
|
||||
preview.setZoom(1);
|
||||
const QSize view = preview.viewport()->size();
|
||||
return std::array<int, 4>{preview.horizontalScrollBar()->value(), preview.verticalScrollBar()->value(), view.width(), view.height()};
|
||||
};
|
||||
const std::array<int, 4> early = offsets(true), late = offsets(false);
|
||||
QCOMPARE(early, late);
|
||||
QVERIFY(early[0] == int(std::lround(600 - early[2] / 2.0)) && early[1] == int(std::lround(400 - early[3] / 2.0)));
|
||||
QVERIFY(early[0] > 300 && early[1] > 200);
|
||||
}
|
||||
|
||||
QTEST_MAIN(JPEGPreviewTests)
|
||||
#include "JPEGPreviewTests.moc"
|
||||
@@ -101,6 +101,13 @@ void KeyboardShortcutsTests::theListIsSwiftsWithoutHide()
|
||||
QVERIFY(!ids.contains("Menus:Hide Compositor"));
|
||||
QCOMPARE(named("Export JPEG").original, ShortcutChord("s", 11));
|
||||
QVERIFY(named("Export JPEG").isMenu());
|
||||
// Swift 1.4.8's Export As follows Export JPEG.
|
||||
QCOMPARE(named("Export As").original, ShortcutChord("w", 11));
|
||||
QVERIFY(named("Export As").isMenu() && named("Export As").group == named("Export JPEG").group);
|
||||
const auto position = [&all](const char *title) {
|
||||
return std::find_if(all.begin(), all.end(), [title](const ShortcutDefinition &each) { return each.id() == QStringLiteral("Menus:") + QLatin1String(title); }) - all.begin();
|
||||
};
|
||||
QCOMPARE(position("Export As"), position("Export JPEG") + 1);
|
||||
QCOMPARE(named("Content-Aware Fill").original, ShortcutChord("\x7f", 8));
|
||||
QCOMPARE(named("Temporary Hand tool (hold)").original, ShortcutChord(" "));
|
||||
QCOMPARE(named("Opacity digit 7 (type two for exact %)").original, ShortcutChord("7"));
|
||||
|
||||
@@ -24,6 +24,7 @@ private slots:
|
||||
void aJPEGNeedsAWindowAndADocument();
|
||||
void theFileMenuOffersBothExports();
|
||||
void exportAsWritesEachFormatAndRemembersTheLast();
|
||||
void theMenuOpensExportAsOnTheLastFormat();
|
||||
};
|
||||
|
||||
namespace {
|
||||
@@ -324,5 +325,42 @@ void ProjectExportTests::exportAsWritesEachFormatAndRemembersTheLast()
|
||||
QCOMPARE(contents(Desk::path("half.png")), ImageExporter::pngData(ImageExporter::resized(raster, 20, 10)));
|
||||
}
|
||||
|
||||
void ProjectExportTests::theMenuOpensExportAsOnTheLastFormat()
|
||||
{
|
||||
QTemporaryDir folder;
|
||||
Bar bar;
|
||||
bar.session().createDocument(8, 8);
|
||||
bar.window.show();
|
||||
DialogDesk desk;
|
||||
const auto shown = [&bar]() -> ExportAsSheet & {
|
||||
if (!QTest::qWaitFor([&bar] { return bar.window.findChild<ExportAsSheet *>() && bar.window.findChild<ExportAsSheet *>()->isVisible(); }))
|
||||
throw std::runtime_error("no sheet");
|
||||
return *bar.window.findChild<ExportAsSheet *>();
|
||||
};
|
||||
const auto checked = [](ExportAsSheet &sheet, const char *name) { return sheet.findChild<QAbstractButton *>(QString::fromLatin1(name))->isChecked(); };
|
||||
const auto closed = [&bar] { return !bar.session().isProjectBusy() && !bar.window.findChild<ExportAsSheet *>(); };
|
||||
// PNG first; a PDF written through the menu comes next.
|
||||
bar.action("exportAs").trigger();
|
||||
QVERIFY(checked(shown(), "exportFormatPNG"));
|
||||
shown().findChild<QAbstractButton *>("exportFormatPDF")->click();
|
||||
desk.replies = {folder.filePath("Page")};
|
||||
QTRY_VERIFY(shown().findChild<QPushButton *>("exportExport")->isEnabled());
|
||||
shown().findChild<QPushButton *>("exportExport")->click();
|
||||
QTRY_VERIFY(QFileInfo::exists(folder.filePath("Page.pdf")) && closed());
|
||||
bar.action("exportAs").trigger();
|
||||
QVERIFY(checked(shown(), "exportFormatPDF"));
|
||||
QTest::keyClick(&shown(), Qt::Key_Escape);
|
||||
QTRY_VERIFY(closed());
|
||||
// Export JPEG starts on JPEG; Export As stays on PDF.
|
||||
bar.action("exportJPEG").trigger();
|
||||
QVERIFY(checked(shown(), "exportFormatJPEG"));
|
||||
QTest::keyClick(&shown(), Qt::Key_Escape);
|
||||
QTRY_VERIFY(closed());
|
||||
bar.action("exportAs").trigger();
|
||||
QVERIFY(checked(shown(), "exportFormatPDF"));
|
||||
QTest::keyClick(&shown(), Qt::Key_Escape);
|
||||
QTRY_VERIFY(closed());
|
||||
}
|
||||
|
||||
QTEST_MAIN(ProjectExportTests)
|
||||
#include "ProjectExportTests.moc"
|
||||
@@ -121,6 +121,7 @@ compositor_test(EditorSessionTests)
|
||||
compositor_test(LayerDuplicateTests)
|
||||
compositor_test(ExportTests)
|
||||
compositor_test(ExportPDFTests)
|
||||
compositor_test(ExportPDFFailureTests)
|
||||
compositor_test(FolderMaskClipTests)
|
||||
compositor_test(EditorCanvasTests)
|
||||
compositor_test(GroupSessionTests)
|
||||
|
||||
Reference in new issue
Block a user