- 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
This commit is contained in:
Bjorn Winckler
2007-08-10 20:13:57 +00:00
parent 4588872456
commit 4e599646f1
4 changed files with 113 additions and 153 deletions
+8 -9
View File
@@ -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
+89 -99
View File
@@ -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
@@ -525,24 +529,17 @@
@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)
+15 -44
View File
@@ -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).
+1 -1
View File
@@ -787,7 +787,7 @@ NSMutableArray *buildMenuAddress(NSMenu *menu)
{
if (!setupDone) return;
NSSize size = [textStorage calculateAverageFontSize];
NSSize size = [textStorage cellSize];
[[self window] setContentResizeIncrements:size];
}