Skip to content

Commit e851fcf

Browse files
committed
feat: fast html parser (#8)
* feat: fast html parser * fix: remove pragma * fix: remove pargrma mark * fix: simplify namings * fix: better names for magic hex * feat: faster html generation * fix: better class namings * feat: integrate our styles with new html parser
1 parent b677e83 commit e851fcf

37 files changed

Lines changed: 1575 additions & 1069 deletions

apps/example/src/App.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ const DEBUG_SCROLLABLE = false;
8787
// See: https://github.com/software-mansion/react-native-enriched/issues/229
8888
const ANDROID_EXPERIMENTAL_SYNCHRONOUS_EVENTS = false;
8989

90-
const html = `<html>
90+
const contentHtml = Array(1)
91+
.fill(
92+
`
9193
<h1>Title H1</h1>
9294
<hr />
9395
<p><u><font color="#FF0000">Test </font><font color="#E6FF5C">te</font></u><font color="#E6FF5C">st</font></p>
@@ -99,7 +101,11 @@ const html = `<html>
99101
<p><b>Bold text</b></p>
100102
<p><i>Italic text</i></p>
101103
<p><u>Underline text</u></p>
102-
</html>`;
104+
`
105+
)
106+
.join('');
107+
108+
const html = '<html>' + contentHtml + '</html>';
103109

104110
export default function App() {
105111
const [isChannelPopupOpen, setIsChannelPopupOpen] = useState(false);

ios/EnrichedTextInputView.mm

Lines changed: 92 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#import "EnrichedTextInputView.h"
2+
#import "CheckboxHitTestUtils.h"
23
#import "ColorExtension.h"
4+
#import "ContentHitTestUtils.h"
35
#import "CoreText/CoreText.h"
6+
#import "DividerHitTestUtils.h"
47
#import "EnrichedImageLoader.h"
58
#import "LayoutManagerExtension.h"
69
#import "ParagraphAttributesUtils.h"
@@ -1266,7 +1269,13 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
12661269
[self toggleRegularStyle:[StrikethroughStyle getStyleType]];
12671270
} else if ([commandName isEqualToString:@"setColor"]) {
12681271
NSString *colorText = (NSString *)args[0];
1269-
[self setColor:colorText];
1272+
UIColor *color = [UIColor colorFromString:colorText];
1273+
id<BaseStyleProtocol> baseStyle = stylesDict[@(Colored)];
1274+
if ([baseStyle isKindOfClass:[ColorStyle class]]) {
1275+
ColorStyle *colorStyle = (ColorStyle *)baseStyle;
1276+
[colorStyle applyStyle:textView.selectedRange color:color];
1277+
}
1278+
[self anyTextMayHaveBeenModified];
12701279
} else if ([commandName isEqualToString:@"removeColor"]) {
12711280
[self removeColor];
12721281
[self anyTextMayHaveBeenModified];
@@ -1519,14 +1528,14 @@ - (void)requestHTML:(NSInteger)requestId {
15191528

15201529
- (void)setColor:(NSString *)colorText {
15211530
UIColor *color = [UIColor colorFromString:colorText];
1522-
ColorStyle *colorStyle = stylesDict[@(Colored)];
1531+
ColorStyle *colorStyle = (ColorStyle *)stylesDict[@(Colored)];
15231532

15241533
[colorStyle applyStyle:textView.selectedRange color:color];
15251534
[self anyTextMayHaveBeenModified];
15261535
}
15271536

15281537
- (void)removeColor {
1529-
ColorStyle *colorStyle = stylesDict[@(Colored)];
1538+
ColorStyle *colorStyle = (ColorStyle *)stylesDict[@(Colored)];
15301539
[colorStyle removeColorInSelectedRange];
15311540
[self anyTextMayHaveBeenModified];
15321541
}
@@ -1733,7 +1742,8 @@ - (void)manageSelectionBasedChanges {
17331742
- (void)handleWordModificationBasedChanges:(NSString *)word
17341743
inRange:(NSRange)range {
17351744
// manual links refreshing and automatic links detection handling
1736-
LinkStyle *linkStyle = [stylesDict objectForKey:@([LinkStyle getStyleType])];
1745+
LinkStyle *linkStyle =
1746+
(LinkStyle *)[stylesDict objectForKey:@([LinkStyle getStyleType])];
17371747

17381748
if (linkStyle != nullptr) {
17391749
// manual links need to be handled first because they can block automatic
@@ -1932,24 +1942,62 @@ - (void)textViewDidEndEditing:(UITextView *)textView {
19321942
}
19331943
}
19341944

1945+
- (BOOL)isReadOnlyParagraphAtLocation:(NSUInteger)location {
1946+
NSTextStorage *storage = textView.textStorage;
1947+
NSUInteger length = storage.length;
1948+
1949+
if (length == 0) {
1950+
return NO;
1951+
}
1952+
if (location >= length) {
1953+
location = length - 1;
1954+
}
1955+
1956+
id currentValue = [storage attribute:ReadOnlyParagraphKey
1957+
atIndex:location
1958+
effectiveRange:nil];
1959+
if (currentValue) {
1960+
return YES;
1961+
}
1962+
1963+
if (location > 0) {
1964+
id previousValue = [storage attribute:ReadOnlyParagraphKey
1965+
atIndex:location - 1
1966+
effectiveRange:nil];
1967+
if (previousValue) {
1968+
return YES;
1969+
}
1970+
}
1971+
1972+
return NO;
1973+
}
1974+
19351975
- (bool)textView:(UITextView *)textView
19361976
shouldChangeTextInRange:(NSRange)range
19371977
replacementText:(NSString *)text {
1978+
if (![text isEqualToString:@"\n"] &&
1979+
[self isReadOnlyParagraphAtLocation:range.location]) {
1980+
if (text.length == 0)
1981+
return YES;
1982+
return NO;
1983+
}
19381984
recentlyChangedRange = NSMakeRange(range.location, text.length);
19391985

19401986
UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])];
19411987
OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getStyleType])];
19421988
BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
19431989
CodeBlockStyle *cbStyle = stylesDict[@([CodeBlockStyle getStyleType])];
1944-
LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])];
1945-
MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])];
1990+
LinkStyle *linkStyle = (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
1991+
MentionStyle *mentionStyle =
1992+
(MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
19461993
H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
19471994
H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
19481995
H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
19491996
H4Style *h4Style = stylesDict[@([H4Style getStyleType])];
19501997
H5Style *h5Style = stylesDict[@([H5Style getStyleType])];
19511998
H6Style *h6Style = stylesDict[@([H6Style getStyleType])];
1952-
CheckBoxStyle *checkBoxStyle = stylesDict[@([CheckBoxStyle getStyleType])];
1999+
CheckBoxStyle *checkBoxStyle =
2000+
(CheckBoxStyle *)stylesDict[@([CheckBoxStyle getStyleType])];
19532001

19542002
// some of the changes these checks do could interfere with later checks and
19552003
// cause a crash so here I rely on short circuiting evaluation of the logical
@@ -2055,8 +2103,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
20552103
return YES;
20562104
}
20572105

2058-
#pragma mark - Checkbox Helpers
2059-
20602106
- (CGPoint)adjustedPointForViewPoint:(CGPoint)pt {
20612107
CGPoint tvPoint = [self convertPoint:pt toView:textView];
20622108
tvPoint.x -= textView.textContainerInset.left;
@@ -2078,55 +2124,45 @@ - (BOOL)getCharIndex:(NSUInteger *)charIndex forAdjustedPoint:(CGPoint)pt {
20782124
return YES;
20792125
}
20802126

2081-
- (CGRect)checkboxRectForGlyphIndex:(NSUInteger)glyphIndex {
2082-
NSRange effective = {0, 0};
2083-
CGRect lineRect =
2084-
[textView.layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex
2085-
effectiveRange:&effective];
2086-
2087-
CGFloat w = [config checkBoxWidth];
2088-
CGFloat h = [config checkBoxHeight];
2089-
CGFloat ml = [config checkboxListMarginLeft];
2090-
CGFloat gap = [config checkboxListGapWidth];
2091-
2092-
return CGRectMake(ml + gap,
2093-
lineRect.origin.y + (lineRect.size.height - h) / 2.0, w, h);
2094-
}
2095-
2096-
- (CheckBoxStyle *)checkboxStyleForCharIndex:(NSUInteger)charIndex {
2097-
CheckBoxStyle *check = stylesDict[@([CheckBoxStyle getStyleType])];
2098-
if (check && [check detectStyle:NSMakeRange(charIndex, 0)]) {
2099-
return check;
2100-
}
2101-
return nil;
2102-
}
2103-
21042127
- (void)handleTap:(UITapGestureRecognizer *)gr {
21052128
if (gr.state != UIGestureRecognizerStateEnded)
21062129
return;
21072130

21082131
CGPoint adjusted = [self adjustedPointForViewPoint:[gr locationInView:self]];
2132+
NSInteger dividerCharIndex =
2133+
[DividerHitTestUtils hitTestDividerAtPoint:adjusted inInput:self];
2134+
if (dividerCharIndex >= 0) {
2135+
NSUInteger newLocation = (NSUInteger)dividerCharIndex + 1;
2136+
if (newLocation > textView.textStorage.length) {
2137+
newLocation = textView.textStorage.length;
2138+
}
21092139

2110-
NSUInteger charIndex;
2111-
if (![self getCharIndex:&charIndex forAdjustedPoint:adjusted])
2140+
textView.selectedRange = NSMakeRange(newLocation, 0);
21122141
return;
2142+
}
21132143

2114-
CheckBoxStyle *check = [self checkboxStyleForCharIndex:charIndex];
2115-
if (!check)
2144+
NSInteger contentCharIndex =
2145+
[ContentHitTestUtils hitTestContentAtPoint:adjusted inInput:self];
2146+
if (contentCharIndex >= 0) {
2147+
NSUInteger newLocation = (NSUInteger)contentCharIndex + 1;
2148+
if (newLocation > textView.textStorage.length) {
2149+
newLocation = textView.textStorage.length;
2150+
}
2151+
textView.selectedRange = NSMakeRange(newLocation, 0);
21162152
return;
2153+
}
21172154

2118-
// Get glyphIndex separately for the rectangle
2119-
NSUInteger glyphIndex =
2120-
[textView.layoutManager glyphIndexForPoint:adjusted
2121-
inTextContainer:textView.textContainer
2122-
fractionOfDistanceThroughGlyph:nil];
2123-
2124-
CGRect checkboxRect = [self checkboxRectForGlyphIndex:glyphIndex];
2125-
if (!CGRectContainsPoint(checkboxRect, adjusted))
2155+
NSInteger checkboxCharIndex =
2156+
[CheckboxHitTestUtils hitTestCheckboxAtPoint:adjusted inInput:self];
2157+
if (checkboxCharIndex >= 0) {
2158+
CheckBoxStyle *check =
2159+
(CheckBoxStyle *)stylesDict[@([CheckBoxStyle getStyleType])];
2160+
if (check) {
2161+
[check toggleCheckedAt:(NSUInteger)checkboxCharIndex];
2162+
[self anyTextMayHaveBeenModified];
2163+
}
21262164
return;
2127-
2128-
[check toggleCheckedAt:charIndex];
2129-
[self anyTextMayHaveBeenModified];
2165+
}
21302166
}
21312167

21322168
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
@@ -2136,23 +2172,17 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
21362172

21372173
CGPoint adjusted = [self adjustedPointForViewPoint:point];
21382174

2139-
NSUInteger charIndex;
2140-
if (![self getCharIndex:&charIndex forAdjustedPoint:adjusted])
2141-
return hit;
2142-
2143-
CheckBoxStyle *check = [self checkboxStyleForCharIndex:charIndex];
2144-
if (!check)
2145-
return hit;
2146-
2147-
NSUInteger glyphIndex =
2148-
[textView.layoutManager glyphIndexForPoint:adjusted
2149-
inTextContainer:textView.textContainer
2150-
fractionOfDistanceThroughGlyph:nil];
2175+
if ([DividerHitTestUtils hitTestDividerAtPoint:adjusted inInput:self] >= 0) {
2176+
return self;
2177+
}
21512178

2152-
CGRect checkboxRect = [self checkboxRectForGlyphIndex:glyphIndex];
2179+
if ([ContentHitTestUtils hitTestContentAtPoint:adjusted inInput:self] >= 0) {
2180+
return self;
2181+
}
21532182

2154-
if (CGRectContainsPoint(checkboxRect, adjusted)) {
2155-
return self; // intercept touch
2183+
if ([CheckboxHitTestUtils hitTestCheckboxAtPoint:adjusted
2184+
inInput:self] >= 0) {
2185+
return self;
21562186
}
21572187

21582188
return hit;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@class HTMLNode;
4+
@class HTMLTextNode;
5+
6+
@interface EnrichedAttributedStringHTMLSerializer : NSObject
7+
- (instancetype)initWithStyles:(NSDictionary<NSNumber *, id> *)stylesDict;
8+
- (NSString *)buildHtmlFromAttributedString:(NSAttributedString *)text
9+
pretify:(BOOL)pretify;
10+
@end

0 commit comments

Comments
 (0)