blob: 2e3ed784625db045a6474dfcf83eff9ada77b704 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "pdf/accessibility.h"
#include <algorithm>
#include <utility>
#include "base/numerics/safe_math.h"
#include "pdf/pdf_engine.h"
namespace chrome_pdf {
namespace {
bool IsCharWithinTextRun(
const pp::PDF::PrivateAccessibilityTextRunInfo& text_run,
uint32_t text_run_start_char_index,
uint32_t char_index) {
return char_index >= text_run_start_char_index &&
char_index - text_run_start_char_index < text_run.len;
}
bool GetEnclosingTextRunRangeForCharRange(
const std::vector<pp::PDF::PrivateAccessibilityTextRunInfo>& text_runs,
int start_char_index,
int char_count,
uint32_t* start_text_run_index,
uint32_t* text_run_count) {
if (start_char_index < 0 || char_count <= 0)
return false;
base::CheckedNumeric<uint32_t> checked_end_char_index = char_count - 1;
checked_end_char_index += start_char_index;
if (!checked_end_char_index.IsValid())
return false;
uint32_t end_char_index = checked_end_char_index.ValueOrDie();
uint32_t current_char_index = 0;
base::Optional<uint32_t> start_text_run;
for (size_t i = 0; i < text_runs.size(); ++i) {
if (!start_text_run.has_value() &&
IsCharWithinTextRun(text_runs[i], current_char_index,
start_char_index)) {
start_text_run = i;
}
if (start_text_run.has_value() &&
IsCharWithinTextRun(text_runs[i], current_char_index, end_char_index)) {
*start_text_run_index = start_text_run.value();
*text_run_count = i - *start_text_run_index + 1;
return true;
}
current_char_index += text_runs[i].len;
}
return false;
}
template <typename T>
bool CompareTextRuns(const T& a, const T& b) {
return a.text_run_index < b.text_run_index;
}
void GetAccessibilityLinkInfo(
PDFEngine* engine,
int32_t page_index,
const std::vector<pp::PDF::PrivateAccessibilityTextRunInfo>& text_runs,
std::vector<pp::PDF::PrivateAccessibilityLinkInfo>* links) {
std::vector<PDFEngine::AccessibilityLinkInfo> engine_link_info =
engine->GetLinkInfo(page_index);
for (size_t i = 0; i < engine_link_info.size(); ++i) {
auto& cur_engine_info = engine_link_info[i];
pp::PDF::PrivateAccessibilityLinkInfo link_info;
link_info.url = std::move(cur_engine_info.url);
link_info.index_in_page = i;
link_info.bounds = std::move(cur_engine_info.bounds);
if (!GetEnclosingTextRunRangeForCharRange(
text_runs, cur_engine_info.start_char_index,
cur_engine_info.char_count, &link_info.text_run_index,
&link_info.text_run_count)) {
// If a valid text run range is not found for the link, set the fallback
// values of |text_run_index| and |text_run_count| for |link_info|.
link_info.text_run_index = text_runs.size();
link_info.text_run_count = 0;
}
links->push_back(std::move(link_info));
}
std::sort(links->begin(), links->end(),
[](const pp::PDF::PrivateAccessibilityLinkInfo& a,
const pp::PDF::PrivateAccessibilityLinkInfo& b) {
return a.text_run_index < b.text_run_index;
});
}
void GetAccessibilityImageInfo(
PDFEngine* engine,
int32_t page_index,
uint32_t text_run_count,
std::vector<pp::PDF::PrivateAccessibilityImageInfo>* images) {
std::vector<PDFEngine::AccessibilityImageInfo> engine_image_info =
engine->GetImageInfo(page_index);
for (auto& cur_engine_info : engine_image_info) {
pp::PDF::PrivateAccessibilityImageInfo image_info;
image_info.alt_text = std::move(cur_engine_info.alt_text);
image_info.bounds = std::move(cur_engine_info.bounds);
// TODO(mohitb): Update text run index to nearest text run to image bounds.
image_info.text_run_index = text_run_count;
images->push_back(std::move(image_info));
}
}
void GetAccessibilityHighlightInfo(
PDFEngine* engine,
int32_t page_index,
const std::vector<pp::PDF::PrivateAccessibilityTextRunInfo>& text_runs,
std::vector<pp::PDF::PrivateAccessibilityHighlightInfo>* highlights) {
std::vector<PDFEngine::AccessibilityHighlightInfo> engine_highlight_info =
engine->GetHighlightInfo(page_index);
for (size_t i = 0; i < engine_highlight_info.size(); ++i) {
auto& cur_highlight_info = engine_highlight_info[i];
pp::PDF::PrivateAccessibilityHighlightInfo highlight_info;
highlight_info.index_in_page = i;
highlight_info.bounds = std::move(cur_highlight_info.bounds);
highlight_info.color = cur_highlight_info.color;
if (!GetEnclosingTextRunRangeForCharRange(
text_runs, cur_highlight_info.start_char_index,
cur_highlight_info.char_count, &highlight_info.text_run_index,
&highlight_info.text_run_count)) {
// If a valid text run range is not found for the highlight, set the
// fallback values of |text_run_index| and |text_run_count| for
// |highlight_info|.
highlight_info.text_run_index = text_runs.size();
highlight_info.text_run_count = 0;
}
highlights->push_back(std::move(highlight_info));
}
std::sort(highlights->begin(), highlights->end(),
CompareTextRuns<pp::PDF::PrivateAccessibilityHighlightInfo>);
}
} // namespace
bool GetAccessibilityInfo(
PDFEngine* engine,
int32_t page_index,
PP_PrivateAccessibilityPageInfo* page_info,
std::vector<pp::PDF::PrivateAccessibilityTextRunInfo>* text_runs,
std::vector<PP_PrivateAccessibilityCharInfo>* chars,
pp::PDF::PrivateAccessibilityPageObjects* page_objects) {
int page_count = engine->GetNumberOfPages();
if (page_index < 0 || page_index >= page_count)
return false;
int char_count = engine->GetCharCount(page_index);
// Treat a char count of -1 (error) as 0 (an empty page), since
// other pages might have valid content.
if (char_count < 0)
char_count = 0;
page_info->page_index = page_index;
page_info->bounds = engine->GetPageBoundsRect(page_index);
page_info->char_count = char_count;
chars->resize(page_info->char_count);
for (uint32_t i = 0; i < page_info->char_count; ++i) {
(*chars)[i].unicode_character = engine->GetCharUnicode(page_index, i);
}
int char_index = 0;
while (char_index < char_count) {
base::Optional<pp::PDF::PrivateAccessibilityTextRunInfo>
text_run_info_result = engine->GetTextRunInfo(page_index, char_index);
DCHECK(text_run_info_result.has_value());
const auto& text_run_info = text_run_info_result.value();
uint32_t text_run_end = char_index + text_run_info.len;
DCHECK_LE(text_run_end, static_cast<uint32_t>(char_count));
text_runs->push_back(text_run_info);
// We need to provide enough information to draw a bounding box
// around any arbitrary text range, but the bounding boxes of characters
// we get from PDFium don't necessarily "line up".
// Example for LTR text direction: walk through the
// characters in each text run and let the width of each character be
// the difference between the x coordinate of one character and the
// x coordinate of the next. The rest of the bounds of each character
// can be computed from the bounds of the text run.
// The same idea is used for RTL, TTB and BTT text direction.
pp::FloatRect char_bounds = engine->GetCharBounds(page_index, char_index);
for (uint32_t i = char_index; i < text_run_end - 1; i++) {
DCHECK_LT(i + 1, static_cast<uint32_t>(char_count));
pp::FloatRect next_char_bounds = engine->GetCharBounds(page_index, i + 1);
double& char_width = (*chars)[i].char_width;
switch (text_run_info.direction) {
case PP_PRIVATEDIRECTION_NONE:
case PP_PRIVATEDIRECTION_LTR:
char_width = next_char_bounds.x() - char_bounds.x();
break;
case PP_PRIVATEDIRECTION_TTB:
char_width = next_char_bounds.y() - char_bounds.y();
break;
case PP_PRIVATEDIRECTION_RTL:
char_width = char_bounds.right() - next_char_bounds.right();
break;
case PP_PRIVATEDIRECTION_BTT:
char_width = char_bounds.bottom() - next_char_bounds.bottom();
break;
}
char_bounds = next_char_bounds;
}
double& char_width = (*chars)[text_run_end - 1].char_width;
if (text_run_info.direction == PP_PRIVATEDIRECTION_BTT ||
text_run_info.direction == PP_PRIVATEDIRECTION_TTB) {
char_width = char_bounds.height();
} else {
char_width = char_bounds.width();
}
char_index += text_run_info.len;
}
page_info->text_run_count = text_runs->size();
GetAccessibilityLinkInfo(engine, page_index, *text_runs,
&page_objects->links);
GetAccessibilityImageInfo(engine, page_index, page_info->text_run_count,
&page_objects->images);
GetAccessibilityHighlightInfo(engine, page_index, *text_runs,
&page_objects->highlights);
// TODO(crbug.com/1030242): Populate text fields.
return true;
}
} // namespace chrome_pdf