| /* |
| * Copyright (C) 2005, 2008, 2010 Apple Inc. All rights reserved. |
| * Copyright (C) 2006 Alexey Proskuryakov |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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/SimpleFontData.h" |
| |
| #include <unicode/unorm.h> |
| #include <unicode/utf16.h> |
| #include <memory> |
| #include "SkPath.h" |
| #include "SkTypeface.h" |
| #include "SkTypes.h" |
| #include "platform/fonts/FontDescription.h" |
| #include "platform/fonts/VDMXParser.h" |
| #include "platform/fonts/skia/SkiaTextMetrics.h" |
| #include "platform/geometry/FloatRect.h" |
| #include "platform/wtf/ByteOrder.h" |
| #include "platform/wtf/MathExtras.h" |
| #include "platform/wtf/PtrUtil.h" |
| #include "platform/wtf/allocator/Partitions.h" |
| #include "platform/wtf/text/CharacterNames.h" |
| #include "platform/wtf/text/Unicode.h" |
| |
| namespace blink { |
| |
| const float kSmallCapsFontSizeMultiplier = 0.7f; |
| const float kEmphasisMarkFontSizeMultiplier = 0.5f; |
| |
| #if OS(LINUX) || OS(ANDROID) |
| // This is the largest VDMX table which we'll try to load and parse. |
| static const size_t kMaxVDMXTableSize = 1024 * 1024; // 1 MB |
| #endif |
| |
| SimpleFontData::SimpleFontData(const FontPlatformData& platform_data, |
| PassRefPtr<CustomFontData> custom_data, |
| bool is_text_orientation_fallback, |
| bool subpixel_ascent_descent) |
| : max_char_width_(-1), |
| avg_char_width_(-1), |
| platform_data_(platform_data), |
| vertical_data_(nullptr), |
| custom_font_data_(std::move(custom_data)), |
| is_text_orientation_fallback_(is_text_orientation_fallback), |
| has_vertical_glyphs_(false), |
| visual_overflow_inflation_for_ascent_(0), |
| visual_overflow_inflation_for_descent_(0) { |
| PlatformInit(subpixel_ascent_descent); |
| PlatformGlyphInit(); |
| if (platform_data.IsVerticalAnyUpright() && !is_text_orientation_fallback) { |
| vertical_data_ = platform_data.VerticalData(); |
| has_vertical_glyphs_ = |
| vertical_data_.Get() && vertical_data_->HasVerticalMetrics(); |
| } |
| } |
| |
| SimpleFontData::SimpleFontData(const FontPlatformData& platform_data, |
| PassRefPtr<OpenTypeVerticalData> vertical_data) |
| : platform_data_(platform_data), |
| vertical_data_(std::move(vertical_data)), |
| is_text_orientation_fallback_(false), |
| has_vertical_glyphs_(false), |
| visual_overflow_inflation_for_ascent_(0), |
| visual_overflow_inflation_for_descent_(0) {} |
| |
| void SimpleFontData::PlatformInit(bool subpixel_ascent_descent) { |
| if (!platform_data_.size()) { |
| font_metrics_.Reset(); |
| avg_char_width_ = 0; |
| max_char_width_ = 0; |
| return; |
| } |
| |
| SkPaint::FontMetrics metrics; |
| |
| platform_data_.SetupPaint(&paint_); |
| paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding); |
| paint_.getFontMetrics(&metrics); |
| SkTypeface* face = paint_.getTypeface(); |
| DCHECK(face); |
| |
| int vdmx_ascent = 0, vdmx_descent = 0; |
| bool is_vdmx_valid = false; |
| |
| #if OS(LINUX) || OS(ANDROID) |
| // Manually digging up VDMX metrics is only applicable when bytecode hinting |
| // using FreeType. With DirectWrite or CoreText, no bytecode hinting is ever |
| // done. This code should be pushed into FreeType (hinted font metrics). |
| static const uint32_t kVdmxTag = SkSetFourByteTag('V', 'D', 'M', 'X'); |
| int pixel_size = platform_data_.size() + 0.5; |
| if (!paint_.isAutohinted() && |
| (paint_.getHinting() == SkPaint::kFull_Hinting || |
| paint_.getHinting() == SkPaint::kNormal_Hinting)) { |
| size_t vdmx_size = face->getTableSize(kVdmxTag); |
| if (vdmx_size && vdmx_size < kMaxVDMXTableSize) { |
| uint8_t* vdmx_table = (uint8_t*)WTF::Partitions::FastMalloc( |
| vdmx_size, WTF_HEAP_PROFILER_TYPE_NAME(SimpleFontData)); |
| if (vdmx_table && |
| face->getTableData(kVdmxTag, 0, vdmx_size, vdmx_table) == vdmx_size && |
| ParseVDMX(&vdmx_ascent, &vdmx_descent, vdmx_table, vdmx_size, |
| pixel_size)) |
| is_vdmx_valid = true; |
| WTF::Partitions::FastFree(vdmx_table); |
| } |
| } |
| #endif |
| |
| float ascent; |
| float descent; |
| |
| // Beware those who step here: This code is designed to match Win32 font |
| // metrics *exactly* except: |
| // - the adjustment of ascent/descent on Linux/Android |
| // - metrics.fAscent and .fDesscent are not rounded to int for tiny fonts |
| if (is_vdmx_valid) { |
| ascent = vdmx_ascent; |
| descent = -vdmx_descent; |
| } else if (subpixel_ascent_descent && |
| (-metrics.fAscent < 3 || |
| -metrics.fAscent + metrics.fDescent < 2)) { |
| // For tiny fonts, the rounding of fAscent and fDescent results in equal |
| // baseline for different types of text baselines (crbug.com/338908). |
| // Please see CanvasRenderingContext2D::getFontBaseline for the heuristic. |
| ascent = -metrics.fAscent; |
| descent = metrics.fDescent; |
| } else { |
| ascent = SkScalarRoundToScalar(-metrics.fAscent); |
| descent = SkScalarRoundToScalar(metrics.fDescent); |
| |
| if (ascent < -metrics.fAscent) |
| visual_overflow_inflation_for_ascent_ = 1; |
| if (descent < metrics.fDescent) { |
| visual_overflow_inflation_for_descent_ = 1; |
| #if OS(LINUX) || OS(ANDROID) |
| // When subpixel positioning is enabled, if the descent is rounded down, |
| // the descent part of the glyph may be truncated when displayed in a |
| // 'overflow: hidden' container. To avoid that, borrow 1 unit from the |
| // ascent when possible. |
| if (PlatformData().GetFontRenderStyle().use_subpixel_positioning && |
| ascent >= 1) { |
| ++descent; |
| --ascent; |
| // We should inflate overflow 1 more pixel for ascent instead. |
| visual_overflow_inflation_for_descent_ = 0; |
| ++visual_overflow_inflation_for_ascent_; |
| } |
| #endif |
| } |
| } |
| |
| #if OS(MACOSX) |
| // We are preserving this ascent hack to match Safari's ascent adjustment |
| // in their SimpleFontDataMac.mm, for details see crbug.com/445830. |
| // We need to adjust Times, Helvetica, and Courier to closely match the |
| // vertical metrics of their Microsoft counterparts that are the de facto |
| // web standard. The AppKit adjustment of 20% is too big and is |
| // incorrectly added to line spacing, so we use a 15% adjustment instead |
| // and add it to the ascent. |
| DEFINE_STATIC_LOCAL(AtomicString, times_name, ("Times")); |
| DEFINE_STATIC_LOCAL(AtomicString, helvetica_name, ("Helvetica")); |
| DEFINE_STATIC_LOCAL(AtomicString, courier_name, ("Courier")); |
| String family_name = platform_data_.FontFamilyName(); |
| if (family_name == times_name || family_name == helvetica_name || |
| family_name == courier_name) |
| ascent += floorf(((ascent + descent) * 0.15f) + 0.5f); |
| #endif |
| |
| font_metrics_.SetAscent(ascent); |
| font_metrics_.SetDescent(descent); |
| |
| float x_height; |
| if (metrics.fXHeight) { |
| x_height = metrics.fXHeight; |
| #if OS(MACOSX) |
| // Mac OS CTFontGetXHeight reports the bounding box height of x, |
| // including parts extending below the baseline and apparently no x-height |
| // value from the OS/2 table. However, the CSS ex unit |
| // expects only parts above the baseline, hence measuring the glyph: |
| // http://www.w3.org/TR/css3-values/#ex-unit |
| const Glyph x_glyph = GlyphForCharacter('x'); |
| if (x_glyph) { |
| FloatRect glyph_bounds(BoundsForGlyph(x_glyph)); |
| // SkGlyph bounds, y down, based on rendering at (0,0). |
| x_height = -glyph_bounds.Y(); |
| } |
| #endif |
| font_metrics_.SetXHeight(x_height); |
| } else { |
| x_height = ascent * 0.56; // Best guess from Windows font metrics. |
| font_metrics_.SetXHeight(x_height); |
| font_metrics_.SetHasXHeight(false); |
| } |
| |
| float line_gap = SkScalarToFloat(metrics.fLeading); |
| font_metrics_.SetLineGap(line_gap); |
| font_metrics_.SetLineSpacing(lroundf(ascent) + lroundf(descent) + |
| lroundf(line_gap)); |
| |
| if (PlatformData().IsVerticalAnyUpright() && !IsTextOrientationFallback()) { |
| static const uint32_t kVheaTag = SkSetFourByteTag('v', 'h', 'e', 'a'); |
| static const uint32_t kVorgTag = SkSetFourByteTag('V', 'O', 'R', 'G'); |
| size_t vhea_size = face->getTableSize(kVheaTag); |
| size_t vorg_size = face->getTableSize(kVorgTag); |
| if ((vhea_size > 0) || (vorg_size > 0)) |
| has_vertical_glyphs_ = true; |
| } |
| |
| // In WebKit/WebCore/platform/graphics/SimpleFontData.cpp, m_spaceWidth is |
| // calculated for us, but we need to calculate m_maxCharWidth and |
| // m_avgCharWidth in order for text entry widgets to be sized correctly. |
| #if OS(WIN) |
| max_char_width_ = SkScalarRoundToInt(metrics.fMaxCharWidth); |
| |
| // Older version of the DirectWrite API doesn't implement support for max |
| // char width. Fall back on a multiple of the ascent. This is entirely |
| // arbitrary but comes pretty close to the expected value in most cases. |
| if (max_char_width_ < 1) |
| max_char_width_ = ascent * 2; |
| #elif OS(MACOSX) |
| // FIXME: The current avg/max character width calculation is not ideal, |
| // it should check either the OS2 table or, better yet, query FontMetrics. |
| // Sadly FontMetrics provides incorrect data on Mac at the moment. |
| // https://crbug.com/420901 |
| max_char_width_ = std::max(avg_char_width_, font_metrics_.FloatAscent()); |
| #else |
| // Better would be to rely on either fMaxCharWidth or fAveCharWidth. |
| // skbug.com/3087 |
| max_char_width_ = SkScalarRoundToInt(metrics.fXMax - metrics.fXMin); |
| |
| #endif |
| |
| #if !OS(MACOSX) |
| if (metrics.fAvgCharWidth) { |
| avg_char_width_ = SkScalarRoundToInt(metrics.fAvgCharWidth); |
| } else { |
| #endif |
| avg_char_width_ = x_height; |
| const Glyph x_glyph = GlyphForCharacter('x'); |
| if (x_glyph) { |
| avg_char_width_ = WidthForGlyph(x_glyph); |
| } |
| #if !OS(MACOSX) |
| } |
| #endif |
| |
| if (int units_per_em = face->getUnitsPerEm()) |
| font_metrics_.SetUnitsPerEm(units_per_em); |
| } |
| |
| void SimpleFontData::PlatformGlyphInit() { |
| SkTypeface* typeface = PlatformData().Typeface(); |
| if (!typeface->countGlyphs()) { |
| space_glyph_ = 0; |
| space_width_ = 0; |
| zero_glyph_ = 0; |
| missing_glyph_data_.font_data = this; |
| missing_glyph_data_.glyph = 0; |
| return; |
| } |
| |
| // Nasty hack to determine if we should round or ceil space widths. |
| // If the font is monospace or fake monospace we ceil to ensure that |
| // every character and the space are the same width. Otherwise we round. |
| space_glyph_ = GlyphForCharacter(' '); |
| float width = WidthForGlyph(space_glyph_); |
| space_width_ = width; |
| zero_glyph_ = GlyphForCharacter('0'); |
| font_metrics_.SetZeroWidth(WidthForGlyph(zero_glyph_)); |
| |
| missing_glyph_data_.font_data = this; |
| missing_glyph_data_.glyph = 0; |
| } |
| |
| const SimpleFontData* SimpleFontData::FontDataForCharacter(UChar32) const { |
| return this; |
| } |
| |
| Glyph SimpleFontData::GlyphForCharacter(UChar32 codepoint) const { |
| uint16_t glyph; |
| SkTypeface* typeface = PlatformData().Typeface(); |
| CHECK(typeface); |
| typeface->charsToGlyphs(&codepoint, SkTypeface::kUTF32_Encoding, &glyph, 1); |
| return glyph; |
| } |
| |
| bool SimpleFontData::IsSegmented() const { |
| return false; |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::VerticalRightOrientationFontData() |
| const { |
| if (!derived_font_data_) |
| derived_font_data_ = DerivedFontData::Create(); |
| if (!derived_font_data_->vertical_right_orientation) { |
| FontPlatformData vertical_right_platform_data(platform_data_); |
| vertical_right_platform_data.SetOrientation(FontOrientation::kHorizontal); |
| derived_font_data_->vertical_right_orientation = |
| Create(vertical_right_platform_data, |
| IsCustomFont() ? CustomFontData::Create() : nullptr, true); |
| } |
| return derived_font_data_->vertical_right_orientation; |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::UprightOrientationFontData() const { |
| if (!derived_font_data_) |
| derived_font_data_ = DerivedFontData::Create(); |
| if (!derived_font_data_->upright_orientation) |
| derived_font_data_->upright_orientation = |
| Create(platform_data_, |
| IsCustomFont() ? CustomFontData::Create() : nullptr, true); |
| return derived_font_data_->upright_orientation; |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::SmallCapsFontData( |
| const FontDescription& font_description) const { |
| if (!derived_font_data_) |
| derived_font_data_ = DerivedFontData::Create(); |
| if (!derived_font_data_->small_caps) |
| derived_font_data_->small_caps = |
| CreateScaledFontData(font_description, kSmallCapsFontSizeMultiplier); |
| |
| return derived_font_data_->small_caps; |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::EmphasisMarkFontData( |
| const FontDescription& font_description) const { |
| if (!derived_font_data_) |
| derived_font_data_ = DerivedFontData::Create(); |
| if (!derived_font_data_->emphasis_mark) |
| derived_font_data_->emphasis_mark = |
| CreateScaledFontData(font_description, kEmphasisMarkFontSizeMultiplier); |
| |
| return derived_font_data_->emphasis_mark; |
| } |
| |
| bool SimpleFontData::IsTextOrientationFallbackOf( |
| const SimpleFontData* font_data) const { |
| if (!IsTextOrientationFallback() || !font_data->derived_font_data_) |
| return false; |
| return font_data->derived_font_data_->upright_orientation == this || |
| font_data->derived_font_data_->vertical_right_orientation == this; |
| } |
| |
| std::unique_ptr<SimpleFontData::DerivedFontData> |
| SimpleFontData::DerivedFontData::Create() { |
| return WTF::WrapUnique(new DerivedFontData()); |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::CreateScaledFontData( |
| const FontDescription& font_description, |
| float scale_factor) const { |
| const float scaled_size = |
| lroundf(font_description.ComputedSize() * scale_factor); |
| return SimpleFontData::Create( |
| FontPlatformData(platform_data_, scaled_size), |
| IsCustomFont() ? CustomFontData::Create() : nullptr); |
| } |
| |
| // Internal leadings can be distributed to ascent and descent. |
| // ------------------------------------------- |
| // | - Internal Leading (in ascent) |
| // |-------------------------------- |
| // Ascent - | | |
| // | | |
| // | | - Em height |
| // ----------|--------------| |
| // | | |
| // Descent - |-------------------------------- |
| // | - Internal Leading (in descent) |
| // ------------------------------------------- |
| LayoutUnit SimpleFontData::EmHeightAscent(FontBaseline baseline_type) const { |
| if (baseline_type == kAlphabeticBaseline) { |
| if (!em_height_ascent_) |
| ComputeEmHeightMetrics(); |
| return em_height_ascent_; |
| } |
| LayoutUnit em_height = LayoutUnit::FromFloatRound(PlatformData().size()); |
| return em_height - em_height / 2; |
| } |
| |
| LayoutUnit SimpleFontData::EmHeightDescent(FontBaseline baseline_type) const { |
| if (baseline_type == kAlphabeticBaseline) { |
| if (!em_height_descent_) |
| ComputeEmHeightMetrics(); |
| return em_height_descent_; |
| } |
| LayoutUnit em_height = LayoutUnit::FromFloatRound(PlatformData().size()); |
| return em_height / 2; |
| } |
| |
| static std::pair<int16_t, int16_t> TypoAscenderAndDescender( |
| SkTypeface* typeface) { |
| // TODO(kojii): This should move to Skia once finalized. We can then move |
| // EmHeightAscender/Descender to FontMetrics. |
| int16_t buffer[2]; |
| size_t size = typeface->getTableData(SkSetFourByteTag('O', 'S', '/', '2'), 68, |
| sizeof(buffer), buffer); |
| if (size == sizeof(buffer)) { |
| return std::make_pair(static_cast<int16_t>(ntohs(buffer[0])), |
| -static_cast<int16_t>(ntohs(buffer[1]))); |
| } |
| return std::make_pair(0, 0); |
| } |
| |
| void SimpleFontData::ComputeEmHeightMetrics() const { |
| // Compute em height metrics from OS/2 sTypoAscender and sTypoDescender. |
| SkTypeface* typeface = platform_data_.Typeface(); |
| int16_t typo_ascender, typo_descender; |
| std::tie(typo_ascender, typo_descender) = TypoAscenderAndDescender(typeface); |
| if (typo_ascender > 0 && |
| NormalizeEmHeightMetrics(typo_ascender, typo_ascender + typo_descender)) { |
| return; |
| } |
| |
| // As the last resort, compute em height metrics from our ascent/descent. |
| const FontMetrics& font_metrics = GetFontMetrics(); |
| if (NormalizeEmHeightMetrics(font_metrics.FloatAscent(), |
| font_metrics.FloatHeight())) { |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| bool SimpleFontData::NormalizeEmHeightMetrics(float ascent, |
| float height) const { |
| if (height <= 0 || ascent < 0 || ascent > height) |
| return false; |
| // While the OpenType specification recommends the sum of sTypoAscender and |
| // sTypoDescender to equal 1em, most fonts do not follow. Most Latin fonts |
| // set to smaller than 1em, and many tall scripts set to larger than 1em. |
| // https://www.microsoft.com/typography/otspec/recom.htm#OS2 |
| // To ensure the sum of ascent and descent is the "em height", normalize by |
| // keeping the ratio of sTypoAscender:sTypoDescender. |
| // This matches to how Gecko computes "em height": |
| // https://github.com/whatwg/html/issues/2470#issuecomment-291425136 |
| float em_height = PlatformData().size(); |
| em_height_ascent_ = LayoutUnit::FromFloatRound(ascent * em_height / height); |
| em_height_descent_ = |
| LayoutUnit::FromFloatRound(em_height) - em_height_ascent_; |
| return true; |
| } |
| |
| FloatRect SimpleFontData::PlatformBoundsForGlyph(Glyph glyph) const { |
| if (!platform_data_.size()) |
| return FloatRect(); |
| |
| static_assert(sizeof(glyph) == 2, "Glyph id should not be truncated."); |
| |
| SkRect bounds; |
| SkiaTextMetrics(&paint_).GetSkiaBoundsForGlyph(glyph, &bounds); |
| return FloatRect(bounds); |
| } |
| |
| float SimpleFontData::PlatformWidthForGlyph(Glyph glyph) const { |
| if (!platform_data_.size()) |
| return 0; |
| |
| static_assert(sizeof(glyph) == 2, "Glyph id should not be truncated."); |
| |
| return SkiaTextMetrics(&paint_).GetSkiaWidthForGlyph(glyph); |
| } |
| |
| } // namespace blink |