From 99c29d1ca1f6b13a387b9adb482c570829212911 Mon Sep 17 00:00:00 2001 From: "bjorn.winckler" Date: Fri, 10 Aug 2007 20:13:57 +0000 Subject: [PATCH] - MMTextStorage ensures that all glyphs have the same width (or twice that, for wide chars) - MMTypesetter only has to make all line fragments the same height, no glyph placement is necessary anymore - MMTextStorage cleaned up now that cell size is fixed across fonts git-svn-id: http://macvim.googlecode.com/svn/trunk@119 96c4425d-ca35-0410-94e5-3396d5c13a8f --- MMTextStorage.h | 17 +++-- MMTextStorage.m | 188 ++++++++++++++++++++++++--------------------------- MMTypesetter.m | 59 ++++------------ MMWindowController.m | 2 +- 4 files changed, 113 insertions(+), 153 deletions(-) diff --git a/MMTextStorage.h b/MMTextStorage.h index 21aa47b8..dd7c7658 100644 --- a/MMTextStorage.h +++ b/MMTextStorage.h @@ -14,13 +14,13 @@ @interface MMTextStorage : NSTextStorage { - NSMutableAttributedString *attribString; - int maxRows, maxColumns; - int actualRows, actualColumns; - NSAttributedString *emptyRowString; - NSFont *font; - NSColor *defaultBackgroundColor; - //NSMutableParagraphStyle *paragraphStyle; + NSMutableAttributedString *attribString; + int maxRows, maxColumns; + int actualRows, actualColumns; + NSAttributedString *emptyRowString; + NSFont *font; + NSColor *defaultBackgroundColor; + NSSize cellSize; } - (NSString *)string; @@ -51,13 +51,12 @@ - (void)setFont:(NSFont*)newFont; - (NSFont*)font; - (NSSize)size; -- (NSSize)calculateAverageFontSize; +- (NSSize)cellSize; - (NSRect)rectForRowsInRange:(NSRange)range; - (NSRect)rectForColumnsInRange:(NSRange)range; - (unsigned)offsetFromRow:(int)row column:(int)col; - (BOOL)resizeToFitSize:(NSSize)size; - (NSSize)fitToSize:(NSSize)size; - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns; -- (float)cellWidth; @end diff --git a/MMTextStorage.m b/MMTextStorage.m index 08610b13..4b174fc4 100644 --- a/MMTextStorage.m +++ b/MMTextStorage.m @@ -22,15 +22,10 @@ #define DRAW_ITALIC 0x10 /* draw italic text */ -//static float LINEHEIGHT = 30.0f; - - @interface MMTextStorage (Private) -- (void)doSetMaxRows:(int)rows columns:(int)cols; - (void)lazyResize; -- (float)widthOfEmptyRow; @end @@ -42,15 +37,10 @@ if ((self = [super init])) { attribString = [[NSMutableAttributedString alloc] initWithString:@""]; // NOTE! It does not matter which font is set here, Vim will set its - // own font on startup anyway. + // own font on startup anyway. Just set some bogus values. font = [[NSFont userFixedPitchFontOfSize:0] retain]; - -#if 0 - paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - [paragraphStyle setMinimumLineHeight:LINEHEIGHT]; - [paragraphStyle setMaximumLineHeight:LINEHEIGHT]; - [paragraphStyle setLineSpacing:0]; -#endif + cellSize.height = [font pointSize]; + cellSize.width = [font defaultLineHeightForFont]; } return self; @@ -61,7 +51,6 @@ //NSLog(@"%@ %s", [self className], _cmd); [emptyRowString release]; - //[paragraphStyle release]; [font release]; [defaultBackgroundColor release]; [attribString release]; @@ -75,34 +64,69 @@ } - (NSDictionary *)attributesAtIndex:(unsigned)index - effectiveRange:(NSRangePointer)aRange + effectiveRange:(NSRangePointer)range { //NSLog(@"%s", _cmd); if (index>=[attribString length]) { //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index); - if (aRange) { - *aRange = NSMakeRange(NSNotFound, 0); + if (range) { + *range = NSMakeRange(NSNotFound, 0); } return [NSDictionary dictionary]; } - return [attribString attributesAtIndex:index effectiveRange:aRange]; + return [attribString attributesAtIndex:index effectiveRange:range]; } -- (void)replaceCharactersInRange:(NSRange)aRange - withString:(NSString *)aString +- (void)replaceCharactersInRange:(NSRange)range + withString:(NSString *)string { - //NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", aRange.location, - // aRange.length, aString); + //NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", range.location, + // range.length, string); NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd); - //[attribString replaceCharactersInRange:aRange withString:aString]; + //[attribString replaceCharactersInRange:range withString:string]; } -- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)aRange +- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range { // NOTE! This method must be implemented since the text system calls it // constantly to 'fix attributes', apply font substitution, etc. - [attribString setAttributes:attributes range:aRange]; +#if 0 + [attribString setAttributes:attributes range:range]; +#else + // HACK! If the font attribute is being modified, then ensure that the new + // font has a fixed advancement which is either the same as the current + // font or twice that, depending on whether it is a 'wide' character that + // is being fixed or not. This code really only works if 'range' has + // length 1 or 2. + NSFont *newFont = [attributes objectForKey:NSFontAttributeName]; + if (newFont) { + float adv = cellSize.width; + if ([attribString length] > range.location+1) { + // If the first char is followed by zero-width space, then it is a + // 'wide' character, so double the advancement. + NSString *string = [attribString string]; + if ([string characterAtIndex:range.location+1] == 0x200b) + adv += adv; + } + + // Create a new font which has the 'fixed advance attribute' set. + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil]; + NSFontDescriptor *desc = [newFont fontDescriptor]; + desc = [desc fontDescriptorByAddingAttributes:dict]; + newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]]; + + // Now modify the 'attributes' dictionary to hold the new font. + NSMutableDictionary *newAttr = [NSMutableDictionary + dictionaryWithDictionary:attributes]; + [newAttr setObject:newFont forKey:NSFontAttributeName]; + + [attribString setAttributes:newAttr range:range]; + } else { + [attribString setAttributes:attributes range:range]; + } +#endif } - (int)maxRows @@ -123,6 +147,7 @@ - (void)setMaxRows:(int)rows columns:(int)cols { + // NOTE: Just remember the new values, the actual resizing is done lazily. maxRows = rows; maxColumns = cols; } @@ -153,7 +178,6 @@ NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, - //paragraphStyle, NSParagraphStyleAttributeName, #if !HEED_DRAW_TRANSP bg, NSBackgroundColorAttributeName, #endif @@ -192,12 +216,6 @@ value:value range:range]; } -#if 0 - [attribString addAttribute:NSParagraphStyleAttributeName - value:paragraphStyle - range:NSMakeRange(0, [attribString length])]; -#endif - [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes) range:range changeInLength:0]; } @@ -352,10 +370,29 @@ - (void)setFont:(NSFont*)newFont { if (newFont && font != newFont) { - //NSLog(@"Setting font %@", newFont); [font release]; - font = [newFont retain]; - // TODO! Change paragraph style to match line height of new font + + // NOTE! When setting a new font we make sure that the advancement of + // each glyph is fixed. + + float em = [newFont widthOfString:@"m"]; + float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults] + floatForKey:MMCellWidthMultiplierKey]; + + cellSize.width = em * cellWidthMultiplier; + + NSDictionary *dict = [NSDictionary + dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width] + forKey:NSFontFixedAdvanceAttribute]; + NSFontDescriptor *desc = [newFont fontDescriptor]; + desc = [desc fontDescriptorByAddingAttributes:dict]; + + font = [NSFont fontWithDescriptor:desc size:[newFont pointSize]]; + [font retain]; + + NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0]; + cellSize.height = lm ? [lm defaultLineHeightForFont:font] + : [font defaultLineHeightForFont]; } } @@ -366,46 +403,25 @@ - (NSSize)size { - if (![[self layoutManagers] count]) return NSZeroSize; - NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0]; - - float h = [lm defaultLineHeightForFont:font]; - NSSize size = NSMakeSize([self cellWidth]*maxColumns, h*maxRows); - - return size; + return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height); } -- (NSSize)calculateAverageFontSize +- (NSSize)cellSize { - if (![[self layoutManagers] count]) return NSZeroSize; - - NSSize size; - NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0]; - size.height = [lm defaultLineHeightForFont:font]; - size.width = [self cellWidth]; - if (size.height < 1.0f) size.height = 1.0f; - if (size.width < 1.0f) size.width = 1.0f; - - return size; + return cellSize; } - (NSRect)rectForRowsInRange:(NSRange)range { - if (![[self layoutManagers] count]) return NSZeroRect; - - // TODO! Take range.location into account when computing height (in case - // the line height varies). NSRect rect = { 0, 0, 0, 0 }; - NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0]; - float fontHeight = [lm defaultLineHeightForFont:font]; - unsigned start = range.location > maxRows ? maxRows : range.location; unsigned length = range.length; + if (start+length > maxRows) length = maxRows - start; - rect.origin.y = fontHeight * start; - rect.size.height = fontHeight * length; + rect.origin.y = cellSize.height * start; + rect.size.height = cellSize.height * length; return rect; } @@ -413,15 +429,14 @@ - (NSRect)rectForColumnsInRange:(NSRange)range { NSRect rect = { 0, 0, 0, 0 }; - float fontWidth = [self cellWidth]; - unsigned start = range.location > maxColumns ? maxColumns : range.location; unsigned length = range.length; + if (start+length > maxColumns) length = maxColumns - start; - rect.origin.x = fontWidth * start; - rect.size.width = fontWidth * length; + rect.origin.x = cellSize.width * start; + rect.size.width = cellSize.width * length; return rect; } @@ -459,9 +474,6 @@ - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns { - if (![[self layoutManagers] count]) return size; - NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0]; - NSSize curSize = [self size]; NSSize fitSize = curSize; int fitRows = maxRows; @@ -472,11 +484,12 @@ // 'size'. However, always make sure there are at least 3 lines in the // text storage. (Why 3? It seem Vim never allows less than 3 lines.) // - // TODO: Use binary search instead of the current linear one. + // TODO: No need to search since line height is fixed, just calculate + // the new height. int rowCount = maxRows; int rowsToRemove; for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) { - float height = [lm defaultLineHeightForFont:font]*rowCount; + float height = cellSize.height*rowCount; if (height <= size.height) { fitSize.height = height; @@ -488,7 +501,7 @@ fitRows -= rowsToRemove; } else if (size.height > curSize.height) { - float fh = [lm defaultLineHeightForFont:font]; + float fh = cellSize.height; if (fh < 1.0f) fh = 1.0f; fitRows = floor(size.height/fh); @@ -496,7 +509,7 @@ } if (size.width != curSize.width) { - float fw = [self cellWidth]; + float fw = cellSize.width; if (fw < 1.0f) fw = 1.0f; fitCols = floor(size.width/fw); @@ -509,15 +522,6 @@ return fitSize; } -- (float)cellWidth -{ - float em = [font widthOfString:@"m"]; - float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults] - floatForKey:MMCellWidthMultiplierKey]; - - return em * cellWidthMultiplier; -} - @end // MMTextStorage @@ -526,23 +530,16 @@ @implementation MMTextStorage (Private) - (void)lazyResize { - if (actualRows != maxRows || actualColumns != maxColumns) { - [self doSetMaxRows:maxRows columns:maxColumns]; - } -} - -- (void)doSetMaxRows:(int)rows columns:(int)cols -{ int i; // Do nothing if the dimensions are already right. - if (actualRows == rows && actualColumns == cols) + if (actualRows == maxRows && actualColumns == maxColumns) return; NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1)); - maxRows = rows; - maxColumns = cols; + actualRows = maxRows; + actualColumns = maxColumns; NSDictionary *dict; if (defaultBackgroundColor) { @@ -573,13 +570,6 @@ NSRange fullRange = NSMakeRange(0, [attribString length]); [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes) range:oldRange changeInLength:fullRange.length-oldRange.length]; - - actualRows = rows; actualColumns = cols; -} - -- (float)widthOfEmptyRow -{ - return [font widthOfString:[emptyRowString string]]; } @end // MMTextStorage (Private) diff --git a/MMTypesetter.m b/MMTypesetter.m index 241f5042..ed4e778b 100644 --- a/MMTypesetter.m +++ b/MMTypesetter.m @@ -25,17 +25,14 @@ @implementation MMTypesetter // -// Layout glyphs so that each glyph takes up exactly one cell. +// Layout glyphs so that each line fragment has a fixed size. // -// The width of a cell is determined by [MMTextStorage cellWidth] (which -// typically sets one cell to equal the width of 'm' in the current font), and -// the height of a cell is given by the default line height for the current -// font. -// -// It is assumed that the text storage is set up so that each wide character is -// followed by a 'zero-width space' character (Unicode 0x200b); these are not -// rendered. If a wide character is not followed by a zero-width space, then -// the next character will render on top of it. +// It is assumed that the font for each character has been chosen so that every +// glyph has the right advancement (either 2*cellSize.width or half that, +// depending on whether it is a wide character or not). This is taken care of +// by MMTextStorage in setAttributes:range: and in setFont:. All that is left +// for the typesetter to do is to make sure each line fragment has the same +// height and that unwanted glyphs are hidden. // - (void)layoutGlyphsInLayoutManager:(NSLayoutManager *)lm startingAtGlyphIndex:(unsigned)startGlyphIdx @@ -49,8 +46,7 @@ NSFont *font = [ts font]; NSString *text = [ts string]; unsigned textLen = [text length]; - float cellWidth = [ts cellWidth]; - float cellHeight = [lm defaultLineHeightForFont:font]; + NSSize cellSize = [ts cellSize]; float baseline = [font descender]; if (!(ts && tv && tc && font && text && textLen)) @@ -58,8 +54,6 @@ float baselineOffset = [[NSUserDefaults standardUserDefaults] floatForKey:MMBaselineOffsetKey]; - BOOL centerGlyphs = [[NSUserDefaults standardUserDefaults] - boolForKey:MMCenterGlyphsKey]; baseline += baselineOffset; @@ -67,7 +61,9 @@ unsigned i, numberOfLines = 0, firstLine = 0; NSRange firstLineRange = { 0, 0 }; - // Find first line and its range, and count the number of lines. + // Find the first line and its range, and count the number of lines. (This + // info could also be gleaned from MMTextStorage, but we do it here anyway + // to make absolutely sure everything is right.) for (i = 0; i < textLen; numberOfLines++) { NSRange lineRange = [text lineRangeForRange:NSMakeRange(i, 0)]; if (NSLocationInRange(startCharIdx, lineRange)) { @@ -84,10 +80,10 @@ for (i = 0; i < maxNumLines && lineRange.length; ++i) { NSRange glyphRange = [lm glyphRangeForCharacterRange:lineRange actualCharacterRange:nil]; - NSRect lineRect = { 0, (firstLine+i)*cellHeight, - cellWidth*(lineRange.length-1), cellHeight }; + NSRect lineRect = { 0, (firstLine+i)*cellSize.height, + cellSize.width*(lineRange.length-1), cellSize.height }; unsigned endLineIdx = NSMaxRange(lineRange); - NSPoint glyphPt = { 0, cellHeight+baseline }; + NSPoint glyphPt = { 0, cellSize.height+baseline }; unsigned j; endGlyphIdx = NSMaxRange(glyphRange); @@ -95,32 +91,7 @@ [lm setTextContainer:tc forGlyphRange:glyphRange]; [lm setLineFragmentRect:lineRect forGlyphRange:glyphRange usedRect:lineRect]; - - if (centerGlyphs) { - // Center each glyph inside its cell. (Optional) - // + Proportional fonts look better. - // - The cursor changes width depending on which glyph it is over - // and selections look uneven. - for (j = glyphRange.location; j < endGlyphIdx; ++j) { - NSGlyph glyph = [lm glyphAtIndex:j]; - NSSize adv = [font advancementForGlyph:glyph]; - NSPoint pt = glyphPt; - if (adv.width > 0 && adv.width < cellWidth) { - pt.x += .5*(cellWidth-adv.width); - } - [lm setLocation:pt forStartOfGlyphRange:NSMakeRange(j, 1)]; - glyphPt.x += cellWidth; - } - } else { - // Position each glyph individually to ensure they take up exactly - // one cell. (Default) - // + The cursor and selections look good - // - Proportional fonts look bad (try entering 'Wi') - for (j = glyphRange.location; j < endGlyphIdx; ++j) { - [lm setLocation:glyphPt forStartOfGlyphRange:NSMakeRange(j, 1)]; - glyphPt.x += cellWidth; - } - } + [lm setLocation:glyphPt forStartOfGlyphRange:glyphRange]; // Hide end-of-line and non-zero space characters (there is one after // every wide character). diff --git a/MMWindowController.m b/MMWindowController.m index bc33cc09..aa511e60 100644 --- a/MMWindowController.m +++ b/MMWindowController.m @@ -787,7 +787,7 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) { if (!setupDone) return; - NSSize size = [textStorage calculateAverageFontSize]; + NSSize size = [textStorage cellSize]; [[self window] setContentResizeIncrements:size]; } -- 2.11.4.GIT