Add experimental FPDFTextObj_GetRenderedBitmap() API. This API lets embedders render text objects to a bitmap, similar to FPDFImageObj_GetRenderedBitmap(). FPDFTextObj_GetRenderedBitmap() has an additional scale parameter, so embedders can take advantage of PDFium's internal rendering code and get high quality output. Bug: pdfium:1834 Change-Id: I3689702ae7c009b00e92a2f22d7704a23c11e62a Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/93872 Reviewed-by: Tom Sepez <tsepez@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/page/cpdf_page.cpp b/core/fpdfapi/page/cpdf_page.cpp index abacbc4..4f11c91 100644 --- a/core/fpdfapi/page/cpdf_page.cpp +++ b/core/fpdfapi/page/cpdf_page.cpp
@@ -225,5 +225,6 @@ : m_pPage(pPage) {} CPDF_Page::RenderContextClearer::~RenderContextClearer() { - m_pPage->ClearRenderContext(); + if (m_pPage) + m_pPage->ClearRenderContext(); }
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp index f6cdc99..070dadd 100644 --- a/fpdfsdk/fpdf_edit_embeddertest.cpp +++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <limits> #include <memory> #include <string> #include <utility> @@ -4174,3 +4175,186 @@ UnloadPage(page); } + +TEST_F(FPDFEditEmbedderTest, GetRenderedBitmapForHelloWorldText) { + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + { + FPDF_PAGEOBJECT text_object = FPDFPage_GetObject(page, 0); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object)); + + ScopedFPDFBitmap bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 1)); + ASSERT_TRUE(bitmap); + const char kChecksum[] = "bb0abe1accca1cfeaaf78afa35762350"; + CompareBitmap(bitmap.get(), 64, 11, kChecksum); + + ScopedFPDFBitmap x2_bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 2.4f)); + ASSERT_TRUE(x2_bitmap); + const char kX2Checksum[] = "80db528ec7146d92247f2339a8f10ba5"; + CompareBitmap(x2_bitmap.get(), 153, 25, kX2Checksum); + + ScopedFPDFBitmap x10_bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 10)); + ASSERT_TRUE(x10_bitmap); +#if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_) + const char kX10Checksum[] = "bfabf04699139d05c3924526beeb4b95"; +#else + const char kX10Checksum[] = "149f63de758ab01d3b75605cdfd4c176"; +#endif + CompareBitmap(x10_bitmap.get(), 631, 103, kX10Checksum); + } + + { + FPDF_PAGEOBJECT text_object = FPDFPage_GetObject(page, 1); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object)); + + ScopedFPDFBitmap bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 1)); + ASSERT_TRUE(bitmap); + const char kChecksum[] = "3fc1101b2408c5484adc24ba0a11ff3d"; + CompareBitmap(bitmap.get(), 116, 16, kChecksum); + + ScopedFPDFBitmap x2_bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 2.4f)); + ASSERT_TRUE(x2_bitmap); + const char kX2Checksum[] = "429960ae7b822f0c630432535e637465"; + CompareBitmap(x2_bitmap.get(), 276, 36, kX2Checksum); + + ScopedFPDFBitmap x10_bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 10)); + ASSERT_TRUE(x10_bitmap); +#if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_) + const char kX10Checksum[] = "c7eef7859332c75ab793ebae1c6e7221"; +#else + const char kX10Checksum[] = "f5f93bf64de579b59e775d7076ca0a5a"; +#endif + CompareBitmap(x10_bitmap.get(), 1143, 150, kX10Checksum); + } + + UnloadPage(page); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedBitmapForRotatedText) { + ASSERT_TRUE(OpenDocument("rotated_text.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT text_object = FPDFPage_GetObject(page, 0); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object)); + + ScopedFPDFBitmap bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 1)); + ASSERT_TRUE(bitmap); + const char kChecksum[] = "08ada0802f780d3fefb161dc6fb45977"; + CompareBitmap(bitmap.get(), 29, 28, kChecksum); + + ScopedFPDFBitmap x2_bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 2.4f)); + ASSERT_TRUE(x2_bitmap); + const char kX2Checksum[] = "09d7ddb647b8653cb59aede349a0c3e1"; + CompareBitmap(x2_bitmap.get(), 67, 67, kX2Checksum); + + ScopedFPDFBitmap x10_bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 10)); + ASSERT_TRUE(x10_bitmap); +#if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_) + const char kX10Checksum[] = "4816dd6782e9a977c58fb1ca0ced74d3"; +#else + const char kX10Checksum[] = "bbd3842a4b50dbfcbce4eee2b067a297"; +#endif + CompareBitmap(x10_bitmap.get(), 275, 275, kX10Checksum); + + UnloadPage(page); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedBitmapForColorText) { + ASSERT_TRUE(OpenDocument("text_color.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT text_object = FPDFPage_GetObject(page, 0); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object)); + + ScopedFPDFBitmap bitmap( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 7.3f)); + ASSERT_TRUE(bitmap); +#if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_) + const char kChecksum[] = "bb3778ba739c921525de44e9ab412868"; +#else + const char kChecksum[] = "e8154fa8ededf4d9b8b35b5260897b6c"; +#endif + CompareBitmap(bitmap.get(), 120, 186, kChecksum); + + UnloadPage(page); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedBitmapForNewlyCreatedText) { + // Start with a blank document. + ASSERT_TRUE(CreateNewDocument()); + + // Create a new text object. + ScopedFPDFPageObject text_object( + FPDFPageObj_NewTextObj(document(), "Arial", 12.0f)); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object.get())); + ScopedFPDFWideString text = GetFPDFWideString(kBottomText); + EXPECT_TRUE(FPDFText_SetText(text_object.get(), text.get())); + + ScopedFPDFBitmap bitmap( + FPDFTextObj_GetRenderedBitmap(document(), nullptr, text_object.get(), 1)); + ASSERT_TRUE(bitmap); + const char kChecksum[] = "fa947759dab76d68a07ccf6f97b2d9c2"; + CompareBitmap(bitmap.get(), 151, 12, kChecksum); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedBitmapForTextWithBadParameters) { + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT text_object = FPDFPage_GetObject(page, 0); + ASSERT_TRUE(text_object); + + // Simple bad parameters testing. + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(nullptr, nullptr, nullptr, 0)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(document(), nullptr, nullptr, 0)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(nullptr, page, nullptr, 0)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(nullptr, nullptr, text_object, 0)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(nullptr, nullptr, nullptr, 1)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(document(), page, nullptr, 0)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(document(), nullptr, nullptr, 1)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(nullptr, page, text_object, 0)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(nullptr, page, nullptr, 1)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(nullptr, nullptr, text_object, 1)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(document(), page, nullptr, 1)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(nullptr, page, text_object, 1)); + + // Test bad scale values. + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 0)); + EXPECT_FALSE( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, -1)); + EXPECT_FALSE( + FPDFTextObj_GetRenderedBitmap(document(), page, text_object, 10000)); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap( + document(), page, text_object, std::numeric_limits<float>::max())); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap( + document(), page, text_object, std::numeric_limits<float>::infinity())); + + { + // `text_object` will render without `page`, but may not render correctly + // without the resources from `page`. Although it does in this simple case. + ScopedFPDFBitmap bitmap( + FPDFTextObj_GetRenderedBitmap(document(), nullptr, text_object, 1)); + EXPECT_TRUE(bitmap); + } + + // Mismatch between the document and the page fails too. + ScopedFPDFDocument empty_document(FPDF_CreateNewDocument()); + EXPECT_FALSE(FPDFTextObj_GetRenderedBitmap(empty_document.get(), page, + text_object, 1)); + + UnloadPage(page); +}
diff --git a/fpdfsdk/fpdf_edittext.cpp b/fpdfsdk/fpdf_edittext.cpp index 04c242c..f8adf3c 100644 --- a/fpdfsdk/fpdf_edittext.cpp +++ b/fpdfsdk/fpdf_edittext.cpp
@@ -22,11 +22,16 @@ #include "core/fpdfapi/parser/cpdf_stream.h" #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fpdfapi/render/charposlist.h" +#include "core/fpdfapi/render/cpdf_pagerendercontext.h" +#include "core/fpdfapi/render/cpdf_rendercontext.h" +#include "core/fpdfapi/render/cpdf_renderstatus.h" +#include "core/fpdfapi/render/cpdf_textrenderer.h" #include "core/fpdftext/cpdf_textpage.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/fx_string_wrappers.h" #include "core/fxcrt/span_util.h" #include "core/fxcrt/stl_util.h" +#include "core/fxge/cfx_defaultrenderdevice.h" #include "core/fxge/cfx_fontmgr.h" #include "core/fxge/fx_font.h" #include "core/fxge/text_char_pos.h" @@ -583,6 +588,71 @@ return Utf16EncodeMaybeCopyAndReturnLength(text, buffer, length); } +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV +FPDFTextObj_GetRenderedBitmap(FPDF_DOCUMENT document, + FPDF_PAGE page, + FPDF_PAGEOBJECT text_object, + float scale) { + CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document); + if (!doc) + return nullptr; + + CPDF_Page* optional_page = CPDFPageFromFPDFPage(page); + if (optional_page && optional_page->GetDocument() != doc) + return nullptr; + + CPDF_TextObject* text = CPDFTextObjectFromFPDFPageObject(text_object); + if (!text) + return nullptr; + + if (scale <= 0) + return nullptr; + + const CFX_Matrix scale_matrix(scale, 0, 0, scale, 0, 0); + const CFX_FloatRect& text_rect = text->GetRect(); + const CFX_FloatRect scaled_text_rect = scale_matrix.TransformRect(text_rect); + + // `rect` has to use integer values. Round up as needed. + const FX_RECT rect = scaled_text_rect.GetOuterRect(); + if (rect.IsEmpty()) + return nullptr; + + auto result_bitmap = pdfium::MakeRetain<CFX_DIBitmap>(); + if (!result_bitmap->Create(rect.Width(), rect.Height(), FXDIB_Format::kArgb)) + return nullptr; + + auto render_context = std::make_unique<CPDF_PageRenderContext>(); + CPDF_PageRenderContext* render_context_ptr = render_context.get(); + CPDF_Page::RenderContextClearer clearer(optional_page); + if (optional_page) + optional_page->SetRenderContext(std::move(render_context)); + + CPDF_Dictionary* page_resources = + optional_page ? optional_page->GetPageResources() : nullptr; + + auto device = std::make_unique<CFX_DefaultRenderDevice>(); + CFX_DefaultRenderDevice* device_ptr = device.get(); + render_context_ptr->m_pDevice = std::move(device); + render_context_ptr->m_pContext = std::make_unique<CPDF_RenderContext>( + doc, page_resources, /*pPageCache=*/nullptr); + + device_ptr->Attach(result_bitmap, /*bRgbByteOrder=*/false, + /*pBackdropBitmap=*/nullptr, /*bGroupKnockout=*/false); + + CFX_Matrix device_matrix(rect.Width(), 0, 0, rect.Height(), 0, 0); + CPDF_RenderStatus status(render_context_ptr->m_pContext.get(), device_ptr); + status.SetDeviceMatrix(device_matrix); + status.Initialize(nullptr, nullptr); + + // Need to flip the rendering and also move it to fit within `result_bitmap`. + CFX_Matrix render_matrix(1, 0, 0, -1, -text_rect.left, text_rect.top); + render_matrix *= scale_matrix; + status.RenderSingleObject(text, render_matrix); + + // Caller takes ownership. + return FPDFBitmapFromCFXDIBitmap(result_bitmap.Leak()); +} + FPDF_EXPORT void FPDF_CALLCONV FPDFFont_Close(FPDF_FONT font) { // Take back ownership from caller and release. RetainPtr<CPDF_Font>().Unleak(CPDFFontFromFPDFFont(font));
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c index 4e3627f..e524f30 100644 --- a/fpdfsdk/fpdf_view_c_api_test.c +++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -249,6 +249,7 @@ CHK(FPDFPath_SetDrawMode); CHK(FPDFTextObj_GetFont); CHK(FPDFTextObj_GetFontSize); + CHK(FPDFTextObj_GetRenderedBitmap); CHK(FPDFTextObj_GetText); CHK(FPDFTextObj_GetTextRenderMode); CHK(FPDFTextObj_SetTextRenderMode);
diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h index b68b7c9..8f4e7f0 100644 --- a/public/fpdf_edit.h +++ b/public/fpdf_edit.h
@@ -647,7 +647,7 @@ // Get a bitmap rasterization of |image_object| that takes the image mask and // image matrix into account. To render correctly, the caller must provide the // |document| associated with |image_object|. If there is a |page| associated -// with |image_object| the caller should provide that as well. +// with |image_object|, the caller should provide that as well. // 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. // @@ -655,7 +655,7 @@ // page - handle to an optional page associated with |image_object|. // image_object - handle to an image object. // -// Returns the bitmap. +// Returns the bitmap or NULL on failure. FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV FPDFImageObj_GetRenderedBitmap(FPDF_DOCUMENT document, FPDF_PAGE page, @@ -1219,6 +1219,25 @@ unsigned long length); // Experimental API. +// Get a bitmap rasterization of |text_object|. To render correctly, the caller +// must provide the |document| associated with |text_object|. If there is a +// |page| associated with |text_object|, the caller should provide that as well. +// 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 a document associated with |text_object|. +// page - handle to an optional page associated with |text_object|. +// text_object - handle to a text object. +// scale - the scaling factor, which must be greater than 0. +// +// Returns the bitmap or NULL on failure. +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV +FPDFTextObj_GetRenderedBitmap(FPDF_DOCUMENT document, + FPDF_PAGE page, + FPDF_PAGEOBJECT text_object, + float scale); + +// Experimental API. // Get the font of a text object. // // text - the handle to the text object.