From a14429a2dfa8ed7373dceb2ee274a18fa24d2728 Mon Sep 17 00:00:00 2001 From: gcasa Date: Wed, 15 Apr 2009 01:27:42 +0000 Subject: [PATCH] * Source/GSHorizontalTypesetter.m: Use thread dictionary to hold the instance of the typesetter for that thread. * Source/NSStringDrawing.m: Add locks to methods to prevent modification of static variables by separate threads from causing the application to get glyph generation errors. NOTE: These changes based on suggestions/ideas from Doug Simons . git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@28219 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 10 + Source/GSHorizontalTypesetter.m | 15 +- Source/NSStringDrawing.m | 486 ++++++++++++++++++++------------ 3 files changed, 328 insertions(+), 183 deletions(-) diff --git a/ChangeLog b/ChangeLog index a0d2b3b72..e1f156607 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2009-04-14 21:24-EDT Gregory John Casamento + + * Source/GSHorizontalTypesetter.m: Use thread dictionary to hold + the instance of the typesetter for that thread. + * Source/NSStringDrawing.m: Add locks to methods to prevent modification + of static variables by separate threads from causing the application + to get glyph generation errors. + NOTE: These changes based on suggestions/ideas from Doug + Simons . + 2009-04-12 15:16-EDT Gregory John Casamento * Source/GSWindowDecorationView.m: initialize offsets to prevent diff --git a/Source/GSHorizontalTypesetter.m b/Source/GSHorizontalTypesetter.m index a0c644cd7..ce556ef5b 100644 --- a/Source/GSHorizontalTypesetter.m +++ b/Source/GSHorizontalTypesetter.m @@ -78,13 +78,22 @@ cache fairly aggressively without having to worry about memory consumption. +(GSHorizontalTypesetter *) sharedInstance { -static GSHorizontalTypesetter *shared; + NSMutableDictionary *threadDict = + [[NSThread currentThread] threadDictionary]; + GSHorizontalTypesetter *shared = + [threadDict objectForKey: @"sharedHorizontalTypesetter"]; + if (!shared) - shared = [[self alloc] init]; + { + shared = [[self alloc] init]; + [threadDict setObject: shared + forKey: @"sharedHorizontalTypesetter"]; + RELEASE(shared); + } + return shared; } - #define CACHE_INITIAL 192 #define CACHE_STEP 192 diff --git a/Source/NSStringDrawing.m b/Source/NSStringDrawing.m index d3e3a42e4..9fded59a1 100644 --- a/Source/NSStringDrawing.m +++ b/Source/NSStringDrawing.m @@ -33,6 +33,7 @@ #include #include +#include #include "AppKit/NSAffineTransform.h" #include "AppKit/NSLayoutManager.h" @@ -84,10 +85,11 @@ typedef struct static BOOL did_init; static cache_t cache[NUM_CACHE_ENTRIES]; -static NSTextStorage *scratchTextStorage; +static NSTextStorage *scratchTextStorage; static NSLayoutManager *scratchLayoutManager; static NSTextContainer *scratchTextContainer; +static NSRecursiveLock *cacheLock = nil; static int total, hits, misses, hash_hits; @@ -122,7 +124,7 @@ static void init_string_drawing(void) #ifdef STATS atexit(NSStringDrawing_dump_stats); #endif - + for (i = 0; i < NUM_CACHE_ENTRIES + 1; i++) { textStorage = [[NSTextStorage alloc] init]; @@ -133,7 +135,7 @@ static void init_string_drawing(void) initWithContainerSize: NSMakeSize(10, 10)]; [layoutManager addTextContainer: textContainer]; [textContainer release]; - + if (i < NUM_CACHE_ENTRIES) { cache[i].textStorage = textStorage; @@ -149,6 +151,19 @@ static void init_string_drawing(void) } } +static void cache_lock() +{ + if(cacheLock == nil) + { + cacheLock = [[NSRecursiveLock alloc] init]; + } + [cacheLock lock]; +} + +static void cache_unlock() +{ + [cacheLock unlock]; +} static int cache_match(int hasSize, NSSize size, int useScreenFonts, int *matched) { @@ -266,45 +281,46 @@ static int cache_lookup_string(NSString *string, NSDictionary *attributes, if (!did_init) init_string_drawing(); - /* - This is a hack, but it's an efficient way of getting the layout manager - to just ditch all old information, and here, we don't want it to try to - be clever and cache or soft-invalidate anything since the new string has - nothing to do with the old one. - - TODO: the layout manager should realize this by itself + This is a hack, but it's an efficient way of getting the layout manager + to just ditch all old information, and here, we don't want it to try to + be clever and cache or soft-invalidate anything since the new string has + nothing to do with the old one. + + TODO: the layout manager should realize this by itself */ [scratchLayoutManager setTextStorage: scratchTextStorage]; - + [scratchTextStorage beginEditing]; [scratchTextStorage replaceCharactersInRange: NSMakeRange(0, [scratchTextStorage length]) - withString: @""]; + withString: @""]; if ([string length]) { [scratchTextStorage replaceCharactersInRange: NSMakeRange(0, 0) - withString: string]; + withString: string]; [scratchTextStorage setAttributes: attributes - range: NSMakeRange(0, [string length])]; + range: NSMakeRange(0, [string length])]; } [scratchTextStorage endEditing]; - + ci = cache_match(hasSize, size, useScreenFonts, &hit); + if (hit) - return ci; - + { + return ci; + } c = &cache[ci]; - + textStorage = c->textStorage; layoutManager = c->layoutManager; textContainer = c->textContainer; - + if (hasSize) [textContainer setContainerSize: NSMakeSize(size.width, LARGE_SIZE)]; else [textContainer setContainerSize: NSMakeSize(LARGE_SIZE, LARGE_SIZE)]; [layoutManager setUsesScreenFonts: useScreenFonts]; - + c->usedRect = [layoutManager usedRectForTextContainer: textContainer]; return ci; @@ -321,28 +337,31 @@ static int cache_lookup_attributed_string(NSAttributedString *string, if (!did_init) init_string_drawing(); - + [scratchTextStorage replaceCharactersInRange: NSMakeRange(0, [scratchTextStorage length]) - withString: @""]; + withString: @""]; [scratchTextStorage replaceCharactersInRange: NSMakeRange(0, 0) - withAttributedString: string]; - + withAttributedString: string]; + ci = cache_match(hasSize, size, useScreenFonts, &hit); + if (hit) - return ci; - + { + return ci; + } + c = &cache[ci]; - + textStorage = c->textStorage; layoutManager = c->layoutManager; textContainer = c->textContainer; - + if (hasSize) [textContainer setContainerSize: NSMakeSize(size.width, LARGE_SIZE)]; else [textContainer setContainerSize: NSMakeSize(LARGE_SIZE, LARGE_SIZE)]; [layoutManager setUsesScreenFonts: useScreenFonts]; - + c->usedRect = [layoutManager usedRectForTextContainer: textContainer]; return ci; @@ -365,8 +384,6 @@ static int use_screen_fonts(void) } } - - /* This is an ugly hack to get text to display correctly in non-flipped views. @@ -389,30 +406,42 @@ glyphs to be drawn upside-down, so we need to tell NSFont to flip the fonts. NSRange r; NSGraphicsContext *ctxt = GSCurrentContext(); - ci = cache_lookup_attributed_string(self, 0, NSZeroSize, use_screen_fonts()); - c = &cache[ci]; + cache_lock(); - r = NSMakeRange(0, [c->layoutManager numberOfGlyphs]); - - if (![[NSView focusView] isFlipped]) + NS_DURING { - DPSscale(ctxt, 1, -1); - point.y = -point.y; - - /* - Adjust point.y so the lower left corner of the used rect is at the - point that was passed to us. - */ - point.y -= NSMaxY(c->usedRect); - - [NSFont _setFontFlipHack: YES]; + ci = cache_lookup_attributed_string(self, 0, NSZeroSize, use_screen_fonts()); + c = &cache[ci]; + + r = NSMakeRange(0, [c->layoutManager numberOfGlyphs]); + + if (![[NSView focusView] isFlipped]) + { + DPSscale(ctxt, 1, -1); + point.y = -point.y; + + /* + Adjust point.y so the lower left corner of the used rect is at the + point that was passed to us. + */ + point.y -= NSMaxY(c->usedRect); + + [NSFont _setFontFlipHack: YES]; + } + + [c->layoutManager drawBackgroundForGlyphRange: r + atPoint: point]; + + [c->layoutManager drawGlyphsForGlyphRange: r + atPoint: point]; } - - [c->layoutManager drawBackgroundForGlyphRange: r - atPoint: point]; - - [c->layoutManager drawGlyphsForGlyphRange: r - atPoint: point]; + NS_HANDLER + { + cache_unlock(); + [localException raise]; + } + NS_ENDHANDLER; + cache_unlock(); if (![[NSView focusView] isFlipped]) { @@ -432,48 +461,61 @@ glyphs to be drawn upside-down, so we need to tell NSFont to flip the fonts. if (rect.size.width <= 0 || rect.size.height <= 0) return; + - ci = cache_lookup_attributed_string(self, 1, rect.size, use_screen_fonts()); - c = &cache[ci]; + cache_lock(); - /* - If the used rect fits completely in the rect we draw in, we save time - by avoiding the DPSrectclip (and the state save and restore). - - This isn't completely safe; the used rect isn't guaranteed to contain - all parts of all glyphs. - */ - if (c->usedRect.origin.x >= 0 && c->usedRect.origin.y <= 0 - && NSMaxX(c->usedRect) <= rect.size.width - && NSMaxY(c->usedRect) <= rect.size.height) + NS_DURING { - need_clip = NO; + ci = cache_lookup_attributed_string(self, 1, rect.size, use_screen_fonts()); + c = &cache[ci]; + + /* + If the used rect fits completely in the rect we draw in, we save time + by avoiding the DPSrectclip (and the state save and restore). + + This isn't completely safe; the used rect isn't guaranteed to contain + all parts of all glyphs. + */ + if (c->usedRect.origin.x >= 0 && c->usedRect.origin.y <= 0 + && NSMaxX(c->usedRect) <= rect.size.width + && NSMaxY(c->usedRect) <= rect.size.height) + { + need_clip = NO; + } + else + { + need_clip = YES; + DPSgsave(ctxt); + DPSrectclip(ctxt, rect.origin.x, rect.origin.y, + rect.size.width, rect.size.height); + } + + r = [c->layoutManager + glyphRangeForBoundingRect: NSMakeRect(0, 0, rect.size.width, + rect.size.height) + inTextContainer: c->textContainer]; + + if (![[NSView focusView] isFlipped]) + { + DPSscale(ctxt, 1, -1); + rect.origin.y = -NSMaxY(rect); + [NSFont _setFontFlipHack: YES]; + } + + [c->layoutManager drawBackgroundForGlyphRange: r + atPoint: rect.origin]; + + [c->layoutManager drawGlyphsForGlyphRange: r + atPoint: rect.origin]; } - else + NS_HANDLER { - need_clip = YES; - DPSgsave(ctxt); - DPSrectclip(ctxt, rect.origin.x, rect.origin.y, - rect.size.width, rect.size.height); + cache_unlock(); + [localException raise]; } - - r = [c->layoutManager - glyphRangeForBoundingRect: NSMakeRect(0, 0, rect.size.width, - rect.size.height) - inTextContainer: c->textContainer]; - - if (![[NSView focusView] isFlipped]) - { - DPSscale(ctxt, 1, -1); - rect.origin.y = -NSMaxY(rect); - [NSFont _setFontFlipHack: YES]; - } - - [c->layoutManager drawBackgroundForGlyphRange: r - atPoint: rect.origin]; - - [c->layoutManager drawGlyphsForGlyphRange: r - atPoint: rect.origin]; + NS_ENDHANDLER; + cache_unlock(); [NSFont _setFontFlipHack: NO]; if (![[NSView focusView] isFlipped]) @@ -498,28 +540,59 @@ glyphs to be drawn upside-down, so we need to tell NSFont to flip the fonts. - (NSSize) size { int ci; - ci = cache_lookup_attributed_string(self, 0, NSZeroSize, 1); - /* - An argument could be made for using NSMaxX/NSMaxY here, but that fails - horribly on right-aligned strings. For now, we handle that case in a - useful way and ignore indents. - */ - return cache[ci].usedRect.size; + NSSize result; + + cache_lock(); + NS_DURING + { + ci = cache_lookup_attributed_string(self, 0, NSZeroSize, 1); + /* + An argument could be made for using NSMaxX/NSMaxY here, but that fails + horribly on right-aligned strings. For now, we handle that case in a + useful way and ignore indents. + */ + + result = cache[ci].usedRect.size; + } + NS_HANDLER + { + cache_unlock(); + [localException raise]; + } + NS_ENDHANDLER; + cache_unlock(); + + return result; } - (NSRect) boundingRectWithSize: (NSSize)size options: (NSStringDrawingOptions)options { int ci; + NSRect result; - // FIXME: This ignores options - ci = cache_lookup_attributed_string(self, 1, size, 1); - /* - An argument could be made for using NSMaxX/NSMaxY here, but that fails - horribly on right-aligned strings. For now, we handle that case in a - useful way and ignore indents. - */ - return cache[ci].usedRect; + cache_lock(); + NS_DURING + { + // FIXME: This ignores options + ci = cache_lookup_attributed_string(self, 1, size, 1); + /* + An argument could be made for using NSMaxX/NSMaxY here, but that fails + horribly on right-aligned strings. For now, we handle that case in a + useful way and ignore indents. + */ + + result = cache[ci].usedRect; + } + NS_HANDLER + { + cache_unlock(); + [localException raise]; + } + NS_ENDHANDLER; + cache_unlock(); + + return result; } @end @@ -538,30 +611,41 @@ NSAttributedString to do the job. NSRange r; NSGraphicsContext *ctxt = GSCurrentContext(); - ci = cache_lookup_string(self, attrs, 0, NSZeroSize, use_screen_fonts()); - c = &cache[ci]; - - r = NSMakeRange(0, [c->layoutManager numberOfGlyphs]); - - if (![[NSView focusView] isFlipped]) + cache_lock(); + NS_DURING { - DPSscale(ctxt, 1, -1); - point.y = -point.y; - - /* - Adjust point.y so the lower left corner of the used rect is at the - point that was passed to us. - */ - point.y -= NSMaxY(c->usedRect); - - [NSFont _setFontFlipHack: YES]; + ci = cache_lookup_string(self, attrs, 0, NSZeroSize, use_screen_fonts()); + c = &cache[ci]; + + r = NSMakeRange(0, [c->layoutManager numberOfGlyphs]); + + if (![[NSView focusView] isFlipped]) + { + DPSscale(ctxt, 1, -1); + point.y = -point.y; + + /* + Adjust point.y so the lower left corner of the used rect is at the + point that was passed to us. + */ + point.y -= NSMaxY(c->usedRect); + + [NSFont _setFontFlipHack: YES]; + } + + [c->layoutManager drawBackgroundForGlyphRange: r + atPoint: point]; + + [c->layoutManager drawGlyphsForGlyphRange: r + atPoint: point]; } - - [c->layoutManager drawBackgroundForGlyphRange: r - atPoint: point]; - - [c->layoutManager drawGlyphsForGlyphRange: r - atPoint: point]; + NS_HANDLER + { + cache_unlock(); + [localException raise]; + } + NS_ENDHANDLER; + cache_unlock(); if (![[NSView focusView] isFlipped]) { @@ -581,49 +665,60 @@ NSAttributedString to do the job. if (rect.size.width <= 0 || rect.size.height <= 0) return; - - ci = cache_lookup_string(self, attrs, 1, rect.size, use_screen_fonts()); - c = &cache[ci]; - - /* - If the used rect fits completely in the rect we draw in, we save time - by avoiding the DPSrectclip (and the state save and restore). - - This isn't completely safe; the used rect isn't guaranteed to contain - all parts of all glyphs. - */ - if (c->usedRect.origin.x >= 0 && c->usedRect.origin.y <= 0 - && NSMaxX(c->usedRect) <= rect.size.width - && NSMaxY(c->usedRect) <= rect.size.height) - { - need_clip = NO; + + cache_lock(); + NS_DURING + { + ci = cache_lookup_string(self, attrs, 1, rect.size, use_screen_fonts()); + c = &cache[ci]; + + /* + If the used rect fits completely in the rect we draw in, we save time + by avoiding the DPSrectclip (and the state save and restore). + + This isn't completely safe; the used rect isn't guaranteed to contain + all parts of all glyphs. + */ + if (c->usedRect.origin.x >= 0 && c->usedRect.origin.y <= 0 + && NSMaxX(c->usedRect) <= rect.size.width + && NSMaxY(c->usedRect) <= rect.size.height) + { + need_clip = NO; + } + else + { + need_clip = YES; + DPSgsave(ctxt); + DPSrectclip(ctxt, rect.origin.x, rect.origin.y, + rect.size.width, rect.size.height); + } + + r = [c->layoutManager + glyphRangeForBoundingRect: NSMakeRect(0, 0, rect.size.width, + rect.size.height) + inTextContainer: c->textContainer]; + + if (![[NSView focusView] isFlipped]) + { + DPSscale(ctxt, 1, -1); + rect.origin.y = -NSMaxY(rect); + [NSFont _setFontFlipHack: YES]; + } + + [c->layoutManager drawBackgroundForGlyphRange: r + atPoint: rect.origin]; + + [c->layoutManager drawGlyphsForGlyphRange: r + atPoint: rect.origin]; } - else + NS_HANDLER { - need_clip = YES; - DPSgsave(ctxt); - DPSrectclip(ctxt, rect.origin.x, rect.origin.y, - rect.size.width, rect.size.height); + cache_unlock(); + [localException raise]; } - - r = [c->layoutManager - glyphRangeForBoundingRect: NSMakeRect(0, 0, rect.size.width, - rect.size.height) - inTextContainer: c->textContainer]; - - if (![[NSView focusView] isFlipped]) - { - DPSscale(ctxt, 1, -1); - rect.origin.y = -NSMaxY(rect); - [NSFont _setFontFlipHack: YES]; - } - - [c->layoutManager drawBackgroundForGlyphRange: r - atPoint: rect.origin]; - - [c->layoutManager drawGlyphsForGlyphRange: r - atPoint: rect.origin]; - + NS_ENDHANDLER; + cache_unlock(); + [NSFont _setFontFlipHack: NO]; if (![[NSView focusView] isFlipped]) { @@ -648,13 +743,29 @@ NSAttributedString to do the job. - (NSSize) sizeWithAttributes: (NSDictionary *)attrs { int ci; - ci = cache_lookup_string(self, attrs, 0, NSZeroSize, 1); - /* - An argument could be made for using NSMaxX/NSMaxY here, but that fails - horribly on right-aligned strings (which may be the right thing). For now, - we handle that case in a useful way and ignore indents. - */ - return cache[ci].usedRect.size; + NSSize result; + + cache_lock(); + NS_DURING + { + ci = cache_lookup_string(self, attrs, 0, NSZeroSize, 1); + /* + An argument could be made for using NSMaxX/NSMaxY here, but that fails + horribly on right-aligned strings (which may be the right thing). For now, + we handle that case in a useful way and ignore indents. + */ + + result = cache[ci].usedRect.size; + } + NS_HANDLER + { + cache_unlock(); + [localException raise]; + } + NS_ENDHANDLER; + cache_unlock(); + + return result; } - (NSRect) boundingRectWithSize: (NSSize)size @@ -662,15 +773,30 @@ NSAttributedString to do the job. attributes: (NSDictionary *)attributes { int ci; + NSRect result; - // FIXME: This ignores options - ci = cache_lookup_string(self, attributes, 1, size, 1); - /* - An argument could be made for using NSMaxX/NSMaxY here, but that fails - horribly on right-aligned strings (which may be the right thing). For now, - we handle that case in a useful way and ignore indents. - */ - return cache[ci].usedRect; + cache_lock(); + NS_DURING + { + // FIXME: This ignores options + ci = cache_lookup_string(self, attributes, 1, size, 1); + /* + An argument could be made for using NSMaxX/NSMaxY here, but that fails + horribly on right-aligned strings (which may be the right thing). For now, + we handle that case in a useful way and ignore indents. + */ + + result = cache[ci].usedRect; + } + NS_HANDLER + { + cache_unlock(); + [localException raise]; + } + NS_ENDHANDLER; + cache_unlock(); + + return result; } @end