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

View File

@ -13,28 +13,12 @@
#include "SkImage.h"
#include "SkImageInfoPriv.h"
#include "SkJpegInfo.h"
#include "SkPDFCanon.h"
#include "SkPDFDocumentPriv.h"
#include "SkPDFTypes.h"
#include "SkPDFUtils.h"
#include "SkStream.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.
@ -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
rendered as grey because of the separate soft mask and color
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());
unsigned r = 0, g = 0, b = 0, n = 0;
// 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 xmax = SkTMin(xOrig + 1, bm.width() - 1);
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) {
SkColor color = scanline[x];
if (color != SK_ColorTRANSPARENT) {
@ -80,173 +64,150 @@ static SkColor get_neighbor_avg_color(const SkBitmap& bm, int xOrig, int yOrig)
: SK_ColorTRANSPARENT;
}
static size_t pixel_count(const SkImage* image) {
return SkToSizeT(image->width()) * SkToSizeT(image->height());
static void emit_stream(SkDynamicMemoryWStream* src, SkWStream* dst) {
dst->writeText(" stream\n");
src->writeToAndReset(dst);
dst->writeText("\nendstream");
}
static size_t pdf_color_component_count(SkColorType ct) {
// Single-channel formats remain that way, all others are converted to RGB
return SkColorTypeIsAlphaOnly(ct) || SkColorTypeIsGray(ct) ? 1 : 3;
static void emit_dict(SkWStream* stream, SkISize size, const char* colorSpace,
const SkPDFIndirectReference* smask, int length) {
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) {
if (kAlpha_8_SkColorType == image->colorType()) {
fill_stream(out, '\x00', pixel_count(image));
} else if (kGray_8_SkColorType == image->colorType()) {
SkBitmap gray;
gray.allocPixels(SkImageInfo::Make(image->width(), image->height(),
kGray_8_SkColorType,
kPremul_SkAlphaType));
if (image->readPixels(gray.pixmap(), 0, 0)) {
out->write(gray.getPixels(), gray.computeByteSize());
} else {
fill_stream(out, '\x00', pixel_count(image));
}
static SkPDFIndirectReference do_deflated_alpha(const SkPixmap& pm, SkPDFDocument* doc,
SkPDFIndirectReference ref) {
SkDynamicMemoryWStream buffer;
SkDeflateWStream deflateWStream(&buffer);
if (kAlpha_8_SkColorType == pm.colorType()) {
SkASSERT(pm.rowBytes() == (size_t)pm.width());
buffer.write(pm.addr8(), pm.width() * pm.height());
} else {
SkBitmap bgra;
// TODO: makeColorSpace(sRGB) or actually tag the images
bgra.allocPixels(SkImageInfo::Make(image->width(), image->height(),
kBGRA_8888_SkColorType,
kUnpremul_SkAlphaType));
if (image->readPixels(bgra.pixmap(), 0, 0)) {
SkAutoTMalloc<uint8_t> scanline(3 * bgra.width());
for (int y = 0; y < bgra.height(); ++y) {
const SkColor* src = bgra.getAddr32(0, y);
uint8_t* dst = scanline.get();
for (int x = 0; x < bgra.width(); ++x) {
SkASSERT(pm.alphaType() == kUnpremul_SkAlphaType);
SkASSERT(pm.colorType() == kBGRA_8888_SkColorType);
SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4);
const uint32_t* ptr = pm.addr32();
const uint32_t* stop = ptr + pm.height() * pm.width();
uint8_t byteBuffer[4092];
uint8_t* bufferStop = byteBuffer + SK_ARRAY_COUNT(byteBuffer);
uint8_t* dst = byteBuffer;
while (ptr != stop) {
*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++;
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++ = SkColorGetG(color);
*dst++ = SkColorGetB(color);
if (dst == bufferStop) {
deflateWStream.write(byteBuffer, sizeof(byteBuffer));
dst = byteBuffer;
}
}
out->write(scanline.get(), 3 * bgra.width());
}
} else {
fill_stream(out, '\x00', 3 * pixel_count(image));
}
deflateWStream.write(byteBuffer, dst - byteBuffer);
}
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 void image_alpha_to_a8(const SkImage* image, SkWStream* out) {
SkBitmap alpha;
alpha.allocPixels(SkImageInfo::MakeA8(image->width(), image->height()));
if (image->readPixels(alpha.pixmap(), 0, 0)) {
out->write(alpha.getPixels(), alpha.computeByteSize());
} else {
fill_stream(out, '\xFF', pixel_count(image));
static bool do_jpeg(const SkData& data, SkPDFDocument* doc, SkISize size,
SkPDFIndirectReference* result) {
SkISize jpegSize;
SkEncodedInfo::Color jpegColorType;
SkEncodedOrigin exifOrientation;
if (!SkGetJpegInfo(data.data(), data.size(), &jpegSize,
&jpegColorType, &exifOrientation)) {
return false;
}
}
static void emit_image_xobject(SkWStream* stream,
const SkImage* image,
bool alpha,
const sk_sp<SkPDFObject>& smask) {
// 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);
bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color;
bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color;
if (jpegSize != size // Sanity check.
|| !goodColorType
|| kTopLeft_SkEncodedOrigin != exifOrientation) {
return false;
}
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");
pdfDict.insertName("Subtype", "Image");
pdfDict.insertInt("Width", image->width());
pdfDict.insertInt("Height", image->height());
if (alpha) {
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.insertInt("Width", jpegSize.width());
pdfDict.insertInt("Height", jpegSize.height());
if (yuv) {
pdfDict.insertName("ColorSpace", "DeviceRGB");
} else {
pdfDict.insertName("ColorSpace", "DeviceGray");
@ -254,60 +215,58 @@ void PDFJpegBitmap::emitObject(SkWStream* stream) const {
pdfDict.insertInt("BitsPerComponent", 8);
pdfDict.insertName("Filter", "DCTDecode");
pdfDict.insertInt("ColorTransform", 0);
pdfDict.insertInt("Length", SkToInt(fData->size()));
pdfDict.insertInt("Length", SkToInt(data.size()));
pdfDict.emitObject(stream);
stream->writeText(kStreamBegin);
stream->write(fData->data(), fData->size());
stream->writeText(kStreamEnd);
stream->writeText(" stream\n");
stream->write(data.data(), data.size());
stream->writeText("\nendstream");
doc->endObject();
return true;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
sk_sp<PDFJpegBitmap> make_jpeg_bitmap(sk_sp<SkData> data, SkISize size) {
SkISize jpegSize;
SkEncodedInfo::Color jpegColorType;
SkEncodedOrigin exifOrientation;
if (data && SkGetJpegInfo(data->data(), data->size(), &jpegSize,
&jpegColorType, &exifOrientation)) {
bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color;
bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color;
if (jpegSize == size // Sanity check.
&& goodColorType
&& kTopLeft_SkEncodedOrigin == exifOrientation) {
// hold on to data, not image.
#ifdef SK_PDF_IMAGE_STATS
gJpegImageObjects.fetch_add(1);
#endif
return sk_make_sp<PDFJpegBitmap>(jpegSize, std::move(data), yuv);
static SkBitmap to_pixels(const SkImage* image) {
SkBitmap bm;
int w = image->width(),
h = image->height();
switch (image->colorType()) {
case kAlpha_8_SkColorType:
bm.allocPixels(SkImageInfo::MakeA8(w, h));
break;
case kGray_8_SkColorType:
bm.allocPixels(SkImageInfo::Make(w, h, kGray_8_SkColorType, kOpaque_SkAlphaType));
break;
default: {
// TODO: makeColorSpace(sRGB) or actually tag the images
SkAlphaType at = bm.isOpaque() ? kOpaque_SkAlphaType : kUnpremul_SkAlphaType;
bm.allocPixels(SkImageInfo::Make(w, h, kBGRA_8888_SkColorType, at));
}
}
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) {
SkASSERT(image);
SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
SkPDFDocument* doc,
int encodingQuality) {
SkPDFIndirectReference result;
SkASSERT(img);
SkASSERT(doc);
SkASSERT(encodingQuality >= 0);
SkISize dimensions = image->dimensions();
sk_sp<SkData> data = image->refEncodedData();
if (auto jpeg = make_jpeg_bitmap(std::move(data), dimensions)) {
return std::move(jpeg);
SkISize dimensions = img->dimensions();
sk_sp<SkData> data = img->refEncodedData();
if (data && do_jpeg(*data, doc, dimensions, &result)) {
return result;
}
const bool isOpaque = image_compute_is_opaque(image.get());
SkBitmap bm = to_pixels(img);
SkPixmap pm = bm.pixmap();
bool isOpaque = pm.isOpaque() || pm.computeIsOpaque();
if (encodingQuality <= 100 && isOpaque) {
data = image->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality);
if (auto jpeg = make_jpeg_bitmap(std::move(data), dimensions)) {
return std::move(jpeg);
sk_sp<SkData> data = img->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality);
if (data && do_jpeg(*data, doc, dimensions, &result)) {
return result;
}
}
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));
return do_deflated_image(pm, doc, isOpaque);
}

View File

@ -7,18 +7,16 @@
#ifndef SkPDFBitmap_DEFINED
#define SkPDFBitmap_DEFINED
#include "SkRefCnt.h"
class SkImage;
class SkPDFObject;
class SkPDFDocument;
struct SkPDFIndirectReference;
/**
* SkPDFBitmap wraps a SkImage and serializes it 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.
*
* Serialize a SkImage as an Image Xobject.
* 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

View File

@ -1801,6 +1801,15 @@ static bool is_integral(const SkRect& r) {
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,
const SkRect* src,
const SkRect& dst,
@ -2028,12 +2037,10 @@ void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
sk_sp<SkPDFObject> pdfimage = pdfimagePtr ? *pdfimagePtr : nullptr;
if (!pdfimage) {
SkASSERT(imageSubset);
pdfimage = SkPDFCreateBitmapObject(imageSubset.release(),
fDocument->metadata().fEncodingQuality);
if (!pdfimage) {
return;
}
fDocument->serialize(pdfimage); // serialize images early.
auto ref = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
fDocument->metadata().fEncodingQuality);
SkASSERT(ref.fValue > 0);
pdfimage = sk_make_sp<PDFObj>(ref);
SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0}));
fDocument->canon()->fPDFBitmapMap.set(key, pdfimage);
}

View File

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

View File

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

View File

@ -16,6 +16,7 @@
#include "SkPDFCanon.h"
#include "SkPDFConvertType1FontStream.h"
#include "SkPDFDevice.h"
#include "SkPDFDocumentPriv.h"
#include "SkPDFMakeCIDGlyphWidthsArray.h"
#include "SkPDFMakeToUnicodeCmap.h"
#include "SkPDFResourceDict.h"
@ -55,7 +56,7 @@ static const int32_t kPdfSymbolic = 4;
struct SkPDFType0Font final : public SkPDFFont {
SkPDFType0Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&);
~SkPDFType0Font() override;
void getFontSubset(SkPDFCanon*) override;
void getFontSubset(SkPDFDocument*) override;
#ifdef SK_DEBUG
void emitObject(SkWStream*) const override;
bool fPopulated;
@ -66,13 +67,13 @@ struct SkPDFType0Font final : public SkPDFFont {
struct SkPDFType1Font final : public SkPDFFont {
SkPDFType1Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&, SkPDFCanon*);
~SkPDFType1Font() override {}
void getFontSubset(SkPDFCanon*) override {} // TODO(halcanary): implement
void getFontSubset(SkPDFDocument*) override {} // TODO(halcanary): implement
};
struct SkPDFType3Font final : public SkPDFFont {
SkPDFType3Font(SkPDFFont::Info, const SkAdvancedTypefaceMetrics&);
~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
void SkPDFType0Font::getFontSubset(SkPDFCanon* canon) {
void SkPDFType0Font::getFontSubset(SkPDFDocument* doc) {
SkPDFCanon* canon = doc->canon();
const SkAdvancedTypefaceMetrics* metricsPtr =
SkPDFFont::GetMetrics(this->typeface(), canon);
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,
SkTypeface* typeface,
const SkPDFGlyphUse& subset,
SkGlyphID firstGlyphID,
SkGlyphID lastGlyphID) {
SkPDFCanon* canon = doc->canon();
const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, canon);
SkASSERT(lastGlyphID >= firstGlyphID);
// 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");
auto proc = sk_make_sp<SkPDFStream>(content.detachAsStream());
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>();
d1->insertObject("XObject", std::move(d0));
proc->dict()->insertObject("Resources", std::move(d1));
@ -796,8 +799,8 @@ SkPDFType3Font::SkPDFType3Font(SkPDFFont::Info info,
const SkAdvancedTypefaceMetrics& metrics)
: SkPDFFont(std::move(info)) {}
void SkPDFType3Font::getFontSubset(SkPDFCanon* canon) {
add_type3_font_info(canon, this, this->typeface(), this->glyphUsage(),
void SkPDFType3Font::getFontSubset(SkPDFDocument* doc) {
add_type3_font_info(doc, this, this->typeface(), this->glyphUsage(),
this->firstGlyphID(), this->lastGlyphID());
}

View File

@ -99,7 +99,7 @@ public:
/** Subset the font based on current usage.
* 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.

View File

@ -202,6 +202,10 @@ void SkPDFUnion::emitObject(SkWStream* stream) const {
case Type::kObject:
fObject->emitObject(stream);
return;
case Type::kRef:
stream->writeDecAsText(fIntValue);
stream->writeText(" 0 R"); // Generation number is always 0.
return;
default:
SkDEBUGFAIL("SkPDFUnion::emitObject with bad type");
}
@ -218,6 +222,7 @@ void SkPDFUnion::addResources(SkPDFObjNumMap* objNumMap) const {
case Type::kString:
case Type::kNameSkS:
case Type::kStringSkS:
case Type::kRef:
return; // These have no resources.
case Type::kObjRef:
objNumMap->addObjectRecursively(fObject);
@ -281,6 +286,10 @@ SkPDFUnion SkPDFUnion::Object(sk_sp<SkPDFObject> objSp) {
return u;
}
SkPDFUnion SkPDFUnion::Ref(SkPDFIndirectReference ref) {
return SkASSERT(ref.fValue > 0), SkPDFUnion(Type::kRef, (int32_t)ref.fValue);
}
////////////////////////////////////////////////////////////////////////////////
#if 0 // Enable if needed.
@ -372,6 +381,10 @@ void SkPDFArray::appendObjRef(sk_sp<SkPDFObject> objSp) {
this->append(SkPDFUnion::ObjRef(std::move(objSp)));
}
void SkPDFArray::appendRef(SkPDFIndirectReference ref) {
this->append(SkPDFUnion::Ref(ref));
}
///////////////////////////////////////////////////////////////////////////////
SkPDFDict::~SkPDFDict() { this->drop(); }
@ -420,6 +433,10 @@ void SkPDFDict::reserve(int 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) {
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)));
}

View File

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