| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/renderer/rasterizer/skia/text_shaper.h" |
| |
| #include <algorithm> |
| #include <limits> |
| |
| #include "base/i18n/char_iterator.h" |
| #include "base/i18n/icu_string_conversions.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/base/unicode/character.h" |
| #include "cobalt/base/unicode/character_values.h" |
| #include "cobalt/renderer/rasterizer/skia/glyph_buffer.h" |
| #include "cobalt/renderer/rasterizer/skia/harfbuzz_font.h" |
| |
| namespace cobalt { |
| namespace renderer { |
| namespace rasterizer { |
| namespace skia { |
| namespace { |
| |
| // A simple implementation of FontProvider where a single font is always used. |
| class SingleFontFontProvider : public render_tree::FontProvider { |
| public: |
| explicit SingleFontFontProvider(const scoped_refptr<render_tree::Font>& font) |
| : size_(base::polymorphic_downcast<Font*>(font.get())->size()), |
| font_(font) {} |
| |
| const render_tree::FontStyle& style() const override { return style_; } |
| float size() const override { return size_; } |
| |
| const scoped_refptr<render_tree::Font>& GetCharacterFont( |
| int32 utf32_character, render_tree::GlyphIndex* glyph_index) override { |
| *glyph_index = font_->GetGlyphForCharacter(utf32_character); |
| return font_; |
| } |
| |
| private: |
| render_tree::FontStyle style_; |
| float size_; |
| scoped_refptr<render_tree::Font> font_; |
| }; |
| |
| void TryAddFontToUsedFonts(Font* font, |
| render_tree::FontVector* maybe_used_fonts) { |
| if (!maybe_used_fonts) { |
| return; |
| } |
| |
| // Verify that the font has not already been added to the used fonts, before |
| // adding it to the end. |
| for (int i = 0; i < maybe_used_fonts->size(); ++i) { |
| if ((*maybe_used_fonts)[i] == font) { |
| return; |
| } |
| } |
| |
| maybe_used_fonts->push_back(font); |
| } |
| |
| bool ShouldFakeBoldText(const render_tree::FontProvider* font_provider, |
| const Font* font) { |
| // A font-weight greater than 500 indicates bold if it is available. Use |
| // synthetic bolding if this is a bold weight and the selected font |
| // synthesizes bold. |
| // https://www.w3.org/TR/css-fonts-3/#font-weight-prop |
| // https://www.w3.org/TR/css-fonts-3/#font-style-matching |
| if (font_provider->style().weight > 500) { |
| const sk_sp<SkTypeface_Cobalt>& typeface(font->GetSkTypeface()); |
| return typeface->synthesizes_bold(); |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| TextShaper::TextShaper() |
| : local_glyph_array_size_(0), local_text_buffer_size_(0) {} |
| |
| scoped_refptr<GlyphBuffer> TextShaper::CreateGlyphBuffer( |
| const char16* text_buffer, size_t text_length, const std::string& language, |
| bool is_rtl, render_tree::FontProvider* font_provider) { |
| math::RectF bounds; |
| SkTextBlobBuilder builder; |
| ShapeText(text_buffer, text_length, language, is_rtl, font_provider, &builder, |
| &bounds, NULL); |
| return make_scoped_refptr(new GlyphBuffer(bounds, &builder)); |
| } |
| |
| scoped_refptr<GlyphBuffer> TextShaper::CreateGlyphBuffer( |
| const std::string& utf8_string, |
| const scoped_refptr<render_tree::Font>& font) { |
| string16 utf16_string; |
| base::CodepageToUTF16(utf8_string, base::kCodepageUTF8, |
| base::OnStringConversionError::SUBSTITUTE, |
| &utf16_string); |
| SingleFontFontProvider font_provider(font); |
| return CreateGlyphBuffer(utf16_string.c_str(), utf16_string.size(), "en-US", |
| false, &font_provider); |
| } |
| |
| float TextShaper::GetTextWidth(const char16* text_buffer, size_t text_length, |
| const std::string& language, bool is_rtl, |
| render_tree::FontProvider* font_provider, |
| render_tree::FontVector* maybe_used_fonts) { |
| return ShapeText(text_buffer, text_length, language, is_rtl, font_provider, |
| NULL, NULL, maybe_used_fonts); |
| } |
| |
| void TextShaper::PurgeCaches() { |
| base::AutoLock lock(shaping_mutex_); |
| harfbuzz_font_provider_.PurgeCaches(); |
| } |
| |
| float TextShaper::ShapeText(const char16* text_buffer, size_t text_length, |
| const std::string& language, bool is_rtl, |
| render_tree::FontProvider* font_provider, |
| SkTextBlobBuilder* maybe_builder, |
| math::RectF* maybe_bounds, |
| render_tree::FontVector* maybe_used_fonts) { |
| base::AutoLock lock(shaping_mutex_); |
| float total_width = 0; |
| VerticalBounds vertical_bounds; |
| // Only set |maybe_vertical_bounds| to a non-NULL value when |maybe_bounds| is |
| // non-NULL. Otherwise, the text bounds are not being calculated. |
| VerticalBounds* maybe_vertical_bounds = |
| maybe_bounds ? &vertical_bounds : NULL; |
| |
| // Check for if the text contains a complex script, meaning that it requires |
| // HarfBuzz. If it does, then attempt to collect the scripts. In the event |
| // that this fails, fall back to the simple shaper. |
| ScriptRuns script_runs; |
| if (base::unicode::ContainsComplexScript(text_buffer, text_length) && |
| CollectScriptRuns(text_buffer, text_length, font_provider, |
| &script_runs)) { |
| // If the direction is RTL, then reverse the script runs, so that the glyphs |
| // will be added in the correct order. |
| if (is_rtl) { |
| std::reverse(script_runs.begin(), script_runs.end()); |
| } |
| |
| for (int i = 0; i < script_runs.size(); ++i) { |
| ScriptRun& run = script_runs[i]; |
| const char16* script_run_text_buffer = text_buffer + run.start_index; |
| |
| // Check to see if the script run requires HarfBuzz. Because HarfBuzz |
| // shaping is much slower than simple shaping, we only want to run |
| // HarfBuzz shaping on the bare minimum possible, so any script run |
| // that doesn't contain complex scripts will be simply shaped. |
| if (base::unicode::ContainsComplexScript(script_run_text_buffer, |
| run.length)) { |
| ShapeComplexRun(script_run_text_buffer, run, language, is_rtl, |
| font_provider, maybe_builder, maybe_vertical_bounds, |
| maybe_used_fonts, &total_width); |
| } else { |
| ShapeSimpleRunWithDirection(script_run_text_buffer, run.length, is_rtl, |
| font_provider, maybe_builder, |
| maybe_vertical_bounds, maybe_used_fonts, |
| &total_width); |
| } |
| } |
| } else { |
| ShapeSimpleRunWithDirection(text_buffer, text_length, is_rtl, font_provider, |
| maybe_builder, maybe_vertical_bounds, |
| maybe_used_fonts, &total_width); |
| } |
| |
| // If |maybe_bounds| has been provided, then update the width of the bounds |
| // with the total width of all of the shaped glyphs. The height is already |
| // correct. |
| if (maybe_bounds) { |
| *maybe_bounds = math::RectF(0, vertical_bounds.GetY(), total_width, |
| vertical_bounds.GetHeight()); |
| } |
| |
| return total_width; |
| } |
| |
| bool TextShaper::CollectScriptRuns(const char16* text_buffer, |
| size_t text_length, |
| render_tree::FontProvider* font_provider, |
| ScriptRuns* runs) { |
| // Initialize the next data with the first character. This allows us to avoid |
| // checking for the first character case within the while loop. |
| int32 next_character = base::unicode::NormalizeSpaces( |
| base::i18n::UTF16CharIterator(text_buffer, text_length).get()); |
| render_tree::GlyphIndex next_glyph = render_tree::kInvalidGlyphIndex; |
| Font* next_font = base::polymorphic_downcast<Font*>( |
| font_provider->GetCharacterFont(next_character, &next_glyph).get()); |
| |
| UErrorCode error_code = U_ZERO_ERROR; |
| UScriptCode next_script = uscript_getScript(next_character, &error_code); |
| // If we're unable to determine the script of a character, we failed to |
| // successfully collect the script runs. Return failure. |
| if (U_FAILURE(error_code)) { |
| return false; |
| } |
| |
| // Iterate for as long as the current index has not reached the end index. |
| unsigned int current_index = 0; |
| unsigned int end_index = text_length; |
| while (current_index < end_index) { |
| Font* current_font = next_font; |
| UScriptCode current_script = next_script; |
| |
| // Create an iterator starting at the current index and containing the |
| // remaining length. |
| base::i18n::UTF16CharIterator iter(text_buffer + current_index, |
| end_index - current_index); |
| // Skip over the first character. It's already set as our current font |
| // and current script. |
| iter.Advance(); |
| size_t next_index_offset = iter.array_pos(); |
| |
| for (; !iter.end(); iter.Advance(), next_index_offset = iter.array_pos()) { |
| // Normalize spaces within the current character, so that we are |
| // guaranteed that they will map to a consistent font and glyph. |
| next_character = base::unicode::NormalizeSpaces(iter.get()); |
| if (next_character == base::unicode::kZeroWidthSpaceCharacter) { |
| continue; |
| } |
| |
| // Check for mark characters. If this is a mark character and the current |
| // font contains a glyph for it, then simply add it to the current run. |
| // Doing so ensures that combining character sequences will be shaped in |
| // one font run if possible. |
| if ((U_GET_GC_MASK(next_character) & U_GC_M_MASK) && |
| (current_font->GetGlyphForCharacter(next_character) != |
| render_tree::kInvalidGlyphIndex)) { |
| continue; |
| } |
| |
| next_font = base::polymorphic_downcast<Font*>( |
| font_provider->GetCharacterFont(next_character, &next_glyph).get()); |
| |
| next_script = uscript_getScript(next_character, &error_code); |
| // If we're unable to determine the script of a character, we failed to |
| // successfully collect the script runs. Return failure. |
| if (U_FAILURE(error_code)) { |
| return false; |
| } |
| |
| // We've reached the end of the script run in two cases: |
| // 1. If the characters use different fonts. |
| // 2. If the characters use different scripts, the next script isn't |
| // inherited, and the next character doesn't support the current |
| // script. |
| if ((current_font != next_font) || |
| ((current_script != next_script) && |
| (next_script != USCRIPT_INHERITED) && |
| (!uscript_hasScript(next_character, current_script)))) { |
| break; |
| } |
| |
| // If the script is inherited, then simply retain the current script. |
| if (next_script == USCRIPT_INHERITED) { |
| next_script = current_script; |
| } |
| } |
| |
| runs->push_back(ScriptRun(current_font, current_script, current_index, |
| next_index_offset)); |
| |
| // Update the index where we're starting the next script run. |
| current_index += next_index_offset; |
| } |
| |
| return true; |
| } |
| |
| void TextShaper::ShapeComplexRun(const char16* text_buffer, |
| const ScriptRun& script_run, |
| const std::string& language, bool is_rtl, |
| render_tree::FontProvider* font_provider, |
| SkTextBlobBuilder* maybe_builder, |
| VerticalBounds* maybe_vertical_bounds, |
| render_tree::FontVector* maybe_used_fonts, |
| float* total_width) { |
| TryAddFontToUsedFonts(script_run.font, maybe_used_fonts); |
| |
| hb_font_t* harfbuzz_font = |
| harfbuzz_font_provider_.GetHarfBuzzFont(script_run.font); |
| |
| // Ensure that the local text buffer is large enough to hold the normalized |
| // string. |
| EnsureLocalTextBufferHasSize(script_run.length); |
| |
| // Normalize the spaces in the run before providing it to HarfBuzz. |
| bool error = false; |
| unsigned int normalized_buffer_length = 0; |
| unsigned int buffer_position = 0; |
| while (buffer_position < script_run.length) { |
| UChar32 character; |
| U16_NEXT(text_buffer, buffer_position, script_run.length, character); |
| character = base::unicode::NormalizeSpacesInComplexScript(character); |
| U16_APPEND(local_text_buffer_.get(), normalized_buffer_length, |
| script_run.length, character, error); |
| } |
| |
| // Create a HarfBuzz buffer and add the string to be shaped. The HarfBuzz |
| // buffer holds our text, run information to be used by the shaping engine, |
| // and the resulting glyph data. |
| hb_buffer_t* buffer = hb_buffer_create(); |
| hb_buffer_add_utf16(buffer, (const uint16_t*)local_text_buffer_.get(), |
| normalized_buffer_length, 0, normalized_buffer_length); |
| hb_buffer_set_script(buffer, hb_icu_script_to_script(script_run.script)); |
| hb_buffer_set_direction(buffer, is_rtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); |
| hb_buffer_set_language( |
| buffer, hb_language_from_string(language.c_str(), language.length())); |
| |
| // Shape the text. |
| hb_shape(harfbuzz_font, buffer, NULL, 0); |
| |
| // Populate the run fields with the resulting glyph data in the buffer. |
| unsigned int glyph_count = 0; |
| |
| hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(buffer, &glyph_count); |
| hb_glyph_position_t* hb_positions = |
| hb_buffer_get_glyph_positions(buffer, NULL); |
| |
| // If |maybe_builder| has been provided, the allocate enough memory within |
| // the builder for the shaped glyphs. |
| const SkTextBlobBuilder::RunBuffer* run_buffer = NULL; |
| if (maybe_builder) { |
| SkPaint paint = script_run.font->GetSkPaint(); |
| paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); |
| paint.setFakeBoldText(ShouldFakeBoldText(font_provider, script_run.font)); |
| run_buffer = &(maybe_builder->allocRunPos(paint, glyph_count)); |
| } |
| |
| // Walk each of the shaped glyphs. |
| size_t pos_index = 0; |
| for (size_t i = 0; i < glyph_count; ++i) { |
| DCHECK_LE(infos[i].codepoint, std::numeric_limits<uint16_t>::max()); |
| // If |run_buffer| has been allocated, then we're populating it with the |
| // glyph and position data. |
| if (run_buffer) { |
| run_buffer->glyphs[i] = static_cast<uint16_t>(infos[i].codepoint); |
| run_buffer->pos[pos_index++] = |
| *total_width + SkFixedToScalar(hb_positions[i].x_offset); |
| run_buffer->pos[pos_index++] = -SkFixedToScalar(hb_positions[i].y_offset); |
| } |
| |
| // If |maybe_vertical_bounds| has been provided, then we're updating it with |
| // the vertical bounds of all of the shaped glyphs. |
| if (maybe_vertical_bounds) { |
| const math::RectF& glyph_bounds = |
| script_run.font->GetGlyphBounds(infos[i].codepoint); |
| float min_y = |
| glyph_bounds.y() - SkFixedToScalar(hb_positions[i].y_offset); |
| float max_y = min_y + glyph_bounds.height(); |
| maybe_vertical_bounds->IncludeRange(min_y, max_y); |
| } |
| |
| // Include the shaped glyph within the total width. |
| *total_width += SkFixedToFloat(hb_positions[i].x_advance); |
| } |
| |
| hb_buffer_destroy(buffer); |
| hb_font_destroy(harfbuzz_font); |
| } |
| |
| void TextShaper::ShapeSimpleRunWithDirection( |
| const char16* text_buffer, size_t text_length, bool is_rtl, |
| render_tree::FontProvider* font_provider, SkTextBlobBuilder* maybe_builder, |
| VerticalBounds* maybe_vertical_bounds, |
| render_tree::FontVector* maybe_used_fonts, float* total_width) { |
| // If the text has an RTL direction and a builder was provided, then reverse |
| // the text. This ensures that the glyphs will appear in the proper order |
| // within the glyph buffer. The width and bounds do not rely on the direction |
| // of the text, so in the case where there is no builder, reversing the text |
| // is not necessary. |
| if (maybe_builder && is_rtl) { |
| // Ensure that the local text buffer is large enough to hold the reversed |
| // string. |
| EnsureLocalTextBufferHasSize(text_length); |
| |
| // Both reverse the text and replace mirror characters so that characters |
| // such as parentheses will appear in the proper direction. |
| bool error = false; |
| unsigned int reversed_buffer_length = 0; |
| unsigned int buffer_position = text_length; |
| while (buffer_position > 0) { |
| UChar32 character; |
| U16_PREV(text_buffer, 0, buffer_position, character); |
| character = u_charMirror(character); |
| U16_APPEND(local_text_buffer_.get(), reversed_buffer_length, text_length, |
| character, error); |
| } |
| |
| ShapeSimpleRun(local_text_buffer_.get(), reversed_buffer_length, |
| font_provider, maybe_builder, maybe_vertical_bounds, |
| maybe_used_fonts, total_width); |
| } else { |
| ShapeSimpleRun(text_buffer, text_length, font_provider, maybe_builder, |
| maybe_vertical_bounds, maybe_used_fonts, total_width); |
| } |
| } |
| |
| void TextShaper::ShapeSimpleRun(const char16* text_buffer, size_t text_length, |
| render_tree::FontProvider* font_provider, |
| SkTextBlobBuilder* maybe_builder, |
| VerticalBounds* maybe_vertical_bounds, |
| render_tree::FontVector* maybe_used_fonts, |
| float* total_width) { |
| // If there's a builder (meaning that a glyph buffer is being generated), then |
| // ensure that the local arrays are large enough to hold all of the glyphs |
| // within the simple run. |
| if (maybe_builder) { |
| EnsureLocalGlyphArraysHaveSize(text_length); |
| } |
| |
| int glyph_count = 0; |
| Font* last_font = NULL; |
| |
| // Walk through each character within the run. |
| for (base::i18n::UTF16CharIterator iter(text_buffer, text_length); |
| !iter.end(); iter.Advance()) { |
| // Retrieve the current character and normalize spaces and zero width |
| // spaces before processing it. |
| int32 character = base::unicode::NormalizeSpaces(iter.get()); |
| render_tree::GlyphIndex glyph = render_tree::kInvalidGlyphIndex; |
| |
| // If a space character is encountered, simply add to the width and continue |
| // on. As an optimization, we don't add space character glyphs to the glyph |
| // buffer. |
| if (character == base::unicode::kSpaceCharacter) { |
| *total_width += font_provider->GetCharacterFont(character, &glyph) |
| ->GetGlyphWidth(glyph); |
| continue; |
| // If a zero width space character is encountered, simply continue on. It |
| // doesn't impact the width or shaping data. |
| } else if (character == base::unicode::kZeroWidthSpaceCharacter) { |
| continue; |
| } |
| |
| // Look up the font and glyph for the current character. |
| Font* current_font = base::polymorphic_downcast<Font*>( |
| font_provider->GetCharacterFont(character, &glyph).get()); |
| |
| // If there's a builder (meaning that a glyph buffer is being generated), |
| // then we need to update the glyph buffer data for the new glyph. |
| if (maybe_builder) { |
| // If at least one glyph has previously been added and the font has |
| // changed, then the current run has ended and we need to add the font run |
| // into the glyph buffer. |
| if (glyph_count > 0 && last_font != current_font) { |
| TryAddFontToUsedFonts(last_font, maybe_used_fonts); |
| AddFontRunToGlyphBuffer(font_provider, last_font, glyph_count, |
| maybe_builder); |
| glyph_count = 0; |
| } |
| |
| local_glyphs_[glyph_count] = glyph; |
| local_positions_[glyph_count] = *total_width; |
| } |
| |
| const math::RectF& glyph_bounds = current_font->GetGlyphBounds(glyph); |
| *total_width += glyph_bounds.width(); |
| |
| // If |maybe_vertical_bounds| has been provided, then we're updating it with |
| // the vertical bounds of all of the shaped glyphs. |
| if (maybe_vertical_bounds) { |
| maybe_vertical_bounds->IncludeRange(glyph_bounds.y(), |
| glyph_bounds.bottom()); |
| } |
| |
| ++glyph_count; |
| last_font = current_font; |
| } |
| |
| // If there's a builder (meaning that a glyph buffer is being generated), and |
| // at least one glyph was generated, then we need to add the final font run |
| // into the glyph buffer. |
| if (maybe_builder && glyph_count > 0) { |
| TryAddFontToUsedFonts(last_font, maybe_used_fonts); |
| AddFontRunToGlyphBuffer(font_provider, last_font, glyph_count, |
| maybe_builder); |
| } |
| } |
| |
| void TextShaper::AddFontRunToGlyphBuffer( |
| const render_tree::FontProvider* font_provider, const Font* font, |
| const int glyph_count, SkTextBlobBuilder* builder) { |
| SkPaint paint = font->GetSkPaint(); |
| paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); |
| paint.setFakeBoldText(ShouldFakeBoldText(font_provider, font)); |
| |
| // Allocate space within the text blob for the glyphs and copy them into the |
| // blob. |
| const SkTextBlobBuilder::RunBuffer& buffer = |
| builder->allocRunPosH(paint, glyph_count, 0); |
| std::copy(&local_glyphs_[0], &local_glyphs_[0] + glyph_count, buffer.glyphs); |
| std::copy(&local_positions_[0], &local_positions_[0] + glyph_count, |
| buffer.pos); |
| } |
| |
| void TextShaper::EnsureLocalGlyphArraysHaveSize(size_t size) { |
| if (local_glyph_array_size_ < size) { |
| local_glyph_array_size_ = size; |
| local_glyphs_.reset(new render_tree::GlyphIndex[size]); |
| local_positions_.reset(new SkScalar[size]); |
| } |
| } |
| |
| void TextShaper::EnsureLocalTextBufferHasSize(size_t size) { |
| if (local_text_buffer_size_ < size) { |
| local_text_buffer_size_ = size; |
| local_text_buffer_.reset(new char16[size]); |
| } |
| } |
| |
| } // namespace skia |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |