diff options
Diffstat (limited to 'vcl/source/glyphs')
-rw-r--r-- | vcl/source/glyphs/gcach_ftyp.cxx | 98 | ||||
-rw-r--r-- | vcl/source/glyphs/gcach_ftyp.hxx | 5 | ||||
-rw-r--r--[-rwxr-xr-x] | vcl/source/glyphs/gcach_layout.cxx | 3 | ||||
-rw-r--r--[-rwxr-xr-x] | vcl/source/glyphs/gcach_rbmp.cxx | 3 | ||||
-rw-r--r-- | vcl/source/glyphs/gcach_vdev.cxx | 3 | ||||
-rw-r--r-- | vcl/source/glyphs/gcach_vdev.hxx | 3 | ||||
-rw-r--r-- | vcl/source/glyphs/glyphcache.cxx | 29 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_adaptors.cxx | 327 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_cache.cxx | 198 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_features.cxx | 289 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_layout.cxx | 1367 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_serverfont.cxx | 88 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_textsrc.cxx | 172 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_textsrc.hxx | 131 | ||||
-rw-r--r-- | vcl/source/glyphs/makefile.mk | 25 |
15 files changed, 2675 insertions, 66 deletions
diff --git a/vcl/source/glyphs/gcach_ftyp.cxx b/vcl/source/glyphs/gcach_ftyp.cxx index 591557eaa091..712c2334b35c 100644 --- a/vcl/source/glyphs/gcach_ftyp.cxx +++ b/vcl/source/glyphs/gcach_ftyp.cxx @@ -6,9 +6,6 @@ * * OpenOffice.org - a multi-platform office productivity suite * - * $RCSfile: gcach_ftyp.cxx,v $ - * $Revision: 1.151 $ - * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify @@ -353,7 +350,7 @@ FT_FaceRec_* FtFontInfo::GetFaceFT() maFaceFT = NULL; } - return maFaceFT; + return maFaceFT; } // ----------------------------------------------------------------------- @@ -1425,6 +1422,20 @@ bool FreetypeServerFont::GetGlyphBitmap1( int nGlyphIndex, RawBitmap& rRawBitmap FT_Glyph_Transform( pGlyphFT, &aMatrix, NULL ); } + // Check for zero area bounding boxes as this crashes some versions of FT. + // This also provides a handy short cut as much of the code following + // becomes an expensive nop when a glyph covers no pixels. + FT_BBox cbox; + FT_Glyph_Get_CBox(pGlyphFT, ft_glyph_bbox_unscaled, &cbox); + + if( (cbox.xMax - cbox.xMin) == 0 || (cbox.yMax - cbox.yMin == 0) ) + { + nAngle = 0; + memset(&rRawBitmap, 0, sizeof rRawBitmap); + FT_Done_Glyph( pGlyphFT ); + return true; + } + if( pGlyphFT->format != FT_GLYPH_FORMAT_BITMAP ) { if( pGlyphFT->format == FT_GLYPH_FORMAT_OUTLINE ) @@ -1698,60 +1709,55 @@ bool FreetypeServerFont::GetGlyphBitmap8( int nGlyphIndex, RawBitmap& rRawBitmap // ----------------------------------------------------------------------- // TODO: replace with GetFontCharMap() -ULONG FreetypeServerFont::GetFontCodeRanges( sal_uInt32* pCodes ) const +bool FreetypeServerFont::GetFontCodeRanges( CmapResult& rResult ) const { - CmapResult aResult; - aResult.mnPairCount = 0; - aResult.mpPairCodes = NULL; - aResult.mbSymbolic = mpFontInfo->IsSymbolFont(); + rResult.mbSymbolic = mpFontInfo->IsSymbolFont(); + // TODO: is the full CmapResult needed on platforms calling this? if( FT_IS_SFNT( maFaceFT ) ) { ULONG nLength = 0; const unsigned char* pCmap = mpFontInfo->GetTable( "cmap", &nLength ); - if( pCmap && nLength && ParseCMAP( pCmap, nLength, aResult ) ) - { - // copy the ranges into the provided array... - if( pCodes ) - for( int i = 0; i < 2*aResult.mnPairCount; ++i ) - pCodes[ i ] = aResult.mpPairCodes[ i ]; - - delete[] aResult.mpPairCodes; - } + if( pCmap && (nLength > 0) ) + if( ParseCMAP( pCmap, nLength, rResult ) ) + return true; } - if( aResult.mnPairCount <= 0 ) + typedef std::vector<sal_uInt32> U32Vector; + U32Vector aCodes; + + // FT's coverage is available since FT>=2.1.0 (OOo-baseline>=2.1.4 => ok) + aCodes.reserve( 0x1000 ); + FT_UInt nGlyphIndex; + for( sal_uInt32 cCode = FT_Get_First_Char( maFaceFT, &nGlyphIndex );; ) { - if( aResult.mbSymbolic ) - { - // we usually get here for Type1 symbol fonts - if( pCodes ) - { - pCodes[ 0 ] = 0xF020; - pCodes[ 1 ] = 0xF100; - } - aResult.mnPairCount = 1; - } - else - { - // we have to use the brute force method... - for( sal_uInt32 cCode = 0x0020;; ) - { - for(; cCode<0xFFF0 && !GetGlyphIndex( cCode ); ++cCode ) ; - if( cCode >= 0xFFF0 ) - break; - ++aResult.mnPairCount; - if( pCodes ) - *(pCodes++) = cCode; - for(; cCode<0xFFF0 && GetGlyphIndex( cCode ); ++cCode ) ; - if( pCodes ) - *(pCodes++) = cCode; - } - } + if( !nGlyphIndex ) + break; + aCodes.push_back( cCode ); // first code inside range + sal_uInt32 cNext = cCode; + do cNext = FT_Get_Next_Char( maFaceFT, cCode, &nGlyphIndex ); while( cNext == ++cCode ); + aCodes.push_back( cCode ); // first code outside range + cCode = cNext; } - return aResult.mnPairCount; + const int nCount = aCodes.size(); + if( !nCount) { + if( !rResult.mbSymbolic ) + return false; + + // we usually get here for Type1 symbol fonts + aCodes.push_back( 0xF020 ); + aCodes.push_back( 0xF100 ); + } + + sal_uInt32* pCodes = new sal_uInt32[ nCount ]; + for( int i = 0; i < nCount; ++i ) + pCodes[i] = aCodes[i]; + rResult.mpRangeCodes = pCodes; + rResult.mnRangeCount = nCount / 2; + return true; } + // ----------------------------------------------------------------------- // kerning stuff // ----------------------------------------------------------------------- diff --git a/vcl/source/glyphs/gcach_ftyp.hxx b/vcl/source/glyphs/gcach_ftyp.hxx index 660f772d43ed..936abdc02e59 100644 --- a/vcl/source/glyphs/gcach_ftyp.hxx +++ b/vcl/source/glyphs/gcach_ftyp.hxx @@ -6,9 +6,6 @@ * * OpenOffice.org - a multi-platform office productivity suite * - * $RCSfile: gcach_ftyp.hxx,v $ - * $Revision: 1.41 $ - * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify @@ -208,7 +205,7 @@ protected: int ApplyGlyphTransform( int nGlyphFlags, FT_GlyphRec_*, bool ) const; virtual void InitGlyphData( int nGlyphIndex, GlyphData& ) const; - virtual ULONG GetFontCodeRanges( sal_uInt32* pCodes ) const; + virtual bool GetFontCodeRanges( CmapResult& ) const; bool ApplyGSUB( const ImplFontSelectData& ); virtual ServerFontLayoutEngine* GetLayoutEngine(); diff --git a/vcl/source/glyphs/gcach_layout.cxx b/vcl/source/glyphs/gcach_layout.cxx index 6bbf78f819b0..364a1fcd3beb 100755..100644 --- a/vcl/source/glyphs/gcach_layout.cxx +++ b/vcl/source/glyphs/gcach_layout.cxx @@ -6,9 +6,6 @@ * * OpenOffice.org - a multi-platform office productivity suite * - * $RCSfile: gcach_layout.cxx,v $ - * $Revision: 1.46 $ - * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify diff --git a/vcl/source/glyphs/gcach_rbmp.cxx b/vcl/source/glyphs/gcach_rbmp.cxx index 9cba5332f281..1419a205f9a6 100755..100644 --- a/vcl/source/glyphs/gcach_rbmp.cxx +++ b/vcl/source/glyphs/gcach_rbmp.cxx @@ -6,9 +6,6 @@ * * OpenOffice.org - a multi-platform office productivity suite * - * $RCSfile: gcach_rbmp.cxx,v $ - * $Revision: 1.10.114.1 $ - * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify diff --git a/vcl/source/glyphs/gcach_vdev.cxx b/vcl/source/glyphs/gcach_vdev.cxx index 31e0824ea0ba..1ac9ee9bf0a6 100644 --- a/vcl/source/glyphs/gcach_vdev.cxx +++ b/vcl/source/glyphs/gcach_vdev.cxx @@ -6,9 +6,6 @@ * * OpenOffice.org - a multi-platform office productivity suite * - * $RCSfile: gcach_vdev.cxx,v $ - * $Revision: 1.20 $ - * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify diff --git a/vcl/source/glyphs/gcach_vdev.hxx b/vcl/source/glyphs/gcach_vdev.hxx index ae90c23d46a4..01ebc0f704cd 100644 --- a/vcl/source/glyphs/gcach_vdev.hxx +++ b/vcl/source/glyphs/gcach_vdev.hxx @@ -6,9 +6,6 @@ * * OpenOffice.org - a multi-platform office productivity suite * - * $RCSfile: gcach_vdev.hxx,v $ - * $Revision: 1.11 $ - * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify diff --git a/vcl/source/glyphs/glyphcache.cxx b/vcl/source/glyphs/glyphcache.cxx index acfc8e3ac38d..e3e840e40730 100644 --- a/vcl/source/glyphs/glyphcache.cxx +++ b/vcl/source/glyphs/glyphcache.cxx @@ -6,9 +6,6 @@ * * OpenOffice.org - a multi-platform office productivity suite * - * $RCSfile: glyphcache.cxx,v $ - * $Revision: 1.44 $ - * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify @@ -41,6 +38,10 @@ #include <vcl/bitmap.hxx> #include <vcl/outfont.hxx> +#ifdef ENABLE_GRAPHITE +#include <vcl/graphite_features.hxx> +#endif + #include <rtl/ustring.hxx> // used only for string=>hashvalue #include <osl/file.hxx> #include <tools/debug.hxx> @@ -85,12 +86,23 @@ size_t GlyphCache::IFSD_Hash::operator()( const ImplFontSelectData& rFontSelData { // TODO: is it worth to improve this hash function? sal_IntPtr nFontId = reinterpret_cast<sal_IntPtr>( rFontSelData.mpFontData ); +#ifdef ENABLE_GRAPHITE + if (rFontSelData.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) + != STRING_NOTFOUND) + { + rtl::OString aFeatName = rtl::OUStringToOString( rFontSelData.maTargetName, RTL_TEXTENCODING_UTF8 ); + nFontId ^= aFeatName.hashCode(); + } +#endif size_t nHash = nFontId << 8; nHash += rFontSelData.mnHeight; nHash += rFontSelData.mnOrientation; nHash += rFontSelData.mbVertical; nHash += rFontSelData.meItalic; nHash += rFontSelData.meWeight; +#ifdef ENABLE_GRAPHITE + nHash += rFontSelData.meLanguage; +#endif return nHash; } @@ -121,7 +133,16 @@ bool GlyphCache::IFSD_Equal::operator()( const ImplFontSelectData& rA, const Imp if( (rA.mnWidth != rB.mnWidth) && ((rA.mnHeight != rB.mnWidth) || (rA.mnWidth != 0)) ) return false; - +#ifdef ENABLE_GRAPHITE + if (rA.meLanguage != rB.meLanguage) + return false; + // check for features + if ((rA.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) + != STRING_NOTFOUND || + rB.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) + != STRING_NOTFOUND) && rA.maTargetName != rB.maTargetName) + return false; +#endif return true; } diff --git a/vcl/source/glyphs/graphite_adaptors.cxx b/vcl/source/glyphs/graphite_adaptors.cxx new file mode 100644 index 000000000000..9b16318fdc40 --- /dev/null +++ b/vcl/source/glyphs/graphite_adaptors.cxx @@ -0,0 +1,327 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: $ + * $Revision: $ + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// Description: Implements the Graphite interfaces with access to the +// platform's font and graphics systems. + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Header files +// +// Standard Library +#include <string> +#include <cassert> +// Libraries +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <i18npool/mslangid.hxx> +// Platform +#ifndef WNT +#include <saldisp.hxx> + +#include <vcl/salgdi.hxx> + +#include <freetype/ftsynth.h> + +// Module +#include "gcach_ftyp.hxx" + +#include <vcl/graphite_features.hxx> +#include <vcl/graphite_adaptors.hxx> + +// Module private type definitions and forward declarations. +// +using gr::GrResult; +namespace +{ + inline float from_hinted(const int x) { + return static_cast<float>(x + 32) / 64.0; + } + typedef std::hash_map<long,bool> SilfMap; + SilfMap sSilfMap; +} + +// class CharacterRenderProperties implentation. +// +FontProperties::FontProperties(const FreetypeServerFont &font) throw() +{ + clrFore = gr::kclrBlack; + clrBack = gr::kclrTransparent; + + pixHeight = from_hinted(font.GetMetricsFT().height); + + switch (font.GetFontSelData().meWeight) + { + case WEIGHT_SEMIBOLD: case WEIGHT_BOLD: + case WEIGHT_ULTRABOLD: case WEIGHT_BLACK: + fBold = true; + break; + default : + fBold = false; + } + + switch (font.GetFontSelData().meItalic) + { + case ITALIC_NORMAL: case ITALIC_OBLIQUE: + fItalic = true; + break; + default : + fItalic = false; + } + + // Get the font name. + const sal_Unicode * name = font.GetFontSelData().maName.GetBuffer(); + const size_t name_sz = std::min(sizeof szFaceName/sizeof(wchar_t)-1, + size_t(font.GetFontSelData().maName.Len())); + + std::copy(name, name + name_sz, szFaceName); + szFaceName[name_sz] = '\0'; +} + +// class GraphiteFontAdaptor implementaion. +// +GraphiteFontAdaptor::GraphiteFontAdaptor(ServerFont & sfont, const sal_Int32 dpiX, const sal_Int32 dpiY) + : mrFont(static_cast<FreetypeServerFont &>(sfont)), + maFontProperties(static_cast<FreetypeServerFont &>(sfont)), + mnDpiX(dpiX), + mnDpiY(dpiY), + mfAscent(from_hinted(static_cast<FreetypeServerFont &>(sfont).GetMetricsFT().ascender)), + mfDescent(from_hinted(static_cast<FreetypeServerFont &>(sfont).GetMetricsFT().descender)), + mfEmUnits(static_cast<FreetypeServerFont &>(sfont).GetMetricsFT().y_ppem), + mpFeatures(NULL) +{ + //std::wstring face_name(maFontProperties.szFaceName); + const rtl::OString aLang = MsLangId::convertLanguageToIsoByteString( sfont.GetFontSelData().meLanguage ); +#ifdef DEBUG + printf("GraphiteFontAdaptor %lx\n", (long)this); +#endif + rtl::OString name = rtl::OUStringToOString( + sfont.GetFontSelData().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(*this, aFeat.getStr(), aLang.getStr()); +#ifdef DEBUG + printf("GraphiteFontAdaptor %s/%s/%s %x language %d features %d errors\n", + rtl::OUStringToOString( sfont.GetFontSelData().maName, + RTL_TEXTENCODING_UTF8 ).getStr(), + rtl::OUStringToOString( sfont.GetFontSelData().maTargetName, + RTL_TEXTENCODING_UTF8 ).getStr(), + rtl::OUStringToOString( sfont.GetFontSelData().maSearchName, + RTL_TEXTENCODING_UTF8 ).getStr(), + sfont.GetFontSelData().meLanguage, + (int)mpFeatures->getFontFeatures(NULL), mpFeatures->parseErrors()); +#endif + } + else + { + mpFeatures = new grutils::GrFeatureParser(*this, aLang.getStr()); + } +} + +GraphiteFontAdaptor::GraphiteFontAdaptor(const GraphiteFontAdaptor &rhs) throw() + : Font(rhs), + mrFont (rhs.mrFont), maFontProperties(rhs.maFontProperties), + mnDpiX(rhs.mnDpiX), mnDpiY(rhs.mnDpiY), + mfAscent(rhs.mfAscent), mfDescent(rhs.mfDescent), mfEmUnits(rhs.mfEmUnits), + mpFeatures(NULL) +{ + if (rhs.mpFeatures) mpFeatures = new grutils::GrFeatureParser(*(rhs.mpFeatures)); +} + + +GraphiteFontAdaptor::~GraphiteFontAdaptor() throw() +{ + maGlyphMetricMap.clear(); + if (mpFeatures) delete mpFeatures; + mpFeatures = NULL; +} + +void GraphiteFontAdaptor::UniqueCacheInfo(std::wstring & face_name_out, bool & bold_out, bool & italic_out) +{ + face_name_out = maFontProperties.szFaceName; + bold_out = maFontProperties.fBold; + italic_out = maFontProperties.fItalic; +} + +bool GraphiteFontAdaptor::IsGraphiteEnabledFont(ServerFont & font) throw() +{ + // NOTE: this assumes that the same FTFace pointer won't be reused, + // so FtFontInfo::ReleaseFaceFT must only be called at shutdown. + FreetypeServerFont & aFtFont = dynamic_cast<FreetypeServerFont &>(font); + FT_Face aFace = reinterpret_cast<FT_FaceRec_*>(aFtFont.GetFtFace()); + SilfMap::iterator i = sSilfMap.find(reinterpret_cast<long>(aFace)); + if (i != sSilfMap.end()) + { +#ifdef DEBUG + if (static_cast<bool>(aFtFont.GetTable("Silf", 0)) != (*i).second) + printf("Silf cache font mismatch\n"); +#endif + return (*i).second; + } + bool bHasSilf = aFtFont.GetTable("Silf", 0); + sSilfMap[reinterpret_cast<long>(aFace)] = bHasSilf; + return bHasSilf; +} + + +gr::Font * GraphiteFontAdaptor::copyThis() { + return new GraphiteFontAdaptor(*this); +} + + +unsigned int GraphiteFontAdaptor::getDPIx() { + return mnDpiX; +} + + +unsigned int GraphiteFontAdaptor::getDPIy() { + return mnDpiY; +} + + +float GraphiteFontAdaptor::ascent() { + return mfAscent; +} + + +float GraphiteFontAdaptor::descent() { + return mfDescent; +} + + +bool GraphiteFontAdaptor::bold() { + return maFontProperties.fBold; +} + + +bool GraphiteFontAdaptor::italic() { + return maFontProperties.fItalic; +} + + +float GraphiteFontAdaptor::height() { + return maFontProperties.pixHeight; +} + + +void GraphiteFontAdaptor::getFontMetrics(float * ascent_out, float * descent_out, float * em_square_out) { + if (ascent_out) *ascent_out = mfAscent; + if (descent_out) *descent_out = mfDescent; + if (em_square_out) *em_square_out = mfEmUnits; +} + + +const void * GraphiteFontAdaptor::getTable(gr::fontTableId32 table_id, size_t * buffer_sz) +{ + char tag_name[5] = {char(table_id >> 24), char(table_id >> 16), char(table_id >> 8), char(table_id), 0}; + ULONG temp = *buffer_sz; + + const void * const tbl_buf = static_cast<FreetypeServerFont &>(mrFont).GetTable(tag_name, &temp); + *buffer_sz = temp; + + return tbl_buf; +} + +#define fix26_6(x) (x >> 6) + (x & 32 ? (x > 0 ? 1 : 0) : (x < 0 ? -1 : 0)) + +// Return the glyph's metrics in pixels. +void GraphiteFontAdaptor::getGlyphMetrics(gr::gid16 nGlyphId, gr::Rect & aBounding, gr::Point & advances) +{ + // Graphite gets really confused if the glyphs have been transformed, so + // if orientation has been set we can't use the font's glyph cache + // unfortunately the font selection data, doesn't always have the orientation + // set, even if it was when the glyphs were cached, so we use our own cache. + +// const GlyphMetric & metric = mrFont.GetGlyphMetric(nGlyphId); +// +// aBounding.right = aBounding.left = metric.GetOffset().X(); +// aBounding.bottom = aBounding.top = -metric.GetOffset().Y(); +// aBounding.right += metric.GetSize().Width(); +// aBounding.bottom -= metric.GetSize().Height(); +// +// advances.x = metric.GetDelta().X(); +// advances.y = -metric.GetDelta().Y(); + + GlyphMetricMap::const_iterator gm_itr = maGlyphMetricMap.find(nGlyphId); + if (gm_itr != maGlyphMetricMap.end()) + { + // We've cached the results from last time. + aBounding = gm_itr->second.first; + advances = gm_itr->second.second; + } + else + { + // We need to look up the glyph. + FT_Int nLoadFlags = mrFont.GetLoadFlags(); + + FT_Face aFace = reinterpret_cast<FT_Face>(mrFont.GetFtFace()); + if (!aFace) + { + aBounding.top = aBounding.bottom = aBounding.left = aBounding.right = 0; + advances.x = advances.y = 0; + return; + } + FT_Error aStatus = -1; + aStatus = FT_Load_Glyph(aFace, nGlyphId, nLoadFlags); + if( aStatus != FT_Err_Ok || (!aFace->glyph)) + { + aBounding.top = aBounding.bottom = aBounding.left = aBounding.right = 0; + advances.x = advances.y = 0; + return; + } + // check whether we need synthetic bold/italic otherwise metric is wrong + if (mrFont.NeedsArtificialBold()) + FT_GlyphSlot_Embolden(aFace->glyph); + + if (mrFont.NeedsArtificialItalic()) + FT_GlyphSlot_Oblique(aFace->glyph); + + const FT_Glyph_Metrics &gm = aFace->glyph->metrics; + + // Fill out the bounding box an advances. + aBounding.top = aBounding.bottom = fix26_6(gm.horiBearingY); + aBounding.bottom -= fix26_6(gm.height); + aBounding.left = aBounding.right = fix26_6(gm.horiBearingX); + aBounding.right += fix26_6(gm.width); + advances.x = fix26_6(gm.horiAdvance); + advances.y = 0; + + // Now add an entry to our metrics map. + maGlyphMetricMap[nGlyphId] = std::make_pair(aBounding, advances); + } +} + +#endif diff --git a/vcl/source/glyphs/graphite_cache.cxx b/vcl/source/glyphs/graphite_cache.cxx new file mode 100644 index 000000000000..8c514c611d2c --- /dev/null +++ b/vcl/source/glyphs/graphite_cache.cxx @@ -0,0 +1,198 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#ifdef WNT +#include <tools/svwin.h> +#include <svsys.h> +#endif + +#include <tools/debug.hxx> +#include <vcl/sallayout.hxx> + +#include <graphite/GrClient.h> +#include <graphite/Segment.h> + +#include <rtl/ustring.hxx> +#include <vcl/graphite_layout.hxx> +#include <vcl/graphite_cache.hxx> + +#include "graphite_textsrc.hxx" + +GrSegRecord::GrSegRecord(rtl::OUString * rope, TextSourceAdaptor * textSrc, gr::Segment * seg, bool bIsRtl) + : m_rope(rope), m_text(textSrc), m_seg(seg), m_nextKey(NULL), + m_fontScale(0.0f), mbIsRtl(bIsRtl), m_lockCount(0) +{ + m_pStr = textSrc->getLayoutArgs().mpStr + seg->startCharacter(); + m_startChar = seg->startCharacter(); +} + +GrSegRecord::~GrSegRecord() +{ + clear(); +} + +void GrSegRecord::reuse(rtl::OUString * rope, TextSourceAdaptor * textSrc, gr::Segment * seg, bool bIsRtl) +{ + clear(); + mnWidth = 0; + m_rope = rope; + m_text = textSrc; + m_seg = seg; + m_nextKey = NULL; + m_pStr = textSrc->getLayoutArgs().mpStr + seg->startCharacter(); + m_startChar = seg->startCharacter(); + mbIsRtl = bIsRtl; +} + +void GrSegRecord::clearVectors() +{ + mvGlyphs.clear(); + mvCharDxs.clear(); + mvChar2BaseGlyph.clear(); + mvGlyph2Char.clear(); +} + +void GrSegRecord::clear() +{ +#ifdef GR_DEBUG_TEXT + if (m_lockCount != 0) + OutputDebugString("GrSegRecord locked!"); +#endif + clearVectors(); + delete m_rope; + delete m_seg; + delete m_text; + m_rope = NULL; + m_seg = NULL; + m_text = NULL; + m_fontScale = 0.0f; + m_lockCount = 0; +} + +GrSegRecord * GraphiteSegmentCache::cacheSegment(TextSourceAdaptor * adapter, gr::Segment * seg, bool bIsRtl) +{ + GrSegRecord * record = NULL; + // We keep a record of the oldest key and the last key added + // when the next key is added, the record for the prevKey's m_nextKey field + // is updated to the newest key so that m_oldestKey can be updated to the + // next oldest key when the record for m_oldestKey is deleted + if (m_segMap.size() > SEG_CACHE_SIZE) + { + GraphiteSegMap::iterator oldestPair = m_segMap.find(reinterpret_cast<long>(m_oldestKey)); + // oldest record may no longer exist if a buffer was changed + if (oldestPair != m_segMap.end()) + { + record = oldestPair->second; + m_segMap.erase(reinterpret_cast<long>(m_oldestKey)); + GrRMEntry range = m_ropeMap.equal_range((*(record->m_rope)).hashCode()); + while (range.first != range.second) + { + if (range.first->second == record) + { + m_ropeMap.erase(range.first); + break; + } + ++range.first; + } + m_oldestKey = record->m_nextKey; + // record will be reused, so don't delete + } + } + + +// const int seg_char_limit = min(adapter->maLayoutArgs().mnLength, +// adapter->maLayoutArgs().mnEndCharPos +// + GraphiteLayout::EXTRA_CONTEXT_LENGTH); +// if (seg->stopCharacter() - seg->startCharacter() <= 0) +// OutputDebugString("Invalid seg indices\n"); + rtl::OUString * pRope = new rtl::OUString(adapter->getLayoutArgs().mpStr + seg->startCharacter(), + seg->stopCharacter() - seg->startCharacter()); + if (!pRope) return NULL; + bool reuse = false; + if (record) + record->reuse(pRope, adapter, seg, bIsRtl); + else + record = new GrSegRecord(pRope, adapter, seg, bIsRtl); + if (!record) + { + delete pRope; + return NULL; + } + GraphiteSegMap::iterator iMap = + m_segMap.find(reinterpret_cast<long>(record->m_pStr)); + if (iMap != m_segMap.end()) + { + // the buffer has changed, so the old cached Segment is useless + reuse = true; + GrSegRecord * found = iMap->second; + // Note: we reuse the old next key to avoid breaking our history + // chain. This means it will be prematurely deleted, but this is + // unlikely to happen very often. + record->m_nextKey = found->m_nextKey; + // overwrite the old record + m_segMap[reinterpret_cast<long>(record->m_pStr)] = record; + // erase the old rope key and save the new one + GrRMEntry range = m_ropeMap.equal_range((*(found->m_rope)).hashCode()); + while (range.first != range.second) + { + if (range.first->second == found) + { + m_ropeMap.erase(range.first); + break; + } + ++range.first; + } + GraphiteRopeMap::value_type mapEntry(record->m_rope->hashCode(), record); + m_ropeMap.insert(mapEntry); + // remove the old record + delete found; + record->m_lockCount++; + return record; + } + m_segMap[reinterpret_cast<long>(record->m_pStr)] = record; + GraphiteRopeMap::value_type mapEntry((*(record->m_rope)).hashCode(), record); + m_ropeMap.insert(mapEntry); + + if (m_oldestKey == NULL) + { + m_oldestKey = record->m_pStr; + m_prevKey = record->m_pStr; + } + else if (reuse == false) + { + DBG_ASSERT(m_segMap.count(reinterpret_cast<long>(m_prevKey)), + "Previous key got lost somehow!"); + m_segMap.find(reinterpret_cast<long>(m_prevKey)) + ->second->m_nextKey = record->m_pStr; + m_prevKey = record->m_pStr; + } + record->m_lockCount++; + return record; +} diff --git a/vcl/source/glyphs/graphite_features.cxx b/vcl/source/glyphs/graphite_features.cxx new file mode 100644 index 000000000000..dae1bfc2866e --- /dev/null +++ b/vcl/source/glyphs/graphite_features.cxx @@ -0,0 +1,289 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: $ + * $Revision: $ + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// Description: +// Parse a string of features specified as & separated pairs. +// e.g. +// 1001=1&2002=2&fav1=0 + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#include <sal/types.h> + +#ifdef WNT +#include <tools/svwin.h> +#include <svsys.h> +#endif + +#include <vcl/graphite_features.hxx> + +using namespace grutils; +// These mustn't conflict with font name lists which use ; and , +const char GrFeatureParser::FEAT_PREFIX = ':'; +const char GrFeatureParser::FEAT_SEPARATOR = '&'; +const char GrFeatureParser::FEAT_ID_VALUE_SEPARATOR = '='; +const std::string GrFeatureParser::ISO_LANG("lang"); + +GrFeatureParser::GrFeatureParser(gr::Font & font, const std::string lang) + : mnNumSettings(0), mbErrors(false) +{ + maLang.rgch[0] = maLang.rgch[1] = maLang.rgch[2] = maLang.rgch[3] = '\0'; + setLang(font, lang); +} + +GrFeatureParser::GrFeatureParser(gr::Font & font, const std::string features, const std::string lang) + : mnNumSettings(0), mbErrors(false) +{ + size_t nEquals = 0; + size_t nFeatEnd = 0; + size_t pos = 0; + maLang.rgch[0] = maLang.rgch[1] = maLang.rgch[2] = maLang.rgch[3] = '\0'; + setLang(font, lang); + while (pos < features.length() && mnNumSettings < MAX_FEATURES) + { + nEquals = features.find(FEAT_ID_VALUE_SEPARATOR,pos); + if (nEquals == std::string::npos) + { + mbErrors = true; + break; + } + // check for a lang=xxx specification + if (features.compare(pos, nEquals - pos, ISO_LANG) == 0) + { + pos = nEquals + 1; + nFeatEnd = features.find(FEAT_SEPARATOR, pos); + if (nFeatEnd == std::string::npos) + { + nFeatEnd = features.length(); + } + if (nFeatEnd - pos > 3) + mbErrors = true; + else + { + gr::isocode aLang = maLang; + for (size_t i = pos; i < nFeatEnd; i++) + aLang.rgch[i-pos] = features[i]; + std::pair<gr::LanguageIterator,gr::LanguageIterator> aSupported + = font.getSupportedLanguages(); + gr::LanguageIterator iL = aSupported.first; + while (iL != aSupported.second) + { + gr::isocode aSupportedLang = *iL; + // here we only expect full 3 letter codes + if (aLang.rgch[0] == aSupportedLang.rgch[0] && + aLang.rgch[1] == aSupportedLang.rgch[1] && + aLang.rgch[2] == aSupportedLang.rgch[2] && + aLang.rgch[3] == aSupportedLang.rgch[3]) break; + ++iL; + } + if (iL == aSupported.second) mbErrors = true; + else maLang = aLang; + } + } + else + { + if (isCharId(features, pos, nEquals - pos)) + maSettings[mnNumSettings].id = getCharId(features, pos, nEquals - pos); + else maSettings[mnNumSettings].id = getIntValue(features, pos, nEquals - pos); + pos = nEquals + 1; + nFeatEnd = features.find(FEAT_SEPARATOR, pos); + if (nFeatEnd == std::string::npos) + { + nFeatEnd = features.length(); + } + if (isCharId(features, pos, nFeatEnd - pos)) + maSettings[mnNumSettings].value = getCharId(features, pos, nFeatEnd - pos); + else + maSettings[mnNumSettings].value= getIntValue(features, pos, nFeatEnd - pos); + if (isValid(font, maSettings[mnNumSettings])) + mnNumSettings++; + else + mbErrors = true; + } + pos = nFeatEnd + 1; + } +} + +void GrFeatureParser::setLang(gr::Font & font, const std::string & lang) +{ + gr::isocode aLang = {{0,0,0,0}}; + if (lang.length() > 2) + { + for (size_t i = 0; i < lang.length() && i < 3; i++) + { + if (lang[i] == '-') break; + aLang.rgch[i] = lang[i]; + } + std::pair<gr::LanguageIterator,gr::LanguageIterator> aSupported + = font.getSupportedLanguages(); + gr::LanguageIterator iL = aSupported.first; + while (iL != aSupported.second) + { + gr::isocode aSupportedLang = *iL; + if (aLang.rgch[0] == aSupportedLang.rgch[0] && + aLang.rgch[1] == aSupportedLang.rgch[1] && + aLang.rgch[2] == aSupportedLang.rgch[2] && + aLang.rgch[3] == aSupportedLang.rgch[3]) break; + ++iL; + } + if (iL != aSupported.second) + maLang = aLang; +#ifdef DEBUG + else + printf("%s has no features\n", aLang.rgch); +#endif + } +} + +GrFeatureParser::GrFeatureParser(const GrFeatureParser & aCopy) + : maLang(aCopy.maLang), mbErrors(aCopy.mbErrors) +{ + mnNumSettings = aCopy.getFontFeatures(maSettings); +} + +GrFeatureParser::~GrFeatureParser() +{ +} + +size_t GrFeatureParser::getFontFeatures(gr::FeatureSetting settings[64]) const +{ + if (settings) + { + std::copy(maSettings, maSettings + mnNumSettings, settings); + } + return mnNumSettings; +} + +bool GrFeatureParser::isValid(gr::Font & font, gr::FeatureSetting & setting) +{ + gr::FeatureIterator i = font.featureWithID(setting.id); + if (font.getFeatures().second == i) + { + return false; + } + std::pair< gr::FeatureSettingIterator, gr::FeatureSettingIterator > + validValues = font.getFeatureSettings(i); + gr::FeatureSettingIterator j = validValues.first; + while (j != validValues.second) + { + if (*j == setting.value) return true; + ++j; + } + return false; +} + +bool GrFeatureParser::isCharId(const std::string & id, size_t offset, size_t length) +{ + if (length > 4) return false; + for (size_t i = 0; i < length; i++) + { + if (i > 0 && id[offset+i] == '\0') continue; + if ((id[offset+i]) < 0x20 || (id[offset+i]) < 0) + return false; + if (i==0 && id[offset+i] < 0x41) + return false; + } + return true; +} + +int GrFeatureParser::getCharId(const std::string & id, size_t offset, size_t length) +{ + FeatId charId; + charId.num = 0; +#ifdef WORDS_BIGENDIAN + for (size_t i = 0; i < length; i++) + { + charId.label[i] = id[offset+i]; + } +#else + for (size_t i = 0; i < length; i++) + { + charId.label[3-i] = id[offset+i]; + } +#endif + return charId.num; +} + +int GrFeatureParser::getIntValue(const std::string & id, size_t offset, size_t length) +{ + int value = 0; + int sign = 1; + for (size_t i = 0; i < length; i++) + { + switch (id[offset + i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + value *= 10; + if (sign < 0) + { + value = -(id[offset + i] - '0'); + sign = 1; + } + value += (id[offset + i] - '0'); + break; + case '-': + if (i == 0) + sign = -1; + else + { + mbErrors = true; + break; + } + default: + mbErrors = true; + break; + } + } + return value; +} + + +sal_Int32 GrFeatureParser::hashCode() const +{ + union IsoHash { sal_Int32 mInt; gr::isocode mCode; }; + IsoHash isoHash; + isoHash.mCode = maLang; + sal_Int32 hash = isoHash.mInt; + for (size_t i = 0; i < mnNumSettings; i++) + { + hash = (hash << 16) ^ ((maSettings[i].id << 8) | maSettings[i].value); + } + return hash; +} diff --git a/vcl/source/glyphs/graphite_layout.cxx b/vcl/source/glyphs/graphite_layout.cxx new file mode 100644 index 000000000000..86dee2749efa --- /dev/null +++ b/vcl/source/glyphs/graphite_layout.cxx @@ -0,0 +1,1367 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: $ + * $Revision: $ + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// Description: An implementation of the SalLayout interface that uses the +// Graphite engine. + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Enable lots of debug info +#ifdef DEBUG +//#define GRLAYOUT_DEBUG 1 +//#undef NDEBUG +#endif + +// Header files +// +// Standard Library +#include <algorithm> +#include <cassert> +#include <functional> +#include <limits> +#include <numeric> +#include <deque> + +// Platform +#ifdef WNT +#include <tools/svwin.h> +#include <svsys.h> +#endif + +#include <vcl/salgdi.hxx> + +#include <unicode/uchar.h> +#include <unicode/ubidi.h> +#include <unicode/uscript.h> + +// Graphite Libraries (must be after vcl headers on windows) +#include <graphite/GrClient.h> +#include <graphite/Font.h> +#include <graphite/ITextSource.h> +#include <graphite/Segment.h> +#include <graphite/SegmentPainter.h> + +#include <vcl/graphite_layout.hxx> +#include <vcl/graphite_features.hxx> +#include "graphite_textsrc.hxx" + + +// Module private type definitions and forward declarations. +// +// Module private names. +// + +#ifdef GRLAYOUT_DEBUG +FILE * grLogFile = NULL; +FILE * grLog() +{ +#ifdef WNT + std::string logFileName(getenv("TEMP")); + logFileName.append("\\graphitelayout.log"); + if (grLogFile == NULL) grLogFile = fopen(logFileName.c_str(),"w"); + else fflush(grLogFile); + return grLogFile; +#else + return stdout; +#endif +} +#endif + +#ifdef GRCACHE +#include <vcl/graphite_cache.hxx> +#endif + + +namespace +{ + typedef std::pair<gr::GlyphIterator, gr::GlyphIterator> glyph_range_t; + typedef std::pair<gr::GlyphSetIterator, gr::GlyphSetIterator> glyph_set_range_t; + + inline long round(const float n) { + return long(n + (n < 0 ? -0.5 : 0.5)); + } + + + template<typename T> + inline bool in_range(const T i, const T b, const T e) { + return !(b > i) && i < e; + } + + + template<typename T> + inline bool is_subrange(const T sb, const T se, const T b, const T e) { + return !(b > sb || se > e); + } + + + template<typename T> + inline bool is_subrange(const std::pair<T, T> &s, const T b, const T e) { + return is_subrange(s.first, s.second, b, e); + } + + int findSameDirLimit(const xub_Unicode* buffer, int charCount, bool rtl) + { + UErrorCode status = U_ZERO_ERROR; + UBiDi *ubidi = ubidi_openSized(charCount, 0, &status); + int limit = 0; + ubidi_setPara(ubidi, reinterpret_cast<const UChar *>(buffer), charCount, + (rtl)?UBIDI_DEFAULT_RTL:UBIDI_DEFAULT_LTR, NULL, &status); + UBiDiLevel level = 0; + ubidi_getLogicalRun(ubidi, 0, &limit, &level); + ubidi_close(ubidi); + if ((rtl && !(level & 1)) || (!rtl && (level & 1))) + { + limit = 0; + } + return limit; + } + +} // namespace + + + +// Impementation of the GraphiteLayout::Glyphs container class. +// This is an extended vector class with methods added to enable +// o Correctly filling with glyphs. +// o Querying clustering relationships. +// o manipulations that affect neighouring glyphs. + +const int GraphiteLayout::EXTRA_CONTEXT_LENGTH = 10; +#ifdef GRCACHE +GraphiteCacheHandler GraphiteCacheHandler::instance; +#endif + +// The Graphite glyph stream is really a sequence of glyph attachment trees +// each rooted at a non-attached base glyph. fill_from walks the glyph stream +// find each non-attached base glyph and calls append to record them as a +// sequence of clusters. +void +GraphiteLayout::Glyphs::fill_from(gr::Segment & rSegment, ImplLayoutArgs &rArgs, + bool bRtl, long &rWidth, float fScaling, std::vector<int> & rChar2Base, std::vector<int> & rGlyph2Char, std::vector<int> & rCharDxs) +{ + // Create a glyph item for each of the glyph and append it to the base class glyph list. + typedef std::pair< gr::GlyphSetIterator, gr::GlyphSetIterator > GrGlyphSet; + int nChar = rArgs.mnEndCharPos - rArgs.mnMinCharPos; + glyph_range_t iGlyphs = rSegment.glyphs(); + int nGlyphs = iGlyphs.second - iGlyphs.first; + gr::GlyphIterator prevBase = iGlyphs.second; + float fMinX = rSegment.advanceWidth(); + float fMaxX = 0.0f; + rGlyph2Char.assign(nGlyphs, -1); + long nDxOffset = 0; + int nGlyphIndex = (bRtl)? (nGlyphs - 1) : 0; + // OOo always expects the glyphs in ltr order + int nDelta = (bRtl)? -1 : 1; + + int nLastGlyph = (bRtl)? nGlyphs - 1: 0; + int nNextChar = (bRtl)? (rSegment.stopCharacter() - 1) : rSegment.startCharacter();//rArgs.mnMinCharPos; + // current glyph number (Graphite glyphs) + //int currGlyph = 0; + int nFirstCharInCluster = nNextChar; + int nFirstGlyphInCluster = nLastGlyph; + + // ltr first char in cluster is lowest, same is true for rtl + // ltr first glyph in cluster is lowest, rtl first glyph is highest + + // loop over the glyphs determining which characters are linked to them + gr::GlyphIterator gi; + for (gi = iGlyphs.first + nGlyphIndex; + nGlyphIndex >= 0 && nGlyphIndex < nGlyphs; + nGlyphIndex+= nDelta, gi = iGlyphs.first + nGlyphIndex) + { + gr::GlyphInfo info = (*gi); +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Glyph %d %f,%f\n", (int)info.logicalIndex(), info.origin(), info.yOffset()); +#endif + // the last character associated with this glyph is after + // our current cluster buffer position + if ((bRtl && ((signed)info.firstChar() <= nNextChar)) || + (!bRtl && ((signed)info.lastChar() >= nNextChar))) + { + if ((bRtl && nGlyphIndex < nLastGlyph) || + (!bRtl && nGlyphIndex > nLastGlyph)) + { + // this glyph is after the previous one left->right + // if insertion is allowed before it then we are in a + // new cluster + int nAttachedBase = (*(info.attachedClusterBase())).logicalIndex(); + if (!info.isAttached() || + !in_range(nAttachedBase, nFirstGlyphInCluster, nGlyphIndex)) + { + if (in_range(nFirstCharInCluster, rArgs.mnMinCharPos, rArgs.mnEndCharPos) && + nFirstGlyphInCluster != nGlyphIndex) + { + std::pair <float,float> aBounds = + appendCluster(rSegment, rArgs, bRtl, nFirstCharInCluster, + nNextChar, nFirstGlyphInCluster, nGlyphIndex, fScaling, + rChar2Base, rGlyph2Char, rCharDxs, nDxOffset); + fMinX = std::min(aBounds.first, fMinX); + fMaxX = std::max(aBounds.second, fMaxX); + } + nFirstCharInCluster = (bRtl)? info.lastChar() : info.firstChar(); + nFirstGlyphInCluster = nGlyphIndex; + } + nLastGlyph = (bRtl)? std::min(nGlyphIndex, nAttachedBase) : + std::max(nGlyphIndex, nAttachedBase); + } + // loop over chacters associated with this glyph and characters + // between nextChar and the last character associated with this glyph + // giving them the current cluster id. This allows for character /glyph + // order reversal. + // For each character we do a reverse glyph id look up + // and store the glyph id with the highest logical index in nLastGlyph + while ((bRtl && ((signed)info.firstChar() <= nNextChar)) || + (!bRtl && (signed)info.lastChar() >= nNextChar)) + { + GrGlyphSet charGlyphs = rSegment.charToGlyphs(nNextChar); + nNextChar += nDelta; + gr::GlyphSetIterator gj = charGlyphs.first; + while (gj != charGlyphs.second) + { + nLastGlyph = (bRtl)? min(nLastGlyph, (signed)(*gj).logicalIndex()) : max(nLastGlyph, (signed)(*gj).logicalIndex()); + ++gj; + } + } + // Loop over attached glyphs and make sure they are all in the cluster since you + // can have glyphs attached with another base glyph in between + glyph_set_range_t iAttached = info.attachedClusterGlyphs(); + for (gr::GlyphSetIterator agi = iAttached.first; agi != iAttached.second; ++agi) + { + nLastGlyph = (bRtl)? min(nLastGlyph, (signed)(*agi).logicalIndex()) : max(nLastGlyph, (signed)(*agi).logicalIndex()); + } + + // if this is a rtl attached glyph, then we need to include its + // base in the cluster, which will have a lower graphite index + if (bRtl) + { + if ((signed)info.attachedClusterBase()->logicalIndex() < nLastGlyph) + { + nLastGlyph = info.attachedClusterBase()->logicalIndex(); + } + } + } + + // it is possible for the lastChar to be after nextChar and + // firstChar to be before the nFirstCharInCluster in rare + // circumstances e.g. Myanmar word for cemetery + if ((bRtl && ((signed)info.lastChar() > nFirstCharInCluster)) || + (!bRtl && ((signed)info.firstChar() < nFirstCharInCluster))) + { + nFirstCharInCluster = info.firstChar(); + } + } + // process last cluster + if (in_range(nFirstCharInCluster, rArgs.mnMinCharPos, rArgs.mnEndCharPos) && + nFirstGlyphInCluster != nGlyphIndex) + { + std::pair <float,float> aBounds = + appendCluster(rSegment, rArgs, bRtl, nFirstCharInCluster, nNextChar, + nFirstGlyphInCluster, nGlyphIndex, fScaling, + rChar2Base, rGlyph2Char, rCharDxs, nDxOffset); + fMinX = std::min(aBounds.first, fMinX); + fMaxX = std::max(aBounds.second, fMaxX); + } + long nXOffset = round(fMinX * fScaling); + rWidth = round(fMaxX * fScaling) - nXOffset + nDxOffset; + if (rWidth < 0) + { + // This can happen when there was no base inside the range + rWidth = 0; + } + // fill up non-base char dx with cluster widths from previous base glyph + if (bRtl) + { + if (rCharDxs[nChar-1] == -1) + rCharDxs[nChar-1] = 0; + else + rCharDxs[nChar-1] -= nXOffset; + for (int i = nChar - 2; i >= 0; i--) + { + if (rCharDxs[i] == -1) rCharDxs[i] = rCharDxs[i+1]; + else rCharDxs[i] -= nXOffset; + } + } + else + { + if (rCharDxs[0] == -1) + rCharDxs[0] = 0; + else + rCharDxs[0] -= nXOffset; + for (int i = 1; i < nChar; i++) + { + if (rCharDxs[i] == -1) rCharDxs[i] = rCharDxs[i-1]; + else rCharDxs[i] -= nXOffset; + } + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Glyphs xOff%ld dropDx%ld w%ld\n", nXOffset, nDxOffset, rWidth); +#endif + // remove offset due to context if there is one + if (nXOffset != 0) + { + for (size_t i = 0; i < size(); i++) + (*this)[i].maLinearPos.X() -= nXOffset; + } +} + +std::pair<float,float> GraphiteLayout::Glyphs::appendCluster(gr::Segment & rSeg, + ImplLayoutArgs & rArgs, bool bRtl, int nFirstCharInCluster, int nNextChar, + int nFirstGlyphInCluster, int nNextGlyph, float fScaling, + std::vector<int> & rChar2Base, std::vector<int> & rGlyph2Char, + std::vector<int> & rCharDxs, long & rDXOffset) +{ + glyph_range_t iGlyphs = rSeg.glyphs(); + int nGlyphs = iGlyphs.second - iGlyphs.first; + int nDelta = (bRtl)? -1 : 1; + gr::GlyphInfo aFirstGlyph = *(iGlyphs.first + nFirstGlyphInCluster); + std::pair <float, float> aBounds; + aBounds.first = aFirstGlyph.origin(); + aBounds.second = aFirstGlyph.origin(); + // before we add the glyphs to this vector, we record the + // glyph's index in the vector (which is not the same as + // the Segment's glyph index!) + assert(size() < rGlyph2Char.size()); + rChar2Base[nFirstCharInCluster-rArgs.mnMinCharPos] = size(); + rGlyph2Char[size()] = nFirstCharInCluster; + bool bBaseGlyph = true; + for (int j = nFirstGlyphInCluster; + j != nNextGlyph; j += nDelta) + { + long nNextOrigin; + float fNextOrigin; + gr::GlyphInfo aGlyph = *(iGlyphs.first + j); + if (j + nDelta >= nGlyphs || j + nDelta < 0) // at rhs ltr,rtl + { + fNextOrigin = rSeg.advanceWidth(); + nNextOrigin = round(rSeg.advanceWidth() * fScaling + rDXOffset); + aBounds.second = std::max(rSeg.advanceWidth(), aBounds.second); + } + else + { + gr::GlyphInfo aNextGlyph = *(iGlyphs.first + j + nDelta); + fNextOrigin = std::max(aNextGlyph.attachedClusterBase()->origin(), aNextGlyph.origin()); + aBounds.second = std::max(fNextOrigin, aBounds.second); + nNextOrigin = round(fNextOrigin * fScaling + rDXOffset); + } + aBounds.first = std::min(aGlyph.origin(), aBounds.first); + if ((signed)aGlyph.firstChar() < rArgs.mnEndCharPos && + (signed)aGlyph.firstChar() >= rArgs.mnMinCharPos) + { + rCharDxs[aGlyph.firstChar()-rArgs.mnMinCharPos] = nNextOrigin; + } + if ((signed)aGlyph.attachedClusterBase()->logicalIndex() == j) + { + append(rSeg, rArgs, aGlyph, fNextOrigin, fScaling, rChar2Base, rGlyph2Char, rCharDxs, rDXOffset, bBaseGlyph); + bBaseGlyph = false; + } + } + // from the point of view of the dx array, the xpos is + // the origin of the first glyph of the next cluster ltr + // rtl it is the origin of the 1st glyph of the cluster + long nXPos = (bRtl)? + round(aFirstGlyph.attachedClusterBase()->origin() * fScaling) + rDXOffset : + round(aBounds.second * fScaling) + rDXOffset; + // force the last char in range to have the width of the cluster + if (bRtl) + { + for (int n = nNextChar + 1; n <= nFirstCharInCluster; n++) + { + if ((n < rArgs.mnEndCharPos) && (n >= rArgs.mnMinCharPos)) + rCharDxs[n-rArgs.mnMinCharPos] = nXPos; + } + } + else + { + for (int n = nNextChar - 1; n >= nFirstCharInCluster; n--) + { + if (n < rArgs.mnEndCharPos && n >= rArgs.mnMinCharPos) + rCharDxs[n-rArgs.mnMinCharPos] = nXPos; + } + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Cluster g[%d-%d) c[%d-%d)%x x%ld y%f\n", nFirstGlyphInCluster, nNextGlyph, nFirstCharInCluster, nNextChar, rArgs.mpStr[nFirstCharInCluster], nXPos, aFirstGlyph.yOffset()); +#endif + return aBounds; +} + +// append walks an attachment tree, flattening it, and converting it into a +// sequence of GlyphItem objects which we can later manipulate. +void +GraphiteLayout::Glyphs::append(gr::Segment &segment, ImplLayoutArgs &args, gr::GlyphInfo & gi, float nextGlyphOrigin, float scaling, std::vector<int> & rChar2Base, std::vector<int> & rGlyph2Char, std::vector<int> & rCharDxs, long & rDXOffset, bool bIsBase) +{ + float nextOrigin = nextGlyphOrigin; + int firstChar = std::min(gi.firstChar(), gi.lastChar()); + assert(size() < rGlyph2Char.size()); + if (!bIsBase) rGlyph2Char[size()] = firstChar; + // is the next glyph attached or in the next cluster? + glyph_set_range_t iAttached = gi.attachedClusterGlyphs(); + if (iAttached.first != iAttached.second) + { + nextOrigin = iAttached.first->origin(); + } + long glyphId = gi.glyphID(); + long deltaOffset = 0; + int glyphWidth = round(nextOrigin * scaling) - round(gi.origin() * scaling); +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"c%d g%d gWidth%d x%f ", firstChar, (int)gi.logicalIndex(), glyphWidth, nextOrigin); +#endif + if (glyphId == 0) + { + args.NeedFallback( + firstChar, + gr::RightToLeftDir(gr::DirCode(gi.directionality()))); + if( (SAL_LAYOUT_FOR_FALLBACK & args.mnFlags )) + { + glyphId = GF_DROPPED; + deltaOffset -= glyphWidth; + glyphWidth = 0; + } + } + else if(args.mnFlags & SAL_LAYOUT_FOR_FALLBACK) + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"fallback c%d %x in run %d\n", firstChar, args.mpStr[firstChar], + args.maRuns.PosIsInAnyRun(firstChar)); +#endif + // glyphs that aren't requested for fallback will be taken from base + // layout, so mark them as dropped (should this wait until Simplify(false) is called?) + if (!args.maRuns.PosIsInAnyRun(firstChar) && + in_range(firstChar, args.mnMinCharPos, args.mnEndCharPos)) + { + glyphId = GF_DROPPED; + deltaOffset -= glyphWidth; + glyphWidth = 0; + } + } + // append this glyph. + long nGlyphFlags = bIsBase ? 0 : GlyphItem::IS_IN_CLUSTER; + // directionality seems to be unreliable + //nGlyphFlags |= gr::RightToLeftDir(gr::DirCode(gi.attachedClusterBase()->directionality())) ? GlyphItem::IS_RTL_GLYPH : 0; + nGlyphFlags |= (gi.directionLevel() & 0x1)? GlyphItem::IS_RTL_GLYPH : 0; + GlyphItem aGlyphItem(size(),//gi.logicalIndex(), + glyphId, + Point(round(gi.origin() * scaling + rDXOffset), + round((-gi.yOffset() * scaling) - segment.AscentOffset()* scaling)), + nGlyphFlags, + glyphWidth); + aGlyphItem.mnOrigWidth = round(gi.advanceWidth() * scaling); + push_back(aGlyphItem); + + // update the offset if this glyph was dropped + rDXOffset += deltaOffset; + + // Recursively apply append all the attached glyphs. + for (gr::GlyphSetIterator agi = iAttached.first; agi != iAttached.second; ++agi) + { + if (agi + 1 == iAttached.second) + append(segment, args, *agi, nextGlyphOrigin, scaling, rChar2Base, rGlyph2Char,rCharDxs, rDXOffset, false); + else + append(segment, args, *agi, (agi + 1)->origin(), scaling, rChar2Base, rGlyph2Char, rCharDxs, rDXOffset, false); + } +} + +// +// An implementation of the SalLayout interface to enable Graphite enabled fonts to be used. +// +GraphiteLayout::GraphiteLayout(const gr::Font & font, const grutils::GrFeatureParser * pFeatures) throw() + : mpTextSrc(0), + mrFont(font), + mnWidth(0), + mfScaling(1.0), + mpFeatures(pFeatures) +{ + // Line settings can have subtle affects on space handling + // since we don't really know whether it is the end of a line or just a run + // in the middle, it is hard to know what to set them to. + // If true, it can cause end of line spaces to be hidden e.g. Doulos SIL + maLayout.setStartOfLine(false); + maLayout.setEndOfLine(false); +// maLayout.setDumbFallback(false); + // trailing ws doesn't seem to always take affect if end of line is true + maLayout.setTrailingWs(gr::ktwshAll); +#ifdef GRLAYOUT_DEBUG + gr::ScriptDirCode aDirCode = font.getSupportedScriptDirections(); + fprintf(grLog(),"GraphiteLayout scripts %x %lx\n", aDirCode, long(this)); +#endif +} + + +GraphiteLayout::~GraphiteLayout() throw() +{ + clear(); + // the features are owned by the platform layers + mpFeatures = NULL; +} + +void GraphiteLayout::clear() +{ + // Destroy the segment and text source from any previous invocation of + // LayoutText + mvGlyphs.clear(); + mvCharDxs.clear(); + mvChar2BaseGlyph.clear(); + mvGlyph2Char.clear(); + +#ifndef GRCACHE + delete mpTextSrc; +#endif + + // Reset the state to the empty state. + mpTextSrc=0; + mnWidth = 0; + // Don't reset the scaling, because it is set before LayoutText +} + +// This method shouldn't be called on windows, since it needs the dc reset +bool GraphiteLayout::LayoutText(ImplLayoutArgs & rArgs) +{ +#ifdef GRCACHE + GrSegRecord * pSegRecord = NULL; + gr::Segment * pSegment = CreateSegment(rArgs, &pSegRecord); + if (!pSegment) + return false; + + // layout the glyphs as required by OpenOffice + bool success = LayoutGlyphs(rArgs, pSegment, pSegRecord); + + if (pSegRecord) pSegRecord->unlock(); + else delete pSegment; +#else + gr::Segment * pSegment = CreateSegment(rArgs); + bool success = LayoutGlyphs(rArgs, pSegment); + delete pSegment; +#endif + return success; +} + +#ifdef GRCACHE +class GrFontHasher : public gr::Font +{ +public: + GrFontHasher(const gr::Font & aFont) : gr::Font(aFont), mrRealFont(const_cast<gr::Font&>(aFont)) {}; + ~GrFontHasher(){}; + virtual bool bold() { return mrRealFont.bold(); }; + virtual bool italic() { return mrRealFont.italic(); }; + virtual float ascent() { return mrRealFont.ascent(); }; + virtual float descent() { return mrRealFont.descent(); }; + virtual float height() { return mrRealFont.height(); }; + virtual gr::Font* copyThis() { return mrRealFont.copyThis(); }; + virtual unsigned int getDPIx() { return mrRealFont.getDPIx(); }; + virtual unsigned int getDPIy() { return mrRealFont.getDPIy(); }; + virtual const void* getTable(gr::fontTableId32 nId, size_t* nSize) + { return mrRealFont.getTable(nId,nSize); } + virtual void getFontMetrics(float*pA, float*pB, float*pC) { mrRealFont.getFontMetrics(pA,pB,pC); }; + + sal_Int32 hashCode(const grutils::GrFeatureParser * mpFeatures) + { + // is this sufficient? + std::wstring aFace; + bool bBold; + bool bItalic; + UniqueCacheInfo(aFace, bBold, bItalic); + sal_Unicode uName[32]; // max length used in gr::Font + // Note: graphite stores font names as UTF-16 even if wchar_t is 32bit + // this conversion should be OK. + for (size_t i = 0; i < aFace.size() && i < 32; i++) + { + uName[i] = aFace[i]; + } + size_t iSize = aFace.size(); + if (0 == iSize) return 0; + sal_Int32 hash = rtl_ustr_hashCode_WithLength(uName, iSize); + hash ^= static_cast<sal_Int32>(height()); + hash |= (bBold)? 0x1000000 : 0; + hash |= (bItalic)? 0x2000000 : 0; + if (mpFeatures) + hash ^= mpFeatures->hashCode(); +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(), "font hash %x size %f\n", (int)hash, height()); +#endif + return hash; + }; + +private: + gr::Font & mrRealFont; +}; +#endif + +#ifdef GRCACHE +gr::Segment * GraphiteLayout::CreateSegment(ImplLayoutArgs& rArgs, GrSegRecord ** pSegRecord) +#else +gr::Segment * GraphiteLayout::CreateSegment(ImplLayoutArgs& rArgs) +#endif +{ + assert(rArgs.mnLength >= 0); + + gr::Segment * pSegment = NULL; + + // Set the SalLayouts values to be the inital ones. + SalLayout::AdjustLayout(rArgs); + // TODO check if this is needed + if (mnUnitsPerPixel > 1) + mfScaling = 1.0f / mnUnitsPerPixel; + + // Clear out any previous buffers + clear(); + bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL; + try + { + // Don't set RTL if font doesn't support it otherwise it forces rtl on + // everything + if (bRtl && (mrFont.getSupportedScriptDirections() & gr::kfsdcHorizRtl)) + maLayout.setRightToLeft(bRtl); + +#ifdef GRCACHE + GrFontHasher hasher(mrFont); + sal_Int32 aFontHash = hasher.hashCode(mpFeatures); + GraphiteSegmentCache * pCache = + (GraphiteCacheHandler::instance).getCache(aFontHash); + if (pCache) + { + *pSegRecord = pCache->getSegment(rArgs, bRtl); + if (*pSegRecord) + { + pSegment = (*pSegRecord)->getSegment(); + mpTextSrc = (*pSegRecord)->getTextSrc(); + maLayout.setRightToLeft((*pSegRecord)->isRtl()); + if (rArgs.mpStr != mpTextSrc->getLayoutArgs().mpStr || + rArgs.mnMinCharPos != mpTextSrc->getLayoutArgs().mnMinCharPos || + rArgs.mnEndCharPos != mpTextSrc->getLayoutArgs().mnEndCharPos || + (SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags) ) + { + (*pSegRecord)->clearVectors(); + } + mpTextSrc->switchLayoutArgs(rArgs); + return pSegment; + } + } +#endif + + // Context is often needed beyond the specified end, however, we don't + // want it if there has been a direction change, since it is hard + // to tell between reordering within one direction and multi-directional + // text. + const int segCharLimit = min(rArgs.mnLength, mnEndCharPos + EXTRA_CONTEXT_LENGTH); + int limit = rArgs.mnEndCharPos; + if (segCharLimit > limit) + { + limit += findSameDirLimit(rArgs.mpStr + rArgs.mnEndCharPos, + segCharLimit - rArgs.mnEndCharPos, bRtl); + } + + // Create a new TextSource object for the engine. + mpTextSrc = new TextSourceAdaptor(rArgs, limit); + if (mpFeatures) mpTextSrc->setFeatures(mpFeatures); + + pSegment = new gr::RangeSegment((gr::Font *)&mrFont, mpTextSrc, &maLayout, mnMinCharPos, limit); + if (pSegment != NULL) + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Gr::LayoutText %d-%d, context %d,len%d rtl%d/%d scaling %f\n", rArgs.mnMinCharPos, + rArgs.mnEndCharPos, limit, rArgs.mnLength, maLayout.rightToLeft(), pSegment->rightToLeft(), mfScaling); +#endif +#ifdef GRCACHE + // on a new segment rightToLeft should be correct + *pSegRecord = pCache->cacheSegment(mpTextSrc, pSegment, pSegment->rightToLeft()); +#endif + } + else + { + clear(); + return NULL; + } + } + catch (...) + { + clear(); // destroy the text source and any partially built segments. + return NULL; + } + return pSegment; +} + +#ifdef GRCACHE +bool GraphiteLayout::LayoutGlyphs(ImplLayoutArgs& rArgs, gr::Segment * pSegment, GrSegRecord * pSegRecord) +#else +bool GraphiteLayout::LayoutGlyphs(ImplLayoutArgs& rArgs, gr::Segment * pSegment) +#endif +{ +#ifdef GRCACHE +#ifdef GRCACHE_REUSE_VECTORS + // if we have an exact match, then we can reuse the glyph vectors from before + if (pSegRecord && (pSegRecord->glyphs().size() > 0) && + !(SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags) ) + { + mnWidth = pSegRecord->width(); + mvGlyphs = pSegRecord->glyphs(); + mvCharDxs = pSegRecord->charDxs(); + mvChar2BaseGlyph = pSegRecord->char2BaseGlyph(); + mvGlyph2Char = pSegRecord->glyph2Char(); + return true; + } +#endif +#endif + // Calculate the initial character dxs. + mvCharDxs.assign(mnEndCharPos - mnMinCharPos, -1); + mvChar2BaseGlyph.assign(mnEndCharPos - mnMinCharPos, -1); + mnWidth = 0; + if (mvCharDxs.size() > 0) + { + // Discover all the clusters. + try + { + // Note: we use the layout rightToLeft() because in cached segments + // rightToLeft() may no longer be valid if the engine has been run + // ltr since the segment was created. +#ifdef GRCACHE + bool bRtl = pSegRecord? pSegRecord->isRtl() : pSegment->rightToLeft(); +#else + bool bRtl = pSegment->rightToLeft(); +#endif + mvGlyphs.fill_from(*pSegment, rArgs, bRtl, + mnWidth, mfScaling, mvChar2BaseGlyph, mvGlyph2Char, mvCharDxs); + + if (bRtl) + { + // not needed for adjacent differences, but for mouse clicks to char + std::transform(mvCharDxs.begin(), mvCharDxs.end(), mvCharDxs.begin(), + std::bind1st(std::minus<long>(), mnWidth)); + // fixup last dx to ensure it always equals the width + mvCharDxs[mvCharDxs.size() - 1] = mnWidth; + } +#ifdef GRCACHE +#ifdef GRCACHE_REUSE_VECTORS + if (pSegRecord && rArgs.maReruns.IsEmpty() && + !(SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags)) + { + pSegRecord->setGlyphVectors(mnWidth, mvGlyphs, mvCharDxs, + mvChar2BaseGlyph, mvGlyph2Char); + } +#endif +#endif + } + catch (std::exception e) + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"LayoutGlyphs failed %s\n", e.what()); +#endif + return false; + } + catch (...) + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"LayoutGlyphs failed with exception"); +#endif + return false; + } + } + else + { + mnWidth = 0; + } + return true; +} + +int GraphiteLayout::GetTextBreak(long maxmnWidth, long char_extra, int factor) const +{ + // Adjust maxmnWidth so FindNextBreakPoint returns a sensible answer. + maxmnWidth -= (mnEndCharPos-mnMinCharPos-1)*char_extra; // extra character spacing. + maxmnWidth /= factor; // scaling factor. + + // Ask the segment for the nearest whole letter break for the width. + //float width; + float targetWidth = maxmnWidth/mfScaling; + // return quickly if this segment is narrower than the target width + // (sometimes graphite doesn't seem to realise this!) + if (targetWidth > mnWidth) + return STRING_LEN; + //int nBreak = mpSegment->findNextBreakPoint(mnMinCharPos, + // gr::klbWordBreak, gr::klbLetterBreak, targetWidth, &width); + + // LineFillSegment seems to give better results that findNextBreakPoint + // though it may be slower + gr::LayoutEnvironment aLE; + gr::LineFillSegment lineSeg(const_cast<gr::Font *>(&mrFont), mpTextSrc, &aLE, + mnMinCharPos, mpTextSrc->getContextLength(), + targetWidth); + int nBreak = lineSeg.stopCharacter(); + + if (nBreak > mnEndCharPos) nBreak = STRING_LEN; + else if (nBreak < mnMinCharPos) nBreak = mnMinCharPos; + return nBreak; +} + + +long GraphiteLayout::FillDXArray( sal_Int32* pDXArray ) const +{ + if (mnEndCharPos == mnMinCharPos) + // Then we must be zero width! + return 0; + + if (pDXArray) + { + for (size_t i = 0; i < mvCharDxs.size(); i++) + { + assert((mvChar2BaseGlyph[i] >= -1) && (mvChar2BaseGlyph[i] < (signed)mvGlyphs.size())); + if (mvChar2BaseGlyph[i] != -1 && + mvGlyphs[mvChar2BaseGlyph[i]].mnGlyphIndex == GF_DROPPED) + { + // when used in MultiSalLayout::GetTextBreak dropped glyphs + // must have zero width + pDXArray[i] = 0; + } + else + { + pDXArray[i] = mvCharDxs[i]; + if (i > 0) pDXArray[i] -= mvCharDxs[i-1]; + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"%d,%d,%ld ", (int)i, (int)mvCharDxs[i], pDXArray[i]); +#endif + } + //std::adjacent_difference(mvCharDxs.begin(), mvCharDxs.end(), pDXArray); + //for (size_t i = 0; i < mvCharDxs.size(); i++) + // fprintf(grLog(),"%d,%d,%d ", (int)i, (int)mvCharDxs[i], pDXArray[i]); + //fprintf(grLog(),"FillDX %ld,%d\n", mnWidth, std::accumulate(pDXArray, pDXArray + mvCharDxs.size(), 0)); + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"FillDXArray %d-%d,%d=%ld\n", mnMinCharPos, mnEndCharPos, (int)mpTextSrc->getLength(), mnWidth); +#endif + return mnWidth; +} + + +void GraphiteLayout::AdjustLayout(ImplLayoutArgs& rArgs) +{ + SalLayout::AdjustLayout(rArgs); + + if(rArgs.mpDXArray) + { + std::vector<int> vDeltaWidths(mvGlyphs.size(), 0); + ApplyDXArray(rArgs, vDeltaWidths); + + if( (mnLayoutFlags & SAL_LAYOUT_BIDI_RTL) && + !(rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK) ) + { + // check if this is a kashida script + bool bKashidaScript = false; + for (int i = rArgs.mnMinCharPos; i < rArgs.mnEndCharPos; i++) + { + UErrorCode aStatus = U_ZERO_ERROR; + UScriptCode scriptCode = uscript_getScript(rArgs.mpStr[i], &aStatus); + if (scriptCode == USCRIPT_ARABIC || scriptCode == USCRIPT_SYRIAC) + { + bKashidaScript = true; + break; + } + } + int nKashidaWidth = 0; + int nKashidaIndex = getKashidaGlyph(nKashidaWidth); + if( nKashidaIndex != 0 && bKashidaScript) + { + kashidaJustify( vDeltaWidths, nKashidaIndex, nKashidaWidth ); + } + } + } +} + + +void GraphiteLayout::ApplyDXArray(ImplLayoutArgs &args, std::vector<int> & rDeltaWidth) +{ + const size_t nChars = args.mnEndCharPos - args.mnMinCharPos; + if (nChars == 0) return; + +#ifdef GRLAYOUT_DEBUG + for (size_t iDx = 0; iDx < mvCharDxs.size(); iDx++) + fprintf(grLog(),"%d,%d,%ld ", (int)iDx, (int)mvCharDxs[iDx], args.mpDXArray[iDx]); + fprintf(grLog(),"ApplyDx\n"); +#endif + bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL; + int nXOffset = 0; + if (bRtl) + { + nXOffset = args.mpDXArray[nChars - 1] - mvCharDxs[nChars - 1]; + } + int nPrevClusterGlyph = (bRtl)? mvGlyphs.size() : -1; + int nPrevClusterLastChar = -1; + for (size_t i = 0; i < nChars; i++) + { + if (mvChar2BaseGlyph[i] > -1 && mvChar2BaseGlyph[i] != nPrevClusterGlyph) + { + assert((mvChar2BaseGlyph[i] > -1) && (mvChar2BaseGlyph[i] < (signed)mvGlyphs.size())); + GlyphItem & gi = mvGlyphs[mvChar2BaseGlyph[i]]; + if (!gi.IsClusterStart()) + continue; + + // find last glyph of this cluster + size_t j = i + 1; + int nLastChar = i; + int nLastGlyph = mvChar2BaseGlyph[i]; + for (; j < nChars; j++) + { + assert((mvChar2BaseGlyph[j] >= -1) && (mvChar2BaseGlyph[j] < (signed)mvGlyphs.size())); + if (mvChar2BaseGlyph[j] != -1 && mvGlyphs[mvChar2BaseGlyph[j]].IsClusterStart()) + { + nLastGlyph = mvChar2BaseGlyph[j] + ((bRtl)? 1 : -1); + nLastChar = j - 1; + break; + } + } + if (nLastGlyph < 0) + { + nLastGlyph = mvChar2BaseGlyph[i]; + } + // Its harder to find the last glyph rtl, since the first of + // cluster is still on the left so we need to search towards + // the previous cluster to the right + if (bRtl) + { + nLastGlyph = mvChar2BaseGlyph[i]; + while (nLastGlyph + 1 < (signed)mvGlyphs.size() && + !mvGlyphs[nLastGlyph+1].IsClusterStart()) + { + ++nLastGlyph; + } + } + if (j == nChars) + { + nLastChar = nChars - 1; + if (!bRtl) nLastGlyph = mvGlyphs.size() - 1; + } + assert((nLastChar > -1) && (nLastChar < (signed)nChars)); + long nNewClusterWidth = args.mpDXArray[nLastChar]; + long nOrigClusterWidth = mvCharDxs[nLastChar]; + long nDGlyphOrigin = 0; + if (nPrevClusterLastChar > - 1) + { + assert(nPrevClusterLastChar < (signed)nChars); + nNewClusterWidth -= args.mpDXArray[nPrevClusterLastChar]; + nOrigClusterWidth -= mvCharDxs[nPrevClusterLastChar]; + nDGlyphOrigin = args.mpDXArray[nPrevClusterLastChar] - mvCharDxs[nPrevClusterLastChar]; + } + long nDWidth = nNewClusterWidth - nOrigClusterWidth; +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(), "c%d last glyph %d/%d\n", i, nLastGlyph, mvGlyphs.size()); +#endif + assert((nLastGlyph > -1) && (nLastGlyph < (signed)mvGlyphs.size())); + mvGlyphs[nLastGlyph].mnNewWidth += nDWidth; + if (gi.mnGlyphIndex != GF_DROPPED) + mvGlyphs[nLastGlyph].mnNewWidth += nDWidth; + else + nDGlyphOrigin += nDWidth; + // update glyph positions + if (bRtl) + { + for (int n = mvChar2BaseGlyph[i]; n <= nLastGlyph; n++) + { + assert((n > - 1) && (n < (signed)mvGlyphs.size())); + mvGlyphs[n].maLinearPos.X() += -nDGlyphOrigin + nXOffset; + } + } + else + { + for (int n = mvChar2BaseGlyph[i]; n <= nLastGlyph; n++) + { + assert((n > - 1) && (n < (signed)mvGlyphs.size())); + mvGlyphs[n].maLinearPos.X() += nDGlyphOrigin + nXOffset; + } + } + rDeltaWidth[mvChar2BaseGlyph[i]] = nDWidth; +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"c%d g%d-%d dW%ld-%ld=%ld dX%ld x%ld\t", (int)i, mvChar2BaseGlyph[i], nLastGlyph, nNewClusterWidth, nOrigClusterWidth, nDWidth, nDGlyphOrigin, mvGlyphs[mvChar2BaseGlyph[i]].maLinearPos.X()); +#endif + nPrevClusterGlyph = mvChar2BaseGlyph[i]; + nPrevClusterLastChar = nLastChar; + i = nLastChar; + } + } + // Update the dx vector with the new values. + std::copy(args.mpDXArray, args.mpDXArray + nChars, + mvCharDxs.begin() + (args.mnMinCharPos - mnMinCharPos)); +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"ApplyDx %ld(%ld)\n", args.mpDXArray[nChars - 1], mnWidth); +#endif + mnWidth = args.mpDXArray[nChars - 1]; +} + +void GraphiteLayout::kashidaJustify(std::vector<int>& rDeltaWidths, sal_GlyphId nKashidaIndex, int nKashidaWidth) +{ + // skip if the kashida glyph in the font looks suspicious + if( nKashidaWidth <= 0 ) + return; + + // calculate max number of needed kashidas + Glyphs::iterator i = mvGlyphs.begin(); + int nKashidaCount = 0; + int nOrigGlyphIndex = -1; + int nGlyphIndex = -1; + while (i != mvGlyphs.end()) + { + nOrigGlyphIndex++; + nGlyphIndex++; + // only inject kashidas in RTL contexts + if( !(*i).IsRTLGlyph() ) + { + ++i; + continue; + } + // no kashida-injection for blank justified expansion either + if( IsSpacingGlyph( (*i).mnGlyphIndex ) ) + { + ++i; + continue; + } + // calculate gap, ignore if too small + int nGapWidth = rDeltaWidths[nOrigGlyphIndex];; + // worst case is one kashida even for mini-gaps + if( 3 * nGapWidth < nKashidaWidth ) + { + ++i; + continue; + } + nKashidaCount = 1 + (nGapWidth / nKashidaWidth); +#ifdef GRLAYOUT_DEBUG + printf("inserting %d kashidas at %ld\n", nKashidaCount, (*i).mnGlyphIndex); +#endif + GlyphItem glyphItem = *i; + Point aPos(0, 0); + aPos.X() = (*i).maLinearPos.X(); + GlyphItem newGi(glyphItem.mnCharPos, nKashidaIndex, aPos, + GlyphItem::IS_IN_CLUSTER|GlyphItem::IS_RTL_GLYPH, nKashidaWidth); + mvGlyphs.reserve(mvGlyphs.size() + nKashidaCount); + i = mvGlyphs.begin() + nGlyphIndex; + mvGlyphs.insert(i, nKashidaCount, newGi); + i = mvGlyphs.begin() + nGlyphIndex; + nGlyphIndex += nKashidaCount; + // now fix up the kashida positions + for (int j = 0; j < nKashidaCount; j++) + { + (*(i)).maLinearPos.X() -= nGapWidth; + nGapWidth -= nKashidaWidth; + i++; + } + + // fixup rightmost kashida for gap remainder + if( nGapWidth < 0 ) + { + if( nKashidaCount <= 1 ) + nGapWidth /= 2; // for small gap move kashida to middle + (*(i-1)).mnNewWidth += nGapWidth; // adjust kashida width to gap width + (*(i-1)).maLinearPos.X() += nGapWidth; + } + + (*i).mnNewWidth = (*i).mnOrigWidth; + ++i; + } + +} + +void GraphiteLayout::GetCaretPositions( int nArraySize, sal_Int32* pCaretXArray ) const +{ + // For each character except the last discover the caret positions + // immediatly before and after that character. + // This is used for underlines in the GUI amongst other things. + // It may be used from MultiSalLayout, in which case it must take into account + // glyphs that have been moved. + std::fill(pCaretXArray, pCaretXArray + nArraySize, -1); + // the layout method doesn't modify the layout even though it isn't + // const in the interface + bool bRtl = const_cast<GraphiteLayout*>(this)->maLayout.rightToLeft(); + int prevBase = -1; + long prevClusterWidth = 0; + for (int i = 0, nCharSlot = 0; i < nArraySize && nCharSlot < static_cast<int>(mvCharDxs.size()); ++nCharSlot, i+=2) + { + if (mvChar2BaseGlyph[nCharSlot] != -1) + { + assert((mvChar2BaseGlyph[nCharSlot] > -1) && (mvChar2BaseGlyph[nCharSlot] < (signed)mvGlyphs.size())); + GlyphItem gi = mvGlyphs[mvChar2BaseGlyph[nCharSlot]]; + if (gi.mnGlyphIndex == GF_DROPPED) + { + continue; + } + int nCluster = mvChar2BaseGlyph[nCharSlot]; + long origClusterWidth = gi.mnNewWidth; + long nMin = gi.maLinearPos.X(); + long nMax = gi.maLinearPos.X() + gi.mnNewWidth; + // attached glyphs are always stored after their base rtl or ltr + while (++nCluster < static_cast<int>(mvGlyphs.size()) && + !mvGlyphs[nCluster].IsClusterStart()) + { + origClusterWidth += mvGlyphs[nCluster].mnNewWidth; + if (mvGlyph2Char[nCluster] == nCharSlot) + { + nMin = std::min(nMin, mvGlyphs[nCluster].maLinearPos.X()); + nMax = std::min(nMax, mvGlyphs[nCluster].maLinearPos.X() + mvGlyphs[nCluster].mnNewWidth); + } + } + if (bRtl) + { + pCaretXArray[i+1] = nMin; + pCaretXArray[i] = nMax; + } + else + { + pCaretXArray[i] = nMin; + pCaretXArray[i+1] = nMax; + } + prevBase = mvChar2BaseGlyph[nCharSlot]; + prevClusterWidth = origClusterWidth; + } + else if (prevBase > -1) + { + // this could probably be improved + assert((prevBase > -1) && (prevBase < (signed)mvGlyphs.size())); + GlyphItem gi = mvGlyphs[prevBase]; + int nGlyph = prevBase + 1; + // try to find a better match, otherwise default to complete cluster + for (; nGlyph < static_cast<int>(mvGlyphs.size()) && + !mvGlyphs[nGlyph].IsClusterStart(); nGlyph++) + { + if (mvGlyph2Char[nGlyph] == nCharSlot) + { + gi = mvGlyphs[nGlyph]; + break; + } + } + long nGWidth = gi.mnNewWidth; + // if no match position at end of cluster + if (nGlyph == static_cast<int>(mvGlyphs.size()) || + mvGlyphs[nGlyph].IsClusterStart()) + { + nGWidth = prevClusterWidth; + if (bRtl) + { + pCaretXArray[i+1] = gi.maLinearPos.X(); + pCaretXArray[i] = gi.maLinearPos.X(); + } + else + { + pCaretXArray[i] = gi.maLinearPos.X() + prevClusterWidth; + pCaretXArray[i+1] = gi.maLinearPos.X() + prevClusterWidth; + } + } + else + { + if (bRtl) + { + pCaretXArray[i+1] = gi.maLinearPos.X(); + pCaretXArray[i] = gi.maLinearPos.X() + gi.mnNewWidth; + } + else + { + pCaretXArray[i] = gi.maLinearPos.X(); + pCaretXArray[i+1] = gi.maLinearPos.X() + gi.mnNewWidth; + } + } + } + else + { + pCaretXArray[i] = pCaretXArray[i+1] = 0; + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"%d,%ld-%ld\t", nCharSlot, pCaretXArray[i], pCaretXArray[i+1]); +#endif + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"\n"); +#endif +} + + +// GetNextGlyphs returns a contiguous sequence of glyphs that can be +// rendered together. It should never return a dropped glyph. +// The glyph_slot returned should be the index of the next visible +// glyph after the last glyph returned by this call. +// The char_index array should be filled with the characters corresponding +// to each glyph returned. +// glyph_adv array should be a virtual width such that if successive +// glyphs returned by this method are added one after the other they +// have the correct spacing. +// The logic in this method must match that expected in MultiSalLayout which +// is used when glyph fallback is in operation. +int GraphiteLayout::GetNextGlyphs( int length, sal_GlyphId * glyph_out, + ::Point & aPosOut, int &glyph_slot, sal_Int32 * glyph_adv, int *char_index) const +{ + // Sanity check on the slot index. + if (glyph_slot >= signed(mvGlyphs.size())) + { + glyph_slot = mvGlyphs.size(); + return 0; + } + assert(glyph_slot >= 0); + // Find the first glyph in the substring. + for (; glyph_slot < signed(mvGlyphs.size()) && + ((mvGlyphs.begin() + glyph_slot)->mnGlyphIndex == GF_DROPPED); + ++glyph_slot) {}; + + // Update the length + const int nGlyphSlotEnd = std::min(size_t(glyph_slot + length), mvGlyphs.size()); + + // We're all out of glyphs here. + if (glyph_slot == nGlyphSlotEnd) + { + return 0; + } + + // Find as many glyphs as we can which can be drawn in one go. + Glyphs::const_iterator glyph_itr = mvGlyphs.begin() + glyph_slot; + const int glyph_slot_begin = glyph_slot; + const int initial_y_pos = glyph_itr->maLinearPos.Y(); + + // Set the position to the position of the start glyph. + ::Point aStartPos = glyph_itr->maLinearPos; + //aPosOut = glyph_itr->maLinearPos; + aPosOut = GetDrawPosition(aStartPos); + + + for (;;) // Forever + { + // last index of the range from glyph_to_chars does not include this glyph + if (char_index) + { + assert((glyph_slot >= -1) && (glyph_slot < (signed)mvGlyph2Char.size())); + if (mvGlyph2Char[glyph_slot] == -1) + *char_index++ = mvCharDxs.size(); + else + *char_index++ = mvGlyph2Char[glyph_slot]; + } + // Copy out this glyphs data. + ++glyph_slot; + *glyph_out++ = glyph_itr->mnGlyphIndex; + + // Find the actual advance - this must be correct if called from + // MultiSalLayout::AdjustLayout which requests one glyph at a time. + const long nGlyphAdvance = (glyph_slot == static_cast<int>(mvGlyphs.size()))? + glyph_itr->mnNewWidth : + ((glyph_itr+1)->maLinearPos.X() - glyph_itr->maLinearPos.X()); + +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"GetNextGlyphs g%d c%d x%ld,%ld adv%ld, pos %ld,%ld\n", glyph_slot - 1, + mvGlyph2Char[glyph_slot-1], glyph_itr->maLinearPos.X(), glyph_itr->maLinearPos.Y(), nGlyphAdvance, + aPosOut.X(), aPosOut.Y()); +#endif + + if (glyph_adv) // If we are returning advance store it. + *glyph_adv++ = nGlyphAdvance; + else // Stop when next advance is unexpected. + if (glyph_itr->mnOrigWidth != nGlyphAdvance) break; + + // Have fetched all the glyphs we need to + if (glyph_slot == nGlyphSlotEnd) + break; + + ++glyph_itr; + // Stop when next y position is unexpected. + if (initial_y_pos != glyph_itr->maLinearPos.Y()) + break; + + // Stop if glyph dropped + if (glyph_itr->mnGlyphIndex == GF_DROPPED) + break; + } + int numGlyphs = glyph_slot - glyph_slot_begin; + // move the next glyph_slot to a glyph that hasn't been dropped + while (glyph_slot < static_cast<int>(mvGlyphs.size()) && + (mvGlyphs.begin() + glyph_slot)->mnGlyphIndex == GF_DROPPED) + ++glyph_slot; + return numGlyphs; +} + + +void GraphiteLayout::MoveGlyph( int nGlyphIndex, long nNewPos ) +{ + // TODO it might be better to actualy implement simplify properly, but this + // needs to be done carefully so the glyph/char maps are maintained + // If a glyph has been dropped then it wasn't returned by GetNextGlyphs, so + // the index here may be wrong + while ((mvGlyphs[nGlyphIndex].mnGlyphIndex == GF_DROPPED) && + (nGlyphIndex < (signed)mvGlyphs.size())) + { + nGlyphIndex++; + } + const long dx = nNewPos - mvGlyphs[nGlyphIndex].maLinearPos.X(); + + if (dx == 0) return; + // GenericSalLayout only changes maLinearPos, mvCharDxs doesn't change +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Move %d (%ld,%ld) c%d by %ld\n", nGlyphIndex, mvGlyphs[nGlyphIndex].maLinearPos.X(), nNewPos, mvGlyph2Char[nGlyphIndex], dx); +#endif + for (size_t gi = nGlyphIndex; gi < mvGlyphs.size(); gi++) + { + mvGlyphs[gi].maLinearPos.X() += dx; + } + // width does need to be updated for correct fallback + mnWidth += dx; +} + + +void GraphiteLayout::DropGlyph( int nGlyphIndex ) +{ + if(nGlyphIndex >= signed(mvGlyphs.size())) + return; + + GlyphItem & glyph = mvGlyphs[nGlyphIndex]; + glyph.mnGlyphIndex = GF_DROPPED; +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Dropped %d\n", nGlyphIndex); +#endif +} + +void GraphiteLayout::Simplify( bool isBaseLayout ) +{ + const sal_GlyphId dropMarker = isBaseLayout ? GF_DROPPED : 0; + + Glyphs::iterator gi = mvGlyphs.begin(); + // TODO check whether we need to adjust positions here + // MultiSalLayout seems to move the glyphs itself, so it may not be needed. + long deltaX = 0; + while (gi != mvGlyphs.end()) + { + if (gi->mnGlyphIndex == dropMarker) + { + deltaX += gi->mnNewWidth; + gi->mnNewWidth = 0; + } + else + { + deltaX = 0; + } + //mvCharDxs[mvGlyph2Char[gi->mnCharPos]] -= deltaX; + ++gi; + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Simplify base%d dx=%ld newW=%ld\n", isBaseLayout, deltaX, mnWidth - deltaX); +#endif + // discard width from trailing dropped glyphs, but not those in the middle + mnWidth -= deltaX; +} diff --git a/vcl/source/glyphs/graphite_serverfont.cxx b/vcl/source/glyphs/graphite_serverfont.cxx new file mode 100644 index 000000000000..be424c94b9d2 --- /dev/null +++ b/vcl/source/glyphs/graphite_serverfont.cxx @@ -0,0 +1,88 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: $ + * $Revision: $ + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Header files +// + +// Platform +#include <vcl/sallayout.hxx> +// Module +#include "gcach_ftyp.hxx" +#include <vcl/graphite_features.hxx> +#include "graphite_textsrc.hxx" +#include <vcl/graphite_serverfont.hxx> + +#ifndef WNT + +// +// An implementation of the GraphiteLayout interface to enable Graphite enabled fonts to be used. +// + +GraphiteServerFontLayout::GraphiteServerFontLayout(GraphiteFontAdaptor * pFont) throw() + : ServerFontLayout(pFont->font()), mpFont(pFont), + maImpl(*mpFont, mpFont->features(), pFont) +{ + // Nothing needed here +} + +GraphiteServerFontLayout::~GraphiteServerFontLayout() throw() +{ + delete mpFont; + mpFont = NULL; +} + +const sal_Unicode* GraphiteServerFontLayout::getTextPtr() const +{ + return maImpl.textSrc()->getLayoutArgs().mpStr + + maImpl.textSrc()->getLayoutArgs().mnMinCharPos; +} + +sal_GlyphId GraphiteLayoutImpl::getKashidaGlyph(int & width) +{ + int nKashidaIndex = mpFont->font().GetGlyphIndex( 0x0640 ); + if( nKashidaIndex != 0 ) + { + const GlyphMetric& rGM = mpFont->font().GetGlyphMetric( nKashidaIndex ); + width = rGM.GetCharWidth(); + } + else + { + width = 0; + } + return nKashidaIndex; +} + +#endif diff --git a/vcl/source/glyphs/graphite_textsrc.cxx b/vcl/source/glyphs/graphite_textsrc.cxx new file mode 100644 index 000000000000..adc2ae99c4f8 --- /dev/null +++ b/vcl/source/glyphs/graphite_textsrc.cxx @@ -0,0 +1,172 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: $ + * $Revision: $ + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Header files +// +// Standard Library +#include <string> +#include <cassert> +#include "graphite_textsrc.hxx" +#include <vcl/graphite_features.hxx> + +// class TextSourceAdaptor implementation. +// +TextSourceAdaptor::~TextSourceAdaptor() +{ + delete mpFeatures; +} + +gr::UtfType TextSourceAdaptor::utfEncodingForm() { + return gr::kutf16; +} + + +size_t TextSourceAdaptor::getLength() +{ + return maLayoutArgs.mnLength; +} + + +size_t TextSourceAdaptor::fetch(gr::toffset, size_t, gr::utf32 *) +{ + assert(false); + return 0; +} + + +size_t TextSourceAdaptor::fetch(gr::toffset offset, size_t char_count, gr::utf16 * char_buffer) +{ + assert(char_buffer); + + size_t copy_count = std::min(size_t(maLayoutArgs.mnLength), char_count); + std::copy(maLayoutArgs.mpStr + offset, maLayoutArgs.mpStr + offset + copy_count, char_buffer); + + return copy_count; +} + + +size_t TextSourceAdaptor::fetch(gr::toffset, size_t, gr::utf8 *) +{ + assert(false); + return 0; +} + + +inline void TextSourceAdaptor::getCharProperties(const int nCharIdx, int & min, int & lim, size_t & depth) +{ + maLayoutArgs.ResetPos(); + bool rtl = maLayoutArgs.mnFlags & SAL_LAYOUT_BIDI_RTL; + for(depth = ((rtl)? 1:0); maLayoutArgs.maRuns.GetRun(&min, &lim, &rtl); maLayoutArgs.maRuns.NextRun()) + { + if (min > nCharIdx) + break; + // Only increase the depth when a change of direction occurs. + depth += int(rtl ^ bool(depth & 0x1)); + if (min <= nCharIdx && nCharIdx < lim) + break; + } + // If there is no run for this position increment the depth, but don't + // change if this is out of bounds context + if (lim > 0 && nCharIdx >= lim && nCharIdx < maLayoutArgs.mnEndCharPos) + depth++; +} + + +bool TextSourceAdaptor::getRightToLeft(gr::toffset nCharIdx) +{ + size_t depth; + int min, lim = 0; + getCharProperties(nCharIdx, min, lim, depth); + //printf("getRtl %d,%x=%d\n", nCharIdx, maLayoutArgs.mpStr[nCharIdx], depth & 0x1); + return depth & 0x1; +} + + +unsigned int TextSourceAdaptor::getDirectionDepth(gr::toffset nCharIdx) +{ + size_t depth; + int min, lim; + getCharProperties(nCharIdx, min, lim, depth); + //printf("getDirectionDepth %d,%x=%d\n", nCharIdx, maLayoutArgs.mpStr[nCharIdx], depth); + return depth; +} + + +float TextSourceAdaptor::getVerticalOffset(gr::toffset) +{ + return 0.0f; //TODO: Implement correctly +} + +gr::isocode TextSourceAdaptor::getLanguage(gr::toffset) +{ + if (mpFeatures && mpFeatures->hasLanguage()) + return mpFeatures->getLanguage(); + gr::isocode unknown = {{0,0,0,0}}; + return unknown; +} + +std::pair<gr::toffset, gr::toffset> TextSourceAdaptor::propertyRange(gr::toffset nCharIdx) +{ + + if (nCharIdx < unsigned(maLayoutArgs.mnMinCharPos)) + return std::make_pair(0, maLayoutArgs.mnMinCharPos); + + if (nCharIdx < mnEnd) + return std::make_pair(maLayoutArgs.mnMinCharPos, mnEnd); + + return std::make_pair(mnEnd, maLayoutArgs.mnLength); +} + +size_t TextSourceAdaptor::getFontFeatures(gr::toffset, gr::FeatureSetting * settings) +{ + if (mpFeatures) return mpFeatures->getFontFeatures(settings); + return 0; +} + + +bool TextSourceAdaptor::sameSegment(gr::toffset char_idx1, gr::toffset char_idx2) +{ + const std::pair<gr::toffset, gr::toffset> + range1 = propertyRange(char_idx1), + range2 = propertyRange(char_idx2); + + return range1 == range2; +} + +void TextSourceAdaptor::setFeatures(const grutils::GrFeatureParser * pFeatures) +{ + mpFeatures = new grutils::GrFeatureParser(*pFeatures); +} diff --git a/vcl/source/glyphs/graphite_textsrc.hxx b/vcl/source/glyphs/graphite_textsrc.hxx new file mode 100644 index 000000000000..6f701988bb01 --- /dev/null +++ b/vcl/source/glyphs/graphite_textsrc.hxx @@ -0,0 +1,131 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: $ + * $Revision: $ + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef _SV_GRAPHITETEXTSRC_HXX +#define _SV_GRAPHITETEXTSRC_HXX +// Description: Implements the Graphite interfaces IGrTextSource and +// IGrGraphics which provide Graphite with access to the +// app's text storage system and the platform's font and +// graphics systems. + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Standard Library +#include <stdexcept> +// Platform + +#ifndef _SVWIN_H +#include <tools/svwin.h> +#endif + +#ifndef _SV_SVSYS_HXX +#include <svsys.h> +#endif + +#ifndef _SV_SALGDI_HXX +#include <vcl/salgdi.hxx> +#endif + +#ifndef _SV_SALLAYOUT_HXX +#include <vcl/sallayout.hxx> +#endif + +// Module +#include "vcl/dllapi.h" + +// Libraries +#include <graphite/GrClient.h> +#include <graphite/Font.h> +#include <graphite/ITextSource.h> + +// Module type definitions and forward declarations. +// +namespace grutils +{ + class GrFeatureParser; +} +// Implements the Adaptor pattern to adapt the LayoutArgs and the ServerFont interfaces to the +// gr::IGrTextSource interface. +// @author tse +// +class TextSourceAdaptor : public gr::ITextSource +{ +public: + TextSourceAdaptor(ImplLayoutArgs &layout_args, const int nContextLen) throw(); + ~TextSourceAdaptor(); + virtual gr::UtfType utfEncodingForm(); + virtual size_t getLength(); + virtual size_t fetch(gr::toffset ichMin, size_t cch, gr::utf32 * prgchBuffer); + virtual size_t fetch(gr::toffset ichMin, size_t cch, gr::utf16 * prgchwBuffer); + virtual size_t fetch(gr::toffset ichMin, size_t cch, gr::utf8 * prgchsBuffer); + virtual bool getRightToLeft(gr::toffset ich); + virtual unsigned int getDirectionDepth(gr::toffset ich); + virtual float getVerticalOffset(gr::toffset ich); + virtual gr::isocode getLanguage(gr::toffset ich); + + virtual std::pair<gr::toffset, gr::toffset> propertyRange(gr::toffset ich); + virtual size_t getFontFeatures(gr::toffset ich, gr::FeatureSetting * prgfset); + virtual bool sameSegment(gr::toffset ich1, gr::toffset ich2); + + operator ImplLayoutArgs & () throw(); + void setFeatures(const grutils::GrFeatureParser * pFeatures); + const ImplLayoutArgs & getLayoutArgs() const { return maLayoutArgs; } + size_t getContextLength() const { return mnEnd; }; + inline void switchLayoutArgs(ImplLayoutArgs & newArgs); +private: + // Prevent the generation of a default assignment operator. + TextSourceAdaptor & operator=(const TextSourceAdaptor &); + + void getCharProperties(const int, int &, int &, size_t &); + + ImplLayoutArgs maLayoutArgs; + size_t mnEnd; + const grutils::GrFeatureParser * mpFeatures; +}; + +inline TextSourceAdaptor::TextSourceAdaptor(ImplLayoutArgs &la, const int nContextLen) throw() + : maLayoutArgs(la), + mnEnd(std::min(la.mnLength, nContextLen)), + mpFeatures(NULL) +{ +} + +inline TextSourceAdaptor::operator ImplLayoutArgs & () throw() { + return maLayoutArgs; +} + +inline void TextSourceAdaptor::switchLayoutArgs(ImplLayoutArgs & aNewArgs) +{ + mnEnd += aNewArgs.mnMinCharPos - maLayoutArgs.mnMinCharPos; + maLayoutArgs = aNewArgs; +} + +#endif diff --git a/vcl/source/glyphs/makefile.mk b/vcl/source/glyphs/makefile.mk index b08777d7020f..3e79cdc63da2 100644 --- a/vcl/source/glyphs/makefile.mk +++ b/vcl/source/glyphs/makefile.mk @@ -49,11 +49,36 @@ CFLAGS+=-DUSE_FT_EMBOLDEN # --- Files -------------------------------------------------------- .IF "$(USE_BUILTIN_RASTERIZER)" != "" +# GlyphCache + FreeType support (only on UNX platforms currently) SLOFILES=\ $(SLO)$/glyphcache.obj \ $(SLO)$/gcach_rbmp.obj \ $(SLO)$/gcach_layout.obj \ $(SLO)$/gcach_ftyp.obj + +.IF "$(ENABLE_GRAPHITE)" != "" +# Graphite support using the glyphcache infrastructure +CFLAGS+=-DENABLE_GRAPHITE +SLOFILES+= $(SLO)$/graphite_adaptors.obj \ + $(SLO)$/graphite_features.obj \ + $(SLO)$/graphite_cache.obj \ + $(SLO)$/graphite_textsrc.obj \ + $(SLO)$/graphite_serverfont.obj \ + $(SLO)$/graphite_layout.obj +.ENDIF + +.ELSE + +.IF "$(ENABLE_GRAPHITE)" == "TRUE" +# Graphite support on non-UNX platforms +# make use of stlport headerfiles +EXT_USE_STLPORT=TRUE +SLOFILES=\ + $(SLO)$/graphite_textsrc.obj \ + $(SLO)$/graphite_cache.obj \ + $(SLO)$/graphite_features.obj \ + $(SLO)$/graphite_layout.obj +.ENDIF .ENDIF # --- Targets ------------------------------------------------------ |