@mathers101/react-native-render-rich-text 0.1.10 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/README.md +140 -140
- package/ReactNativeRenderRichText.podspec +23 -23
- package/android/build.gradle +86 -86
- package/android/consumer-rules.pro +3 -3
- package/android/src/main/AndroidManifest.xml +2 -2
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextLayoutManager.kt +24 -24
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextPackage.kt +13 -13
- package/android/src/main/java/com/renderrichtext/MeasurementStore.kt +644 -644
- package/android/src/main/java/com/renderrichtext/accessibility/AccessibilityLabels.kt +17 -17
- package/android/src/main/java/com/renderrichtext/accessibility/AccessibleTiptapTextView.kt +112 -112
- package/android/src/main/java/com/renderrichtext/accessibility/TiptapAccessibilityHelper.kt +576 -576
- package/android/src/main/java/com/renderrichtext/events/CodeBlockPressEvent.kt +24 -24
- package/android/src/main/java/com/renderrichtext/events/ContextMenuItemPressEvent.kt +28 -28
- package/android/src/main/java/com/renderrichtext/events/CopyPressEvent.kt +24 -24
- package/android/src/main/java/com/renderrichtext/events/ImagePressEvent.kt +25 -25
- package/android/src/main/java/com/renderrichtext/events/LatexErrorEvent.kt +29 -29
- package/android/src/main/java/com/renderrichtext/events/LinkLongPressEvent.kt +23 -23
- package/android/src/main/java/com/renderrichtext/events/LinkPressEvent.kt +23 -23
- package/android/src/main/java/com/renderrichtext/events/MentionLongPressEvent.kt +25 -25
- package/android/src/main/java/com/renderrichtext/events/MentionPressEvent.kt +25 -25
- package/android/src/main/java/com/renderrichtext/events/TaskListItemPressEvent.kt +26 -26
- package/android/src/main/java/com/renderrichtext/math/LatexErrorReporter.kt +9 -9
- package/android/src/main/java/com/renderrichtext/parser/Parser.kt +59 -59
- package/android/src/main/java/com/renderrichtext/parser/TextLinkMatching.kt +35 -35
- package/android/src/main/java/com/renderrichtext/renderer/BaselineShiftRenderer.kt +45 -45
- package/android/src/main/java/com/renderrichtext/renderer/BlankLineRenderer.kt +39 -39
- package/android/src/main/java/com/renderrichtext/renderer/BlockquoteRenderer.kt +243 -243
- package/android/src/main/java/com/renderrichtext/renderer/BlockquoteTextRenderer.kt +56 -56
- package/android/src/main/java/com/renderrichtext/renderer/CodeBlockRenderer.kt +135 -135
- package/android/src/main/java/com/renderrichtext/renderer/CodeRenderer.kt +37 -37
- package/android/src/main/java/com/renderrichtext/renderer/EmphasisRenderer.kt +28 -28
- package/android/src/main/java/com/renderrichtext/renderer/HeadingRenderer.kt +81 -81
- package/android/src/main/java/com/renderrichtext/renderer/HighlightRenderer.kt +26 -26
- package/android/src/main/java/com/renderrichtext/renderer/LineBreakRenderer.kt +17 -17
- package/android/src/main/java/com/renderrichtext/renderer/LinkRenderer.kt +90 -90
- package/android/src/main/java/com/renderrichtext/renderer/ListContextManager.kt +109 -109
- package/android/src/main/java/com/renderrichtext/renderer/ListItemRenderer.kt +211 -211
- package/android/src/main/java/com/renderrichtext/renderer/ListRenderer.kt +133 -133
- package/android/src/main/java/com/renderrichtext/renderer/MentionRenderer.kt +85 -85
- package/android/src/main/java/com/renderrichtext/renderer/ParagraphRenderer.kt +91 -91
- package/android/src/main/java/com/renderrichtext/renderer/Renderer.kt +148 -148
- package/android/src/main/java/com/renderrichtext/renderer/SoftBreakRenderer.kt +17 -17
- package/android/src/main/java/com/renderrichtext/renderer/SpanStyleCache.kt +143 -143
- package/android/src/main/java/com/renderrichtext/renderer/StrikethroughRenderer.kt +28 -28
- package/android/src/main/java/com/renderrichtext/renderer/StrongRenderer.kt +28 -28
- package/android/src/main/java/com/renderrichtext/renderer/TextRenderer.kt +31 -31
- package/android/src/main/java/com/renderrichtext/renderer/ThematicBreakRenderer.kt +46 -46
- package/android/src/main/java/com/renderrichtext/renderer/UnderlineRenderer.kt +28 -28
- package/android/src/main/java/com/renderrichtext/spans/BaseListSpan.kt +164 -164
- package/android/src/main/java/com/renderrichtext/spans/BaselineShiftSpan.kt +27 -27
- package/android/src/main/java/com/renderrichtext/spans/BlockquoteSpan.kt +302 -302
- package/android/src/main/java/com/renderrichtext/spans/CodeBackgroundSpan.kt +205 -205
- package/android/src/main/java/com/renderrichtext/spans/CodeBlockSpan.kt +216 -216
- package/android/src/main/java/com/renderrichtext/spans/CodeSpan.kt +32 -32
- package/android/src/main/java/com/renderrichtext/spans/EmphasisSpan.kt +42 -42
- package/android/src/main/java/com/renderrichtext/spans/FadeInSpan.kt +29 -29
- package/android/src/main/java/com/renderrichtext/spans/HeadingSpan.kt +38 -38
- package/android/src/main/java/com/renderrichtext/spans/HighlightSpan.kt +80 -80
- package/android/src/main/java/com/renderrichtext/spans/ImageSpan.kt +481 -481
- package/android/src/main/java/com/renderrichtext/spans/InlineBackgroundGeometry.kt +42 -42
- package/android/src/main/java/com/renderrichtext/spans/LineHeightSpan.kt +39 -39
- package/android/src/main/java/com/renderrichtext/spans/LinkPillIconCache.kt +154 -154
- package/android/src/main/java/com/renderrichtext/spans/LinkPillSpan.kt +381 -381
- package/android/src/main/java/com/renderrichtext/spans/LinkSpan.kt +68 -68
- package/android/src/main/java/com/renderrichtext/spans/ListItemSpacingSpan.kt +33 -33
- package/android/src/main/java/com/renderrichtext/spans/ListMarkerAnchorSpan.kt +38 -38
- package/android/src/main/java/com/renderrichtext/spans/MarginBottomSpan.kt +76 -76
- package/android/src/main/java/com/renderrichtext/spans/MentionChipSpan.kt +132 -132
- package/android/src/main/java/com/renderrichtext/spans/MentionSpan.kt +46 -46
- package/android/src/main/java/com/renderrichtext/spans/OrderedListSpan.kt +127 -127
- package/android/src/main/java/com/renderrichtext/spans/SpoilerSpan.kt +33 -33
- package/android/src/main/java/com/renderrichtext/spans/StrikethroughSpan.kt +12 -12
- package/android/src/main/java/com/renderrichtext/spans/StrongSpan.kt +39 -39
- package/android/src/main/java/com/renderrichtext/spans/TaskListSpan.kt +143 -143
- package/android/src/main/java/com/renderrichtext/spans/TextSpan.kt +26 -26
- package/android/src/main/java/com/renderrichtext/spans/ThematicBreakSpan.kt +69 -69
- package/android/src/main/java/com/renderrichtext/spans/UnorderedListSpan.kt +96 -96
- package/android/src/main/java/com/renderrichtext/spoiler/SpoilerStubs.kt +46 -46
- package/android/src/main/java/com/renderrichtext/styles/BaseBlockStyle.kt +11 -11
- package/android/src/main/java/com/renderrichtext/styles/BlockquoteStyle.kt +83 -83
- package/android/src/main/java/com/renderrichtext/styles/CodeBlockStyle.kt +80 -80
- package/android/src/main/java/com/renderrichtext/styles/CodeStyle.kt +26 -26
- package/android/src/main/java/com/renderrichtext/styles/EmphasisStyle.kt +21 -21
- package/android/src/main/java/com/renderrichtext/styles/FactoidStyle.kt +21 -21
- package/android/src/main/java/com/renderrichtext/styles/HeadingStyle.kt +33 -33
- package/android/src/main/java/com/renderrichtext/styles/HighlightStyle.kt +19 -19
- package/android/src/main/java/com/renderrichtext/styles/ImageStyle.kt +29 -29
- package/android/src/main/java/com/renderrichtext/styles/InlineImageStyle.kt +17 -17
- package/android/src/main/java/com/renderrichtext/styles/InlineMathStyle.kt +17 -17
- package/android/src/main/java/com/renderrichtext/styles/LinkPillContent.kt +36 -36
- package/android/src/main/java/com/renderrichtext/styles/LinkPillStyle.kt +44 -44
- package/android/src/main/java/com/renderrichtext/styles/LinkStyle.kt +23 -23
- package/android/src/main/java/com/renderrichtext/styles/LinkVariantEntry.kt +34 -34
- package/android/src/main/java/com/renderrichtext/styles/ListStyle.kt +65 -65
- package/android/src/main/java/com/renderrichtext/styles/MathStyle.kt +29 -29
- package/android/src/main/java/com/renderrichtext/styles/MentionStyle.kt +19 -19
- package/android/src/main/java/com/renderrichtext/styles/ParagraphStyle.kt +33 -33
- package/android/src/main/java/com/renderrichtext/styles/SpoilerStyle.kt +27 -27
- package/android/src/main/java/com/renderrichtext/styles/StrikethroughStyle.kt +17 -17
- package/android/src/main/java/com/renderrichtext/styles/StrongStyle.kt +21 -21
- package/android/src/main/java/com/renderrichtext/styles/StyleConfig.kt +411 -411
- package/android/src/main/java/com/renderrichtext/styles/StyleParser.kt +92 -92
- package/android/src/main/java/com/renderrichtext/styles/SubscriptStyle.kt +16 -16
- package/android/src/main/java/com/renderrichtext/styles/SuperscriptStyle.kt +16 -16
- package/android/src/main/java/com/renderrichtext/styles/TableStyle.kt +75 -75
- package/android/src/main/java/com/renderrichtext/styles/TaskListStyle.kt +29 -29
- package/android/src/main/java/com/renderrichtext/styles/TextAlignment.kt +32 -32
- package/android/src/main/java/com/renderrichtext/styles/ThematicBreakStyle.kt +23 -23
- package/android/src/main/java/com/renderrichtext/styles/UnderlineStyle.kt +17 -17
- package/android/src/main/java/com/renderrichtext/styles/VideoStyle.kt +31 -31
- package/android/src/main/java/com/renderrichtext/utils/common/BorderStyleUtils.kt +86 -86
- package/android/src/main/java/com/renderrichtext/utils/common/BreakStrategyUtils.kt +32 -32
- package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockHighlighter.kt +36 -36
- package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockNode.kt +42 -42
- package/android/src/main/java/com/renderrichtext/utils/common/EllipsizeUtils.kt +27 -27
- package/android/src/main/java/com/renderrichtext/utils/common/EnrichedTiptapAncestorUtils.kt +10 -10
- package/android/src/main/java/com/renderrichtext/utils/common/FeatureFlags.kt +9 -9
- package/android/src/main/java/com/renderrichtext/utils/common/LinkRecognitionProps.kt +31 -31
- package/android/src/main/java/com/renderrichtext/utils/common/LinkRegexConfig.kt +56 -56
- package/android/src/main/java/com/renderrichtext/utils/common/MotionPreferences.kt +18 -18
- package/android/src/main/java/com/renderrichtext/utils/common/ReadableMapExtensions.kt +28 -28
- package/android/src/main/java/com/renderrichtext/utils/common/layout/RTLUtils.kt +6 -6
- package/android/src/main/java/com/renderrichtext/utils/common/serialization/TiptapASTSerializer.kt +368 -368
- package/android/src/main/java/com/renderrichtext/utils/text/ImageCache.kt +88 -88
- package/android/src/main/java/com/renderrichtext/utils/text/ImageDownloader.kt +203 -203
- package/android/src/main/java/com/renderrichtext/utils/text/LocalImageLoader.kt +167 -167
- package/android/src/main/java/com/renderrichtext/utils/text/TailFadeInAnimator.kt +84 -84
- package/android/src/main/java/com/renderrichtext/utils/text/conversion/HTMLGenerator.kt +1105 -1105
- package/android/src/main/java/com/renderrichtext/utils/text/conversion/TiptapExtractor.kt +423 -423
- package/android/src/main/java/com/renderrichtext/utils/text/extensions/ParagraphUtils.kt +9 -9
- package/android/src/main/java/com/renderrichtext/utils/text/extensions/SpannableExtensions.kt +9 -9
- package/android/src/main/java/com/renderrichtext/utils/text/extensions/TextPaintExtensions.kt +76 -76
- package/android/src/main/java/com/renderrichtext/utils/text/interaction/CheckboxTouchHelper.kt +61 -61
- package/android/src/main/java/com/renderrichtext/utils/text/interaction/TaskListTapUtils.kt +286 -286
- package/android/src/main/java/com/renderrichtext/utils/text/span/ApplyLineHeight.kt +39 -39
- package/android/src/main/java/com/renderrichtext/utils/text/span/BlockSpacing.kt +142 -142
- package/android/src/main/java/com/renderrichtext/utils/text/span/SpanFlags.kt +43 -43
- package/android/src/main/java/com/renderrichtext/utils/text/span/WidthAwareSpans.kt +30 -30
- package/android/src/main/java/com/renderrichtext/utils/text/view/CodeBlockPressHost.kt +15 -15
- package/android/src/main/java/com/renderrichtext/utils/text/view/ImagePressHost.kt +19 -19
- package/android/src/main/java/com/renderrichtext/utils/text/view/SelectionActionMode.kt +235 -235
- package/android/src/main/java/com/renderrichtext/utils/text/view/TextSelectionColors.kt +48 -48
- package/android/src/main/java/com/renderrichtext/utils/text/view/TextViewSetup.kt +119 -119
- package/android/src/main/java/com/renderrichtext/views/ContextMenuPopup.kt +217 -217
- package/android/src/main/java/com/renderrichtext/views/CopyGlyph.kt +101 -101
- package/android/src/main/java/com/renderrichtext/views/WrapGlyph.kt +40 -40
- package/android/src/main/jni/CMakeLists.txt +52 -52
- package/android/src/main/jni/react/renderer/components/RichTextSpec/ComponentDescriptors.h +30 -30
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextComponentDescriptor.h +29 -29
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.cpp +45 -45
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.h +21 -21
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.cpp +38 -38
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.h +53 -53
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.cpp +9 -9
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.h +24 -24
- package/android/src/main/jni/react/renderer/components/RichTextSpec/conversions.h +40 -40
- package/cpp/parser/TiptapParser.hpp +13 -13
- package/cpp/vendor/nlohmann/json.hpp +24765 -24765
- package/ios/EnrichedTiptapText.h +27 -27
- package/ios/attachments/ENRMImageAttachment.h +51 -51
- package/ios/attachments/ENRMImageAttachment.m +398 -398
- package/ios/attachments/ENRMLinkPillAttachment.h +47 -47
- package/ios/attachments/ENRMLinkPillAttachment.m +324 -324
- package/ios/attachments/ENRMLinkPillIconCache.h +28 -28
- package/ios/attachments/ENRMLinkPillIconCache.m +181 -181
- package/ios/attachments/ENRMLinkPillText.h +27 -27
- package/ios/attachments/ENRMLinkPillText.m +118 -118
- package/ios/attachments/ENRMMathInlineAttachment.h +7 -7
- package/ios/attachments/ENRMMathInlineAttachment.m +4 -4
- package/ios/attachments/ThematicBreakAttachment.h +15 -15
- package/ios/attachments/ThematicBreakAttachment.m +33 -33
- package/ios/internals/ENRMViewFreeMeasurement.h +208 -208
- package/ios/internals/EnrichedTiptapTextComponentDescriptor.h +19 -19
- package/ios/internals/EnrichedTiptapTextShadowNode.h +43 -43
- package/ios/internals/EnrichedTiptapTextShadowNode.mm +69 -69
- package/ios/internals/EnrichedTiptapTextState.h +24 -24
- package/ios/internals/MeasurementCache.h +308 -308
- package/ios/internals/ShadowMeasurementUtils.h +145 -145
- package/ios/parser/ENRMTextLinkAttributes.h +7 -7
- package/ios/parser/ENRMTiptapParser.h +33 -33
- package/ios/parser/ENRMTiptapParser.mm +73 -73
- package/ios/parser/TiptapASTNode.m +52 -52
- package/ios/renderer/AttributedRenderer.h +23 -23
- package/ios/renderer/AttributedRenderer.m +229 -229
- package/ios/renderer/BaseRenderer.h +33 -33
- package/ios/renderer/BaseRenderer.m +46 -46
- package/ios/renderer/BaselineShiftTextAttributes.h +10 -10
- package/ios/renderer/BaselineShiftTextAttributes.m +26 -26
- package/ios/renderer/BlankLineRenderer.h +4 -4
- package/ios/renderer/BlankLineRenderer.m +51 -51
- package/ios/renderer/BlockquoteRenderer.h +6 -6
- package/ios/renderer/BlockquoteRenderer.m +258 -258
- package/ios/renderer/CodeBlockRenderer.h +9 -9
- package/ios/renderer/CodeBlockRenderer.m +99 -99
- package/ios/renderer/CodeRenderer.h +9 -9
- package/ios/renderer/CodeRenderer.m +69 -69
- package/ios/renderer/ENRMBlockquoteTextRenderer.h +25 -25
- package/ios/renderer/ENRMBlockquoteTextRenderer.m +48 -48
- package/ios/renderer/EmphasisRenderer.h +5 -5
- package/ios/renderer/EmphasisRenderer.m +99 -99
- package/ios/renderer/HeadingRenderer.h +6 -6
- package/ios/renderer/HeadingRenderer.m +102 -102
- package/ios/renderer/HighlightRenderer.h +7 -7
- package/ios/renderer/HighlightRenderer.m +33 -33
- package/ios/renderer/LinkRenderer.h +6 -6
- package/ios/renderer/LinkRenderer.m +157 -157
- package/ios/renderer/ListItemRenderer.h +24 -24
- package/ios/renderer/ListItemRenderer.m +283 -283
- package/ios/renderer/ListRenderer.h +13 -13
- package/ios/renderer/ListRenderer.m +126 -126
- package/ios/renderer/MentionRenderer.h +9 -9
- package/ios/renderer/MentionRenderer.m +96 -96
- package/ios/renderer/NodeRenderer.h +8 -8
- package/ios/renderer/ParagraphRenderer.h +6 -6
- package/ios/renderer/ParagraphRenderer.m +85 -85
- package/ios/renderer/RenderContext.h +145 -145
- package/ios/renderer/RenderContext.m +402 -402
- package/ios/renderer/RendererFactory.h +12 -12
- package/ios/renderer/StrikethroughRenderer.h +5 -5
- package/ios/renderer/StrikethroughRenderer.m +30 -30
- package/ios/renderer/StrongRenderer.h +5 -5
- package/ios/renderer/StrongRenderer.m +87 -87
- package/ios/renderer/TextRenderer.h +6 -6
- package/ios/renderer/TextRenderer.m +18 -18
- package/ios/renderer/ThematicBreakRenderer.h +4 -4
- package/ios/renderer/ThematicBreakRenderer.m +41 -41
- package/ios/renderer/UnderlineRenderer.h +4 -4
- package/ios/renderer/UnderlineRenderer.m +41 -41
- package/ios/styles/ENRMFontSlot.h +12 -12
- package/ios/styles/ENRMFontSlot.m +24 -24
- package/ios/styles/StyleConfig.h +488 -488
- package/ios/styles/StyleConfig.mm +2820 -2820
- package/ios/utils/AccessibilityInfo.h +35 -35
- package/ios/utils/AccessibilityInfo.m +24 -24
- package/ios/utils/BlockquoteBorder.h +26 -26
- package/ios/utils/BlockquoteBorder.m +294 -294
- package/ios/utils/CodeBackground.h +19 -19
- package/ios/utils/CodeBackground.m +196 -196
- package/ios/utils/CodeBlockBackground.h +17 -17
- package/ios/utils/CodeBlockBackground.m +102 -102
- package/ios/utils/ContextMenuUtils.h +76 -76
- package/ios/utils/ContextMenuUtils.mm +75 -75
- package/ios/utils/ENRMAccessibilityLabels.h +40 -40
- package/ios/utils/ENRMAccessibilityLabels.m +5 -5
- package/ios/utils/ENRMAsyncRenderCoordinator.h +24 -24
- package/ios/utils/ENRMAsyncRenderCoordinator.m +37 -37
- package/ios/utils/ENRMAtomicSize.h +65 -65
- package/ios/utils/ENRMCodeBlockContent.h +37 -37
- package/ios/utils/ENRMCodeBlockContent.mm +89 -89
- package/ios/utils/ENRMFeatureFlags.h +25 -25
- package/ios/utils/ENRMImageDownloader.h +30 -30
- package/ios/utils/ENRMImageDownloader.m +168 -168
- package/ios/utils/ENRMLatexErrorCoordinator.h +27 -27
- package/ios/utils/ENRMLatexErrorCoordinator.mm +75 -75
- package/ios/utils/ENRMLatexErrorReporting.h +13 -13
- package/ios/utils/ENRMLinkContextMenus.h +93 -93
- package/ios/utils/ENRMLinkContextMenus.m +178 -178
- package/ios/utils/ENRMLinkRegexConfig.h +30 -30
- package/ios/utils/ENRMLinkRegexConfig.mm +104 -104
- package/ios/utils/ENRMLinkRegexProps.h +23 -23
- package/ios/utils/ENRMLocalImageLoader.h +35 -35
- package/ios/utils/ENRMLocalImageLoader.m +111 -111
- package/ios/utils/ENRMMenuAction.h +27 -27
- package/ios/utils/ENRMMenuAction.m +33 -33
- package/ios/utils/ENRMSpoilerOverlayManager.h +15 -15
- package/ios/utils/ENRMSpoilerOverlayManager.m +19 -19
- package/ios/utils/ENRMSpoilerTapUtils.h +6 -6
- package/ios/utils/ENRMSpoilerTapUtils.m +11 -11
- package/ios/utils/ENRMTailFadeInAnimator.h +16 -16
- package/ios/utils/ENRMTailFadeInAnimator.m +161 -161
- package/ios/utils/ENRMTextHitTest.h +23 -23
- package/ios/utils/ENRMTextHitTest.m +20 -20
- package/ios/utils/ENRMTextInteractionUtils.h +20 -20
- package/ios/utils/ENRMTextInteractionUtils.m +15 -15
- package/ios/utils/ENRMTextRenderer.h +38 -38
- package/ios/utils/ENRMTextRenderer.m +54 -54
- package/ios/utils/ENRMTextViewSetup.h +132 -132
- package/ios/utils/ENRMUIKit.h +429 -429
- package/ios/utils/EditMenuUtils.h +105 -105
- package/ios/utils/EditMenuUtils.m +143 -143
- package/ios/utils/FontScaleObserver.h +10 -10
- package/ios/utils/FontScaleObserver.m +55 -55
- package/ios/utils/FontUtils.h +31 -31
- package/ios/utils/FontUtils.m +63 -63
- package/ios/utils/HTMLGenerator.h +25 -25
- package/ios/utils/HTMLGenerator.m +1041 -1041
- package/ios/utils/HeightUpdateUtils.h +25 -25
- package/ios/utils/ImageRequestHeaderUtils.h +35 -35
- package/ios/utils/LastElementUtils.h +97 -97
- package/ios/utils/LinkContextMenuUtils.h +55 -55
- package/ios/utils/LinkPillContentUtils.h +46 -46
- package/ios/utils/ListMarkerDrawer.h +15 -15
- package/ios/utils/ListMarkerDrawer.m +239 -239
- package/ios/utils/MentionBackground.h +26 -26
- package/ios/utils/MentionBackground.m +86 -86
- package/ios/utils/ParagraphStyleUtils.h +87 -87
- package/ios/utils/ParagraphStyleUtils.m +484 -484
- package/ios/utils/PasteboardUtils.h +48 -48
- package/ios/utils/PasteboardUtils.m +166 -166
- package/ios/utils/RTFExportUtils.h +24 -24
- package/ios/utils/RTFExportUtils.m +299 -299
- package/ios/utils/SelectionColorUtils.h +14 -14
- package/ios/utils/StylePropsUtils.h +1340 -1340
- package/ios/utils/TaskListTapUtils.h +6 -6
- package/ios/utils/TaskListTapUtils.m +14 -14
- package/ios/utils/TextViewLayoutManager.h +14 -14
- package/ios/utils/TiptapAccessibilityElementBuilder.h +43 -43
- package/ios/utils/TiptapAccessibilityElementBuilder.m +432 -432
- package/ios/utils/TiptapExtractor.h +17 -17
- package/ios/utils/TiptapExtractor.m +367 -367
- package/ios/views/ENRMTiptapTextView.h +25 -25
- package/ios/views/ENRMTiptapTextView.m +247 -247
- package/package.json +65 -70
- package/react-native.config.js +10 -10
- package/src/admonitionDefaults.ts +58 -58
- package/src/darkModeTextStyle.ts +99 -99
- package/src/headingDefaults.ts +21 -21
- package/src/index.tsx +4 -4
- package/src/linkVariantUtils.ts +120 -120
- package/src/normalizeTiptapStyle.ts +397 -397
- package/src/rn-globals.d.ts +1 -1
- package/src/styleUtils.ts +107 -107
- package/src/types/TiptapStyle.ts +532 -532
- package/src/types/TiptapStyleInternal.ts +299 -299
- package/src/types/linkPill.ts +9 -9
- package/src/utils/isDev.ts +4 -4
|
@@ -1,398 +1,398 @@
|
|
|
1
|
-
#import "ENRMImageAttachment.h"
|
|
2
|
-
#import "ENRMImageDownloader.h"
|
|
3
|
-
#import "ENRMUIKit.h"
|
|
4
|
-
#import "RuntimeKeys.h"
|
|
5
|
-
#import "StyleConfig.h"
|
|
6
|
-
#import <objc/runtime.h>
|
|
7
|
-
|
|
8
|
-
#define CACHE_KEY_PROCESSED(url, w, h, r, m) [NSString stringWithFormat:@"%@_w%.1f_h%.1f_r%.1f_m%@", url, w, h, r, m]
|
|
9
|
-
|
|
10
|
-
static NSCache<NSString *, RCTUIImage *> *_originalImageCache;
|
|
11
|
-
static NSCache<NSString *, RCTUIImage *> *_processedImageCache;
|
|
12
|
-
|
|
13
|
-
@implementation RCTUIView (ENRMImageLayoutObserver)
|
|
14
|
-
|
|
15
|
-
- (id<ENRMImageLayoutObserver>)enrm_imageLayoutObserver
|
|
16
|
-
{
|
|
17
|
-
RCTUIView *view = self;
|
|
18
|
-
while (view && ![view conformsToProtocol:@protocol(ENRMImageLayoutObserver)]) {
|
|
19
|
-
view = view.superview;
|
|
20
|
-
}
|
|
21
|
-
return (id<ENRMImageLayoutObserver>)view;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
@end
|
|
25
|
-
|
|
26
|
-
@interface ENRMImageAttachment ()
|
|
27
|
-
|
|
28
|
-
@property (nonatomic, copy) NSString *imageURL;
|
|
29
|
-
@property (nonatomic, copy, nullable) NSDictionary<NSString *, NSString *> *requestHeaders;
|
|
30
|
-
@property (nonatomic, copy) NSString *cacheKey;
|
|
31
|
-
@property (nonatomic, assign) BOOL isInline;
|
|
32
|
-
@property (nonatomic, assign) CGFloat cachedHeight;
|
|
33
|
-
@property (nonatomic, assign) CGFloat cachedMaxHeight;
|
|
34
|
-
@property (nonatomic, assign) CGFloat cachedAspectRatio;
|
|
35
|
-
@property (nonatomic, copy) NSString *cachedResizeMode;
|
|
36
|
-
@property (nonatomic, assign) CGFloat cachedBorderRadius;
|
|
37
|
-
@property (nonatomic, weak) NSTextContainer *textContainer;
|
|
38
|
-
@property (nonatomic, weak) ENRMPlatformTextView *textView;
|
|
39
|
-
@property (nonatomic, strong) RCTUIImage *originalImage;
|
|
40
|
-
@property (nonatomic, strong) RCTUIImage *loadedImage;
|
|
41
|
-
@property (nonatomic, strong) RCTUIImage *placeholderImage;
|
|
42
|
-
@property (nonatomic, copy) NSString *lastProcessedKey;
|
|
43
|
-
|
|
44
|
-
@end
|
|
45
|
-
|
|
46
|
-
@implementation ENRMImageAttachment
|
|
47
|
-
|
|
48
|
-
+ (NSCache<NSString *, RCTUIImage *> *)originalImageCache
|
|
49
|
-
{
|
|
50
|
-
static dispatch_once_t onceToken;
|
|
51
|
-
dispatch_once(&onceToken, ^{
|
|
52
|
-
_originalImageCache = [[NSCache alloc] init];
|
|
53
|
-
_originalImageCache.countLimit = 50;
|
|
54
|
-
_originalImageCache.totalCostLimit = 1024 * 1024 * 20; // 20 MB
|
|
55
|
-
});
|
|
56
|
-
return _originalImageCache;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
+ (NSCache<NSString *, RCTUIImage *> *)processedImageCache
|
|
60
|
-
{
|
|
61
|
-
static dispatch_once_t onceToken;
|
|
62
|
-
dispatch_once(&onceToken, ^{
|
|
63
|
-
_processedImageCache = [[NSCache alloc] init];
|
|
64
|
-
_processedImageCache.countLimit = 100;
|
|
65
|
-
_processedImageCache.totalCostLimit = 1024 * 1024 * 30; // 30 MB
|
|
66
|
-
});
|
|
67
|
-
return _processedImageCache;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
+ (instancetype)attachmentForURL:(NSString *)imageURL config:(StyleConfig *)config isInline:(BOOL)isInline
|
|
71
|
-
{
|
|
72
|
-
// Always a fresh instance, never shared across positions: the table renderer can draw
|
|
73
|
-
// the same image URL at a different width than a copy outside the table, and a shared
|
|
74
|
-
// NSTextAttachment would thrash its single last-processed width into a redraw loop. The
|
|
75
|
-
// image caches keep fresh instances cheap (no re-fetch or re-scale).
|
|
76
|
-
return [[self alloc] initWithImageURL:imageURL config:config isInline:isInline];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
- (instancetype)initWithImageURL:(NSString *)imageURL config:(StyleConfig *)config isInline:(BOOL)isInline
|
|
80
|
-
{
|
|
81
|
-
self = [super init];
|
|
82
|
-
if (self) {
|
|
83
|
-
_imageURL = imageURL;
|
|
84
|
-
_requestHeaders = [[config imageRequestHeaders] copy];
|
|
85
|
-
_cacheKey = ENRMImageCacheKey(imageURL, _requestHeaders);
|
|
86
|
-
_isInline = isInline;
|
|
87
|
-
|
|
88
|
-
_cachedHeight = isInline ? [config inlineImageSize] : [config imageHeight];
|
|
89
|
-
_cachedMaxHeight = [config imageMaxHeight];
|
|
90
|
-
_cachedAspectRatio = [config imageAspectRatio];
|
|
91
|
-
_cachedResizeMode = [config imageResizeMode];
|
|
92
|
-
_cachedBorderRadius = [config imageBorderRadius];
|
|
93
|
-
|
|
94
|
-
[self setupPlaceholder];
|
|
95
|
-
[self startDownloadingImage];
|
|
96
|
-
}
|
|
97
|
-
return self;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
- (CGFloat)resolvedBoxHeightForWidth:(CGFloat)width
|
|
101
|
-
{
|
|
102
|
-
if (self.cachedAspectRatio > 0 && width > 0) {
|
|
103
|
-
return width / self.cachedAspectRatio;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (self.cachedMaxHeight > 0) {
|
|
107
|
-
RCTUIImage *source = self.originalImage;
|
|
108
|
-
if (source && width > 0 && source.size.width > 0 && source.size.height > 0) {
|
|
109
|
-
CGFloat fitted = width * source.size.height / source.size.width;
|
|
110
|
-
return MIN(self.cachedMaxHeight, fitted);
|
|
111
|
-
}
|
|
112
|
-
// Intrinsic size not known yet — fall back to the full max height; the box
|
|
113
|
-
// shrinks once the image loads and layout is invalidated.
|
|
114
|
-
return self.cachedMaxHeight;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return self.cachedHeight;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// True when resizeMode is unset ('') - legacy always implies a fixed height box.
|
|
121
|
-
|
|
122
|
-
- (BOOL)isLegacyBlockSizing
|
|
123
|
-
{
|
|
124
|
-
return !self.isInline && self.cachedResizeMode.length == 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
- (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer
|
|
128
|
-
proposedLineFragment:(CGRect)lineFragment
|
|
129
|
-
glyphPosition:(CGPoint)position
|
|
130
|
-
characterIndex:(NSUInteger)characterIndex
|
|
131
|
-
{
|
|
132
|
-
CGFloat height = self.cachedHeight;
|
|
133
|
-
CGFloat width = self.isInline ? height : (lineFragment.size.width > 0 ? lineFragment.size.width : height);
|
|
134
|
-
|
|
135
|
-
if (!self.isInline) {
|
|
136
|
-
height = [self resolvedBoxHeightForWidth:width];
|
|
137
|
-
return CGRectMake(0, 0, width, height);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
UIFont *appliedFont = nil;
|
|
141
|
-
NSLayoutManager *layoutManager = textContainer.layoutManager;
|
|
142
|
-
NSTextStorage *textStorage = layoutManager.textStorage;
|
|
143
|
-
|
|
144
|
-
if (textStorage && characterIndex < textStorage.length) {
|
|
145
|
-
appliedFont = [textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
CGFloat verticalOffset;
|
|
149
|
-
if (appliedFont) {
|
|
150
|
-
verticalOffset = (appliedFont.capHeight - height) / 2.0;
|
|
151
|
-
} else {
|
|
152
|
-
verticalOffset = (lineFragment.size.height - height) / 2.0;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return CGRectMake(0, verticalOffset, width, height);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
- (RCTUIImage *)imageForBounds:(CGRect)imageBounds
|
|
159
|
-
textContainer:(NSTextContainer *)textContainer
|
|
160
|
-
characterIndex:(NSUInteger)characterIndex
|
|
161
|
-
{
|
|
162
|
-
self.textContainer = textContainer;
|
|
163
|
-
|
|
164
|
-
if (self.originalImage && imageBounds.size.width > 0) {
|
|
165
|
-
self.bounds = imageBounds;
|
|
166
|
-
[self processAndApplyImage:self.originalImage withTargetWidth:imageBounds.size.width];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return self.loadedImage ?: self.placeholderImage;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
- (void)handleLoadedImage:(RCTUIImage *)image
|
|
173
|
-
{
|
|
174
|
-
if (!image)
|
|
175
|
-
return;
|
|
176
|
-
|
|
177
|
-
// The downloader completes synchronously on cache hits, which can happen on the render queue
|
|
178
|
-
if (!NSThread.isMainThread) {
|
|
179
|
-
dispatch_async(dispatch_get_main_queue(), ^{ [self handleLoadedImage:image]; });
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
self.originalImage = image;
|
|
184
|
-
// UIKit reads the plain image property for save/drag/copy — it must hold the original
|
|
185
|
-
self.image = image;
|
|
186
|
-
CGFloat targetWidth = self.isInline ? self.cachedHeight : self.bounds.size.width;
|
|
187
|
-
|
|
188
|
-
// Defer processing if we don't have valid bounds yet (common for non-inline block images)
|
|
189
|
-
if (!self.isInline && targetWidth <= 0) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
[self processAndApplyImage:image withTargetWidth:targetWidth];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
- (void)processAndApplyImage:(RCTUIImage *)image withTargetWidth:(CGFloat)targetWidth
|
|
197
|
-
{
|
|
198
|
-
if (targetWidth <= 0)
|
|
199
|
-
return;
|
|
200
|
-
|
|
201
|
-
CGFloat boxHeight = self.isInline ? self.cachedHeight : [self resolvedBoxHeightForWidth:targetWidth];
|
|
202
|
-
|
|
203
|
-
NSString *processedKey =
|
|
204
|
-
CACHE_KEY_PROCESSED(self.cacheKey, targetWidth, boxHeight, self.cachedBorderRadius, self.cachedResizeMode);
|
|
205
|
-
|
|
206
|
-
if ([processedKey isEqualToString:self.lastProcessedKey])
|
|
207
|
-
return;
|
|
208
|
-
self.lastProcessedKey = processedKey;
|
|
209
|
-
|
|
210
|
-
RCTUIImage *cachedProcessed = [[ENRMImageAttachment processedImageCache] objectForKey:processedKey];
|
|
211
|
-
|
|
212
|
-
if (cachedProcessed) {
|
|
213
|
-
self.loadedImage = cachedProcessed;
|
|
214
|
-
[self refreshDisplay];
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
__weak typeof(self) weakSelf = self;
|
|
219
|
-
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
220
|
-
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
221
|
-
if (!strongSelf)
|
|
222
|
-
return;
|
|
223
|
-
|
|
224
|
-
RCTUIImage *processedImage = [strongSelf createScaledImage:image
|
|
225
|
-
toWidth:targetWidth
|
|
226
|
-
height:boxHeight
|
|
227
|
-
borderRadius:strongSelf.cachedBorderRadius];
|
|
228
|
-
|
|
229
|
-
if (processedImage) {
|
|
230
|
-
[[ENRMImageAttachment processedImageCache] setObject:processedImage
|
|
231
|
-
forKey:processedKey
|
|
232
|
-
cost:ENRMImageByteCost(processedImage)];
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
236
|
-
strongSelf.loadedImage = processedImage;
|
|
237
|
-
if (strongSelf.isInline) {
|
|
238
|
-
strongSelf.bounds = CGRectMake(0, 0, strongSelf.cachedHeight, strongSelf.cachedHeight);
|
|
239
|
-
}
|
|
240
|
-
[strongSelf refreshDisplay];
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
- (RCTUIImage *)createScaledImage:(RCTUIImage *)image
|
|
246
|
-
toWidth:(CGFloat)targetWidth
|
|
247
|
-
height:(CGFloat)targetHeight
|
|
248
|
-
borderRadius:(CGFloat)radius
|
|
249
|
-
{
|
|
250
|
-
CGFloat sourceWidth = image.size.width;
|
|
251
|
-
CGFloat sourceHeight = image.size.height;
|
|
252
|
-
if (sourceWidth <= 0 || sourceHeight <= 0)
|
|
253
|
-
return nil;
|
|
254
|
-
|
|
255
|
-
CGSize source = CGSizeMake(sourceWidth, sourceHeight);
|
|
256
|
-
CGSize box = CGSizeMake(targetWidth, targetHeight);
|
|
257
|
-
BOOL legacy = [self isLegacyBlockSizing];
|
|
258
|
-
|
|
259
|
-
CGRect drawingRect;
|
|
260
|
-
if (self.isInline || legacy) {
|
|
261
|
-
CGFloat drawingWidth, drawingHeight;
|
|
262
|
-
if (!self.isInline) {
|
|
263
|
-
CGFloat aspectRatioScale = targetWidth / sourceWidth;
|
|
264
|
-
drawingWidth = targetWidth;
|
|
265
|
-
drawingHeight = sourceHeight * aspectRatioScale;
|
|
266
|
-
} else {
|
|
267
|
-
drawingWidth = targetWidth;
|
|
268
|
-
drawingHeight = targetHeight;
|
|
269
|
-
}
|
|
270
|
-
drawingRect = CGRectMake((targetWidth - drawingWidth) / 2.0, (targetHeight - drawingHeight) / 2.0, drawingWidth,
|
|
271
|
-
drawingHeight);
|
|
272
|
-
} else {
|
|
273
|
-
drawingRect = [self drawingRectForResizeMode:self.cachedResizeMode source:source box:box];
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
RCTUIGraphicsImageRenderer *renderer = ImageRendererForSize(box);
|
|
277
|
-
|
|
278
|
-
return [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *context) {
|
|
279
|
-
if (radius > 0) {
|
|
280
|
-
CGRect clipRect = CGRectIntersection(CGRectMake(0, 0, targetWidth, targetHeight), drawingRect);
|
|
281
|
-
UIBezierPath *path = UIBezierPathWithRoundedRect(clipRect, radius);
|
|
282
|
-
[path addClip];
|
|
283
|
-
}
|
|
284
|
-
[image drawInRect:drawingRect];
|
|
285
|
-
}];
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
- (CGRect)drawingRectForResizeMode:(NSString *)mode source:(CGSize)source box:(CGSize)box
|
|
289
|
-
{
|
|
290
|
-
if ([mode isEqualToString:@"stretch"]) {
|
|
291
|
-
return CGRectMake(0, 0, box.width, box.height);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
CGFloat widthScale = box.width / source.width;
|
|
295
|
-
CGFloat heightScale = box.height / source.height;
|
|
296
|
-
|
|
297
|
-
CGFloat scale;
|
|
298
|
-
if ([mode isEqualToString:@"contain"]) {
|
|
299
|
-
scale = MIN(widthScale, heightScale);
|
|
300
|
-
} else if ([mode isEqualToString:@"center"]) {
|
|
301
|
-
scale = MIN(1.0, MIN(widthScale, heightScale));
|
|
302
|
-
} else if ([mode isEqualToString:@"none"]) {
|
|
303
|
-
scale = 1.0;
|
|
304
|
-
} else { // cover (default)
|
|
305
|
-
scale = MAX(widthScale, heightScale);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
CGFloat drawingWidth = source.width * scale;
|
|
309
|
-
CGFloat drawingHeight = source.height * scale;
|
|
310
|
-
return CGRectMake((box.width - drawingWidth) / 2.0, (box.height - drawingHeight) / 2.0, drawingWidth, drawingHeight);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
- (void)startDownloadingImage
|
|
314
|
-
{
|
|
315
|
-
if (self.imageURL.length == 0)
|
|
316
|
-
return;
|
|
317
|
-
|
|
318
|
-
__weak typeof(self) weakSelf = self;
|
|
319
|
-
[[ENRMImageDownloader shared] downloadURL:self.imageURL
|
|
320
|
-
headers:self.requestHeaders
|
|
321
|
-
completion:^(RCTUIImage *image) { [weakSelf handleLoadedImage:image]; }];
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
- (void)refreshDisplay
|
|
325
|
-
{
|
|
326
|
-
// Notify self-drawing hosts (e.g. a table grid) that rasterize us via
|
|
327
|
-
// -drawWithRect: and have no live text view for the invalidation below to reach.
|
|
328
|
-
if (self.onImageLoaded) {
|
|
329
|
-
self.onImageLoaded();
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
UITextView *textView = [self fetchAssociatedTextView];
|
|
333
|
-
if (!textView)
|
|
334
|
-
return;
|
|
335
|
-
|
|
336
|
-
NSRange range = [self findAttachmentRangeInText:textView.textStorage];
|
|
337
|
-
if (range.location != NSNotFound) {
|
|
338
|
-
[textView.layoutManager invalidateDisplayForCharacterRange:range];
|
|
339
|
-
if (!self.isInline) {
|
|
340
|
-
[textView.layoutManager invalidateLayoutForCharacterRange:range actualCharacterRange:NULL];
|
|
341
|
-
[self notifyImageLayoutObserver:textView];
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// With maxHeight/aspectRatio sizing the box height can settle after the image
|
|
347
|
-
// loads, while the component was measured (and its size cached) with the
|
|
348
|
-
// pre-load fallback. Notify the hosting component so it can re-measure.
|
|
349
|
-
// Dispatched async: refreshDisplay can run mid-layout (imageForBounds) and
|
|
350
|
-
// re-measuring would re-enter the layout manager.
|
|
351
|
-
- (void)notifyImageLayoutObserver:(UITextView *)textView
|
|
352
|
-
{
|
|
353
|
-
// Only maxHeight/aspectRatio boxes can change height after load; a fixed
|
|
354
|
-
// height box (legacy or explicit resizeMode) never needs a re-measure.
|
|
355
|
-
if (self.cachedMaxHeight <= 0 && self.cachedAspectRatio <= 0)
|
|
356
|
-
return;
|
|
357
|
-
|
|
358
|
-
id<ENRMImageLayoutObserver> observer = [textView enrm_imageLayoutObserver];
|
|
359
|
-
if (!observer)
|
|
360
|
-
return;
|
|
361
|
-
|
|
362
|
-
dispatch_async(dispatch_get_main_queue(), ^{ [observer imageAttachmentDidResolveLayout]; });
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
- (ENRMPlatformTextView *)fetchAssociatedTextView
|
|
366
|
-
{
|
|
367
|
-
if (self.textView)
|
|
368
|
-
return self.textView;
|
|
369
|
-
if (!self.textContainer)
|
|
370
|
-
return nil;
|
|
371
|
-
self.textView = objc_getAssociatedObject(self.textContainer, kTextViewKey);
|
|
372
|
-
return self.textView;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
- (void)setupPlaceholder
|
|
376
|
-
{
|
|
377
|
-
CGFloat size = self.cachedHeight;
|
|
378
|
-
self.bounds = CGRectMake(0, 0, size, size);
|
|
379
|
-
RCTUIGraphicsImageRenderer *renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(1, 1)];
|
|
380
|
-
self.placeholderImage = [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *ctx){}];
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
- (NSRange)findAttachmentRangeInText:(NSAttributedString *)attributedString
|
|
384
|
-
{
|
|
385
|
-
__block NSRange foundRange = NSMakeRange(NSNotFound, 0);
|
|
386
|
-
[attributedString enumerateAttribute:NSAttachmentAttributeName
|
|
387
|
-
inRange:NSMakeRange(0, attributedString.length)
|
|
388
|
-
options:0
|
|
389
|
-
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
390
|
-
if (value == self) {
|
|
391
|
-
foundRange = range;
|
|
392
|
-
*stop = YES;
|
|
393
|
-
}
|
|
394
|
-
}];
|
|
395
|
-
return foundRange;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
@end
|
|
1
|
+
#import "ENRMImageAttachment.h"
|
|
2
|
+
#import "ENRMImageDownloader.h"
|
|
3
|
+
#import "ENRMUIKit.h"
|
|
4
|
+
#import "RuntimeKeys.h"
|
|
5
|
+
#import "StyleConfig.h"
|
|
6
|
+
#import <objc/runtime.h>
|
|
7
|
+
|
|
8
|
+
#define CACHE_KEY_PROCESSED(url, w, h, r, m) [NSString stringWithFormat:@"%@_w%.1f_h%.1f_r%.1f_m%@", url, w, h, r, m]
|
|
9
|
+
|
|
10
|
+
static NSCache<NSString *, RCTUIImage *> *_originalImageCache;
|
|
11
|
+
static NSCache<NSString *, RCTUIImage *> *_processedImageCache;
|
|
12
|
+
|
|
13
|
+
@implementation RCTUIView (ENRMImageLayoutObserver)
|
|
14
|
+
|
|
15
|
+
- (id<ENRMImageLayoutObserver>)enrm_imageLayoutObserver
|
|
16
|
+
{
|
|
17
|
+
RCTUIView *view = self;
|
|
18
|
+
while (view && ![view conformsToProtocol:@protocol(ENRMImageLayoutObserver)]) {
|
|
19
|
+
view = view.superview;
|
|
20
|
+
}
|
|
21
|
+
return (id<ENRMImageLayoutObserver>)view;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@end
|
|
25
|
+
|
|
26
|
+
@interface ENRMImageAttachment ()
|
|
27
|
+
|
|
28
|
+
@property (nonatomic, copy) NSString *imageURL;
|
|
29
|
+
@property (nonatomic, copy, nullable) NSDictionary<NSString *, NSString *> *requestHeaders;
|
|
30
|
+
@property (nonatomic, copy) NSString *cacheKey;
|
|
31
|
+
@property (nonatomic, assign) BOOL isInline;
|
|
32
|
+
@property (nonatomic, assign) CGFloat cachedHeight;
|
|
33
|
+
@property (nonatomic, assign) CGFloat cachedMaxHeight;
|
|
34
|
+
@property (nonatomic, assign) CGFloat cachedAspectRatio;
|
|
35
|
+
@property (nonatomic, copy) NSString *cachedResizeMode;
|
|
36
|
+
@property (nonatomic, assign) CGFloat cachedBorderRadius;
|
|
37
|
+
@property (nonatomic, weak) NSTextContainer *textContainer;
|
|
38
|
+
@property (nonatomic, weak) ENRMPlatformTextView *textView;
|
|
39
|
+
@property (nonatomic, strong) RCTUIImage *originalImage;
|
|
40
|
+
@property (nonatomic, strong) RCTUIImage *loadedImage;
|
|
41
|
+
@property (nonatomic, strong) RCTUIImage *placeholderImage;
|
|
42
|
+
@property (nonatomic, copy) NSString *lastProcessedKey;
|
|
43
|
+
|
|
44
|
+
@end
|
|
45
|
+
|
|
46
|
+
@implementation ENRMImageAttachment
|
|
47
|
+
|
|
48
|
+
+ (NSCache<NSString *, RCTUIImage *> *)originalImageCache
|
|
49
|
+
{
|
|
50
|
+
static dispatch_once_t onceToken;
|
|
51
|
+
dispatch_once(&onceToken, ^{
|
|
52
|
+
_originalImageCache = [[NSCache alloc] init];
|
|
53
|
+
_originalImageCache.countLimit = 50;
|
|
54
|
+
_originalImageCache.totalCostLimit = 1024 * 1024 * 20; // 20 MB
|
|
55
|
+
});
|
|
56
|
+
return _originalImageCache;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
+ (NSCache<NSString *, RCTUIImage *> *)processedImageCache
|
|
60
|
+
{
|
|
61
|
+
static dispatch_once_t onceToken;
|
|
62
|
+
dispatch_once(&onceToken, ^{
|
|
63
|
+
_processedImageCache = [[NSCache alloc] init];
|
|
64
|
+
_processedImageCache.countLimit = 100;
|
|
65
|
+
_processedImageCache.totalCostLimit = 1024 * 1024 * 30; // 30 MB
|
|
66
|
+
});
|
|
67
|
+
return _processedImageCache;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
+ (instancetype)attachmentForURL:(NSString *)imageURL config:(StyleConfig *)config isInline:(BOOL)isInline
|
|
71
|
+
{
|
|
72
|
+
// Always a fresh instance, never shared across positions: the table renderer can draw
|
|
73
|
+
// the same image URL at a different width than a copy outside the table, and a shared
|
|
74
|
+
// NSTextAttachment would thrash its single last-processed width into a redraw loop. The
|
|
75
|
+
// image caches keep fresh instances cheap (no re-fetch or re-scale).
|
|
76
|
+
return [[self alloc] initWithImageURL:imageURL config:config isInline:isInline];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
- (instancetype)initWithImageURL:(NSString *)imageURL config:(StyleConfig *)config isInline:(BOOL)isInline
|
|
80
|
+
{
|
|
81
|
+
self = [super init];
|
|
82
|
+
if (self) {
|
|
83
|
+
_imageURL = imageURL;
|
|
84
|
+
_requestHeaders = [[config imageRequestHeaders] copy];
|
|
85
|
+
_cacheKey = ENRMImageCacheKey(imageURL, _requestHeaders);
|
|
86
|
+
_isInline = isInline;
|
|
87
|
+
|
|
88
|
+
_cachedHeight = isInline ? [config inlineImageSize] : [config imageHeight];
|
|
89
|
+
_cachedMaxHeight = [config imageMaxHeight];
|
|
90
|
+
_cachedAspectRatio = [config imageAspectRatio];
|
|
91
|
+
_cachedResizeMode = [config imageResizeMode];
|
|
92
|
+
_cachedBorderRadius = [config imageBorderRadius];
|
|
93
|
+
|
|
94
|
+
[self setupPlaceholder];
|
|
95
|
+
[self startDownloadingImage];
|
|
96
|
+
}
|
|
97
|
+
return self;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
- (CGFloat)resolvedBoxHeightForWidth:(CGFloat)width
|
|
101
|
+
{
|
|
102
|
+
if (self.cachedAspectRatio > 0 && width > 0) {
|
|
103
|
+
return width / self.cachedAspectRatio;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (self.cachedMaxHeight > 0) {
|
|
107
|
+
RCTUIImage *source = self.originalImage;
|
|
108
|
+
if (source && width > 0 && source.size.width > 0 && source.size.height > 0) {
|
|
109
|
+
CGFloat fitted = width * source.size.height / source.size.width;
|
|
110
|
+
return MIN(self.cachedMaxHeight, fitted);
|
|
111
|
+
}
|
|
112
|
+
// Intrinsic size not known yet — fall back to the full max height; the box
|
|
113
|
+
// shrinks once the image loads and layout is invalidated.
|
|
114
|
+
return self.cachedMaxHeight;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return self.cachedHeight;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// True when resizeMode is unset ('') - legacy always implies a fixed height box.
|
|
121
|
+
|
|
122
|
+
- (BOOL)isLegacyBlockSizing
|
|
123
|
+
{
|
|
124
|
+
return !self.isInline && self.cachedResizeMode.length == 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
- (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer
|
|
128
|
+
proposedLineFragment:(CGRect)lineFragment
|
|
129
|
+
glyphPosition:(CGPoint)position
|
|
130
|
+
characterIndex:(NSUInteger)characterIndex
|
|
131
|
+
{
|
|
132
|
+
CGFloat height = self.cachedHeight;
|
|
133
|
+
CGFloat width = self.isInline ? height : (lineFragment.size.width > 0 ? lineFragment.size.width : height);
|
|
134
|
+
|
|
135
|
+
if (!self.isInline) {
|
|
136
|
+
height = [self resolvedBoxHeightForWidth:width];
|
|
137
|
+
return CGRectMake(0, 0, width, height);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
UIFont *appliedFont = nil;
|
|
141
|
+
NSLayoutManager *layoutManager = textContainer.layoutManager;
|
|
142
|
+
NSTextStorage *textStorage = layoutManager.textStorage;
|
|
143
|
+
|
|
144
|
+
if (textStorage && characterIndex < textStorage.length) {
|
|
145
|
+
appliedFont = [textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
CGFloat verticalOffset;
|
|
149
|
+
if (appliedFont) {
|
|
150
|
+
verticalOffset = (appliedFont.capHeight - height) / 2.0;
|
|
151
|
+
} else {
|
|
152
|
+
verticalOffset = (lineFragment.size.height - height) / 2.0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return CGRectMake(0, verticalOffset, width, height);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
- (RCTUIImage *)imageForBounds:(CGRect)imageBounds
|
|
159
|
+
textContainer:(NSTextContainer *)textContainer
|
|
160
|
+
characterIndex:(NSUInteger)characterIndex
|
|
161
|
+
{
|
|
162
|
+
self.textContainer = textContainer;
|
|
163
|
+
|
|
164
|
+
if (self.originalImage && imageBounds.size.width > 0) {
|
|
165
|
+
self.bounds = imageBounds;
|
|
166
|
+
[self processAndApplyImage:self.originalImage withTargetWidth:imageBounds.size.width];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return self.loadedImage ?: self.placeholderImage;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
- (void)handleLoadedImage:(RCTUIImage *)image
|
|
173
|
+
{
|
|
174
|
+
if (!image)
|
|
175
|
+
return;
|
|
176
|
+
|
|
177
|
+
// The downloader completes synchronously on cache hits, which can happen on the render queue
|
|
178
|
+
if (!NSThread.isMainThread) {
|
|
179
|
+
dispatch_async(dispatch_get_main_queue(), ^{ [self handleLoadedImage:image]; });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
self.originalImage = image;
|
|
184
|
+
// UIKit reads the plain image property for save/drag/copy — it must hold the original
|
|
185
|
+
self.image = image;
|
|
186
|
+
CGFloat targetWidth = self.isInline ? self.cachedHeight : self.bounds.size.width;
|
|
187
|
+
|
|
188
|
+
// Defer processing if we don't have valid bounds yet (common for non-inline block images)
|
|
189
|
+
if (!self.isInline && targetWidth <= 0) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
[self processAndApplyImage:image withTargetWidth:targetWidth];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
- (void)processAndApplyImage:(RCTUIImage *)image withTargetWidth:(CGFloat)targetWidth
|
|
197
|
+
{
|
|
198
|
+
if (targetWidth <= 0)
|
|
199
|
+
return;
|
|
200
|
+
|
|
201
|
+
CGFloat boxHeight = self.isInline ? self.cachedHeight : [self resolvedBoxHeightForWidth:targetWidth];
|
|
202
|
+
|
|
203
|
+
NSString *processedKey =
|
|
204
|
+
CACHE_KEY_PROCESSED(self.cacheKey, targetWidth, boxHeight, self.cachedBorderRadius, self.cachedResizeMode);
|
|
205
|
+
|
|
206
|
+
if ([processedKey isEqualToString:self.lastProcessedKey])
|
|
207
|
+
return;
|
|
208
|
+
self.lastProcessedKey = processedKey;
|
|
209
|
+
|
|
210
|
+
RCTUIImage *cachedProcessed = [[ENRMImageAttachment processedImageCache] objectForKey:processedKey];
|
|
211
|
+
|
|
212
|
+
if (cachedProcessed) {
|
|
213
|
+
self.loadedImage = cachedProcessed;
|
|
214
|
+
[self refreshDisplay];
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
__weak typeof(self) weakSelf = self;
|
|
219
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
220
|
+
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
221
|
+
if (!strongSelf)
|
|
222
|
+
return;
|
|
223
|
+
|
|
224
|
+
RCTUIImage *processedImage = [strongSelf createScaledImage:image
|
|
225
|
+
toWidth:targetWidth
|
|
226
|
+
height:boxHeight
|
|
227
|
+
borderRadius:strongSelf.cachedBorderRadius];
|
|
228
|
+
|
|
229
|
+
if (processedImage) {
|
|
230
|
+
[[ENRMImageAttachment processedImageCache] setObject:processedImage
|
|
231
|
+
forKey:processedKey
|
|
232
|
+
cost:ENRMImageByteCost(processedImage)];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
236
|
+
strongSelf.loadedImage = processedImage;
|
|
237
|
+
if (strongSelf.isInline) {
|
|
238
|
+
strongSelf.bounds = CGRectMake(0, 0, strongSelf.cachedHeight, strongSelf.cachedHeight);
|
|
239
|
+
}
|
|
240
|
+
[strongSelf refreshDisplay];
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
- (RCTUIImage *)createScaledImage:(RCTUIImage *)image
|
|
246
|
+
toWidth:(CGFloat)targetWidth
|
|
247
|
+
height:(CGFloat)targetHeight
|
|
248
|
+
borderRadius:(CGFloat)radius
|
|
249
|
+
{
|
|
250
|
+
CGFloat sourceWidth = image.size.width;
|
|
251
|
+
CGFloat sourceHeight = image.size.height;
|
|
252
|
+
if (sourceWidth <= 0 || sourceHeight <= 0)
|
|
253
|
+
return nil;
|
|
254
|
+
|
|
255
|
+
CGSize source = CGSizeMake(sourceWidth, sourceHeight);
|
|
256
|
+
CGSize box = CGSizeMake(targetWidth, targetHeight);
|
|
257
|
+
BOOL legacy = [self isLegacyBlockSizing];
|
|
258
|
+
|
|
259
|
+
CGRect drawingRect;
|
|
260
|
+
if (self.isInline || legacy) {
|
|
261
|
+
CGFloat drawingWidth, drawingHeight;
|
|
262
|
+
if (!self.isInline) {
|
|
263
|
+
CGFloat aspectRatioScale = targetWidth / sourceWidth;
|
|
264
|
+
drawingWidth = targetWidth;
|
|
265
|
+
drawingHeight = sourceHeight * aspectRatioScale;
|
|
266
|
+
} else {
|
|
267
|
+
drawingWidth = targetWidth;
|
|
268
|
+
drawingHeight = targetHeight;
|
|
269
|
+
}
|
|
270
|
+
drawingRect = CGRectMake((targetWidth - drawingWidth) / 2.0, (targetHeight - drawingHeight) / 2.0, drawingWidth,
|
|
271
|
+
drawingHeight);
|
|
272
|
+
} else {
|
|
273
|
+
drawingRect = [self drawingRectForResizeMode:self.cachedResizeMode source:source box:box];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
RCTUIGraphicsImageRenderer *renderer = ImageRendererForSize(box);
|
|
277
|
+
|
|
278
|
+
return [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *context) {
|
|
279
|
+
if (radius > 0) {
|
|
280
|
+
CGRect clipRect = CGRectIntersection(CGRectMake(0, 0, targetWidth, targetHeight), drawingRect);
|
|
281
|
+
UIBezierPath *path = UIBezierPathWithRoundedRect(clipRect, radius);
|
|
282
|
+
[path addClip];
|
|
283
|
+
}
|
|
284
|
+
[image drawInRect:drawingRect];
|
|
285
|
+
}];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
- (CGRect)drawingRectForResizeMode:(NSString *)mode source:(CGSize)source box:(CGSize)box
|
|
289
|
+
{
|
|
290
|
+
if ([mode isEqualToString:@"stretch"]) {
|
|
291
|
+
return CGRectMake(0, 0, box.width, box.height);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
CGFloat widthScale = box.width / source.width;
|
|
295
|
+
CGFloat heightScale = box.height / source.height;
|
|
296
|
+
|
|
297
|
+
CGFloat scale;
|
|
298
|
+
if ([mode isEqualToString:@"contain"]) {
|
|
299
|
+
scale = MIN(widthScale, heightScale);
|
|
300
|
+
} else if ([mode isEqualToString:@"center"]) {
|
|
301
|
+
scale = MIN(1.0, MIN(widthScale, heightScale));
|
|
302
|
+
} else if ([mode isEqualToString:@"none"]) {
|
|
303
|
+
scale = 1.0;
|
|
304
|
+
} else { // cover (default)
|
|
305
|
+
scale = MAX(widthScale, heightScale);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
CGFloat drawingWidth = source.width * scale;
|
|
309
|
+
CGFloat drawingHeight = source.height * scale;
|
|
310
|
+
return CGRectMake((box.width - drawingWidth) / 2.0, (box.height - drawingHeight) / 2.0, drawingWidth, drawingHeight);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
- (void)startDownloadingImage
|
|
314
|
+
{
|
|
315
|
+
if (self.imageURL.length == 0)
|
|
316
|
+
return;
|
|
317
|
+
|
|
318
|
+
__weak typeof(self) weakSelf = self;
|
|
319
|
+
[[ENRMImageDownloader shared] downloadURL:self.imageURL
|
|
320
|
+
headers:self.requestHeaders
|
|
321
|
+
completion:^(RCTUIImage *image) { [weakSelf handleLoadedImage:image]; }];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
- (void)refreshDisplay
|
|
325
|
+
{
|
|
326
|
+
// Notify self-drawing hosts (e.g. a table grid) that rasterize us via
|
|
327
|
+
// -drawWithRect: and have no live text view for the invalidation below to reach.
|
|
328
|
+
if (self.onImageLoaded) {
|
|
329
|
+
self.onImageLoaded();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
UITextView *textView = [self fetchAssociatedTextView];
|
|
333
|
+
if (!textView)
|
|
334
|
+
return;
|
|
335
|
+
|
|
336
|
+
NSRange range = [self findAttachmentRangeInText:textView.textStorage];
|
|
337
|
+
if (range.location != NSNotFound) {
|
|
338
|
+
[textView.layoutManager invalidateDisplayForCharacterRange:range];
|
|
339
|
+
if (!self.isInline) {
|
|
340
|
+
[textView.layoutManager invalidateLayoutForCharacterRange:range actualCharacterRange:NULL];
|
|
341
|
+
[self notifyImageLayoutObserver:textView];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// With maxHeight/aspectRatio sizing the box height can settle after the image
|
|
347
|
+
// loads, while the component was measured (and its size cached) with the
|
|
348
|
+
// pre-load fallback. Notify the hosting component so it can re-measure.
|
|
349
|
+
// Dispatched async: refreshDisplay can run mid-layout (imageForBounds) and
|
|
350
|
+
// re-measuring would re-enter the layout manager.
|
|
351
|
+
- (void)notifyImageLayoutObserver:(UITextView *)textView
|
|
352
|
+
{
|
|
353
|
+
// Only maxHeight/aspectRatio boxes can change height after load; a fixed
|
|
354
|
+
// height box (legacy or explicit resizeMode) never needs a re-measure.
|
|
355
|
+
if (self.cachedMaxHeight <= 0 && self.cachedAspectRatio <= 0)
|
|
356
|
+
return;
|
|
357
|
+
|
|
358
|
+
id<ENRMImageLayoutObserver> observer = [textView enrm_imageLayoutObserver];
|
|
359
|
+
if (!observer)
|
|
360
|
+
return;
|
|
361
|
+
|
|
362
|
+
dispatch_async(dispatch_get_main_queue(), ^{ [observer imageAttachmentDidResolveLayout]; });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
- (ENRMPlatformTextView *)fetchAssociatedTextView
|
|
366
|
+
{
|
|
367
|
+
if (self.textView)
|
|
368
|
+
return self.textView;
|
|
369
|
+
if (!self.textContainer)
|
|
370
|
+
return nil;
|
|
371
|
+
self.textView = objc_getAssociatedObject(self.textContainer, kTextViewKey);
|
|
372
|
+
return self.textView;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
- (void)setupPlaceholder
|
|
376
|
+
{
|
|
377
|
+
CGFloat size = self.cachedHeight;
|
|
378
|
+
self.bounds = CGRectMake(0, 0, size, size);
|
|
379
|
+
RCTUIGraphicsImageRenderer *renderer = [[RCTUIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(1, 1)];
|
|
380
|
+
self.placeholderImage = [renderer imageWithActions:^(RCTUIGraphicsImageRendererContext *ctx){}];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
- (NSRange)findAttachmentRangeInText:(NSAttributedString *)attributedString
|
|
384
|
+
{
|
|
385
|
+
__block NSRange foundRange = NSMakeRange(NSNotFound, 0);
|
|
386
|
+
[attributedString enumerateAttribute:NSAttachmentAttributeName
|
|
387
|
+
inRange:NSMakeRange(0, attributedString.length)
|
|
388
|
+
options:0
|
|
389
|
+
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
390
|
+
if (value == self) {
|
|
391
|
+
foundRange = range;
|
|
392
|
+
*stop = YES;
|
|
393
|
+
}
|
|
394
|
+
}];
|
|
395
|
+
return foundRange;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
@end
|