Skip to content

Commit f412b2e

Browse files
committed
refactor: move logic to ItalicStyle.mm
1 parent 23961d2 commit f412b2e

3 files changed

Lines changed: 175 additions & 190 deletions

File tree

ios/styles/ItalicStyle.mm

Lines changed: 175 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
#import "EnrichedTextInputView.h"
2-
#import "ItalicUtils.h"
2+
#import "FontExtension.h"
33
#import "StyleHeaders.h"
4+
#import <CoreText/CoreText.h>
5+
6+
// slant used when a font has no italic face
7+
static const CGFloat kObliquenessFallback = 0.2;
8+
9+
typedef NS_ENUM(NSInteger, ItalicKind) {
10+
// character must not be slanted at all (whitespace, control characters,
11+
// text attachments)
12+
ItalicKindNone,
13+
// font has a real italic glyph for the character
14+
ItalicKindFont,
15+
// no italic glyph available, the slant has to be used
16+
ItalicKindOblique,
17+
};
18+
19+
static NSCharacterSet *NonNeutralCharacters(void) {
20+
static NSCharacterSet *nonNeutral = nil;
21+
static dispatch_once_t onceToken;
22+
dispatch_once(&onceToken, ^{
23+
NSMutableCharacterSet *set =
24+
[[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
25+
[set formUnionWithCharacterSet:[NSCharacterSet controlCharacterSet]];
26+
// ZWS
27+
[set addCharactersInString:[NSString
28+
stringWithFormat:@"%C", (unichar)0x200B]];
29+
nonNeutral = [[set invertedSet] copy];
30+
});
31+
return nonNeutral;
32+
}
33+
34+
// returns YES when the font renders the given UTF-16 sequence itself
35+
static BOOL FontCoversCharacters(UIFont *font, const unichar *chars,
36+
CFIndex count) {
37+
if (font == nullptr) {
38+
return NO;
39+
}
40+
CGGlyph glyphs[2] = {0, 0};
41+
return CTFontGetGlyphsForCharacters((__bridge CTFontRef)font, chars, glyphs,
42+
count);
43+
}
444

545
@implementation ItalicStyle : StyleBase
646

@@ -17,8 +57,7 @@ - (BOOL)isParagraph {
1757
}
1858

1959
- (void)applyStyling:(NSRange)range {
20-
[ItalicUtils applyItalicInTextStorage:self.host.textView.textStorage
21-
inRange:range];
60+
[self applyItalicInTextStorage:self.host.textView.textStorage inRange:range];
2261
}
2362

2463
// some styles might apply a new font (inline code), so we need to apply
@@ -28,4 +67,137 @@ - (NSInteger)stylePriority {
2867
return 3;
2968
}
3069

70+
- (void)applyItalicInTextStorage:(NSTextStorage *)textStorage
71+
inRange:(NSRange)range {
72+
if (textStorage == nullptr || range.length == 0 ||
73+
NSMaxRange(range) > textStorage.length) {
74+
return;
75+
}
76+
77+
// we process each present font
78+
[textStorage enumerateAttribute:NSFontAttributeName
79+
inRange:range
80+
options:0
81+
usingBlock:^(id _Nullable value, NSRange fontRange,
82+
BOOL *_Nonnull stop) {
83+
UIFont *font = (UIFont *)value;
84+
if (font == nullptr) {
85+
return;
86+
}
87+
[self applyItalicForFont:font
88+
inTextStorage:textStorage
89+
inRange:fontRange];
90+
}];
91+
}
92+
93+
- (void)applyItalicForFont:(UIFont *)font
94+
inTextStorage:(NSTextStorage *)textStorage
95+
inRange:(NSRange)range {
96+
UIFont *italicFont = [font setItalic];
97+
BOOL hasItalicFace = [italicFont isItalic];
98+
99+
NSMutableArray<NSValue *> *clusterRanges = [NSMutableArray array];
100+
NSMutableArray<NSNumber *> *clusterKinds = [NSMutableArray array];
101+
102+
// we process each composed character sequence and classify it to a specific
103+
// ItalicKind
104+
[textStorage.string
105+
enumerateSubstringsInRange:range
106+
options:NSStringEnumerationByComposedCharacterSequences
107+
usingBlock:^(NSString *_Nullable cluster,
108+
NSRange clusterRange, NSRange _,
109+
BOOL *_Nonnull stop) {
110+
if (cluster.length == 0) {
111+
return;
112+
}
113+
[clusterRanges
114+
addObject:[NSValue valueWithRange:clusterRange]];
115+
[clusterKinds
116+
addObject:@([self kindForCluster:cluster
117+
font:font
118+
italicFont:italicFont
119+
hasItalicFace:hasItalicFace])];
120+
}];
121+
122+
// merge neighbouring clusters of the same kind and apply the style
123+
NSUInteger index = 0;
124+
while (index < clusterKinds.count) {
125+
NSUInteger endIndex = index + 1;
126+
ItalicKind kind = (ItalicKind)[clusterKinds[index] integerValue];
127+
while (endIndex < clusterKinds.count &&
128+
(ItalicKind)[clusterKinds[endIndex] integerValue] == kind) {
129+
endIndex += 1;
130+
}
131+
132+
NSRange startRange = [clusterRanges[index] rangeValue];
133+
NSRange endRange = [clusterRanges[endIndex - 1] rangeValue];
134+
NSRange segment = NSMakeRange(startRange.location,
135+
NSMaxRange(endRange) - startRange.location);
136+
137+
[self applyKind:kind
138+
toSegment:segment
139+
inTextStorage:textStorage
140+
withItalicFont:italicFont];
141+
142+
index = endIndex;
143+
}
144+
}
145+
146+
- (ItalicKind)kindForCluster:(NSString *)cluster
147+
font:(UIFont *)font
148+
italicFont:(UIFont *)italicFont
149+
hasItalicFace:(BOOL)hasItalicFace {
150+
if ([cluster rangeOfCharacterFromSet:NonNeutralCharacters()].location ==
151+
NSNotFound) {
152+
return ItalicKindNone;
153+
}
154+
155+
// we just need to analyze the first unicode character to classify the whole
156+
// cluster
157+
unichar chars[2] = {0, 0};
158+
CFIndex count = 1;
159+
chars[0] = [cluster characterAtIndex:0];
160+
if (CFStringIsSurrogateHighCharacter(chars[0]) && cluster.length > 1) {
161+
chars[1] = [cluster characterAtIndex:1];
162+
count = 2;
163+
}
164+
165+
if (chars[0] == (unichar)NSAttachmentCharacter) {
166+
return ItalicKindNone;
167+
}
168+
169+
BOOL coveredByFont = FontCoversCharacters(font, chars, count);
170+
171+
// italic style is supported - we use it
172+
if (coveredByFont && hasItalicFace &&
173+
FontCoversCharacters(italicFont, chars, count)) {
174+
return ItalicKindFont;
175+
}
176+
177+
// italic is not supported, we use the slant instead
178+
return ItalicKindOblique;
179+
}
180+
181+
- (void)applyKind:(ItalicKind)kind
182+
toSegment:(NSRange)segment
183+
inTextStorage:(NSTextStorage *)textStorage
184+
withItalicFont:(UIFont *)italicFont {
185+
switch (kind) {
186+
case ItalicKindFont:
187+
[textStorage addAttribute:NSFontAttributeName
188+
value:italicFont
189+
range:segment];
190+
[textStorage removeAttribute:NSObliquenessAttributeName range:segment];
191+
break;
192+
case ItalicKindOblique:
193+
[textStorage addAttribute:NSObliquenessAttributeName
194+
value:@(kObliquenessFallback)
195+
range:segment];
196+
break;
197+
case ItalicKindNone:
198+
[textStorage removeAttribute:NSObliquenessAttributeName range:segment];
199+
break;
200+
}
201+
}
202+
31203
@end

ios/utils/ItalicUtils.h

Lines changed: 0 additions & 8 deletions
This file was deleted.

ios/utils/ItalicUtils.mm

Lines changed: 0 additions & 179 deletions
This file was deleted.

0 commit comments

Comments
 (0)