Skip to content

Commit 4784ab8

Browse files
committed
refactor: extract a utils file
1 parent aff020e commit 4784ab8

3 files changed

Lines changed: 183 additions & 136 deletions

File tree

ios/styles/OrderedListStyle.mm

Lines changed: 16 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
#import "EnrichedTextInputView.h"
2+
#import "OrderedListMetricsUtils.h"
23
#import "RangeUtils.h"
34
#import "StyleHeaders.h"
45
#import "StyleUtils.h"
56
#import "TextInsertionUtils.h"
67

7-
@implementation OrderedListStyle {
8-
// we don't want to re-measure each marker's actual
9-
// width. We estimate the width with cached metrics instead
10-
UIFont *_cachedMarkerFont;
11-
CGFloat _cachedDigitWidth;
12-
CGFloat _cachedDotWidth;
13-
}
8+
@implementation OrderedListStyle
149

1510
+ (StyleType)getType {
1611
return OrderedList;
@@ -37,8 +32,10 @@ - (void)applyStyling:(NSRange)range {
3732
// re-indent all of it - even when only a single paragraph is dirty (e.g. an
3833
// item was just added)
3934
NSInteger itemCount = 0;
40-
NSRange listRange = [self contiguousOrderedListRangeContaining:range
41-
itemCount:&itemCount];
35+
NSRange listRange =
36+
[OrderedListMetricsUtils contiguousOrderedListRangeContaining:range
37+
forStyle:self
38+
itemCount:&itemCount];
4239

4340
[self applyIndentForListRange:listRange itemCount:itemCount];
4441
}
@@ -85,52 +82,20 @@ - (void)recalculateListsAroundEditedRange:(NSRange)range {
8582
}
8683

8784
NSInteger itemCount = 0;
88-
NSRange listRange =
89-
[self contiguousOrderedListRangeContaining:NSMakeRange(seed, 0)
90-
itemCount:&itemCount];
85+
NSRange listRange = [OrderedListMetricsUtils
86+
contiguousOrderedListRangeContaining:NSMakeRange(seed, 0)
87+
forStyle:self
88+
itemCount:&itemCount];
9189
[handled addObject:[NSValue valueWithRange:listRange]];
9290
[self applyIndentForListRange:listRange itemCount:itemCount];
9391
}
9492
}
9593

96-
- (void)ensureMarkerMetricsForFont:(UIFont *)font {
97-
if (_cachedMarkerFont != nil && [_cachedMarkerFont isEqual:font]) {
98-
return;
99-
}
100-
_cachedMarkerFont = font;
101-
NSDictionary *attrs = @{NSFontAttributeName : font};
102-
_cachedDigitWidth = [@"0" sizeWithAttributes:attrs].width;
103-
_cachedDotWidth = [@"." sizeWithAttributes:attrs].width;
104-
}
105-
106-
- (NSInteger)digitCountOf:(NSInteger)n {
107-
NSInteger count = 1;
108-
NSInteger value = MAX(n, 1);
109-
while (value >= 10) {
110-
value /= 10;
111-
count += 1;
112-
}
113-
return count;
114-
}
115-
116-
// computes the shared marker-column indent for a list of the given item
117-
// count. The largest marker value equals the item count (numbering starts
118-
// at 1); if its width overflows the configured margin we expand to fit it
119-
- (CGFloat)headIndentForItemCount:(NSInteger)itemCount {
120-
UIFont *markerFont = [self.host.config orderedListMarkerFont];
121-
[self ensureMarkerMetricsForFont:markerFont];
122-
123-
NSInteger digitCount = [self digitCountOf:itemCount];
124-
CGFloat widestMarkerWidth = digitCount * _cachedDigitWidth + _cachedDotWidth;
125-
126-
CGFloat markerColumnWidth =
127-
MAX([self.host.config orderedListMarginLeft], widestMarkerWidth);
128-
return markerColumnWidth + [self.host.config orderedListGapWidth];
129-
}
130-
13194
- (void)applyIndentForListRange:(NSRange)listRange
13295
itemCount:(NSInteger)itemCount {
133-
CGFloat listHeadIndent = [self headIndentForItemCount:itemCount];
96+
CGFloat listHeadIndent =
97+
[OrderedListMetricsUtils headIndentForItemCount:itemCount
98+
config:self.host.config];
13499

135100
[self.host.textView.textStorage
136101
enumerateAttribute:NSParagraphStyleAttributeName
@@ -185,99 +150,14 @@ - (void)applyStylingToTypingAttrs:(NSMutableDictionary *)attributes {
185150
pStyle.headIndent = existingStyle.headIndent;
186151
pStyle.firstLineHeadIndent = existingStyle.firstLineHeadIndent;
187152
} else {
188-
CGFloat fallbackIndent = [self headIndentForItemCount:1];
153+
CGFloat fallbackIndent =
154+
[OrderedListMetricsUtils headIndentForItemCount:1
155+
config:self.host.config];
189156
pStyle.headIndent = fallbackIndent;
190157
pStyle.firstLineHeadIndent = fallbackIndent;
191158
}
192159

193160
attributes[NSParagraphStyleAttributeName] = pStyle;
194161
}
195162

196-
// walks paragraphs backward and forward from the given range to find the full
197-
// contiguous run of ordered-list items it belongs to, and counts them
198-
- (NSRange)contiguousOrderedListRangeContaining:(NSRange)range
199-
itemCount:(NSInteger *)outCount {
200-
NSString *fullText = self.host.textView.textStorage.string;
201-
NSUInteger length = fullText.length;
202-
if (length == 0) {
203-
if (outCount != nullptr) {
204-
*outCount = 0;
205-
}
206-
return NSMakeRange(range.location, 0);
207-
}
208-
209-
NSTextStorage *textStorage = self.host.textView.textStorage;
210-
NSRange fullRange = NSMakeRange(0, length);
211-
NSUInteger seedLocation = MIN(range.location, length - 1);
212-
213-
NSRange seedRun;
214-
[textStorage attribute:NSParagraphStyleAttributeName
215-
atIndex:seedLocation
216-
longestEffectiveRange:&seedRun
217-
inRange:fullRange];
218-
219-
NSUInteger firstParagraphStart = seedRun.location;
220-
NSUInteger lastParagraphEnd = NSMaxRange(seedRun);
221-
222-
// seek backward over preceding ordered-list runs
223-
while (firstParagraphStart > 0) {
224-
if (![self detect:NSMakeRange(firstParagraphStart - 1, 0)]) {
225-
break;
226-
}
227-
NSRange previousRun;
228-
[textStorage attribute:NSParagraphStyleAttributeName
229-
atIndex:firstParagraphStart - 1
230-
longestEffectiveRange:&previousRun
231-
inRange:fullRange];
232-
firstParagraphStart = previousRun.location;
233-
}
234-
235-
// seek forward over following ordered-list runs
236-
while (lastParagraphEnd < length) {
237-
if (![self detect:NSMakeRange(lastParagraphEnd, 0)]) {
238-
break;
239-
}
240-
NSRange nextRun;
241-
[textStorage attribute:NSParagraphStyleAttributeName
242-
atIndex:lastParagraphEnd
243-
longestEffectiveRange:&nextRun
244-
inRange:fullRange];
245-
lastParagraphEnd = NSMaxRange(nextRun);
246-
}
247-
248-
NSRange listRange =
249-
NSMakeRange(firstParagraphStart, lastParagraphEnd - firstParagraphStart);
250-
251-
if (outCount != nullptr) {
252-
*outCount =
253-
[self countParagraphsInRange:listRange
254-
inText:self.host.textView.textStorage.string];
255-
}
256-
return listRange;
257-
}
258-
259-
// counts paragraphs (newline-delimited) within a range that is already known
260-
// to start and end exactly on paragraph boundaries
261-
- (NSInteger)countParagraphsInRange:(NSRange)listRange inText:(NSString *)text {
262-
if (listRange.length == 0) {
263-
return 0;
264-
}
265-
266-
NSCharacterSet *newlineSet = [NSCharacterSet newlineCharacterSet];
267-
NSUInteger rangeEnd = NSMaxRange(listRange);
268-
NSUInteger cursor = listRange.location;
269-
NSInteger count = 0;
270-
271-
while (cursor < rangeEnd) {
272-
count += 1;
273-
NSRange newline =
274-
[text rangeOfCharacterFromSet:newlineSet
275-
options:0
276-
range:NSMakeRange(cursor, rangeEnd - cursor)];
277-
cursor = newline.location != NSNotFound ? NSMaxRange(newline) : rangeEnd;
278-
}
279-
280-
return count;
281-
}
282-
283163
@end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
#import "StyleHeaders.h"
3+
#import <UIKit/UIKit.h>
4+
5+
@interface OrderedListMetricsUtils : NSObject
6+
7+
+ (NSInteger)digitCountOf:(NSInteger)n;
8+
9+
+ (CGFloat)headIndentForItemCount:(NSInteger)itemCount
10+
config:(EnrichedConfig *)config;
11+
12+
+ (NSRange)contiguousOrderedListRangeContaining:(NSRange)range
13+
forStyle:(OrderedListStyle *)style
14+
itemCount:(NSInteger *)outCount;
15+
16+
@end
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#import "OrderedListMetricsUtils.h"
2+
3+
typedef struct {
4+
CGFloat digitWidth;
5+
CGFloat dotWidth;
6+
} OrderedListMarkerMetrics;
7+
8+
static NSMutableDictionary<UIFont *, NSValue *> *markerMetricsCache(void) {
9+
static NSMutableDictionary<UIFont *, NSValue *> *cache;
10+
static dispatch_once_t onceToken;
11+
dispatch_once(&onceToken, ^{
12+
cache = [NSMutableDictionary dictionary];
13+
});
14+
return cache;
15+
}
16+
17+
static OrderedListMarkerMetrics markerMetricsForFont(UIFont *font) {
18+
NSMutableDictionary<UIFont *, NSValue *> *cache = markerMetricsCache();
19+
NSValue *cached = cache[font];
20+
OrderedListMarkerMetrics metrics;
21+
if (cached != nil) {
22+
[cached getValue:&metrics];
23+
return metrics;
24+
}
25+
26+
NSDictionary *attrs = @{NSFontAttributeName : font};
27+
metrics.digitWidth = [@"0" sizeWithAttributes:attrs].width;
28+
metrics.dotWidth = [@"." sizeWithAttributes:attrs].width;
29+
30+
cache[font] = [NSValue value:&metrics
31+
withObjCType:@encode(OrderedListMarkerMetrics)];
32+
return metrics;
33+
}
34+
35+
static NSInteger digitCountOf(NSInteger n) {
36+
NSInteger count = 1;
37+
NSInteger value = MAX(n, 1);
38+
while (value >= 10) {
39+
value /= 10;
40+
count += 1;
41+
}
42+
return count;
43+
}
44+
45+
// counts paragraphs (newline-delimited) within a range that is already known
46+
// to start and end exactly on paragraph boundaries
47+
static NSInteger countParagraphsInRange(NSRange listRange, NSString *text) {
48+
if (listRange.length == 0) {
49+
return 0;
50+
}
51+
52+
NSCharacterSet *newlineSet = [NSCharacterSet newlineCharacterSet];
53+
NSUInteger rangeEnd = NSMaxRange(listRange);
54+
NSUInteger cursor = listRange.location;
55+
NSInteger count = 0;
56+
57+
while (cursor < rangeEnd) {
58+
count += 1;
59+
NSRange newline =
60+
[text rangeOfCharacterFromSet:newlineSet
61+
options:0
62+
range:NSMakeRange(cursor, rangeEnd - cursor)];
63+
cursor = newline.location != NSNotFound ? NSMaxRange(newline) : rangeEnd;
64+
}
65+
66+
return count;
67+
}
68+
69+
@implementation OrderedListMetricsUtils
70+
71+
+ (NSInteger)digitCountOf:(NSInteger)n {
72+
return digitCountOf(n);
73+
}
74+
75+
+ (CGFloat)headIndentForItemCount:(NSInteger)itemCount
76+
config:(EnrichedConfig *)config {
77+
// we don't want to re-measure each marker's actual
78+
// width. We estimate the width with cached metrics instead
79+
OrderedListMarkerMetrics metrics =
80+
markerMetricsForFont([config orderedListMarkerFont]);
81+
82+
NSInteger digitCount = digitCountOf(itemCount);
83+
CGFloat widestMarkerWidth =
84+
digitCount * metrics.digitWidth + metrics.dotWidth;
85+
86+
CGFloat markerColumnWidth =
87+
MAX([config orderedListMarginLeft], widestMarkerWidth);
88+
return markerColumnWidth + [config orderedListGapWidth];
89+
}
90+
91+
+ (NSRange)contiguousOrderedListRangeContaining:(NSRange)range
92+
forStyle:(OrderedListStyle *)style
93+
itemCount:(NSInteger *)outCount {
94+
NSString *fullText = style.host.textView.textStorage.string;
95+
NSUInteger length = fullText.length;
96+
if (length == 0) {
97+
if (outCount != nullptr) {
98+
*outCount = 0;
99+
}
100+
return NSMakeRange(range.location, 0);
101+
}
102+
103+
NSTextStorage *textStorage = style.host.textView.textStorage;
104+
NSRange fullRange = NSMakeRange(0, length);
105+
NSUInteger seedLocation = MIN(range.location, length - 1);
106+
107+
NSRange seedRun;
108+
[textStorage attribute:NSParagraphStyleAttributeName
109+
atIndex:seedLocation
110+
longestEffectiveRange:&seedRun
111+
inRange:fullRange];
112+
113+
NSUInteger firstParagraphStart = seedRun.location;
114+
NSUInteger lastParagraphEnd = NSMaxRange(seedRun);
115+
116+
// seek backward over preceding ordered-list runs
117+
while (firstParagraphStart > 0) {
118+
if (![style detect:NSMakeRange(firstParagraphStart - 1, 0)]) {
119+
break;
120+
}
121+
NSRange previousRun;
122+
[textStorage attribute:NSParagraphStyleAttributeName
123+
atIndex:firstParagraphStart - 1
124+
longestEffectiveRange:&previousRun
125+
inRange:fullRange];
126+
firstParagraphStart = previousRun.location;
127+
}
128+
129+
// seek forward over following ordered-list runs
130+
while (lastParagraphEnd < length) {
131+
if (![style detect:NSMakeRange(lastParagraphEnd, 0)]) {
132+
break;
133+
}
134+
NSRange nextRun;
135+
[textStorage attribute:NSParagraphStyleAttributeName
136+
atIndex:lastParagraphEnd
137+
longestEffectiveRange:&nextRun
138+
inRange:fullRange];
139+
lastParagraphEnd = NSMaxRange(nextRun);
140+
}
141+
142+
NSRange listRange =
143+
NSMakeRange(firstParagraphStart, lastParagraphEnd - firstParagraphStart);
144+
145+
if (outCount != nullptr) {
146+
*outCount = countParagraphsInRange(listRange, fullText);
147+
}
148+
return listRange;
149+
}
150+
151+
@end

0 commit comments

Comments
 (0)