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.