skia2/bench/PDFBench.cpp
Ben Wagner 225c8861b7 [pdf] Differentiate text from byte strings.
In PDF there are two different physical encodings of strings (as a
sequence of bytes). There is the literal string encoding which is
delimited by '(' and ') which stores the bytes as-is except for '(',
')', and the escape character '\' (which can introduce octal encoded
bytes). There is also the hex string encoding delimited by '<' and '>'
and the bytes are encoded in hex pairs (with an implicit '0' at the end
for odd length encodings).

The interpretation of these bytes depends on the logical string type of
the dictionary key. There is a base abstract (well, almost abstract
except for legacy purposes) string type. The subtypes of the string type
are `text string`, `ASCII string`, and `byte string`. The `text string`
is logically further subtyped into `PDFDocEncoded string` and `UTF-16BE
with BOM`. In theory any of these logical string types may have its
encoded bytes written out in either of the two physical string
encodings.

In practice for Skia this means there are two types of string to keep
track of, since `ASCII string` and `byte string` can be treated the
same (in this change they are both treated as `byte string`). If the
type is `text string` then the bytes Skia has are interpreted as UTF-8
and may be converted to `UTF-16BE with BOM` or used directly as
`PDFDocEncoded string` if that is valid. If the type is `byte string`
then the bytes Skia has may not be converted and must be written as-is.

This means that when Skia sets a dictionary key to a string value it
must, at the very least, remember if the key's type was `text string`.
This change replaces all `String` methods with `ByteString` and
`TextString` methods and updates all the callers to the correct one
based on the key being written.

With the string handling corrected, the `/ActualText` string is now
emitted with this new common code as well for better output and to
reduce code duplication. A few no longer used public APIs involving
these strings are removed. The documentation for the URI annotation is
updated to reflect reality.

This change outputs `UTF-16BE with BOM` with the hex string encoding
only and does not attempt to fix the literal string encoding which
always escapes bytes > 0x7F. These changes may be attempted in a
separate change.

Bug: chromium:1323159
Change-Id: I00bdd5c90ad1ff2edfb74a9de41424c4eeac5ccb
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/543084
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
Reviewed-by: Herb Derby <herb@google.com>
2022-05-24 18:46:42 +00:00

448 lines
16 KiB
C++

/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "bench/Benchmark.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkData.h"
#include "include/core/SkExecutor.h"
#include "include/core/SkImage.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkStream.h"
#include "include/effects/SkGradientShader.h"
#include "include/private/SkTo.h"
#include "include/utils/SkRandom.h"
#include "src/core/SkAutoPixmapStorage.h"
#include "src/pdf/SkPDFUnion.h"
#include "src/utils/SkFloatToDecimal.h"
#include "tools/Resources.h"
namespace {
struct WStreamWriteTextBenchmark : public Benchmark {
std::unique_ptr<SkWStream> fWStream;
WStreamWriteTextBenchmark() : fWStream(new SkNullWStream) {}
const char* onGetName() override { return "WStreamWriteText"; }
bool isSuitableFor(Backend backend) override {
return backend == kNonRendering_Backend;
}
void onDraw(int loops, SkCanvas*) override {
while (loops-- > 0) {
for (int i = 1000; i-- > 0;) {
fWStream->writeText("HELLO SKIA!\n");
}
}
}
};
} // namespace
DEF_BENCH(return new WStreamWriteTextBenchmark;)
// Test speed of SkFloatToDecimal for typical floats that
// might be found in a PDF document.
struct PDFScalarBench : public Benchmark {
PDFScalarBench(const char* n, float (*f)(SkRandom*)) : fName(n), fNextFloat(f) {}
const char* fName;
float (*fNextFloat)(SkRandom*);
bool isSuitableFor(Backend b) override {
return b == kNonRendering_Backend;
}
const char* onGetName() override { return fName; }
void onDraw(int loops, SkCanvas*) override {
SkRandom random;
char dst[kMaximumSkFloatToDecimalLength];
while (loops-- > 0) {
auto f = fNextFloat(&random);
(void)SkFloatToDecimal(f, dst);
}
}
};
float next_common(SkRandom* random) {
return random->nextRangeF(-500.0f, 1500.0f);
}
float next_any(SkRandom* random) {
union { uint32_t u; float f; };
u = random->nextU();
static_assert(sizeof(float) == sizeof(uint32_t), "");
return f;
}
DEF_BENCH(return new PDFScalarBench("PDFScalar_common", next_common);)
DEF_BENCH(return new PDFScalarBench("PDFScalar_random", next_any);)
#ifdef SK_SUPPORT_PDF
#include "src/pdf/SkPDFBitmap.h"
#include "src/pdf/SkPDFDocumentPriv.h"
#include "src/pdf/SkPDFShader.h"
#include "src/pdf/SkPDFUtils.h"
namespace {
class PDFImageBench : public Benchmark {
public:
PDFImageBench() {}
~PDFImageBench() override {}
protected:
const char* onGetName() override { return "PDFImage"; }
bool isSuitableFor(Backend backend) override {
return backend == kNonRendering_Backend;
}
void onDelayedSetup() override {
sk_sp<SkImage> img(GetResourceAsImage("images/color_wheel.png"));
if (img) {
// force decoding, throw away reference to encoded data.
SkAutoPixmapStorage pixmap;
pixmap.alloc(SkImageInfo::MakeN32Premul(img->dimensions()));
if (img->readPixels(nullptr, pixmap, 0, 0)) {
fImage = SkImage::MakeRasterCopy(pixmap);
}
}
}
void onDraw(int loops, SkCanvas*) override {
if (!fImage) {
return;
}
while (loops-- > 0) {
SkNullWStream nullStream;
SkPDFDocument doc(&nullStream, SkPDF::Metadata());
doc.beginPage(256, 256);
(void)SkPDFSerializeImage(fImage.get(), &doc);
}
}
private:
sk_sp<SkImage> fImage;
};
class PDFJpegImageBench : public Benchmark {
public:
PDFJpegImageBench() {}
~PDFJpegImageBench() override {}
protected:
const char* onGetName() override { return "PDFJpegImage"; }
bool isSuitableFor(Backend backend) override {
return backend == kNonRendering_Backend;
}
void onDelayedSetup() override {
sk_sp<SkImage> img(GetResourceAsImage("images/mandrill_512_q075.jpg"));
if (!img) { return; }
sk_sp<SkData> encoded = img->refEncodedData();
SkASSERT(encoded);
if (!encoded) { return; }
fImage = img;
}
void onDraw(int loops, SkCanvas*) override {
if (!fImage) {
SkDEBUGFAIL("");
return;
}
while (loops-- > 0) {
SkNullWStream nullStream;
SkPDFDocument doc(&nullStream, SkPDF::Metadata());
doc.beginPage(256, 256);
(void)SkPDFSerializeImage(fImage.get(), &doc);
}
}
private:
sk_sp<SkImage> fImage;
};
/** Test calling DEFLATE on a 78k PDF command stream. Used for measuring
alternate zlib settings, usage, and library versions. */
class PDFCompressionBench : public Benchmark {
public:
PDFCompressionBench() {}
~PDFCompressionBench() override {}
protected:
const char* onGetName() override { return "PDFCompression"; }
bool isSuitableFor(Backend backend) override {
return backend == kNonRendering_Backend;
}
void onDelayedSetup() override {
fAsset = GetResourceAsStream("pdf_command_stream.txt");
}
void onDraw(int loops, SkCanvas*) override {
SkASSERT(fAsset);
if (!fAsset) { return; }
while (loops-- > 0) {
SkNullWStream wStream;
SkPDFDocument doc(&wStream, SkPDF::Metadata());
doc.beginPage(256, 256);
(void)SkPDFStreamOut(nullptr, fAsset->duplicate(), &doc, true);
}
}
private:
std::unique_ptr<SkStreamAsset> fAsset;
};
struct PDFColorComponentBench : public Benchmark {
bool isSuitableFor(Backend b) override {
return b == kNonRendering_Backend;
}
const char* onGetName() override { return "PDFColorComponent"; }
void onDraw(int loops, SkCanvas*) override {
char dst[5];
while (loops-- > 0) {
for (int i = 0; i < 256; ++i) {
(void)SkPDFUtils::ColorToDecimal(SkToU8(i), dst);
}
}
}
};
struct PDFShaderBench : public Benchmark {
sk_sp<SkShader> fShader;
const char* onGetName() final { return "PDFShader"; }
bool isSuitableFor(Backend b) final { return b == kNonRendering_Backend; }
void onDelayedSetup() final {
const SkPoint pts[2] = {{0.0f, 0.0f}, {100.0f, 100.0f}};
const SkColor colors[] = {
SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE,
SK_ColorWHITE, SK_ColorBLACK,
};
fShader = SkGradientShader::MakeLinear(
pts, colors, nullptr, SK_ARRAY_COUNT(colors),
SkTileMode::kClamp);
}
void onDraw(int loops, SkCanvas*) final {
SkASSERT(fShader);
while (loops-- > 0) {
SkNullWStream nullStream;
SkPDFDocument doc(&nullStream, SkPDF::Metadata());
doc.beginPage(256, 256);
(void) SkPDFMakeShader(&doc, fShader.get(), SkMatrix::I(),
{0, 0, 400, 400}, SkColors::kBlack);
}
}
};
struct WritePDFTextBenchmark : public Benchmark {
std::unique_ptr<SkWStream> fWStream;
WritePDFTextBenchmark() : fWStream(new SkNullWStream) {}
const char* onGetName() override { return "WritePDFText"; }
bool isSuitableFor(Backend backend) override {
return backend == kNonRendering_Backend;
}
void onDraw(int loops, SkCanvas*) override {
static const char kHello[] = "HELLO SKIA!\n";
static const char kBinary[] = "\001\002\003\004\005\006";
while (loops-- > 0) {
for (int i = 1000; i-- > 0;) {
SkPDFWriteTextString(fWStream.get(), kHello, strlen(kHello));
SkPDFWriteByteString(fWStream.get(), kBinary, strlen(kBinary));
}
}
}
};
// Test for regression chromium:947381
// with 5c83ae81aa : 2364.99 microsec
// without 5c83ae81aa : 302821.78 microsec
struct PDFClipPathBenchmark : public Benchmark {
SkPath fPath;
void onDelayedSetup() override {
SkBitmap bitmap;
bitmap.allocN32Pixels(256, 256);
bitmap.eraseColor(SK_ColorWHITE);
{
SkCanvas tmp(bitmap);
SkPaint paint;
paint.setAntiAlias(false);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(10);
for (int r : {20, 40, 60, 80, 100, 120}) {
tmp.drawCircle(128, 128, (float)r, paint);
}
}
fPath.reset();
for (int y = 0; y < 256; ++y) {
SkColor current = bitmap.getColor(0, y);
int start = 0;
for (int x = 0; x < 256; ++x) {
SkColor color = bitmap.getColor(x, y);
if (color == current) {
continue;
}
if (color == SK_ColorBLACK) {
start = x;
} else {
fPath.addRect(SkRect::Make(SkIRect{start, y, x, y + 1}));
}
current = color;
}
if (current == SK_ColorBLACK) {
fPath.addRect(SkRect::Make(SkIRect{start, y, 256, y + 1}));
}
}
}
const char* onGetName() override { return "PDFClipPath"; }
bool isSuitableFor(Backend backend) override {
return backend == kNonRendering_Backend;
}
void onDraw(int loops, SkCanvas*) override {
while (loops-- > 0) {
SkNullWStream wStream;
SkPDFDocument doc(&wStream, SkPDF::Metadata());
SkCanvas* canvas = doc.beginPage(256, 256);
canvas->clipPath(fPath);
canvas->translate(4.0f/3, 4.0f/3);
canvas->clipPath(fPath);
canvas->clear(SK_ColorRED);
doc.endPage();
}
}
};
} // namespace
DEF_BENCH(return new PDFImageBench;)
DEF_BENCH(return new PDFJpegImageBench;)
DEF_BENCH(return new PDFCompressionBench;)
DEF_BENCH(return new PDFColorComponentBench;)
DEF_BENCH(return new PDFShaderBench;)
DEF_BENCH(return new WritePDFTextBenchmark;)
DEF_BENCH(return new PDFClipPathBenchmark;)
#ifdef SK_PDF_ENABLE_SLOW_TESTS
#include "include/core/SkExecutor.h"
namespace {
void big_pdf_test(SkDocument* doc, const SkBitmap& background) {
static const char* kText[] = {
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do",
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad",
"minim veniam, quis nostrud exercitation ullamco laboris nisi ut",
"aliquip ex ea commodo consequat. Duis aute irure dolor in",
"reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla",
"pariatur. Excepteur sint occaecat cupidatat non proident, sunt in",
"culpa qui officia deserunt mollit anim id est laborum.",
"",
"Sed ut perspiciatis, unde omnis iste natus error sit voluptatem",
"accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae",
"ab illo inventore veritatis et quasi architecto beatae vitae dicta",
"sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit,",
"aspernatur aut odit aut fugit, sed quia consequuntur magni dolores",
"eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,",
"qui dolorem ipsum, quia dolor sit amet consectetur adipiscing velit,",
"sed quia non numquam do eius modi tempora incididunt, ut labore et",
"dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam,",
"quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi",
"ut aliquid ex ea commodi consequatur? Quis autem vel eum iure",
"reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae",
"consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla",
"pariatur?",
"",
"At vero eos et accusamus et iusto odio dignissimos ducimus, qui",
"blanditiis praesentium voluptatum deleniti atque corrupti, quos",
"dolores et quas molestias excepturi sint, obcaecati cupiditate non",
"provident, similique sunt in culpa, qui officia deserunt mollitia",
"animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis",
"est et expedita distinctio. Nam libero tempore, cum soluta nobis est",
"eligendi optio, cumque nihil impedit, quo minus id, quod maxime",
"placeat, facere possimus, omnis voluptas assumenda est, omnis dolor",
"repellendus. Temporibus autem quibusdam et aut officiis debitis aut",
"rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint",
"et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente",
"delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut",
"perferendis doloribus asperiores repellat",
"",
"Sed ut perspiciatis, unde omnis iste natus error sit voluptatem",
"accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae",
"ab illo inventore veritatis et quasi architecto beatae vitae dicta",
"sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit,",
"aspernatur aut odit aut fugit, sed quia consequuntur magni dolores",
"eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,",
"qui dolorem ipsum, quia dolor sit amet consectetur adipiscing velit,",
"sed quia non numquam do eius modi tempora incididunt, ut labore et",
"dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam,",
"quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi",
"ut aliquid ex ea commodi consequatur? Quis autem vel eum iure",
"reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae",
"consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla",
"pariatur?",
"",
};
SkCanvas* canvas = nullptr;
float x = 36;
float y = 36;
constexpr size_t kLineCount = SK_ARRAY_COUNT(kText);
constexpr int kLoopCount = 200;
SkFont font;
SkPaint paint;
for (int loop = 0; loop < kLoopCount; ++loop) {
for (size_t line = 0; line < kLineCount; ++line) {
y += font.getSpacing();
if (!canvas || y > 792 - 36) {
y = 36 + font.getSpacing();
canvas = doc->beginPage(612, 792);
background.notifyPixelsChanged();
canvas->drawBitmap(background, 0, 0);
}
canvas->drawString(kText[line], x, y, font, paint);
}
}
}
SkBitmap make_background() {
SkBitmap background;
SkBitmap bitmap;
bitmap.allocN32Pixels(32, 32);
bitmap.eraseColor(SK_ColorWHITE);
SkCanvas tmp(bitmap);
SkPaint gray;
gray.setColor(SkColorSetARGB(0xFF, 0xEE, 0xEE, 0xEE));
tmp.drawRect({0,0,16,16}, gray);
tmp.drawRect({16,16,32,32}, gray);
SkPaint shader;
shader.setShader(
SkShader::MakeBitmapShader(
bitmap, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
background.allocN32Pixels(612, 792);
SkCanvas tmp2(background);
tmp2.drawPaint(shader);
return background;
}
struct PDFBigDocBench : public Benchmark {
bool fFast;
SkBitmap fBackground;
std::unique_ptr<SkExecutor> fExecutor;
PDFBigDocBench(bool fast) : fFast(fast) {}
void onDelayedSetup() override {
fBackground = make_background();
fExecutor = fFast ? SkExecutor::MakeFIFOThreadPool() : nullptr;
}
const char* onGetName() override {
static const char kNameFast[] = "PDFBigDocBench_fast";
static const char kNameSlow[] = "PDFBigDocBench_slow";
return fFast ? kNameFast : kNameSlow;
}
bool isSuitableFor(Backend backend) override { return backend == kNonRendering_Backend; }
void onDraw(int loops, SkCanvas*) override {
while (loops-- > 0) {
#ifdef SK_PDF_TEST_BIGDOCBENCH_OUTPUT
SkFILEWStream wStream("/tmp/big_pdf.pdf");
#else
SkNullWStream wStream;
#endif
SkPDF::Metadata metadata;
metadata.fExecutor = fExecutor.get();
auto doc = SkPDF::MakeDocument(&wStream, metadata);
big_pdf_test(doc.get(), fBackground);
}
}
};
} // namespace
DEF_BENCH(return new PDFBigDocBench(false);)
DEF_BENCH(return new PDFBigDocBench(true);)
#endif
#endif // SK_SUPPORT_PDF