Skip to content

Commit b619f09

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 5c05975 commit b619f09

37 files changed

Lines changed: 1575 additions & 1063 deletions

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"
@@ -1262,7 +1265,13 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
12621265
[self toggleRegularStyle:[StrikethroughStyle getStyleType]];
12631266
} else if ([commandName isEqualToString:@"setColor"]) {
12641267
NSString *colorText = (NSString *)args[0];
1265-
[self setColor:colorText];
1268+
UIColor *color = [UIColor colorFromString:colorText];
1269+
id<BaseStyleProtocol> baseStyle = stylesDict[@(Colored)];
1270+
if ([baseStyle isKindOfClass:[ColorStyle class]]) {
1271+
ColorStyle *colorStyle = (ColorStyle *)baseStyle;
1272+
[colorStyle applyStyle:textView.selectedRange color:color];
1273+
}
1274+
[self anyTextMayHaveBeenModified];
12661275
} else if ([commandName isEqualToString:@"removeColor"]) {
12671276
[self removeColor];
12681277
[self anyTextMayHaveBeenModified];
@@ -1478,14 +1487,14 @@ - (void)requestHTML:(NSInteger)requestId {
14781487

14791488
- (void)setColor:(NSString *)colorText {
14801489
UIColor *color = [UIColor colorFromString:colorText];
1481-
ColorStyle *colorStyle = stylesDict[@(Colored)];
1490+
ColorStyle *colorStyle = (ColorStyle *)stylesDict[@(Colored)];
14821491

14831492
[colorStyle applyStyle:textView.selectedRange color:color];
14841493
[self anyTextMayHaveBeenModified];
14851494
}
14861495

14871496
- (void)removeColor {
1488-
ColorStyle *colorStyle = stylesDict[@(Colored)];
1497+
ColorStyle *colorStyle = (ColorStyle *)stylesDict[@(Colored)];
14891498
[colorStyle removeColorInSelectedRange];
14901499
[self anyTextMayHaveBeenModified];
14911500
}
@@ -1692,7 +1701,8 @@ - (void)manageSelectionBasedChanges {
16921701
- (void)handleWordModificationBasedChanges:(NSString *)word
16931702
inRange:(NSRange)range {
16941703
// manual links refreshing and automatic links detection handling
1695-
LinkStyle *linkStyle = [stylesDict objectForKey:@([LinkStyle getStyleType])];
1704+
LinkStyle *linkStyle =
1705+
(LinkStyle *)[stylesDict objectForKey:@([LinkStyle getStyleType])];
16961706

16971707
if (linkStyle != nullptr) {
16981708
// manual links need to be handled first because they can block automatic
@@ -1891,24 +1901,62 @@ - (void)textViewDidEndEditing:(UITextView *)textView {
18911901
}
18921902
}
18931903

1904+
- (BOOL)isReadOnlyParagraphAtLocation:(NSUInteger)location {
1905+
NSTextStorage *storage = textView.textStorage;
1906+
NSUInteger length = storage.length;
1907+
1908+
if (length == 0) {
1909+
return NO;
1910+
}
1911+
if (location >= length) {
1912+
location = length - 1;
1913+
}
1914+
1915+
id currentValue = [storage attribute:ReadOnlyParagraphKey
1916+
atIndex:location
1917+
effectiveRange:nil];
1918+
if (currentValue) {
1919+
return YES;
1920+
}
1921+
1922+
if (location > 0) {
1923+
id previousValue = [storage attribute:ReadOnlyParagraphKey
1924+
atIndex:location - 1
1925+
effectiveRange:nil];
1926+
if (previousValue) {
1927+
return YES;
1928+
}
1929+
}
1930+
1931+
return NO;
1932+
}
1933+
18941934
- (bool)textView:(UITextView *)textView
18951935
shouldChangeTextInRange:(NSRange)range
18961936
replacementText:(NSString *)text {
1937+
if (![text isEqualToString:@"\n"] &&
1938+
[self isReadOnlyParagraphAtLocation:range.location]) {
1939+
if (text.length == 0)
1940+
return YES;
1941+
return NO;
1942+
}
18971943
recentlyChangedRange = NSMakeRange(range.location, text.length);
18981944

18991945
UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])];
19001946
OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getStyleType])];
19011947
BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
19021948
CodeBlockStyle *cbStyle = stylesDict[@([CodeBlockStyle getStyleType])];
1903-
LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])];
1904-
MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])];
1949+
LinkStyle *linkStyle = (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
1950+
MentionStyle *mentionStyle =
1951+
(MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
19051952
H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
19061953
H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
19071954
H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
19081955
H4Style *h4Style = stylesDict[@([H4Style getStyleType])];
19091956
H5Style *h5Style = stylesDict[@([H5Style getStyleType])];
19101957
H6Style *h6Style = stylesDict[@([H6Style getStyleType])];
1911-
CheckBoxStyle *checkBoxStyle = stylesDict[@([CheckBoxStyle getStyleType])];
1958+
CheckBoxStyle *checkBoxStyle =
1959+
(CheckBoxStyle *)stylesDict[@([CheckBoxStyle getStyleType])];
19121960

19131961
// some of the changes these checks do could interfere with later checks and
19141962
// cause a crash so here I rely on short circuiting evaluation of the logical
@@ -2014,8 +2062,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
20142062
return YES;
20152063
}
20162064

2017-
#pragma mark - Checkbox Helpers
2018-
20192065
- (CGPoint)adjustedPointForViewPoint:(CGPoint)pt {
20202066
CGPoint tvPoint = [self convertPoint:pt toView:textView];
20212067
tvPoint.x -= textView.textContainerInset.left;
@@ -2037,55 +2083,45 @@ - (BOOL)getCharIndex:(NSUInteger *)charIndex forAdjustedPoint:(CGPoint)pt {
20372083
return YES;
20382084
}
20392085

2040-
- (CGRect)checkboxRectForGlyphIndex:(NSUInteger)glyphIndex {
2041-
NSRange effective = {0, 0};
2042-
CGRect lineRect =
2043-
[textView.layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex
2044-
effectiveRange:&effective];
2045-
2046-
CGFloat w = [config checkBoxWidth];
2047-
CGFloat h = [config checkBoxHeight];
2048-
CGFloat ml = [config checkboxListMarginLeft];
2049-
CGFloat gap = [config checkboxListGapWidth];
2050-
2051-
return CGRectMake(ml + gap,
2052-
lineRect.origin.y + (lineRect.size.height - h) / 2.0, w, h);
2053-
}
2054-
2055-
- (CheckBoxStyle *)checkboxStyleForCharIndex:(NSUInteger)charIndex {
2056-
CheckBoxStyle *check = stylesDict[@([CheckBoxStyle getStyleType])];
2057-
if (check && [check detectStyle:NSMakeRange(charIndex, 0)]) {
2058-
return check;
2059-
}
2060-
return nil;
2061-
}
2062-
20632086
- (void)handleTap:(UITapGestureRecognizer *)gr {
20642087
if (gr.state != UIGestureRecognizerStateEnded)
20652088
return;
20662089

20672090
CGPoint adjusted = [self adjustedPointForViewPoint:[gr locationInView:self]];
2091+
NSInteger dividerCharIndex =
2092+
[DividerHitTestUtils hitTestDividerAtPoint:adjusted inInput:self];
2093+
if (dividerCharIndex >= 0) {
2094+
NSUInteger newLocation = (NSUInteger)dividerCharIndex + 1;
2095+
if (newLocation > textView.textStorage.length) {
2096+
newLocation = textView.textStorage.length;
2097+
}
20682098

2069-
NSUInteger charIndex;
2070-
if (![self getCharIndex:&charIndex forAdjustedPoint:adjusted])
2099+
textView.selectedRange = NSMakeRange(newLocation, 0);
20712100
return;
2101+
}
20722102

2073-
CheckBoxStyle *check = [self checkboxStyleForCharIndex:charIndex];
2074-
if (!check)
2103+
NSInteger contentCharIndex =
2104+
[ContentHitTestUtils hitTestContentAtPoint:adjusted inInput:self];
2105+
if (contentCharIndex >= 0) {
2106+
NSUInteger newLocation = (NSUInteger)contentCharIndex + 1;
2107+
if (newLocation > textView.textStorage.length) {
2108+
newLocation = textView.textStorage.length;
2109+
}
2110+
textView.selectedRange = NSMakeRange(newLocation, 0);
20752111
return;
2112+
}
20762113

2077-
// Get glyphIndex separately for the rectangle
2078-
NSUInteger glyphIndex =
2079-
[textView.layoutManager glyphIndexForPoint:adjusted
2080-
inTextContainer:textView.textContainer
2081-
fractionOfDistanceThroughGlyph:nil];
2082-
2083-
CGRect checkboxRect = [self checkboxRectForGlyphIndex:glyphIndex];
2084-
if (!CGRectContainsPoint(checkboxRect, adjusted))
2114+
NSInteger checkboxCharIndex =
2115+
[CheckboxHitTestUtils hitTestCheckboxAtPoint:adjusted inInput:self];
2116+
if (checkboxCharIndex >= 0) {
2117+
CheckBoxStyle *check =
2118+
(CheckBoxStyle *)stylesDict[@([CheckBoxStyle getStyleType])];
2119+
if (check) {
2120+
[check toggleCheckedAt:(NSUInteger)checkboxCharIndex];
2121+
[self anyTextMayHaveBeenModified];
2122+
}
20852123
return;
2086-
2087-
[check toggleCheckedAt:charIndex];
2088-
[self anyTextMayHaveBeenModified];
2124+
}
20892125
}
20902126

20912127
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
@@ -2095,23 +2131,17 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
20952131

20962132
CGPoint adjusted = [self adjustedPointForViewPoint:point];
20972133

2098-
NSUInteger charIndex;
2099-
if (![self getCharIndex:&charIndex forAdjustedPoint:adjusted])
2100-
return hit;
2101-
2102-
CheckBoxStyle *check = [self checkboxStyleForCharIndex:charIndex];
2103-
if (!check)
2104-
return hit;
2105-
2106-
NSUInteger glyphIndex =
2107-
[textView.layoutManager glyphIndexForPoint:adjusted
2108-
inTextContainer:textView.textContainer
2109-
fractionOfDistanceThroughGlyph:nil];
2134+
if ([DividerHitTestUtils hitTestDividerAtPoint:adjusted inInput:self] >= 0) {
2135+
return self;
2136+
}
21102137

2111-
CGRect checkboxRect = [self checkboxRectForGlyphIndex:glyphIndex];
2138+
if ([ContentHitTestUtils hitTestContentAtPoint:adjusted inInput:self] >= 0) {
2139+
return self;
2140+
}
21122141

2113-
if (CGRectContainsPoint(checkboxRect, adjusted)) {
2114-
return self; // intercept touch
2142+
if ([CheckboxHitTestUtils hitTestCheckboxAtPoint:adjusted
2143+
inInput:self] >= 0) {
2144+
return self;
21152145
}
21162146

21172147
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)