-
Notifications
You must be signed in to change notification settings - Fork 398
/
MMTypesetter.m
190 lines (152 loc) · 6.1 KB
/
MMTypesetter.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* vi:set ts=8 sts=4 sw=4 ft=objc:
*
* VIM - Vi IMproved by Bram Moolenaar
* MacVim GUI port by Bjorn Winckler
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* MMTypesetter
*
* Ensures that each line has a fixed height and deals with some baseline
* issues.
*/
#import "MMTextStorage.h"
#import "MMTypesetter.h"
#import "Miscellaneous.h"
@implementation MMTypesetter
- (void)willSetLineFragmentRect:(NSRectPointer)lineRect
forGlyphRange:(NSRange)glyphRange
usedRect:(NSRectPointer)usedRect
baselineOffset:(float *)baselineOffset
{
MMTextStorage *ts = (MMTextStorage*)[[self layoutManager] textStorage];
float h = [ts cellSize].height;
// HACK! Force each line fragment rect to have a fixed height. By also
// forcing the 'usedRect' to the same height we also ensure that the cursor
// is as high as the line itself.
lineRect->size.height = h;
usedRect->size.height = h;
// See [MMTextStorage setLinespace:] for info on how 'linespace' support
// works.
*baselineOffset += floor(.5*[ts linespace]);
}
#if 0
- (void)setNotShownAttribute:(BOOL)flag forGlyphRange:(NSRange)glyphRange
{
if (1 != glyphRange.length)
return;
NSLayoutManager *lm = [self layoutManager];
unsigned charIdx = [lm characterIndexForGlyphAtIndex:glyphRange.location];
if ('\n' == [[[lm textStorage] string] characterAtIndex:charIdx])
[lm setNotShownAttribute:flag forGlyphAtIndex:glyphRange.location];
}
#endif
#if 0
- (NSTypesetterControlCharacterAction)
actionForControlCharacterAtIndex:(unsigned)charIndex
{
//NSLog(@"%s%d", _cmd, charIndex);
/*NSTextStorage *ts = [[self layoutManager] textStorage];
if ('\n' == [[ts string] characterAtIndex:charIndex])
return NSTypesetterLineBreakAction;*/
return NSTypesetterWhitespaceAction;
}
#endif
#if 0
- (void)setLocation:(NSPoint)location
withAdvancements:(const float *)advancements
forStartOfGlyphRange:(NSRange)glyphRange
{
NSLog(@"setLocation:%@ withAdvancements:%f forStartOfGlyphRange:%@",
NSStringFromPoint(location), advancements ? *advancements : 0,
NSStringFromRange(glyphRange));
[super setLocation:location withAdvancements:advancements
forStartOfGlyphRange:glyphRange];
}
#endif
@end // MMTypesetter
@implementation MMTypesetter2
//
// Layout glyphs so that each line fragment has a fixed size.
//
// 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 EOL glyphs are hidden.
//
- (void)layoutGlyphsInLayoutManager:(NSLayoutManager *)lm
startingAtGlyphIndex:(unsigned)startGlyphIdx
maxNumberOfLineFragments:(unsigned)maxNumLines
nextGlyphIndex:(unsigned *)nextGlyph
{
// TODO: Check that it really is an MMTextStorage?
MMTextStorage *ts = (MMTextStorage*)[lm textStorage];
NSTextContainer *tc = [[lm firstTextView] textContainer];
NSFont *font = [ts font];
NSString *text = [ts string];
if (!(lm && ts && tc && font && text
&& [lm isValidGlyphIndex:startGlyphIdx]))
return;
// Note that we always start laying out lines from the beginning of a line,
// even if 'startCharIdx' may be somewhere in the middle.
unsigned startCharIdx = [lm characterIndexForGlyphAtIndex:startGlyphIdx];
if (startCharIdx >= [text length])
return;
[lm setTextContainer:tc forGlyphRange:
[lm glyphRangeForCharacterRange:NSMakeRange(0, [text length])
actualCharacterRange:nil]];
//
// STEP 1: Locate the line containing 'startCharIdx'.
//
MMRowCacheEntry *cache = [ts rowCache];
unsigned lineIdx = 0, nextLineIdx = 0;
int actualRows = [ts actualRows];
int line = 0;
for (; line < actualRows; ++line, ++cache) {
lineIdx = nextLineIdx;
nextLineIdx += cache->length;
if (startCharIdx < nextLineIdx)
break;
}
//
// STEP 2: Generate line fragment rects one line at a time until there are
// no more lines in the text storage, or until 'maxNumLines' have been
// exhausted. (There is no point in just laying out one line, the layout
// manager will keep calling this method until there are no more lines in
// the text storage.)
//
// NOTE: With non-zero linespace the baseline is adjusted so that the text
// is centered within a line.
float baseline = [font descender] - floor(.5*[ts linespace])
+ [[NSUserDefaults standardUserDefaults]
floatForKey:MMBaselineOffsetKey];
NSSize cellSize = [ts cellSize];
NSPoint glyphPt = { 0, cellSize.height+baseline };
NSRange lineRange = { lineIdx, 0 };
NSRange glyphRange = { startGlyphIdx, 0 };
NSRect lineRect = { 0, line*cellSize.height,
[ts actualColumns]*cellSize.width, cellSize.height };
int endLine = line + maxNumLines;
if (endLine > actualRows)
endLine = actualRows;
for (; line < endLine; ++line, ++cache) {
lineRange.length = cache->length;
glyphRange = [lm glyphRangeForCharacterRange:lineRange
actualCharacterRange:nil];
[lm setLineFragmentRect:lineRect forGlyphRange:glyphRange
usedRect:lineRect];
[lm setLocation:glyphPt forStartOfGlyphRange:glyphRange];
lineRange.location += lineRange.length;
lineRect.origin.y += cellSize.height;
// Hide EOL character (otherwise a square will be rendered).
[lm setNotShownAttribute:YES forGlyphAtIndex:lineRange.location-1];
}
if (nextGlyph)
*nextGlyph = NSMaxRange(glyphRange);
}
@end // MMTypesetter2