| /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This file is part of the Collabora Office 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 <sal/config.h> |
| |
| #include <rtl/ustrbuf.hxx> |
| #include <sal/log.hxx> |
| #include <tools/debug.hxx> |
| #include <tools/mapunit.hxx> |
| #include <i18nlangtag/mslangid.hxx> |
| #include <i18nlangtag/lang.h> |
| #include <comphelper/configuration.hxx> |
| |
| #include <vcl/event.hxx> |
| #include <vcl/fontcharmap.hxx> |
| #include <vcl/metaact.hxx> |
| #include <vcl/metric.hxx> |
| #include <vcl/print.hxx> |
| #include <vcl/sysdata.hxx> |
| #include <vcl/virdev.hxx> |
| |
| #include <window.h> |
| #include <font/EmphasisMark.hxx> |
| |
| #include <ImplLayoutArgs.hxx> |
| #include <drawmode.hxx> |
| #include <impfontcache.hxx> |
| #include <font/DirectFontSubstitution.hxx> |
| #include <font/PhysicalFontFaceCollection.hxx> |
| #include <font/PhysicalFontCollection.hxx> |
| #include <font/FeatureCollector.hxx> |
| #include <impglyphitem.hxx> |
| #include <sallayout.hxx> |
| #include <salgdi.hxx> |
| #include <svdata.hxx> |
| |
| #include <unicode/uchar.h> |
| |
| #include <strings.hrc> |
| |
| void OutputDevice::SetFont( const vcl::Font& rNewFont ) |
| { |
| vcl::Font aFont = vcl::drawmode::GetFont(rNewFont, GetDrawMode(), GetSettings().GetStyleSettings()); |
| |
| if ( mpMetaFile ) |
| { |
| mpMetaFile->AddAction( new MetaFontAction( aFont ) ); |
| // the color and alignment actions don't belong here |
| // TODO: get rid of them without breaking anything... |
| mpMetaFile->AddAction( new MetaTextAlignAction( aFont.GetAlignment() ) ); |
| mpMetaFile->AddAction( new MetaTextFillColorAction( aFont.GetFillColor(), !aFont.IsTransparent() ) ); |
| } |
| |
| if ( maFont.IsSameInstance( aFont ) ) |
| return; |
| |
| // Optimization MT/HDU: COL_TRANSPARENT means SetFont should ignore the font color, |
| // because SetTextColor() is used for this. |
| // #i28759# maTextColor might have been changed behind our back, commit then, too. |
| if( aFont.GetColor() != COL_TRANSPARENT |
| && (aFont.GetColor() != maFont.GetColor() || aFont.GetColor() != maTextColor ) ) |
| { |
| maTextColor = aFont.GetColor(); |
| mbInitTextColor = true; |
| if( mpMetaFile ) |
| mpMetaFile->AddAction( new MetaTextColorAction( aFont.GetColor() ) ); |
| } |
| maFont = aFont; |
| mbNewFont = true; |
| } |
| |
| FontMetric OutputDevice::GetFontMetricFromCollection(int nDevFontIndex) const |
| { |
| ImplInitFontList(); |
| |
| if (nDevFontIndex < GetFontFaceCollectionCount()) |
| return FontMetric(*mpFontFaceCollection->Get(nDevFontIndex)); |
| |
| return FontMetric(); |
| } |
| |
| int OutputDevice::GetFontFaceCollectionCount() const |
| { |
| if( !mpFontFaceCollection ) |
| { |
| if (!mxFontCollection) |
| { |
| return 0; |
| } |
| |
| mpFontFaceCollection = mxFontCollection->GetFontFaceCollection(); |
| |
| if (!mpFontFaceCollection->Count()) |
| { |
| mpFontFaceCollection.reset(); |
| return 0; |
| } |
| } |
| return mpFontFaceCollection->Count(); |
| } |
| |
| bool OutputDevice::IsFontAvailable( std::u16string_view rFontName ) const |
| { |
| ImplInitFontList(); |
| vcl::font::PhysicalFontFamily* pFound = mxFontCollection->FindFontFamily( rFontName ); |
| return (pFound != nullptr); |
| } |
| |
| bool OutputDevice::AddTempDevFont( const OUString& rFileURL, const OUString& rFontName ) |
| { |
| ImplInitFontList(); |
| |
| if( !mpGraphics && !AcquireGraphics() ) |
| return false; |
| assert(mpGraphics); |
| |
| bool bRC = mpGraphics->AddTempDevFont( mxFontCollection.get(), rFileURL, rFontName ); |
| if( !bRC ) |
| return false; |
| |
| return true; |
| } |
| |
| bool OutputDevice::RemoveTempDevFont(const OUString& rFileURL, const OUString& rFontName) |
| { |
| if( !mpGraphics && !AcquireGraphics() ) |
| return true; // No graphics -> no fonts used |
| assert(mpGraphics); |
| |
| return mpGraphics->RemoveTempDevFont(rFileURL, rFontName); |
| } |
| |
| bool OutputDevice::GetFontFeatures(std::vector<vcl::font::Feature>& rFontFeatures) const |
| { |
| if (!ImplNewFont()) |
| return false; |
| |
| LogicalFontInstance* pFontInstance = mpFontInstance.get(); |
| if (!pFontInstance) |
| return false; |
| |
| const LanguageTag& rOfficeLanguage = Application::GetSettings().GetUILanguageTag(); |
| |
| vcl::font::FeatureCollector aFeatureCollector(pFontInstance->GetFontFace(), rFontFeatures, rOfficeLanguage); |
| aFeatureCollector.collect(); |
| |
| return true; |
| } |
| |
| FontMetric OutputDevice::GetFontMetric() const |
| { |
| FontMetric aMetric; |
| if (!ImplNewFont()) |
| return aMetric; |
| |
| LogicalFontInstance* pFontInstance = mpFontInstance.get(); |
| FontMetricDataRef xFontMetric = pFontInstance->mxFontMetric; |
| |
| // prepare metric |
| aMetric = maFont; |
| |
| // set aMetric with info from font |
| aMetric.SetFamilyName( maFont.GetFamilyName() ); |
| aMetric.SetStyleName( xFontMetric->GetStyleName() ); |
| aMetric.SetFontSize( PixelToLogic( Size( xFontMetric->GetWidth(), xFontMetric->GetAscent() + xFontMetric->GetDescent() - xFontMetric->GetInternalLeading() ) ) ); |
| aMetric.SetCharSet( xFontMetric->IsMicrosoftSymbolEncoded() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); |
| aMetric.SetFamily( xFontMetric->GetFamilyType() ); |
| aMetric.SetPitch( xFontMetric->GetPitch() ); |
| aMetric.SetWeight( xFontMetric->GetWeight() ); |
| aMetric.SetItalic( xFontMetric->GetItalic() ); |
| aMetric.SetAlignment( TextAlign::ALIGN_TOP ); |
| aMetric.SetWidthType( xFontMetric->GetWidthType() ); |
| if ( pFontInstance->mnOwnOrientation ) |
| aMetric.SetOrientation( pFontInstance->mnOwnOrientation ); |
| else |
| aMetric.SetOrientation( xFontMetric->GetOrientation() ); |
| |
| // set remaining metric fields |
| aMetric.SetFullstopCenteredFlag( xFontMetric->IsFullstopCentered() ); |
| aMetric.SetBulletOffset( xFontMetric->GetBulletOffset() ); |
| aMetric.SetAscent( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + mnEmphasisAscent ) ); |
| aMetric.SetDescent( ImplDevicePixelToLogicHeight( xFontMetric->GetDescent() + mnEmphasisDescent ) ); |
| aMetric.SetInternalLeading( ImplDevicePixelToLogicHeight( xFontMetric->GetInternalLeading() + mnEmphasisAscent ) ); |
| // OutputDevice has its own external leading function due to #i60945# |
| aMetric.SetExternalLeading( ImplDevicePixelToLogicHeight( GetFontExtLeading() ) ); |
| aMetric.SetLineHeight( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + xFontMetric->GetDescent() + mnEmphasisAscent + mnEmphasisDescent ) ); |
| aMetric.SetSlant( ImplDevicePixelToLogicHeight( xFontMetric->GetSlant() ) ); |
| aMetric.SetHangingBaseline( ImplDevicePixelToLogicHeight( xFontMetric->GetHangingBaseline() ) ); |
| |
| aMetric.SetUnitEm(ImplDevicePixelToLogicWidth(xFontMetric->GetUnitEm())); |
| aMetric.SetHorCJKAdvance(ImplDevicePixelToLogicWidth(xFontMetric->GetHorCJKAdvance())); |
| aMetric.SetVertCJKAdvance(ImplDevicePixelToLogicHeight(xFontMetric->GetVertCJKAdvance())); |
| |
| // get miscellaneous data |
| aMetric.SetQuality( xFontMetric->GetQuality() ); |
| |
| SAL_INFO("vcl.gdi.fontmetric", "OutputDevice::GetFontMetric:" << aMetric); |
| |
| return aMetric; |
| } |
| |
| FontMetric OutputDevice::GetFontMetric( const vcl::Font& rFont ) const |
| { |
| // select font, query metrics, select original font again |
| vcl::Font aOldFont = GetFont(); |
| const_cast<OutputDevice*>(this)->SetFont( rFont ); |
| FontMetric aMetric( GetFontMetric() ); |
| const_cast<OutputDevice*>(this)->SetFont( aOldFont ); |
| return aMetric; |
| } |
| |
| bool OutputDevice::GetFontCharMap( FontCharMapRef& rxFontCharMap ) const |
| { |
| if (!InitFont()) |
| return false; |
| |
| FontCharMapRef xFontCharMap ( mpGraphics->GetFontCharMap() ); |
| if (!xFontCharMap.is()) |
| rxFontCharMap = FontCharMapRef(new FontCharMap()); |
| else |
| rxFontCharMap = std::move(xFontCharMap); |
| |
| return !rxFontCharMap->IsDefaultMap(); |
| } |
| |
| bool OutputDevice::GetFontCapabilities( vcl::FontCapabilities& rFontCapabilities ) const |
| { |
| if (!InitFont()) |
| return false; |
| return mpGraphics->GetFontCapabilities(rFontCapabilities); |
| } |
| |
| tools::Long OutputDevice::GetFontExtLeading() const |
| { |
| return mpFontInstance->mxFontMetric->GetExternalLeading(); |
| } |
| |
| void OutputDevice::ImplClearFontData( const bool bNewFontLists ) |
| { |
| // the currently selected logical font is no longer needed |
| mpFontInstance.clear(); |
| |
| mbInitFont = true; |
| mbNewFont = true; |
| |
| if ( bNewFontLists ) |
| { |
| mpFontFaceCollection.reset(); |
| |
| // release all physically selected fonts on this device |
| if( AcquireGraphics() ) |
| mpGraphics->ReleaseFonts(); |
| } |
| |
| ImplSVData* pSVData = ImplGetSVData(); |
| |
| if (mxFontCache && mxFontCache != pSVData->maGDIData.mxScreenFontCache) |
| mxFontCache->Invalidate(); |
| |
| if (bNewFontLists && AcquireGraphics()) |
| { |
| if (mxFontCollection && mxFontCollection != pSVData->maGDIData.mxScreenFontList) |
| mxFontCollection->Clear(); |
| } |
| } |
| |
| void OutputDevice::RefreshFontData( const bool bNewFontLists ) |
| { |
| ImplRefreshFontData( bNewFontLists ); |
| } |
| |
| void OutputDevice::ImplRefreshFontData( const bool bNewFontLists ) |
| { |
| if (bNewFontLists && AcquireGraphics()) |
| mpGraphics->GetDevFontList( mxFontCollection.get() ); |
| } |
| |
| void OutputDevice::ImplUpdateFontData() |
| { |
| ImplClearFontData( true/*bNewFontLists*/ ); |
| ImplRefreshFontData( true/*bNewFontLists*/ ); |
| } |
| |
| void OutputDevice::ImplClearAllFontData(bool bNewFontLists) |
| { |
| ImplSVData* pSVData = ImplGetSVData(); |
| |
| ImplUpdateFontDataForAllFrames( &OutputDevice::ImplClearFontData, bNewFontLists ); |
| |
| // clear global font lists to have them updated |
| pSVData->maGDIData.mxScreenFontCache->Invalidate(); |
| if ( !bNewFontLists ) |
| return; |
| |
| pSVData->maGDIData.mxScreenFontList->Clear(); |
| vcl::Window * pFrame = pSVData->maFrameData.mpFirstFrame; |
| if (!pFrame) |
| return; |
| |
| if ( pFrame->GetOutDev()->AcquireGraphics() ) |
| { |
| OutputDevice *pDevice = pFrame->GetOutDev(); |
| pDevice->mpGraphics->ClearDevFontCache(); |
| pDevice->mpGraphics->GetDevFontList(pFrame->mpWindowImpl->mpFrameData->mxFontCollection.get()); |
| } |
| } |
| |
| void OutputDevice::ImplRefreshAllFontData(bool bNewFontLists) |
| { |
| ImplUpdateFontDataForAllFrames( &OutputDevice::ImplRefreshFontData, bNewFontLists ); |
| } |
| |
| void OutputDevice::ImplUpdateAllFontData(bool bNewFontLists) |
| { |
| OutputDevice::ImplClearAllFontData(bNewFontLists); |
| OutputDevice::ImplRefreshAllFontData(bNewFontLists); |
| } |
| |
| void OutputDevice::ImplUpdateFontDataForAllFrames( const FontUpdateHandler_t pHdl, const bool bNewFontLists ) |
| { |
| ImplSVData* const pSVData = ImplGetSVData(); |
| |
| // update all windows |
| vcl::Window* pFrame = pSVData->maFrameData.mpFirstFrame; |
| while ( pFrame ) |
| { |
| ( pFrame->GetOutDev()->*pHdl )( bNewFontLists ); |
| |
| vcl::Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap; |
| while ( pSysWin ) |
| { |
| ( pSysWin->GetOutDev()->*pHdl )( bNewFontLists ); |
| pSysWin = pSysWin->mpWindowImpl->mpNextOverlap; |
| } |
| |
| pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame; |
| } |
| |
| // update all virtual devices |
| VirtualDevice* pVirDev = pSVData->maGDIData.mpFirstVirDev; |
| while ( pVirDev ) |
| { |
| ( pVirDev->*pHdl )( bNewFontLists ); |
| pVirDev = pVirDev->mpNext; |
| } |
| |
| // update all printers |
| Printer* pPrinter = pSVData->maGDIData.mpFirstPrinter; |
| while ( pPrinter ) |
| { |
| ( pPrinter->*pHdl )( bNewFontLists ); |
| pPrinter = pPrinter->mpNext; |
| } |
| } |
| |
| void OutputDevice::BeginFontSubstitution() |
| { |
| ImplSVData* pSVData = ImplGetSVData(); |
| pSVData->maGDIData.mbFontSubChanged = false; |
| } |
| |
| void OutputDevice::EndFontSubstitution() |
| { |
| ImplSVData* pSVData = ImplGetSVData(); |
| if ( pSVData->maGDIData.mbFontSubChanged ) |
| { |
| ImplUpdateAllFontData( false ); |
| |
| DataChangedEvent aDCEvt( DataChangedEventType::FONTSUBSTITUTION ); |
| Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt); |
| Application::NotifyAllWindows( aDCEvt ); |
| pSVData->maGDIData.mbFontSubChanged = false; |
| } |
| } |
| |
| void OutputDevice::AddFontSubstitute( const OUString& rFontName, |
| const OUString& rReplaceFontName, |
| AddFontSubstituteFlags nFlags ) |
| { |
| vcl::font::DirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; |
| if( !rpSubst ) |
| rpSubst = new vcl::font::DirectFontSubstitution; |
| rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags ); |
| ImplGetSVData()->maGDIData.mbFontSubChanged = true; |
| } |
| |
| void OutputDevice::RemoveFontsSubstitute() |
| { |
| vcl::font::DirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; |
| if( pSubst ) |
| pSubst->RemoveFontsSubstitute(); |
| } |
| |
| //hidpi TODO: This routine has hard-coded font-sizes that break places such as DialControl |
| vcl::Font OutputDevice::GetDefaultFont( DefaultFontType nType, LanguageType eLang, |
| GetDefaultFontFlags nFlags, const OutputDevice* pOutDev ) |
| { |
| static bool bFuzzing = comphelper::IsFuzzing(); |
| static bool bAbortOnFontSubstitute = [] { |
| const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE"); |
| return pEnv && strcmp(pEnv, "abort") == 0; |
| }(); |
| |
| if (!pOutDev && !bFuzzing) // default is NULL |
| pOutDev = Application::GetDefaultDevice(); |
| |
| OUString aSearch; |
| if (!bFuzzing) |
| { |
| LanguageTag aLanguageTag( |
| ( eLang == LANGUAGE_NONE || eLang == LANGUAGE_SYSTEM || eLang == LANGUAGE_DONTKNOW ) ? |
| Application::GetSettings().GetUILanguageTag() : |
| LanguageTag( eLang )); |
| |
| utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get(); |
| OUString aDefault = rDefaults.getDefaultFont( aLanguageTag, nType ); |
| |
| if( !aDefault.isEmpty() ) |
| aSearch = aDefault; |
| else |
| aSearch = rDefaults.getUserInterfaceFont( aLanguageTag ); // use the UI font as a fallback |
| |
| // during cppunit tests with SAL_NON_APPLICATION_FONT_USE set we don't have any bundled fonts |
| // that support the default CTL and CJK languages of Hindi and Chinese, so just pick something |
| // (unsuitable) that does exist, if they get used with SAL_NON_APPLICATION_FONT_USE=abort then |
| // glyph fallback will trigger std::abort |
| if (bAbortOnFontSubstitute) |
| { |
| if (eLang == LANGUAGE_HINDI || eLang == LANGUAGE_CHINESE_SIMPLIFIED) |
| aSearch = "DejaVu Sans"; |
| } |
| } |
| else |
| aSearch = "Liberation Serif"; |
| |
| vcl::Font aFont; |
| aFont.SetPitch( PITCH_VARIABLE ); |
| |
| switch ( nType ) |
| { |
| case DefaultFontType::SANS_UNICODE: |
| case DefaultFontType::UI_SANS: |
| case DefaultFontType::SANS: |
| case DefaultFontType::LATIN_HEADING: |
| case DefaultFontType::LATIN_SPREADSHEET: |
| case DefaultFontType::LATIN_DISPLAY: |
| aFont.SetFamily( FAMILY_SWISS ); |
| break; |
| |
| case DefaultFontType::SERIF: |
| case DefaultFontType::LATIN_TEXT: |
| case DefaultFontType::LATIN_PRESENTATION: |
| aFont.SetFamily( FAMILY_ROMAN ); |
| break; |
| |
| case DefaultFontType::FIXED: |
| case DefaultFontType::LATIN_FIXED: |
| case DefaultFontType::UI_FIXED: |
| aFont.SetPitch( PITCH_FIXED ); |
| aFont.SetFamily( FAMILY_MODERN ); |
| break; |
| |
| case DefaultFontType::SYMBOL: |
| aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); |
| break; |
| |
| case DefaultFontType::CJK_TEXT: |
| case DefaultFontType::CJK_PRESENTATION: |
| case DefaultFontType::CJK_SPREADSHEET: |
| case DefaultFontType::CJK_HEADING: |
| case DefaultFontType::CJK_DISPLAY: |
| case DefaultFontType::CTL_TEXT: |
| case DefaultFontType::CTL_PRESENTATION: |
| case DefaultFontType::CTL_SPREADSHEET: |
| case DefaultFontType::CTL_HEADING: |
| case DefaultFontType::CTL_DISPLAY: |
| aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later... |
| break; |
| } |
| |
| if ( !aSearch.isEmpty() ) |
| { |
| aFont.SetFontHeight( 12 ); // corresponds to nDefaultHeight |
| aFont.SetWeight( WEIGHT_NORMAL ); |
| aFont.SetLanguage( eLang ); |
| |
| if ( aFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW ) |
| aFont.SetCharSet( osl_getThreadTextEncoding() ); |
| |
| // Should we only return available fonts on the given device |
| if ( pOutDev ) |
| { |
| pOutDev->ImplInitFontList(); |
| |
| // Search Font in the FontList |
| OUString aName; |
| sal_Int32 nIndex = 0; |
| do |
| { |
| vcl::font::PhysicalFontFamily* pFontFamily = pOutDev->mxFontCollection->FindFontFamily( GetNextFontToken( aSearch, nIndex ) ); |
| if( pFontFamily ) |
| { |
| AddTokenFontName( aName, pFontFamily->GetFamilyName() ); |
| if( nFlags & GetDefaultFontFlags::OnlyOne ) |
| break; |
| } |
| } |
| while ( nIndex != -1 ); |
| aFont.SetFamilyName( aName ); |
| } |
| |
| // No Name, then set all names |
| if ( aFont.GetFamilyName().isEmpty() ) |
| { |
| if ( nFlags & GetDefaultFontFlags::OnlyOne ) |
| { |
| if( !pOutDev ) |
| { |
| SAL_WARN_IF(!comphelper::IsFuzzing(), "vcl.gdi", "No default window has been set for the application - we really shouldn't be able to get here"); |
| aFont.SetFamilyName( aSearch.getToken( 0, ';' ) ); |
| } |
| else |
| { |
| pOutDev->ImplInitFontList(); |
| |
| aFont.SetFamilyName( aSearch ); |
| |
| // convert to pixel height |
| Size aSize = pOutDev->ImplLogicToDevicePixel( aFont.GetFontSize() ); |
| if ( !aSize.Height() ) |
| { |
| // use default pixel height only when logical height is zero |
| if ( aFont.GetFontHeight() ) |
| aSize.setHeight( 1 ); |
| else |
| aSize.setHeight( (12*pOutDev->mnDPIY)/72 ); |
| } |
| |
| // use default width only when logical width is zero |
| if( (0 == aSize.Width()) && (0 != aFont.GetFontSize().Width()) ) |
| aSize.setWidth( 1 ); |
| |
| // get the name of the first available font |
| float fExactHeight = static_cast<float>(aSize.Height()); |
| rtl::Reference<LogicalFontInstance> pFontInstance = pOutDev->mxFontCache->GetFontInstance( pOutDev->mxFontCollection.get(), aFont, aSize, fExactHeight ); |
| if (pFontInstance) |
| { |
| assert(pFontInstance->GetFontFace()); |
| aFont.SetFamilyName(pFontInstance->GetFontFace()->GetFamilyName()); |
| } |
| } |
| } |
| else |
| aFont.SetFamilyName( aSearch ); |
| } |
| } |
| |
| #if OSL_DEBUG_LEVEL > 2 |
| const char* s = "SANS_UNKNOWN"; |
| switch ( nType ) |
| { |
| case DefaultFontType::SANS_UNICODE: s = "SANS_UNICODE"; break; |
| case DefaultFontType::UI_SANS: s = "UI_SANS"; break; |
| |
| case DefaultFontType::SANS: s = "SANS"; break; |
| case DefaultFontType::LATIN_HEADING: s = "LATIN_HEADING"; break; |
| case DefaultFontType::LATIN_SPREADSHEET: s = "LATIN_SPREADSHEET"; break; |
| case DefaultFontType::LATIN_DISPLAY: s = "LATIN_DISPLAY"; break; |
| |
| case DefaultFontType::SERIF: s = "SERIF"; break; |
| case DefaultFontType::LATIN_TEXT: s = "LATIN_TEXT"; break; |
| case DefaultFontType::LATIN_PRESENTATION: s = "LATIN_PRESENTATION"; break; |
| |
| case DefaultFontType::FIXED: s = "FIXED"; break; |
| case DefaultFontType::LATIN_FIXED: s = "LATIN_FIXED"; break; |
| case DefaultFontType::UI_FIXED: s = "UI_FIXED"; break; |
| |
| case DefaultFontType::SYMBOL: s = "SYMBOL"; break; |
| |
| case DefaultFontType::CJK_TEXT: s = "CJK_TEXT"; break; |
| case DefaultFontType::CJK_PRESENTATION: s = "CJK_PRESENTATION"; break; |
| case DefaultFontType::CJK_SPREADSHEET: s = "CJK_SPREADSHEET"; break; |
| case DefaultFontType::CJK_HEADING: s = "CJK_HEADING"; break; |
| case DefaultFontType::CJK_DISPLAY: s = "CJK_DISPLAY"; break; |
| |
| case DefaultFontType::CTL_TEXT: s = "CTL_TEXT"; break; |
| case DefaultFontType::CTL_PRESENTATION: s = "CTL_PRESENTATION"; break; |
| case DefaultFontType::CTL_SPREADSHEET: s = "CTL_SPREADSHEET"; break; |
| case DefaultFontType::CTL_HEADING: s = "CTL_HEADING"; break; |
| case DefaultFontType::CTL_DISPLAY: s = "CTL_DISPLAY"; break; |
| } |
| SAL_INFO("vcl.gdi", |
| "OutputDevice::GetDefaultFont() Type=" << s |
| << " lang=" << eLang |
| << " flags=" << static_cast<int>(nFlags) |
| << " family=\"" << aFont.GetFamilyName() << "\""); |
| #endif |
| |
| return aFont; |
| } |
| |
| void OutputDevice::ImplInitFontList() const |
| { |
| if( mxFontCollection->Count() ) |
| return; |
| |
| if( !(mpGraphics || AcquireGraphics()) ) |
| return; |
| assert(mpGraphics); |
| |
| SAL_INFO( "vcl.gdi", "OutputDevice::ImplInitFontList()" ); |
| mpGraphics->GetDevFontList(mxFontCollection.get()); |
| |
| // There is absolutely no way there should be no fonts available on the device |
| if( !mxFontCollection->Count() ) |
| { |
| OUString aError( u"Application error: no fonts and no vcl resource found on your system"_ustr ); |
| OUString aResStr(VclResId(SV_ACCESSERROR_NO_FONTS)); |
| if (!aResStr.isEmpty()) |
| aError = aResStr; |
| Application::Abort(aError); |
| } |
| } |
| |
| bool OutputDevice::InitFont() const |
| { |
| DBG_TESTSOLARMUTEX(); |
| |
| if (!ImplNewFont()) |
| return false; |
| if (!mpFontInstance) |
| return false; |
| if (!mpGraphics) |
| { |
| if (!AcquireGraphics()) |
| return false; |
| } |
| else if (!mbInitFont) |
| return true; |
| |
| assert(mpGraphics); |
| mpGraphics->SetFont(mpFontInstance.get(), 0); |
| mbInitFont = false; |
| return true; |
| } |
| |
| const LogicalFontInstance* OutputDevice::GetFontInstance() const |
| { |
| if (!InitFont()) |
| return nullptr; |
| return mpFontInstance.get(); |
| } |
| |
| bool OutputDevice::ImplNewFont() const |
| { |
| DBG_TESTSOLARMUTEX(); |
| |
| if ( !mbNewFont ) |
| return true; |
| |
| // we need a graphics |
| if ( !mpGraphics && !AcquireGraphics() ) |
| { |
| SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no Graphics, no Font"); |
| return false; |
| } |
| assert(mpGraphics); |
| |
| ImplInitFontList(); |
| |
| // convert to pixel height |
| // TODO: replace integer based aSize completely with subpixel accurate type |
| float fExactHeight = ImplLogicHeightToDeviceSubPixel(maFont.GetFontHeight()); |
| Size aSize = ImplLogicToDevicePixel( maFont.GetFontSize() ); |
| if ( !aSize.Height() ) |
| { |
| // use default pixel height only when logical height is zero |
| if ( maFont.GetFontSize().Height() ) |
| aSize.setHeight( 1 ); |
| else |
| aSize.setHeight( (12*mnDPIY)/72 ); |
| fExactHeight = static_cast<float>(aSize.Height()); |
| } |
| |
| // select the default width only when logical width is zero |
| if( (0 == aSize.Width()) && (0 != maFont.GetFontSize().Width()) ) |
| aSize.setWidth( 1 ); |
| |
| // decide if antialiasing is appropriate |
| bool bNonAntialiased(GetAntialiasing() & AntialiasingFlags::DisableText); |
| if (!comphelper::IsFuzzing()) |
| { |
| const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); |
| bNonAntialiased |= bool(rStyleSettings.GetDisplayOptions() & DisplayOptions::AADisable); |
| bNonAntialiased |= (int(rStyleSettings.GetAntialiasingMinPixelHeight()) > maFont.GetFontSize().Height()); |
| } |
| |
| // get font entry |
| rtl::Reference<LogicalFontInstance> pOldFontInstance = mpFontInstance; |
| mpFontInstance = mxFontCache->GetFontInstance(mxFontCollection.get(), maFont, aSize, fExactHeight, bNonAntialiased); |
| const bool bNewFontInstance = pOldFontInstance.get() != mpFontInstance.get(); |
| pOldFontInstance.clear(); |
| |
| LogicalFontInstance* pFontInstance = mpFontInstance.get(); |
| |
| if (!pFontInstance) |
| { |
| SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no LogicalFontInstance, no Font"); |
| return false; |
| } |
| |
| |
| // Compute font size in points for optical sizing. |
| if (!pFontInstance->GetPointSize()) |
| { |
| auto nHeight = maFont.GetFontHeight(); |
| auto eFrom = MapToO3tlLength(GetMapMode().GetMapUnit()); |
| float fPointSize = o3tl::convert(float(nHeight), eFrom, o3tl::Length::pt); |
| pFontInstance->SetPointSize(fPointSize); |
| } |
| |
| // mark when lower layers need to get involved |
| mbNewFont = false; |
| if( bNewFontInstance ) |
| mbInitFont = true; |
| |
| // select font when it has not been initialized yet |
| if (!pFontInstance->mbInit && InitFont()) |
| { |
| // get metric data from device layers |
| pFontInstance->mbInit = true; |
| |
| pFontInstance->mxFontMetric->SetOrientation( mpFontInstance->GetFontSelectPattern().mnOrientation ); |
| mpGraphics->GetFontMetric( pFontInstance->mxFontMetric, 0 ); |
| |
| pFontInstance->mxFontMetric->ImplInitTextLineSize( this ); |
| pFontInstance->mxFontMetric->ImplInitAboveTextLineSize( this ); |
| pFontInstance->mxFontMetric->ImplInitFlags( this ); |
| |
| pFontInstance->mnLineHeight = pFontInstance->mxFontMetric->GetAscent() + pFontInstance->mxFontMetric->GetDescent(); |
| |
| SetFontOrientation( pFontInstance ); |
| } |
| |
| // calculate EmphasisArea |
| mnEmphasisAscent = 0; |
| mnEmphasisDescent = 0; |
| if ( maFont.GetEmphasisMark() & FontEmphasisMark::Style ) |
| { |
| FontEmphasisMark nEmphasisMark = maFont.GetEmphasisMarkStyle(); |
| tools::Long nEmphasisHeight = (pFontInstance->mnLineHeight*250)/1000; |
| if ( nEmphasisHeight < 1 ) |
| nEmphasisHeight = 1; |
| if ( nEmphasisMark & FontEmphasisMark::PosBelow ) |
| mnEmphasisDescent = nEmphasisHeight; |
| else |
| mnEmphasisAscent = nEmphasisHeight; |
| } |
| |
| // calculate text offset depending on TextAlignment |
| TextAlign eAlign = maFont.GetAlignment(); |
| if ( eAlign == ALIGN_BASELINE ) |
| { |
| mnTextOffX = 0; |
| mnTextOffY = 0; |
| } |
| else if ( eAlign == ALIGN_TOP ) |
| { |
| mnTextOffX = 0; |
| mnTextOffY = +pFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent; |
| if ( pFontInstance->mnOrientation ) |
| { |
| Point aOriginPt(0, 0); |
| aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation ); |
| } |
| } |
| else // eAlign == ALIGN_BOTTOM |
| { |
| mnTextOffX = 0; |
| mnTextOffY = -pFontInstance->mxFontMetric->GetDescent() + mnEmphasisDescent; |
| if ( pFontInstance->mnOrientation ) |
| { |
| Point aOriginPt(0, 0); |
| aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation ); |
| } |
| } |
| |
| mbTextLines = ((maFont.GetUnderline() != LINESTYLE_NONE) && (maFont.GetUnderline() != LINESTYLE_DONTKNOW)) || |
| ((maFont.GetOverline() != LINESTYLE_NONE) && (maFont.GetOverline() != LINESTYLE_DONTKNOW)) || |
| ((maFont.GetStrikeout() != STRIKEOUT_NONE) && (maFont.GetStrikeout() != STRIKEOUT_DONTKNOW)); |
| mbTextSpecial = maFont.IsShadow() || maFont.IsOutline() || |
| (maFont.GetRelief() != FontRelief::NONE); |
| |
| |
| bool bRet = true; |
| |
| // #95414# fix for OLE objects which use scale factors very creatively |
| if (mbMap && !aSize.Width()) |
| bRet = AttemptOLEFontScaleFix(const_cast<vcl::Font&>(maFont), aSize.Height()); |
| |
| return bRet; |
| } |
| |
| bool OutputDevice::AttemptOLEFontScaleFix(vcl::Font& rFont, tools::Long nHeight) const |
| { |
| if (maMapRes.mfMapScY == 0.0) |
| return false; |
| const double fStretch = maMapRes.mfMapScX / maMapRes.mfMapScY; |
| const int nOrigWidth = mpFontInstance->mxFontMetric->GetWidth(); |
| const int nNewWidth = static_cast<int>(nOrigWidth * fStretch + 0.5); |
| |
| if (nNewWidth == nOrigWidth || nNewWidth == 0) |
| return true; |
| |
| Size aOrigSize = rFont.GetFontSize(); |
| rFont.SetFontSize(Size(nNewWidth, nHeight)); |
| mbMap = false; |
| mbNewFont = true; |
| |
| const bool bRet = ImplNewFont(); // recurse once using stretched width |
| |
| mbMap = true; |
| rFont.SetFontSize(aOrigSize); |
| |
| return bRet; |
| } |
| |
| void OutputDevice::SetFontOrientation( LogicalFontInstance* const pFontInstance ) const |
| { |
| if( pFontInstance->GetFontSelectPattern().mnOrientation && !pFontInstance->mxFontMetric->GetOrientation() ) |
| { |
| pFontInstance->mnOwnOrientation = pFontInstance->GetFontSelectPattern().mnOrientation; |
| pFontInstance->mnOrientation = pFontInstance->mnOwnOrientation; |
| } |
| else |
| { |
| pFontInstance->mnOrientation = pFontInstance->mxFontMetric->GetOrientation(); |
| } |
| } |
| |
| void OutputDevice::ImplDrawEmphasisMark( tools::Long nBaseX, tools::Long nX, tools::Long nY, |
| const tools::PolyPolygon& rPolyPoly, bool bPolyLine, |
| const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 ) |
| { |
| if( IsRTLEnabled() ) |
| nX = nBaseX - (nX - nBaseX - 1); |
| |
| nX -= mnOutOffX; |
| nY -= mnOutOffY; |
| |
| if ( rPolyPoly.Count() ) |
| { |
| if ( bPolyLine ) |
| { |
| tools::Polygon aPoly = rPolyPoly.GetObject( 0 ); |
| aPoly.Move( nX, nY ); |
| DrawPolyLine( aPoly ); |
| } |
| else |
| { |
| tools::PolyPolygon aPolyPoly = rPolyPoly; |
| aPolyPoly.Move( nX, nY ); |
| DrawPolyPolygon( aPolyPoly ); |
| } |
| } |
| |
| if ( !rRect1.IsEmpty() ) |
| { |
| tools::Rectangle aRect( Point( nX+rRect1.Left(), |
| nY+rRect1.Top() ), rRect1.GetSize() ); |
| DrawRect( aRect ); |
| } |
| |
| if ( !rRect2.IsEmpty() ) |
| { |
| tools::Rectangle aRect( Point( nX+rRect2.Left(), |
| nY+rRect2.Top() ), rRect2.GetSize() ); |
| |
| DrawRect( aRect ); |
| } |
| } |
| |
| void OutputDevice::ImplDrawEmphasisMarks( SalLayout& rSalLayout ) |
| { |
| auto popIt = ScopedPush(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR | vcl::PushFlags::MAPMODE); |
| GDIMetaFile* pOldMetaFile = mpMetaFile; |
| mpMetaFile = nullptr; |
| EnableMapMode( false ); |
| |
| FontEmphasisMark nEmphasisMark = maFont.GetEmphasisMarkStyle(); |
| tools::Long nEmphasisHeight; |
| |
| if ( nEmphasisMark & FontEmphasisMark::PosBelow ) |
| nEmphasisHeight = mnEmphasisDescent; |
| else |
| nEmphasisHeight = mnEmphasisAscent; |
| |
| vcl::font::EmphasisMark aEmphasisMark(nEmphasisMark, nEmphasisHeight, GetDPIY()); |
| |
| if (aEmphasisMark.IsShapePolyLine()) |
| { |
| SetLineColor( GetTextColor() ); |
| SetFillColor(); |
| } |
| else |
| { |
| SetLineColor(); |
| SetFillColor( GetTextColor() ); |
| } |
| |
| Point aOffset(0,0); |
| Point aOffsetVert(0,0); |
| |
| if ( nEmphasisMark & FontEmphasisMark::PosBelow ) |
| { |
| aOffset.AdjustY(mpFontInstance->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()); |
| aOffsetVert = aOffset; |
| } |
| else |
| { |
| aOffset.AdjustY(-(mpFontInstance->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset())); |
| // Todo: use ideographic em-box or ideographic character face information. |
| aOffsetVert.AdjustY(-(mpFontInstance->mxFontMetric->GetAscent() + |
| mpFontInstance->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset())); |
| } |
| |
| tools::Long nEmphasisWidth2 = aEmphasisMark.GetWidth() / 2; |
| tools::Long nEmphasisHeight2 = nEmphasisHeight / 2; |
| aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 ); |
| |
| basegfx::B2DPoint aOutPoint; |
| basegfx::B2DRectangle aRectangle; |
| const GlyphItem* pGlyph; |
| const LogicalFontInstance* pGlyphFont; |
| int nStart = 0; |
| while (rSalLayout.GetNextGlyph(&pGlyph, aOutPoint, nStart, &pGlyphFont)) |
| { |
| if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle)) |
| continue; |
| |
| if (!pGlyph->IsSpacing()) |
| { |
| Point aAdjPoint; |
| if (pGlyph->IsVertical()) |
| { |
| aAdjPoint = aOffsetVert; |
| aAdjPoint.AdjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2); |
| } |
| else |
| { |
| aAdjPoint = aOffset; |
| aAdjPoint.AdjustX(aRectangle.getMinX() + (aRectangle.getWidth() - aEmphasisMark.GetWidth()) / 2 ); |
| } |
| |
| if ( mpFontInstance->mnOrientation ) |
| { |
| Point aOriginPt(0, 0); |
| aOriginPt.RotateAround( aAdjPoint, mpFontInstance->mnOrientation ); |
| } |
| aOutPoint.adjustX(aAdjPoint.X() - nEmphasisWidth2); |
| aOutPoint.adjustY(aAdjPoint.Y() - nEmphasisHeight2); |
| ImplDrawEmphasisMark( rSalLayout.DrawBase().getX(), |
| aOutPoint.getX(), aOutPoint.getY(), |
| aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(), |
| aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() ); |
| } |
| } |
| |
| mpMetaFile = pOldMetaFile; |
| } |
| |
| std::unique_ptr<SalLayout> OutputDevice::getFallbackLayout( |
| LogicalFontInstance* pLogicalFont, int nFallbackLevel, |
| vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs) const |
| { |
| // we need a graphics |
| if (!mpGraphics && !AcquireGraphics()) |
| return nullptr; |
| |
| assert(mpGraphics != nullptr); |
| mpGraphics->SetFont( pLogicalFont, nFallbackLevel ); |
| |
| rLayoutArgs.ResetPos(); |
| std::unique_ptr<GenericSalLayout> pFallback = mpGraphics->GetTextLayout(nFallbackLevel); |
| |
| if (!pFallback) |
| return nullptr; |
| |
| if (!pFallback->LayoutText(rLayoutArgs, pGlyphs ? pGlyphs->Impl(nFallbackLevel) : nullptr)) |
| { |
| // there is no need for a font that couldn't resolve anything |
| return nullptr; |
| } |
| |
| return pFallback; |
| } |
| |
| bool OutputDevice::ForceFallbackFont(vcl::Font const& rFallbackFont) |
| { |
| vcl::Font aOldFont = GetFont(); |
| SetFont(rFallbackFont); |
| if (!InitFont()) |
| return false; |
| |
| mpForcedFallbackInstance = mpFontInstance; |
| SetFont(aOldFont); |
| if (!InitFont()) |
| return false; |
| |
| if (mpForcedFallbackInstance) |
| return true; |
| |
| return false; |
| } |
| |
| std::unique_ptr<SalLayout> OutputDevice::ImplGlyphFallbackLayout( std::unique_ptr<SalLayout> pSalLayout, |
| vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs ) const |
| { |
| // This function relies on a valid mpFontInstance, if it doesn't exist bail out |
| // - we'd have crashed later on anyway. At least here we can catch the error in debug |
| // mode. |
| if ( !mpFontInstance ) |
| { |
| SAL_WARN ("vcl.gdi", "No font entry set in OutputDevice"); |
| assert(mpFontInstance); |
| return nullptr; |
| } |
| |
| // prepare multi level glyph fallback |
| std::unique_ptr<MultiSalLayout> pMultiSalLayout; |
| ImplLayoutRuns aLayoutRuns = rLayoutArgs.maRuns; |
| rLayoutArgs.PrepareFallback(nullptr); |
| rLayoutArgs.mnFlags |= SalLayoutFlags::ForFallback; |
| |
| // get list of code units that need glyph fallback |
| bool bRTL; |
| int nMinRunPos, nEndRunPos; |
| OUStringBuffer aMissingCodeBuf(512); |
| while (rLayoutArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL)) |
| aMissingCodeBuf.append(rLayoutArgs.mrStr.subView(nMinRunPos, nEndRunPos - nMinRunPos)); |
| rLayoutArgs.ResetPos(); |
| OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear(); |
| |
| vcl::font::FontSelectPattern aFontSelData(mpFontInstance->GetFontSelectPattern()); |
| SalLayoutGlyphsImpl* pGlyphsImpl = pGlyphs ? pGlyphs->Impl(1) : nullptr; |
| |
| bool bHasUsedFallback = false; |
| |
| // try if fallback fonts support the missing code units |
| for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel ) |
| { |
| rtl::Reference<LogicalFontInstance> pFallbackFont; |
| if (!bHasUsedFallback && mpForcedFallbackInstance) |
| { |
| pFallbackFont = mpForcedFallbackInstance; |
| bHasUsedFallback = true; |
| } |
| else if(pGlyphsImpl != nullptr) |
| { |
| pFallbackFont = pGlyphsImpl->GetFont(); |
| } |
| |
| // find a font family suited for glyph fallback |
| // GetGlyphFallbackFont() needs a valid FontInstance |
| // if the system-specific glyph fallback is active |
| OUString oldMissingCodes = aMissingCodes; |
| if( !pFallbackFont ) |
| pFallbackFont = mxFontCache->GetGlyphFallbackFont( mxFontCollection.get(), |
| aFontSelData, mpFontInstance.get(), nFallbackLevel, aMissingCodes ); |
| if( !pFallbackFont ) |
| break; |
| |
| SAL_INFO("vcl", "Fallback font (level " << nFallbackLevel << "): family: " << pFallbackFont->GetFontFace()->GetFamilyName() |
| << ", style: " << pFallbackFont->GetFontFace()->GetStyleName()); |
| |
| if( nFallbackLevel < MAX_FALLBACK-1) |
| { |
| // ignore fallback font if it is the same as the original font |
| // TODO: This seems broken. Either the font does not provide any of the missing |
| // codes, in which case the fallback should not select it. Or it does provide |
| // some of the missing codes, and then why weren't they used the first time? |
| // This will just loop repeatedly finding the same font (it used to remove |
| // the found font from mxFontCache, but doesn't do that anymore and I don't |
| // see how doing that would remove the font from consideration for fallback). |
| if( mpFontInstance->GetFontFace() == pFallbackFont->GetFontFace()) |
| { |
| if(aMissingCodes != oldMissingCodes) |
| { |
| SAL_INFO("vcl.gdi", "Font fallback to the same font, but has missing codes"); |
| // Restore the missing codes if we're not going to use this font. |
| aMissingCodes = oldMissingCodes; |
| } |
| continue; |
| } |
| } |
| |
| // create and add glyph fallback layout to multilayout |
| std::unique_ptr<SalLayout> pFallback = getFallbackLayout(pFallbackFont.get(), |
| nFallbackLevel, rLayoutArgs, pGlyphs); |
| if (pFallback) |
| { |
| if( !pMultiSalLayout ) |
| pMultiSalLayout.reset( new MultiSalLayout( std::move(pSalLayout) ) ); |
| pMultiSalLayout->AddFallback(std::move(pFallback), rLayoutArgs.maRuns); |
| if (nFallbackLevel == MAX_FALLBACK-1) |
| pMultiSalLayout->SetIncomplete(true); |
| } |
| |
| if (pGlyphs != nullptr) |
| pGlyphsImpl = pGlyphs->Impl(nFallbackLevel + 1); |
| |
| // break when this fallback was sufficient |
| if( !rLayoutArgs.PrepareFallback(pGlyphsImpl) ) |
| break; |
| } |
| |
| if (pMultiSalLayout) // due to missing glyphs, multilevel layout fallback attempted |
| { |
| // if it works, use that Layout |
| if (pMultiSalLayout->LayoutText(rLayoutArgs, nullptr)) |
| pSalLayout = std::move(pMultiSalLayout); |
| else |
| { |
| // if it doesn't, give up and restore ownership of the pSalLayout |
| // back to its original state |
| pSalLayout = pMultiSalLayout->ReleaseBaseLayout(); |
| } |
| } |
| |
| // restore orig font settings |
| rLayoutArgs.maRuns = std::move(aLayoutRuns); |
| |
| return pSalLayout; |
| } |
| |
| tools::Long OutputDevice::GetMinKashida() const |
| { |
| if (!ImplNewFont()) |
| return 0; |
| |
| auto nKashidaWidth = mpFontInstance->mxFontMetric->GetMinKashida(); |
| if (!mbMap) |
| nKashidaWidth = std::ceil(nKashidaWidth); |
| |
| return ImplDevicePixelToLogicWidth(nKashidaWidth); |
| } |
| |
| // tdf#163105: Get map of valid kashida positions for a single word |
| void OutputDevice::GetWordKashidaPositions(const OUString& rText, |
| std::vector<bool>* pOutMap) const |
| { |
| pOutMap->clear(); |
| |
| auto nEnd = rText.getLength(); |
| |
| // do layout |
| std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rText, 0, nEnd); |
| if (!pSalLayout) |
| return; |
| |
| // tdf#163215: VCL cannot suggest valid kashida positions for certain fonts (e.g. AAT). |
| if (!pSalLayout->HasFontKashidaPositions()) |
| return; |
| |
| pOutMap->resize(nEnd, false); |
| for (sal_Int32 i = 0; i < nEnd; ++i) |
| { |
| auto nNextPos = i + 1; |
| |
| // Skip combining marks to find the next character after this position. |
| while (nNextPos < nEnd |
| && u_getIntPropertyValue(rText[nNextPos], UCHAR_JOINING_TYPE) == U_JT_TRANSPARENT) |
| { |
| ++nNextPos; |
| } |
| |
| pOutMap->at(i) = pSalLayout->IsKashidaPosValid(i, nNextPos); |
| } |
| } |
| |
| bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr, |
| int nIndex, int nLen, std::vector< tools::Rectangle >& rVector ) const |
| { |
| rVector.clear(); |
| |
| if( nIndex >= rStr.getLength() ) |
| return false; |
| |
| if( nLen < 0 || nIndex + nLen >= rStr.getLength() ) |
| { |
| nLen = rStr.getLength() - nIndex; |
| } |
| |
| tools::Rectangle aRect; |
| for( int i = 0; i < nLen; i++ ) |
| { |
| if( !GetTextBoundRect( aRect, rStr, nIndex, nIndex + i, 1 ) ) |
| break; |
| aRect.Move( rOrigin.X(), rOrigin.Y() ); |
| rVector.push_back( aRect ); |
| } |
| |
| return (nLen == static_cast<int>(rVector.size())); |
| } |
| |
| sal_Int32 OutputDevice::HasGlyphs( const vcl::Font& rTempFont, std::u16string_view rStr, |
| sal_Int32 nIndex, sal_Int32 nLen ) const |
| { |
| if( nIndex >= static_cast<sal_Int32>(rStr.size()) ) |
| return nIndex; |
| sal_Int32 nEnd; |
| if( nLen == -1 ) |
| nEnd = rStr.size(); |
| else |
| nEnd = std::min<sal_Int32>( rStr.size(), nIndex + nLen ); |
| |
| SAL_WARN_IF( nIndex >= nEnd, "vcl.gdi", "StartPos >= EndPos?" ); |
| SAL_WARN_IF( nEnd > static_cast<sal_Int32>(rStr.size()), "vcl.gdi", "String too short" ); |
| |
| // to get the map temporarily set font |
| const vcl::Font aOrigFont = GetFont(); |
| const_cast<OutputDevice&>(*this).SetFont( rTempFont ); |
| FontCharMapRef xFontCharMap; |
| bool bRet = GetFontCharMap( xFontCharMap ); |
| const_cast<OutputDevice&>(*this).SetFont( aOrigFont ); |
| |
| // if fontmap is unknown assume it doesn't have the glyphs |
| if( !bRet ) |
| return nIndex; |
| |
| for( sal_Int32 i = nIndex; nIndex < nEnd; ++i, ++nIndex ) |
| if( ! xFontCharMap->HasChar( rStr[i] ) ) |
| return nIndex; |
| |
| return -1; |
| } |
| |
| void OutputDevice::ReleaseFontCache() { mxFontCache.reset(); } |
| |
| void OutputDevice::ReleaseFontCollection() { mxFontCollection.reset(); } |
| |
| void OutputDevice::SetFontCollectionFromSVData() |
| { |
| mxFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList->Clone(); |
| } |
| |
| void OutputDevice::ResetNewFontCache() |
| { |
| mxFontCache = std::make_shared<ImplFontCache>(); |
| } |
| |
| void OutputDevice::ImplReleaseFonts() |
| { |
| mpGraphics->ReleaseFonts(); |
| |
| mbNewFont = true; |
| mbInitFont = true; |
| |
| mpFontInstance.clear(); |
| mpForcedFallbackInstance.clear(); |
| mpFontFaceCollection.reset(); |
| } |
| |
| /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ |