blob: 781ea8ac1e476326b393c32bed6ff2bf230aad35 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"
#include "rtl/ustring.hxx"
#include "osl/module.h"
#include "osl/file.h"
#include "tools/svwin.h"
#include "vcl/svapp.hxx"
#include "win/salgdi.h"
#include "win/saldata.hxx"
// for GetMirroredChar
#include "sft.hxx"
#include "sallayout.hxx"
#include <cstdio>
#include <malloc.h>
#ifndef __MINGW32__
#define alloca _alloca
#endif
#ifdef GCP_KERN_HACK
#include <algorithm>
#endif // GCP_KERN_HACK
#define USE_UNISCRIBE
#ifdef USE_UNISCRIBE
#include <Usp10.h>
#include <ShLwApi.h>
#include <winver.h>
#endif // USE_UNISCRIBE
#include <hash_map>
#include <set>
typedef std::hash_map<int,int> IntMap;
typedef std::set<int> IntSet;
// Graphite headers
#ifdef ENABLE_GRAPHITE
#include <i18npool/mslangid.hxx>
#include <graphite/GrClient.h>
#include <graphite/WinFont.h>
#include <graphite/Segment.h>
#include <graphite_layout.hxx>
#include <graphite_cache.hxx>
#include <graphite_features.hxx>
#endif
#define DROPPED_OUTGLYPH 0xFFFF
using namespace rtl;
// =======================================================================
// win32 specific physical font instance
class ImplWinFontEntry : public ImplFontEntry
{
public:
explicit ImplWinFontEntry( ImplFontSelectData& );
virtual ~ImplWinFontEntry();
private:
// TODO: also add HFONT??? Watch out for issues with too many active fonts...
#ifdef GCP_KERN_HACK
public:
bool HasKernData() const;
void SetKernData( int, const KERNINGPAIR* );
int GetKerning( sal_Unicode, sal_Unicode ) const;
private:
KERNINGPAIR* mpKerningPairs;
int mnKerningPairs;
#endif // GCP_KERN_HACK
#ifdef USE_UNISCRIBE
public:
SCRIPT_CACHE& GetScriptCache() const
{ return maScriptCache; }
private:
mutable SCRIPT_CACHE maScriptCache;
#endif // USE_UNISCRIBE
public:
int GetCachedGlyphWidth( int nCharCode ) const;
void CacheGlyphWidth( int nCharCode, int nCharWidth );
bool InitKashidaHandling( HDC );
int GetMinKashidaWidth() const { return mnMinKashidaWidth; }
int GetMinKashidaGlyph() const { return mnMinKashidaGlyph; }
private:
IntMap maWidthMap;
mutable int mnMinKashidaWidth;
mutable int mnMinKashidaGlyph;
};
// -----------------------------------------------------------------------
inline void ImplWinFontEntry::CacheGlyphWidth( int nCharCode, int nCharWidth )
{
maWidthMap[ nCharCode ] = nCharWidth;
}
inline int ImplWinFontEntry::GetCachedGlyphWidth( int nCharCode ) const
{
IntMap::const_iterator it = maWidthMap.find( nCharCode );
if( it == maWidthMap.end() )
return -1;
return it->second;
}
// =======================================================================
class WinLayout : public SalLayout
{
public:
WinLayout( HDC, const ImplWinFontData&, ImplWinFontEntry& );
virtual void InitFont() const;
void SetFontScale( float f ) { mfFontScale = f; }
float GetFontScale() const { return mfFontScale; }
HFONT DisableFontScaling( void) const;
#ifdef USE_UNISCRIBE
SCRIPT_CACHE& GetScriptCache() const
{ return mrWinFontEntry.GetScriptCache(); }
#endif // USE_UNISCRIBE
protected:
HDC mhDC; // WIN32 device handle
HFONT mhFont; // WIN32 font handle
int mnBaseAdv; // x-offset relative to Layout origin
float mfFontScale; // allows metrics emulation of huge font sizes
const ImplWinFontData& mrWinFontData;
ImplWinFontEntry& mrWinFontEntry;
};
// =======================================================================
class SimpleWinLayout : public WinLayout
{
public:
SimpleWinLayout( HDC, BYTE nCharSet, const ImplWinFontData&, ImplWinFontEntry& );
virtual ~SimpleWinLayout();
virtual bool LayoutText( ImplLayoutArgs& );
virtual void AdjustLayout( ImplLayoutArgs& );
virtual void DrawText( SalGraphics& ) const;
virtual int GetNextGlyphs( int nLen, sal_GlyphId* pGlyphs, Point& rPos, int&,
sal_Int32* pGlyphAdvances, int* pCharIndexes ) const;
virtual long FillDXArray( long* pDXArray ) const;
virtual int GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const;
virtual void GetCaretPositions( int nArraySize, long* pCaretXArray ) const;
// for glyph+font+script fallback
virtual void MoveGlyph( int nStart, long nNewXPos );
virtual void DropGlyph( int nStart );
virtual void Simplify( bool bIsBase );
protected:
void Justify( long nNewWidth );
void ApplyDXArray( const ImplLayoutArgs& );
private:
int mnGlyphCount;
int mnCharCount;
WCHAR* mpOutGlyphs;
int* mpGlyphAdvances; // if possible this is shared with mpGlyphAdvances[]
int* mpGlyphOrigAdvs;
int* mpCharWidths; // map rel char pos to char width
int* mpChars2Glyphs; // map rel char pos to abs glyph pos
int* mpGlyphs2Chars; // map abs glyph pos to abs char pos
bool* mpGlyphRTLFlags; // BiDi status for glyphs: true=>RTL
mutable long mnWidth;
bool mbDisableGlyphs;
int mnNotdefWidth;
BYTE mnCharSet;
};
// =======================================================================
WinLayout::WinLayout( HDC hDC, const ImplWinFontData& rWFD, ImplWinFontEntry& rWFE )
: mhDC( hDC ),
mhFont( (HFONT)::GetCurrentObject(hDC,OBJ_FONT) ),
mnBaseAdv( 0 ),
mfFontScale( 1.0 ),
mrWinFontData( rWFD ),
mrWinFontEntry( rWFE )
{}
// -----------------------------------------------------------------------
void WinLayout::InitFont() const
{
::SelectObject( mhDC, mhFont );
}
// -----------------------------------------------------------------------
// Using reasonably sized fonts to emulate huge fonts works around
// a lot of problems in printer and display drivers. Huge fonts are
// mostly used by high resolution reference devices which are never
// painted to anyway. In the rare case that a huge font needs to be
// displayed somewhere then the workaround doesn't help anymore.
// If the drivers fail silently for huge fonts, so be it...
HFONT WinLayout::DisableFontScaling() const
{
if( mfFontScale == 1.0 )
return 0;
LOGFONTW aLogFont;
::GetObjectW( mhFont, sizeof(LOGFONTW), &aLogFont);
aLogFont.lfHeight = (LONG)(mfFontScale * aLogFont.lfHeight);
aLogFont.lfWidth = (LONG)(mfFontScale * aLogFont.lfWidth);
HFONT hHugeFont = ::CreateFontIndirectW( &aLogFont);
if( !hHugeFont )
return 0;
return SelectFont( mhDC, hHugeFont );
}
// =======================================================================
SimpleWinLayout::SimpleWinLayout( HDC hDC, BYTE nCharSet,
const ImplWinFontData& rWinFontData, ImplWinFontEntry& rWinFontEntry )
: WinLayout( hDC, rWinFontData, rWinFontEntry ),
mnGlyphCount( 0 ),
mnCharCount( 0 ),
mpOutGlyphs( NULL ),
mpGlyphAdvances( NULL ),
mpGlyphOrigAdvs( NULL ),
mpCharWidths( NULL ),
mpChars2Glyphs( NULL ),
mpGlyphs2Chars( NULL ),
mpGlyphRTLFlags( NULL ),
mnWidth( 0 ),
mnNotdefWidth( -1 ),
mnCharSet( nCharSet ),
mbDisableGlyphs( false )
{
mbDisableGlyphs = true;
}
// -----------------------------------------------------------------------
SimpleWinLayout::~SimpleWinLayout()
{
delete[] mpGlyphRTLFlags;
delete[] mpGlyphs2Chars;
delete[] mpChars2Glyphs;
if( mpCharWidths != mpGlyphAdvances )
delete[] mpCharWidths;
delete[] mpGlyphOrigAdvs;
delete[] mpGlyphAdvances;
delete[] mpOutGlyphs;
}
// -----------------------------------------------------------------------
bool SimpleWinLayout::LayoutText( ImplLayoutArgs& rArgs )
{
// prepare layout
// TODO: fix case when recyclying old SimpleWinLayout object
mbDisableGlyphs |= ((rArgs.mnFlags & SAL_LAYOUT_DISABLE_GLYPH_PROCESSING) != 0);
mnCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
if( !mbDisableGlyphs )
{
// Win32 glyph APIs have serious problems with vertical layout
// => workaround is to use the unicode methods then
if( rArgs.mnFlags & SAL_LAYOUT_VERTICAL )
mbDisableGlyphs = true;
else
// use cached value from font face
mbDisableGlyphs = mrWinFontData.IsGlyphApiDisabled();
}
// TODO: use a cached value for bDisableAsianKern from upper layers
if( rArgs.mnFlags & SAL_LAYOUT_KERNING_ASIAN )
{
TEXTMETRICA aTextMetricA;
if( ::GetTextMetricsA( mhDC, &aTextMetricA )
&& !(aTextMetricA.tmPitchAndFamily & TMPF_FIXED_PITCH) && !(aTextMetricA.tmCharSet == 0x86) )
rArgs.mnFlags &= ~SAL_LAYOUT_KERNING_ASIAN;
}
// layout text
int i, j;
mnGlyphCount = 0;
bool bVertical = (rArgs.mnFlags & SAL_LAYOUT_VERTICAL) != 0;
// count the number of chars to process if no RTL run
rArgs.ResetPos();
bool bHasRTL = false;
while( rArgs.GetNextRun( &i, &j, &bHasRTL ) && !bHasRTL )
mnGlyphCount += j - i;
// if there are RTL runs we need room to remember individual BiDi flags
if( bHasRTL )
{
mpGlyphRTLFlags = new bool[ mnCharCount ];
for( i = 0; i < mnCharCount; ++i )
mpGlyphRTLFlags[i] = false;
}
// rewrite the logical string if needed to prepare for the API calls
const sal_Unicode* pBidiStr = rArgs.mpStr + rArgs.mnMinCharPos;
if( (mnGlyphCount != mnCharCount) || bVertical )
{
// we need to rewrite the pBidiStr when any of
// - BiDirectional layout
// - vertical layout
// - partial runs (e.g. with control chars or for glyph fallback)
// are involved
sal_Unicode* pRewrittenStr = (sal_Unicode*)alloca( mnCharCount * sizeof(sal_Unicode) );
pBidiStr = pRewrittenStr;
// note: glyph to char mapping is relative to first character
mpChars2Glyphs = new int[ mnCharCount ];
mpGlyphs2Chars = new int[ mnCharCount ];
for( i = 0; i < mnCharCount; ++i )
mpChars2Glyphs[i] = mpGlyphs2Chars[i] = -1;
mnGlyphCount = 0;
rArgs.ResetPos();
bool bIsRTL = false;
while( rArgs.GetNextRun( &i, &j, &bIsRTL ) )
{
do
{
// get the next leftmost character in this run
int nCharPos = bIsRTL ? --j : i++;
sal_UCS4 cChar = rArgs.mpStr[ nCharPos ];
// in the RTL case mirror the character and remember its RTL status
if( bIsRTL )
{
cChar = ::GetMirroredChar( cChar );
mpGlyphRTLFlags[ mnGlyphCount ] = true;
}
// for vertical writing use vertical alternatives
if( bVertical )
{
sal_UCS4 cVert = ::GetVerticalChar( cChar );
if( cVert )
cChar = cVert;
}
// rewrite the original string
// update the mappings between original and rewritten string
// TODO: support surrogates in rewritten strings
pRewrittenStr[ mnGlyphCount ] = static_cast<sal_Unicode>(cChar);
mpGlyphs2Chars[ mnGlyphCount ] = nCharPos;
mpChars2Glyphs[ nCharPos - rArgs.mnMinCharPos ] = mnGlyphCount;
++mnGlyphCount;
} while( i < j );
}
}
mpOutGlyphs = new WCHAR[ mnGlyphCount ];
mpGlyphAdvances = new int[ mnGlyphCount ];
if( rArgs.mnFlags & (SAL_LAYOUT_KERNING_PAIRS | SAL_LAYOUT_KERNING_ASIAN) )
mpGlyphOrigAdvs = new int[ mnGlyphCount ];
#ifndef GCP_KERN_HACK
DWORD nGcpOption = 0;
// enable kerning if requested
if( rArgs.mnFlags & SAL_LAYOUT_KERNING_PAIRS )
nGcpOption |= GCP_USEKERNING;
#endif // GCP_KERN_HACK
for( i = 0; i < mnGlyphCount; ++i )
mpOutGlyphs[i] = pBidiStr[ i ];
mnWidth = 0;
for( i = 0; i < mnGlyphCount; ++i )
{
// get the current UCS-4 code point, check for surrogate pairs
const WCHAR* pCodes = reinterpret_cast<LPCWSTR>(&pBidiStr[i]);
unsigned nCharCode = pCodes[0];
bool bSurrogate = ((nCharCode >= 0xD800) && (nCharCode <= 0xDFFF));
if( bSurrogate )
{
// ignore high surrogates, they were already processed with their low surrogates
if( nCharCode >= 0xDC00 )
continue;
// check the second half of the surrogate pair
bSurrogate &= (0xDC00 <= pCodes[1]) && (pCodes[1] <= 0xDFFF);
// calculate the UTF-32 code of valid surrogate pairs
if( bSurrogate )
nCharCode = 0x10000 + ((pCodes[0] - 0xD800) << 10) + (pCodes[1] - 0xDC00);
else // or fall back to a replacement character
nCharCode = '?';
}
// get the advance width for the current UTF-32 code point
int nGlyphWidth = mrWinFontEntry.GetCachedGlyphWidth( nCharCode );
if( nGlyphWidth == -1 )
{
ABC aABC;
SIZE aExtent;
if( ::GetTextExtentPoint32W( mhDC, &pCodes[0], bSurrogate ? 2 : 1, &aExtent) )
nGlyphWidth = aExtent.cx;
else if( ::GetCharABCWidthsW( mhDC, nCharCode, nCharCode, &aABC ) )
nGlyphWidth = aABC.abcA + aABC.abcB + aABC.abcC;
else if( !::GetCharWidth32W( mhDC, nCharCode, nCharCode, &nGlyphWidth )
&& !::GetCharWidthW( mhDC, nCharCode, nCharCode, &nGlyphWidth ) )
nGlyphWidth = 0;
mrWinFontEntry.CacheGlyphWidth( nCharCode, nGlyphWidth );
}
mpGlyphAdvances[ i ] = nGlyphWidth;
mnWidth += nGlyphWidth;
// the second half of surrogate pair gets a zero width
if( bSurrogate && ((i+1) < mnGlyphCount) )
mpGlyphAdvances[ i+1 ] = 0;
// check with the font face if glyph fallback is needed
if( mrWinFontData.HasChar( nCharCode ) )
continue;
// request glyph fallback at this position in the string
bool bRTL = mpGlyphRTLFlags ? mpGlyphRTLFlags[i] : false;
int nCharPos = mpGlyphs2Chars ? mpGlyphs2Chars[i]: i + rArgs.mnMinCharPos;
rArgs.NeedFallback( nCharPos, bRTL );
if( bSurrogate && ((nCharPos+1) < rArgs.mnLength) )
rArgs.NeedFallback( nCharPos+1, bRTL );
// replace the current glyph shape with the NotDef glyph shape
if( rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK )
{
// when we already are layouting for glyph fallback
// then a new unresolved glyph is not interesting
mnNotdefWidth = 0;
mpOutGlyphs[i] = DROPPED_OUTGLYPH;
}
else
{
if( mnNotdefWidth < 0 )
{
// get the width of the NotDef glyph
SIZE aExtent;
WCHAR cNotDef = rArgs.mpStr[ nCharPos ];
mnNotdefWidth = 0;
if( ::GetTextExtentPoint32W( mhDC, &cNotDef, 1, &aExtent) )
mnNotdefWidth = aExtent.cx;
}
// use a better NotDef glyph
if( !mbDisableGlyphs && !bSurrogate )
mpOutGlyphs[i] = 0;
}
if( bSurrogate && ((i+1) < mnGlyphCount) )
mpOutGlyphs[i+1] = DROPPED_OUTGLYPH;
// adjust the current glyph width to the NotDef glyph width
mnWidth += mnNotdefWidth - mpGlyphAdvances[i];
mpGlyphAdvances[i] = mnNotdefWidth;
if( mpGlyphOrigAdvs )
mpGlyphOrigAdvs[i] = mnNotdefWidth;
}
#ifdef GCP_KERN_HACK
// apply kerning if the layout engine has not yet done it
if( rArgs.mnFlags & (SAL_LAYOUT_KERNING_ASIAN|SAL_LAYOUT_KERNING_PAIRS) )
{
#else // GCP_KERN_HACK
// apply just asian kerning
if( rArgs.mnFlags & SAL_LAYOUT_KERNING_ASIAN )
{
if( !(rArgs.mnFlags & SAL_LAYOUT_KERNING_PAIRS) )
#endif // GCP_KERN_HACK
for( i = 0; i < mnGlyphCount; ++i )
mpGlyphOrigAdvs[i] = mpGlyphAdvances[i];
// #99658# also apply asian kerning on the substring border
int nLen = mnGlyphCount;
if( rArgs.mnMinCharPos + nLen < rArgs.mnLength )
++nLen;
for( i = 1; i < nLen; ++i )
{
#ifdef GCP_KERN_HACK
if( rArgs.mnFlags & SAL_LAYOUT_KERNING_PAIRS )
{
int nKernAmount = mrWinFontEntry.GetKerning( pBidiStr[i-1], pBidiStr[i] );
mpGlyphAdvances[ i-1 ] += nKernAmount;
mnWidth += nKernAmount;
}
else if( rArgs.mnFlags & SAL_LAYOUT_KERNING_ASIAN )
#endif // GCP_KERN_HACK
if( ( (0x3000 == (0xFF00 & pBidiStr[i-1])) || (0x2010 == (0xFFF0 & pBidiStr[i-1])) || (0xFF00 == (0xFF00 & pBidiStr[i-1])))
&& ( (0x3000 == (0xFF00 & pBidiStr[i])) || (0x2010 == (0xFFF0 & pBidiStr[i])) || (0xFF00 == (0xFF00 & pBidiStr[i])) ) )
{
long nKernFirst = +CalcAsianKerning( pBidiStr[i-1], true, bVertical );
long nKernNext = -CalcAsianKerning( pBidiStr[i], false, bVertical );
long nDelta = (nKernFirst < nKernNext) ? nKernFirst : nKernNext;
if( nDelta<0 && nKernFirst!=0 && nKernNext!=0 )
{
nDelta = (nDelta * mpGlyphAdvances[i-1] + 2) / 4;
mpGlyphAdvances[i-1] += nDelta;
mnWidth += nDelta;
}
}
}
}
// calculate virtual char widths
if( !mpGlyphs2Chars )
mpCharWidths = mpGlyphAdvances;
else
{
mpCharWidths = new int[ mnCharCount ];
for( i = 0; i < mnCharCount; ++i )
mpCharWidths[ i ] = 0;
for( i = 0; i < mnGlyphCount; ++i )
{
int j = mpGlyphs2Chars[ i ] - rArgs.mnMinCharPos;
if( j >= 0 )
mpCharWidths[ j ] += mpGlyphAdvances[ i ];
}
}
// scale layout metrics if needed
// TODO: does it make the code more simple if the metric scaling
// is moved to the methods that need metric scaling (e.g. FillDXArray())?
if( mfFontScale != 1.0 )
{
mnWidth = (long)(mnWidth * mfFontScale);
mnBaseAdv = (int)(mnBaseAdv * mfFontScale);
for( i = 0; i < mnCharCount; ++i )
mpCharWidths[i] = (int)(mpCharWidths[i] * mfFontScale);
if( mpGlyphAdvances != mpCharWidths )
for( i = 0; i < mnGlyphCount; ++i )
mpGlyphAdvances[i] = (int)(mpGlyphAdvances[i] * mfFontScale);
if( mpGlyphOrigAdvs && (mpGlyphOrigAdvs != mpGlyphAdvances) )
for( i = 0; i < mnGlyphCount; ++i )
mpGlyphOrigAdvs[i] = (int)(mpGlyphOrigAdvs[i] * mfFontScale);
}
return true;
}
// -----------------------------------------------------------------------
int SimpleWinLayout::GetNextGlyphs( int nLen, sal_GlyphId* pGlyphIds, Point& rPos, int& nStart,
long* pGlyphAdvances, int* pCharIndexes ) const
{
// return zero if no more glyph found
if( nStart >= mnGlyphCount )
return 0;
// calculate glyph position relative to layout base
// TODO: avoid for nStart!=0 case by reusing rPos
long nXOffset = mnBaseAdv;
for( int i = 0; i < nStart; ++i )
nXOffset += mpGlyphAdvances[ i ];
// calculate absolute position in pixel units
Point aRelativePos( nXOffset, 0 );
rPos = GetDrawPosition( aRelativePos );
int nCount = 0;
while( nCount < nLen )
{
// update return values {aGlyphId,nCharPos,nGlyphAdvance}
sal_GlyphId aGlyphId = mpOutGlyphs[ nStart ];
if( mbDisableGlyphs )
{
if( mnLayoutFlags & SAL_LAYOUT_VERTICAL )
{
const sal_UCS4 cChar = static_cast<sal_UCS4>(aGlyphId & GF_IDXMASK);
if( mrWinFontData.HasGSUBstitutions( mhDC )
&& mrWinFontData.IsGSUBstituted( cChar ) )
aGlyphId |= GF_GSUB | GF_ROTL;
else
{
aGlyphId |= GetVerticalFlags( cChar );
if( (aGlyphId & GF_ROTMASK) == 0 )
aGlyphId |= GF_VERT;
}
}
aGlyphId |= GF_ISCHAR;
}
++nCount;
*(pGlyphIds++) = aGlyphId;
if( pGlyphAdvances )
*(pGlyphAdvances++) = mpGlyphAdvances[ nStart ];
if( pCharIndexes )
{
int nCharPos;
if( !mpGlyphs2Chars )
nCharPos = nStart + mnMinCharPos;
else
nCharPos = mpGlyphs2Chars[nStart];
*(pCharIndexes++) = nCharPos;
}
// stop at last glyph
if( ++nStart >= mnGlyphCount )
break;
// stop when next x-position is unexpected
if( !pGlyphAdvances && mpGlyphOrigAdvs )
if( mpGlyphAdvances[nStart-1] != mpGlyphOrigAdvs[nStart-1] )
break;
}
return nCount;
}
// -----------------------------------------------------------------------
void SimpleWinLayout::DrawText( SalGraphics& rGraphics ) const
{
if( mnGlyphCount <= 0 )
return;
WinSalGraphics& rWinGraphics = static_cast<WinSalGraphics&>(rGraphics);
HDC aHDC = rWinGraphics.getHDC();
HFONT hOrigFont = DisableFontScaling();
UINT mnDrawOptions = ETO_GLYPH_INDEX;
if( mbDisableGlyphs )
mnDrawOptions = 0;
Point aPos = GetDrawPosition( Point( mnBaseAdv, 0 ) );
// #108267#, break up into glyph portions of a limited size required by Win32 API
const unsigned int maxGlyphCount = 8192;
UINT numGlyphPortions = mnGlyphCount / maxGlyphCount;
UINT remainingGlyphs = mnGlyphCount % maxGlyphCount;
if( numGlyphPortions )
{
// #108267#,#109387# break up string into smaller chunks
// the output positions will be updated by windows (SetTextAlign)
POINT oldPos;
UINT oldTa = ::GetTextAlign( aHDC );
::SetTextAlign( aHDC, (oldTa & ~TA_NOUPDATECP) | TA_UPDATECP );
::MoveToEx( aHDC, aPos.X(), aPos.Y(), &oldPos );
unsigned int i = 0;
for( unsigned int n = 0; n < numGlyphPortions; ++n, i+=maxGlyphCount )
::ExtTextOutW( aHDC, 0, 0, mnDrawOptions, NULL,
mpOutGlyphs+i, maxGlyphCount, mpGlyphAdvances+i );
::ExtTextOutW( aHDC, 0, 0, mnDrawOptions, NULL,
mpOutGlyphs+i, remainingGlyphs, mpGlyphAdvances+i );
::MoveToEx( aHDC, oldPos.x, oldPos.y, (LPPOINT) NULL);
::SetTextAlign( aHDC, oldTa );
}
else
::ExtTextOutW( aHDC, aPos.X(), aPos.Y(), mnDrawOptions, NULL,
mpOutGlyphs, mnGlyphCount, mpGlyphAdvances );
if( hOrigFont )
DeleteFont( SelectFont( aHDC, hOrigFont ) );
}
// -----------------------------------------------------------------------
long SimpleWinLayout::FillDXArray( long* pDXArray ) const
{
if( !mnWidth )
{
long mnWidth = mnBaseAdv;
for( int i = 0; i < mnGlyphCount; ++i )
mnWidth += mpGlyphAdvances[ i ];
}
if( pDXArray != NULL )
{
for( int i = 0; i < mnCharCount; ++i )
pDXArray[ i ] = mpCharWidths[ i ];
}
return mnWidth;
}
// -----------------------------------------------------------------------
int SimpleWinLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const
// NOTE: the nFactor is used to prevent rounding errors for small nCharExtra values
{
if( mnWidth )
if( (mnWidth * nFactor + mnCharCount * nCharExtra) <= nMaxWidth )
return STRING_LEN;
long nExtraWidth = mnBaseAdv * nFactor;
for( int n = 0; n < mnCharCount; ++n )
{
// skip unused characters
if( mpChars2Glyphs && (mpChars2Glyphs[n] < 0) )
continue;
// add char widths until max
nExtraWidth += mpCharWidths[ n ] * nFactor;
if( nExtraWidth >= nMaxWidth )
return (mnMinCharPos + n);
nExtraWidth += nCharExtra;
}
return STRING_LEN;
}
// -----------------------------------------------------------------------
void SimpleWinLayout::GetCaretPositions( int nMaxIdx, long* pCaretXArray ) const
{
long nXPos = mnBaseAdv;
if( !mpGlyphs2Chars )
{
for( int i = 0; i < nMaxIdx; i += 2 )
{
pCaretXArray[ i ] = nXPos;
nXPos += mpGlyphAdvances[ i>>1 ];
pCaretXArray[ i+1 ] = nXPos;
}
}
else
{
int i;
for( i = 0; i < nMaxIdx; ++i )
pCaretXArray[ i ] = -1;
// assign glyph positions to character positions
for( i = 0; i < mnGlyphCount; ++i )
{
int nCurrIdx = mpGlyphs2Chars[ i ] - mnMinCharPos;
long nXRight = nXPos + mpCharWidths[ nCurrIdx ];
nCurrIdx *= 2;
if( !(mpGlyphRTLFlags && mpGlyphRTLFlags[i]) )
{
// normal positions for LTR case
pCaretXArray[ nCurrIdx ] = nXPos;
pCaretXArray[ nCurrIdx+1 ] = nXRight;
}
else
{
// reverse positions for RTL case
pCaretXArray[ nCurrIdx ] = nXRight;
pCaretXArray[ nCurrIdx+1 ] = nXPos;
}
nXPos += mpGlyphAdvances[ i ];
}
}
}
// -----------------------------------------------------------------------
void SimpleWinLayout::Justify( long nNewWidth )
{
long nOldWidth = mnWidth;
mnWidth = nNewWidth;
if( mnGlyphCount <= 0 )
return;
if( nNewWidth == nOldWidth )
return;
// the rightmost glyph cannot be stretched
const int nRight = mnGlyphCount - 1;
nOldWidth -= mpGlyphAdvances[ nRight ];
nNewWidth -= mpGlyphAdvances[ nRight ];
// count stretchable glyphs
int nStretchable = 0, i;
for( i = 0; i < nRight; ++i )
if( mpGlyphAdvances[i] >= 0 )
++nStretchable;
// stretch these glyphs
int nDiffWidth = nNewWidth - nOldWidth;
for( i = 0; (i < nRight) && (nStretchable > 0); ++i )
{
if( mpGlyphAdvances[i] <= 0 )
continue;
int nDeltaWidth = nDiffWidth / nStretchable;
mpGlyphAdvances[i] += nDeltaWidth;
--nStretchable;
nDiffWidth -= nDeltaWidth;
}
}
// -----------------------------------------------------------------------
void SimpleWinLayout::AdjustLayout( ImplLayoutArgs& rArgs )
{
SalLayout::AdjustLayout( rArgs );
// adjust positions if requested
if( rArgs.mpDXArray )
ApplyDXArray( rArgs );
else if( rArgs.mnLayoutWidth )
Justify( rArgs.mnLayoutWidth );
else
return;
// recalculate virtual char widths if they were changed
if( mpCharWidths != mpGlyphAdvances )
{
int i;
if( !mpGlyphs2Chars )
{
// standard LTR case
for( i = 0; i < mnGlyphCount; ++i )
mpCharWidths[ i ] = mpGlyphAdvances[ i ];
}
else
{
// BiDi or complex case
for( i = 0; i < mnCharCount; ++i )
mpCharWidths[ i ] = 0;
for( i = 0; i < mnGlyphCount; ++i )
{
int j = mpGlyphs2Chars[ i ] - rArgs.mnMinCharPos;
if( j >= 0 )
mpCharWidths[ j ] += mpGlyphAdvances[ i ];
}
}
}
}
// -----------------------------------------------------------------------
void SimpleWinLayout::ApplyDXArray( const ImplLayoutArgs& rArgs )
{
// try to avoid disturbance of text flow for LSB rounding case;
const long* pDXArray = rArgs.mpDXArray;
int i = 0;
long nOldWidth = mnBaseAdv;
for(; i < mnCharCount; ++i )
{
int j = !mpChars2Glyphs ? i : mpChars2Glyphs[i];
if( j >= 0 )
{
nOldWidth += mpGlyphAdvances[ j ];
int nDiff = nOldWidth - pDXArray[ i ];
// disabled because of #104768#
// works great for static text, but problems when typing
// if( nDiff>+1 || nDiff<-1 )
// only bother with changing anything when something moved
if( nDiff != 0 )
break;
}
}
if( i >= mnCharCount )
return;
if( !mpGlyphOrigAdvs )
{
mpGlyphOrigAdvs = new int[ mnGlyphCount ];
for( i = 0; i < mnGlyphCount; ++i )
mpGlyphOrigAdvs[ i ] = mpGlyphAdvances[ i ];
}
mnWidth = mnBaseAdv;
for( i = 0; i < mnCharCount; ++i )
{
int j = !mpChars2Glyphs ? i : mpChars2Glyphs[i];
if( j >= 0 )
mpGlyphAdvances[j] = pDXArray[i] - mnWidth;
mnWidth = pDXArray[i];
}
}
// -----------------------------------------------------------------------
void SimpleWinLayout::MoveGlyph( int nStart, long nNewXPos )
{
if( nStart > mnGlyphCount )
return;
// calculate the current x-position of the requested glyph
// TODO: cache absolute positions
int nXPos = mnBaseAdv;
for( int i = 0; i < nStart; ++i )
nXPos += mpGlyphAdvances[i];
// calculate the difference to the current glyph position
int nDelta = nNewXPos - nXPos;
// adjust the width of the layout if it was already cached
if( mnWidth )
mnWidth += nDelta;
// depending on whether the requested glyph is leftmost in the layout
// adjust either the layout's or the requested glyph's relative position
if( nStart > 0 )
mpGlyphAdvances[ nStart-1 ] += nDelta;
else
mnBaseAdv += nDelta;
}
// -----------------------------------------------------------------------
void SimpleWinLayout::DropGlyph( int nStart )
{
mpOutGlyphs[ nStart ] = DROPPED_OUTGLYPH;
}
// -----------------------------------------------------------------------
void SimpleWinLayout::Simplify( bool /*bIsBase*/ )
{
// return early if no glyph has been dropped
int i = mnGlyphCount;
while( (--i >= 0) && (mpOutGlyphs[ i ] != DROPPED_OUTGLYPH) );
if( i < 0 )
return;
// convert the layout to a sparse layout if it is not already
if( !mpGlyphs2Chars )
{
mpGlyphs2Chars = new int[ mnGlyphCount ];
mpCharWidths = new int[ mnCharCount ];
// assertion: mnGlyphCount == mnCharCount
for( int k = 0; k < mnGlyphCount; ++k )
{
mpGlyphs2Chars[ k ] = mnMinCharPos + k;
mpCharWidths[ k ] = mpGlyphAdvances[ k ];
}
}
// remove dropped glyphs that are rightmost in the layout
for( i = mnGlyphCount; --i >= 0; )
{
if( mpOutGlyphs[ i ] != DROPPED_OUTGLYPH )
break;
if( mnWidth )
mnWidth -= mpGlyphAdvances[ i ];
int nRelCharPos = mpGlyphs2Chars[ i ] - mnMinCharPos;
if( nRelCharPos >= 0 )
mpCharWidths[ nRelCharPos ] = 0;
}
mnGlyphCount = i + 1;
// keep original glyph widths around
if( !mpGlyphOrigAdvs )
{
mpGlyphOrigAdvs = new int[ mnGlyphCount ];
for( int k = 0; k < mnGlyphCount; ++k )
mpGlyphOrigAdvs[ k ] = mpGlyphAdvances[ k ];
}
// remove dropped glyphs inside the layout
int nNewGC = 0;
for( i = 0; i < mnGlyphCount; ++i )
{
if( mpOutGlyphs[ i ] == DROPPED_OUTGLYPH )
{
// adjust relative position to last valid glyph
int nDroppedWidth = mpGlyphAdvances[ i ];
mpGlyphAdvances[ i ] = 0;
if( nNewGC > 0 )
mpGlyphAdvances[ nNewGC-1 ] += nDroppedWidth;
else
mnBaseAdv += nDroppedWidth;
// zero the virtual char width for the char that has a fallback
int nRelCharPos = mpGlyphs2Chars[ i ] - mnMinCharPos;
if( nRelCharPos >= 0 )
mpCharWidths[ nRelCharPos ] = 0;
}
else
{
if( nNewGC != i )
{
// rearrange the glyph array to get rid of the dropped glyph
mpOutGlyphs[ nNewGC ] = mpOutGlyphs[ i ];
mpGlyphAdvances[ nNewGC ] = mpGlyphAdvances[ i ];
mpGlyphOrigAdvs[ nNewGC ] = mpGlyphOrigAdvs[ i ];
mpGlyphs2Chars[ nNewGC ] = mpGlyphs2Chars[ i ];
}
++nNewGC;
}
}
mnGlyphCount = nNewGC;
if( mnGlyphCount <= 0 )
mnWidth = mnBaseAdv = 0;
}
// =======================================================================
#ifdef USE_UNISCRIBE
struct VisualItem
{
public:
SCRIPT_ITEM* mpScriptItem;
int mnMinGlyphPos;
int mnEndGlyphPos;
int mnMinCharPos;
int mnEndCharPos;
//long mnPixelWidth;
int mnXOffset;
ABC maABCWidths;
bool mbHasKashidas;
public:
bool IsEmpty() const { return (mnEndGlyphPos <= 0); }
bool IsRTL() const { return mpScriptItem->a.fRTL; }
bool HasKashidas() const { return mbHasKashidas; }
};
// -----------------------------------------------------------------------
class UniscribeLayout : public WinLayout
{
public:
UniscribeLayout( HDC, const ImplWinFontData&, ImplWinFontEntry& );
virtual bool LayoutText( ImplLayoutArgs& );
virtual void AdjustLayout( ImplLayoutArgs& );
virtual void DrawText( SalGraphics& ) const;
virtual int GetNextGlyphs( int nLen, sal_GlyphId* pGlyphs, Point& rPos, int&,
sal_Int32* pGlyphAdvances, int* pCharPosAry ) const;
virtual long FillDXArray( long* pDXArray ) const;
virtual int GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const;
virtual void GetCaretPositions( int nArraySize, long* pCaretXArray ) const;
virtual bool IsKashidaPosValid ( int nCharPos ) const;
// for glyph+font+script fallback
virtual void MoveGlyph( int nStart, long nNewXPos );
virtual void DropGlyph( int nStart );
virtual void Simplify( bool bIsBase );
virtual void DisableGlyphInjection( bool bDisable ) { mbDisableGlyphInjection = bDisable; }
protected:
virtual ~UniscribeLayout();
void Justify( long nNewWidth );
void ApplyDXArray( const ImplLayoutArgs& );
bool GetItemSubrange( const VisualItem&,
int& rMinIndex, int& rEndIndex ) const;
private:
// item specific info
SCRIPT_ITEM* mpScriptItems; // in logical order
VisualItem* mpVisualItems; // in visual order
int mnItemCount; // number of visual items
// string specific info
// everything is in logical order
int mnCharCapacity;
WORD* mpLogClusters; // map from absolute_char_pos to relative_glyph_pos
int* mpCharWidths; // map from absolute_char_pos to char_width
int mnSubStringMin; // char_pos of first char in context
// glyph specific info
// everything is in visual order
int mnGlyphCount;
int mnGlyphCapacity;
int* mpGlyphAdvances; // glyph advance width before justification
int* mpJustifications; // glyph advance width after justification
WORD* mpOutGlyphs; // glyphids in visual order
GOFFSET* mpGlyphOffsets; // glyph offsets to the "naive" layout
SCRIPT_VISATTR* mpVisualAttrs; // glyph visual attributes
mutable int* mpGlyphs2Chars; // map from absolute_glyph_pos to absolute_char_pos
// kashida stuff
void InitKashidaHandling();
void KashidaItemFix( int nMinGlyphPos, int nEndGlyphPos );
bool KashidaWordFix( int nMinGlyphPos, int nEndGlyphPos, int* pnCurrentPos );
int mnMinKashidaWidth;
int mnMinKashidaGlyph;
bool mbDisableGlyphInjection;
};
// -----------------------------------------------------------------------
// dynamic loading of usp library
static oslModule aUspModule = NULL;
static bool bUspEnabled = true;
static HRESULT ((WINAPI *pScriptIsComplex)( const WCHAR*, int, DWORD ));
static HRESULT ((WINAPI *pScriptItemize)( const WCHAR*, int, int,
const SCRIPT_CONTROL*, const SCRIPT_STATE*, SCRIPT_ITEM*, int* ));
static HRESULT ((WINAPI *pScriptShape)( HDC, SCRIPT_CACHE*, const WCHAR*,
int, int, SCRIPT_ANALYSIS*, WORD*, WORD*, SCRIPT_VISATTR*, int* ));
static HRESULT ((WINAPI *pScriptPlace)( HDC, SCRIPT_CACHE*, const WORD*, int,
const SCRIPT_VISATTR*, SCRIPT_ANALYSIS*, int*, GOFFSET*, ABC* ));
static HRESULT ((WINAPI *pScriptGetLogicalWidths)( const SCRIPT_ANALYSIS*,
int, int, const int*, const WORD*, const SCRIPT_VISATTR*, int* ));
static HRESULT ((WINAPI *pScriptApplyLogicalWidth)( const int*, int, int, const WORD*,
const SCRIPT_VISATTR*, const int*, const SCRIPT_ANALYSIS*, ABC*, int* ));
static HRESULT ((WINAPI *pScriptJustify)( const SCRIPT_VISATTR*,
const int*, int, int, int, int* ));
static HRESULT ((WINAPI *pScriptTextOut)( const HDC, SCRIPT_CACHE*,
int, int, UINT, const RECT*, const SCRIPT_ANALYSIS*, const WCHAR*,
int, const WORD*, int, const int*, const int*, const GOFFSET* ));
static HRESULT ((WINAPI *pScriptGetFontProperties)( HDC, SCRIPT_CACHE*, SCRIPT_FONTPROPERTIES* ));
static HRESULT ((WINAPI *pScriptFreeCache)( SCRIPT_CACHE* ));
static bool bManualCellAlign = true;
// -----------------------------------------------------------------------
static bool InitUSP()
{
aUspModule = osl_loadAsciiModule( "usp10", SAL_LOADMODULE_DEFAULT );
if( !aUspModule )
return (bUspEnabled = false);
pScriptIsComplex = (HRESULT (WINAPI*)(const WCHAR*,int,DWORD))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptIsComplex" );
bUspEnabled &= (NULL != pScriptIsComplex);
pScriptItemize = (HRESULT (WINAPI*)(const WCHAR*,int,int,
const SCRIPT_CONTROL*,const SCRIPT_STATE*,SCRIPT_ITEM*,int*))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptItemize" );
bUspEnabled &= (NULL != pScriptItemize);
pScriptShape = (HRESULT (WINAPI*)(HDC,SCRIPT_CACHE*,const WCHAR*,
int,int,SCRIPT_ANALYSIS*,WORD*,WORD*,SCRIPT_VISATTR*,int*))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptShape" );
bUspEnabled &= (NULL != pScriptShape);
pScriptPlace = (HRESULT (WINAPI*)(HDC, SCRIPT_CACHE*, const WORD*, int,
const SCRIPT_VISATTR*,SCRIPT_ANALYSIS*,int*,GOFFSET*,ABC*))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptPlace" );
bUspEnabled &= (NULL != pScriptPlace);
pScriptGetLogicalWidths = (HRESULT (WINAPI*)(const SCRIPT_ANALYSIS*,
int,int,const int*,const WORD*,const SCRIPT_VISATTR*,int*))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptGetLogicalWidths" );
bUspEnabled &= (NULL != pScriptGetLogicalWidths);
pScriptApplyLogicalWidth = (HRESULT (WINAPI*)(const int*,int,int,const WORD*,
const SCRIPT_VISATTR*,const int*,const SCRIPT_ANALYSIS*,ABC*,int*))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptApplyLogicalWidth" );
bUspEnabled &= (NULL != pScriptApplyLogicalWidth);
pScriptJustify = (HRESULT (WINAPI*)(const SCRIPT_VISATTR*,const int*,
int,int,int,int*))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptJustify" );
bUspEnabled &= (NULL != pScriptJustify);
pScriptGetFontProperties = (HRESULT (WINAPI*)( HDC,SCRIPT_CACHE*,SCRIPT_FONTPROPERTIES*))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptGetFontProperties" );
bUspEnabled &= (NULL != pScriptGetFontProperties);
pScriptTextOut = (HRESULT (WINAPI*)(const HDC,SCRIPT_CACHE*,
int,int,UINT,const RECT*,const SCRIPT_ANALYSIS*,const WCHAR*,
int,const WORD*,int,const int*,const int*,const GOFFSET*))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptTextOut" );
bUspEnabled &= (NULL != pScriptTextOut);
pScriptFreeCache = (HRESULT (WINAPI*)(SCRIPT_CACHE*))
osl_getAsciiFunctionSymbol( aUspModule, "ScriptFreeCache" );
bUspEnabled &= (NULL != pScriptFreeCache);
if( !bUspEnabled )
{
osl_unloadModule( aUspModule );
aUspModule = NULL;
}
// get the DLL version info
int nUspVersion = 0;
// TODO: there must be a simpler way to get the friggin version info from OSL?
rtl_uString* pModuleURL = NULL;
osl_getModuleURLFromAddress( (void*)pScriptIsComplex, &pModuleURL );
rtl_uString* pModuleFileName = NULL;
if( pModuleURL )
osl_getSystemPathFromFileURL( pModuleURL, &pModuleFileName );
const sal_Unicode* pModuleFileCStr = NULL;
if( pModuleFileName )
pModuleFileCStr = rtl_uString_getStr( pModuleFileName );
if( pModuleFileCStr )
{
DWORD nHandle;
DWORD nBufSize = ::GetFileVersionInfoSizeW( const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(pModuleFileCStr)), &nHandle );
char* pBuffer = (char*)alloca( nBufSize );
BOOL bRC = ::GetFileVersionInfoW( const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(pModuleFileCStr)), nHandle, nBufSize, pBuffer );
VS_FIXEDFILEINFO* pFixedFileInfo = NULL;
UINT nFixedFileSize = 0;
if( bRC )
::VerQueryValueW( pBuffer, const_cast<LPWSTR>(L"\\"), (void**)&pFixedFileInfo, &nFixedFileSize );
if( pFixedFileInfo && pFixedFileInfo->dwSignature == 0xFEEF04BD )
nUspVersion = HIWORD(pFixedFileInfo->dwProductVersionMS) * 10000
+ LOWORD(pFixedFileInfo->dwProductVersionMS);
}
// #i77976# USP>=1.0600 changed the need to manually align glyphs in their cells
if( nUspVersion >= 10600 )
bManualCellAlign = false;
return bUspEnabled;
}
// -----------------------------------------------------------------------
UniscribeLayout::UniscribeLayout( HDC hDC,
const ImplWinFontData& rWinFontData, ImplWinFontEntry& rWinFontEntry )
: WinLayout( hDC, rWinFontData, rWinFontEntry ),
mnItemCount( 0 ),
mpScriptItems( NULL ),
mpVisualItems( NULL ),
mpLogClusters( NULL ),
mpCharWidths( NULL ),
mnCharCapacity( 0 ),
mnSubStringMin( 0 ),
mnGlyphCapacity( 0 ),
mnGlyphCount( 0 ),
mpOutGlyphs( NULL ),
mpGlyphAdvances( NULL ),
mpJustifications( NULL ),
mpGlyphOffsets( NULL ),
mpVisualAttrs( NULL ),
mpGlyphs2Chars( NULL ),
mnMinKashidaGlyph( 0 ),
mbDisableGlyphInjection( false )
{}
// -----------------------------------------------------------------------
UniscribeLayout::~UniscribeLayout()
{
delete[] mpScriptItems;
delete[] mpVisualItems;
delete[] mpLogClusters;
delete[] mpCharWidths;
delete[] mpOutGlyphs;
delete[] mpGlyphAdvances;
delete[] mpJustifications;
delete[] mpGlyphOffsets;
delete[] mpVisualAttrs;
delete[] mpGlyphs2Chars;
}
// -----------------------------------------------------------------------
bool UniscribeLayout::LayoutText( ImplLayoutArgs& rArgs )
{
// for a base layout only the context glyphs have to be dropped
// => when the whole string is involved there is no extra context
typedef std::vector<int> TIntVector;
TIntVector aDropChars;
if( rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK )
{
// calculate superfluous context char positions
aDropChars.push_back( 0 );
aDropChars.push_back( rArgs.mnLength );
int nMin, nEnd;
bool bRTL;
for( rArgs.ResetPos(); rArgs.GetNextRun( &nMin, &nEnd, &bRTL ); )
{
aDropChars.push_back( nMin );
aDropChars.push_back( nEnd );
}
// prepare aDropChars for binary search which will allow to
// not bother with visual items that will be dropped anyway
std::sort( aDropChars.begin(), aDropChars.end() );
}
// prepare layout
// TODO: fix case when recyclying old UniscribeLayout object
mnMinCharPos = rArgs.mnMinCharPos;
mnEndCharPos = rArgs.mnEndCharPos;
// determine script items from string
// prepare itemization
// TODO: try to avoid itemization since it costs a lot of performance
SCRIPT_STATE aScriptState = {0,false,false,false,false,false,false,false,false,0,0};
aScriptState.uBidiLevel = (0 != (rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL));
aScriptState.fOverrideDirection = (0 != (rArgs.mnFlags & SAL_LAYOUT_BIDI_STRONG));
aScriptState.fDigitSubstitute = (0 != (rArgs.mnFlags & SAL_LAYOUT_SUBSTITUTE_DIGITS));
aScriptState.fArabicNumContext = aScriptState.fDigitSubstitute & aScriptState.uBidiLevel;
DWORD nLangId = 0; // TODO: get language from font
SCRIPT_CONTROL aScriptControl = {nLangId,false,false,false,false,false,false,false,false,0};
aScriptControl.fNeutralOverride = aScriptState.fOverrideDirection;
aScriptControl.fContextDigits = (0 != (rArgs.mnFlags & SAL_LAYOUT_SUBSTITUTE_DIGITS));
aScriptControl.fMergeNeutralItems = true;
// determine relevant substring and work only on it
// when Bidi status is unknown we need to look at the whole string though
mnSubStringMin = 0;
int nSubStringEnd = rArgs.mnLength;
if( aScriptState.fOverrideDirection )
{
// TODO: limit substring to portion limits
mnSubStringMin = rArgs.mnMinCharPos - 8;
if( mnSubStringMin < 0 )
mnSubStringMin = 0;
nSubStringEnd = rArgs.mnEndCharPos + 8;
if( nSubStringEnd > rArgs.mnLength )
nSubStringEnd = rArgs.mnLength;
}
// now itemize the substring with its context
for( int nItemCapacity = 16;; nItemCapacity *= 8 )
{
mpScriptItems = new SCRIPT_ITEM[ nItemCapacity ];
HRESULT nRC = (*pScriptItemize)(
reinterpret_cast<LPCWSTR>(rArgs.mpStr + mnSubStringMin), nSubStringEnd - mnSubStringMin,
nItemCapacity - 1, &aScriptControl, &aScriptState,
mpScriptItems, &mnItemCount );
if( !nRC ) // break loop when everything is correctly itemized
break;
// prepare bigger buffers for another itemization round
delete[] mpScriptItems;
mpScriptItems = NULL;
if( nRC != E_OUTOFMEMORY )
return false;
if( nItemCapacity > (nSubStringEnd - mnSubStringMin) + 16 )
return false;
}
// calculate the order of visual items
int nItem, i;
// adjust char positions by substring offset
for( nItem = 0; nItem <= mnItemCount; ++nItem )
mpScriptItems[ nItem ].iCharPos += mnSubStringMin;
// default visual item ordering
mpVisualItems = new VisualItem[ mnItemCount ];
for( nItem = 0; nItem < mnItemCount; ++nItem )
{
// initialize char specific item info
VisualItem& rVisualItem = mpVisualItems[ nItem ];
SCRIPT_ITEM* pScriptItem = &mpScriptItems[ nItem ];
rVisualItem.mpScriptItem = pScriptItem;
rVisualItem.mnMinCharPos = pScriptItem[0].iCharPos;
rVisualItem.mnEndCharPos = pScriptItem[1].iCharPos;
}
// reorder visual item order if needed
if( rArgs.mnFlags & SAL_LAYOUT_BIDI_STRONG )
{
// force RTL item ordering if requested
if( rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL )
{
VisualItem* pVI0 = &mpVisualItems[ 0 ];
VisualItem* pVI1 = &mpVisualItems[ mnItemCount ];
while( pVI0 < --pVI1 )
{
VisualItem aVtmp = *pVI0;
*(pVI0++) = *pVI1;
*pVI1 = aVtmp;
}
}
}
else if( mnItemCount > 1 )
{
// apply bidi algorithm's rule L2 on item level
// TODO: use faster L2 algorithm
int nMaxBidiLevel = 0;
VisualItem* pVI = &mpVisualItems[0];
VisualItem* const pVIend = pVI + mnItemCount;
for(; pVI < pVIend; ++pVI )
if( nMaxBidiLevel < pVI->mpScriptItem->a.s.uBidiLevel )
nMaxBidiLevel = pVI->mpScriptItem->a.s.uBidiLevel;
while( --nMaxBidiLevel >= 0 )
{
for( pVI = &mpVisualItems[0]; pVI < pVIend; )
{
// find item range that needs reordering
for(; pVI < pVIend; ++pVI )
if( nMaxBidiLevel < pVI->mpScriptItem->a.s.uBidiLevel )
break;
VisualItem* pVImin = pVI++;
for(; pVI < pVIend; ++pVI )
if( nMaxBidiLevel >= pVI->mpScriptItem->a.s.uBidiLevel )
break;
VisualItem* pVImax = pVI++;
// reverse order of items in this range
while( pVImin < --pVImax )
{
VisualItem aVtmp = *pVImin;
*(pVImin++) = *pVImax;
*pVImax = aVtmp;
}
}
}
}
// allocate arrays
// TODO: when reusing object reuse old allocations or delete them
// TODO: use only [nSubStringMin..nSubStringEnd) instead of [0..nSubStringEnd)
mnCharCapacity = nSubStringEnd;
mpLogClusters = new WORD[ mnCharCapacity ];
mpCharWidths = new int[ mnCharCapacity ];
mnGlyphCount = 0;
mnGlyphCapacity = 16 + 4 * (nSubStringEnd - mnSubStringMin); // worst case assumption
mpGlyphAdvances = new int[ mnGlyphCapacity ];
mpOutGlyphs = new WORD[ mnGlyphCapacity ];
mpGlyphOffsets = new GOFFSET[ mnGlyphCapacity ];
mpVisualAttrs = new SCRIPT_VISATTR[ mnGlyphCapacity ];
long nXOffset = 0;
for( int j = mnSubStringMin; j < nSubStringEnd; ++j )
mpCharWidths[j] = 0;
// layout script items
SCRIPT_CACHE& rScriptCache = GetScriptCache();
for( nItem = 0; nItem < mnItemCount; ++nItem )
{
VisualItem& rVisualItem = mpVisualItems[ nItem ];
// initialize glyph specific item info
rVisualItem.mnMinGlyphPos = mnGlyphCount;
rVisualItem.mnEndGlyphPos = 0;
rVisualItem.mnXOffset = nXOffset;
//rVisualItem.mnPixelWidth = 0;
// shortcut ignorable items
if( (rArgs.mnEndCharPos <= rVisualItem.mnMinCharPos)
|| (rArgs.mnMinCharPos >= rVisualItem.mnEndCharPos) )
{
for( int i = rVisualItem.mnMinCharPos; i < rVisualItem.mnEndCharPos; ++i )
mpLogClusters[i] = sal::static_int_cast<WORD>(~0U);
continue;
}
// override bidi analysis if requested
if( rArgs.mnFlags & SAL_LAYOUT_BIDI_STRONG )
{
// FIXME: is this intended ?
rVisualItem.mpScriptItem->a.fRTL = (aScriptState.uBidiLevel & 1);
rVisualItem.mpScriptItem->a.s.uBidiLevel = aScriptState.uBidiLevel;
rVisualItem.mpScriptItem->a.s.fOverrideDirection = aScriptState.fOverrideDirection;
}
// convert the unicodes to glyphs
int nGlyphCount = 0;
int nCharCount = rVisualItem.mnEndCharPos - rVisualItem.mnMinCharPos;
HRESULT nRC = (*pScriptShape)( mhDC, &rScriptCache,
reinterpret_cast<LPCWSTR>(rArgs.mpStr + rVisualItem.mnMinCharPos),
nCharCount,
mnGlyphCapacity - rVisualItem.mnMinGlyphPos, // problem when >0xFFFF
&rVisualItem.mpScriptItem->a,
mpOutGlyphs + rVisualItem.mnMinGlyphPos,
mpLogClusters + rVisualItem.mnMinCharPos,
mpVisualAttrs + rVisualItem.mnMinGlyphPos,
&nGlyphCount );
// find and handle problems in the unicode to glyph conversion
if( nRC == USP_E_SCRIPT_NOT_IN_FONT )
{
// the whole visual item needs a fallback, but make sure that the next
// fallback request is limited to the characters in the original request
// => this is handled in ImplLayoutArgs::PrepareFallback()
rArgs.NeedFallback( rVisualItem.mnMinCharPos, rVisualItem.mnEndCharPos,
rVisualItem.IsRTL() );
// don't bother to do a default layout in a fallback level
if( 0 != (rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK) )
continue;
// the primitive layout engine is good enough for the default layout
rVisualItem.mpScriptItem->a.eScript = SCRIPT_UNDEFINED;
nRC = (*pScriptShape)( mhDC, &rScriptCache,
reinterpret_cast<LPCWSTR>(rArgs.mpStr + rVisualItem.mnMinCharPos),
nCharCount,
mnGlyphCapacity - rVisualItem.mnMinGlyphPos,
&rVisualItem.mpScriptItem->a,
mpOutGlyphs + rVisualItem.mnMinGlyphPos,
mpLogClusters + rVisualItem.mnMinCharPos,
mpVisualAttrs + rVisualItem.mnMinGlyphPos,
&nGlyphCount );
if( nRC != 0 )
continue;
#if 0 // keep the glyphs for now because they are better than nothing
// mark as NotDef glyphs
for( i = 0; i < nGlyphCount; ++i )
mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] = 0;
#endif
}
else if( nRC != 0 )
// something undefined happened => give up for this visual item
continue;
else // if( nRC == 0 )
{
// check if there are any NotDef glyphs
for( i = 0; i < nGlyphCount; ++i )
if( 0 == mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] )
break;
if( i < nGlyphCount )
{
// clip charpos limits to the layout string without context
int nMinCharPos = rVisualItem.mnMinCharPos;
if( nMinCharPos < rArgs.mnMinCharPos )
nMinCharPos = rArgs.mnMinCharPos;
int nEndCharPos = rVisualItem.mnEndCharPos;
if( nEndCharPos > rArgs.mnEndCharPos )
nEndCharPos = rArgs.mnEndCharPos;
// request fallback for individual NotDef glyphs
do
{
// ignore non-NotDef glyphs
if( 0 != mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] )
continue;
mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] = DROPPED_OUTGLYPH;
// request fallback for the whole cell that resulted in a NotDef glyph
// TODO: optimize algorithm
const bool bRTL = rVisualItem.IsRTL();
if( !bRTL )
{
// request fallback for the left-to-right cell
for( int c = nMinCharPos; c < nEndCharPos; ++c )
{
if( mpLogClusters[ c ] == i )
{
// #i55716# skip WORDJOINER
if( rArgs.mpStr[ c ] == 0x2060 )
mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] = 1;
else
rArgs.NeedFallback( c, false );
}
}
}
else
{
// request fallback for the right to left cell
for( int c = nEndCharPos; --c >= nMinCharPos; )
{
if( mpLogClusters[ c ] == i )
{
// #i55716# skip WORDJOINER
if( rArgs.mpStr[ c ] == 0x2060 )
mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] = 1;
else
rArgs.NeedFallback( c, true );
}
}
}
} while( ++i < nGlyphCount );
}
}
// now place the glyphs
nRC = (*pScriptPlace)( mhDC, &rScriptCache,
mpOutGlyphs + rVisualItem.mnMinGlyphPos,
nGlyphCount,
mpVisualAttrs + rVisualItem.mnMinGlyphPos,
&rVisualItem.mpScriptItem->a,
mpGlyphAdvances + rVisualItem.mnMinGlyphPos,
mpGlyphOffsets + rVisualItem.mnMinGlyphPos,
&rVisualItem.maABCWidths );
if( nRC != 0 )
continue;
// calculate the logical char widths from the glyph layout
nRC = (*pScriptGetLogicalWidths)(
&rVisualItem.mpScriptItem->a,
nCharCount, nGlyphCount,
mpGlyphAdvances + rVisualItem.mnMinGlyphPos,
mpLogClusters + rVisualItem.mnMinCharPos,
mpVisualAttrs + rVisualItem.mnMinGlyphPos,
mpCharWidths + rVisualItem.mnMinCharPos );
// update the glyph counters
mnGlyphCount += nGlyphCount;
rVisualItem.mnEndGlyphPos = mnGlyphCount;
// update nXOffset
int nEndGlyphPos;
if( GetItemSubrange( rVisualItem, i, nEndGlyphPos ) )
for(; i < nEndGlyphPos; ++i )
nXOffset += mpGlyphAdvances[ i ];
// TODO: shrink glyphpos limits to match charpos/fallback limits
//pVI->mnMinGlyphPos = nMinGlyphPos;
//pVI->mnEndGlyphPos = nEndGlyphPos;
// drop the superfluous context glyphs
TIntVector::const_iterator it = aDropChars.begin();
while( it != aDropChars.end() )
{
// find matching "drop range"
int nMinDropPos = *(it++); // begin of drop range
if( nMinDropPos >= rVisualItem.mnEndCharPos )
break;
int nEndDropPos = *(it++); // end of drop range
if( nEndDropPos <= rVisualItem.mnMinCharPos )
continue;
// clip "drop range" to visual item's char range
if( nMinDropPos <= rVisualItem.mnMinCharPos )
{
nMinDropPos = rVisualItem.mnMinCharPos;
// drop the whole visual item if possible
if( nEndDropPos >= rVisualItem.mnEndCharPos )
{
rVisualItem.mnEndGlyphPos = 0;
break;
}
}
if( nEndDropPos > rVisualItem.mnEndCharPos )
nEndDropPos = rVisualItem.mnEndCharPos;
// drop the glyphs which correspond to the charpos range
// drop the corresponding glyphs in the cluster
for( int c = nMinDropPos; c < nEndDropPos; ++c )
{
int nGlyphPos = mpLogClusters[c] + rVisualItem.mnMinGlyphPos;
// no need to bother when the cluster was already dropped
if( mpOutGlyphs[ nGlyphPos ] != DROPPED_OUTGLYPH )
{
for(;;)
{
mpOutGlyphs[ nGlyphPos ] = DROPPED_OUTGLYPH;
// until the end of visual item
if( ++nGlyphPos >= rVisualItem.mnEndGlyphPos )
break;
// until the next cluster start
if( mpVisualAttrs[ nGlyphPos ].fClusterStart )
break;
}
}
}
}
}
// scale layout metrics if needed
// TODO: does it make the code more simple if the metric scaling
// is moved to the methods that need metric scaling (e.g. FillDXArray())?
if( mfFontScale != 1.0 )
{
mnBaseAdv = (int)((double)mnBaseAdv*mfFontScale);
for( i = 0; i < mnItemCount; ++i )
mpVisualItems[i].mnXOffset = (int)((double)mpVisualItems[i].mnXOffset*mfFontScale);
mnBaseAdv = (int)((double)mnBaseAdv*mfFontScale);
for( i = 0; i < mnGlyphCount; ++i )
{
mpGlyphAdvances[i] = (int)(mpGlyphAdvances[i] * mfFontScale);
mpGlyphOffsets[i].du = (LONG)(mpGlyphOffsets[i].du * mfFontScale);
mpGlyphOffsets[i].dv = (LONG)(mpGlyphOffsets[i].dv * mfFontScale);
// mpJustifications are still NULL
}
for( i = mnSubStringMin; i < nSubStringEnd; ++i )
mpCharWidths[i] = (int)(mpCharWidths[i] * mfFontScale);
}
return true;
}
// -----------------------------------------------------------------------
// calculate the range of relevant glyphs for this visual item
bool UniscribeLayout::GetItemSubrange( const VisualItem& rVisualItem,
int& rMinGlyphPos, int& rEndGlyphPos ) const
{
// return early when nothing of interest in this item
if( rVisualItem.IsEmpty()
|| (rVisualItem.mnEndCharPos <= mnMinCharPos)
|| (mnEndCharPos <= rVisualItem.mnMinCharPos) )
return false;
// default: subrange is complete range
rMinGlyphPos = rVisualItem.mnMinGlyphPos;
rEndGlyphPos = rVisualItem.mnEndGlyphPos;
// return early when the whole item is of interest
if( (mnMinCharPos <= rVisualItem.mnMinCharPos)
&& (rVisualItem.mnEndCharPos <= mnEndCharPos ) )
return true;
// get glyph range from char range by looking at cluster boundries
// TODO: optimize for case that LTR/RTL correspond to monotonous glyph indexes
rMinGlyphPos = rVisualItem.mnEndGlyphPos;
int nMaxGlyphPos = 0;
int i = mnMinCharPos;
if( i < rVisualItem.mnMinCharPos )
i = rVisualItem.mnMinCharPos;
int nCharPosLimit = rVisualItem.mnEndCharPos;
if( nCharPosLimit > mnEndCharPos )
nCharPosLimit = mnEndCharPos;
for(; i < nCharPosLimit; ++i )
{
int n = mpLogClusters[ i ] + rVisualItem.mnMinGlyphPos;
if( rMinGlyphPos > n )
rMinGlyphPos = n;
if( nMaxGlyphPos < n )
nMaxGlyphPos = n;
}
if (nMaxGlyphPos > rVisualItem.mnEndGlyphPos)
nMaxGlyphPos = rVisualItem.mnEndGlyphPos - 1;
// extend the glyph range to account for all glyphs in referenced clusters
if( !rVisualItem.IsRTL() ) // LTR-item
{
// extend to rightmost glyph of rightmost referenced cluster
for( i = nMaxGlyphPos; ++i < rVisualItem.mnEndGlyphPos; nMaxGlyphPos = i )
if( mpVisualAttrs[i].fClusterStart )
break;
}
else // RTL-item
{
// extend to leftmost glyph of leftmost referenced cluster
for( i = rMinGlyphPos; --i >= rVisualItem.mnMinGlyphPos; rMinGlyphPos = i )
if( mpVisualAttrs[i].fClusterStart )
break;
}
rEndGlyphPos = nMaxGlyphPos + 1;
return true;
}
// -----------------------------------------------------------------------
int UniscribeLayout::GetNextGlyphs( int nLen, sal_GlyphId* pGlyphs, Point& rPos,
int& nStartx8, sal_Int32* pGlyphAdvances, int* pCharPosAry ) const
{
// HACK to allow fake-glyph insertion (e.g. for kashidas)
// TODO: use iterator idiom instead of GetNextGlyphs(...)
// TODO: else make sure that the limit for glyph injection is sufficient (currently 256)
int nSubIter = nStartx8 & 0xff;
int nStart = nStartx8 >> 8;
// check the glyph iterator
if( nStart > mnGlyphCount ) // nStart>MAX means no more glyphs
return 0;
// find the visual item for the nStart glyph position
int nItem = 0;
const VisualItem* pVI = mpVisualItems;
if( nStart <= 0 ) // nStart<=0 requests the first visible glyph
{
// find first visible item
for(; nItem < mnItemCount; ++nItem, ++pVI )
if( !pVI->IsEmpty() )
break;
// it is possible that there are glyphs but no valid visual item
// TODO: get rid of these visual items more early
if( nItem < mnItemCount )
nStart = pVI->mnMinGlyphPos;
}
else //if( nStart > 0 ) // nStart>0 means absolute glyph pos +1
{
--nStart;
// find matching item
for(; nItem < mnItemCount; ++nItem, ++pVI )
if( (nStart >= pVI->mnMinGlyphPos)
&& (nStart < pVI->mnEndGlyphPos) )
break;
}
// after the last visual item there are no more glyphs
if( (nItem >= mnItemCount) || (nStart < 0) )
{
nStartx8 = (mnGlyphCount + 1) << 8;
return 0;
}
// calculate the first glyph in the next visual item
int nNextItemStart = mnGlyphCount;
while( ++nItem < mnItemCount )
{
if( mpVisualItems[nItem].IsEmpty() )
continue;
nNextItemStart = mpVisualItems[nItem].mnMinGlyphPos;
break;
}
// get the range of relevant glyphs in this visual item
int nMinGlyphPos, nEndGlyphPos;
bool bRC = GetItemSubrange( *pVI, nMinGlyphPos, nEndGlyphPos );
DBG_ASSERT( bRC, "USPLayout::GNG GISR() returned false" );
if( !bRC )
{
nStartx8 = (mnGlyphCount + 1) << 8;
return 0;
}
// make sure nStart is inside the range of relevant glyphs
if( nStart < nMinGlyphPos )
nStart = nMinGlyphPos;
// calculate the start glyph xoffset relative to layout's base position,
// advance to next visual glyph position by using adjusted glyph widths
// TODO: speed up the calculation for nStart!=0 case by using rPos as a cache
long nXOffset = pVI->mnXOffset;
const int* pGlyphWidths = mpJustifications ? mpJustifications : mpGlyphAdvances;
for( int i = nMinGlyphPos; i < nStart; ++i )
nXOffset += pGlyphWidths[ i ];
// adjust the nXOffset relative to glyph cluster start
int c = mnMinCharPos;
if( !pVI->IsRTL() ) // LTR-case
{
// LTR case: subtract the remainder of the cell from xoffset
int nTmpIndex = mpLogClusters[c];
while( (--c >= pVI->mnMinCharPos)
&& (nTmpIndex == mpLogClusters[c]) )
nXOffset -= mpCharWidths[c];
}
else // RTL-case
{
// RTL case: add the remainder of the cell from xoffset
int nTmpIndex = mpLogClusters[ pVI->mnEndCharPos - 1 ];
while( (--c >= pVI->mnMinCharPos)
&& (nTmpIndex == mpLogClusters[c]) )
nXOffset += mpCharWidths[c];
// adjust the xoffset if justified glyphs are not positioned at their justified positions yet
if( mpJustifications && !bManualCellAlign )
nXOffset += mpJustifications[ nStart ] - mpGlyphAdvances[ nStart ];
}
// create mpGlyphs2Chars[] if it is needed later
if( pCharPosAry && !mpGlyphs2Chars )
{
// create and reset the new array
mpGlyphs2Chars = new int[ mnGlyphCapacity ];
static const int CHARPOS_NONE = -1;
for( int i = 0; i < mnGlyphCount; ++i )
mpGlyphs2Chars[i] = CHARPOS_NONE;
// calculate the char->glyph mapping
for( nItem = 0; nItem < mnItemCount; ++nItem )
{
// ignore invisible visual items
const VisualItem& rVI = mpVisualItems[ nItem ];
if( rVI.IsEmpty() )
continue;
// calculate the mapping by using mpLogClusters[]
// mpGlyphs2Chars[] should obey the logical order
// => reversing the loop does this by overwriting higher logicals
for( c = rVI.mnEndCharPos; --c >= rVI.mnMinCharPos; )
{
int i = mpLogClusters[c] + rVI.mnMinGlyphPos;
mpGlyphs2Chars[i] = c;
}
// use a heuristic to fill the gaps in the glyphs2chars array
c = !rVI.IsRTL() ? rVI.mnMinCharPos : rVI.mnEndCharPos - 1;
for( int i = rVI.mnMinGlyphPos; i < rVI.mnEndGlyphPos; ++i ) {
if( mpGlyphs2Chars[i] == CHARPOS_NONE )
mpGlyphs2Chars[i] = c;
else
c = mpGlyphs2Chars[i];
}
}
}
// calculate the absolute position of the first result glyph in pixel units
const GOFFSET aGOffset = mpGlyphOffsets[ nStart ];
Point aRelativePos( nXOffset + aGOffset.du, -aGOffset.dv );
rPos = GetDrawPosition( aRelativePos );
// fill the result arrays
int nCount = 0;
while( nCount < nLen )
{
// prepare return values
sal_GlyphId aGlyphId = mpOutGlyphs[ nStart ];
int nGlyphWidth = pGlyphWidths[ nStart ];
int nCharPos = -1; // no need to determine charpos
if( mpGlyphs2Chars ) // unless explicitly requested+provided
nCharPos = mpGlyphs2Chars[ nStart ];
// inject kashida glyphs if needed
if( !mbDisableGlyphInjection
&& mpJustifications
&& mnMinKashidaWidth
&& mpVisualAttrs[nStart].uJustification >= SCRIPT_JUSTIFY_ARABIC_NORMAL )
{
// prepare draw position adjustment
int nExtraOfs = (nSubIter++) * mnMinKashidaWidth;
// calculate space available for the injected glyphs
nGlyphWidth = mpGlyphAdvances[ nStart ];
const int nExtraWidth = mpJustifications[ nStart ] - nGlyphWidth;
const int nToFillWidth = nExtraWidth - nExtraOfs;
if( (4*nToFillWidth >= mnMinKashidaWidth) // prevent glyph-injection if there is no room
|| ((nSubIter > 1) && (nToFillWidth > 0)) ) // unless they can overlap with others
{
// handle if there is not sufficient room for a full glyph
if( nToFillWidth < mnMinKashidaWidth )
{
// overlap it with the previously injected glyph if possible
int nOverlap = mnMinKashidaWidth - nToFillWidth;
// else overlap it with both neighboring glyphs
if( nSubIter <= 1 )
nOverlap /= 2;
nExtraOfs -= nOverlap;
}
nGlyphWidth = mnMinKashidaWidth;
aGlyphId = mnMinKashidaGlyph;
nCharPos = -1;
}
else
{
nExtraOfs += nToFillWidth; // at right of cell
nSubIter = 0; // done with glyph injection
}
if( !bManualCellAlign )
nExtraOfs -= nExtraWidth; // adjust for right-aligned cells
// adjust the draw position for the injected-glyphs case
if( nExtraOfs )
{
aRelativePos.X() += nExtraOfs;
rPos = GetDrawPosition( aRelativePos );
}
}
// update return values
*(pGlyphs++) = aGlyphId;
if( pGlyphAdvances )
*(pGlyphAdvances++) = nGlyphWidth;
if( pCharPosAry )
*(pCharPosAry++) = nCharPos;
// increment counter of returned glyphs
++nCount;
// reduce code complexity by returning early in glyph-injection case
if( nSubIter != 0 )
break;
// stop after the last visible glyph in this visual item
if( ++nStart >= nEndGlyphPos )
{
nStart = nNextItemStart;
break;
}
// RTL-justified glyph positioning is not easy
// simplify the code by just returning only one glyph at a time
if( mpJustifications && pVI->IsRTL() )
break;
// stop when the x-position of the next glyph is unexpected
if( !pGlyphAdvances )
if( (mpGlyphOffsets && (mpGlyphOffsets[nStart].du != aGOffset.du) )
|| (mpJustifications && (mpJustifications[nStart] != mpGlyphAdvances[nStart]) ) )
break;
// stop when the y-position of the next glyph is unexpected
if( mpGlyphOffsets && (mpGlyphOffsets[nStart].dv != aGOffset.dv) )
break;
}
++nStart;
nStartx8 = (nStart << 8) + nSubIter;
return nCount;
}
// -----------------------------------------------------------------------
void UniscribeLayout::MoveGlyph( int nStartx8, long nNewXPos )
{
DBG_ASSERT( !(nStartx8 & 0xff), "USP::MoveGlyph(): glyph injection not disabled!" );
int nStart = nStartx8 >> 8;
if( nStart > mnGlyphCount )
return;
VisualItem* pVI = mpVisualItems;
int nMinGlyphPos = 0, nEndGlyphPos;
if( nStart == 0 ) // nStart==0 for first visible glyph
{
for( int i = mnItemCount; --i >= 0; ++pVI )
if( GetItemSubrange( *pVI, nMinGlyphPos, nEndGlyphPos ) )
break;
nStart = nMinGlyphPos;
DBG_ASSERT( nStart <= mnGlyphCount, "USPLayout::MoveG overflow" );
}
else //if( nStart > 0 ) // nStart>0 means absolute_glyphpos+1
{
--nStart;
for( int i = mnItemCount; --i >= 0; ++pVI )
if( (nStart >= pVI->mnMinGlyphPos) && (nStart < pVI->mnEndGlyphPos) )
break;
bool bRC = GetItemSubrange( *pVI, nMinGlyphPos, nEndGlyphPos );
(void)bRC; // avoid var-not-used warning
DBG_ASSERT( bRC, "USPLayout::MoveG GISR() returned false" );
}
long nDelta = nNewXPos - pVI->mnXOffset;
if( nStart > nMinGlyphPos )
{
// move the glyph by expanding its left glyph but ignore dropped glyphs
int i, nLastUndropped = nMinGlyphPos - 1;
for( i = nMinGlyphPos; i < nStart; ++i )
{
if (mpOutGlyphs[i] != DROPPED_OUTGLYPH)
{
nDelta -= (mpJustifications)? mpJustifications[ i ] : mpGlyphAdvances[ i ];
nLastUndropped = i;
}
}
if (nLastUndropped >= nMinGlyphPos)
{
mpGlyphAdvances[ nLastUndropped ] += nDelta;
if (mpJustifications) mpJustifications[ nLastUndropped ] += nDelta;
}
else
{
pVI->mnXOffset += nDelta;
}
}
else
{
// move the visual item by having an offset
pVI->mnXOffset += nDelta;
}
// move subsequent items - this often isn't necessary because subsequent
// moves will correct subsequent items. However, if there is a contiguous
// range not involving fallback which spans items, this will be needed
while (++pVI - mpVisualItems < mnItemCount)
{
pVI->mnXOffset += nDelta;
}
}
// -----------------------------------------------------------------------
void UniscribeLayout::DropGlyph( int nStartx8 )
{
DBG_ASSERT( !(nStartx8 & 0xff), "USP::DropGlyph(): glyph injection not disabled!" );
int nStart = nStartx8 >> 8;
DBG_ASSERT( nStart<=mnGlyphCount, "USPLayout::MoveG nStart overflow" );
if( nStart > 0 ) // nStart>0 means absolute glyph pos + 1
--nStart;
else // nStart<=0 for first visible glyph
{
VisualItem* pVI = mpVisualItems;
for( int i = mnItemCount, nDummy; --i >= 0; ++pVI )
if( GetItemSubrange( *pVI, nStart, nDummy ) )
break;
DBG_ASSERT( nStart <= mnGlyphCount, "USPLayout::DropG overflow" );
int nOffset = 0;
int j = pVI->mnMinGlyphPos;
while (mpOutGlyphs[j] == DROPPED_OUTGLYPH) j++;
if (j == nStart)
{
pVI->mnXOffset += ((mpJustifications)? mpJustifications[nStart] : mpGlyphAdvances[nStart]);
}
}
mpOutGlyphs[ nStart ] = DROPPED_OUTGLYPH;
}
// -----------------------------------------------------------------------
void UniscribeLayout::Simplify( bool /*bIsBase*/ )
{
static const WCHAR cDroppedGlyph = DROPPED_OUTGLYPH;
int i;
// if there are no dropped glyphs don't bother
for( i = 0; i < mnGlyphCount; ++i )
if( mpOutGlyphs[ i ] == cDroppedGlyph )
break;
if( i >= mnGlyphCount )
return;
// prepare for sparse layout
// => make sure mpGlyphs2Chars[] exists
if( !mpGlyphs2Chars )
{
mpGlyphs2Chars = new int[ mnGlyphCapacity ];
for( i = 0; i < mnGlyphCount; ++i )
mpGlyphs2Chars[ i ] = -1;
for( int nItem = 0; nItem < mnItemCount; ++nItem )
{
// skip invisible items
VisualItem& rVI = mpVisualItems[ nItem ];
if( rVI.IsEmpty() )
continue;
for( i = rVI.mnEndCharPos; --i >= rVI.mnMinCharPos; )
{
int j = mpLogClusters[ i ] + rVI.mnMinGlyphPos;
mpGlyphs2Chars[ j ] = i;
}
}
}
// remove the dropped glyphs
const int* pGlyphWidths = mpJustifications ? mpJustifications : mpGlyphAdvances;
for( int nItem = 0; nItem < mnItemCount; ++nItem )
{
VisualItem& rVI = mpVisualItems[ nItem ];
if( rVI.IsEmpty() )
continue;
// mark replaced character widths
for( i = rVI.mnMinCharPos; i < rVI.mnEndCharPos; ++i )
{
int j = mpLogClusters[ i ] + rVI.mnMinGlyphPos;
if( mpOutGlyphs[ j ] == cDroppedGlyph )
mpCharWidths[ i ] = 0;
}
// handle dropped glyphs at start of visual item
int nMinGlyphPos, nEndGlyphPos, nOrigMinGlyphPos = rVI.mnMinGlyphPos;
GetItemSubrange( rVI, nMinGlyphPos, nEndGlyphPos );
i = nMinGlyphPos;
while( (mpOutGlyphs[i] == cDroppedGlyph) && (i < nEndGlyphPos) )
{
//rVI.mnXOffset += pGlyphWidths[ i ];
rVI.mnMinGlyphPos = ++i;
}
// when all glyphs in item got dropped mark it as empty
if( i >= nEndGlyphPos )
{
rVI.mnEndGlyphPos = 0;
continue;
}
// If there are still glyphs in the cluster and mnMinGlyphPos
// has changed then we need to remove the dropped glyphs at start
// to correct logClusters, which is unsigned and relative to the
// item start.
if (rVI.mnMinGlyphPos != nOrigMinGlyphPos)
{
// drop any glyphs in the visual item outside the range
for (i = nOrigMinGlyphPos; i < nMinGlyphPos; i++)
mpOutGlyphs[ i ] = cDroppedGlyph;
rVI.mnMinGlyphPos = i = nOrigMinGlyphPos;
}
// handle dropped glyphs in the middle of visual item
for(; i < nEndGlyphPos; ++i )
if( mpOutGlyphs[ i ] == cDroppedGlyph )
break;
int j = i;
while( ++i < nEndGlyphPos )
{
if( mpOutGlyphs[ i ] == cDroppedGlyph )
continue;
mpOutGlyphs[ j ] = mpOutGlyphs[ i ];
mpGlyphOffsets[ j ] = mpGlyphOffsets[ i ];
mpVisualAttrs[ j ] = mpVisualAttrs[ i ];
mpGlyphAdvances[ j ] = mpGlyphAdvances[ i ];
if( mpJustifications )
mpJustifications[ j ] = mpJustifications[ i ];
const int k = mpGlyphs2Chars[ i ];
mpGlyphs2Chars[ j ] = k;
const int nRelGlyphPos = (j++) - rVI.mnMinGlyphPos;
if( k < 0) // extra glyphs are already mapped
continue;
mpLogClusters[ k ] = static_cast<WORD>(nRelGlyphPos);
}
rVI.mnEndGlyphPos = j;
}
}
// -----------------------------------------------------------------------
void UniscribeLayout::DrawText( SalGraphics& ) const
{
HFONT hOrigFont = DisableFontScaling();
int nBaseClusterOffset = 0;
int nBaseGlyphPos = -1;
for( int nItem = 0; nItem < mnItemCount; ++nItem )
{
const VisualItem& rVisualItem = mpVisualItems[ nItem ];
// skip if there is nothing to display
int nMinGlyphPos, nEndGlyphPos;
if( !GetItemSubrange( rVisualItem, nMinGlyphPos, nEndGlyphPos ) )
continue;
if( nBaseGlyphPos < 0 )
{
// adjust draw position relative to cluster start
if( rVisualItem.IsRTL() )
nBaseGlyphPos = nEndGlyphPos - 1;
else
nBaseGlyphPos = nMinGlyphPos;
const int* pGlyphWidths;
if( mpJustifications )
pGlyphWidths = mpJustifications;
else
pGlyphWidths = mpGlyphAdvances;
int i = mnMinCharPos;
while( (--i >= rVisualItem.mnMinCharPos)
&& (nBaseGlyphPos == mpLogClusters[i]) )
nBaseClusterOffset += mpCharWidths[i];
if( !rVisualItem.IsRTL() )
nBaseClusterOffset = -nBaseClusterOffset;
}
// now draw the matching glyphs in this item
Point aRelPos( rVisualItem.mnXOffset + nBaseClusterOffset, 0 );
Point aPos = GetDrawPosition( aRelPos );
SCRIPT_CACHE& rScriptCache = GetScriptCache();
(*pScriptTextOut)( mhDC, &rScriptCache,
aPos.X(), aPos.Y(), 0, NULL,
&rVisualItem.mpScriptItem->a, NULL, 0,
mpOutGlyphs + nMinGlyphPos,
nEndGlyphPos - nMinGlyphPos,
mpGlyphAdvances + nMinGlyphPos,
mpJustifications ? mpJustifications + nMinGlyphPos : NULL,
mpGlyphOffsets + nMinGlyphPos );
}
if( hOrigFont )
DeleteFont( SelectFont( mhDC, hOrigFont ) );
}
// -----------------------------------------------------------------------
long UniscribeLayout::FillDXArray( long* pDXArray ) const
{
// calculate width of the complete layout
long nWidth = mnBaseAdv;
for( int nItem = mnItemCount; --nItem >= 0; )
{
const VisualItem& rVI = mpVisualItems[ nItem ];
// skip if there is nothing to display
int nMinGlyphPos, nEndGlyphPos;
if( !GetItemSubrange( rVI, nMinGlyphPos, nEndGlyphPos ) )
continue;
// width = xoffset + width of last item
nWidth = rVI.mnXOffset;
const int* pGlyphWidths = mpJustifications ? mpJustifications : mpGlyphAdvances;
for( int i = nMinGlyphPos; i < nEndGlyphPos; ++i )
nWidth += pGlyphWidths[i];
break;
}
// copy the virtual char widths into pDXArray[]
if( pDXArray )
for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
pDXArray[ i - mnMinCharPos ] = mpCharWidths[ i ];
return nWidth;
}
// -----------------------------------------------------------------------
int UniscribeLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const
{
long nWidth = 0;
for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
{
nWidth += mpCharWidths[ i ] * nFactor;
// check if the nMaxWidth still fits the current sub-layout
if( nWidth >= nMaxWidth )
{
// go back to cluster start
// we have to find the visual item first since the mpLogClusters[]
// needed to find the cluster start is relative to to the visual item
int nMinGlyphIndex = 0;
for( int nItem = 0; nItem < mnItemCount; ++nItem )
{
const VisualItem& rVisualItem = mpVisualItems[ nItem ];
nMinGlyphIndex = rVisualItem.mnMinGlyphPos;
if( (i >= rVisualItem.mnMinCharPos)
&& (i < rVisualItem.mnEndCharPos) )
break;
}
// now go back to the matching cluster start
do
{
int nGlyphPos = mpLogClusters[i] + nMinGlyphIndex;
if( 0 != mpVisualAttrs[ nGlyphPos ].fClusterStart )
return i;
} while( --i >= mnMinCharPos );
// if the cluster starts before the start of the visual item
// then set the visual breakpoint before this item
return mnMinCharPos;
}
// the visual break also depends on the nCharExtra between the characters
nWidth += nCharExtra;
}
// the whole layout did fit inside the nMaxWidth
return STRING_LEN;
}
// -----------------------------------------------------------------------
void UniscribeLayout::GetCaretPositions( int nMaxIdx, long* pCaretXArray ) const
{
int i;
for( i = 0; i < nMaxIdx; ++i )
pCaretXArray[ i ] = -1;
long* const pGlyphPos = (long*)alloca( (mnGlyphCount+1) * sizeof(long) );
for( i = 0; i <= mnGlyphCount; ++i )
pGlyphPos[ i ] = -1;
long nXPos = 0;
for( int nItem = 0; nItem < mnItemCount; ++nItem )
{
const VisualItem& rVisualItem = mpVisualItems[ nItem ];
if( rVisualItem.IsEmpty() )
continue;
if (mnLayoutFlags & SAL_LAYOUT_FOR_FALLBACK)
{
nXPos = rVisualItem.mnXOffset;
}
// get glyph positions
// TODO: handle when rVisualItem's glyph range is only partially used
for( i = rVisualItem.mnMinGlyphPos; i < rVisualItem.mnEndGlyphPos; ++i )
{
pGlyphPos[ i ] = nXPos;
nXPos += mpGlyphAdvances[ i ];
}
// rightmost position of this visualitem
pGlyphPos[ i ] = nXPos;
// convert glyph positions to character positions
i = rVisualItem.mnMinCharPos;
if( i < mnMinCharPos )
i = mnMinCharPos;
for(; (i < rVisualItem.mnEndCharPos) && (i < mnEndCharPos); ++i )
{
int j = mpLogClusters[ i ] + rVisualItem.mnMinGlyphPos;
int nCurrIdx = i * 2;
if( !rVisualItem.IsRTL() )
{
// normal positions for LTR case
pCaretXArray[ nCurrIdx ] = pGlyphPos[ j ];
pCaretXArray[ nCurrIdx+1 ] = pGlyphPos[ j+1 ];
}
else
{
// reverse positions for RTL case
pCaretXArray[ nCurrIdx ] = pGlyphPos[ j+1 ];
pCaretXArray[ nCurrIdx+1 ] = pGlyphPos[ j ];
}
}
}
if (!(mnLayoutFlags & SAL_LAYOUT_FOR_FALLBACK))
{
nXPos = 0;
// fixup unknown character positions to neighbor
for( i = 0; i < nMaxIdx; ++i )
{
if( pCaretXArray[ i ] >= 0 )
nXPos = pCaretXArray[ i ];
else
pCaretXArray[ i ] = nXPos;
}
}
}
// -----------------------------------------------------------------------
void UniscribeLayout::AdjustLayout( ImplLayoutArgs& rArgs )
{
SalLayout::AdjustLayout( rArgs );
// adjust positions if requested
if( rArgs.mpDXArray )
ApplyDXArray( rArgs );
else if( rArgs.mnLayoutWidth )
Justify( rArgs.mnLayoutWidth );
}
// -----------------------------------------------------------------------
void UniscribeLayout::ApplyDXArray( const ImplLayoutArgs& rArgs )
{
const long* pDXArray = rArgs.mpDXArray;
// increase char widths in string range to desired values
bool bModified = false;
int nOldWidth = 0;
DBG_ASSERT( mnUnitsPerPixel==1, "UniscribeLayout.mnUnitsPerPixel != 1" );
int i,j;
for( i = mnMinCharPos, j = 0; i < mnEndCharPos; ++i, ++j )
{
int nNewCharWidth = (pDXArray[j] - nOldWidth);
// TODO: nNewCharWidth *= mnUnitsPerPixel;
if( mpCharWidths[i] != nNewCharWidth )
{
mpCharWidths[i] = nNewCharWidth;
bModified = true;
}
nOldWidth = pDXArray[j];
}
if( !bModified )
return;
// initialize justifications array
mpJustifications = new int[ mnGlyphCapacity ];
for( i = 0; i < mnGlyphCount; ++i )
mpJustifications[ i ] = mpGlyphAdvances[ i ];
// apply new widths to script items
long nXOffset = 0;
for( int nItem = 0; nItem < mnItemCount; ++nItem )
{
VisualItem& rVisualItem = mpVisualItems[ nItem ];
// set the position of this visual item
rVisualItem.mnXOffset = nXOffset;
// ignore empty visual items
if( rVisualItem.IsEmpty() )
{
for (i = rVisualItem.mnMinCharPos; i < rVisualItem.mnEndCharPos; i++)
nXOffset += mpCharWidths[i];
continue;
}
// ignore irrelevant visual items
if( (rVisualItem.mnMinCharPos >= mnEndCharPos)
|| (rVisualItem.mnEndCharPos <= mnMinCharPos) )
continue;
// if needed prepare special handling for arabic justification
rVisualItem.mbHasKashidas = false;
if( rVisualItem.IsRTL() )
{
for( i = rVisualItem.mnMinGlyphPos; i < rVisualItem.mnEndGlyphPos; ++i )
if ( (1U << mpVisualAttrs[i].uJustification) & 0xFF82 ) // any Arabic justification
{ // excluding SCRIPT_JUSTIFY_NONE
// yes
rVisualItem.mbHasKashidas = true;
// so prepare for kashida handling
InitKashidaHandling();
break;
}
if( rVisualItem.HasKashidas() )
for( i = rVisualItem.mnMinGlyphPos; i < rVisualItem.mnEndGlyphPos; ++i )
{
// TODO: check if we still need this hack after correction of kashida placing?
// (i87688): apparently yes, we still need it!
if ( mpVisualAttrs[i].uJustification == SCRIPT_JUSTIFY_NONE )
// usp decided that justification can't be applied here
// but maybe our Kashida algorithm thinks differently.
// To avoid trouble (gaps within words, last character of
// a word gets a Kashida appended) override this.
// I chose SCRIPT_JUSTIFY_ARABIC_KASHIDA to replace SCRIPT_JUSTIFY_NONE
// just because this previous hack (which I haven't understand, sorry) used
// the same value to replace. Don't know if this is really the best
// thing to do, but it seems to fix things
mpVisualAttrs[i].uJustification = SCRIPT_JUSTIFY_ARABIC_KASHIDA;
}
}
// convert virtual charwidths to glyph justification values
HRESULT nRC = (*pScriptApplyLogicalWidth)(
mpCharWidths + rVisualItem.mnMinCharPos,
rVisualItem.mnEndCharPos - rVisualItem.mnMinCharPos,
rVisualItem.mnEndGlyphPos - rVisualItem.mnMinGlyphPos,
mpLogClusters + rVisualItem.mnMinCharPos,
mpVisualAttrs + rVisualItem.mnMinGlyphPos,
mpGlyphAdvances + rVisualItem.mnMinGlyphPos,
&rVisualItem.mpScriptItem->a,
&rVisualItem.maABCWidths,
mpJustifications + rVisualItem.mnMinGlyphPos );
if( nRC != 0 )
{
delete[] mpJustifications;
mpJustifications = NULL;
break;
}
// to prepare for the next visual item
// update nXOffset to the next items position
// before the mpJustifications[] array gets modified
int nMinGlyphPos, nEndGlyphPos;
if( GetItemSubrange( rVisualItem, nMinGlyphPos, nEndGlyphPos ) )
{
for( i = nMinGlyphPos; i < nEndGlyphPos; ++i )
nXOffset += mpJustifications[ i ];
if( rVisualItem.mbHasKashidas )
KashidaItemFix( nMinGlyphPos, nEndGlyphPos );
}
// workaround needed for older USP versions:
// right align the justification-adjusted glyphs in their cells for RTL-items
// unless the right alignment is done by inserting kashidas
if( bManualCellAlign && rVisualItem.IsRTL() && !rVisualItem.HasKashidas() )
{
for( i = nMinGlyphPos; i < nEndGlyphPos; ++i )
{
const int nXOffsetAdjust = mpJustifications[i] - mpGlyphAdvances[i];
// #i99862# skip diacritics, we mustn't add extra justification to diacritics
int nIdxAdd = i - 1;
while( (nIdxAdd >= nMinGlyphPos) && !mpGlyphAdvances[nIdxAdd] )
--nIdxAdd;
if( nIdxAdd < nMinGlyphPos )
rVisualItem.mnXOffset += nXOffsetAdjust;
else
mpJustifications[nIdxAdd] += nXOffsetAdjust;
mpJustifications[i] -= nXOffsetAdjust;
}
}
}
}
// -----------------------------------------------------------------------
void UniscribeLayout::InitKashidaHandling()
{
if( mnMinKashidaGlyph != 0 ) // already initialized
return;
mrWinFontEntry.InitKashidaHandling( mhDC );
mnMinKashidaWidth = static_cast<int>(mfFontScale * mrWinFontEntry.GetMinKashidaWidth());
mnMinKashidaGlyph = mrWinFontEntry.GetMinKashidaGlyph();
}
// adjust the kashida placement matching to the WriterEngine
void UniscribeLayout::KashidaItemFix( int nMinGlyphPos, int nEndGlyphPos )
{
// workaround needed for all known USP versions:
// ApplyLogicalWidth does not match ScriptJustify behaviour
for( int i = nMinGlyphPos; i < nEndGlyphPos; ++i )
{
// check for vowels
if( (i > nMinGlyphPos && !mpGlyphAdvances[ i-1 ])
&& (1U << mpVisualAttrs[i].uJustification) & 0xFF83 ) // all Arabic justifiction types
{ // including SCRIPT_JUSTIFY_NONE
// vowel, we do it like ScriptJustify does
// the vowel gets the extra width
long nSpaceAdded = mpJustifications[ i ] - mpGlyphAdvances[ i ];
mpJustifications [ i ] = mpGlyphAdvances [ i ];
mpJustifications [ i - 1 ] += nSpaceAdded;
}
}
// redistribute the widths for kashidas
for( int i = nMinGlyphPos; i < nEndGlyphPos; )
KashidaWordFix ( nMinGlyphPos, nEndGlyphPos, &i );
}
bool UniscribeLayout::KashidaWordFix ( int nMinGlyphPos, int nEndGlyphPos, int* pnCurrentPos )
{
// doing pixel work within a word.
// sometimes we have extra pixels and sometimes we miss some pixels to get to mnMinKashidaWidth
// find the next kashida
int nMinPos = *pnCurrentPos;
int nMaxPos = *pnCurrentPos;
for( int i = nMaxPos; i < nEndGlyphPos; ++i )
{
if( (mpVisualAttrs[ i ].uJustification >= SCRIPT_JUSTIFY_ARABIC_BLANK)
&& (mpVisualAttrs[ i ].uJustification < SCRIPT_JUSTIFY_ARABIC_NORMAL) )
break;
nMaxPos = i;
}
*pnCurrentPos = nMaxPos + 1;
if( nMinPos == nMaxPos )
return false;
// calculate the available space for an extra kashida
long nMaxAdded = 0;
int nKashPos = -1;
for( int i = nMaxPos; i >= nMinPos; --i )
{
long nSpaceAdded = mpJustifications[ i ] - mpGlyphAdvances[ i ];
if( nSpaceAdded > nMaxAdded )
{
nKashPos = i;
nMaxAdded = nSpaceAdded;
}
}
// return early if there is no need for an extra kashida
if ( nMaxAdded <= 0 )
return false;
// return early if there is not enough space for an extra kashida
if( 2*nMaxAdded < mnMinKashidaWidth )
return false;
// redistribute the extra spacing to the kashida position
for( int i = nMinPos; i <= nMaxPos; ++i )
{
if( i == nKashPos )
continue;
// everything else should not have extra spacing
long nSpaceAdded = mpJustifications[ i ] - mpGlyphAdvances[ i ];
if( nSpaceAdded > 0 )
{
mpJustifications[ i ] -= nSpaceAdded;
mpJustifications[ nKashPos ] += nSpaceAdded;
}
}
// check if we fulfill minimal kashida width
long nSpaceAdded = mpJustifications[ nKashPos ] - mpGlyphAdvances[ nKashPos ];
if( nSpaceAdded < mnMinKashidaWidth )
{
// ugly: steal some pixels
long nSteal = 1;
if ( nMaxPos - nMinPos > 0 && ((mnMinKashidaWidth - nSpaceAdded) > (nMaxPos - nMinPos)))
nSteal = (mnMinKashidaWidth - nSpaceAdded) / (nMaxPos - nMinPos);
for( int i = nMinPos; i <= nMaxPos; ++i )
{
if( i == nKashPos )
continue;
nSteal = Min( mnMinKashidaWidth - nSpaceAdded, nSteal );
if ( nSteal > 0 )
{
mpJustifications [ i ] -= nSteal;
mpJustifications [ nKashPos ] += nSteal;
nSpaceAdded += nSteal;
}
if( nSpaceAdded >= mnMinKashidaWidth )
return true;
}
}
// blank padding
long nSpaceMissing = mnMinKashidaWidth - nSpaceAdded;
if( nSpaceMissing > 0 )
{
// inner glyph: distribute extra space evenly
if( (nMinPos > nMinGlyphPos) && (nMaxPos < nEndGlyphPos - 1) )
{
mpJustifications [ nKashPos ] += nSpaceMissing;
long nHalfSpace = nSpaceMissing / 2;
mpJustifications [ nMinPos - 1 ] -= nHalfSpace;
mpJustifications [ nMaxPos + 1 ] -= nSpaceMissing - nHalfSpace;
}
// rightmost: left glyph gets extra space
else if( nMinPos > nMinGlyphPos )
{
mpJustifications [ nMinPos - 1 ] -= nSpaceMissing;
mpJustifications [ nKashPos ] += nSpaceMissing;
}
// leftmost: right glyph gets extra space
else if( nMaxPos < nEndGlyphPos - 1 )
{
mpJustifications [ nKashPos ] += nSpaceMissing;
mpJustifications [ nMaxPos + 1 ] -= nSpaceMissing;
}
else
return false;
}
return true;
}
// -----------------------------------------------------------------------
void UniscribeLayout::Justify( long nNewWidth )
{
long nOldWidth = 0;
int i;
for( i = mnMinCharPos; i < mnEndCharPos; ++i )
nOldWidth += mpCharWidths[ i ];
if( nOldWidth <= 0 )
return;
nNewWidth *= mnUnitsPerPixel; // convert into font units
if( nNewWidth == nOldWidth )
return;
// prepare to distribute the extra width evenly among the visual items
const double fStretch = (double)nNewWidth / nOldWidth;
// initialize justifications array
mpJustifications = new int[ mnGlyphCapacity ];
for( i = 0; i < mnGlyphCapacity; ++i )
mpJustifications[ i ] = mpGlyphAdvances[ i ];
// justify stretched script items
long nXOffset = 0;
SCRIPT_CACHE& rScriptCache = GetScriptCache();
for( int nItem = 0; nItem < mnItemCount; ++nItem )
{
VisualItem& rVisualItem = mpVisualItems[ nItem ];
if( rVisualItem.IsEmpty() )
continue;
if( (rVisualItem.mnMinCharPos < mnEndCharPos)
&& (rVisualItem.mnEndCharPos > mnMinCharPos) )
{
long nItemWidth = 0;
for( i = rVisualItem.mnMinCharPos; i < rVisualItem.mnEndCharPos; ++i )
nItemWidth += mpCharWidths[ i ];
nItemWidth = (int)((fStretch - 1.0) * nItemWidth + 0.5);
HRESULT nRC = (*pScriptJustify) (
mpVisualAttrs + rVisualItem.mnMinGlyphPos,
mpGlyphAdvances + rVisualItem.mnMinGlyphPos,
rVisualItem.mnEndGlyphPos - rVisualItem.mnMinGlyphPos,
nItemWidth,
mnMinKashidaWidth,
mpJustifications + rVisualItem.mnMinGlyphPos );
rVisualItem.mnXOffset = nXOffset;
nXOffset += nItemWidth;
}
}
}
// -----------------------------------------------------------------------
bool UniscribeLayout::IsKashidaPosValid ( int nCharPos ) const
{
// we have to find the visual item first since the mpLogClusters[]
// needed to find the cluster start is relative to to the visual item
int nMinGlyphIndex = -1;
for( int nItem = 0; nItem < mnItemCount; ++nItem )
{
const VisualItem& rVisualItem = mpVisualItems[ nItem ];
if( (nCharPos >= rVisualItem.mnMinCharPos)
&& (nCharPos < rVisualItem.mnEndCharPos) )
{
nMinGlyphIndex = rVisualItem.mnMinGlyphPos;
break;
}
}
// Invalid char pos or leftmost glyph in visual item
if ( nMinGlyphIndex == -1 || !mpLogClusters[ nCharPos ] )
return false;
// This test didn't give the expected results
/* if( mpLogClusters[ nCharPos+1 ] == mpLogClusters[ nCharPos ])
// two chars, one glyph
return false;*/
const int nGlyphPos = mpLogClusters[ nCharPos ] + nMinGlyphIndex;
if( nGlyphPos <= 0 )
return true;
// justification is only allowed if the glyph to the left has not SCRIPT_JUSTIFY_NONE
// and not SCRIPT_JUSTIFY_ARABIC_BLANK
// special case: glyph to the left is vowel (no advance width)
if ( mpVisualAttrs[ nGlyphPos-1 ].uJustification == SCRIPT_JUSTIFY_ARABIC_BLANK
|| ( mpVisualAttrs[ nGlyphPos-1 ].uJustification == SCRIPT_JUSTIFY_NONE
&& mpGlyphAdvances [ nGlyphPos-1 ] ))
return false;
return true;
}
#endif // USE_UNISCRIBE
#ifdef ENABLE_GRAPHITE
class GraphiteLayoutWinImpl : public GraphiteLayout
{
public:
GraphiteLayoutWinImpl(const gr::Font & font, ImplWinFontEntry & rFont)
throw()
: GraphiteLayout(font), mrFont(rFont) {};
virtual ~GraphiteLayoutWinImpl() throw() {};
virtual sal_GlyphId getKashidaGlyph(int & rWidth);
private:
ImplWinFontEntry & mrFont;
};
sal_GlyphId GraphiteLayoutWinImpl::getKashidaGlyph(int & rWidth)
{
rWidth = mrFont.GetMinKashidaWidth();
return mrFont.GetMinKashidaGlyph();
}
// This class uses the SIL Graphite engine to provide complex text layout services to the VCL
// @author tse
//
class GraphiteWinLayout : public WinLayout
{
private:
mutable GraphiteWinFont mpFont;
grutils::GrFeatureParser * mpFeatures;
mutable GraphiteLayoutWinImpl maImpl;
public:
GraphiteWinLayout(HDC hDC, const ImplWinFontData& rWFD, ImplWinFontEntry& rWFE) throw();
static bool IsGraphiteEnabledFont(HDC hDC) throw();
// used by upper layers
virtual bool LayoutText( ImplLayoutArgs& ); // first step of layout
virtual void AdjustLayout( ImplLayoutArgs& ); // adjusting after fallback etc.
// virtual void InitFont() const;
virtual void DrawText( SalGraphics& ) const;
// methods using string indexing
virtual int GetTextBreak( long nMaxWidth, long nCharExtra=0, int nFactor=1 ) const;
virtual long FillDXArray( long* pDXArray ) const;
virtual void GetCaretPositions( int nArraySize, long* pCaretXArray ) const;
// methods using glyph indexing
virtual int GetNextGlyphs(int nLen, sal_GlyphId* pGlyphIdxAry, ::Point & rPos, int&,
long* pGlyphAdvAry = 0, int* pCharPosAry = 0 ) const;
// used by glyph+font+script fallback
virtual void MoveGlyph( int nStart, long nNewXPos );
virtual void DropGlyph( int nStart );
virtual void Simplify( bool bIsBase );
~GraphiteWinLayout() { delete mpFeatures; mpFeatures = NULL; };
protected:
virtual void ReplaceDC(gr::Segment & segment) const;
virtual void RestoreDC(gr::Segment & segment) const;
};
bool GraphiteWinLayout::IsGraphiteEnabledFont(HDC hDC) throw()
{
return gr::WinFont::FontHasGraphiteTables(hDC);
}
GraphiteWinLayout::GraphiteWinLayout(HDC hDC, const ImplWinFontData& rWFD, ImplWinFontEntry& rWFE) throw()
: WinLayout(hDC, rWFD, rWFE), mpFont(hDC),
maImpl(mpFont, rWFE)
{
const rtl::OString aLang = MsLangId::convertLanguageToIsoByteString( rWFE.maFontSelData.meLanguage );
rtl::OString name = rtl::OUStringToOString(
rWFE.maFontSelData.maTargetName, RTL_TEXTENCODING_UTF8 );
sal_Int32 nFeat = name.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) + 1;
if (nFeat > 0)
{
rtl::OString aFeat = name.copy(nFeat, name.getLength() - nFeat);
mpFeatures = new grutils::GrFeatureParser(mpFont, aFeat.getStr(), aLang.getStr());
}
else
{
mpFeatures = new grutils::GrFeatureParser(mpFont, aLang.getStr());
}
maImpl.SetFeatures(mpFeatures);
}
void GraphiteWinLayout::ReplaceDC(gr::Segment & segment) const
{
COLORREF color = GetTextColor(mhDC);
dynamic_cast<gr::WinFont&>(segment.getFont()).replaceDC(mhDC);
SetTextColor(mhDC, color);
}
void GraphiteWinLayout::RestoreDC(gr::Segment & segment) const
{
dynamic_cast<gr::WinFont&>(segment.getFont()).restoreDC();
}
bool GraphiteWinLayout::LayoutText( ImplLayoutArgs & args)
{
if (args.mnMinCharPos >= args.mnEndCharPos)
{
maImpl.clear();
return true;
}
HFONT hUnRotatedFont;
if (args.mnOrientation)
{
// Graphite gets very confused if the font is rotated
LOGFONTW aLogFont;
::GetObjectW( mhFont, sizeof(LOGFONTW), &aLogFont);
aLogFont.lfEscapement = 0;
aLogFont.lfOrientation = 0;
hUnRotatedFont = ::CreateFontIndirectW( &aLogFont);
::SelectFont(mhDC, hUnRotatedFont);
}
WinLayout::AdjustLayout(args);
mpFont.replaceDC(mhDC);
maImpl.SetFontScale(WinLayout::mfFontScale);
//bool succeeded = maImpl.LayoutText(args);
#ifdef GRCACHE
GrSegRecord * pSegRecord = NULL;
gr::Segment * pSegment = maImpl.CreateSegment(args, &pSegRecord);
#else
gr::Segment * pSegment = maImpl.CreateSegment(args);
#endif
bool bSucceeded = false;
if (pSegment)
{
// replace the DC on the font within the segment
ReplaceDC(*pSegment);
// create glyph vectors
#ifdef GRCACHE
bSucceeded = maImpl.LayoutGlyphs(args, pSegment, pSegRecord);
#else
bSucceeded = maImpl.LayoutGlyphs(args, pSegment);
#endif
// restore original DC
RestoreDC(*pSegment);
#ifdef GRCACHE
if (pSegRecord) pSegRecord->unlock();
else delete pSegment;
#else
delete pSegment;
#endif
}
mpFont.restoreDC();
if (args.mnOrientation)
{
// restore the rotated font
::SelectFont(mhDC, mhFont);
::DeleteObject(hUnRotatedFont);
}
return bSucceeded;
}
void GraphiteWinLayout::AdjustLayout(ImplLayoutArgs& rArgs)
{
WinLayout::AdjustLayout(rArgs);
maImpl.DrawBase() = WinLayout::maDrawBase;
maImpl.DrawOffset() = WinLayout::maDrawOffset;
if ( (rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL) && rArgs.mpDXArray)
{
mrWinFontEntry.InitKashidaHandling(mhDC);
}
maImpl.AdjustLayout(rArgs);
}
void GraphiteWinLayout::DrawText(SalGraphics &sal_graphics) const
{
HFONT hOrigFont = DisableFontScaling();
const HDC aHDC = static_cast<WinSalGraphics&>(sal_graphics).getHDC();
maImpl.DrawBase() = WinLayout::maDrawBase;
maImpl.DrawOffset() = WinLayout::maDrawOffset;
const int MAX_GLYPHS = 2;
sal_GlyphId glyphIntStr[MAX_GLYPHS];
WORD glyphWStr[MAX_GLYPHS];
int glyphIndex = 0;
Point aPos(0,0);
int nGlyphs = 0;
do
{
nGlyphs = maImpl.GetNextGlyphs(1, glyphIntStr, aPos, glyphIndex);
if (nGlyphs < 1)
break;
std::copy(glyphIntStr, glyphIntStr + nGlyphs, glyphWStr);
::ExtTextOutW(aHDC, aPos.X(), aPos.Y(), ETO_GLYPH_INDEX,
NULL, (LPCWSTR)&(glyphWStr), nGlyphs, NULL);
} while (nGlyphs);
if( hOrigFont )
DeleteFont( SelectFont( aHDC, hOrigFont ) );
}
int GraphiteWinLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const
{
mpFont.replaceDC(mhDC);
int nBreak = maImpl.GetTextBreak(nMaxWidth, nCharExtra, nFactor);
mpFont.restoreDC();
return nBreak;
}
long GraphiteWinLayout::FillDXArray( long* pDXArray ) const
{
return maImpl.FillDXArray(pDXArray);
}
void GraphiteWinLayout::GetCaretPositions( int nArraySize, long* pCaretXArray ) const
{
maImpl.GetCaretPositions(nArraySize, pCaretXArray);
}
int GraphiteWinLayout::GetNextGlyphs( int length, sal_GlyphId* glyph_out,
::Point & pos_out, int &glyph_slot, long * glyph_adv, int *char_index) const
{
maImpl.DrawBase() = WinLayout::maDrawBase;
maImpl.DrawOffset() = WinLayout::maDrawOffset;
return maImpl.GetNextGlyphs(length, glyph_out, pos_out, glyph_slot, glyph_adv, char_index);
}
void GraphiteWinLayout::MoveGlyph( int glyph_idx, long new_x_pos )
{
maImpl.MoveGlyph(glyph_idx, new_x_pos);
}
void GraphiteWinLayout::DropGlyph( int glyph_idx )
{
maImpl.DropGlyph(glyph_idx);
}
void GraphiteWinLayout::Simplify( bool is_base )
{
maImpl.Simplify(is_base);
}
#endif // ENABLE_GRAPHITE
// =======================================================================
SalLayout* WinSalGraphics::GetTextLayout( ImplLayoutArgs& rArgs, int nFallbackLevel )
{
DBG_ASSERT( mpWinFontEntry[nFallbackLevel], "WinSalGraphics mpWinFontEntry==NULL");
WinLayout* pWinLayout = NULL;
const ImplWinFontData& rFontFace = *mpWinFontData[ nFallbackLevel ];
ImplWinFontEntry& rFontInstance = *mpWinFontEntry[ nFallbackLevel ];
#if defined( USE_UNISCRIBE )
if( !(rArgs.mnFlags & SAL_LAYOUT_COMPLEX_DISABLED)
&& (aUspModule || (bUspEnabled && InitUSP())) ) // CTL layout engine
{
#ifdef ENABLE_GRAPHITE
if (rFontFace.SupportsGraphite())
pWinLayout = new GraphiteWinLayout( getHDC(), rFontFace, rFontInstance);
else
#endif // ENABLE_GRAPHITE
// script complexity is determined in upper layers
pWinLayout = new UniscribeLayout( getHDC(), rFontFace, rFontInstance );
// NOTE: it must be guaranteed that the WinSalGraphics lives longer than
// the created UniscribeLayout, otherwise the data passed into the
// constructor might become invalid too early
}
else
#endif // USE_UNISCRIBE
{
#ifdef GCP_KERN_HACK
if( (rArgs.mnFlags & SAL_LAYOUT_KERNING_PAIRS) && !rFontInstance.HasKernData() )
{
// TODO: directly cache kerning info in the rFontInstance
// TODO: get rid of kerning methods+data in WinSalGraphics object
GetKernPairs( 0, NULL );
rFontInstance.SetKernData( mnFontKernPairCount, mpFontKernPairs );
}
#endif // GCP_KERN_HACK
BYTE eCharSet = ANSI_CHARSET;
if( mpLogFont )
eCharSet = mpLogFont->lfCharSet;
#ifdef ENABLE_GRAPHITE
if (rFontFace.SupportsGraphite())
pWinLayout = new GraphiteWinLayout( getHDC(), rFontFace, rFontInstance);
else
#endif // ENABLE_GRAPHITE
pWinLayout = new SimpleWinLayout( getHDC(), eCharSet, rFontFace, rFontInstance );
}
if( mfFontScale != 1.0 )
pWinLayout->SetFontScale( mfFontScale );
return pWinLayout;
}
// -----------------------------------------------------------------------
int WinSalGraphics::GetMinKashidaWidth()
{
if( !mpWinFontEntry[0] )
return 0;
mpWinFontEntry[0]->InitKashidaHandling( getHDC() );
int nMinKashida = static_cast<int>(mfFontScale * mpWinFontEntry[0]->GetMinKashidaWidth());
return nMinKashida;
}
// =======================================================================
ImplWinFontEntry::ImplWinFontEntry( ImplFontSelectData& rFSD )
: ImplFontEntry( rFSD )
, maWidthMap( 512 )
, mpKerningPairs( NULL )
, mnKerningPairs( -1 )
, mnMinKashidaWidth( -1 )
, mnMinKashidaGlyph( -1 )
{
#ifdef USE_UNISCRIBE
maScriptCache = NULL;
#endif // USE_UNISCRIBE
}
// -----------------------------------------------------------------------
ImplWinFontEntry::~ImplWinFontEntry()
{
#ifdef USE_UNISCRIBE
if( maScriptCache != NULL )
(*pScriptFreeCache)( &maScriptCache );
#endif // USE_UNISCRIBE
#ifdef GCP_KERN_HACK
delete[] mpKerningPairs;
#endif // GCP_KERN_HACK
}
// -----------------------------------------------------------------------
bool ImplWinFontEntry::HasKernData() const
{
return (mnKerningPairs >= 0);
}
// -----------------------------------------------------------------------
void ImplWinFontEntry::SetKernData( int nPairCount, const KERNINGPAIR* pPairData )
{
mnKerningPairs = nPairCount;
mpKerningPairs = new KERNINGPAIR[ mnKerningPairs ];
::memcpy( mpKerningPairs, (const void*)pPairData, nPairCount*sizeof(KERNINGPAIR) );
}
// -----------------------------------------------------------------------
int ImplWinFontEntry::GetKerning( sal_Unicode cLeft, sal_Unicode cRight ) const
{
int nKernAmount = 0;
if( mpKerningPairs )
{
const KERNINGPAIR aRefPair = { cLeft, cRight, 0 };
const KERNINGPAIR* pFirstPair = mpKerningPairs;
const KERNINGPAIR* pEndPair = mpKerningPairs + mnKerningPairs;
const KERNINGPAIR* pPair = std::lower_bound( pFirstPair,
pEndPair, aRefPair, ImplCmpKernData );
if( (pPair != pEndPair)
&& (pPair->wFirst == aRefPair.wFirst)
&& (pPair->wSecond == aRefPair.wSecond) )
nKernAmount = pPair->iKernAmount;
}
return nKernAmount;
}
// -----------------------------------------------------------------------
bool ImplWinFontEntry::InitKashidaHandling( HDC hDC )
{
if( mnMinKashidaWidth >= 0 ) // already cached?
return mnMinKashidaWidth;
// initialize the kashida width
mnMinKashidaWidth = 0;
mnMinKashidaGlyph = 0;
#ifdef USE_UNISCRIBE
if (aUspModule || (bUspEnabled && InitUSP()))
{
SCRIPT_FONTPROPERTIES aFontProperties;
aFontProperties.cBytes = sizeof (aFontProperties);
SCRIPT_CACHE& rScriptCache = GetScriptCache();
HRESULT nRC = (*pScriptGetFontProperties)( hDC, &rScriptCache, &aFontProperties );
if( nRC != 0 )
return false;
mnMinKashidaWidth = aFontProperties.iKashidaWidth;
mnMinKashidaGlyph = aFontProperties.wgKashida;
}
#endif // USE_UNISCRIBE
return true;
}
// =======================================================================
ImplFontData* ImplWinFontData::Clone() const
{
if( mpUnicodeMap )
mpUnicodeMap->AddReference();
ImplFontData* pClone = new ImplWinFontData( *this );
return pClone;
}
// -----------------------------------------------------------------------
ImplFontEntry* ImplWinFontData::CreateFontInstance( ImplFontSelectData& rFSD ) const
{
ImplFontEntry* pEntry = new ImplWinFontEntry( rFSD );
return pEntry;
}
// =======================================================================