SkPDF: move image serialization over to new immediate mode.

Change-Id: If2bd4cd61bc3231edd860b0e08f78a6f24bd3094
Reviewed-on: https://skia-review.googlesource.com/c/171532
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
This commit is contained in:
Hal Canary 2018-11-13 16:45:14 -05:00 committed by Skia Commit-Bot
parent f2a7a20b32
commit a121183204
10 changed files with 280 additions and 265 deletions

View File

@ -119,12 +119,10 @@ protected:
return; return;
} }
while (loops-- > 0) { while (loops-- > 0) {
auto object = SkPDFCreateBitmapObject(fImage); SkNullWStream nullStream;
SkASSERT(object); SkPDFDocument doc(&nullStream, SkPDF::Metadata());
if (!object) { doc.beginPage(256, 256);
return; (void)SkPDFSerializeImage(fImage.get(), &doc);
}
test_pdf_object_serialization(object);
} }
} }
@ -156,12 +154,10 @@ protected:
return; return;
} }
while (loops-- > 0) { while (loops-- > 0) {
auto object = SkPDFCreateBitmapObject(fImage); SkNullWStream nullStream;
SkASSERT(object); SkPDFDocument doc(&nullStream, SkPDF::Metadata());
if (!object) { doc.beginPage(256, 256);
return; (void)SkPDFSerializeImage(fImage.get(), &doc);
}
test_pdf_object_serialization(object);
} }
} }

View File

@ -13,28 +13,12 @@
#include "SkImage.h" #include "SkImage.h"
#include "SkImageInfoPriv.h" #include "SkImageInfoPriv.h"
#include "SkJpegInfo.h" #include "SkJpegInfo.h"
#include "SkPDFCanon.h" #include "SkPDFDocumentPriv.h"
#include "SkPDFTypes.h" #include "SkPDFTypes.h"
#include "SkPDFUtils.h" #include "SkPDFUtils.h"
#include "SkStream.h" #include "SkStream.h"
#include "SkTo.h" #include "SkTo.h"
bool image_compute_is_opaque(const SkImage* image) {
if (image->isOpaque()) {
return true;
}
// keep output PDF small at cost of possible resource use.
SkBitmap bm;
// if image can not be read, treat as transparent.
return SkPDFUtils::ToBitmap(image, &bm) && SkBitmap::ComputeIsOpaque(bm);
}
////////////////////////////////////////////////////////////////////////////////
static const char kStreamBegin[] = " stream\n";
static const char kStreamEnd[] = "\nendstream";
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// write a single byte to a stream n times. // write a single byte to a stream n times.
@ -56,7 +40,7 @@ static void fill_stream(SkWStream* out, char value, size_t n) {
channel goes to black, and the should-be-transparent pixels are channel goes to black, and the should-be-transparent pixels are
rendered as grey because of the separate soft mask and color rendered as grey because of the separate soft mask and color
resizing. e.g.: gm/bitmappremul.cpp */ resizing. e.g.: gm/bitmappremul.cpp */
static SkColor get_neighbor_avg_color(const SkBitmap& bm, int xOrig, int yOrig) { static SkColor get_neighbor_avg_color(const SkPixmap& bm, int xOrig, int yOrig) {
SkASSERT(kBGRA_8888_SkColorType == bm.colorType()); SkASSERT(kBGRA_8888_SkColorType == bm.colorType());
unsigned r = 0, g = 0, b = 0, n = 0; unsigned r = 0, g = 0, b = 0, n = 0;
// Clamp the range to the edge of the bitmap. // Clamp the range to the edge of the bitmap.
@ -65,7 +49,7 @@ static SkColor get_neighbor_avg_color(const SkBitmap& bm, int xOrig, int yOrig)
int xmin = SkTMax(0, xOrig - 1); int xmin = SkTMax(0, xOrig - 1);
int xmax = SkTMin(xOrig + 1, bm.width() - 1); int xmax = SkTMin(xOrig + 1, bm.width() - 1);
for (int y = ymin; y <= ymax; ++y) { for (int y = ymin; y <= ymax; ++y) {
const SkColor* scanline = bm.getAddr32(0, y); const SkColor* scanline = bm.addr32(0, y);
for (int x = xmin; x <= xmax; ++x) { for (int x = xmin; x <= xmax; ++x) {
SkColor color = scanline[x]; SkColor color = scanline[x];
if (color != SK_ColorTRANSPARENT) { if (color != SK_ColorTRANSPARENT) {
@ -80,173 +64,150 @@ static SkColor get_neighbor_avg_color(const SkBitmap& bm, int xOrig, int yOrig)
: SK_ColorTRANSPARENT; : SK_ColorTRANSPARENT;
} }
static size_t pixel_count(const SkImage* image) { static void emit_stream(SkDynamicMemoryWStream* src, SkWStream* dst) {
return SkToSizeT(image->width()) * SkToSizeT(image->height()); dst->writeText(" stream\n");
src->writeToAndReset(dst);
dst->writeText("\nendstream");
} }
static size_t pdf_color_component_count(SkColorType ct) { static void emit_dict(SkWStream* stream, SkISize size, const char* colorSpace,
// Single-channel formats remain that way, all others are converted to RGB const SkPDFIndirectReference* smask, int length) {
return SkColorTypeIsAlphaOnly(ct) || SkColorTypeIsGray(ct) ? 1 : 3; SkPDFDict pdfDict("XObject");
pdfDict.insertName("Subtype", "Image");
pdfDict.insertInt("Width", size.width());
pdfDict.insertInt("Height", size.height());
pdfDict.insertName("ColorSpace", colorSpace);
if (smask) {
pdfDict.insertRef("SMask", *smask);
}
pdfDict.insertInt("BitsPerComponent", 8);
pdfDict.insertName("Filter", "FlateDecode");
pdfDict.insertInt("Length", length);
pdfDict.emitObject(stream);
} }
static void image_to_pdf_pixels(const SkImage* image, SkWStream* out) { static SkPDFIndirectReference do_deflated_alpha(const SkPixmap& pm, SkPDFDocument* doc,
if (kAlpha_8_SkColorType == image->colorType()) { SkPDFIndirectReference ref) {
fill_stream(out, '\x00', pixel_count(image)); SkDynamicMemoryWStream buffer;
} else if (kGray_8_SkColorType == image->colorType()) { SkDeflateWStream deflateWStream(&buffer);
SkBitmap gray; if (kAlpha_8_SkColorType == pm.colorType()) {
gray.allocPixels(SkImageInfo::Make(image->width(), image->height(), SkASSERT(pm.rowBytes() == (size_t)pm.width());
kGray_8_SkColorType, buffer.write(pm.addr8(), pm.width() * pm.height());
kPremul_SkAlphaType));
if (image->readPixels(gray.pixmap(), 0, 0)) {
out->write(gray.getPixels(), gray.computeByteSize());
} else {
fill_stream(out, '\x00', pixel_count(image));
}
} else { } else {
SkBitmap bgra; SkASSERT(pm.alphaType() == kUnpremul_SkAlphaType);
// TODO: makeColorSpace(sRGB) or actually tag the images SkASSERT(pm.colorType() == kBGRA_8888_SkColorType);
bgra.allocPixels(SkImageInfo::Make(image->width(), image->height(), SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4);
kBGRA_8888_SkColorType, const uint32_t* ptr = pm.addr32();
kUnpremul_SkAlphaType)); const uint32_t* stop = ptr + pm.height() * pm.width();
if (image->readPixels(bgra.pixmap(), 0, 0)) {
SkAutoTMalloc<uint8_t> scanline(3 * bgra.width()); uint8_t byteBuffer[4092];
for (int y = 0; y < bgra.height(); ++y) { uint8_t* bufferStop = byteBuffer + SK_ARRAY_COUNT(byteBuffer);
const SkColor* src = bgra.getAddr32(0, y); uint8_t* dst = byteBuffer;
uint8_t* dst = scanline.get(); while (ptr != stop) {
for (int x = 0; x < bgra.width(); ++x) { *dst++ = 0xFF & ((*ptr++) >> SK_BGRA_A32_SHIFT);
if (dst == bufferStop) {
deflateWStream.write(byteBuffer, sizeof(byteBuffer));
dst = byteBuffer;
}
}
deflateWStream.write(byteBuffer, dst - byteBuffer);
}
deflateWStream.finalize();
SkWStream* stream = doc->beginObject(ref);
emit_dict(stream, pm.info().dimensions(), "DeviceGray", nullptr, buffer.bytesWritten());
emit_stream(&buffer, stream);
doc->endObject();
return ref;
}
static SkPDFIndirectReference do_deflated_image(const SkPixmap& pm,
SkPDFDocument* doc,
bool isOpaque) {
SkPDFIndirectReference sMask;
if (!isOpaque) {
sMask = doc->reserve();
}
SkDynamicMemoryWStream buffer;
SkDeflateWStream deflateWStream(&buffer);
const char* colorSpace = "DeviceGray";
switch (pm.colorType()) {
case kAlpha_8_SkColorType:
fill_stream(&deflateWStream, '\x00', pm.width() * pm.height());
break;
case kGray_8_SkColorType:
SkASSERT(sMask.fValue = -1);
SkASSERT(pm.rowBytes() == (size_t)pm.width());
deflateWStream.write(pm.addr8(), pm.width() * pm.height());
break;
default:
colorSpace = "DeviceRGB";
SkASSERT(pm.alphaType() == kUnpremul_SkAlphaType);
SkASSERT(pm.colorType() == kBGRA_8888_SkColorType);
SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4);
uint8_t byteBuffer[3072];
static_assert(SK_ARRAY_COUNT(byteBuffer) % 3 == 0, "");
uint8_t* bufferStop = byteBuffer + SK_ARRAY_COUNT(byteBuffer);
uint8_t* dst = byteBuffer;
for (int y = 0; y < pm.height(); ++y) {
const SkColor* src = pm.addr32(0, y);
for (int x = 0; x < pm.width(); ++x) {
SkColor color = *src++; SkColor color = *src++;
if (SkColorGetA(color) == SK_AlphaTRANSPARENT) { if (SkColorGetA(color) == SK_AlphaTRANSPARENT) {
color = get_neighbor_avg_color(bgra, x, y); color = get_neighbor_avg_color(pm, x, y);
} }
*dst++ = SkColorGetR(color); *dst++ = SkColorGetR(color);
*dst++ = SkColorGetG(color); *dst++ = SkColorGetG(color);
*dst++ = SkColorGetB(color); *dst++ = SkColorGetB(color);
if (dst == bufferStop) {
deflateWStream.write(byteBuffer, sizeof(byteBuffer));
dst = byteBuffer;
}
} }
out->write(scanline.get(), 3 * bgra.width());
} }
} else { deflateWStream.write(byteBuffer, dst - byteBuffer);
fill_stream(out, '\x00', 3 * pixel_count(image));
}
} }
deflateWStream.finalize();
SkPDFIndirectReference ref = doc->reserve();
SkWStream* stream = doc->beginObject(ref);
emit_dict(stream, pm.info().dimensions(), colorSpace,
sMask.fValue != -1 ? &sMask : nullptr,
buffer.bytesWritten());
emit_stream(&buffer, stream);
doc->endObject();
if (!isOpaque) {
do_deflated_alpha(pm, doc, sMask);
}
return ref;
} }
//////////////////////////////////////////////////////////////////////////////// static bool do_jpeg(const SkData& data, SkPDFDocument* doc, SkISize size,
SkPDFIndirectReference* result) {
static void image_alpha_to_a8(const SkImage* image, SkWStream* out) { SkISize jpegSize;
SkBitmap alpha; SkEncodedInfo::Color jpegColorType;
alpha.allocPixels(SkImageInfo::MakeA8(image->width(), image->height())); SkEncodedOrigin exifOrientation;
if (image->readPixels(alpha.pixmap(), 0, 0)) { if (!SkGetJpegInfo(data.data(), data.size(), &jpegSize,
out->write(alpha.getPixels(), alpha.computeByteSize()); &jpegColorType, &exifOrientation)) {
} else { return false;
fill_stream(out, '\xFF', pixel_count(image));
} }
} bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color;
bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color;
static void emit_image_xobject(SkWStream* stream, if (jpegSize != size // Sanity check.
const SkImage* image, || !goodColorType
bool alpha, || kTopLeft_SkEncodedOrigin != exifOrientation) {
const sk_sp<SkPDFObject>& smask) { return false;
// Write to a temporary buffer to get the compressed length.
SkDynamicMemoryWStream buffer;
SkDeflateWStream deflateWStream(&buffer);
if (alpha) {
image_alpha_to_a8(image, &deflateWStream);
} else {
image_to_pdf_pixels(image, &deflateWStream);
} }
deflateWStream.finalize(); // call before buffer.bytesWritten(). #ifdef SK_PDF_IMAGE_STATS
gJpegImageObjects.fetch_add(1);
#endif
SkPDFIndirectReference ref = doc->reserve();
*result = ref;
SkWStream* stream = doc->beginObject(ref);
SkPDFDict pdfDict("XObject"); SkPDFDict pdfDict("XObject");
pdfDict.insertName("Subtype", "Image"); pdfDict.insertName("Subtype", "Image");
pdfDict.insertInt("Width", image->width()); pdfDict.insertInt("Width", jpegSize.width());
pdfDict.insertInt("Height", image->height()); pdfDict.insertInt("Height", jpegSize.height());
if (alpha) { if (yuv) {
pdfDict.insertName("ColorSpace", "DeviceGray");
} else if (1 == pdf_color_component_count(image->colorType())) {
pdfDict.insertName("ColorSpace", "DeviceGray");
} else {
pdfDict.insertName("ColorSpace", "DeviceRGB");
}
if (smask) {
pdfDict.insertObjRef("SMask", smask);
}
pdfDict.insertInt("BitsPerComponent", 8);
pdfDict.insertName("Filter", "FlateDecode");
pdfDict.insertInt("Length", buffer.bytesWritten());
pdfDict.emitObject(stream);
stream->writeText(kStreamBegin);
buffer.writeToAndReset(stream);
stream->writeText(kStreamEnd);
}
////////////////////////////////////////////////////////////////////////////////
namespace {
// This SkPDFObject only outputs the alpha layer of the given bitmap.
class PDFAlphaBitmap final : public SkPDFObject {
public:
PDFAlphaBitmap(sk_sp<SkImage> image) : fImage(std::move(image)) { SkASSERT(fImage); }
void emitObject(SkWStream* stream) const override {
SkASSERT(fImage);
emit_image_xobject(stream, fImage.get(), true, nullptr);
}
void drop() override { fImage = nullptr; }
private:
sk_sp<SkImage> fImage;
};
} // namespace
////////////////////////////////////////////////////////////////////////////////
namespace {
class PDFDefaultBitmap final : public SkPDFObject {
public:
void emitObject(SkWStream* stream) const override {
SkASSERT(fImage);
emit_image_xobject(stream, fImage.get(), false, fSMask);
}
void addResources(SkPDFObjNumMap* catalog) const override {
catalog->addObjectRecursively(fSMask.get());
}
void drop() override { fImage = nullptr; fSMask = nullptr; }
PDFDefaultBitmap(sk_sp<SkImage> image, sk_sp<SkPDFObject> smask)
: fImage(std::move(image)), fSMask(std::move(smask)) { SkASSERT(fImage); }
private:
sk_sp<SkImage> fImage;
sk_sp<SkPDFObject> fSMask;
};
} // namespace
////////////////////////////////////////////////////////////////////////////////
namespace {
/**
* This PDFObject assumes that its constructor was handed YUV or
* Grayscale JFIF Jpeg-encoded data that can be directly embedded
* into a PDF.
*/
class PDFJpegBitmap final : public SkPDFObject {
public:
SkISize fSize;
sk_sp<SkData> fData;
bool fIsYUV;
PDFJpegBitmap(SkISize size, sk_sp<SkData> data, bool isYUV)
: fSize(size), fData(std::move(data)), fIsYUV(isYUV) { SkASSERT(fData); }
void emitObject(SkWStream*) const override;
void drop() override { fData = nullptr; }
};
void PDFJpegBitmap::emitObject(SkWStream* stream) const {
SkASSERT(fData);
SkPDFDict pdfDict("XObject");
pdfDict.insertName("Subtype", "Image");
pdfDict.insertInt("Width", fSize.width());
pdfDict.insertInt("Height", fSize.height());
if (fIsYUV) {
pdfDict.insertName("ColorSpace", "DeviceRGB"); pdfDict.insertName("ColorSpace", "DeviceRGB");
} else { } else {
pdfDict.insertName("ColorSpace", "DeviceGray"); pdfDict.insertName("ColorSpace", "DeviceGray");
@ -254,60 +215,58 @@ void PDFJpegBitmap::emitObject(SkWStream* stream) const {
pdfDict.insertInt("BitsPerComponent", 8); pdfDict.insertInt("BitsPerComponent", 8);
pdfDict.insertName("Filter", "DCTDecode"); pdfDict.insertName("Filter", "DCTDecode");
pdfDict.insertInt("ColorTransform", 0); pdfDict.insertInt("ColorTransform", 0);
pdfDict.insertInt("Length", SkToInt(fData->size())); pdfDict.insertInt("Length", SkToInt(data.size()));
pdfDict.emitObject(stream); pdfDict.emitObject(stream);
stream->writeText(kStreamBegin); stream->writeText(" stream\n");
stream->write(fData->data(), fData->size()); stream->write(data.data(), data.size());
stream->writeText(kStreamEnd); stream->writeText("\nendstream");
doc->endObject();
return true;
} }
} // namespace
//////////////////////////////////////////////////////////////////////////////// static SkBitmap to_pixels(const SkImage* image) {
sk_sp<PDFJpegBitmap> make_jpeg_bitmap(sk_sp<SkData> data, SkISize size) { SkBitmap bm;
SkISize jpegSize; int w = image->width(),
SkEncodedInfo::Color jpegColorType; h = image->height();
SkEncodedOrigin exifOrientation; switch (image->colorType()) {
if (data && SkGetJpegInfo(data->data(), data->size(), &jpegSize, case kAlpha_8_SkColorType:
&jpegColorType, &exifOrientation)) { bm.allocPixels(SkImageInfo::MakeA8(w, h));
bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color; break;
bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color; case kGray_8_SkColorType:
if (jpegSize == size // Sanity check. bm.allocPixels(SkImageInfo::Make(w, h, kGray_8_SkColorType, kOpaque_SkAlphaType));
&& goodColorType break;
&& kTopLeft_SkEncodedOrigin == exifOrientation) { default: {
// hold on to data, not image. // TODO: makeColorSpace(sRGB) or actually tag the images
#ifdef SK_PDF_IMAGE_STATS SkAlphaType at = bm.isOpaque() ? kOpaque_SkAlphaType : kUnpremul_SkAlphaType;
gJpegImageObjects.fetch_add(1); bm.allocPixels(SkImageInfo::Make(w, h, kBGRA_8888_SkColorType, at));
#endif
return sk_make_sp<PDFJpegBitmap>(jpegSize, std::move(data), yuv);
} }
} }
return nullptr; if (!image->readPixels(bm.pixmap(), 0, 0)) {
bm.eraseColor(SkColorSetARGB(0xFF, 0, 0, 0));
}
return bm;
} }
sk_sp<SkPDFObject> SkPDFCreateBitmapObject(sk_sp<SkImage> image, int encodingQuality) { SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
SkASSERT(image); SkPDFDocument* doc,
int encodingQuality) {
SkPDFIndirectReference result;
SkASSERT(img);
SkASSERT(doc);
SkASSERT(encodingQuality >= 0); SkASSERT(encodingQuality >= 0);
SkISize dimensions = image->dimensions(); SkISize dimensions = img->dimensions();
sk_sp<SkData> data = image->refEncodedData(); sk_sp<SkData> data = img->refEncodedData();
if (auto jpeg = make_jpeg_bitmap(std::move(data), dimensions)) { if (data && do_jpeg(*data, doc, dimensions, &result)) {
return std::move(jpeg); return result;
} }
SkBitmap bm = to_pixels(img);
const bool isOpaque = image_compute_is_opaque(image.get()); SkPixmap pm = bm.pixmap();
bool isOpaque = pm.isOpaque() || pm.computeIsOpaque();
if (encodingQuality <= 100 && isOpaque) { if (encodingQuality <= 100 && isOpaque) {
data = image->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality); sk_sp<SkData> data = img->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality);
if (auto jpeg = make_jpeg_bitmap(std::move(data), dimensions)) { if (data && do_jpeg(*data, doc, dimensions, &result)) {
return std::move(jpeg); return result;
} }
} }
return do_deflated_image(pm, doc, isOpaque);
sk_sp<SkPDFObject> smask;
if (!isOpaque) {
smask = sk_make_sp<PDFAlphaBitmap>(image);
}
#ifdef SK_PDF_IMAGE_STATS
gRegularImageObjects.fetch_add(1);
#endif
return sk_make_sp<PDFDefaultBitmap>(std::move(image), std::move(smask));
} }

View File

@ -7,18 +7,16 @@
#ifndef SkPDFBitmap_DEFINED #ifndef SkPDFBitmap_DEFINED
#define SkPDFBitmap_DEFINED #define SkPDFBitmap_DEFINED
#include "SkRefCnt.h"
class SkImage; class SkImage;
class SkPDFObject; class SkPDFDocument;
struct SkPDFIndirectReference;
/** /**
* SkPDFBitmap wraps a SkImage and serializes it as an image Xobject. * Serialize a SkImage as an Image Xobject.
* It is designed to use a minimal amout of memory, aside from refing
* the image, and its emitObject() does not cache any data.
*
* quality > 100 means lossless * quality > 100 means lossless
*/ */
sk_sp<SkPDFObject> SkPDFCreateBitmapObject(sk_sp<SkImage>, int encodingQuality = 101); SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
SkPDFDocument* doc,
int encodingQuality = 101);
#endif // SkPDFBitmap_DEFINED #endif // SkPDFBitmap_DEFINED

View File

@ -1801,6 +1801,15 @@ static bool is_integral(const SkRect& r) {
is_integer(r.bottom()); is_integer(r.bottom());
} }
namespace {
// This struct will go away when fIndirectReference goes away.
struct PDFObj final : public SkPDFObject {
PDFObj(SkPDFIndirectReference ref) { fIndirectReference = ref; }
// emitObject() is never called since the Object already has a indirect ref.
void emitObject(SkWStream*) const override { SK_ABORT("DO NOT REACH HERE"); }
};
} // namespace
void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset, void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
const SkRect* src, const SkRect* src,
const SkRect& dst, const SkRect& dst,
@ -2028,12 +2037,10 @@ void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
sk_sp<SkPDFObject> pdfimage = pdfimagePtr ? *pdfimagePtr : nullptr; sk_sp<SkPDFObject> pdfimage = pdfimagePtr ? *pdfimagePtr : nullptr;
if (!pdfimage) { if (!pdfimage) {
SkASSERT(imageSubset); SkASSERT(imageSubset);
pdfimage = SkPDFCreateBitmapObject(imageSubset.release(), auto ref = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
fDocument->metadata().fEncodingQuality); fDocument->metadata().fEncodingQuality);
if (!pdfimage) { SkASSERT(ref.fValue > 0);
return; pdfimage = sk_make_sp<PDFObj>(ref);
}
fDocument->serialize(pdfimage); // serialize images early.
SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0})); SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0}));
fDocument->canon()->fPDFBitmapMap.set(key, pdfimage); fDocument->canon()->fPDFBitmapMap.set(key, pdfimage);
} }

View File

@ -26,19 +26,15 @@ const char* SkPDFGetNodeIdKey() {
void SkPDFOffsetMap::set(SkPDFIndirectReference iRef, SkPDFFileOffset offset) { void SkPDFOffsetMap::set(SkPDFIndirectReference iRef, SkPDFFileOffset offset) {
SkASSERT(iRef.fValue > 0); SkASSERT(iRef.fValue > 0);
SkASSERT(SkToSizeT(iRef.fValue - 1) == fOffsets.size()); size_t index = SkToSizeT(iRef.fValue - 1);
fOffsets.push_back(offset); if (index == fOffsets.size()) {
// The following logic can be used in a later CL when we serialize objects fOffsets.push_back(offset);
// out of order: } else if (index < fOffsets.size()) {
//size_t index = SkToSizeT(iRef.fValue - 1); fOffsets[index] = offset;
//if (index == fOffsets.size()) { } else {
// fOffsets.push_back(offset); fOffsets.resize(index + 1);
//} else if (index < fOffsets.size()) { fOffsets[index] = offset;
// fOffsets[index] = offset; }
//} else {
// fOffsets.resize(index + 1);
// fOffsets[index] = offset;
//}
} }
SkPDFFileOffset SkPDFOffsetMap::get(SkPDFIndirectReference r) { SkPDFFileOffset SkPDFOffsetMap::get(SkPDFIndirectReference r) {
@ -76,6 +72,18 @@ void SkPDFObjectSerializer::serializeHeader(SkWStream* wStream,
} }
#undef SKPDF_MAGIC #undef SKPDF_MAGIC
SkWStream* SkPDFObjectSerializer::beginObject(SkPDFIndirectReference ref, SkWStream* wStream) {
SkASSERT(ref.fValue > 0);
fOffsets.set(ref, this->offset(wStream));
wStream->writeDecAsText(ref.fValue);
wStream->writeText(" 0 obj\n"); // Generation number is always 0.
return wStream;
}
void SkPDFObjectSerializer::endObject(SkWStream* wStream) {
wStream->writeText("\nendobj\n");
}
void SkPDFObjectSerializer::serializeObject(const sk_sp<SkPDFObject>& object, void SkPDFObjectSerializer::serializeObject(const sk_sp<SkPDFObject>& object,
SkWStream* wStream) { SkWStream* wStream) {
SkPDFObjNumMap objNumMap; SkPDFObjNumMap objNumMap;
@ -83,16 +91,15 @@ void SkPDFObjectSerializer::serializeObject(const sk_sp<SkPDFObject>& object,
objNumMap.addObjectRecursively(object.get()); objNumMap.addObjectRecursively(object.get());
for (const sk_sp<SkPDFObject>& object : objNumMap.fObjects) { for (const sk_sp<SkPDFObject>& object : objNumMap.fObjects) {
fOffsets.set(object->fIndirectReference, this->offset(wStream)); this->beginObject(object->fIndirectReference, wStream);
wStream->writeDecAsText(object->fIndirectReference.fValue);
wStream->writeText(" 0 obj\n"); // Generation number is always 0.
object->emitObject(wStream); object->emitObject(wStream);
wStream->writeText("\nendobj\n"); this->endObject(wStream);
object->drop(); object->drop();
} }
fNextObjectNumber = objNumMap.fNextObjectNumber; // save for later. fNextObjectNumber = objNumMap.fNextObjectNumber; // save for later.
} }
// Xref table and footer // Xref table and footer
void SkPDFObjectSerializer::serializeFooter(SkWStream* wStream, void SkPDFObjectSerializer::serializeFooter(SkWStream* wStream,
const sk_sp<SkPDFObject> docCatalog, const sk_sp<SkPDFObject> docCatalog,
@ -105,6 +112,7 @@ void SkPDFObjectSerializer::serializeFooter(SkWStream* wStream,
wStream->writeDecAsText(objCount); wStream->writeDecAsText(objCount);
wStream->writeText("\n0000000000 65535 f \n"); wStream->writeText("\n0000000000 65535 f \n");
for (int i = 1; i < objCount; ++i) { for (int i = 1; i < objCount; ++i) {
SkASSERT(fOffsets.get(SkPDFIndirectReference{i}).fValue > 0);
wStream->writeBigDecAsText(fOffsets.get(SkPDFIndirectReference{i}).fValue, 10); wStream->writeBigDecAsText(fOffsets.get(SkPDFIndirectReference{i}).fValue, 10);
wStream->writeText(" 00000 n \n"); wStream->writeText(" 00000 n \n");
} }
@ -219,6 +227,19 @@ void SkPDFDocument::serialize(const sk_sp<SkPDFObject>& object) {
fObjectSerializer.serializeObject(object, this->getStream()); fObjectSerializer.serializeObject(object, this->getStream());
} }
SkPDFIndirectReference SkPDFDocument::reserve() {
++fOutstandingRefs;
return SkPDFIndirectReference{fObjectSerializer.fNextObjectNumber++};
};
SkWStream* SkPDFDocument::beginObject(SkPDFIndirectReference ref) {
--fOutstandingRefs;
return fObjectSerializer.beginObject(ref, this->getStream());
};
void SkPDFDocument::endObject() {
fObjectSerializer.endObject(this->getStream());
};
static SkSize operator*(SkISize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; } static SkSize operator*(SkISize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; }
static SkSize operator*(SkSize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; } static SkSize operator*(SkSize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; }
@ -554,10 +575,11 @@ void SkPDFDocument::onClose(SkWStream* stream) {
} }
} }
// Build font subsetting info before calling addObjectRecursively(). // Build font subsetting info before serializing the catalog and all of
SkPDFCanon* canon = &fCanon; // its transitive dependecies.
fFonts.foreach([canon](SkPDFFont* p){ p->getFontSubset(canon); }); fFonts.foreach([this](SkPDFFont* p){ p->getFontSubset(this); });
fObjectSerializer.serializeObject(docCatalog, this->getStream()); fObjectSerializer.serializeObject(docCatalog, this->getStream());
SkASSERT(fOutstandingRefs == 0);
fObjectSerializer.serializeFooter(this->getStream(), docCatalog, fID); fObjectSerializer.serializeFooter(this->getStream(), docCatalog, fID);
this->reset(); this->reset();
} }

View File

@ -43,6 +43,8 @@ struct SkPDFObjectSerializer {
SkPDFObjectSerializer(const SkPDFObjectSerializer&) = delete; SkPDFObjectSerializer(const SkPDFObjectSerializer&) = delete;
SkPDFObjectSerializer& operator=(const SkPDFObjectSerializer&) = delete; SkPDFObjectSerializer& operator=(const SkPDFObjectSerializer&) = delete;
SkWStream* beginObject(SkPDFIndirectReference, SkWStream*);
void endObject(SkWStream*);
void serializeHeader(SkWStream*, const SkPDF::Metadata&); void serializeHeader(SkWStream*, const SkPDF::Metadata&);
void serializeObject(const sk_sp<SkPDFObject>&, SkWStream*); void serializeObject(const sk_sp<SkPDFObject>&, SkWStream*);
void serializeFooter(SkWStream*, const sk_sp<SkPDFObject>, sk_sp<SkPDFObject>); void serializeFooter(SkWStream*, const sk_sp<SkPDFObject>, sk_sp<SkPDFObject>);
@ -79,6 +81,10 @@ public:
// Returns -1 if no mark ID. // Returns -1 if no mark ID.
int getMarkIdForNodeId(int nodeId); int getMarkIdForNodeId(int nodeId);
SkPDFIndirectReference reserve();
SkWStream* beginObject(SkPDFIndirectReference);
void endObject();
private: private:
sk_sp<SkPDFTag> recursiveBuildTagTree(const SkPDF::StructureElementNode& node, sk_sp<SkPDFTag> recursiveBuildTagTree(const SkPDF::StructureElementNode& node,
sk_sp<SkPDFTag> parent); sk_sp<SkPDFTag> parent);
@ -105,6 +111,8 @@ private:
// A mapping from node ID to tag for fast lookup. // A mapping from node ID to tag for fast lookup.
SkTHashMap<int, sk_sp<SkPDFTag>> fNodeIdToTag; SkTHashMap<int, sk_sp<SkPDFTag>> fNodeIdToTag;
int fOutstandingRefs = 0;
void reset(); void reset();
}; };

View File

@ -16,6 +16,7 @@
#include "SkPDFCanon.h" #include "SkPDFCanon.h"
#include "SkPDFConvertType1FontStream.h" #include "SkPDFConvertType1FontStream.h"
#include "SkPDFDevice.h" #include "SkPDFDevice.h"
#include "SkPDFDocumentPriv.h"
#include "SkPDFMakeCIDGlyphWidthsArray.h" #include "SkPDFMakeCIDGlyphWidthsArray.h"
#include "SkPDFMakeToUnicodeCmap.h" #include "SkPDFMakeToUnicodeCmap.h"
#include "SkPDFResourceDict.h" #include "SkPDFResourceDict.h"
@ -55,7 +56,7 @@ static const int32_t kPdfSymbolic = 4;
struct SkPDFType0Font final : public SkPDFFont { struct SkPDFType0Font final : public SkPDFFont {
SkPDFType0Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&); SkPDFType0Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&);
~SkPDFType0Font() override; ~SkPDFType0Font() override;
void getFontSubset(SkPDFCanon*) override; void getFontSubset(SkPDFDocument*) override;
#ifdef SK_DEBUG #ifdef SK_DEBUG
void emitObject(SkWStream*) const override; void emitObject(SkWStream*) const override;
bool fPopulated; bool fPopulated;
@ -66,13 +67,13 @@ struct SkPDFType0Font final : public SkPDFFont {
struct SkPDFType1Font final : public SkPDFFont { struct SkPDFType1Font final : public SkPDFFont {
SkPDFType1Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&, SkPDFCanon*); SkPDFType1Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&, SkPDFCanon*);
~SkPDFType1Font() override {} ~SkPDFType1Font() override {}
void getFontSubset(SkPDFCanon*) override {} // TODO(halcanary): implement void getFontSubset(SkPDFDocument*) override {} // TODO(halcanary): implement
}; };
struct SkPDFType3Font final : public SkPDFFont { struct SkPDFType3Font final : public SkPDFFont {
SkPDFType3Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&); SkPDFType3Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&);
~SkPDFType3Font() override {} ~SkPDFType3Font() override {}
void getFontSubset(SkPDFCanon*) override; void getFontSubset(SkPDFDocument*) override;
}; };
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -343,7 +344,8 @@ static sk_sp<SkData> stream_to_data(std::unique_ptr<SkStreamAsset> stream) {
} }
#endif // SK_PDF_SUBSET_SUPPORTED #endif // SK_PDF_SUBSET_SUPPORTED
void SkPDFType0Font::getFontSubset(SkPDFCanon* canon) { void SkPDFType0Font::getFontSubset(SkPDFDocument* doc) {
SkPDFCanon* canon = doc->canon();
const SkAdvancedTypefaceMetrics* metricsPtr = const SkAdvancedTypefaceMetrics* metricsPtr =
SkPDFFont::GetMetrics(this->typeface(), canon); SkPDFFont::GetMetrics(this->typeface(), canon);
SkASSERT(metricsPtr); SkASSERT(metricsPtr);
@ -644,12 +646,13 @@ static ImageAndOffset to_image(SkGlyphID gid, SkGlyphCache* cache) {
} }
} }
static void add_type3_font_info(SkPDFCanon* canon, static void add_type3_font_info(SkPDFDocument* doc,
SkPDFDict* font, SkPDFDict* font,
SkTypeface* typeface, SkTypeface* typeface,
const SkPDFGlyphUse& subset, const SkPDFGlyphUse& subset,
SkGlyphID firstGlyphID, SkGlyphID firstGlyphID,
SkGlyphID lastGlyphID) { SkGlyphID lastGlyphID) {
SkPDFCanon* canon = doc->canon();
const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, canon); const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, canon);
SkASSERT(lastGlyphID >= firstGlyphID); SkASSERT(lastGlyphID >= firstGlyphID);
// Remove unused glyphs at the end of the range. // Remove unused glyphs at the end of the range.
@ -734,7 +737,7 @@ static void add_type3_font_info(SkPDFCanon* canon,
content.writeText("/X Do\n"); content.writeText("/X Do\n");
auto proc = sk_make_sp<SkPDFStream>(content.detachAsStream()); auto proc = sk_make_sp<SkPDFStream>(content.detachAsStream());
auto d0 = sk_make_sp<SkPDFDict>(); auto d0 = sk_make_sp<SkPDFDict>();
d0->insertObjRef("X", SkPDFCreateBitmapObject(std::move(pimg.fImage))); d0->insertRef("X", SkPDFSerializeImage(pimg.fImage.get(), doc));
auto d1 = sk_make_sp<SkPDFDict>(); auto d1 = sk_make_sp<SkPDFDict>();
d1->insertObject("XObject", std::move(d0)); d1->insertObject("XObject", std::move(d0));
proc->dict()->insertObject("Resources", std::move(d1)); proc->dict()->insertObject("Resources", std::move(d1));
@ -796,8 +799,8 @@ SkPDFType3Font::SkPDFType3Font(SkPDFFont::Info info,
const SkAdvancedTypefaceMetrics& metrics) const SkAdvancedTypefaceMetrics& metrics)
: SkPDFFont(std::move(info)) {} : SkPDFFont(std::move(info)) {}
void SkPDFType3Font::getFontSubset(SkPDFCanon* canon) { void SkPDFType3Font::getFontSubset(SkPDFDocument* doc) {
add_type3_font_info(canon, this, this->typeface(), this->glyphUsage(), add_type3_font_info(doc, this, this->typeface(), this->glyphUsage(),
this->firstGlyphID(), this->lastGlyphID()); this->firstGlyphID(), this->lastGlyphID());
} }

View File

@ -99,7 +99,7 @@ public:
/** Subset the font based on current usage. /** Subset the font based on current usage.
* Must be called before emitObject(). * Must be called before emitObject().
*/ */
virtual void getFontSubset(SkPDFCanon*) = 0; virtual void getFontSubset(SkPDFDocument*) = 0;
/** /**
* Return false iff the typeface has its NotEmbeddable flag set. * Return false iff the typeface has its NotEmbeddable flag set.

View File

@ -202,6 +202,10 @@ void SkPDFUnion::emitObject(SkWStream* stream) const {
case Type::kObject: case Type::kObject:
fObject->emitObject(stream); fObject->emitObject(stream);
return; return;
case Type::kRef:
stream->writeDecAsText(fIntValue);
stream->writeText(" 0 R"); // Generation number is always 0.
return;
default: default:
SkDEBUGFAIL("SkPDFUnion::emitObject with bad type"); SkDEBUGFAIL("SkPDFUnion::emitObject with bad type");
} }
@ -218,6 +222,7 @@ void SkPDFUnion::addResources(SkPDFObjNumMap* objNumMap) const {
case Type::kString: case Type::kString:
case Type::kNameSkS: case Type::kNameSkS:
case Type::kStringSkS: case Type::kStringSkS:
case Type::kRef:
return; // These have no resources. return; // These have no resources.
case Type::kObjRef: case Type::kObjRef:
objNumMap->addObjectRecursively(fObject); objNumMap->addObjectRecursively(fObject);
@ -281,6 +286,10 @@ SkPDFUnion SkPDFUnion::Object(sk_sp<SkPDFObject> objSp) {
return u; return u;
} }
SkPDFUnion SkPDFUnion::Ref(SkPDFIndirectReference ref) {
return SkASSERT(ref.fValue > 0), SkPDFUnion(Type::kRef, (int32_t)ref.fValue);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#if 0 // Enable if needed. #if 0 // Enable if needed.
@ -372,6 +381,10 @@ void SkPDFArray::appendObjRef(sk_sp<SkPDFObject> objSp) {
this->append(SkPDFUnion::ObjRef(std::move(objSp))); this->append(SkPDFUnion::ObjRef(std::move(objSp)));
} }
void SkPDFArray::appendRef(SkPDFIndirectReference ref) {
this->append(SkPDFUnion::Ref(ref));
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
SkPDFDict::~SkPDFDict() { this->drop(); } SkPDFDict::~SkPDFDict() { this->drop(); }
@ -420,6 +433,10 @@ void SkPDFDict::reserve(int n) {
fRecords.reserve(n); fRecords.reserve(n);
} }
void SkPDFDict::insertRef(const char key[], SkPDFIndirectReference ref) {
fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Ref(ref)});
}
void SkPDFDict::insertObjRef(const char key[], sk_sp<SkPDFObject> objSp) { void SkPDFDict::insertObjRef(const char key[], sk_sp<SkPDFObject> objSp) {
fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ObjRef(std::move(objSp))}); fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ObjRef(std::move(objSp))});
} }
@ -538,7 +555,7 @@ void SkPDFSharedStream::addResources(
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
SkPDFStream:: SkPDFStream(sk_sp<SkData> data) { SkPDFStream::SkPDFStream(sk_sp<SkData> data) {
this->setData(skstd::make_unique<SkMemoryStream>(std::move(data))); this->setData(skstd::make_unique<SkMemoryStream>(std::move(data)));
} }

View File

@ -145,6 +145,8 @@ public:
static SkPDFUnion Object(sk_sp<SkPDFObject>); static SkPDFUnion Object(sk_sp<SkPDFObject>);
static SkPDFUnion ObjRef(sk_sp<SkPDFObject>); static SkPDFUnion ObjRef(sk_sp<SkPDFObject>);
static SkPDFUnion Ref(SkPDFIndirectReference);
/** These two non-virtual methods mirror SkPDFObject's /** These two non-virtual methods mirror SkPDFObject's
corresponding virtuals. */ corresponding virtuals. */
void emitObject(SkWStream*) const; void emitObject(SkWStream*) const;
@ -176,6 +178,7 @@ private:
kStringSkS, kStringSkS,
kObjRef, kObjRef,
kObject, kObject,
kRef,
}; };
Type fType; Type fType;
@ -252,6 +255,7 @@ public:
void appendString(const SkString&); void appendString(const SkString&);
void appendObject(sk_sp<SkPDFObject>); void appendObject(sk_sp<SkPDFObject>);
void appendObjRef(sk_sp<SkPDFObject>); void appendObjRef(sk_sp<SkPDFObject>);
void appendRef(SkPDFIndirectReference);
private: private:
std::vector<SkPDFUnion> fValues; std::vector<SkPDFUnion> fValues;
@ -310,6 +314,7 @@ public:
void insertObject(const SkString& key, sk_sp<SkPDFObject>); void insertObject(const SkString& key, sk_sp<SkPDFObject>);
void insertObjRef(const char key[], sk_sp<SkPDFObject>); void insertObjRef(const char key[], sk_sp<SkPDFObject>);
void insertObjRef(const SkString& key, sk_sp<SkPDFObject>); void insertObjRef(const SkString& key, sk_sp<SkPDFObject>);
void insertRef(const char key[], SkPDFIndirectReference);
/** Add the value to the dictionary with the given key. /** Add the value to the dictionary with the given key.
* @param key The text of the key for this dictionary entry. * @param key The text of the key for this dictionary entry.