From 2dfa3eedce366ae4023074ebcdfcc0f75e09cdd1 Mon Sep 17 00:00:00 2001 From: Eric Maciel <11020939+emaciel10@users.noreply.github.com> Date: Wed, 14 Oct 2026 14:55:10 -0400 Subject: [PATCH 1/2] Fix pattern stroke paint order over solid fills --- core/fpdfapi/render/cpdf_renderstatus.cpp | 16 +++- .../fpdf_render_pattern_embeddertest.cpp | 34 +++++++ .../gradient_pattern_stroke_order.in | 90 +++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 testing/resources/gradient_pattern_stroke_order.in diff --git a/core/fpdfapi/render/cpdf_renderstatus.cpp b/core/fpdfapi/render/cpdf_renderstatus.cpp index 5753aa5b33..013f50f9fb 100644 --- a/core/fpdfapi/render/cpdf_renderstatus.cpp +++ b/core/fpdfapi/render/cpdf_renderstatus.cpp @@ -455,8 +455,18 @@ 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(); + 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 +489,15 @@ bool CPDF_RenderStatus::ProcessPath(CPDF_PathObject* path_obj, return true; } - return device_->DrawPath( + 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..f1b68556e0 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 must remain 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 From 2aa703d33a061b92d09a707267047802eb39e7a6 Mon Sep 17 00:00:00 2001 From: Bob Singor Date: Sat, 17 Oct 2026 21:15:43 +0300 Subject: [PATCH 2/2] Add the fixture PDF and fork tags for the pattern stroke fix Check in gradient_pattern_stroke_order.pdf, generated from its .in with testing/tools/fixup_pdf_template.py; the embedder test opens the .pdf. Tag the ProcessPath changes EmbedPDF(bug-fix) so check-fork-tags.py --strict passes, and clang-format the new test. --- core/fpdfapi/render/cpdf_renderstatus.cpp | 6 ++++++ .../render/fpdf_render_pattern_embeddertest.cpp | 10 +++++----- .../resources/gradient_pattern_stroke_order.pdf | Bin 0 -> 1319 bytes 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 testing/resources/gradient_pattern_stroke_order.pdf diff --git a/core/fpdfapi/render/cpdf_renderstatus.cpp b/core/fpdfapi/render/cpdf_renderstatus.cpp index 013f50f9fb..a5ef266a84 100644 --- a/core/fpdfapi/render/cpdf_renderstatus.cpp +++ b/core/fpdfapi/render/cpdf_renderstatus.cpp @@ -455,6 +455,9 @@ 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() @@ -489,6 +492,9 @@ bool CPDF_RenderStatus::ProcessPath(CPDF_PathObject* path_obj, return true; } + // 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, diff --git a/core/fpdfapi/render/fpdf_render_pattern_embeddertest.cpp b/core/fpdfapi/render/fpdf_render_pattern_embeddertest.cpp index f1b68556e0..01212225e2 100644 --- a/core/fpdfapi/render/fpdf_render_pattern_embeddertest.cpp +++ b/core/fpdfapi/render/fpdf_render_pattern_embeddertest.cpp @@ -26,15 +26,15 @@ TEST_F(FPDFRenderPatternEmbedderTest, ShadingStrokePaintsAfterSolidFill) { 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)); + 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)); + /*color=*/0xFFFFFFFF)); FPDF_RenderPageBitmap(bitmap.get(), page.get(), 0, 0, size, size, - /*rotate=*/0, /*flags=*/0); + /*rotate=*/0, /*flags=*/0); - // Both halves of the 10-point stroke must remain visible for B, B*, and S. + // 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); diff --git a/testing/resources/gradient_pattern_stroke_order.pdf b/testing/resources/gradient_pattern_stroke_order.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a386c20f72882f0eb2d850ca66ea0da99e5dbd48 GIT binary patch literal 1319 zcmbVM!H&}~5WV{==F$riteqxlDpfr+-EshyRl=>}kZrx$psu}gTqt}E2fh&T0n9jQ z9XTtsB26RvWjyod%}ge@>&%!R;veiek!@!FLQLt7T)vY-s&U(27q~jTpr{@@TIT(p?yCfV9Wf5p0CRYO|wD(Tl zOI6zkNH|%v*2wA+)uNxN*@abJ;+jL5-!RPJ8;dyLox0mdSdnD?;a)sSz(?s6?{nl13S zhpjb73G{I2=BReI(F$fkaN7IeXEPsttskpKQ4rCUp2L<}tqYcmKD>K(17c7F>IU!m zEKe=}S;=`okM`clxsoo-e^>I=xRkDEeViAP6(q#4?;POW^mOAP-Iw-TIVO2B@}6NG zBE~Nlq)9SO;2tiRz_z3dl*dx6MSw*Dha|)@oW1~?W@CSGf?X5XB^i!ofTgoBHj7cT wz%Gl&T;{_(tf`uVH2t@&(AwhglJqoH%@0YMxY@3Zbt>VyI1u7v>d9oB#j- literal 0 HcmV?d00001