Add FPDFPageObj_GetRenderedStrokePattern() API This allows getting the rendered bitmap of a stroke pattern of a page object. Change-Id: I4603be175bfe8721d4e11500afcc2be8c2c9f760 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154910 Reviewed-by: Lei Zhang <thestig@chromium.org> Reviewed-by: Tom Sepez <tsepez@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/render/cpdf_rendertiling.cpp b/core/fpdfapi/render/cpdf_rendertiling.cpp index f407f24..7702735 100644 --- a/core/fpdfapi/render/cpdf_rendertiling.cpp +++ b/core/fpdfapi/render/cpdf_rendertiling.cpp
@@ -26,7 +26,17 @@ namespace { -RetainPtr<CFX_DIBitmap> DrawPatternBitmap( +std::optional<int> CheckedFloatToInt(float value) { + if (!pdfium::IsValueInRangeForNumericType<int>(value)) { + return std::nullopt; + } + return static_cast<int>(value); +} + +} // namespace + +// static +RetainPtr<CFX_DIBitmap> CPDF_RenderTiling::DrawPatternBitmap( CPDF_Document* doc, CPDF_PageImageCache* pCache, CPDF_TilingPattern* pPattern, @@ -72,15 +82,6 @@ return pBitmap; } -std::optional<int> CheckedFloatToInt(float value) { - if (!pdfium::IsValueInRangeForNumericType<int>(value)) { - return std::nullopt; - } - return static_cast<int>(value); -} - -} // namespace - // static RetainPtr<CFX_DIBitmap> CPDF_RenderTiling::Draw( CPDF_RenderStatus* pRenderStatus,
diff --git a/core/fpdfapi/render/cpdf_rendertiling.h b/core/fpdfapi/render/cpdf_rendertiling.h index 34fb56b..2a12446 100644 --- a/core/fpdfapi/render/cpdf_rendertiling.h +++ b/core/fpdfapi/render/cpdf_rendertiling.h
@@ -7,11 +7,14 @@ #ifndef CORE_FPDFAPI_RENDER_CPDF_RENDERTILING_H_ #define CORE_FPDFAPI_RENDER_CPDF_RENDERTILING_H_ +#include "core/fpdfapi/render/cpdf_renderoptions.h" #include "core/fxcrt/retain_ptr.h" class CFX_DIBitmap; class CFX_Matrix; +class CPDF_Document; class CPDF_Form; +class CPDF_PageImageCache; class CPDF_PageObject; class CPDF_RenderStatus; class CPDF_TilingPattern; @@ -27,6 +30,17 @@ const FX_RECT& clip_box, bool bStroke); + // Renders a single cell of pPattern into a width x height bitmap. + static RetainPtr<CFX_DIBitmap> DrawPatternBitmap( + CPDF_Document* doc, + CPDF_PageImageCache* pCache, + CPDF_TilingPattern* pPattern, + CPDF_Form* pPatternForm, + const CFX_Matrix& mtObject2Device, + int width, + int height, + const CPDF_RenderOptions::Options& draw_options); + CPDF_RenderTiling() = delete; CPDF_RenderTiling(const CPDF_RenderTiling&) = delete; CPDF_RenderTiling& operator=(const CPDF_RenderTiling&) = delete;
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp index 82485d9..ac66807 100644 --- a/fpdfsdk/fpdf_edit_embeddertest.cpp +++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -5529,6 +5529,70 @@ text_object, 1)); } +TEST_F(FPDFEditEmbedderTest, GetRenderedStrokePattern) { + ASSERT_TRUE(OpenDocument("pattern_stroke.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path_object)); + + ScopedFPDFBitmap bitmap( + FPDFPageObj_GetRenderedStrokePattern(document(), path_object)); + ASSERT_TRUE(bitmap); + // This is the case when a colored pattern has a color of its own. + CompareBitmapWithExpectationSuffix(bitmap.get(), "pattern_stroke"); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedStrokePatternUncolored) { + ASSERT_TRUE(OpenDocument("pattern_stroke_uncolored.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path_object)); + + ScopedFPDFBitmap bitmap( + FPDFPageObj_GetRenderedStrokePattern(document(), path_object)); + ASSERT_TRUE(bitmap); + // This is the case when an uncolored pattern has no color of its own. + CompareBitmapWithExpectationSuffix(bitmap.get(), "pattern_stroke_uncolored"); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedStrokePatternBadParams) { + ASSERT_TRUE(OpenDocument("pattern_stroke.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_TRUE(path_object); + + // Simple bad parameters testing. + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(nullptr, nullptr)); + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(document(), nullptr)); + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(nullptr, path_object)); + + // A page object that is not a path has no stroke pattern, either. + FPDF_PAGEOBJECT text_object = + FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object)); + EXPECT_TRUE(FPDFPage_InsertObject(page.get(), text_object)); + ASSERT_EQ(2, FPDFPage_CountObjects(page.get())); + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(document(), text_object)); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedStrokePatternNoPattern) { + ASSERT_TRUE(OpenDocument("rectangles.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path_object)); + + // The stroke color is a plain color, not a pattern. + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(document(), path_object)); +} + TEST_F(FPDFEditEmbedderTest, GetRenderedBitmapForRotatedImage) { ScopedFPDFDocument doc(FPDF_CreateNewDocument()); ScopedFPDFPage page(FPDFPage_New(doc.get(), 0, 100, 100));
diff --git a/fpdfsdk/fpdf_editpage.cpp b/fpdfsdk/fpdf_editpage.cpp index 758aae8..9b9474e 100644 --- a/fpdfsdk/fpdf_editpage.cpp +++ b/fpdfsdk/fpdf_editpage.cpp
@@ -25,12 +25,15 @@ #include "core/fpdfapi/page/cpdf_pathobject.h" #include "core/fpdfapi/page/cpdf_shadingobject.h" #include "core/fpdfapi/page/cpdf_textobject.h" +#include "core/fpdfapi/page/cpdf_tilingpattern.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_document.h" #include "core/fpdfapi/parser/cpdf_number.h" #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fpdfapi/render/cpdf_docrenderdata.h" +#include "core/fpdfapi/render/cpdf_renderoptions.h" +#include "core/fpdfapi/render/cpdf_rendertiling.h" #include "core/fpdfdoc/cpdf_annot.h" #include "core/fpdfdoc/cpdf_annotlist.h" #include "core/fxcrt/compiler_specific.h" @@ -42,6 +45,7 @@ #include "core/fxcrt/span_util.h" #include "core/fxcrt/stl_util.h" #include "core/fxcrt/unowned_ptr.h" +#include "core/fxge/dib/cfx_dibitmap.h" #include "fpdfsdk/cpdfsdk_helpers.h" #include "public/fpdf_formfill.h" @@ -193,6 +197,64 @@ return date; } +FPDF_BITMAP RenderTilingPatternToBitmap(CPDF_Pattern* pattern, + CPDF_Document* doc, + CPDF_PageObject* page_obj) { + CPDF_TilingPattern* tiling_pattern = pattern->AsTilingPattern(); + if (!tiling_pattern) { + return nullptr; + } + const std::unique_ptr<CPDF_Form> pattern_form = + tiling_pattern->Load(page_obj); + if (!pattern_form) { + return nullptr; + } + const FX_RECT rect = tiling_pattern->bbox().GetOuterRect(); + if (rect.IsEmpty() || !rect.Valid()) { + return nullptr; + } + + RetainPtr<CFX_DIBitmap> cell_bitmap = CPDF_RenderTiling::DrawPatternBitmap( + doc, /*pCache=*/nullptr, tiling_pattern, pattern_form.get(), CFX_Matrix(), + rect.Width(), rect.Height(), CPDF_RenderOptions::Options()); + if (!cell_bitmap) { + return nullptr; + } + + if (tiling_pattern->colored()) { + ValidateBitmapPremultiplyState(cell_bitmap); + + // Caller takes ownership. + return FPDFBitmapFromCFXDIBitmap(cell_bitmap.Leak()); + } + + // An uncolored pattern gets its color from the stroke color operands. + const FX_COLORREF stroke_colorref = + page_obj->color_state().GetStrokeColorRef(); + if (stroke_colorref == 0xFFFFFFFF) { + return nullptr; + } + + auto result_bitmap = pdfium::MakeRetain<CFX_DIBitmap>(); + if (!result_bitmap->Create(rect.Width(), rect.Height(), + FXDIB_Format::kBgra)) { + return nullptr; + } + const int stroke_alpha = + static_cast<int>(page_obj->general_state().GetStrokeAlpha() * 255); + if (!result_bitmap->CompositeMask( + /*dest_left=*/0, /*dest_top=*/0, rect.Width(), rect.Height(), + cell_bitmap, AlphaAndColorRefToArgb(stroke_alpha, stroke_colorref), + /*src_left=*/0, /*src_top=*/0, BlendMode::kNormal)) { + return nullptr; + } + + ValidateBitmapPremultiplyState(result_bitmap); + + // Caller takes ownership. + return FPDFBitmapFromCFXDIBitmap(result_bitmap.Leak()); +} + } // namespace FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV FPDF_CreateNewDocument() { @@ -1196,6 +1258,32 @@ return true; } +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV +FPDFPageObj_GetRenderedStrokePattern(FPDF_DOCUMENT document, + FPDF_PAGEOBJECT page_object) { + CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document); + if (!doc) { + return nullptr; + } + + CPDF_PageObject* object = CPDFPageObjectFromFPDFPageObject(page_object); + if (!object) { + return nullptr; + } + + const CPDF_Color* stroke = object->color_state().GetStrokeColor(); + if (!stroke || !stroke->IsPattern()) { + return nullptr; + } + + RetainPtr<CPDF_Pattern> pattern = stroke->GetPattern(); + if (!pattern) { + return nullptr; + } + + return RenderTilingPatternToBitmap(pattern.Get(), doc, object); +} + FPDF_EXPORT int FPDF_CALLCONV FPDFFormObj_CountObjects(FPDF_PAGEOBJECT form_object) { const auto* pObjectList = CPDFPageObjHolderFromFPDFFormObject(form_object);
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c index b51a810..4269321 100644 --- a/fpdfsdk/fpdf_view_c_api_test.c +++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -231,6 +231,7 @@ CHK(FPDFPageObj_GetMark); CHK(FPDFPageObj_GetMarkedContentID); CHK(FPDFPageObj_GetMatrix); + CHK(FPDFPageObj_GetRenderedStrokePattern); CHK(FPDFPageObj_GetRotatedBounds); CHK(FPDFPageObj_GetStrokeColor); CHK(FPDFPageObj_GetStrokeWidth);
diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h index 2dbd60d..c7fca05 100644 --- a/public/fpdf_edit.h +++ b/public/fpdf_edit.h
@@ -1190,6 +1190,22 @@ size_t dash_count, float phase); +// Experimental API. +// Get a bitmap rasterization of the stroke pattern of |page_object|. +// To render correctly, the caller must provide the |document| associated with +// |page_object|. The returned bitmap will be owned by the caller, and +// FPDFBitmap_Destroy() must be called on the returned bitmap when it is no +// longer needed. +// +// document - handle to the document containing |page_object|. +// page_object - handle to a page object. +// +// Returns the bitmap, or NULL if the stroke is not a tiling pattern or on +// failure. +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV +FPDFPageObj_GetRenderedStrokePattern(FPDF_DOCUMENT document, + FPDF_PAGEOBJECT page_object); + // Get number of segments inside |path|. // // path - handle to a path.
diff --git a/testing/resources/embedder_tests/pattern_stroke.png b/testing/resources/embedder_tests/pattern_stroke.png new file mode 100644 index 0000000..3a84afe --- /dev/null +++ b/testing/resources/embedder_tests/pattern_stroke.png Binary files differ
diff --git a/testing/resources/embedder_tests/pattern_stroke_uncolored.png b/testing/resources/embedder_tests/pattern_stroke_uncolored.png new file mode 100644 index 0000000..fb17d2c --- /dev/null +++ b/testing/resources/embedder_tests/pattern_stroke_uncolored.png Binary files differ
diff --git a/testing/resources/pattern_stroke.in b/testing/resources/pattern_stroke.in new file mode 100644 index 0000000..3829c8b --- /dev/null +++ b/testing/resources/pattern_stroke.in
@@ -0,0 +1,59 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 200 300] + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /Pattern << + /P0 5 0 R + >> + >> +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +q +/Pattern CS +/P0 SCN +40 w +20 150 m +180 150 l +S +Q +endstream +endobj +{{object 5 0}} << + /Type /Pattern + /PatternType 1 + /PaintType 1 + /TilingType 1 + /BBox [0 0 72 72] + /XStep 72 + /YStep 72 + /Resources <<>> + {{streamlen}} +>> +stream +0 1 0.91 0 k +0 0 72 72 re +f +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF
diff --git a/testing/resources/pattern_stroke.pdf b/testing/resources/pattern_stroke.pdf new file mode 100644 index 0000000..de3e429 --- /dev/null +++ b/testing/resources/pattern_stroke.pdf
@@ -0,0 +1,71 @@ +%PDF-1.7 +% ò¤ô +1 0 obj << + /Type /Catalog + /Pages 2 0 R +>> +endobj +2 0 obj << + /Type /Pages + /MediaBox [0 0 200 300] + /Count 1 + /Kids [3 0 R] +>> +endobj +3 0 obj << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /Pattern << + /P0 5 0 R + >> + >> +>> +endobj +4 0 obj << + /Length 49 +>> +stream +q +/Pattern CS +/P0 SCN +40 w +20 150 m +180 150 l +S +Q +endstream +endobj +5 0 obj << + /Type /Pattern + /PatternType 1 + /PaintType 1 + /TilingType 1 + /BBox [0 0 72 72] + /XStep 72 + /YStep 72 + /Resources <<>> + /Length 27 +>> +stream +0 1 0.91 0 k +0 0 72 72 re +f +endstream +endobj +xref +0 6 +0000000000 65535 f +0000000015 00000 n +0000000068 00000 n +0000000157 00000 n +0000000286 00000 n +0000000387 00000 n +trailer << + /Root 1 0 R + /Size 6 +>> +startxref +593 +%%EOF
diff --git a/testing/resources/pattern_stroke_uncolored.in b/testing/resources/pattern_stroke_uncolored.in new file mode 100644 index 0000000..5cd5c16 --- /dev/null +++ b/testing/resources/pattern_stroke_uncolored.in
@@ -0,0 +1,61 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 200 300] + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /ColorSpace << + /CS0 [/Pattern /DeviceRGB] + >> + /Pattern << + /P0 5 0 R + >> + >> +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +q +/CS0 CS +0 0 1 /P0 SCN +40 w +20 150 m +180 150 l +S +Q +endstream +endobj +{{object 5 0}} << + /Type /Pattern + /PatternType 1 + /PaintType 2 + /TilingType 1 + /BBox [0 0 72 72] + /XStep 72 + /YStep 72 + /Resources <<>> + {{streamlen}} +>> +stream +0 0 72 72 re +f +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF
diff --git a/testing/resources/pattern_stroke_uncolored.pdf b/testing/resources/pattern_stroke_uncolored.pdf new file mode 100644 index 0000000..b167d72 --- /dev/null +++ b/testing/resources/pattern_stroke_uncolored.pdf
@@ -0,0 +1,73 @@ +%PDF-1.7 +% ò¤ô +1 0 obj << + /Type /Catalog + /Pages 2 0 R +>> +endobj +2 0 obj << + /Type /Pages + /MediaBox [0 0 200 300] + /Count 1 + /Kids [3 0 R] +>> +endobj +3 0 obj << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /ColorSpace << + /CS0 [/Pattern /DeviceRGB] + >> + /Pattern << + /P0 5 0 R + >> + >> +>> +endobj +4 0 obj << + /Length 51 +>> +stream +q +/CS0 CS +0 0 1 /P0 SCN +40 w +20 150 m +180 150 l +S +Q +endstream +endobj +5 0 obj << + /Type /Pattern + /PatternType 1 + /PaintType 2 + /TilingType 1 + /BBox [0 0 72 72] + /XStep 72 + /YStep 72 + /Resources <<>> + /Length 14 +>> +stream +0 0 72 72 re +f +endstream +endobj +xref +0 6 +0000000000 65535 f +0000000015 00000 n +0000000068 00000 n +0000000157 00000 n +0000000345 00000 n +0000000448 00000 n +trailer << + /Root 1 0 R + /Size 6 +>> +startxref +641 +%%EOF