diff --git a/bench/PDFBench.cpp b/bench/PDFBench.cpp index b9643c7d0d..f612357c6c 100644 --- a/bench/PDFBench.cpp +++ b/bench/PDFBench.cpp @@ -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); } } diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp index 905a50d102..a5754efd5a 100644 --- a/src/pdf/SkPDFBitmap.cpp +++ b/src/pdf/SkPDFBitmap.cpp @@ -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 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& 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 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 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 image, sk_sp smask) - : fImage(std::move(image)), fSMask(std::move(smask)) { SkASSERT(fImage); } - -private: - sk_sp fImage; - sk_sp 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 fData; - bool fIsYUV; - PDFJpegBitmap(SkISize size, sk_sp 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 make_jpeg_bitmap(sk_sp 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(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 SkPDFCreateBitmapObject(sk_sp 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 data = image->refEncodedData(); - if (auto jpeg = make_jpeg_bitmap(std::move(data), dimensions)) { - return std::move(jpeg); + SkISize dimensions = img->dimensions(); + sk_sp 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 data = img->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality); + if (data && do_jpeg(*data, doc, dimensions, &result)) { + return result; } } - - sk_sp smask; - if (!isOpaque) { - smask = sk_make_sp(image); - } - #ifdef SK_PDF_IMAGE_STATS - gRegularImageObjects.fetch_add(1); - #endif - return sk_make_sp(std::move(image), std::move(smask)); + return do_deflated_image(pm, doc, isOpaque); } diff --git a/src/pdf/SkPDFBitmap.h b/src/pdf/SkPDFBitmap.h index 52ac59fea8..bc2c57bd3b 100644 --- a/src/pdf/SkPDFBitmap.h +++ b/src/pdf/SkPDFBitmap.h @@ -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 SkPDFCreateBitmapObject(sk_sp, int encodingQuality = 101); +SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img, + SkPDFDocument* doc, + int encodingQuality = 101); #endif // SkPDFBitmap_DEFINED diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp index 490edfbb20..a4124385d3 100644 --- a/src/pdf/SkPDFDevice.cpp +++ b/src/pdf/SkPDFDevice.cpp @@ -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 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(ref); SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0})); fDocument->canon()->fPDFBitmapMap.set(key, pdfimage); } diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp index 0efc11bc62..fb71dc1635 100644 --- a/src/pdf/SkPDFDocument.cpp +++ b/src/pdf/SkPDFDocument.cpp @@ -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& object, SkWStream* wStream) { SkPDFObjNumMap objNumMap; @@ -83,16 +91,15 @@ void SkPDFObjectSerializer::serializeObject(const sk_sp& object, objNumMap.addObjectRecursively(object.get()); for (const sk_sp& 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 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& 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(); } diff --git a/src/pdf/SkPDFDocumentPriv.h b/src/pdf/SkPDFDocumentPriv.h index 774a30acd9..b68162c494 100644 --- a/src/pdf/SkPDFDocumentPriv.h +++ b/src/pdf/SkPDFDocumentPriv.h @@ -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&, SkWStream*); void serializeFooter(SkWStream*, const sk_sp, sk_sp); @@ -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 recursiveBuildTagTree(const SkPDF::StructureElementNode& node, sk_sp parent); @@ -105,6 +111,8 @@ private: // A mapping from node ID to tag for fast lookup. SkTHashMap> fNodeIdToTag; + int fOutstandingRefs = 0; + void reset(); }; diff --git a/src/pdf/SkPDFFont.cpp b/src/pdf/SkPDFFont.cpp index 8d55b6bfa7..dbcf12cff7 100644 --- a/src/pdf/SkPDFFont.cpp +++ b/src/pdf/SkPDFFont.cpp @@ -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 stream_to_data(std::unique_ptr 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(content.detachAsStream()); auto d0 = sk_make_sp(); - d0->insertObjRef("X", SkPDFCreateBitmapObject(std::move(pimg.fImage))); + d0->insertRef("X", SkPDFSerializeImage(pimg.fImage.get(), doc)); auto d1 = sk_make_sp(); 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()); } diff --git a/src/pdf/SkPDFFont.h b/src/pdf/SkPDFFont.h index 28957b0ccb..c77ae1a021 100644 --- a/src/pdf/SkPDFFont.h +++ b/src/pdf/SkPDFFont.h @@ -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. diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp index 4f6c4410b2..7cf35f9f49 100644 --- a/src/pdf/SkPDFTypes.cpp +++ b/src/pdf/SkPDFTypes.cpp @@ -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 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 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 objSp) { fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ObjRef(std::move(objSp))}); } @@ -538,7 +555,7 @@ void SkPDFSharedStream::addResources( //////////////////////////////////////////////////////////////////////////////// -SkPDFStream:: SkPDFStream(sk_sp data) { +SkPDFStream::SkPDFStream(sk_sp data) { this->setData(skstd::make_unique(std::move(data))); } diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h index 1549c23a92..b7d5e3fd55 100644 --- a/src/pdf/SkPDFTypes.h +++ b/src/pdf/SkPDFTypes.h @@ -145,6 +145,8 @@ public: static SkPDFUnion Object(sk_sp); static SkPDFUnion ObjRef(sk_sp); + 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); void appendObjRef(sk_sp); + void appendRef(SkPDFIndirectReference); private: std::vector fValues; @@ -310,6 +314,7 @@ public: void insertObject(const SkString& key, sk_sp); void insertObjRef(const char key[], sk_sp); void insertObjRef(const SkString& key, sk_sp); + 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.