diff --git a/core/fpdfapi/page/cpdf_docpagedata.cpp b/core/fpdfapi/page/cpdf_docpagedata.cpp index 22fd0d49f..6c8bd3c23 100644 --- a/core/fpdfapi/page/cpdf_docpagedata.cpp +++ b/core/fpdfapi/page/cpdf_docpagedata.cpp @@ -226,8 +226,10 @@ RetainPtr CPDF_DocPageData::GetFont( return pdfium::WrapRetain(it->second.Get()); } - // EmbedPDF(layer-seam): an unpromoted base object uses the base's entry. - if (fallback_ && CanUseFallbackForObject(font_dict.Get())) { + // EmbedPDF(layer-seam): an unpromoted base object uses the base's entry, + // except a Type3 font: its glyphs are content, see GetImage(). + if (fallback_ && CanUseFallbackForObject(font_dict.Get()) && + font_dict->GetByteStringFor("Subtype") != "Type3") { return fallback_->GetFont(font_dict); } @@ -432,13 +434,16 @@ RetainPtr CPDF_DocPageData::GetPattern( return pdfium::WrapRetain(it->second.Get()); } - // EmbedPDF(layer-seam): an unpromoted base object uses the base's entry. - if (fallback_ && CanUseFallbackForObject(pPatternObj.Get())) { + // EmbedPDF(layer-seam): an unpromoted base object uses the base's entry, + // except a tiling pattern: its cell is content, see GetImage(). + const int pattern_type = pPatternObj->GetDict()->GetIntegerFor("PatternType"); + if (fallback_ && CanUseFallbackForObject(pPatternObj.Get()) && + pattern_type != CPDF_Pattern::kTiling) { return fallback_->GetPattern(pPatternObj, matrix); } RetainPtr pattern; - switch (pPatternObj->GetDict()->GetIntegerFor("PatternType")) { + switch (pattern_type) { case CPDF_Pattern::kTiling: pattern = pdfium::MakeRetain(GetDocument(), pPatternObj, matrix); @@ -491,6 +496,9 @@ RetainPtr CPDF_DocPageData::GetImage(uint32_t dwStreamObjNum) { // CPDF_Image carries a document back-pointer and CPDF_PageImageCache rejects // cross-document images. Build a document-local CPDF_Image even when the // underlying stream falls through to a shared base document. + // Content makes images of the document that parses it, so what holds content + // of its own (a tiling pattern's cell, a Type3 font's glyphs) never uses the + // base's entry either: GetPattern(), GetFont(). auto pImage = pdfium::MakeRetain(GetDocument(), dwStreamObjNum); image_map_[dwStreamObjNum] = pImage; return pImage; diff --git a/core/fpdfapi/page/cpdf_imageloader.cpp b/core/fpdfapi/page/cpdf_imageloader.cpp index a8387d9f9..f49ed99f6 100644 --- a/core/fpdfapi/page/cpdf_imageloader.cpp +++ b/core/fpdfapi/page/cpdf_imageloader.cpp @@ -28,7 +28,12 @@ bool CPDF_ImageLoader::Start(const CPDF_ImageObject* pImage, CPDF_ColorSpace::Family eFamily, bool bLoadMask, const CFX_Size& max_size_required) { - cache_ = pPageImageCache; + // EmbedPDF(bug-fix): the page image cache refuses an image of another + // document, and Finish() then read the cache's current entry: null (a crash) + // or the previous image's (nothing drawn). Such an image loads uncached. + cache_ = pPageImageCache && pPageImageCache->Accepts(*pImage->GetImage()) + ? pPageImageCache + : nullptr; image_object_ = pImage; bool should_continue; if (cache_) { diff --git a/core/fpdfapi/page/cpdf_pageimagecache.cpp b/core/fpdfapi/page/cpdf_pageimagecache.cpp index 5a59e69c9..90c984398 100644 --- a/core/fpdfapi/page/cpdf_pageimagecache.cpp +++ b/core/fpdfapi/page/cpdf_pageimagecache.cpp @@ -166,6 +166,11 @@ void CPDF_PageImageCache::ClearImageCacheEntry(const CPDF_Stream* pStream) { image_cache_.erase(it); } +// EmbedPDF(bug-fix): see the header. +bool CPDF_PageImageCache::Accepts(const CPDF_Image& image) const { + return page_->GetDocument() == image.GetDocument(); +} + bool CPDF_PageImageCache::StartGetCachedBitmap( RetainPtr pImage, const CPDF_Dictionary* pFormResources, @@ -175,7 +180,8 @@ bool CPDF_PageImageCache::StartGetCachedBitmap( bool bLoadMask, const CFX_Size& max_size_required) { // A cross-document image may have come from the embedder. - if (page_->GetDocument() != pImage->GetDocument()) { + // EmbedPDF(bug-fix): the rule lives in Accepts(); the loader asks it first. + if (!Accepts(*pImage)) { return false; } diff --git a/core/fpdfapi/page/cpdf_pageimagecache.h b/core/fpdfapi/page/cpdf_pageimagecache.h index 886333cf3..44ded0bce 100644 --- a/core/fpdfapi/page/cpdf_pageimagecache.h +++ b/core/fpdfapi/page/cpdf_pageimagecache.h @@ -36,6 +36,10 @@ class CPDF_PageImageCache { uint32_t GetTimeCount() const { return time_count_; } CPDF_Page* GetPage() const { return page_; } + // EmbedPDF(bug-fix): whether StartGetCachedBitmap() loads |image|. An image + // of another document is not the cache's: see CPDF_ImageLoader::Start(). + bool Accepts(const CPDF_Image& image) const; + bool StartGetCachedBitmap(RetainPtr pImage, const CPDF_Dictionary* pFormResources, const CPDF_Dictionary* pPageResources, diff --git a/core/fpdfapi/render/cpdf_docrenderdata.cpp b/core/fpdfapi/render/cpdf_docrenderdata.cpp index 90cabd1e2..0de957d51 100644 --- a/core/fpdfapi/render/cpdf_docrenderdata.cpp +++ b/core/fpdfapi/render/cpdf_docrenderdata.cpp @@ -55,12 +55,6 @@ RetainPtr CPDF_DocRenderData::GetCachedType3( return pdfium::WrapRetain(it->second.Get()); } - // EmbedPDF(layer-seam): an unpromoted base font uses the base's cache. - if (fallback_ && font->GetFontDictObjNum() != 0 && - !GetDocument()->IsObjectPromoted(font->GetFontDictObjNum())) { - return fallback_->GetCachedType3(font); - } - auto cache = pdfium::MakeRetain(font); type3_face_map_[font].Reset(cache.Get()); return cache; diff --git a/fpdfsdk/epdf_layer_embeddertest.cpp b/fpdfsdk/epdf_layer_embeddertest.cpp index ca706e150..e11c87e92 100644 --- a/fpdfsdk/epdf_layer_embeddertest.cpp +++ b/fpdfsdk/epdf_layer_embeddertest.cpp @@ -2013,3 +2013,40 @@ TEST_F(EPDFLayerEmbedderTest, LayerDeltaReplaysRemoveAllAnnots) { EPDF_ReleaseBaseDocument(base); } + +// A layer draws the images inside content: a tiling pattern's one large cell, +// a pattern of small cells, a Type3 glyph. Each page renders as the file does +// on its own. The layer took that content from its base, parsed for the base, +// so its images were the base's, which the layer page's image cache refuses: +// the render crashed. +TEST_F(EPDFLayerEmbedderTest, LayerDrawsImagesInPatternsAndType3Glyphs) { + static constexpr char kFile[] = "layer_content_images.pdf"; + static constexpr int kPageCount = 3; + std::vector expected; + ASSERT_TRUE(OpenDocument(kFile)); + for (int page_index = 0; page_index < kPageCount; ++page_index) { + ScopedPage page = LoadScopedPage(page_index); + ASSERT_TRUE(page); + ScopedFPDFBitmap bitmap = RenderPage(page.get()); + expected.push_back(HashBitmap(bitmap.get())); + } + + FileAccessForTesting base_access(kFile); + EPDF_BASE_DOCUMENT base = EPDF_LoadBaseDocument(&base_access, nullptr); + ASSERT_TRUE(base); + { + EPDFLayerOpenStatus status = EPDFLayerOpenStatus_kOpenFailed; + ScopedFPDFDocument layer( + EPDFLayer_OpenLayer(base, nullptr, nullptr, &status)); + ASSERT_TRUE(layer); + for (int page_index = 0; page_index < kPageCount; ++page_index) { + ScopedFPDFPage page(FPDF_LoadPage(layer.get(), page_index)); + ASSERT_TRUE(page); + ScopedFPDFBitmap bitmap = RenderPage(page.get()); + EXPECT_EQ(expected[page_index], HashBitmap(bitmap.get())) + << "page " << page_index; + } + } + + EPDF_ReleaseBaseDocument(base); +} diff --git a/fpdfsdk/epdf_view_embeddertest.cpp b/fpdfsdk/epdf_view_embeddertest.cpp index 355c40fe2..d3e76b179 100644 --- a/fpdfsdk/epdf_view_embeddertest.cpp +++ b/fpdfsdk/epdf_view_embeddertest.cpp @@ -961,3 +961,29 @@ TEST_F(EPDFViewEmbedderTest, StandardFontsDrawWithBuiltInFaces) { EXPECT_EQ((std::vector{"Tinos", "Arimo"}), HelloWorldFontFamilies(page.get())); } + +// An image object of another document draws as one of the page's own (rule +// bug-fix). The page image cache refuses it, and the image loader then read +// the cache's current entry: with none yet, a crash. +TEST_F(EPDFViewEmbedderTest, ImageOfAnotherDocumentDraws) { + ScopedFPDFBitmap red(FPDFBitmap_Create(2, 2, 0)); + ASSERT_TRUE(red); + ASSERT_TRUE(FPDFBitmap_FillRect(red.get(), 0, 0, 2, 2, 0xFFFF0000)); + // Draws an image of |image_document| on a new page of |document|. + auto render = [&red](FPDF_DOCUMENT document, FPDF_DOCUMENT image_document) { + ScopedFPDFPage page(FPDFPage_New(document, 0, 200, 200)); + FPDF_PAGEOBJECT image = FPDFPageObj_NewImageObj(image_document); + EXPECT_TRUE(FPDFImageObj_SetBitmap(nullptr, 0, image, red.get())); + EXPECT_TRUE(FPDFImageObj_SetMatrix(image, 100, 0, 0, 100, 50, 50)); + FPDFPage_InsertObject(page.get(), image); + ScopedFPDFBitmap bitmap = RenderPage(page.get()); + return HashBitmap(bitmap.get()); + }; + + ScopedFPDFDocument document(FPDF_CreateNewDocument()); + ScopedFPDFDocument other(FPDF_CreateNewDocument()); + ASSERT_TRUE(document); + ASSERT_TRUE(other); + EXPECT_EQ(render(document.get(), document.get()), + render(document.get(), other.get())); +} diff --git a/testing/resources/layer_content_images.in b/testing/resources/layer_content_images.in new file mode 100644 index 000000000..b09f5899f --- /dev/null +++ b/testing/resources/layer_content_images.in @@ -0,0 +1,151 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 200 200] + /Count 3 + /Kids [3 0 R 4 0 R 5 0 R] +>> +endobj +% A tiling pattern whose one cell is larger than the area it fills, the way +% Chrome prints a CSS background. +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Resources << + /Pattern << /P1 10 0 R >> + >> + /Contents 6 0 R +>> +endobj +% A tiling pattern of small cells. +{{object 4 0}} << + /Type /Page + /Parent 2 0 R + /Resources << + /Pattern << /P2 11 0 R >> + >> + /Contents 7 0 R +>> +endobj +% A Type3 glyph that draws a rectangle and the image. +{{object 5 0}} << + /Type /Page + /Parent 2 0 R + /Resources << + /Font << /F1 12 0 R >> + >> + /Contents 8 0 R +>> +endobj +{{object 6 0}} << + {{streamlen}} +>> +stream +/Pattern cs /P1 scn +20 20 160 160 re f +endstream +endobj +{{object 7 0}} << + {{streamlen}} +>> +stream +/Pattern cs /P2 scn +0 0 200 200 re f +endstream +endobj +{{object 8 0}} << + {{streamlen}} +>> +stream +BT +/F1 100 Tf +50 50 Td +(a) Tj +ET +endstream +endobj +% Red and green on top, blue and yellow below. +{{object 9 0}} << + /Type /XObject + /Subtype /Image + /Width 2 + /Height 2 + /ColorSpace /DeviceRGB + /BitsPerComponent 8 + /Filter /ASCIIHexDecode + {{streamlen}} +>> +stream +FF0000 00FF00 0000FF FFFF00> +endstream +endobj +{{object 10 0}} << + /Type /Pattern + /PatternType 1 + /PaintType 1 + /TilingType 1 + /BBox [0 0 200 200] + /XStep 202 + /YStep 202 + /Resources << + /XObject << /Im1 9 0 R >> + >> + {{streamlen}} +>> +stream +q 200 0 0 200 0 0 cm /Im1 Do Q +endstream +endobj +{{object 11 0}} << + /Type /Pattern + /PatternType 1 + /PaintType 1 + /TilingType 1 + /BBox [0 0 20 20] + /XStep 20 + /YStep 20 + /Resources << + /XObject << /Im1 9 0 R >> + >> + {{streamlen}} +>> +stream +q 20 0 0 20 0 0 cm /Im1 Do Q +endstream +endobj +{{object 12 0}} << + /Type /Font + /Subtype /Type3 + /FontBBox [0 0 1000 1000] + /FontMatrix [0.001 0 0 0.001 0 0] + /FirstChar 97 + /LastChar 97 + /Widths [1000] + /Encoding << + /Type /Encoding + /Differences [97 /a] + >> + /CharProcs << /a 13 0 R >> + /Resources << + /XObject << /Im1 9 0 R >> + >> +>> +endobj +{{object 13 0}} << + {{streamlen}} +>> +stream +1000 0 d0 +0 0 500 1000 re f +q 500 0 0 1000 500 0 cm /Im1 Do Q +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/layer_content_images.pdf b/testing/resources/layer_content_images.pdf new file mode 100644 index 000000000..e0d274ecb Binary files /dev/null and b/testing/resources/layer_content_images.pdf differ