| /* |
| * Copyright (c) 2012 Google Inc. All rights reserved. |
| * Copyright (C) 2013 BlackBerry Limited. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "platform/fonts/shaping/ShapeResult.h" |
| |
| #include <hb.h> |
| #include <memory> |
| #include "platform/fonts/Font.h" |
| #include "platform/fonts/shaping/ShapeResultInlineHeaders.h" |
| #include "platform/fonts/shaping/ShapeResultSpacing.h" |
| #include "platform/wtf/PtrUtil.h" |
| |
| namespace blink { |
| |
| unsigned ShapeResult::RunInfo::NextSafeToBreakOffset(unsigned offset) const { |
| DCHECK_LT(offset, num_characters_); |
| for (unsigned i = 0; i < safe_break_offsets_.size(); i++) { |
| if (safe_break_offsets_[i] >= offset) |
| return safe_break_offsets_[i]; |
| } |
| |
| // Next safe break is at the end of the run. |
| return num_characters_; |
| } |
| |
| unsigned ShapeResult::RunInfo::PreviousSafeToBreakOffset( |
| unsigned offset) const { |
| if (offset >= num_characters_) |
| return num_characters_; |
| |
| for (unsigned i = safe_break_offsets_.size(); i > 0; i--) { |
| if (safe_break_offsets_[i - 1] <= offset) |
| return safe_break_offsets_[i - 1]; |
| } |
| |
| // Next safe break is at the start of the run. |
| return 0; |
| } |
| |
| float ShapeResult::RunInfo::XPositionForVisualOffset( |
| unsigned offset, |
| AdjustMidCluster adjust_mid_cluster) const { |
| DCHECK_LT(offset, num_characters_); |
| if (Rtl()) |
| offset = num_characters_ - offset - 1; |
| return XPositionForOffset(offset, adjust_mid_cluster); |
| } |
| |
| float ShapeResult::RunInfo::XPositionForOffset( |
| unsigned offset, |
| AdjustMidCluster adjust_mid_cluster) const { |
| DCHECK_LE(offset, num_characters_); |
| const unsigned num_glyphs = glyph_data_.size(); |
| unsigned glyph_index = 0; |
| float position = 0; |
| if (Rtl()) { |
| while (glyph_index < num_glyphs && |
| glyph_data_[glyph_index].character_index > offset) { |
| position += glyph_data_[glyph_index].advance; |
| ++glyph_index; |
| } |
| // Adjust offset if it's not on the cluster boundary. In RTL, this means |
| // that the adjusted position is the left side of the character. |
| if (adjust_mid_cluster == kAdjustToEnd && |
| (glyph_index < num_glyphs ? glyph_data_[glyph_index].character_index |
| : num_characters_) < offset) { |
| return position; |
| } |
| // For RTL, we need to return the right side boundary of the character. |
| // Add advance of glyphs which are part of the character. |
| while (glyph_index < num_glyphs - 1 && |
| glyph_data_[glyph_index].character_index == |
| glyph_data_[glyph_index + 1].character_index) { |
| position += glyph_data_[glyph_index].advance; |
| ++glyph_index; |
| } |
| position += glyph_data_[glyph_index].advance; |
| } else { |
| while (glyph_index < num_glyphs && |
| glyph_data_[glyph_index].character_index < offset) { |
| position += glyph_data_[glyph_index].advance; |
| ++glyph_index; |
| } |
| // Adjust offset if it's not on the cluster boundary. |
| if (adjust_mid_cluster == kAdjustToStart && glyph_index && |
| (glyph_index < num_glyphs ? glyph_data_[glyph_index].character_index |
| : num_characters_) > offset) { |
| offset = glyph_data_[--glyph_index].character_index; |
| for (; glyph_data_[glyph_index].character_index == offset; |
| --glyph_index) { |
| position -= glyph_data_[glyph_index].advance; |
| if (!glyph_index) |
| break; |
| } |
| } |
| } |
| return position; |
| } |
| |
| static bool TargetPastEdge(bool rtl, float target_x, float next_x) { |
| // In LTR, the edge belongs to the character on right. |
| if (!rtl) |
| return target_x < next_x; |
| |
| // In RTL, the edge belongs to the character on left. |
| return target_x <= next_x; |
| } |
| |
| int ShapeResult::RunInfo::CharacterIndexForXPosition( |
| float target_x, |
| bool include_partial_glyphs) const { |
| DCHECK(target_x >= 0 && target_x <= width_); |
| if (target_x <= 0) |
| return !Rtl() ? 0 : num_characters_; |
| const unsigned num_glyphs = glyph_data_.size(); |
| float current_x = 0; |
| float current_advance = 0; |
| unsigned glyph_index = 0; |
| unsigned prev_character_index = num_characters_; // used only when rtl() |
| |
| while (glyph_index < num_glyphs) { |
| float prev_advance = current_advance; |
| unsigned current_character_index = glyph_data_[glyph_index].character_index; |
| current_advance = glyph_data_[glyph_index].advance; |
| while (glyph_index < num_glyphs - 1 && |
| current_character_index == |
| glyph_data_[glyph_index + 1].character_index) |
| current_advance += glyph_data_[++glyph_index].advance; |
| float next_x; |
| if (include_partial_glyphs) { |
| // For hit testing, find the closest caret point by incuding |
| // end-half of the previous character and start-half of the current |
| // character. |
| current_advance = current_advance / 2.0; |
| next_x = current_x + prev_advance + current_advance; |
| // When include_partial_glyphs, "<=" or "<" is not a big deal because |
| // |next_x| is not at the character boundary. |
| if (target_x <= next_x) |
| return Rtl() ? prev_character_index : current_character_index; |
| } else { |
| next_x = current_x + current_advance; |
| if (TargetPastEdge(Rtl(), target_x, next_x)) |
| return current_character_index; |
| } |
| current_x = next_x; |
| prev_character_index = current_character_index; |
| ++glyph_index; |
| } |
| |
| return Rtl() ? 0 : num_characters_; |
| } |
| |
| void ShapeResult::RunInfo::SetGlyphAndPositions(unsigned index, |
| uint16_t glyph_id, |
| float advance, |
| float offset_x, |
| float offset_y) { |
| HarfBuzzRunGlyphData& data = glyph_data_[index]; |
| data.glyph = glyph_id; |
| data.advance = advance; |
| data.offset = FloatSize(offset_x, offset_y); |
| } |
| |
| ShapeResult::ShapeResult(const Font* font, |
| unsigned num_characters, |
| TextDirection direction) |
| : width_(0), |
| primary_font_(const_cast<SimpleFontData*>(font->PrimaryFont())), |
| num_characters_(num_characters), |
| num_glyphs_(0), |
| direction_(static_cast<unsigned>(direction)), |
| has_vertical_offsets_(0) {} |
| |
| ShapeResult::ShapeResult(const ShapeResult& other) |
| : width_(other.width_), |
| glyph_bounding_box_(other.glyph_bounding_box_), |
| primary_font_(other.primary_font_), |
| num_characters_(other.num_characters_), |
| num_glyphs_(other.num_glyphs_), |
| direction_(other.direction_), |
| has_vertical_offsets_(other.has_vertical_offsets_) { |
| runs_.ReserveCapacity(other.runs_.size()); |
| for (const auto& run : other.runs_) |
| runs_.push_back(base::MakeUnique<RunInfo>(*run)); |
| } |
| |
| ShapeResult::~ShapeResult() {} |
| |
| size_t ShapeResult::ByteSize() const { |
| size_t self_byte_size = sizeof(this); |
| for (unsigned i = 0; i < runs_.size(); ++i) { |
| self_byte_size += runs_[i]->ByteSize(); |
| } |
| return self_byte_size; |
| } |
| |
| unsigned ShapeResult::StartIndexForResult() const { |
| return !Rtl() ? runs_.front()->start_index_ : runs_.back()->start_index_; |
| } |
| |
| unsigned ShapeResult::EndIndexForResult() const { |
| return StartIndexForResult() + NumCharacters(); |
| } |
| |
| RefPtr<ShapeResult> ShapeResult::MutableUnique() const { |
| if (HasOneRef()) |
| return const_cast<ShapeResult*>(this); |
| return ShapeResult::Create(*this); |
| } |
| |
| unsigned ShapeResult::NextSafeToBreakOffset(unsigned absolute_offset) const { |
| if (!absolute_offset) |
| return StartIndexForResult(); |
| |
| // The absolute_offset argument represents the offset for the entire |
| // ShapeResult while offset is continuously updated to be relative to the |
| // current run. |
| unsigned offset = absolute_offset; |
| unsigned run_offset = 0; |
| for (const auto& run : runs_) { |
| if (!run) |
| continue; |
| |
| unsigned num_characters = run->num_characters_; |
| if (offset < num_characters) |
| return run->NextSafeToBreakOffset(offset) + run_offset; |
| |
| offset -= num_characters; |
| run_offset += num_characters; |
| } |
| |
| return EndIndexForResult(); |
| } |
| |
| unsigned ShapeResult::PreviousSafeToBreakOffset( |
| unsigned absolute_offset) const { |
| if (absolute_offset >= NumCharacters()) |
| return NumCharacters(); |
| |
| for (unsigned i = runs_.size(); i > 0; i--) { |
| const auto& run = runs_[i - 1]; |
| if (!run) |
| continue; |
| |
| unsigned run_start = run->start_index_; |
| unsigned run_end = run_start + run->num_characters_; |
| if (absolute_offset >= run_start && absolute_offset < run_end) { |
| unsigned start = |
| absolute_offset > run_start ? absolute_offset - run_start : 0; |
| return run->PreviousSafeToBreakOffset(start) + run_start; |
| } |
| } |
| |
| return StartIndexForResult(); |
| } |
| |
| // If the position is outside of the result, returns the start or the end offset |
| // depends on the position. |
| unsigned ShapeResult::OffsetForPosition(float target_x, |
| bool include_partial_glyphs) const { |
| unsigned characters_so_far = 0; |
| float current_x = 0; |
| |
| if (Rtl()) { |
| if (target_x <= 0) |
| return num_characters_; |
| characters_so_far = num_characters_; |
| for (unsigned i = 0; i < runs_.size(); ++i) { |
| if (!runs_[i]) |
| continue; |
| characters_so_far -= runs_[i]->num_characters_; |
| float next_x = current_x + runs_[i]->width_; |
| float offset_for_run = target_x - current_x; |
| if (offset_for_run >= 0 && offset_for_run <= runs_[i]->width_) { |
| // The x value in question is within this script run. |
| const unsigned index = runs_[i]->CharacterIndexForXPosition( |
| offset_for_run, include_partial_glyphs); |
| return characters_so_far + index; |
| } |
| current_x = next_x; |
| } |
| } else { |
| if (target_x <= 0) |
| return 0; |
| for (unsigned i = 0; i < runs_.size(); ++i) { |
| if (!runs_[i]) |
| continue; |
| float next_x = current_x + runs_[i]->width_; |
| float offset_for_run = target_x - current_x; |
| if (offset_for_run >= 0 && offset_for_run <= runs_[i]->width_) { |
| const unsigned index = runs_[i]->CharacterIndexForXPosition( |
| offset_for_run, include_partial_glyphs); |
| return characters_so_far + index; |
| } |
| characters_so_far += runs_[i]->num_characters_; |
| current_x = next_x; |
| } |
| } |
| |
| return characters_so_far; |
| } |
| |
| float ShapeResult::PositionForOffset(unsigned absolute_offset) const { |
| float x = 0; |
| float offset_x = 0; |
| |
| // The absolute_offset argument represents the offset for the entire |
| // ShapeResult while offset is continuously updated to be relative to the |
| // current run. |
| unsigned offset = absolute_offset; |
| |
| if (Rtl()) { |
| // Convert logical offsets to visual offsets, because results are in |
| // logical order while runs are in visual order. |
| x = width_; |
| if (offset < NumCharacters()) |
| offset = NumCharacters() - offset - 1; |
| x -= Width(); |
| } |
| |
| for (unsigned i = 0; i < runs_.size(); i++) { |
| if (!runs_[i]) |
| continue; |
| DCHECK_EQ(Rtl(), runs_[i]->Rtl()); |
| unsigned num_characters = runs_[i]->num_characters_; |
| |
| if (!offset_x && offset < num_characters) { |
| offset_x = runs_[i]->XPositionForVisualOffset(offset, kAdjustToEnd) + x; |
| break; |
| } |
| |
| offset -= num_characters; |
| x += runs_[i]->width_; |
| } |
| |
| // The position in question might be just after the text. |
| if (!offset_x && absolute_offset == NumCharacters()) |
| return Rtl() ? 0 : width_; |
| |
| return offset_x; |
| } |
| |
| void ShapeResult::FallbackFonts( |
| HashSet<const SimpleFontData*>* fallback) const { |
| DCHECK(fallback); |
| DCHECK(primary_font_); |
| for (unsigned i = 0; i < runs_.size(); ++i) { |
| if (runs_[i] && runs_[i]->font_data_ && |
| runs_[i]->font_data_ != primary_font_ && |
| !runs_[i]->font_data_->IsTextOrientationFallbackOf( |
| primary_font_.Get())) { |
| fallback->insert(runs_[i]->font_data_.Get()); |
| } |
| } |
| } |
| |
| // TODO(kojii): VC2015 fails to explicit instantiation of a member function. |
| // Typed functions + this private function are to instantiate instances. |
| template <typename TextContainerType> |
| void ShapeResult::ApplySpacingImpl( |
| ShapeResultSpacing<TextContainerType>& spacing, |
| int text_start_offset) { |
| float offset = 0; |
| float total_space = 0; |
| for (auto& run : runs_) { |
| if (!run) |
| continue; |
| unsigned run_start_index = run->start_index_ + text_start_offset; |
| float total_space_for_run = 0; |
| for (size_t i = 0; i < run->glyph_data_.size(); i++) { |
| HarfBuzzRunGlyphData& glyph_data = run->glyph_data_[i]; |
| |
| // Skip if it's not a grapheme cluster boundary. |
| if (i + 1 < run->glyph_data_.size() && |
| glyph_data.character_index == |
| run->glyph_data_[i + 1].character_index) { |
| continue; |
| } |
| |
| float space = spacing.ComputeSpacing( |
| run_start_index + glyph_data.character_index, offset); |
| glyph_data.advance += space; |
| total_space_for_run += space; |
| |
| // |offset| is non-zero only when justifying CJK characters that follow |
| // non-CJK characters. |
| if (UNLIKELY(offset)) { |
| if (run->IsHorizontal()) { |
| glyph_data.offset.SetWidth(glyph_data.offset.Width() + offset); |
| } else { |
| glyph_data.offset.SetHeight(glyph_data.offset.Height() + offset); |
| has_vertical_offsets_ = true; |
| } |
| offset = 0; |
| } |
| } |
| run->width_ += total_space_for_run; |
| total_space += total_space_for_run; |
| } |
| width_ += total_space; |
| // Glyph bounding box is in logical space. |
| glyph_bounding_box_.SetWidth(glyph_bounding_box_.Width() + total_space); |
| } |
| |
| void ShapeResult::ApplySpacing(ShapeResultSpacing<String>& spacing, |
| int text_start_offset) { |
| ApplySpacingImpl(spacing, text_start_offset); |
| } |
| |
| PassRefPtr<ShapeResult> ShapeResult::ApplySpacingToCopy( |
| ShapeResultSpacing<TextRun>& spacing, |
| const TextRun& run) const { |
| unsigned index_of_sub_run = spacing.Text().IndexOfSubRun(run); |
| DCHECK_NE(std::numeric_limits<unsigned>::max(), index_of_sub_run); |
| RefPtr<ShapeResult> result = ShapeResult::Create(*this); |
| if (index_of_sub_run != std::numeric_limits<unsigned>::max()) |
| result->ApplySpacingImpl(spacing, index_of_sub_run); |
| return result; |
| } |
| |
| namespace { |
| |
| float HarfBuzzPositionToFloat(hb_position_t value) { |
| return static_cast<float>(value) / (1 << 16); |
| } |
| |
| // Checks whether it's safe to break without reshaping before the given glyph. |
| bool IsSafeToBreakBefore(const hb_glyph_info_t* glyph_infos, |
| unsigned num_glyphs, |
| unsigned i) { |
| // At the end of the run. |
| if (i == num_glyphs - 1) |
| return true; |
| |
| // Not at a cluster boundary. |
| if (glyph_infos[i].cluster == glyph_infos[i + 1].cluster) |
| return false; |
| |
| // The HB_GLYPH_FLAG_UNSAFE_TO_BREAK flag is set for all glyphs in a |
| // given cluster so we only need to check the last one. |
| hb_glyph_flags_t flags = hb_glyph_info_get_glyph_flags(glyph_infos + i); |
| return (flags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) == 0; |
| } |
| |
| } // anonymous namespace |
| |
| // Computes glyph positions, sets advance and offset of each glyph to RunInfo. |
| // |
| // Also computes glyph bounding box of the run. In this function, glyph bounding |
| // box is in physical. |
| template <bool is_horizontal_run> |
| void ShapeResult::ComputeGlyphPositions(ShapeResult::RunInfo* run, |
| unsigned start_glyph, |
| unsigned num_glyphs, |
| hb_buffer_t* harf_buzz_buffer, |
| FloatRect* glyph_bounding_box) { |
| DCHECK_EQ(is_horizontal_run, run->IsHorizontal()); |
| const SimpleFontData* current_font_data = run->font_data_.Get(); |
| const hb_glyph_info_t* glyph_infos = |
| hb_buffer_get_glyph_infos(harf_buzz_buffer, 0); |
| const hb_glyph_position_t* glyph_positions = |
| hb_buffer_get_glyph_positions(harf_buzz_buffer, 0); |
| const unsigned start_cluster = |
| HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(harf_buzz_buffer)) |
| ? glyph_infos[start_glyph].cluster |
| : glyph_infos[start_glyph + num_glyphs - 1].cluster; |
| |
| // Compute glyph_origin and glyph_bounding_box in physical, since both offsets |
| // and boudning box of glyphs are in physical. It's the caller's |
| // responsibility to convert the united physical bounds to logical. |
| float total_advance = 0.0f; |
| FloatPoint glyph_origin; |
| if (is_horizontal_run) |
| glyph_origin.SetX(width_); |
| else |
| glyph_origin.SetY(width_); |
| bool has_vertical_offsets = !is_horizontal_run; |
| |
| // HarfBuzz returns result in visual order, no need to flip for RTL. |
| for (unsigned i = 0; i < num_glyphs; ++i) { |
| uint16_t glyph = glyph_infos[start_glyph + i].codepoint; |
| hb_glyph_position_t pos = glyph_positions[start_glyph + i]; |
| |
| // Offset is primarily used when painting glyphs. Keep it in physical. |
| float offset_x = HarfBuzzPositionToFloat(pos.x_offset); |
| float offset_y = -HarfBuzzPositionToFloat(pos.y_offset); |
| |
| // One out of x_advance and y_advance is zero, depending on |
| // whether the buffer direction is horizontal or vertical. |
| // Convert to float and negate to avoid integer-overflow for ULONG_MAX. |
| float advance; |
| if (is_horizontal_run) |
| advance = HarfBuzzPositionToFloat(pos.x_advance); |
| else |
| advance = -HarfBuzzPositionToFloat(pos.y_advance); |
| |
| uint16_t character_index = |
| glyph_infos[start_glyph + i].cluster - start_cluster; |
| run->glyph_data_[i].character_index = character_index; |
| |
| run->SetGlyphAndPositions(i, glyph, advance, offset_x, offset_y); |
| total_advance += advance; |
| has_vertical_offsets |= (offset_y != 0); |
| |
| // SetGlyphAndPositions() above sets to draw glyphs at |glyph_origin + |
| // offset_{x,y}|. Move glyph_bounds to that point. |
| // Then move the current point by |advance| from |glyph_origin|. |
| // All positions in hb_glyph_position_t are relative to the current point. |
| // https://behdad.github.io/harfbuzz/harfbuzz-Buffers.html#hb-glyph-position-t-struct |
| FloatRect glyph_bounds = current_font_data->BoundsForGlyph(glyph); |
| if (!glyph_bounds.IsEmpty()) { |
| glyph_bounds.Move(glyph_origin.X() + offset_x, |
| glyph_origin.Y() + offset_y); |
| glyph_bounding_box->Unite(glyph_bounds); |
| } |
| if (is_horizontal_run) |
| glyph_origin.SetX(glyph_origin.X() + advance); |
| else |
| glyph_origin.SetY(glyph_origin.Y() + advance); |
| |
| // Check if it is safe to break without reshaping before the cluster. |
| if (IsSafeToBreakBefore(glyph_infos + start_glyph, num_glyphs, i)) { |
| if (run->Rtl()) |
| run->safe_break_offsets_.push_front(character_index); |
| else |
| run->safe_break_offsets_.push_back(character_index); |
| } |
| } |
| |
| run->width_ = std::max(0.0f, total_advance); |
| has_vertical_offsets_ |= has_vertical_offsets; |
| } |
| |
| void ShapeResult::InsertRun(std::unique_ptr<ShapeResult::RunInfo> run_to_insert, |
| unsigned start_glyph, |
| unsigned num_glyphs, |
| hb_buffer_t* harf_buzz_buffer) { |
| DCHECK_GT(num_glyphs, 0u); |
| std::unique_ptr<ShapeResult::RunInfo> run(std::move(run_to_insert)); |
| DCHECK_EQ(num_glyphs, run->glyph_data_.size()); |
| |
| FloatRect glyph_bounding_box; |
| if (run->IsHorizontal()) { |
| // Inserting a horizontal run into a horizontal or vertical result. In both |
| // cases, no adjustments are needed because |glyph_bounding_box_| is in |
| // logical coordinates and uses alphabetic baseline. |
| ComputeGlyphPositions<true>(run.get(), start_glyph, num_glyphs, |
| harf_buzz_buffer, &glyph_bounding_box); |
| } else { |
| // Inserting a vertical run to a vertical result. |
| ComputeGlyphPositions<false>(run.get(), start_glyph, num_glyphs, |
| harf_buzz_buffer, &glyph_bounding_box); |
| // Convert physical glyph_bounding_box to logical. |
| glyph_bounding_box = glyph_bounding_box.TransposedRect(); |
| // The glyph bounding box of a vertical run uses ideographic baseline. |
| // Adjust the box Y position because the bounding box of a ShapeResult uses |
| // alphabetic baseline. |
| // See diagrams of base lines at |
| // https://drafts.csswg.org/css-writing-modes-3/#intro-baselines |
| const FontMetrics& font_metrics = run->font_data_->GetFontMetrics(); |
| int baseline_adjust = font_metrics.Ascent(kIdeographicBaseline) - |
| font_metrics.Ascent(kAlphabeticBaseline); |
| glyph_bounding_box.SetY(glyph_bounding_box.Y() + baseline_adjust); |
| } |
| glyph_bounding_box_.Unite(glyph_bounding_box); |
| width_ += run->width_; |
| num_glyphs_ += num_glyphs; |
| DCHECK_GE(num_glyphs_, num_glyphs); |
| |
| // The runs are stored in result->m_runs in visual order. For LTR, we place |
| // the run to be inserted before the next run with a bigger character |
| // start index. For RTL, we place the run before the next run with a lower |
| // character index. Otherwise, for both directions, at the end. |
| if (HB_DIRECTION_IS_FORWARD(run->direction_)) { |
| for (size_t pos = 0; pos < runs_.size(); ++pos) { |
| if (runs_.at(pos)->start_index_ > run->start_index_) { |
| runs_.insert(pos, std::move(run)); |
| break; |
| } |
| } |
| } else { |
| for (size_t pos = 0; pos < runs_.size(); ++pos) { |
| if (runs_.at(pos)->start_index_ < run->start_index_) { |
| runs_.insert(pos, std::move(run)); |
| break; |
| } |
| } |
| } |
| // If we didn't find an existing slot to place it, append. |
| if (run) |
| runs_.push_back(std::move(run)); |
| } |
| |
| // Moves runs at (run_size_before, end) to the front of |runs_|. |
| // |
| // Runs in RTL result are in visual order, and that new runs should be |
| // prepended. This function adjusts the run order after runs were appended. |
| void ShapeResult::ReorderRtlRuns(unsigned run_size_before) { |
| DCHECK(Rtl()); |
| DCHECK_GT(runs_.size(), run_size_before); |
| if (runs_.size() == run_size_before + 1) { |
| if (!run_size_before) |
| return; |
| std::unique_ptr<RunInfo> new_run(std::move(runs_.back())); |
| runs_.Shrink(runs_.size() - 1); |
| runs_.push_front(std::move(new_run)); |
| return; |
| } |
| |
| // |push_front| is O(n) that we should not call it multiple times. |
| // Create a new list in the correct order and swap it. |
| Vector<std::unique_ptr<RunInfo>> new_runs; |
| new_runs.ReserveInitialCapacity(runs_.size()); |
| for (unsigned i = run_size_before; i < runs_.size(); i++) |
| new_runs.push_back(std::move(runs_[i])); |
| |
| // Recompute |start_index_| in the decreasing order. |
| unsigned index = num_characters_; |
| for (auto it = new_runs.rbegin(); it != new_runs.rend(); it++) { |
| auto& run = *it; |
| run->start_index_ = index; |
| index += run->num_characters_; |
| } |
| |
| // Then append existing runs. |
| for (unsigned i = 0; i < run_size_before; i++) |
| new_runs.push_back(std::move(runs_[i])); |
| runs_.swap(new_runs); |
| } |
| |
| void ShapeResult::CopyRange(unsigned start_offset, |
| unsigned end_offset, |
| ShapeResult* target) const { |
| if (!runs_.size()) |
| return; |
| |
| unsigned index = target->num_characters_; |
| unsigned target_run_size_before = target->runs_.size(); |
| float total_width = 0; |
| for (const auto& run : runs_) { |
| unsigned run_start = run->start_index_; |
| unsigned run_end = run_start + run->num_characters_; |
| |
| if (start_offset < run_end && end_offset > run_start) { |
| unsigned start = start_offset > run_start ? start_offset - run_start : 0; |
| unsigned end = std::min(end_offset, run_end) - run_start; |
| DCHECK(end > start); |
| |
| auto sub_run = run->CreateSubRun(start, end); |
| sub_run->start_index_ = index; |
| total_width += sub_run->width_; |
| index += sub_run->num_characters_; |
| target->runs_.push_back(std::move(sub_run)); |
| } |
| } |
| |
| if (target->runs_.size() == target_run_size_before) |
| return; |
| |
| // Runs in RTL result are in visual order, and that new runs should be |
| // prepended. Reorder appended runs. |
| DCHECK_EQ(Rtl(), target->Rtl()); |
| if (target->Rtl()) |
| target->ReorderRtlRuns(target_run_size_before); |
| |
| // Compute new glyph bounding box. |
| // If |start_offset| or |end_offset| are the start/end of |this|, use |
| // |glyph_bounding_box_| from |this| for the side. Otherwise, we cannot |
| // compute accurate glyph bounding box; approximate by assuming there are no |
| // glyph overflow nor underflow. |
| float left = target->width_; |
| target->width_ += total_width; |
| float right = target->width_; |
| if (start_offset <= StartIndexForResult()) |
| left += glyph_bounding_box_.X(); |
| if (end_offset >= EndIndexForResult()) |
| right += glyph_bounding_box_.MaxX() - width_; |
| FloatRect adjusted_box(left, glyph_bounding_box_.Y(), |
| std::max(right - left, 0.0f), |
| glyph_bounding_box_.Height()); |
| target->glyph_bounding_box_.UniteIfNonZero(adjusted_box); |
| |
| DCHECK_EQ(index - target->num_characters_, |
| std::min(end_offset, EndIndexForResult()) - |
| std::max(start_offset, StartIndexForResult())); |
| target->num_characters_ = index; |
| } |
| |
| PassRefPtr<ShapeResult> ShapeResult::CreateForTabulationCharacters( |
| const Font* font, |
| const TextRun& text_run, |
| float position_offset, |
| unsigned count) { |
| const SimpleFontData* font_data = font->PrimaryFont(); |
| // Tab characters are always LTR or RTL, not TTB, even when |
| // isVerticalAnyUpright(). |
| std::unique_ptr<ShapeResult::RunInfo> run = base::MakeUnique<RunInfo>( |
| font_data, text_run.Rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, |
| HB_SCRIPT_COMMON, 0, count, count); |
| float position = text_run.XPos() + position_offset; |
| float start_position = position; |
| for (unsigned i = 0; i < count; i++) { |
| float advance = font->TabWidth(font_data, text_run.GetTabSize(), position); |
| run->glyph_data_[i].character_index = i; |
| run->SetGlyphAndPositions(i, font_data->SpaceGlyph(), advance, 0, 0); |
| |
| // Assume it's safe to break after a tab character. |
| run->safe_break_offsets_.push_back(run->glyph_data_[i].character_index); |
| position += advance; |
| } |
| run->width_ = position - start_position; |
| |
| RefPtr<ShapeResult> result = |
| ShapeResult::Create(font, count, text_run.Direction()); |
| result->width_ = run->width_; |
| result->num_glyphs_ = count; |
| DCHECK_EQ(result->num_glyphs_, count); // no overflow |
| result->has_vertical_offsets_ = |
| font_data->PlatformData().IsVerticalAnyUpright(); |
| result->runs_.push_back(std::move(run)); |
| return result; |
| } |
| |
| } // namespace blink |