pdfium/core/fpdfdoc/cpvt_section.cpp
Seung Hyun Jin d0d349d9a7 Rewrite CPVT_Section layout using ICU UBA
Replace CFX_BidiString usage in CPVT_Section::OutputLines() with
CFX_BidiResolver to implement the Unicode Bidirectional Algorithm. Pass
the state from CPVT_VariableText::GetDirection() down to the resolver to
establish the base paragraph direction context. Resolve visual runs
globally across the entire paragraph to correctly reverse mixed
direction text and handle trailing spaces. Update unit tests to assert
new layout coordinates.

Bug: 534486929
Change-Id: I67ce57e19f25a7e99ad09e19d0cef0aca076fd67
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/152251
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Seung Hyun Jin <seunghyunjin@google.com>
2026-07-23 16:55:30 -07:00

828 lines
27 KiB
C++

// Copyright 2016 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "core/fpdfdoc/cpvt_section.h"
#include <algorithm>
#include <array>
#include <memory>
#include <utility>
#include "core/fpdfdoc/cpvt_variabletext.h"
#include "core/fpdfdoc/cpvt_wordinfo.h"
#include "core/fxcrt/cfx_bidi_resolver.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/containers/adapters.h"
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/stl_util.h"
namespace {
constexpr std::array<const uint8_t, 128> kSpecialChars = {{
0x00, 0x0C, 0x08, 0x0C, 0x08, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00,
0x10, 0x00, 0x00, 0x28, 0x0C, 0x08, 0x00, 0x00, 0x28, 0x28, 0x28, 0x28,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x08, 0x08,
0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x0C, 0x00, 0x08, 0x00, 0x00,
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x0C, 0x00, 0x08, 0x00, 0x00,
}};
bool IsLatin(uint16_t word) {
if (word <= 0x007F) {
return !!(kSpecialChars[word] & 0x01);
}
return ((word >= 0x00C0 && word <= 0x00FF) ||
(word >= 0x0100 && word <= 0x024F) ||
(word >= 0x1E00 && word <= 0x1EFF) ||
(word >= 0x2C60 && word <= 0x2C7F) ||
(word >= 0xA720 && word <= 0xA7FF) ||
(word >= 0xFF21 && word <= 0xFF3A) ||
(word >= 0xFF41 && word <= 0xFF5A));
}
bool IsDigit(uint32_t word) {
return word >= 0x0030 && word <= 0x0039;
}
bool IsCJK(uint32_t word) {
if ((word >= 0x1100 && word <= 0x11FF) ||
(word >= 0x2E80 && word <= 0x2FFF) ||
(word >= 0x3040 && word <= 0x9FBF) ||
(word >= 0xAC00 && word <= 0xD7AF) ||
(word >= 0xF900 && word <= 0xFAFF) ||
(word >= 0xFE30 && word <= 0xFE4F) ||
(word >= 0x20000 && word <= 0x2A6DF) ||
(word >= 0x2F800 && word <= 0x2FA1F)) {
return true;
}
if (word >= 0x3000 && word <= 0x303F) {
return (
word == 0x3005 || word == 0x3006 || word == 0x3021 || word == 0x3022 ||
word == 0x3023 || word == 0x3024 || word == 0x3025 || word == 0x3026 ||
word == 0x3027 || word == 0x3028 || word == 0x3029 || word == 0x3031 ||
word == 0x3032 || word == 0x3033 || word == 0x3034 || word == 0x3035);
}
return word >= 0xFF66 && word <= 0xFF9D;
}
bool IsPunctuation(uint32_t word) {
if (word <= 0x007F) {
return !!(kSpecialChars[word] & 0x08);
}
if (word >= 0x0080 && word <= 0x00FF) {
return (word == 0x0082 || word == 0x0084 || word == 0x0085 ||
word == 0x0091 || word == 0x0092 || word == 0x0093 ||
word <= 0x0094 || word == 0x0096 || word == 0x00B4 ||
word == 0x00B8);
}
if (word >= 0x2000 && word <= 0x206F) {
return (
word == 0x2010 || word == 0x2011 || word == 0x2012 || word == 0x2013 ||
word == 0x2018 || word == 0x2019 || word == 0x201A || word == 0x201B ||
word == 0x201C || word == 0x201D || word == 0x201E || word == 0x201F ||
word == 0x2032 || word == 0x2033 || word == 0x2034 || word == 0x2035 ||
word == 0x2036 || word == 0x2037 || word == 0x203C || word == 0x203D ||
word == 0x203E || word == 0x2044);
}
if (word >= 0x3000 && word <= 0x303F) {
return (
word == 0x3001 || word == 0x3002 || word == 0x3003 || word == 0x3005 ||
word == 0x3009 || word == 0x300A || word == 0x300B || word == 0x300C ||
word == 0x300D || word == 0x300F || word == 0x300E || word == 0x3010 ||
word == 0x3011 || word == 0x3014 || word == 0x3015 || word == 0x3016 ||
word == 0x3017 || word == 0x3018 || word == 0x3019 || word == 0x301A ||
word == 0x301B || word == 0x301D || word == 0x301E || word == 0x301F);
}
if (word >= 0xFE50 && word <= 0xFE6F) {
return (word >= 0xFE50 && word <= 0xFE5E) || word == 0xFE63;
}
if (word >= 0xFF00 && word <= 0xFFEF) {
return (
word == 0xFF01 || word == 0xFF02 || word == 0xFF07 || word == 0xFF08 ||
word == 0xFF09 || word == 0xFF0C || word == 0xFF0E || word == 0xFF0F ||
word == 0xFF1A || word == 0xFF1B || word == 0xFF1F || word == 0xFF3B ||
word == 0xFF3D || word == 0xFF40 || word == 0xFF5B || word == 0xFF5C ||
word == 0xFF5D || word == 0xFF61 || word == 0xFF62 || word == 0xFF63 ||
word == 0xFF64 || word == 0xFF65 || word == 0xFF9E || word == 0xFF9F);
}
return false;
}
bool IsConnectiveSymbol(uint32_t word) {
return word <= 0x007F && (kSpecialChars[word] & 0x20);
}
bool IsOpenStylePunctuation(uint32_t word) {
if (word <= 0x007F) {
return !!(kSpecialChars[word] & 0x04);
}
return (word == 0x300A || word == 0x300C || word == 0x300E ||
word == 0x3010 || word == 0x3014 || word == 0x3016 ||
word == 0x3018 || word == 0x301A || word == 0xFF08 ||
word == 0xFF3B || word == 0xFF5B || word == 0xFF62);
}
bool IsCurrencySymbol(uint16_t word) {
return (word == 0x0024 || word == 0x0080 || word == 0x00A2 ||
word == 0x00A3 || word == 0x00A4 || word == 0x00A5 ||
(word >= 0x20A0 && word <= 0x20CF) || word == 0xFE69 ||
word == 0xFF04 || word == 0xFFE0 || word == 0xFFE1 ||
word == 0xFFE5 || word == 0xFFE6);
}
bool IsPrefixSymbol(uint16_t word) {
return IsCurrencySymbol(word) || word == 0x2116;
}
bool IsSpace(uint16_t word) {
return word == 0x0020 || word == 0x3000;
}
bool NeedDivision(uint16_t prevWord, uint16_t curWord) {
if ((IsLatin(prevWord) || IsDigit(prevWord)) &&
(IsLatin(curWord) || IsDigit(curWord))) {
return false;
}
if (IsSpace(curWord) || IsPunctuation(curWord)) {
return false;
}
if (IsConnectiveSymbol(prevWord) || IsConnectiveSymbol(curWord)) {
return false;
}
if (IsSpace(prevWord) || IsPunctuation(prevWord)) {
return true;
}
if (IsPrefixSymbol(prevWord)) {
return false;
}
if (IsPrefixSymbol(curWord) || IsCJK(curWord)) {
return true;
}
if (IsCJK(prevWord)) {
return true;
}
return false;
}
} // namespace
CPVT_Section::Line::Line(const CPVT_LineInfo& lineinfo)
: line_info_(lineinfo) {}
CPVT_Section::Line::~Line() = default;
CPVT_WordPlace CPVT_Section::Line::GetBeginWordPlace() const {
return CPVT_WordPlace(line_place_.nSecIndex, line_place_.nLineIndex, -1);
}
CPVT_WordPlace CPVT_Section::Line::GetEndWordPlace() const {
return CPVT_WordPlace(line_place_.nSecIndex, line_place_.nLineIndex,
line_info_.nEndWordIndex);
}
CPVT_WordPlace CPVT_Section::Line::GetPrevWordPlace(
const CPVT_WordPlace& place) const {
if (place.nWordIndex > line_info_.nEndWordIndex) {
return CPVT_WordPlace(place.nSecIndex, place.nLineIndex,
line_info_.nEndWordIndex);
}
return CPVT_WordPlace(place.nSecIndex, place.nLineIndex,
place.nWordIndex - 1);
}
CPVT_WordPlace CPVT_Section::Line::GetNextWordPlace(
const CPVT_WordPlace& place) const {
if (place.nWordIndex < line_info_.nBeginWordIndex) {
return CPVT_WordPlace(place.nSecIndex, place.nLineIndex,
line_info_.nBeginWordIndex);
}
return CPVT_WordPlace(place.nSecIndex, place.nLineIndex,
place.nWordIndex + 1);
}
CPVT_Section::CPVT_Section(CPVT_VariableText* pVT) : vt_(pVT) {
DCHECK(vt_);
}
CPVT_Section::~CPVT_Section() = default;
void CPVT_Section::ResetLinePlace() {
int32_t i = 0;
for (auto& pLine : line_array_) {
pLine->line_place_ = CPVT_WordPlace(sec_place_.nSecIndex, i, -1);
++i;
}
}
CPVT_WordPlace CPVT_Section::AddWord(const CPVT_WordPlace& place,
const CPVT_WordInfo& wordinfo) {
int32_t nWordIndex = std::clamp(place.nWordIndex, 0,
fxcrt::CollectionSize<int32_t>(word_array_));
word_array_.insert(word_array_.begin() + nWordIndex,
std::make_unique<CPVT_WordInfo>(wordinfo));
return place;
}
CPVT_WordPlace CPVT_Section::AddLine(const CPVT_LineInfo& lineinfo) {
line_array_.push_back(std::make_unique<Line>(lineinfo));
return CPVT_WordPlace(sec_place_.nSecIndex,
fxcrt::CollectionSize<int32_t>(line_array_) - 1, -1);
}
CPVT_FloatRect CPVT_Section::Rearrange() {
if (vt_->GetCharArray() > 0) {
return RearrangeCharArray();
}
return RearrangeTypeset();
}
CFX_SizeF CPVT_Section::GetSectionSize(float fFontSize) {
CPVT_FloatRect result = SplitLines(/*bTypeset=*/false, fFontSize);
return CFX_SizeF(result.Width(), result.Height());
}
CPVT_WordPlace CPVT_Section::GetBeginWordPlace() const {
if (line_array_.empty()) {
return sec_place_;
}
return line_array_.front()->GetBeginWordPlace();
}
CPVT_WordPlace CPVT_Section::GetEndWordPlace() const {
if (line_array_.empty()) {
return sec_place_;
}
return line_array_.back()->GetEndWordPlace();
}
CPVT_WordPlace CPVT_Section::GetPrevWordPlace(
const CPVT_WordPlace& place) const {
if (place.nLineIndex < 0) {
return GetBeginWordPlace();
}
if (place.nLineIndex >= fxcrt::CollectionSize<int32_t>(line_array_)) {
return GetEndWordPlace();
}
Line* pLine = line_array_[place.nLineIndex].get();
if (place.nWordIndex == pLine->line_info_.nBeginWordIndex) {
return CPVT_WordPlace(place.nSecIndex, place.nLineIndex, -1);
}
if (place.nWordIndex >= pLine->line_info_.nBeginWordIndex) {
return pLine->GetPrevWordPlace(place);
}
if (!fxcrt::IndexInBounds(line_array_, place.nLineIndex - 1)) {
return place;
}
return line_array_[place.nLineIndex - 1]->GetEndWordPlace();
}
CPVT_WordPlace CPVT_Section::GetNextWordPlace(
const CPVT_WordPlace& place) const {
if (place.nLineIndex < 0) {
return GetBeginWordPlace();
}
if (place.nLineIndex >= fxcrt::CollectionSize<int32_t>(line_array_)) {
return GetEndWordPlace();
}
Line* pLine = line_array_[place.nLineIndex].get();
if (place.nWordIndex < pLine->line_info_.nEndWordIndex) {
return pLine->GetNextWordPlace(place);
}
if (!fxcrt::IndexInBounds(line_array_, place.nLineIndex + 1)) {
return place;
}
return line_array_[place.nLineIndex + 1]->GetBeginWordPlace();
}
void CPVT_Section::UpdateWordPlace(CPVT_WordPlace& place) const {
int32_t nLeft = 0;
int32_t nRight = fxcrt::CollectionSize<int32_t>(line_array_) - 1;
int32_t nMid = (nLeft + nRight) / 2;
while (nLeft <= nRight) {
Line* pLine = line_array_[nMid].get();
if (place.nWordIndex < pLine->line_info_.nBeginWordIndex) {
nRight = nMid - 1;
nMid = (nLeft + nRight) / 2;
} else if (place.nWordIndex > pLine->line_info_.nEndWordIndex) {
nLeft = nMid + 1;
nMid = (nLeft + nRight) / 2;
} else {
place.nLineIndex = nMid;
return;
}
}
}
CPVT_WordPlace CPVT_Section::SearchWordPlace(const CFX_PointF& point) const {
CPVT_WordPlace place = GetBeginWordPlace();
bool bUp = true;
bool bDown = true;
int32_t nLeft = 0;
int32_t nRight = fxcrt::CollectionSize<int32_t>(line_array_) - 1;
int32_t nMid = fxcrt::CollectionSize<int32_t>(line_array_) / 2;
while (nLeft <= nRight) {
Line* pLine = line_array_[nMid].get();
float fTop = pLine->line_info_.fLineY - pLine->line_info_.fLineAscent -
vt_->GetLineLeading();
float fBottom = pLine->line_info_.fLineY - pLine->line_info_.fLineDescent;
if (FXSYS_IsFloatBigger(point.y, fTop)) {
bUp = false;
}
if (FXSYS_IsFloatSmaller(point.y, fBottom)) {
bDown = false;
}
if (FXSYS_IsFloatSmaller(point.y, fTop)) {
nRight = nMid - 1;
nMid = (nLeft + nRight) / 2;
continue;
}
if (FXSYS_IsFloatBigger(point.y, fBottom)) {
nLeft = nMid + 1;
nMid = (nLeft + nRight) / 2;
continue;
}
place = SearchWordPlaceImpl(
point.x,
CPVT_WordRange(pLine->GetNextWordPlace(pLine->GetBeginWordPlace()),
pLine->GetEndWordPlace()));
place.nLineIndex = nMid;
return place;
}
if (bUp) {
place = GetBeginWordPlace();
}
if (bDown) {
place = GetEndWordPlace();
}
return place;
}
CPVT_WordPlace CPVT_Section::SearchWordPlace(
float fx,
const CPVT_WordPlace& lineplace) const {
if (!fxcrt::IndexInBounds(line_array_, lineplace.nLineIndex)) {
return GetBeginWordPlace();
}
Line* pLine = line_array_[lineplace.nLineIndex].get();
return SearchWordPlaceImpl(
fx - rect_.left,
CPVT_WordRange(pLine->GetNextWordPlace(pLine->GetBeginWordPlace()),
pLine->GetEndWordPlace()));
}
CPVT_WordPlace CPVT_Section::SearchWordPlaceImpl(
float fx,
const CPVT_WordRange& range) const {
CPVT_WordPlace wordplace = range.BeginPos;
wordplace.nWordIndex = -1;
int32_t nLeft = range.BeginPos.nWordIndex;
int32_t nRight = range.EndPos.nWordIndex + 1;
int32_t nMid = (nLeft + nRight) / 2;
while (nLeft < nRight) {
if (nMid == nLeft) {
break;
}
if (nMid == nRight) {
nMid--;
break;
}
if (!fxcrt::IndexInBounds(word_array_, nMid)) {
break;
}
CPVT_WordInfo* pWord = word_array_[nMid].get();
if (fx > pWord->fWordX + vt_->GetWordWidth(*pWord) * 0.5f) {
nLeft = nMid;
nMid = (nLeft + nRight) / 2;
continue;
}
nRight = nMid;
nMid = (nLeft + nRight) / 2;
}
if (fxcrt::IndexInBounds(word_array_, nMid)) {
CPVT_WordInfo* pWord = word_array_[nMid].get();
if (fx > pWord->fWordX + vt_->GetWordWidth(*pWord) * 0.5f) {
wordplace.nWordIndex = nMid;
}
}
return wordplace;
}
int32_t CPVT_Section::GetLineArraySize() const {
return fxcrt::CollectionSize<int32_t>(line_array_);
}
const CPVT_Section::Line* CPVT_Section::GetLineFromArray(int32_t index) const {
if (!fxcrt::IndexInBounds(line_array_, index)) {
return nullptr;
}
return line_array_[index].get();
}
int32_t CPVT_Section::GetWordArraySize() const {
return fxcrt::CollectionSize<int32_t>(word_array_);
}
const CPVT_WordInfo* CPVT_Section::GetWordFromArray(int32_t index) const {
if (!fxcrt::IndexInBounds(word_array_, index)) {
return nullptr;
}
return word_array_[index].get();
}
void CPVT_Section::EraseWordsFrom(int32_t index) {
if (!fxcrt::IndexInBounds(word_array_, index)) {
return;
}
word_array_.erase(word_array_.begin() + index, word_array_.end());
}
CPVT_FloatRect CPVT_Section::RearrangeCharArray() const {
if (line_array_.empty()) {
return CPVT_FloatRect();
}
float fNodeWidth = vt_->GetPlateWidth() /
(vt_->GetCharArray() <= 0 ? 1 : vt_->GetCharArray());
float fLineAscent =
vt_->GetFontAscent(vt_->GetDefaultFontIndex(), vt_->GetFontSize());
float fLineDescent =
vt_->GetFontDescent(vt_->GetDefaultFontIndex(), vt_->GetFontSize());
float x = 0.0f;
float y = vt_->GetLineLeading() + fLineAscent;
int32_t nStart = 0;
CPVT_Section::Line* pLine = line_array_.front().get();
switch (vt_->GetAlignment()) {
case CPVT_VariableText::Alignment::kLeft:
pLine->line_info_.fLineX = fNodeWidth * 0.5f;
break;
case CPVT_VariableText::Alignment::kCenter:
nStart =
(vt_->GetCharArray() - fxcrt::CollectionSize<int32_t>(word_array_)) /
2;
pLine->line_info_.fLineX = fNodeWidth * nStart - fNodeWidth * 0.5f;
break;
case CPVT_VariableText::Alignment::kRight:
nStart =
vt_->GetCharArray() - fxcrt::CollectionSize<int32_t>(word_array_);
pLine->line_info_.fLineX = fNodeWidth * nStart - fNodeWidth * 0.5f;
break;
}
for (int32_t w = 0, sz = fxcrt::CollectionSize<int32_t>(word_array_); w < sz;
w++) {
if (w >= vt_->GetCharArray()) {
break;
}
float fNextWidth = 0;
if (fxcrt::IndexInBounds(word_array_, w + 1)) {
CPVT_WordInfo* pNextWord = word_array_[w + 1].get();
pNextWord->fWordTail = 0;
fNextWidth = vt_->GetWordWidth(*pNextWord);
}
CPVT_WordInfo* pWord = word_array_[w].get();
pWord->fWordTail = 0;
float fWordWidth = vt_->GetWordWidth(*pWord);
float fWordAscent = vt_->GetWordAscent(*pWord);
float fWordDescent = vt_->GetWordDescent(*pWord);
x = (float)(fNodeWidth * (w + nStart + 0.5) - fWordWidth * 0.5f);
pWord->fWordX = x;
pWord->fWordY = y;
if (w == 0) {
pLine->line_info_.fLineX = x;
}
if (w != fxcrt::CollectionSize<int32_t>(word_array_) - 1) {
pWord->fWordTail = (fNodeWidth - (fWordWidth + fNextWidth) * 0.5f > 0
? fNodeWidth - (fWordWidth + fNextWidth) * 0.5f
: 0);
} else {
pWord->fWordTail = 0;
}
x += fWordWidth;
fLineAscent = std::max(fLineAscent, fWordAscent);
fLineDescent = std::min(fLineDescent, fWordDescent);
}
pLine->line_info_.nBeginWordIndex = 0;
pLine->line_info_.nEndWordIndex =
fxcrt::CollectionSize<int32_t>(word_array_) - 1;
pLine->line_info_.fLineY = y;
pLine->line_info_.fLineWidth = x - pLine->line_info_.fLineX;
pLine->line_info_.fLineAscent = fLineAscent;
pLine->line_info_.fLineDescent = fLineDescent;
return CPVT_FloatRect(0, 0, x, y - fLineDescent);
}
CPVT_FloatRect CPVT_Section::RearrangeTypeset() {
line_array_.clear();
return OutputLines(SplitLines(/*bTypeset=*/true, /*fFontSize=*/0.0f));
}
CPVT_FloatRect CPVT_Section::SplitLines(bool bTypeset, float fFontSize) {
CPVT_LineInfo line;
if (word_array_.empty()) {
float fLineAscent;
float fLineDescent;
if (bTypeset) {
fLineAscent = vt_->GetLineAscent();
fLineDescent = vt_->GetLineDescent();
line.nBeginWordIndex = -1;
line.nEndWordIndex = -1;
line.fLineWidth = 0;
line.fLineAscent = fLineAscent;
line.fLineDescent = fLineDescent;
AddLine(line);
} else {
fLineAscent = vt_->GetFontAscent(vt_->GetDefaultFontIndex(), fFontSize);
fLineDescent = vt_->GetFontDescent(vt_->GetDefaultFontIndex(), fFontSize);
}
float fMaxY = vt_->GetLineLeading() + fLineAscent - fLineDescent;
return CPVT_FloatRect(0, 0, 0, fMaxY);
}
int32_t nLineHead = 0;
int32_t nLineTail = 0;
float fMaxX = 0.0f;
float fMaxY = 0.0f;
float fLineWidth = 0.0f;
float fBackupLineWidth = 0.0f;
float fLineAscent = 0.0f;
float fBackupLineAscent = 0.0f;
float fLineDescent = 0.0f;
float fBackupLineDescent = 0.0f;
int32_t nWordStartPos = 0;
bool bFullWord = false;
int32_t nLineFullWordIndex = 0;
int32_t nCharIndex = 0;
float fWordWidth = 0;
float fTypesetWidth =
std::max(vt_->GetPlateWidth() - vt_->GetLineIndent(), 0.0f);
int32_t nTotalWords = fxcrt::CollectionSize<int32_t>(word_array_);
bool bOpened = false;
int32_t i = 0;
while (i < nTotalWords) {
CPVT_WordInfo* pWord = word_array_[i].get();
CPVT_WordInfo* pOldWord = pWord;
if (i > 0) {
pOldWord = word_array_[i - 1].get();
}
if (pWord) {
if (bTypeset) {
fLineAscent = std::max(fLineAscent, vt_->GetWordAscent(*pWord));
fLineDescent = std::min(fLineDescent, vt_->GetWordDescent(*pWord));
fWordWidth = vt_->GetWordWidth(*pWord);
} else {
fLineAscent =
std::max(fLineAscent, vt_->GetWordAscent(*pWord, fFontSize));
fLineDescent =
std::min(fLineDescent, vt_->GetWordDescent(*pWord, fFontSize));
fWordWidth =
vt_->GetWordWidth(pWord->nFontIndex, pWord->Word, vt_->GetSubWord(),
fFontSize, pWord->fWordTail);
}
if (!bOpened) {
if (IsOpenStylePunctuation(pWord->Word)) {
bOpened = true;
bFullWord = true;
} else if (pOldWord) {
if (NeedDivision(pOldWord->Word, pWord->Word)) {
bFullWord = true;
}
}
} else {
if (!IsSpace(pWord->Word) && !IsOpenStylePunctuation(pWord->Word)) {
bOpened = false;
}
}
if (bFullWord) {
bFullWord = false;
if (nCharIndex > 0) {
nLineFullWordIndex++;
}
nWordStartPos = i;
fBackupLineWidth = fLineWidth;
fBackupLineAscent = fLineAscent;
fBackupLineDescent = fLineDescent;
}
nCharIndex++;
}
if (vt_->IsAutoReturn() && fTypesetWidth > 0 &&
fLineWidth + fWordWidth > fTypesetWidth) {
if (nLineFullWordIndex > 0) {
i = nWordStartPos;
fLineWidth = fBackupLineWidth;
fLineAscent = fBackupLineAscent;
fLineDescent = fBackupLineDescent;
}
if (nCharIndex == 1) {
fLineWidth = fWordWidth;
i++;
}
nLineTail = i - 1;
if (bTypeset) {
line.nBeginWordIndex = nLineHead;
line.nEndWordIndex = nLineTail;
line.fLineWidth = fLineWidth;
line.fLineAscent = fLineAscent;
line.fLineDescent = fLineDescent;
AddLine(line);
}
fMaxY += (fLineAscent + vt_->GetLineLeading());
fMaxY -= fLineDescent;
fMaxX = std::max(fLineWidth, fMaxX);
nLineHead = i;
fLineWidth = 0.0f;
fLineAscent = 0.0f;
fLineDescent = 0.0f;
nCharIndex = 0;
nLineFullWordIndex = 0;
bFullWord = false;
} else {
fLineWidth += fWordWidth;
i++;
}
}
if (nLineHead <= nTotalWords - 1) {
nLineTail = nTotalWords - 1;
if (bTypeset) {
line.nBeginWordIndex = nLineHead;
line.nEndWordIndex = nLineTail;
line.fLineWidth = fLineWidth;
line.fLineAscent = fLineAscent;
line.fLineDescent = fLineDescent;
AddLine(line);
}
fMaxY += (fLineAscent + vt_->GetLineLeading());
fMaxY -= fLineDescent;
fMaxX = std::max(fLineWidth, fMaxX);
}
return CPVT_FloatRect(0, 0, fMaxX, fMaxY);
}
CPVT_FloatRect CPVT_Section::OutputLines(const CPVT_FloatRect& rect) const {
float fLineIndent = vt_->GetLineIndent();
float fTypesetWidth = std::max(vt_->GetPlateWidth() - fLineIndent, 0.0f);
CPVT_VariableText::Alignment alignment = vt_->GetAlignment();
auto get_alignment_offset = [alignment, fTypesetWidth](float line_width) {
switch (alignment) {
case CPVT_VariableText::Alignment::kLeft:
return 0.0f;
case CPVT_VariableText::Alignment::kCenter:
return (fTypesetWidth - line_width) * 0.5f;
case CPVT_VariableText::Alignment::kRight:
return fTypesetWidth - line_width;
}
NOTREACHED();
};
float fMinX = get_alignment_offset(rect.Width());
float fMaxX = fMinX + rect.Width();
float fMinY = 0.0f;
float fMaxY = rect.Height();
std::unique_ptr<CFX_BidiResolver> bidi_resolver;
if (!word_array_.empty()) {
std::u16string paragraph_text;
paragraph_text.reserve(word_array_.size());
for (const std::unique_ptr<CPVT_WordInfo>& word : word_array_) {
paragraph_text.push_back(static_cast<char16_t>(word->Word));
}
bidi_resolver = CFX_BidiResolver::Create(std::move(paragraph_text),
vt_->GetTextDirection());
}
float fPosY = 0.0f;
for (const std::unique_ptr<CPVT_Section::Line>& line : line_array_) {
float fPosX =
get_alignment_offset(line->line_info_.fLineWidth) + fLineIndent;
fPosY += vt_->GetLineLeading();
fPosY += line->line_info_.fLineAscent;
line->line_info_.fLineX = fPosX - fMinX;
line->line_info_.fLineY = fPosY - fMinY;
if (!bidi_resolver) {
fPosY -= line->line_info_.fLineDescent;
continue;
}
int32_t line_start = line->line_info_.nBeginWordIndex;
// `nEndWordIndex` is inclusive. Thus, `line_start == nEndWordIndex` implies
// a single-word line (length 1), except for the special case where both are
// -1 (representing an empty line), which also computes to length 1 but
// safely yields an empty span due to internal clamping in
// GetWordRangeIteratorPair().
int32_t line_length = line->line_info_.nEndWordIndex - line_start + 1;
auto position_word = [&fPosX, fMinX, fPosY, fMinY, this](
CPVT_WordInfo& word, bool is_rtl) {
word.is_rtl = is_rtl;
word.fWordX = fPosX - fMinX;
word.fWordY = fPosY - fMinY;
fPosX += vt_->GetWordWidth(word);
};
std::vector<CFX_BidiResolver::ResolvedRun> visual_runs =
bidi_resolver->GetVisualRunsForLine(line_start, line_length);
for (const CFX_BidiResolver::ResolvedRun& run : visual_runs) {
pdfium::span<const std::unique_ptr<CPVT_WordInfo>> run_words =
GetWordRangeSpan(run.start, run.length);
if (run.is_rtl) {
for (const std::unique_ptr<CPVT_WordInfo>& word :
pdfium::Reversed(run_words)) {
position_word(*word, true);
}
} else {
for (const std::unique_ptr<CPVT_WordInfo>& word : run_words) {
position_word(*word, false);
}
}
}
fPosY -= line->line_info_.fLineDescent;
}
return CPVT_FloatRect(fMinX, fMinY, fMaxX, fMaxY);
}
void CPVT_Section::ClearLeftWords(int32_t word_index) {
WordRangeIteratorPair range = GetWordRangeIteratorPair(0, word_index + 1);
word_array_.erase(range.begin, range.end);
}
void CPVT_Section::ClearRightWords(int32_t word_index) {
WordRangeIteratorPair range =
GetWordRangeIteratorPair(word_index + 1, GetWordArraySize());
word_array_.erase(range.begin, range.end);
}
void CPVT_Section::ClearMidWords(int32_t begin_index, int32_t end_index) {
WordRangeIteratorPair range =
GetWordRangeIteratorPair(begin_index + 1, end_index + 1);
word_array_.erase(range.begin, range.end);
}
CPVT_Section::WordRangeIteratorPair CPVT_Section::GetWordRangeIteratorPair(
int32_t begin_index,
int32_t end_index) const {
int32_t size = fxcrt::CollectionSize<int32_t>(word_array_);
end_index = std::clamp(end_index, 0, size);
begin_index = std::clamp(begin_index, 0, end_index);
// SAFETY: indices clamped to `word_array_` size.
return UNSAFE_BUFFERS(
{word_array_.begin() + begin_index, word_array_.begin() + end_index});
}
pdfium::span<const std::unique_ptr<CPVT_WordInfo>>
CPVT_Section::GetWordRangeSpan(int32_t start, int32_t length) const {
WordRangeIteratorPair range = GetWordRangeIteratorPair(start, start + length);
// SAFETY: GetWordRangeIteratorPair() above returns valid iterators or
// `word_array_.end()`.
return UNSAFE_BUFFERS(pdfium::span<const std::unique_ptr<CPVT_WordInfo>>(
std::to_address(range.begin), std::to_address(range.end)));
}
void CPVT_Section::ClearWords(const CPVT_WordRange& PlaceRange) {
CPVT_WordPlace SecBeginPos = GetBeginWordPlace();
CPVT_WordPlace SecEndPos = GetEndWordPlace();
if (PlaceRange.BeginPos >= SecBeginPos) {
if (PlaceRange.EndPos <= SecEndPos) {
ClearMidWords(PlaceRange.BeginPos.nWordIndex,
PlaceRange.EndPos.nWordIndex);
} else {
ClearRightWords(PlaceRange.BeginPos.nWordIndex);
}
} else if (PlaceRange.EndPos <= SecEndPos) {
ClearLeftWords(PlaceRange.EndPos.nWordIndex);
} else {
word_array_.clear();
}
}
void CPVT_Section::ClearWord(const CPVT_WordPlace& place) {
if (fxcrt::IndexInBounds(word_array_, place.nWordIndex)) {
word_array_.erase(word_array_.begin() + place.nWordIndex);
}
}