forked from amazingfate/loongoffice
2179 lines
70 KiB
C++
2179 lines
70 KiB
C++
/* -*- 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 "i18nlangtag/mslangid.hxx"
|
|
|
|
#include <vcl/virdev.hxx>
|
|
#include <vcl/print.hxx>
|
|
#include <vcl/outdev.hxx>
|
|
#include <vcl/edit.hxx>
|
|
#include <vcl/settings.hxx>
|
|
#include <vcl/sysdata.hxx>
|
|
|
|
#include "sallayout.hxx"
|
|
#include "svdata.hxx"
|
|
|
|
#include "impfont.hxx"
|
|
#include "outdata.hxx"
|
|
#include "outfont.hxx"
|
|
|
|
#include "outdev.h"
|
|
#include "window.h"
|
|
|
|
#include "PhysicalFontCollection.hxx"
|
|
#include "PhysicalFontFace.hxx"
|
|
#include "PhysicalFontFamily.hxx"
|
|
|
|
#include "svids.hrc"
|
|
|
|
#include <config_graphite.h>
|
|
#if ENABLE_GRAPHITE
|
|
#include "graphite_features.hxx"
|
|
#endif
|
|
|
|
#include "../gdi/pdfwriter_impl.hxx"
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
#include <memory>
|
|
#include <algorithm>
|
|
|
|
using namespace ::com::sun::star;
|
|
using namespace ::com::sun::star::uno;
|
|
using namespace ::rtl;
|
|
using namespace ::utl;
|
|
using namespace ::vcl;
|
|
|
|
vcl::FontInfo OutputDevice::GetDevFont( int nDevFontIndex ) const
|
|
{
|
|
vcl::FontInfo aFontInfo;
|
|
|
|
ImplInitFontList();
|
|
|
|
int nCount = GetDevFontCount();
|
|
if( nDevFontIndex < nCount )
|
|
{
|
|
const PhysicalFontFace& rData = *mpGetDevFontList->Get( nDevFontIndex );
|
|
aFontInfo.SetName( rData.GetFamilyName() );
|
|
aFontInfo.SetStyleName( rData.GetStyleName() );
|
|
aFontInfo.SetCharSet( rData.IsSymbolFont() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE );
|
|
aFontInfo.SetFamily( rData.GetFamilyType() );
|
|
aFontInfo.SetPitch( rData.GetPitch() );
|
|
aFontInfo.SetWeight( rData.GetWeight() );
|
|
aFontInfo.SetItalic( rData.GetSlant() );
|
|
aFontInfo.SetWidthType( rData.GetWidthType() );
|
|
if( rData.IsScalable() )
|
|
aFontInfo.mpImplMetric->mnMiscFlags |= ImplFontMetric::SCALABLE_FLAG;
|
|
if( rData.mbDevice )
|
|
aFontInfo.mpImplMetric->mnMiscFlags |= ImplFontMetric::DEVICE_FLAG;
|
|
}
|
|
|
|
return aFontInfo;
|
|
}
|
|
|
|
int OutputDevice::GetDevFontCount() const
|
|
{
|
|
if( !mpGetDevFontList )
|
|
mpGetDevFontList = mpFontCollection->GetDevFontList();
|
|
return mpGetDevFontList->Count();
|
|
}
|
|
|
|
bool OutputDevice::IsFontAvailable( const OUString& rFontName ) const
|
|
{
|
|
PhysicalFontFamily* pFound = mpFontCollection->FindFontFamily( rFontName );
|
|
return (pFound != NULL);
|
|
}
|
|
|
|
int OutputDevice::GetDevFontSizeCount( const Font& rFont ) const
|
|
{
|
|
delete mpGetDevSizeList;
|
|
|
|
ImplInitFontList();
|
|
mpGetDevSizeList = mpFontCollection->GetDevSizeList( rFont.GetName() );
|
|
return mpGetDevSizeList->Count();
|
|
}
|
|
|
|
Size OutputDevice::GetDevFontSize( const Font& rFont, int nSizeIndex ) const
|
|
{
|
|
// check range
|
|
int nCount = GetDevFontSizeCount( rFont );
|
|
if ( nSizeIndex >= nCount )
|
|
return Size();
|
|
|
|
// when mapping is enabled round to .5 points
|
|
Size aSize( 0, mpGetDevSizeList->Get( nSizeIndex ) );
|
|
if ( mbMap )
|
|
{
|
|
aSize.Height() *= 10;
|
|
MapMode aMap( MAP_10TH_INCH, Point(), Fraction( 1, 72 ), Fraction( 1, 72 ) );
|
|
aSize = PixelToLogic( aSize, aMap );
|
|
aSize.Height() += 5;
|
|
aSize.Height() /= 10;
|
|
long nRound = aSize.Height() % 5;
|
|
if ( nRound >= 3 )
|
|
aSize.Height() += (5-nRound);
|
|
else
|
|
aSize.Height() -= nRound;
|
|
aSize.Height() *= 10;
|
|
aSize = LogicToPixel( aSize, aMap );
|
|
aSize = PixelToLogic( aSize );
|
|
aSize.Height() += 5;
|
|
aSize.Height() /= 10;
|
|
}
|
|
return aSize;
|
|
}
|
|
|
|
bool OutputDevice::AddTempDevFont( const OUString& rFileURL, const OUString& rFontName )
|
|
{
|
|
ImplInitFontList();
|
|
|
|
if( !mpGraphics && !AcquireGraphics() )
|
|
return false;
|
|
|
|
bool bRC = mpGraphics->AddTempDevFont( mpFontCollection, rFileURL, rFontName );
|
|
if( !bRC )
|
|
return false;
|
|
|
|
if( mpAlphaVDev )
|
|
mpAlphaVDev->AddTempDevFont( rFileURL, rFontName );
|
|
|
|
mpFontCache->Invalidate();
|
|
return true;
|
|
}
|
|
|
|
FontMetric OutputDevice::GetFontMetric() const
|
|
{
|
|
FontMetric aMetric;
|
|
if( mbNewFont && !ImplNewFont() )
|
|
return aMetric;
|
|
|
|
ImplFontEntry* pEntry = mpFontEntry;
|
|
ImplFontMetricData* pMetric = &(pEntry->maMetric);
|
|
|
|
// prepare metric
|
|
aMetric.Font::operator=( maFont );
|
|
|
|
// set aMetric with info from font
|
|
aMetric.SetName( maFont.GetName() );
|
|
aMetric.SetStyleName( pMetric->GetStyleName() );
|
|
aMetric.SetSize( PixelToLogic( Size( pMetric->mnWidth, pMetric->mnAscent+pMetric->mnDescent-pMetric->mnIntLeading ) ) );
|
|
aMetric.SetCharSet( pMetric->IsSymbolFont() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE );
|
|
aMetric.SetFamily( pMetric->GetFamilyType() );
|
|
aMetric.SetPitch( pMetric->GetPitch() );
|
|
aMetric.SetWeight( pMetric->GetWeight() );
|
|
aMetric.SetItalic( pMetric->GetSlant() );
|
|
aMetric.SetWidthType( pMetric->GetWidthType() );
|
|
if ( pEntry->mnOwnOrientation )
|
|
aMetric.SetOrientation( pEntry->mnOwnOrientation );
|
|
else
|
|
aMetric.SetOrientation( pMetric->mnOrientation );
|
|
if( !pEntry->maMetric.mbKernableFont )
|
|
aMetric.SetKerning( maFont.GetKerning() & ~KERNING_FONTSPECIFIC );
|
|
|
|
// set remaining metric fields
|
|
aMetric.mpImplMetric->mnMiscFlags = 0;
|
|
if( pMetric->mbDevice )
|
|
aMetric.mpImplMetric->mnMiscFlags |= ImplFontMetric::DEVICE_FLAG;
|
|
if( pMetric->mbScalableFont )
|
|
aMetric.mpImplMetric->mnMiscFlags |= ImplFontMetric::SCALABLE_FLAG;
|
|
aMetric.mpImplMetric->mnAscent = ImplDevicePixelToLogicHeight( pMetric->mnAscent+mnEmphasisAscent );
|
|
aMetric.mpImplMetric->mnDescent = ImplDevicePixelToLogicHeight( pMetric->mnDescent+mnEmphasisDescent );
|
|
aMetric.mpImplMetric->mnIntLeading = ImplDevicePixelToLogicHeight( pMetric->mnIntLeading+mnEmphasisAscent );
|
|
aMetric.mpImplMetric->mnExtLeading = ImplDevicePixelToLogicHeight( GetFontExtLeading() );
|
|
aMetric.mpImplMetric->mnExtLeading = ImplDevicePixelToLogicHeight( pMetric->mnExtLeading );
|
|
aMetric.mpImplMetric->mnLineHeight = ImplDevicePixelToLogicHeight( pMetric->mnAscent+pMetric->mnDescent+mnEmphasisAscent+mnEmphasisDescent );
|
|
aMetric.mpImplMetric->mnSlant = ImplDevicePixelToLogicHeight( pMetric->mnSlant );
|
|
|
|
SAL_INFO("vcl.gdi.fontmetric", "OutputDevice::GetFontMetric:" << aMetric);
|
|
|
|
return aMetric;
|
|
}
|
|
|
|
FontMetric OutputDevice::GetFontMetric( const Font& rFont ) const
|
|
{
|
|
// select font, query metrics, select original font again
|
|
Font aOldFont = GetFont();
|
|
const_cast<OutputDevice*>(this)->SetFont( rFont );
|
|
FontMetric aMetric( GetFontMetric() );
|
|
const_cast<OutputDevice*>(this)->SetFont( aOldFont );
|
|
return aMetric;
|
|
}
|
|
|
|
bool OutputDevice::GetFontCharMap( FontCharMap& rFontCharMap ) const
|
|
{
|
|
rFontCharMap.Reset();
|
|
|
|
// we need a graphics
|
|
if( !mpGraphics && !AcquireGraphics() )
|
|
return false;
|
|
|
|
if( mbNewFont )
|
|
ImplNewFont();
|
|
if( mbInitFont )
|
|
InitFont();
|
|
if( !mpFontEntry )
|
|
return false;
|
|
|
|
const ImplFontCharMap* pNewMap = mpGraphics->GetImplFontCharMap();
|
|
rFontCharMap.Reset( pNewMap );
|
|
|
|
if( rFontCharMap.IsDefaultMap() )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool OutputDevice::GetFontCapabilities( FontCapabilities& rFontCapabilities ) const
|
|
{
|
|
// we need a graphics
|
|
if( !mpGraphics && !AcquireGraphics() )
|
|
return false;
|
|
|
|
if( mbNewFont )
|
|
ImplNewFont();
|
|
if( mbInitFont )
|
|
InitFont();
|
|
if( !mpFontEntry )
|
|
return false;
|
|
|
|
return mpGraphics->GetImplFontCapabilities(rFontCapabilities);
|
|
}
|
|
|
|
SystemFontData OutputDevice::GetSysFontData(int nFallbacklevel) const
|
|
{
|
|
SystemFontData aSysFontData;
|
|
aSysFontData.nSize = sizeof(aSysFontData);
|
|
|
|
if (!mpGraphics)
|
|
(void) AcquireGraphics();
|
|
|
|
if (mpGraphics)
|
|
aSysFontData = mpGraphics->GetSysFontData(nFallbacklevel);
|
|
|
|
return aSysFontData;
|
|
}
|
|
|
|
void OutputDevice::ImplGetEmphasisMark( PolyPolygon& rPolyPoly, bool& rPolyLine,
|
|
Rectangle& rRect1, Rectangle& rRect2,
|
|
long& rYOff, long& rWidth,
|
|
FontEmphasisMark eEmphasis,
|
|
long nHeight, short /*nOrient*/ )
|
|
{
|
|
static const sal_uInt8 aAccentPolyFlags[24] =
|
|
{
|
|
0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 0, 2, 0, 2, 2
|
|
};
|
|
|
|
static const long aAccentPos[48] =
|
|
{
|
|
78, 0,
|
|
348, 79,
|
|
599, 235,
|
|
843, 469,
|
|
938, 574,
|
|
990, 669,
|
|
990, 773,
|
|
990, 843,
|
|
964, 895,
|
|
921, 947,
|
|
886, 982,
|
|
860, 999,
|
|
825, 999,
|
|
764, 999,
|
|
721, 964,
|
|
686, 895,
|
|
625, 791,
|
|
556, 660,
|
|
469, 504,
|
|
400, 400,
|
|
261, 252,
|
|
61, 61,
|
|
0, 27,
|
|
9, 0
|
|
};
|
|
|
|
rWidth = 0;
|
|
rYOff = 0;
|
|
rPolyLine = false;
|
|
|
|
if ( !nHeight )
|
|
return;
|
|
|
|
FontEmphasisMark nEmphasisStyle = eEmphasis & EMPHASISMARK_STYLE;
|
|
long nDotSize = 0;
|
|
switch ( nEmphasisStyle )
|
|
{
|
|
case EMPHASISMARK_DOT:
|
|
// Dot has 55% of the height
|
|
nDotSize = (nHeight*550)/1000;
|
|
if ( !nDotSize )
|
|
nDotSize = 1;
|
|
if ( nDotSize <= 2 )
|
|
rRect1 = Rectangle( Point(), Size( nDotSize, nDotSize ) );
|
|
else
|
|
{
|
|
long nRad = nDotSize/2;
|
|
Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
|
|
rPolyPoly.Insert( aPoly );
|
|
}
|
|
rYOff = ((nHeight*250)/1000)/2; // Center to the another EmphasisMarks
|
|
rWidth = nDotSize;
|
|
break;
|
|
|
|
case EMPHASISMARK_CIRCLE:
|
|
// Dot has 80% of the height
|
|
nDotSize = (nHeight*800)/1000;
|
|
if ( !nDotSize )
|
|
nDotSize = 1;
|
|
if ( nDotSize <= 2 )
|
|
rRect1 = Rectangle( Point(), Size( nDotSize, nDotSize ) );
|
|
else
|
|
{
|
|
long nRad = nDotSize/2;
|
|
Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
|
|
rPolyPoly.Insert( aPoly );
|
|
// BorderWidth is 15%
|
|
long nBorder = (nDotSize*150)/1000;
|
|
if ( nBorder <= 1 )
|
|
rPolyLine = true;
|
|
else
|
|
{
|
|
Polygon aPoly2( Point( nRad, nRad ),
|
|
nRad-nBorder, nRad-nBorder );
|
|
rPolyPoly.Insert( aPoly2 );
|
|
}
|
|
}
|
|
rWidth = nDotSize;
|
|
break;
|
|
|
|
case EMPHASISMARK_DISC:
|
|
// Dot has 80% of the height
|
|
nDotSize = (nHeight*800)/1000;
|
|
if ( !nDotSize )
|
|
nDotSize = 1;
|
|
if ( nDotSize <= 2 )
|
|
rRect1 = Rectangle( Point(), Size( nDotSize, nDotSize ) );
|
|
else
|
|
{
|
|
long nRad = nDotSize/2;
|
|
Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
|
|
rPolyPoly.Insert( aPoly );
|
|
}
|
|
rWidth = nDotSize;
|
|
break;
|
|
|
|
case EMPHASISMARK_ACCENT:
|
|
// Dot has 80% of the height
|
|
nDotSize = (nHeight*800)/1000;
|
|
if ( !nDotSize )
|
|
nDotSize = 1;
|
|
if ( nDotSize <= 2 )
|
|
{
|
|
if ( nDotSize == 1 )
|
|
{
|
|
rRect1 = Rectangle( Point(), Size( nDotSize, nDotSize ) );
|
|
rWidth = nDotSize;
|
|
}
|
|
else
|
|
{
|
|
rRect1 = Rectangle( Point(), Size( 1, 1 ) );
|
|
rRect2 = Rectangle( Point( 1, 1 ), Size( 1, 1 ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Polygon aPoly( sizeof( aAccentPos ) / sizeof( long ) / 2,
|
|
(const Point*)aAccentPos,
|
|
aAccentPolyFlags );
|
|
double dScale = ((double)nDotSize)/1000.0;
|
|
aPoly.Scale( dScale, dScale );
|
|
Polygon aTemp;
|
|
aPoly.AdaptiveSubdivide( aTemp );
|
|
Rectangle aBoundRect = aTemp.GetBoundRect();
|
|
rWidth = aBoundRect.GetWidth();
|
|
nDotSize = aBoundRect.GetHeight();
|
|
rPolyPoly.Insert( aTemp );
|
|
}
|
|
break;
|
|
}
|
|
|
|
// calculate position
|
|
long nOffY = 1+(mnDPIY/300); // one visible pixel space
|
|
long nSpaceY = nHeight-nDotSize;
|
|
if ( nSpaceY >= nOffY*2 )
|
|
rYOff += nOffY;
|
|
if ( !(eEmphasis & EMPHASISMARK_POS_BELOW) )
|
|
rYOff += nDotSize;
|
|
}
|
|
|
|
FontEmphasisMark OutputDevice::ImplGetEmphasisMarkStyle( const Font& rFont )
|
|
{
|
|
FontEmphasisMark nEmphasisMark = rFont.GetEmphasisMark();
|
|
|
|
// If no Position is set, then calculate the default position, which
|
|
// depends on the language
|
|
if ( !(nEmphasisMark & (EMPHASISMARK_POS_ABOVE | EMPHASISMARK_POS_BELOW)) )
|
|
{
|
|
LanguageType eLang = rFont.GetLanguage();
|
|
// In Chinese Simplified the EmphasisMarks are below/left
|
|
if (MsLangId::isSimplifiedChinese(eLang))
|
|
nEmphasisMark |= EMPHASISMARK_POS_BELOW;
|
|
else
|
|
{
|
|
eLang = rFont.GetCJKContextLanguage();
|
|
// In Chinese Simplified the EmphasisMarks are below/left
|
|
if (MsLangId::isSimplifiedChinese(eLang))
|
|
nEmphasisMark |= EMPHASISMARK_POS_BELOW;
|
|
else
|
|
nEmphasisMark |= EMPHASISMARK_POS_ABOVE;
|
|
}
|
|
}
|
|
|
|
return nEmphasisMark;
|
|
}
|
|
|
|
long OutputDevice::GetFontExtLeading() const
|
|
{
|
|
ImplFontEntry* pEntry = mpFontEntry;
|
|
ImplFontMetricData* pMetric = &(pEntry->maMetric);
|
|
|
|
return pMetric->mnExtLeading;
|
|
}
|
|
|
|
void OutputDevice::ImplClearFontData( const bool bNewFontLists )
|
|
{
|
|
// the currently selected logical font is no longer needed
|
|
if ( mpFontEntry )
|
|
{
|
|
mpFontCache->Release( mpFontEntry );
|
|
mpFontEntry = NULL;
|
|
}
|
|
|
|
mbInitFont = true;
|
|
mbNewFont = true;
|
|
|
|
if ( bNewFontLists )
|
|
{
|
|
if ( mpGetDevFontList )
|
|
{
|
|
delete mpGetDevFontList;
|
|
mpGetDevFontList = NULL;
|
|
}
|
|
if ( mpGetDevSizeList )
|
|
{
|
|
delete mpGetDevSizeList;
|
|
mpGetDevSizeList = NULL;
|
|
}
|
|
|
|
// release all physically selected fonts on this device
|
|
if( AcquireGraphics() )
|
|
mpGraphics->ReleaseFonts();
|
|
}
|
|
|
|
// if ( GetOutDevType() == OUTDEV_PRINTER || mpPDFWriter )
|
|
{
|
|
ImplSVData* pSVData = ImplGetSVData();
|
|
|
|
if( mpFontCache && mpFontCache != pSVData->maGDIData.mpScreenFontCache )
|
|
mpFontCache->Invalidate();
|
|
|
|
if ( bNewFontLists )
|
|
{
|
|
// we need a graphics
|
|
if ( AcquireGraphics() )
|
|
{
|
|
if( mpFontCollection && mpFontCollection != pSVData->maGDIData.mpScreenFontList )
|
|
mpFontCollection->Clear();
|
|
|
|
if( mpPDFWriter )
|
|
{
|
|
if( mpFontCollection && mpFontCollection != pSVData->maGDIData.mpScreenFontList )
|
|
delete mpFontCollection;
|
|
if( mpFontCache && mpFontCache != pSVData->maGDIData.mpScreenFontCache )
|
|
delete mpFontCache;
|
|
mpFontCollection = 0;
|
|
mpFontCache = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// also update child windows if needed
|
|
if ( GetOutDevType() == OUTDEV_WINDOW )
|
|
{
|
|
Window* pChild = ((Window*)this)->mpWindowImpl->mpFirstChild;
|
|
while ( pChild )
|
|
{
|
|
pChild->ImplClearFontData( true );
|
|
pChild = pChild->mpWindowImpl->mpNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OutputDevice::ImplRefreshFontData( const bool bNewFontLists )
|
|
{
|
|
// if ( GetOutDevType() == OUTDEV_PRINTER || mpPDFWriter )
|
|
{
|
|
ImplSVData* pSVData = ImplGetSVData();
|
|
|
|
if ( bNewFontLists )
|
|
{
|
|
// we need a graphics
|
|
if ( AcquireGraphics() )
|
|
{
|
|
if( mpPDFWriter )
|
|
{
|
|
mpFontCollection = pSVData->maGDIData.mpScreenFontList->Clone( true, true );
|
|
mpFontCache = new ImplFontCache();
|
|
}
|
|
else
|
|
{
|
|
mpGraphics->GetDevFontList( mpFontCollection );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// also update child windows if needed
|
|
if ( GetOutDevType() == OUTDEV_WINDOW )
|
|
{
|
|
Window* pChild = ((Window*)this)->mpWindowImpl->mpFirstChild;
|
|
while ( pChild )
|
|
{
|
|
pChild->ImplRefreshFontData( true );
|
|
pChild = pChild->mpWindowImpl->mpNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OutputDevice::ImplUpdateFontData( bool bNewFontLists )
|
|
{
|
|
ImplClearFontData( bNewFontLists );
|
|
ImplRefreshFontData( bNewFontLists );
|
|
}
|
|
|
|
void OutputDevice::ImplUpdateAllFontData( bool bNewFontLists )
|
|
{
|
|
ImplSVData* pSVData = ImplGetSVData();
|
|
|
|
ImplUpdateFontDataForAllFrames( &OutputDevice::ImplClearFontData, bNewFontLists );
|
|
|
|
// clear global font lists to have them updated
|
|
pSVData->maGDIData.mpScreenFontCache->Invalidate();
|
|
if ( bNewFontLists )
|
|
{
|
|
pSVData->maGDIData.mpScreenFontList->Clear();
|
|
Window * pFrame = pSVData->maWinData.mpFirstFrame;
|
|
if ( pFrame )
|
|
{
|
|
if ( pFrame->AcquireGraphics() )
|
|
{
|
|
// Stupid typecast here and somewhere ((OutputDevice*)&aVDev)->, because bug in .NET2002 compiler
|
|
OutputDevice *pDevice = (OutputDevice*)pFrame;
|
|
pDevice->mpGraphics->ClearDevFontCache();
|
|
pDevice->mpGraphics->GetDevFontList(pFrame->mpWindowImpl->mpFrameData->mpFontCollection);
|
|
}
|
|
}
|
|
}
|
|
|
|
ImplUpdateFontDataForAllFrames( &OutputDevice::ImplRefreshFontData, bNewFontLists );
|
|
}
|
|
|
|
void OutputDevice::ImplUpdateFontDataForAllFrames( const FontUpdateHandler_t pHdl, const bool bNewFontLists )
|
|
{
|
|
ImplSVData* const pSVData = ImplGetSVData();
|
|
|
|
// update all windows
|
|
Window* pFrame = pSVData->maWinData.mpFirstFrame;
|
|
while ( pFrame )
|
|
{
|
|
( pFrame->*pHdl )( bNewFontLists );
|
|
|
|
Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap;
|
|
while ( pSysWin )
|
|
{
|
|
( pSysWin->*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 );
|
|
|
|
Application* pApp = GetpApp();
|
|
DataChangedEvent aDCEvt( DATACHANGED_FONTSUBSTITUTION );
|
|
pApp->DataChanged( aDCEvt );
|
|
Application::NotifyAllWindows( aDCEvt );
|
|
pSVData->maGDIData.mbFontSubChanged = false;
|
|
}
|
|
}
|
|
|
|
void OutputDevice::AddFontSubstitute( const OUString& rFontName,
|
|
const OUString& rReplaceFontName,
|
|
sal_uInt16 nFlags )
|
|
{
|
|
ImplDirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
|
|
if( !rpSubst )
|
|
rpSubst = new ImplDirectFontSubstitution();
|
|
rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags );
|
|
ImplGetSVData()->maGDIData.mbFontSubChanged = true;
|
|
}
|
|
|
|
void ImplDirectFontSubstitution::AddFontSubstitute( const OUString& rFontName,
|
|
const OUString& rSubstFontName, sal_uInt16 nFlags )
|
|
{
|
|
maFontSubstList.push_back( ImplFontSubstEntry( rFontName, rSubstFontName, nFlags ) );
|
|
}
|
|
|
|
ImplFontSubstEntry::ImplFontSubstEntry( const OUString& rFontName,
|
|
const OUString& rSubstFontName, sal_uInt16 nSubstFlags )
|
|
: maName( rFontName )
|
|
, maReplaceName( rSubstFontName )
|
|
, mnFlags( nSubstFlags )
|
|
{
|
|
maSearchName = GetEnglishSearchFontName( rFontName );
|
|
maSearchReplaceName = GetEnglishSearchFontName( rSubstFontName );
|
|
}
|
|
|
|
void OutputDevice::RemoveFontSubstitute( sal_uInt16 n )
|
|
{
|
|
ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
|
|
if( pSubst )
|
|
pSubst->RemoveFontSubstitute( n );
|
|
}
|
|
|
|
void ImplDirectFontSubstitution::RemoveFontSubstitute( int nIndex )
|
|
{
|
|
FontSubstList::iterator it = maFontSubstList.begin();
|
|
for( int nCount = 0; (it != maFontSubstList.end()) && (nCount++ != nIndex); ++it ) ;
|
|
if( it != maFontSubstList.end() )
|
|
maFontSubstList.erase( it );
|
|
}
|
|
|
|
sal_uInt16 OutputDevice::GetFontSubstituteCount()
|
|
{
|
|
const ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
|
|
if( !pSubst )
|
|
return 0;
|
|
int nCount = pSubst->GetFontSubstituteCount();
|
|
return (sal_uInt16)nCount;
|
|
}
|
|
|
|
bool ImplDirectFontSubstitution::FindFontSubstitute( OUString& rSubstName,
|
|
const OUString& rSearchName, sal_uInt16 nFlags ) const
|
|
{
|
|
// TODO: get rid of O(N) searches
|
|
FontSubstList::const_iterator it = maFontSubstList.begin();
|
|
for(; it != maFontSubstList.end(); ++it )
|
|
{
|
|
const ImplFontSubstEntry& rEntry = *it;
|
|
if( ((rEntry.mnFlags & nFlags) || !nFlags)
|
|
&& (rEntry.maSearchName == rSearchName) )
|
|
{
|
|
rSubstName = rEntry.maSearchReplaceName;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ImplFontSubstitute( OUString& rFontName )
|
|
{
|
|
// must be canonicalised
|
|
assert( GetEnglishSearchFontName( rFontName ) == rFontName );
|
|
|
|
OUString aSubstFontName;
|
|
|
|
// apply user-configurable font replacement (eg, from the list in Tools->Options)
|
|
const ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
|
|
if( pSubst && pSubst->FindFontSubstitute( aSubstFontName, rFontName, FONT_SUBSTITUTE_ALWAYS ) )
|
|
{
|
|
rFontName = aSubstFontName;
|
|
return;
|
|
}
|
|
}
|
|
|
|
//hidpi TODO: This routine has hard-coded font-sizes that break places such as DialControl
|
|
Font OutputDevice::GetDefaultFont( sal_uInt16 nType, LanguageType eLang,
|
|
sal_uLong nFlags, const OutputDevice* pOutDev )
|
|
{
|
|
if (!pOutDev) // default is NULL
|
|
pOutDev = Application::GetDefaultDevice();
|
|
|
|
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 );
|
|
OUString aSearch;
|
|
|
|
if( !aDefault.isEmpty() )
|
|
aSearch = aDefault;
|
|
else
|
|
aSearch = rDefaults.getUserInterfaceFont( aLanguageTag ); // use the UI font as a fallback
|
|
|
|
Font aFont;
|
|
aFont.SetPitch( PITCH_VARIABLE );
|
|
|
|
switch ( nType )
|
|
{
|
|
case DEFAULTFONT_SANS_UNICODE:
|
|
case DEFAULTFONT_UI_SANS:
|
|
aFont.SetFamily( FAMILY_SWISS );
|
|
break;
|
|
|
|
case DEFAULTFONT_SANS:
|
|
case DEFAULTFONT_LATIN_HEADING:
|
|
case DEFAULTFONT_LATIN_SPREADSHEET:
|
|
case DEFAULTFONT_LATIN_DISPLAY:
|
|
aFont.SetFamily( FAMILY_SWISS );
|
|
break;
|
|
|
|
case DEFAULTFONT_SERIF:
|
|
case DEFAULTFONT_LATIN_TEXT:
|
|
case DEFAULTFONT_LATIN_PRESENTATION:
|
|
aFont.SetFamily( FAMILY_ROMAN );
|
|
break;
|
|
|
|
case DEFAULTFONT_FIXED:
|
|
case DEFAULTFONT_LATIN_FIXED:
|
|
case DEFAULTFONT_UI_FIXED:
|
|
aFont.SetPitch( PITCH_FIXED );
|
|
aFont.SetFamily( FAMILY_MODERN );
|
|
break;
|
|
|
|
case DEFAULTFONT_SYMBOL:
|
|
aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL );
|
|
break;
|
|
|
|
case DEFAULTFONT_CJK_TEXT:
|
|
case DEFAULTFONT_CJK_PRESENTATION:
|
|
case DEFAULTFONT_CJK_SPREADSHEET:
|
|
case DEFAULTFONT_CJK_HEADING:
|
|
case DEFAULTFONT_CJK_DISPLAY:
|
|
aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later...
|
|
break;
|
|
|
|
case DEFAULTFONT_CTL_TEXT:
|
|
case DEFAULTFONT_CTL_PRESENTATION:
|
|
case DEFAULTFONT_CTL_SPREADSHEET:
|
|
case DEFAULTFONT_CTL_HEADING:
|
|
case DEFAULTFONT_CTL_DISPLAY:
|
|
aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later...
|
|
break;
|
|
}
|
|
|
|
if ( !aSearch.isEmpty() )
|
|
{
|
|
aFont.SetHeight( 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;
|
|
OUString aSearchName;
|
|
sal_Int32 nIndex = 0;
|
|
do
|
|
{
|
|
aSearchName = GetEnglishSearchFontName( GetNextFontToken( aSearch, nIndex ) );
|
|
|
|
PhysicalFontFamily* pFontFamily = pOutDev->mpFontCollection->ImplFindBySearchName( aSearchName );
|
|
if( pFontFamily )
|
|
{
|
|
AddTokenFontName( aName, pFontFamily->GetFamilyName() );
|
|
if( nFlags & DEFAULTFONT_FLAGS_ONLYONE )
|
|
break;
|
|
}
|
|
}
|
|
while ( nIndex != -1 );
|
|
aFont.SetName( aName );
|
|
}
|
|
|
|
// No Name, than set all names
|
|
if ( aFont.GetName().isEmpty() )
|
|
{
|
|
if ( nFlags & DEFAULTFONT_FLAGS_ONLYONE )
|
|
{
|
|
if( !pOutDev )
|
|
{
|
|
SAL_WARN ("vcl.gdi", "No default window has been set for the application - we really shouldn't be able to get here");
|
|
sal_Int32 nIndex = 0;
|
|
aFont.SetName( aSearch.getToken( 0, ';', nIndex ) );
|
|
}
|
|
else
|
|
{
|
|
pOutDev->ImplInitFontList();
|
|
|
|
aFont.SetName( aSearch );
|
|
|
|
// convert to pixel height
|
|
Size aSize = pOutDev->ImplLogicToDevicePixel( aFont.GetSize() );
|
|
if ( !aSize.Height() )
|
|
{
|
|
// use default pixel height only when logical height is zero
|
|
if ( aFont.GetHeight() )
|
|
aSize.Height() = 1;
|
|
else
|
|
aSize.Height() = (12*pOutDev->mnDPIY)/72;
|
|
}
|
|
|
|
// use default width only when logical width is zero
|
|
if( (0 == aSize.Width()) && (0 != aFont.GetSize().Width()) )
|
|
aSize.Width() = 1;
|
|
|
|
// get the name of the first available font
|
|
float fExactHeight = static_cast<float>(aSize.Height());
|
|
ImplFontEntry* pEntry = pOutDev->mpFontCache->GetFontEntry( pOutDev->mpFontCollection, aFont, aSize, fExactHeight );
|
|
if (pEntry)
|
|
{
|
|
if( pEntry->maFontSelData.mpFontData )
|
|
aFont.SetName( pEntry->maFontSelData.mpFontData->GetFamilyName() );
|
|
else
|
|
aFont.SetName( pEntry->maFontSelData.maTargetName );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
aFont.SetName( aSearch );
|
|
}
|
|
}
|
|
|
|
#if OSL_DEBUG_LEVEL > 2
|
|
const char* s = "DEFAULTFONT_SANS_UNKNOWN";
|
|
switch ( nType )
|
|
{
|
|
case DEFAULTFONT_SANS_UNICODE: s = "DEFAULTFONT_SANS_UNICODE"; break;
|
|
case DEFAULTFONT_UI_SANS: s = "DEFAULTFONT_UI_SANS"; break;
|
|
|
|
case DEFAULTFONT_SANS: s = "DEFAULTFONT_SANS"; break;
|
|
case DEFAULTFONT_LATIN_HEADING: s = "DEFAULTFONT_LATIN_HEADING"; break;
|
|
case DEFAULTFONT_LATIN_SPREADSHEET: s = "DEFAULTFONT_LATIN_SPREADSHEET"; break;
|
|
case DEFAULTFONT_LATIN_DISPLAY: s = "DEFAULTFONT_LATIN_DISPLAY"; break;
|
|
|
|
case DEFAULTFONT_SERIF: s = "DEFAULTFONT_SERIF"; break;
|
|
case DEFAULTFONT_LATIN_TEXT: s = "DEFAULTFONT_LATIN_TEXT"; break;
|
|
case DEFAULTFONT_LATIN_PRESENTATION: s = "DEFAULTFONT_LATIN_PRESENTATION"; break;
|
|
|
|
case DEFAULTFONT_FIXED: s = "DEFAULTFONT_FIXED"; break;
|
|
case DEFAULTFONT_LATIN_FIXED: s = "DEFAULTFONT_LATIN_FIXED"; break;
|
|
case DEFAULTFONT_UI_FIXED: s = "DEFAULTFONT_UI_FIXED"; break;
|
|
|
|
case DEFAULTFONT_SYMBOL: s = "DEFAULTFONT_SYMBOL"; break;
|
|
|
|
case DEFAULTFONT_CJK_TEXT: s = "DEFAULTFONT_CJK_TEXT"; break;
|
|
case DEFAULTFONT_CJK_PRESENTATION: s = "DEFAULTFONT_CJK_PRESENTATION"; break;
|
|
case DEFAULTFONT_CJK_SPREADSHEET: s = "DEFAULTFONT_CJK_SPREADSHEET"; break;
|
|
case DEFAULTFONT_CJK_HEADING: s = "DEFAULTFONT_CJK_HEADING"; break;
|
|
case DEFAULTFONT_CJK_DISPLAY: s = "DEFAULTFONT_CJK_DISPLAY"; break;
|
|
|
|
case DEFAULTFONT_CTL_TEXT: s = "DEFAULTFONT_CTL_TEXT"; break;
|
|
case DEFAULTFONT_CTL_PRESENTATION: s = "DEFAULTFONT_CTL_PRESENTATION"; break;
|
|
case DEFAULTFONT_CTL_SPREADSHEET: s = "DEFAULTFONT_CTL_SPREADSHEET"; break;
|
|
case DEFAULTFONT_CTL_HEADING: s = "DEFAULTFONT_CTL_HEADING"; break;
|
|
case DEFAULTFONT_CTL_DISPLAY: s = "DEFAULTFONT_CTL_DISPLAY"; break;
|
|
}
|
|
fprintf( stderr, " OutputDevice::GetDefaultFont() Type=\"%s\" lang=%d flags=%ld FontName=\"%s\"\n",
|
|
s, eLang, nFlags,
|
|
OUStringToOString( aFont.GetName(), RTL_TEXTENCODING_UTF8 ).getStr()
|
|
);
|
|
#endif
|
|
|
|
return aFont;
|
|
}
|
|
|
|
ImplFontEntry::ImplFontEntry( const FontSelectPattern& rFontSelData )
|
|
: maFontSelData( rFontSelData )
|
|
, maMetric( rFontSelData )
|
|
, mpConversion( NULL )
|
|
, mnLineHeight( 0 )
|
|
, mnRefCount( 1 )
|
|
, mnSetFontFlags( 0 )
|
|
, mnOwnOrientation( 0 )
|
|
, mnOrientation( 0 )
|
|
, mbInit( false )
|
|
, mpUnicodeFallbackList( NULL )
|
|
{
|
|
maFontSelData.mpFontEntry = this;
|
|
}
|
|
|
|
ImplFontEntry::~ImplFontEntry()
|
|
{
|
|
delete mpUnicodeFallbackList;
|
|
}
|
|
|
|
size_t ImplFontEntry::GFBCacheKey_Hash::operator()( const GFBCacheKey& rData ) const
|
|
{
|
|
boost::hash<sal_UCS4> a;
|
|
boost::hash<int > b;
|
|
return a(rData.first) ^ b(rData.second);
|
|
}
|
|
|
|
void ImplFontEntry::AddFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, const OUString& rFontName )
|
|
{
|
|
if( !mpUnicodeFallbackList )
|
|
mpUnicodeFallbackList = new UnicodeFallbackList;
|
|
(*mpUnicodeFallbackList)[ GFBCacheKey(cChar,eWeight) ] = rFontName;
|
|
}
|
|
|
|
bool ImplFontEntry::GetFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, OUString* pFontName ) const
|
|
{
|
|
if( !mpUnicodeFallbackList )
|
|
return false;
|
|
|
|
UnicodeFallbackList::const_iterator it = mpUnicodeFallbackList->find( GFBCacheKey(cChar,eWeight) );
|
|
if( it == mpUnicodeFallbackList->end() )
|
|
return false;
|
|
|
|
*pFontName = (*it).second;
|
|
return true;
|
|
}
|
|
|
|
void ImplFontEntry::IgnoreFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, const OUString& rFontName )
|
|
{
|
|
// DBG_ASSERT( mpUnicodeFallbackList, "ImplFontEntry::IgnoreFallbackForUnicode no list" );
|
|
UnicodeFallbackList::iterator it = mpUnicodeFallbackList->find( GFBCacheKey(cChar,eWeight) );
|
|
// DBG_ASSERT( it != mpUnicodeFallbackList->end(), "ImplFontEntry::IgnoreFallbackForUnicode no match" );
|
|
if( it == mpUnicodeFallbackList->end() )
|
|
return;
|
|
if( (*it).second == rFontName )
|
|
mpUnicodeFallbackList->erase( it );
|
|
}
|
|
|
|
FontSelectPatternAttributes::FontSelectPatternAttributes( const Font& rFont,
|
|
const OUString& rSearchName, const Size& rSize, float fExactHeight )
|
|
: maSearchName( rSearchName )
|
|
, mnWidth( rSize.Width() )
|
|
, mnHeight( rSize.Height() )
|
|
, mfExactHeight( fExactHeight)
|
|
, mnOrientation( rFont.GetOrientation() )
|
|
, meLanguage( rFont.GetLanguage() )
|
|
, mbVertical( rFont.IsVertical() )
|
|
, mbNonAntialiased( false )
|
|
, mbEmbolden( false )
|
|
{
|
|
maTargetName = GetFamilyName();
|
|
|
|
rFont.GetFontAttributes( *this );
|
|
|
|
// normalize orientation between 0 and 3600
|
|
if( 3600 <= (unsigned)mnOrientation )
|
|
{
|
|
if( mnOrientation >= 0 )
|
|
mnOrientation %= 3600;
|
|
else
|
|
mnOrientation = 3600 - (-mnOrientation % 3600);
|
|
}
|
|
|
|
// normalize width and height
|
|
if( mnHeight < 0 )
|
|
mnHeight = -mnHeight;
|
|
if( mnWidth < 0 )
|
|
mnWidth = -mnWidth;
|
|
}
|
|
|
|
FontSelectPattern::FontSelectPattern( const Font& rFont,
|
|
const OUString& rSearchName, const Size& rSize, float fExactHeight)
|
|
: FontSelectPatternAttributes(rFont, rSearchName, rSize, fExactHeight)
|
|
, mpFontData( NULL )
|
|
, mpFontEntry( NULL )
|
|
{
|
|
}
|
|
|
|
// NOTE: this ctor is still used on Windows. Do not remove.
|
|
#ifdef WNT
|
|
FontSelectPatternAttributes::FontSelectPatternAttributes( const PhysicalFontFace& rFontData,
|
|
const Size& rSize, float fExactHeight, int nOrientation, bool bVertical )
|
|
: ImplFontAttributes( rFontData )
|
|
, mnWidth( rSize.Width() )
|
|
, mnHeight( rSize.Height() )
|
|
, mfExactHeight( fExactHeight )
|
|
, mnOrientation( nOrientation )
|
|
, meLanguage( 0 )
|
|
, mbVertical( bVertical )
|
|
, mbNonAntialiased( false )
|
|
, mbEmbolden( false )
|
|
{
|
|
maTargetName = maSearchName = GetFamilyName();
|
|
// NOTE: no normalization for width/height/orientation
|
|
}
|
|
|
|
FontSelectPattern::FontSelectPattern( const PhysicalFontFace& rFontData,
|
|
const Size& rSize, float fExactHeight, int nOrientation, bool bVertical )
|
|
: FontSelectPatternAttributes(rFontData, rSize, fExactHeight, nOrientation, bVertical)
|
|
, mpFontData( &rFontData )
|
|
, mpFontEntry( NULL )
|
|
{
|
|
}
|
|
#endif
|
|
|
|
void FontSelectPattern::copyAttributes(const FontSelectPatternAttributes &rAttributes)
|
|
{
|
|
static_cast<FontSelectPatternAttributes&>(*this) = rAttributes;
|
|
}
|
|
|
|
size_t ImplFontCache::IFSD_Hash::operator()( const FontSelectPattern& rFSD ) const
|
|
{
|
|
return rFSD.hashCode();
|
|
}
|
|
|
|
size_t FontSelectPatternAttributes::hashCode() const
|
|
{
|
|
// TODO: does it pay off to improve this hash function?
|
|
size_t nHash;
|
|
#if ENABLE_GRAPHITE
|
|
// check for features and generate a unique hash if necessary
|
|
if (maTargetName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX)
|
|
!= -1)
|
|
{
|
|
nHash = maTargetName.hashCode();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
nHash = maSearchName.hashCode();
|
|
}
|
|
nHash += 11 * mnHeight;
|
|
nHash += 19 * GetWeight();
|
|
nHash += 29 * GetSlant();
|
|
nHash += 37 * mnOrientation;
|
|
nHash += 41 * meLanguage;
|
|
if( mbVertical )
|
|
nHash += 53;
|
|
return nHash;
|
|
}
|
|
|
|
bool FontSelectPatternAttributes::operator==(const FontSelectPatternAttributes& rOther) const
|
|
{
|
|
if (static_cast<const ImplFontAttributes&>(*this) != static_cast<const ImplFontAttributes&>(rOther))
|
|
return false;
|
|
|
|
if (maTargetName != rOther.maTargetName)
|
|
return false;
|
|
|
|
if (maSearchName != rOther.maSearchName)
|
|
return false;
|
|
|
|
if (mnWidth != rOther.mnWidth)
|
|
return false;
|
|
|
|
if (mnHeight != rOther.mnHeight)
|
|
return false;
|
|
|
|
if (mfExactHeight != rOther.mfExactHeight)
|
|
return false;
|
|
|
|
if (mnOrientation != rOther.mnOrientation)
|
|
return false;
|
|
|
|
if (meLanguage != rOther.meLanguage)
|
|
return false;
|
|
|
|
if (mbVertical != rOther.mbVertical)
|
|
return false;
|
|
|
|
if (mbNonAntialiased != rOther.mbNonAntialiased)
|
|
return false;
|
|
|
|
if (mbEmbolden != rOther.mbEmbolden)
|
|
return false;
|
|
|
|
if (maItalicMatrix != rOther.maItalicMatrix)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ImplFontCache::IFSD_Equal::operator()(const FontSelectPattern& rA, const FontSelectPattern& rB) const
|
|
{
|
|
// check normalized font family name
|
|
if( rA.maSearchName != rB.maSearchName )
|
|
return false;
|
|
|
|
// check font transformation
|
|
if( (rA.mnHeight != rB.mnHeight)
|
|
|| (rA.mnWidth != rB.mnWidth)
|
|
|| (rA.mnOrientation != rB.mnOrientation) )
|
|
return false;
|
|
|
|
// check mapping relevant attributes
|
|
if( (rA.mbVertical != rB.mbVertical)
|
|
|| (rA.meLanguage != rB.meLanguage) )
|
|
return false;
|
|
|
|
// check font face attributes
|
|
if( (rA.GetWeight() != rB.GetWeight())
|
|
|| (rA.GetSlant() != rB.GetSlant())
|
|
// || (rA.meFamily != rB.meFamily) // TODO: remove this mostly obsolete member
|
|
|| (rA.GetPitch() != rB.GetPitch()) )
|
|
return false;
|
|
|
|
// check style name
|
|
if( rA.GetStyleName() != rB.GetStyleName() )
|
|
return false;
|
|
|
|
// Symbol fonts may recode from one type to another So they are only
|
|
// safely equivalent for equal targets
|
|
if (
|
|
(rA.mpFontData && rA.mpFontData->IsSymbolFont()) ||
|
|
(rB.mpFontData && rB.mpFontData->IsSymbolFont())
|
|
)
|
|
{
|
|
if (rA.maTargetName != rB.maTargetName)
|
|
return false;
|
|
}
|
|
|
|
#if ENABLE_GRAPHITE
|
|
// check for features
|
|
if ((rA.maTargetName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX)
|
|
!= -1 ||
|
|
rB.maTargetName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX)
|
|
!= -1) && rA.maTargetName != rB.maTargetName)
|
|
return false;
|
|
#endif
|
|
|
|
if (rA.mbEmbolden != rB.mbEmbolden)
|
|
return false;
|
|
|
|
if (rA.maItalicMatrix != rB.maItalicMatrix)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
ImplFontCache::ImplFontCache()
|
|
: mpFirstEntry( NULL ),
|
|
mnRef0Count( 0 )
|
|
{}
|
|
|
|
ImplFontCache::~ImplFontCache()
|
|
{
|
|
FontInstanceList::iterator it = maFontInstanceList.begin();
|
|
for(; it != maFontInstanceList.end(); ++it )
|
|
{
|
|
ImplFontEntry* pEntry = (*it).second;
|
|
delete pEntry;
|
|
}
|
|
}
|
|
|
|
ImplFontEntry* ImplFontCache::GetFontEntry( PhysicalFontCollection* pFontList,
|
|
const Font& rFont, const Size& rSize, float fExactHeight )
|
|
{
|
|
OUString aSearchName = rFont.GetName();
|
|
|
|
// initialize internal font request object
|
|
FontSelectPattern aFontSelData( rFont, aSearchName, rSize, fExactHeight );
|
|
return GetFontEntry( pFontList, aFontSelData );
|
|
}
|
|
|
|
ImplFontEntry* ImplFontCache::GetFontEntry( PhysicalFontCollection* pFontList,
|
|
FontSelectPattern& aFontSelData )
|
|
{
|
|
const FontSelectPattern aFontSelDataOrig(aFontSelData);
|
|
// check if a directly matching logical font instance is already cached,
|
|
// the most recently used font usually has a hit rate of >50%
|
|
ImplFontEntry *pEntry = NULL;
|
|
PhysicalFontFamily* pFontFamily = NULL;
|
|
IFSD_Equal aIFSD_Equal;
|
|
if( mpFirstEntry && aIFSD_Equal( aFontSelData, mpFirstEntry->maFontSelData ) )
|
|
pEntry = mpFirstEntry;
|
|
else
|
|
{
|
|
FontInstanceList::iterator it = maFontInstanceList.find( aFontSelData );
|
|
if( it != maFontInstanceList.end() )
|
|
pEntry = (*it).second;
|
|
}
|
|
|
|
if( !pEntry ) // no direct cache hit
|
|
{
|
|
// find the best matching logical font family and update font selector accordingly
|
|
pFontFamily = pFontList->ImplFindByFont( aFontSelData );
|
|
DBG_ASSERT( (pFontFamily != NULL), "ImplFontCache::Get() No logical font found!" );
|
|
if( pFontFamily )
|
|
aFontSelData.maSearchName = pFontFamily->GetSearchName();
|
|
|
|
// check if an indirectly matching logical font instance is already cached
|
|
FontInstanceList::iterator it = maFontInstanceList.find( aFontSelData );
|
|
if( it != maFontInstanceList.end() )
|
|
{
|
|
// we have an indirect cache hit
|
|
pEntry = (*it).second;
|
|
}
|
|
}
|
|
|
|
PhysicalFontFace* pFontData = NULL;
|
|
|
|
if (!pEntry && pFontFamily)// no cache hit => find the best matching physical font face
|
|
{
|
|
bool bOrigWasSymbol = aFontSelData.mpFontData && aFontSelData.mpFontData->IsSymbolFont();
|
|
pFontData = pFontFamily->FindBestFontFace( aFontSelData );
|
|
aFontSelData.mpFontData = pFontData;
|
|
bool bNewIsSymbol = aFontSelData.mpFontData && aFontSelData.mpFontData->IsSymbolFont();
|
|
|
|
if (bNewIsSymbol != bOrigWasSymbol)
|
|
{
|
|
// it is possible, though generally unlikely, that at this point we
|
|
// will attempt to use a symbol font as a last-ditch fallback for a
|
|
// non-symbol font request or vice versa, and by changing
|
|
// aFontSelData.mpFontData to/from a symbol font we may now find
|
|
// something in the cache that can be reused which previously
|
|
// wasn't a candidate
|
|
FontInstanceList::iterator it = maFontInstanceList.find( aFontSelData );
|
|
if( it != maFontInstanceList.end() )
|
|
pEntry = (*it).second;
|
|
}
|
|
}
|
|
|
|
if( pEntry ) // cache hit => use existing font instance
|
|
{
|
|
// increase the font instance's reference count
|
|
if( !pEntry->mnRefCount++ )
|
|
--mnRef0Count;
|
|
}
|
|
|
|
if (!pEntry && pFontData)// still no cache hit => create a new font instance
|
|
{
|
|
// create a new logical font instance from this physical font face
|
|
pEntry = pFontData->CreateFontInstance( aFontSelData );
|
|
|
|
// if we're subtituting from or to a symbol font we may need a symbol
|
|
// conversion table
|
|
if( pFontData->IsSymbolFont() || aFontSelData.IsSymbolFont() )
|
|
{
|
|
if( aFontSelData.maTargetName != aFontSelData.maSearchName )
|
|
pEntry->mpConversion = ConvertChar::GetRecodeData( aFontSelData.maTargetName, aFontSelData.maSearchName );
|
|
}
|
|
|
|
#ifdef MACOSX
|
|
//It might be better to dig out the font version of the target font
|
|
//to see if it's a modern re-coded apple symbol font in case that
|
|
//font shows up on a different platform
|
|
if (!pEntry->mpConversion &&
|
|
aFontSelData.maTargetName.equalsIgnoreAsciiCase("symbol") &&
|
|
aFontSelData.maSearchName.equalsIgnoreAsciiCase("symbol"))
|
|
{
|
|
pEntry->mpConversion = ConvertChar::GetRecodeData( OUString("Symbol"), OUString("AppleSymbol") );
|
|
}
|
|
#endif
|
|
|
|
// Add the new entry to the cache with the original FontSelectPattern,
|
|
// so that we can find it next time as a direct cache hit.
|
|
maFontInstanceList[ aFontSelDataOrig ] = pEntry;
|
|
}
|
|
|
|
mpFirstEntry = pEntry;
|
|
return pEntry;
|
|
}
|
|
|
|
ImplFontEntry* ImplFontCache::GetGlyphFallbackFont( PhysicalFontCollection* pFontCollection,
|
|
FontSelectPattern& rFontSelData, int nFallbackLevel, OUString& rMissingCodes )
|
|
{
|
|
// get a candidate font for glyph fallback
|
|
// unless the previously selected font got a device specific substitution
|
|
// e.g. PsPrint Arial->Helvetica for udiaeresis when Helvetica doesn't support it
|
|
if( nFallbackLevel >= 1)
|
|
{
|
|
PhysicalFontFamily* pFallbackData = NULL;
|
|
|
|
//fdo#33898 If someone has EUDC installed then they really want that to
|
|
//be used as the first-choice glyph fallback seeing as it's filled with
|
|
//private area codes with don't make any sense in any other font so
|
|
//prioritise it here if it's available. Ideally we would remove from
|
|
//rMissingCodes all the glyphs which it is able to resolve as an
|
|
//optimization, but that's tricky to achieve cross-platform without
|
|
//sufficient heavy-weight code that's likely to undo the value of the
|
|
//optimization
|
|
if (nFallbackLevel == 1)
|
|
pFallbackData = pFontCollection->FindFontFamily(OUString("EUDC"));
|
|
if (!pFallbackData)
|
|
pFallbackData = pFontCollection->GetGlyphFallbackFont(rFontSelData, rMissingCodes, nFallbackLevel-1);
|
|
// escape when there are no font candidates
|
|
if( !pFallbackData )
|
|
return NULL;
|
|
// override the font name
|
|
rFontSelData.SetFamilyName( pFallbackData->GetFamilyName() );
|
|
// clear the cached normalized name
|
|
rFontSelData.maSearchName = "";
|
|
}
|
|
|
|
ImplFontEntry* pFallbackFont = GetFontEntry( pFontCollection, rFontSelData );
|
|
return pFallbackFont;
|
|
}
|
|
|
|
void ImplFontCache::Release( ImplFontEntry* pEntry )
|
|
{
|
|
static const int FONTCACHE_MAX = 50;
|
|
|
|
DBG_ASSERT( (pEntry->mnRefCount > 0), "ImplFontCache::Release() - font refcount underflow" );
|
|
if( --pEntry->mnRefCount > 0 )
|
|
return;
|
|
|
|
if( ++mnRef0Count < FONTCACHE_MAX )
|
|
return;
|
|
|
|
// remove unused entries from font instance cache
|
|
FontInstanceList::iterator it_next = maFontInstanceList.begin();
|
|
while( it_next != maFontInstanceList.end() )
|
|
{
|
|
FontInstanceList::iterator it = it_next++;
|
|
ImplFontEntry* pFontEntry = (*it).second;
|
|
if( pFontEntry->mnRefCount > 0 )
|
|
continue;
|
|
|
|
maFontInstanceList.erase( it );
|
|
delete pFontEntry;
|
|
--mnRef0Count;
|
|
DBG_ASSERT( (mnRef0Count>=0), "ImplFontCache::Release() - refcount0 underflow" );
|
|
|
|
if( mpFirstEntry == pFontEntry )
|
|
mpFirstEntry = NULL;
|
|
}
|
|
|
|
DBG_ASSERT( (mnRef0Count==0), "ImplFontCache::Release() - refcount0 mismatch" );
|
|
}
|
|
|
|
void ImplFontCache::Invalidate()
|
|
{
|
|
// delete unreferenced entries
|
|
FontInstanceList::iterator it = maFontInstanceList.begin();
|
|
for(; it != maFontInstanceList.end(); ++it )
|
|
{
|
|
ImplFontEntry* pFontEntry = (*it).second;
|
|
if( pFontEntry->mnRefCount > 0 )
|
|
continue;
|
|
|
|
delete pFontEntry;
|
|
--mnRef0Count;
|
|
}
|
|
|
|
// #112304# make sure the font cache is really clean
|
|
mpFirstEntry = NULL;
|
|
maFontInstanceList.clear();
|
|
|
|
DBG_ASSERT( (mnRef0Count==0), "ImplFontCache::Invalidate() - mnRef0Count non-zero" );
|
|
}
|
|
|
|
void OutputDevice::ImplInitFontList() const
|
|
{
|
|
if( !mpFontCollection->Count() )
|
|
{
|
|
if( mpGraphics || AcquireGraphics() )
|
|
{
|
|
SAL_INFO( "vcl.gdi", "OutputDevice::ImplInitFontList()" );
|
|
mpGraphics->GetDevFontList( mpFontCollection );
|
|
|
|
// There is absolutely no way there should be no fonts available on the device
|
|
if( !mpFontCollection->Count() )
|
|
{
|
|
OUString aError( "Application error: no fonts and no vcl resource found on your system" );
|
|
ResMgr* pMgr = ImplGetResMgr();
|
|
if( pMgr )
|
|
{
|
|
OUString aResStr(ResId(SV_ACCESSERROR_NO_FONTS, *pMgr).toString());
|
|
if( !aResStr.isEmpty() )
|
|
aError = aResStr;
|
|
}
|
|
Application::Abort( aError );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OutputDevice::InitFont() const
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
if (!mpFontEntry)
|
|
return;
|
|
|
|
if ( mbInitFont )
|
|
{
|
|
// decide if antialiasing is appropriate
|
|
bool bNonAntialiased = (GetAntialiasing() & ANTIALIASING_DISABLE_TEXT) != 0;
|
|
const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
|
|
bNonAntialiased |= ((rStyleSettings.GetDisplayOptions() & DISPLAY_OPTION_AA_DISABLE) != 0);
|
|
bNonAntialiased |= (int(rStyleSettings.GetAntialiasingMinPixelHeight()) > mpFontEntry->maFontSelData.mnHeight);
|
|
mpFontEntry->maFontSelData.mbNonAntialiased = bNonAntialiased;
|
|
|
|
// select font in the device layers
|
|
mpFontEntry->mnSetFontFlags = mpGraphics->SetFont( &(mpFontEntry->maFontSelData), 0 );
|
|
mbInitFont = false;
|
|
}
|
|
}
|
|
|
|
bool OutputDevice::ImplNewFont() const
|
|
{
|
|
DBG_TESTSOLARMUTEX();
|
|
|
|
// get correct font list on the PDF writer if necessary
|
|
if( mpPDFWriter )
|
|
{
|
|
const ImplSVData* pSVData = ImplGetSVData();
|
|
if( mpFontCollection == pSVData->maGDIData.mpScreenFontList
|
|
|| mpFontCache == pSVData->maGDIData.mpScreenFontCache )
|
|
const_cast<OutputDevice&>(*this).ImplUpdateFontData( true );
|
|
}
|
|
|
|
if ( !mbNewFont )
|
|
return true;
|
|
|
|
// we need a graphics
|
|
if ( !mpGraphics && !AcquireGraphics() )
|
|
return false;
|
|
SalGraphics* pGraphics = mpGraphics;
|
|
ImplInitFontList();
|
|
|
|
// convert to pixel height
|
|
// TODO: replace integer based aSize completely with subpixel accurate type
|
|
float fExactHeight = ImplFloatLogicHeightToDevicePixel( static_cast<float>(maFont.GetHeight()) );
|
|
Size aSize = ImplLogicToDevicePixel( maFont.GetSize() );
|
|
if ( !aSize.Height() )
|
|
{
|
|
// use default pixel height only when logical height is zero
|
|
if ( maFont.GetSize().Height() )
|
|
aSize.Height() = 1;
|
|
else
|
|
aSize.Height() = (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.GetSize().Width()) )
|
|
aSize.Width() = 1;
|
|
|
|
// get font entry
|
|
ImplFontEntry* pOldEntry = mpFontEntry;
|
|
mpFontEntry = mpFontCache->GetFontEntry( mpFontCollection, maFont, aSize, fExactHeight );
|
|
if( pOldEntry )
|
|
mpFontCache->Release( pOldEntry );
|
|
|
|
ImplFontEntry* pFontEntry = mpFontEntry;
|
|
|
|
if (!pFontEntry)
|
|
return false;
|
|
|
|
// mark when lower layers need to get involved
|
|
mbNewFont = false;
|
|
if( pFontEntry != pOldEntry )
|
|
mbInitFont = true;
|
|
|
|
// select font when it has not been initialized yet
|
|
if ( !pFontEntry->mbInit )
|
|
{
|
|
InitFont();
|
|
|
|
// get metric data from device layers
|
|
if ( pGraphics )
|
|
{
|
|
pFontEntry->mbInit = true;
|
|
|
|
pFontEntry->maMetric.mnOrientation = sal::static_int_cast<short>(pFontEntry->maFontSelData.mnOrientation);
|
|
pGraphics->GetFontMetric( &(pFontEntry->maMetric) );
|
|
|
|
pFontEntry->maMetric.ImplInitTextLineSize( this );
|
|
pFontEntry->maMetric.ImplInitAboveTextLineSize();
|
|
|
|
pFontEntry->mnLineHeight = pFontEntry->maMetric.mnAscent + pFontEntry->maMetric.mnDescent;
|
|
|
|
SetFontOrientation( pFontEntry );
|
|
}
|
|
}
|
|
|
|
// enable kerning array if requested
|
|
if ( maFont.GetKerning() & KERNING_FONTSPECIFIC )
|
|
{
|
|
// TODO: test if physical font supports kerning and disable if not
|
|
if( pFontEntry->maMetric.mbKernableFont )
|
|
mbKerning = true;
|
|
}
|
|
else
|
|
mbKerning = false;
|
|
if ( maFont.GetKerning() & KERNING_ASIAN )
|
|
mbKerning = true;
|
|
|
|
// calculate EmphasisArea
|
|
mnEmphasisAscent = 0;
|
|
mnEmphasisDescent = 0;
|
|
if ( maFont.GetEmphasisMark() & EMPHASISMARK_STYLE )
|
|
{
|
|
FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont );
|
|
long nEmphasisHeight = (pFontEntry->mnLineHeight*250)/1000;
|
|
if ( nEmphasisHeight < 1 )
|
|
nEmphasisHeight = 1;
|
|
if ( nEmphasisMark & EMPHASISMARK_POS_BELOW )
|
|
mnEmphasisDescent = nEmphasisHeight;
|
|
else
|
|
mnEmphasisAscent = nEmphasisHeight;
|
|
}
|
|
|
|
// calculate text offset depending on TextAlignment
|
|
TextAlign eAlign = maFont.GetAlign();
|
|
if ( eAlign == ALIGN_BASELINE )
|
|
{
|
|
mnTextOffX = 0;
|
|
mnTextOffY = 0;
|
|
}
|
|
else if ( eAlign == ALIGN_TOP )
|
|
{
|
|
mnTextOffX = 0;
|
|
mnTextOffY = +pFontEntry->maMetric.mnAscent + mnEmphasisAscent;
|
|
if ( pFontEntry->mnOrientation )
|
|
ImplRotatePos( 0, 0, mnTextOffX, mnTextOffY, pFontEntry->mnOrientation );
|
|
}
|
|
else // eAlign == ALIGN_BOTTOM
|
|
{
|
|
mnTextOffX = 0;
|
|
mnTextOffY = -pFontEntry->maMetric.mnDescent + mnEmphasisDescent;
|
|
if ( pFontEntry->mnOrientation )
|
|
ImplRotatePos( 0, 0, mnTextOffX, mnTextOffY, pFontEntry->mnOrientation );
|
|
}
|
|
|
|
mbTextLines = ((maFont.GetUnderline() != UNDERLINE_NONE) && (maFont.GetUnderline() != UNDERLINE_DONTKNOW)) ||
|
|
((maFont.GetOverline() != UNDERLINE_NONE) && (maFont.GetOverline() != UNDERLINE_DONTKNOW)) ||
|
|
((maFont.GetStrikeout() != STRIKEOUT_NONE) && (maFont.GetStrikeout() != STRIKEOUT_DONTKNOW));
|
|
mbTextSpecial = maFont.IsShadow() || maFont.IsOutline() ||
|
|
(maFont.GetRelief() != RELIEF_NONE);
|
|
|
|
|
|
// #95414# fix for OLE objects which use scale factors very creatively
|
|
if( mbMap && !aSize.Width() )
|
|
{
|
|
int nOrigWidth = pFontEntry->maMetric.mnWidth;
|
|
float fStretch = (float)maMapRes.mnMapScNumX * maMapRes.mnMapScDenomY;
|
|
fStretch /= (float)maMapRes.mnMapScNumY * maMapRes.mnMapScDenomX;
|
|
int nNewWidth = (int)(nOrigWidth * fStretch + 0.5);
|
|
if( (nNewWidth != nOrigWidth) && (nNewWidth != 0) )
|
|
{
|
|
Size aOrigSize = maFont.GetSize();
|
|
const_cast<Font&>(maFont).SetSize( Size( nNewWidth, aSize.Height() ) );
|
|
mbMap = false;
|
|
mbNewFont = true;
|
|
ImplNewFont(); // recurse once using stretched width
|
|
mbMap = true;
|
|
const_cast<Font&>(maFont).SetSize( aOrigSize );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void OutputDevice::SetFontOrientation( ImplFontEntry* const pFontEntry ) const
|
|
{
|
|
if( pFontEntry->maFontSelData.mnOrientation && !pFontEntry->maMetric.mnOrientation )
|
|
{
|
|
pFontEntry->mnOwnOrientation = sal::static_int_cast<short>(pFontEntry->maFontSelData.mnOrientation);
|
|
pFontEntry->mnOrientation = pFontEntry->mnOwnOrientation;
|
|
}
|
|
else
|
|
{
|
|
pFontEntry->mnOrientation = pFontEntry->maMetric.mnOrientation;
|
|
}
|
|
}
|
|
|
|
bool ImplFontAttributes::operator==(const ImplFontAttributes& rOther) const
|
|
{
|
|
if (maName != rOther.maName)
|
|
return false;
|
|
|
|
if (maStyleName != rOther.maStyleName)
|
|
return false;
|
|
|
|
if (meWeight != rOther.meWeight)
|
|
return false;
|
|
|
|
if (meItalic != rOther.meItalic)
|
|
return false;
|
|
|
|
if (meFamily != rOther.meFamily)
|
|
return false;
|
|
|
|
if (mePitch != rOther.mePitch)
|
|
return false;
|
|
|
|
if (meWidthType != rOther.meWidthType)
|
|
return false;
|
|
|
|
if (mbSymbolFlag != rOther.mbSymbolFlag)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
ImplFontMetricData::ImplFontMetricData( const FontSelectPattern& rFontSelData )
|
|
: ImplFontAttributes( rFontSelData )
|
|
, mnWidth ( rFontSelData.mnWidth)
|
|
, mnOrientation( (short)(rFontSelData.mnOrientation))
|
|
, mnAscent( 0 )
|
|
, mnDescent( 0 )
|
|
, mnIntLeading( 0 )
|
|
, mnExtLeading( 0 )
|
|
, mnSlant( 0 )
|
|
, mnMinKashida( 0 )
|
|
, meFamilyType(FAMILY_DONTKNOW)
|
|
, mbScalableFont(false)
|
|
, 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 )
|
|
{
|
|
// intialize the used font name
|
|
if( rFontSelData.mpFontData )
|
|
{
|
|
SetFamilyName( rFontSelData.mpFontData->GetFamilyName() );
|
|
SetStyleName( rFontSelData.mpFontData->GetStyleName() );
|
|
mbDevice = rFontSelData.mpFontData->mbDevice;
|
|
mbKernableFont = true;
|
|
}
|
|
else
|
|
{
|
|
sal_Int32 nTokenPos = 0;
|
|
SetFamilyName( GetNextFontToken( rFontSelData.GetFamilyName(), nTokenPos ) );
|
|
SetStyleName( rFontSelData.GetStyleName() );
|
|
mbDevice = false;
|
|
mbKernableFont = false;
|
|
}
|
|
}
|
|
|
|
void ImplFontMetricData::ImplInitTextLineSize( const OutputDevice* pDev )
|
|
{
|
|
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;
|
|
|
|
long nLineHeight = ((nDescent*25)+50) / 100;
|
|
if ( !nLineHeight )
|
|
nLineHeight = 1;
|
|
long nLineHeight2 = nLineHeight / 2;
|
|
if ( !nLineHeight2 )
|
|
nLineHeight2 = 1;
|
|
|
|
long nBLineHeight = ((nDescent*50)+50) / 100;
|
|
if ( nBLineHeight == nLineHeight )
|
|
nBLineHeight++;
|
|
long nBLineHeight2 = nBLineHeight/2;
|
|
if ( !nBLineHeight2 )
|
|
nBLineHeight2 = 1;
|
|
|
|
long n2LineHeight = ((nDescent*16)+50) / 100;
|
|
if ( !n2LineHeight )
|
|
n2LineHeight = 1;
|
|
long n2LineDY = n2LineHeight;
|
|
/* #117909#
|
|
* add some pixels to minimum double line distance on higher resolution devices
|
|
*/
|
|
long nMin2LineDY = 1 + pDev->GetDPIY()/150;
|
|
if ( n2LineDY < nMin2LineDY )
|
|
n2LineDY = nMin2LineDY;
|
|
long n2LineDY2 = n2LineDY/2;
|
|
if ( !n2LineDY2 )
|
|
n2LineDY2 = 1;
|
|
|
|
long nUnderlineOffset = mnDescent/2 + 1;
|
|
long nStrikeoutOffset = -((mnAscent - mnIntLeading) / 3);
|
|
|
|
mnUnderlineSize = nLineHeight;
|
|
mnUnderlineOffset = nUnderlineOffset - nLineHeight2;
|
|
|
|
mnBUnderlineSize = nBLineHeight;
|
|
mnBUnderlineOffset = nUnderlineOffset - nBLineHeight2;
|
|
|
|
mnDUnderlineSize = n2LineHeight;
|
|
mnDUnderlineOffset1 = nUnderlineOffset - n2LineDY2 - n2LineHeight;
|
|
mnDUnderlineOffset2 = mnDUnderlineOffset1 + n2LineDY + n2LineHeight;
|
|
|
|
long nWCalcSize = mnDescent;
|
|
if ( nWCalcSize < 6 )
|
|
{
|
|
if ( (nWCalcSize == 1) || (nWCalcSize == 2) )
|
|
mnWUnderlineSize = nWCalcSize;
|
|
else
|
|
mnWUnderlineSize = 3;
|
|
}
|
|
else
|
|
mnWUnderlineSize = ((nWCalcSize*50)+50) / 100;
|
|
|
|
// #109280# the following line assures that wavelnes are never placed below the descent, however
|
|
// for most fonts the waveline then is drawn into the text, so we better keep the old solution
|
|
// pFontEntry->maMetric.mnWUnderlineOffset = pFontEntry->maMetric.mnDescent + 1 - pFontEntry->maMetric.mnWUnderlineSize;
|
|
mnWUnderlineOffset = nUnderlineOffset;
|
|
|
|
mnStrikeoutSize = nLineHeight;
|
|
mnStrikeoutOffset = nStrikeoutOffset - nLineHeight2;
|
|
|
|
mnBStrikeoutSize = nBLineHeight;
|
|
mnBStrikeoutOffset = nStrikeoutOffset - nBLineHeight2;
|
|
|
|
mnDStrikeoutSize = n2LineHeight;
|
|
mnDStrikeoutOffset1 = nStrikeoutOffset - n2LineDY2 - n2LineHeight;
|
|
mnDStrikeoutOffset2 = mnDStrikeoutOffset1 + n2LineDY + n2LineHeight;
|
|
}
|
|
|
|
void ImplFontMetricData::ImplInitAboveTextLineSize()
|
|
{
|
|
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;
|
|
}
|
|
|
|
long nLineHeight = ((nIntLeading*25)+50) / 100;
|
|
if ( !nLineHeight )
|
|
nLineHeight = 1;
|
|
|
|
long nBLineHeight = ((nIntLeading*50)+50) / 100;
|
|
if ( nBLineHeight == nLineHeight )
|
|
nBLineHeight++;
|
|
|
|
long n2LineHeight = ((nIntLeading*16)+50) / 100;
|
|
if ( !n2LineHeight )
|
|
n2LineHeight = 1;
|
|
|
|
long nCeiling = -mnAscent;
|
|
|
|
mnAboveUnderlineSize = nLineHeight;
|
|
mnAboveUnderlineOffset = nCeiling + (nIntLeading - nLineHeight + 1) / 2;
|
|
|
|
mnAboveBUnderlineSize = nBLineHeight;
|
|
mnAboveBUnderlineOffset = nCeiling + (nIntLeading - nBLineHeight + 1) / 2;
|
|
|
|
mnAboveDUnderlineSize = n2LineHeight;
|
|
mnAboveDUnderlineOffset1 = nCeiling + (nIntLeading - 3*n2LineHeight + 1) / 2;
|
|
mnAboveDUnderlineOffset2 = nCeiling + (nIntLeading + n2LineHeight + 1) / 2;
|
|
|
|
long nWCalcSize = nIntLeading;
|
|
if ( nWCalcSize < 6 )
|
|
{
|
|
if ( (nWCalcSize == 1) || (nWCalcSize == 2) )
|
|
mnAboveWUnderlineSize = nWCalcSize;
|
|
else
|
|
mnAboveWUnderlineSize = 3;
|
|
}
|
|
else
|
|
mnAboveWUnderlineSize = ((nWCalcSize*50)+50) / 100;
|
|
|
|
mnAboveWUnderlineOffset = nCeiling + (nIntLeading + 1) / 2;
|
|
}
|
|
|
|
void OutputDevice::ImplDrawEmphasisMark( long nBaseX, long nX, long nY,
|
|
const PolyPolygon& rPolyPoly, bool bPolyLine,
|
|
const Rectangle& rRect1, const Rectangle& rRect2 )
|
|
{
|
|
if( IsRTLEnabled() )
|
|
// --- RTL --- mirror at basex
|
|
nX = nBaseX - (nX - nBaseX - 1);
|
|
|
|
nX -= mnOutOffX;
|
|
nY -= mnOutOffY;
|
|
|
|
if ( rPolyPoly.Count() )
|
|
{
|
|
if ( bPolyLine )
|
|
{
|
|
Polygon aPoly = rPolyPoly.GetObject( 0 );
|
|
aPoly.Move( nX, nY );
|
|
DrawPolyLine( aPoly );
|
|
}
|
|
else
|
|
{
|
|
PolyPolygon aPolyPoly = rPolyPoly;
|
|
aPolyPoly.Move( nX, nY );
|
|
DrawPolyPolygon( aPolyPoly );
|
|
}
|
|
}
|
|
|
|
if ( !rRect1.IsEmpty() )
|
|
{
|
|
Rectangle aRect( Point( nX+rRect1.Left(),
|
|
nY+rRect1.Top() ), rRect1.GetSize() );
|
|
DrawRect( aRect );
|
|
}
|
|
|
|
if ( !rRect2.IsEmpty() )
|
|
{
|
|
Rectangle aRect( Point( nX+rRect2.Left(),
|
|
nY+rRect2.Top() ), rRect2.GetSize() );
|
|
|
|
DrawRect( aRect );
|
|
}
|
|
}
|
|
|
|
void OutputDevice::ImplDrawEmphasisMarks( SalLayout& rSalLayout )
|
|
{
|
|
Color aOldLineColor = GetLineColor();
|
|
Color aOldFillColor = GetFillColor();
|
|
bool bOldMap = mbMap;
|
|
GDIMetaFile* pOldMetaFile = mpMetaFile;
|
|
mpMetaFile = NULL;
|
|
EnableMapMode( false );
|
|
|
|
FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont );
|
|
PolyPolygon aPolyPoly;
|
|
Rectangle aRect1;
|
|
Rectangle aRect2;
|
|
long nEmphasisYOff;
|
|
long nEmphasisWidth;
|
|
long nEmphasisHeight;
|
|
bool bPolyLine;
|
|
|
|
if ( nEmphasisMark & EMPHASISMARK_POS_BELOW )
|
|
nEmphasisHeight = mnEmphasisDescent;
|
|
else
|
|
nEmphasisHeight = mnEmphasisAscent;
|
|
|
|
ImplGetEmphasisMark( aPolyPoly, bPolyLine,
|
|
aRect1, aRect2,
|
|
nEmphasisYOff, nEmphasisWidth,
|
|
nEmphasisMark,
|
|
nEmphasisHeight, mpFontEntry->mnOrientation );
|
|
|
|
if ( bPolyLine )
|
|
{
|
|
SetLineColor( GetTextColor() );
|
|
SetFillColor();
|
|
}
|
|
else
|
|
{
|
|
SetLineColor();
|
|
SetFillColor( GetTextColor() );
|
|
}
|
|
|
|
Point aOffset = Point(0,0);
|
|
|
|
if ( nEmphasisMark & EMPHASISMARK_POS_BELOW )
|
|
aOffset.Y() += mpFontEntry->maMetric.mnDescent + nEmphasisYOff;
|
|
else
|
|
aOffset.Y() -= mpFontEntry->maMetric.mnAscent + nEmphasisYOff;
|
|
|
|
long nEmphasisWidth2 = nEmphasisWidth / 2;
|
|
long nEmphasisHeight2 = nEmphasisHeight / 2;
|
|
aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 );
|
|
|
|
Point aOutPoint;
|
|
Rectangle aRectangle;
|
|
for( int nStart = 0;;)
|
|
{
|
|
sal_GlyphId aGlyphId;
|
|
if( !rSalLayout.GetNextGlyphs( 1, &aGlyphId, aOutPoint, nStart ) )
|
|
break;
|
|
|
|
if( !mpGraphics->GetGlyphBoundRect( aGlyphId, aRectangle ) )
|
|
continue;
|
|
|
|
if( !rSalLayout.IsSpacingGlyph( aGlyphId ) )
|
|
{
|
|
Point aAdjPoint = aOffset;
|
|
aAdjPoint.X() += aRectangle.Left() + (aRectangle.GetWidth() - nEmphasisWidth) / 2;
|
|
if ( mpFontEntry->mnOrientation )
|
|
ImplRotatePos( 0, 0, aAdjPoint.X(), aAdjPoint.Y(), mpFontEntry->mnOrientation );
|
|
aOutPoint += aAdjPoint;
|
|
aOutPoint -= Point( nEmphasisWidth2, nEmphasisHeight2 );
|
|
ImplDrawEmphasisMark( rSalLayout.DrawBase().X(),
|
|
aOutPoint.X(), aOutPoint.Y(),
|
|
aPolyPoly, bPolyLine, aRect1, aRect2 );
|
|
}
|
|
}
|
|
|
|
SetLineColor( aOldLineColor );
|
|
SetFillColor( aOldFillColor );
|
|
EnableMapMode( bOldMap );
|
|
mpMetaFile = pOldMetaFile;
|
|
}
|
|
|
|
SalLayout* OutputDevice::getFallbackFont(ImplFontEntry &rFallbackFont,
|
|
FontSelectPattern &rFontSelData, int nFallbackLevel,
|
|
ImplLayoutArgs& rLayoutArgs) const
|
|
{
|
|
rFallbackFont.mnSetFontFlags = mpGraphics->SetFont( &rFontSelData, nFallbackLevel );
|
|
|
|
rLayoutArgs.ResetPos();
|
|
SalLayout* pFallback = mpGraphics->GetTextLayout( rLayoutArgs, nFallbackLevel );
|
|
|
|
if (!pFallback)
|
|
return NULL;
|
|
|
|
if (!pFallback->LayoutText(rLayoutArgs))
|
|
{
|
|
// there is no need for a font that couldn't resolve anything
|
|
pFallback->Release();
|
|
return NULL;
|
|
}
|
|
|
|
pFallback->AdjustLayout( rLayoutArgs );
|
|
|
|
return pFallback;
|
|
}
|
|
|
|
SalLayout* OutputDevice::ImplGlyphFallbackLayout( SalLayout* pSalLayout, ImplLayoutArgs& rLayoutArgs ) const
|
|
{
|
|
// This function relies on a valid mpFontEntry, 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 ( !mpFontEntry )
|
|
{
|
|
SAL_WARN ("vcl.gdi", "No font entry set in OutputDevice");
|
|
assert(mpFontEntry);
|
|
return NULL;
|
|
}
|
|
|
|
// prepare multi level glyph fallback
|
|
MultiSalLayout* pMultiSalLayout = NULL;
|
|
ImplLayoutRuns aLayoutRuns = rLayoutArgs.maRuns;
|
|
rLayoutArgs.PrepareFallback();
|
|
rLayoutArgs.mnFlags |= SAL_LAYOUT_FOR_FALLBACK;
|
|
|
|
// get list of unicodes that need glyph fallback
|
|
int nCharPos = -1;
|
|
bool bRTL = false;
|
|
OUStringBuffer aMissingCodeBuf;
|
|
while( rLayoutArgs.GetNextPos( &nCharPos, &bRTL) )
|
|
aMissingCodeBuf.append( rLayoutArgs.mpStr[ nCharPos ] );
|
|
rLayoutArgs.ResetPos();
|
|
OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear();
|
|
|
|
FontSelectPattern aFontSelData = mpFontEntry->maFontSelData;
|
|
|
|
// try if fallback fonts support the missing unicodes
|
|
for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel )
|
|
{
|
|
// find a font family suited for glyph fallback
|
|
#ifndef FONTFALLBACK_HOOKS_DISABLED
|
|
// GetGlyphFallbackFont() needs a valid aFontSelData.mpFontEntry
|
|
// if the system-specific glyph fallback is active
|
|
aFontSelData.mpFontEntry = mpFontEntry; // reset the fontentry to base-level
|
|
#endif
|
|
ImplFontEntry* pFallbackFont = mpFontCache->GetGlyphFallbackFont( mpFontCollection,
|
|
aFontSelData, nFallbackLevel, aMissingCodes );
|
|
if( !pFallbackFont )
|
|
break;
|
|
|
|
aFontSelData.mpFontEntry = pFallbackFont;
|
|
aFontSelData.mpFontData = pFallbackFont->maFontSelData.mpFontData;
|
|
if( nFallbackLevel < MAX_FALLBACK-1)
|
|
{
|
|
// ignore fallback font if it is the same as the original font
|
|
if( mpFontEntry->maFontSelData.mpFontData == aFontSelData.mpFontData )
|
|
{
|
|
mpFontCache->Release( pFallbackFont );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// create and add glyph fallback layout to multilayout
|
|
SalLayout* pFallback = getFallbackFont(*pFallbackFont, aFontSelData,
|
|
nFallbackLevel, rLayoutArgs);
|
|
if (pFallback)
|
|
{
|
|
if( !pMultiSalLayout )
|
|
pMultiSalLayout = new MultiSalLayout( *pSalLayout );
|
|
pMultiSalLayout->AddFallback( *pFallback,
|
|
rLayoutArgs.maRuns, aFontSelData.mpFontData );
|
|
if (nFallbackLevel == MAX_FALLBACK-1)
|
|
pMultiSalLayout->SetInComplete();
|
|
}
|
|
|
|
mpFontCache->Release( pFallbackFont );
|
|
|
|
// break when this fallback was sufficient
|
|
if( !rLayoutArgs.PrepareFallback() )
|
|
break;
|
|
}
|
|
|
|
if( pMultiSalLayout && pMultiSalLayout->LayoutText( rLayoutArgs ) )
|
|
pSalLayout = pMultiSalLayout;
|
|
|
|
// restore orig font settings
|
|
pSalLayout->InitFont();
|
|
rLayoutArgs.maRuns = aLayoutRuns;
|
|
|
|
return pSalLayout;
|
|
}
|
|
|
|
long OutputDevice::GetMinKashida() const
|
|
{
|
|
if( mbNewFont && !ImplNewFont() )
|
|
return 0;
|
|
|
|
ImplFontEntry* pEntry = mpFontEntry;
|
|
ImplFontMetricData* pMetric = &(pEntry->maMetric);
|
|
return ImplDevicePixelToLogicWidth( pMetric->mnMinKashida );
|
|
}
|
|
|
|
sal_Int32 OutputDevice::ValidateKashidas ( const OUString& rTxt,
|
|
sal_Int32 nIdx, sal_Int32 nLen,
|
|
sal_Int32 nKashCount,
|
|
const sal_Int32* pKashidaPos,
|
|
sal_Int32* pKashidaPosDropped ) const
|
|
{
|
|
// do layout
|
|
SalLayout* pSalLayout = ImplLayout( rTxt, nIdx, nLen );
|
|
if( !pSalLayout )
|
|
return 0;
|
|
sal_Int32 nDropped = 0;
|
|
for( int i = 0; i < nKashCount; ++i )
|
|
{
|
|
if( !pSalLayout->IsKashidaPosValid( pKashidaPos[ i ] ))
|
|
{
|
|
pKashidaPosDropped[ nDropped ] = pKashidaPos [ i ];
|
|
++nDropped;
|
|
}
|
|
}
|
|
pSalLayout->Release();
|
|
return nDropped;
|
|
}
|
|
|
|
bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr,
|
|
int nIndex, int nLen, int nBase, MetricVector& rVector )
|
|
{
|
|
|
|
rVector.clear();
|
|
|
|
if(nLen == 0x0FFFF)
|
|
{
|
|
SAL_INFO("sal.rtl.xub",
|
|
"GetGlyphBoundRects Suspicious arguments nLen:" << nLen);
|
|
}
|
|
|
|
if( nIndex >= rStr.getLength() )
|
|
return false;
|
|
|
|
if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
|
|
{
|
|
nLen = rStr.getLength() - nIndex;
|
|
}
|
|
|
|
Rectangle aRect;
|
|
for( int i = 0; i < nLen; i++ )
|
|
{
|
|
if( !GetTextBoundRect( aRect, rStr, nBase, nIndex + i, 1 ) )
|
|
break;
|
|
aRect.Move( rOrigin.X(), rOrigin.Y() );
|
|
rVector.push_back( aRect );
|
|
}
|
|
|
|
return (nLen == (int)rVector.size());
|
|
}
|
|
|
|
sal_Int32 OutputDevice::HasGlyphs( const Font& rTempFont, const OUString& rStr,
|
|
sal_Int32 nIndex, sal_Int32 nLen ) const
|
|
{
|
|
if( nIndex >= rStr.getLength() )
|
|
return nIndex;
|
|
sal_Int32 nEnd;
|
|
if( nLen == -1 )
|
|
nEnd = rStr.getLength();
|
|
else
|
|
nEnd = std::min( rStr.getLength(), nIndex + nLen );
|
|
|
|
DBG_ASSERT( nIndex < nEnd, "StartPos >= EndPos?" );
|
|
DBG_ASSERT( nEnd <= rStr.getLength(), "String too short" );
|
|
|
|
// to get the map temporarily set font
|
|
const Font aOrigFont = GetFont();
|
|
const_cast<OutputDevice&>(*this).SetFont( rTempFont );
|
|
FontCharMap aFontCharMap;
|
|
bool bRet = GetFontCharMap( aFontCharMap );
|
|
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( ! aFontCharMap.HasChar( rStr[i] ) )
|
|
return nIndex;
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|