diff --git a/core/fpdfapi/render/cpdf_renderstatus.cpp b/core/fpdfapi/render/cpdf_renderstatus.cpp index 5753aa5b33..a5ef266a84 100644 --- a/core/fpdfapi/render/cpdf_renderstatus.cpp +++ b/core/fpdfapi/render/cpdf_renderstatus.cpp @@ -455,8 +455,21 @@ bool CPDF_RenderStatus::ProcessPath(CPDF_PathObject* path_obj, const CFX_Matrix& mtObj2Device) { CFX_FillRenderOptions::FillType fill_type = path_obj->filltype(); bool stroke = path_obj->stroke(); + // EmbedPDF(bug-fix): upstream drew a pattern stroke before the path's solid + // fill, so with B and B* the fill covered the stroke's inner half. The + // pattern stroke is held back here and drawn after the fill. + const CPDF_Color* pattern_stroke = + stroke && path_obj->color_state().GetStrokeColor()->IsPattern() + ? path_obj->color_state().GetStrokeColor() + : nullptr; + if (pattern_stroke) { + stroke = false; + } ProcessPathPattern(path_obj, mtObj2Device, &fill_type, &stroke); if (fill_type == CFX_FillRenderOptions::FillType::kNoFill && !stroke) { + if (pattern_stroke) { + DrawPathWithPattern(path_obj, mtObj2Device, pattern_stroke, true); + } return true; } @@ -479,11 +492,18 @@ bool CPDF_RenderStatus::ProcessPath(CPDF_PathObject* path_obj, return true; } - return device_->DrawPath( + // EmbedPDF(bug-fix): the held-back pattern stroke goes on top of the fill. + // When DrawPath() fails, the caller redraws the whole object, stroke + // included, so the stroke is skipped here. + const bool result = device_->DrawPath( *path_obj->path().GetObject(), &path_matrix, path_obj->graph_state().GetObject(), fill_argb, stroke_argb, GetFillOptionsForDrawPathWithBlend(options, path_obj, fill_type, stroke, type3_char_)); + if (result && pattern_stroke) { + DrawPathWithPattern(path_obj, mtObj2Device, pattern_stroke, true); + } + return result; } RetainPtr CPDF_RenderStatus::GetTransferFunc( diff --git a/core/fpdfapi/render/fpdf_render_pattern_embeddertest.cpp b/core/fpdfapi/render/fpdf_render_pattern_embeddertest.cpp index 2b5198822f..01212225e2 100644 --- a/core/fpdfapi/render/fpdf_render_pattern_embeddertest.cpp +++ b/core/fpdfapi/render/fpdf_render_pattern_embeddertest.cpp @@ -2,13 +2,47 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include +#include + #include "public/cpp/fpdf_scopers.h" +#include "public/fpdfview.h" #include "testing/embedder_test.h" #include "testing/embedder_test_constants.h" #include "testing/gtest/include/gtest/gtest.h" class FPDFRenderPatternEmbedderTest : public EmbedderTest {}; +TEST_F(FPDFRenderPatternEmbedderTest, ShadingStrokePaintsAfterSolidFill) { + ASSERT_TRUE(OpenDocument("gradient_pattern_stroke_order.pdf")); + ASSERT_EQ(3, FPDF_GetPageCount(document())); + + for (int page_index = 0; page_index < 3; ++page_index) { + SCOPED_TRACE(page_index); + ScopedPage page = LoadScopedPage(page_index); + ASSERT_TRUE(page); + for (int scale : {1, 2, 4}) { + SCOPED_TRACE(scale); + const int size = 100 * scale; + const int stride = size * 4; + std::vector pixels(size * stride); + ScopedFPDFBitmap bitmap(FPDFBitmap_CreateEx(size, size, FPDFBitmap_BGRA, + pixels.data(), stride)); + ASSERT_TRUE(bitmap); + ASSERT_TRUE(FPDFBitmap_FillRect(bitmap.get(), 0, 0, size, size, + /*color=*/0xFFFFFFFF)); + FPDF_RenderPageBitmap(bitmap.get(), page.get(), 0, 0, size, size, + /*rotate=*/0, /*flags=*/0); + + // Both halves of the 10-point stroke stay visible for B, B* and S. + const int row = 50 * scale * stride; + EXPECT_LT(pixels[row + 17 * scale * 4 + 1], 20); + EXPECT_LT(pixels[row + 23 * scale * 4 + 1], 20); + EXPECT_EQ(pixels[row + 50 * scale * 4 + 1], 255); + } + } +} + TEST_F(FPDFRenderPatternEmbedderTest, LoadError547706) { // Test shading where object is a dictionary instead of a stream. ASSERT_TRUE(OpenDocument("bug_547706.pdf")); diff --git a/testing/resources/gradient_pattern_stroke_order.in b/testing/resources/gradient_pattern_stroke_order.in new file mode 100644 index 0000000000..942bbeedf7 --- /dev/null +++ b/testing/resources/gradient_pattern_stroke_order.in @@ -0,0 +1,90 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 100 100] + /Count 3 + /Kids [3 0 R 8 0 R 10 0 R] + /Resources << /Pattern << /P0 5 0 R >> >> +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +1 1 1 rg +/Pattern CS +/P0 SCN +10 w +20 20 60 60 re B +endstream +endobj +{{object 5 0}} << + /Type /Pattern + /PatternType 2 + /Shading 6 0 R +>> +endobj +{{object 6 0}} << + /ShadingType 2 + /ColorSpace /DeviceRGB + /Coords [0 0 100 0] + /Extend [true true] + /Function 7 0 R +>> +endobj +{{object 7 0}} << + /FunctionType 2 + /Domain [0 1] + /C0 [1 0 0] + /C1 [0 0 1] + /N 1 +>> +endobj +{{object 8 0}} << + /Type /Page + /Parent 2 0 R + /Contents 9 0 R +>> +endobj +{{object 9 0}} << + {{streamlen}} +>> +stream +1 1 1 rg +/Pattern CS +/P0 SCN +10 w +20 20 60 60 re B* +endstream +endobj +{{object 10 0}} << + /Type /Page + /Parent 2 0 R + /Contents 11 0 R +>> +endobj +{{object 11 0}} << + {{streamlen}} +>> +stream +/Pattern CS +/P0 SCN +10 w +20 20 60 60 re S +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/gradient_pattern_stroke_order.pdf b/testing/resources/gradient_pattern_stroke_order.pdf new file mode 100644 index 0000000000..a386c20f72 Binary files /dev/null and b/testing/resources/gradient_pattern_stroke_order.pdf differ