blob: 50e302e358a7c4b256c07eb6f63b149058eef408 [file]
// Copyright 2026 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/fxcrt/cfx_bidi_resolver.h"
#include <limits>
#include <utility>
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/ptr_util.h"
#include "third_party/icu/source/common/unicode/ubidi.h"
void CFX_BidiResolver::UBiDiDeleter::operator()(UBiDi* bidi) const {
ubidi_close(bidi);
}
// static
std::unique_ptr<CFX_BidiResolver> CFX_BidiResolver::Create(
std::u16string utf16_text,
ParagraphDirection direction) {
// Private ctor
auto resolver = pdfium::WrapUnique(
new CFX_BidiResolver(std::move(utf16_text), direction));
if (!resolver->paragraph_bidi_) {
return nullptr;
}
return resolver;
}
CFX_BidiResolver::CFX_BidiResolver(std::u16string utf16_text,
ParagraphDirection direction)
: utf16_text_(std::move(utf16_text)) {
if (utf16_text_.empty()) {
return;
}
// A workaround for integer overflow in ICU. See crbug.com/504629701.
// Can remove this after fixing the ICU issue, and rolling out the ICU
// update.
constexpr size_t kIcuRunSize = sizeof(int32_t) * 3;
CHECK_LE(utf16_text_.size(),
std::numeric_limits<int32_t>::max() / kIcuRunSize);
UErrorCode status = U_ZERO_ERROR;
ScopedUBiDi bidi(
ubidi_openSized(static_cast<int32_t>(utf16_text_.size()), 0, &status));
if (U_FAILURE(status)) {
return;
}
UBiDiLevel para_level = UBIDI_DEFAULT_LTR;
if (direction == ParagraphDirection::kLeftToRight) {
para_level = UBIDI_LTR;
} else if (direction == ParagraphDirection::kRightToLeft) {
para_level = UBIDI_RTL;
}
ubidi_setPara(bidi.get(), utf16_text_.data(),
static_cast<int32_t>(utf16_text_.size()), para_level, nullptr,
&status);
if (U_FAILURE(status)) {
return;
}
paragraph_bidi_ = std::move(bidi);
}
CFX_BidiResolver::~CFX_BidiResolver() = default;
std::vector<CFX_BidiResolver::ResolvedRun>
CFX_BidiResolver::GetVisualRunsForLine(int line_start, int line_length) const {
std::vector<ResolvedRun> runs;
if (line_length <= 0) {
return runs;
}
UErrorCode status = U_ZERO_ERROR;
ScopedUBiDi line_bidi(ubidi_openSized(line_length, 0, &status));
if (U_FAILURE(status)) {
return runs;
}
FX_SAFE_INT32 safe_line_end = pdfium::CheckAdd(line_start, line_length);
if (!safe_line_end.IsValid()) {
return runs;
}
ubidi_setLine(paragraph_bidi_.get(), line_start, safe_line_end.ValueOrDie(),
line_bidi.get(), &status);
if (U_FAILURE(status)) {
return runs;
}
int32_t run_count = ubidi_countRuns(line_bidi.get(), &status);
if (U_FAILURE(status) || run_count <= 0) {
return runs;
}
runs.reserve(run_count);
for (int32_t i = 0; i < run_count; ++i) {
int32_t run_start = 0;
int32_t run_length = 0;
UBiDiDirection dir =
ubidi_getVisualRun(line_bidi.get(), i, &run_start, &run_length);
// `ubidi_getVisualRun()` guarantees `run_start` is strictly bounded by
// `line_length`. Proved `line_start + line_length` doesn't overflow
// above with `safe_line_end`. Therefore, `run_start + line_start`
// cannot overflow.
runs.push_back({run_start + line_start, run_length, dir == UBIDI_RTL});
}
return runs;
}