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
0 commit comments