| /* |
| * 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 "SkPath.h" |
| #include "SkTypeface.h" |
| #include "SkTypes.h" |
| #include "platform/fonts/FontDescription.h" |
| #include "platform/fonts/GlyphPage.h" |
| #include "platform/fonts/VDMXParser.h" |
| #include "platform/fonts/skia/SkiaTextMetrics.h" |
| #include "platform/geometry/FloatRect.h" |
| #include "wtf/MathExtras.h" |
| #include "wtf/PtrUtil.h" |
| #include "wtf/allocator/Partitions.h" |
| #include "wtf/text/CharacterNames.h" |
| #include "wtf/text/Unicode.h" |
| #include <memory> |
| #include <unicode/unorm.h> |
| #include <unicode/utf16.h> |
| |
| namespace blink { |
| |
| const float smallCapsFontSizeMultiplier = 0.7f; |
| const float emphasisMarkFontSizeMultiplier = 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 maxVDMXTableSize = 1024 * 1024; // 1 MB |
| #endif |
| |
| SimpleFontData::SimpleFontData(const FontPlatformData& platformData, |
| PassRefPtr<CustomFontData> customData, |
| bool isTextOrientationFallback) |
| : m_maxCharWidth(-1), |
| m_avgCharWidth(-1), |
| m_platformData(platformData), |
| m_isTextOrientationFallback(isTextOrientationFallback), |
| m_verticalData(nullptr), |
| m_hasVerticalGlyphs(false), |
| m_customFontData(customData) { |
| platformInit(); |
| platformGlyphInit(); |
| if (platformData.isVerticalAnyUpright() && !isTextOrientationFallback) { |
| m_verticalData = platformData.verticalData(); |
| m_hasVerticalGlyphs = |
| m_verticalData.get() && m_verticalData->hasVerticalMetrics(); |
| } |
| } |
| |
| SimpleFontData::SimpleFontData(PassRefPtr<CustomFontData> customData, |
| float fontSize, |
| bool syntheticBold, |
| bool syntheticItalic) |
| : m_platformData( |
| FontPlatformData(fontSize, syntheticBold, syntheticItalic)), |
| m_isTextOrientationFallback(false), |
| m_verticalData(nullptr), |
| m_hasVerticalGlyphs(false), |
| m_customFontData(customData) {} |
| |
| void SimpleFontData::platformInit() { |
| if (!m_platformData.size()) { |
| m_fontMetrics.reset(); |
| m_avgCharWidth = 0; |
| m_maxCharWidth = 0; |
| return; |
| } |
| |
| SkPaint::FontMetrics metrics; |
| |
| m_platformData.setupPaint(&m_paint); |
| m_paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); |
| m_paint.getFontMetrics(&metrics); |
| SkTypeface* face = m_paint.getTypeface(); |
| ASSERT(face); |
| |
| int vdmxAscent = 0, vdmxDescent = 0; |
| bool isVDMXValid = 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 vdmxTag = SkSetFourByteTag('V', 'D', 'M', 'X'); |
| int pixelSize = m_platformData.size() + 0.5; |
| if (!m_paint.isAutohinted() && |
| (m_paint.getHinting() == SkPaint::kFull_Hinting || |
| m_paint.getHinting() == SkPaint::kNormal_Hinting)) { |
| size_t vdmxSize = face->getTableSize(vdmxTag); |
| if (vdmxSize && vdmxSize < maxVDMXTableSize) { |
| uint8_t* vdmxTable = (uint8_t*)WTF::Partitions::fastMalloc( |
| vdmxSize, WTF_HEAP_PROFILER_TYPE_NAME(SimpleFontData)); |
| if (vdmxTable && |
| face->getTableData(vdmxTag, 0, vdmxSize, vdmxTable) == vdmxSize && |
| parseVDMX(&vdmxAscent, &vdmxDescent, vdmxTable, vdmxSize, pixelSize)) |
| isVDMXValid = true; |
| WTF::Partitions::fastFree(vdmxTable); |
| } |
| } |
| #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). |
| if (isVDMXValid) { |
| ascent = vdmxAscent; |
| descent = -vdmxDescent; |
| } else { |
| ascent = SkScalarRoundToInt(-metrics.fAscent); |
| descent = SkScalarRoundToInt(metrics.fDescent); |
| #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. |
| // FIXME: This can be removed if sub-pixel ascent/descent is supported. |
| if (platformData().getFontRenderStyle().useSubpixelPositioning && |
| descent < SkScalarToFloat(metrics.fDescent) && ascent >= 1) { |
| ++descent; |
| --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, timesName, ("Times")); |
| DEFINE_STATIC_LOCAL(AtomicString, helveticaName, ("Helvetica")); |
| DEFINE_STATIC_LOCAL(AtomicString, courierName, ("Courier")); |
| String familyName = m_platformData.fontFamilyName(); |
| if (familyName == timesName || familyName == helveticaName || |
| familyName == courierName) |
| ascent += floorf(((ascent + descent) * 0.15f) + 0.5f); |
| #endif |
| |
| m_fontMetrics.setAscent(ascent); |
| m_fontMetrics.setDescent(descent); |
| |
| float xHeight; |
| if (metrics.fXHeight) { |
| xHeight = 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 xGlyph = glyphForCharacter('x'); |
| if (xGlyph) { |
| FloatRect glyphBounds(boundsForGlyph(xGlyph)); |
| // SkGlyph bounds, y down, based on rendering at (0,0). |
| xHeight = -glyphBounds.y(); |
| } |
| #endif |
| m_fontMetrics.setXHeight(xHeight); |
| } else { |
| xHeight = ascent * 0.56; // Best guess from Windows font metrics. |
| m_fontMetrics.setXHeight(xHeight); |
| m_fontMetrics.setHasXHeight(false); |
| } |
| |
| float lineGap = SkScalarToFloat(metrics.fLeading); |
| m_fontMetrics.setLineGap(lineGap); |
| m_fontMetrics.setLineSpacing(lroundf(ascent) + lroundf(descent) + |
| lroundf(lineGap)); |
| |
| if (platformData().isVerticalAnyUpright() && !isTextOrientationFallback()) { |
| static const uint32_t vheaTag = SkSetFourByteTag('v', 'h', 'e', 'a'); |
| static const uint32_t vorgTag = SkSetFourByteTag('V', 'O', 'R', 'G'); |
| size_t vheaSize = face->getTableSize(vheaTag); |
| size_t vorgSize = face->getTableSize(vorgTag); |
| if ((vheaSize > 0) || (vorgSize > 0)) |
| m_hasVerticalGlyphs = 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) |
| m_maxCharWidth = 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 (m_maxCharWidth < 1) |
| m_maxCharWidth = 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 |
| m_maxCharWidth = std::max(m_avgCharWidth, m_fontMetrics.floatAscent()); |
| #else |
| // Better would be to rely on either fMaxCharWidth or fAveCharWidth. |
| // skbug.com/3087 |
| m_maxCharWidth = SkScalarRoundToInt(metrics.fXMax - metrics.fXMin); |
| |
| #endif |
| |
| #if !OS(MACOSX) |
| if (metrics.fAvgCharWidth) { |
| m_avgCharWidth = SkScalarRoundToInt(metrics.fAvgCharWidth); |
| } else { |
| #endif |
| m_avgCharWidth = xHeight; |
| const Glyph xGlyph = glyphForCharacter('x'); |
| if (xGlyph) { |
| m_avgCharWidth = widthForGlyph(xGlyph); |
| } |
| #if !OS(MACOSX) |
| } |
| #endif |
| |
| if (int unitsPerEm = face->getUnitsPerEm()) |
| m_fontMetrics.setUnitsPerEm(unitsPerEm); |
| } |
| |
| void SimpleFontData::platformGlyphInit() { |
| SkTypeface* typeface = platformData().typeface(); |
| if (!typeface->countGlyphs()) { |
| m_spaceGlyph = 0; |
| m_spaceWidth = 0; |
| m_zeroGlyph = 0; |
| m_missingGlyphData.fontData = this; |
| m_missingGlyphData.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. |
| m_spaceGlyph = glyphForCharacter(' '); |
| float width = widthForGlyph(m_spaceGlyph); |
| m_spaceWidth = width; |
| m_zeroGlyph = glyphForCharacter('0'); |
| m_fontMetrics.setZeroWidth(widthForGlyph(m_zeroGlyph)); |
| |
| m_missingGlyphData.fontData = this; |
| m_missingGlyphData.glyph = 0; |
| } |
| |
| SimpleFontData::~SimpleFontData() { |
| if (isCustomFont()) |
| GlyphPageTreeNode::pruneTreeCustomFontData(this); |
| else |
| GlyphPageTreeNode::pruneTreeFontData(this); |
| } |
| |
| const SimpleFontData* SimpleFontData::fontDataForCharacter(UChar32) const { |
| return this; |
| } |
| |
| Glyph SimpleFontData::glyphForCharacter(UChar32 codepoint) const { |
| uint16_t glyph; |
| SkTypeface* typeface = platformData().typeface(); |
| RELEASE_ASSERT(typeface); |
| typeface->charsToGlyphs(&codepoint, SkTypeface::kUTF32_Encoding, &glyph, 1); |
| return glyph; |
| } |
| |
| bool SimpleFontData::isSegmented() const { |
| return false; |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::verticalRightOrientationFontData() |
| const { |
| if (!m_derivedFontData) |
| m_derivedFontData = DerivedFontData::create(isCustomFont()); |
| if (!m_derivedFontData->verticalRightOrientation) { |
| FontPlatformData verticalRightPlatformData(m_platformData); |
| verticalRightPlatformData.setOrientation(FontOrientation::Horizontal); |
| m_derivedFontData->verticalRightOrientation = |
| create(verticalRightPlatformData, |
| isCustomFont() ? CustomFontData::create() : nullptr, true); |
| } |
| return m_derivedFontData->verticalRightOrientation; |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::uprightOrientationFontData() const { |
| if (!m_derivedFontData) |
| m_derivedFontData = DerivedFontData::create(isCustomFont()); |
| if (!m_derivedFontData->uprightOrientation) |
| m_derivedFontData->uprightOrientation = |
| create(m_platformData, |
| isCustomFont() ? CustomFontData::create() : nullptr, true); |
| return m_derivedFontData->uprightOrientation; |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::smallCapsFontData( |
| const FontDescription& fontDescription) const { |
| if (!m_derivedFontData) |
| m_derivedFontData = DerivedFontData::create(isCustomFont()); |
| if (!m_derivedFontData->smallCaps) |
| m_derivedFontData->smallCaps = |
| createScaledFontData(fontDescription, smallCapsFontSizeMultiplier); |
| |
| return m_derivedFontData->smallCaps; |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::emphasisMarkFontData( |
| const FontDescription& fontDescription) const { |
| if (!m_derivedFontData) |
| m_derivedFontData = DerivedFontData::create(isCustomFont()); |
| if (!m_derivedFontData->emphasisMark) |
| m_derivedFontData->emphasisMark = |
| createScaledFontData(fontDescription, emphasisMarkFontSizeMultiplier); |
| |
| return m_derivedFontData->emphasisMark; |
| } |
| |
| bool SimpleFontData::isTextOrientationFallbackOf( |
| const SimpleFontData* fontData) const { |
| if (!isTextOrientationFallback() || !fontData->m_derivedFontData) |
| return false; |
| return fontData->m_derivedFontData->uprightOrientation == this || |
| fontData->m_derivedFontData->verticalRightOrientation == this; |
| } |
| |
| std::unique_ptr<SimpleFontData::DerivedFontData> |
| SimpleFontData::DerivedFontData::create(bool forCustomFont) { |
| return wrapUnique(new DerivedFontData(forCustomFont)); |
| } |
| |
| SimpleFontData::DerivedFontData::~DerivedFontData() { |
| if (!forCustomFont) |
| return; |
| |
| if (smallCaps) |
| GlyphPageTreeNode::pruneTreeCustomFontData(smallCaps.get()); |
| if (emphasisMark) |
| GlyphPageTreeNode::pruneTreeCustomFontData(emphasisMark.get()); |
| if (verticalRightOrientation) |
| GlyphPageTreeNode::pruneTreeCustomFontData(verticalRightOrientation.get()); |
| if (uprightOrientation) |
| GlyphPageTreeNode::pruneTreeCustomFontData(uprightOrientation.get()); |
| } |
| |
| PassRefPtr<SimpleFontData> SimpleFontData::createScaledFontData( |
| const FontDescription& fontDescription, |
| float scaleFactor) const { |
| const float scaledSize = |
| lroundf(fontDescription.computedSize() * scaleFactor); |
| return SimpleFontData::create( |
| FontPlatformData(m_platformData, scaledSize), |
| isCustomFont() ? CustomFontData::create() : nullptr); |
| } |
| |
| FloatRect SimpleFontData::platformBoundsForGlyph(Glyph glyph) const { |
| if (!m_platformData.size()) |
| return FloatRect(); |
| |
| static_assert(sizeof(glyph) == 2, "Glyph id should not be truncated."); |
| |
| SkRect bounds; |
| SkiaTextMetrics(&m_paint).getSkiaBoundsForGlyph(glyph, &bounds); |
| return FloatRect(bounds); |
| } |
| |
| float SimpleFontData::platformWidthForGlyph(Glyph glyph) const { |
| if (!m_platformData.size()) |
| return 0; |
| |
| static_assert(sizeof(glyph) == 2, "Glyph id should not be truncated."); |
| |
| return SkiaTextMetrics(&m_paint).getSkiaWidthForGlyph(glyph); |
| } |
| |
| bool SimpleFontData::fillGlyphPage(GlyphPage* pageToFill, |
| unsigned offset, |
| unsigned length, |
| UChar* buffer, |
| unsigned bufferLength) const { |
| if (U16_IS_LEAD(buffer[bufferLength - 1])) { |
| DLOG(ERROR) << "Last UTF-16 code unit is high-surrogate."; |
| return false; |
| } |
| |
| SkTypeface* typeface = platformData().typeface(); |
| if (!typeface) { |
| DLOG(ERROR) << "fillGlyphPage called on an empty Skia typeface."; |
| return false; |
| } |
| |
| SkAutoSTMalloc<GlyphPage::size, uint16_t> glyphStorage(length); |
| uint16_t* glyphs = glyphStorage.get(); |
| typeface->charsToGlyphs(buffer, SkTypeface::kUTF16_Encoding, glyphs, length); |
| |
| bool haveGlyphs = false; |
| for (unsigned i = 0; i < length; i++) { |
| if (glyphs[i]) { |
| pageToFill->setGlyphDataForIndex(offset + i, glyphs[i], this); |
| haveGlyphs = true; |
| } |
| } |
| |
| return haveGlyphs; |
| } |
| |
| } // namespace blink |