0

[PDF] Change Searchified text tracking method

Currently, PDFiumPage tracks Searchified text at the granuarity of
words, but the tracking gets confused after PDFiumPage::Unload() gets
called, because the handles to the tracked text objects are no longer
valid. Given the assumption that a PDFiumPage either only contains text
from the PDF itself or only contains text from Searchify, simplify
Searchified text tracking from word granuarity to page granuarity. This
avoids the tracking confusion without requiring writing tags into the
PDF.

Bug: 376304020
Change-Id: I890b2be37ef7974b10920bc6b47126711d533d57
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6223665
Reviewed-by: Ramin Halavati <rhalavati@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1417182}
This commit is contained in:
Lei Zhang
2025-02-06 23:37:45 -08:00
committed by Chromium LUCI CQ
parent bf7963212d
commit 35be0f8e3c
6 changed files with 48 additions and 85 deletions

@ -185,17 +185,12 @@ void PDFiumOnDemandSearchifier::CommitResultsToPage() {
bool added_text = false;
for (auto& result : current_page_ocr_results_) {
FPDF_PAGEOBJECT image = FPDFPage_GetObject(page, result.image_index);
std::vector<FPDF_PAGEOBJECT> added_text_objects =
added_text |=
AddTextOnImage(engine_->doc(), page, font_.get(), image,
std::move(result.annotation), result.image_size);
current_page_->OnSearchifyGotOcrResult(added_text_objects);
added_text |= !added_text_objects.empty();
}
if (added_text) {
engine_->OnHasSearchifyText();
}
current_page_ocr_results_.clear();
current_page_->OnSearchifyGotOcrResult(added_text);
current_page_->ReloadTextPage();
if (!FPDFPage_GenerateContent(page)) {
LOG(ERROR) << "Failed to generate content";

@ -36,7 +36,7 @@ constexpr base::TimeDelta kOcrDelay = base::Milliseconds(100);
class SearchifierTestClient : public TestClient {
public:
explicit SearchifierTestClient() = default;
SearchifierTestClient() = default;
SearchifierTestClient(const SearchifierTestClient&) = delete;
SearchifierTestClient& operator=(const SearchifierTestClient&) = delete;
~SearchifierTestClient() override = default;
@ -380,9 +380,7 @@ TEST_P(PDFiumOnDemandSearchifierTest, MultiplePagesWithUnload) {
// Fetch `page3_info` again.
page3_info = page3.GetTextRunInfo(0);
ASSERT_TRUE(page3_info.has_value());
// TODO(crbug.com/376304020): Figure out how to properly track Searchified
// text, so this returns true.
EXPECT_FALSE(page3_info.value().is_searchified);
EXPECT_TRUE(page3_info.value().is_searchified);
}
TEST_P(PDFiumOnDemandSearchifierTest, OcrCancellation) {

@ -49,7 +49,6 @@
#include "ui/gfx/range/range.h"
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
#include "base/containers/contains.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/skbitmap_operations.h"
#endif
@ -549,7 +548,9 @@ std::optional<AccessibilityTextRunInfo> PDFiumPage::GetTextRunInfo(
AccessibilityTextRunInfo info;
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
info.is_searchified = IsCharacterAddedBySearchify(start_char_index);
// This assumes all text on the page are either from the PDF itself, or from
// Searchify.
info.is_searchified = has_searchify_added_text_.value_or(false);
#endif
int actual_start_char_index = GetFirstNonUnicodeWhiteSpaceCharIndex(
@ -924,17 +925,16 @@ SkBitmap PDFiumPage::GetImageForOcr(int page_object_index) {
return SkBitmapOperations::Rotate(bitmap, rotation);
}
void PDFiumPage::OnSearchifyGotOcrResult(
base::span<FPDF_PAGEOBJECT> text_objects) {
got_searchify_results_ = true;
for (FPDF_PAGEOBJECT text_object : text_objects) {
bool inserted = searchify_added_text_.insert(text_object).second;
CHECK(inserted);
void PDFiumPage::OnSearchifyGotOcrResult(bool added_text) {
CHECK(!has_searchify_added_text_.has_value());
has_searchify_added_text_ = added_text;
if (added_text) {
engine_->OnHasSearchifyText();
}
}
bool PDFiumPage::IsPageSearchified() const {
return got_searchify_results_;
return has_searchify_added_text_.has_value();
}
#endif // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
@ -1883,13 +1883,6 @@ Thumbnail PDFiumPage::CreateThumbnail(float device_pixel_ratio) {
return Thumbnail(page_size, device_pixel_ratio);
}
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
bool PDFiumPage::IsCharacterAddedBySearchify(int char_index) {
FPDF_PAGEOBJECT object = FPDFText_GetTextObject(GetTextPage(), char_index);
return base::Contains(searchify_added_text_, object);
}
#endif // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
void PDFiumPage::MarkAvailable() {
available_ = true;

@ -32,10 +32,6 @@
#include "ui/gfx/geometry/size.h"
#endif
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
#include "base/containers/span.h"
#endif
namespace gfx {
class Point;
class RectF;
@ -128,11 +124,12 @@ class PDFiumPage {
SkBitmap GetImageForOcr(int page_object_index);
// Called to inform PDFiumPage that OCR operations performed on this page
// added `text_objects` into the page.
// May be called several times if the page has more than one image.
void OnSearchifyGotOcrResult(base::span<FPDF_PAGEOBJECT> text_objects);
// added text into the page or not.
// May only be called once per PDFiumPage instance.
void OnSearchifyGotOcrResult(bool added_text);
// Returns if searchify has run on the page.
// Returns if Searchify has run on the page, regardless of whether it added
// any text to the page or not.
bool IsPageSearchified() const;
#endif // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
@ -472,10 +469,6 @@ class PDFiumPage {
// thumbnail.
Thumbnail CreateThumbnail(float device_pixel_ratio);
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
bool IsCharacterAddedBySearchify(int char_index);
#endif // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
raw_ptr<PDFiumEngine> engine_;
ScopedFPDFPage page_;
ScopedFPDFTextPage text_page_;
@ -500,13 +493,9 @@ class PDFiumPage {
bool available_ = false;
#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
// Indicates whether this page received any Searchify results. Note that it is
// possible to receive Searchify results, but the results list is empty.
bool got_searchify_results_ = false;
// The set of text objects added by running Searchify on this page.
// Used to help identify if text objects are created by Searchify or not.
std::set<FPDF_PAGEOBJECT> searchify_added_text_;
// Indicates whether Searchify added text to this page or not. Note that if
// this page has never been Searchified, then this is null.
std::optional<bool> has_searchify_added_text_;
#endif
};

@ -121,12 +121,12 @@ FS_MATRIX CalculateWordMoveMatrix(const SearchifyBoundingBoxOrigin& word_origin,
return move_matrix;
}
// Returns the newly created text object, or nullptr on error.
FPDF_PAGEOBJECT AddWordOnImage(FPDF_DOCUMENT document,
FPDF_PAGE page,
FPDF_FONT font,
const screen_ai::mojom::WordBox& word,
base::span<const FS_MATRIX> transform_matrices) {
// Returns whether this function succeeded or not.
bool AddWordOnImage(FPDF_DOCUMENT document,
FPDF_PAGE page,
FPDF_FONT font,
const screen_ai::mojom::WordBox& word,
base::span<const FS_MATRIX> transform_matrices) {
ScopedFPDFPageObject text(
FPDFPageObj_CreateTextObj(document, font, word.bounding_box.height()));
CHECK(text);
@ -134,7 +134,7 @@ FPDF_PAGEOBJECT AddWordOnImage(FPDF_DOCUMENT document,
std::vector<uint32_t> charcodes = Utf8ToCharcodes(word.word);
if (charcodes.empty()) {
DLOG(ERROR) << "Got empty word";
return nullptr;
return false;
}
bool result =
FPDFText_SetCharcodes(text.get(), charcodes.data(), charcodes.size());
@ -157,9 +157,8 @@ FPDF_PAGEOBJECT AddWordOnImage(FPDF_DOCUMENT document,
FPDFPageObj_TransformF(text.get(), &matrix);
}
FPDF_PAGEOBJECT text_ptr = text.get();
FPDFPage_InsertObject(page, text.release());
return text_ptr;
return true;
}
double IsInRange(double v, double min_value, double max_value) {
@ -312,17 +311,16 @@ std::vector<uint8_t> PDFiumSearchify(
return output_file_write.TakeBuffer();
}
std::vector<FPDF_PAGEOBJECT> AddTextOnImage(
FPDF_DOCUMENT document,
FPDF_PAGE page,
FPDF_FONT font,
FPDF_PAGEOBJECT image,
screen_ai::mojom::VisualAnnotationPtr annotation,
const gfx::Size& image_pixel_size) {
bool AddTextOnImage(FPDF_DOCUMENT document,
FPDF_PAGE page,
FPDF_FONT font,
FPDF_PAGEOBJECT image,
screen_ai::mojom::VisualAnnotationPtr annotation,
const gfx::Size& image_pixel_size) {
const gfx::SizeF image_rendered_size = GetRenderedImageSize(image);
if (image_rendered_size.IsEmpty()) {
DLOG(ERROR) << "Failed to get image rendered dimensions";
return {};
return false;
}
// The transformation matrices is applied as follows:
@ -341,19 +339,10 @@ std::vector<FPDF_PAGEOBJECT> AddTextOnImage(
if (!CalculateImageWithoutScalingMatrix(image, image_rendered_size,
image_without_scaling_matrix)) {
DLOG(ERROR) << "Failed to get image matrix";
return {};
return false;
}
size_t estimated_word_count = 0;
for (const auto& line : annotation->lines) {
// Assume there are spaces between each two words.
if (line->words.size()) {
estimated_word_count += line->words.size() * 2 - 1;
}
}
std::vector<FPDF_PAGEOBJECT> added_text_objects;
added_text_objects.reserve(estimated_word_count);
bool added_text = false;
for (const auto& line : annotation->lines) {
SearchifyBoundingBoxOrigin baseline_origin =
ConvertToPdfOrigin(line->baseline_box, line->baseline_box_angle,
@ -375,11 +364,11 @@ std::vector<FPDF_PAGEOBJECT> AddTextOnImage(
word.bounding_box.width(),
word.direction ==
screen_ai::mojom::Direction::DIRECTION_RIGHT_TO_LEFT);
added_text_objects.push_back(
AddWordOnImage(document, page, font, word, transform_matrices));
added_text |=
AddWordOnImage(document, page, font, word, transform_matrices);
}
}
return added_text_objects;
return added_text;
}
SearchifyBoundingBoxOrigin ConvertToPdfOriginForTesting(

@ -44,14 +44,13 @@ ScopedFPDFFont CreateFont(FPDF_DOCUMENT document);
// Adds the recognized text in `annotation` to the given `page`, to be written
// over `image`.
//
// Returns all the newly added PDFium text objects.
std::vector<FPDF_PAGEOBJECT> AddTextOnImage(
FPDF_DOCUMENT document,
FPDF_PAGE page,
FPDF_FONT font,
FPDF_PAGEOBJECT image,
screen_ai::mojom::VisualAnnotationPtr annotation,
const gfx::Size& image_pixel_size);
// Returns if any new PDFium text objects has been added.
bool AddTextOnImage(FPDF_DOCUMENT document,
FPDF_PAGE page,
FPDF_FONT font,
FPDF_PAGEOBJECT image,
screen_ai::mojom::VisualAnnotationPtr annotation,
const gfx::Size& image_pixel_size);
// Internal functions exposed for testing.
SearchifyBoundingBoxOrigin ConvertToPdfOriginForTesting(