/************************************************************** * * 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. * *************************************************************/ // Description: Classes to cache Graphite Segments to try to improve // rendering performance. #ifndef GraphiteSegmentCache_h #define GraphiteSegmentCache_h #include #include #define GRCACHE_REUSE_VECTORS 1 #include class TextSourceAdaptor; /** * GrSegRecord stores a Graphite Segment and its associated text */ class GrSegRecord { public: GrSegRecord(rtl::OUString * rope, TextSourceAdaptor * textSrc, gr::Segment * seg, bool bIsRtl); ~GrSegRecord(); void reuse(rtl::OUString * rope, TextSourceAdaptor * textSrc, gr::Segment * seg, bool bIsRtl); void clearVectors(); void clear(); #ifdef GRCACHE_REUSE_VECTORS void setGlyphVectors(long nWidth, GraphiteLayout::Glyphs & vGlyphs, std::vector vCharDxs, std::vector & vChar2Base, std::vector & vGlyph2Char, float fScale) { clearVectors(); mnWidth = nWidth; m_fontScale = fScale; mvGlyphs.insert(mvGlyphs.begin(), vGlyphs.begin(), vGlyphs.end()); mvCharDxs.insert(mvCharDxs.begin(),vCharDxs.begin(),vCharDxs.end()); mvChar2BaseGlyph.insert(mvChar2BaseGlyph.begin(),vChar2Base.begin(),vChar2Base.end()); mvGlyph2Char.insert(mvGlyph2Char.begin(),vGlyph2Char.begin(),vGlyph2Char.end()); } #endif gr::Segment * getSegment() { return m_seg; } TextSourceAdaptor * getTextSrc() { return m_text; } void unlock() { --m_lockCount; } bool isRtl() const { return mbIsRtl; } #ifdef GRCACHE_REUSE_VECTORS const long & width() const { return mnWidth; } const GraphiteLayout::Glyphs & glyphs() const { return mvGlyphs; } const std::vector & charDxs() const { return mvCharDxs; } const std::vector & char2BaseGlyph() const { return mvChar2BaseGlyph; } const std::vector & glyph2Char() const { return mvGlyph2Char; } float & fontScale() { return m_fontScale; } #endif private: rtl::OUString * m_rope; TextSourceAdaptor * m_text; gr::Segment * m_seg; const xub_Unicode * m_nextKey; const xub_Unicode* m_pStr; size_t m_startChar; float m_fontScale; long mnWidth; GraphiteLayout::Glyphs mvGlyphs; // glyphs in display order std::vector mvCharDxs; // right hand side x offset of each glyph std::vector mvChar2BaseGlyph; std::vector mvGlyph2Char; bool mbIsRtl; int m_lockCount; friend class GraphiteSegmentCache; }; typedef std::hash_map > GraphiteSegMap; typedef std::hash_multimap GraphiteRopeMap; typedef std::pair GrRMEntry; /** * GraphiteSegmentCache contains the cached Segments for one particular font size */ class GraphiteSegmentCache { public: enum { // not really sure what good values are here, // bucket size should be >> cache size SEG_BUCKET_FACTOR = 4, SEG_DEFAULT_CACHE_SIZE = 2047 }; GraphiteSegmentCache(sal_uInt32 nSegCacheSize) : m_segMap(nSegCacheSize * SEG_BUCKET_FACTOR), m_nSegCacheSize(nSegCacheSize), m_oldestKey(NULL) {}; ~GraphiteSegmentCache() { m_ropeMap.clear(); GraphiteSegMap::iterator i = m_segMap.begin(); while (i != m_segMap.end()) { GrSegRecord *r = i->second; delete r; ++i; } m_segMap.clear(); }; GrSegRecord * getSegment(ImplLayoutArgs & layoutArgs, bool bIsRtl, int segCharLimit) { GrSegRecord * found = NULL; // try to find a segment starting at correct place, if not, try to find a // match for the complete buffer GraphiteSegMap::iterator iMap = m_segMap.find(reinterpret_cast(layoutArgs.mpStr + layoutArgs.mnMinCharPos)); if (iMap != m_segMap.end()) { found = iMap->second; } else { iMap = m_segMap.find(reinterpret_cast(layoutArgs.mpStr)); if (iMap != m_segMap.end()) { found = iMap->second; } } if (found) { if (found->m_seg->startCharacter() <= layoutArgs.mnMinCharPos && found->m_seg->stopCharacter() >= layoutArgs.mnEndCharPos) { DBG_ASSERT(found && found->m_seg, "null entry in GraphiteSegmentCache"); // restore original start character, in case it has changed found->m_seg->setTextSourceOffset(found->m_startChar); // check that characters are the same, at least in the range of // interest // We could use substr and ==, but substr does a copy, // so its probably faster to do it like this for (int i = layoutArgs.mnMinCharPos; i < segCharLimit; i++) { //if (!found->m_rope->match(rtl::OUString(layoutArgs.mpStr[i], layoutArgs.mnLength), i - found->m_seg->startCharacter())) if (found->m_rope->getStr()[i-found->m_seg->startCharacter()] != layoutArgs.mpStr[i]) return NULL; } if (found->isRtl() != bIsRtl) { return NULL; } if (found->m_seg->stopCharacter() > layoutArgs.mnEndCharPos && static_cast(found->char2BaseGlyph().size()) > layoutArgs.mnEndCharPos) { // check that the requested end character isn't mid cluster if (found->char2BaseGlyph()[layoutArgs.mnEndCharPos-layoutArgs.mnMinCharPos] == -1) { return NULL; } } // if (found->m_lockCount != 0) // OutputDebugString("Multple users of SegRecord!"); found->m_lockCount++; } else found = NULL; } else { // the pointers aren't the same, but we might still have the same text in a segment // this is expecially needed when editing a large paragraph // each edit changes the pointers, but if we don't reuse any segments it gets very // slow. rtl::OUString * rope = new rtl::OUString(layoutArgs.mpStr + layoutArgs.mnMinCharPos, segCharLimit - layoutArgs.mnMinCharPos); if (!rope) return NULL; size_t nHash = (*(rope)).hashCode(); GrRMEntry range = m_ropeMap.equal_range(nHash); while (range.first != range.second) { found = range.first->second; if (found->m_lockCount == 0) { if(rope->match(*(found->m_rope))) { // found, but the pointers are all wrong found->m_seg->setTextSourceOffset(layoutArgs.mnMinCharPos); // the switch is done in graphite_layout.cxx //found->m_text->switchLayoutArgs(layoutArgs); found->m_lockCount++; break; } else found = NULL; } else found = NULL; ++(range.first); } delete rope; } return found; }; GrSegRecord * cacheSegment(TextSourceAdaptor * adapter, gr::Segment * seg, bool bIsRtl); private: GraphiteSegMap m_segMap; GraphiteRopeMap m_ropeMap; sal_uInt32 m_nSegCacheSize; const xub_Unicode * m_oldestKey; const xub_Unicode * m_prevKey; }; typedef std::hash_map > GraphiteCacheMap; /** * GraphiteCacheHandler maps a particular font, style, size to a GraphiteSegmentCache */ class GraphiteCacheHandler { public: GraphiteCacheHandler() : m_cacheMap(255) { const char * pEnvCache = getenv( "SAL_GRAPHITE_CACHE_SIZE" ); if (pEnvCache != NULL) { int envCacheSize = atoi(pEnvCache); if (envCacheSize <= 0) m_nSegCacheSize = GraphiteSegmentCache::SEG_DEFAULT_CACHE_SIZE; else { m_nSegCacheSize = envCacheSize; } } else { m_nSegCacheSize = GraphiteSegmentCache::SEG_DEFAULT_CACHE_SIZE; } }; ~GraphiteCacheHandler() { GraphiteCacheMap::iterator i = m_cacheMap.begin(); while (i != m_cacheMap.end()) { GraphiteSegmentCache *r = i->second; delete r; ++i; } m_cacheMap.clear(); }; static GraphiteCacheHandler instance; GraphiteSegmentCache * getCache(sal_Int32 & fontHash) { if (m_cacheMap.count(fontHash) > 0) { return m_cacheMap.find(fontHash)->second; } GraphiteSegmentCache *pCache = new GraphiteSegmentCache(m_nSegCacheSize); m_cacheMap[fontHash] = pCache; return pCache; } private: GraphiteCacheMap m_cacheMap; sal_uInt32 m_nSegCacheSize; }; #endif