/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::rtl; using namespace ::utl; FontMetric::FontMetric() : mnAscent( 0 ), mnDescent( 0 ), mnIntLeading( 0 ), mnExtLeading( 0 ), mnLineHeight( 0 ), mnSlant( 0 ), mnBulletOffset( 0 ), mnHangingBaseline( 0 ), mbFullstopCentered( false ) {} FontMetric::FontMetric( const FontMetric& rFontMetric ) = default; FontMetric::FontMetric(vcl::font::PhysicalFontFace const& rFace) : FontMetric() { SetFamilyName(rFace.GetFamilyName()); SetStyleName(rFace.GetStyleName()); SetCharSet(rFace.IsMicrosoftSymbolEncoded() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE); SetFamily(rFace.GetFamilyType()); SetPitch(rFace.GetPitch()); SetWeight(rFace.GetWeight()); SetItalic(rFace.GetItalic()); SetAlignment(TextAlign::ALIGN_TOP); SetWidthType(rFace.GetWidthType()); SetQuality(rFace.GetQuality() ); } FontMetric::~FontMetric() { } FontMetric& FontMetric::operator=(const FontMetric& rFontMetric) = default; FontMetric& FontMetric::operator=(FontMetric&& rFontMetric) = default; bool FontMetric::EqualNoBase( const FontMetric& r ) const { if (mbFullstopCentered != r.mbFullstopCentered) return false; if( mnAscent != r.mnAscent ) return false; if( mnDescent != r.mnDescent ) return false; if( mnIntLeading != r.mnIntLeading ) return false; if( mnExtLeading != r.mnExtLeading ) return false; if( mnSlant != r.mnSlant ) return false; return true; } bool FontMetric::operator==( const FontMetric& r ) const { if( Font::operator!=(r) ) return false; return EqualNoBase(r); } bool FontMetric::EqualIgnoreColor( const FontMetric& r ) const { if( !Font::EqualIgnoreColor(r) ) return false; return EqualNoBase(r); } size_t FontMetric::GetHashValueNoBase() const { size_t hash = 0; o3tl::hash_combine( hash, mbFullstopCentered ); o3tl::hash_combine( hash, mnAscent ); o3tl::hash_combine( hash, mnDescent ); o3tl::hash_combine( hash, mnIntLeading ); o3tl::hash_combine( hash, mnExtLeading ); o3tl::hash_combine( hash, mnSlant ); return hash; } size_t FontMetric::GetHashValueIgnoreColor() const { size_t hash = GetHashValueNoBase(); o3tl::hash_combine( hash, Font::GetHashValueIgnoreColor()); return hash; } FontMetricData::FontMetricData( const vcl::font::FontSelectPattern& rFontSelData ) : FontAttributes( rFontSelData ) , mnHeight ( rFontSelData.mnHeight ) , mnWidth ( rFontSelData.mnWidth ) , mnOrientation( static_cast(rFontSelData.mnOrientation) ) , mnAscent( 0 ) , mnDescent( 0 ) , mnIntLeading( 0 ) , mnExtLeading( 0 ) , mnSlant( 0 ) , mnMinKashida( 0 ) , mnHangingBaseline( 0 ) , mbFullstopCentered( false ) , mnBulletOffset( 0 ) , mnUnderlineSize( 0 ) , mnUnderlineOffset( 0 ) , mnBUnderlineSize( 0 ) , mnBUnderlineOffset( 0 ) , mnDUnderlineSize( 0 ) , mnDUnderlineOffset1( 0 ) , mnDUnderlineOffset2( 0 ) , mnWUnderlineSize( 0 ) , mnWUnderlineOffset( 0 ) , mnAboveUnderlineSize( 0 ) , mnAboveUnderlineOffset( 0 ) , mnAboveBUnderlineSize( 0 ) , mnAboveBUnderlineOffset( 0 ) , mnAboveDUnderlineSize( 0 ) , mnAboveDUnderlineOffset1( 0 ) , mnAboveDUnderlineOffset2( 0 ) , mnAboveWUnderlineSize( 0 ) , mnAboveWUnderlineOffset( 0 ) , mnStrikeoutSize( 0 ) , mnStrikeoutOffset( 0 ) , mnBStrikeoutSize( 0 ) , mnBStrikeoutOffset( 0 ) , mnDStrikeoutSize( 0 ) , mnDStrikeoutOffset1( 0 ) , mnDStrikeoutOffset2( 0 ) { // initialize the used font name sal_Int32 nTokenPos = 0; SetFamilyName( OUString(GetNextFontToken( rFontSelData.GetFamilyName(), nTokenPos )) ); SetStyleName( rFontSelData.GetStyleName() ); } bool FontMetricData::ShouldNotUseUnderlineMetrics() const { if (utl::ConfigManager::IsFuzzing()) return false; css::uno::Sequence rNoUnderlineMetricsList( officecfg::Office::Common::Misc::FontsDontUseUnderlineMetrics::get()); if (comphelper::findValue(rNoUnderlineMetricsList, GetFamilyName()) != -1) { SAL_INFO("vcl.gdi.fontmetric", "Not using underline metrics for: " << GetFamilyName()); return true; } return false; } bool FontMetricData::ImplInitTextLineSizeHarfBuzz(LogicalFontInstance* pFont) { if (ShouldNotUseUnderlineMetrics()) return false; auto* pHbFont = pFont->GetHbFont(); hb_position_t nUnderlineSize; if (!hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE, &nUnderlineSize)) return false; hb_position_t nUnderlineOffset; if (!hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET, &nUnderlineOffset)) return false; hb_position_t nStrikeoutSize; if (!hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE, &nStrikeoutSize)) return false; hb_position_t nStrikeoutOffset; if (!hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET, &nStrikeoutOffset)) return false; double fScale = 0; pFont->GetScale(nullptr, &fScale); double nOffset = -nUnderlineOffset * fScale; double nSize = nUnderlineSize * fScale; double nSize2 = nSize / 2.; double nBSize = nSize * 2.; double n2Size = nBSize / 3.; mnUnderlineSize = std::ceil(nSize); mnUnderlineOffset = std::ceil(nOffset); mnBUnderlineSize = std::ceil(nBSize); mnBUnderlineOffset = std::ceil(nOffset - nSize2); mnDUnderlineSize = std::ceil(n2Size); mnDUnderlineOffset1 = mnBUnderlineOffset; mnDUnderlineOffset2 = mnBUnderlineOffset + mnDUnderlineSize * 2; mnWUnderlineSize = mnBUnderlineSize; mnWUnderlineOffset = std::ceil(nOffset + nSize); nOffset = -nStrikeoutOffset * fScale; nSize = nStrikeoutSize * fScale; nSize2 = nSize / 2.; nBSize = nSize * 2.; n2Size = nBSize / 3.; mnStrikeoutSize = std::ceil(nSize); mnStrikeoutOffset = std::ceil(nOffset); mnBStrikeoutSize = std::ceil(nBSize); mnBStrikeoutOffset = std::round(nOffset - nSize2); mnDStrikeoutSize = std::ceil(n2Size); mnDStrikeoutOffset1 = mnBStrikeoutOffset; mnDStrikeoutOffset2 = mnBStrikeoutOffset + mnDStrikeoutSize * 2; return true; } void FontMetricData::ImplInitTextLineSize( const OutputDevice* pDev ) { mnBulletOffset = ( pDev->GetTextWidth( OUString( u' ' ) ) - pDev->GetTextWidth( OUString( u'\x00b7' ) ) ) >> 1 ; if (ImplInitTextLineSizeHarfBuzz(const_cast(pDev->GetFontInstance()))) return; tools::Long nDescent = mnDescent; if ( nDescent <= 0 ) { nDescent = mnAscent / 10; if ( !nDescent ) nDescent = 1; } // #i55341# for some fonts it is not a good idea to calculate // their text line metrics from the real font descent // => work around this problem just for these fonts if( 3*nDescent > mnAscent ) nDescent = mnAscent / 3; tools::Long nLineHeight = ((nDescent*25)+50) / 100; if ( !nLineHeight ) nLineHeight = 1; tools::Long nLineHeight2 = nLineHeight / 2; if ( !nLineHeight2 ) nLineHeight2 = 1; tools::Long nBLineHeight = ((nDescent*50)+50) / 100; if ( nBLineHeight == nLineHeight ) nBLineHeight++; tools::Long nBLineHeight2 = nBLineHeight/2; if ( !nBLineHeight2 ) nBLineHeight2 = 1; tools::Long n2LineHeight = ((nDescent*16)+50) / 100; if ( !n2LineHeight ) n2LineHeight = 1; tools::Long n2LineDY = n2LineHeight; /* #117909# * add some pixels to minimum double line distance on higher resolution devices */ tools::Long nMin2LineDY = 1 + pDev->GetDPIY()/150; if ( n2LineDY < nMin2LineDY ) n2LineDY = nMin2LineDY; tools::Long n2LineDY2 = n2LineDY/2; if ( !n2LineDY2 ) n2LineDY2 = 1; const vcl::Font& rFont ( pDev->GetFont() ); bool bCJKVertical = MsLangId::isCJK(rFont.GetLanguage()) && rFont.IsVertical(); tools::Long nUnderlineOffset = bCJKVertical ? mnDescent : (mnDescent/2 + 1); tools::Long nStrikeoutOffset = rFont.IsVertical() ? -((mnAscent - mnDescent) / 2) : -((mnAscent - mnIntLeading) / 3); mnUnderlineSize = nLineHeight; mnUnderlineOffset = nUnderlineOffset - nLineHeight2; mnBUnderlineSize = nBLineHeight; mnBUnderlineOffset = nUnderlineOffset - nBLineHeight2; mnDUnderlineSize = n2LineHeight; mnDUnderlineOffset1 = nUnderlineOffset - n2LineDY2 - n2LineHeight; mnDUnderlineOffset2 = mnDUnderlineOffset1 + n2LineDY + n2LineHeight; tools::Long nWCalcSize = mnDescent; if ( nWCalcSize < 6 ) { if ( (nWCalcSize == 1) || (nWCalcSize == 2) ) mnWUnderlineSize = nWCalcSize; else mnWUnderlineSize = 3; } else mnWUnderlineSize = ((nWCalcSize*50)+50) / 100; // Don't assume that wavelines are never placed below the descent, because for most fonts the waveline // is drawn into the text mnWUnderlineOffset = nUnderlineOffset; mnStrikeoutSize = nLineHeight; mnStrikeoutOffset = nStrikeoutOffset - nLineHeight2; mnBStrikeoutSize = nBLineHeight; mnBStrikeoutOffset = nStrikeoutOffset - nBLineHeight2; mnDStrikeoutSize = n2LineHeight; mnDStrikeoutOffset1 = nStrikeoutOffset - n2LineDY2 - n2LineHeight; mnDStrikeoutOffset2 = mnDStrikeoutOffset1 + n2LineDY + n2LineHeight; } void FontMetricData::ImplInitAboveTextLineSize( const OutputDevice* pDev ) { ImplInitTextLineSize(pDev); tools::Long nIntLeading = mnIntLeading; // TODO: assess usage of nLeading below (changed in extleading CWS) // if no leading is available, we assume 15% of the ascent if ( nIntLeading <= 0 ) { nIntLeading = mnAscent*15/100; if ( !nIntLeading ) nIntLeading = 1; } tools::Long nCeiling = -mnAscent; mnAboveUnderlineSize = mnUnderlineSize; mnAboveUnderlineOffset = nCeiling + (nIntLeading - mnUnderlineSize + 1) / 2; mnAboveBUnderlineSize = mnBUnderlineSize; mnAboveBUnderlineOffset = nCeiling + (nIntLeading - mnBUnderlineSize + 1) / 2; mnAboveDUnderlineSize = mnDUnderlineSize; mnAboveDUnderlineOffset1 = nCeiling + (nIntLeading - 3*mnDUnderlineSize + 1) / 2; mnAboveDUnderlineOffset2 = nCeiling + (nIntLeading + mnDUnderlineSize + 1) / 2; mnAboveWUnderlineSize = mnWUnderlineSize; mnAboveWUnderlineOffset = nCeiling + (nIntLeading + 1) / 2; } void FontMetricData::ImplInitFlags( const OutputDevice* pDev ) { const vcl::Font& rFont ( pDev->GetFont() ); bool bCentered = true; if (MsLangId::isCJK(rFont.GetLanguage())) { tools::Rectangle aRect; pDev->GetTextBoundRect( aRect, u"\x3001"_ustr ); // Fullwidth fullstop const auto nH = rFont.GetFontSize().Height(); const auto nB = aRect.Left(); // Use 18.75% as a threshold to define a centered fullwidth fullstop. // In general, nB/nH < 5% for most Japanese fonts. bCentered = nB > (((nH >> 1)+nH)>>3); } SetFullstopCenteredFlag( bCentered ); } bool FontMetricData::ShouldUseWinMetrics(int nAscent, int nDescent, int nTypoAscent, int nTypoDescent, int nWinAscent, int nWinDescent) const { if (utl::ConfigManager::IsFuzzing()) return false; OUString aFontIdentifier( GetFamilyName() + "," + OUString::number(nAscent) + "," + OUString::number(nDescent) + "," + OUString::number(nTypoAscent) + "," + OUString::number(nTypoDescent) + "," + OUString::number(nWinAscent) + "," + OUString::number(nWinDescent)); css::uno::Sequence rWinMetricFontList( officecfg::Office::Common::Misc::FontsUseWinMetrics::get()); if (comphelper::findValue(rWinMetricFontList, aFontIdentifier) != -1) { SAL_INFO("vcl.gdi.fontmetric", "Using win metrics for: " << aFontIdentifier); return true; } return false; } // These are “private” HarfBuzz metrics tags, they are supported by not exposed // in the public header. They are safe to use, HarfBuzz just does not want to // advertise them. constexpr auto ASCENT_OS2 = static_cast(HB_TAG('O', 'a', 's', 'c')); constexpr auto DESCENT_OS2 = static_cast(HB_TAG('O', 'd', 's', 'c')); constexpr auto LINEGAP_OS2 = static_cast(HB_TAG('O', 'l', 'g', 'p')); constexpr auto ASCENT_HHEA = static_cast(HB_TAG('H', 'a', 's', 'c')); constexpr auto DESCENT_HHEA = static_cast(HB_TAG('H', 'd', 's', 'c')); constexpr auto LINEGAP_HHEA = static_cast(HB_TAG('H', 'l', 'g', 'p')); void FontMetricData::ImplCalcLineSpacing(LogicalFontInstance* pFontInstance) { mnAscent = mnDescent = mnExtLeading = mnIntLeading = 0; auto* pFace = pFontInstance->GetFontFace(); auto* pHbFont = pFontInstance->GetHbFont(); double fScale = 0; pFontInstance->GetScale(nullptr, &fScale); double fAscent = 0, fDescent = 0, fExtLeading = 0; auto aFvar(pFace->GetRawFontData(HB_TAG('f', 'v', 'a', 'r'))); if (!aFvar.empty()) { // This is a variable font, trust HarfBuzz to give us the right metrics // and apply variations to them. hb_position_t nAscent, nDescent, nLineGap; if (hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, &nAscent) && hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, &nDescent) && hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, &nLineGap)) { fAscent = nAscent * fScale; fDescent = -nDescent * fScale; fExtLeading = nLineGap * fScale; } } else { // This is not a variable font, we try to choose the best metrics // ourselves for backward comparability: // // - hhea metrics should be used, since hhea is a mandatory font table // and should always be present. // - But if OS/2 is present, it should be used since it is mandatory in // Windows. // OS/2 has Typo and Win metrics, but the later was meant to control // text clipping not line spacing and can be ridiculously large. // Unfortunately many Windows application incorrectly use the Win // metrics (thanks to GDI’s TEXTMETRIC) and old fonts might be // designed with this in mind, so OpenType introduced a flag for // fonts to indicate that they really want to use Typo metrics. So // for best backward compatibility: // - Use Win metrics if available. // - Unless USE_TYPO_METRICS flag is set, in which case use Typo // metrics. // Try hhea table first. hb_position_t nAscent = 0, nDescent = 0, nLineGap = 0; if (hb_ot_metrics_get_position(pHbFont, ASCENT_HHEA, &nAscent) && hb_ot_metrics_get_position(pHbFont, DESCENT_HHEA, &nDescent) && hb_ot_metrics_get_position(pHbFont, LINEGAP_HHEA, &nLineGap)) { // tdf#107605: Some fonts have weird values here, so check that // ascender is +ve and descender is -ve as they normally should. if (nAscent >= 0 && nDescent <= 0) { fAscent = nAscent * fScale; fDescent = -nDescent * fScale; fExtLeading = nLineGap * fScale; } } // But if OS/2 is present, prefer it. hb_position_t nTypoAscent, nTypoDescent, nTypoLineGap, nWinAscent, nWinDescent; if (hb_ot_metrics_get_position(pHbFont, ASCENT_OS2, &nTypoAscent) && hb_ot_metrics_get_position(pHbFont, DESCENT_OS2, &nTypoDescent) && hb_ot_metrics_get_position(pHbFont, LINEGAP_OS2, &nTypoLineGap) && hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_CLIPPING_ASCENT, &nWinAscent) && hb_ot_metrics_get_position(pHbFont, HB_OT_METRICS_TAG_HORIZONTAL_CLIPPING_DESCENT, &nWinDescent)) { if ((fAscent == 0.0 && fDescent == 0.0) || ShouldUseWinMetrics(nAscent, nDescent, nTypoAscent, nTypoDescent, nWinAscent, nWinDescent)) { fAscent = nWinAscent * fScale; fDescent = nWinDescent * fScale; fExtLeading = 0; } bool bUseTypoMetrics = false; { // TODO: Use HarfBuzz API instead of raw access // https://github.com/harfbuzz/harfbuzz/issues/1920 sal_uInt16 fsSelection = 0; auto aOS2(pFace->GetRawFontData(HB_TAG('O', 'S', '/', '2'))); SvMemoryStream aStream(const_cast(aOS2.data()), aOS2.size(), StreamMode::READ); // Font data are big endian. aStream.SetEndian(SvStreamEndian::BIG); if (aStream.Seek(vcl::OS2_fsSelection_offset) == vcl::OS2_fsSelection_offset) aStream.ReadUInt16(fsSelection); bUseTypoMetrics = fsSelection & (1 << 7); } if (bUseTypoMetrics && nTypoAscent >= 0 && nTypoDescent <= 0) { fAscent = nTypoAscent * fScale; fDescent = -nTypoDescent * fScale; fExtLeading = nTypoLineGap * fScale; } } } mnAscent = round(fAscent); mnDescent = round(fDescent); mnExtLeading = round(fExtLeading); if (mnAscent || mnDescent) mnIntLeading = mnAscent + mnDescent - mnHeight; } void FontMetricData::ImplInitBaselines(LogicalFontInstance *pFontInstance) { hb_font_t* pHbFont = pFontInstance->GetHbFont(); double fScale = 0; pFontInstance->GetScale(nullptr, &fScale); hb_position_t nBaseline = 0; if (hb_ot_layout_get_baseline(pHbFont, HB_OT_LAYOUT_BASELINE_TAG_HANGING, HB_DIRECTION_INVALID, HB_SCRIPT_UNKNOWN, HB_TAG_NONE, &nBaseline)) { mnHangingBaseline = nBaseline * fScale; } else { mnHangingBaseline = 0; } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */