From 28a430f0216a6868f27778d2033abdc4e7eabd9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 8 Apr 2026 07:46:34 +0200 Subject: [PATCH 01/19] feat: add EnrichedStyleHost --- ios/EnrichedTextInputView.h | 3 +- ios/EnrichedTextInputView.mm | 167 +---- ios/extensions/LayoutManagerExtension.mm | 104 ++- ios/htmlParser/HtmlParser.h | 8 + ios/htmlParser/HtmlParser.mm | 772 +++++++++++++++++++++++ ios/inputParser/InputParser.mm | 765 +--------------------- ios/interfaces/BaseStyleProtocol.h | 2 +- ios/interfaces/StyleBase.h | 7 +- ios/interfaces/StyleBase.mm | 47 +- ios/interfaces/StyleHeaders.h | 1 + ios/styles/BlockQuoteStyle.mm | 29 +- ios/styles/BoldStyle.mm | 4 +- ios/styles/CheckboxListStyle.mm | 34 +- ios/styles/CodeBlockStyle.mm | 10 +- ios/styles/H1Style.mm | 4 +- ios/styles/H2Style.mm | 4 +- ios/styles/H3Style.mm | 4 +- ios/styles/H4Style.mm | 4 +- ios/styles/H5Style.mm | 4 +- ios/styles/H6Style.mm | 4 +- ios/styles/HeadingStyleBase.mm | 15 +- ios/styles/ImageStyle.mm | 48 +- ios/styles/InlineCodeStyle.mm | 24 +- ios/styles/ItalicStyle.mm | 4 +- ios/styles/LinkStyle.mm | 140 ++-- ios/styles/MentionStyle.mm | 123 ++-- ios/styles/OrderedListStyle.mm | 22 +- ios/styles/StrikethroughStyle.mm | 4 +- ios/styles/UnderlineStyle.mm | 6 +- ios/styles/UnorderedListStyle.mm | 22 +- ios/utils/AttachmentLayoutUtils.h | 24 + ios/utils/AttachmentLayoutUtils.mm | 143 +++++ ios/utils/OccurenceUtils.h | 16 +- ios/utils/OccurenceUtils.mm | 42 +- 34 files changed, 1340 insertions(+), 1270 deletions(-) create mode 100644 ios/htmlParser/HtmlParser.h create mode 100644 ios/htmlParser/HtmlParser.mm create mode 100644 ios/utils/AttachmentLayoutUtils.h create mode 100644 ios/utils/AttachmentLayoutUtils.mm diff --git a/ios/EnrichedTextInputView.h b/ios/EnrichedTextInputView.h index 1ea44ad9f..a107e3ce2 100644 --- a/ios/EnrichedTextInputView.h +++ b/ios/EnrichedTextInputView.h @@ -1,6 +1,7 @@ #pragma once #import "AttributesManager.h" #import "BaseStyleProtocol.h" +#import "EnrichedStyleHost.h" #import "InputConfig.h" #import "InputParser.h" #import "InputTextView.h" @@ -15,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @interface EnrichedTextInputView - : RCTViewComponentView { + : RCTViewComponentView { @public InputTextView *textView; @public diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 66c22512b..75a84980f 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1,4 +1,5 @@ #import "EnrichedTextInputView.h" +#import "AttachmentLayoutUtils.h" #import "CoreText/CoreText.h" #import "DotReplacementUtils.h" #import "ImageAttachment.h" @@ -104,29 +105,29 @@ - (void)setDefaults { [[NSMutableDictionary alloc] init]; stylesDict = @{ - @([BoldStyle getType]) : [[BoldStyle alloc] initWithInput:self], - @([ItalicStyle getType]) : [[ItalicStyle alloc] initWithInput:self], - @([UnderlineStyle getType]) : [[UnderlineStyle alloc] initWithInput:self], + @([BoldStyle getType]) : [[BoldStyle alloc] initWithHost:self], + @([ItalicStyle getType]) : [[ItalicStyle alloc] initWithHost:self], + @([UnderlineStyle getType]) : [[UnderlineStyle alloc] initWithHost:self], @([StrikethroughStyle getType]) : - [[StrikethroughStyle alloc] initWithInput:self], - @([InlineCodeStyle getType]) : [[InlineCodeStyle alloc] initWithInput:self], - @([LinkStyle getType]) : [[LinkStyle alloc] initWithInput:self], - @([MentionStyle getType]) : [[MentionStyle alloc] initWithInput:self], - @([H1Style getType]) : [[H1Style alloc] initWithInput:self], - @([H2Style getType]) : [[H2Style alloc] initWithInput:self], - @([H3Style getType]) : [[H3Style alloc] initWithInput:self], - @([H4Style getType]) : [[H4Style alloc] initWithInput:self], - @([H5Style getType]) : [[H5Style alloc] initWithInput:self], - @([H6Style getType]) : [[H6Style alloc] initWithInput:self], + [[StrikethroughStyle alloc] initWithHost:self], + @([InlineCodeStyle getType]) : [[InlineCodeStyle alloc] initWithHost:self], + @([LinkStyle getType]) : [[LinkStyle alloc] initWithHost:self], + @([MentionStyle getType]) : [[MentionStyle alloc] initWithHost:self], + @([H1Style getType]) : [[H1Style alloc] initWithHost:self], + @([H2Style getType]) : [[H2Style alloc] initWithHost:self], + @([H3Style getType]) : [[H3Style alloc] initWithHost:self], + @([H4Style getType]) : [[H4Style alloc] initWithHost:self], + @([H5Style getType]) : [[H5Style alloc] initWithHost:self], + @([H6Style getType]) : [[H6Style alloc] initWithHost:self], @([UnorderedListStyle getType]) : - [[UnorderedListStyle alloc] initWithInput:self], + [[UnorderedListStyle alloc] initWithHost:self], @([OrderedListStyle getType]) : - [[OrderedListStyle alloc] initWithInput:self], + [[OrderedListStyle alloc] initWithHost:self], @([CheckboxListStyle getType]) : - [[CheckboxListStyle alloc] initWithInput:self], - @([BlockQuoteStyle getType]) : [[BlockQuoteStyle alloc] initWithInput:self], - @([CodeBlockStyle getType]) : [[CodeBlockStyle alloc] initWithInput:self], - @([ImageStyle getType]) : [[ImageStyle alloc] initWithInput:self] + [[CheckboxListStyle alloc] initWithHost:self], + @([BlockQuoteStyle getType]) : [[BlockQuoteStyle alloc] initWithHost:self], + @([CodeBlockStyle getType]) : [[CodeBlockStyle alloc] initWithHost:self], + @([ImageStyle getType]) : [[ImageStyle alloc] initWithHost:self] }; conflictingStyles = [@{ @@ -2205,129 +2206,19 @@ - (void)textStorage:(NSTextStorage *)textStorage // MARK: - Media attachments delegate - (void)mediaAttachmentDidUpdate:(NSTextAttachment *)attachment { - NSTextStorage *storage = textView.textStorage; - NSRange fullRange = NSMakeRange(0, storage.length); - - __block NSRange foundRange = NSMakeRange(NSNotFound, 0); - - [storage enumerateAttribute:NSAttachmentAttributeName - inRange:fullRange - options:0 - usingBlock:^(id value, NSRange range, BOOL *stop) { - if (value == attachment) { - foundRange = range; - *stop = YES; - } - }]; - - if (foundRange.location == NSNotFound) { - return; - } - - [storage edited:NSTextStorageEditedAttributes - range:foundRange - changeInLength:0]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [self layoutAttachments]; - }); + [AttachmentLayoutUtils handleAttachmentUpdate:attachment + textView:textView + onLayoutBlock:^{ + [self layoutAttachments]; + }]; } // MARK: - Image/GIF Overlay Management - (void)layoutAttachments { - NSTextStorage *storage = textView.textStorage; - NSMutableDictionary *activeAttachmentViews = - [NSMutableDictionary dictionary]; - - // Iterate over the entire text to find ImageAttachments - [storage enumerateAttribute:NSAttachmentAttributeName - inRange:NSMakeRange(0, storage.length) - options:0 - usingBlock:^(id value, NSRange range, BOOL *stop) { - if ([value isKindOfClass:[ImageAttachment class]]) { - ImageAttachment *attachment = (ImageAttachment *)value; - - CGRect rect = [self frameForAttachment:attachment - atRange:range]; - - // Get or Create the UIImageView for this specific - // attachment key - NSValue *key = - [NSValue valueWithNonretainedObject:attachment]; - UIImageView *imgView = _attachmentViews[key]; - - if (!imgView) { - // It doesn't exist yet, create it - imgView = [[UIImageView alloc] initWithFrame:rect]; - imgView.contentMode = UIViewContentModeScaleAspectFit; - imgView.tintColor = [UIColor labelColor]; - - // Add it directly to the TextView - [textView addSubview:imgView]; - } - - // Update position (in case text moved/scrolled) - if (!CGRectEqualToRect(imgView.frame, rect)) { - imgView.frame = rect; - } - UIImage *targetImage = - attachment.storedAnimatedImage ?: attachment.image; - - // Only set if different to avoid resetting the animation - // loop - if (imgView.image != targetImage) { - imgView.image = targetImage; - } - - // Ensure it is visible on top - imgView.hidden = NO; - [textView bringSubviewToFront:imgView]; - - activeAttachmentViews[key] = imgView; - // Remove from the old map so we know it has been claimed - [_attachmentViews removeObjectForKey:key]; - } - }]; - - // Everything remaining in _attachmentViews is dead or off-screen - for (UIImageView *danglingView in _attachmentViews.allValues) { - [danglingView removeFromSuperview]; - } - _attachmentViews = activeAttachmentViews; -} - -- (CGRect)frameForAttachment:(ImageAttachment *)attachment - atRange:(NSRange)range { - NSLayoutManager *layoutManager = textView.layoutManager; - NSTextContainer *textContainer = textView.textContainer; - NSTextStorage *storage = textView.textStorage; - - NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range - actualCharacterRange:NULL]; - CGRect glyphRect = [layoutManager boundingRectForGlyphRange:glyphRange - inTextContainer:textContainer]; - - CGRect lineRect = - [layoutManager lineFragmentRectForGlyphAtIndex:glyphRange.location - effectiveRange:NULL]; - CGSize attachmentSize = attachment.bounds.size; - - UIFont *font = [storage attribute:NSFontAttributeName - atIndex:range.location - effectiveRange:NULL]; - if (!font) { - font = [config primaryFont]; - } - - // Calculate (Baseline Alignment) - CGFloat targetY = - CGRectGetMaxY(lineRect) + font.descender - attachmentSize.height; - CGRect rect = - CGRectMake(glyphRect.origin.x + textView.textContainerInset.left, - targetY + textView.textContainerInset.top, - attachmentSize.width, attachmentSize.height); - - return CGRectIntegral(rect); + _attachmentViews = + [AttachmentLayoutUtils layoutAttachmentsInTextView:textView + config:config + existingViews:_attachmentViews]; } @end diff --git a/ios/extensions/LayoutManagerExtension.mm b/ios/extensions/LayoutManagerExtension.mm index 58f4620a3..efd77d0d9 100644 --- a/ios/extensions/LayoutManagerExtension.mm +++ b/ios/extensions/LayoutManagerExtension.mm @@ -1,6 +1,6 @@ #import "LayoutManagerExtension.h" #import "ColorExtension.h" -#import "EnrichedTextInputView.h" +#import "EnrichedStyleHost.h" #import "RangeUtils.h" #import "StyleHeaders.h" #import "WeakBox.h" @@ -49,28 +49,23 @@ - (void)my_drawBackgroundForGlyphRange:(NSRange)glyphRange atPoint:(CGPoint)origin { [self my_drawBackgroundForGlyphRange:glyphRange atPoint:origin]; - EnrichedTextInputView *typedInput = (EnrichedTextInputView *)self.input; - if (typedInput == nullptr) { + id host = self.input; + if (host == nullptr) { return; } NSRange visibleCharRange = [self characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - [self drawBlockQuotes:typedInput - origin:origin - visibleCharRange:visibleCharRange]; - [self drawLists:typedInput origin:origin visibleCharRange:visibleCharRange]; - [self drawCodeBlocks:typedInput - origin:origin - visibleCharRange:visibleCharRange]; + [self drawBlockQuotes:host origin:origin visibleCharRange:visibleCharRange]; + [self drawLists:host origin:origin visibleCharRange:visibleCharRange]; + [self drawCodeBlocks:host origin:origin visibleCharRange:visibleCharRange]; } -- (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput +- (void)drawCodeBlocks:(id)host origin:(CGPoint)origin visibleCharRange:(NSRange)visibleCharRange { - CodeBlockStyle *codeBlockStyle = - typedInput->stylesDict[@([CodeBlockStyle getType])]; + CodeBlockStyle *codeBlockStyle = host.stylesDict[@([CodeBlockStyle getType])]; if (codeBlockStyle == nullptr) { return; } @@ -78,9 +73,9 @@ - (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput NSArray *allCodeBlocks = [codeBlockStyle all:visibleCharRange]; NSArray *mergedCodeBlocks = [self mergeContiguousStylePairs:allCodeBlocks]; - UIColor *bgColor = [[typedInput->config codeBlockBgColor] - colorWithAlphaIfNotTransparent:0.4]; - CGFloat radius = [typedInput->config codeBlockBorderRadius]; + UIColor *bgColor = + [[host.config codeBlockBgColor] colorWithAlphaIfNotTransparent:0.4]; + CGFloat radius = [host.config codeBlockBorderRadius]; [bgColor setFill]; for (StylePair *pair in mergedCodeBlocks) { @@ -89,7 +84,7 @@ - (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput continue; NSArray *paragraphs = - [RangeUtils getSeparateParagraphsRangesIn:typedInput->textView + [RangeUtils getSeparateParagraphsRangesIn:host.textView range:blockCharacterRange]; if (paragraphs.count == 0) continue; @@ -204,11 +199,10 @@ - (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput return mergedPairs; } -- (void)drawBlockQuotes:(EnrichedTextInputView *)typedInput +- (void)drawBlockQuotes:(id)host origin:(CGPoint)origin visibleCharRange:(NSRange)visibleCharRange { - BlockQuoteStyle *bqStyle = - typedInput->stylesDict[@([BlockQuoteStyle getType])]; + BlockQuoteStyle *bqStyle = host.stylesDict[@([BlockQuoteStyle getType])]; if (bqStyle == nullptr) { return; } @@ -216,7 +210,7 @@ - (void)drawBlockQuotes:(EnrichedTextInputView *)typedInput NSArray *allBlockquotes = [bqStyle all:visibleCharRange]; for (StylePair *pair in allBlockquotes) { - NSRange paragraphRange = [typedInput->textView.textStorage.string + NSRange paragraphRange = [host.textView.textStorage.string paragraphRangeForRange:[pair.rangeValue rangeValue]]; NSRange paragraphGlyphRange = [self glyphRangeForCharacterRange:paragraphRange @@ -232,28 +226,25 @@ - (void)drawBlockQuotes:(EnrichedTextInputView *)typedInput CGFloat x = paddingLeft; CGFloat y = paddingTop + rect.origin.y; CGFloat width = - [typedInput - ->config blockquoteBorderWidth]; + [host.config blockquoteBorderWidth]; CGFloat height = rect.size.height; CGRect lineRect = CGRectMake(x, y, width, height); - [[typedInput->config blockquoteBorderColor] + [[host.config blockquoteBorderColor] setFill]; UIRectFill(lineRect); }]; } } -- (void)drawLists:(EnrichedTextInputView *)typedInput +- (void)drawLists:(id)host origin:(CGPoint)origin visibleCharRange:(NSRange)visibleCharRange { UnorderedListStyle *ulStyle = - typedInput->stylesDict[@([UnorderedListStyle getType])]; - OrderedListStyle *olStyle = - typedInput->stylesDict[@([OrderedListStyle getType])]; - CheckboxListStyle *cbStyle = - typedInput->stylesDict[@([CheckboxListStyle getType])]; + host.stylesDict[@([UnorderedListStyle getType])]; + OrderedListStyle *olStyle = host.stylesDict[@([OrderedListStyle getType])]; + CheckboxListStyle *cbStyle = host.stylesDict[@([CheckboxListStyle getType])]; if (ulStyle == nullptr || olStyle == nullptr || cbStyle == nullptr) { return; } @@ -267,13 +258,12 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput for (StylePair *pair in allLists) { NSParagraphStyle *pStyle = (NSParagraphStyle *)pair.styleValue; NSDictionary *markerAttributes = @{ - NSFontAttributeName : [typedInput->config orderedListMarkerFont], - NSForegroundColorAttributeName : - [typedInput->config orderedListMarkerColor] + NSFontAttributeName : [host.config orderedListMarkerFont], + NSForegroundColorAttributeName : [host.config orderedListMarkerColor] }; NSArray *paragraphs = - [RangeUtils getSeparateParagraphsRangesIn:typedInput->textView + [RangeUtils getSeparateParagraphsRangesIn:host.textView range:[pair.rangeValue rangeValue]]; for (NSValue *paragraph in paragraphs) { @@ -290,11 +280,10 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput NSUInteger charIdx = [self characterIndexForGlyphAtIndex: lineGlyphRange.location]; - UIFont *font = - [typedInput->textView.textStorage - attribute:NSFontAttributeName - atIndex:charIdx - effectiveRange:nil]; + UIFont *font = [host.textView.textStorage + attribute:NSFontAttributeName + atIndex:charIdx + effectiveRange:nil]; CGRect textUsedRect = [self getTextAlignedUsedRect:usedRect font:font]; @@ -307,9 +296,9 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput isEqualToString: @"EnrichedOrderedList"]) { NSString *marker = [self - getDecimalMarkerForList:typedInput + getDecimalMarkerForList:host charIndex:charIdx]; - [self drawDecimal:typedInput + [self drawDecimal:host marker:marker markerAttributes:markerAttributes origin:origin @@ -318,13 +307,13 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput isEqualToString: @"EnrichedUnorderedLis" @"t"]) { - [self drawBullet:typedInput + [self drawBullet:host origin:origin usedRect:textUsedRect]; } else if ([markerFormat hasPrefix: @"EnrichedCheckbox"]) { - [self drawCheckbox:typedInput + [self drawCheckbox:host markerFormat:markerFormat origin:origin usedRect:textUsedRect]; @@ -337,16 +326,15 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput } } -- (NSString *)getDecimalMarkerForList:(EnrichedTextInputView *)input +- (NSString *)getDecimalMarkerForList:(id)host charIndex:(NSUInteger)index { - NSString *fullText = input->textView.textStorage.string; + NSString *fullText = host.textView.textStorage.string; NSInteger itemNumber = 1; NSRange currentParagraph = [fullText paragraphRangeForRange:NSMakeRange(index, 0)]; if (currentParagraph.location > 0) { - OrderedListStyle *olStyle = - input->stylesDict[@([OrderedListStyle getType])]; + OrderedListStyle *olStyle = host.stylesDict[@([OrderedListStyle getType])]; NSInteger prevParagraphsCount = 0; NSInteger recentParagraphLocation = @@ -392,16 +380,16 @@ - (CGRect)getTextAlignedUsedRect:(CGRect)usedRect font:(UIFont *)font { return usedRect; } -- (void)drawCheckbox:(EnrichedTextInputView *)typedInput +- (void)drawCheckbox:(id)host markerFormat:(NSString *)markerFormat origin:(CGPoint)origin usedRect:(CGRect)usedRect { BOOL isChecked = [markerFormat isEqualToString:@"EnrichedCheckbox1"]; - UIImage *image = isChecked ? typedInput->config.checkboxCheckedImage - : typedInput->config.checkboxUncheckedImage; - CGFloat gapWidth = [typedInput->config checkboxListGapWidth]; - CGFloat boxSize = [typedInput->config checkboxListBoxSize]; + UIImage *image = isChecked ? host.config.checkboxCheckedImage + : host.config.checkboxUncheckedImage; + CGFloat gapWidth = [host.config checkboxListGapWidth]; + CGFloat boxSize = [host.config checkboxListBoxSize]; CGFloat centerY = CGRectGetMidY(usedRect) + origin.y; CGFloat boxX = origin.x + usedRect.origin.x - gapWidth - boxSize; @@ -410,18 +398,18 @@ - (void)drawCheckbox:(EnrichedTextInputView *)typedInput [image drawAtPoint:CGPointMake(boxX, boxY)]; } -- (void)drawBullet:(EnrichedTextInputView *)typedInput +- (void)drawBullet:(id)host origin:(CGPoint)origin usedRect:(CGRect)usedRect { - CGFloat gapWidth = [typedInput->config unorderedListGapWidth]; - CGFloat bulletSize = [typedInput->config unorderedListBulletSize]; + CGFloat gapWidth = [host.config unorderedListGapWidth]; + CGFloat bulletSize = [host.config unorderedListBulletSize]; CGFloat bulletX = origin.x + usedRect.origin.x - gapWidth - bulletSize / 2; CGFloat centerY = CGRectGetMidY(usedRect) + origin.y; CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context); { - [[typedInput->config unorderedListBulletColor] setFill]; + [[host.config unorderedListBulletColor] setFill]; CGContextAddArc(context, bulletX, centerY, bulletSize / 2, 0, 2 * M_PI, YES); CGContextFillPath(context); @@ -429,12 +417,12 @@ - (void)drawBullet:(EnrichedTextInputView *)typedInput CGContextRestoreGState(context); } -- (void)drawDecimal:(EnrichedTextInputView *)typedInput +- (void)drawDecimal:(id)host marker:(NSString *)marker markerAttributes:(NSDictionary *)markerAttributes origin:(CGPoint)origin usedRect:(CGRect)usedRect { - CGFloat gapWidth = [typedInput->config orderedListGapWidth]; + CGFloat gapWidth = [host.config orderedListGapWidth]; CGSize markerSize = [marker sizeWithAttributes:markerAttributes]; CGFloat markerX = usedRect.origin.x - gapWidth - markerSize.width / 2; CGFloat centerY = CGRectGetMidY(usedRect) + origin.y; diff --git a/ios/htmlParser/HtmlParser.h b/ios/htmlParser/HtmlParser.h new file mode 100644 index 000000000..7453828c4 --- /dev/null +++ b/ios/htmlParser/HtmlParser.h @@ -0,0 +1,8 @@ +#pragma once +#import + +@interface HtmlParser : NSObject ++ (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html + useHtmlNormalizer:(BOOL)useHtmlNormalizer; ++ (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml; +@end diff --git a/ios/htmlParser/HtmlParser.mm b/ios/htmlParser/HtmlParser.mm new file mode 100644 index 000000000..a315ffffe --- /dev/null +++ b/ios/htmlParser/HtmlParser.mm @@ -0,0 +1,772 @@ +#import "HtmlParser.h" +#import "ImageData.h" +#import "LinkData.h" +#import "MentionParams.h" +#import "StringExtension.h" +#import "StyleHeaders.h" +#import "StylePair.h" + +#include "GumboParser.hpp" + +@implementation HtmlParser + ++ (BOOL)isBlockTag:(NSString *)tagName { + return [tagName isEqualToString:@"ul"] || [tagName isEqualToString:@"ol"] || + [tagName isEqualToString:@"blockquote"] || + [tagName isEqualToString:@"codeblock"]; +} + +/** + * Prepares HTML for the parser by stripping extraneous whitespace and newlines + * from structural tags, while preserving them within text content. + * + * APPROACH: + * This function treats the HTML as having two distinct states: + * 1. Structure Mode (Depth == 0): We are inside or between container tags (like + * blockquote, ul, codeblock). In this mode whitespace and newlines are + * considered layout artifacts and are REMOVED to prevent the parser from + * creating unwanted spaces. + * 2. Content Mode (Depth > 0): We are inside a text-containing tag (like p, + * b, li). In this mode, all whitespace is PRESERVED exactly as is, ensuring + * that sentences and inline formatting remain readable. + * + * The function iterates character-by-character, using a depth counter to track + * nesting levels of the specific tags defined in `textTags`. + * + * IMPORTANT: + * The `textTags` set acts as a whitelist for "Content Mode". If you add support + * for a new HTML tag that contains visible text (e.g., h4, h5, h6), + * you MUST add it to the `textTags` set below. + */ +- (NSString *)stripExtraWhiteSpacesAndNewlines:(NSString *)html { + NSSet *textTags = [NSSet setWithObjects:@"p", @"h1", @"h2", @"h3", @"h4", + @"h5", @"h6", @"li", @"b", @"a", @"s", + @"mention", @"code", @"u", @"i", nil]; + + NSMutableString *output = [NSMutableString stringWithCapacity:html.length]; + NSMutableString *currentTagBuffer = [NSMutableString string]; + NSCharacterSet *whitespaceAndNewlineSet = + [NSCharacterSet whitespaceAndNewlineCharacterSet]; + + BOOL isReadingTag = NO; + NSInteger textDepth = 0; + + for (NSUInteger i = 0; i < html.length; i++) { + unichar c = [html characterAtIndex:i]; + + if (c == '<') { + isReadingTag = YES; + [currentTagBuffer setString:@""]; + [output appendString:@"<"]; + } else if (c == '>') { + isReadingTag = NO; + [output appendString:@">"]; + + NSString *fullTag = [currentTagBuffer lowercaseString]; + + NSString *cleanName = [fullTag + stringByTrimmingCharactersInSet: + [NSCharacterSet characterSetWithCharactersInString:@"/"]]; + NSArray *parts = + [cleanName componentsSeparatedByCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *tagName = parts.firstObject; + + if (![textTags containsObject:tagName]) { + continue; + } + + if ([fullTag hasPrefix:@"/"]) { + textDepth--; + if (textDepth < 0) + textDepth = 0; + } else { + // Opening tag (e.g.

) -> Enter Text Mode + // (Ignore self-closing tags like if they happen to be in the + // list) + if (![fullTag hasSuffix:@"/"]) { + textDepth++; + } + } + } else { + if (isReadingTag) { + [currentTagBuffer appendFormat:@"%C", c]; + [output appendFormat:@"%C", c]; + continue; + } + + if (textDepth > 0) { + [output appendFormat:@"%C", c]; + } else { + if (![whitespaceAndNewlineSet characterIsMember:c]) { + [output appendFormat:@"%C", c]; + } + } + } + } + + return output; +} + +- (NSString *)stringByAddingNewlinesToTag:(NSString *)tag + inString:(NSString *)html + leading:(BOOL)leading + trailing:(BOOL)trailing { + NSString *str = [html copy]; + if (leading) { + NSString *formattedTag = [NSString stringWithFormat:@">%@", tag]; + NSString *formattedNewTag = [NSString stringWithFormat:@">\n%@", tag]; + str = [str stringByReplacingOccurrencesOfString:formattedTag + withString:formattedNewTag]; + } + if (trailing) { + NSString *formattedTag = [NSString stringWithFormat:@"%@<", tag]; + NSString *formattedNewTag = [NSString stringWithFormat:@"%@\n<", tag]; + str = [str stringByReplacingOccurrencesOfString:formattedTag + withString:formattedNewTag]; + } + return str; +} + +#pragma mark - External HTML normalization + +/** + * Normalizes external HTML (from Google Docs, Word, web pages, etc.) into our + * canonical tag subset using the Gumbo-based C++ normalizer. + * + * Converts: strong → b, em → i, span style="font-weight:bold" → b, + * strips unknown tags while preserving text + */ ++ (NSString *_Nullable)normalizeExternalHtml:(NSString *_Nonnull)html { + std::string result = + GumboParser::normalizeHtml(std::string([html UTF8String])); + if (result.empty()) + return nil; + return [NSString stringWithUTF8String:result.c_str()]; +} + ++ (void)finalizeTagEntry:(NSMutableString *)tagName + ongoingTags:(NSMutableDictionary *)ongoingTags + initiallyProcessedTags:(NSMutableArray *)processedTags + plainText:(NSMutableString *)plainText + precedingImageCount:(NSInteger *)precedingImageCount { + NSMutableArray *tagEntry = [[NSMutableArray alloc] init]; + + NSArray *tagData = ongoingTags[tagName]; + NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue]; + + // 'tagLocation' is an index based on 'plainText' which currently only holds + // raw text. + // + // Since 'plainText' does not yet contain the special placeholders for images, + // the indices for any text following an image are lower than they will be + // in the final NSTextStorage. + // + // We add 'precedingImageCount' to shift the start index forward, aligning + // this style's range with the actual position in the final text (where each + // image adds 1 character). + NSRange tagRange = NSMakeRange(tagLocation + *precedingImageCount, + plainText.length - tagLocation); + + [tagEntry addObject:[tagName copy]]; + [tagEntry addObject:[NSValue valueWithRange:tagRange]]; + if (tagData.count > 1) { + [tagEntry addObject:[(NSString *)tagData[1] copy]]; + } + + [processedTags addObject:tagEntry]; + [ongoingTags removeObjectForKey:tagName]; + + if ([tagName isEqualToString:@"img"]) { + (*precedingImageCount)++; + } +} + ++ (BOOL)isUlCheckboxList:(NSString *)params { + return ([params containsString:@"data-type=\"checkbox\""] || + [params containsString:@"data-type='checkbox'"]); +} + ++ (NSDictionary *)prepareCheckboxListStyleValue:(NSValue *)rangeValue + checkboxStates:(NSDictionary *)checkboxStates { + NSRange range = [rangeValue rangeValue]; + NSMutableDictionary *statesInRange = [[NSMutableDictionary alloc] init]; + + for (NSNumber *key in checkboxStates) { + NSUInteger pos = [key unsignedIntegerValue]; + if (pos >= range.location && pos < range.location + range.length) { + [statesInRange setObject:checkboxStates[key] forKey:key]; + } + } + + return statesInRange; +} + ++ (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html + useHtmlNormalizer:(BOOL)useHtmlNormalizer { + NSString *htmlWithoutSpaces = [self stripExtraWhiteSpacesAndNewlines:html]; + NSString *fixedHtml = nullptr; + + if (htmlWithoutSpaces.length >= 13) { + NSString *firstSix = + [htmlWithoutSpaces substringWithRange:NSMakeRange(0, 6)]; + NSString *lastSeven = [htmlWithoutSpaces + substringWithRange:NSMakeRange(htmlWithoutSpaces.length - 7, 7)]; + + if ([firstSix isEqualToString:@""] && + [lastSeven isEqualToString:@""]) { + // remove html tags, might be with newlines or without them + fixedHtml = [htmlWithoutSpaces copy]; + // firstly remove newlined html tags if any: + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" + withString:@""]; + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" + withString:@""]; + // fallback; remove html tags without their newlines + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"" + withString:@""]; + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"" + withString:@""]; + } else if (_input->useHtmlNormalizer) { + // External HTML (from Google Docs, Word, web pages, etc.) + // Run through the Gumbo-based normalizer to convert arbitrary HTML + // into our canonical tag subset. + NSString *normalized = [self normalizeExternalHtml:html]; + if (normalized != nil) { + fixedHtml = normalized; + } + } + + // Additionally, try getting the content from between body tags if there are + // some: + + // Firstly make sure there are no newlines between them. + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" + withString:@""]; + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" + withString:@""]; + // Then, if there actually are body tags, use the content between them. + NSRange openingBodyRange = [htmlWithoutSpaces rangeOfString:@""]; + NSRange closingBodyRange = [htmlWithoutSpaces rangeOfString:@""]; + if (openingBodyRange.length != 0 && closingBodyRange.length != 0) { + NSInteger newStart = openingBodyRange.location + 6; + NSInteger newEnd = closingBodyRange.location - 1; + fixedHtml = [htmlWithoutSpaces + substringWithRange:NSMakeRange(newStart, newEnd - newStart + 1)]; + } + } + + // second processing - try fixing htmls with wrong newlines' setup + if (fixedHtml != nullptr) { + // add
tag wherever needed + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"

" + withString:@"
"]; + + // remove

tags inside of

  • + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
  • " + withString:@"

  • "]; + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"

  • " + withString:@""]; + + // change
    to
    + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
    " + withString:@"
    "]; + + // remove

    tags around
    + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"


    " + withString:@"
    "]; + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"

    " + withString:@"
    "]; + + // add
    tags inside empty blockquote and codeblock tags + fixedHtml = [fixedHtml + stringByReplacingOccurrencesOfString:@"
    " + withString:@"

    "]; + fixedHtml = [fixedHtml + stringByReplacingOccurrencesOfString:@"" + withString:@"
    "]; + + // remove empty ul and ol tags + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
      " + withString:@""]; + fixedHtml = [fixedHtml + stringByReplacingOccurrencesOfString:@"
        " + withString:@""]; + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
          " + withString:@""]; + + // tags that have to be in separate lines + fixedHtml = [self stringByAddingNewlinesToTag:@"
          " + inString:fixedHtml + leading:YES + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
            " + inString:fixedHtml + leading:YES + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
          " + inString:fixedHtml + leading:YES + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
            " + inString:fixedHtml + leading:YES + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
          " + inString:fixedHtml + leading:YES + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
          " + inString:fixedHtml + leading:YES + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
          " + inString:fixedHtml + leading:YES + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:YES + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:YES + trailing:YES]; + + // line opening tags + fixedHtml = [self stringByAddingNewlinesToTag:@"

          " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

        1. " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
        2. " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

          " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

          " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

          " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

          " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

          " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
          " + inString:fixedHtml + leading:YES + trailing:NO]; + + // line closing tags + fixedHtml = [self stringByAddingNewlinesToTag:@"

          " + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
        3. " + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

          " + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + + // this is more like a hack but for some reason the last
          in + //
          and are not properly changed into zero width + // space so we do that manually here + fixedHtml = [fixedHtml + stringByReplacingOccurrencesOfString:@"
          \n
          " + withString:@"

          \u200B

          \n"]; + fixedHtml = [fixedHtml + stringByReplacingOccurrencesOfString:@"
          \n" + withString:@"

          \u200B

          \n"]; + + // replace "
          " at the end with "
          \n" if input is not empty to properly + // handle last
          in html + if ([fixedHtml hasSuffix:@"
          "] && fixedHtml.length != 4) { + fixedHtml = [fixedHtml stringByAppendingString:@"\n"]; + } + } + + return fixedHtml; +} + ++ (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml { + NSMutableString *plainText = [[NSMutableString alloc] initWithString:@""]; + NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init]; + NSMutableArray *initiallyProcessedTags = [[NSMutableArray alloc] init]; + NSMutableDictionary *checkboxStates = [[NSMutableDictionary alloc] init]; + BOOL insideCheckboxList = NO; + NSInteger precedingImageCount = 0; + BOOL insideTag = NO; + BOOL gettingTagName = NO; + BOOL gettingTagParams = NO; + BOOL closingTag = NO; + NSMutableString *currentTagName = + [[NSMutableString alloc] initWithString:@""]; + NSMutableString *currentTagParams = + [[NSMutableString alloc] initWithString:@""]; + NSDictionary *htmlEntitiesDict = + [NSString getEscapedCharactersInfoFrom:fixedHtml]; + + // firstly, extract text and initially processed tags + for (int i = 0; i < fixedHtml.length; i++) { + NSString *currentCharacterStr = + [fixedHtml substringWithRange:NSMakeRange(i, 1)]; + unichar currentCharacterChar = [fixedHtml characterAtIndex:i]; + + if (currentCharacterChar == '<') { + // opening the tag, mark that we are inside and getting its name + insideTag = YES; + gettingTagName = YES; + } else if (currentCharacterChar == '>') { + // finishing some tag, no longer marked as inside or getting its + // name/params + insideTag = NO; + gettingTagName = NO; + gettingTagParams = NO; + + BOOL isSelfClosing = NO; + + // Check if params ended with '/' (e.g. ) + if ([currentTagParams hasSuffix:@"/"]) { + [currentTagParams + deleteCharactersInRange:NSMakeRange(currentTagParams.length - 1, + 1)]; + isSelfClosing = YES; + } + + if ([currentTagName isEqualToString:@"p"] || + [currentTagName isEqualToString:@"br"]) { + // do nothing, we don't include these tags in styles + } else if ([currentTagName isEqualToString:@"li"]) { + // Only track checkbox state if we're inside a checkbox list + if (insideCheckboxList && !closingTag) { + BOOL isChecked = [currentTagParams containsString:@"checked"]; + checkboxStates[@(plainText.length)] = @(isChecked); + } + } else if (!closingTag) { + // we finish opening tag - get its location and optionally params and + // put them under tag name key in ongoingTags + NSMutableArray *tagArr = [[NSMutableArray alloc] init]; + [tagArr addObject:[NSNumber numberWithInteger:plainText.length]]; + if (currentTagParams.length > 0) { + [tagArr addObject:[currentTagParams copy]]; + } + ongoingTags[currentTagName] = tagArr; + + // Check if this is a checkbox list + if ([currentTagName isEqualToString:@"ul"] && + [self isUlCheckboxList:currentTagParams]) { + insideCheckboxList = YES; + } + + // skip one newline if it was added after opening tags that are in + // separate lines + if ([self isBlockTag:currentTagName] && i + 1 < fixedHtml.length && + [[NSCharacterSet newlineCharacterSet] + characterIsMember:[fixedHtml characterAtIndex:i + 1]]) { + i += 1; + } + + if (isSelfClosing) { + [self finalizeTagEntry:currentTagName + ongoingTags:ongoingTags + initiallyProcessedTags:initiallyProcessedTags + plainText:plainText + precedingImageCount:&precedingImageCount]; + } + } else { + // we finish closing tags - pack tag name, tag range and optionally tag + // params into an entry that goes inside initiallyProcessedTags + + // Check if we're closing a checkbox list by looking at the params + if ([currentTagName isEqualToString:@"ul"] && + [self isUlCheckboxList:currentTagParams]) { + insideCheckboxList = NO; + } + + BOOL isBlockTag = [self isBlockTag:currentTagName]; + + // skip one newline if it was added before some closing tags that are + // in separate lines + if (isBlockTag && plainText.length > 0 && + [[NSCharacterSet newlineCharacterSet] + characterIsMember:[plainText + characterAtIndex:plainText.length - 1]]) { + plainText = [[plainText + substringWithRange:NSMakeRange(0, plainText.length - 1)] + mutableCopy]; + } + + [self finalizeTagEntry:currentTagName + ongoingTags:ongoingTags + initiallyProcessedTags:initiallyProcessedTags + plainText:plainText + precedingImageCount:&precedingImageCount]; + } + // post-tag cleanup + closingTag = NO; + currentTagName = [[NSMutableString alloc] initWithString:@""]; + currentTagParams = [[NSMutableString alloc] initWithString:@""]; + } else { + if (!insideTag) { + // no tags logic - just append the right text + + // html entity on the index; use unescaped character and forward + // iterator accordingly + NSArray *entityInfo = htmlEntitiesDict[@(i)]; + if (entityInfo != nullptr) { + NSString *escaped = entityInfo[0]; + NSString *unescaped = entityInfo[1]; + [plainText appendString:unescaped]; + // the iterator will forward by 1 itself + i += escaped.length - 1; + } else { + [plainText appendString:currentCharacterStr]; + } + } else { + if (gettingTagName) { + if (currentCharacterChar == ' ') { + // no longer getting tag name - switch to params + gettingTagName = NO; + gettingTagParams = YES; + } else if (currentCharacterChar == '/') { + // mark that the tag is closing + closingTag = YES; + } else { + // append next tag char + [currentTagName appendString:currentCharacterStr]; + } + } else if (gettingTagParams) { + // append next tag params char + [currentTagParams appendString:currentCharacterStr]; + } + } + } + } + + // process tags into proper StyleType + StylePair values + NSMutableArray *processedStyles = [[NSMutableArray alloc] init]; + + for (NSArray *arr in initiallyProcessedTags) { + NSString *tagName = (NSString *)arr[0]; + NSValue *tagRangeValue = (NSValue *)arr[1]; + NSMutableString *params = [[NSMutableString alloc] initWithString:@""]; + if (arr.count > 2) { + [params appendString:(NSString *)arr[2]]; + } + + NSMutableArray *styleArr = [[NSMutableArray alloc] init]; + StylePair *stylePair = [[StylePair alloc] init]; + + if ([tagName isEqualToString:@"b"]) { + [styleArr addObject:@([BoldStyle getType])]; + } else if ([tagName isEqualToString:@"i"]) { + [styleArr addObject:@([ItalicStyle getType])]; + } else if ([tagName isEqualToString:@"img"]) { + NSRegularExpression *srcRegex = + [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\"" + options:0 + error:nullptr]; + NSTextCheckingResult *match = + [srcRegex firstMatchInString:params + options:0 + range:NSMakeRange(0, params.length)]; + + if (match == nullptr) { + continue; + } + + NSRange srcRange = match.range; + [styleArr addObject:@([ImageStyle getType])]; + // cut only the uri from the src="..." string + NSString *uri = + [params substringWithRange:NSMakeRange(srcRange.location + 5, + srcRange.length - 6)]; + ImageData *imageData = [[ImageData alloc] init]; + imageData.uri = uri; + + NSRegularExpression *widthRegex = [NSRegularExpression + regularExpressionWithPattern:@"width=\"([0-9.]+)\"" + options:0 + error:nil]; + NSTextCheckingResult *widthMatch = + [widthRegex firstMatchInString:params + options:0 + range:NSMakeRange(0, params.length)]; + + if (widthMatch) { + NSString *widthString = + [params substringWithRange:[widthMatch rangeAtIndex:1]]; + imageData.width = [widthString floatValue]; + } + + NSRegularExpression *heightRegex = [NSRegularExpression + regularExpressionWithPattern:@"height=\"([0-9.]+)\"" + options:0 + error:nil]; + NSTextCheckingResult *heightMatch = + [heightRegex firstMatchInString:params + options:0 + range:NSMakeRange(0, params.length)]; + + if (heightMatch) { + NSString *heightString = + [params substringWithRange:[heightMatch rangeAtIndex:1]]; + imageData.height = [heightString floatValue]; + } + + stylePair.styleValue = imageData; + } else if ([tagName isEqualToString:@"u"]) { + [styleArr addObject:@([UnderlineStyle getType])]; + } else if ([tagName isEqualToString:@"s"]) { + [styleArr addObject:@([StrikethroughStyle getType])]; + } else if ([tagName isEqualToString:@"code"]) { + [styleArr addObject:@([InlineCodeStyle getType])]; + } else if ([tagName isEqualToString:@"a"]) { + NSRegularExpression *hrefRegex = + [NSRegularExpression regularExpressionWithPattern:@"href=\".+\"" + options:0 + error:nullptr]; + NSTextCheckingResult *match = + [hrefRegex firstMatchInString:params + options:0 + range:NSMakeRange(0, params.length)]; + + if (match == nullptr) { + // same as on Android, no href (or empty href) equals no link style + continue; + } + + NSRange hrefRange = match.range; + [styleArr addObject:@([LinkStyle getType])]; + // cut only the url from the href="..." string + NSString *url = + [params substringWithRange:NSMakeRange(hrefRange.location + 6, + hrefRange.length - 7)]; + NSString *text = [plainText substringWithRange:tagRangeValue.rangeValue]; + + LinkData *linkData = [[LinkData alloc] init]; + linkData.url = url; + linkData.text = text; + linkData.isManual = ![text isEqualToString:url]; + + stylePair.styleValue = linkData; + } else if ([tagName isEqualToString:@"mention"]) { + [styleArr addObject:@([MentionStyle getType])]; + // extract html expression into dict using some regex + NSMutableDictionary *paramsDict = [[NSMutableDictionary alloc] init]; + NSString *pattern = @"(\\w+)=(['\"])(.*?)\\2"; + NSRegularExpression *regex = + [NSRegularExpression regularExpressionWithPattern:pattern + options:0 + error:nil]; + + [regex enumerateMatchesInString:params + options:0 + range:NSMakeRange(0, params.length) + usingBlock:^(NSTextCheckingResult *_Nullable result, + NSMatchingFlags flags, + BOOL *_Nonnull stop) { + if (result.numberOfRanges == 4) { + NSString *key = [params + substringWithRange:[result rangeAtIndex:1]]; + NSString *value = [params + substringWithRange:[result rangeAtIndex:3]]; + paramsDict[key] = value; + } + }]; + + MentionParams *mentionParams = [[MentionParams alloc] init]; + mentionParams.text = paramsDict[@"text"]; + mentionParams.indicator = paramsDict[@"indicator"]; + + [paramsDict removeObjectsForKeys:@[ @"text", @"indicator" ]]; + NSError *error; + NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict + options:0 + error:&error]; + NSString *formattedAttrsString = + [[NSString alloc] initWithData:attrsData + encoding:NSUTF8StringEncoding]; + mentionParams.attributes = formattedAttrsString; + + stylePair.styleValue = mentionParams; + } else if ([tagName isEqualToString:@"h1"]) { + [styleArr addObject:@([H1Style getType])]; + } else if ([tagName isEqualToString:@"h2"]) { + [styleArr addObject:@([H2Style getType])]; + } else if ([tagName isEqualToString:@"h3"]) { + [styleArr addObject:@([H3Style getType])]; + } else if ([tagName isEqualToString:@"h4"]) { + [styleArr addObject:@([H4Style getType])]; + } else if ([tagName isEqualToString:@"h5"]) { + [styleArr addObject:@([H5Style getType])]; + } else if ([tagName isEqualToString:@"h6"]) { + [styleArr addObject:@([H6Style getType])]; + } else if ([tagName isEqualToString:@"ul"]) { + if ([self isUlCheckboxList:params]) { + [styleArr addObject:@([CheckboxListStyle getType])]; + stylePair.styleValue = + [self prepareCheckboxListStyleValue:tagRangeValue + checkboxStates:checkboxStates]; + } else { + [styleArr addObject:@([UnorderedListStyle getType])]; + } + } else if ([tagName isEqualToString:@"ol"]) { + [styleArr addObject:@([OrderedListStyle getType])]; + } else if ([tagName isEqualToString:@"blockquote"]) { + [styleArr addObject:@([BlockQuoteStyle getType])]; + } else if ([tagName isEqualToString:@"codeblock"]) { + [styleArr addObject:@([CodeBlockStyle getType])]; + } else { + // some other external tags like span just don't get put into the + // processed styles + continue; + } + + stylePair.rangeValue = tagRangeValue; + [styleArr addObject:stylePair]; + [processedStyles addObject:styleArr]; + } + + return @[ plainText, processedStyles ]; +} + +@end diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm index dbe5f9d6c..5cd5bbc64 100644 --- a/ios/inputParser/InputParser.mm +++ b/ios/inputParser/InputParser.mm @@ -1,30 +1,20 @@ #import "InputParser.h" #import "EnrichedTextInputView.h" +#import "HtmlParser.h" #import "StringExtension.h" #import "StyleHeaders.h" #import "TextInsertionUtils.h" -#import "UIView+React.h" - -#include "GumboParser.hpp" @implementation InputParser { EnrichedTextInputView __weak *_input; - NSInteger _precedingImageCount; } - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; - _precedingImageCount = 0; return self; } -- (BOOL)isBlockTag:(NSString *)tagName { - return [tagName isEqualToString:@"ul"] || [tagName isEqualToString:@"ol"] || - [tagName isEqualToString:@"blockquote"] || - [tagName isEqualToString:@"codeblock"]; -} - - (NSString *)parseToHtmlFromRange:(NSRange)range { NSInteger offset = range.location; NSString *text = @@ -592,7 +582,7 @@ - (NSString *)tagContentForStyle:(NSNumber *)style } - (void)replaceWholeFromHtml:(NSString *_Nonnull)html { - NSArray *processingResult = [self getTextAndStylesFromHtml:html]; + NSArray *processingResult = [HtmlParser getTextAndStylesFromHtml:html]; NSString *plainText = (NSString *)processingResult[0]; NSArray *stylesInfo = (NSArray *)processingResult[1]; @@ -610,7 +600,7 @@ - (void)replaceWholeFromHtml:(NSString *_Nonnull)html { } - (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range { - NSArray *processingResult = [self getTextAndStylesFromHtml:html]; + NSArray *processingResult = [HtmlParser getTextAndStylesFromHtml:html]; NSString *plainText = (NSString *)processingResult[0]; NSArray *stylesInfo = (NSArray *)processingResult[1]; @@ -627,7 +617,7 @@ - (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range { } - (void)insertFromHtml:(NSString *_Nonnull)html location:(NSInteger)location { - NSArray *processingResult = [self getTextAndStylesFromHtml:html]; + NSArray *processingResult = [HtmlParser getTextAndStylesFromHtml:html]; NSString *plainText = (NSString *)processingResult[0]; NSArray *stylesInfo = (NSArray *)processingResult[1]; @@ -712,752 +702,9 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles [_input anyTextMayHaveBeenModified]; } -#pragma mark - External HTML normalization - -/** - * Normalizes external HTML (from Google Docs, Word, web pages, etc.) into our - * canonical tag subset using the Gumbo-based C++ normalizer. - * - * Converts: strong → b, em → i, span style="font-weight:bold" → b, - * strips unknown tags while preserving text - */ -- (NSString *_Nullable)normalizeExternalHtml:(NSString *_Nonnull)html { - std::string result = - GumboParser::normalizeHtml(std::string([html UTF8String])); - if (result.empty()) - return nil; - return [NSString stringWithUTF8String:result.c_str()]; -} - - (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html { - NSString *htmlWithoutSpaces = [self stripExtraWhiteSpacesAndNewlines:html]; - NSString *fixedHtml = nullptr; - - if (htmlWithoutSpaces.length >= 13) { - NSString *firstSix = - [htmlWithoutSpaces substringWithRange:NSMakeRange(0, 6)]; - NSString *lastSeven = [htmlWithoutSpaces - substringWithRange:NSMakeRange(htmlWithoutSpaces.length - 7, 7)]; - - if ([firstSix isEqualToString:@""] && - [lastSeven isEqualToString:@""]) { - // remove html tags, might be with newlines or without them - fixedHtml = [htmlWithoutSpaces copy]; - // firstly remove newlined html tags if any: - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" - withString:@""]; - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" - withString:@""]; - // fallback; remove html tags without their newlines - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"" - withString:@""]; - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"" - withString:@""]; - } else if (_input->useHtmlNormalizer) { - // External HTML (from Google Docs, Word, web pages, etc.) - // Run through the Gumbo-based normalizer to convert arbitrary HTML - // into our canonical tag subset. - NSString *normalized = [self normalizeExternalHtml:html]; - if (normalized != nil) { - fixedHtml = normalized; - } - } - - // Additionally, try getting the content from between body tags if there are - // some: - - // Firstly make sure there are no newlines between them. - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" - withString:@""]; - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" - withString:@""]; - // Then, if there actually are body tags, use the content between them. - NSRange openingBodyRange = [htmlWithoutSpaces rangeOfString:@""]; - NSRange closingBodyRange = [htmlWithoutSpaces rangeOfString:@""]; - if (openingBodyRange.length != 0 && closingBodyRange.length != 0) { - NSInteger newStart = openingBodyRange.location + 6; - NSInteger newEnd = closingBodyRange.location - 1; - fixedHtml = [htmlWithoutSpaces - substringWithRange:NSMakeRange(newStart, newEnd - newStart + 1)]; - } - } - - // second processing - try fixing htmls with wrong newlines' setup - if (fixedHtml != nullptr) { - // add
          tag wherever needed - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"

          " - withString:@"
          "]; - - // remove

          tags inside of

        4. - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
        5. " - withString:@"

        6. "]; - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"

        7. " - withString:@""]; - - // change
          to
          - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
          " - withString:@"
          "]; - - // remove

          tags around
          - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"


          " - withString:@"
          "]; - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"

          " - withString:@"
          "]; - - // add
          tags inside empty blockquote and codeblock tags - fixedHtml = [fixedHtml - stringByReplacingOccurrencesOfString:@"
          " - withString:@"

          "]; - fixedHtml = [fixedHtml - stringByReplacingOccurrencesOfString:@"" - withString:@"
          "]; - - // remove empty ul and ol tags - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
            " - withString:@""]; - fixedHtml = [fixedHtml - stringByReplacingOccurrencesOfString:@"
              " - withString:@""]; - fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
                " - withString:@""]; - - // tags that have to be in separate lines - fixedHtml = [self stringByAddingNewlinesToTag:@"
                " - inString:fixedHtml - leading:YES - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
                  " - inString:fixedHtml - leading:YES - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
                " - inString:fixedHtml - leading:YES - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
                  " - inString:fixedHtml - leading:YES - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
                " - inString:fixedHtml - leading:YES - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
                " - inString:fixedHtml - leading:YES - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
                " - inString:fixedHtml - leading:YES - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:YES - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:YES - trailing:YES]; - - // line opening tags - fixedHtml = [self stringByAddingNewlinesToTag:@"

                " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

              1. " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
              2. " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

                " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

                " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

                " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

                " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

                " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
                " - inString:fixedHtml - leading:YES - trailing:NO]; - - // line closing tags - fixedHtml = [self stringByAddingNewlinesToTag:@"

                " - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
              3. " - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:NO - trailing:YES]; - - // this is more like a hack but for some reason the last
                in - //
                and are not properly changed into zero width - // space so we do that manually here - fixedHtml = [fixedHtml - stringByReplacingOccurrencesOfString:@"
                \n
                " - withString:@"

                \u200B

                \n
                "]; - fixedHtml = [fixedHtml - stringByReplacingOccurrencesOfString:@"
                \n" - withString:@"

                \u200B

                \n"]; - - // replace "
                " at the end with "
                \n" if input is not empty to properly - // handle last
                in html - if ([fixedHtml hasSuffix:@"
                "] && fixedHtml.length != 4) { - fixedHtml = [fixedHtml stringByAppendingString:@"\n"]; - } - } - - return fixedHtml; -} - -/** - * Prepares HTML for the parser by stripping extraneous whitespace and newlines - * from structural tags, while preserving them within text content. - * - * APPROACH: - * This function treats the HTML as having two distinct states: - * 1. Structure Mode (Depth == 0): We are inside or between container tags (like - * blockquote, ul, codeblock). In this mode whitespace and newlines are - * considered layout artifacts and are REMOVED to prevent the parser from - * creating unwanted spaces. - * 2. Content Mode (Depth > 0): We are inside a text-containing tag (like p, - * b, li). In this mode, all whitespace is PRESERVED exactly as is, ensuring - * that sentences and inline formatting remain readable. - * - * The function iterates character-by-character, using a depth counter to track - * nesting levels of the specific tags defined in `textTags`. - * - * IMPORTANT: - * The `textTags` set acts as a whitelist for "Content Mode". If you add support - * for a new HTML tag that contains visible text (e.g., h4, h5, h6), - * you MUST add it to the `textTags` set below. - */ -- (NSString *)stripExtraWhiteSpacesAndNewlines:(NSString *)html { - NSSet *textTags = [NSSet setWithObjects:@"p", @"h1", @"h2", @"h3", @"h4", - @"h5", @"h6", @"li", @"b", @"a", @"s", - @"mention", @"code", @"u", @"i", nil]; - - NSMutableString *output = [NSMutableString stringWithCapacity:html.length]; - NSMutableString *currentTagBuffer = [NSMutableString string]; - NSCharacterSet *whitespaceAndNewlineSet = - [NSCharacterSet whitespaceAndNewlineCharacterSet]; - - BOOL isReadingTag = NO; - NSInteger textDepth = 0; - - for (NSUInteger i = 0; i < html.length; i++) { - unichar c = [html characterAtIndex:i]; - - if (c == '<') { - isReadingTag = YES; - [currentTagBuffer setString:@""]; - [output appendString:@"<"]; - } else if (c == '>') { - isReadingTag = NO; - [output appendString:@">"]; - - NSString *fullTag = [currentTagBuffer lowercaseString]; - - NSString *cleanName = [fullTag - stringByTrimmingCharactersInSet: - [NSCharacterSet characterSetWithCharactersInString:@"/"]]; - NSArray *parts = - [cleanName componentsSeparatedByCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]; - NSString *tagName = parts.firstObject; - - if (![textTags containsObject:tagName]) { - continue; - } - - if ([fullTag hasPrefix:@"/"]) { - textDepth--; - if (textDepth < 0) - textDepth = 0; - } else { - // Opening tag (e.g.

                ) -> Enter Text Mode - // (Ignore self-closing tags like if they happen to be in the - // list) - if (![fullTag hasSuffix:@"/"]) { - textDepth++; - } - } - } else { - if (isReadingTag) { - [currentTagBuffer appendFormat:@"%C", c]; - [output appendFormat:@"%C", c]; - continue; - } - - if (textDepth > 0) { - [output appendFormat:@"%C", c]; - } else { - if (![whitespaceAndNewlineSet characterIsMember:c]) { - [output appendFormat:@"%C", c]; - } - } - } - } - - return output; -} - -- (NSString *)stringByAddingNewlinesToTag:(NSString *)tag - inString:(NSString *)html - leading:(BOOL)leading - trailing:(BOOL)trailing { - NSString *str = [html copy]; - if (leading) { - NSString *formattedTag = [NSString stringWithFormat:@">%@", tag]; - NSString *formattedNewTag = [NSString stringWithFormat:@">\n%@", tag]; - str = [str stringByReplacingOccurrencesOfString:formattedTag - withString:formattedNewTag]; - } - if (trailing) { - NSString *formattedTag = [NSString stringWithFormat:@"%@<", tag]; - NSString *formattedNewTag = [NSString stringWithFormat:@"%@\n<", tag]; - str = [str stringByReplacingOccurrencesOfString:formattedTag - withString:formattedNewTag]; - } - return str; -} - -- (void)finalizeTagEntry:(NSMutableString *)tagName - ongoingTags:(NSMutableDictionary *)ongoingTags - initiallyProcessedTags:(NSMutableArray *)processedTags - plainText:(NSMutableString *)plainText { - NSMutableArray *tagEntry = [[NSMutableArray alloc] init]; - - NSArray *tagData = ongoingTags[tagName]; - NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue]; - - // 'tagLocation' is an index based on 'plainText' which currently only holds - // raw text. - // - // Since 'plainText' does not yet contain the special placeholders for images, - // the indices for any text following an image are lower than they will be - // in the final NSTextStorage. - // - // We add '_precedingImageCount' to shift the start index forward, aligning - // this style's range with the actual position in the final text (where each - // image adds 1 character). - NSRange tagRange = NSMakeRange(tagLocation + _precedingImageCount, - plainText.length - tagLocation); - - [tagEntry addObject:[tagName copy]]; - [tagEntry addObject:[NSValue valueWithRange:tagRange]]; - if (tagData.count > 1) { - [tagEntry addObject:[(NSString *)tagData[1] copy]]; - } - - [processedTags addObject:tagEntry]; - [ongoingTags removeObjectForKey:tagName]; - - if ([tagName isEqualToString:@"img"]) { - _precedingImageCount++; - } -} - -- (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml { - NSMutableString *plainText = [[NSMutableString alloc] initWithString:@""]; - NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init]; - NSMutableArray *initiallyProcessedTags = [[NSMutableArray alloc] init]; - NSMutableDictionary *checkboxStates = [[NSMutableDictionary alloc] init]; - BOOL insideCheckboxList = NO; - _precedingImageCount = 0; - BOOL insideTag = NO; - BOOL gettingTagName = NO; - BOOL gettingTagParams = NO; - BOOL closingTag = NO; - NSMutableString *currentTagName = - [[NSMutableString alloc] initWithString:@""]; - NSMutableString *currentTagParams = - [[NSMutableString alloc] initWithString:@""]; - NSDictionary *htmlEntitiesDict = - [NSString getEscapedCharactersInfoFrom:fixedHtml]; - - // firstly, extract text and initially processed tags - for (int i = 0; i < fixedHtml.length; i++) { - NSString *currentCharacterStr = - [fixedHtml substringWithRange:NSMakeRange(i, 1)]; - unichar currentCharacterChar = [fixedHtml characterAtIndex:i]; - - if (currentCharacterChar == '<') { - // opening the tag, mark that we are inside and getting its name - insideTag = YES; - gettingTagName = YES; - } else if (currentCharacterChar == '>') { - // finishing some tag, no longer marked as inside or getting its - // name/params - insideTag = NO; - gettingTagName = NO; - gettingTagParams = NO; - - BOOL isSelfClosing = NO; - - // Check if params ended with '/' (e.g. ) - if ([currentTagParams hasSuffix:@"/"]) { - [currentTagParams - deleteCharactersInRange:NSMakeRange(currentTagParams.length - 1, - 1)]; - isSelfClosing = YES; - } - - if ([currentTagName isEqualToString:@"p"] || - [currentTagName isEqualToString:@"br"]) { - // do nothing, we don't include these tags in styles - } else if ([currentTagName isEqualToString:@"li"]) { - // Only track checkbox state if we're inside a checkbox list - if (insideCheckboxList && !closingTag) { - BOOL isChecked = [currentTagParams containsString:@"checked"]; - checkboxStates[@(plainText.length)] = @(isChecked); - } - } else if (!closingTag) { - // we finish opening tag - get its location and optionally params and - // put them under tag name key in ongoingTags - NSMutableArray *tagArr = [[NSMutableArray alloc] init]; - [tagArr addObject:[NSNumber numberWithInteger:plainText.length]]; - if (currentTagParams.length > 0) { - [tagArr addObject:[currentTagParams copy]]; - } - ongoingTags[currentTagName] = tagArr; - - // Check if this is a checkbox list - if ([currentTagName isEqualToString:@"ul"] && - [self isUlCheckboxList:currentTagParams]) { - insideCheckboxList = YES; - } - - // skip one newline if it was added after opening tags that are in - // separate lines - if ([self isBlockTag:currentTagName] && i + 1 < fixedHtml.length && - [[NSCharacterSet newlineCharacterSet] - characterIsMember:[fixedHtml characterAtIndex:i + 1]]) { - i += 1; - } - - if (isSelfClosing) { - [self finalizeTagEntry:currentTagName - ongoingTags:ongoingTags - initiallyProcessedTags:initiallyProcessedTags - plainText:plainText]; - } - } else { - // we finish closing tags - pack tag name, tag range and optionally tag - // params into an entry that goes inside initiallyProcessedTags - - // Check if we're closing a checkbox list by looking at the params - if ([currentTagName isEqualToString:@"ul"] && - [self isUlCheckboxList:currentTagParams]) { - insideCheckboxList = NO; - } - - BOOL isBlockTag = [self isBlockTag:currentTagName]; - - // skip one newline if it was added before some closing tags that are - // in separate lines - if (isBlockTag && plainText.length > 0 && - [[NSCharacterSet newlineCharacterSet] - characterIsMember:[plainText - characterAtIndex:plainText.length - 1]]) { - plainText = [[plainText - substringWithRange:NSMakeRange(0, plainText.length - 1)] - mutableCopy]; - } - - [self finalizeTagEntry:currentTagName - ongoingTags:ongoingTags - initiallyProcessedTags:initiallyProcessedTags - plainText:plainText]; - } - // post-tag cleanup - closingTag = NO; - currentTagName = [[NSMutableString alloc] initWithString:@""]; - currentTagParams = [[NSMutableString alloc] initWithString:@""]; - } else { - if (!insideTag) { - // no tags logic - just append the right text - - // html entity on the index; use unescaped character and forward - // iterator accordingly - NSArray *entityInfo = htmlEntitiesDict[@(i)]; - if (entityInfo != nullptr) { - NSString *escaped = entityInfo[0]; - NSString *unescaped = entityInfo[1]; - [plainText appendString:unescaped]; - // the iterator will forward by 1 itself - i += escaped.length - 1; - } else { - [plainText appendString:currentCharacterStr]; - } - } else { - if (gettingTagName) { - if (currentCharacterChar == ' ') { - // no longer getting tag name - switch to params - gettingTagName = NO; - gettingTagParams = YES; - } else if (currentCharacterChar == '/') { - // mark that the tag is closing - closingTag = YES; - } else { - // append next tag char - [currentTagName appendString:currentCharacterStr]; - } - } else if (gettingTagParams) { - // append next tag params char - [currentTagParams appendString:currentCharacterStr]; - } - } - } - } - - // process tags into proper StyleType + StylePair values - NSMutableArray *processedStyles = [[NSMutableArray alloc] init]; - - for (NSArray *arr in initiallyProcessedTags) { - NSString *tagName = (NSString *)arr[0]; - NSValue *tagRangeValue = (NSValue *)arr[1]; - NSMutableString *params = [[NSMutableString alloc] initWithString:@""]; - if (arr.count > 2) { - [params appendString:(NSString *)arr[2]]; - } - - NSMutableArray *styleArr = [[NSMutableArray alloc] init]; - StylePair *stylePair = [[StylePair alloc] init]; - if ([tagName isEqualToString:@"b"]) { - [styleArr addObject:@([BoldStyle getType])]; - } else if ([tagName isEqualToString:@"i"]) { - [styleArr addObject:@([ItalicStyle getType])]; - } else if ([tagName isEqualToString:@"img"]) { - NSRegularExpression *srcRegex = - [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\"" - options:0 - error:nullptr]; - NSTextCheckingResult *match = - [srcRegex firstMatchInString:params - options:0 - range:NSMakeRange(0, params.length)]; - - if (match == nullptr) { - continue; - } - - NSRange srcRange = match.range; - [styleArr addObject:@([ImageStyle getType])]; - // cut only the uri from the src="..." string - NSString *uri = - [params substringWithRange:NSMakeRange(srcRange.location + 5, - srcRange.length - 6)]; - ImageData *imageData = [[ImageData alloc] init]; - imageData.uri = uri; - - NSRegularExpression *widthRegex = [NSRegularExpression - regularExpressionWithPattern:@"width=\"([0-9.]+)\"" - options:0 - error:nil]; - NSTextCheckingResult *widthMatch = - [widthRegex firstMatchInString:params - options:0 - range:NSMakeRange(0, params.length)]; - - if (widthMatch) { - NSString *widthString = - [params substringWithRange:[widthMatch rangeAtIndex:1]]; - imageData.width = [widthString floatValue]; - } - - NSRegularExpression *heightRegex = [NSRegularExpression - regularExpressionWithPattern:@"height=\"([0-9.]+)\"" - options:0 - error:nil]; - NSTextCheckingResult *heightMatch = - [heightRegex firstMatchInString:params - options:0 - range:NSMakeRange(0, params.length)]; - - if (heightMatch) { - NSString *heightString = - [params substringWithRange:[heightMatch rangeAtIndex:1]]; - imageData.height = [heightString floatValue]; - } - - stylePair.styleValue = imageData; - } else if ([tagName isEqualToString:@"u"]) { - [styleArr addObject:@([UnderlineStyle getType])]; - } else if ([tagName isEqualToString:@"s"]) { - [styleArr addObject:@([StrikethroughStyle getType])]; - } else if ([tagName isEqualToString:@"code"]) { - [styleArr addObject:@([InlineCodeStyle getType])]; - } else if ([tagName isEqualToString:@"a"]) { - NSRegularExpression *hrefRegex = - [NSRegularExpression regularExpressionWithPattern:@"href=\".+\"" - options:0 - error:nullptr]; - NSTextCheckingResult *match = - [hrefRegex firstMatchInString:params - options:0 - range:NSMakeRange(0, params.length)]; - - if (match == nullptr) { - // same as on Android, no href (or empty href) equals no link style - continue; - } - - NSRange hrefRange = match.range; - [styleArr addObject:@([LinkStyle getType])]; - // cut only the url from the href="..." string - NSString *url = - [params substringWithRange:NSMakeRange(hrefRange.location + 6, - hrefRange.length - 7)]; - NSString *text = [plainText substringWithRange:tagRangeValue.rangeValue]; - - LinkData *linkData = [[LinkData alloc] init]; - linkData.url = url; - linkData.text = text; - linkData.isManual = ![text isEqualToString:url]; - - stylePair.styleValue = linkData; - } else if ([tagName isEqualToString:@"mention"]) { - [styleArr addObject:@([MentionStyle getType])]; - // extract html expression into dict using some regex - NSMutableDictionary *paramsDict = [[NSMutableDictionary alloc] init]; - NSString *pattern = @"(\\w+)=(['\"])(.*?)\\2"; - NSRegularExpression *regex = - [NSRegularExpression regularExpressionWithPattern:pattern - options:0 - error:nil]; - - [regex enumerateMatchesInString:params - options:0 - range:NSMakeRange(0, params.length) - usingBlock:^(NSTextCheckingResult *_Nullable result, - NSMatchingFlags flags, - BOOL *_Nonnull stop) { - if (result.numberOfRanges == 4) { - NSString *key = [params - substringWithRange:[result rangeAtIndex:1]]; - NSString *value = [params - substringWithRange:[result rangeAtIndex:3]]; - paramsDict[key] = value; - } - }]; - - MentionParams *mentionParams = [[MentionParams alloc] init]; - mentionParams.text = paramsDict[@"text"]; - mentionParams.indicator = paramsDict[@"indicator"]; - - [paramsDict removeObjectsForKeys:@[ @"text", @"indicator" ]]; - NSError *error; - NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict - options:0 - error:&error]; - NSString *formattedAttrsString = - [[NSString alloc] initWithData:attrsData - encoding:NSUTF8StringEncoding]; - mentionParams.attributes = formattedAttrsString; - - stylePair.styleValue = mentionParams; - } else if ([tagName isEqualToString:@"h1"]) { - [styleArr addObject:@([H1Style getType])]; - } else if ([tagName isEqualToString:@"h2"]) { - [styleArr addObject:@([H2Style getType])]; - } else if ([tagName isEqualToString:@"h3"]) { - [styleArr addObject:@([H3Style getType])]; - } else if ([tagName isEqualToString:@"h4"]) { - [styleArr addObject:@([H4Style getType])]; - } else if ([tagName isEqualToString:@"h5"]) { - [styleArr addObject:@([H5Style getType])]; - } else if ([tagName isEqualToString:@"h6"]) { - [styleArr addObject:@([H6Style getType])]; - } else if ([tagName isEqualToString:@"ul"]) { - if ([self isUlCheckboxList:params]) { - [styleArr addObject:@([CheckboxListStyle getType])]; - stylePair.styleValue = - [self prepareCheckboxListStyleValue:tagRangeValue - checkboxStates:checkboxStates]; - } else { - [styleArr addObject:@([UnorderedListStyle getType])]; - } - } else if ([tagName isEqualToString:@"ol"]) { - [styleArr addObject:@([OrderedListStyle getType])]; - } else if ([tagName isEqualToString:@"blockquote"]) { - [styleArr addObject:@([BlockQuoteStyle getType])]; - } else if ([tagName isEqualToString:@"codeblock"]) { - [styleArr addObject:@([CodeBlockStyle getType])]; - } else { - // some other external tags like span just don't get put into the - // processed styles - continue; - } - - stylePair.rangeValue = tagRangeValue; - [styleArr addObject:stylePair]; - [processedStyles addObject:styleArr]; - } - - return @[ plainText, processedStyles ]; -} - -- (BOOL)isUlCheckboxList:(NSString *)params { - return ([params containsString:@"data-type=\"checkbox\""] || - [params containsString:@"data-type='checkbox'"]); -} - -- (NSDictionary *)prepareCheckboxListStyleValue:(NSValue *)rangeValue - checkboxStates:(NSDictionary *)checkboxStates { - NSRange range = [rangeValue rangeValue]; - NSMutableDictionary *statesInRange = [[NSMutableDictionary alloc] init]; - - for (NSNumber *key in checkboxStates) { - NSUInteger pos = [key unsignedIntegerValue]; - if (pos >= range.location && pos < range.location + range.length) { - [statesInRange setObject:checkboxStates[key] forKey:key]; - } - } - - return statesInRange; + return [HtmlParser initiallyProcessHtml:html + useHtmlNormalizer:_input->useHtmlNormalizer]; } @end diff --git a/ios/interfaces/BaseStyleProtocol.h b/ios/interfaces/BaseStyleProtocol.h index 5e08e558a..96a3d03ae 100644 --- a/ios/interfaces/BaseStyleProtocol.h +++ b/ios/interfaces/BaseStyleProtocol.h @@ -5,7 +5,7 @@ @protocol BaseStyleProtocol + (StyleType)getStyleType; + (BOOL)isParagraphStyle; -- (instancetype _Nonnull)initWithInput:(id _Nonnull)input; +- (instancetype _Nonnull)initWithHost:(id _Nonnull)host; - (void)applyStyle:(NSRange)range; - (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr; - (void)removeAttributes:(NSRange)range; diff --git a/ios/interfaces/StyleBase.h b/ios/interfaces/StyleBase.h index df65437ab..83bbfb0b7 100644 --- a/ios/interfaces/StyleBase.h +++ b/ios/interfaces/StyleBase.h @@ -1,19 +1,18 @@ #pragma once #import "AttributeEntry.h" +#import "EnrichedStyleHost.h" #import "StylePair.h" #import "StyleTypeEnum.h" #import -@class EnrichedTextInputView; - @interface StyleBase : NSObject -@property(nonatomic, weak) EnrichedTextInputView *input; +@property(nonatomic, weak) id host; + (StyleType)getType; - (NSString *)getKey; - (NSString *)getValue; - (BOOL)isParagraph; - (BOOL)needsZWS; -- (instancetype)initWithInput:(EnrichedTextInputView *)input; +- (instancetype)initWithHost:(id)host; - (NSRange)actualUsedRange:(NSRange)range; - (void)toggle:(NSRange)range; - (void)add:(NSRange)range diff --git a/ios/interfaces/StyleBase.mm b/ios/interfaces/StyleBase.mm index b91d4e685..6c1277fdc 100644 --- a/ios/interfaces/StyleBase.mm +++ b/ios/interfaces/StyleBase.mm @@ -1,6 +1,5 @@ #import "StyleBase.h" #import "AttributeEntry.h" -#import "EnrichedTextInputView.h" #import "OccurenceUtils.h" #import "RangeUtils.h" #import "ZeroWidthSpaceUtils.h" @@ -35,9 +34,9 @@ - (BOOL)needsZWS { return NO; } -- (instancetype)initWithInput:(EnrichedTextInputView *)input { +- (instancetype)initWithHost:(id)host { self = [super init]; - _input = input; + _host = host; return self; } @@ -45,7 +44,7 @@ - (instancetype)initWithInput:(EnrichedTextInputView *)input { - (NSRange)actualUsedRange:(NSRange)range { if (![self isParagraph]) return range; - return [_input->textView.textStorage.string paragraphRangeForRange:range]; + return [self.host.textView.textStorage.string paragraphRangeForRange:range]; } - (void)toggle:(NSRange)range { @@ -76,11 +75,11 @@ - (void)add:(NSRange)range NSRange actualRange = [self actualUsedRange:range]; if (![self isParagraph]) { - [_input->textView.textStorage addAttribute:[self getKey] - value:value - range:actualRange]; + [self.host.textView.textStorage addAttribute:[self getKey] + value:value + range:actualRange]; } else { - [_input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:actualRange options:0 @@ -93,7 +92,7 @@ - (void)add:(NSRange)range pStyle.textLists = @[ [[NSTextList alloc] initWithMarkerFormat:value options:0] ]; - [_input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:subRange]; @@ -106,7 +105,7 @@ - (void)add:(NSRange)range // Notify attributes manager of styling to be re-done if needed. if (withDirtyRange) { - [self.input->attributesManager addDirtyRange:actualRange]; + [self.host.attributesManager addDirtyRange:actualRange]; } } @@ -114,10 +113,10 @@ - (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange { NSRange actualRange = [self actualUsedRange:range]; if (![self isParagraph]) { - [_input->textView.textStorage removeAttribute:[self getKey] - range:actualRange]; + [self.host.textView.textStorage removeAttribute:[self getKey] + range:actualRange]; } else { - [_input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:actualRange options:0 @@ -128,7 +127,7 @@ - (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange { if (pStyle == nullptr) return; pStyle.textLists = @[]; - [_input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:subRange]; @@ -138,13 +137,13 @@ - (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange { // Notify attributes manager of styling to be re-done if needed. if (withDirtyRange) { - [self.input->attributesManager addDirtyRange:actualRange]; + [self.host.attributesManager addDirtyRange:actualRange]; } } - (void)addTypingWithValue:(NSString *)value { NSMutableDictionary *newTypingAttrs = - [_input->textView.typingAttributes mutableCopy]; + [self.host.textView.typingAttributes mutableCopy]; if (![self isParagraph]) { newTypingAttrs[[self getKey]] = value; @@ -156,18 +155,18 @@ - (void)addTypingWithValue:(NSString *)value { newTypingAttrs[NSParagraphStyleAttributeName] = pStyle; } - _input->textView.typingAttributes = newTypingAttrs; + self.host.textView.typingAttributes = newTypingAttrs; } - (void)removeTyping { NSMutableDictionary *newTypingAttrs = - [_input->textView.typingAttributes mutableCopy]; + [self.host.textView.typingAttributes mutableCopy]; if (![self isParagraph]) { [newTypingAttrs removeObjectForKey:[self getKey]]; // attributes manager also needs to be notified of custom attributes that // shouldn't be extended - [_input->attributesManager didRemoveTypingAttribute:[self getKey]]; + [self.host.attributesManager didRemoveTypingAttribute:[self getKey]]; } else { NSMutableParagraphStyle *pStyle = [newTypingAttrs[NSParagraphStyleAttributeName] mutableCopy]; @@ -175,7 +174,7 @@ - (void)removeTyping { newTypingAttrs[NSParagraphStyleAttributeName] = pStyle; } - _input->textView.typingAttributes = newTypingAttrs; + self.host.textView.typingAttributes = newTypingAttrs; } // custom styles (e.g. ImageStyle, MentionStyle) will likely need to override @@ -195,14 +194,14 @@ - (BOOL)styleCondition:(id)value range:(NSRange)range { - (BOOL)detect:(NSRange)range { if (range.length >= 1) { return [OccurenceUtils detect:[self getKey] - withInput:_input + withHost:self.host inRange:range withCondition:^BOOL(id _Nullable value, NSRange range) { return [self styleCondition:value range:range]; }]; } else { return [OccurenceUtils detect:[self getKey] - withInput:_input + withHost:self.host atIndex:range.location checkPrevious:[self isParagraph] withCondition:^BOOL(id _Nullable value, NSRange range) { @@ -213,7 +212,7 @@ - (BOOL)detect:(NSRange)range { - (BOOL)any:(NSRange)range { return [OccurenceUtils any:[self getKey] - withInput:_input + withHost:self.host inRange:range withCondition:^BOOL(id _Nullable value, NSRange range) { return [self styleCondition:value range:range]; @@ -222,7 +221,7 @@ - (BOOL)any:(NSRange)range { - (NSArray *)all:(NSRange)range { return [OccurenceUtils all:[self getKey] - withInput:_input + withHost:self.host inRange:range withCondition:^BOOL(id _Nullable value, NSRange range) { return [self styleCondition:value range:range]; diff --git a/ios/interfaces/StyleHeaders.h b/ios/interfaces/StyleHeaders.h index 7f1d4c885..247ce2451 100644 --- a/ios/interfaces/StyleHeaders.h +++ b/ios/interfaces/StyleHeaders.h @@ -27,6 +27,7 @@ - (NSRange)getFullLinkRangeAt:(NSUInteger)location; - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange; - (void)handleManualLinks:(NSString *)word inRange:(NSRange)wordRange; +- (void)applyLinkMetaWithData:(LinkData *)linkData range:(NSRange)range; @end @interface MentionStyle : StyleBase diff --git a/ios/styles/BlockQuoteStyle.mm b/ios/styles/BlockQuoteStyle.mm index 98f30587c..796a3cb52 100644 --- a/ios/styles/BlockQuoteStyle.mm +++ b/ios/styles/BlockQuoteStyle.mm @@ -21,9 +21,9 @@ - (BOOL)needsZWS { } - (void)applyStyling:(NSRange)range { - CGFloat indent = [self.input->config blockquoteBorderWidth] + - [self.input->config blockquoteGapWidth]; - [self.input->textView.textStorage + CGFloat indent = [self.host.config blockquoteBorderWidth] + + [self.host.config blockquoteGapWidth]; + [self.host.textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:range options:0 @@ -33,23 +33,22 @@ - (void)applyStyling:(NSRange)range { [(NSParagraphStyle *)value mutableCopy]; pStyle.headIndent = indent; pStyle.firstLineHeadIndent = indent; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:subRange]; }]; - UIColor *bqColor = [self.input->config blockquoteColor]; - [self.input->textView.textStorage addAttribute:NSForegroundColorAttributeName - value:bqColor - range:range]; - [self.input->textView.textStorage addAttribute:NSUnderlineColorAttributeName - value:bqColor - range:range]; - [self.input->textView.textStorage - addAttribute:NSStrikethroughColorAttributeName - value:bqColor - range:range]; + UIColor *bqColor = [self.host.config blockquoteColor]; + [self.host.textView.textStorage addAttribute:NSForegroundColorAttributeName + value:bqColor + range:range]; + [self.host.textView.textStorage addAttribute:NSUnderlineColorAttributeName + value:bqColor + range:range]; + [self.host.textView.textStorage addAttribute:NSStrikethroughColorAttributeName + value:bqColor + range:range]; } @end diff --git a/ios/styles/BoldStyle.mm b/ios/styles/BoldStyle.mm index c9f25123d..025866c9b 100644 --- a/ios/styles/BoldStyle.mm +++ b/ios/styles/BoldStyle.mm @@ -17,7 +17,7 @@ - (BOOL)isParagraph { } - (void)applyStyling:(NSRange)range { - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSFontAttributeName inRange:range options:0 @@ -26,7 +26,7 @@ - (void)applyStyling:(NSRange)range { UIFont *font = (UIFont *)value; if (font != nullptr) { UIFont *newFont = [font setBold]; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range]; diff --git a/ios/styles/CheckboxListStyle.mm b/ios/styles/CheckboxListStyle.mm index 6465db091..6cf8ef32b 100644 --- a/ios/styles/CheckboxListStyle.mm +++ b/ios/styles/CheckboxListStyle.mm @@ -22,11 +22,11 @@ - (BOOL)needsZWS { } - (void)applyStyling:(NSRange)range { - CGFloat listHeadIndent = [self.input->config checkboxListMarginLeft] + - [self.input->config checkboxListGapWidth] + - [self.input->config checkboxListBoxSize]; + CGFloat listHeadIndent = [self.host.config checkboxListMarginLeft] + + [self.host.config checkboxListGapWidth] + + [self.host.config checkboxListBoxSize]; - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:range options:0 @@ -36,7 +36,7 @@ - (void)applyStyling:(NSRange)range { [(NSParagraphStyle *)value mutableCopy]; pStyle.headIndent = listHeadIndent; pStyle.firstLineHeadIndent = listHeadIndent; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range]; @@ -88,20 +88,20 @@ - (void)reapplyFromStylePair:(StylePair *)pair { } - (void)toggleCheckedAt:(NSUInteger)location { - if (location >= self.input->textView.textStorage.length) { + if (location >= self.host.textView.textStorage.length) { return; } NSParagraphStyle *pStyle = - [self.input->textView.textStorage attribute:NSParagraphStyleAttributeName - atIndex:location - effectiveRange:NULL]; + [self.host.textView.textStorage attribute:NSParagraphStyleAttributeName + atIndex:location + effectiveRange:NULL]; NSTextList *list = pStyle.textLists.firstObject; BOOL isCurrentlyChecked = [list.markerFormat isEqualToString:@"EnrichedCheckbox1"]; - NSRange paragraphRange = [self.input->textView.textStorage.string + NSRange paragraphRange = [self.host.textView.textStorage.string paragraphRangeForRange:NSMakeRange(location, 0)]; [self addWithChecked:!isCurrentlyChecked @@ -111,14 +111,14 @@ - (void)toggleCheckedAt:(NSUInteger)location { } - (BOOL)getCheckboxStateAt:(NSUInteger)location { - if (location >= self.input->textView.textStorage.length) { + if (location >= self.host.textView.textStorage.length) { return NO; } NSParagraphStyle *style = - [self.input->textView.textStorage attribute:NSParagraphStyleAttributeName - atIndex:location - effectiveRange:NULL]; + [self.host.textView.textStorage attribute:NSParagraphStyleAttributeName + atIndex:location + effectiveRange:NULL]; if (style && style.textLists.count > 0) { NSTextList *list = style.textLists.firstObject; @@ -131,18 +131,18 @@ - (BOOL)getCheckboxStateAt:(NSUInteger)location { } - (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text { - if ([self detect:self.input->textView.selectedRange] && text.length > 0 && + if ([self detect:self.host.textView.selectedRange] && text.length > 0 && [[NSCharacterSet newlineCharacterSet] characterIsMember:[text characterAtIndex:text.length - 1]]) { // do the replacement manually [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr - input:self.input + input:self.host withSelection:YES]; // apply unchecked checkbox attributes to the new paragraph [self addWithChecked:NO - range:self.input->textView.selectedRange + range:self.host.textView.selectedRange withTyping:YES withDirtyRange:YES]; return YES; diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm index 65d44af59..74c3dbeb1 100644 --- a/ios/styles/CodeBlockStyle.mm +++ b/ios/styles/CodeBlockStyle.mm @@ -21,7 +21,7 @@ - (BOOL)needsZWS { } - (void)applyStyling:(NSRange)range { - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSFontAttributeName inRange:range options:0 @@ -30,19 +30,19 @@ - (void)applyStyling:(NSRange)range { UIFont *currentFont = (UIFont *)value; if (currentFont == nullptr) return; - UIFont *monoFont = [[[self.input->config monospacedFont] + UIFont *monoFont = [[[self.host.config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; if (monoFont != nullptr) { - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSFontAttributeName value:monoFont range:subRange]; } }]; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSForegroundColorAttributeName - value:[self.input->config codeBlockFgColor] + value:[self.host.config codeBlockFgColor] range:range]; } diff --git a/ios/styles/H1Style.mm b/ios/styles/H1Style.mm index c04c26f3b..c68c46a62 100644 --- a/ios/styles/H1Style.mm +++ b/ios/styles/H1Style.mm @@ -12,9 +12,9 @@ - (BOOL)isParagraph { return YES; } - (CGFloat)getHeadingFontSize { - return [self.input->config h1FontSize]; + return [self.host.config h1FontSize]; } - (BOOL)isHeadingBold { - return [self.input->config h1Bold]; + return [self.host.config h1Bold]; } @end diff --git a/ios/styles/H2Style.mm b/ios/styles/H2Style.mm index 785b2b72a..98021bad0 100644 --- a/ios/styles/H2Style.mm +++ b/ios/styles/H2Style.mm @@ -12,9 +12,9 @@ - (BOOL)isParagraph { return YES; } - (CGFloat)getHeadingFontSize { - return [self.input->config h2FontSize]; + return [self.host.config h2FontSize]; } - (BOOL)isHeadingBold { - return [self.input->config h2Bold]; + return [self.host.config h2Bold]; } @end diff --git a/ios/styles/H3Style.mm b/ios/styles/H3Style.mm index 9091f6e3b..41190238b 100644 --- a/ios/styles/H3Style.mm +++ b/ios/styles/H3Style.mm @@ -12,9 +12,9 @@ - (BOOL)isParagraph { return YES; } - (CGFloat)getHeadingFontSize { - return [self.input->config h3FontSize]; + return [self.host.config h3FontSize]; } - (BOOL)isHeadingBold { - return [self.input->config h3Bold]; + return [self.host.config h3Bold]; } @end diff --git a/ios/styles/H4Style.mm b/ios/styles/H4Style.mm index a641f5ed8..135412af1 100644 --- a/ios/styles/H4Style.mm +++ b/ios/styles/H4Style.mm @@ -12,9 +12,9 @@ - (BOOL)isParagraph { return YES; } - (CGFloat)getHeadingFontSize { - return [self.input->config h4FontSize]; + return [self.host.config h4FontSize]; } - (BOOL)isHeadingBold { - return [self.input->config h4Bold]; + return [self.host.config h4Bold]; } @end diff --git a/ios/styles/H5Style.mm b/ios/styles/H5Style.mm index 40fab0f28..01f77517c 100644 --- a/ios/styles/H5Style.mm +++ b/ios/styles/H5Style.mm @@ -12,9 +12,9 @@ - (BOOL)isParagraph { return YES; } - (CGFloat)getHeadingFontSize { - return [self.input->config h5FontSize]; + return [self.host.config h5FontSize]; } - (BOOL)isHeadingBold { - return [self.input->config h5Bold]; + return [self.host.config h5Bold]; } @end diff --git a/ios/styles/H6Style.mm b/ios/styles/H6Style.mm index 2e576bb0a..0f87b37fe 100644 --- a/ios/styles/H6Style.mm +++ b/ios/styles/H6Style.mm @@ -12,9 +12,9 @@ - (BOOL)isParagraph { return YES; } - (CGFloat)getHeadingFontSize { - return [self.input->config h6FontSize]; + return [self.host.config h6FontSize]; } - (BOOL)isHeadingBold { - return [self.input->config h6Bold]; + return [self.host.config h6Bold]; } @end diff --git a/ios/styles/HeadingStyleBase.mm b/ios/styles/HeadingStyleBase.mm index 6c2e83a2c..15c389d45 100644 --- a/ios/styles/HeadingStyleBase.mm +++ b/ios/styles/HeadingStyleBase.mm @@ -23,7 +23,7 @@ - (BOOL)isParagraph { } - (void)applyStyling:(NSRange)range { - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSFontAttributeName inRange:range options:0 @@ -36,27 +36,26 @@ - (void)applyStyling:(NSRange)range { if ([self isHeadingBold]) { newFont = [newFont setBold]; } - [self.input->textView.textStorage - addAttribute:NSFontAttributeName - value:newFont - range:subRange]; + [self.host.textView.textStorage addAttribute:NSFontAttributeName + value:newFont + range:subRange]; }]; } // used to make sure headings dont persist after a newline is placed - (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text { // in a heading and a new text ends with a newline - if ([self detect:self.input->textView.selectedRange] && text.length > 0 && + if ([self detect:self.host.textView.selectedRange] && text.length > 0 && [[NSCharacterSet newlineCharacterSet] characterIsMember:[text characterAtIndex:text.length - 1]]) { // do the replacement manually [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr - input:self.input + input:self.host withSelection:YES]; // remove the attributes at the new selection - [self remove:self.input->textView.selectedRange withDirtyRange:YES]; + [self remove:self.host.textView.selectedRange withDirtyRange:YES]; return YES; } return NO; diff --git a/ios/styles/ImageStyle.mm b/ios/styles/ImageStyle.mm index 4149e30c1..dfc0c58c1 100644 --- a/ios/styles/ImageStyle.mm +++ b/ios/styles/ImageStyle.mm @@ -33,13 +33,13 @@ - (void)reapplyFromStylePair:(StylePair *)pair { ImageAttachment *attachment = [[ImageAttachment alloc] initWithImageData:imageData]; - attachment.delegate = self.input; + attachment.delegate = (id)self.host; - [self.input->textView.textStorage addAttributes:@{ + [self.host.textView.textStorage addAttributes:@{ NSAttachmentAttributeName : attachment, ImageAttributeName : imageData } - range:range]; + range:range]; } - (AttributeEntry *)getEntryIfPresent:(NSRange)range { @@ -51,15 +51,15 @@ - (void)toggle:(NSRange)range { } - (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange { - [self.input->textView.textStorage beginEditing]; - [self.input->textView.textStorage removeAttribute:ImageAttributeName - range:range]; - [self.input->textView.textStorage removeAttribute:NSAttachmentAttributeName - range:range]; - [self.input->textView.textStorage endEditing]; + [self.host.textView.textStorage beginEditing]; + [self.host.textView.textStorage removeAttribute:ImageAttributeName + range:range]; + [self.host.textView.textStorage removeAttribute:NSAttachmentAttributeName + range:range]; + [self.host.textView.textStorage endEditing]; if (withDirtyRange) { - [self.input->attributesManager addDirtyRange:range]; + [self.host.attributesManager addDirtyRange:range]; } [self removeTyping]; @@ -67,11 +67,11 @@ - (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange { - (void)removeTyping { NSMutableDictionary *currentAttributes = - [self.input->textView.typingAttributes mutableCopy]; + [self.host.textView.typingAttributes mutableCopy]; [currentAttributes removeObjectForKey:ImageAttributeName]; [currentAttributes removeObjectForKey:NSAttachmentAttributeName]; - [self.input->attributesManager didRemoveTypingAttribute:ImageAttributeName]; - self.input->textView.typingAttributes = currentAttributes; + [self.host.attributesManager didRemoveTypingAttribute:ImageAttributeName]; + self.host.textView.typingAttributes = currentAttributes; } - (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range { @@ -80,19 +80,19 @@ - (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range { - (ImageData *)getImageDataAt:(NSUInteger)location { NSRange imageRange = NSMakeRange(0, 0); - NSRange inputRange = NSMakeRange(0, self.input->textView.textStorage.length); + NSRange inputRange = NSMakeRange(0, self.host.textView.textStorage.length); // don't search at the very end of input NSUInteger searchLocation = location; - if (searchLocation == self.input->textView.textStorage.length) { + if (searchLocation == self.host.textView.textStorage.length) { return nullptr; } ImageData *imageData = - [self.input->textView.textStorage attribute:ImageAttributeName - atIndex:searchLocation - longestEffectiveRange:&imageRange - inRange:inputRange]; + [self.host.textView.textStorage attribute:ImageAttributeName + atIndex:searchLocation + longestEffectiveRange:&imageRange + inRange:inputRange]; return imageData; } @@ -105,7 +105,7 @@ - (void)addImageAtRange:(NSRange)range ImageAttachment *attachment = [[ImageAttachment alloc] initWithImageData:imageData]; - attachment.delegate = self.input; + attachment.delegate = (id)self.host; NSDictionary *attributes = @{NSAttachmentAttributeName : attachment, ImageAttributeName : imageData}; @@ -118,18 +118,18 @@ - (void)addImageAtRange:(NSRange)range [TextInsertionUtils insertText:placeholderChar at:range.location additionalAttributes:attributes - input:self.input + input:self.host withSelection:withSelection]; } else { [TextInsertionUtils replaceText:placeholderChar at:range additionalAttributes:attributes - input:self.input + input:self.host withSelection:withSelection]; } NSRange insertedImageRange = NSMakeRange(range.location, 1); - [self.input->attributesManager addDirtyRange:insertedImageRange]; + [self.host.attributesManager addDirtyRange:insertedImageRange]; } - (void)addImage:(NSString *)uri width:(CGFloat)width height:(CGFloat)height { @@ -138,7 +138,7 @@ - (void)addImage:(NSString *)uri width:(CGFloat)width height:(CGFloat)height { data.width = width; data.height = height; - [self addImageAtRange:self.input->textView.selectedRange + [self addImageAtRange:self.host.textView.selectedRange imageData:data withSelection:YES]; } diff --git a/ios/styles/InlineCodeStyle.mm b/ios/styles/InlineCodeStyle.mm index 054332a75..4511858ab 100644 --- a/ios/styles/InlineCodeStyle.mm +++ b/ios/styles/InlineCodeStyle.mm @@ -21,29 +21,29 @@ - (BOOL)isParagraph { - (void)applyStyling:(NSRange)range { // we don't want to apply inline code to newline characters, it looks bad NSArray *nonNewlineRanges = - [RangeUtils getNonNewlineRangesIn:self.input->textView range:range]; + [RangeUtils getNonNewlineRangesIn:self.host.textView range:range]; for (NSValue *value in nonNewlineRanges) { NSRange subRange = [value rangeValue]; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSBackgroundColorAttributeName - value:[[self.input->config inlineCodeBgColor] + value:[[self.host.config inlineCodeBgColor] colorWithAlphaIfNotTransparent:0.4] range:subRange]; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSForegroundColorAttributeName - value:[self.input->config inlineCodeFgColor] + value:[self.host.config inlineCodeFgColor] range:subRange]; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSUnderlineColorAttributeName - value:[self.input->config inlineCodeFgColor] + value:[self.host.config inlineCodeFgColor] range:subRange]; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSStrikethroughColorAttributeName - value:[self.input->config inlineCodeFgColor] + value:[self.host.config inlineCodeFgColor] range:subRange]; - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSFontAttributeName inRange:subRange options:0 @@ -51,9 +51,9 @@ - (void)applyStyling:(NSRange)range { BOOL *_Nonnull stop) { UIFont *font = (UIFont *)value; if (font != nullptr) { - UIFont *newFont = [[[self.input->config monospacedFont] + UIFont *newFont = [[[self.host.config monospacedFont] withFontTraits:font] setSize:font.pointSize]; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSFontAttributeName value:newFont range:fontRange]; diff --git a/ios/styles/ItalicStyle.mm b/ios/styles/ItalicStyle.mm index 963a213e6..f2a161141 100644 --- a/ios/styles/ItalicStyle.mm +++ b/ios/styles/ItalicStyle.mm @@ -17,7 +17,7 @@ - (BOOL)isParagraph { } - (void)applyStyling:(NSRange)range { - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSFontAttributeName inRange:range options:0 @@ -26,7 +26,7 @@ - (void)applyStyling:(NSRange)range { UIFont *font = (UIFont *)value; if (font != nullptr) { UIFont *newFont = [font setItalic]; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range]; diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm index 626969e86..3cc00762c 100644 --- a/ios/styles/LinkStyle.mm +++ b/ios/styles/LinkStyle.mm @@ -31,13 +31,13 @@ - (void)applyStyling:(NSRange)range { } NSMutableDictionary *newAttrs = [[NSMutableDictionary alloc] init]; - newAttrs[NSForegroundColorAttributeName] = [self.input->config linkColor]; - newAttrs[NSUnderlineColorAttributeName] = [self.input->config linkColor]; - newAttrs[NSStrikethroughColorAttributeName] = [self.input->config linkColor]; - if ([self.input->config linkDecorationLine] == DecorationUnderline) { + newAttrs[NSForegroundColorAttributeName] = [self.host.config linkColor]; + newAttrs[NSUnderlineColorAttributeName] = [self.host.config linkColor]; + newAttrs[NSStrikethroughColorAttributeName] = [self.host.config linkColor]; + if ([self.host.config linkDecorationLine] == DecorationUnderline) { newAttrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle); } - [self.input->textView.textStorage addAttributes:newAttrs range:range]; + [self.host.textView.textStorage addAttributes:newAttrs range:range]; } - (void)reapplyFromStylePair:(StylePair *)pair { @@ -63,34 +63,34 @@ - (void)toggle:(NSRange)range { // we have to make sure all links in the range get fully removed here - (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange { NSArray *links = [self all:range]; - [self.input->textView.textStorage beginEditing]; + [self.host.textView.textStorage beginEditing]; for (StylePair *pair in links) { NSRange linkRange = [self getFullLinkRangeAt:[pair.rangeValue rangeValue].location]; - [self.input->textView.textStorage removeAttribute:ManualLinkAttributeName - range:linkRange]; - [self.input->textView.textStorage removeAttribute:AutomaticLinkAttributeName - range:linkRange]; + [self.host.textView.textStorage removeAttribute:ManualLinkAttributeName + range:linkRange]; + [self.host.textView.textStorage removeAttribute:AutomaticLinkAttributeName + range:linkRange]; if (withDirtyRange) { - [self.input->attributesManager addDirtyRange:linkRange]; + [self.host.attributesManager addDirtyRange:linkRange]; } } - [self.input->textView.textStorage endEditing]; + [self.host.textView.textStorage endEditing]; [self removeLinkMetaFromTypingAttributes]; } // used for conflicts, we have to remove the whole link - (void)removeTyping { NSRange linkRange = - [self getFullLinkRangeAt:self.input->textView.selectedRange.location]; + [self getFullLinkRangeAt:self.host.textView.selectedRange.location]; if (linkRange.length > 0) { - [self.input->textView.textStorage beginEditing]; - [self.input->textView.textStorage removeAttribute:ManualLinkAttributeName - range:linkRange]; - [self.input->textView.textStorage removeAttribute:AutomaticLinkAttributeName - range:linkRange]; - [self.input->textView.textStorage endEditing]; - [self.input->attributesManager addDirtyRange:linkRange]; + [self.host.textView.textStorage beginEditing]; + [self.host.textView.textStorage removeAttribute:ManualLinkAttributeName + range:linkRange]; + [self.host.textView.textStorage removeAttribute:AutomaticLinkAttributeName + range:linkRange]; + [self.host.textView.textStorage endEditing]; + [self.host.attributesManager addDirtyRange:linkRange]; } [self removeLinkMetaFromTypingAttributes]; } @@ -104,7 +104,7 @@ - (BOOL)detect:(NSRange)range { if (range.length >= 1) { BOOL onlyLinks = [OccurenceUtils detectMultiple:@[ ManualLinkAttributeName, AutomaticLinkAttributeName ] - withInput:self.input + withHost:self.host inRange:range withCondition:^BOOL(id _Nullable value, NSRange subrange) { return [self styleCondition:value range:subrange]; @@ -117,7 +117,7 @@ - (BOOL)detect:(NSRange)range { - (BOOL)any:(NSRange)range { return [OccurenceUtils anyMultiple:@[ ManualLinkAttributeName, AutomaticLinkAttributeName ] - withInput:self.input + withHost:self.host inRange:range withCondition:^BOOL(id _Nullable value, NSRange subrange) { return [self styleCondition:value range:subrange]; @@ -127,7 +127,7 @@ - (BOOL)any:(NSRange)range { - (NSArray *)all:(NSRange)range { return [OccurenceUtils allMultiple:@[ ManualLinkAttributeName, AutomaticLinkAttributeName ] - withInput:self.input + withHost:self.host inRange:range withCondition:^BOOL(id _Nullable value, NSRange subrange) { return [self styleCondition:value range:subrange]; @@ -140,21 +140,21 @@ - (void)applyLinkMetaWithData:(LinkData *)linkData range:(NSRange)range { } NSString *key = linkData.isManual ? ManualLinkAttributeName : AutomaticLinkAttributeName; - [self.input->textView.textStorage addAttribute:key - value:[linkData copy] - range:range]; + [self.host.textView.textStorage addAttribute:key + value:[linkData copy] + range:range]; } - (void)removeLinkMetaFromTypingAttributes { NSMutableDictionary *newTypingAttrs = - [self.input->textView.typingAttributes mutableCopy]; + [self.host.textView.typingAttributes mutableCopy]; [newTypingAttrs removeObjectForKey:ManualLinkAttributeName]; [newTypingAttrs removeObjectForKey:AutomaticLinkAttributeName]; - self.input->textView.typingAttributes = newTypingAttrs; + self.host.textView.typingAttributes = newTypingAttrs; - [self.input->attributesManager + [self.host.attributesManager didRemoveTypingAttribute:ManualLinkAttributeName]; - [self.input->attributesManager + [self.host.attributesManager didRemoveTypingAttribute:AutomaticLinkAttributeName]; } @@ -162,7 +162,7 @@ - (void)addLink:(LinkData *)linkData range:(NSRange)range withSelection:(BOOL)withSelection { NSString *currentText = - [self.input->textView.textStorage.string substringWithRange:range]; + [self.host.textView.textStorage.string substringWithRange:range]; NSString *key = linkData.isManual ? ManualLinkAttributeName : AutomaticLinkAttributeName; @@ -175,7 +175,7 @@ - (void)addLink:(LinkData *)linkData [TextInsertionUtils insertText:linkData.text at:range.location additionalAttributes:metaAttrs - input:self.input + input:self.host withSelection:withSelection]; dirtyRange = NSMakeRange(range.location, linkData.text.length); } else if ([currentText isEqualToString:linkData.text]) { @@ -185,8 +185,8 @@ - (void)addLink:(LinkData *)linkData // manually set it behind the link ONLY with manual links, automatic ones // don't need the selection fix if (linkData.isManual && withSelection) { - [self.input->textView reactFocus]; - self.input->textView.selectedRange = + [self.host.textView reactFocus]; + self.host.textView.selectedRange = NSMakeRange(range.location + linkData.text.length, 0); } } else { @@ -194,18 +194,18 @@ - (void)addLink:(LinkData *)linkData [TextInsertionUtils replaceText:linkData.text at:range additionalAttributes:metaAttrs - input:self.input + input:self.host withSelection:withSelection]; dirtyRange = NSMakeRange(range.location, linkData.text.length); } // add new dirty range - [self.input->attributesManager addDirtyRange:dirtyRange]; + [self.host.attributesManager addDirtyRange:dirtyRange]; // mandatory connected links check NSDictionary *currentWord = - [WordsUtils getCurrentWord:self.input->textView.textStorage.string - range:self.input->textView.selectedRange]; + [WordsUtils getCurrentWord:self.host.textView.textStorage.string + range:self.host.textView.selectedRange]; if (currentWord != nullptr) { // get word properties NSString *wordText = (NSString *)[currentWord objectForKey:@"word"]; @@ -221,24 +221,24 @@ - (void)addLink:(LinkData *)linkData - (LinkData *)getLinkDataAt:(NSUInteger)location { NSRange manualLinkRange = NSMakeRange(0, 0); NSRange automaticLinkRange = NSMakeRange(0, 0); - NSRange inputRange = NSMakeRange(0, self.input->textView.textStorage.length); + NSRange inputRange = NSMakeRange(0, self.host.textView.textStorage.length); // don't search at the very end of input NSUInteger searchLocation = location; - if (searchLocation == self.input->textView.textStorage.length) { + if (searchLocation == self.host.textView.textStorage.length) { return nullptr; } LinkData *manualData = - [self.input->textView.textStorage attribute:ManualLinkAttributeName - atIndex:searchLocation - longestEffectiveRange:&manualLinkRange - inRange:inputRange]; + [self.host.textView.textStorage attribute:ManualLinkAttributeName + atIndex:searchLocation + longestEffectiveRange:&manualLinkRange + inRange:inputRange]; LinkData *automaticData = - [self.input->textView.textStorage attribute:AutomaticLinkAttributeName - atIndex:searchLocation - longestEffectiveRange:&automaticLinkRange - inRange:inputRange]; + [self.host.textView.textStorage attribute:AutomaticLinkAttributeName + atIndex:searchLocation + longestEffectiveRange:&automaticLinkRange + inRange:inputRange]; if ((manualData == nullptr && automaticData == nullptr) || (manualLinkRange.length == 0 && automaticLinkRange.length == 0)) { @@ -252,11 +252,11 @@ - (LinkData *)getLinkDataAt:(NSUInteger)location { - (NSRange)getFullLinkRangeAt:(NSUInteger)location { NSRange manualLinkRange = NSMakeRange(0, 0); NSRange automaticLinkRange = NSMakeRange(0, 0); - NSRange inputRange = NSMakeRange(0, self.input->textView.textStorage.length); + NSRange inputRange = NSMakeRange(0, self.host.textView.textStorage.length); // get the previous index if possible when at the very end of input NSUInteger searchLocation = location; - if (searchLocation == self.input->textView.textStorage.length) { + if (searchLocation == self.host.textView.textStorage.length) { if (searchLocation == 0) { return NSMakeRange(0, 0); } @@ -264,15 +264,15 @@ - (NSRange)getFullLinkRangeAt:(NSUInteger)location { } LinkData *manualData = - [self.input->textView.textStorage attribute:ManualLinkAttributeName - atIndex:searchLocation - longestEffectiveRange:&manualLinkRange - inRange:inputRange]; + [self.host.textView.textStorage attribute:ManualLinkAttributeName + atIndex:searchLocation + longestEffectiveRange:&manualLinkRange + inRange:inputRange]; LinkData *automaticData = - [self.input->textView.textStorage attribute:AutomaticLinkAttributeName - atIndex:searchLocation - longestEffectiveRange:&automaticLinkRange - inRange:inputRange]; + [self.host.textView.textStorage attribute:AutomaticLinkAttributeName + atIndex:searchLocation + longestEffectiveRange:&automaticLinkRange + inRange:inputRange]; return manualData == nullptr ? automaticData == nullptr ? NSMakeRange(0, 0) : automaticLinkRange @@ -281,7 +281,7 @@ - (NSRange)getFullLinkRangeAt:(NSUInteger)location { // handles detecting and removing automatic links - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange { - LinkRegexConfig *linkRegexConfig = [self.input->config linkRegexConfig]; + LinkRegexConfig *linkRegexConfig = [self.host.config linkRegexConfig]; // no automatic links with isDisabled if (linkRegexConfig.isDisabled) { @@ -289,11 +289,11 @@ - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange { } InlineCodeStyle *inlineCodeStyle = - [self.input->stylesDict objectForKey:@([InlineCodeStyle getType])]; + [self.host.stylesDict objectForKey:@([InlineCodeStyle getType])]; MentionStyle *mentionStyle = - [self.input->stylesDict objectForKey:@([MentionStyle getType])]; + [self.host.stylesDict objectForKey:@([MentionStyle getType])]; CodeBlockStyle *codeBlockStyle = - [self.input->stylesDict objectForKey:@([CodeBlockStyle getType])]; + [self.host.stylesDict objectForKey:@([CodeBlockStyle getType])]; // we don't recognize links along mentions, inline code or codeblocks if (mentionStyle != nullptr && [mentionStyle any:wordRange]) { @@ -311,7 +311,7 @@ - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange { // we don't recognize automatic links along manual ones __block BOOL manualLinkPresent = NO; - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:ManualLinkAttributeName inRange:wordRange options:0 @@ -336,7 +336,7 @@ - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange { matchRange:matchingRange]; } else { // use user defined regex if it exists - NSRegularExpression *userRegex = [self.input->config parsedLinkRegex]; + NSRegularExpression *userRegex = [self.host.config parsedLinkRegex]; if (userRegex == nullptr) { // fallback to default regex @@ -368,7 +368,7 @@ - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange { [self addLink:newData range:wordRange withSelection:NO]; // emit onLinkDetected if style was added - [self.input emitOnLinkDetectedEvent:newData range:wordRange]; + [(id)self.host emitOnLinkDetectedEvent:newData range:wordRange]; } } else if ([self any:wordRange]) { // there was some automatic link (because anyOccurence is true and we are @@ -403,7 +403,7 @@ - (void)handleManualLinks:(NSString *)word inRange:(NSRange)wordRange { __block NSInteger manualLinkMinIdx = -1; __block NSInteger manualLinkMaxIdx = -1; - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:ManualLinkAttributeName inRange:wordRange options:0 @@ -437,7 +437,7 @@ - (void)handleManualLinks:(NSString *)word inRange:(NSRange)wordRange { NSRange newRange = NSMakeRange(manualLinkMinIdx, manualLinkMaxIdx - manualLinkMinIdx + 1); [self applyLinkMetaWithData:manualLinkMinValue range:newRange]; - [self.input->attributesManager addDirtyRange:newRange]; + [self.host.attributesManager addDirtyRange:newRange]; } } @@ -452,14 +452,14 @@ - (BOOL)isSingleLinkIn:(NSRange)range { - (void)removeConnectedLinksIfNeeded:(NSString *)word range:(NSRange)wordRange { BOOL anyAutomatic = [OccurenceUtils any:AutomaticLinkAttributeName - withInput:self.input + withHost:self.host inRange:wordRange withCondition:^BOOL(id _Nullable value, NSRange subrange) { return [self styleCondition:value range:subrange]; }]; BOOL anyManual = [OccurenceUtils any:ManualLinkAttributeName - withInput:self.input + withHost:self.host inRange:wordRange withCondition:^BOOL(id _Nullable value, NSRange subrange) { return [self styleCondition:value range:subrange]; @@ -474,7 +474,7 @@ - (void)removeConnectedLinksIfNeeded:(NSString *)word range:(NSRange)wordRange { // covers the whole word BOOL onlyLinks = [OccurenceUtils detectMultiple:@[ ManualLinkAttributeName, AutomaticLinkAttributeName ] - withInput:self.input + withHost:self.host inRange:wordRange withCondition:^BOOL(id _Nullable value, NSRange r) { return [self styleCondition:value range:r]; diff --git a/ios/styles/MentionStyle.mm b/ios/styles/MentionStyle.mm index bd34971e8..529bc0297 100644 --- a/ios/styles/MentionStyle.mm +++ b/ios/styles/MentionStyle.mm @@ -27,8 +27,8 @@ - (BOOL)isParagraph { return NO; } -- (instancetype)initWithInput:(id)input { - self = [super initWithInput:(EnrichedTextInputView *)input]; +- (instancetype)initWithHost:(id)host { + self = [super initWithHost:host]; if (self) { _activeMentionRange = nullptr; _activeMentionIndicator = nullptr; @@ -47,7 +47,7 @@ - (void)applyStyling:(NSRange)range { } MentionStyleProps *styleProps = - [self.input->config mentionStylePropsForIndicator:params.indicator]; + [self.host.config mentionStylePropsForIndicator:params.indicator]; NSMutableDictionary *newAttrs = [@{ NSForegroundColorAttributeName : styleProps.color, @@ -61,7 +61,7 @@ - (void)applyStyling:(NSRange)range { newAttrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle); } - [self.input->textView.textStorage addAttributes:newAttrs range:range]; + [self.host.textView.textStorage addAttributes:newAttrs range:range]; } - (void)reapplyFromStylePair:(StylePair *)pair { @@ -86,20 +86,20 @@ - (void)toggle:(NSRange)range { // other styles - (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange { NSArray *mentions = [self all:range]; - [self.input->textView.textStorage beginEditing]; + [self.host.textView.textStorage beginEditing]; for (StylePair *pair in mentions) { NSRange mentionRange = [self getFullMentionRangeAt:[pair.rangeValue rangeValue].location]; if (mentionRange.length == 0) { continue; } - [self.input->textView.textStorage removeAttribute:MentionAttributeName - range:mentionRange]; + [self.host.textView.textStorage removeAttribute:MentionAttributeName + range:mentionRange]; if (withDirtyRange) { - [self.input->attributesManager addDirtyRange:mentionRange]; + [self.host.attributesManager addDirtyRange:mentionRange]; } } - [self.input->textView.textStorage endEditing]; + [self.host.textView.textStorage endEditing]; [super removeTyping]; } @@ -107,13 +107,13 @@ - (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange { // used for conflicts, we have to remove the whole mention - (void)removeTyping { NSRange mentionRange = - [self getFullMentionRangeAt:self.input->textView.selectedRange.location]; + [self getFullMentionRangeAt:self.host.textView.selectedRange.location]; if (mentionRange.length > 0) { - [self.input->textView.textStorage beginEditing]; - [self.input->textView.textStorage removeAttribute:MentionAttributeName - range:mentionRange]; - [self.input->textView.textStorage endEditing]; - [self.input->attributesManager addDirtyRange:mentionRange]; + [self.host.textView.textStorage beginEditing]; + [self.host.textView.textStorage removeAttribute:MentionAttributeName + range:mentionRange]; + [self.host.textView.textStorage endEditing]; + [self.host.attributesManager addDirtyRange:mentionRange]; } [super removeTyping]; } @@ -131,9 +131,9 @@ - (BOOL)detect:(NSRange)range { } - (void)applyMentionMeta:(MentionParams *)params range:(NSRange)range { - [self.input->textView.textStorage addAttribute:MentionAttributeName - value:params - range:range]; + [self.host.textView.textStorage addAttribute:MentionAttributeName + value:params + range:range]; } // MARK: - Public non-standard methods @@ -158,13 +158,13 @@ - (void)addMention:(NSString *)indicator [TextInsertionUtils replaceText:newText at:rangeToBeReplaced additionalAttributes:nullptr - input:self.input + input:self.host withSelection:YES]; // THEN, add the attributes to not apply them on the space NSRange mentionRange = NSMakeRange(rangeToBeReplaced.location, text.length); [self applyMentionMeta:params range:mentionRange]; - [self.input->attributesManager addDirtyRange:mentionRange]; + [self.host.attributesManager addDirtyRange:mentionRange]; // mention editing should finish [self removeActiveMentionRange]; @@ -175,19 +175,19 @@ - (void)addMentionAtRange:(NSRange)range params:(MentionParams *)params { _blockMentionEditing = YES; [self applyMentionMeta:params range:range]; - [self.input->attributesManager addDirtyRange:range]; + [self.host.attributesManager addDirtyRange:range]; _blockMentionEditing = NO; } - (void)startMentionWithIndicator:(NSString *)indicator { - NSRange currentRange = self.input->textView.selectedRange; + NSRange currentRange = self.host.textView.selectedRange; BOOL addSpaceBefore = NO; BOOL addSpaceAfter = NO; if (currentRange.location > 0) { - unichar charBefore = [self.input->textView.textStorage.string + unichar charBefore = [self.host.textView.textStorage.string characterAtIndex:(currentRange.location - 1)]; if (![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:charBefore]) { @@ -196,8 +196,8 @@ - (void)startMentionWithIndicator:(NSString *)indicator { } if (currentRange.location + currentRange.length < - self.input->textView.textStorage.string.length) { - unichar charAfter = [self.input->textView.textStorage.string + self.host.textView.textStorage.string.length) { + unichar charAfter = [self.host.textView.textStorage.string characterAtIndex:(currentRange.location + currentRange.length)]; if (![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:charAfter]) { @@ -216,18 +216,18 @@ - (void)startMentionWithIndicator:(NSString *)indicator { [TextInsertionUtils insertText:finalString at:currentRange.location additionalAttributes:nullptr - input:self.input + input:self.host withSelection:NO]; } else { [TextInsertionUtils replaceText:finalString at:currentRange additionalAttributes:nullptr - input:self.input + input:self.host withSelection:NO]; } - [self.input->textView reactFocus]; - self.input->textView.selectedRange = newSelect; + [self.host.textView reactFocus]; + self.host.textView.selectedRange = newSelect; } // handles removing no longer valid mentions @@ -237,7 +237,7 @@ - (void)handleExistingMentions { // any number of spaces, which makes one mention any number of words long NSRange wholeText = - NSMakeRange(0, self.input->textView.textStorage.string.length); + NSMakeRange(0, self.host.textView.textStorage.string.length); // get mentions in ascending range.location order NSArray *mentions = [[self all:wholeText] sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, @@ -271,8 +271,8 @@ - (void)handleExistingMentions { } // check for text, any modifications to it makes mention invalid - NSString *existingText = [self.input->textView.textStorage.string - substringWithRange:currentRange]; + NSString *existingText = + [self.host.textView.textStorage.string substringWithRange:currentRange]; if (![existingText isEqualToString:currentText]) { [rangesToRemove addObject:[NSValue valueWithRange:currentRange]]; } @@ -291,7 +291,7 @@ - (void)manageMentionEditing { } // we don't take longer selections into consideration - if (self.input->textView.selectedRange.length > 0) { + if (self.host.textView.selectedRange.length > 0) { [self removeActiveMentionRange]; return; } @@ -307,14 +307,14 @@ - (void)manageMentionEditing { // get style classes that the mention shouldn't be recognized in, together // with other mentions - NSArray *conflicts = self.input->conflictingStyles[@([MentionStyle getType])]; - NSArray *blocks = self.input->blockingStyles[@([MentionStyle getType])]; + NSArray *conflicts = self.host.conflictingStyles[@([MentionStyle getType])]; + NSArray *blocks = self.host.blockingStyles[@([MentionStyle getType])]; NSArray *allConflicts = [[conflicts arrayByAddingObjectsFromArray:blocks] arrayByAddingObject:@([MentionStyle getType])]; BOOL conflictingStyle = NO; for (NSNumber *styleType in allConflicts) { - StyleBase *styleInst = self.input->stylesDict[styleType]; + StyleBase *styleInst = self.host.stylesDict[styleType]; if (styleInst != nullptr && [styleInst any:candidateRange]) { conflictingStyle = YES; break; @@ -334,19 +334,19 @@ - (void)manageMentionEditing { // returns mention params if it exists - (MentionParams *)getMentionParamsAt:(NSUInteger)location { NSRange mentionRange = NSMakeRange(0, 0); - NSRange inputRange = NSMakeRange(0, self.input->textView.textStorage.length); + NSRange inputRange = NSMakeRange(0, self.host.textView.textStorage.length); // don't search at the very end of input NSUInteger searchLocation = location; - if (searchLocation == self.input->textView.textStorage.length) { + if (searchLocation == self.host.textView.textStorage.length) { return nullptr; } MentionParams *value = - [self.input->textView.textStorage attribute:MentionAttributeName - atIndex:searchLocation - longestEffectiveRange:&mentionRange - inRange:inputRange]; + [self.host.textView.textStorage attribute:MentionAttributeName + atIndex:searchLocation + longestEffectiveRange:&mentionRange + inRange:inputRange]; return value; } @@ -357,26 +357,26 @@ - (NSValue *)getActiveMentionRange { // returns full range of a mention at some location - (NSRange)getFullMentionRangeAt:(NSUInteger)location { NSRange mentionRange = NSMakeRange(0, 0); - NSRange inputRange = NSMakeRange(0, self.input->textView.textStorage.length); + NSRange inputRange = NSMakeRange(0, self.host.textView.textStorage.length); // get the previous index if possible when at the very end of input NSUInteger searchLocation = location; - if (searchLocation == self.input->textView.textStorage.length) { + if (searchLocation == self.host.textView.textStorage.length) { if (searchLocation == 0) { return mentionRange; } searchLocation = searchLocation - 1; } - [self.input->textView.textStorage attribute:MentionAttributeName - atIndex:searchLocation - longestEffectiveRange:&mentionRange - inRange:inputRange]; + [self.host.textView.textStorage attribute:MentionAttributeName + atIndex:searchLocation + longestEffectiveRange:&mentionRange + inRange:inputRange]; return mentionRange; } - (MentionStyleProps *)stylePropsWithParams:(MentionParams *)params { - return [self.input->config mentionStylePropsForIndicator:params.indicator]; + return [self.host.config mentionStylePropsForIndicator:params.indicator]; } // finds if any word/words around current selection are eligible to be edited as @@ -389,9 +389,8 @@ - (NSArray *)getMentionCandidate { NSRange finalRange; // word at the current selection - currentWord = - [WordsUtils getCurrentWord:self.input->textView.textStorage.string - range:self.input->textView.selectedRange]; + currentWord = [WordsUtils getCurrentWord:self.host.textView.textStorage.string + range:self.host.textView.selectedRange]; if (currentWord != nullptr) { currentWordText = (NSString *)[currentWord objectForKey:@"word"]; currentWordRange = (NSValue *)[currentWord objectForKey:@"range"]; @@ -401,7 +400,7 @@ - (NSArray *)getMentionCandidate { // current word exists unichar currentFirstChar = [currentWordText characterAtIndex:0]; - if ([[self.input->config mentionIndicators] + if ([[self.host.config mentionIndicators] containsObject:@(currentFirstChar)]) { // current word exists and has a mention indicator; no need to check for // the previous word @@ -418,7 +417,7 @@ - (NSArray *)getMentionCandidate { return nullptr; } - unichar separatorChar = [self.input->textView.textStorage.string + unichar separatorChar = [self.host.textView.textStorage.string characterAtIndex:previousWordSearchLocation]; if (![[NSCharacterSet whitespaceCharacterSet] characterIsMember:separatorChar]) { @@ -428,7 +427,7 @@ - (NSArray *)getMentionCandidate { } previousWord = [WordsUtils - getCurrentWord:self.input->textView.textStorage.string + getCurrentWord:self.host.textView.textStorage.string range:NSMakeRange(previousWordSearchLocation, 0)]; if (previousWord != nullptr) { @@ -439,7 +438,7 @@ - (NSArray *)getMentionCandidate { // check for the mention indicators in the previous word unichar previousFirstChar = [previousWordText characterAtIndex:0]; - if ([[self.input->config mentionIndicators] + if ([[self.host.config mentionIndicators] containsObject:@(previousFirstChar)]) { // previous word has a proper mention indicator: treat both words as // an editable mention @@ -464,13 +463,13 @@ - (NSArray *)getMentionCandidate { // current word doesn't exist; try getting the previous one NSInteger previousWordSearchLocation = - self.input->textView.selectedRange.location - 1; + self.host.textView.selectedRange.location - 1; if (previousWordSearchLocation < 0) { // previous word can't exist return nullptr; } - unichar separatorChar = [self.input->textView.textStorage.string + unichar separatorChar = [self.host.textView.textStorage.string characterAtIndex:previousWordSearchLocation]; if (![[NSCharacterSet whitespaceCharacterSet] characterIsMember:separatorChar]) { @@ -480,7 +479,7 @@ - (NSArray *)getMentionCandidate { } previousWord = - [WordsUtils getCurrentWord:self.input->textView.textStorage.string + [WordsUtils getCurrentWord:self.host.textView.textStorage.string range:NSMakeRange(previousWordSearchLocation, 0)]; if (previousWord != nullptr) { @@ -491,7 +490,7 @@ - (NSArray *)getMentionCandidate { // check for the mention indicators in the previous word unichar previousFirstChar = [previousWordText characterAtIndex:0]; - if ([[self.input->config mentionIndicators] + if ([[self.host.config mentionIndicators] containsObject:@(previousFirstChar)]) { // previous word has a proper mention indicator; treat previous word + a // space as a editable mention @@ -521,7 +520,7 @@ - (void)setActiveMentionRange:(NSRange)range text:(NSString *)text { [text substringWithRange:NSMakeRange(1, text.length - 1)]; _activeMentionIndicator = indicatorString; _activeMentionRange = [NSValue valueWithRange:range]; - [self.input emitOnMentionEvent:indicatorString text:textString]; + [(id)self.host emitOnMentionEvent:indicatorString text:textString]; } // removes stored mention range + indicator, which means that we no longer edit @@ -531,7 +530,7 @@ - (void)removeActiveMentionRange { NSString *indicatorCopy = [_activeMentionIndicator copy]; _activeMentionIndicator = nullptr; _activeMentionRange = nullptr; - [self.input emitOnMentionEvent:indicatorCopy text:nullptr]; + [(id)self.host emitOnMentionEvent:indicatorCopy text:nullptr]; } } diff --git a/ios/styles/OrderedListStyle.mm b/ios/styles/OrderedListStyle.mm index 60a6089a5..291d1a98c 100644 --- a/ios/styles/OrderedListStyle.mm +++ b/ios/styles/OrderedListStyle.mm @@ -24,10 +24,10 @@ - (BOOL)needsZWS { - (void)applyStyling:(NSRange)range { // lists are drawn manually // margin before marker + gap between marker and paragraph - CGFloat listHeadIndent = [self.input->config orderedListMarginLeft] + - [self.input->config orderedListGapWidth]; + CGFloat listHeadIndent = [self.host.config orderedListMarginLeft] + + [self.host.config orderedListGapWidth]; - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:range options:0 @@ -37,7 +37,7 @@ - (void)applyStyling:(NSRange)range { [(NSParagraphStyle *)value mutableCopy]; pStyle.headIndent = listHeadIndent; pStyle.firstLineHeadIndent = listHeadIndent; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range]; @@ -47,28 +47,28 @@ - (void)applyStyling:(NSRange)range { - (BOOL)tryHandlingListShorcutInRange:(NSRange)range replacementText:(NSString *)text { NSRange paragraphRange = - [self.input->textView.textStorage.string paragraphRangeForRange:range]; + [self.host.textView.textStorage.string paragraphRangeForRange:range]; // a dot was added - check if we are both at the paragraph beginning + 1 // character (which we want to be a digit '1') if ([text isEqualToString:@"."] && range.location - 1 == paragraphRange.location) { - unichar charBefore = [self.input->textView.textStorage.string + unichar charBefore = [self.host.textView.textStorage.string characterAtIndex:range.location - 1]; if (charBefore == '1') { // we got a match - add a list if possible - if ([self.input handleStyleBlocksAndConflicts:[[self class] getType] - range:paragraphRange]) { + if ([self.host handleStyleBlocksAndConflicts:[[self class] getType] + range:paragraphRange]) { // don't emit during the replacing - self.input->blockEmitting = YES; + self.host.blockEmitting = YES; // remove the number [TextInsertionUtils replaceText:@"" at:NSMakeRange(paragraphRange.location, 1) additionalAttributes:nullptr - input:self.input + input:self.host withSelection:YES]; - self.input->blockEmitting = NO; + self.host.blockEmitting = NO; // add attributes on the paragraph [self add:NSMakeRange(paragraphRange.location, diff --git a/ios/styles/StrikethroughStyle.mm b/ios/styles/StrikethroughStyle.mm index ad829df31..5b5883389 100644 --- a/ios/styles/StrikethroughStyle.mm +++ b/ios/styles/StrikethroughStyle.mm @@ -16,10 +16,10 @@ - (BOOL)isParagraph { } - (void)applyStyling:(NSRange)range { - [self.input->textView.textStorage addAttributes:@{ + [self.host.textView.textStorage addAttributes:@{ NSStrikethroughStyleAttributeName : @(NSUnderlineStyleSingle) } - range:range]; + range:range]; } @end diff --git a/ios/styles/UnderlineStyle.mm b/ios/styles/UnderlineStyle.mm index 2d30eec58..c09a0fae3 100644 --- a/ios/styles/UnderlineStyle.mm +++ b/ios/styles/UnderlineStyle.mm @@ -16,9 +16,9 @@ - (BOOL)isParagraph { } - (void)applyStyling:(NSRange)range { - [self.input->textView.textStorage addAttribute:NSUnderlineStyleAttributeName - value:@(NSUnderlineStyleSingle) - range:range]; + [self.host.textView.textStorage addAttribute:NSUnderlineStyleAttributeName + value:@(NSUnderlineStyleSingle) + range:range]; } @end diff --git a/ios/styles/UnorderedListStyle.mm b/ios/styles/UnorderedListStyle.mm index 2e0a1a103..8044ab3de 100644 --- a/ios/styles/UnorderedListStyle.mm +++ b/ios/styles/UnorderedListStyle.mm @@ -24,10 +24,10 @@ - (BOOL)needsZWS { - (void)applyStyling:(NSRange)range { // lists are drawn manually // margin before bullet + gap between bullet and paragraph - CGFloat listHeadIndent = [self.input->config unorderedListMarginLeft] + - [self.input->config unorderedListGapWidth]; + CGFloat listHeadIndent = [self.host.config unorderedListMarginLeft] + + [self.host.config unorderedListGapWidth]; - [self.input->textView.textStorage + [self.host.textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:range options:0 @@ -37,7 +37,7 @@ - (void)applyStyling:(NSRange)range { [(NSParagraphStyle *)value mutableCopy]; pStyle.headIndent = listHeadIndent; pStyle.firstLineHeadIndent = listHeadIndent; - [self.input->textView.textStorage + [self.host.textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range]; @@ -47,28 +47,28 @@ - (void)applyStyling:(NSRange)range { - (BOOL)tryHandlingListShorcutInRange:(NSRange)range replacementText:(NSString *)text { NSRange paragraphRange = - [self.input->textView.textStorage.string paragraphRangeForRange:range]; + [self.host.textView.textStorage.string paragraphRangeForRange:range]; // space was added - check if we are both at the paragraph beginning + 1 // character (which we want to be a dash) if ([text isEqualToString:@" "] && range.location - 1 == paragraphRange.location) { - unichar charBefore = [self.input->textView.textStorage.string + unichar charBefore = [self.host.textView.textStorage.string characterAtIndex:range.location - 1]; if (charBefore == '-') { // we got a match - add a list if possible - if ([self.input handleStyleBlocksAndConflicts:[[self class] getType] - range:paragraphRange]) { + if ([self.host handleStyleBlocksAndConflicts:[[self class] getType] + range:paragraphRange]) { // don't emit during the replacing - self.input->blockEmitting = YES; + self.host.blockEmitting = YES; // remove the dash [TextInsertionUtils replaceText:@"" at:NSMakeRange(paragraphRange.location, 1) additionalAttributes:nullptr - input:self.input + input:self.host withSelection:YES]; - self.input->blockEmitting = NO; + self.host.blockEmitting = NO; // add attributes on the dashless paragraph [self add:NSMakeRange(paragraphRange.location, diff --git a/ios/utils/AttachmentLayoutUtils.h b/ios/utils/AttachmentLayoutUtils.h new file mode 100644 index 000000000..79b6515d6 --- /dev/null +++ b/ios/utils/AttachmentLayoutUtils.h @@ -0,0 +1,24 @@ +#pragma once +#import "ImageAttachment.h" +#import "InputConfig.h" +#import + +@interface AttachmentLayoutUtils : NSObject + ++ (void)handleAttachmentUpdate:(MediaAttachment *)attachment + textView:(UITextView *)textView + onLayoutBlock:(dispatch_block_t)layoutBlock; + ++ (NSMutableDictionary *) + layoutAttachmentsInTextView:(UITextView *)textView + config:(InputConfig *)config + existingViews: + (NSMutableDictionary *) + attachmentViews; + ++ (CGRect)frameForAttachment:(ImageAttachment *)attachment + atRange:(NSRange)range + textView:(UITextView *)textView + config:(InputConfig *)config; + +@end diff --git a/ios/utils/AttachmentLayoutUtils.mm b/ios/utils/AttachmentLayoutUtils.mm new file mode 100644 index 000000000..1b08ad750 --- /dev/null +++ b/ios/utils/AttachmentLayoutUtils.mm @@ -0,0 +1,143 @@ +#import "AttachmentLayoutUtils.h" + +@implementation AttachmentLayoutUtils + ++ (void)handleAttachmentUpdate:(MediaAttachment *)attachment + textView:(UITextView *)textView + onLayoutBlock:(dispatch_block_t)layoutBlock { + NSTextStorage *storage = textView.textStorage; + NSRange fullRange = NSMakeRange(0, storage.length); + + __block NSRange foundRange = NSMakeRange(NSNotFound, 0); + + [storage enumerateAttribute:NSAttachmentAttributeName + inRange:fullRange + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if (value == attachment) { + foundRange = range; + *stop = YES; + } + }]; + + if (foundRange.location == NSNotFound) { + return; + } + + [storage edited:NSTextStorageEditedAttributes + range:foundRange + changeInLength:0]; + + dispatch_async(dispatch_get_main_queue(), layoutBlock); +} + ++ (NSMutableDictionary *) + layoutAttachmentsInTextView:(UITextView *)textView + config:(InputConfig *)config + existingViews: + (NSMutableDictionary *) + attachmentViews { + NSTextStorage *storage = textView.textStorage; + if (storage.length == 0) + return attachmentViews; + + NSMutableDictionary *activeAttachmentViews = + [NSMutableDictionary dictionary]; + + // Iterate over the entire text to find ImageAttachments + [storage enumerateAttribute:NSAttachmentAttributeName + inRange:NSMakeRange(0, storage.length) + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if ([value isKindOfClass:[ImageAttachment class]]) { + ImageAttachment *attachment = (ImageAttachment *)value; + + CGRect rect = [self frameForAttachment:attachment + atRange:range + textView:textView + config:config]; + + // Get or Create the UIImageView for this specific + // attachment key + NSValue *key = + [NSValue valueWithNonretainedObject:attachment]; + UIImageView *imgView = attachmentViews[key]; + + if (!imgView) { + // It doesn't exist yet, create it + imgView = [[UIImageView alloc] initWithFrame:rect]; + imgView.contentMode = UIViewContentModeScaleAspectFit; + imgView.tintColor = [UIColor labelColor]; + + // Add it directly to the TextView + [textView addSubview:imgView]; + } + + // Update position (in case text moved/scrolled) + if (!CGRectEqualToRect(imgView.frame, rect)) { + imgView.frame = rect; + } + UIImage *targetImage = + attachment.storedAnimatedImage ?: attachment.image; + + // Only set if different to avoid resetting the animation + // loop + if (imgView.image != targetImage) { + imgView.image = targetImage; + } + + // Ensure it is visible on top + imgView.hidden = NO; + [textView bringSubviewToFront:imgView]; + + activeAttachmentViews[key] = imgView; + // Remove from the old map so we know it has been claimed + [attachmentViews removeObjectForKey:key]; + } + }]; + + // Everything remaining in attachmentViews is dead or off-screen + for (UIImageView *danglingView in attachmentViews.allValues) { + [danglingView removeFromSuperview]; + } + + return activeAttachmentViews; +} + ++ (CGRect)frameForAttachment:(ImageAttachment *)attachment + atRange:(NSRange)range + textView:(UITextView *)textView + config:(InputConfig *)config { + NSLayoutManager *layoutManager = textView.layoutManager; + NSTextContainer *textContainer = textView.textContainer; + NSTextStorage *storage = textView.textStorage; + + NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range + actualCharacterRange:NULL]; + CGRect glyphRect = [layoutManager boundingRectForGlyphRange:glyphRange + inTextContainer:textContainer]; + + CGRect lineRect = + [layoutManager lineFragmentRectForGlyphAtIndex:glyphRange.location + effectiveRange:NULL]; + CGSize attachmentSize = attachment.bounds.size; + + UIFont *font = [storage attribute:NSFontAttributeName + atIndex:range.location + effectiveRange:NULL]; + if (!font) { + font = [config primaryFont]; + } + + // Calculate (Baseline Alignment) + CGFloat targetY = + CGRectGetMaxY(lineRect) + font.descender - attachmentSize.height; + CGRect rect = + CGRectMake(glyphRect.origin.x + textView.textContainerInset.left, + targetY + textView.textContainerInset.top, + attachmentSize.width, attachmentSize.height); + + return CGRectIntegral(rect); +} + +@end diff --git a/ios/utils/OccurenceUtils.h b/ios/utils/OccurenceUtils.h index fd0143d57..a27ce313e 100644 --- a/ios/utils/OccurenceUtils.h +++ b/ios/utils/OccurenceUtils.h @@ -1,43 +1,43 @@ #pragma once -#import "EnrichedTextInputView.h" +#import "EnrichedStyleHost.h" #import "StylePair.h" @interface OccurenceUtils : NSObject + (BOOL)detect:(NSAttributedStringKey _Nonnull)key - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (BOOL)detect:(NSAttributedStringKey _Nonnull)key - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host atIndex:(NSUInteger)index checkPrevious:(BOOL)checkPrev withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (BOOL)detectMultiple:(NSArray *_Nonnull)keys - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (BOOL)any:(NSAttributedStringKey _Nonnull)key - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (BOOL)anyMultiple:(NSArray *_Nonnull)keys - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (NSArray *_Nullable)all:(NSAttributedStringKey _Nonnull)key - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (NSArray *_Nullable) allMultiple:(NSArray *_Nonnull)keys - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; diff --git a/ios/utils/OccurenceUtils.mm b/ios/utils/OccurenceUtils.mm index 872783c6c..bab3685a6 100644 --- a/ios/utils/OccurenceUtils.mm +++ b/ios/utils/OccurenceUtils.mm @@ -4,12 +4,12 @@ @implementation OccurenceUtils + (BOOL)detect:(NSAttributedStringKey _Nonnull)key - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { __block NSInteger totalLength = 0; - [input->textView.textStorage + [host.textView.textStorage enumerateAttribute:key inRange:range options:0 @@ -26,24 +26,24 @@ + (BOOL)detect:(NSAttributedStringKey _Nonnull)key // it means that first character of paragraph will be checked instead if the // detection is not in input's selected range and at the end of the input + (BOOL)detect:(NSAttributedStringKey _Nonnull)key - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host atIndex:(NSUInteger)index checkPrevious:(BOOL)checkPrev withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { NSRange detectionRange = NSMakeRange(index, 0); id attrValue; - if (NSEqualRanges(input->textView.selectedRange, detectionRange)) { - attrValue = input->textView.typingAttributes[key]; - } else if (index == input->textView.textStorage.string.length) { + if (NSEqualRanges(host.textView.selectedRange, detectionRange)) { + attrValue = host.textView.typingAttributes[key]; + } else if (index == host.textView.textStorage.string.length) { if (checkPrev) { - NSRange paragraphRange = [input->textView.textStorage.string + NSRange paragraphRange = [host.textView.textStorage.string paragraphRangeForRange:detectionRange]; if (paragraphRange.location == detectionRange.location) { return NO; } else { return [self detect:key - withInput:input + withHost:host inRange:NSMakeRange(paragraphRange.location, 1) withCondition:condition]; } @@ -52,21 +52,21 @@ + (BOOL)detect:(NSAttributedStringKey _Nonnull)key } } else { NSRange attrRange = NSMakeRange(0, 0); - attrValue = [input->textView.textStorage attribute:key - atIndex:index - effectiveRange:&attrRange]; + attrValue = [host.textView.textStorage attribute:key + atIndex:index + effectiveRange:&attrRange]; } return condition(attrValue, detectionRange); } + (BOOL)detectMultiple:(NSArray *_Nonnull)keys - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { __block NSInteger totalLength = 0; for (NSString *key in keys) { - [input->textView.textStorage + [host.textView.textStorage enumerateAttribute:key inRange:range options:0 @@ -81,12 +81,12 @@ + (BOOL)detectMultiple:(NSArray *_Nonnull)keys } + (BOOL)any:(NSAttributedStringKey _Nonnull)key - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { __block BOOL found = NO; - [input->textView.textStorage + [host.textView.textStorage enumerateAttribute:key inRange:range options:0 @@ -101,13 +101,13 @@ + (BOOL)any:(NSAttributedStringKey _Nonnull)key } + (BOOL)anyMultiple:(NSArray *_Nonnull)keys - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { __block BOOL found = NO; for (NSString *key in keys) { - [input->textView.textStorage + [host.textView.textStorage enumerateAttribute:key inRange:range options:0 @@ -126,7 +126,7 @@ + (BOOL)anyMultiple:(NSArray *_Nonnull)keys } + (NSArray *_Nullable)all:(NSAttributedStringKey _Nonnull)key - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition: (BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, @@ -134,7 +134,7 @@ + (BOOL)anyMultiple:(NSArray *_Nonnull)keys condition { __block NSMutableArray *occurences = [[NSMutableArray alloc] init]; - [input->textView.textStorage + [host.textView.textStorage enumerateAttribute:key inRange:range options:0 @@ -152,14 +152,14 @@ + (BOOL)anyMultiple:(NSArray *_Nonnull)keys + (NSArray *_Nullable) allMultiple:(NSArray *_Nonnull)keys - withInput:(EnrichedTextInputView *_Nonnull)input + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { __block NSMutableArray *occurences = [[NSMutableArray alloc] init]; for (NSString *key in keys) { - [input->textView.textStorage + [host.textView.textStorage enumerateAttribute:key inRange:range options:0 From 732fcac98a7cf91c35e1132798907961885f3aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 8 Apr 2026 10:48:49 +0200 Subject: [PATCH 02/19] feat: add EnrichedText shadow nodes --- .../EnrichedTextComponentDescriptor.h | 19 +++++ ios/internals/EnrichedTextViewShadowNode.h | 34 +++++++++ ios/internals/EnrichedTextViewShadowNode.mm | 72 +++++++++++++++++++ ios/internals/EnrichedTextViewState.cpp | 7 ++ ios/internals/EnrichedTextViewState.h | 18 +++++ 5 files changed, 150 insertions(+) create mode 100644 ios/internals/EnrichedTextComponentDescriptor.h create mode 100644 ios/internals/EnrichedTextViewShadowNode.h create mode 100644 ios/internals/EnrichedTextViewShadowNode.mm create mode 100644 ios/internals/EnrichedTextViewState.cpp create mode 100644 ios/internals/EnrichedTextViewState.h diff --git a/ios/internals/EnrichedTextComponentDescriptor.h b/ios/internals/EnrichedTextComponentDescriptor.h new file mode 100644 index 000000000..996aec1fc --- /dev/null +++ b/ios/internals/EnrichedTextComponentDescriptor.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include +#include + +namespace facebook::react { +class EnrichedTextComponentDescriptor final + : public ConcreteComponentDescriptor { +public: + using ConcreteComponentDescriptor::ConcreteComponentDescriptor; + void adopt(ShadowNode &shadowNode) const override { + react_native_assert( + dynamic_cast(&shadowNode)); + ConcreteComponentDescriptor::adopt(shadowNode); + } +}; + +} // namespace facebook::react diff --git a/ios/internals/EnrichedTextViewShadowNode.h b/ios/internals/EnrichedTextViewShadowNode.h new file mode 100644 index 000000000..631c8a190 --- /dev/null +++ b/ios/internals/EnrichedTextViewShadowNode.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +JSI_EXPORT extern const char EnrichedTextViewComponentName[]; + +class EnrichedTextViewShadowNode + : public ConcreteViewShadowNode< + EnrichedTextViewComponentName, EnrichedTextViewProps, + EnrichedTextViewEventEmitter, EnrichedTextViewState> { +public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + Size + measureContent(const LayoutContext &layoutContext, + const LayoutConstraints &layoutConstraints) const override; + + static ShadowNodeTraits BaseTraits() { + auto traits = ConcreteViewShadowNode::BaseTraits(); + traits.set(ShadowNodeTraits::Trait::LeafYogaNode); + traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode); + return traits; + } + +private: + id setupMockTextView_() const; +}; + +} // namespace facebook::react diff --git a/ios/internals/EnrichedTextViewShadowNode.mm b/ios/internals/EnrichedTextViewShadowNode.mm new file mode 100644 index 000000000..ac5bc500c --- /dev/null +++ b/ios/internals/EnrichedTextViewShadowNode.mm @@ -0,0 +1,72 @@ +#import "EnrichedTextViewShadowNode.h" +#import "CoreText/CoreText.h" +#import +#import +#import + +namespace facebook::react { + +extern const char EnrichedTextViewComponentName[] = "EnrichedTextView"; + +id EnrichedTextViewShadowNode::setupMockTextView_() const { + const int veryFarAway = 20000; + const int mockSize = 1000; + EnrichedTextView *mockView = [[EnrichedTextView alloc] + initWithFrame:(CGRectMake(veryFarAway, veryFarAway, mockSize, mockSize))]; + const auto props = this->getProps(); + [mockView updateProps:props oldProps:nullptr]; + return mockView; +} + +Size EnrichedTextViewShadowNode::measureContent( + const LayoutContext &layoutContext, + const LayoutConstraints &layoutConstraints) const { + const auto state = this->getStateData(); + const auto componentRef = state.getComponentViewRef(); + RCTInternalGenericWeakWrapper *weakWrapper = + (RCTInternalGenericWeakWrapper *)unwrapManagedObject(componentRef); + + if (weakWrapper != nullptr) { + id componentObject = weakWrapper.object; + EnrichedTextView *typedComponentObject = + (EnrichedTextView *)componentObject; + + if (typedComponentObject != nullptr) { + __block CGSize estimatedSize; + + if ([NSThread isMainThread]) { + estimatedSize = [typedComponentObject + measureSize:layoutConstraints.maximumSize.width]; + } else { + dispatch_sync(dispatch_get_main_queue(), ^{ + estimatedSize = [typedComponentObject + measureSize:layoutConstraints.maximumSize.width]; + }); + } + + return {estimatedSize.width, + MIN(estimatedSize.height, layoutConstraints.maximumSize.height)}; + } + } else { + __block CGSize estimatedSize; + + if ([NSThread isMainThread]) { + EnrichedTextView *mockView = setupMockTextView_(); + estimatedSize = + [mockView measureSize:layoutConstraints.maximumSize.width]; + } else { + dispatch_sync(dispatch_get_main_queue(), ^{ + EnrichedTextView *mockView = setupMockTextView_(); + estimatedSize = + [mockView measureSize:layoutConstraints.maximumSize.width]; + }); + } + + return {estimatedSize.width, + MIN(estimatedSize.height, layoutConstraints.maximumSize.height)}; + } + + return Size(); +} + +} // namespace facebook::react diff --git a/ios/internals/EnrichedTextViewState.cpp b/ios/internals/EnrichedTextViewState.cpp new file mode 100644 index 000000000..ec988d054 --- /dev/null +++ b/ios/internals/EnrichedTextViewState.cpp @@ -0,0 +1,7 @@ +#include "EnrichedTextViewState.h" + +namespace facebook::react { +std::shared_ptr EnrichedTextViewState::getComponentViewRef() const { + return componentViewRef_; +} +} // namespace facebook::react diff --git a/ios/internals/EnrichedTextViewState.h b/ios/internals/EnrichedTextViewState.h new file mode 100644 index 000000000..c32efa0f7 --- /dev/null +++ b/ios/internals/EnrichedTextViewState.h @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace facebook::react { + +class EnrichedTextViewState { +public: + EnrichedTextViewState() : componentViewRef_(nullptr) {} + explicit EnrichedTextViewState(std::shared_ptr ref) { + componentViewRef_ = ref; + } + std::shared_ptr getComponentViewRef() const; + +private: + std::shared_ptr componentViewRef_{}; +}; + +} // namespace facebook::react From d9cf6e880d84000929a8e4ba6c64f96c9b64e6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 9 Apr 2026 16:24:44 +0200 Subject: [PATCH 03/19] fix: add StyleUtils --- ios/EnrichedTextInputView.mm | 275 +++------------------- ios/EnrichedTextViewManager.mm | 13 + ios/htmlParser/HtmlParser.mm | 6 +- ios/textStyles/EnrichedTextStyleHeaders.h | 59 +++++ ios/utils/StyleUtils.h | 30 +++ ios/utils/StyleUtils.mm | 268 +++++++++++++++++++++ 6 files changed, 405 insertions(+), 246 deletions(-) create mode 100644 ios/EnrichedTextViewManager.mm create mode 100644 ios/textStyles/EnrichedTextStyleHeaders.h create mode 100644 ios/utils/StyleUtils.h create mode 100644 ios/utils/StyleUtils.mm diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 75a84980f..78699dc6a 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -9,6 +9,7 @@ #import "RCTFabricComponentsPlugins.h" #import "StringExtension.h" #import "StyleHeaders.h" +#import "StyleUtils.h" #import "TextBlockTapGestureRecognizer.h" #import "UIView+React.h" #import "WordsUtils.h" @@ -104,149 +105,9 @@ - (void)setDefaults { defaultTypingAttributes = [[NSMutableDictionary alloc] init]; - stylesDict = @{ - @([BoldStyle getType]) : [[BoldStyle alloc] initWithHost:self], - @([ItalicStyle getType]) : [[ItalicStyle alloc] initWithHost:self], - @([UnderlineStyle getType]) : [[UnderlineStyle alloc] initWithHost:self], - @([StrikethroughStyle getType]) : - [[StrikethroughStyle alloc] initWithHost:self], - @([InlineCodeStyle getType]) : [[InlineCodeStyle alloc] initWithHost:self], - @([LinkStyle getType]) : [[LinkStyle alloc] initWithHost:self], - @([MentionStyle getType]) : [[MentionStyle alloc] initWithHost:self], - @([H1Style getType]) : [[H1Style alloc] initWithHost:self], - @([H2Style getType]) : [[H2Style alloc] initWithHost:self], - @([H3Style getType]) : [[H3Style alloc] initWithHost:self], - @([H4Style getType]) : [[H4Style alloc] initWithHost:self], - @([H5Style getType]) : [[H5Style alloc] initWithHost:self], - @([H6Style getType]) : [[H6Style alloc] initWithHost:self], - @([UnorderedListStyle getType]) : - [[UnorderedListStyle alloc] initWithHost:self], - @([OrderedListStyle getType]) : - [[OrderedListStyle alloc] initWithHost:self], - @([CheckboxListStyle getType]) : - [[CheckboxListStyle alloc] initWithHost:self], - @([BlockQuoteStyle getType]) : [[BlockQuoteStyle alloc] initWithHost:self], - @([CodeBlockStyle getType]) : [[CodeBlockStyle alloc] initWithHost:self], - @([ImageStyle getType]) : [[ImageStyle alloc] initWithHost:self] - }; - - conflictingStyles = [@{ - @([BoldStyle getType]) : @[], - @([ItalicStyle getType]) : @[], - @([UnderlineStyle getType]) : @[], - @([StrikethroughStyle getType]) : @[], - @([InlineCodeStyle getType]) : - @[ @([LinkStyle getType]), @([MentionStyle getType]) ], - @([LinkStyle getType]) : @[ - @([InlineCodeStyle getType]), @([LinkStyle getType]), - @([MentionStyle getType]) - ], - @([MentionStyle getType]) : - @[ @([InlineCodeStyle getType]), @([LinkStyle getType]) ], - @([H1Style getType]) : @[ - @([H2Style getType]), @([H3Style getType]), @([H4Style getType]), - @([H5Style getType]), @([H6Style getType]), - @([UnorderedListStyle getType]), @([OrderedListStyle getType]), - @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), - @([CheckboxListStyle getType]) - ], - @([H2Style getType]) : @[ - @([H1Style getType]), @([H3Style getType]), @([H4Style getType]), - @([H5Style getType]), @([H6Style getType]), - @([UnorderedListStyle getType]), @([OrderedListStyle getType]), - @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), - @([CheckboxListStyle getType]) - ], - @([H3Style getType]) : @[ - @([H1Style getType]), @([H2Style getType]), @([H4Style getType]), - @([H5Style getType]), @([H6Style getType]), - @([UnorderedListStyle getType]), @([OrderedListStyle getType]), - @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), - @([CheckboxListStyle getType]) - ], - @([H4Style getType]) : @[ - @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), - @([H5Style getType]), @([H6Style getType]), - @([UnorderedListStyle getType]), @([OrderedListStyle getType]), - @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), - @([CheckboxListStyle getType]) - ], - @([H5Style getType]) : @[ - @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), - @([H4Style getType]), @([H6Style getType]), - @([UnorderedListStyle getType]), @([OrderedListStyle getType]), - @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), - @([CheckboxListStyle getType]) - ], - @([H6Style getType]) : @[ - @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), - @([H4Style getType]), @([H5Style getType]), - @([UnorderedListStyle getType]), @([OrderedListStyle getType]), - @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), - @([CheckboxListStyle getType]) - ], - @([UnorderedListStyle getType]) : @[ - @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), - @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), - @([OrderedListStyle getType]), @([BlockQuoteStyle getType]), - @([CodeBlockStyle getType]), @([CheckboxListStyle getType]) - ], - @([OrderedListStyle getType]) : @[ - @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), - @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), - @([UnorderedListStyle getType]), @([BlockQuoteStyle getType]), - @([CodeBlockStyle getType]), @([CheckboxListStyle getType]) - ], - @([CheckboxListStyle getType]) : @[ - @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), - @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), - @([UnorderedListStyle getType]), @([OrderedListStyle getType]), - @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]) - ], - @([BlockQuoteStyle getType]) : @[ - @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), - @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), - @([UnorderedListStyle getType]), @([OrderedListStyle getType]), - @([CodeBlockStyle getType]), @([CheckboxListStyle getType]) - ], - @([CodeBlockStyle getType]) : @[ - @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), - @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), - @([BoldStyle getType]), @([UnderlineStyle getType]), - @([ItalicStyle getType]), @([StrikethroughStyle getType]), - @([UnorderedListStyle getType]), @([OrderedListStyle getType]), - @([BlockQuoteStyle getType]), @([InlineCodeStyle getType]), - @([MentionStyle getType]), @([LinkStyle getType]), - @([CheckboxListStyle getType]) - ], - @([ImageStyle getType]) : - @[ @([LinkStyle getType]), @([MentionStyle getType]) ] - } mutableCopy]; - - blockingStyles = [@{ - @([BoldStyle getType]) : @[ @([CodeBlockStyle getType]) ], - @([ItalicStyle getType]) : @[ @([CodeBlockStyle getType]) ], - @([UnderlineStyle getType]) : @[ @([CodeBlockStyle getType]) ], - @([StrikethroughStyle getType]) : @[ @([CodeBlockStyle getType]) ], - @([InlineCodeStyle getType]) : - @[ @([CodeBlockStyle getType]), @([ImageStyle getType]) ], - @([LinkStyle getType]) : - @[ @([CodeBlockStyle getType]), @([ImageStyle getType]) ], - @([MentionStyle getType]) : - @[ @([CodeBlockStyle getType]), @([ImageStyle getType]) ], - @([H1Style getType]) : @[], - @([H2Style getType]) : @[], - @([H3Style getType]) : @[], - @([H4Style getType]) : @[], - @([H5Style getType]) : @[], - @([H6Style getType]) : @[], - @([UnorderedListStyle getType]) : @[], - @([OrderedListStyle getType]) : @[], - @([CheckboxListStyle getType]) : @[], - @([BlockQuoteStyle getType]) : @[], - @([CodeBlockStyle getType]) : @[], - @([ImageStyle getType]) : @[ @([InlineCodeStyle getType]) ] - } mutableCopy]; + stylesDict = [StyleUtils stylesDictForHost:self isInput:YES]; + conflictingStyles = [[StyleUtils conflictMap] mutableCopy]; + blockingStyles = [[StyleUtils blockingMap] mutableCopy]; parser = [[InputParser alloc] initWithInput:self]; _attachmentViews = [[NSMutableDictionary alloc] init]; @@ -370,11 +231,11 @@ - (void)updateProps:(Props::Shared const &)props // Update style blocks and conflicts for bold if (newViewProps.htmlStyle.h1.bold) { - [self addStyleBlock:H1 to:Bold]; - [self addStyleConflict:Bold to:H1]; + [StyleUtils addStyleBlock:H1 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H1 forHost:self]; } else { - [self removeStyleBlock:H1 from:Bold]; - [self removeStyleConflict:Bold from:H1]; + [StyleUtils removeStyleBlock:H1 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H1 forHost:self]; } stylePropChanged = YES; @@ -391,11 +252,11 @@ - (void)updateProps:(Props::Shared const &)props // Update style blocks and conflicts for bold if (newViewProps.htmlStyle.h2.bold) { - [self addStyleBlock:H2 to:Bold]; - [self addStyleConflict:Bold to:H2]; + [StyleUtils addStyleBlock:H2 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H2 forHost:self]; } else { - [self removeStyleBlock:H2 from:Bold]; - [self removeStyleConflict:Bold from:H2]; + [StyleUtils removeStyleBlock:H2 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H2 forHost:self]; } stylePropChanged = YES; @@ -412,11 +273,11 @@ - (void)updateProps:(Props::Shared const &)props // Update style blocks and conflicts for bold if (newViewProps.htmlStyle.h3.bold) { - [self addStyleBlock:H3 to:Bold]; - [self addStyleConflict:Bold to:H3]; + [StyleUtils addStyleBlock:H3 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H3 forHost:self]; } else { - [self removeStyleBlock:H3 from:Bold]; - [self removeStyleConflict:Bold from:H3]; + [StyleUtils removeStyleBlock:H3 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H3 forHost:self]; } stylePropChanged = YES; @@ -433,11 +294,11 @@ - (void)updateProps:(Props::Shared const &)props // Update style blocks and conflicts for bold if (newViewProps.htmlStyle.h4.bold) { - [self addStyleBlock:H4 to:Bold]; - [self addStyleConflict:Bold to:H4]; + [StyleUtils addStyleBlock:H4 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H4 forHost:self]; } else { - [self removeStyleBlock:H4 from:Bold]; - [self removeStyleConflict:Bold from:H4]; + [StyleUtils removeStyleBlock:H4 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H4 forHost:self]; } stylePropChanged = YES; @@ -454,11 +315,11 @@ - (void)updateProps:(Props::Shared const &)props // Update style blocks and conflicts for bold if (newViewProps.htmlStyle.h5.bold) { - [self addStyleBlock:H5 to:Bold]; - [self addStyleConflict:Bold to:H5]; + [StyleUtils addStyleBlock:H5 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H5 forHost:self]; } else { - [self removeStyleBlock:H5 from:Bold]; - [self removeStyleConflict:Bold from:H5]; + [StyleUtils removeStyleBlock:H5 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H5 forHost:self]; } stylePropChanged = YES; @@ -475,11 +336,11 @@ - (void)updateProps:(Props::Shared const &)props // Update style blocks and conflicts for bold if (newViewProps.htmlStyle.h6.bold) { - [self addStyleBlock:H6 to:Bold]; - [self addStyleConflict:Bold to:H6]; + [StyleUtils addStyleBlock:H6 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H6 forHost:self]; } else { - [self removeStyleBlock:H6 from:Bold]; - [self removeStyleConflict:Bold from:H6]; + [StyleUtils removeStyleBlock:H6 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H6 forHost:self]; } stylePropChanged = YES; @@ -1234,38 +1095,6 @@ - (bool)textInputShouldSubmitOnReturn { [_submitBehavior isEqualToString:@"submit"]; } -- (void)addStyleBlock:(StyleType)blocking to:(StyleType)blocked { - NSMutableArray *blocksArr = [blockingStyles[@(blocked)] mutableCopy]; - if (![blocksArr containsObject:@(blocking)]) { - [blocksArr addObject:@(blocking)]; - blockingStyles[@(blocked)] = blocksArr; - } -} - -- (void)removeStyleBlock:(StyleType)blocking from:(StyleType)blocked { - NSMutableArray *blocksArr = [blockingStyles[@(blocked)] mutableCopy]; - if ([blocksArr containsObject:@(blocking)]) { - [blocksArr removeObject:@(blocking)]; - blockingStyles[@(blocked)] = blocksArr; - } -} - -- (void)addStyleConflict:(StyleType)conflicting to:(StyleType)conflicted { - NSMutableArray *conflictsArr = [conflictingStyles[@(conflicted)] mutableCopy]; - if (![conflictsArr containsObject:@(conflicting)]) { - [conflictsArr addObject:@(conflicting)]; - conflictingStyles[@(conflicted)] = conflictsArr; - } -} - -- (void)removeStyleConflict:(StyleType)conflicting from:(StyleType)conflicted { - NSMutableArray *conflictsArr = [conflictingStyles[@(conflicted)] mutableCopy]; - if ([conflictsArr containsObject:@(conflicting)]) { - [conflictsArr removeObject:@(conflicting)]; - conflictingStyles[@(conflicted)] = conflictsArr; - } -} - // MARK: - Native commands and events - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args { @@ -1649,55 +1478,15 @@ - (void)startMentionWithIndicator:(NSString *)indicator { } } -// returns false when style shouldn't be applied and true when it can be - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range { - // handle blocking styles: if any is present we do not apply the toggled style - NSArray *blocking = - [self getPresentStyleTypesFrom:blockingStyles[@(type)] range:range]; - if (blocking.count != 0) { - return NO; - } - - // handle conflicting styles: remove styles within the range - NSArray *conflicting = - [self getPresentStyleTypesFrom:conflictingStyles[@(type)] range:range]; - if (conflicting.count != 0) { - for (NSNumber *type in conflicting) { - StyleBase *style = stylesDict[type]; - - if ([style isParagraph]) { - // for paragraph styles we can just call remove since it will pick up - // proper paragraph range - [style remove:range withDirtyRange:YES]; - } else { - // for inline styles we have to differentiate betweeen normal and typing - // attributes removal - range.length >= 1 ? [style remove:range withDirtyRange:YES] - : [style removeTyping]; - } - } - } - return YES; + return [StyleUtils handleStyleBlocksAndConflicts:type + range:range + forHost:self]; } - (NSArray *)getPresentStyleTypesFrom:(NSArray *)types range:(NSRange)range { - NSMutableArray *resultArray = - [[NSMutableArray alloc] init]; - for (NSNumber *type in types) { - StyleBase *style = stylesDict[type]; - - if (range.length >= 1) { - if ([style any:range]) { - [resultArray addObject:type]; - } - } else { - if ([style detect:range]) { - [resultArray addObject:type]; - } - } - } - return resultArray; + return [StyleUtils getPresentStyleTypesFrom:types range:range forHost:self]; } - (void)manageSelectionBasedChanges { diff --git a/ios/EnrichedTextViewManager.mm b/ios/EnrichedTextViewManager.mm new file mode 100644 index 000000000..2e65c1c30 --- /dev/null +++ b/ios/EnrichedTextViewManager.mm @@ -0,0 +1,13 @@ +#import +#import + +@interface EnrichedTextViewManager : RCTViewManager +@end + +@implementation EnrichedTextViewManager + +RCT_EXPORT_MODULE(EnrichedTextView) + +RCT_EXPORT_VIEW_PROPERTY(text, NSString) + +@end diff --git a/ios/htmlParser/HtmlParser.mm b/ios/htmlParser/HtmlParser.mm index a315ffffe..994257129 100644 --- a/ios/htmlParser/HtmlParser.mm +++ b/ios/htmlParser/HtmlParser.mm @@ -38,7 +38,7 @@ + (BOOL)isBlockTag:(NSString *)tagName { * for a new HTML tag that contains visible text (e.g., h4, h5, h6), * you MUST add it to the `textTags` set below. */ -- (NSString *)stripExtraWhiteSpacesAndNewlines:(NSString *)html { ++ (NSString *)stripExtraWhiteSpacesAndNewlines:(NSString *)html { NSSet *textTags = [NSSet setWithObjects:@"p", @"h1", @"h2", @"h3", @"h4", @"h5", @"h6", @"li", @"b", @"a", @"s", @"mention", @"code", @"u", @"i", nil]; @@ -108,7 +108,7 @@ - (NSString *)stripExtraWhiteSpacesAndNewlines:(NSString *)html { return output; } -- (NSString *)stringByAddingNewlinesToTag:(NSString *)tag ++ (NSString *)stringByAddingNewlinesToTag:(NSString *)tag inString:(NSString *)html leading:(BOOL)leading trailing:(BOOL)trailing { @@ -227,7 +227,7 @@ + (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html withString:@""]; fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"" withString:@""]; - } else if (_input->useHtmlNormalizer) { + } else if (useHtmlNormalizer) { // External HTML (from Google Docs, Word, web pages, etc.) // Run through the Gumbo-based normalizer to convert arbitrary HTML // into our canonical tag subset. diff --git a/ios/textStyles/EnrichedTextStyleHeaders.h b/ios/textStyles/EnrichedTextStyleHeaders.h new file mode 100644 index 000000000..9c717aaea --- /dev/null +++ b/ios/textStyles/EnrichedTextStyleHeaders.h @@ -0,0 +1,59 @@ +#pragma once +#import "StyleHeaders.h" + +@interface EnrichedTextBoldStyle : BoldStyle +@end + +@interface EnrichedTextItalicStyle : ItalicStyle +@end + +@interface EnrichedTextUnderlineStyle : UnderlineStyle +@end + +@interface EnrichedTextStrikethroughStyle : StrikethroughStyle +@end + +@interface EnrichedTextInlineCodeStyle : InlineCodeStyle +@end + +@interface EnrichedTextH1Style : H1Style +@end + +@interface EnrichedTextH2Style : H2Style +@end + +@interface EnrichedTextH3Style : H3Style +@end + +@interface EnrichedTextH4Style : H4Style +@end + +@interface EnrichedTextH5Style : H5Style +@end + +@interface EnrichedTextH6Style : H6Style +@end + +@interface EnrichedTextBlockQuoteStyle : BlockQuoteStyle +@end + +@interface EnrichedTextCodeBlockStyle : CodeBlockStyle +@end + +@interface EnrichedTextUnorderedListStyle : UnorderedListStyle +@end + +@interface EnrichedTextOrderedListStyle : OrderedListStyle +@end + +@interface EnrichedTextLinkStyle : LinkStyle +@end + +@interface EnrichedTextMentionStyle : MentionStyle +@end + +@interface EnrichedTextImageStyle : ImageStyle +@end + +@interface EnrichedTextCheckboxListStyle : CheckboxListStyle +@end diff --git a/ios/utils/StyleUtils.h b/ios/utils/StyleUtils.h new file mode 100644 index 000000000..124c109e2 --- /dev/null +++ b/ios/utils/StyleUtils.h @@ -0,0 +1,30 @@ +#import "EnrichedTextStyleHeaders.h" +#import "StyleHeaders.h" + +@interface StyleUtils : NSObject ++ (NSDictionary *> *)conflictMap; ++ (NSDictionary *> *)blockingMap; ++ (NSDictionary *)stylesDictForHost: + (id)host + isInput:(BOOL)isInput; + ++ (BOOL)handleStyleBlocksAndConflicts:(StyleType)type + range:(NSRange)range + forHost:(id)host; ++ (NSArray *)getPresentStyleTypesFrom:(NSArray *)types + range:(NSRange)range + forHost:(id)host; ++ (void)addStyleBlock:(StyleType)blocking + to:(StyleType)blocked + forHost:(id)host; ++ (void)removeStyleBlock:(StyleType)blocking + from:(StyleType)blocked + forHost:(id)host; + ++ (void)addStyleConflict:(StyleType)conflicting + to:(StyleType)conflicted + forHost:(id)host; ++ (void)removeStyleConflict:(StyleType)conflicting + from:(StyleType)conflicted + forHost:(id)host; +@end diff --git a/ios/utils/StyleUtils.mm b/ios/utils/StyleUtils.mm new file mode 100644 index 000000000..fd121b7a5 --- /dev/null +++ b/ios/utils/StyleUtils.mm @@ -0,0 +1,268 @@ +#import "StyleUtils.h" + +@implementation StyleUtils + ++ (NSDictionary *)conflictMap { + return @{ + @([BoldStyle getType]) : @[], + @([ItalicStyle getType]) : @[], + @([UnderlineStyle getType]) : @[], + @([StrikethroughStyle getType]) : @[], + @([InlineCodeStyle getType]) : + @[ @([LinkStyle getType]), @([MentionStyle getType]) ], + @([LinkStyle getType]) : @[ + @([InlineCodeStyle getType]), @([LinkStyle getType]), + @([MentionStyle getType]) + ], + @([MentionStyle getType]) : + @[ @([InlineCodeStyle getType]), @([LinkStyle getType]) ], + @([H1Style getType]) : @[ + @([H2Style getType]), @([H3Style getType]), @([H4Style getType]), + @([H5Style getType]), @([H6Style getType]), + @([UnorderedListStyle getType]), @([OrderedListStyle getType]), + @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), + @([CheckboxListStyle getType]) + ], + @([H2Style getType]) : @[ + @([H1Style getType]), @([H3Style getType]), @([H4Style getType]), + @([H5Style getType]), @([H6Style getType]), + @([UnorderedListStyle getType]), @([OrderedListStyle getType]), + @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), + @([CheckboxListStyle getType]) + ], + @([H3Style getType]) : @[ + @([H1Style getType]), @([H2Style getType]), @([H4Style getType]), + @([H5Style getType]), @([H6Style getType]), + @([UnorderedListStyle getType]), @([OrderedListStyle getType]), + @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), + @([CheckboxListStyle getType]) + ], + @([H4Style getType]) : @[ + @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), + @([H5Style getType]), @([H6Style getType]), + @([UnorderedListStyle getType]), @([OrderedListStyle getType]), + @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), + @([CheckboxListStyle getType]) + ], + @([H5Style getType]) : @[ + @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), + @([H4Style getType]), @([H6Style getType]), + @([UnorderedListStyle getType]), @([OrderedListStyle getType]), + @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), + @([CheckboxListStyle getType]) + ], + @([H6Style getType]) : @[ + @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), + @([H4Style getType]), @([H5Style getType]), + @([UnorderedListStyle getType]), @([OrderedListStyle getType]), + @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]), + @([CheckboxListStyle getType]) + ], + @([UnorderedListStyle getType]) : @[ + @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), + @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), + @([OrderedListStyle getType]), @([BlockQuoteStyle getType]), + @([CodeBlockStyle getType]), @([CheckboxListStyle getType]) + ], + @([OrderedListStyle getType]) : @[ + @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), + @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), + @([UnorderedListStyle getType]), @([BlockQuoteStyle getType]), + @([CodeBlockStyle getType]), @([CheckboxListStyle getType]) + ], + @([CheckboxListStyle getType]) : @[ + @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), + @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), + @([UnorderedListStyle getType]), @([OrderedListStyle getType]), + @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]) + ], + @([BlockQuoteStyle getType]) : @[ + @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), + @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), + @([UnorderedListStyle getType]), @([OrderedListStyle getType]), + @([CodeBlockStyle getType]), @([CheckboxListStyle getType]) + ], + @([CodeBlockStyle getType]) : @[ + @([H1Style getType]), @([H2Style getType]), @([H3Style getType]), + @([H4Style getType]), @([H5Style getType]), @([H6Style getType]), + @([BoldStyle getType]), @([UnderlineStyle getType]), + @([ItalicStyle getType]), @([StrikethroughStyle getType]), + @([UnorderedListStyle getType]), @([OrderedListStyle getType]), + @([BlockQuoteStyle getType]), @([InlineCodeStyle getType]), + @([MentionStyle getType]), @([LinkStyle getType]), + @([CheckboxListStyle getType]) + ], + @([ImageStyle getType]) : + @[ @([LinkStyle getType]), @([MentionStyle getType]) ] + }; +} + ++ (NSDictionary *)blockingMap { + return @{ + @([BoldStyle getType]) : @[ @([CodeBlockStyle getType]) ], + @([ItalicStyle getType]) : @[ @([CodeBlockStyle getType]) ], + @([UnderlineStyle getType]) : @[ @([CodeBlockStyle getType]) ], + @([StrikethroughStyle getType]) : @[ @([CodeBlockStyle getType]) ], + @([InlineCodeStyle getType]) : + @[ @([CodeBlockStyle getType]), @([ImageStyle getType]) ], + @([LinkStyle getType]) : + @[ @([CodeBlockStyle getType]), @([ImageStyle getType]) ], + @([MentionStyle getType]) : + @[ @([CodeBlockStyle getType]), @([ImageStyle getType]) ], + @([H1Style getType]) : @[], + @([H2Style getType]) : @[], + @([H3Style getType]) : @[], + @([H4Style getType]) : @[], + @([H5Style getType]) : @[], + @([H6Style getType]) : @[], + @([UnorderedListStyle getType]) : @[], + @([OrderedListStyle getType]) : @[], + @([CheckboxListStyle getType]) : @[], + @([BlockQuoteStyle getType]) : @[], + @([CodeBlockStyle getType]) : @[], + @([ImageStyle getType]) : @[ @([InlineCodeStyle getType]) ] + }; +} + ++ (NSDictionary *)stylesDictForHost:(id)host + isInput:(BOOL)isInput { + NSArray *baseClasses = @[ + [BoldStyle class], [ItalicStyle class], [UnderlineStyle class], + [StrikethroughStyle class], [InlineCodeStyle class], [LinkStyle class], + [MentionStyle class], [H1Style class], [H2Style class], [H3Style class], + [H4Style class], [H5Style class], [H6Style class], + [UnorderedListStyle class], [OrderedListStyle class], + [CheckboxListStyle class], [BlockQuoteStyle class], [CodeBlockStyle class], + [ImageStyle class] + ]; + + NSArray *viewerClasses = @[ + [EnrichedTextBoldStyle class], [EnrichedTextItalicStyle class], + [EnrichedTextUnderlineStyle class], [EnrichedTextStrikethroughStyle class], + [EnrichedTextInlineCodeStyle class], [EnrichedTextLinkStyle class], + [EnrichedTextMentionStyle class], [EnrichedTextH1Style class], + [EnrichedTextH2Style class], [EnrichedTextH3Style class], + [EnrichedTextH4Style class], [EnrichedTextH5Style class], + [EnrichedTextH6Style class], [EnrichedTextUnorderedListStyle class], + [EnrichedTextOrderedListStyle class], [EnrichedTextCheckboxListStyle class], + [EnrichedTextBlockQuoteStyle class], [EnrichedTextCodeBlockStyle class], + [EnrichedTextImageStyle class] + ]; + + NSMutableDictionary *dict = [NSMutableDictionary new]; + + for (NSUInteger i = 0; i < baseClasses.count; i++) { + // Choose the class based on the context + Class targetClass = isInput ? baseClasses[i] : viewerClasses[i]; + + // Instantiate and add to dictionary + // We use [baseClasses[i] getType] for the key to ensure the + // conflict maps (which use base types) always match. + StyleBase *instance = [[targetClass alloc] initWithHost:host]; + dict[@([baseClasses[i] getType])] = instance; + } + + return [dict copy]; +} + +// returns false when style shouldn't be applied and true when it can be ++ (BOOL)handleStyleBlocksAndConflicts:(StyleType)type + range:(NSRange)range + forHost:(id)host { + // handle blocking styles: if any is present we do not apply the toggled style + NSArray *blocking = + [self getPresentStyleTypesFrom:host.blockingStyles[@(type)] + range:range + forHost:host]; + if (blocking.count != 0) { + return NO; + } + + // handle conflicting styles: remove styles within the range + NSArray *conflicting = + [self getPresentStyleTypesFrom:host.conflictingStyles[@(type)] + range:range + forHost:host]; + if (conflicting.count != 0) { + for (NSNumber *type in conflicting) { + StyleBase *style = host.stylesDict[type]; + + if ([style isParagraph]) { + // for paragraph styles we can just call remove since it will pick up + // proper paragraph range + [style remove:range withDirtyRange:YES]; + } else { + // for inline styles we have to differentiate betweeen normal and typing + // attributes removal + range.length >= 1 ? [style remove:range withDirtyRange:YES] + : [style removeTyping]; + } + } + } + return YES; +} + ++ (NSArray *)getPresentStyleTypesFrom:(NSArray *)types + range:(NSRange)range + forHost:(id)host { + NSMutableArray *resultArray = + [[NSMutableArray alloc] init]; + for (NSNumber *type in types) { + StyleBase *style = host.stylesDict[type]; + + if (range.length >= 1) { + if ([style any:range]) { + [resultArray addObject:type]; + } + } else { + if ([style detect:range]) { + [resultArray addObject:type]; + } + } + } + return resultArray; +} + ++ (void)addStyleBlock:(StyleType)blocking + to:(StyleType)blocked + forHost:(id)host { + NSMutableArray *blocksArr = [host.blockingStyles[@(blocked)] mutableCopy]; + if (![blocksArr containsObject:@(blocking)]) { + [blocksArr addObject:@(blocking)]; + host.blockingStyles[@(blocked)] = blocksArr; + } +} + ++ (void)removeStyleBlock:(StyleType)blocking + from:(StyleType)blocked + forHost:(id)host { + NSMutableArray *blocksArr = [host.blockingStyles[@(blocked)] mutableCopy]; + if ([blocksArr containsObject:@(blocking)]) { + [blocksArr removeObject:@(blocking)]; + host.blockingStyles[@(blocked)] = blocksArr; + } +} + ++ (void)addStyleConflict:(StyleType)conflicting + to:(StyleType)conflicted + forHost:(id)host { + NSMutableArray *conflictsArr = + [host.conflictingStyles[@(conflicted)] mutableCopy]; + if (![conflictsArr containsObject:@(conflicting)]) { + [conflictsArr addObject:@(conflicting)]; + host.conflictingStyles[@(conflicted)] = conflictsArr; + } +} + ++ (void)removeStyleConflict:(StyleType)conflicting + from:(StyleType)conflicted + forHost:(id)host { + NSMutableArray *conflictsArr = + [host.conflictingStyles[@(conflicted)] mutableCopy]; + if ([conflictsArr containsObject:@(conflicting)]) { + [conflictsArr removeObject:@(conflicting)]; + host.conflictingStyles[@(conflicted)] = conflictsArr; + } +} + +@end From b6e44ad639d20d5ba631c774cb12a0bf4d96e35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 9 Apr 2026 16:38:32 +0200 Subject: [PATCH 04/19] fix: use Style utils --- ios/EnrichedTextInputView.mm | 5 ----- ios/utils/ParagraphAttributesUtils.mm | 11 +++++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 78699dc6a..1b995691b 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1484,11 +1484,6 @@ - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range { forHost:self]; } -- (NSArray *)getPresentStyleTypesFrom:(NSArray *)types - range:(NSRange)range { - return [StyleUtils getPresentStyleTypesFrom:types range:range forHost:self]; -} - - (void)manageSelectionBasedChanges { NSString *currentString = [textView.textStorage.string copy]; diff --git a/ios/utils/ParagraphAttributesUtils.mm b/ios/utils/ParagraphAttributesUtils.mm index 0f4a4a199..95efd8f75 100644 --- a/ios/utils/ParagraphAttributesUtils.mm +++ b/ios/utils/ParagraphAttributesUtils.mm @@ -2,6 +2,7 @@ #import "EnrichedTextInputView.h" #import "RangeUtils.h" #import "StyleHeaders.h" +#import "StyleUtils.h" #import "TextInsertionUtils.h" @implementation ParagraphAttributesUtils @@ -159,12 +160,14 @@ + (BOOL)handleParagraphStylesMergeOnBackspace:(NSRange)range StyleType type = [[leftParagraphStyle class] getType]; - NSArray *conflictingStyles = [typedInput + NSArray *conflictingStyles = [StyleUtils getPresentStyleTypesFrom:typedInput->conflictingStyles[@(type)] - range:rightRange]; + range:rightRange + forHost:typedInput]; NSArray *blockingStyles = - [typedInput getPresentStyleTypesFrom:typedInput->blockingStyles[@(type)] - range:rightRange]; + [StyleUtils getPresentStyleTypesFrom:typedInput->blockingStyles[@(type)] + range:rightRange + forHost:typedInput]; NSArray *allToBeRemoved = [conflictingStyles arrayByAddingObjectsFromArray:blockingStyles]; From b2e29e55038ca9b0819cc731066c77a726a4bfe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 10 Apr 2026 10:08:44 +0200 Subject: [PATCH 05/19] feat: add EnrichedText styles --- ios/EnrichedTextInputView.h | 3 +- ios/EnrichedTextInputView.mm | 2 +- ios/inputParser/InputParser.mm | 5 +- .../EnrichedTextStyleHeaders.h | 0 ios/interfaces/StyleHeaders.h | 7 ++- ios/styles/CheckboxListStyle.mm | 5 +- ios/styles/EnrichedTextStyles.mm | 58 +++++++++++++++++++ ios/styles/ImageStyle.mm | 12 ++-- 8 files changed, 79 insertions(+), 13 deletions(-) rename ios/{textStyles => interfaces}/EnrichedTextStyleHeaders.h (100%) create mode 100644 ios/styles/EnrichedTextStyles.mm diff --git a/ios/EnrichedTextInputView.h b/ios/EnrichedTextInputView.h index a107e3ce2..cba58a3f6 100644 --- a/ios/EnrichedTextInputView.h +++ b/ios/EnrichedTextInputView.h @@ -45,8 +45,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)anyTextMayHaveBeenModified; - (void)scheduleRelayoutIfNeeded; - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range; -- (NSArray *)getPresentStyleTypesFrom:(NSArray *)types - range:(NSRange)range; + @end NS_ASSUME_NONNULL_END diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 1b995691b..e078da3f0 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1934,7 +1934,7 @@ - (void)onTextBlockTap:(TextBlockTapGestureRecognizer *)gr { if (checkboxStyle) { NSUInteger charIndex = (NSUInteger)gr.characterIndex; - [checkboxStyle toggleCheckedAt:charIndex]; + [checkboxStyle toggleCheckedAt:charIndex withDirtyRange:YES]; [self anyTextMayHaveBeenModified]; NSString *fullText = textView.textStorage.string; diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm index 5cd5bbc64..260556699 100644 --- a/ios/inputParser/InputParser.mm +++ b/ios/inputParser/InputParser.mm @@ -665,7 +665,8 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles ImageData *imgData = (ImageData *)stylePair.styleValue; [((ImageStyle *)baseStyle) addImageAtRange:styleRange imageData:imgData - withSelection:NO]; + withSelection:NO + withDirtyRange:YES]; } else if ([styleType isEqualToNumber:@([CheckboxListStyle getType])]) { NSDictionary *checkboxStates = (NSDictionary *)stylePair.styleValue; CheckboxListStyle *cbLStyle = (CheckboxListStyle *)baseStyle; @@ -687,7 +688,7 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles NSUInteger checkboxPosition = offset + [key unsignedIntegerValue]; BOOL isChecked = [checkboxStates[key] boolValue]; if (isChecked) { - [cbLStyle toggleCheckedAt:checkboxPosition]; + [cbLStyle toggleCheckedAt:checkboxPosition withDirtyRange:YES]; } } } else { diff --git a/ios/textStyles/EnrichedTextStyleHeaders.h b/ios/interfaces/EnrichedTextStyleHeaders.h similarity index 100% rename from ios/textStyles/EnrichedTextStyleHeaders.h rename to ios/interfaces/EnrichedTextStyleHeaders.h diff --git a/ios/interfaces/StyleHeaders.h b/ios/interfaces/StyleHeaders.h index 247ce2451..e28769369 100644 --- a/ios/interfaces/StyleHeaders.h +++ b/ios/interfaces/StyleHeaders.h @@ -41,6 +41,7 @@ - (MentionParams *)getMentionParamsAt:(NSUInteger)location; - (NSRange)getFullMentionRangeAt:(NSUInteger)location; - (NSValue *)getActiveMentionRange; +- (void)applyMentionMeta:(MentionParams *)params range:(NSRange)range; @end @interface HeadingStyleBase : StyleBase @@ -83,7 +84,8 @@ range:(NSRange)range withTyping:(BOOL)withTyping withDirtyRange:(BOOL)withDirtyRange; -- (void)toggleCheckedAt:(NSUInteger)location; +- (void)toggleCheckedAt:(NSUInteger)location + withDirtyRange:(BOOL)withDirtyRange; - (BOOL)getCheckboxStateAt:(NSUInteger)location; - (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text; @end @@ -98,6 +100,7 @@ - (void)addImage:(NSString *)uri width:(CGFloat)width height:(CGFloat)height; - (void)addImageAtRange:(NSRange)range imageData:(ImageData *)imageData - withSelection:(BOOL)withSelection; + withSelection:(BOOL)withSelection + withDirtyRange:(BOOL)withDirtyRange; - (ImageData *)getImageDataAt:(NSUInteger)location; @end diff --git a/ios/styles/CheckboxListStyle.mm b/ios/styles/CheckboxListStyle.mm index 6cf8ef32b..2b0104a02 100644 --- a/ios/styles/CheckboxListStyle.mm +++ b/ios/styles/CheckboxListStyle.mm @@ -87,7 +87,8 @@ - (void)reapplyFromStylePair:(StylePair *)pair { [self addWithChecked:checked range:range withTyping:NO withDirtyRange:NO]; } -- (void)toggleCheckedAt:(NSUInteger)location { +- (void)toggleCheckedAt:(NSUInteger)location + withDirtyRange:(BOOL)withDirtyRange { if (location >= self.host.textView.textStorage.length) { return; } @@ -107,7 +108,7 @@ - (void)toggleCheckedAt:(NSUInteger)location { [self addWithChecked:!isCurrentlyChecked range:paragraphRange withTyping:NO - withDirtyRange:YES]; + withDirtyRange:withDirtyRange]; } - (BOOL)getCheckboxStateAt:(NSUInteger)location { diff --git a/ios/styles/EnrichedTextStyles.mm b/ios/styles/EnrichedTextStyles.mm new file mode 100644 index 000000000..d4b6cbfbf --- /dev/null +++ b/ios/styles/EnrichedTextStyles.mm @@ -0,0 +1,58 @@ +#import "EnrichedTextStyleHeaders.h" + +@implementation EnrichedTextBoldStyle +@end + +@implementation EnrichedTextItalicStyle +@end + +@implementation EnrichedTextUnderlineStyle +@end + +@implementation EnrichedTextStrikethroughStyle +@end + +@implementation EnrichedTextInlineCodeStyle +@end + +@implementation EnrichedTextH1Style +@end + +@implementation EnrichedTextH2Style +@end + +@implementation EnrichedTextH3Style +@end + +@implementation EnrichedTextH4Style +@end + +@implementation EnrichedTextH5Style +@end + +@implementation EnrichedTextH6Style +@end + +@implementation EnrichedTextBlockQuoteStyle +@end + +@implementation EnrichedTextCodeBlockStyle +@end + +@implementation EnrichedTextUnorderedListStyle +@end + +@implementation EnrichedTextOrderedListStyle +@end + +@implementation EnrichedTextLinkStyle +@end + +@implementation EnrichedTextMentionStyle +@end + +@implementation EnrichedTextCheckboxListStyle +@end + +@implementation EnrichedTextImageStyle +@end diff --git a/ios/styles/ImageStyle.mm b/ios/styles/ImageStyle.mm index dfc0c58c1..4dfa1bbdb 100644 --- a/ios/styles/ImageStyle.mm +++ b/ios/styles/ImageStyle.mm @@ -99,7 +99,8 @@ - (ImageData *)getImageDataAt:(NSUInteger)location { - (void)addImageAtRange:(NSRange)range imageData:(ImageData *)imageData - withSelection:(BOOL)withSelection { + withSelection:(BOOL)withSelection + withDirtyRange:(BOOL)withDirtyRange { if (!imageData) return; @@ -128,8 +129,10 @@ - (void)addImageAtRange:(NSRange)range withSelection:withSelection]; } - NSRange insertedImageRange = NSMakeRange(range.location, 1); - [self.host.attributesManager addDirtyRange:insertedImageRange]; + if (withDirtyRange) { + NSRange insertedImageRange = NSMakeRange(range.location, 1); + [self.host.attributesManager addDirtyRange:insertedImageRange]; + } } - (void)addImage:(NSString *)uri width:(CGFloat)width height:(CGFloat)height { @@ -140,7 +143,8 @@ - (void)addImage:(NSString *)uri width:(CGFloat)width height:(CGFloat)height { [self addImageAtRange:self.host.textView.selectedRange imageData:data - withSelection:YES]; + withSelection:YES + withDirtyRange:YES]; } @end From afac0d477ca5d2e14775e99eab3b1e4ded57aa6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 13 Apr 2026 13:34:08 +0200 Subject: [PATCH 06/19] feat: handle link and mention presses --- ios/EnrichedTextInputView.mm | 4 +- .../{InputConfig.h => EnrichedConfig.h} | 6 +- .../{InputConfig.mm => EnrichedConfig.mm} | 22 ++- ios/interfaces/MentionStyleProps.h | 4 + ios/interfaces/MentionStyleProps.mm | 17 ++ ios/utils/AttachmentLayoutUtils.h | 6 +- ios/utils/AttachmentLayoutUtils.mm | 4 +- ios/utils/CheckboxHitTestUtils.mm | 4 +- ios/utils/EnrichedTextTouchHandler.h | 14 ++ ios/utils/EnrichedTextTouchHandler.mm | 150 ++++++++++++++++++ ios/utils/EnrichedTouchableTextView.h | 14 ++ ios/utils/EnrichedTouchableTextView.mm | 30 ++++ 12 files changed, 262 insertions(+), 13 deletions(-) rename ios/config/{InputConfig.h => EnrichedConfig.h} (96%) rename ios/config/{InputConfig.mm => EnrichedConfig.mm} (97%) create mode 100644 ios/utils/EnrichedTextTouchHandler.h create mode 100644 ios/utils/EnrichedTextTouchHandler.mm create mode 100644 ios/utils/EnrichedTouchableTextView.h create mode 100644 ios/utils/EnrichedTouchableTextView.mm diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index e078da3f0..8754f5e34 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -163,13 +163,13 @@ - (void)updateProps:(Props::Shared const &)props // initial config if (config == nullptr) { isFirstMount = YES; - config = [[InputConfig alloc] init]; + config = [[EnrichedConfig alloc] init]; } // any style prop changes: // firstly we create the new config for the changes - InputConfig *newConfig = [config copy]; + EnrichedConfig *newConfig = [config copy]; if (newViewProps.color != oldViewProps.color) { if (isColorMeaningful(newViewProps.color)) { diff --git a/ios/config/InputConfig.h b/ios/config/EnrichedConfig.h similarity index 96% rename from ios/config/InputConfig.h rename to ios/config/EnrichedConfig.h index 73685213b..44a7c9102 100644 --- a/ios/config/InputConfig.h +++ b/ios/config/EnrichedConfig.h @@ -4,7 +4,7 @@ #import "TextDecorationLineEnum.h" #import -@interface InputConfig : NSObject +@interface EnrichedConfig : NSObject - (instancetype)init; - (UIColor *)primaryColor; - (void)setPrimaryColor:(UIColor *)newValue; @@ -101,4 +101,8 @@ - (void)setCheckboxListBoxColor:(UIColor *)newValue; - (UIImage *)checkboxCheckedImage; - (UIImage *)checkboxUncheckedImage; + +// MARK: - Text only props +- (UIColor *)linkPressColor; +- (void)setLinkPressColor:(UIColor *)newValue; @end diff --git a/ios/config/InputConfig.mm b/ios/config/EnrichedConfig.mm similarity index 97% rename from ios/config/InputConfig.mm rename to ios/config/EnrichedConfig.mm index 6bc55d7f1..327bddd56 100644 --- a/ios/config/InputConfig.mm +++ b/ios/config/EnrichedConfig.mm @@ -1,7 +1,7 @@ -#import +#import #import -@implementation InputConfig { +@implementation EnrichedConfig { UIColor *_primaryColor; NSNumber *_primaryFontSize; CGFloat _primaryLineHeight; @@ -54,6 +54,9 @@ @implementation InputConfig { UIColor *_checkboxListBoxColor; UIImage *_checkboxCheckedImage; UIImage *_checkboxUncheckedImage; + + // text only + UIColor *_linkPressColor; } - (instancetype)init { @@ -65,7 +68,7 @@ - (instancetype)init { } - (id)copyWithZone:(NSZone *)zone { - InputConfig *copy = [[[self class] allocWithZone:zone] init]; + EnrichedConfig *copy = [[[self class] allocWithZone:zone] init]; copy->_primaryColor = [_primaryColor copy]; copy->_primaryFontSize = [_primaryFontSize copy]; copy->_primaryLineHeight = _primaryLineHeight; @@ -115,6 +118,9 @@ - (id)copyWithZone:(NSZone *)zone { copy->_checkboxListBoxColor = [_checkboxListBoxColor copy]; copy->_checkboxCheckedImage = _checkboxCheckedImage; copy->_checkboxUncheckedImage = _checkboxUncheckedImage; + + // text only + copy->_linkPressColor = [_linkPressColor copy]; return copy; } @@ -661,4 +667,14 @@ - (UIImage *)generateCheckboxImage:(BOOL)isChecked { return result; } +// MARK: - Text only props + +- (UIColor *)linkPressColor { + return _linkPressColor; +} + +- (void)setLinkPressColor:(UIColor *)newValue { + _linkPressColor = newValue; +} + @end diff --git a/ios/interfaces/MentionStyleProps.h b/ios/interfaces/MentionStyleProps.h index d0d59dd5f..f83d1bc40 100644 --- a/ios/interfaces/MentionStyleProps.h +++ b/ios/interfaces/MentionStyleProps.h @@ -10,4 +10,8 @@ @property TextDecorationLineEnum decorationLine; + (NSDictionary *)getSinglePropsFromFollyDynamic:(folly::dynamic)folly; + (NSDictionary *)getComplexPropsFromFollyDynamic:(folly::dynamic)folly; + +// MARK: - Text only props +@property UIColor *pressColor; +@property UIColor *pressBackgroundColor; @end diff --git a/ios/interfaces/MentionStyleProps.mm b/ios/interfaces/MentionStyleProps.mm index 5ea235383..b4ca19c1a 100644 --- a/ios/interfaces/MentionStyleProps.mm +++ b/ios/interfaces/MentionStyleProps.mm @@ -34,6 +34,23 @@ + (MentionStyleProps *)getSingleMentionStylePropsFromFollyDynamic: nativeProps.decorationLine = DecorationUnderline; } + // text only + if (folly["pressColor"].isNumber()) { + facebook::react::SharedColor pressColor = facebook::react::SharedColor( + facebook::react::Color(int32_t(folly["pressColor"].asInt()))); + nativeProps.pressColor = RCTUIColorFromSharedColor(pressColor); + } else { + nativeProps.pressColor = [UIColor blueColor]; + } + + if (folly["pressBackgroundColor"].isNumber()) { + facebook::react::SharedColor bgColor = facebook::react::SharedColor( + facebook::react::Color(int32_t(folly["pressBackgroundColor"].asInt()))); + nativeProps.pressBackgroundColor = RCTUIColorFromSharedColor(bgColor); + } else { + nativeProps.pressBackgroundColor = [UIColor yellowColor]; + } + return nativeProps; } diff --git a/ios/utils/AttachmentLayoutUtils.h b/ios/utils/AttachmentLayoutUtils.h index 79b6515d6..78a37dadd 100644 --- a/ios/utils/AttachmentLayoutUtils.h +++ b/ios/utils/AttachmentLayoutUtils.h @@ -1,6 +1,6 @@ #pragma once +#import "EnrichedConfig.h" #import "ImageAttachment.h" -#import "InputConfig.h" #import @interface AttachmentLayoutUtils : NSObject @@ -11,7 +11,7 @@ + (NSMutableDictionary *) layoutAttachmentsInTextView:(UITextView *)textView - config:(InputConfig *)config + config:(EnrichedConfig *)config existingViews: (NSMutableDictionary *) attachmentViews; @@ -19,6 +19,6 @@ + (CGRect)frameForAttachment:(ImageAttachment *)attachment atRange:(NSRange)range textView:(UITextView *)textView - config:(InputConfig *)config; + config:(EnrichedConfig *)config; @end diff --git a/ios/utils/AttachmentLayoutUtils.mm b/ios/utils/AttachmentLayoutUtils.mm index 1b08ad750..2cd39052f 100644 --- a/ios/utils/AttachmentLayoutUtils.mm +++ b/ios/utils/AttachmentLayoutUtils.mm @@ -33,7 +33,7 @@ + (void)handleAttachmentUpdate:(MediaAttachment *)attachment + (NSMutableDictionary *) layoutAttachmentsInTextView:(UITextView *)textView - config:(InputConfig *)config + config:(EnrichedConfig *)config existingViews: (NSMutableDictionary *) attachmentViews { @@ -107,7 +107,7 @@ + (void)handleAttachmentUpdate:(MediaAttachment *)attachment + (CGRect)frameForAttachment:(ImageAttachment *)attachment atRange:(NSRange)range textView:(UITextView *)textView - config:(InputConfig *)config { + config:(EnrichedConfig *)config { NSLayoutManager *layoutManager = textView.layoutManager; NSTextContainer *textContainer = textView.textContainer; NSTextStorage *storage = textView.textStorage; diff --git a/ios/utils/CheckboxHitTestUtils.mm b/ios/utils/CheckboxHitTestUtils.mm index 851d48b32..4055b8b2b 100644 --- a/ios/utils/CheckboxHitTestUtils.mm +++ b/ios/utils/CheckboxHitTestUtils.mm @@ -1,6 +1,6 @@ #import "CheckboxHitTestUtils.h" +#import "EnrichedConfig.h" #import "EnrichedTextInputView.h" -#import "InputConfig.h" #import "StyleHeaders.h" static const CGFloat kCheckboxHitSlopLeft = 8.0; @@ -56,7 +56,7 @@ + (CGRect)checkboxRectForGlyphIndex:(NSUInteger)glyphIndex inInput:(EnrichedTextInputView *)input { UITextView *textView = input->textView; NSLayoutManager *layoutManager = textView.layoutManager; - InputConfig *config = input->config; + EnrichedConfig *config = input->config; if (!config) { return CGRectNull; diff --git a/ios/utils/EnrichedTextTouchHandler.h b/ios/utils/EnrichedTextTouchHandler.h new file mode 100644 index 000000000..8f59bf2f6 --- /dev/null +++ b/ios/utils/EnrichedTextTouchHandler.h @@ -0,0 +1,14 @@ +#import + +@class EnrichedTextView; + +@interface EnrichedTextTouchHandler : NSObject + +@property(nonatomic, weak) EnrichedTextView *view; + +- (instancetype)initWithView:(EnrichedTextView *)view; +- (void)handleTouchBeganAtPoint:(CGPoint)point; +- (void)handleTouchEndedAtPoint:(CGPoint)point; +- (void)handleTouchCancelled; + +@end diff --git a/ios/utils/EnrichedTextTouchHandler.mm b/ios/utils/EnrichedTextTouchHandler.mm new file mode 100644 index 000000000..15be60d2e --- /dev/null +++ b/ios/utils/EnrichedTextTouchHandler.mm @@ -0,0 +1,150 @@ +#import "EnrichedTextTouchHandler.h" +#import "EnrichedTextView.h" +#import "LinkData.h" +#import "MentionParams.h" +#import "MentionStyleProps.h" +#import "StyleBase.h" + +@implementation EnrichedTextTouchHandler { + NSRange _activeRange; + NSString *_activeAttrKey; + id _activeValue; +} + +- (instancetype)initWithView:(EnrichedTextView *)view { + if (self = [super init]) { + _view = view; + } + return self; +} + +- (void)handleTouchBeganAtPoint:(CGPoint)point { + NSUInteger charIndex = [self characterIndexAtPoint:point]; + NSRange range; + NSString *key; + id value = [self findClickableAt:charIndex range:&range key:&key]; + if (value) { + _activeRange = range; + _activeAttrKey = key; + _activeValue = value; + [self updateVisualsPressed:YES]; + } +} + +- (void)handleTouchEndedAtPoint:(CGPoint)point { + if (!_activeValue) { + return; + } + + NSUInteger charIndex = [self characterIndexAtPoint:point]; + if (NSLocationInRange(charIndex, _activeRange)) { + [self dispatchEvent]; + } + + [self updateVisualsPressed:NO]; + [self reset]; +} + +- (void)handleTouchCancelled { + [self updateVisualsPressed:NO]; + [self reset]; +} + +- (NSUInteger)characterIndexAtPoint:(CGPoint)point { + UITextView *tv = self.view.textView; + NSLayoutManager *lm = tv.layoutManager; + NSTextContainer *tc = tv.textContainer; + + CGPoint textOffset = + CGPointMake(tv.textContainerInset.left, tv.textContainerInset.top); + CGPoint locationInContainer = + CGPointMake(point.x - textOffset.x, point.y - textOffset.y); + + NSUInteger glyphIndex = [lm glyphIndexForPoint:locationInContainer + inTextContainer:tc]; + CGRect glyphRect = [lm boundingRectForGlyphRange:NSMakeRange(glyphIndex, 1) + inTextContainer:tc]; + + if (!CGRectContainsPoint(glyphRect, locationInContainer)) { + return NSNotFound; + } + return [lm characterIndexForGlyphAtIndex:glyphIndex]; +} + +- (id)findClickableAt:(NSUInteger)idx + range:(NSRangePointer)range + key:(NSString **)key { + if (idx == NSNotFound || idx >= self.view.textView.textStorage.length) + return nil; + + NSArray *keys = + @[ @"EnrichedManualLink", @"EnrichedAutomaticLink", @"EnrichedMention" ]; + for (NSString *k in keys) { + id val = [self.view.textView.textStorage attribute:k + atIndex:idx + effectiveRange:range]; + if (val) { + *key = k; + return val; + } + } + return nil; +} + +- (void)updateVisualsPressed:(BOOL)pressed { + if (pressed) { + UIColor *color = nil; + UIColor *bgColor = nil; + + if ([_activeAttrKey isEqualToString:@"EnrichedMention"]) { + MentionParams *m = (MentionParams *)_activeValue; + MentionStyleProps *mProps = + [self.view.config mentionStylePropsForIndicator:m.indicator]; + color = [mProps pressColor]; + bgColor = [mProps pressBackgroundColor]; + } else { + color = [self.view.config linkPressColor]; + } + + NSMutableDictionary *newAttrs = [[NSMutableDictionary alloc] init]; + if (color) { + newAttrs[NSForegroundColorAttributeName] = color; + newAttrs[NSUnderlineColorAttributeName] = color; + newAttrs[NSStrikethroughColorAttributeName] = color; + } + if (bgColor) { + newAttrs[NSBackgroundColorAttributeName] = bgColor; + } + + [self.view.textView.textStorage addAttributes:newAttrs range:_activeRange]; + } else { + // REVERT using the Style Engine in the View + for (StyleBase *style in self.view.stylesDict.allValues) { + NSString *styleKey = [style getKey]; + // If the style key matches exactly (Mentions) + // OR if both the style and the active key are Link-related + BOOL isMatch = [styleKey isEqualToString:_activeAttrKey]; + BOOL isLinkMatch = ([_activeAttrKey containsString:@"Link"] && + [styleKey containsString:@"Link"]); + + if (isMatch || isLinkMatch) { + [style applyStyling:_activeRange]; + } + } + } +} + +- (void)dispatchEvent { + if ([_activeAttrKey containsString:@"Link"]) { + [self.view emitOnLinkPressEvent:((LinkData *)_activeValue).url]; + } else if ([_activeAttrKey isEqualToString:@"EnrichedMention"]) { + [self.view emitOnMentionPressEvent:(MentionParams *)_activeValue]; + } +} + +- (void)reset { + _activeValue = nil; + _activeAttrKey = nil; + _activeRange = NSMakeRange(0, 0); +} +@end diff --git a/ios/utils/EnrichedTouchableTextView.h b/ios/utils/EnrichedTouchableTextView.h new file mode 100644 index 000000000..6b98def84 --- /dev/null +++ b/ios/utils/EnrichedTouchableTextView.h @@ -0,0 +1,14 @@ +#pragma once + +#import + +@class EnrichedTextTouchHandler; + +// Forwards single-finger touches to `EnrichedTextTouchHandler` before `super` +// so link/mention pressed styling is not delayed by `UITextView` gesture +// arbitration. +@interface EnrichedTouchableTextView : UITextView + +@property(nonatomic, weak) EnrichedTextTouchHandler *touchHandler; + +@end diff --git a/ios/utils/EnrichedTouchableTextView.mm b/ios/utils/EnrichedTouchableTextView.mm new file mode 100644 index 000000000..b1aab2cbf --- /dev/null +++ b/ios/utils/EnrichedTouchableTextView.mm @@ -0,0 +1,30 @@ +#import "EnrichedTouchableTextView.h" +#import "EnrichedTextTouchHandler.h" + +@implementation EnrichedTouchableTextView + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + if (touches.count == 1) { + UITouch *touch = touches.anyObject; + CGPoint point = [touch locationInView:self]; + [self.touchHandler handleTouchBeganAtPoint:point]; + } + [super touchesBegan:touches withEvent:event]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + if (touches.count == 1) { + UITouch *touch = touches.anyObject; + CGPoint point = [touch locationInView:self]; + [self.touchHandler handleTouchEndedAtPoint:point]; + } + [super touchesEnded:touches withEvent:event]; +} + +- (void)touchesCancelled:(NSSet *)touches + withEvent:(UIEvent *)event { + [self.touchHandler handleTouchCancelled]; + [super touchesCancelled:touches withEvent:event]; +} + +@end From 2c6e9f51ca1b9bc0709a9723c9e60752f78e0a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 13 Apr 2026 13:56:06 +0200 Subject: [PATCH 07/19] fix: rename EnrichedStyleHost --- ios/EnrichedTextInputView.h | 8 ++--- ios/EnrichedTextInputView.mm | 38 ++++++++++++++++++++---- ios/EnrichedTextView.h | 32 ++++++++++++++++++++ ios/extensions/LayoutManagerExtension.mm | 18 +++++------ ios/interfaces/EnrichedViewHost.h | 25 ++++++++++++++++ ios/interfaces/StyleBase.h | 6 ++-- ios/interfaces/StyleBase.mm | 2 +- ios/styles/MentionStyle.mm | 2 +- ios/utils/OccurenceUtils.h | 16 +++++----- ios/utils/OccurenceUtils.mm | 14 ++++----- ios/utils/StyleUtils.h | 14 ++++----- ios/utils/StyleUtils.mm | 14 ++++----- 12 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 ios/EnrichedTextView.h create mode 100644 ios/interfaces/EnrichedViewHost.h diff --git a/ios/EnrichedTextInputView.h b/ios/EnrichedTextInputView.h index cba58a3f6..432c8c1d5 100644 --- a/ios/EnrichedTextInputView.h +++ b/ios/EnrichedTextInputView.h @@ -1,8 +1,8 @@ #pragma once #import "AttributesManager.h" #import "BaseStyleProtocol.h" -#import "EnrichedStyleHost.h" -#import "InputConfig.h" +#import "EnrichedConfig.h" +#import "EnrichedViewHost.h" #import "InputParser.h" #import "InputTextView.h" #import "LinkData.h" @@ -16,11 +16,11 @@ NS_ASSUME_NONNULL_BEGIN @interface EnrichedTextInputView - : RCTViewComponentView { + : RCTViewComponentView { @public InputTextView *textView; @public - InputConfig *config; + EnrichedConfig *config; @public InputParser *parser; @public diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 8754f5e34..c323b2b28 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -23,11 +23,9 @@ #import #define GET_STYLE_STATE(TYPE_ENUM) \ - { \ - .isActive = [self isStyleActive:TYPE_ENUM], \ - .isBlocking = [self isStyle:TYPE_ENUM activeInMap:blockingStyles], \ - .isConflicting = [self isStyle:TYPE_ENUM activeInMap:conflictingStyles] \ - } + {.isActive = [self isStyleActive:TYPE_ENUM], \ + .isBlocking = [self isStyle:TYPE_ENUM activeInMap:blockingStyles], \ + .isConflicting = [self isStyle:TYPE_ENUM activeInMap:conflictingStyles]} using namespace facebook::react; @@ -73,6 +71,36 @@ + (BOOL)shouldBeRecycled { return NO; } +// MARK: - EnrichedViewHost protocol + +- (UITextView *)textView { + return textView; +} + +- (EnrichedConfig *)config { + return config; +} + +- (NSDictionary *)stylesDict { + return stylesDict; +} + +- (AttributesManager *)attributesManager { + return attributesManager; +} + +- (NSMutableDictionary *> *)conflictingStyles { + return conflictingStyles; +} + +- (NSMutableDictionary *> *)blockingStyles { + return blockingStyles; +} + +- (NSMutableDictionary *)defaultTypingAttributes { + return defaultTypingAttributes; +} + // MARK: - Init - (instancetype)initWithFrame:(CGRect)frame { diff --git a/ios/EnrichedTextView.h b/ios/EnrichedTextView.h new file mode 100644 index 000000000..b994a4311 --- /dev/null +++ b/ios/EnrichedTextView.h @@ -0,0 +1,32 @@ +#pragma once +#import "EnrichedConfig.h" +#import "EnrichedViewHost.h" +#import "MediaAttachment.h" +#import "MentionParams.h" +#import +#import + +#ifndef EnrichedTextViewNativeComponent_h +#define EnrichedTextViewNativeComponent_h + +NS_ASSUME_NONNULL_BEGIN + +@interface EnrichedTextView + : RCTViewComponentView { +@public + UITextView *textView; +@public + EnrichedConfig *config; +@public + NSDictionary *stylesDict; + NSMutableDictionary *> *conflictingStyles; + NSMutableDictionary *> *blockingStyles; +} +- (CGSize)measureSize:(CGFloat)maxWidth; +- (void)emitOnLinkPressEvent:(NSString *)url; +- (void)emitOnMentionPressEvent:(MentionParams *)mention; +@end + +NS_ASSUME_NONNULL_END + +#endif /* EnrichedTextViewNativeComponent_h */ diff --git a/ios/extensions/LayoutManagerExtension.mm b/ios/extensions/LayoutManagerExtension.mm index efd77d0d9..5ff476216 100644 --- a/ios/extensions/LayoutManagerExtension.mm +++ b/ios/extensions/LayoutManagerExtension.mm @@ -1,6 +1,6 @@ #import "LayoutManagerExtension.h" #import "ColorExtension.h" -#import "EnrichedStyleHost.h" +#import "EnrichedViewHost.h" #import "RangeUtils.h" #import "StyleHeaders.h" #import "WeakBox.h" @@ -49,7 +49,7 @@ - (void)my_drawBackgroundForGlyphRange:(NSRange)glyphRange atPoint:(CGPoint)origin { [self my_drawBackgroundForGlyphRange:glyphRange atPoint:origin]; - id host = self.input; + id host = self.input; if (host == nullptr) { return; } @@ -62,7 +62,7 @@ - (void)my_drawBackgroundForGlyphRange:(NSRange)glyphRange [self drawCodeBlocks:host origin:origin visibleCharRange:visibleCharRange]; } -- (void)drawCodeBlocks:(id)host +- (void)drawCodeBlocks:(id)host origin:(CGPoint)origin visibleCharRange:(NSRange)visibleCharRange { CodeBlockStyle *codeBlockStyle = host.stylesDict[@([CodeBlockStyle getType])]; @@ -199,7 +199,7 @@ - (void)drawCodeBlocks:(id)host return mergedPairs; } -- (void)drawBlockQuotes:(id)host +- (void)drawBlockQuotes:(id)host origin:(CGPoint)origin visibleCharRange:(NSRange)visibleCharRange { BlockQuoteStyle *bqStyle = host.stylesDict[@([BlockQuoteStyle getType])]; @@ -238,7 +238,7 @@ - (void)drawBlockQuotes:(id)host } } -- (void)drawLists:(id)host +- (void)drawLists:(id)host origin:(CGPoint)origin visibleCharRange:(NSRange)visibleCharRange { UnorderedListStyle *ulStyle = @@ -326,7 +326,7 @@ - (void)drawLists:(id)host } } -- (NSString *)getDecimalMarkerForList:(id)host +- (NSString *)getDecimalMarkerForList:(id)host charIndex:(NSUInteger)index { NSString *fullText = host.textView.textStorage.string; NSInteger itemNumber = 1; @@ -380,7 +380,7 @@ - (CGRect)getTextAlignedUsedRect:(CGRect)usedRect font:(UIFont *)font { return usedRect; } -- (void)drawCheckbox:(id)host +- (void)drawCheckbox:(id)host markerFormat:(NSString *)markerFormat origin:(CGPoint)origin usedRect:(CGRect)usedRect { @@ -398,7 +398,7 @@ - (void)drawCheckbox:(id)host [image drawAtPoint:CGPointMake(boxX, boxY)]; } -- (void)drawBullet:(id)host +- (void)drawBullet:(id)host origin:(CGPoint)origin usedRect:(CGRect)usedRect { CGFloat gapWidth = [host.config unorderedListGapWidth]; @@ -417,7 +417,7 @@ - (void)drawBullet:(id)host CGContextRestoreGState(context); } -- (void)drawDecimal:(id)host +- (void)drawDecimal:(id)host marker:(NSString *)marker markerAttributes:(NSDictionary *)markerAttributes origin:(CGPoint)origin diff --git a/ios/interfaces/EnrichedViewHost.h b/ios/interfaces/EnrichedViewHost.h new file mode 100644 index 000000000..d56e05c1d --- /dev/null +++ b/ios/interfaces/EnrichedViewHost.h @@ -0,0 +1,25 @@ +#pragma once +#import "AttributesManager.h" +#import "EnrichedConfig.h" +#import "StyleTypeEnum.h" +#import + +@protocol EnrichedViewHost +@required +@property(nonatomic, readonly) UITextView *textView; +@property(nonatomic, readonly) EnrichedConfig *config; +@property(nonatomic, readonly) NSDictionary *stylesDict; +@property(nonatomic, readonly, nullable) AttributesManager *attributesManager; +@property(nonatomic, readonly, nullable) + NSMutableDictionary *> *conflictingStyles; +@property(nonatomic, readonly, nullable) + NSMutableDictionary *> *blockingStyles; +@property(nonatomic, readonly, nullable) + NSMutableDictionary *defaultTypingAttributes; +@property(nonatomic) BOOL blockEmitting; +@optional +- (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range; +- (void)emitOnLinkDetectedEvent:(id _Nonnull)linkData range:(NSRange)range; +- (void)emitOnMentionEvent:(NSString *_Nonnull)indicator + text:(NSString *_Nullable)text; +@end diff --git a/ios/interfaces/StyleBase.h b/ios/interfaces/StyleBase.h index 83bbfb0b7..26be1ba42 100644 --- a/ios/interfaces/StyleBase.h +++ b/ios/interfaces/StyleBase.h @@ -1,18 +1,18 @@ #pragma once #import "AttributeEntry.h" -#import "EnrichedStyleHost.h" +#import "EnrichedViewHost.h" #import "StylePair.h" #import "StyleTypeEnum.h" #import @interface StyleBase : NSObject -@property(nonatomic, weak) id host; +@property(nonatomic, weak) id host; + (StyleType)getType; - (NSString *)getKey; - (NSString *)getValue; - (BOOL)isParagraph; - (BOOL)needsZWS; -- (instancetype)initWithHost:(id)host; +- (instancetype)initWithHost:(id)host; - (NSRange)actualUsedRange:(NSRange)range; - (void)toggle:(NSRange)range; - (void)add:(NSRange)range diff --git a/ios/interfaces/StyleBase.mm b/ios/interfaces/StyleBase.mm index 6c1277fdc..7754008f3 100644 --- a/ios/interfaces/StyleBase.mm +++ b/ios/interfaces/StyleBase.mm @@ -34,7 +34,7 @@ - (BOOL)needsZWS { return NO; } -- (instancetype)initWithHost:(id)host { +- (instancetype)initWithHost:(id)host { self = [super init]; _host = host; return self; diff --git a/ios/styles/MentionStyle.mm b/ios/styles/MentionStyle.mm index 529bc0297..e844708b0 100644 --- a/ios/styles/MentionStyle.mm +++ b/ios/styles/MentionStyle.mm @@ -27,7 +27,7 @@ - (BOOL)isParagraph { return NO; } -- (instancetype)initWithHost:(id)host { +- (instancetype)initWithHost:(id)host { self = [super initWithHost:host]; if (self) { _activeMentionRange = nullptr; diff --git a/ios/utils/OccurenceUtils.h b/ios/utils/OccurenceUtils.h index a27ce313e..a355399bd 100644 --- a/ios/utils/OccurenceUtils.h +++ b/ios/utils/OccurenceUtils.h @@ -1,43 +1,43 @@ #pragma once -#import "EnrichedStyleHost.h" +#import "EnrichedViewHost.h" #import "StylePair.h" @interface OccurenceUtils : NSObject + (BOOL)detect:(NSAttributedStringKey _Nonnull)key - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (BOOL)detect:(NSAttributedStringKey _Nonnull)key - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host atIndex:(NSUInteger)index checkPrevious:(BOOL)checkPrev withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (BOOL)detectMultiple:(NSArray *_Nonnull)keys - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (BOOL)any:(NSAttributedStringKey _Nonnull)key - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (BOOL)anyMultiple:(NSArray *_Nonnull)keys - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (NSArray *_Nullable)all:(NSAttributedStringKey _Nonnull)key - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; + (NSArray *_Nullable) allMultiple:(NSArray *_Nonnull)keys - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition; diff --git a/ios/utils/OccurenceUtils.mm b/ios/utils/OccurenceUtils.mm index bab3685a6..c92f739fd 100644 --- a/ios/utils/OccurenceUtils.mm +++ b/ios/utils/OccurenceUtils.mm @@ -4,7 +4,7 @@ @implementation OccurenceUtils + (BOOL)detect:(NSAttributedStringKey _Nonnull)key - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { @@ -26,7 +26,7 @@ + (BOOL)detect:(NSAttributedStringKey _Nonnull)key // it means that first character of paragraph will be checked instead if the // detection is not in input's selected range and at the end of the input + (BOOL)detect:(NSAttributedStringKey _Nonnull)key - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host atIndex:(NSUInteger)index checkPrevious:(BOOL)checkPrev withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, @@ -60,7 +60,7 @@ + (BOOL)detect:(NSAttributedStringKey _Nonnull)key } + (BOOL)detectMultiple:(NSArray *_Nonnull)keys - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { @@ -81,7 +81,7 @@ + (BOOL)detectMultiple:(NSArray *_Nonnull)keys } + (BOOL)any:(NSAttributedStringKey _Nonnull)key - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { @@ -101,7 +101,7 @@ + (BOOL)any:(NSAttributedStringKey _Nonnull)key } + (BOOL)anyMultiple:(NSArray *_Nonnull)keys - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { @@ -126,7 +126,7 @@ + (BOOL)anyMultiple:(NSArray *_Nonnull)keys } + (NSArray *_Nullable)all:(NSAttributedStringKey _Nonnull)key - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition: (BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, @@ -152,7 +152,7 @@ + (BOOL)anyMultiple:(NSArray *_Nonnull)keys + (NSArray *_Nullable) allMultiple:(NSArray *_Nonnull)keys - withHost:(id _Nonnull)host + withHost:(id _Nonnull)host inRange:(NSRange)range withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value, NSRange range))condition { diff --git a/ios/utils/StyleUtils.h b/ios/utils/StyleUtils.h index 124c109e2..d0850ddb2 100644 --- a/ios/utils/StyleUtils.h +++ b/ios/utils/StyleUtils.h @@ -5,26 +5,26 @@ + (NSDictionary *> *)conflictMap; + (NSDictionary *> *)blockingMap; + (NSDictionary *)stylesDictForHost: - (id)host + (id)host isInput:(BOOL)isInput; + (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range - forHost:(id)host; + forHost:(id)host; + (NSArray *)getPresentStyleTypesFrom:(NSArray *)types range:(NSRange)range - forHost:(id)host; + forHost:(id)host; + (void)addStyleBlock:(StyleType)blocking to:(StyleType)blocked - forHost:(id)host; + forHost:(id)host; + (void)removeStyleBlock:(StyleType)blocking from:(StyleType)blocked - forHost:(id)host; + forHost:(id)host; + (void)addStyleConflict:(StyleType)conflicting to:(StyleType)conflicted - forHost:(id)host; + forHost:(id)host; + (void)removeStyleConflict:(StyleType)conflicting from:(StyleType)conflicted - forHost:(id)host; + forHost:(id)host; @end diff --git a/ios/utils/StyleUtils.mm b/ios/utils/StyleUtils.mm index fd121b7a5..5372e8cdc 100644 --- a/ios/utils/StyleUtils.mm +++ b/ios/utils/StyleUtils.mm @@ -124,7 +124,7 @@ + (NSDictionary *)blockingMap { }; } -+ (NSDictionary *)stylesDictForHost:(id)host ++ (NSDictionary *)stylesDictForHost:(id)host isInput:(BOOL)isInput { NSArray *baseClasses = @[ [BoldStyle class], [ItalicStyle class], [UnderlineStyle class], @@ -168,7 +168,7 @@ + (NSDictionary *)stylesDictForHost:(id)host // returns false when style shouldn't be applied and true when it can be + (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range - forHost:(id)host { + forHost:(id)host { // handle blocking styles: if any is present we do not apply the toggled style NSArray *blocking = [self getPresentStyleTypesFrom:host.blockingStyles[@(type)] @@ -204,7 +204,7 @@ + (BOOL)handleStyleBlocksAndConflicts:(StyleType)type + (NSArray *)getPresentStyleTypesFrom:(NSArray *)types range:(NSRange)range - forHost:(id)host { + forHost:(id)host { NSMutableArray *resultArray = [[NSMutableArray alloc] init]; for (NSNumber *type in types) { @@ -225,7 +225,7 @@ + (NSArray *)getPresentStyleTypesFrom:(NSArray *)types + (void)addStyleBlock:(StyleType)blocking to:(StyleType)blocked - forHost:(id)host { + forHost:(id)host { NSMutableArray *blocksArr = [host.blockingStyles[@(blocked)] mutableCopy]; if (![blocksArr containsObject:@(blocking)]) { [blocksArr addObject:@(blocking)]; @@ -235,7 +235,7 @@ + (void)addStyleBlock:(StyleType)blocking + (void)removeStyleBlock:(StyleType)blocking from:(StyleType)blocked - forHost:(id)host { + forHost:(id)host { NSMutableArray *blocksArr = [host.blockingStyles[@(blocked)] mutableCopy]; if ([blocksArr containsObject:@(blocking)]) { [blocksArr removeObject:@(blocking)]; @@ -245,7 +245,7 @@ + (void)removeStyleBlock:(StyleType)blocking + (void)addStyleConflict:(StyleType)conflicting to:(StyleType)conflicted - forHost:(id)host { + forHost:(id)host { NSMutableArray *conflictsArr = [host.conflictingStyles[@(conflicted)] mutableCopy]; if (![conflictsArr containsObject:@(conflicting)]) { @@ -256,7 +256,7 @@ + (void)addStyleConflict:(StyleType)conflicting + (void)removeStyleConflict:(StyleType)conflicting from:(StyleType)conflicted - forHost:(id)host { + forHost:(id)host { NSMutableArray *conflictsArr = [host.conflictingStyles[@(conflicted)] mutableCopy]; if ([conflictsArr containsObject:@(conflicting)]) { From 44af57cbb891b1138b2fe01f4f85a9cfcbfe38cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 13 Apr 2026 16:31:21 +0200 Subject: [PATCH 08/19] fix: use host instead of input inside utils --- ios/EnrichedTextInputView.mm | 2 + ios/utils/TextInsertionUtils.h | 6 +- ios/utils/TextInsertionUtils.mm | 16 ++--- ios/utils/ZeroWidthSpaceUtils.h | 5 +- ios/utils/ZeroWidthSpaceUtils.mm | 103 +++++++++++++++---------------- 5 files changed, 64 insertions(+), 68 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index c323b2b28..182543abf 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -56,6 +56,8 @@ @implementation EnrichedTextInputView { NSString *_submitBehavior; } +@synthesize blockEmitting = blockEmitting; + // MARK: - Component utils + (ComponentDescriptorProvider)componentDescriptorProvider { diff --git a/ios/utils/TextInsertionUtils.h b/ios/utils/TextInsertionUtils.h index 1012f3837..79e11bd81 100644 --- a/ios/utils/TextInsertionUtils.h +++ b/ios/utils/TextInsertionUtils.h @@ -1,3 +1,4 @@ +#import "EnrichedViewHost.h" #import @interface TextInsertionUtils : NSObject @@ -5,13 +6,12 @@ at:(NSInteger)index additionalAttributes: (NSDictionary *)additionalAttrs - input:(id)input + input:(id)host withSelection:(BOOL)withSelection; + (void)replaceText:(NSString *)text at:(NSRange)range additionalAttributes: (NSDictionary *)additionalAttrs - input:(id)input + input:(id)host withSelection:(BOOL)withSelection; -; @end diff --git a/ios/utils/TextInsertionUtils.mm b/ios/utils/TextInsertionUtils.mm index 20927ba0c..70bf31acf 100644 --- a/ios/utils/TextInsertionUtils.mm +++ b/ios/utils/TextInsertionUtils.mm @@ -1,5 +1,4 @@ #import "TextInsertionUtils.h" -#import "EnrichedTextInputView.h" #import "UIView+React.h" @implementation TextInsertionUtils @@ -7,14 +6,13 @@ + (void)insertText:(NSString *)text at:(NSInteger)index additionalAttributes: (NSDictionary *)additionalAttrs - input:(id)input + input:(id)host withSelection:(BOOL)withSelection { - EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input; - if (typedInput == nullptr) { + if (host == nullptr) { return; } - UITextView *textView = typedInput->textView; + UITextView *textView = host.textView; NSMutableDictionary *copiedAttrs = [textView.typingAttributes mutableCopy]; @@ -38,15 +36,13 @@ + (void)replaceText:(NSString *)text at:(NSRange)range additionalAttributes: (NSDictionary *)additionalAttrs - input:(id)input + input:(id)host withSelection:(BOOL)withSelection { - EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input; - if (typedInput == nullptr) { + if (host == nullptr) { return; } - UITextView *textView = typedInput->textView; - + UITextView *textView = host.textView; [textView.textStorage replaceCharactersInRange:range withString:text]; if (additionalAttrs != nullptr) { [textView.textStorage diff --git a/ios/utils/ZeroWidthSpaceUtils.h b/ios/utils/ZeroWidthSpaceUtils.h index 7678749de..72b1d116d 100644 --- a/ios/utils/ZeroWidthSpaceUtils.h +++ b/ios/utils/ZeroWidthSpaceUtils.h @@ -1,9 +1,10 @@ +#import "EnrichedViewHost.h" #import #pragma once @interface ZeroWidthSpaceUtils : NSObject -+ (void)handleZeroWidthSpacesInInput:(id)input; ++ (void)handleZeroWidthSpacesInInput:(id)host; + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text - input:(id)input; + input:(id)host; @end diff --git a/ios/utils/ZeroWidthSpaceUtils.mm b/ios/utils/ZeroWidthSpaceUtils.mm index bebb21e9a..ebbfb4b67 100644 --- a/ios/utils/ZeroWidthSpaceUtils.mm +++ b/ios/utils/ZeroWidthSpaceUtils.mm @@ -5,26 +5,25 @@ #import "UIView+React.h" @implementation ZeroWidthSpaceUtils -+ (void)handleZeroWidthSpacesInInput:(id)input { - EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input; - if (typedInput == nullptr) { ++ (void)handleZeroWidthSpacesInInput:(id)host { + if (host == nullptr) { return; } - [self removeSpacesIfNeededinInput:typedInput]; - [self addSpacesIfNeededinInput:typedInput]; + [self removeSpacesIfNeededinInput:host]; + [self addSpacesIfNeededinInput:host]; } -+ (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input { ++ (void)removeSpacesIfNeededinInput:(id)host { NSMutableArray *indexesToBeRemoved = [[NSMutableArray alloc] init]; - NSRange preRemoveSelection = input->textView.selectedRange; + NSRange preRemoveSelection = host.textView.selectedRange; - for (int i = 0; i < input->textView.textStorage.string.length; i++) { - unichar character = [input->textView.textStorage.string characterAtIndex:i]; + for (int i = 0; i < host.textView.textStorage.string.length; i++) { + unichar character = [host.textView.textStorage.string characterAtIndex:i]; if (character == 0x200B) { NSRange characterRange = NSMakeRange(i, 1); - NSRange paragraphRange = [input->textView.textStorage.string + NSRange paragraphRange = [host.textView.textStorage.string paragraphRangeForRange:characterRange]; // having paragraph longer than 1 character means someone most likely // added something and we probably can remove the space @@ -33,7 +32,7 @@ + (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input { // here, we still need zero width space to keep the empty list items if (paragraphRange.length == 2 && paragraphRange.location == i && [[NSCharacterSet newlineCharacterSet] - characterIsMember:[input->textView.textStorage.string + characterIsMember:[host.textView.textStorage.string characterAtIndex:i + 1]]) { removeSpace = NO; } @@ -44,7 +43,7 @@ + (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input { } // zero width spaces with no needsZWS style on them get removed - if (![self anyZWSStylePresentInRange:characterRange input:input]) { + if (![self anyZWSStylePresentInRange:characterRange input:host]) { [indexesToBeRemoved addObject:@(characterRange.location)]; } } @@ -59,7 +58,7 @@ + (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input { [TextInsertionUtils replaceText:@"" at:replaceRange additionalAttributes:nullptr - input:input + input:host withSelection:NO]; offset -= 1; if ([index integerValue] < preRemoveSelection.location) { @@ -72,8 +71,8 @@ + (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input { } // fix the selection if needed - if ([input->textView isFirstResponder]) { - input->textView.selectedRange = + if ([host.textView isFirstResponder]) { + host.textView.selectedRange = NSMakeRange(preRemoveSelection.location + postRemoveLocationOffset, preRemoveSelection.length + postRemoveLengthOffset); } @@ -83,13 +82,13 @@ + (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input { // dictionary so that ZWS characters carry the same meta-attributes that are // currently active in the typing attributes. Only within the currently selected // range! -+ (NSDictionary *)inlineMetaAttributesForInput:(EnrichedTextInputView *)input { ++ (NSDictionary *)inlineMetaAttributesForInput:(id)host { NSMutableDictionary *metaAttrs = [NSMutableDictionary new]; - for (NSNumber *type in input->stylesDict) { - StyleBase *style = input->stylesDict[type]; + for (NSNumber *type in host.stylesDict) { + StyleBase *style = host.stylesDict[type]; if (![style isParagraph]) { AttributeEntry *entry = - [style getEntryIfPresent:input->textView.selectedRange]; + [style getEntryIfPresent:host.textView.selectedRange]; if (entry) { metaAttrs[entry.key] = entry.value; } @@ -98,20 +97,20 @@ + (NSDictionary *)inlineMetaAttributesForInput:(EnrichedTextInputView *)input { return metaAttrs.count > 0 ? metaAttrs : nullptr; } -+ (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input { ++ (void)addSpacesIfNeededinInput:(id)host { NSMutableArray *indexesToBeInserted = [[NSMutableArray alloc] init]; - NSRange preAddSelection = input->textView.selectedRange; + NSRange preAddSelection = host.textView.selectedRange; - for (NSUInteger i = 0; i < input->textView.textStorage.string.length; i++) { - unichar character = [input->textView.textStorage.string characterAtIndex:i]; + for (NSUInteger i = 0; i < host.textView.textStorage.string.length; i++) { + unichar character = [host.textView.textStorage.string characterAtIndex:i]; if ([[NSCharacterSet newlineCharacterSet] characterIsMember:character]) { NSRange characterRange = NSMakeRange(i, 1); - NSRange paragraphRange = [input->textView.textStorage.string + NSRange paragraphRange = [host.textView.textStorage.string paragraphRangeForRange:characterRange]; if (paragraphRange.length == 1) { - if ([self anyZWSStylePresentInRange:characterRange input:input]) { + if ([self anyZWSStylePresentInRange:characterRange input:host]) { // we have an empty list or quote item with no space: add it! [indexesToBeInserted addObject:@(paragraphRange.location)]; } @@ -119,7 +118,7 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input { } } - NSDictionary *metaAttrs = [self inlineMetaAttributesForInput:input]; + NSDictionary *metaAttrs = [self inlineMetaAttributesForInput:host]; // do the replacing NSInteger offset = 0; @@ -130,7 +129,7 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input { [TextInsertionUtils replaceText:@"\u200B\n" at:replaceRange additionalAttributes:metaAttrs - input:input + input:host withSelection:NO]; offset += 1; if ([index integerValue] < preAddSelection.location) { @@ -143,21 +142,21 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input { } // additional check for last index of the input - NSRange lastRange = NSMakeRange(input->textView.textStorage.string.length, 0); + NSRange lastRange = NSMakeRange(host.textView.textStorage.string.length, 0); NSRange lastParagraphRange = - [input->textView.textStorage.string paragraphRangeForRange:lastRange]; + [host.textView.textStorage.string paragraphRangeForRange:lastRange]; if (lastParagraphRange.length == 0 && - [self anyZWSStylePresentInRange:lastRange input:input]) { + [self anyZWSStylePresentInRange:lastRange input:host]) { [TextInsertionUtils insertText:@"\u200B" at:lastRange.location additionalAttributes:metaAttrs - input:input + input:host withSelection:NO]; } // fix the selection if needed - if ([input->textView isFirstResponder]) { - input->textView.selectedRange = + if ([host.textView isFirstResponder]) { + host.textView.selectedRange = NSMakeRange(preAddSelection.location + postAddLocationOffset, preAddSelection.length + postAddLengthOffset); } @@ -165,12 +164,11 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input { + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text - input:(id)input { + input:(id)host { if (![text isEqualToString:@""]) { return NO; } - EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input; - if (typedInput == nullptr) { + if (host == nullptr) { return NO; } @@ -178,9 +176,9 @@ + (BOOL)handleBackspaceInRange:(NSRange)range // Nothing to delete, but if the first paragraph has a needsZWS style, // remove it. if (range.length == 0 && range.location == 0) { - NSRange firstParagraphRange = [typedInput->textView.textStorage.string + NSRange firstParagraphRange = [host.textView.textStorage.string paragraphRangeForRange:NSMakeRange(0, 0)]; - if ([self removeZWSStyleInRange:firstParagraphRange input:typedInput]) { + if ([self removeZWSStyleInRange:firstParagraphRange input:host]) { return YES; } return NO; @@ -191,19 +189,19 @@ + (BOOL)handleBackspaceInRange:(NSRange)range } unichar character = - [typedInput->textView.textStorage.string characterAtIndex:range.location]; + [host.textView.textStorage.string characterAtIndex:range.location]; // zero-width space got backspaced if (character == 0x200B) { // in such case: remove the whole line without the endline if there is one NSRange paragraphRange = - [typedInput->textView.textStorage.string paragraphRangeForRange:range]; + [host.textView.textStorage.string paragraphRangeForRange:range]; NSRange removalRange = paragraphRange; // if whole paragraph gets removed then 0 length for style removal NSRange styleRemovalRange = NSMakeRange(paragraphRange.location, 0); if ([[NSCharacterSet newlineCharacterSet] - characterIsMember:[typedInput->textView.textStorage.string + characterIsMember:[host.textView.textStorage.string characterAtIndex:NSMaxRange(paragraphRange) - 1]]) { // if endline is there, don't remove it @@ -217,11 +215,11 @@ + (BOOL)handleBackspaceInRange:(NSRange)range [TextInsertionUtils replaceText:@"" at:removalRange additionalAttributes:nullptr - input:typedInput + input:host withSelection:YES]; // and then remove associated styling - [self removeZWSStyleInRange:styleRemovalRange input:typedInput]; + [self removeZWSStyleInRange:styleRemovalRange input:host]; return YES; } @@ -232,10 +230,10 @@ + (BOOL)handleBackspaceInRange:(NSRange)range // style from the current paragraph. if ([[NSCharacterSet newlineCharacterSet] characterIsMember:character]) { NSUInteger nextParaStart = NSMaxRange(range); - if (nextParaStart < typedInput->textView.textStorage.string.length) { - NSRange nextParagraphRange = [typedInput->textView.textStorage.string + if (nextParaStart < host.textView.textStorage.string.length) { + NSRange nextParagraphRange = [host.textView.textStorage.string paragraphRangeForRange:NSMakeRange(nextParaStart, 0)]; - if ([self removeZWSStyleInRange:nextParagraphRange input:typedInput]) { + if ([self removeZWSStyleInRange:nextParagraphRange input:host]) { return YES; } } @@ -245,9 +243,9 @@ + (BOOL)handleBackspaceInRange:(NSRange)range } + (BOOL)anyZWSStylePresentInRange:(NSRange)range - input:(EnrichedTextInputView *)input { - for (NSNumber *type in input->stylesDict) { - StyleBase *style = input->stylesDict[type]; + input:(id)host { + for (NSNumber *type in host.stylesDict) { + StyleBase *style = host.stylesDict[type]; if ([style needsZWS] && [style detect:range]) { return YES; } @@ -255,10 +253,9 @@ + (BOOL)anyZWSStylePresentInRange:(NSRange)range return NO; } -+ (BOOL)removeZWSStyleInRange:(NSRange)range - input:(EnrichedTextInputView *)input { - for (NSNumber *type in input->stylesDict) { - StyleBase *style = input->stylesDict[type]; ++ (BOOL)removeZWSStyleInRange:(NSRange)range input:(id)host { + for (NSNumber *type in host.stylesDict) { + StyleBase *style = host.stylesDict[type]; if ([style needsZWS] && [style detect:range]) { [style remove:range withDirtyRange:YES]; return YES; From caff2f0af111fb46f95b5e1ea3ba2470b29c8430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Tue, 14 Apr 2026 16:26:44 +0200 Subject: [PATCH 09/19] fix: zero width space handling --- ios/EnrichedTextInputView.mm | 1 + ios/EnrichedTextView.h | 1 + ios/EnrichedTextView.mm | 852 +++++++++++++++++++++++ ios/extensions/LayoutManagerExtension.mm | 15 +- ios/utils/OccurenceUtils.mm | 8 +- 5 files changed, 870 insertions(+), 7 deletions(-) create mode 100644 ios/EnrichedTextView.mm diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 182543abf..b4e3a551d 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1224,6 +1224,7 @@ - (void)setValue:(NSString *)value { NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:value]; if (initiallyProcessedHtml == nullptr) { // just plain text + textView.typingAttributes = defaultTypingAttributes; textView.text = value; } else { // we've got some seemingly proper html diff --git a/ios/EnrichedTextView.h b/ios/EnrichedTextView.h index b994a4311..6a090fe5b 100644 --- a/ios/EnrichedTextView.h +++ b/ios/EnrichedTextView.h @@ -21,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN NSDictionary *stylesDict; NSMutableDictionary *> *conflictingStyles; NSMutableDictionary *> *blockingStyles; + NSMutableDictionary *defaultTypingAttributes; } - (CGSize)measureSize:(CGFloat)maxWidth; - (void)emitOnLinkPressEvent:(NSString *)url; diff --git a/ios/EnrichedTextView.mm b/ios/EnrichedTextView.mm new file mode 100644 index 000000000..f689946f8 --- /dev/null +++ b/ios/EnrichedTextView.mm @@ -0,0 +1,852 @@ +#import "EnrichedTextView.h" +#import "AttachmentLayoutUtils.h" +#import "EnrichedTextStyleHeaders.h" +#import "EnrichedTextTouchHandler.h" +#import "EnrichedTouchableTextView.h" +#import "HtmlParser.h" +#import "LayoutManagerExtension.h" +#import "LinkData.h" +#import "MentionParams.h" +#import "MentionStyleProps.h" +#import "RCTFabricComponentsPlugins.h" +#import "StringExtension.h" +#import "StyleUtils.h" +#import "TextDecorationLineEnum.h" +#import "ZeroWidthSpaceUtils.h" +#import +#import +#import +#import +#import +#import + +using namespace facebook::react; + +@interface EnrichedTextView () +@end + +@implementation EnrichedTextView { + EnrichedTextViewShadowNode::ConcreteState::Shared _state; + NSMutableDictionary *_attachmentViews; + EnrichedTextTouchHandler *_touchHandler; +} + +@synthesize blockEmitting = _blockEmitting; + +// MARK: - Component utils + ++ (ComponentDescriptorProvider)componentDescriptorProvider { + return concreteComponentDescriptorProvider(); +} + +Class EnrichedTextViewCls(void) { + return EnrichedTextView.class; +} + ++ (BOOL)shouldBeRecycled { + return NO; +} + +// MARK: - Init + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = + std::make_shared(); + _props = defaultProps; + _attachmentViews = [[NSMutableDictionary alloc] init]; + defaultTypingAttributes = [[NSMutableDictionary alloc] init]; + [self setupTextView]; + [self setupStyles]; + self.contentView = textView; + } + return self; +} + +- (void)setupTextView { + EnrichedTouchableTextView *tv = [[EnrichedTouchableTextView alloc] init]; + _touchHandler = [[EnrichedTextTouchHandler alloc] initWithView:self]; + tv.touchHandler = _touchHandler; + textView = tv; + + textView.backgroundColor = UIColor.clearColor; + textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0); + textView.textContainer.lineFragmentPadding = 0; + textView.editable = NO; + textView.scrollEnabled = NO; + textView.adjustsFontForContentSizeCategory = YES; + textView.layoutManager.input = self; +} + +- (void)setupStyles { + stylesDict = [StyleUtils stylesDictForHost:self isInput:NO]; + conflictingStyles = [[StyleUtils conflictMap] mutableCopy]; + blockingStyles = [[StyleUtils blockingMap] mutableCopy]; +} + +// MARK: - EnrichedViewHost protocol + +- (UITextView *)textView { + return textView; +} + +- (EnrichedConfig *)config { + return config; +} + +- (NSDictionary *)stylesDict { + return stylesDict; +} + +- (AttributesManager *)attributesManager { + return nil; +} + +- (NSMutableDictionary *> *)conflictingStyles { + return conflictingStyles; +} + +- (NSMutableDictionary *> *)blockingStyles { + return blockingStyles; +} + +- (NSMutableDictionary *)defaultTypingAttributes { + return defaultTypingAttributes; +} + +// MARK: - Props + +- (void)updateProps:(Props::Shared const &)props + oldProps:(Props::Shared const &)oldProps { + const auto &oldViewProps = + *std::static_pointer_cast(_props); + const auto &newViewProps = + *std::static_pointer_cast(props); + BOOL isFirstMount = NO; + BOOL stylePropChanged = NO; + BOOL textChanged = NO; + + if (config == nullptr) { + isFirstMount = YES; + config = [[EnrichedConfig alloc] init]; + } + + EnrichedConfig *newConfig = [config copy]; + + // color + if (newViewProps.color != oldViewProps.color) { + if (isColorMeaningful(newViewProps.color)) { + UIColor *uiColor = RCTUIColorFromSharedColor(newViewProps.color); + [newConfig setPrimaryColor:uiColor]; + } else { + [newConfig setPrimaryColor:nullptr]; + } + stylePropChanged = YES; + } + + // fontSize + if (newViewProps.fontSize != oldViewProps.fontSize) { + if (newViewProps.fontSize) { + NSNumber *fontSize = @(newViewProps.fontSize); + [newConfig setPrimaryFontSize:fontSize]; + } else { + [newConfig setPrimaryFontSize:nullptr]; + } + stylePropChanged = YES; + } + + // fontWeight + if (newViewProps.fontWeight != oldViewProps.fontWeight) { + if (!newViewProps.fontWeight.empty()) { + [newConfig + setPrimaryFontWeight:[NSString + fromCppString:newViewProps.fontWeight]]; + } else { + [newConfig setPrimaryFontWeight:nullptr]; + } + stylePropChanged = YES; + } + + // fontFamily + if (newViewProps.fontFamily != oldViewProps.fontFamily) { + if (!newViewProps.fontFamily.empty()) { + [newConfig + setPrimaryFontFamily:[NSString + fromCppString:newViewProps.fontFamily]]; + } else { + [newConfig setPrimaryFontFamily:nullptr]; + } + stylePropChanged = YES; + } + + // fontStyle + if (newViewProps.fontStyle != oldViewProps.fontStyle) { + // TODO: Implement fontStyle setter on EnrichedConfig + // fontStyle doesn't have a dedicated setter on EnrichedConfig yet, + // but we track it for future use + stylePropChanged = YES; + } + + // htmlStyle headings + if (newViewProps.htmlStyle.h1.fontSize != + oldViewProps.htmlStyle.h1.fontSize) { + [newConfig setH1FontSize:newViewProps.htmlStyle.h1.fontSize]; + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h1.bold != oldViewProps.htmlStyle.h1.bold) { + [newConfig setH1Bold:newViewProps.htmlStyle.h1.bold]; + + // Update style blocks and conflicts for bold + if (newViewProps.htmlStyle.h1.bold) { + [StyleUtils addStyleBlock:H1 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H1 forHost:self]; + } else { + [StyleUtils removeStyleBlock:H1 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H1 forHost:self]; + } + + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h2.fontSize != + oldViewProps.htmlStyle.h2.fontSize) { + [newConfig setH2FontSize:newViewProps.htmlStyle.h2.fontSize]; + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h2.bold != oldViewProps.htmlStyle.h2.bold) { + [newConfig setH2Bold:newViewProps.htmlStyle.h2.bold]; + + // Update style blocks and conflicts for bold + if (newViewProps.htmlStyle.h2.bold) { + [StyleUtils addStyleBlock:H2 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H2 forHost:self]; + } else { + [StyleUtils removeStyleBlock:H2 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H2 forHost:self]; + } + + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h3.fontSize != + oldViewProps.htmlStyle.h3.fontSize) { + [newConfig setH3FontSize:newViewProps.htmlStyle.h3.fontSize]; + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h3.bold != oldViewProps.htmlStyle.h3.bold) { + [newConfig setH3Bold:newViewProps.htmlStyle.h3.bold]; + + // Update style blocks and conflicts for bold + if (newViewProps.htmlStyle.h3.bold) { + [StyleUtils addStyleBlock:H3 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H3 forHost:self]; + } else { + [StyleUtils removeStyleBlock:H3 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H3 forHost:self]; + } + + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h4.fontSize != + oldViewProps.htmlStyle.h4.fontSize) { + [newConfig setH4FontSize:newViewProps.htmlStyle.h4.fontSize]; + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h4.bold != oldViewProps.htmlStyle.h4.bold) { + [newConfig setH4Bold:newViewProps.htmlStyle.h4.bold]; + + // Update style blocks and conflicts for bold + if (newViewProps.htmlStyle.h4.bold) { + [StyleUtils addStyleBlock:H4 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H4 forHost:self]; + } else { + [StyleUtils removeStyleBlock:H4 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H4 forHost:self]; + } + + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h5.fontSize != + oldViewProps.htmlStyle.h5.fontSize) { + [newConfig setH5FontSize:newViewProps.htmlStyle.h5.fontSize]; + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h5.bold != oldViewProps.htmlStyle.h5.bold) { + [newConfig setH5Bold:newViewProps.htmlStyle.h5.bold]; + + // Update style blocks and conflicts for bold + if (newViewProps.htmlStyle.h5.bold) { + [StyleUtils addStyleBlock:H5 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H5 forHost:self]; + } else { + [StyleUtils removeStyleBlock:H5 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H5 forHost:self]; + } + + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h6.fontSize != + oldViewProps.htmlStyle.h6.fontSize) { + [newConfig setH6FontSize:newViewProps.htmlStyle.h6.fontSize]; + stylePropChanged = YES; + } + + if (newViewProps.htmlStyle.h6.bold != oldViewProps.htmlStyle.h6.bold) { + [newConfig setH6Bold:newViewProps.htmlStyle.h6.bold]; + + // Update style blocks and conflicts for bold + if (newViewProps.htmlStyle.h6.bold) { + [StyleUtils addStyleBlock:H6 to:Bold forHost:self]; + [StyleUtils addStyleConflict:Bold to:H6 forHost:self]; + } else { + [StyleUtils removeStyleBlock:H6 from:Bold forHost:self]; + [StyleUtils removeStyleConflict:Bold from:H6 forHost:self]; + } + + stylePropChanged = YES; + } + + // blockquote + if (newViewProps.htmlStyle.blockquote.borderColor != + oldViewProps.htmlStyle.blockquote.borderColor) { + if (isColorMeaningful(newViewProps.htmlStyle.blockquote.borderColor)) { + [newConfig setBlockquoteBorderColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.blockquote + .borderColor)]; + stylePropChanged = YES; + } + } + if (newViewProps.htmlStyle.blockquote.borderWidth != + oldViewProps.htmlStyle.blockquote.borderWidth) { + [newConfig + setBlockquoteBorderWidth:newViewProps.htmlStyle.blockquote.borderWidth]; + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.blockquote.gapWidth != + oldViewProps.htmlStyle.blockquote.gapWidth) { + [newConfig + setBlockquoteGapWidth:newViewProps.htmlStyle.blockquote.gapWidth]; + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.blockquote.color != + oldViewProps.htmlStyle.blockquote.color || + isFirstMount) { + if (isColorMeaningful(newViewProps.htmlStyle.blockquote.color)) { + [newConfig + setBlockquoteColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.blockquote.color)]; + } else { + [newConfig setBlockquoteColor:[newConfig primaryColor]]; + } + stylePropChanged = YES; + } + + // inline code + if (newViewProps.htmlStyle.code.color != oldViewProps.htmlStyle.code.color) { + if (isColorMeaningful(newViewProps.htmlStyle.code.color)) { + [newConfig setInlineCodeFgColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.code.color)]; + stylePropChanged = YES; + } + } + if (newViewProps.htmlStyle.code.backgroundColor != + oldViewProps.htmlStyle.code.backgroundColor) { + if (isColorMeaningful(newViewProps.htmlStyle.code.backgroundColor)) { + [newConfig setInlineCodeBgColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.code + .backgroundColor)]; + stylePropChanged = YES; + } + } + + // codeblock + if (newViewProps.htmlStyle.codeblock.color != + oldViewProps.htmlStyle.codeblock.color) { + if (isColorMeaningful(newViewProps.htmlStyle.codeblock.color)) { + [newConfig + setCodeBlockFgColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.codeblock.color)]; + stylePropChanged = YES; + } + } + if (newViewProps.htmlStyle.codeblock.backgroundColor != + oldViewProps.htmlStyle.codeblock.backgroundColor) { + if (isColorMeaningful(newViewProps.htmlStyle.codeblock.backgroundColor)) { + [newConfig setCodeBlockBgColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.codeblock + .backgroundColor)]; + stylePropChanged = YES; + } + } + if (newViewProps.htmlStyle.codeblock.borderRadius != + oldViewProps.htmlStyle.codeblock.borderRadius) { + [newConfig + setCodeBlockBorderRadius:newViewProps.htmlStyle.codeblock.borderRadius]; + stylePropChanged = YES; + } + + // ordered list + if (newViewProps.htmlStyle.ol.gapWidth != + oldViewProps.htmlStyle.ol.gapWidth) { + [newConfig setOrderedListGapWidth:newViewProps.htmlStyle.ol.gapWidth]; + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.ol.marginLeft != + oldViewProps.htmlStyle.ol.marginLeft) { + [newConfig setOrderedListMarginLeft:newViewProps.htmlStyle.ol.marginLeft]; + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.ol.markerFontWeight != + oldViewProps.htmlStyle.ol.markerFontWeight || + isFirstMount) { + if (!newViewProps.htmlStyle.ol.markerFontWeight.empty()) { + [newConfig + setOrderedListMarkerFontWeight: + [NSString + fromCppString:newViewProps.htmlStyle.ol.markerFontWeight]]; + } else { + [newConfig setOrderedListMarkerFontWeight:[newConfig primaryFontWeight]]; + } + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.ol.markerColor != + oldViewProps.htmlStyle.ol.markerColor || + isFirstMount) { + if (isColorMeaningful(newViewProps.htmlStyle.ol.markerColor)) { + [newConfig + setOrderedListMarkerColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.ol.markerColor)]; + } else { + [newConfig setOrderedListMarkerColor:[newConfig primaryColor]]; + } + stylePropChanged = YES; + } + + // unordered list + if (newViewProps.htmlStyle.ul.bulletColor != + oldViewProps.htmlStyle.ul.bulletColor) { + if (isColorMeaningful(newViewProps.htmlStyle.ul.bulletColor)) { + [newConfig setUnorderedListBulletColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.ul + .bulletColor)]; + stylePropChanged = YES; + } + } + if (newViewProps.htmlStyle.ul.bulletSize != + oldViewProps.htmlStyle.ul.bulletSize) { + [newConfig setUnorderedListBulletSize:newViewProps.htmlStyle.ul.bulletSize]; + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.ul.gapWidth != + oldViewProps.htmlStyle.ul.gapWidth) { + [newConfig setUnorderedListGapWidth:newViewProps.htmlStyle.ul.gapWidth]; + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.ul.marginLeft != + oldViewProps.htmlStyle.ul.marginLeft) { + [newConfig setUnorderedListMarginLeft:newViewProps.htmlStyle.ul.marginLeft]; + stylePropChanged = YES; + } + + // link + if (newViewProps.htmlStyle.a.color != oldViewProps.htmlStyle.a.color) { + if (isColorMeaningful(newViewProps.htmlStyle.a.color)) { + [newConfig setLinkColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.a.color)]; + stylePropChanged = YES; + } + } + if (newViewProps.htmlStyle.a.pressColor != + oldViewProps.htmlStyle.a.pressColor) { + if (isColorMeaningful(newViewProps.htmlStyle.a.pressColor)) { + [newConfig setLinkPressColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.a.pressColor)]; + stylePropChanged = YES; + } + } + if (newViewProps.htmlStyle.a.textDecorationLine != + oldViewProps.htmlStyle.a.textDecorationLine) { + NSString *objcString = + [NSString fromCppString:newViewProps.htmlStyle.a.textDecorationLine]; + if ([objcString isEqualToString:DecorationUnderline]) { + [newConfig setLinkDecorationLine:DecorationUnderline]; + } else { + [newConfig setLinkDecorationLine:DecorationNone]; + } + stylePropChanged = YES; + } + + // checkbox list + if (newViewProps.htmlStyle.ulCheckbox.boxSize != + oldViewProps.htmlStyle.ulCheckbox.boxSize) { + [newConfig + setCheckboxListBoxSize:newViewProps.htmlStyle.ulCheckbox.boxSize]; + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.ulCheckbox.gapWidth != + oldViewProps.htmlStyle.ulCheckbox.gapWidth) { + [newConfig + setCheckboxListGapWidth:newViewProps.htmlStyle.ulCheckbox.gapWidth]; + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.ulCheckbox.marginLeft != + oldViewProps.htmlStyle.ulCheckbox.marginLeft) { + [newConfig + setCheckboxListMarginLeft:newViewProps.htmlStyle.ulCheckbox.marginLeft]; + stylePropChanged = YES; + } + if (newViewProps.htmlStyle.ulCheckbox.boxColor != + oldViewProps.htmlStyle.ulCheckbox.boxColor) { + if (isColorMeaningful(newViewProps.htmlStyle.ulCheckbox.boxColor)) { + [newConfig setCheckboxListBoxColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.ulCheckbox + .boxColor)]; + stylePropChanged = YES; + } + } + + // mention + folly::dynamic oldMentionStyle = oldViewProps.htmlStyle.mention; + folly::dynamic newMentionStyle = newViewProps.htmlStyle.mention; + if (oldMentionStyle != newMentionStyle) { + bool newSingleProps = NO; + + for (const auto &obj : newMentionStyle.items()) { + if (obj.second.isInt() || obj.second.isString()) { + newSingleProps = YES; + break; + } else if (obj.second.isObject()) { + newSingleProps = NO; + break; + } + } + + if (newSingleProps) { + [newConfig setMentionStyleProps: + [MentionStyleProps + getSinglePropsFromFollyDynamic:newMentionStyle]]; + } else { + [newConfig setMentionStyleProps: + [MentionStyleProps + getComplexPropsFromFollyDynamic:newMentionStyle]]; + } + + stylePropChanged = YES; + } + + // text prop + if (newViewProps.text != oldViewProps.text || isFirstMount) { + textChanged = YES; + } + + // ellipsizeMode + if (newViewProps.ellipsizeMode != oldViewProps.ellipsizeMode || + isFirstMount) { + NSString *mode = [NSString fromCppString:newViewProps.ellipsizeMode]; + if ([mode isEqualToString:@"head"]) { + textView.textContainer.lineBreakMode = NSLineBreakByTruncatingHead; + } else if ([mode isEqualToString:@"middle"]) { + textView.textContainer.lineBreakMode = NSLineBreakByTruncatingMiddle; + } else if ([mode isEqualToString:@"clip"]) { + textView.textContainer.lineBreakMode = NSLineBreakByClipping; + } else { + textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail; + } + } + + // numberOfLines + if (newViewProps.numberOfLines != oldViewProps.numberOfLines || + isFirstMount) { + textView.textContainer.maximumNumberOfLines = newViewProps.numberOfLines; + } + + // selectable + if (newViewProps.selectable != oldViewProps.selectable || isFirstMount) { + textView.selectable = newViewProps.selectable; + } + + // selectionColor + if (newViewProps.selectionColor != oldViewProps.selectionColor) { + if (isColorMeaningful(newViewProps.selectionColor)) { + textView.tintColor = + RCTUIColorFromSharedColor(newViewProps.selectionColor); + } + } + + if (stylePropChanged) { + config = newConfig; + } + + [self syncDefaultTypingAttributesFromConfig]; + + if (textChanged || stylePropChanged) { + [self renderText:[NSString fromCppString:newViewProps.text]]; + } + + [super updateProps:props oldProps:oldProps]; + [self tryUpdatingHeight]; +} + +- (void)syncDefaultTypingAttributesFromConfig { + defaultTypingAttributes[NSForegroundColorAttributeName] = + [config primaryColor]; + defaultTypingAttributes[NSFontAttributeName] = [config primaryFont]; + defaultTypingAttributes[NSUnderlineColorAttributeName] = + [config primaryColor]; + defaultTypingAttributes[NSStrikethroughColorAttributeName] = + [config primaryColor]; + NSMutableParagraphStyle *pStyle = [[NSMutableParagraphStyle alloc] init]; + pStyle.minimumLineHeight = [config scaledPrimaryLineHeight]; + defaultTypingAttributes[NSParagraphStyleAttributeName] = pStyle; + textView.typingAttributes = defaultTypingAttributes; +} + +// MARK: - Rendering + +- (void)renderText:(NSString *)html { + if (html.length == 0) { + [textView.textStorage + setAttributedString:[[NSAttributedString alloc] initWithString:@""]]; + return; + } + + NSString *normalized = [HtmlParser initiallyProcessHtml:html + useHtmlNormalizer:NO]; + if (normalized == nil) { + [textView.textStorage + setAttributedString:[[NSAttributedString alloc] + initWithString:html + attributes:defaultTypingAttributes]]; + return; + } + + NSArray *result = [HtmlParser getTextAndStylesFromHtml:normalized]; + NSString *plainText = result[0]; + NSArray *processedStyles = result[1]; + + NSMutableAttributedString *body = [[NSMutableAttributedString alloc] + initWithString:plainText + attributes:defaultTypingAttributes]; + [textView.textStorage setAttributedString:body]; + [self applyProcessedStyles:processedStyles]; + [self layoutAttachments]; +} + +- (void)applyProcessedStyles:(NSArray *)processedStyles { + // Some paragraph styles (codeblock, blockquote, etc.) insert \u200B + // into empty lines, mutating NSTextStorage length. We need to + // shift subsequent ranges by this offset. + NSInteger zeroWidthSpaceOffset = 0; + + for (NSArray *arr in processedStyles) { + NSNumber *styleType = (NSNumber *)arr[0]; + StylePair *stylePair = (StylePair *)arr[1]; + StyleBase *style = stylesDict[styleType]; + if (style == nullptr) + continue; + + NSRange parsedRange = [stylePair.rangeValue rangeValue]; + NSUInteger textLengthBeforeStyleApplied = + textView.textStorage.string.length; + + // range must be taking zeroWidthSpaceOffset into consideration + // because processed styles ranges are relative to only the new text while + // we need absolute ranges relative to the whole existing text + NSRange styleRange = NSMakeRange( + zeroWidthSpaceOffset + parsedRange.location, parsedRange.length); + + if (![StyleUtils handleStyleBlocksAndConflicts:[[style class] getType] + range:styleRange + forHost:self]) { + continue; + } + + if ([styleType isEqualToNumber:@([LinkStyle getType])]) { + LinkData *linkData = (LinkData *)stylePair.styleValue; + [((LinkStyle *)style) applyLinkMetaWithData:linkData range:styleRange]; + } else if ([styleType isEqualToNumber:@([MentionStyle getType])]) { + MentionParams *params = (MentionParams *)stylePair.styleValue; + [((MentionStyle *)style) applyMentionMeta:params range:styleRange]; + } else if ([styleType isEqualToNumber:@([ImageStyle getType])]) { + ImageData *imgData = (ImageData *)stylePair.styleValue; + [((ImageStyle *)style) addImageAtRange:styleRange + imageData:imgData + withSelection:NO + withDirtyRange:NO]; + } else if ([styleType isEqualToNumber:@([CheckboxListStyle getType])]) { + NSDictionary *checkboxStates = (NSDictionary *)stylePair.styleValue; + CheckboxListStyle *cbStyle = (CheckboxListStyle *)style; + + [cbStyle addWithChecked:NO + range:styleRange + withTyping:YES + withDirtyRange:NO]; + + if (checkboxStates && checkboxStates.count > 0) { + for (NSNumber *key in checkboxStates) { + NSUInteger checkboxPosition = + zeroWidthSpaceOffset + [key unsignedIntegerValue]; + BOOL isChecked = [checkboxStates[key] boolValue]; + + if (isChecked) { + [cbStyle toggleCheckedAt:checkboxPosition withDirtyRange:NO]; + } + } + } + } else { + [style add:styleRange withTyping:YES withDirtyRange:NO]; + } + + [ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self]; + + NSInteger delta = + textView.textStorage.string.length - textLengthBeforeStyleApplied; + + // Use an adjusted range so that applyStyling covers any ZWS characters that + // were just inserted by handleZeroWidthSpacesInInput. Without this, a style + // applied to an empty range {0,0} would call applyStyling on {0,0} even + // after a ZWS was inserted, leaving headIndent/firstLineHeadIndent unset. + NSRange adjustedStyleRange = NSMakeRange( + styleRange.location, styleRange.length + (NSUInteger)MAX(0LL, delta)); + [style applyStyling:adjustedStyleRange]; + + // Image shifts are already handled by _precedingImageCount during tag + // finalization. + if (delta != 0 && ![styleType isEqualToNumber:@([ImageStyle getType])]) { + zeroWidthSpaceOffset += delta; + } + } +} + +// MARK: - Measuring and state + +- (CGSize)measureSize:(CGFloat)maxWidth { + if (textView.textStorage.length == 0) { + return CGSizeMake(maxWidth, 0); + } + + NSMutableAttributedString *currentStr = [[NSMutableAttributedString alloc] + initWithAttributedString:textView.textStorage]; + + // edge case: input with only a zero width space should still be of a height + // of a single line, so we add a mock "I" character + if ([currentStr length] == 1 && + [[currentStr.string substringWithRange:NSMakeRange(0, 1)] + isEqualToString:@"\u200B"]) { + [currentStr + appendAttributedString:[[NSAttributedString alloc] + initWithString:@"I" + attributes:defaultTypingAttributes]]; + } + + // edge case: trailing newlines aren't counted towards height calculations, so + // we add a mock "I" character + if (currentStr.length > 0) { + unichar lastChar = + [currentStr.string characterAtIndex:currentStr.length - 1]; + if ([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) { + [currentStr + appendAttributedString:[[NSAttributedString alloc] + initWithString:@"I" + attributes:defaultTypingAttributes]]; + } + } + + CGRect boundingBox = + [currentStr boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX) + options:NSStringDrawingUsesLineFragmentOrigin | + NSStringDrawingUsesFontLeading + context:nullptr]; + + return CGSizeMake(maxWidth, ceil(boundingBox.size.height)); +} + +- (void)updateState:(State::Shared const &)state + oldState:(State::Shared const &)oldState { + _state = + std::static_pointer_cast( + state); + + if (oldState == nullptr) { + [self tryUpdatingHeight]; + } +} + +- (void)tryUpdatingHeight { + if (_state == nullptr) { + return; + } + auto selfRef = wrapManagedObjectWeakly(self); + _state->updateState(EnrichedTextViewState(selfRef)); +} + +- (std::shared_ptr)getEventEmitter { + if (_eventEmitter != nullptr) { + auto emitter = + static_cast(*_eventEmitter); + return std::make_shared(emitter); + } + return nullptr; +} + +- (void)emitOnLinkPressEvent:(NSString *)url { + auto emitter = [self getEventEmitter]; + if (emitter != nullptr) { + emitter->onLinkPress({.url = [url toCppString]}); + } +} + +- (void)emitOnMentionPressEvent:(MentionParams *)mention { + auto emitter = [self getEventEmitter]; + if (emitter != nullptr) { + folly::dynamic attrsObj = folly::dynamic::object; + if (mention.attributes != nullptr) { + NSData *data = + [mention.attributes dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&error]; + if (dict != nil) { + for (NSString *key in dict) { + attrsObj[[key toCppString]] = [dict[key] toCppString]; + } + } + } + + emitter->onMentionPress({ + .text = mention.text ? [mention.text toCppString] : std::string(""), + .indicator = mention.indicator ? [mention.indicator toCppString] + : std::string(""), + .attributes = attrsObj, + }); + } +} + +// MARK: - Media attachments delegate + +- (void)mediaAttachmentDidUpdate:(MediaAttachment *)attachment { + [AttachmentLayoutUtils handleAttachmentUpdate:attachment + textView:textView + onLayoutBlock:^{ + [self layoutAttachments]; + [self tryUpdatingHeight]; + }]; +} + +- (void)layoutAttachments { + _attachmentViews = + [AttachmentLayoutUtils layoutAttachmentsInTextView:textView + config:config + existingViews:_attachmentViews]; +} + +@end diff --git a/ios/extensions/LayoutManagerExtension.mm b/ios/extensions/LayoutManagerExtension.mm index 5ff476216..5a9b1797c 100644 --- a/ios/extensions/LayoutManagerExtension.mm +++ b/ios/extensions/LayoutManagerExtension.mm @@ -245,15 +245,18 @@ - (void)drawLists:(id)host host.stylesDict[@([UnorderedListStyle getType])]; OrderedListStyle *olStyle = host.stylesDict[@([OrderedListStyle getType])]; CheckboxListStyle *cbStyle = host.stylesDict[@([CheckboxListStyle getType])]; - if (ulStyle == nullptr || olStyle == nullptr || cbStyle == nullptr) { - return; - } NSMutableArray *allLists = [[NSMutableArray alloc] init]; - [allLists addObjectsFromArray:[ulStyle all:visibleCharRange]]; - [allLists addObjectsFromArray:[olStyle all:visibleCharRange]]; - [allLists addObjectsFromArray:[cbStyle all:visibleCharRange]]; + if (ulStyle != nullptr) { + [allLists addObjectsFromArray:[ulStyle all:visibleCharRange]]; + } + if (olStyle != nullptr) { + [allLists addObjectsFromArray:[olStyle all:visibleCharRange]]; + } + if (cbStyle != nullptr) { + [allLists addObjectsFromArray:[cbStyle all:visibleCharRange]]; + } for (StylePair *pair in allLists) { NSParagraphStyle *pStyle = (NSParagraphStyle *)pair.styleValue; diff --git a/ios/utils/OccurenceUtils.mm b/ios/utils/OccurenceUtils.mm index c92f739fd..08469b2d5 100644 --- a/ios/utils/OccurenceUtils.mm +++ b/ios/utils/OccurenceUtils.mm @@ -40,7 +40,13 @@ + (BOOL)detect:(NSAttributedStringKey _Nonnull)key NSRange paragraphRange = [host.textView.textStorage.string paragraphRangeForRange:detectionRange]; if (paragraphRange.location == detectionRange.location) { - return NO; + // At the start of an empty paragraph at the end of text. + // Fall back to typing attributes so non-editable views (where + // selectedRange != detectionRange) can still detect paragraph styles + // that were applied via add: (which only updates typingAttributes when + // the textStorage range is empty). + attrValue = host.textView.typingAttributes[key]; + // fall through to condition(attrValue, detectionRange) below } else { return [self detect:key withHost:host From 894ee0c1b085cb1ef026f9b5a32d9e45617621c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 15 Apr 2026 08:20:55 +0200 Subject: [PATCH 10/19] fix: empty paragraph elements --- ios/utils/OccurenceUtils.mm | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/ios/utils/OccurenceUtils.mm b/ios/utils/OccurenceUtils.mm index 08469b2d5..c92f739fd 100644 --- a/ios/utils/OccurenceUtils.mm +++ b/ios/utils/OccurenceUtils.mm @@ -40,13 +40,7 @@ + (BOOL)detect:(NSAttributedStringKey _Nonnull)key NSRange paragraphRange = [host.textView.textStorage.string paragraphRangeForRange:detectionRange]; if (paragraphRange.location == detectionRange.location) { - // At the start of an empty paragraph at the end of text. - // Fall back to typing attributes so non-editable views (where - // selectedRange != detectionRange) can still detect paragraph styles - // that were applied via add: (which only updates typingAttributes when - // the textStorage range is empty). - attrValue = host.textView.typingAttributes[key]; - // fall through to condition(attrValue, detectionRange) below + return NO; } else { return [self detect:key withHost:host From ddc7f5a10796660edf921214fbde7bbe0b96e08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 15 Apr 2026 09:42:53 +0200 Subject: [PATCH 11/19] fix: checkbox display --- apps/example/src/components/TextRenderer.tsx | 2 +- ios/EnrichedTextInputView.mm | 8 +++-- ios/EnrichedTextView.mm | 31 ++++++++++++-------- ios/styles/MentionStyle.mm | 4 +-- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/apps/example/src/components/TextRenderer.tsx b/apps/example/src/components/TextRenderer.tsx index 2cef09cc0..74e2e0ab0 100644 --- a/apps/example/src/components/TextRenderer.tsx +++ b/apps/example/src/components/TextRenderer.tsx @@ -52,7 +52,7 @@ const styles = StyleSheet.create({ borderRadius: 8, }, text: { - fontSize: 16, + fontSize: 18, color: 'black', marginTop: 4, fontFamily: 'Nunito-Regular', diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index b4e3a551d..20a005c08 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -23,9 +23,11 @@ #import #define GET_STYLE_STATE(TYPE_ENUM) \ - {.isActive = [self isStyleActive:TYPE_ENUM], \ - .isBlocking = [self isStyle:TYPE_ENUM activeInMap:blockingStyles], \ - .isConflicting = [self isStyle:TYPE_ENUM activeInMap:conflictingStyles]} +{ \ + .isActive = [self isStyleActive:TYPE_ENUM], \ + .isBlocking = [self isStyle:TYPE_ENUM activeInMap:blockingStyles], \ + .isConflicting = [self isStyle:TYPE_ENUM activeInMap:conflictingStyles] \ +} using namespace facebook::react; diff --git a/ios/EnrichedTextView.mm b/ios/EnrichedTextView.mm index f689946f8..15f7b71ab 100644 --- a/ios/EnrichedTextView.mm +++ b/ios/EnrichedTextView.mm @@ -182,9 +182,7 @@ - (void)updateProps:(Props::Shared const &)props // fontStyle if (newViewProps.fontStyle != oldViewProps.fontStyle) { // TODO: Implement fontStyle setter on EnrichedConfig - // fontStyle doesn't have a dedicated setter on EnrichedConfig yet, - // but we track it for future use - stylePropChanged = YES; + // stylePropChanged = YES; } // htmlStyle headings @@ -713,7 +711,7 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles { // Use an adjusted range so that applyStyling covers any ZWS characters that // were just inserted by handleZeroWidthSpacesInInput. Without this, a style // applied to an empty range {0,0} would call applyStyling on {0,0} even - // after a ZWS was inserted, leaving headIndent/firstLineHeadIndent unset. + // after a ZWS was inserted. NSRange adjustedStyleRange = NSMakeRange( styleRange.location, styleRange.length + (NSUInteger)MAX(0LL, delta)); [style applyStyling:adjustedStyleRange]; @@ -798,6 +796,8 @@ - (void)tryUpdatingHeight { } - (void)emitOnLinkPressEvent:(NSString *)url { + if (!url) + return; auto emitter = [self getEventEmitter]; if (emitter != nullptr) { emitter->onLinkPress({.url = [url toCppString]}); @@ -808,24 +808,31 @@ - (void)emitOnMentionPressEvent:(MentionParams *)mention { auto emitter = [self getEventEmitter]; if (emitter != nullptr) { folly::dynamic attrsObj = folly::dynamic::object; - if (mention.attributes != nullptr) { + if (mention.attributes != nil) { NSData *data = [mention.attributes dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error; + NSError *error = nil; NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - if (dict != nil) { - for (NSString *key in dict) { - attrsObj[[key toCppString]] = [dict[key] toCppString]; + if (error != nil) { + NSLog(@"[EnrichedTextView] Failed to parse mention attributes JSON: %@", + error); + return; + } + + for (NSString *key in dict) { + id val = dict[key]; + if ([val isKindOfClass:[NSString class]]) { + attrsObj[[key toCppString]] = [val toCppString]; } } } emitter->onMentionPress({ - .text = mention.text ? [mention.text toCppString] : std::string(""), - .indicator = mention.indicator ? [mention.indicator toCppString] - : std::string(""), + .text = mention.text ? [mention.text toCppString] : std::string{}, + .indicator = + mention.indicator ? [mention.indicator toCppString] : std::string{}, .attributes = attrsObj, }); } diff --git a/ios/styles/MentionStyle.mm b/ios/styles/MentionStyle.mm index e844708b0..1201b4728 100644 --- a/ios/styles/MentionStyle.mm +++ b/ios/styles/MentionStyle.mm @@ -520,7 +520,7 @@ - (void)setActiveMentionRange:(NSRange)range text:(NSString *)text { [text substringWithRange:NSMakeRange(1, text.length - 1)]; _activeMentionIndicator = indicatorString; _activeMentionRange = [NSValue valueWithRange:range]; - [(id)self.host emitOnMentionEvent:indicatorString text:textString]; + [self.host emitOnMentionEvent:indicatorString text:textString]; } // removes stored mention range + indicator, which means that we no longer edit @@ -530,7 +530,7 @@ - (void)removeActiveMentionRange { NSString *indicatorCopy = [_activeMentionIndicator copy]; _activeMentionIndicator = nullptr; _activeMentionRange = nullptr; - [(id)self.host emitOnMentionEvent:indicatorCopy text:nullptr]; + [self.host emitOnMentionEvent:indicatorCopy text:nullptr]; } } From 1156407026f9f9e4d23404fb43ddead4bec90461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 15 Apr 2026 09:44:12 +0200 Subject: [PATCH 12/19] fix: styling --- ios/EnrichedTextInputView.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 20a005c08..a82227c55 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -23,11 +23,11 @@ #import #define GET_STYLE_STATE(TYPE_ENUM) \ -{ \ - .isActive = [self isStyleActive:TYPE_ENUM], \ - .isBlocking = [self isStyle:TYPE_ENUM activeInMap:blockingStyles], \ - .isConflicting = [self isStyle:TYPE_ENUM activeInMap:conflictingStyles] \ -} + { \ + .isActive = [self isStyleActive:TYPE_ENUM], \ + .isBlocking = [self isStyle:TYPE_ENUM activeInMap:blockingStyles], \ + .isConflicting = [self isStyle:TYPE_ENUM activeInMap:conflictingStyles] \ + } using namespace facebook::react; From dd06a038377c7a0eac9e5b70408ed7d62244bfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 15 Apr 2026 10:50:56 +0200 Subject: [PATCH 13/19] fix: setting test as input value --- ios/EnrichedTextInputView.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index a82227c55..fa8d1fe8c 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1225,8 +1225,10 @@ - (void)focus { - (void)setValue:(NSString *)value { NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:value]; if (initiallyProcessedHtml == nullptr) { - // just plain text + // reset the text first and reset typing attributes + textView.text = @""; textView.typingAttributes = defaultTypingAttributes; + // set new text textView.text = value; } else { // we've got some seemingly proper html From 697a2d22e418d907c6575470be6f6a95fa106d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 15 Apr 2026 11:27:01 +0200 Subject: [PATCH 14/19] fix: applying style order --- ios/EnrichedTextView.mm | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/ios/EnrichedTextView.mm b/ios/EnrichedTextView.mm index 15f7b71ab..18b5e3171 100644 --- a/ios/EnrichedTextView.mm +++ b/ios/EnrichedTextView.mm @@ -644,6 +644,14 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles { // shift subsequent ranges by this offset. NSInteger zeroWidthSpaceOffset = 0; + // Inline styles collected during the first pass so their applyStyling: can + // be re-run after all paragraph styles have applied their visual attributes. + // Each entry is @[style, adjustedRange]. + NSMutableArray *pendingInlineApply = [NSMutableArray array]; + + // Paragraph styles call applyStyling: immediately; inline styles + // defer it so that paragraph visual attributes are already in + // place when inline styles override them. for (NSArray *arr in processedStyles) { NSNumber *styleType = (NSNumber *)arr[0]; StylePair *stylePair = (StylePair *)arr[1]; @@ -655,7 +663,7 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles { NSUInteger textLengthBeforeStyleApplied = textView.textStorage.string.length; - // range must be taking zeroWidthSpaceOffset into consideration + // Range must be taking zeroWidthSpaceOffset into consideration // because processed styles ranges are relative to only the new text while // we need absolute ranges relative to the whole existing text NSRange styleRange = NSMakeRange( @@ -714,7 +722,13 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles { // after a ZWS was inserted. NSRange adjustedStyleRange = NSMakeRange( styleRange.location, styleRange.length + (NSUInteger)MAX(0LL, delta)); - [style applyStyling:adjustedStyleRange]; + + if ([style isParagraph]) { + [style applyStyling:adjustedStyleRange]; + } else { + [pendingInlineApply + addObject:@[ style, [NSValue valueWithRange:adjustedStyleRange] ]]; + } // Image shifts are already handled by _precedingImageCount during tag // finalization. @@ -722,6 +736,13 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles { zeroWidthSpaceOffset += delta; } } + + // Apply visual styling for inline styles + for (NSArray *entry in pendingInlineApply) { + StyleBase *style = entry[0]; + NSRange adjustedStyleRange = [((NSValue *)entry[1]) rangeValue]; + [style applyStyling:adjustedStyleRange]; + } } // MARK: - Measuring and state From 6d2ba09c90d26648329e27cd8373d32b5bfc8715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 16 Apr 2026 15:09:12 +0200 Subject: [PATCH 15/19] test: add e2e tests for enriched text --- .../screenshots/ios/custom_styles_display.png | Bin 0 -> 130773 bytes .../ios/empty_list_elements_display.png | Bin 0 -> 25543 bytes .../screenshots/ios/inline_styles_display.png | Bin 0 -> 26274 bytes .../screenshots/ios/link_press.png | Bin 0 -> 16564 bytes .../screenshots/ios/mention_press.png | Bin 0 -> 30107 bytes .../ios/paragraph_styles_display.png | Bin 0 -> 123538 bytes ios/inputParser/InputParser.mm | 3 ++- package.json | 1 + 8 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .maestro/enrichedText/screenshots/ios/custom_styles_display.png create mode 100644 .maestro/enrichedText/screenshots/ios/empty_list_elements_display.png create mode 100644 .maestro/enrichedText/screenshots/ios/inline_styles_display.png create mode 100644 .maestro/enrichedText/screenshots/ios/link_press.png create mode 100644 .maestro/enrichedText/screenshots/ios/mention_press.png create mode 100644 .maestro/enrichedText/screenshots/ios/paragraph_styles_display.png diff --git a/.maestro/enrichedText/screenshots/ios/custom_styles_display.png b/.maestro/enrichedText/screenshots/ios/custom_styles_display.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac807c3e456a5e7fb23109214040b5bc25b74c6 GIT binary patch literal 130773 zcmeFYWmJ^^_cp2siXf!}s7Q;_s7QB=AV_xzD%~-3ht!ag(%lT*FrXscFhh3?okPd) z4EX*2o}d49&YSbRIP0u+c)^1C-1jH;-q*FSz3+R1!k;z272L%~#}o?)N$)jjmuw{8tsS+`-mKiO>|*lE~lY7Hjkdi!>1 zc^M1iwni+^%@tmjV(Gpa_pk`T)TFv@uYnJ7UbGcOe$5ONPIcSw9D!)v!+W}fJk`|F z;_{(1T*(Y>#tI6z{(jbb`t zZ^4r}Upnd7lS>p22*G~{Q@TPOH;1y^+<8lu8RrE+jy_9XIsJ!dS?gE^yF{R{uIJ@p zj!dG@&$*#&>8MSjn@i`!Gx9{e{l4J(k%C{f!2_fL-ZRcC$pr-kIXOAUGp_x$_&3+3 zaix!m&im0kzXvVw#wAa zuTYn5#DDe#CBu3GW(SytFfH%~3BxK23UC}zH$%ddKpTHinOugB$KDj~2MRbJI)wgA znX8bOZmictvt5dP(8ss)Rg_x$>h8_Z%iCa1fc1k4+GpVFgW0R75F`HFlv8m zEWVrom?o3R$-%*4B6J-yw{9h2(j(-@20hHfg(=;3MP5Drjr6=3n0qevI^b6r=d6e; zre?W*C)z*+Re$c`){q}d?`UaKqaApe>b7uv{u=k$pUI->H|3Yo=a8Pv7#7p@_I*h_ zZRh~K{&|TD<_EZ;!hvZbzIO%CzG-{$-vg^;+bsk_2d8c~E|d8)BufHtrazItnBPW+ z)X0eHqRY+aUQxJMlwhH$h1Otnq?l{pzxmYKHhoTPFTl*$_~a*n$EUwP?e6Y&T}gHf zVfy12Syq4>J<8f!cy2LVh_e{;W&-Xy`Q7xBmd^4ExSH-0wSUZ`eY1f;8@!uqesoIq z3Iz(^{5>E?g>f^Tu#gY-pD8Y}X$Wt|4ru?qDE$Aw=>OaV?k_Md_cL`-+nydq+7s>M zC*9uXo;Fu2y6%?$AZv7O6;c?hOs_Xt)+eY-CXi9Rry}XS<-$u=kNxSF zp&Qq0-=gBV-i)-B&FqDjT>T^&!e<_<_|eo?Jj(>htEr&i z+ZguraB-4L<}ScK2}r%Ha#C1F1+KhZ`pSEL{o1=MO8|??cL2Z5tw1G zS+`~C88GOggbysX-H|hVI?ZC*wCj%k9w9np%WB9ozalA1#?$p#-6yWF_lEa6*aRxGuuKdu&FO5JhkLos|Rz^t$ zn+78MMo{{YGKpk;hq)>tD9PC4f#bTp?9hKV^!8>$F7GkF&_QB~FZN<|ug=Z%hHG=L z&P`0yIKn=Xougf)XAYYWX`}KcH2uV*nqKGRwZ_d79Srhd7^+{kzxlqO#|wLq9`BEDE#bSUHNf z2jKWqSHb6pYQqd_4B^elT1&ngdviPN-TI0hEsoh$N8Mgqk9)ROy(0K#w6m(RL= z!x7sI)1nRpMb^#oJDxb)++w}Ca2uP5J_sAu*f5U1C2A+R$O3LB7iD9A4~ai@(iM8f zv)nl9-hfUW4jA?FDJ9A zO0G`r7ddYh+3n3c4G_bWqQS6-w{1SAk)Lry9_+k6tcTU0&SGtDX1#UN2D@mX#;_-{ ziZs!4ctF%4e}mCK$)NqtjrlIAF|y622Ad@|R7TK94g9~k)p%GsF;=Kb`-*dqS^0Vg zUfXF1N_?WcV5g`LWEFaW!bPB#5bO(6kM92J$N!#6xL4>IjXdezi+E1M_GjK5eV2TP z{a6{A=TTJwR)3UNyX=2mGy5FlTY%?b=^B`^mkC&8h zE{uPOeZBw1hu$4%Rz>s{Siu$IX4Hbz})xjta{C-LloF}wRV1| z8h@bqlBg#9WK%cw$>g6@YKxxpFdrKLPBj{EsEx%OeR!8)yfjb_DYYZnbOY&Uh9yP3k|Cd}GlQW`#+U z>Unrt`*HDLC)?1q7fpVYdjEvz!<(q4zcn^Get3Is%ybAEbug{SJ*X;xoMAsxNf6#(6(0S%R%=qm7bs!R4U3gx( ztt>i*NE1lBUyMIa;h7lEYrZ(^emUd5fTmYdZJS!X)eAY#tKpQIW4HZqxe%rsPg31S zXOlnn_w@aLC}p5;G2$t$!0VN<{B^%i8vcN?*e1lrMvguFP$8M*k23n9fTqnaH>-L@ z5&fU4r6FnBfuEk_;_338(R*{(C-C}8=q;bu^s&wNCL|t;Vv44pIXoetxL#5cn+V%) zebRv6>H0q^YH=z+fmPi92XB<^!>-&xx`$uPf76<6vLD_^RBfuXof$@wpp54y+|6p5 zt1iog1gnpSp$M6#_t4!MQqIlU-K7Az1hwSvE+>}(CGLw8YA6)g8U9|C&GXFGXX1Q( z=FS!WrCZ52d~c21eHitv=!a0(3Ab!*kQA&HP;J3`Nk zh}CPN#d`4S8(2}DtrLm7K}rL}LJc%#(y^zRZvo8-jvp7L8mJ-quANH(FG`#ZdIIhq@7Hau0=@iw((*!A`zvu*2m7#{tb}{NC5i^B(koK9gUh4pCZBIi z<@Ui{$SPbz>O>sX=6n4n@Xaqy-+_Fi(vIAl`m1LQQ74f_bBH+~Ba%k+fqMi}G5_P~ zYkZp^Y;%LoXo2s-|n(ZDsZA)7gq!Y9Y;KSGu#x*pBeki7WVmjqN|?eWUz(Y#d1)p=P3yJqG;x%os7>D3fdQo+ESZ5oJ7n< zdp^kZCG?gRo<0dCJ4{t>?E_Og;=fkwwct3Q_AkLzni#AZC51pYns?qbi_fzB>#cvV zhI~-9JV)a0l}>H7(E@R7cC4v0@R>M1ZNfX}0?aBF9n$L3=~f@V0d-^gVcTJJp_67E z)TsZS>quj{zfbWg4zCTQc0W4?A#-N@FKz<$LV{froRf0liB~S5`EZi`LT3wczWwcQ zY^eTVMC|(!vMVP&)G+c;t({yYLKu_xw*3Pl$-+26_aM}#Ml$FbMIPGfW|mW=-A=pL zP8SxvrpV!*MSciEKtPVHRnb!8V2TC@h%l7UYM7bMo>mf{o;f>$AUM&~F>pW6>Y=x~ z!1<_Aw`24M-?!1u2G5Ggsq+4qf6=>eJ8wrkZDXcZmh9KifagkyW5VP!5cFcLJ6`Ke zRbT4q>Jtzdh#Di=>kL7(`pFIPqwSpR@bYUh9{F*Ac=E{`3jXVTMac8tI{b!xuzOBm zc5GkERok$rkQIKp_j-#YGjfZrvxg`EZM5}TajJq#-R;qSYT2sQI`pR93;*Nwhw_wx z!X+MirW1H36CH>CTvo4YcvugU@5~PBEQ3=&ezyEXFP`>DzMTm}K>)SVeu!*!=5jek z$M94U!GE8JO+1a5t!Tg>Qh_pyt@~+HG<=lQrjxge5JhT=U7v(Ej3^L{@;HDV9*)od zyFP$G7%GUdb?&D73FL*Rjz+*F`dg%)tuk2n)Y8tp_Us`tz9K!ha^8q_mg`SE>wqa7 zhh!utRT%Y^uf$pK)Y8S{?p8_>dh4JLmJdrHy1P9-odhxz9;3VO31oRUf+~t~298s*yx+mDO&yLGq(7swwX(^yt--MAb@@5ooj`llnR&UpQ?576Qx`DnEgGkoX3 z@<1_3a^EmlmXT8PDTLv_>1bpYPDu3j?O*?if6MXchQ0(BV#P{ z@I^^f)O)t}W2IqIcf#43^Q|c|P_KKrtyOpe;j(P&~w)$oZj-oDb^L;q|2PK z^GXc**@my~c;*6I98EQ93d1hHdnz^6QCjrKh|T=YJVh5*(TYoj`beGlV1LAh&PSu| zpYjN7alX_~S;Z=SBT2M|K_NSa##{N5OMLxPk*AkJ?z2<#WpJBt;j?^r{JX=+kZ#X_v6m?8?FJyQ*3c}Sx@tN z4+4$BF z<3haH=0D}#CR#=4&J^vu`q^kbeUM+4p_@-FH^df&l4rV`>OUTw%$%v>HS>xW25y_37{CA2Pj`Qx4#8REanTu$ceUMgdf1CFRE7}Ll-~^!6OANV zeVKak+c{Lp?ERBX?^&TwmTjgRY}NH#wN-5gBiom~P=sO80fS#mtt$W3II`E*M3rx5 zB0U)|6GxYnvxY|}IiMKNL}5M=h7O_1t26vW3gb65JY&hJPY;W(rWHHhTbtdGJvv`i zwMBOwn1%j#7MCHLS-Yz8Kns$d%og80=@_sT!$<C^nsM%S#7A~y+VwQq_{>o2cz+(uG-M$j(I zg8WPEgx4zht%Xw!IzL)ujlrAqtl$Nz;o!=8D#XyjxYAUo zP~{Dg@v)V|MhT&`wM0}?pXZUA#Un55?u(*Q~&NivdQ)-T%{aXh>CYZn|zS zuJkPa@(s9Bx+Kay;(eSttYJ30W@lYgjmo%G4J{`(KvZecJ@S4x%`RmYk1tiW!Ep$TWL}czc=IRYx>TZs*3V^jKUvs@0MKd{&%*(<4 z5jNcal1x4EcdlUAV{hY{LsE%eG-cGusAUUfe`=nXS!4fv$l&*G9dS3AWUHWBCAY;+ zb$Y^iA5Kmr=_&pFbX@-0H2r6ftt>yWK8-X@G#99a zPkF^SV3?w1<(m@tdLWT88WfdxwMXNS_L>A2sN0v-oAX)zL%GrHo=!P+BDvX@U+Riq z-r*kWO@67{E`YJ7_llU#Ldh?dJrSq)9^#R!~ zd~4n9ITRg=6KT8EHCF6gui`wwewZLP85iq^W)*YsSm@Xxe~qyg08#$f+caP@n5Wjf zg_i5TrqGs&Om(H|j2?pg*}xN7*yEgdK{WIy&PGQzMkzoL)VS$L`oD_nCZU-u0=EzW z#RwMe>yY;SwSz9~5qA+oDPK&QCsO9}Ine4$qXN=Ah6WRyh;{(YjIgKNB{m6U&si_Y z8A$nEz3W73a-uD_Zirvfb%vD*N$igf3l`MgmrH3%kO}BF+24Vw14H!fTOK6bOE|l$ zOwfE)TIXH&Wwc%W98(DjJy?@23N;8d*fDbL#jE7~f|;?D#z8m=PL^GdNTbg+Nz^@= z@!Fl7b~lwy)h+O3i{1*aMl+Y-Wk9Z31@qqthEoR? zF3tyiY|_hQR(V$o-x7@o*w)TeaaHQYfId2$CiJJ01z*3@l?V}@=&>zILG7A=5{tX6 zM^QD$9+Oi!T1ryaXe6J-7FW=iom`#H?6s)hQ5rtptZy@zxiJ1)Ay>{%*Q<@1EVkwf zx^(fQ8WyMdD(_mbRmYR9ZlEQb23kmrc%Uo49t(Bi8SRdV9#5AhXGjwo0hX@koRHgb zQ&GW{E{IH{gGrt9wkibYzb8L=N42#sr5gRd=VtY*R>f=EAt=HM{#BAUb=fte=l0jH zKwWzlg)U(_(OM6uCP4#s;jU_XhLa`LWp}%Hm#=fql(4SY_OlFobyrtNoj2-zkuqQ% zJvHM~fsL9iyVY3;L$(&5TvM1H=5_sHw>h!f?OX%Gq&Iy3hu9iQ4fl}jP8#4o?w`Qc9TS+X*u1+DFS z@$!t5l23pQj25cAj1x@PX(H*5)1826H+&h3t2)tWidb)wV_9r?qUkHUv%_2Pcec31 zE;%~Vws4<2{ui4fbm9(<~OC1Fy2u+YpR?-DIgd#2j7U4JnL5cs_ zU9ET;x-#$DZSdbX?`&ph9wzHix1Y_;Nwo|^VSA#}ey>F&+u!UMzFR2~y{nlf;)Kd&yZ4Of=Zcncobl*w5Ljb)Z#7on7meDm8S%;Pm*Bu0MtF1Ke ze9pMCa@o3@nld7t9=WPpGb|COsBm>=xVTMjWT{uIam67$`SoaJyjjr95>%S_CyL#X z*p+Ci9M>v8OqQ2-USN~^u4M{GJ2i?@@V#e`W`#+(EPKuSroUrK=-1?B;`GF)y@gb5 za|#Saf#W?8TKTWuD?*@UNRG`bGoi(E_8z>-46o>(CEH59vj!>c(b|VeijS|LduMUn z3sAbsJ7-Ag=sUd@>h@+*Ya{3y0$n1~TlLJaCk_QR-8-AM^fYUzD@(tImRzTBVv(l> zv@Wy`A6*Dk>sn+wl?c)O-5xmmJd3Vw*^k?_In{f17H;R8+tr@52e(#bc=U8hHA$tM zpJ-olSVgP!IqyGffru0tcc3~bDPA9!l%PvN-MwEpEoOAzb;H)tjwc*@Hc*p#wEA{y zko7Es%NEVr>&Glx{*{-yc0-XW{}SE=_Q5)7$qaHv0Pr2Hu|@e7b7PQCjouI)e{%FN zJr|rc;%4G-IX0HsN5mGb0!vQDi6>;l^Jm|~RsUA+pD$|mR>-QpEwETkf1#iRtC2V3f=bqQgLkoS1E94>~ZndC(O^NSuYGPk$;WnNn zSUtM9=F)U>m{RbvN1}gfZ<~BWS4+-jI^s9Ys6?g5Us2QhN}V5FX-tUg{OtWn+oxzf z)xDecLCnCaQ`T159m!@jV#y_ylU0}{o2r>vVkyILtu(Sz$u7SKqikaZn-Ab@IvmHT zwe^cidO(H_YgYRBjO6jdZC1#mztF@^Kr-4~twV+pHZIUMm8RAGYRg*{-JE^8eRWWg zn!vSIaO75cEigf92*$>`SMdx_x*VcS619)p?9oDJUn&io&aRwmQd({uBzyp2ao!`ja@w1bJ6?Bv^_S2ABru5N|e=tXND9&}Tyw~M%6anb|u-6R81Uyv)M_!5I<<+C&X2VkNMRz5c#ZJuYAATK~KgC$&(a~EaFS3 z0=VH|S(;D4?{82cY-WC*h&iFtbEU{8r8IBsFjb7R6KONaaIJWp9 znle~jEWf$>YEkOdp8WoaJ_5(gtkcLNBQz$L9r4|a+O_yNo10F{p>Zr1gQA)g^_==+ zUkeWBM;7fR_;BoL{`Wq(G0G;+*JiN#nYj@GZP;d5IKbKSt>YenOnLW=zu|{&&}ySw z#rtS6$R#kaG`YmS@(iS~&oHUOd9`+mj)ssrE)Awk?ltv4kc~D9+G^ddJQF6kC9$98#l6+UXI^Nm7SinD7tSe)Gv>zBNI#2_QEuiX zu6m~0M-7q<0tF^1&1uuDJanot-jmFbCL|7S?a~sjQWGhatQ3wmmu*_@NoF>o#V7&(zC}fleXC$v&X3$LuzN`CEpAw3i1kG7sCw zoQTf*D43Kx1=VjjpF}caFwf7wGSAILEGNxPw#Bmb@Y3@688Lklysy(i)^?YFtNJYH z*_kXn*;5KW-w210^e0B3oIc?=&hRRzP=ugW`>urOC} zC3LQO9-L75yo>wbs?j)1m_+)#xD;DISR&1fe#zyT?~qc06iBX+iO0%r8Ms)$MxhI} z6FrGi4e}0%XyG<_Z67FF=k~8qXZ?(oD{dxsLhY1 zfVr?p6nGTwAep7>9TR1dBu>;Zqk;Ja_s^RhvJatlfevFQgIR@n!|;6_*EapLfU=c?cDWiacfXISw&46ZCdWoI_#1RQ4cmIdDI#4Zp?W??^fG6-s#zyf3X z{Km}y5@KmY7DbxpVvElQ&_r~zqTla|b&3ok4HnD67C-I$5(kfxRp#8=#fIrLGGjAO zO8Ptt7Z&B`++Dwd(f?MA00Lm_z;zP_5$jzyunGm?0e-iP8HAPBUBA`G1|Iwp#+tASPPP%qV^!^5Z(ExK$}$G@`XQX zjK!4d;C-xD$30BePxEejAQ4tgVk@cv-4MwgUBq*ymx?&$3fk=N(vEgp7PQ+};NN_f z!T?`yo-hum{qcKy6e*d7<5Ta=a=-oX?ibFR<)fbu$LLDL_W736x}VxIF?jGSMJYBa zcCg${7X86`0+=z&rpije*w-*PGbIHkQguapGG{#3G|(v1w2)1F>YYcA!2@X8!2Zpc zLhp141Jmp}c!MOLVNq+f`&nyZDvEn|P1v}g4e3BbPvNz|L$9+QL>24;eHIh5lFUz8 z#gP4NU~FM(O^mVciB^TR=+;6|mGsL3BahfowHl6*BJ&J@az)YSxmG+l?n~tqfea^A z3t2rIC#Tq&H|G6&fou4$^&bhm$re2P9T!7t&c6b1N@dO(%Tf+V+KwJFk5y)(jHw0S zu7v9n87;zUsId1QiO>M4;+r&KkHq$p4POTWsW^U=b+nX6)DK1Y{0XTuunw;{pZUeu zlP31IcQ4xT>_TMZu-#FgxiAzk!7G$MEs2vj2O%#gnPU1G&KWM8%!yKINvyN|l1+$= zG{LkS#^GlcMB$MOk-qp0pW-gSOBGT8@1%@RnUbH%BuPXv^l7vhFnG*OPd*!j2#aQ5 z>pybJ{mrv{7#;@ic)aPvC%#~Q~OyFB_w*TQC6%(c=LonSa(o5z&Ie3 zk}oqXiy-bp2oL|^v$nB=ub(ovj+BH=vF9PtS_?!S@9&(2DG4jDpHkL5JW@c|7=3lu zFwrU4RJKxuMLj|Pn`Cm^sAnUvc8fY-K~2Aex$ZfMw^L_E*gjp?%W`uy*v72-IKfVkOd6LfeS zE1@ixsvl8Hew4x^=SxXWl4Ht*KRVdid92r7 z9PBxl+0+fruX%TQWe%|9GOWXG`kZhgnN2@r=|)JJ1cvX}UA)LS=}Zm7&YC%=ZEcag zSQjMGP6NG0;L(ty&&a(ac$TYp5*&ORpP&&te=dOyue<(P68u zxD{La>mJ1I$BV47W#$0&)hZPrl-4!_k8XUp0M~E?&|l2L3#m5Nbl(1&)KU7q&*=~xK0kumUiR!#>`Xo7)?#QT6@b)YWowdxS9$(P8qWof zl&u)jPiS$NF^^|6wzN%~0LtvNz;+7T2{+D87F>sK^~><_`9JkxffV5^w!Jv&9R1=?<1`ry}Zbj@9bBQ&NqamWi8{V8Bcs zUH3h5(umd)XEQm`t*BpRJq&qB5^IXWq-~_Kep{OgpzrbP-J~F2HuGYwA;p@aY)fZk zT6Io=3JV9*OL%e8ZyNdDp&W1Aw%9&Mw-t*tdDLRz{YjjuSZ1;~)5Q!N(rOJFS%%(B zzfbqT!`?nd|$iy~5SK|L2xsb)TqH)KrBn`PNpQyQN=9SXR( zrsLw~**YG`V>0$UAkM;Jd&%5-`=g*(;B9;G!NR+_D_~}v|eMS?#^8b(DtS2 zQ+R^P`32K}pCaEWGskl;O-8*dPT@7&sbwPC})c}|tj zRcpFAyl%CSK5TfS=1}mGt2Z%qo2>^MzXO{%=N<#raw|q1k!P$6Fo|hh!ABNH z!KbB*=?z z&-C}GvT|@a;Jc#j%K8V;EWsxd`n^5z?Yo($%Mar`w8UC<?0y#K|yg=+JeN)`@xR>bgAhsliFW}09D_RXv=Y@RnhVMzc- zoiVW(vOUB*cOD|ScVPDQ3M}o&Zo~AY%y(4r!MfPF2+9CnHlNjUY1W2K2T5|Ew76-S zBtIRc{bqmRsl+$#xESQ*DWHb%kg3co)FsR*CQvOoJ*;a-k>8=vg(WGkPvm9$teV8$ z=EH`tS^g3rRpFT-R*6ao@A9uSkhMcz9pi=;tu#U|ucyX&`+~iphDMH(x^I?^;Q3E~``a=Cc;dPikxb@2;=Ky`1PM>y0MNFKr zm^96UP=H+r11nuCsQ7v0qBRHn!kXXs$8PQ}i+5EF9QijC-g zxF?l=TT62wWxWOnl^V;`!H-)gJ55pz{|F@y+)tEoul#oQJ?$s}UGsovzx;mDX?Tp0 zKP+DevM7`jw(X9Mkm=a(B7S{*vaotke*tP!VK#qOTU+OHx)ezq)jgrvt%0LCqgZA9 z<8c#EW%(v$w2qen*CTRTEV@n&(Ggwn3dU`BqYji89vpQp?k$QI)I~=`d1vTJ=2r zlsJ2$4%S`S!G%Z|_EJqJbpT7NiJT3i^5?jRHj{axmelJ}N_V6*f5dL9y}7JzENytT zPDY3%&Bg+=!?b+M;n@1PF5(10Dj*b7R)LnVBwSc0ZsB;$S5QLgNBl!;%toA;ewK#K zb^ZIBL);C&>c~Y0=-%-w_PiWs%E8*)1L%j#qinQ%W$pVc!(C>-2@^@OXe3khQA<16 z=a}QVt}^n;IIfCvi$G-pX(FT-?%<>%9)guNY4?3092&XdtaWK+h=op$ZY}B9npj0l zO&&iQdH8;8SL>lECa;nD&k!82@C?ywgS1p8M^Ls-G5_-iQZ?bMI2>w5NeYR4R@Nvi zG2FNi<|cjZ(}Xtf#9Y|)TTl-Kw6ViqxZDTQbWC}@)=)tWmMw1APSj^YB43>`ykM-n5qiUU%dU2NPS{5B7p$i{9#}4DG z-Qwxb{rht5m+VO@+(ir0u=@k}$cKP9;d(!Otp{Hf_WOit{Fk>?iZT zP~1})qs_sx(D-a53ri+F1c*cFo}}*=y(%m(+V!#{@s3W@+U16=#1{}rBFx{&wJM@Z za%Xxdx+?EWlS{{Of>7k1%_%2Nq<|)PO0*uhE`L{3?dLv?2}u*6UHU-40|zWA_~3UM zsl7C}Mi;M64%Df`jJyW%j43`&X99UfP<@41x_DG@6x}yL(%j$Y5bc8DH&ps$k&6}u z9}?NiS1)oBvX$u10HB6&H}mC-ZuD$&NPz z{c87EsuY~X!y3~0c`dB_8tX4`{YiEB3u-OQ*9;cB)kf7U6ZlsV@tND)(D@Y5%dHfl z@!1(m?DPseV^&dV5BEWdx;Br%3_PdB&m8EwKM9c4{Om5R{5?`?DKuP*? za?skF*GK-mjw&_Lhmej^tD{79!051!F0SQ^8uvuk){+|q^+FWGMQPUqo^qP)qEbGA z)huj4DK16pLL^T>$M`C#>`sDD=u_|KjQ3P7xIQWAH}sm;FmNWy1S!SIt1HqHrXom! zX~XLIb@dF^O=$1RV`_3*?1Gh~)IKnMd|hR}tPB>0Grdq_F)32#E_On7+ym@ zvu(`$(p4Jx_U()Cj>>S#IV*^4q5%KZ1(9EHJAjQ+*P-+4zLgxa)<@l@{OqGxjm&Mq zhP|d!$T7?Ja#vL4N2Khy^HJqaUvlGi^jY;85i&p}Rp+C&ZW0$W7nG4BTCQ0?5ddJe zOsQ$#!BxzZb92D7e}fb=^5!k}|Il%AeDl2qf!sL!!5$*XRMvy{ZSu@lrBw$p9{PK} zQsjP=IFe|Gk?7$k8^4J`(Wze*F}Yh!FkViaJo13`ELLj?;J0p6DhzT_*prwPcAMIF zm5V7>M2O)DMQX)D!J8EtVzREVMJc2!J4RDanh@>Wdt{w@_PwcbA|nNd(t3JmF^!{> z{m(paKfSy7k2T_jD&M-wE42Oi1=~-G{$)qWAuU(-&R4O6?WEE4dzluWhfv0uJXImD z45tD0;b#%cfVtxibT_9NzC$d)8r(fd;oWE{&1~F@&sVOPm&8NlXbDRs&k0B%)MG*K$9j5g*OJ6ZrBJSi40?wGNJfx zGm)2p)r;N+`4lJAZaIJ1A~(85rqDRCKnJ&`vTQ%Yh7R`~Niqkwm8yY@nSzviwX3$j zo_V#|mvl!(pp)UpBMrS^(ohx+!Lo%&{{ky6<&O%wWiDUF{SoVlMqz2#c2^f8_eefK zJ%3iZ_NSJa{R|;rNpZQX(NvR5thHMx^~=1Qtf@64ulnkZkguYAN;`#;c=HdwHS)pP zTkX<&FYDWUMqpVtSv8ejW@Up`<+IG4|!L z2F4&T@9Cf6yy34~D=t!&F#Y`$KghD7zGeNKcw$HO3rI(aB!g#vS}%EyRS}?bb{TAQh7VwE~oeRgh2g;`m*& zgNAZZWQKasv*yRLsyiWyCCNyQ(+4Hm@ynD-PZO0Bfkeh0!$0V9A82w^xGjggkrH8& z%h%?CNQDZ^#00m+>Ab2xNvgl#fyd{@zI#c0Hxbv31^9CWyc_cKiBTA<_JDd>M%md= z>_qoVxXz%qyTKh`+K|~(rkWH*$MY$h*~HQPIjmud+Bw)cNnhiN&v*^nclY7(VXov6 zm*wan3mLsW3U;S=9}T1p2JV-4<$jy=Sqx*u1Rr>au^$>Cg=mTnZw(I;*m1rmJ(pTVgs0#5UAP3}XB zCs_*#IIFsK0%CLo=Bn>O`v9p-n~u5ir`psdOsh6n)7xI%#MZo*_!2+nv3ZlJ3Z?@_C`JWA(&zBQ=Yjb|1O}C8Ed@CI|gI;!G27x zDw?y$HK9dyrh8w@{q(^7WP)=&KkIT;43ZQ{1hV;gp^TgroEF==NuZYi-7!V`&McAH z6#kR_PTykVCr$=>%L)GFcrq#gZ_8wM!($#9f)t@GZ=~Gi4A$^Y-6sV3O8*R})ySby zlI>y7d$p9I(mdyby2XGLPB*ITG$QA8M zgE`m%7P85CJQ512I$hw)j@Z@6@CBti-xEAVbzb^WPDB|GNCRi{G&)vf_gus%E{(K6Cw z7{Ex{3Xm<)p7dBTq&d-e*u&nj?nAKa4+EdCeG9{CyyXweEz{2xs zEKeQ870pGOmyqiGtQ;6XcSXyw^qjN@LfL6PZ*?8UVn|AX&=EjfOg2?+S`HOszz;qc zc2*nNQK>RVemOSGi_*bZ*2`g=dx&-(TxF{fvLq&1Md1(pTky z7d%-ml!d6HDM0FQAFnn+%l7AL}`-#1Mh&lf|!0ldMi=K0!i zMb{_UY417WYLs=y)$LXEPIF?txnwC_CTn2W2S0om&`G%3^=D-RNs`OtMf@b9w{gB_ zjcnyZovESpWaU|~W?Ks1=aZ27f}reEjwj!KdGYmr#?u6bKmVPYP$1a!zIt~(+V`JY zMIH_FHClOzUqJzc8qk)n$vr{3pMixi)#ZyZt5rZU7j&t{T6M40X#Li zg>~0lIdHKW6;jz24&ODV1m;3BW{$9<3uL|La-@(A!*{le09HCZy3yYhQyxKBB`H5D zaeGLwfWo^@$eyCw+sNf%2k+U+T^dd2iX}qlxJ|@nVR-cHa}1!a+X- zx*T6C_8Q$TXL0~mO6|Rj1xJ5yN{j)~Cv~^EeiR*GlJGfFAB%tLFvN9`*>7Sqc@m){ zdCV}*(AW6AhH5%B7115!@1)|iaqAiZaJilK5-69HKs`SeDhalu>qv^{G~+wcVg|3$p)d0;nw_X*fVXndj~Kro!op41HZ`zNv~* zuK`O{Tn$WTkbk$UZM`ZqhAhFXCW+T=CCSDydD?Er8FHM$wVZ;?+2*yHfS^U0!%-!R z(TBQitzI%CE}hpmaeR^{hsxPF&l3QAGmN~%HzPQ-t58;%sv=1&J|LS)bD>PjgqUgM$8?j*c|Jv~!)oD? zJGj49R!m{DGtj?Wu_eAN`am+mCqgqi@WK=p)T$>3TNEs6?8_S^+MCD_JS55+S5Onl zsd?V7tMfRIQR<0ql#q=)b>UtWR%XGzf2`ri14&3vFu>rXqNR8Rl=KOT`3I zrp{1eL*V|9m`HQ`S|t2t`26lN!Q>H}#S{0vlX~Zt0z*}VTu&%GrxrI^vA!ojyssPNaJZMZ zMIaVBOOPc<-jUH43z-2LI&rcFOZ@K#2X8S<9z9-_7Ai}0*^oW2ycb~jNofJ8Tj-oa zivjczjP=Bzc**j1iIT))Ox!|*fR4nmV*{s_9AG{M71JbqBTjXeaw++(bBEWi;H8Ii z;!{_3)Mud~H4<<7s{yr-m^fMY-1Y!V9J%O+2v7F%`YE;oh5IfZ^n00G*%X&B&vG@s zbyo(Ctah0fd|TEK0}tOrNJzc8MAHE^dv+y_XOCS0?+AX{Grv{w+Dv5a?08s4wL zAIo^0@!AoYf;;1KZWK@(vaeKC9NrbDv)tOGt8{N z`aJ{9N`(8;r?cG^RnNDH$K&M>!G#Obba%Rg@L7AZ!Y*sQka`EE<$^1HUuNn%j9WUD zMqX5%GWg=siUlhL#ALyVb4LK;-4z&E3TmqI?~(ZguT)Y$KP@%zPv&_|fJI%fnHM1X zZ8Uof@IIC$vZM2%k(yI@)P6W%zni*bvjVhNeuC~_G595oczxXO%Fs%F;04m4$J(QA zcx=yvF8=k1N}ky&q2@2~Q)nWz=AVrm;o`!vpXY_U#Vo9R(B~{nF;@T#Zby8yY+bCu z5TUK5v%cM5p!Qg1D^@L@_X}afvyj zrh;y%zINaLV(T4)BY~f8;f=MiHclq?#^slB&vV~f^}ki~ zX+BNW^zZcP)2F)=?A@hCFln&(^vBu?h7Ka=v9-MBUgt%glBF^%Q{=Gkua5oIWU-q} z?&DZ!16P=rt89L`sTf`zj}5d)6LcPEMZ4x~bzEFhy5=l+Y@{c)%vg8bHNmh={_^~a znp>Ir)ordiz`~TdoTcbHcF2Iag2-hmyhNG&X6OJ|`s3;^7OwG@H`q?WIZQx8S|%se zTt{B4xUG;VFNXDhSz@5k5Zsy#R_YY|{+#%#@BV4jY<%E*M-^TV$76+If#XXGqqwSj zD{l@>Z1@z)77OTEwENA3F)bNgBqo>?5P(nK36SO580h(P;#EfRS=T5U@0a04v0*f< z*q~S*YA4DpEcHkUG#Y$G^I!3mylQ|;KDZ_X0Bp#M^U7A@@tqVsC?_8=Pavfp0*xX1 z8b0}rQ*8BZ?BoU7YA96I7Lol8t}o+gftH-V9@i8+Ml<5}ip8P0wr5m^bxim$U;&(lD!N4Eqc5B|h)W4oZ4SUolaU$83gqj9YqzTk; zF+z{sGb^j&^5@^X>Xr;4}1Y%u>7S4cuA_nx_Mk!JCG;vU_^@nt4)Tz!fU~U?c?o z7fiu(FcHhYUx2ut#dajn>&ZHD`YAp_YV`yhsxi2fLx>1RuOaj6ducsqo3a){MmPSd zmfx!Bs8q5qM`Ai_Xr2KrlW{yuhojI60S%?^$z>TTgK<)~?Nv6V`%O{^nM3)5#BrQ( zS!w&J13s4QKJ-WsL=pVDnVe&pTKijoT2VrOSGcYni#dQi4ZeRr{&@-jwV1F^Olu9I z^_>#v9jG6i0j+m$g_x$&vbrkuv(o)bjdmwu_#HJVuiC-KbUq0^mhQvLH>y^;F^nEs zf-ueNtvn}k5RuBNTxC_Gxu(6gtiHW0-_ek7WGr+S5gu%f3RP7_uPMUEs>n=bsgw($ z++NV$VeN25S{+zxRjtcZ{9Hj9xFUnqWaDtvk&;oV1*rtyv6rO4Hj`s_aW=(u!m9M$ z?(XvCS{mE!vR)>mBx+;CQkVmnU1Gvnmd$lrOR~pgL*TO%UP>DOtbZ$KDfjoPr5&g) zS{1IfvJX2CPHiwlUaEwJU;(}BQl;9O`M=XR`jK%Xc3ft1ZnErKP(Nfb0xZ!b9bQU6 z*?wbFIdYhcLN6#vr%kj@6xxhpY!PQ%V8`LR}-zDEatY9k9(F)*F&&Dh%fL2a8BZxbmifwl+ zq^rmZssRE=;0Wt?rUhCGU4xV|NCC=c2)=7lK$0l80^r z{Z6qe-ixtHySUsZO}BLZ4`FEj;1T@DsVv7T)Pg*n5E1-$nPI|Ie}(SXjUxEZomT(l zc7OOpSZqBE?QooAIZn&4IVCzybA$r`SV=_zSm?j0%k-x?E?5b%7PdbQIhe>NH#tmA zt|yhZ%0I-s_$pH!x~5+~8htKmT)Qoo8dce#v$j=ERAw4;p4?%>e!v3uM@OUi!C|At zO%>H~IrRoHq}V;WCyl^``fJA)VZu3ir|F17;4g7&3pke-bBr#=><|xMp&Y*gIkx+( zUY2oRIz#VyHtxGN?!Pu*L4`#6u7(&qrNBPM3lNh+MJEIc zix~=s<_tyh2T1KlCQ#&5O668gq?e3yYsNS=;$4bJ+`E4NX(Ig7^!ra0g?Sagu!<83 zx~Z1RQ%&QT|Br*Erjtw*@l3HC^C*vEjmV&?qnQC@uoNl7m@sy@IMLo1IOrXd*Q}Sb z)a**GEsc7eeqR6t90tt}-43Ncw+q~&HB)DmXdc9GFl~^auw8KXenhCZe`{ebJ&!Xd zrcZ=8%_1(8`VtV@k7NXO@Z&gb4HblS@NSUVlLDCieV+WnxA?D`Nph7sLvn}51cw{_ zneZ0DBDMCKKjlL;Y+1~rupv5d5on{HsIYM7%JY}LP~zGNi8>yhERZ2Ud=>?XlLP4)3#DZ-J&r}IbIjVLooOpt|Xy251894Sx`L?%mMd&`tjvcoO|et-i+U1Ws_`J5&aWSzbrU(&$71$s zqxn{M-ye%e8mn;Xow;jB8VC+Abiey>Fc5Rt7j-yzIU&cVki)4S$E_Y;*Gq0LWpvb% zuuyYxvkquw9?v+N9X2De3GSsZqF!|(@|p)AloGWT?}h7uLK7ol{P*8J;hJ@m#19e+ zhJDoXk)_%?g{BaPlC2XT>1Kcr7d)F9yYJ7CDj;C$qk0!OJ+BV08HyEV@Y`ZHQ+&=H z-^l_x8$xkn&<;-=42&HDZ7hgend@{Dc|2!H)CL5lKlO+IZe}64psBwfKV&oRDwx=7 z`rMNJ>0+EB;MJj#y*;jQw-JpSK~r9SkPoz4y&&<2&!I{2W)VNMQ5@V)*dE>KIq@_} zB9UqGu{Ae{(7Zzw748I4{yErk4~8wmKZJ7hDt5ToAPrnu8MaBjfVjAr1Eu%|irn$n z&z}Up9@5=lVo73=ptGS9>c&7h{o(lmgpK@G0Dn0^7=KsXIx?ugF9}V-uh+p| z33d{!RH}oyV(pGFk&^&;Xm(8_E_M_I2Be%@cbv1)XAlyqoMDpsz06+vV&pU@!Ae_kZ=PV@B9WdgksO_1 zUHRE(!JU}>vMu8|&v8QpWwBV*p(wS`;mQH1N&`f-Vf^wiKFu7rD!`e##(aFj;D88B z;affNTD}g}9M^n)es=mE569idgrX?*f*3W^vzl>~vdJ}t)YVk1r7X;1kWMhKV}btr zQIE^ZR$+J(%7OnnrGpU6Ry0{I&&B`>FmP^J^1lMrXN{f99B0RH+dy+b{_*dTzG}81 z)C#IiJ*W`7f{gVl-xB9n?ple``azK*)j_cb=>~yhohK<0B8@_QnIl8otufLGO&gB4 zU#ftb3k%-H>QaG$VjVFCN?XK9{B(#{AY!I^g@gb;`|_cmSa9^l`y~2^kUwwB!sHSB z11>FiDWFN?B>58?!ubh7;6rh-gh3KX^ikXiabaU5M4<-_cZG2Sr1?SqQ4Bd1$I%@q zh)6T$N*>0AYcqz+6^EIjQDy@S<@@-k=ydvu$J@&Spz{bRQ)y1-&_F|?X0%C>&`D=c z-U#C4j6Tea0AbRkvl>RMsl!K#LxLfqeT}Cg2;HMJr58cqkx7Ruj`LafEDG<@cQMz> zzP6%2(3s=SaK_;;seWG4!p5LMJtu>UN)|jP5yGy+z*5~`1uWd3daLBmWu(<-%=WF{ z=4Y%9EY*uu9>l7W;RdRb;L%C^nn8>`4t_Bix|BpjC>4b#6@xDZZz&z|3wTZ@!z+{K zkxzHYqw>~M8J@mh{0?|+i8>7tw$+$TF;N-9HpU13WKvTFaArVgfN20pJp6tSf+*qt zzGvyRa-@D=P0A8%2nwUrCzBRdjb=bNtA!=ds8iVr$vr>se#VKGMiE)7aVD**OdMEkG6!;oTYW)hXrA z-`9=a_q8+!Wl=CvVThm_il7o4t2~5P9wr7vggOczUzUM`Zj%uJM7 z*qG8$cfnuNeiQr6SXpGzGPO-+M<&seMJd=SD1nMrpotXmn#@$OLuk+L2?ha_dufOA zPTEQIyG;WkZW82)Wdf20`KnZ`6h@<|W$2L2pj((YPly)yP8iU`mEeLWF{-IE{?`A+ z5{|8t`O5;TAmLx3bO>MmH7I3;m7)MVd&+~ktJTfoB&1>6HOhZeCz}+XJ0_nxuqO&y zDZ@TF`oQK3XkBZ_cQ zpv8zy&W)^+!|>OZGDwb4NO1##0z>_iq(Gr1IM9&ch>&s-#ITj6OF{k7D!~nqhM2p; zLSDrN`Ke422BjO2dV(a66T)dg@u3G_r3r5l!*LblfiVJz5$szyBf&@xDTKY>lwUT*_H~MHb_W09;y~UU##8G?WL7NLs zyAzfVwT#5VRpSSzBKoQtI=4JJk19HoE;_F=mS-9Fv%JP;{`_lw|7v{JDNnjTUU|~_P8!8uk}N?$!0qsv{BW!>9)Q#j9rRx`@xKD(p63V{8-~{I zLMjq6=Dg}x{>_E z-%U)MZcONIJm_p}LAHryDw5M>yHk97?6S1d1+RMv3J_x;h+Yx8{lBT%U7~xdF^R$FVWUw(5`>Z%0pJFE zXjWaC!c3#980b4C=o-wRi4lfzjm#(+S#l9bVL!kHTV_n+!G8i`We?{@cL|6RiHzsb zq%#sk)EvwtS!JdH6KQ_b0VIkV zet@BXh!O3bp#cIVcV{KM{E)!tjrNbY3nIj3VZ&KTnXE7{HPKmol$5#(Y1bCg_s=Ap zUrIXPnY6#mpZk7(EYpm%SbfOX7<%@2iWw1YPA8P<6wmfhFsLYW^z9U>8hi zxO*s_6%iO35Wfp=6F6v?;6BBes&F_9CoP#GDV|bi$DcP^C<_D1iNA)H8E_&W1`4S% zXfUS!^ENhPVAc-H0zqf2gksf*Ba*}vc`t0!Sa?Xt-3S*5dkwL;IpJdXaj=&lpK%hS z%d7fehYY|+_ov(mkR#A9@{P`&8FjE=4~wrsOn5Pb)|r9F82Lcc;1FvgGdiENYbN31 zT*U6Fh?lv5m#JjkZQj!JQ_DBR`u9W&%Sb!z4$Dtis+e^RlvNe%O)cbAEo2@=I{K<;5Y8h^n;rm-YYy+{cLQQw4H!N~YElpmjl z_Uav9TA~=9D;fSduIJlR|KGKydYxCNS1CC5_04uexwqp7B$}XQrIe-Q!^AxkT%#ht zz--pyYt*2;hoH2i)sJkni04&)0lLKP3YEf9e^A?9v{f(>)6m(}S;{TX3WNI#qQV~IRayVRl z^dPVe4L35#5ao^r#a~Vgx@fm<5w|2|Wd2S=6JKel*hhe{fPxVY9DF~)j;6n~x~Y%P z3J@*;2=I-2BLI!Ty+ecvB|fl2 zbetuuC2T}|L^W(Q&22O#TrB>WC<=H8+E{42C{pmy#i3n=KX3)eX0BK{oHm&h_fv=+ zs{qM?0`i&;yev9nw=!s%d?a{$_sSroQGf`-(tuR%GpoGTBQ|?R6BT=K{|~%EoRNH7 z!N{0CzSE4>CDS5J_zabY-c^&u0+E-nK&=Df0w*mKKBO62 zb**WM99Q5NiaeRjQ2{NiVrad+&<7CG2K%T5h6wM z8zI4rCj3U@dxiF~8#S?)$3_?-LvAwy4GlU$vNyoir8m&n#Z<88fmEo{i8oBe=ukly z_{w0HQfeq9U0Ta9xDjs#zyd=@ve8Di@!4ecqaiDhnCQEVN`otRDMSEwiDM&ZwNSsFpOFf~T2_Q5NBd{yJsuBC|^IQZ9@@xdPBPfBi|B zN@kGYrpyiX4P6vQv^VqX>_7D@(U5l++qI_dT1P2YDcg5}(oODdd7DQqg(i;4UE z-u#Mw8YBgCS&KiS1~!sbqF%pQF6Dl{_T#(u+Bo9CyHpQXsA?!nKDuBl3+FP zcEThcJBTYa@CJ(_gzkQGc`kTOc_gIKDrFH9|0c-tc?zX=_~SwTIzn0=Cf;e==3cqV zLq}2?NF?Z6Fl_*IK~wmrnP81n z9Ckb*gSH#J;ft8dYvGt$NVl+*uCtY>Yb8$MQKrcH$!9?L%Xd_>Ev)5UZ6}-%P{G>J z!(35BZ>iv~>tJjvVQ?v8wDQe^`Q`4n&F!~=1@C#kG3@5vCA$mjCRL)>Cvj(_iJ;Bid0``r7ZUl(Av|?{+`#=eZw)y5cl^K7&egB=&7S5` z=Q>M6-;t21>gcU0_#gQ(PtI4-&DIwd{K6NOk}IkhOUcLn!9N5c&R{sk_9kCf7v-f5 z2&oQTK!UnVP}N7vS2&zBk&x}6o51eh68PhdO&E>ztXRZF4cS1HA1VBIPn&-6{4YVZ z5DAnlzfmY;l+5Auoy`xv1A_HEYH>+}uq|dBYG|gyH%_g2lqODx91663k3i5zJbT6< z`srQKzyNNc0eif{gSF|JV4K1Hc6N0-yBgkd-f%#*S#kJ}M7uIfD2)MXpWrBhElP^Q zSgW}(LiS*}P|H9|xtl11%n%V(nj1@|K2qGI0qi1`A$t;TYsk$UjJ9Tu)I@WoIvJJ> zF+hR?904M|M;`^bcov`y0#{#7!W&ql&)|#RT~vDmDqV7YY*MSp ztb=(X@bRbdd=U_5=c+%}3B{B{9YkYIM02YdRu*y$t|gisdFIR4P9Mk8YQ8b9zxgaI z?I-0W+{7d%rxR{#5>|Og>pde4#QB_H^FBb*ms8_D{Aj*NuOLij_R#OKr^xCEFosjUy%+9g< z1hI45>MobPr@LYNN_mD~MFuKl8!L6@G$&4wOb@5Hn#E zVG@Im;=;VaGzJ>_MfSrxnieh^W>_(`&8XWK6lAIY_mxbb4y{lomkYw<<=T3u@5k{)#@LdReWv&`L;1O33-}`TToBz(M=)behGNMXb2qAHKFA>_7MlHK|5Gj9Z~s2Myvu1~to z6&rhvsM82``PtHs;6+&P!+OD6pG*Nuw)38fsXVQ(oRZPMC~u+6vWeTaY^0(euuKcA ztcy3er+mDuE?1jxCocpvU1=I_Zzg9?nKEjGSeM5Y>q@P192x>UL*gg3j{cBsNn7bB_pKFRDtDM9$}FtoyVUkxTB4P%Z99rHx6XgNRme&ri3L?)#nWAV?DFoC1FxHnP+tQlg|Am)Z3fkcK= zMt$^bvV3_X<_cW*QzVJ8qF>##FTXj!Xqu4X#Z{w!;r z7mY8ACqI{rUXn|iPsnR#pjSvml7n^L6k2FnDaZ0->&w18(v>LdBsoMGCI}7s^&r1_ zH9@ZalS^xLa)q)w-(22oby`oHPG5z-T=V+q?}X2Np#rsEsF7Var=9J)EiObW0_?RG zh;7!@we5Bv9Y-A*CFi5{`}vOj{)*4dh)pXqcUe_?QB7S30}pnx?UQi(^)HM>soLfW zG?U&~K@%lvJk4gUQhQ4kgy#N=1dSxcOwH@X@-8y(UXyT_RWFRh9g7fRkH~nLQu}mZ z8&JSh!BX0xS)gK33Js%pxtz&8N;P*4;VHw)ClV|Mp&D5?pfw#Gn>{#HkWNq6Le*31 zW%PP3@}!2h4st(UgP$H4~T-`BI$zaK-k|Go$4eI6EUegEyP z{O!EE9UGSEjw#C%rBjAgXT;YS9-@9_uZA{FNxY=$kQ#V4d*zKDtW^=gdi3e;vz%>* z$1c@1Uy6W-%@G|`zQ=;a7n&}dX#2?!OS;eKPJ^8(`$sX%Nzn)vnh^Y+XcwF$D=9#U z(H@&sc7*7njk(DKe~UstL&PdG=_C#=3pQVi)P+*yciqIR-5lUJEr`Y~)_-QUfl?A% zSA^UwjRY^V;6xr?J0o6HvF%MdfA zzEW^YIkqYy*{BS6Z)#CGj<`dU3=`{U0K@;u(OnRqZ?-tweBCx)Yw*NGpMUu9^OhnK za62B(F3t_u1u5|(^Fuo^w>W;}(1PWaOwmqdM_X#0Tdt_7R8i8_&{Fv=qaA|B`~#|vf48uI$gEl0Bo5^>X{vArY8$IsTxu3h{Mgw%XV~E9vLW`W9x7o_S6-*6 zWTIuwuW9T0+x>C7{iRky$L;;Nzt>$#pEET%MMFFNS4-3DZ`~Fz}2uiu{T7lm>?!tR(o^gT;v)POzDxCW;b2VdEu8e^Y{! zVXaL?N%g~wvQxsB8tpY%b;U@5yyZcx`q}kz{J>x2(~Vv%Ovyt6MaxHOkZilfj|_{x zA0|6y_mj?rTlc57Yl>LCrNaI5bA?Azfu9^PeNh6zTX$Cbmmvgb@`wiY0gXz1Nk&}{ zuj~#|sb$m(8!2`kGPH&!5(I}+swMcfGoYZ^)>K_>^{<*@Z&x|zp4}{Yd7j;~6&smUHO{HJmlPZunob?XyGBxd zcPagav=C5i$FXdfxMozpG*UYo=?taZ?~nUeV=i}xnQgthT4~mmPF;=KqbON*x-_sD zM?p2~2xN``WNlR$PEbp|# zenUu2r(h=8Gevj5lx4HW^Ho6oH$}TgjO?(T#G^e@$4=`t!RXYil$E`H{}{>JIB9Ip zf{c`?W~+2!%^tIE@tj1++`e4Y65Qq&^^$b=vDY$u=*ib9jmhca^uC)u!ilxr$#s8s z@Nsd!o|?bk|3JL)`F`EQyxIP0YPd(fo9WC=QRSuS{(5~Ln4u={(eytU$=&&Wok(?z z43(88D=Eo51g(y%L5+J93{Z$_9lJvy1+D`6V2&~?{M}b0QHNttJ)9?SF-LJF8LlzS zCVah^YTwI8!W-RNva4al9T;V*820lPF?p~kLBokLQqo8SUwNEp@6{+PEkp^}O9#hG zg}oQB1fLJ%@XJ#9_v-ABs|xuwFKD(U8LtL`5+`hKEYJ-K+kak073;w z;}|=~1p?dmeNkaE77*2@Suk~MXuIcB z+)W*?zIxt0yPXl=EeRf1Hm|BX>dWsXUDtwR=F405m8^$Qt%u0hBV%1t@GmI1=2czl ziVu6=19+Y|22{=hizT{)fu*MXh2ns7c;7v6PJ4IsE`4Du#3+<(y8t>9?LPyKv&r;j zue-C4Zv^p@i^JsyH29*xRG=Vq^K3in$aF3@f>;~$O;GJRUzf4kzmjSl>&y(Cjg@7P zpp1%%l@!dCRJ293W7R{G6q91~64E1*VUt18(qUo$V3a~`7$4WH5&V-DGPf%UA9Chp z!&nz}FAOjwU?I95e~v2}xM2|P&~6wwM|pQw4g*7J4`V;K=eKdw+a{PeZuO}z1fi^J#fb~$u{m^<}1M=M=RH%CX?qp^*#wV~^C?`nMHsQ87N znqa%RJ5|m?quaSzoL?~w6qm`14l|(k526!Zk(Kb&&kTGZt6pqI8jEf6G#!Er` z+f5_6Mf4XTW;jD53mh*Xp4i(>5)sI`fq~suuoJ|V9qy&n0LP0pL1~x;CRyq*kzuI4 zA8!1-Ab}{>NxC2q$knWpKRe2>WM0OXZDN?8Wnza#OBqx;9zF!aqP8EwZISjswENjtX`ajws2 zWn*1mOUucnxGI&jvMq4M9qnY`t>zFgDPsE;k$pYBe?H(y)wHDUoR{~ksQs(^yC<-d zAr|8T>>Ywn>YpeZFg-|;A~qnHQSRzh(&gsNWSH4G8^_n%b-F|p@tKtRkD0S7|| z0U>zyFM?pu&|YqS!rPglvBXqancg436-J!y`Zw`BT+g;Rt3Bn7f1QQY4lF+EH6^72p>3Dtaj#Uzv=>BzkI6k`Ezq|)tx*G5AY?Zp%q4jlZy|+Kw5+j`DHZ%KU z{O4@zegpA__p_<(@%pH8Ei^V)r_=r9u)O@L^O@J}*6D3)B$n?VETPlM_dztRb#Q%7 zgRq>CthA?yfv?c}Zb#E~#cGL{t#?Nu&2+-S0ukahGsUN5z0!DNSQ;e0$`D}mlpQ&c zFb@@Yf+JLNS5X3&EO?~Ae(fhh=GY$w_Q@I80u&B3(AWq}MrcJ0g=w-gh@VC(T1C4A zk934iu+Y$G%oROWO%mPfzY?*Znp=X}w%;eaPkV zx;`9tha%?ic)eb(HLf(;Vqjuy)6sV8X?(XeJ%S8BJlLL`sA3%N^uIoB?q{|Sy%o2@ zQQw$is_9Nn@_2GMe)lKO)eq2CXLGa%>*~puvJvn&IGC91eTC+xrt)oeznE>LYANWc+-IQK+V`T=rh1TC!KD@$A~XNYN{Hbm zR^cCbh(~#KN{Ev{cfbPRYmJ^jyW#StP8~G7SwY5%oG_MilZlKLh_fqm#O9;@mPVoT zNJP~9RVxef=6qj!i)y%B#cH(d=dy48j@C-LWjb@qwF_-%RGuS(W2g+_bWc(J6ZTaD zkI?c&IiN0wz!s%=&3KJYBu6a%CLYthh!kI@6F>Mx7DYjf@K{_(kEWjvxU-=k=&H9+|*c2 z#laQ2nAQX$ODg`lJYz?Xm67#X&3i%qd6$1gl?uFH+1CMBZe zVi1D5Y)aQIo7B5yT`7LaN{vm`3iRqBN?iM{uTZH|~jTDQVXurq^H#guw!9I_WD3IiM zwiq-;eHb2N?m*pb95MtW2syq&XsHV=(!5ruyA@S&PFXO!|hm!ca~ z2jXSN)L$MH#Q6tzIX?9(IrZ*%ltTfmq|+~d(OZkrB7}4FGGBgSdG=_AQNyN9_QKyo zWMfX_4%+wJtu#BNw07Zsd!Qqj$Db(d)Xu8f&@t%)E!wO9CFOjaLBR9$n36XYx!Gr)RV8r9#niA=m`))t!Hx zx32nFRv^#mUY2xED>*i_c)Hu)rEQNs&9)+Kmb^#=79^Q4&;M#y6&$A zhofSW%N0!Q{SZ`>%Wd;GUrin>OqIpw^)zzY@4Xh{YQ5fQa96V>S2vUT2a($remQ07~0u(1?-;J?LK{*H)6E*hTH3> z&GE^8Ew`D;&keQ4iCEvXcKes@(N#BRA)()Nx`xaWXp;5XeaE+yKo7GtDGH?#qY9Z) zOS21*?mfxf_EE*<#{4_}-qYK{`PvksPJQO%{pWrrYeUn*DgwmKtsvb zrtvG3BwMB~Cadl*P;!sXRR-%9uzeLc3d}Hl`p#XtsElT<_`$)SZIsF}WeEl$;4dU0 zi`oWVGos;K7ErL)DZ(nn|7oZk90lzkr$HD>Gg3JIQ1Q?;uH8zrLNwkCcc0xam|C_< zO9cV_W~?G0A`{g?SEYkmY8%E@71_v2y3R(%wV}u-18zP3_-N|cdid^&CPGjq&E7(0 zXd(0++>&6aqvGnZ!o9x!XjKOB?gWjt_-Isib~pPDwSj$OqvL5QrJHPo~I;LURK zZZQL>=GxF=>u$HHC05mP7CsN%a~?WJPSGkTRNOAdB%nwvzLy5y^zi7 zCyoCs8?d(+y4LHnHv|MKJBh2NLGxR|vUz;aw$MOSW|NghYO{HL49^dz&S2PebJ!WZ zUw`nQp62Y`T?KkVdAPhd=VP2(>uxmm;1dQ-BlPJjDS7(_@q5!=Y+#>bk4sg;-jClq zjKjfUUo34J*8#@}e@F-mC|_)_w%BZNdR>l=Nu}Coce=c61MmIi%=BbvfKGninw$-7 zj$Z`3+NSVM()k7*R0g}&0<&Z?R$IgU!RJP9p552b<^5A<>_}|(`Q?VHq6z^XfvsaF zIwxM@47s8?IAgGIK8O#rFrz|Pp+*j+p7)F=loyA-|G)w&HQ;`0Z5?PTiR^h4%tKG5A*XZLp@Z&`hh9t^knLCgYDtwBo)}2dV`63FnJ^H&k^?*;`56%NzI+2>6fBE+1d7Z z_1eEY!uvpIdH$KS!ZKWJf`iA~qa0o+Z)e09FG?(5=7xeLM{)o|Qsf5E1o#i;G012h zL{F^v-T`I&iJMeYOg$c-4zIo0&`3i*J)D0})pvh8`MzHeN4{^(E)io9zwXBB`S`M5 z4-OW5U2h&oEJsr5@!VYuP99s1^I6g@6|Eq6%jE``q%=H;WClpjUX=rbj1wMcsPbi~ zo6n3O+xf}S%SgSNmCNEIpQzd;DjR+^sS?IL{Ub!u{8cFO5}>t(jYHdEgaA1QY3u|p zxQpv_Opej>{Jq5nZvpC(wK1C<% z3h9J%DA>k4TuuRBUPYv`(vW$Ug=nGUzdSykQWcdvTU*I{n@MNaGY{6ZVZdHHeytR5 z3!R|_a8Uhu-jZj@^?e1@KL??e8v?@G)}3|p)5vXLAio4^V|iz-<@g@?;Mpg zA=+#M_c_i@Myf?NK0GwSJvd6i0oN&G?99;msH=b5+uR*&?#DV#@BzRz-+wH+bF|)j znIIoNJT84+iScQ7`MBP1CU+Z(vAH}Mqo;I&)e8p43?n0(;o_!;6VD-o3P>0g38TdD z_wR+X&W-x z;Vd5y&u6S$?Ds+=>c4}vLq1-Phf=YHzrMe}GCNIuI@}$;Km9#J-(?Y(uPmo^6HycK z4B@nl=twuJ_uH1ZQzLGa?{qZdrO4|kUPZCwsE;g)y|T5fN>#!Ht8F5p9Y!nT0b0W%InY78n9y5zI64m@F@Lr3UXI`eQ1({>%C?8*j8&V#>Mx6 z7*_e&#QbBZjf`ts5ojm4bZAXi=e#|Ke;E^j@Tri?+NYIVJ6f(bN>^*w8I3wiW~E$} z(@x6S*JYeb3Jhzy?hW0aZenk9F%86c#bWCdJ^#V?gZ81GW_-z!I>esO2Le>tMtgqS z?~kV|sg=qO+|8GVN22ClBL^&4F6if%SK4Lqf?@7e?tTDIGBL5&b-OOymR!^7WDjJr zMc-^g+`E4r-d#o#VCeqU&B^p_ZMwg^8YvDwYF{gEUpsnxOMVM>eUKu+OyQfR_fFIM zWM}$!9m*zEd#tKE9q5a3&HcW2eigcaS&MjsXL)rFH(Xo>&zd7FVGMVQOO!L-AODLT zfJYe~Lar1Cc;F-9_797k{Pfx*XcHzm0D9NGXKrg&X7qg2JwANyuI4M#xro1F5gxmF z8JZkyUg&Lcfv7a#>*H`L7mM(9wztQ}%l;nfN&da}eaOe1_8ls=;QL~CzVbru+u{AV z)eIJy>Y|5zwY8won*W=K&uIGb0>!E!PT^Pa(OWZjuIjGx-KAuY#xxi-7Mft9ro~ca zHb&rxNle43TK2;kSuS*tJVf8_L^qN=nRkbnnR6&xGr0FLCE6z{YP%Q(kn+R5-4A>R ztWx~yr3{1C21k;tyFk6HKx<`X8&*xm-%?NO$|u1yR~XNzvQq$FhgdT9WM0!Xu2Iw3 zRU_6i5i=hL&4}=8p&JGVw~%ybDRJj)=-OJl-qyZZE-1@3X85o~iK&s|T1#SCQnhO@ z-8a$u>ZXlG49)1{lMCI*xiRx>={~wk1z(?cGyE^jqsIfNZGC$X&IV64pLkkKI}=0v z%etuI-OylJQgAORM1cm+KBeHBQ}C(kzBKcsq&WT0{;1v-+@re^xKZHc(g?UrRBR2E z+4<=oC)&+5)RB@{g5ZNUnBJMgGgWX!nvu)KoQLP#f@ZJ*rJ-Bsrc930#8 z9>F%*ub< zj%PFUGv9k}hc~%d>h0cE$M<_&BO!=SHD~40)nTp2V&;mM1?t3Xq9ae`Yq~5eg&1v% zfKXAZ6$w1Z;+2XQE*gOga!~K~2av3S1pz%-`1}XK(+ZAXp~PHzP)S3eFu+R?v<4 zaUz*48}{M$H#HQFt^P7h)J{B~Iee{q{Vn+|sf03{1m(mscO23N?LW_xz1EY1E9cwe zTSnsHYV20UySMuOICh+M_S{dMSz&|7?B{|{T)DfR1MkYaGxzP(b~Gpd@MVp2ZLQ(! zNiCRn5olTaSlhm+7W;l;V0#rT!*cTfvGtZ=adu1AFzy!IB_wEYcMA|4g1fuByK55& z?v}<0?lc5_iytE#qd`J}sM4&X*ybG~(=yq-*b z_B2KHeQGsW(Ss64Y>|9><4Cl~1!me=|=7Sc9)nDYGjU8nqfj(5t;=e%{&xAQm) zWXaBG;}r709K(&9%;WdBwt2csVZ3)-^KrR9yGF#!6Y;xw+3ckt7Y=y6In3G@8SBc- z@a5nUY5(2va0kAMY-XfL#b2A4_|oBbxfi)#lZL;}#M}`c61ZoPW(YFydONut8~c-$ z(XZ=Co^T<`}>OwR{8FYNelg*Zc;Fwqf_X-Grv zL6|x)Z;nt;b@`N(SZVzXj0|0_+k1`yj}?G(k(YN5>gJ(q5hxYd!FJh>2n_V!9|A1i zzaA|FQ`DRUcse|{*9!!+v0kklOTWEXJ*^uUJ_1^gA>5v~d4_q<*RiUBPsUdi2Dy;e zSX7bqylg%m7uN>Mjv}{whE_JPHujtu|L0371Fgx}1ymS>+I6~HqFsafWy|@;(+I+D z`JH$5wcOI3;6(i{AO7wWuR=7lYtjl#xR|)vK9AcZ&z#7?9-@q*YTKj%E(IK9&WvV$ zzj(nM*iBe%2Cp}iip;DUv!1loTmny)!FIOF8z}Z&Ud%<_K%A9hM*)@h`|AQ{9v|3p z?Bb|SFq1TohQd3fyz7%yB}V2#n7u-D^@1AB)i!gl-lx=)N*=+hR*o)HS%gl5&!n~Fw%IG$8*H@R3iJ;8<+{md3ufMp-U0q9 z-D-HO^O2RuI5|u|wDZRw-8Qm?B1U5Gi5vW_Wr@7new>^N$; zkazN)b6Czlug>>wgJLi1dly!0{qF>rPe#XodY$5zZ|D#o@1ZV+Gnk-6nwKuf=Iyg} zv&6B_oC|WgULKFUCl?O9dYQl0|FX%X5D> zzWU7LdTz1zejR~A-*toz$i-v-C=c)#GgNS{;M?ZOP5&#ksecxtO~vtS((EfaNX&G@ z`Qg*Ya7EJ((YVU!b;(@O;^G5e1rrFDRx;(GE;gcgi^Jxr>*k@`KAmv{PkB~$Ii`rJ zvG<#CCXf^Q11!vU$9G*h_7z{RHg82HS6`psi^_M&>Yo%gniUJFbvg}X0zcL43P5Zx z??9kdXt<~2QPehknE8gz zH#$M*kovO(Dif z*|-l|O~{`m-u?YxKR}m7HDDa)PQ0D-1Q$s&Tp;US&MuTsDG|P|MPRC(w(&RpE<^Nl zaL)Xuzb!TfPf4!L=sQz?U$1^LAbJY6k4Fk_qK5OS>wF$|dfv8Y!fbMYf~-bA><@G= zSG?LL(o?$aA<~EBDZL$*!$I`Us@sAlVB9qn+za5!Gf%2B532c>LjRB1j8a#P*b&4| z2x5ZO^goj~ocl+$7y6k5duc4nqKY{>ura`n{9nPq*FX48g(4xEur8Sy2e|&VL-b#GT zpM-lN2@uAiW2X-rOcql!tJC^IPDq%ZmGyjax&7SiNZ<(e6Lx>?|5Wuc3>u~xyAC@0BzJ7xqF`Z`Z?E+kdMkk#Zt%(<&?aC`f;1#!5<3fgtd|gRk27? z$lQQ`2ntD@e7`sgOS?4iaFKqCNkH)i3%8G(bMq_sw2K{c+W6!9bNacqGux?$GhOjk z2o6_&^DH0jc*@MTw(scVn+N~c1^+y!_=0{`j&bhzjkk{KR?T<9O>?tC0&!Ch39Pi#q{ zs^IzL_ZI@9zx{7#CTwlLzC4G8M1?~PFwE?I9}_dRm$t~yG7~v zEqHBVY+(UCz+yPj<--@5`^VYC1KA))`ur4{xKrCl==p5-Mf#vVqq+GE)ZuY_m{GrY z4vc-@UBcAZbHod0=-r5;$0$_}zOv_hE5!cx3FgC61C)P0W@3bB7>{y3 zQ~}FEQ`H2tB#j@|ow9IRTrbt=-p5xFq_339bhy~jUrnb-vKB;qt{z?oG0)dJUoQ{q zfvT#G=X#Ls)!9HjuNx)TW{Can8i-~65wN=gX>~gmA#ivdd|{%TdcRiQYHC>Cc6VFn~8Jz`w z-zwL$&h3fj7&G_!34{HF>&>a>vwNPmZL(w_iq62Z@!LT5b%w0PCa;iV_74uRah2~b zYRU$5!;^#2+C>jz&|~#y1Gth3FwGmtT>C28bK!lAhz{ged}of$EAw+JPjXgF-+-cP z8UjJ{9rqL$aNGBT0XNgANnIG~YdP>UhF&Oz=SYYM!l^5_3bnm@cEL!&VK4-m1q0)*7V%_rVe5&peiL0C=JF|@+~pE81KYabFpOEqCj!W!1aZt z!}@%O@%fHinbKv>{m*R0-;2?7+B9NnX09+pI21MX#-Pv_MCWs}{!SdqlzT-r&~;wh zGq5~=!O3#OeX&@ar$m@n^<_i;!%#0rIOb%fKgihVU$FpKFNJ~b^`_{&=n%Ps%{?cf zf`XI{O~USlR#aJR)xr>m_iwOj`(fehcJCqa>3tw@A&(aJ3&!0^gT0`Cz(C5OE^*2n z`eIF;o0F;Tceh1`nfF^Hz}%tk&=fXwoFeebk;3lPtZ3K*M}m@@CM!NpWVyk-qITFK zpAKQFmfT2y!fv_4eVc}L%JA&E2DYny8p;N{`7rOxK@Q9z_a7p{^z?K>F&}fMor535 zW>pO`G-=H@7jQRMC`3)pYGZ0VY&2QJ913AFDxook6?-3nUbpojQs^M(2wk4xYBhE_ z8bbIAl&=B8Vk5bf<_cIdgO`(1tn?D4Kf5A|&z|};Z|QT;t;e~BZ$jwg?~;>RvZgh= zgqkM>=K9AL_iS0@h5~P%*MlB|;CQt{D8!~`O|mH5+y-ybNOm+ZVGePJ7HuNa9(kTT zke;%XdI$w?6*hw|4q)A^(yE9`4;)cYT$-F~xm}ys-GBeJwrrYRumd)>mNm9!q-2zp z94gk^8)o@mmz+-6C*TX{F?BgFKUxmjgIr&pIDn4A?kjJP#w#^S{O5xE=H6W0cWEd; zoqJgBeVi`A{-VJ)ZYW{#ZC~SB#bUy7qexxTu>D=*Vdi1sebDHY`$JW`18-2&ya{3q zf`cG)BE0(kkTHnb`~SE?{OxzI5yAfoZIOT?l%HuSZ|njb5O0f~*vb%sC6+wPeUMjf za>}hB^|iU8gSLz|d|@HRQcy4U&?n;f5i{1mU%1_7BFB?iTTKw8;r?vNwEUsLN)R2{ zK_McA&SKd^>id$bd{3P>W>kp$^`h?wJqv0qd8Eke*&LB4W{qRh>BGr2OOxH})Zee3 z#e<^FIpb5`7Hbs!?sKY?LAc0|QTu`TEf9F|o0}KE9b| z3a5qmdgfJO-w4y3=dm+00Sgz$`|ypft0?F|zf@he(W=Z%4Y7DEUZl?zZXqh1ih+D$ zqa;+jm>*CgoV-KR_hI9S{N3q0=aGd{romBB4qv?t&ApT)y$H6Pr5~xiFNNN&D;`>K zmiC3nBmDfAeD|$+MvFXF?$clCz4j?!$KObeE*tZ9x$~vSQ`fz8&M$s19a=jc9YWRh z&KhR{dwFs(41EOxPLHk5EG^G%ZTC&hckLZEYh5Fs6YR+an!L|f_bj_O{w{o+TjTFr zqv@M=Xs+KT*IX2Gs4l2x7E@I0@@Puf`!$ie$E=Y$L79rLn2Mi(zjtRLVDDj9zxLLc zbI7BZ`pcmSpZqS>fqNrsg>0;=4tz=bOcsdJ#4WUA=RdNz=~6m+3hMw!Xp_vl`6j%d z6UPgPZVQWTjRSOw$04@LM}Z<@J5gfdl~_2xX4hbfXx{HU zXG&X^llw)scov>auq9pz3}Sf#BtE)9%x*)^ZOC(B%4W!L%3A_apsR5GHLEcRLs2?D zU?BW=>^1i}st4C`7zj_o4Udlu4S$>T&DH;|97M-dQC4nU>iD&&)=Mx0W-M}ezUEGJ zGn&s2Ig7>ovP7OzTo5^w5y7-cbYM$P%a1LY*2D>34L@xdXNJ%3OZ4~mO%elxd7WlVzRZf^Yh1#%cFV5vaibw zDjT*W-n=J4L1n2^wBAz)Y9(4n24<%gc5^n#;Owog9`mM`AQr8`qF4O7O{w5oUl}eXU0wYdsIx*s%#b>;jB!%xKRk z0fus!i8Wa$rmmdIgX&fuOY)ZPgzi!YJ5O1{Ti5t@-X79@$2@a~_xT39NXFmmhCk+o@ zJvD7{ey&i(>SOTNlBq*C{Mdd|XSgp37ZjV(Vs3JxQ`i24q*U?!K-LyB{;Y52Y9C47 zUvrI%lk0Fh9axiy%n=goT5G)yRTAM^Zc$+&0g*wbO)ngntOgUvHa9m>!o?X^7sDo>V(f!{e6;k3l4Ij3X-p5SPc2iVB~WVK_3jvrKd*#5@Y0 zKW#oFLpO_)O?YLJIWKjsJ3l)N=1?W~K34rZAeU`JH+q4k9{sup^Y5DM_ z`z-7D=&bl^FL~=Md8O;NWo5RWuO7C#0-Ki7*!@YbTTY1}8bdFOa#`NvSf+1dib-e|ad<2%f z_;gxVG6ckI#SDGzAq;cNH@Zsgu?5whn7$Dd_i;k-xI;YGE~0CVfn}o3nogp@8k)fq zhZ?~X3oxC~G_L1xTN9Tk8zP=cYHI?>4D*7-Pl zOkskNOvSEWIa3n{aHq7z{r?KGg-=-bVU04_)VrdX;HXxwb=2Q)e9)CnV|jbnkJoJ? zCd>)~P7f$Z4bTkm=ZEru7INL2DRId1{O^{+Q3<|&RB?56ZSP1{GF3TB{4QRkGmyPw zI&g@`aS}r$b@-`KS&~U@CYJV~1Sf6=*=HE8$|^*MSInPJY=Vq-7l4CG38C6i+%POf zT2k#hA^P4FkzGP%Udkq8^W~jpC-WvGP&mY$N=P-PiP$i7^p1AQ$%3?TZeOpno~gTT zyKwEEt0E7{2koiuRzgYTVY9#ZpN@B)e(5-=@*}l>(L3?iKj|d16SS*Zt*p{xvIl>$ z12fsLjxmA9#=r^+Lh=foqEfiOmd?)*!-7B;CT5 zo=#F}2!L)I<{6dykZbj!(R9IYYd0*6&|%Zx@RW%#hhvdr8!G93osY^yelQvHK{O^8Ka;MG{)WxT zX*xultU+!u>feui*An!+oeB`<;#`8``o+z~3>;Y!1s|ZP%Z6Onj>G zG6+{ieJ~IiQpwL~+ym~5UmH`Q;oE4vydzV5voif*Q(V{}J=3MLZ<-zg**Eq4TlW4# zgUn;|+*3=->%#+!JqxoF1K{O>@#W?DnUVQF%S)@v(_<)t`d=#b#;f$kzpToS3y#Tm zj#0s1HL>0{vST*XqyLi)cHO&Id~4D?)7zyJ>*W$-kKclVE>@b8@`M$=HIqN4)Tq0-Yf%IdioiHWY z_oHpt4_8lTTPu+aXSt%n{40dcAIQ9kaNLPVt`&n#YM`jar48)jcxyECeJHs_t@E1} zX>awhH1G~`ARU_d{2Owc`)j(12Eh#ZcgVDhUF)p>6UT!`VZQd>odw6)h%@B;u~O#W z%iN5F(P+|!@u$)Su~Q_AS))1eqbJ~(CCrgEdT}rNU4AsA^x$hOaDE;5bSC8F7MXFI ziYWp%YMAb}D%u=iGRkjH+9&W{58TM{B|Oqv@6WqOfqPR3g0WxgAUenZfkd#Q)rRoc z%Q_?%YK%Ey+2D&0i^k2$N)5v&!Wh>@svN$e zTfg3x!76``AWo7dNgkLUX0VGPPUDYOQs4eQpp4vmTN`>?3w!JA?|#{N8=W)HW{>wH zW`uS;1+(A$o)6yhLR&NTV*;%{vabvO##(fD)}T0sj{y0F2ePZ@mFw^8-ywF0yntQ| z#&-MErWK}Bg<$w4UfsVfIy&vzT5Z}|Ug=rB+Ubv4>F0Hg$Zo$;JzS7n-Pca{@VAbL z54N_>4-Rb4e~%9DDl03Gy*_6+JM#?>PvYPtQ-5^G69ElQ4zBkPuk8Oz8wAXPcc3XJ3R0%*gclRoTBFC>I*V>R=lR#Ak2dyH z%fD%$Yv6c*_RZ@64TF_}Azq=7ZCDLIe4>E8#f&&h)B=m%jy)7nyx5ubtB+8`YEZ$# z9L`76q{-}dVyuqE)tNI-(DO9`2;y>$m`6AC<$ub+qC5>6oT4JPBY*vmSijq<6?E~i;ix1kZ2WK>Z>NhwZa_~`av375GnhQp`S-SUZm$yf1Ujr%{ZsPHq60nhtPP8+DoYmZ9)eco8CQ#J5nks%xLPXB{p z&}R+WRol^H^zeaS>|Sm(fQhZybU&12iC?ZVoES(y1_qKdc-fN&@RJT-_&FaXp|_mf zW-?-V?n(I%k2WoB9G;&=0y=L)mEz-?3A60DSeF}mL_H4^f@_0+M4xGV@0|x0RQGWG zMx&Mp^_;k#B&;5kYC&(irFiZ61PeCcH4?+e$G<;cclnDpzH1z(PQKBq<#+w^tL zmz4Yf9c2z$ahq_i+3Xl=#a&z=F-nC?jEb-%V{Re7V)8Yu44=)GD;Jxtv;E0yshzDc&5ke~0?4Lq$lSdBU)YWJk)cRy(DNs<~%X~*vS&DBA zMa#;QPi5gn9m~$h9$&vE;@GA)_Lr2^sw^_=3;R51DNhAZfS{xaFy(X!Tp0-%Njyme zSP*F`q-jME5k-6jL$SN&<|YB&bGWPf(ASzqt^D5x6aUNly~ITXIpaI-I-j zdzu53l%D!>?i4z)68%4atr1PR?|M#wk%UEULt=vK*Bg-1|0CEL6vMkt7=VE?l{IMj z15;^Hm=`{m(j71nB-^`Pzd?P$g>^HF%5ToUj^zw}d-L(z_H3+?-u(_Kb`+K$7sLNg z?xN?H{vmg;VUZ&{#}XD7-`p^wC+1n6H|W27icyX$RR}b=p!>Kv_edhvjw#Uc6ku<0 zKYZTx_At91c(G}j#T^K@GdSgx9K1OY*w@_$)mA^J?73a~BbdrQ2H4J#%J)8vw!$j}eGufZo;pfCvC#f8z`i66`ul~&Uh%n?D zugW%~rdh&EwzI;sN>XQ&VaX~?l~+Ll4#9KGb!cY_^Ucl?B=3wN|EX88yP{+;&SwjN zO5qKXCGIZ9Fv`6xoLRO_F5aCEDFlWT(pf*MO1i6AN|=aHxrXJ|?4&Bi9yc&OgnK-h z5O_AF6xkU!PqW2kCS~qh%e-6HyqBlbP#`PdB>`Ou#7KSSiQwu9F=j=viMwFM zCb0x=O5at|d*3#)i%Z^(yN?vVEvrr~$?2bSt- z?@v*$FMU8C&IoGf23LAU7JK-|ChkhxuUjc=bP-=EC@`b=cPRoFFzJ*Rb~n-%?^E9- zf|-k`Ym5r>uSL#r53m)1qE;K+oBPWA@GwoPh-3SkhY|OCH~|=b(0=G7-XGvzP>8KC zFY<=uL`(C(8j499z%avgAVp36M-gZL51mtMN*u#YBou^62`&09<3^66BvBYulHy*L z(dtPdncM0#&SEsF==uy0hSX30QbJP@=3IVSLP*9$k8dUIc4e-7rc7C;YwvT#yPaUPn~`-8r%v@xdph^2k<&lJ*4$c z3F`UAyGpaJ>N_VYe#=laWzB2_6~bK=%muI+lr=FasglvMAHzRS&&2dCw8a_AZdT^I zi0L$c9PH58NY!z=R?xD?F<24Tu_|KmaM|PcR!u~zMUcc*l(2rRPcN+ivXCMPZfs+3 zoM4?C;U4WZ49=S80Y?om2f@`%#?0L9`llFP{A4%I0z1dff7g%mL6Zg)=U7SrO3Zsv zOaPRprwA=jRC7^(Vxl|N!`C;`#|NRU6~46{p@sihK;VII=~(~-$N|+e4KWk6LlOAq zz_jWi-L<;%vc8xpk7We)oxiTrPj%G^tZP8Q`?yuJk`W}GF@a8W- zw`=I;9y$U^fxV-xY30geQyU!IQbHH}AC~Kt?N@R^|7HjOUI!8q5?o@YW54;C`T37& zwL|g6%W2f#Us!<_*{Wq`X^h(DIAd4H7E8RHdc&pZW*QT}D>$N5AP{nV z2mrbVbng^+X4YX{!qk+wmw7=^#NvSth@7;XT-O>YgADg>U7DZ{vjl!Au%?0Iy;$5j3?7M^4C-E?poOTGpGXw3SNXrxzt%kw@?z;BDM&l$PY3lo-z<^eP|> z=)U6`Y7!j*0#T#%&qH{f9UH4GZPA)uSzlXOSzz9u`CXj(A}nkZ22ew``Smcb+EFg! zu=@$9(_fXbvoLXyNG$Ar(@c=@c`U)u61Ksny`qu2!fD0)ES{>i}&Y9-; z=mRw3a`6l1{_v+j@%7Y@5qzAV8Cc@}V~6r$M<9|BEbUt;gegGa1;M&C{`=CHOv ziX9i5v~4zu6$$c%=hezP@4sYs?gIEb&-ULodb;%sgbkp{9iYz~x&zl36RQ=nI{`U4 zHJv#I!6D%6VEEv$mi933wv#&#{dnLgGyy@rD1o@2BiZWhgO}0Gp}x$1D0_9-?%H2N zw`V6Jx8B3UWyF0dcpsFP{qhL|H{Z=d7Kjoa5<^!qg~`c07qq0`n0hvIw|CG>VzNUr zj$<(N6qT~xb3kwRzoL?&qW-jT_jXUlY{t{F<_v|;$ol-LKd&k9?zu*u+uzKpczve{ z8SZ<|y)HP8JGia7ZrUhxxW>c?jqF)_{K<{$VSVqFt@!@*5b8arnwy!a>7FZFt{Yie zwx>%NbEbN@9;KMv#Nn2taF2;uXXDLRcs1f=+k6JfRvlyTjElWjPO;cK*}r|VHU7@0 zuYJ#r>kf=^KykEb_%xw6&<%Mv^e3NIK&K0y4xzN|-<1d=4Z`71xI`((Xc zl|KTR4*_I_&GxJ2e6C_r(^1Z|Ap>#c18g@zDBjl$90Lr_~7`)lOnq4r`hgk(G{mxv;nFmqT9t} zS|sJ#6y(|@6gm_V+Qs5J|1W69H$%?I_Av5Z&yE!+{4A_G=BIXGt;P{`HoYx{l_Pn+ zxwyengZU_^L(!$C0HBffl)U*DTGMzKA`%kFp;92f`;(iirK7H=p{{PlPwDNg3jU05 zDY!uEKUU`P93P9{ljx2HptfQl7{=Llt|OD)V4SnYB_{Tuf4^w~$g@~1%YehzuM!fY z#m8Tiroziyz0hE6a6cPBy!YZR|8Ty&1&Wt_a zDm;-YHO(ZFv+B!$j6+qXDxVY!aS=fr2kL+}ylLf&ipLvavr~v>F)l8nxA}Hu%m*oN zg1zwKk3=~x*SlcHSnRGMxS08u#~ zOrA?w;BeN%Tn(L|_;!v<*FDWPw8Zvz9vMzcIX&{Y9Om%FvLftx;}bF-+c|5DOf{m~NEU5}X1wPy%zYUd+XB7LkVy2R{W8 zrM?JKzLja-m^NPwY8LK#{Lr9Ex8IYOv$BC!8J1*Rx@GW)&s66dX2>(cQ>sIm9}7YD zIYZ*}4N6~98Y88&KvSucre_(TZDl!U4|jp+ax{ObL;LuO$QF%L$58~KZ@{^Hi8fNmx21IP|H96kEkJjPxjcWDG<*SXl7N1_tkR(k$=b3tOL0Z>Wact zLIP|rocLH7lfdRSu%Bite16S>w{JKy{Zx3Lci#;A4n{|C+x*;IT;Im9Zae>q&dO&& zqDHYW20x%^ypDE}R7583BH*I@`CvDv4+j3JwUS#QDry^FI@*>)_p<-BQpX)9pdjnf zBMee63v-n$`%SIYT8#x+rEO(rCEidO?(E2{U70u|%`cWCkdI_^_E?nZvkjLM3v;XkArL6 z;H}A;RNrny67+&&MaXdA+brYM^ZO3!-`2Cg)*+jiQAZJbsdu>v6K6C9*$MbuxTPh$ z_8c0F64u4&-GLE*@$6gl*w}M4GsY_9vDJZ`^Cb+#GxO|QE;SFp+EmCdhv!(?w`;Re zK=~ez>>)|X4r0nq)%Pn4y(^2a4XcxeUj5TeY>iB4_Nca9;X>4wr8;yt>|5zb9Di&H zjWq$a3_quyln&7<)JB=7Sz|2@;b-U4l79-~@p)!nn4m%Y;x0_!QUsN6t$sgUTIu(} zF?eLAmz#G?u^KWA^VJ-w67pfnWrAzvKk*R3#pIm(ylm?dlr(n+tQA=A2JvfxG z03R{}e^?4%{l$T&BbHZ7cVxOF6zyjCgm*k;xMijIMdtVbl07Kk&?r%5TVz7H`@wVP z-v9i#>XfWdxBnp({uioHX>5wh^iI+i}n1DqgieON3 z**MoDQDkv&zRP5?iNj)tQ-Q-@aRLE_vnroKHyX#KqmHO3iL+F)(MD_@x5h73mH+*K zj$SMyp1w;kPR+zx_K5sQ6iFB->)k15owS(xar8v18vlXVZ&mQK`;Q-uLn9fM1f|yA zLI-Hx`uvaPY74#RH5BNA*c+rfHmBQGo2O=Y8+$F2D~M(Q;3CWYpH2Z(I5IdC7iYL> zbW)gXyVsMdj>_@XanF{(TZP}@{cKA66~2BHc}NM608iqE4-V@r6EI>ZYLEvMhAm>6 zBYtk;wq8UMefAO{=H39awQ3oRiL7RL?HIQy}FE!EKhTA>t_m z2iJ=av!y|&=)9Qfp7`G=lnwu#cqX5`6VG2A*oUVCHep;q_OL)hswZH)6C?@p38h#BwOEs~JC#Y7#jta6b(L*dgf&0W{kklqEsg9U z$-zVnl|5dQxR)t5nKIA)2kPKwbkrIv5%lXfZsw=wunote4hm`A076?|fwd-fWo2Q^NC?s>F}J%k5T|CjLhVL1Uy#fdpijz)zs89whqyc(Ncr6yyb z?sR6VR6JVQ2X&iAa~;KD(h4mJmUNn3#VuDF=T;lL1ngvKnD-+8vFB@DVmU<$o5WgtiFYeIr0 zY=C>OlODpJI!p~g(&A&B*I9-pti+KgY9EtTY^wQLB1~JJDo7o zCN&&ej&<0Mu$$1!y-K?OfJ_SRXhU_N{S9$0O!w(CCBpBbpS~N&b^l)T(JuW{^T8$r zoXqXkcxYwWz2UZXnl3B9aBWm#S&-JZiR#iLa7QNc{(uAhhTT{HIwoN1OjspUbN>Qx6Sr3kE(f& zCd}2JU%sxaem$OPG4a-`<$T9z$~by7*Xy4pgC0E))WhUeqSpsBpGUdQj$Prm%dgU;a4z+HJF3S-|$`Lu-l`M|`0FVF7 zyMH)(vch99E@o~jy7?e-jKco%b!T{rb{)ZB{n(kR@6j#jwOM!pvGdElm(Iy$?b@*= zXmQ;d!n_CzefIpK7YrZn%^Tjfj3}^zAh2}eT=%drdMTcM4|bIJIC%m9dC~2T6xw6_MzZo9!H(+u`I%PYTgd*Lb%>gBJ1>qScxI<#&UKBVkN!G{N zv=v#|(Osr1O|tinc_t>kU)hqmM-JNLXI=u!m-ScklOxr6YGfj%%kPLyqg}=y)ZHJz z&18FAb((#_N~EB_$a@r{Lk-<3p}HLTy>t89%v`i#h8FIv9tO8m>Ef5+MOzx^Z@+<+ z%-+bGSpOmepG zh2Ae0``&~wjVol7w7&#k{Cz+L83&zhqzzcUOnbCENUZh~pG*jhh0L%$Wc8WfIw_q^ z!k~x3c++1;~no$@Ocd-FtqONyD%(5XtsEw(0?!_TMZ11dFIV zKDgQU-QWHoASq<#`Kk|H=Iet;;0#X@KtvEAvxxf4PJhNOeD;y~j8u4&lx_Fp?vlCA zfV#yVy2paO!-BrUg1^BYtizLp!xL=4GLErLG1RBiGD>thWjI1nJeKWVY7>qun7!(L3bPN<&Yh$M|B(~Qkw9h56UWntI#Ne?-{Jb3SQPhwKGrO?I%-X>&v0namASS&Xv?pn(F9;8m;U(#3z=rW-uE_l?Qr3$xWOyPPgWLX ziSd5i8B11$PP^kLyKi3Jx5hu|5E`JMF71njNzsrcQS4d>uQwbR;?=Trr{ygOYea~M=d z2*e{yCUZ=;*ZJVa?Jq%>(PjrNd?~BG2%Q`+g$K zzC%pONph4~@|c<4ej=5^8!Hu`;tOqD7IeBP9E~e#iy2s{tlAGNchL%715#{8Q9cU39Ym?gO z65DALTNw~0hK!+qYoHAMe)?NpU{z3rF5{fwu%467n`8N<*J^adcsJ7UQ)FjiiEaJ2v zNR$oN>Ijj=5@)AYFN5x$<|}2{vzP|xqZo5+MVe%aV}DfMrzp_3k++WwPZ{@DOxKTJ zn5~J^IOTkP6BfBDIRCtmYEZsz*M0XxGBG0hbX_Y(&59y1cvlQ7j0v+xl&_Yn=v z6+iPHKl2$e)g}V{Ar0Rn!skZ>>Rkld(`w3B;Y14Sf$d*6w8}HNL+piSOSLnVgfcW{ zVlS4Ps||xEKO-x55aqq_kp5re>(PJVlZv(P)32Q;@X+aALRM42+u4`I=z`!6m7-Eo zQo15fH-AbPaT%&WKwy1It9sx5mWt_V-I77SU92TI$fh^28FUw8Q0Wu_yW zcO53gFmW3n@1bAgj|+)&Nqj>kLbnI!8Y zN9*5?Q$IJx79PP1o8)RX4KZqO;jy@);Aij=%MRcQyTjy^_WhFoQD<@BU0+#ft@wxr zvWYRQ+ENG9f9M^1p^_3~;Zq78!J04r{UQ2bH7X7fSWSK-ykkvtWJP>p^ufxK=){P? z6BLN zQjlxyRcxpALU^I=_^x2!$WvOS9v{N29UtXi6VomQI^>JPO(acWf@_xm4+$QouD zc4z+|)Y{$FnPxZ&5bSv4pv4CZDb})`i_nSRt&{;*U6oaVzk1K4-Xma?x@m610H)q(SGy>^J+TZyNvf9qkr&ZV*~s~-VB?wve95aHb-ba z;A`gv=wBtNW)9L18{?U?q2#im&ZIxe+^q82czoh}{cyWPy;ncjapUA{VWz6lu;r~c z7#lW`{Z-OHe0J1J+adcCtNwZUoSZwJNI7D`E`5oI}r(iHp# zVbL)B&)?!pWvNTk@zkusQgr+u@Q59j(AuqHe$*6p5Yn2(naoB^TT{2hA-hcAR(Pcv z@-dcY;d7OS#hboFP&t4pD{R72MdbbY#2E?Aegkv_yuE>#zOpj@&W<*{24lZ9)nm^X5;2W;qm4&nrY^_0 zgrc*}+zCUXjNh=B1mty+L0DrqJep8Ai^hFea zOl%3?sJdEC@4&UAAM#|avfThzgm0|=aTTh^m{S2uDg zGkiI{P4~q*ZF@o@fFJ(pO!TBb`H+WsXBDLJu!He&PsZOykeN2rK!r|p@f$rMU*sy) z?p;H)FMshX*uM7_Y%0;YEde%tC8PGYFY1i>;jm;R0Me8?-!NJ^L`N|(=P1|GNv4%F zlapgN|4gLNPc*Zo=B!fK!YcN_o{&e<8~CArG6C-6r=bKW?SB`E{<{j1+)5(EhEec3 zcU}d(24r3S0w}~a6O%ZyhiL}8onpmt2O8DghLSWpx_ay7i~8%Ij{AAwWbsdZoVjUc z0l58tTzyk`rCXPE#j4o0ZQHhOR&29k+qP{~uw&b{txEFebbtLn-Dh3C7yD-Ii81Dw zWA)%!dNI$vnUz7^uz(l1&FdHJNv`SI$BoL%!H`H(4(P!UJF)8zK=*|dA z+f$u@t~3{AW$v$#)Z@ItM_?^=R?}#wsMJj&Of6=GDUhP=wMo(D8KBHKOtyGAWcD6p z=@pu+#@SbiX{Z!y8*yM1TNF`gRWQ>ktIZ4_SK0`KEJQ6Z11+P}3rf)D|6{2D#c0Lo ztK*M(zyfz)$K>pw`!*j;Jxuqz9p8}^w)tah{BHVt90LE&rMH29b2AbDa(>*1w=ogx zp_755{QegcNO4ezcu1x|XDqoj0wufQ>9eJ#UVU?{XKOS*;50lR^KfS3W#?dIX5r>! zVPs`tC#}NGrb?P~j&p09X#sGFn7E!!!I|FyrAj4+7FMATjhG&ofa0r&R7(($h&?#x_F?G~HQl@SZ#4Me#9zP^J(cvu;k z9&RTrd7`efGmMKN6`%oGTqH6`F7Rkq)pmxhETJA2Gr7l*AIPFsEM& zudY}lI@G9EE}KGgY{*TM?s2bXe+#nestD*tp`;CH2BxGHumm?rI;BLZA=2}bV&Wsn zF;SiVc7PcUM4;SvKy_05$~sb|)HLn$OF2AQzHB1}vr<#yr4;Z#PAG7Y)Z-_wS%{!& z`s)Z=D}!I1hof>dxQL}VNm6SS6@p=u5^Hazf;8a_<57~#R-6X}_Ld+1sU@Vjvv00J zK?=($SB_aqOx|9GiRh~qs2({Zkp8<}q8NpHRmNRZJWr1&oCDi==5QS0{Zg=H9o}z)cmX*Fy0acX6J4jLIw%XA>SA>4L#L7>sK$kedQ#i7E* zx5LF&DPOhON;TW5LjC?M2TXPQ?@OGkjghUlji-gmDlgL<+s5|7_S88-V(|hOTt$>} z@&N6)0ct4|3=*ct?Zk$=E1rG6`&1;y)&0u9a7Ot*ID-L&FMzJZA=43>TKE~~^`(*D zcJnU0epc*7j{p(`WZ-*8{_`PmGZao`LFMuB+10JNRZ0Yysysv(J0-G2k9FEvvVeIK};}O>{^3zT?B1Znykq|U6>5V zGJ74tbY-2vBj5)~<3S)0(!z68TzDygdJYP_5sS%0UX&Hi{#j8HqB>t-rAsAfHmEwI ztdfsNtB|J*g`@u{%4p#)5Rbajtl^>;;Uqe ztPCa6BE0m&kwL2kfm-_6E1vxz{V*2KU6qG?ts0xVO zZKZb^4IFBkn0WP4xSe~Ii91c($Df^6!3s0Fxv)$^6rf5h4a!Sr(BFuJ;U!e+eTYD@N6B%nj2$d@Z0?M3?7=DJS1n!DvXHoH^yP!cVM?|W06ei{^Z z^Hehx^VFh#SuN`w6DDSJI45>k5RR5pkzQ_FBFmm=o*D49b9C4S#_203xX^Nj8V203 zBBljwNeT=$H^u;xw0D%jvNUdQHbpF{X@k#Zo}%AKW2qBgKvq`F8$IoXl>?6qL#~-T zzkyX<=c^c@6~{7hQxWj>112MNNe~w?cu~2dQ2@Ehh_urrL!FIYh$f86xjYM9{p@K- z)k-9}v0o~>%~MOG`df!(k0_;Atff$iv=DP!@jESyPwBMsZSU%Bk5A>40gh3yBBzMB zRP%BE;*Ao{F+sWTXi+9S-U<3)o$m~Ops8Iw&DR&Pmam2J+3a&a^Hu&Tg;b32p2@!1 zlm+@Xpm06|G%1G6QJ>qg2%VqLKO0AII;wzWpW+hKN-jh%ZIDI|8Lc$@UfI1Gi@S1g zf});T>TePfKWBh?^e!NjF^G5Nlg`_YyS+7fYaVc+#=xCP0S%-GAXA<>-~#PwXsf4a z<)g2>dTo5GVo@=}B4dWl%xU@8SY+{^au-keC(a$TBTfR@gpZ!7xo*4blsHcSAVvld z1AHx_LK+y^)VYj@_P+1EQ>$kjL`+S^Y^m!)ThQawr%U45B1W+~CCg5XlC8-wd(?zL z9+;P-_RdMoj6pHezNDMsB#~XnFpI56We0&m@oz_EBN2s`k?NLJO#vnd9eczS-qixW zq`6Oz+4g8^UDH%pL?|!{Qz|r*snm*VSamL|^28>E%2p4*>zE_+Su;*IMP=?0aneKq zl&*3crrv5+oy<6W_95!Dy>yA7J1O&yIN?k37^Pl%f-cV(1y0E!{QSLm`jGL&9-`+g zLbE}f&Og+CAL_Gl#P6G-{ewG7vF6{(>;u)AM#8`?G{ZfYL?0{Cx+jD1P-8=7DmI_p za|ceZ^`&9$)4{BV)harj{PE<}Gms6IRsd3qsiZXV*|k~(@=Iw_~CL?OUXg=|Ri zRR+q^79&QCrj5Owtu-W66ab6Z!tK4@M^sHzL`7vhM6H=)jx0RfFQCng=6kdMsyB9qEA1}CrBLun43;NkW0WX_ zo(;|_wKbG)ooS{`3PIaTzIoNw!5R%X8hRfZwDtOFDvbR{MjG0S%0 zgJY0EnmEI|dqt}$3|hCS40gG3^@cH(DkjoG=PWfIF-jbAbXg_@nEtor$+lds5&qtS znkGbm2kOR{yU1SgM%eXO0-Z8K{jVsb;$T)%ba_{{*k=k)PwnDO+J_o5o!A10x<;$I z#=2VG+FIt)R^rl9=Gu2vUpKcul~?^W>I6f&xT3hfUaRjmQc8}QG*dJ(D3glr2d7v1 zS-{zo0aB#%%C`q7EOf;^jO{IK`M`+kYFOGjNq2jqo?35>Nli_INe?dv6DxzqvrzOz z`}!h--R?Mz|2GKD7yb#t078Ibh{$gY`CZhMg)B}tm;0mGzQOI4nL&)RNLcE)-QSa$ zY&NSG*ho6M`rkV;v&-xAt7}9iO4=S#>04^DZiywrWxq_PM=70THL9B&1*CW+3XZLMjlTDiB~=)qru+57ScB5lBNnNKqfjQa?olzl+6a+FF5XTK=J9HFR0d zFF-fNh_1ssOC_+?h+_qY!6G?=MRF7$d9_ko#c9G(>7@|<{#e|DLt+BEKQA7hj@*Ra z7g(|aHlIagASh+_`R!g3cA_{~$tmTRzjq)HBgIm1 z$7#W?dzb+F+5+~G%C;xPT#}m>E8UyuYAU}s#2~V#swn)Vfo#^3`_1ucoLFU?t698p zs|eS6hIWwnR1emaRKQs(nk(C?OCNoWZ+VF~r*FP~IorgNC(4{1b&ir{$~KHXk-Tr3 zFb(0kWo~h=>_D^81^u###kNHO0XWZRDhLbdmZ>jrBjM)LiO|>Vr*ESgq4b;FuQ$H) zLgZfDb6tCQyO0L>9KG&)WKVbJQXisAys(3AWb$17f_tWm^opHomp9!eX1-P|bCW2_ z>~5<7@K%67Kg^e>2Vy-hTa!RPn6uy`7+y0{=Nb)1vWlH+sbB4p-%x&gdn1OMk3-D`LiY;kn2_A&T` z>48H}3mB9~)80tGvTyr5dwqTe`1Sz#&gc;c_-h9Q_TD~jcYVD>z~je6#agmbg@_E6 zAc*>q6be}+G|2ApMaEei=2zojC=l%M!-*xv&$9t8F^*kj+U7Y&&zWvJX#Ubs<7+6S z`mndL6U+gc}TE7*ueOAK+-WJ zT!>24c#XgeQw6B~22(`alUAvU+2z662a0i;$OLcKrAaJBN}BUG%cB+qD198drV3 z!m&`h)^u;|KBP_@Tq{*l#<>hbj&{7pOdc8&+32ZEh#?O#eBnccv$Q!^4?wtWgm8Bn zEN@ZNXksL1X9tJm1pb?EJ%(o+lwW__rKW1AQ-0r`C{_IjEFEy0OzU>mm2^c4H`VKT)h%3l=48U3@p*UW!a|nBXT<{k~09dXVE? zeB;N6D5IljU|skQ8R7;_ptdL^LSxesSxt*#WM2kF!0_NDCQ-tW-WL~CKZzYwjx*SI%AEqBURb(%!>h(nWV(s zJ}5?8e}_LcYK`tP2{R_f{T>hQkgex{a&v5wlwFx-reao zSTu2#w63VciN_Mplp0-N(hFyIPquqO}$D|?LIr=ay4A{BKC6;(d0%vomWovVK= znwYV0s?5ksSO3_$I%?|%#$Y-eH&wZaEm<9?S=T+Eu(sD}kJe#J9)xmct^Eziab#|s zq!W-G4O1TDt4y#}VgR5DPo^lt=@kL2|Fz`GE-84N9 z%=zLcylv$#zzfX@`1&!te*1j&u4BKi^Un6JBR_8CjBH;=zZu9Mgm(7xuV6lXiW6S@ zxt|Qc!(YBIm@JFud-45TeMtjiqm@{^ zGm`sH-y7U_#faJtL-+awZ1TufI>k}Z1~*9R%!0Pq7aeaAR0xF9%;a6c8sPnWU+zuYM-C4JFE{dN#^qI{nqs>)HKzwq>@%c?PcV49r0u`eT8Aa`RITGiBaqKdOIZ%m(5Kiz_*6@v>T&f$G3(F-mt%KFaNL1 z8}wh9x1|S;0s%=n_*tpLfdwNV-t}zH?ehuWJFxeO9QB0W4hZK4ho3+8y6R(mNw4jA zla)cg_s5sNz}Js2{m(u0n=86#p^9Y+6(YRUjeRu&)b%|zf&$?{C;U(1V0oAhosB0+ z-(ikkpQv@VeoJg4Hh89t-x!m|Rcyqq<5VIO5)N>LfQi6Z#r06hYT}|6Nv+nz&}{U? zU)~F#odbhGKrk8cPc$P8<46+?kj)$L`g?AZjaoWQp47Ybi1+p=x*WkB?7G`O|lDJ6IQj!01g8Ax+W~s)PvZ|!U*e)|gQ31jN^HZ5s zkUG-jcNxMth#IjaYkmc8Q&+wps2QqlH4o0C7szG7aeIF6qs{7$Vs;|Uy8=l4P%ys? zB_=}OiTPJ>KmXLsz`K5%Ti{;a;u~<_eF4!6^4x)3j1WuYVAjF}e5cr}S-jZSiw)G< z+p7^>uL7P`cRyi){~Q}UYxs?Sg755XEM|(%M4rAN74*EgDQ|Qc@7rvY-uDpsZ%eHH z&DmOOG<4IL{MWzuec6&9lcPk{O$K`(3k|DrK-$t!7ef!t7`09&9 z=MU54!b_f}=*WgTL^QOqF|lL7^drddDagX2Ej&QZ|`6ZS;ee zQmr*7kmEy(udzCV;2;1yn(F3Qw;SR8*6YBbIuqq&$Ug8yRt~SZ0r#RxYKociH$qO( zlw`EVNYr{N`)#UF*TmGKGl6vT_V#AKq96U+hY3YBh_R@d$eS*rfMF}pQ z&kW;>dyey+f0#dSS;YOT9=iWi56iuEw$|3>qDB5Nf@;t=!^qd3p1eMWzPEJU>*9aW z^7VXCqnuP`W_~i9V(+vNeD%?;Z@s3PZ06U0wdWR zcyQ~rFRRGN8)pYCSsmA9E7K2}Fg1TAUckZht`iE$hR?9^^%IJ84z5^2ZISkKf?`lZ zu#R4^&2`f5&_a!oUZy7-A0Ku*84w6f-E@|&;>wvtX0I6#E@MI^7k0;r)tL^oH8A!U zLP;HxRIf?Y;ubDPGoKA(x)?WgAUa+`7H>(|)!Q4&A-W36YGWBpmSGzI)x&#(s`vz| zdc&*8Pg+r3mk;Rt{bnvUcyBx>T4?mAEG;{Zb9k^saH}nbxxf&mfwR)1zm9i_kMfa`B&qC-BpFW??(&xaY8>F+oJ~s zdV2?Xe*^`!{`%V8{>wo~C|DeK${InKtn#Or$xG`U>N2CJ#t_^lVbOHonWOzu`HD&* zF?rtXs3~*h7ny5i`li{TYkMrn_sdXwIhd5Laxt07yL|>y!SygRYIsP4WCGkFE0&hm zO$E!O12W+Xa6lV?(}sq%Z#4mR^`ew@!(2wSkF1&10Ljp%h;5b<^Q=1isJoFFt6T#X zAnnn3f*komjJbA`?8Nl}B>7BtM&X#{L6I$ET{pVCds|t5V%QB%>ZFh=$3eg-cV=o; zT>YX@{<2W|DIRS7Cdy^T5R*aP3IISACT6M@ESL!&qyiEjBxq1XX{&f@I=ny_ERG{=6LV6W0v4)%6Ma|`5t}-`ua-hpdY4FqRNrdb&Qi2O1$D4wO zlZA(rhliAghLwhem4yTh^k@IT98v%LAJndg2n2<^N3;XQyiKnH{w-XnJqNrUe*DY7 z16O(f3MYmCYAe0u*say6q9Wv2p7gVuozYQ&kf_WJ>!xmC*ko_^}1cY z9@?ox8>a?`%zlTK;&CL^9SdAs1vz*PAU}(iUPMZ6A}2PGley^IU}0RxO7bEkdr~m5 zvM{moFwrtG@nVsjK}x6`7$Qt#kT4N}oXosw#VrSRh2-&i8S_|gxn70?6CEfL)Pnu$ z>Ln!j&tZ#g4n2#W&xGx2?kVnRXl%v|xf0wTsndhb8sTtzoZ|m`^x^;O`sD?z4ZEOP zId8FNrKi%#j}ZDm_x?y1=Gf=-_;a}fb%fqPx-X@*rT6}-_jA2AU8X~d3{OSc#i+$U z_~41f`rW|TCO9P4{*m~2Xv722Ms8+zZvbb54!aZN1iFn2)Lxz6N-G$g=m04{;P1q! z!@U&nNLB(vA;FLPbX9*w2dO#ZT{n40F2E@*@dCh5YJj5IGH}TV_-Np&!LeflqR!C} zjc#J_G{eL#FwLDI^2Zg*z;V(IDeTc9=>DtycGho(snflIu?}fERXtus&r=OM=}}yo~1PX$~@*lHJkS0LuMfW1|fwMlLos4u&e_pXKkcQY~^qs*(0gd`f*b zrSTPUb=-@r=rDL$ZwFR@`tQ&Z7amZ8!;|2sqqn`@TIqf_e#VvJK*(|Y_AfWE0sLue z#)%k*44;II9)*mZ3X7Twi{Zn;_95XTW9ECZ*uW=eBZE1KYei`4L2L5m#Cg#@M;%dy zyo;Fm7md>O4~=5LmD{wC|$?GeaZ*Z*k4;=}2TP>OU6@(Ki?u@)Tyoh8ID z)e_25+rvr5>PEFdA*$g`Oq^z!G|Mw+sn4^GUNAIr(`L~b@S)-1oTP~QGu+3U0gX|e zr6=qjFC3JDkC_j=l!BxUIb<~VN-yX_(sL=haCTGiSKdx}a6*~QLmowg$m8x)$Ao(f zzjV!?G(=}cgYTF5j+wa1WRQ^qAjH8&hB`u;tO zK2Sa`P>u~9GQs&NxzWmgwbK(UOaOtj*#W#j%0o-S#7pvKFL4n+!x1-8w=!|FGBJ`i zQI=MlxXE+%H;PGivxS*?%Q>LuT#?$wj=ue{gy zPu~0Eve2xS>ddlaFe?EZ{&P6NpxfsW_rxbYTzmU9C00MLw)P93GH%`fvj7FsUCtnn zUqZltyW;635NPDg#9zK|Dev<7b1&B~J1hRNt{_9kcMIpd zAFhB#2t2qJt8I#Wd`G zwdm!M{E;$>nc3NIIRgYz+BS(HDMa*=1;%#|8vDR!NvZdbgekN<6 zjl)sI>nNJt>-3L5;fcK3e z-TSqz9uy{cC~ziU+%y_(ZmA|Dmc3KB=7xhpKrr_Jpp~L8zG3> z&ESxg+5uY%V0T#u`|w$S&B?b|GamYGy8w?bRUUju$ZvlqaE|E;L@-b=Fxyq6znj-S zc77VppOc-{o4h}BBueJWUBIj5YVl{_&YQuLRr-mPcP6R80Td+|U@K5?8)M=>T+$H> zznRMPPrOtF-XU?WHb@9~K2niR5#D*Eiz=^a_|fho*$A*H=0BHMrme9K+|@=+!oSm% z5z&%@EujEcd<3Yb$V3y~ZK7dW2~ShyqXND%LP@%7+=V4Hm>TqekX+4g0jo~ok4fv! zO$XLH!zBopV_{5I4=tHjj_RR);SQ67L&AJfv0U5)A2!xotFBd08(Bx@QT|v9p6j0UeFlF+=>TA$U7)*tM8Z4)1#u3@|2|>tiOs_g{}eN1#aQd| zlQN%`MV;ly%hNycaU9Sgv>l$MFZh0s+W7o=0zkgDYN>tIV?nj=Z42x*xPiwE%JhC# zTbG=^-%jrDU>Gm$u_BLI5oc(mH&^R+y^TKaPrYxIy`IJ<5&3e@e&ra{29VS0F+LFvr5+{lJld@O}Nv5g7=D+>efB=7l z*V`qq(GL+GWFkGyi(WK29n=IJB^~6TmgoR0gS=>nV>Ch;Z}Di9Yh(j*qjhu+vBF_Z zkUK5DMW_^UKWx#6ej!aH0beU2nXw!kB0i0eejXbKQj#jlTbnm+*mWm<95@1Z5X5V{ z$whYY%hfbEYGr>&q=FtND3>l)0eMcB)!Esqd~ERXF|!dEBke{f$6o1mx*cv*N4Bfe z>rl{8lvUT*o}XO|=4m_dE}mR*SHq%Lj>m5HW+#0~9-JyeFvDF(%CDg#wtgurki+JHeQo!j`-BXeG`f3vsBEUs#-N`5vD@6nf%o0CxMmLL}vP}Dd%RTL5IM)NlM5W-0jM@1Q=cc z!!YNcA|-1p5=o6ZNI=X^(5r;pkHN4`G`hUg>%=iv_unZs;rONj5FRz&6|cf(;3${M z`((?*n6>Wa8K>?ToaDC6Qw&VHSx@e|O0b8yuFMMYeEX|JK-1)4BTW{iE5$==rEdd-UUOEqym$H*S!J;YvzyqvUx|FwwEFa2*(^juf1QB)ZI@vd9Vb zj}Rmzq#za04VZTOtTw`~-q-M$cdC_t$r@*Ei6&*FezMH}JO_ zIAD+#P%vAs=gsTC2ju);n|AyXQZn5h`*P~!Iur<%iy+V;T7_&9DxL)V5dnw>f=rWn zO6+BL7%EneRMB$9O45TLHe8~$D47t+M?m3{#B~-gW~l2GM%+a{a*V5lL~Ej;2&KWG zdvOSx@qq|7NLivA)@X*4NL8}mYFoO2s_t($yF!a&1H0zo(V>HodjobB`If(CWAIIao%huebP z`&Yr7fBmOmZmG#w(g`l3{>e!q=F*xu!OIh{AnJw@9vf?~``EmG1+#U^g*!OjVr#S# z0OGTS@tjAs_~7W{XP~&D)z3BH-`@H0RsHF!-DeG#6-)KK~}6u;>HYW zifVxS`z{$@q+)aFlFmq&g-n2z9N8OF#gz$iJ>)WR)qYrZuNMbCpf>-*e> zqRB-(G%;R}++?OWtShv78gp)$%^r(1M$e3m|1e<9Lh=$EXB!VA{@(s@HB~bvo>t|fOtp?;DG`Ei}Y2`)9s@(nLye@=Jf4*4J0N)%Nu*WydWr zsP`$c_rp}m(ac4aIF@?0EcAeB)8AI5m_>pVt6qW>vGUIk8L5qa<~-~i1xx@aQ?$sh zA3=nLlNL^#W=H1espiLMdVZg;=--J;LsKKiWr|y-g|XX5#(e&U*73Kd7_($Kfh*W% zz6nRtj#T4lxfof3 zD1~{gB&YqsfXehvd|j!&<%4sH=J|%4<$(u!04MW@z2&g(L3d&^x-!)iDbGZBZBJ4N zT`%R8C3D1Tqi{!Q$tY@~^=D_OOp4yEGrJ zQP{3S+L}&edE-}Y*Y={ye301gC<{u!@BF%Jo~pM;FZ@?)v!h1w5456G zNpKRP=rVoS8l`%lfNR0`mN(`Ivvy+Y;qJQeB(%BHeHi@nqso$!Y8bqZ) zp%3O}&_mm(2LcZXw-N$AY=WOXmHs;FaQqQYz_MG`;aBn|yqZluXxXzN>~M}-J&603 zv2`~xB`MFGB00w(;4xR;Y1Nu0=}XXZ=ju7K6?WfLB>AVYmFaU;=hD)4>v_L<-lkg` zB*~k_BXpC1g2M|dAX>oTO_L*Hg7QR2Sc&K3v$LTm@piZ$3tl}2CZ^hz&V{^af zT8JmnTwbT)> zsgUtpo$ke%7zMZ(ZnT_9o(~((!^Md)bEj#&X}Vqw-&-r+)787i<61tDE-gJB4V^UY zmS`H8vXanZ*X)^sql_dO1OibsQsl5W-AUK7bXU^Nnxgimi6O$zd#F)!vcpEk+eXCQ zq%4}D1)_2KMV)a^XU^s)GqjGg1`KsZNy7$hv}-y~U_`y=C&$LH-K-!87xW@>u$zDHU1V;*BYdR$PlL_`iE?(k5v z!h(+mJ0lS(DFth9=R>B)wp+Xor*HB1>hn_v!}#3jIi6HBhbkI(qj4{V`HbTI#<3y* z+?XiI$S9eZD2a#&DX2&e95e?|5}rE=5fd{L6*cFF*|bFwantH>h2VGEn}9l(@Q63z zFOb+vcbXAvku5REZ`YnkGoET_uysy`^Z|qyyd_RXcUCM0IE21C>({n z%k|c;mtlV2uaDy6gqN`<~dqk+_Eq}~ufj;GU$gHQO9C1_F zWq#U;uZO3hN_N`~+6r46a>iHi!p-&^##uh@=ZC|Ky~fwWV|o29 z&(FKXy}h|c^J#u<=dKJq{dx?uVHCKak*eKvRh-6%?j;(*@$9)djif>8a%!Pmn>$*nhqU3dx z6Iw;LZNw#gvMv54N7}}k9isYnE5YC5us?qI|Bw_BR8bl9W&8EzZTR`Pv3DL)N`ItB zBg^Ymf8)8-!c&H2t{l>BPlUDkSqv81Wc1e6pPoBer?!>~* zb+4lWNUuA{ni*6M-IK!!8{}c+xE~ z(plC^O3l!YQ*vneeA?^&UJRM%_JU8p4cr$e-a5Lstq~VIpWY0cXlFa1&+Y z$J4|_-7Dp?j&_9dY};s$~kJgxQ}n`C8ZiBiohEVm$SjwhhA&s&qvz%Bb?&^l zeLCe>U!GeH+=Yxyc4J;v#MNMjb|fVuWF;tVwxyl$0T5Zq@w{aT#Uj46f@XguY!`Utuz05uYU>ocg@YG#dn^3hZaBghdzF< zfIhw(e_Qn3oqkMbqflSROLc7NW7e?WM#G^i&a;SszrFy$%k(yc3^+I_I0V?QRVNl=k50og%8h1Yef5=;h29)6PkEnaVnc^3dyRy! ztuYU*+l({oX3k9>ywAXxhYV;yz|CY}O%0ECQ- zW{uO+^mKI96H~O)Q?(PcJ!$%HHfBH?tH<+=^uvHs5hC31k)gaY3Xc3nOikr21^4&e zan|M z-m)s1zUEIpTi-1*&{wC&OR#ri**&O-fwZ_)eUgePPIiG-I>5-x%FEwf*KqT2*ogjJ zqBJ~jy>Ex^UyrF9<1wk_bhhC7!I5r-3r^pi^zNirt%TyfI zOUv}Ty?7lPYkYk@d<`juk)Ae3&*?PF1^TLAOMUP}&ZHGCr1(X90(hGmE_QLVLp#=Q zi}$*li=W`x;ioKb@AE@KY777#8p?gJi1LD6uQ+p`96smAO9#S2M*T4TApa~{4rIfC zYxC>{2H&BT?;EfEJm9Ytz@~xxUyVZ|yGlS&T_}-8JBI4GIPafURdzL=L4qBNpKu}P z=YUyi?f7!?dw*aso39xCo8@ot{;9#7{6*H2L$|JP72C@W^v&UbxN%$ief2HY*j{zS zd2MPN#OW*4*%{RFBjo!NxMjAs_I_qxf9-t4^2tqdXg$;c$6S~?3_(BU=@cK|6tk4X zjUqycQiV_Sp~FGHg}wlTWEPq-prAPN0}Ikp)$pdn=6pKI=e9dm<~8-?XUd?S{4B|5 z3?1U^{d@M<-?;2FEY7RHFTVT-`4tK;3##B=D}p4ABV9yQ7Kt9}B5TsQHE6Cv9+VKmNlAncJf z+jPhUY&nuXr#r{#BC#;kX?||nvnLTS-_5`V6Qp#@oxWHHN^e>&DYuo(efLP z_nc$8`%V4EznZ1JN#-vU(1W=H|6jKo-E98#-7Q38eP}?_ry8G;w59NA_}uS@*0rdy zl%R(c_B%9@aqNz5if_MW>Vx-u=x+C2eorKNu4*7{!QSxMg*B=ns_V@J1-t~706hjj zeX~1{4$eWgpzq*+d^Qfz+gH~5?X+C#e+}_}T8qehTV5<>AFG)LGgreES8OvQKx|NH;1^ryl9?>;Bzay+fZy(!V!*sO3d8!*Vte~aTN ztsOsWY*x;XnVY zw%4cyR4Evo9|WtB71URs8CvVYVnv_+QC4ur`nj&IxBWWr_ZGY3)^m(}lTvG!*NGlz zPPpNj{d&EpO2E%w`vv;)3jPMNdkb@S5Bn|R1O*D($!iS^+=kwIYR3OKK<|9Uo#4rF zsx9ln{Y^H%@}Y!Ut52B%|6|wi9@V)e_)m&Gm{foUIfA8!5Nl*%o&rgB_CY9oX01Mb zuXj5IfA7s&bLe5L7GpO}yorJH#;G%}TStx-uKaM698S2h(pH??;nr?d&+`Sn2TJ?1`ei{eCUMbQ&b^dy79(m5GK69DM)AZ&|? zk;gPA&224bQqHf1MjHJU!9sswl!*#XichMRU}}A{Z{ZuV!y0i6%7TaE`&o@h&*eJb0cLAlwEnI(=+r zUaf{08hPR0%-Ui!8|)jPLo_Ef2%=MyAhFZj`k0c+w%MWQxw|o$D1IMPlr1UGm7J24 zo#D#Hc^EYu$a6~-=cc;uiKunWZ>_uYnFQ>C*6#82>R+W!Fb$r6vxw@D<0H7`n5Z5A z7gFSbGGZ+Ftw}nlL<6Nn_#fSMCSXnY>l@*lA?QS^-@y&Eksbq z!Gt9*yoX5(*?;Yd=lQP9e*?R6q(;E+H?5ZQ6$;(t=?<@uC*ZaU0U4`r@BfwZ-{dLC z-`3afrSG-%?ax_wzo$z5kE_1?5oPncvrI1kmyv}arbU7{d^CN)!PpBCETnjz(S5Q` zz;9|5W)4SSQUCtA%=NnGt0kRLdOnvsE4imqFfi3$mxFv6I{7SGm(uryT zrKiRtGa6!_%>`vEGI7Y{cT)G$v)s7~8Pw}n`js8d9e+3K->BPvwbJEB*fL>Lv9w(g zV%eriGC{AZg{+h)YOq8|Husk$z*m|Qa~&SUP4V$@nX}fmmFP`P`}BaKSiDA-j_cFV zcV%RH^8M1Sm!FWu${LUw_a7-bl(lL{#=0s?;3YBsDV>fiwN<2Lpaer>m9I>mgPkCM zE~>26qdb`o3|&;BT|B5rCnRsp{LrkGbU_;WYmH@f+Js1Jy;2H)S(R6Cz^k5j;fhX3 z8oA@n;k~oA*1k?a)DMfjz3B zy3SPz2$;`d1P%4s)L*8*ha3_)mjd6CjYc%vZm?ezp#Rp_eEyTxTS|r2REYwUVRjaO zYS4Dq^<-N7Th?Kc?tL?_>H0hP6#`%KC?oUto~nKu5b1C45Z_ae z+9mJ6e?|+dEsxmSuTSv3U){OhH}Bn+l*8j==d0eFwfGV-fy4?7?MhUIxdOX}H!?Wk z!sgPaHk$a#LgwS`z__8cfE|ux);j83J|5RSN2UJqN_8nJe7AzQ2j6_deva!5U*1E{ z_PQ5GXYUAr3LA^D9hyqkXt%?;>riy2sR#PCZ-QItjv8gd16<{+!}?`cg0f|-6LV&5 zmStO(vfr-z{2|UmkagG5FqTaz@I0r$DemE1v{8>F9aE(VTC=wXA)bnqSODUw%hT*G zW~1fmt~(?1`)YsxdGg`3KF`KAv2B}^WMbR4ZB49>ZQHgdb~3ST+qOF=@B6*ytnZxXTK%K{_3CxkUDvLvU3;VM zI~_@m=lO?I_GPSRo@d}#X;;u@N=u*F=`nmB^krWtFEhm+6c=Yn%W`FB?Z{~<*@~UH ze?MZ-Une~^-`De+0_zYbPTXIuLC)V*5C-h$|95Uf z>Yp}xzKI`-{~`wPgFl*$D`p)pfmUD~qA$120d>3q7dA^v;sCgZW=(r@cO! zTi1!Jn)85!udywx8&;^Ti}(USpPxU0KYM{cwZF-W-wkyPq#HH+8*cuW-s$Jd$Zn5E zKDZD}s0F51q+0!cOiNA>^qdJ%qc<#m@1N`N;Z}vM_-)RTh@5Ix&71@Ri{VS;c(-9 zHvz`?A15Zt4G)_Q4{q_=@^>k~D4wuzB50Q^lEM z#+j4G4VM6Y(Ji)tWCnts&x6;Sggxf^8&_R*9Zyx+$Oq6^cK>(F=lH)tT-CjQV}rzu z-h?HCX}?w4NXgn>K+F&q3rhGY`2&jO0H2(y_c{acl>KL~-TRx}?IS!C+)FNNkanqn zHE^GB2NMbGgObR8eaU9~r5DlX>2{d7txes^ni~p$;Qw#eF$v|CD7N-zbKsS!GqD66 zep)j<9+vW{5{CNVt~K@_a=2Ny+P^@PDb_q*?DSln35J>~L4#YNI_C)%#ukoeBcG$D z`6na5%%Dlx*cdm1mlEJ!P1aCxzBE@Yi&xox-{bW#@|h5Wm%3FiBRvBZJ;CdBB6$ke zJuEYKA%{~b)2WsG`rz~6Z)YGOhPlY{A%SI2#=0^fTkSq>QDf4^F3DKYgUdzcUqiX- zyhot1{Xesr3>T|HEzTN+DB5JP=qaY(Cb)Q+8NaJ#CcCq=KfGSCk@BZg)%6${x-`aR*USQ$vRCnDU>xw+K!{^UPP4J?iKN3+Hs7otL z{1<@Y2Q`=tL6Vepaus~J`UP6^l^krpMPQ4IX3<|meC^R(OEaUsP6N;fwxC0q?0>@l z&kO&v&b^BF>%XSi0C_(2md)dqEps-^IqmWHcB{;>D^``1{B}bC4|F7{A+(AK;;n(j zL`(PmvX3n|j59mk%iWJ$hIi~%&00>q?v1m3TcTslyQA|K8U<^N16VYR^{1BY&m*_) zFJItM?$;rEC}Qr%!~8ou{BM%@enVQnj|auUKPf{02#J(Ms_|P>5b*F8LrL#OybJh3 z;HZ#5Cd*ZvorYHE^v~pt+xT;FXDP-N> z#Y`Rd()+PfNebn3-X9!vb>(+ILnfj+Cm!yHnWjo9b0%cC+>cIP&oOONytGyv9)-+( zt`ZxD2?*tB8ib&?;aM%?(TzM z4_8k`hhG9T+3gl4Vra}li(&iciUn`$^L^d@dWy{GsNRoEi!6zUhR$$E*{x<`gKXTw zWBE?oy)Sm#9;`V_aipY1>^abbdtHgw&a}s1Xzv&{IK20iq~^-g$_nF=#ryjkNJwHt z#dE^KxzI7Q@d-V-h_4#eeU=!4KofJCY|c|#L~Ebi?`dX2gl0UxZj=1_mYRfbG~y`F zBl-1pp^je*h66gik-s?)df+WI{Q7i11y%ol?S5ABm2cYSU+X(jI=7ceH*9z}%SrNu zr?HsfVEM7gxa>u7kG1=GbJwLmCz| zMem%mj5sNy~X`IBlkLGcfau=GI8xL0HeX3Zp4}? zr;0uO@m5C6rw!cV6O)}P?z!U}-QszRU-!!&4eEdPq4#8sC*j%T?ASKiY4=h<1f70X z-Xathr)_D1*V5a1sDxQqlRX(haq#>lVhVP69gFq_F8dCGfG}Fs^@Xy;A7U#UAq_$6t2?^?UQ!f% zA@sS-^m&D~_^u|z5X2}gXz8i%k!*q}`u9K{0@JDRpQkhdi=+`5m}K2j`%_isz_s-J zpQ(b^cvyeWA^gBO;Yiu~!xXK5pM_97_AkXdq^{QgKJ;k*{bgi%yLfkZK3L9xA(K0e zqrVoKgR?l^UqAR0m=(cDTHG4+0&O+?yIU}_BB>eEDQBZVMNe>Rx~3;hB?a}?((GY0 zDJkpWVWY~}owF@{rTCb!1qk}4G_cfII!*O(v@Xi8|#~QZ2CPcx?JyhdUWe~4J7=$yx!W5 zcjxK&aG*<`QnU&wg5iy(CQ{-+!|92oFJLBzONp9Pb$rD^kQX){fgq&;^XHO&MpA5pu%@_+Fx$EzM!~U zDQ`uNik-}1xU@#1AG^YD+pc>s(H(b2uJEA=KiPN~?^d#D$;p?;=}odL6ODJRNYtc>Pc6K; zAatkq;BH^cf=9}eWoxx&8U>GMEN!%^jRp8kv0M>iG#-V;vJG{(f7jsthKH9;r${X= z&6%9+)XDPgI(Rc0ydA9njMUtxUEi5^ILt~hM~xrC4iof>#Cy{C0c2m?q)0g?y*gsk z8};(yD}Qjl2t-AGO|PSzpJPf)P&P(IDcnObPDt3FPuxQs7BLqI@?6R{4GJQFhIY|b z(0G}>fie&r-lW|t*fc`DKV0P_K-ThQnSlU*vm&?`02 zfv7Nzl&;G;RB^FxpqJq*w+^lBJBR<*W&l7KUE<}y%6A{$wjX{ealNWU$o1G*N7z!_ zR8d=&v%NA}3JgJ{D9*y>ax+wjWP1$=|K(ThFSzdIwVcxbU%yG;XgbaomzPKP&ldzF z!0*FrA9wrX+n0&}8Qra6N07Z<{ntDbCrKP%NSqX=A8qt1;zy{&(c_+G7l19CB#*gR zB?BzHgq0=VZo6iL$BUkghMDhbW$~kL$~7Ra78mbtCPgZS8NV;^A#(LJ6;ul{y28(G zRw;3sU?tJ zyEIlk4xCuFtxh2$!bugWaLs5qc!$g2p%>#hfYKB=@zu#Q!7T$90pAhw{p`-SI|mE?tDpS!YGJ|h zjiU3r$Dn_gB;<36iP?efD6MVT`KeW*&NRb&PtRH^o-Y?8aKgXKG9NP5B)8M$%Xa^j z`C0yZ72CV5@l4RvP>R4QrlOV@IF2C*^J>)b`K9BA7svGZ3!qnI`O`nI@pj={XeZHe!s4bj-?`>EB0_BAraLo*ed%CKEHWafcxV zGwk!`r0t^j*WKw7huqUr^|SPI=>39nX^csexyPATTB*+5!|?&s~@O+$3s zXI<~;tTUVC3d=6c;xBZ%MJ-Hnco8P;(@K(vyQe!It0<7Xz)Vwf5`<#`3|N$!pW zN$`N9K;eaoqHgrgHoMv?N(wvwDH*<3z-;;VZ|CJ;q3^BzDt>o#a5eipl7Y|n@vFP_ zWqChv`Fwm>sGEeLl7hCnzNEAAcT4X!_tY>c1w$nOumuq*EV{@HzdmEs; z$=Rjz{qF3e0W;ONwe6!NRsHMs?9EpH;X_Q-2s;wC4vOBA)reMbOZHcW6x`o zv`ZIs}IkIe1EIZ(v?79uW~lD3MR>9w2WIRlR!Ga z4_Nox&75f?{YW3)d?69<1R4J^@2KX?qAgS43`PmjHxJnMjyBWf9{)z2_vH%>7VRhS zltxl!b*;bsza7heU&kV{`5RGM8&jU4qRU#|rQm65=clPo+5EDk8@H~lMtgWhKt#BM zeC_PbugVev%ebk&{@AJQeyXnZz3o>U+I@vnlMRdH@c6)~1~trDHB1I|jPEb>OBl6FsWoh=465`w)sxvZ(>c}C1ebpZ@bCz(2=OkivM#PV zc{qJs0DsxJ>ArI>?S$;@bIh%CmEl!M4QA7s`&1WR`#Qgy z^|9?oS;KM88BF`S*0WD0mk;n5tlq;76A#}%;4QS^3HbPbtd8%2F9N6*?ewk{^tSbk z?)99;o#g6ET0}OrNR3GiGt?^^%zD`_KW|&J~g(vmW)3RWa5Mfr#1h z(kMcbG7H+n?zT6wFp(n4IJrVyWUGgj!a-4n{S4ga>Fm#W7ZVvYO;T?`h^9(eMvR%# z)9w05#bnpdBGk95#=Ctl>beZ5!LZfyXe&&{gtkFP&pO-3$U)0q*BGt&^{3G2@-fG# zs_cp-3v?{bcKV^Z_Q<4YVL%LhPz=+&XzAF51@f%~`*cpmrHsRyMiBSdkIioSSNtN2 z(;O3L_^tPB{x4s|lvwG+SkLTI>EKG6x(3ra*oK8{%`-#I1KXTqDd~E#;CL5fIvM3l z2Tl4U61&QF`{%2qxZci}{DOnJrMvR(6j7h&b}*GVER~tTkAuU#iNm{%!?C`_v%bT# z4{&>%T}R==!MBaop^4YGiL0ZvvA3zdy|sgZhcXd1a>Cps2}mvN2IB%=IyzfOb{RKX zT6XJi@*l0W6D&lsAjN!`30IEDFp7l+3BEMju+pzO73*J%5W;n$$``-UAn_J8lLegB z>)UWQ`!=)6-&j*HY*R35WFlLji>}f;RGsYTo!u-7fuwHJr@n8Z0?*yZVZc)T2#@8C zp53oFftHS)2}LXj2_ojA3SXItk^eWtu=tzdpp^Q_QYThpk2U|%)Sj{sAGO!EG*{L0 zl!Z>+B*r_qZ3`6kLhcCNKSX_TX{!makf?@AgZpN84Y3uV&|*MD3mE+c`u1aQ!B9K%!})5mHYsha4v!9& zAwnqDIbXF`Jzif#OG{E4>swKkttm@uYwfzha`ZdfI^Vq94J%u0@`_6~$49pl{^VlC z&+2V2>rBSLj!($dguzI)l(=(gzEX;zfq766Jy>9omT>lx&oFoYZpd%U<7I3fm}ecz zIF%pjn3K$5%m+V(MyF2!K9QyVF3qbynP^`4Xbw_QGf`3Ylg}Ehuo7t2>((;vYj=BB) zrM#rYxjNrTW?=V?)eZ^@!)NEQ2M7BW4(Dv3=%%w7rhhP~nKLXO*RUNiu$wU~l|C<} zVq&T%?I-qs>@E#0)pr(5yd=#Wce|=^fqwMxLs-X<{th>xNFO3%9udJIKrGr1#}N4C z@BcFZH32>5Payfa(TJ->Jzd71*g_6eiyE>x)T1kBSVX51$i&mpMtjIl(3rE3woF1_ z#-J;B8(UwPsa)oXZJ+uyexw}{4n%ZIh1 zkjDSUmB?S2!i_kV0eAdWt7{V8TxXnpHhzwPO`wsMM2v`#M)P{hk zsgJUextF2?za1#`p7x6|hphpD1|&O!;&*8Oh?}DPPFWV4I4*?KS$JVxCv?q=td+Nl zi>n+62Wi=_THm*WMBgRTgpE#w(;qNOcK51gKz^k|_Iw@P32zcy&WLEK)M&VpV^LC$?6@Fu zwhXPDS(5p^vUfg|&%f8K$a-vk$ku<>snGF|k9zeYiY(bKY zA*)ACKNHC>cb%WbqIY&ucXCqo*i?1lC+J5{kaG$tQp_$x6Ng##m#L>j`2!8WtvWmR z+j>H@ARt98$Y=ZsCFl2CYxk`_{NE?-c^EQ7!1(cWKl*fRX(2oY&D3qq!ma8rDGr%} zzA;k=Uq@}RM^HaDHXic5crl5npig>D&F=EId?F$6Xhr9fGe?oXY<)P7hf zxN)E1;LHq1DDCA5iao$FM_{!U=W)c(8_%6VeUt#J)^=~ORISZ@PgqB*E1PRm%hRRvi=mbt_+nT4^?rD2e$K3|yjOly|8{o~KaVcTzJol@aUKm#7IAhA?4FVp>?!E7{M+70!$`5wyM`WXh;x#d!^8TyXs6Hv92xz`(mTdD z|LPte>*-zOD=(Jy_m_ERomWkzrEy#r!x4To0$~jF^FKvmP6a8%r278Rx z+=jvQPs!1QGV!f>nkx%abv1F%msM1{@)DB(WdEqV0I&jsa=3yv@X@&}3(=*LmFkr{0d)KSi6t#4m(htzTW@G zX147GB>@#qazq9SNmSOwjIkWPsd*#c>?y;&e ze|UM6e~yxeoxU)?EMU7=?G7R}sDGHb!5o%WnsncE)ICOo8@9+r$Ux0U$tF!)tJPf0 z*2`O&x;Ee9AI`Pu4Rwx*g)m$F>?>F>8xzB*Wexq+gj~h1aZAHR`+gpjs#`fW4M+yq zX{_a)#^c@58opHmK5)G)7h?a;sG_!zyX>o@O}Qn44$#)gtCbT={qyTJ92^Zo91RRO znTO{4b6PG~Cnu24&M>gM{?M0LKGy~22;;SY9uw2Q@=kvg+&m@>4BK0o^kKmgD{HK&a~I*DL5@a*ml6PhuSmM#FrpXl+m(R$7Y{imK5>QK zQU_LicnLj9PP~i#v)7UjC~2tZp57)KUEM!d7yUTdFh9?r*ZpmQb^qnk`E2I9FEzux zrL;J&xU8L{i+zH!V|}fek+Hlzg3{t0>|5Jr+fj9M;8gw6D8=1ETN=jp9!a`2_5r0S^RAZNHL^=|N$ zjBgbJ_$`>|!jKkjBF``{dF*D-%)hgw6QjKHCKT4C4gBE zzL_M!LWTqfO~y)3f{;sq8&e#5DlySON1B5xqz%Zyo3^SL|N)e$3{t zqMWx5R!^yj=cfQJIJdQV_Jn!u4m#1oi0=`u$z}?M;k}3tV+E`!yJTl)4!B zQM3h4sBSySnrnz*={fy17``SF{`h9Nz#QhSts*+aom+^k)?pGB6Sj#sE*P zH*pV&q>?2Wc)CBbmuv^Dq8?&HuR}@2>S!hl9qXY1NRG`2MhQH0P*;}mUury_zfQo`Yovi|CnWN(}yN5;asYkJ_DzdI@ z(@#ycFmJBnp-;m>>qfxluom*2KaTIq?;dKa+2fa0r$<(z-lG~e@r~^YYRVj1`XqKV zgmyJ?pxXcJEYdEla=t~TPpooGS|%UdCLB9TA-T{m*eta-4QwRc9Bhrv#99L*;Uh#? z`x2wWM2wlB1&jAiRCuC=^5Snx~xx;q(IwAX8kRJIL!^l=be%rONGKCTyCyfrNU43}z80I-LPpTfl&kBZU#t*?`^r zzeRo-@kR;XLIvAG0exQq^;Smm)k@CgTGFXz_K|Kpj!I_ggl!or0$dEta1lu`z(j~7 z8%;7vsNB&4ki<`Q1PAy0*L_oWW6D*|jk2~ddwq#*W@ABHaSI1I$ISfN+;o5cm4}P5 zyeL1OnX9|o^X7FilE1qZxc_?ELx}HH$KuOWzLElk$%Hb7%!O88Zt5p(A*A4FYBng* zV&G*!Hp|ZPZ>Wuhuei2dZ*{$4?0DkwQDSXWROW#JA5E~Kw#2*3d3~TR*0)s38ko<=Uy1zA9Y?_|v%foCd z56E*nE$v$TzBfCN{PQon(-S_A+=yiDEL3F-w9;|$Q__-GvSH^@v8PfoxFd47zs9gD ze&N(m$Fr!)IJ43_uBk(Gq(E{lL2xa-bE~aX!&ZenUNyHhz+>}wemnn4@E6m_lS!vib}GU~56;VvcZQ4b7v4q) zuwNpg5dasI2aA`G%TuCKS-iBTIVDgDkt(yfBA?0S8Fl)OB7V%G6hj21F;EXMs0czH z!6tA)^4S=H_DVW`6JfG-kY4C;6UYomrSEt`<9QA`;-7(nB8$TgfcQ>K+fd)BZ_?cC zKQQ!#$n*EBvBlyh7n7Q|;@kS|YDKV-eyyK*q??YSk(oJCL4$vGN{C**L@WTiEmp(~ z60bytgLy*^`YGJS8z`V=P58HKh@TcPRe zeQTFculp@Fb@{8C!);@o4)#gf{R&4lX;VXaAmT|H+BJ4U{#VU(r8S*Zoa=4N!F^wf zEg@{C1ZB%m6rP}LbXA?1G}Q_Lc3X~iYnJXt3XPan2b1vL3i{;5*ph$y6m-o2z~}sN zYW247?eJ{sb#7>52CA~EnW(UttaMZ+w&WemT>4oulJ43j`G|a}NIcnNHDpehZ5vF9tfx0WD}&qu_k;54Qe{ROf~T+DTN&}Deog>vw@h`gRT;oWK8eQ@>Tjz>_{ zzpO@MR*iL5uh61A%}8~+-iiWu9NmQ+wz?v*?yN7qK5I8Y#?W#}!jxV?C7rTjEOjYa z#)W0VnRW8-_tmD2DIjvYS{cv{E*Dx~Y5BMQFE4i!A7?(%ufEJ7FlZB7p@MWJi6IlF z1hIbYxLfBLehoP2KnlZI76opLBJi=O@Q-=0z5>-iBAz7}=fTTMlNNLlXRv$_)i|jP z?%*1f_C?t)D8Xz2-}<@BO?-zleCQUR#qW#C9sv1kiXgI~fA`WJjGJc`BaC!q!z!9o zL(u3p1uS6FCbPWry$mslOMuGrY6uaf_eVxXc@9W`Yp-A=Qr8Nf?~gC0XZ+_XVx@yL4c z=7uVlVb@WV_o6o|i%Eam-t+zV+y9qu3z>W{IiAHo8AVV-9T1wHrSvF3OMwZuzFhdQ@9<;H0#aLH zeMVnvw6-kGgtZ3lp$5@$ni6ezKf?HMVe5sgr`z;zzD-e=xs^gAU&V%^oL#j1mAq{3 z#B?tGL@vYk-6qp;1~GPr!;J%&O!h8brAvOr56Br^vdT$x|1mm{VqlQ}S+~pYbx$Y& z?mmm(W#NiuLt5wyTsgt)ahYF?BuTi_-NYg7TSwj3LN(G#G-_u;^}L&Zi|kwk4l+u;#Hazlef|ti>#`3ZA@z& zHrB$s*4%fnhk7=Py|D*_M&hLubRm!_nA>^?ODX9|q#ctZl(I)u#8C$Q1w6P%i4!iaFPZb^bcv{07J;B;z5=fXf7vYHW^l3$9N08E zAFC^ldUyO}Bv_U}LK_D5t8Br)JW@2^B=DVBBK(Es;-S{KPg@vNuZbos%t}<4l_TQo zD!lSu{4t0v&I=kFk@I|WtTHxwx(yBf=WG!YT7Zm32vb%VncVShUVh@5RO&V@qj4$N}8u9N5}wJ zDQ1cKFhmJiND(VW%ff-0c`#)IZRR2Ghx^t@3E*O|Z|roSvA(d9mA8SDT2LX2@)v{F z#U)o#l#Yy?&h%vV^ptIE42{S8P`l@lr^hi^fKBiR!0gOk%7LSrBS%$_Zd$LVYGN!! z6x7&ekvI61)v}J9T5)hReoz%W8{86}PmWOp)jEoAGJf~S^iY<)gopsw>T@q~+vkh2 z2ROBj4ZIm%(f^3Jn;k~rTHAHD>W)>HR{Epv%EZlpPKMON0!ua{plGG&g3TY5D#@_tc4alu;)_A=gBFu{Z~Gxw*Z z8^%k_2(h3g7DK9+B z>M1LSUL8m?F$e=)1yx}P2ow|ejw1`K?f*B9!eK#7$@7g(cjppjb_0IMHOu=p6Sfc~ z=@U%X%Hftxom7nMHJl|E&8JU+L>Ss$o;K0bgGy@7v`2 z%}KJpIhGk=0MKYR7cEgK#e`pIhRNhAs@8P^?NlBdczn1p^7Zxf@K6Ovcsk0sdCaM3 z?{n3DDNyh!j>%ID$(K?t4fJ8l_0+d`rY!fcQt)Lf=S=t^CFD+xF<>UAO5bZYaGxt{ zn`){G=EQHs5O2xt)+`D7)YPd4>$((ooz|HOUODt$&CxYnYSJvKbHF$kv{+Zwm^T&K=T-TKZFNp;^e*glkN&1W ztR1~;QE;CRz>|00U34elxjN`ctBJ`;wC1*Dqt4DD*bNS7D8(O{umpyc7%7U7fmmCG zurZ2eW*AORND$#7nK>6>hY`Afc&iGgt;4kAgK6#yf+Ob(#-XO~f$WJd@}re@NUtw^ zf|4USQ{r+|2upl2E>ez8i6o#R*v0k+0PDmG(ge`fP@?OEjOk`qYR4eM%{HXO^Hd6E z;ovfU##o@?2Nw~?6Zr@n$H~e{Pz;IeYxhC}EMc~Sw%CdN+2P?-puueX19QIokND&M z15+gw{EH9xVVLu!P_vE1fdK{^*&ixh2xeN>S^tug08lgZ@)eG6?J;(?uvZ~djhdX8 zwxOfPzq-=L%iYY<>*VV2f{#f}%>}iy1wI}fZBI?{t*`OqW^80-uUgvf@jOQf5vQ|M zM552HPm+@`Hn(Q7G7Y3dJw&k1_L4qAiJ*S>R76-QUe;RGicH+l)c;D;w4yKGu`Rt3 zeVI5r>yzPGmt9Lqn~{q=nUF=ZtI+>lUxs{l69a=Z-XjhEBTui1?1%~WIo_WIpT1)t zs*31sKnEW^ZX0DmI z8O6#N3)H+{=6~Cq?*6*>eap=K_~_sVZn||pzu5U&+}!K$@OxXB8tlaMgE3<(KB?7B zu~ARs-v{tS3U^uU1!yUga5EdNq%(T2Is^tw?nTHrxcIlWYnaN$x0Zn6*2H*+9<|Y~a>Cqf2 z;-L13cv#uB80Yp5nQz9MI%mtVAl z$axkDk?^hXwYC9`e6QagJ{DgWacB4`j~N7Ev$MPScsv}uJ{;|?rSdgu%CEwrBp9e| z=_l+So@EVYHod&}Q1+#b3hpkeJ}!TG?-x2UqeRk$J;nM~1}_b^YH@&bdzOZZESesn zi-@TY47nU0!KFhX2z%di&euGu9Uvek6`F0xACsPKKBI!9e?6!PC=M z8de`-GgIuT06ACrW?IsMkcna?OzvNaxp4rLv*IwxLOFQ42r<5!Wze6%mR`DQjU6J1DkZA~$%|=>oojRs7s^+4+7<}^Xex98h`^|8o3GF~ZZ_RqMf6dM?fv?`5gcFmg z2>wShsvrA18v*5Rx&TnEs^@YjpwQIIrm7A@O%Z*@401Mkcf$z67;42BdUw=Z)P|i; z6(6R2H)o*l=HTew;_l|*Xyf8(gMfAhI5IQ$%&sjF66RiB5eziie;zUrFk{v-Lg;ZrhL54bC zA6HE@?w&==hOBKG0%u2Vc7OXemFntgGb!cJmeYLSAesaxE2#H!Xr$sWyX|2O^t)Bz zf*_B9`%5P~TWLQ4zxlj)7#+Fj__MONv$M9+Gx#-ExAZrX?!|GXGK~Y3)6mSny~YEJ zYtj1?rR+s;=VczD)?%Y&of%_NY#s8$+Z-eZsVz;ZPepITPl*k8ZEf{1WaPOPv!xu# zce5QNJd5f`hgMP}VqF@?;BTtJVb8~;4$w>;rSAabuQbxIFQ&nrcV0g3{(1EZ3aVsf zGzaQOh^m+{){&wvH!<&mL)Jg@KSZiIlBmqkGx6Fggh4y+tF+wfm`==1)j?k5?V(0_!{ zScrzHcUC>Uo@V74p{eSfhI>FOnUn$=lLp=|s#vV5h#Cp!a*0R$Ll)hunodrB?k&Dn zu69n2_IBC4^TK(|VRMG(*c$S}dF*s@nG#rnVK|s5L|9pP1Q{53sZpugipncVIVztw zsMmc$px!-WgkN(p3jSw~xC<+yGy5Z1Fgb?;UAE;>d2jFKU~qArB6?l?BsDZX_Ncde zI`ie^Ip}64=!GhqbxE<7P7Wo_bg~+zT+?#(?dm%d5SV75yU@c{aF&a%xV;XRfOt6` zia0DmZgqcn!7ErEuyDjl$x3i4U`0hh`PIjb8@|{YrJb{0cNP=08zYS zXpM2S7|8#KDBCzOLcyQV4&ah7o>I~5$0HzUrC*YZ1Cs|k9cP4sv+6-y1!w<-7t9dzT$+!!N*g9)i*(>BFHt?=)MEo<^qqD@KfF2|&IJE#njvgqC z*I&K z06)e6_7P5n&ut&_jPS&rzy+$@NuufahqbpSY?c)e^lf+`%9VC;0L}z)a7E?ul6MBg zT0$5#ARijR!sKI-*Uk-ATpSV_jPf~X3({(9D7M{~&$|dwC3NOLxB((go%Pg$in)1Z zvUs&}IJFXZw2_#pA_(Z1hxz%k&KDuyzxb^fhIqrEKCOt{~B2`vPy{108PTl{|Qe z*VUzB%!dS_E~%0;$3`d1+nU1Nixm`8wDC{SPj~UKP}1V!)z;=#XffpEXi|>N*_YIx znwJqv(3y>mUXj#i=C>A=<`r~xw6fB(u~Bo8uuvX1xF;4_S9)ic$Hub7T>cqV$;wvA zM&Zp;F~D$6t~ZeJ6wy=HaPx=i>oRI+4%N~ht0&*mNWDqjX|7scu2Nremg93$S6vjP zM74$-l${0rjY!+c3iB&6=#jWSzDiL$(m+8MB_=%u1B15~>m{M4V&;MI2Zat2G_t3{ zLlFH(`mKNnGZhs9nyct7izM+~yQvT*35;kfQmKOE&qS>U|EWOoSQi3VM_JK7EE*h7 zhkh4#pQ4HuN1H%Kw&42EKPEyXt(LF4g8uBY&`F~B4Tb1pOB z5x?1I&92i+EitKTPw1$$lhvPWOF7exaX9iaguK5!ZsNPc`N$bd^19rq=;}6}-Lo2m z?f?2JsjnO8h-6GDd+(b={H!5J)=ZCfU;S)46pl`QET3KNt`Y6k)76ypUX?7k>l{5g zdwueV6WDhtPN)coAMmbDn7rM6 z1a7TfJ>A}o9c^sX8+l2QCBZ#7a%-)h(^^S8!Q3k|)f=HG36bu7f{^ld3TJPA+FbBj zz(K-I%`HS(8l8##b5|b8UrMp$n;thVz)?!%zN`0?f_WQ{cpHmY5pClYZM*2ZsQG|X zlao^R(2+F~ahI`o2=aBzGcRkYtZfK{50+vX+>?RAGDevTvoMno$UuoN(I~>k2o^9# zvcLn06yJe{LYa*cVktz9bB+^;yB0ce#RyV@=0y$?Do;TbKo9EstxEjhN>YULvhjH~ z#~({*k>2Fd-+P$=nmjXU*YYXstK1wY8s8rJdi>viQ%(hlN|? zw~KUWGLD{irB4I};rq&C-zylG+5bp90O`(7Y9BZ<1)Dj5Y(zbqcRia;J)dbkv0(wD zM<=6<5+TnV+M4d3GLmsB&L908Wagp6tZKXK3j%-O>IOgfaP;PWc3@<7;CS?9!{K5- z@~yL%PD~8jTko)k*c%ziRE(q(;)CRfTs6Y2G;3-5xy7zC%k1=~Q2l&FV5jiKWHqVR z$6F>YCRx9d>$$y?szZ@}89C!nj2bTLLJl5hT;{Ku33aw4c~*5f7F8;o@nM|tfhN(p zp}F#_kj4C~$M`K-Y&T`LR$}}O9Q-x#TT;yH5wYfC(hjCue;qF-VvbAn{_Va|1(n zVNOm;j97;@;ft89l(*)gv5>XBvAKzltk$H1ow5PHzkux>3B=qV+UDvNZM-oikwRKn zI3W1Hsra45(z0#pQ^jp13?yYtRE>0Otb}wOyu*s{Uu8a>Lk3u=6)EL#4^tbo?DYf!_Sn6Sumrb&4KwdG4`kB{iV?UEH%XKBp- zIV7op_^>+M{Tu~YqS8u@ZX+SqOtz<+ejz~5nShujcN0q)f|%5&TMV!^`XfuM|G&uk zs-QN&rrl!2THM{CXmNKf?hYv)thhVHU5ZO^m*Nh^-Q696yW7ddIsZTB`(|>L$yH|O z-Q8y&A<|-uQnF*TAvw>?0=2hxzV^=dA6gW%C@I+~DgiPx4T(X9XIWiKLY>;Hyy<}D z^i-Wc!^M4&CI%QIMf>7>?>1L=98n%Qz=v0W&HB>R0Tl``kXiEbev4M7-J0q&w{ZB%FHHmO3PY7m0+MJhF0KVu zws~>1aTSIBW@M2SUGE zx?X?+!T^!#Tf7=OeQG;hT3dWNJ3U(KoBdp#?AR0aJeC`rYe(}p3EvKI(;2` zC=77Q$0Vk$LP24$wKLGzjiIe8Yh7n^YEhNtuvW`7u(D=bL&eU{LH7^S6R&0$$5eC@ z(R4=AzfmRZWb@GzS##}%Sz?##10)@_w7h)R3FX%&(YJ;%E8vi$!!;W`$#R5b5cH8 zT_wcdEc-9;N0kI-`eR{+v!waThr8B&&yvYP?{Pqhou^zKF2Mv)NjP? z%9XO9VT!fiZ)Pf8xHnLMEirf~EoJdJl*aujh>yopwS?Kdo$~XUztjr(INs+K)3aiU z=+H-6)Jr8&G)_|GdQ#R~PR1_{Km;GQfj|Bb$dVu(oKE(ors!EaPDEEJs-83Yu0Hw} zgnukJJHI%?Iy1xWYwkbAC3%up_@uNeO-*7Q)2p*}B~P@l1K~`Qx%)%V9$B(D z!Q*$RjWdv&7Yd!#*XSQ)0q3`<&uJ(a?oHkt4GLtr*8xF0@C6gQgPw7yq{k&bw=%e8 zWS2iKi$$PIccpLA@Rj8Vp%5qjL)5_$kh~(cxI)RzXVS#C+qeSx?A&W3df6g+F*&R% zOqbuGvnK`Y2sAay=$4(alisorN54-`fI*q@T_XrLTQ$ciNwttcr0)0c-*1WIXS4H_ zb@N##4eN`etJRu~oANA~u73;9&*M@3s$Svwi2d%*0%m#v3#DMTYKABur+|Ux$u~CUO)7n39D#C&_!ikKo8Ia zKx4=(Yx2*}^USVtZEn`^EY;EP*@Qi1%xr%Rw^yTCNtScPt`+PDcj|jGViL1Vs$ItuA^O1F<8TmKdp%!*A z6aTL6-iOEUr~9AJudWZydyWUjUK5NRJ@u=wBKyUEL;te#LoKiC$So`^tEy>lW$pUQ z%+N;9)WJjkjh2m%nuoDlm~LQEcw~@eW|ekqmU3vG^WU`4)UeRdGV{OgnZ<>*(S$vt z7`Fly#vdH?NU@w^IkyVvtwl5|1D^>#dn(=f>&DiDO6R6MDvDnu09w5wfY~AC(Z0*H z(p;1tWl~#He^%BWa`|vNSp464#?#J>ApsiE{&?C+2awam5IIqDDTYaSulDv|O>JmQ z@t=8DIKWIeq*0NdU<8iwFOuw%Dhl!R3UOFT2(>l%%R^D7K%_HQO1#eXC$#{PaQz{;(LJC|Sh;szH+-m`gSMp@MDB<;#&0dr4-)jD(9SMt!2Q zO?^NoM9TDhu-_k<5s6zpQPs)Q_I&+H0Ev%n@piKJt#fj$@o;Z%Z0f4*sH^Yn{M#-d z$e*uHzhA3TYA-Rgi+Yb@-{#p|SrIUBkiGJJA5m76H*?(HQ(Li?cUuuS7u8cgHgkG< zotZf^b{n|YHwF&t`?-k+h(%BuB*V2vCA8L~PmCeoj#dH2iSW@#rGI`_a&{rLgyAFs zLUWP|%s#72pX8p`4SKF9-MVBGl+1C=c$k-!+ZI(va&Yyf6YPp2kdF^d88*9Zri}ax zfbG%Vs*z8^lWV6BVBEY%-GKI36*dQIyta>L^Ak{R+{J#I{ulJAy>0&a{Mag|%=;H6 z0>VS4$PEA&SsZi|}g5q(ZLT`3yUH0Pv%M-=3uuO-jYq?P|I zM#>d*tP%7K$8K1|X_z7Cn89jMC9TmUZ80Qmn8vGHLE|()XB_qtirbkZFjILXLbrS` zg+I24p%HEr_uWzCjJT<>WmMEc;I&4s z%O3KtG+Pco<-XU%$lKA+WCOHKJTVt9ID zyL(~de&t~0`g&&P_4W03tX`}%E_zs+YPXltu~T`3nBaW>y7l>arIjy3yW%-{VWlWJ zJ>=k6SJOwZysD}tFRzHKi;9wyg_W6-kMUdI)^jFSX(N~~f z8JzcX_&{Yk0SIU*;9hi9+IS#NoU=e_P&w$CPViYbJ!D0f#*+^LM(|HO)W8y&g|WmZ z3i)9fb|l%mbic>)#~wgK{^?ohzWZwVxUb#L28+l$VHJz z5tTS*UI1(NKRi2o$jtrz6uF#_b33Vpcsqxu{`HRtgi}cli`apPco&--65g1Mk480h zc6sRfo{!9=tE>>(aBbP$cpnRjGQiIfZqts;605jL%*rGJcf@CE}UlliP1y{e4 zRxAzCrsY!pG9C=3PbvZwn|lR72tVdT~^^C^l7$=jZ3Y&Pl3% zfhuf3cEs%~OKm#K?b_?jOB)>oUJcOg zOj6ru_85^{7!t_iLnD``>0B_NMQg!osY=`KcLk zO0jZDY}2VSWcUPFSG>JzLL`u%H|4i=)s=U3|Zfz14w9k-aW7QY3?$usAhs)4NdI1 zRR%`z3z~XdDeDziPziAU*+v6S)R^@E0qZM<=gQ$ZjPMphhtq??9qv1LGsb_esiTx1 z{(3{s1-ev`Lmja|QxH9{{e{xy11um8wxVpX1F;0+Fs)&)2)X^I)|$#Ln%Fi5-#Q{2 z7@lD(mu{z!ZX1?S9&2DFuWhNQYbBdm6_bzzi+J7Msz!8@KPDF#-qKkjq2&WOE^aL9 zDmZ!nV{}W%B3GAueT&Fy;pFnm&BEH+%g^M{-TK4I8^6411m5A3%C^kg<=k(OJFmZkca5_4>oq9`13CJ6}=6=Kt|& zb(Ju~x}1NMfnRQvKT=N5>4vbbu3RlqPIf=j*xS?FS&W_}G2C=!8@=?WHX9up%9qEOk zsIQ`^r$b1W^g)oe^*o`L*}1KCL+bM3-{bK-hw#YdGG_vEW69;1nc?toWf;nEBiOtu zf9STD^FtOSE|>uIPa6E!a2`UKFZs1*<^UFs!K{egr;5_2mD;0-)u`&5LnFCw8I5Nd zmy`}BrzSRe6#_mpGMMGEEVgOvMx2)S1eR6ELE<&4>V;A+5g=$muz))-9NNa6OoRH+ zgc=jGz~#pMFv*2lwcyI4*)+-hP_5NCvEWv%yBf^ODv55SgIM#FR!R*uDISi{GrQ@1 z`vQ9H+u8+4^RDiH_S6V@;goo3>W6dfhrgo^wO1AQC^dRLZO+{p+fHC41Cqa=F#-!H zls1&PhRbxomWIdHdZ)G-7EO$|^;M31O>i*$;xL521y3^14TQWx7wcH3YKD*2cIuj7 z1qolXHAFY_a_l@z?QLyLoy0m(sUcxL8Qh?*=TlS`p_iTK8x~0Z(D8f*E_ZcxwILw8 ztCh#c!{h$=`1J1XDFgSg8j!i6pJfOj0c3;p^)s^p*;#<>>}*hGCNXKY4XL3mk-pt2 zp!&j~>MXnZS2kny*>4J6-8Jo0Ko-bAH&aJ9H65fTuMitE8cZp`)z;F=$IaV6xI9iI zO_7=-@_}c4j1L5Zg%)OfO9-IWHxM3fHRrLw!w^N+yTpF}rnSJc_U`rtr;tz1)1?|y zPFXO?k&s70@l~J5O)0su-smY8xpK&}_LP`1WIiep3Bn&D*)fP7N3e>PzY?*}dq;P- z{24XR-2*0Q_j#1oBXp?tn}bMfNx{^Qc=cqWhiPe$r%T3`DX|M|+tVQ!8lo#gY!KAA z@d5r%;8EGmW5`~7jgG-KkHM>m-=c`psfg3Ih}xr(*Q4qiV)Imb`%*I7YC?%kSh8m5 zmE7ABZU_F? zVY`22gEuVTY&V7Qg~dEYN!qmhtAYS&mpifTpH37pz$o}g8uWf3E&M*d`TlQBnR_lG z`{F_c7k`Rqf0JZ)laO@i2huhToYQPxv&Xmg*SYRM4tq98s~VPr!lo+!mJY1a2V3bw zU(K!yEKc*x8VWBi*UzoDF0DfwPPlRWa^zch)*d}QC_F#d&rY8AU7&9=Nn31y?1PLu zl`#=qG<#kQ$cuyU)qsumr&H8wp^xi>Iu^j|_UL4%``t1w3&7`n=b-N7gj*|x9-!}i zf4y^~g$wH7^YL)H-s!)(i%cxXwb0@|)Z)fX;XcG=nNX}oVctA7^lfx{H$S|cUzm=J zev6FG+PxcpySvMZp06CAA3vLqK1+gBV{jU4%U{r};bvUoAEIr-t$%;1((~e4rKAX2 zKP%PWkqFOv6g-`(eFY-jg7c*>ykF5CYetlWJ+F^0vIQQ&!?ay~W%u0|oTRUYC)+S* z(x77j-;(kx2^Y6zc7=H!xS+TIk_BZ_3sZk?E-F02jx!?2X>6?{h0HyNJNF&xQ2C>z zOk9t;guvwtA|ctpf(QV}(wtpXJX>Xa45-vOTUX5Ej_Fz#C8yFun@AVLkHO{bxWZFf zsO$fUE^(R(9coGri$J?rD7ly`H55;}m`=EvGB#K+Hd$0Sn^w4*GrIZHMQCwJM9+uF zmHiGKh1k!02;Xn;@s-s9mV75IU*?eecZVP4Y1pcUO=-^0$q^A9jQiHJn%fwlBH)ah zy}n_DwIK&L#%4YkTnN?~rH9<}9=>!6Z|PYBIuknb#ToqKEcHYjdG$fQ z@ZoMO1Cg3tjWuKHTn(}rx2fj0&E!FNEes>=+G&2}r8~c@@3NB1G8C{Ek7Wb6k4Nn| z{s5{O;5JS^1*m&9RX)4Q?T;5-6GrcI>z4g;;an5kxZgSrmxd9Ht=4zH+B!c44_)u< zu!UteXx&%~PgZx~RKeIXR#yB*($&^BNYyjQ)HBF7Fv!v|$S~B&($~$_)+PYyWvA+8 zr|YFb#dEZ=vDXO*Ht_H@aS1fNJG$QO&&@=JKjE6p_U0$%Q<|9c_G0$36o-b=hK3jl zct2jQ%)es}S|cIvii-&l6Q4yXeC6bLa^{jwlGRerMOOz#lwm=GNvW~Fpv#D0Tp|d# zW0i(Uiv}W5>?lioNe`3ymZ!26A13h*^B}+otde#L8_TUVwXJ? znk*5n6w$OMKv%^6%LcU4GLbEc7Q8?pF220b*C6=2NvRIo2+(--Zy4ZPs3Punyma}? zL;o-KV-{20Zd=np0eX!rS#;Ul5)~1+_~2D|6$$=4)o)fDWqglcWXNjN)9qDc3iuw` z{#73SuR<<#mr^0;+WoNVH76jX&)W7#D%1@*0jm(-9>d z31JsaPRdAC>Bm2q8w&&APsBTPDcfVEp0dIOohg2#Lb#&-TRH61wUoR&!1T+{xt$58 zCwGOAZ=2HTs~3|3U|V$nzU4I-?sYXgn%4WaQ^K|y-07n%i`ak?=4NFz=Z2PJMYqx4 z?1Q9tARlj!%l@~vI!a18WVulWLS*aegB)00fT12h*8s+#zP`DYqpOvzsg|#5697l}FRG&{?FQ#U(W zr)KS>c2c)1M>$DDvf1W(4@unm@k?JlY!HfD&Iy?*Kqxf-hec`p(U&}WIB6_dWtwe~ z4?6nXONjy=UxX=Lw0fk1Fcs(>RtZKaZBe{>i~@Tmgz~^a#Kk}n1Sm-cF}tSafPpU} z8D|KyVpPO)2nOP*)D)tHVW>+sOfoofnBF1ur=)1SU8uZEYr=X(|7;DRG43WWx@2Ez zArqa95wyy`!(c?J3bJXnGP-~qI|M`@iz_9SZP>v7Yk~BX`Y3~0Lbuj>61LeN(x-QJ z#I2`#-dHSrKH)o)byL7_GI(DPKmOw(eaCKIYNG_DONG$4gX`7UV+%D6o`7PU4V6wZ zT@Lohk2L4SCue&@{Br_4Y@6*eKVvGt?GsWr^Gfk*BKQ({cpRk^_Af@>RMVIr(jFdK;Ns%q;3VUk11iVfYWdoP-W;Ec z^@I(Coc7&KluUl;sA?CqtwWhGSwpR={?4B)K#&>H$JPi*pvP9CGD0w|pg@#U??wxg zArgh6#8O8?x{aj^;JQPN^M@LztRQ1FmxC_daWG}n#SZI@yVORize3>%)%_#Rh!M_| z07I3?ixmOf4t~}oP{>j|43IUdffe}-Qx9K)rOau^;*QDk5~gM`3OsFk_)zA|p(ewT zBkYTnflB;#N~$vHAA(*K1FlAf+k{2%p$6-Pcg6ylz?H{SO5uL?Cr|(De6tNHCc4GR zEs68o6NTXies)qz* zEm!}>OkC8y$q9Eoi2&3}dU<7xg6w7h?-G`avfe;)-C$TNq59NS$SM5dWRyS`KiB)~ z)6h+Eey@09;v@}jwmhRSi9z#=>FG#TG43w`db-M{T30Db?5>yB)eE7g)pMl}simt9 zUPVyWC zp|#y>&EZ^PcPA-}zPszo{n+SqVM~9~yq4Ta_2GhFwt-JWV`EKcCklzyq9IdtXEwk? zuVNXX!1 zr)jal82!;I(8zVQo8O&p+rs)I8;6`L@Dnnpop^Ev^ZE~w1{F%e*`#{)zsK&d#E~>c z4q~jZ8XSg*!H~D)HRtM@E|Ul;wlBiFjVMSO9RVmQL!^sdH@~SBG$ligB}PY38YNn~ z^&Lwf8R<;COeTln+jN*jnf9f+PFhu7&!@67wjWeDX=6pIEgb0tSttoGw}^C@$z}9% zKf>YGpI|DVL`v%;IUvJ=uE~AgYs^Y--_nHgWF%k{fAQNwwxw(JH5`){@?|ll5Inc+TNH zveY_=wJkloc^id^&KpbW;4~GOEA-~vA4RctEz%(wtrtu-noS0-pj86!DTc0oN-S@o zTFDa7`x)ia>{*j|1Duis~{BDqO8-Lg@T z!cEvX_NeyUd>VWPi$M(DH(&J(&4puK`W6c*9)D`Yc_nqpx%}1hskgK4**?E5IUfU8 z-45MD2!`>}bvsH2#i*LNU)6C8$|{=|mp!}6&m0XD{LDP;JS+i{%Y_52{1$u{Sx2pF znyY$xp0zy1f()h9g74t?C z@uYRRaCKFOp@COj?eodZ$R@7VM@{qfWPg$$Q}z4e%D{)m-SbC9#jA<$`Q6&>^UUeM zz;y=qNmtca_1n$(N|)i}hmXr^Q-5xBjn~5jXA-SDypCh$eB3!fDxJ0BUsmd=Fvp>| zgHmayoy0MGcVaVGR2K1GGIdt%93-UTs6Gsca7fwRgGv!4AW3ou4Udc@pqKKJEJy@8 zygwJ=f_mx#J4gy$1z`t7i)Oea91-znJVG47io8Tb#KtBD^K#(;7K7z3>}?h)P%KOl zHyIU8HfJ2W{z*v$pGI@dL>VD`SNiqozh<2xx>Y%|TqQnsklW$wQuckb;I5_CePX_J zw(Fsw%_tFLv`x5+qNDoFbWd~X@XHYf#)GxGG+*}ESE2O%jih}?fyFb^?A03i!5Gug z1H9c8;d*$+9Exmhj^3Hlu~W%^nqZ|Ls$!LN0!!wsdsKCnF>%*1Q+M)_x3Y2`c`M9EpI5b6ObWEx8$G`NPTQVtDP0ck zpYOk0loS4FeLU8R6Den?*3uG1ers%fj!kNSRK?rk$jZm(D!W%u^xa2C{pHC_&&hm0 zXfefiLt7sJ0<36jzrwY@S>1z1SyHkA9Zs!{LgOqW0d7Nac;-b3+j)k2`CfbeQdMnR zVWEl(}TiwYU0-6AI?Lsypx98rOP{ig8Duyhf_H6_Tdt`Sr|=zf}^Yr}CAlm!3P zhD8Yz-wDt*g0{U!#iq)P_lJ$Z41--KmiVWF;2uE{@P+D=Bzps5Uau9=gE?(Pg}hE(M&1n4 zNRi??buNjAasuD!ZY%xuCjZyP26a$6U8HzMuu4Nh$OiJ9WIA?zdvdV2uRDLW<_sU4 z@ri_C+$G`&j4Jj0yocu{YM5iIQ6cRy*Zl>MK_|UVg}NT79QkZ|Sateac#q_jeDE&x z%b>~d!dcCLM!+kTmn4Ar8{q&K!vYD5lcQngYJ*d>IOx~uV>$Deb8%G6iE4=P4$26K0^Oj?~TP}p+5i8YS4BHFF12@ra*U`*XoQLRqg*R8@ zjQhL63487ls?M4A*kA+b+#tjP|9|212x0NkqGIGhD*vE!(R#=nfLOkXK?E-xulRGGs95(ktC{~3xl0xX`G~iN2L;Oi&Mt7wQ%#l zHe0<{@ibE+hFg_oOVZ;m#kpZYQ-uz^X>^vi5wZj@0lV| z$55`mI5jR+k8&E(^I9do+RmwYE0pi($C!J8m~KxPE`>n5o;2f){2K+#si5-$tP7}{8rMbPOJxHE=+MxLE11aie^Nr2c&brCqOC++j z+;w?G9a63Fj){-ASk{?1amCKM&1^^3HS|3$=>`B}M-}5(7k_^~4sR(PeL5R^H344_ zM4*>JpoNB?0N%yk+X7eE?N!+Aa zrmz0yt2NijDFU_SM7M+U2Mss8nmJfIzPEiB?h@!~Z~Xgy)AMY-O>N%;`z)z0+ErQc zPOREYSO02mdvv&XV&}X1_ymH4f;pL7T`;IwCn{xc@CZ*C=p~+J9Osf6^+wec;ksM9 z^=X4-UBRPkew-g$XKY<3{Tn_y@G%3cTCf>3q*J%-`Kwphca{j*wTxN*RJ-<66@LSR z5Q8Kz&=8bGA#DhaDHJW-dsPexuLcNvAB$LD7`Q==ri&kmqd9ef7zB5f58Y>!RMV4& zB-YJ})-Ln!H<79aRa{tvY0XwC*d-y4g1C(AYRvj-{uh}`W0cgA_iwcD9+kqsGU{Au z1G!V82o9xR(8S0>MQ|O|hvdyL#D8GlqJAwYk;g7CkXO^lwbPVL`>d`B9W1r(>H9xs zNZvd`Gq9~1_--8n_qrZyKUL2zm`|i-!BhZQdmHWiqT4n3aGJ)0Z5+rjtARHCfpYSU5;m0Pa7x2 zN+=|@f|`2z@q{3}i$mh`W4vu+Ol10@#m?_bx!;#c$(Bm#<{Bsl+L)j0)ufzF%=Hzl zY#j96+pCM48j>1{8vP}3m9>iqo)rfg-nl~2|!BoK#h>617K!L>wqmw0r zhW?=}0ySNG4djI@2@D#^_r(qHUB#M;AJlwmFE ztg`ahZfpY4{@0|OfPvPUW&+{iMd75;w~^6Z)BRG31po0x)mc-umM@Y5DPhwzyJwGk zk1zu+)6B(e9nX#^DCQTWV}H-&>I85~XR!(lPk%(#NVHtLYR`1JOeYNU+IZJCTyC6| z_p)RwM#Xlz^YZZt@pHAj8arQI)g6(7SM{%Krg zVQF`OMVxMb_k1=y-15{?n5`xl9$$0b7=!XWzy)o9%)F^9K04kf@a?ot73>#90VC@WM7b~Cm@56Y|5*+DJSQRGxV6-w-$dR8a9sjDbxnK^ zHVQ^Ms_H?ZU+!&H5#X?~A&vvH6bxlEmpshq#nLo9SL%rd zx5vG<6Gbih!-DpLi$|&<^T8Z-?;JOn4k7bUYZCB*DfdF~_YVtEw&C3|w6P6B%|5ob z{WsIoHSzIB+ci(P`Ep93IKJL&FSB*ap{UY!27Vmw?=#`|=OH^gW8lAZq)lktFH65Q zrC%#$#U4^ZNa+w^lGcf=O$q5;$SbN*${Wv_UM1R>_r)D{t zzAuyFX1E+3yu}Yc)r2q47Ecz_6g;JkpjMDq=)L+NBe+XE4%lVY0qp(4q>6;`Ztt9@}h8( z*3L!(O-~M}3=vvNIy+2+{6HymbR<|fxON#81o92)sWvo>epD%UH8g@~k$h#mT)%9Q zu2?BFnmFKSjvMx^7xdfJ3a=4dz+%6(L`JY--_IfKQ;0vBx~T|+f>DWF`tGo(UL#BO z8QmEryhx&mK@Bf%3lG7Nszmv6#55BF0tWTdmE|Cg(6wVw5m3G+WN4M=R3p7d%fSBn zPef)Yng#?MuST%1KyV$tuOCKrJ}Vs))X_-A*fXiFwl2vvDl;KdUQ|^r_(n%ZsjMz( z_7yuFMN}iPKqh}D37;pEkHoSh(B70H@r<|9$Q%YghTAdg*WQxhFbAnT``P4Q@bF)& zNcNHYpYB$2+NxdOe^LtQ+Z;1Q`g*$CIeI&Kdz*VZTH09I0&VUFEu56HCqHhTpQrQV z`MF6Ak2~zkIy$Pzn#g$hsQYNASDAZ9IavNRexLg$Z2Ex?S(>wxzUAI6hu^hWD)f60 zYa|hKx_m9KLqR>-crlUwK~5pA_CD72fUd-zDuw&uhd9R~vRb zdx*QkOr6YYm9w+w3vYBf=QYyt@h|YMXwk0dy~zZLn#AIzu5@uTsdre ztRpOr;4pLpxKK?o3M}lstLtl?cx41zMY5>&?*TdQPmncvPS}nC+$Bfm0N2sQ2Yc2X zA(hCbg@z>-M;5xlLRurCVRRhEAVVY^QKr6?{3!Yp+YgsoK5Lu;>o3wZ-q3%o%9*{{ z)HU6lmaNOUzUMU0I0C;sfk0bdBV*v7j?%2`klgHlnUn11Y`1h+wiuESy)pIl?U1vN zWW=Z9o_>buq$C-2YU(Ps39)D_<8lL>9ecLf2Qj06^Awxb!G?>G0##T<;PHCf*r{QM zzR;8NmC45qQ3au%A5(uY-**br2+Da@*PAOK5POXyHlsJlZPaN86JV^b3YYX{NZ_|uTg(}B~|*82L0pn|BJ zURII~Rv}*PWjnT@Be%@w2f%l%z8dmKOb7oyK<7;wQ!Vb@M*N=`<(ai#lZ|p{PaX_fYK8No3NKR8#01i8AEH z!9v_&M8f*X(X@j%`#JFGVyeQ_E3jzN%lbM z3VTZ#9%(&wo*qNbLy*22ybu{lYji&yQgsCkXRt;$VpoHGAQ1;YN=cqP?)ehT*UAl! zCW7=0niVy0rNs6?1&5?0eVjba2Hgrb{E_;&_fC)dm9*=Uw?0KVUovyHc-v(?cs z_*IEgE*8Kj;K_G$IlZ*LI$34V?9+OqZenR5s_z&PtCe{ez|#fugFG8?J9|5H9p$Oj z-J|8@@w1ea1?=bI7Sbq}4bp8ODTH=*a&(-8K{%_g=J*br6_FdC-pL$@>PxQJ+m&pw zV2}%-l|xqggT-ZGhBMf2At^x;9@*u5u(lT&4;fQHi<|8$k7g#jDR+0#+`?V&XLplb z9lCEWX7}T3L#H>ZZeF^rkvmA@M2Y$hi3YIJ-d}Vv^gSa{H?8EoA=kRs(Ds__nVQbp ztAOk9&Y7AzIF?w84zMi#xjg=nJpL~w--!ZG_N&1;a5;_y74nns2r?@=)5Gjl3G`62~N2X6Yq zK~OtJGj8tM!Fo=SZ;Zk;X%bp0W%qX#Hnp7ni*gs?? zw)>zpunB_4s1or+66lE$3{_N$#4r%mCEJy^2P7yqql+nbzf*Cj;$!?)g$jhh5ze#NJ)#E(1}poz<@a;sC(pmg|31bu9Je46m!n&JXXbE3Anu&+_Wa%?Z3MIEZME8F?M3C`g}c4!I4=htm<% zp!RV|IxHWZ{DTWBr4!Um1%T3YvJ+I}(>$)`j;?4Yxp0yG^3Zre4h1DMJKc)Q%)pjjtHYkhoUW$KzmDm_M>Zx(+SL?vW zgh|?k!8YkAq_jXX^oKoGBo$2N^<}iK;J?wa9*K?!%cg{KQJJvJHV@!RkMU#_M+q12 zQNfC*?{UFr1d~D|!S%;ByS}SCKpX9I-^CI^Gobn^*Ar2#NNX$!_rUfTE4w?u8A&1r zp)83lP^zLn;9LCYlVd*3f1tELA=_v#@wNYQk)|}TUze)1&E~} z5EI`2eHaTW!@6&qfPMMdIV2lNFJ)+sd|f<*4h~?qO`1I4)Yi)c zrDtZNO)zQWk)#oN3f-L^J=TE+NY*?Y-*(P!q?H;E4-ch+Xr8_Lo_VDoS6)Gq?>YWr3kVfRf7!@KJlR4X-#YC zIW*?&satL@kIazDM)F_Zq~FBUmThsC8TXYo|cj6 zfQa}TY8FxGtReiT?V-J+*Q41dOyNDji=k#id>8uBE{ZwU;q|^r*`N86xXOqe6oEm~ z^Z^v;{iv6ykpg}P2RImZAbQ0dx5Xh*euVH}_!!h^>Y^f-#ouM1J!d)XcCOI}V*>>U z=&H!BXg4(yl{uV!*4u)~fJ_K;G4A6QH()C;3~=wl*GJAZP@>llws1TW#X~B2n zrtJSsVj+!*I9sEt#XevR zro#%B-@kdJVT>X9U6?;Irboh%j3xcL*>X2K-#Pkrc1BFyzl<)5`2K5~gex@ji@`VtBd6tO zMh#5Pm?V)^(Z!|tsh;trnaKf~o^{@%I6cq5dcO*kOw1|3AWZ6(!!lV<>yZgftSrW1 zegn-pJ+)~)J&(q+Q+J)K>Drw{7Yzu3MjxN}-PZDIe5PmSacJabwMmJ4(JW}NIpaC4 z_ANTCCMGa}?4$~7@&>M3t2{58c$`2mOW3!_{#CyFak*#MseiVk@Lx^mwb9m7ZHh%? z=p>dubNUGbND#o~&1t;F~tz;)?^&t$`Ph>A= ze^0r#7IMg|*@5-+a#?FTQ_F?zY0pUaPmuhy`E^1nA5EJB13nnyrph4(KDb_PzlOP+ zOh#I1CxZP-wWKNv+aB>4!BgU$a|$H}QxQNG(5OF8W4Hk&=22JZk+Xu%@+dM+$rK*) zCqV@2zbc}cD5f<%y)~ULvRhMCJtE@X{+n@{NpG$`!3~!-K`5JcOkq5X>H8k-C*tSm|2{oD62#YcLa1u z9U6oWCf3K1=ch+?{Z1W)Y8J?YO8ZKg-!hMw;7Zw@I-Tpgj7@VqZE`%lb3Bc69M8qh zUV~RkH%7J-sZn+7Cb1>!sh&-rmx6e}ZFpt)_UcCx1sJ+9VUifie#^oap;HIJIpgIb z+B$IcjA5i6WU5k7VF}_$Nz&xl-*W;t!o|NKtY-@9jaWox2-7@Mx%YJfM0d~vYb|aT z1nmZ`B3R5$ zyEQ~z8#DiqlSH*f-m3n%-ygPtWZg=TW)x~^;g3yFuCdoRYD5>-W*gF!sxs3(eC-^J z-4(S(cUh#1vSY`De0U%Bu7-9AAAa2%fGzEql$7Leh0(x2-KSE$L{iSH6x?=a*WE>m z;6b~^y{MeCa!YpcCrw>!Gv(ypN@%{-vT`;G((Z@mojN5+!no|G5Lj0=&TWE$ohvK$ zrKJbPYIng!$Ar=u7oej~x5l=n!dk{}0(1QoYazrYqZ7`HRVZ~r+1rBJ7>@aT&nl2O zsVmAviwJX1S)6BjoM(ojN$lV6X?%KYi+Ze^0A8m~oBA%#Q?a+b!hxgF=-Ctkb7l_t z+j*2M`nJt%uUxiLCe9qHF{Xv_wL!5_smMY}ts*q#2%rb_XL3cz`uaxrVXv~*<`qCT zZTBLbDuQ6Cf-p=G=hy?gLWT2<<@W6{K1Ey zN2J-zijI^2guGa7mJ)Y zG`=0vF`C7~K4;ib3x9YoY5~rdTjWHg2x7bAWT;Sncbl+z5OF~w22%2ejjU^QR^%I* zvkIk6j%Kw(IAU0TTaT%-@0FdDm0xkT*q8{7F3T|W{h+;x^RUgksnOqjH{N6@+(?n@ zfJa(WqFP;mJuz?lPN{%s=&gfWSUH_FB=c?EEj*u8x@e`BGtZ@DGml$NgjI$i zk01%49h;uo=4J2Z8sM9*B!L}s_bz)E_EH98qq4^^tL`%y(r^cq_IE_)dqn1K?E1%^ zB9=RkTi#ce-_wVku=8OWARZ00EZjG9OupiyoqXlfcU=UTnhgnD+%O!K;3)UhoMFL zf2jb{Ly#*jkpfJk2xxrK;`$bB#iyR!$QB&>PxW4QyYpT1T3C4b{%F(H`+51En7`n! zHa1P<;OrCxvY&4zu(IhSAXJaz%BURuc{I&S!@=p4-bx*9@k5QfxSX(V?#}%bl$scuhngkbX(PLOzdt#50pyViKo z=@g-b-eC4xHi&>2l9I_-Dt>}6(Y4kjmMXQFk{u;3yW ziG>KJNNfj`>Ziw@xyJAQe!Fy_=iAfYI#kEo(#=AfNyS-D%kZwHYHKP38~Z#p9HgHx zHEdmJ>b+IoKU?}-bsq;q{TzQ48DIOkE^sNf``VAQH72s#_p}lp@C@O0o8V@Iyqx&)X*iS%y5;cg zOZb|q$^NtqvLJY5fVX|Ki6Xk?_`<`%S&I=ZqfEUO#5#lQgH zkE*MS5{7X<`GOeqX&qYiu+kCSgm`<7ZFJeLW{88h_B!%1GD4P%gaJTrGLu?+IT4?`xW*R5F#QEXkw{jLRHoB=++04I7ta#SE*>}YX@G-y2oj}$*6 zlOQeIi?`v{+xcmbHnGW0xRcMlR%{X~!8cpN5AN<3+%3T+z(8`f>wPqH4*iqVUDmK7%8*Tw2Y%h$%K)5*s5&+mlRUYtqT2 z4~33{^5Bdr?>D--9%4jh0}34meas5f0W5)tF{UvPe`f-9&7HUD2D z9V`a+xH^eB1uFX_T)e-(`rr|c$BpskRNs7r@4s)^>3Fai2fhrJqu&fR&}j$u(W zUE3fh>PlMb&v6|9DkIil|VqPi(x z)PkI+rvf*!uC4Q5g8nqdQX6Udi}SC*Fp z_}0;C1^w!wJJDZ}iD`J0$Z$znk^JCpma|xoJ88pMNvyFYvg`qmgHU)tI9q1D)(Q0Z z7blRl@yY@g9sNSjKvC%VJ<4xtoAhZ`iP(S^R|_p9|8z0*Z7)?M)Aw!_3Koxj`|Br5 zO+=spC|D35lGxZDRFWL=kb?Rq_fhSVV1>EHmE$c{wuDs?qIodL5XaNX9P+ZW3uz~~ z@U4igoql_wBef5IclFBdc!bcxz|o7(vq+q2hOR--o3(eQ!R;MaF&0mL7BN*@;)bmt zdf`tCk1OH*nI8R3cGHM z231hqwBnj$%$ZG!b0^opboRzXKs9znb7SY;PG#;_XGXNCvG{%T8GNH+&XzR;_nNvr zZT->Fx#5zie5!yk0($oXME<^?luFkfn&6i$#aW)P%&y|auA}6Dl*51}NY~S1G)8SSdTq4v0eQy+@6x*9)Vko(IHBV# z5y@M$8VBJU->(b{uk!xpA3{E+?&W;oZ1MaewLv?s@B&ztFyuZE7bemzh=%mFkh?l$ zMD+Y=ANU7=O0#Q@|E4P-D`=fvJ%tL~?Wmx-i~?-Ib4-s6LI&~5YeZ<-?)|Y*u~c5_)q=i>OEdv~GSof5^i}SqbKijvZRE|g1ni(xe9NqWs zG#U8vk@i0Ph<4$Jo8?Ff%D8yw`Rvj23$tH=D&iun^1ha^ApUgd;ctuC>fxAQLl(>gd1{i*^V^kulbbt|%`5!?JC4~YMj zQPW(Grm@k9zrg+YgAK#B{1?4Sk3rV=oc!-aebduhhsZ^A0bJd|asp=bOz7^`O2TgC zOebDSE3f^%NKsY%wls*5aMAhDZoICfaM3BzH~WVLCeE6%;hC0s@6i5!X~ses>B0{p zo?cDo^0w|85>Acj_0i=llX1!moT|6n-+wJ$nWWdMV-cHsf8Bo++I?;&d~BJO^yKS( zs1HPyj}%BD@n&PZlyo@4dOR#dtr<%o7t}%hE2Q(sk3np+h$*019V?wRtZ_Ext-8>s zw!*8l!@(!(yHBO{mdSB%U-V``{eBz!7hn6HAVX6kP{4mqz+L$aTs)4&u8qZBpK^}Z zqX)qY8G5zxKASl!x(7NIgUUMUqI3C{asL@I0n1If37} zdsy4py_az8&w~o`4tMKxF@brtOaW$o=akHXg3L`~bTM&(9|PzJcIbAY&&-SvjDDp; zzz|au_;zCiwfWI655XJ2dZ63cGrjC{d7CRoV)6u?V>p0`CA)9bHI0olI?hznfXFhXp;wsGMLzstXPOqomQ+Wm?kMOivk# zpS@iB05{xIMD7QQ5ujheiMivbEb#RJFg^Hf8&T@r8h8M7j?@6N^|h}S^hMD7m|3a0s4T#9OV}2&g;&v%57d^Q;a(k=E)ZeV8mt; z_>2+>S8P(nLi`(-R1WI1vTpC*Ea=yZCR+V`Cq69a&lU6V1+y&?a3vwmI|REcIV?Xs zBs=t8o(G#(8oXkRqBW7p8ilPAysRz`_-#egy$8SKGD=pPJ+XJZZf5SsNg3;s`Xocz z2>D4z?Qsq(|BuHhe)Ioq1_aYrqYT>&7yD>M5GEIYDL9-wKtwOVNj2?O8`GHHa_9Tj$N&B~v~TQli-8!)Uss759u}@3@8K+#H^xt+!MkW+YER*f z(T-vKjS(A#!s^Pt4*rK;%8w&*!(!a8Bq63OcogV$Jy;8b!QPZ2xV}xI$9|S;qy2GY z?#}ZzIUEV08GkQXF!&($1Wvc|i0_}^5=?H#S72{eE z_&o8FQ(<1zu>1I!u)m)YI@W-GUmtZ>A9ce6%CIIEn-=(4?|h`V`Y?1vR5)_xv3sM_ zU!V85izq~1KU=jO%Nsw5Ui%pV0+7zujIpApiwOH?F-TQ2hH-FYbJxs?OTBw&BGusm zM@85_Zn1?(om}GG5LWITs0c05-{h?(5mBj~5yHR7et!9OjXTkTw$=%v+6@s#L&5Ur znnImYSG^mQkUs&#v`11h4|?p<{{5i#cmj8}QbpwQKj&DGqw=>27UhROxZafG#O;( zCnROof0q>WnAkv^Z+IUIU^AKs*QI5qmG!5M6sE87%3I#0Do)>^CS)b8ZIfbFXatw|+4?^1MpwQ-DVXXQjs z^~!uZ*C2#p2VmbQ;~VGrMe&>I%I2?!%3nJN{c#KZam3#=1kMF%LRuk6J2kg-2?F7q@$PFUD0#gB8&t~uGIjnQ_ zniJ37@b+?99%!PdCViK1=HtfkA(dYL7zYgE~|IX858DaukSrCQkf3Lw^;(#b?XV zxx|Glipor@j}I>PGEH>R&|UyE^e+0AW>|T{6H9V9bb<|-v0Bx=4kWs|?qqX)bmaZ1 zyQT3qO-JQ1-2{x%frq*>w$7KEa7c=?O|V*@DcVA#R?8KZ%*Vl(yxpN+i~5&8a;Nh| zuKZ^4Ewz!jn-8HV0^JmW?ySQuD?k{dHbEWQ%TYxZhr`Wc-O55;sz!z9#ma+r_*}2; z?mA8>vBf%1mw)_grg2D?g=R<2lTAZPF1@syu>_f^IE5$T1K45Egt$5>D+FD@P6Sgw zadZxmKr-JYOx_G0_x`jTD(c+s0)6fdfj&=%mc~AHuKPv^U*otkdTCsFlk`&9MA*x# zFvsO(dX$p@q+>qqB?a{@_2opy%OPW*G}I^QaGQ?o4Dk##kW4lBU332t-qz$?E->q8 zpE?+cS4$khH0BU=tLxpDhTQiV?B9Jnop~+((7*7y#6%>bCB}Lx*%3rS-L-jkHulSF z4i;18YeM9^k;2aw1lPot1iQ*l)ct?b9168JN;S@`wTX@lu;^9eJPd1%?cKg9xjDHT z=qmY`xf_^i83+iJD?#VJP|km?!aX%CD;(23Oe@+O zIZ~54QdO!`X2GQqfw0>0h_v*f32{>Lveb8H8VhY?RhC3H=a@I`=}gSwz-cl4>V4?Q zV)w}A8(lz_vs2jph)n6<4NwUwY6MGM{yQA9Bw_`L;~7c7B8jq(M#&XBme9F zE6Dai1zF)c>U&vJbAq@}x=?T8FY23WT0GXpOKw^e3|~QCxW7#Yc-a+v0GBE3AE*lI z(Ixz%iDwV*^+$bL)>-$tdL+v9eVd&(26h~Kqx`hy!BNOzb`{VJ@VCu_IFt|7;IAR% z?G{1WjK9#SEXNMjP^r6BO7e1PMUM0HeX6bHt#jkzzOLp2-Bogeu8&1=f0WbtWZhi! z;da>OujwePjO8V1A=%^!Ok<^PU|#LoAO2CcvGSm(PURTQD6|^wIxIe!?FAN=J(PX2 zZo!k8l0hP}k&v(4G83@bYa!Et9@hzOoRKCkjkBucoE1XeGIq?hHCUMW}|dE^nnQs!7ey^W%Ksry+i^lW)}^->}>vaZ@E%_u^k%34wn! z{(z|m1K(n$736a1#LHz_PL>NRJu14}MZ$c&RdKy>sJ!#?-izYW^5RN^=~OZCWAiQ` z2Y`nNUF^@@%uY1B^!dBN4$augZtQ8Ld1<9iGj&j^wxhpG4<&D3kX&$Gs-MlIhGoxV z6P7S+WYk>}gL{?3eTDB}=zpBSsf1Jnz3;1M0Rb;re5{O-zcgcz2!ds(#bKEPWt`BB zViS90icJGyiPnA)A{=5N5@ul{{F$WUU(R!xAvAI<*&rv4HPOs0A9D zD6F}K*dXF(z=Ri8sa>0Y%Brrw#w z_Aa8PZWP^gviGslGSK~MAn9zNX{~NzE^VQtEbi{0_Vb6h+EeURZSBgzL1|^oN>Sx- z?Ze#5Z_`~F<@udkegeaDmKI?3)m!+VmVs+WcMC&%<&z3j;pFq_*fRqn+hkm~k1Na5 z>wSx(!&7=XGO43x%s5&xvaP?-rPXoinFIdBNy^IyZm7k?{6n)=wuyW!C12E9``4n2 zwVKLW(osCH5I}+=H^_e8e!Xf4z~$n4&NHVsd;j+Nx}*yc;*G=AT5f4aijq>l^#JC074D$Xe^sMIMAPGF7AQ%(Fss3f+kYz!a* z86gHBaFJ7skNZNUiu-bWKXks#+TZ~?$ynuT@sM-;F|W*ACG6q<7K_jE?9+kQ==+Sh zQE$HvykEU@>{vzo*F4cpL%MhS5_^+<=E(=ZJwVfH_KBC3Pf+kP$C;CxiIY`agK==E zWn6=0V29(;_xF>qtUtY#1+h>t{gm+O$&c`Cz8AhK3CH&7YfaZiVc38mf`O1 zSzDi7oF1DS>RnLO$QsJx4wuxzmse9hd$0B|9=A_JY1yFvgeSO<4hs*U*-vx*HYT`F z9!HO;M$?yu&psl!Po@9Ie(?kFI$dMmq@>>F-s>Ig8&L2KIqq{t4bhhJt;V<;FR7BA z#w^jxELn5h5ZiYyd16JFpdpp^aU`a&v0Sb{uL`i{x9jxS&sG}2+wekvRQA&S0oE^o zH<&$`D{2$uEPyyO?BqWI{@4!J}c5Z`O;&vJg*RSo~f`xdTnq%F|H$z^F3FM8!)R~UHb*1!pB~nC zL1aW?x-D08wO$nY%BkH7xwp#!d;T+~rCH8sNkG^)BhzruQ^ znanU%Rl@=7;G_{VzT_Egqcd&DtDdi{WrWS>r!w9Gn7Te5!j7o}j%nQxb@p_v2+7H- zs|!nIQx|1ZR96%M{FdOtyPq~fO9RVG{jgmOks>hoJ6E$pzPHN8xr_@Zk+lf4_HW-b zI*p0Y=J_@2?|3~N4Vtg62hQIq#eCidkQ&;Tgq~-g7N*lL8TH}B9PeLSXRqCIuS0L| zDzsD9J2ZdVu6UI72xu^$-a9W*)Jk(V$wx*GO{$Tws704dt1gwTPXsrQR;ee)Nt9;>85CwzvXRhw&l=TX8W}I=3#8({>cR|M^^` zNEi~!c8f4;rXDJnZ=S+fG7u2Q!i|y22AjVFro)aLM%);(^V-uV70a5y#{LkZfER}9 zARwJpYfrV7M-V5d2!D8>1y6&M%!&C-w+rsZLckBA{^!wb3uzFkwviYm<$uI19_T zYFSiBod01igRS~CjGbF`5(w)uM#AQ=D$WtEh|M>1%g%XPR=!3;(Q$G+FSx3O>5)I8 ze~iNO5HuR#q(HSmj6M?z>wX(=Z|aLTKhEjnbAR^!x~%d(E(Uxu&J=s_dhTvWogQ~c zf4%*7H2->O*%GAE(41-K?%T2UcBuXu^f=g`b!q}O`OI)~U$aax<3*dT9ul2Mi?@3~ zijh70m;5GiO#7&uuf@?##}0kQ0mO6myWvd9onj1YwOzpZc$>&S!F%ZQM2h*S!)9s-u zG{RD+j+A|Njr~|H1jR z`9wT4OvZ&5M~RMIEm!11`dfVt6cFFiWD&=LGt#x zwqYcH2}l)m%`uC9`-P0rYUw_jKq25)pEDXNlc1%OV4<@odIQ&FRg?4k>wWv`nqN(c z?bLRei@>&nNBn^P_NAuxXDxA{JI&I>(gL@bp99eSIoBJ@@A>Nb{djTv{W^5q&)4#N zc~wCLlk@Gz{Z>uTq2)7p{7%|$>F{Us>Z?`m>$NS0VFHN+sbvN&F9iX}ZkXVH3xD-RH!5r{1Re}(r{Hq`w`DkSp8iPi${%?7j%%I}N>zs1 z4aT+;q@_oysLT#JY&>E_WF#*l>`)BkUWL=^gq0l({4{?MK=@F!P_24HU-d#E<2h6r zdV)8=Cu^%x=0Q9cIg5$z!{?mj9#MahkdVGZ3$b-*X8+$Q-pE*)DPZ9NmW71~2|v!Y z_~X6e_wO*UQBpWeR=D6n{F*+}4CsYa2cnTx+$I@@duDu?G6s_w#+m-gDSY+hZWdG3 za(4y%AF;i?W{KMutvNI{4w#rhW;U~D;Z+Lv=Y9`!shR8?cG9#_A3Q?l!52TbK6uRR zN6%^UVY*?kXLLM$dxwsB`@Nh*j=#-g8NXjg-e)h)$C23-iy|=|xLe_P5?`I!; z>x_)k2j(|0JUk^%Grm=~JL!L%B8{$-HW{7NHT}G%`c=(1xu`5mt2b~nW}M7P_(hr3 zYQh|?$q*)V#I$PpmLXTZghOLcVZ@9cHypsW7b)*aFHtVriF2h3iwPb#b4qr@gi{UZ z&(|PMhL`78%KH03XQGNrSJOiJD?k+64{%85PstD^8@d3;EAjwm|Au(_j);S-hYXK8 z28O4Z*#p<6yYg+)UO$cr1*>k&+@-q)rH?sgWZ`F)Z4VSCmLL zWOJ1>KfB_p&?Dio%0V?${T_^ruYUBJg<4mrX|d@0t0;>H^oGO&(GdunGQxT zCf6t1UjSJlmJQ?Y=*YGFk0;+w7B@Sm4FOrv6%IVUjjFyt(MCbD_7wwBLuO)3?v~XZ zLbP)2&{9nT9SF{?mro&BgbIkto#;;FeW+YVhb0p=v+1 zes7pQt`wxuPMx-KUMzJ;9ks7(VdG2fGMhT6bE%VH;lvxE>z+~>bHd94$V+lxDyQZ?; z#j1VOCk){72!`{A`6H;Dbl>ueVe_0~TtUy2&9t@# zp6rwTFOm)o<+0-t6#5vIp-;%j^ua#rUCcYl8d8siIAJd)JURbFveU(=X=2724&y@} z)gxt(V&v0zp{9U;OEiLqHT{wqvL-?`p#hDpP8Ov11(1ImH&R9~!5iKwMWA*ymNh8w zt69;Q1d>jgPC}Me6sD;qWttx4KHUS?QZlj*yjmMK2Hp(ZXTHrhP`+Iql7ZG&-=D_n zR2n#as(9a5*O~@G_cKq%-`qZ4A8(#Of!r7x%7O>L>gDTK>1Tdh_DsVP;J*5c(^TD5 zzNil82axlVPaL0c`I>*JI*}Grq)qJsADKN9T}Ig_1A7qX{Y)u34?9+^!KrQfxgBY7 z1q2PzhoNM7>qF+Ri zl@5p-#=i3rCIG%PE-?CsT5i=;WdK7>z6cm08<+@yxQlKK`kZCb`7wD;)^g@8be{zsAVV+fBCiAL5Iin~c{lcI@^ ztf5V^PBsb)I!+yP@h3(!{zQU3x4lDBi?9+>kiRx zKHhtar;FRck}vqVwC7FJ|B^zK_1N=dbU5`ZTe2F??;VRXXmc2Z?}fv8`fp) z>%JeO50bK1YlG~o)tf)B#3pS?xbE2K+|Fh+*VyyY)>YkERgRs5np#Cmmd$SkPrnz^ zp1ElWuhuE6q>s!WpwvEepLXf$Fs0ZDED?meOXpC;T6wcAkw)PZQ6!#)-M_8qbj_p^{${nfFKT^3=kKZC9 zC$gkMpk+6345;vogr9-|YJ|5bp=Px(l{EIizHQE(A#adiHlV$Z%^kCsp@#zTT zB;dC?egg~$K?5d19~iN%5}c+U0_HF&uE(8WCt(7pUh)uuP|lTZi3O9-FXl-QPy{b; z!;URUMaY!i|LX;g!vb1!V@XmJZEPvL!VMwehxAh8=njg>J?5d|A^#T+@4qt&X2Um& z5(_V%oL~-{3Kc-$H6+GpX_d24l?jN_r1AH2Rb|;SB(q%((5b=HCfxZF8S2_Ls_~0E zEFMiutn;1(UYSc9)2VRG@1CQC4SgqWx)jPKv-VYz24e-fo~60ww=XBc-p838&c{Qc zNaLc5URQ5N9#}+TB1zts_7`sw%UwRV{Z`-d=j(Y-)<`Z_l2Lu#>NYROHYhy`#ep3n z@h9mJ5 z;+<-#ZrjIKB}kTjA}mHjV)@eUIe~X82pLXLm}UzVyc-|pNkh#wXfnt0hf zY!rOh0L4x*&H!}7u5CF1Hk?TMZxfz8q=_rnq~TIj6JaK9&MsqQ)kPn3$6sC305O5p znU4Pv#Qqm;uHb}%bIY{WJkpn%sJ$_Nbf!Tn09tqxey_D%7qeRziQ9Kk!~nXDFf*ET z36n`C13nEl4z&b@TXR(JLr@n9k;aGJ&A-pbL5^&-XX83{zdd!H?QMMvZjH9jVov9h zj!z#C=2_W0&amF^+mgl36yJXcJT7m}I5re!tR}x4I^7JccAYnPbr_yMyy(5Q5YaaD zpB}fR*iu|YV&sES95O*m4vaG-ll}mA1`}}l+9m-O-HOO}OYnS3CFzS-nL>%j0s9Z| z2rQG?70D=d4F+}*DfZwl#MS$_?d=JW^@-f3JfdNS?oU~CgJwUp5!Ot60pcfhLU_gO znBQ|wu_H{M%$p%5umMZhYZtSz@D|DCa}CDpSvN^&vTP@YHND_sb_Ayjxxi;hM5}fZ zUVMekqT+}gS9%H^H%Dz$98=B{HZNyGjx_Kt8wHa|JiH$bg1BVW@0ZKGncp!}e4tXw~e8Zt0KE)jjt`Qy32w z*VA1{Ku>%m<%e?LYp44{EC;clmgmpU$C(b}t!*pL=O#T}27O&_ZkB&r1g+MO_|Gwt z9HVq86`R+S(R`fi_71)7#jpn6_g5Fi9F*Sc#cr3^ZYq$t#VD0U7Aar9dml{Rb>^03 zIL_&b85Mb%_SbM*HLgJ#r|8nlaM2MD(v{V7QP7YzO*{E@i`dKmL}%qrn#$?AiKY)j z5Z_JOq%3*K(xij*9dAe!slmK*RD~`5r;tleTc7rntPT$HqKe;qn)!MpWfjQt{Xy~H zDIdg$ZkBvMqz>3S!^)G)3bh&Jv+5j;#3MF!XQ`4B$hR4k#}8@d=kIpoqUcbW)F>p` zQG?mPxxllP$Bg1t{`4VaqKq3*!iXpK^=(K~l`zOy;xc!r#4@t-`5ft$7L@AOXq6@oazzV(IK=6?RkP@IZa$uZj|0DG7Q`-Y$v4VFof z>c1j34V-`{pXE(tbCy=Jmac3-iJoR#eHV&PAXFX?Vx=W?iWpbDQr>j^CwS}|8tMfT zK29j(pD<0*sJ=a9`5jzHyTNyL8#cW%twMhNGQNfp989mJl;U#36h9}Y>mF}19|-DTl}b*S zE>$sOJ*>r&EJ3SrFpQt#f>0Tw&7u`4g_;fM-;S>qq(^U#QQn`n`b=sFutXuT6UAc* z9p4FrP7!i2Q9LjTK3o%>JVvTzCEn7*2VQ*XR~r;ynfY4>_?iotXi?2BK#5ypz$85i zz=tQ`!s1!FW_k3N*putn;fdt8Uda0i)>dT*h+a?l7e{J#6?MM%*)4mFI1OKr&wMv% zs~fM7En3jV)K+F<5#OCe8{cqM*+jc?3X@=KCPsV1X8110gBvuof1S5+?o-!q$FpF- z;bATXwR3yzetFnCif=e~@Og5&k8G$H?Er3Xzp|b?{%x!K>~$|O-=#RR=GiwE1FTuE zSv_B&bXq0(u}=1*U2yO1TmO4s--1pe-nwy_Z}ZgMve!HC^!K}~;}dALN$kh?dzJU) zeb9BsN`|+Wz_lCj>agnp+?I_WaI`K;*34^Rd_Kw6kYH>R^H(b#Jw(8e!z(>pHM_5` z?x8Un<^^vep(W0oFQmJ8N^eFwXsJ|fQIn-mdVe-mLRw+QCTjWAIaK7RTIO?f*g6S@ z0IjG>?|!uBDh`PC#2GOiGqdNYD%Fq=JZ6k?D_k)i?dluack+2$IRER{zWHC;(?)B1nkp>CSSaRxE>cZZ+t9+E(~g(NhEIC#d|8qD#meW1 z=m?x_H2=Ub1j|Y(%a|~7cos?}KrFE&hYAyz=~k#@oT$9S{^0a5BQ@RNYkO@++f{#A zs^e<=<=AFjXbaK0ZfbMQ$4X$iuWj|&o~nsGRWmR4(&_#A?K37)n@6ZGR=j^F!c#J(8qw)#?1t}yhgB~2f1V;Cyg+(e*Ih(2=- z>u{-jzEl}EBuXz{F2A;T21)};xDjs(Du>AHlY+Ft>lQacN=6lZ`!!F7V=>boYL{X8 zO2$pDk`$G}UEgPxF@#dZ9cPc(Y~r(971`P26?)Cpos&yzTNPnh644%Z4eEc7{`p8k zLmIC_?JOsW8QV#XEg(tbj}4O8RR;%2d=^TVF}<)-#0F#YvBiB14z=%PR}T@zC1Q`rT}k(Y13w|4L59aU=~Pu^pC~$rc%z zMpaMzUt)4r+hS*zofVLwRg7dcGweJ{+u??uW3CxdWXUFfqKg_bX!U^wr0#~O;aS{x zp-D}HB*#?XB*92Cux;{yY9}`!){C{A3*tj+H$H!|sJk7B z|23XaO!@ceUYguiQr zYAG+~I1e{OR+E7xHZVe}THTq%iUg$JZy}%jTU`jc9Jm)70KBk!Q4{q4LWi3uv8fLE zOd2JV4}e4ae8C?#0b@gH77FRb8sM8oY0Ipfd(&hmRTD}1!YE(y||Z+xhRgD zw<=zBDx_l87cU_|05=3Im35Q;G2s7_9#1Ig!L;mdu*eDJp46YNTAq&gu(ZKqwAJ>e z**{KOb@(YO&FfmFt}aUC`s=p<0)8-yXcXg4qWq$BWPiZZ*0m68(q0?0!dn{|I6)zWd zyM;tH?aeC}t0vc2BkW=vb)x#Gucv#RVtmqXwysy1%Qf#S!q4k1L~m8DoAaZtD*XGv zh2)>LB9E_o3-_r#>yCH8hQ-%aivj-QGUHV{pJ$VuZ&P(kmICWkWT>us6md2p9OeCz zvkNhQOcEm$O|r!eauQ|Ts$JqWzcQ%F=cjNr$(2_#^C-vn=T8ao*t95{a~InYMvWHK z3Ep6q^jXS14dl-*Xv%|I4RnkO5aJVU30y=jK#Ik<;bs!wApj~u)L!~-^;lKdXmt*m z;etdiYZ@Ub0L(^L4YDo+W>2R{XRK6bRX~`3BoiG+JbL^W^VE%MH1IoakT)GuaT42tfT?s>EzexKZLcT{u4Q zL|r z2Y3SfU8zpV1CXAQGO?6`6^uj6|GD_*amSnoCHr6c;zV2Wk@vKg2lxTfD6Id}w%){p zt6J#9_`qFd#+KB;E|a`$f1j1uGWcOyv6tEF(wbUO%tR~Av0^>>gWvVh3L`(KU%UHT z?reBNLNld-wU<-V-dWQ{6wIn#hee%A(_G=o!Tbp(3FbhlN1?&JWt(H>Z3mG0WwUQ# zAdywHP5t>%Vp~VyblvdW+sE~xaCFF0n7iX_?6n?A==r(pL=4l?Pb}%p-EnL24B9jf zms!?Ww~q!k2tK{eUEH_8t)Jfd)l5G=T3iqC+nyVv+C}M?{}^U0d2I9317jHHOZCNv zHR2K^;EmGz1o>AW5rn(phRd_CCdMh`yI40BYnC&|+vcODD}L8xQnbmJs;E!q!HgXH zJ}Wqi*r2vQ11Yl!sWK>zjfl^uznc2v0?{>MP>Yiz*usVQGwwJ)_;45x62&10XKhIK zeveqJ#buBJFgi1P65{l9=342(YdVe5WpzpMp^Q*(cVZk?Gf5(bpvyCY0bs`kxi&3) zuY4vFkOEP&%gxwiu6J04jh6wk3(f^@qijDOK+f~?hftWl?~_^3bLPEN;*(6!zlYfl zdyjpgO9PY0fr2CeKL}qL+)Wizlw)G{9VaKNQlR4>8#N=p?+!-d${YfLfuIe2Cl%*O zHB@f(i_ec0%0|*I8bd>}|7sx|jF}-EoI-j}ArEDTy_~b05=grzY8r_jX3yO2rSa4Y*ZH&ZBnI7k={O8QRu}>eJ$q%7X<(%1G%bHwe{2) z*yBe!L5Z>tD|7*#`p1-Vb_YcNOSf9B#S;HnYB$ zS6+rYsa!RW|QUd%gL05q)P7`D+X6DjPP5*DfuW>LpJusXk3I zKiiDkKIN^Gal^f@q(E;h|A2dM6{PgJ_u0uE2_H|e>1djKvT8fN)LsXdWf$t_irItS zp%6WCcEj1iz42h@akRL$!|9FJaT4`jE%r3>Hpqzt?pR5AessD!+?)aTR8ihNbr?8$ zJ60XtwR*Vzb<6)Eq(4FyKI#3^%ZF6n6$qJvd8hIZlyLEh0N4>EmhD{cNMtMO(h$bQ zA@@mCl}hF?#j;3oX+acO!G}A$UUe&3@nr6(U>GM^^GT*rmZofIsD|Ow`y|IO5?s;I z1k`G3GT5rzC|sQ2bUWe;1G z!Oy5TYsbird8)NI`DDnBFktE};39~L10I30OA1+5HQW1-#v&>z+BF)g%k`D3%Qawp zhnxU_yn$o@G;%URmW(r2g+EhKwirPiSI`0zT$~!OB>$`45h4ejgQPuVdX{Os$~HUu zE8q8G&ik|W^*WB_8(!^7teb}xD0O%Q5U2_ISR`#05xhjF43$f2WQ9eJ-eSJDc7N8l z6kNA!S#kJ!z{BoNR(^Y=jpTmZ^K13fbywc&TdV0l-sfVCekxvz`0xn&V)h=5^8%DYxT8^y?4rqlT7ivGtTk zXUChC(>Kn!rL`74ckj6xzM{4z4w2=~%%X3Y!Gq}z29X#VmJ(VvIL=zm2H~=}MZtyJ zRa&3x1IO8kF7jam!Z@)}<%4OU-Es!(L^K5gns7au$w4#H5XF)>8!fJM=>na2vbbF@ z!3i~pJR2L46aDANDU2^?l<$RAfl+GWjUJs7RP9AqzEOyO0At z0(AcI18^7ii=&$SL8-XHLJ19dQb&lr91^sCudbj`^&&J_62tb3h(zuDB!Uf3&_%+#FTlQVwT5dS&!3^wzs1HSojUAi*F|2s*}sXDoSto*dmv3PEHe7$nu z@uf|4!|2Qv7Qyh${-CMw&8_D~$>fZ0lk2I7}tXFuvH_qOBdS6`L zU2K=dGV155)c0pNfJD~FgOoGQe!tcVKcQWWZyr=M_yha>+ITOgyLNIi1T;2;6>u`fX@YMnhrUvq` zISPV-T6mUbC+lWqqoPoxa{e?EkE&C5)~6&o*vK$*+$wa~UjZ_d@&8evs2Icnd#Mqc3r zk)Bx4q=0_$vD>Kf#)26g7g+y~280(6)qrf%qvJU&o9DgA=e^TQ;4)Hs+Uuy#Z(+ux z#MdPnT+6#YlIpm5_uVpR>qg%zd~}yqPSg>?o&U?5#96mk{zH$brHs`#p^p0hYws(( zqU^f2VHi>vQW~itM5McB=5}-}KI@C;d*6TI zTWi)j>ptg>Sts|t_Oz?O$EAwP+v=Hz zwVx>4_!$6jgF#z?e^z^x7_wEmxrMmMCK1&TxG=)Ux-GAjh>W!elk~di%NJG@c_81HYv%iMuFpLqH?5LDV&3NfQbQ} zwH_QZ@^pg>om~X~!_cz(j1;PR&l=W@3P8urO(=naC`d9jkbHC$%!K`PW;;H(ta#CC zf`O55Wi`1Jcqm!1u!lQ)*YA2@-a;X_$=3LUvA(5Y*~F}KV!{2eV9DGcQhQ;!5|`>P zCGQFS-A2|6GxRh*#`4?&8b^KD{Uz3ii~ky$LDjxzL!Tq{2DMro5fkn6jvZrLhO{S+ zaTrbg(nf1}2XBscL!3(oeM@J=^vh{tQ!ag!MKW*i6CCoj{M%=mf2|=TwoPa}svsiH z5K)2t%ogoz(bG^tj;JLBWpHrKZox*gdL@5QR| zXiJsn!7TzucSD`DT429s-t3c+U+RAfUURI2KywZ?60s^^s76d-g7gS#SJK-iR8)lHIzg^$TYRjEGplQu&s?V zQAfoLt=EvNh=e1pYJ>wIO%dJ1r#?iXa6JZgV(h`5uv)F`FCU&g4rdmGv7+6FMumg~ zkn5<`@*J{zJNplo!&`t?LpH_o47_wy;G(xQC9^m_^T}X#?z)+D(tRT+^I{X@p z5;PuI8m8rKUAI+*{ek1^;!4gXZ8H=n?$Kiw@5W)wd*sL<5)D^45aaAe>4ZiyEoy{Y z5&@`Rg=7QmLX4J2DNxK})OBwC{u$^M?nCQx&P1`g&3|!+IQVus`KhWJUyzDAD4Sf^ z%Y#px;?Xae8UJXHh41{jFknT%OQstpo!Wlo!i)+qvdSm2?FCGB>Z@>l1mx~SlRFo7 zmOCAeTsVCGIBefvA9W8Ndy|#Jn)M!L;x>Vkq{9$U#_P7`hT4A%U}`&`Q`|dQZf9hm znO2>LuQlz3$2Ud=&r<}}3f@}+qxx^8ennXoFbH zGjh;-S(chr^>|44n1whqk=2q;VdrEu+AA({2h#X%LJ<0~kA;LO8_9qSsPq#j;i-}=wJWxfPFHFOj&BjD z^3xzu?Y5Xmq-t?3IiEmmC;B~VHfW24h$f!NT*F6$JstinvDC+yIcR)X(SA#}XticX zlO_h95P(I_x80$oA>jO;jEE35ytjaR=d+-&Le+($pg8N<)`0pU#przIM6SmBCx<%U zy1)HP=hi{XZ8K4 z?e7kU%$E**w$0f5glOh6uztVCXo?Q7ZyPQWn3!T-n6kbr5?I@SoJys77LC+!?=ECu zFN#nh{5kwCjn@~i2LQP1H@i+plg^(NrL|%~!*+v%E4PY2d*_Q)(|_9Ag^z}Y@|>q7 zF(v6A0y>1xx-SOSOhR%r2VhH%7eR>GixsJbjII1=;_L(ZxuGX(N-bWcvYtgX`%+m} z?Q(oCI;n6bTIM52shY=dZo^AP$$IN$x$zkCh%c!T}>|$ja?k0Gy6P9t#V)tgL*+Luksjsw^{K7#y)r6hd3_0t|XJE?jyHa zel3Jc7}rzerP_@K(X1v{kl6XWk^F=*%(~~*aICUGI6Q3)mInT=Gr9!T@Dsx7!iPj+ z^fG87O)3)QXOZ(qcF-glY-%>MdS|`Kb{CBt ze1c1Nx;H+?>(9d4#;#}L4o504fsOC7H}1abCrVIM#}XxgnB3UfoQ+)^6&&Qri-s>b zoCVa5-wqd*`Pb#OXn)%A0At8v^WZ~b6lP<0ib}Xqd~k2Qt?{M~~N8;X)SlYA8(fC6KI^#yox`crX)7UZt&@ zWB@Z9o*zrNj0K1Z>AN2nkLc=RR;6q=src@bGu&lz3))3qm%!J=c3wNYK%V_rq8~M7 z`43LK6t;)uH-APZ+R+Bor5g=1cW6B?QqOXk@Rr?L%Olm_&jo_3 zCojet-pA`VOPHX7cOO>!zUkYC1Hc9JxQ_Q`;pgb|UYqF`uRyPYgYe!c!4Vb+OJGKL zBh^CGhSz~b`HfxOiG}oafai`wku5_X6?Ppa=!X+$#gw95^>SIvxgl>985eEmV!^zYJ6(mj?J2A(2CUvvi0!TH*+b$D3ON}^=9Twh2%;Tkyw zXcO*A0IhAAmcs~T$hScAlNCc;OmPC%BqRFRR}Y+wn1X!5t5uMP`4b|NSUABb76XPN zXc$m}q{H1cT)0iPl{Uq#ZpXf<`DJbi`SWaq%NOK+Ci(C@=LZp&f$cix6{fBZZhDl4 z-w2w5m)hQ-^x4!=css)oeEJzd*|SI>1w5^0(4KSQa(NkSahe zw`O3`$0L}*i;Hl927CWK*qH@0EI)d!Gs|3Xy8`*G)5FL8N0Z35u{sg!&9c^1MymZ{ ztMT2k8^4gRf8L?vat``(&gLbl(W0r{A2Y;Sw|y2ZDrjN*kA>&aAir65p0hymon71S zi9MR%oP2G8rGQWP)j`_m;#V8XQLnbSualn<<6oLJIAKPN8KngA{on$^MYDv3JxdS7 zP6b3MJW}V?w_ZS2gOKT%FKpPhki2>>o55rd`)*lhOv>6kQl0|;MY%=g_n2q`bLl^=uH<4I;Q)smojorhs|P=xak=&y$j!4Ar$|2rK*I(^ ztl0P@JysgRA#kV|UO1Fwqu`Mn;k7W9jyWNzW`r~;u?C71itsip78U2Fu2AQpHSEs4 zajrc5LY+DE$o7cv57`hkC=3hy6e|u6pLR!=BC0p3RMmP%IO7 zJ}quuJmXgu?gF)26}~^j8YR?jsoC)1NNFKlncvy?3o7kWtIosEls<1#!$l)I|6s&j zxIGPRC7`j4-y!1`N2YHiVE`uV(6F_(mj$dKaeyK!L^Jc`^b4LrxhIhQH_N=jYL3)G-W8y z{hB>A0S&8XtJasAG<9g5YlodJO^zYq*npw_6cvI;Agf*p$*)F&F-T=OIKFLe(YdRW zk!_Iz$s|W3BY&-0P=i9{8L$DXGu~}DDmqIVy6N4_q>q~3LlnSC`aZw1?86%!k`O7Y zkxam*cIckPtT79djl4#`R(xnR%LW1$jvZ$!I^m}NDYM34CivgX{^4LN0L`!ToYV8$ zhf50HWZI);ud|N2So!lcijrb3JOQ+gtLd9oqwdZ-CY4V9)SS}x@^Cso~1V=~W4mieia!!jK8S6x{#&0!K5Q-x0- zpBV75rNBQgBXvM2a#kqlp^VD};XkYNbOZ_!kPlts2CAKhhu!${76 zK^Rw_h5dWS;&qe4uAg`Gt;~gnIzcc5qPpOcM%`l-iB~@r_BY|S6|ib<>Uj#-EF;w! z0d+A-GKvXkju9?@+;Yl)Is7whQT2Qg(JaWpC)PTDJ1jN#XHfnM^={d4x+T^TpHpxi zrtdC9yhMTj1z))8GL1~sGu0?%i)wIl56U~0kHlFq_yD8c#KZU+hyvr#D-NHrE*6|A z!6UV+2-PZO>`%U@zgd@w#&v$$J2O~v;r3Y?c}@-`Y-wcP{}SuFx7t4Ysfle@4N5)s ziILP@rl3;en|af=l?8pOnoUB92Lo%IT5kCG5g(g`?{Gesum=Ptd>w}@2EfdG1Yi=Kgk=3s8QLFGfpGKD?5yy|k1Ys# z<*OydX`2-DnMzX&eF>HYt;w2Uj(IA7`Fb6abhac|1luK%NNnd2aR5FY-NYQNZ_+1Z zWw~a~b-1`S0)L-;^e@H#p&F2ZpL?c#Z2mFbT=7i&JyqZ#aGDQ=(wU76g$~@9Iasm= zxpVosbG33)FH*+ttI2s6=Zk71*8sfKSxoqaQ!qo2kglgc;P0Vwuz&zU(o-E~*$1oz ztcAx8;d3^%yEoh|^uE`bLycZ+pS|ge6bZ>Ws9D|(aYxMg6sZQ*ya=k46fiXvF=9-w zWTS{jyLGt<{Nf~7Fd^yOG-fzdZ%y3LsZc7A^z6LwQ#yejVVIHyT=L}mQ#IkKmvX1J zV2QR#XRV0+=7{N+#OG50HTzgYwuctqe-&GvJzGZux{HfW!_lJof_bIxlLtfE$AGxk zN#cqR%Ox5+G9t-cW-RhSwtP5`m!qW^lHU|g5@@(>*~<_%ejQCznn}QbL2Enf%s(uzD!xM3G-vV$^XoYsWv=TE&0x1b@=u zy7eubla*DLt_0YSMsJ<d8evj6VjyBuBr|2j9eIBbU{ggZ`zE-1e_W5ZTsrS5Lk+ zKCCs~Q-gpPwgH&zbZHy!m*HE}iDn?4@)47I`(3xEx5wRqL6 z_Qa*^8Rv0&MzZx$E>Lc=L6;cl*llOtX>uR5Wjj=-Ntyn7>OMVjUWhYgvyIg)|Cqa3?@^2Rn!-{og;?gmA*>s{Ef1wgTcUfOF zi9s+$-?MJNK`aU3-eLCDWw%K1AlGy6nb;c=FPn3IkMVU?m|- zR~x=foaoG#sl6qdir@f&lwmrpZ7|y%B6xtDhZSA>7boJ4>h z9Kpc>e@vFE*(*eyuF^Ax1T(V=b>@|lLn&Su;I5;kqtjchjKz!sNA-v@z}~Ij{2oQD;oSToUb!zD8B40j(c#fOb!PexVHOaZDvfrz*uB%W zCswGRi65poEj2hjxarf)WG|nG4h$vs+_2g@1hO|vJem3Q&Pfdg#B|aB44=77t3o34 zOPlzDi+AJkeZc8m-E+9)<*>j~mUFAPQ3p)7jsJ-$Eu;!^Re9-vLL6MP1~mS;McLIw2_R~pRI z#C?^Ph#s{5IWQ<|ED@_xur}a9jedepD65hJ?;=795t-rZfB;l%)fG;xfJ-D)M4$_1 zPtMUHsKttxO~f{!#2!ImTK(jMOJnj5Jd3)C1hTLb=KVm;NhQLXPn$_4+Pihg~&BiF|g4E<47cEC0{e~ff!Q%jv^ZDYp=PY)IOyGZ8g$eRvX+%T^E9!( z7KG01>v(=9kXTLnM7P(tlpp_Ex|5LPM1-n6jK@M?()cbyZXXaS{*m-I8b*R@Mbuh4 ze9ceM>>wPrKFcvH5JwRmC^^Q)EP-6dZY^~6Hms}lHf-K_v`g$;UTgksfQ%$6aIB!= zrA4R`;@Dy?W7?hQ+cXehAF{KfsN$Ws_P|EuLr2s8bghiqYOu4t;9~&%W_s{>uJM&` zipf#$ebHrQ(dp!!B~)kDZL{#fd1oo97hfIkt0PIBA76+yHeP>g+MQGBgceC?Kc z4%k-g;w|&~8-A_5JUp*B!<5wKU1hLG8#~Xt+H~o?P?#FF<>zCq7baXvtDMiMSj;I0 zgty$5x~Vzs$M%foN6H-Dzc?G_z8y=Izp9jkX%=OcB&q4W=_Be|!RiA0I6CvT9<(9l;=}&b}WTjkY5PdfBvKJz5fJ3;_-u zH#&<@vjH6j>}aWiw{L3{NFw18ePIedM1(oUSYSe|P!ul3%G{@{D49=x3_W9I1y_Gt zK9T{wk;>ILw!)!Ql_O1l`v^Po`s=N+iW3CH$4Qo$gZaK!*aipg?tXfDf&-Vt(S8a- za&mls&M_=acM_Y3j^#nfHdTP_FcR%r`w{=KIYDHuffXS{_g!zUivgdI#i#-_U+DVT z>1j?!1PdpNAZ&rx7rpotM}2FPRy^w=s22OgA{WRA4}@^K$BoGBSR~{)y$^o&pOm8; zmf0CEwC!uJwO)v}l4N6HSaa5+h03BQzZ=?7wh2QqEEPd<)P%P7`4;v(s3mBRGtnRm z)Nxyl6r}~M*B~Ig_(nOwbJG419>+$N*)4o%{=RtYoW|V!&sSp|;EQK~xo|SaFAo z&C#!@yU=}3B`8&V^|A+q8Uv@#fo-3ow=KGS!y{xR#MYCMPC>qs%1ap8t9>!ZRK!mC zm^CJ(GwB$t0jCSWgVO=d;&7JOerSJiwJqHvYK%zPJT3(&CA@^3t|%9afR&T>2pyH= z12rE*B5G*00UL&_Ed4+X1A~T!q=X0z9~}#Po1AI{1_UG)SW#}F`bkbPR}}#PYf&zP zf@Fylw2W2Q`M{J2%fvWDlC;RwIem7n8RcZe-Dy1=iBuMRlQSQtE$qw5wYr3Sv?@#* zBN(ijdp7iM0k_*t>Om&Merl*`XQYv0xQT+ZK7+%{fWy;}(|fkcLxi+>L0Md4G-Yue zJ~|6O+XO@|p&EW;Y?6BM7ik|yZVX7OZ4gVC%N~gCpqe4SMkH364fG|3NIEMf4OZ&K zRiw#FwbbaD73+H8ZB+rKwlrf$sP6U~?&nYQ>E%5pL(*5Z64*n;iwxq(NvaZw8hDZv zk*?%cr5}q7oz(T51X0jSvq;2D1fh%j(?Y$q8c;?J#} zEX6)jUr<0y_AX5h+@!P@LI96B-CVfD9lfh_ZXqWth$VMHLNYpIZquqkdR+3UBvGsW6<=~vD&;M}iK^tR+qELC z`0<(3n_Sl^cK8+d*DrFpbny$j4RdXf;*kxAUtkD45l*-59))(O@Qn(O`LPbgBQ%h1 zU;dccH*@Z1R$3g%s7HWz%`6+8Cqz|Bnm(t4AK0c?o_}BA$I?2%>@rT~t8Xq5Wenz_eGDW5DW_{nZ81k(A(R&^LAs3UYg5;wu|Fq2q=c@{oe5Rb@jhjWtEdDp$*qN^1zzGIyN~h6|(44ZJ`QY6W-P|)8IygA%6B`?7ry*W`J_j6X z<>6CSMS_MDczV`tkuNL@b$3g-@t%T@82eiWk04Jwwchf6Ts^l4(^JujMeb_V$|+5M zQU>OQNng))1VDn75-T~|x)v*#6h?iWolneYXFL*SeZeH)Cyb?pZ>LLr`<9$eb zk(rT5FdmQoobx%y(1`9J(;Xc6eidhu9_=}luVt>me_A7zQp%~SzaT2%9W-HTQ=_VU zjBUWGChY(c+x>Ki_=q+YjDs#39y%P-(LL=oxrg0s5>#BE%1IX8eUM-^i}*5NT~}_| zs0?~-nTV4##-?D>+`JO!SoOY|SFdRyzfygxx_O`U&tfFrg;!*A$=sm=It_PuJg1#= z>;5qad-CgC?CbjgG!&HO4Iobb*w2yTpCiwx$mC$H3HRF;o5_O+_30DADwTksS0}Od zhM+b?7X17=Rjgh3vbSM%=vgEX*#3+Wwr7+4JNwPUtJezOw+^m@D0Xt}HIK?ye&2ovr_3n*_TV)M&M()ecqk|#sWv%5vi=XoCwICVH;6I(;d1P=V*rdEwXsiv_jYuA&c?bt zH_}z4OABu2{&T)J72GOtIyLn@RsTEfm$LqVMI*J_^OR2?MsmpViD1E*v90`bEXM^o zr(Unh_Qrc^9$jCYA_~gWMrXJm$_v_gm99L>EL{r&Ii6JF-Z1}ZLrAE9o=!e=q&hfe zQoEM(3r8yWv(568it=Y~4?+uOCUXTe#Y3%XktG*58s_YpKMKDijs1WcKG(_0N&A?V&GuQlbf7f5AT$?9Je^u2U}Qy2 zT7DKkxa(niY+3iKNnYHd;Qhj(4*uG{PC`1oLW?Acv_kLtdCT<1nXkB{snM_BqHK)n z0gm)-VUx!<;@JX$uISYU|R- z^^|dJ-&SL>AlKDji21R{&x1b72UZofDl;A$&YRNU=;nu7^yqK9#(THOsW+!Msh9Pq z?qm1)OVuo1$gKYw`1WRf`RS{#>Tl%b*TOB|&<;Q`-Ids3 zlq1g=lhelXDWzs#xLmhp!&59YO-Wh|(=w|*+D)Czd%SMsVPbp?J?0fONi6-S46F+t zQrMIy!>>*EnSDR!uU~f4!8{j!x3-oIlU0i~rH>~+z$=*3e(IIaST!m9_%%Ok5( z?t2%i{*(7(5}tT*HMGPfu6xdtx7QdbDBlL_)I0Lq{$OsjrPQ2H)qjM~WP#ILOVy4y zXFt6AFk(Z-EFqlwu)KeD^Xz-eqc`50md+#it^8zTYw{ycUS})s7D;50;n0-NRDT#L zx4h?7n%gO~;a(~0r9SPxK+w;rOd1{HllliDoYZe3ly}^iTEt47Ov?m6y!BAH?P18O zQJ8jF?{O&<{kcR;yG={+jyXCiDI5(M3WS$Glv!U_czF7%gp(%TS}y8UA943}&7_R= zmrp|~8*V&Fd|Hy)k#A0{YQ%)O)6`RA5j*2OXOl8T1_|<|xrm(;hoUd~qh~tYsZZiB zWM{+a`hlS&9`_v`}!h$8UI#Na}Y@+ z!JgyON}T<zBM&f5?)B{w;s>9cP)FR`t1 zIUP3ha10=iptvk63@8kX)de?Zr^1b9DO^`vK2818yVP_^Ttqw%zV}88?!Y5PG?JPh zdV6IL$^K4!vz%myM4!Zn*M<+d$PuQ|kBwRv6-G@`d82)O82Xrh7sIb;s4mn1Y6N{r zIo%QT*;Iip>$Vr@F)%2!p`ig2|A|=OO}#HZa3{)DpyDTmY=uIFGKK2f@0J1`Y@r^& z1Zj5PQe#d&{&P1tIM@UXd+@{k=H}L!3->VKg|#DyAm}`X)lr cz5EvE0fR;5tS`U1>7Nl$lvR_dlrjtZANl6yo&W#< literal 0 HcmV?d00001 diff --git a/.maestro/enrichedText/screenshots/ios/empty_list_elements_display.png b/.maestro/enrichedText/screenshots/ios/empty_list_elements_display.png new file mode 100644 index 0000000000000000000000000000000000000000..58982761ef2681710c040c9c52e6f5bb36700a09 GIT binary patch literal 25543 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

                ZDg1gu))$%}AF?(dGf7mB!p>dcsSNgbXHkeZL#+&COz3N}Q8UcU&_SD>0 zEQ5EceaGg%56q_#-WZdGKS;*niv{`$n=a4juZABc=Tr|ZLAx{2ccep#67?94Q6LrctS=fcPA++cdx05iHV%z%FCCU)L}|%#k}d7G{h*^QhcbkPOgJjsb4UTcyB*R{2^4JJ8Z z36TcJSw@Njr}FEqt*tZp%%&dx0=0(LlmOeA!4nKrqlS@6&Oi}y5&9)owOa#*mBa=~ z(=XfNDgN0foakZfwzj@mjd{hhp74^pWXh#jJK00YU@&$%^md#=A`5P3>p+5yjSY{u z_ORxc*-mg6r%B3Z1q1~A!ymUkR`8)K&6ZMS>Z7~*%El&9mr|Q_?)`hKeR#ijFq6ro zHjTgF`-|hsK&8A+XwLy&?@BWeD_Yk&f*we}dmB~QP?8d>m%5t;AJs`AN0%xFna88IzOL z*qD{`FSmGcvKrviu`v9oIUlZ4j~`po{fF{O*-2mU^~kg`k6Z_(N={n(FmItjK)V|r zk6OL_c(Eh!hz*rw7?H_jAD=1UK22?(Wr3Q9b0}0=%O& z;taVM&A0pqZ99A+Y;8JS2Z)ol4 zMI|L|cb9NL^fppQuWUP8z2M`r_k~thvj=Zn|5JI?8HJ z)xDB{k+N69e;^2(*A%?OqRB2RQ~wZ>wewG}}yZGtJ&o*0HQ_X~};X~1Obt7lzj3X2P-AyW$6`BLT z!85CrtvhfhIVDoqqjm|Y!NB{lJje6P`b1(yVVcNO#z;@kHiH#XYbsCm(qcK= zKDHFer5B2KG~!c$62UHnFnrOna?<=Xdw8qg!CKF8+nv^dU|FKU>OHcL)4l2W6Q5|z z+N}HV^dLnxU7=W zd0;g~rI8;WxkUc@BhMAsgrc98KEtD+(kkMD*Y?j;)LAr*w$P0dou6#Z+mxQAD=T7c zX6kL0-D-W37ON~9bZG9cx9*A~0aKlrsz)B}yS2P-APdOOq)YAGdGO=3y1abD-54X` zU#wMmv#5ADqLQn>qFN{QBhm+e%c%ePQYzN)o~^n0o#eWnoPw&G=N(Vi#adH@{YiHM zye2z%{c&_2m|m+`H`$||R#bEz=4fR7&QD(oC8eb!p$o@Y08M!JwQ`@D8Vq?p`uX{_ z%vrj-yX)xGAa6@MOt|d*TPw1nZbCa)TBuCc=g5(I;HbHLQD}5T0%iKBcY2m$XIG#n z)&}e}ghhMr6rXppyiJ>iR14(H&U4S#Pg$5ELDL$Q-w4~Xr$M_;FJdD%mnKy}6fkwj z$I#GFDcZEDUC~-~>T7fiTD()QrBf_g8w{O!R;@%4U^d+3rV%_pqui|db-pR`43q%m zw4{^?^g*6$C(`L+?#Y-aj$e<;UG2{HBse`|l*mT#5MAi>ZpML@hrt3ev(&6>zWE7i z`#U@n2&{URA2xS#gwJqC>Qtyk0ZJeW#q#*^L+$^^;#esIgN|#AhTP4?b$vzLQ~XY1vz*zZDPde<;af8~*-{vrcZ=7GFGd+4=s* z#{ctW6vgK9Gez#ui@>LKYLkcw;;WJdn%6a6rxDKG_#}j@L;5&k&%a|CtI3$Yg?7USk#>3!-o%< zIXOQKiBSqwBnL7U6TrN%w6NgZA>P_x$Cxo#AewiwyLPqy?}dI4)|epfwrzO(zwhCf ziHL~IO^<$RYs+XRFxLYWx9cVFz0G#KtLqyZ^Kx_9%Z|yRf@D9&Or7{5^8m1T!fx{x z@m<8nAE7;ApS2RPM)MkMHO8frS$6%KjeflJ{Z(Q-TSI3WOn%l zHb!M-<@aJli{U_uRQe%i1ivWlg!Q%}kb07mL~V%BzNCF*Fg!*iTXpWL%W5v0T?Sly z7he!#&+%r|8PLau_t&@z$hdZGjE{<{86o2Jx48}3d&s9dr>ozmS>3T!j#H^@qm4H*drT2av~Hk-@6VSxnaR8yN#^J2se<7a!4ybB0bMr(D*1h>Z9xO Qf4O3)Z+b9a*Z#_X0kHPawg3PC literal 0 HcmV?d00001 diff --git a/.maestro/enrichedText/screenshots/ios/link_press.png b/.maestro/enrichedText/screenshots/ios/link_press.png new file mode 100644 index 0000000000000000000000000000000000000000..4a664d37ff08b522376cc93941823c63a083ae73 GIT binary patch literal 16564 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 literal 0 HcmV?d00001 diff --git a/.maestro/enrichedText/screenshots/ios/mention_press.png b/.maestro/enrichedText/screenshots/ios/mention_press.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec98e5fa29410a3003dd778911f86f27e2cb0a6 GIT binary patch literal 30107 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 z%0A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 z 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 Qo%^!wQP)wsqjJRWKP~Hw_5c6? literal 0 HcmV?d00001 diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm index 749af899e..fb719d933 100644 --- a/ios/inputParser/InputParser.mm +++ b/ios/inputParser/InputParser.mm @@ -4,6 +4,7 @@ #import "StringExtension.h" #import "StyleHeaders.h" #import "TextInsertionUtils.h" +#import @implementation InputParser { EnrichedTextInputView __weak *_input; @@ -591,7 +592,7 @@ - (void)replaceWholeFromHtml:(NSString *_Nonnull)html { _input->textView.typingAttributes = _input->defaultTypingAttributes; @try { - NSArray *processingResult = [self getTextAndStylesFromHtml:html]; + NSArray *processingResult = [HtmlParser getTextAndStylesFromHtml:html]; NSString *plainText = (NSString *)processingResult[0]; NSArray *stylesInfo = (NSArray *)processingResult[1]; diff --git a/package.json b/package.json index c7637eb4a..f8b7a8fd6 100644 --- a/package.json +++ b/package.json @@ -218,6 +218,7 @@ }, "ios": { "componentProvider": { + "EnrichedTextView": "EnrichedTextView", "EnrichedTextInputView": "EnrichedTextInputView" } }, From 84748410f58edcc66c1ee42ecb577efaaeef5516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 16 Apr 2026 17:14:10 +0200 Subject: [PATCH 16/19] test: remove android only tag --- .maestro/enrichedText/flows/empty_list_elements_display.yaml | 2 -- .maestro/enrichedText/flows/inline_styles_display.yaml | 2 -- .maestro/enrichedText/flows/link_press.yaml | 2 -- .maestro/enrichedText/flows/mention_press.yaml | 2 -- .maestro/enrichedText/flows/paragraph_styles_display.yaml | 2 -- 5 files changed, 10 deletions(-) diff --git a/.maestro/enrichedText/flows/empty_list_elements_display.yaml b/.maestro/enrichedText/flows/empty_list_elements_display.yaml index 6344898c1..d0a23a8bd 100644 --- a/.maestro/enrichedText/flows/empty_list_elements_display.yaml +++ b/.maestro/enrichedText/flows/empty_list_elements_display.yaml @@ -1,6 +1,4 @@ appId: swmansion.enriched.example -tags: - - android-only --- # Validates that lists with empty items are parsed and displayed correctly - launchApp diff --git a/.maestro/enrichedText/flows/inline_styles_display.yaml b/.maestro/enrichedText/flows/inline_styles_display.yaml index 32c21b08c..d9046d9e8 100644 --- a/.maestro/enrichedText/flows/inline_styles_display.yaml +++ b/.maestro/enrichedText/flows/inline_styles_display.yaml @@ -1,6 +1,4 @@ appId: swmansion.enriched.example -tags: - - android-only --- # Validates that inline styles are displayed correctly - launchApp diff --git a/.maestro/enrichedText/flows/link_press.yaml b/.maestro/enrichedText/flows/link_press.yaml index 7b232aad0..c3a39e890 100644 --- a/.maestro/enrichedText/flows/link_press.yaml +++ b/.maestro/enrichedText/flows/link_press.yaml @@ -1,6 +1,4 @@ appId: swmansion.enriched.example -tags: - - android-only --- # Validates that link press events are triggered correctly - launchApp diff --git a/.maestro/enrichedText/flows/mention_press.yaml b/.maestro/enrichedText/flows/mention_press.yaml index 0133f49c0..cc1ec2ea7 100644 --- a/.maestro/enrichedText/flows/mention_press.yaml +++ b/.maestro/enrichedText/flows/mention_press.yaml @@ -1,6 +1,4 @@ appId: swmansion.enriched.example -tags: - - android-only --- # Validates that mention press events are triggered correctly - launchApp diff --git a/.maestro/enrichedText/flows/paragraph_styles_display.yaml b/.maestro/enrichedText/flows/paragraph_styles_display.yaml index 434e4101a..f0d0230f1 100644 --- a/.maestro/enrichedText/flows/paragraph_styles_display.yaml +++ b/.maestro/enrichedText/flows/paragraph_styles_display.yaml @@ -1,6 +1,4 @@ appId: swmansion.enriched.example -tags: - - android-only --- # Validates that paragraph styles are displayed correctly - launchApp From 3d348c075bf6ae31c451d9d8a3072bcf95540c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 16 Apr 2026 17:20:26 +0200 Subject: [PATCH 17/19] fix: use html normalizer --- ios/EnrichedTextView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/EnrichedTextView.mm b/ios/EnrichedTextView.mm index 18b5e3171..122ec0903 100644 --- a/ios/EnrichedTextView.mm +++ b/ios/EnrichedTextView.mm @@ -617,7 +617,7 @@ - (void)renderText:(NSString *)html { } NSString *normalized = [HtmlParser initiallyProcessHtml:html - useHtmlNormalizer:NO]; + useHtmlNormalizer:YES]; if (normalized == nil) { [textView.textStorage setAttributedString:[[NSAttributedString alloc] From 5f7c7601999e76c289a8e1c1a6e4da554664bf6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 17 Apr 2026 10:19:11 +0200 Subject: [PATCH 18/19] fix: list ordering --- .../ios/empty_list_elements_display.png | Bin 25543 -> 26096 bytes ios/extensions/LayoutManagerExtension.mm | 2 +- ios/inputParser/InputParser.mm | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.maestro/enrichedText/screenshots/ios/empty_list_elements_display.png b/.maestro/enrichedText/screenshots/ios/empty_list_elements_display.png index 58982761ef2681710c040c9c52e6f5bb36700a09..03dcbd827362adf31619d2e9bdbb9ddbab05b4f9 100644 GIT binary patch literal 26096 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 literal 25543 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 // seek for previous lists while (true) { - if ([olStyle detect:NSMakeRange(recentParagraphLocation, 0)]) { + if ([olStyle detect:NSMakeRange(recentParagraphLocation, 1)]) { prevParagraphsCount += 1; if (recentParagraphLocation > 0) { diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm index fb719d933..1dbc264ee 100644 --- a/ios/inputParser/InputParser.mm +++ b/ios/inputParser/InputParser.mm @@ -584,8 +584,6 @@ - (NSString *)tagContentForStyle:(NSNumber *)style - (void)replaceWholeFromHtml:(NSString *_Nonnull)html { NSArray *processingResult = [HtmlParser getTextAndStylesFromHtml:html]; - NSString *plainText = (NSString *)processingResult[0]; - NSArray *stylesInfo = (NSArray *)processingResult[1]; // reset the text first and reset typing attributes _input->textView.text = @""; From dc71799b9ab2d4b5286ba498a02f899a77c3142a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 17 Apr 2026 13:22:38 +0200 Subject: [PATCH 19/19] fix: displaying images --- ios/EnrichedTextView.mm | 12 +++--- ios/extensions/LayoutManagerExtension.mm | 2 +- ios/htmlParser/HtmlParser.mm | 49 ++++++++++++++++-------- ios/utils/OccurenceUtils.mm | 9 ++++- ios/utils/ZeroWidthSpaceUtils.h | 2 + ios/utils/ZeroWidthSpaceUtils.mm | 41 +++++++++++++------- 6 files changed, 76 insertions(+), 39 deletions(-) diff --git a/ios/EnrichedTextView.mm b/ios/EnrichedTextView.mm index 122ec0903..53ed0202d 100644 --- a/ios/EnrichedTextView.mm +++ b/ios/EnrichedTextView.mm @@ -693,7 +693,7 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles { [cbStyle addWithChecked:NO range:styleRange - withTyping:YES + withTyping:NO withDirtyRange:NO]; if (checkboxStates && checkboxStates.count > 0) { @@ -708,18 +708,18 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles { } } } else { - [style add:styleRange withTyping:YES withDirtyRange:NO]; + [style add:styleRange withTyping:NO withDirtyRange:NO]; } - [ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self]; + [ZeroWidthSpaceUtils addSpacesIfNeededinInput:self inRange:styleRange]; NSInteger delta = textView.textStorage.string.length - textLengthBeforeStyleApplied; // Use an adjusted range so that applyStyling covers any ZWS characters that - // were just inserted by handleZeroWidthSpacesInInput. Without this, a style - // applied to an empty range {0,0} would call applyStyling on {0,0} even - // after a ZWS was inserted. + // were just inserted by addSpacesIfNeededinInput:inRange:. Without this, a + // style applied to an empty range {0,0} would call applyStyling on {0,0} + // even after a ZWS was inserted. NSRange adjustedStyleRange = NSMakeRange( styleRange.location, styleRange.length + (NSUInteger)MAX(0LL, delta)); diff --git a/ios/extensions/LayoutManagerExtension.mm b/ios/extensions/LayoutManagerExtension.mm index 48f0d755f..5a9b1797c 100644 --- a/ios/extensions/LayoutManagerExtension.mm +++ b/ios/extensions/LayoutManagerExtension.mm @@ -347,7 +347,7 @@ - (NSString *)getDecimalMarkerForList:(id)host // seek for previous lists while (true) { - if ([olStyle detect:NSMakeRange(recentParagraphLocation, 1)]) { + if ([olStyle detect:NSMakeRange(recentParagraphLocation, 0)]) { prevParagraphsCount += 1; if (recentParagraphLocation > 0) { diff --git a/ios/htmlParser/HtmlParser.mm b/ios/htmlParser/HtmlParser.mm index 994257129..ee6919e6c 100644 --- a/ios/htmlParser/HtmlParser.mm +++ b/ios/htmlParser/HtmlParser.mm @@ -154,24 +154,23 @@ + (void)finalizeTagEntry:(NSMutableString *)tagName NSArray *tagData = ongoingTags[tagName]; NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue]; - - // 'tagLocation' is an index based on 'plainText' which currently only holds - // raw text. - // - // Since 'plainText' does not yet contain the special placeholders for images, - // the indices for any text following an image are lower than they will be - // in the final NSTextStorage. - // - // We add 'precedingImageCount' to shift the start index forward, aligning - // this style's range with the actual position in the final text (where each - // image adds 1 character). - NSRange tagRange = NSMakeRange(tagLocation + *precedingImageCount, - plainText.length - tagLocation); + NSInteger openImageCount = [((NSNumber *)tagData[1]) intValue]; + NSInteger currentImageCount = *precedingImageCount; + + // 'plainText' doesn't contain image placeholders yet, but the final + // NSTextStorage will, so each image adds one character that ranges here + // must account for. 'openImageCount' (captured when the tag opened) shifts + // the start past images finalized BEFORE this tag, while the diff against + // 'currentImageCount' extends the length to cover images finalized INSIDE + // it. + NSRange tagRange = NSMakeRange(tagLocation + openImageCount, + (plainText.length - tagLocation) + + (currentImageCount - openImageCount)); [tagEntry addObject:[tagName copy]]; [tagEntry addObject:[NSValue valueWithRange:tagRange]]; - if (tagData.count > 1) { - [tagEntry addObject:[(NSString *)tagData[1] copy]]; + if (tagData.count > 2) { + [tagEntry addObject:[(NSString *)tagData[2] copy]]; } [processedTags addObject:tagEntry]; @@ -416,6 +415,18 @@ + (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html stringByReplacingOccurrencesOfString:@"
                \n" withString:@"

                \u200B

                \n"]; + // The same like above for (blockquote and codeblock) this is more like a + // hack but for some reason the last
              4. in
                  and
                    are not + // properly changed into zero width space so we do that manually here + // TODO: investigate this further, issue is already described here: + // https://github.com/software-mansion/react-native-enriched/issues/505 + fixedHtml = [fixedHtml + stringByReplacingOccurrencesOfString:@"
                  1. \n
                " + withString:@"
              5. \u200B
              6. \n"]; + fixedHtml = [fixedHtml + stringByReplacingOccurrencesOfString:@"
              7. \n" + withString:@"
              8. \u200B
              9. \n"]; + // replace "
                " at the end with "
                \n" if input is not empty to properly // handle last
                in html if ([fixedHtml hasSuffix:@"
                "] && fixedHtml.length != 4) { @@ -481,10 +492,14 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml { checkboxStates[@(plainText.length)] = @(isChecked); } } else if (!closingTag) { - // we finish opening tag - get its location and optionally params and - // put them under tag name key in ongoingTags + // we finish opening tag - get its location, the current + // precedingImageCount and optionally params and put them under tag name + // key in ongoingTags. Storing the open-time image count lets + // finalizeTagEntry: correctly shift the start and extend the length + // so the range covers any images finalized between open and close. NSMutableArray *tagArr = [[NSMutableArray alloc] init]; [tagArr addObject:[NSNumber numberWithInteger:plainText.length]]; + [tagArr addObject:[NSNumber numberWithInteger:precedingImageCount]]; if (currentTagParams.length > 0) { [tagArr addObject:[currentTagParams copy]]; } diff --git a/ios/utils/OccurenceUtils.mm b/ios/utils/OccurenceUtils.mm index c92f739fd..1cf092c29 100644 --- a/ios/utils/OccurenceUtils.mm +++ b/ios/utils/OccurenceUtils.mm @@ -33,7 +33,14 @@ + (BOOL)detect:(NSAttributedStringKey _Nonnull)key NSRange range))condition { NSRange detectionRange = NSMakeRange(index, 0); id attrValue; - if (NSEqualRanges(host.textView.selectedRange, detectionRange)) { + // Only trust typingAttributes when the textView is actually editable. + // Non-editable hosts (e.g. EnrichedTextView) keep selectedRange pinned at + // (0, 0), so without this gate every detection at index 0 would match the + // selection and read stale/default typingAttributes instead of the real + // attribute at that position in textStorage. + if (host.textView.isEditable && + NSEqualRanges(host.textView.selectedRange, detectionRange)) { + NSLog(@"[OccurenceUtils] isEditable srodek: %d", host.textView.isEditable); attrValue = host.textView.typingAttributes[key]; } else if (index == host.textView.textStorage.string.length) { if (checkPrev) { diff --git a/ios/utils/ZeroWidthSpaceUtils.h b/ios/utils/ZeroWidthSpaceUtils.h index 72b1d116d..dc8bca25f 100644 --- a/ios/utils/ZeroWidthSpaceUtils.h +++ b/ios/utils/ZeroWidthSpaceUtils.h @@ -4,6 +4,8 @@ @interface ZeroWidthSpaceUtils : NSObject + (void)handleZeroWidthSpacesInInput:(id)host; ++ (void)addSpacesIfNeededinInput:(id)host + inRange:(NSRange)range; + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text input:(id)host; diff --git a/ios/utils/ZeroWidthSpaceUtils.mm b/ios/utils/ZeroWidthSpaceUtils.mm index ebbfb4b67..ed9025ffe 100644 --- a/ios/utils/ZeroWidthSpaceUtils.mm +++ b/ios/utils/ZeroWidthSpaceUtils.mm @@ -11,7 +11,10 @@ + (void)handleZeroWidthSpacesInInput:(id)host { } [self removeSpacesIfNeededinInput:host]; - [self addSpacesIfNeededinInput:host]; + [self + addSpacesIfNeededinInput:host + inRange:NSMakeRange( + 0, host.textView.textStorage.string.length)]; } + (void)removeSpacesIfNeededinInput:(id)host { @@ -97,11 +100,18 @@ + (NSDictionary *)inlineMetaAttributesForInput:(id)host { return metaAttrs.count > 0 ? metaAttrs : nullptr; } -+ (void)addSpacesIfNeededinInput:(id)host { ++ (void)addSpacesIfNeededinInput:(id)host + inRange:(NSRange)range { NSMutableArray *indexesToBeInserted = [[NSMutableArray alloc] init]; NSRange preAddSelection = host.textView.selectedRange; - for (NSUInteger i = 0; i < host.textView.textStorage.string.length; i++) { + // Expand to paragraph boundaries so callers can pass any style range + // without worrying about missing the terminating newline of an empty + // paragraph that starts before range.location. + NSRange scanRange = + [host.textView.textStorage.string paragraphRangeForRange:range]; + + for (NSUInteger i = scanRange.location; i < NSMaxRange(scanRange); i++) { unichar character = [host.textView.textStorage.string characterAtIndex:i]; if ([[NSCharacterSet newlineCharacterSet] characterIsMember:character]) { @@ -141,17 +151,20 @@ + (void)addSpacesIfNeededinInput:(id)host { } } - // additional check for last index of the input - NSRange lastRange = NSMakeRange(host.textView.textStorage.string.length, 0); - NSRange lastParagraphRange = - [host.textView.textStorage.string paragraphRangeForRange:lastRange]; - if (lastParagraphRange.length == 0 && - [self anyZWSStylePresentInRange:lastRange input:host]) { - [TextInsertionUtils insertText:@"\u200B" - at:lastRange.location - additionalAttributes:metaAttrs - input:host - withSelection:NO]; + // additional check for last index of the input - only when the caller's + // range actually reaches the end of the input + if (NSMaxRange(scanRange) == host.textView.textStorage.string.length) { + NSRange lastRange = NSMakeRange(host.textView.textStorage.string.length, 0); + NSRange lastParagraphRange = + [host.textView.textStorage.string paragraphRangeForRange:lastRange]; + if (lastParagraphRange.length == 0 && + [self anyZWSStylePresentInRange:lastRange input:host]) { + [TextInsertionUtils insertText:@"\u200B" + at:lastRange.location + additionalAttributes:metaAttrs + input:host + withSelection:NO]; + } } // fix the selection if needed