1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // Description: Classes to cache Graphite Segments to try to improve 25 // rendering performance. 26 27 #ifndef GraphiteSegmentCache_h 28 #define GraphiteSegmentCache_h 29 30 #include <tools/solar.h> 31 #include <rtl/ustring.h> 32 33 #define GRCACHE_REUSE_VECTORS 1 34 35 //#include <rope> 36 #include <hash_map> 37 38 class TextSourceAdaptor; 39 /** 40 * GrSegRecord stores a Graphite Segment and its associated text 41 */ 42 class GrSegRecord { 43 public: 44 GrSegRecord(rtl::OUString * rope, TextSourceAdaptor * textSrc, gr::Segment * seg, bool bIsRtl); 45 46 ~GrSegRecord(); 47 48 void reuse(rtl::OUString * rope, TextSourceAdaptor * textSrc, gr::Segment * seg, bool bIsRtl); 49 50 void clearVectors(); 51 void clear(); 52 #ifdef GRCACHE_REUSE_VECTORS 53 void setGlyphVectors(long nWidth, GraphiteLayout::Glyphs & vGlyphs, std::vector<int> vCharDxs, 54 std::vector<int> & vChar2Base, std::vector<int> & vGlyph2Char, float fScale) 55 { 56 clearVectors(); 57 mnWidth = nWidth; 58 m_fontScale = fScale; 59 mvGlyphs.insert(mvGlyphs.begin(), vGlyphs.begin(), vGlyphs.end()); 60 mvCharDxs.insert(mvCharDxs.begin(),vCharDxs.begin(),vCharDxs.end()); 61 mvChar2BaseGlyph.insert(mvChar2BaseGlyph.begin(),vChar2Base.begin(),vChar2Base.end()); 62 mvGlyph2Char.insert(mvGlyph2Char.begin(),vGlyph2Char.begin(),vGlyph2Char.end()); 63 } 64 #endif 65 gr::Segment * getSegment() { return m_seg; } 66 TextSourceAdaptor * getTextSrc() { return m_text; } 67 void unlock() { --m_lockCount; } 68 bool isRtl() const { return mbIsRtl; } 69 #ifdef GRCACHE_REUSE_VECTORS 70 const long & width() const { return mnWidth; } 71 const GraphiteLayout::Glyphs & glyphs() const { return mvGlyphs; } 72 const std::vector<int> & charDxs() const { return mvCharDxs; } 73 const std::vector<int> & char2BaseGlyph() const { return mvChar2BaseGlyph; } 74 const std::vector<int> & glyph2Char() const { return mvGlyph2Char; } 75 float & fontScale() { return m_fontScale; } 76 #endif 77 private: 78 rtl::OUString * m_rope; 79 TextSourceAdaptor * m_text; 80 gr::Segment * m_seg; 81 const xub_Unicode * m_nextKey; 82 const xub_Unicode* m_pStr; 83 size_t m_startChar; 84 float m_fontScale; 85 long mnWidth; 86 GraphiteLayout::Glyphs mvGlyphs; // glyphs in display order 87 std::vector<int> mvCharDxs; // right hand side x offset of each glyph 88 std::vector<int> mvChar2BaseGlyph; 89 std::vector<int> mvGlyph2Char; 90 bool mbIsRtl; 91 int m_lockCount; 92 friend class GraphiteSegmentCache; 93 }; 94 95 typedef std::hash_map<long, GrSegRecord*, std::hash<long> > GraphiteSegMap; 96 typedef std::hash_multimap<size_t, GrSegRecord*> GraphiteRopeMap; 97 typedef std::pair<GraphiteRopeMap::iterator, GraphiteRopeMap::iterator> GrRMEntry; 98 99 /** 100 * GraphiteSegmentCache contains the cached Segments for one particular font size 101 */ 102 class GraphiteSegmentCache 103 { 104 public: 105 enum { 106 // not really sure what good values are here, 107 // bucket size should be >> cache size 108 SEG_BUCKET_FACTOR = 4, 109 SEG_DEFAULT_CACHE_SIZE = 2047 110 }; 111 GraphiteSegmentCache(sal_uInt32 nSegCacheSize) 112 : m_segMap(nSegCacheSize * SEG_BUCKET_FACTOR), 113 m_nSegCacheSize(nSegCacheSize), 114 m_oldestKey(NULL) {}; 115 ~GraphiteSegmentCache() 116 { 117 m_ropeMap.clear(); 118 GraphiteSegMap::iterator i = m_segMap.begin(); 119 while (i != m_segMap.end()) 120 { 121 GrSegRecord *r = i->second; 122 delete r; 123 ++i; 124 } 125 m_segMap.clear(); 126 }; 127 GrSegRecord * getSegment(ImplLayoutArgs & layoutArgs, bool bIsRtl, int segCharLimit) 128 { 129 GrSegRecord * found = NULL; 130 // try to find a segment starting at correct place, if not, try to find a 131 // match for the complete buffer 132 GraphiteSegMap::iterator iMap = 133 m_segMap.find(reinterpret_cast<long>(layoutArgs.mpStr + 134 layoutArgs.mnMinCharPos)); 135 if (iMap != m_segMap.end()) 136 { 137 found = iMap->second; 138 } 139 else 140 { 141 iMap = m_segMap.find(reinterpret_cast<long>(layoutArgs.mpStr)); 142 if (iMap != m_segMap.end()) 143 { 144 found = iMap->second; 145 } 146 } 147 if (found) 148 { 149 if (found->m_seg->startCharacter() <= layoutArgs.mnMinCharPos && 150 found->m_seg->stopCharacter() >= layoutArgs.mnEndCharPos) 151 { 152 DBG_ASSERT(found && found->m_seg, "null entry in GraphiteSegmentCache"); 153 // restore original start character, in case it has changed 154 found->m_seg->setTextSourceOffset(found->m_startChar); 155 // check that characters are the same, at least in the range of 156 // interest 157 // We could use substr and ==, but substr does a copy, 158 // so its probably faster to do it like this 159 for (int i = layoutArgs.mnMinCharPos; i < segCharLimit; i++) 160 { 161 //if (!found->m_rope->match(rtl::OUString(layoutArgs.mpStr[i], layoutArgs.mnLength), i - found->m_seg->startCharacter())) 162 if (found->m_rope->getStr()[i-found->m_seg->startCharacter()] != layoutArgs.mpStr[i]) 163 return NULL; 164 } 165 if (found->isRtl() != bIsRtl) 166 { 167 return NULL; 168 } 169 if (found->m_seg->stopCharacter() > layoutArgs.mnEndCharPos && 170 static_cast<int>(found->char2BaseGlyph().size()) > layoutArgs.mnEndCharPos) 171 { 172 // check that the requested end character isn't mid cluster 173 if (found->char2BaseGlyph()[layoutArgs.mnEndCharPos-layoutArgs.mnMinCharPos] == -1) 174 { 175 return NULL; 176 } 177 } 178 // if (found->m_lockCount != 0) 179 // OutputDebugString("Multple users of SegRecord!"); 180 found->m_lockCount++; 181 } 182 else found = NULL; 183 } 184 else 185 { 186 // the pointers aren't the same, but we might still have the same text in a segment 187 // this is expecially needed when editing a large paragraph 188 // each edit changes the pointers, but if we don't reuse any segments it gets very 189 // slow. 190 rtl::OUString * rope = new rtl::OUString(layoutArgs.mpStr + layoutArgs.mnMinCharPos, 191 segCharLimit - layoutArgs.mnMinCharPos); 192 if (!rope) return NULL; 193 size_t nHash = (*(rope)).hashCode(); 194 GrRMEntry range = m_ropeMap.equal_range(nHash); 195 while (range.first != range.second) 196 { 197 found = range.first->second; 198 if (found->m_lockCount == 0) 199 { 200 if(rope->match(*(found->m_rope))) 201 { 202 // found, but the pointers are all wrong 203 found->m_seg->setTextSourceOffset(layoutArgs.mnMinCharPos); 204 // the switch is done in graphite_layout.cxx 205 //found->m_text->switchLayoutArgs(layoutArgs); 206 found->m_lockCount++; 207 break; 208 } 209 else 210 found = NULL; 211 } 212 else 213 found = NULL; 214 ++(range.first); 215 } 216 delete rope; 217 } 218 return found; 219 }; 220 GrSegRecord * cacheSegment(TextSourceAdaptor * adapter, gr::Segment * seg, bool bIsRtl); 221 private: 222 GraphiteSegMap m_segMap; 223 GraphiteRopeMap m_ropeMap; 224 sal_uInt32 m_nSegCacheSize; 225 const xub_Unicode * m_oldestKey; 226 const xub_Unicode * m_prevKey; 227 }; 228 229 typedef std::hash_map<int, GraphiteSegmentCache *, std::hash<int> > GraphiteCacheMap; 230 231 /** 232 * GraphiteCacheHandler maps a particular font, style, size to a GraphiteSegmentCache 233 */ 234 class GraphiteCacheHandler 235 { 236 public: 237 GraphiteCacheHandler() : m_cacheMap(255) 238 { 239 const char * pEnvCache = getenv( "SAL_GRAPHITE_CACHE_SIZE" ); 240 if (pEnvCache != NULL) 241 { 242 int envCacheSize = atoi(pEnvCache); 243 if (envCacheSize <= 0) 244 m_nSegCacheSize = GraphiteSegmentCache::SEG_DEFAULT_CACHE_SIZE; 245 else 246 { 247 m_nSegCacheSize = envCacheSize; 248 } 249 } 250 else 251 { 252 m_nSegCacheSize = GraphiteSegmentCache::SEG_DEFAULT_CACHE_SIZE; 253 } 254 }; 255 ~GraphiteCacheHandler() 256 { 257 GraphiteCacheMap::iterator i = m_cacheMap.begin(); 258 while (i != m_cacheMap.end()) 259 { 260 GraphiteSegmentCache *r = i->second; 261 delete r; 262 ++i; 263 } 264 m_cacheMap.clear(); 265 }; 266 267 static GraphiteCacheHandler instance; 268 269 GraphiteSegmentCache * getCache(sal_Int32 & fontHash) 270 { 271 if (m_cacheMap.count(fontHash) > 0) 272 { 273 return m_cacheMap.find(fontHash)->second; 274 } 275 GraphiteSegmentCache *pCache = new GraphiteSegmentCache(m_nSegCacheSize); 276 m_cacheMap[fontHash] = pCache; 277 return pCache; 278 } 279 private: 280 GraphiteCacheMap m_cacheMap; 281 sal_uInt32 m_nSegCacheSize; 282 }; 283 284 #endif 285 286