| // 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/fpdfapi/page/cpdf_textobject.h" | |
| #include <algorithm> | |
| #include "core/fpdfapi/font/cpdf_cidfont.h" | |
| #include "core/fpdfapi/font/cpdf_font.h" | |
| #include "core/fxcrt/check.h" | |
| #include "core/fxcrt/fx_coordinates.h" | |
| #include "core/fxcrt/span.h" | |
| #include "core/fxcrt/span_util.h" | |
| #include "core/fxcrt/zip.h" | |
| #define ISLATINWORD(u) (u != 0x20 && u <= 0x28FF) | |
| namespace { | |
| bool IsVertWritingCIDFont(const CPDF_CIDFont* font) { | |
| return font && font->IsVertWriting(); | |
| } | |
| float GetWordSpaceIfNeeded(const CPDF_CIDFont* cid_font, | |
| uint32_t char_code, | |
| float word_space) { | |
| if (char_code == ' ' && (!cid_font || cid_font->GetCharSize(' ') == 1)) { | |
| return word_space; | |
| } | |
| return 0; | |
| } | |
| } // namespace | |
| CPDF_TextObject::Item::Item() = default; | |
| CPDF_TextObject::Item::Item(const Item& that) = default; | |
| CPDF_TextObject::Item::~Item() = default; | |
| CPDF_TextObject::CPDF_TextObject(int32_t content_stream) | |
| : CPDF_PageObject(content_stream) {} | |
| CPDF_TextObject::CPDF_TextObject() : CPDF_TextObject(kNoContentStream) {} | |
| CPDF_TextObject::~CPDF_TextObject() = default; | |
| size_t CPDF_TextObject::CountItems() const { | |
| return char_codes_.size(); | |
| } | |
| CPDF_TextObject::Item CPDF_TextObject::GetItemInfo(size_t index) const { | |
| DCHECK(index < char_codes_.size()); | |
| Item info; | |
| info.char_code_ = char_codes_[index]; | |
| info.origin_ = CFX_PointF(char_positions_[index], 0); | |
| RetainPtr<CPDF_Font> font = GetFont(); | |
| const CPDF_CIDFont* cid_font = font->AsCIDFont(); | |
| if (!IsVertWritingCIDFont(cid_font)) { | |
| return info; | |
| } | |
| uint16_t cid = cid_font->CIDFromCharCode(info.char_code_); | |
| info.origin_ = CFX_PointF(0, info.origin_.x); | |
| CFX_Point16 vertical_origin = cid_font->GetVertOrigin(cid); | |
| float font_size = GetFontSize(); | |
| info.origin_.x -= font_size * vertical_origin.x / 1000; | |
| info.origin_.y -= font_size * vertical_origin.y / 1000; | |
| return info; | |
| } | |
| uint32_t CPDF_TextObject::GetCharCode(size_t index) const { | |
| return index < char_codes_.size() ? char_codes_[index] | |
| : CPDF_Font::kInvalidCharCode; | |
| } | |
| CPDF_TextObject::Item CPDF_TextObject::GetCharInfo(size_t index) const { | |
| return index < char_codes_.size() ? GetItemInfo(index) : Item(); | |
| } | |
| int CPDF_TextObject::CountWords() const { | |
| RetainPtr<CPDF_Font> font = GetFont(); | |
| bool is_in_latin_word = false; | |
| int word_count = 0; | |
| for (uint32_t char_code : char_codes_) { | |
| WideString unicode_str = font->UnicodeFromCharCode(char_code); | |
| uint16_t unicode = unicode_str.Front(); // Front() safe when empty. | |
| bool is_latin = ISLATINWORD(unicode); | |
| if (is_latin && is_in_latin_word) { | |
| continue; | |
| } | |
| is_in_latin_word = is_latin; | |
| if (unicode != 0x20) { | |
| word_count++; | |
| } | |
| } | |
| return word_count; | |
| } | |
| WideString CPDF_TextObject::GetWordString(int word_index) const { | |
| RetainPtr<CPDF_Font> font = GetFont(); | |
| WideString result_str; | |
| int word_count = 0; | |
| bool is_in_latin_word = false; | |
| for (uint32_t char_code : char_codes_) { | |
| WideString unicode_str = font->UnicodeFromCharCode(char_code); | |
| uint16_t unicode = unicode_str.Front(); // Front() safe when empty. | |
| const bool is_latin = ISLATINWORD(unicode); | |
| if (!is_latin || !is_in_latin_word) { | |
| is_in_latin_word = is_latin; | |
| if (unicode != 0x20) { | |
| word_count++; | |
| } | |
| } | |
| if (word_count - 1 == word_index) { | |
| result_str += unicode; | |
| } | |
| } | |
| return result_str; | |
| } | |
| std::unique_ptr<CPDF_TextObject> CPDF_TextObject::Clone() const { | |
| auto obj = std::make_unique<CPDF_TextObject>(); | |
| obj->CopyData(this); | |
| obj->char_codes_ = char_codes_; | |
| obj->char_kernings_ = char_kernings_; | |
| obj->char_positions_ = char_positions_; | |
| obj->pos_ = pos_; | |
| return obj; | |
| } | |
| CPDF_PageObject::Type CPDF_TextObject::GetType() const { | |
| return Type::kText; | |
| } | |
| void CPDF_TextObject::Transform(const CFX_Matrix& matrix) { | |
| SetTextMatrix(GetTextMatrix() * matrix); | |
| SetDirty(true); | |
| } | |
| bool CPDF_TextObject::IsText() const { | |
| return true; | |
| } | |
| CPDF_TextObject* CPDF_TextObject::AsText() { | |
| return this; | |
| } | |
| const CPDF_TextObject* CPDF_TextObject::AsText() const { | |
| return this; | |
| } | |
| bool CPDF_TextObject::SetAbsolutePositions( | |
| pdfium::span<const float> absolute_positions) { | |
| if (char_codes_.size() <= 1) { | |
| return false; | |
| } | |
| if (absolute_positions.size() != char_codes_.size() - 1) { | |
| return false; | |
| } | |
| float font_size = GetFontSize(); | |
| if (font_size == 0.0f) { | |
| return false; | |
| } | |
| font_size /= 1000; | |
| RetainPtr<CPDF_Font> font = GetFont(); | |
| const CPDF_CIDFont* cid_font = font->AsCIDFont(); | |
| const float char_space = text_state().GetCharSpace(); | |
| const float word_space = text_state().GetWordSpace(); | |
| // Re-calculate `char_kernings_` based on `absolute_positions`. Then let | |
| // CalcPositionDataInternal() re-calculate `char_positions_`. | |
| auto char_codes = pdfium::span(char_codes_).first(absolute_positions.size()); | |
| auto char_kernings = | |
| pdfium::span(char_kernings_).first(absolute_positions.size()); | |
| float current_position = 0; | |
| for (auto [char_code, next_position, kerning] : | |
| fxcrt::Zip(char_codes, absolute_positions, char_kernings)) { | |
| current_position += GetCharWidth(char_code); | |
| current_position += GetWordSpaceIfNeeded(cid_font, char_code, word_space); | |
| current_position += char_space; | |
| kerning = (current_position - next_position) / font_size; | |
| current_position = next_position; | |
| } | |
| char_kernings_.back() = 0; | |
| CalcPositionDataInternal(font); | |
| return true; | |
| } | |
| CFX_Matrix CPDF_TextObject::GetTextMatrix() const { | |
| pdfium::span<const float, 4> text_matrix = text_state().GetMatrix(); | |
| return CFX_Matrix(text_matrix[0], text_matrix[2], text_matrix[1], | |
| text_matrix[3], pos_.x, pos_.y); | |
| } | |
| void CPDF_TextObject::SetTextMatrix(const CFX_Matrix& matrix) { | |
| pdfium::span<float, 4> text_matrix = mutable_text_state().GetMutableMatrix(); | |
| text_matrix[0] = matrix.a; | |
| text_matrix[1] = matrix.c; | |
| text_matrix[2] = matrix.b; | |
| text_matrix[3] = matrix.d; | |
| pos_ = CFX_PointF(matrix.e, matrix.f); | |
| CalcPositionDataInternal(GetFont()); | |
| } | |
| void CPDF_TextObject::SetSegments(pdfium::span<const ByteString> strings, | |
| pdfium::span<const float> kernings) { | |
| size_t segments_count = strings.size(); | |
| CHECK(segments_count); | |
| char_codes_.clear(); | |
| char_kernings_.clear(); | |
| char_positions_.clear(); | |
| RetainPtr<CPDF_Font> font = GetFont(); | |
| size_t char_count = 0; | |
| for (const auto& str : strings) { | |
| char_count += font->CountChar(str.AsStringView()); | |
| } | |
| CHECK(char_count); | |
| char_codes_.resize(char_count); | |
| char_kernings_.resize(char_count); | |
| char_positions_.resize(char_count); | |
| size_t index = 0; | |
| for (size_t i = 0; i < segments_count; ++i) { | |
| ByteStringView segment = strings[i].AsStringView(); | |
| size_t offset = 0; | |
| while (offset < segment.GetLength()) { | |
| DCHECK(index < char_codes_.size()); | |
| char_codes_[index++] = font->GetNextChar(segment, &offset); | |
| } | |
| if (i != segments_count - 1) { | |
| char_kernings_[index - 1] = kernings[i]; | |
| } | |
| } | |
| } | |
| void CPDF_TextObject::SetText(const ByteString& str) { | |
| SetSegments(pdfium::span_from_ref(str), pdfium::span<float>()); | |
| CalcPositionDataInternal(GetFont()); | |
| SetDirty(true); | |
| } | |
| float CPDF_TextObject::GetCharWidth(uint32_t char_code) const { | |
| const float font_size = GetFontSize() / 1000; | |
| RetainPtr<CPDF_Font> font = GetFont(); | |
| const CPDF_CIDFont* cid_font = font->AsCIDFont(); | |
| if (!IsVertWritingCIDFont(cid_font)) { | |
| return font->GetCharWidth(char_code) * font_size; | |
| } | |
| uint16_t cid = cid_font->CIDFromCharCode(char_code); | |
| return cid_font->GetVertWidth(cid) * font_size; | |
| } | |
| RetainPtr<CPDF_Font> CPDF_TextObject::GetFont() const { | |
| return text_state().GetFont(); | |
| } | |
| float CPDF_TextObject::GetFontSize() const { | |
| return text_state().GetFontSize(); | |
| } | |
| TextRenderingMode CPDF_TextObject::GetTextRenderMode() const { | |
| return text_state().GetTextMode(); | |
| } | |
| void CPDF_TextObject::SetTextRenderMode(TextRenderingMode mode) { | |
| mutable_text_state().SetTextMode(mode); | |
| SetDirty(true); | |
| } | |
| CFX_PointF CPDF_TextObject::CalcPositionData(float horz_scale) { | |
| RetainPtr<CPDF_Font> font = GetFont(); | |
| const float current_position = CalcPositionDataInternal(font); | |
| if (IsVertWritingCIDFont(font->AsCIDFont())) { | |
| return {0, current_position}; | |
| } | |
| return {current_position * horz_scale, 0}; | |
| } | |
| float CPDF_TextObject::CalcPositionDataInternal( | |
| const RetainPtr<CPDF_Font>& font) { | |
| float current_position = 0; | |
| float min_x = 10000.0f; | |
| float max_x = -10000.0f; | |
| float min_y = 10000.0f; | |
| float max_y = -10000.0f; | |
| const CPDF_CIDFont* cid_font = font->AsCIDFont(); | |
| const bool is_vertical_writing = IsVertWritingCIDFont(cid_font); | |
| const float font_size = GetFontSize(); | |
| const float char_space = text_state().GetCharSpace(); | |
| const float word_space = text_state().GetWordSpace(); | |
| for (size_t i = 0; i < char_codes_.size(); ++i) { | |
| const uint32_t char_code = char_codes_[i]; | |
| char_positions_[i] = current_position; | |
| FX_RECT char_rect = font->GetCharBBox(char_code); | |
| float char_width; | |
| if (is_vertical_writing) { | |
| uint16_t cid = cid_font->CIDFromCharCode(char_code); | |
| CFX_Point16 vertical_origin = cid_font->GetVertOrigin(cid); | |
| char_rect.Offset(-vertical_origin.x, -vertical_origin.y); | |
| min_x = std::min({min_x, static_cast<float>(char_rect.left), | |
| static_cast<float>(char_rect.right)}); | |
| max_x = std::max({max_x, static_cast<float>(char_rect.left), | |
| static_cast<float>(char_rect.right)}); | |
| const float char_top = | |
| current_position + char_rect.top * font_size / 1000; | |
| const float char_bottom = | |
| current_position + char_rect.bottom * font_size / 1000; | |
| min_y = std::min({min_y, char_top, char_bottom}); | |
| max_y = std::max({max_y, char_top, char_bottom}); | |
| char_width = cid_font->GetVertWidth(cid) * font_size / 1000; | |
| } else { | |
| min_y = std::min({min_y, static_cast<float>(char_rect.top), | |
| static_cast<float>(char_rect.bottom)}); | |
| max_y = std::max({max_y, static_cast<float>(char_rect.top), | |
| static_cast<float>(char_rect.bottom)}); | |
| const float char_left = | |
| current_position + char_rect.left * font_size / 1000; | |
| const float char_right = | |
| current_position + char_rect.right * font_size / 1000; | |
| min_x = std::min({min_x, char_left, char_right}); | |
| max_x = std::max({max_x, char_left, char_right}); | |
| char_width = font->GetCharWidth(char_code) * font_size / 1000; | |
| } | |
| current_position += char_width; | |
| current_position += GetWordSpaceIfNeeded(cid_font, char_code, word_space); | |
| current_position += char_space; | |
| current_position -= (char_kernings_[i] * font_size) / 1000; | |
| } | |
| if (is_vertical_writing) { | |
| min_x = min_x * font_size / 1000; | |
| max_x = max_x * font_size / 1000; | |
| } else { | |
| min_y = min_y * font_size / 1000; | |
| max_y = max_y * font_size / 1000; | |
| } | |
| SetOriginalRect(CFX_FloatRect(min_x, min_y, max_x, max_y)); | |
| CFX_FloatRect rect = GetTextMatrix().TransformRect(GetOriginalRect()); | |
| if (TextRenderingModeIsStrokeMode(text_state().GetTextMode())) { | |
| // TODO(crbug.com/42270854): Does the original rect need a similar | |
| // adjustment? | |
| const float half_width = graph_state().GetLineWidth() / 2; | |
| rect.Inflate(half_width, half_width); | |
| } | |
| SetRect(rect); | |
| return current_position; | |
| } |