react-native-jet-markdown 0.2.0-beta.7

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.
Files changed (157) hide show
  1. package/JetMarkdown.podspec +42 -0
  2. package/LICENSE +20 -0
  3. package/README.md +247 -0
  4. package/android/CMakeLists.txt +52 -0
  5. package/android/build.gradle +55 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/JniBindings.cpp +243 -0
  8. package/android/src/main/java/com/jetmarkdown/JetMarkdownNative.kt +114 -0
  9. package/android/src/main/java/com/jetmarkdown/JetMarkdownPackage.kt +22 -0
  10. package/android/src/main/java/com/jetmarkdown/JetMarkdownView.kt +235 -0
  11. package/android/src/main/java/com/jetmarkdown/JetMarkdownViewManager.kt +75 -0
  12. package/android/src/main/java/com/jetmarkdown/editor/EditorSpans.kt +217 -0
  13. package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorManager.kt +224 -0
  14. package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorView.kt +1736 -0
  15. package/android/src/main/java/com/jetmarkdown/measure/MarkdownMeasurer.kt +54 -0
  16. package/android/src/main/java/com/jetmarkdown/parser/AstDecoder.kt +59 -0
  17. package/android/src/main/java/com/jetmarkdown/parser/MdNode.kt +43 -0
  18. package/android/src/main/java/com/jetmarkdown/render/ContentCache.kt +53 -0
  19. package/android/src/main/java/com/jetmarkdown/render/RenderedContent.kt +318 -0
  20. package/android/src/main/java/com/jetmarkdown/render/SpannableRenderer.kt +658 -0
  21. package/android/src/main/java/com/jetmarkdown/render/spans/ChipSpan.kt +17 -0
  22. package/android/src/main/java/com/jetmarkdown/render/spans/InteractiveSpans.kt +11 -0
  23. package/android/src/main/java/com/jetmarkdown/render/spans/MarkdownLineHeightSpan.kt +59 -0
  24. package/android/src/main/java/com/jetmarkdown/render/spans/RunSpan.kt +40 -0
  25. package/android/src/main/java/com/jetmarkdown/style/Fonts.kt +34 -0
  26. package/android/src/main/java/com/jetmarkdown/style/LayoutStyleSpec.kt +71 -0
  27. package/android/src/main/java/com/jetmarkdown/style/PlatformColorResolver.kt +77 -0
  28. package/android/src/main/java/com/jetmarkdown/style/StyleConfig.kt +80 -0
  29. package/android/src/main/java/com/jetmarkdown/style/TextStyleSpec.kt +61 -0
  30. package/android/src/main/java/com/jetmarkdown/views/BlockStackView.kt +292 -0
  31. package/android/src/main/java/com/jetmarkdown/views/BlockTextView.kt +378 -0
  32. package/android/src/main/java/com/jetmarkdown/views/BoxDrawing.kt +50 -0
  33. package/android/src/main/java/com/jetmarkdown/views/MarkdownHost.kt +12 -0
  34. package/android/src/main/java/com/jetmarkdown/views/MarkdownImageView.kt +113 -0
  35. package/android/src/main/java/com/jetmarkdown/views/NestedHorizontalScrollView.kt +87 -0
  36. package/android/src/main/java/com/jetmarkdown/views/TableBlockView.kt +139 -0
  37. package/cpp/core/Ast.h +85 -0
  38. package/cpp/core/AstJson.cpp +129 -0
  39. package/cpp/core/AstJson.h +12 -0
  40. package/cpp/core/AstSerializer.cpp +40 -0
  41. package/cpp/core/AstSerializer.h +21 -0
  42. package/cpp/core/AstToMarkdown.cpp +549 -0
  43. package/cpp/core/AstToMarkdown.h +16 -0
  44. package/cpp/core/EditorRuns.cpp +640 -0
  45. package/cpp/core/EditorRuns.h +94 -0
  46. package/cpp/core/EditorText.cpp +15 -0
  47. package/cpp/core/EditorText.h +18 -0
  48. package/cpp/core/InlineExtensions.cpp +305 -0
  49. package/cpp/core/InlineExtensions.h +17 -0
  50. package/cpp/core/Parser.cpp +470 -0
  51. package/cpp/core/Parser.h +16 -0
  52. package/cpp/core/Preprocess.cpp +181 -0
  53. package/cpp/core/Preprocess.h +14 -0
  54. package/cpp/md4c/LICENSE +22 -0
  55. package/cpp/md4c/VERSION +1 -0
  56. package/cpp/md4c/md4c.c +6462 -0
  57. package/cpp/md4c/md4c.h +407 -0
  58. package/cpp/react/JetMarkdownEditorShadowNode.cpp +54 -0
  59. package/cpp/react/JetMarkdownEditorShadowNode.h +55 -0
  60. package/cpp/react/JetMarkdownEditorState.h +35 -0
  61. package/cpp/react/JetMarkdownMeasurer.cpp +32 -0
  62. package/cpp/react/JetMarkdownMeasurer.h +41 -0
  63. package/cpp/react/JetMarkdownShadowNode.cpp +100 -0
  64. package/cpp/react/JetMarkdownShadowNode.h +53 -0
  65. package/cpp/react/JetMarkdownState.h +52 -0
  66. package/cpp/react/override/react/renderer/components/JetMarkdownViewSpec/ComponentDescriptors.h +21 -0
  67. package/cpp/tests/parser_tests.cpp +662 -0
  68. package/cpp/tests/run_tests.sh +25 -0
  69. package/ios/JetMarkdownView.h +14 -0
  70. package/ios/JetMarkdownView.mm +468 -0
  71. package/ios/editor/JetMarkdownEditor.h +12 -0
  72. package/ios/editor/JetMarkdownEditor.mm +2002 -0
  73. package/ios/measure/JMDMarkdownMeasurer.h +22 -0
  74. package/ios/measure/JMDMarkdownMeasurer.mm +38 -0
  75. package/ios/render/JMDBlock.h +97 -0
  76. package/ios/render/JMDBlock.m +28 -0
  77. package/ios/render/JMDBlockRenderer.h +17 -0
  78. package/ios/render/JMDBlockRenderer.mm +852 -0
  79. package/ios/render/JMDContentCache.h +17 -0
  80. package/ios/render/JMDContentCache.mm +42 -0
  81. package/ios/render/JMDRenderedContent.h +35 -0
  82. package/ios/render/JMDRenderedContent.mm +274 -0
  83. package/ios/style/JMDFontScale.h +33 -0
  84. package/ios/style/JMDLayoutStyle.h +37 -0
  85. package/ios/style/JMDLayoutStyle.mm +68 -0
  86. package/ios/style/JMDStyleConfig.h +39 -0
  87. package/ios/style/JMDStyleConfig.mm +111 -0
  88. package/ios/style/JMDTextStyle.h +25 -0
  89. package/ios/style/JMDTextStyle.mm +68 -0
  90. package/ios/views/JMDBlockStackView.h +18 -0
  91. package/ios/views/JMDBlockStackView.m +97 -0
  92. package/ios/views/JMDBlockTextView.h +24 -0
  93. package/ios/views/JMDBlockTextView.m +340 -0
  94. package/ios/views/JMDBoxViews.h +33 -0
  95. package/ios/views/JMDBoxViews.m +292 -0
  96. package/ios/views/JMDImageView.h +19 -0
  97. package/ios/views/JMDImageView.m +68 -0
  98. package/ios/views/JMDMarkdownHost.h +16 -0
  99. package/ios/views/JMDTableView.h +16 -0
  100. package/ios/views/JMDTableView.m +172 -0
  101. package/lib/module/JetMarkdownEditor.js +85 -0
  102. package/lib/module/JetMarkdownEditor.js.map +1 -0
  103. package/lib/module/JetMarkdownEditor.native.js +244 -0
  104. package/lib/module/JetMarkdownEditor.native.js.map +1 -0
  105. package/lib/module/JetMarkdownEditorNativeComponent.ts +173 -0
  106. package/lib/module/JetMarkdownView.js +19 -0
  107. package/lib/module/JetMarkdownView.js.map +1 -0
  108. package/lib/module/JetMarkdownView.native.js +44 -0
  109. package/lib/module/JetMarkdownView.native.js.map +1 -0
  110. package/lib/module/JetMarkdownViewNativeComponent.ts +35 -0
  111. package/lib/module/defaultStyles.js +122 -0
  112. package/lib/module/defaultStyles.js.map +1 -0
  113. package/lib/module/index.js +9 -0
  114. package/lib/module/index.js.map +1 -0
  115. package/lib/module/package.json +1 -0
  116. package/lib/module/serializeStyles.js +259 -0
  117. package/lib/module/serializeStyles.js.map +1 -0
  118. package/lib/module/types.js +4 -0
  119. package/lib/module/types.js.map +1 -0
  120. package/lib/module/useJetMarkdownEditor.js +41 -0
  121. package/lib/module/useJetMarkdownEditor.js.map +1 -0
  122. package/lib/typescript/package.json +1 -0
  123. package/lib/typescript/src/JetMarkdownEditor.d.ts +6 -0
  124. package/lib/typescript/src/JetMarkdownEditor.d.ts.map +1 -0
  125. package/lib/typescript/src/JetMarkdownEditor.native.d.ts +6 -0
  126. package/lib/typescript/src/JetMarkdownEditor.native.d.ts.map +1 -0
  127. package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts +105 -0
  128. package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts.map +1 -0
  129. package/lib/typescript/src/JetMarkdownView.d.ts +3 -0
  130. package/lib/typescript/src/JetMarkdownView.d.ts.map +1 -0
  131. package/lib/typescript/src/JetMarkdownView.native.d.ts +3 -0
  132. package/lib/typescript/src/JetMarkdownView.native.d.ts.map +1 -0
  133. package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts +28 -0
  134. package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts.map +1 -0
  135. package/lib/typescript/src/defaultStyles.d.ts +20 -0
  136. package/lib/typescript/src/defaultStyles.d.ts.map +1 -0
  137. package/lib/typescript/src/index.d.ts +7 -0
  138. package/lib/typescript/src/index.d.ts.map +1 -0
  139. package/lib/typescript/src/serializeStyles.d.ts +34 -0
  140. package/lib/typescript/src/serializeStyles.d.ts.map +1 -0
  141. package/lib/typescript/src/types.d.ts +352 -0
  142. package/lib/typescript/src/types.d.ts.map +1 -0
  143. package/lib/typescript/src/useJetMarkdownEditor.d.ts +36 -0
  144. package/lib/typescript/src/useJetMarkdownEditor.d.ts.map +1 -0
  145. package/package.json +150 -0
  146. package/react-native.config.js +11 -0
  147. package/src/JetMarkdownEditor.native.tsx +301 -0
  148. package/src/JetMarkdownEditor.tsx +90 -0
  149. package/src/JetMarkdownEditorNativeComponent.ts +173 -0
  150. package/src/JetMarkdownView.native.tsx +67 -0
  151. package/src/JetMarkdownView.tsx +16 -0
  152. package/src/JetMarkdownViewNativeComponent.ts +35 -0
  153. package/src/defaultStyles.ts +102 -0
  154. package/src/index.tsx +34 -0
  155. package/src/serializeStyles.ts +369 -0
  156. package/src/types.ts +438 -0
  157. package/src/useJetMarkdownEditor.ts +50 -0
@@ -0,0 +1,17 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ #import "JMDRenderedContent.h"
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ /// Shared parse/render cache. The Fabric measurer populates it on the layout
8
+ /// thread; the mounted view reads the same entry on the main thread.
9
+ @interface JMDContentCache : NSObject
10
+
11
+ + (JMDRenderedContent *)contentForMarkdown:(NSString *)markdown
12
+ stylesJson:(NSString *)stylesJson
13
+ fontScale:(CGFloat)fontScale;
14
+
15
+ @end
16
+
17
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,42 @@
1
+ #import "JMDContentCache.h"
2
+
3
+ #import "../style/JMDStyleConfig.h"
4
+ #import "JMDBlockRenderer.h"
5
+
6
+ @implementation JMDContentCache
7
+
8
+ + (JMDRenderedContent *)contentForMarkdown:(NSString *)markdown
9
+ stylesJson:(NSString *)stylesJson
10
+ fontScale:(CGFloat)fontScale {
11
+ static NSCache<NSString *, JMDRenderedContent *> *cache;
12
+ static dispatch_once_t onceToken;
13
+ dispatch_once(&onceToken, ^{
14
+ cache = [NSCache new];
15
+ cache.countLimit = 64;
16
+ });
17
+
18
+ // Full contents, not hashes: CFString's hash samples at most 96
19
+ // characters, so distinct long documents collide and would render each
20
+ // other's content. NSCache copies nothing — the key strings are shared.
21
+ NSString *key = [NSString stringWithFormat:@"%@\x1f%@\x1f%.3f",
22
+ markdown,
23
+ stylesJson,
24
+ fontScale];
25
+ JMDRenderedContent *cached = [cache objectForKey:key];
26
+ if (cached != nil) {
27
+ return cached;
28
+ }
29
+
30
+ JMDStyleConfig *styles = [JMDStyleConfig configWithJson:stylesJson];
31
+ NSArray<JMDBlock *> *blocks = [JMDBlockRenderer renderMarkdown:markdown
32
+ styles:styles
33
+ fontScale:fontScale];
34
+ JMDRenderedContent *content = [[JMDRenderedContent alloc] initWithBlocks:blocks
35
+ gap:styles.gap
36
+ topPadding:styles.paddingTop
37
+ bottomPadding:styles.paddingBottom];
38
+ [cache setObject:content forKey:key];
39
+ return content;
40
+ }
41
+
42
+ @end
@@ -0,0 +1,35 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ #import "JMDBlock.h"
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @interface JMDWidthLayout : NSObject
8
+ @property (nonatomic, strong) NSArray<JMDMeasuredBlock *> *measured;
9
+ @property (nonatomic, assign) CGFloat totalHeight;
10
+ @end
11
+
12
+ /// Parsed + rendered markdown block tree, shared between the Fabric measurer
13
+ /// (layout thread) and the mounted view (main thread). Per-width layout
14
+ /// results are cached.
15
+ @interface JMDRenderedContent : NSObject
16
+
17
+ @property (nonatomic, readonly) CGFloat gap;
18
+
19
+ - (instancetype)initWithBlocks:(NSArray<JMDBlock *> *)blocks
20
+ gap:(CGFloat)gap
21
+ topPadding:(CGFloat)topPadding
22
+ bottomPadding:(CGFloat)bottomPadding;
23
+
24
+ /// imageSizes: url -> @[@(width), @(height)] in points (dp).
25
+ + (NSString *)keyForImageSizes:
26
+ (nullable NSDictionary<NSString *, NSArray<NSNumber *> *> *)imageSizes;
27
+
28
+ - (JMDWidthLayout *)layoutForWidth:(CGFloat)width
29
+ imageSizes:(nullable NSDictionary<NSString *, NSArray<NSNumber *> *> *)imageSizes;
30
+
31
+ + (CGFloat)stackHeight:(NSArray<JMDMeasuredBlock *> *)children gap:(CGFloat)gap;
32
+
33
+ @end
34
+
35
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,274 @@
1
+ #import "JMDRenderedContent.h"
2
+
3
+ #include <vector>
4
+
5
+ @implementation JMDWidthLayout
6
+ @end
7
+
8
+ @implementation JMDRenderedContent {
9
+ NSArray<JMDBlock *> *_blocks;
10
+ CGFloat _topPadding;
11
+ CGFloat _bottomPadding;
12
+ NSMutableDictionary<NSString *, JMDWidthLayout *> *_layoutCache;
13
+ }
14
+
15
+ - (instancetype)initWithBlocks:(NSArray<JMDBlock *> *)blocks
16
+ gap:(CGFloat)gap
17
+ topPadding:(CGFloat)topPadding
18
+ bottomPadding:(CGFloat)bottomPadding {
19
+ if (self = [super init]) {
20
+ _blocks = [blocks copy];
21
+ _gap = gap;
22
+ _topPadding = topPadding;
23
+ _bottomPadding = bottomPadding;
24
+ _layoutCache = [NSMutableDictionary new];
25
+ }
26
+ return self;
27
+ }
28
+
29
+ + (CGFloat)stackHeight:(NSArray<JMDMeasuredBlock *> *)children gap:(CGFloat)gap {
30
+ CGFloat height = 0;
31
+ for (NSUInteger i = 0; i < children.count; i++) {
32
+ height += children[i].height;
33
+ if (i + 1 < children.count) {
34
+ height += gap;
35
+ }
36
+ }
37
+ return height;
38
+ }
39
+
40
+ // NSDictionary.hash is just the entry COUNT — size changes that keep the
41
+ // count would collide. Key on the sorted contents instead.
42
+ + (NSString *)keyForImageSizes:
43
+ (nullable NSDictionary<NSString *, NSArray<NSNumber *> *> *)imageSizes {
44
+ if (imageSizes.count == 0) {
45
+ return @"";
46
+ }
47
+ NSMutableString *out = [NSMutableString string];
48
+ for (NSString *url in
49
+ [imageSizes.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
50
+ NSArray<NSNumber *> *size = imageSizes[url];
51
+ [out appendFormat:@"%@\x1e%@x%@\x1f", url, size.firstObject, size.lastObject];
52
+ }
53
+ return out;
54
+ }
55
+
56
+ - (JMDWidthLayout *)layoutForWidth:(CGFloat)width
57
+ imageSizes:(nullable NSDictionary<NSString *, NSArray<NSNumber *> *> *)imageSizes {
58
+ NSString *key = [NSString stringWithFormat:@"%.1f|%@",
59
+ width,
60
+ [JMDRenderedContent keyForImageSizes:imageSizes]];
61
+ @synchronized(self) {
62
+ JMDWidthLayout *cached = _layoutCache[key];
63
+ if (cached != nil) {
64
+ return cached;
65
+ }
66
+ }
67
+
68
+ NSMutableArray<JMDMeasuredBlock *> *measured = [NSMutableArray arrayWithCapacity:_blocks.count];
69
+ for (JMDBlock *block in _blocks) {
70
+ [measured addObject:[self measureBlock:block width:width imageSizes:imageSizes]];
71
+ }
72
+
73
+ JMDWidthLayout *layout = [JMDWidthLayout new];
74
+ layout.measured = measured;
75
+ layout.totalHeight =
76
+ [JMDRenderedContent stackHeight:measured gap:_gap] + _topPadding + _bottomPadding;
77
+
78
+ @synchronized(self) {
79
+ if (_layoutCache.count > 4) {
80
+ [_layoutCache removeAllObjects];
81
+ }
82
+ _layoutCache[key] = layout;
83
+ }
84
+ return layout;
85
+ }
86
+
87
+ - (CGSize)textSize:(NSAttributedString *)text width:(CGFloat)width {
88
+ // Measured with TextKit, not NSStringDrawing: boundingRect stops at the
89
+ // last line's drawn descent, dropping the bottom of the final line box
90
+ // under a custom lineHeight. The full box keeps overlays (spoiler covers,
91
+ // run background chips) from being clipped at the view's bottom edge and
92
+ // matches how RN's <Text> and the Android renderer measure.
93
+ NSTextStorage *storage = [[NSTextStorage alloc] initWithAttributedString:text];
94
+ NSLayoutManager *layoutManager = [NSLayoutManager new];
95
+ NSTextContainer *container = [[NSTextContainer alloc]
96
+ initWithSize:CGSizeMake(MIN(width, (CGFloat)1e6), CGFLOAT_MAX)];
97
+ container.lineFragmentPadding = 0;
98
+ [layoutManager addTextContainer:container];
99
+ [storage addLayoutManager:layoutManager];
100
+ [layoutManager ensureLayoutForTextContainer:container];
101
+ const CGRect used = [layoutManager usedRectForTextContainer:container];
102
+ return CGSizeMake(ceil(used.size.width), ceil(used.size.height));
103
+ }
104
+
105
+ - (JMDMeasuredBlock *)measureBlock:(JMDBlock *)block
106
+ width:(CGFloat)width
107
+ imageSizes:(nullable NSDictionary<NSString *, NSArray<NSNumber *> *> *)imageSizes {
108
+ JMDMeasuredBlock *measured = [JMDMeasuredBlock new];
109
+ measured.block = block;
110
+ measured.contentWidth = width;
111
+
112
+ switch (block.kind) {
113
+ case JMDBlockKindText: {
114
+ const CGSize size = [self textSize:block.attributedText width:width];
115
+ measured.height = size.height;
116
+ measured.textHeight = size.height;
117
+ break;
118
+ }
119
+ case JMDBlockKindCode: {
120
+ const CGSize size = [self textSize:block.attributedText width:CGFLOAT_MAX];
121
+ measured.contentWidth = size.width;
122
+ measured.textHeight = size.height;
123
+ measured.height =
124
+ size.height + block.layoutStyle.paddingTop + block.layoutStyle.paddingBottom;
125
+ break;
126
+ }
127
+ case JMDBlockKindQuote: {
128
+ const CGFloat innerWidth = MAX(width - block.layoutStyle.horizontalInset, 1);
129
+ NSMutableArray<JMDMeasuredBlock *> *children =
130
+ [NSMutableArray arrayWithCapacity:block.children.count];
131
+ for (JMDBlock *child in block.children) {
132
+ [children addObject:[self measureBlock:child width:innerWidth imageSizes:imageSizes]];
133
+ }
134
+ measured.children = children;
135
+ measured.height = [JMDRenderedContent stackHeight:children gap:_gap] +
136
+ block.layoutStyle.verticalInset;
137
+ break;
138
+ }
139
+ case JMDBlockKindList: {
140
+ const CGFloat contentX =
141
+ block.listMarginLeft + block.markerMarginLeft + block.markerWidth;
142
+ const CGFloat contentWidth = MAX(width - contentX, 1);
143
+ measured.contentWidth = contentWidth;
144
+ NSMutableArray<NSNumber *> *markerHeights = [NSMutableArray new];
145
+ NSMutableArray<NSArray<JMDMeasuredBlock *> *> *rowContents = [NSMutableArray new];
146
+ CGFloat height = 0;
147
+ for (NSUInteger i = 0; i < block.rows.count; i++) {
148
+ JMDListRow *row = block.rows[i];
149
+ const CGSize markerSize = [self textSize:row.marker width:block.markerWidth];
150
+ NSMutableArray<JMDMeasuredBlock *> *content = [NSMutableArray new];
151
+ for (JMDBlock *child in row.content) {
152
+ [content addObject:[self measureBlock:child width:contentWidth imageSizes:imageSizes]];
153
+ }
154
+ [markerHeights addObject:@(markerSize.height)];
155
+ [rowContents addObject:content];
156
+ height += MAX(markerSize.height, [JMDRenderedContent stackHeight:content gap:_gap]);
157
+ if (i + 1 < block.rows.count) {
158
+ height += _gap / 2;
159
+ }
160
+ }
161
+ measured.markerHeights = markerHeights;
162
+ measured.rowContents = rowContents;
163
+ measured.height = height;
164
+ break;
165
+ }
166
+ case JMDBlockKindDivider:
167
+ measured.height = block.dividerThickness;
168
+ break;
169
+ case JMDBlockKindTable: {
170
+ // Intelligent column widths: natural (unwrapped) width per column,
171
+ // clamped to [min, max]; surplus distributed proportionally when the
172
+ // table fits, horizontal scroll when it does not.
173
+ NSUInteger columnCount = 0;
174
+ for (JMDTableRow *row in block.tableRows) {
175
+ columnCount = MAX(columnCount, row.cells.count);
176
+ }
177
+ if (columnCount == 0) {
178
+ measured.height = 0;
179
+ break;
180
+ }
181
+
182
+ std::vector<CGFloat> natural(columnCount, 0);
183
+ for (JMDTableRow *row in block.tableRows) {
184
+ const UIEdgeInsets pad = row.isHeader ? block.headerCellPadding : block.cellPadding;
185
+ const CGFloat cellPadH = pad.left + pad.right;
186
+ for (NSUInteger column = 0; column < row.cells.count; column++) {
187
+ const CGFloat desired =
188
+ [self textSize:row.cells[column] width:CGFLOAT_MAX].width + cellPadH;
189
+ natural[column] = MAX(natural[column], desired);
190
+ }
191
+ }
192
+
193
+ std::vector<CGFloat> columnWidths(columnCount, 0);
194
+ CGFloat total = 0;
195
+ CGFloat naturalTotal = 0;
196
+ const CGFloat maxColumnWidth =
197
+ block.maxColumnWidth > 0 ? block.maxColumnWidth : CGFLOAT_MAX;
198
+ for (NSUInteger i = 0; i < columnCount; i++) {
199
+ columnWidths[i] = MIN(MAX(natural[i], block.minColumnWidth), maxColumnWidth);
200
+ total += columnWidths[i];
201
+ naturalTotal += natural[i];
202
+ }
203
+ const CGFloat availableWidth = width - block.layoutStyle.horizontalInset;
204
+ if (total < availableWidth && naturalTotal > 0) {
205
+ const CGFloat surplus = availableWidth - total;
206
+ for (NSUInteger i = 0; i < columnCount; i++) {
207
+ columnWidths[i] += surplus * (natural[i] / naturalTotal);
208
+ }
209
+ }
210
+
211
+ NSMutableArray<NSNumber *> *rowHeights = [NSMutableArray new];
212
+ CGFloat contentHeight = 0;
213
+ for (JMDTableRow *row in block.tableRows) {
214
+ JMDLayoutStyle *rowStyle = row.isHeader ? block.headerRowStyle : block.bodyRowStyle;
215
+ const UIEdgeInsets pad = row.isHeader ? block.headerCellPadding : block.cellPadding;
216
+ const CGFloat cellPadH = pad.left + pad.right;
217
+ const CGFloat cellPadV = pad.top + pad.bottom;
218
+ const CGFloat rowExtra = rowStyle.borderTopWidth + rowStyle.borderBottomWidth;
219
+ CGFloat rowHeight = 0;
220
+ for (NSUInteger column = 0; column < row.cells.count; column++) {
221
+ const CGFloat cellWidth = MAX(columnWidths[column] - cellPadH, 1);
222
+ rowHeight = MAX(rowHeight, [self textSize:row.cells[column] width:cellWidth].height);
223
+ }
224
+ rowHeight += cellPadV + rowExtra;
225
+ [rowHeights addObject:@(rowHeight)];
226
+ contentHeight += rowHeight;
227
+ }
228
+
229
+ NSMutableArray<NSNumber *> *widths = [NSMutableArray new];
230
+ CGFloat contentWidth = 0;
231
+ for (NSUInteger i = 0; i < columnCount; i++) {
232
+ [widths addObject:@(columnWidths[i])];
233
+ contentWidth += columnWidths[i];
234
+ }
235
+
236
+ measured.columnWidths = widths;
237
+ measured.rowHeights = rowHeights;
238
+ measured.contentWidth = contentWidth;
239
+ measured.height = contentHeight + block.layoutStyle.verticalInset;
240
+ break;
241
+ }
242
+ case JMDBlockKindImage: {
243
+ NSArray<NSNumber *> *known = block.imageUrl != nil ? imageSizes[block.imageUrl] : nil;
244
+ CGFloat displayH;
245
+ CGFloat displayW;
246
+ if (known.count == 2 && known[0].doubleValue > 0 && known[1].doubleValue > 0) {
247
+ const CGFloat intrinsicW = known[0].doubleValue;
248
+ const CGFloat intrinsicH = known[1].doubleValue;
249
+ const CGFloat scale = MIN(width / intrinsicW, 1.0);
250
+ displayH = intrinsicH * scale;
251
+ if (block.imageHeight > 0) {
252
+ displayH = block.imageHeight;
253
+ }
254
+ if (block.imageMaxHeight > 0) {
255
+ displayH = MIN(displayH, block.imageMaxHeight);
256
+ }
257
+ displayW = MIN(intrinsicW * displayH / intrinsicH, width);
258
+ } else {
259
+ // Full-width placeholder until the intrinsic size is known.
260
+ displayH = block.imageHeight > 0 ? block.imageHeight : block.imagePlaceholder;
261
+ if (block.imageMaxHeight > 0) {
262
+ displayH = MIN(displayH, block.imageMaxHeight);
263
+ }
264
+ displayW = width;
265
+ }
266
+ measured.height = displayH;
267
+ measured.contentWidth = displayW;
268
+ break;
269
+ }
270
+ }
271
+ return measured;
272
+ }
273
+
274
+ @end
@@ -0,0 +1,33 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /// The Dynamic Type multiplier for the current content size category, using
6
+ /// React Native's own category table (RCTAccessibilityManager) so heights
7
+ /// measured with LayoutContext::fontSizeMultiplier match what the host
8
+ /// views render.
9
+ static inline CGFloat JMDFontSizeMultiplier(void) {
10
+ static NSDictionary<UIContentSizeCategory, NSNumber *> *table;
11
+ static dispatch_once_t onceToken;
12
+ dispatch_once(&onceToken, ^{
13
+ table = @{
14
+ UIContentSizeCategoryExtraSmall : @0.823,
15
+ UIContentSizeCategorySmall : @0.882,
16
+ UIContentSizeCategoryMedium : @0.941,
17
+ UIContentSizeCategoryLarge : @1.0,
18
+ UIContentSizeCategoryExtraLarge : @1.118,
19
+ UIContentSizeCategoryExtraExtraLarge : @1.235,
20
+ UIContentSizeCategoryExtraExtraExtraLarge : @1.353,
21
+ UIContentSizeCategoryAccessibilityMedium : @1.786,
22
+ UIContentSizeCategoryAccessibilityLarge : @2.143,
23
+ UIContentSizeCategoryAccessibilityExtraLarge : @2.643,
24
+ UIContentSizeCategoryAccessibilityExtraExtraLarge : @3.143,
25
+ UIContentSizeCategoryAccessibilityExtraExtraExtraLarge : @3.571,
26
+ };
27
+ });
28
+ NSNumber *value =
29
+ table[UIApplication.sharedApplication.preferredContentSizeCategory];
30
+ return value != nil ? value.doubleValue : 1.0;
31
+ }
32
+
33
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,37 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /// One element's box style from stylesJson (point values).
6
+ @interface JMDLayoutStyle : NSObject
7
+
8
+ @property (nonatomic, readonly, nullable) UIColor *backgroundColor;
9
+ @property (nonatomic, readonly) CGFloat paddingLeft;
10
+ @property (nonatomic, readonly) CGFloat paddingRight;
11
+ @property (nonatomic, readonly) CGFloat paddingTop;
12
+ @property (nonatomic, readonly) CGFloat paddingBottom;
13
+ @property (nonatomic, readonly) CGFloat borderRadius;
14
+ @property (nonatomic, readonly) BOOL continuousCorners;
15
+ @property (nonatomic, readonly, nullable) UIColor *borderLeftColor;
16
+ @property (nonatomic, readonly) CGFloat borderLeftWidth;
17
+ @property (nonatomic, readonly, nullable) UIColor *borderRightColor;
18
+ @property (nonatomic, readonly) CGFloat borderRightWidth;
19
+ @property (nonatomic, readonly, nullable) UIColor *borderTopColor;
20
+ @property (nonatomic, readonly) CGFloat borderTopWidth;
21
+ @property (nonatomic, readonly, nullable) UIColor *borderBottomColor;
22
+ @property (nonatomic, readonly) CGFloat borderBottomWidth;
23
+
24
+ @property (nonatomic, readonly) CGFloat horizontalInset;
25
+ @property (nonatomic, readonly) CGFloat verticalInset;
26
+
27
+ + (instancetype)fromJson:(nullable NSDictionary *)json defaults:(nullable JMDLayoutStyle *)defaults;
28
+
29
+ + (instancetype)defaultsWithBackground:(nullable UIColor *)background
30
+ padding:(UIEdgeInsets)padding
31
+ borderRadius:(CGFloat)radius
32
+ borderLeftColor:(nullable UIColor *)borderLeftColor
33
+ borderLeftWidth:(CGFloat)borderLeftWidth;
34
+
35
+ @end
36
+
37
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,68 @@
1
+ #import "JMDLayoutStyle.h"
2
+
3
+ #import "JMDTextStyle.h"
4
+
5
+ @implementation JMDLayoutStyle
6
+
7
+ + (instancetype)defaultsWithBackground:(nullable UIColor *)background
8
+ padding:(UIEdgeInsets)padding
9
+ borderRadius:(CGFloat)radius
10
+ borderLeftColor:(nullable UIColor *)borderLeftColor
11
+ borderLeftWidth:(CGFloat)borderLeftWidth {
12
+ JMDLayoutStyle *style = [JMDLayoutStyle new];
13
+ style->_backgroundColor = background;
14
+ style->_paddingLeft = padding.left;
15
+ style->_paddingRight = padding.right;
16
+ style->_paddingTop = padding.top;
17
+ style->_paddingBottom = padding.bottom;
18
+ style->_borderRadius = radius;
19
+ style->_borderLeftColor = borderLeftColor;
20
+ style->_borderLeftWidth = borderLeftWidth;
21
+ return style;
22
+ }
23
+
24
+ + (instancetype)fromJson:(nullable NSDictionary *)json defaults:(nullable JMDLayoutStyle *)defaults {
25
+ JMDLayoutStyle *base = defaults ?: [JMDLayoutStyle new];
26
+ if (![json isKindOfClass:[NSDictionary class]]) {
27
+ return base;
28
+ }
29
+
30
+ JMDLayoutStyle *style = [JMDLayoutStyle new];
31
+ auto number = [json](NSString *key, CGFloat fallback) -> CGFloat {
32
+ NSNumber *value = [json[key] isKindOfClass:[NSNumber class]] ? json[key] : nil;
33
+ return value != nil ? value.doubleValue : fallback;
34
+ };
35
+ auto color = [json](NSString *key, UIColor *fallback) -> UIColor * {
36
+ UIColor *value = [JMDTextStyle colorFromJson:json[key]];
37
+ return value ?: fallback;
38
+ };
39
+
40
+ style->_backgroundColor = color(@"backgroundColor", base.backgroundColor);
41
+ style->_paddingLeft = number(@"paddingLeft", base.paddingLeft);
42
+ style->_paddingRight = number(@"paddingRight", base.paddingRight);
43
+ style->_paddingTop = number(@"paddingTop", base.paddingTop);
44
+ style->_paddingBottom = number(@"paddingBottom", base.paddingBottom);
45
+ style->_borderRadius = number(@"borderRadius", base.borderRadius);
46
+ style->_continuousCorners =
47
+ [json[@"borderCurve"] isKindOfClass:[NSString class]] &&
48
+ [json[@"borderCurve"] isEqualToString:@"continuous"];
49
+ style->_borderLeftColor = color(@"borderLeftColor", base.borderLeftColor);
50
+ style->_borderLeftWidth = number(@"borderLeftWidth", base.borderLeftWidth);
51
+ style->_borderRightColor = color(@"borderRightColor", base.borderRightColor);
52
+ style->_borderRightWidth = number(@"borderRightWidth", base.borderRightWidth);
53
+ style->_borderTopColor = color(@"borderTopColor", base.borderTopColor);
54
+ style->_borderTopWidth = number(@"borderTopWidth", base.borderTopWidth);
55
+ style->_borderBottomColor = color(@"borderBottomColor", base.borderBottomColor);
56
+ style->_borderBottomWidth = number(@"borderBottomWidth", base.borderBottomWidth);
57
+ return style;
58
+ }
59
+
60
+ - (CGFloat)horizontalInset {
61
+ return _paddingLeft + _paddingRight + _borderLeftWidth + _borderRightWidth;
62
+ }
63
+
64
+ - (CGFloat)verticalInset {
65
+ return _paddingTop + _paddingBottom + _borderTopWidth + _borderBottomWidth;
66
+ }
67
+
68
+ @end
@@ -0,0 +1,39 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ #import "JMDTextStyle.h"
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @interface JMDMentionVariant : NSObject
8
+ @property (nonatomic, readonly) NSRegularExpression *pattern;
9
+ @property (nonatomic, readonly, nullable) JMDTextStyle *style;
10
+ @end
11
+
12
+ /// Parsed stylesJson with cached instances per JSON string.
13
+ @interface JMDStyleConfig : NSObject
14
+
15
+ @property (nonatomic, readonly) CGFloat gap;
16
+ @property (nonatomic, readonly) CGFloat paddingLeft;
17
+ @property (nonatomic, readonly) CGFloat paddingRight;
18
+ @property (nonatomic, readonly) CGFloat paddingTop;
19
+ @property (nonatomic, readonly) CGFloat paddingBottom;
20
+ @property (nonatomic, readonly, nullable) UIColor *backgroundColor;
21
+
22
+ /// Ordered longest-pattern-first; a link whose URL matches becomes a mention.
23
+ @property (nonatomic, readonly) NSArray<JMDMentionVariant *> *mentionVariants;
24
+
25
+ + (instancetype)configWithJson:(NSString *)json;
26
+
27
+ /// Raw JSON section for an element key (layout parsing).
28
+ - (nullable NSDictionary *)rawSectionFor:(NSString *)key;
29
+
30
+ /// User style for an element key (paragraph, h1..h6, bold, italic,
31
+ /// strikethrough, link, mention, inlineCode, superscript, subscript,
32
+ /// listItem, tableCell, codeBlock, blockQuote). Nil when not provided.
33
+ - (nullable JMDTextStyle *)textStyleFor:(NSString *)key;
34
+
35
+ /// Built-in default font size for a heading level 1-6 or body text (0).
36
+
37
+ @end
38
+
39
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,111 @@
1
+ #import "JMDStyleConfig.h"
2
+
3
+ @implementation JMDMentionVariant
4
+
5
+ - (instancetype)initWithPattern:(NSRegularExpression *)pattern
6
+ style:(nullable JMDTextStyle *)style {
7
+ if (self = [super init]) {
8
+ _pattern = pattern;
9
+ _style = style;
10
+ }
11
+ return self;
12
+ }
13
+
14
+ @end
15
+
16
+ @implementation JMDStyleConfig {
17
+ NSDictionary *_root;
18
+ NSMutableDictionary<NSString *, id> *_textStyles;
19
+ }
20
+
21
+ + (instancetype)configWithJson:(NSString *)json {
22
+ static NSCache<NSString *, JMDStyleConfig *> *cache;
23
+ static dispatch_once_t onceToken;
24
+ dispatch_once(&onceToken, ^{
25
+ cache = [NSCache new];
26
+ cache.countLimit = 16;
27
+ });
28
+
29
+ NSString *key = json.length > 0 ? json : @"{}";
30
+ JMDStyleConfig *cached = [cache objectForKey:key];
31
+ if (cached != nil) {
32
+ return cached;
33
+ }
34
+ JMDStyleConfig *config = [[JMDStyleConfig alloc] initWithJson:key];
35
+ [cache setObject:config forKey:key];
36
+ return config;
37
+ }
38
+
39
+ - (instancetype)initWithJson:(NSString *)json {
40
+ if (self = [super init]) {
41
+ NSDictionary *root = nil;
42
+ NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
43
+ if (data != nil) {
44
+ id parsed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
45
+ if ([parsed isKindOfClass:[NSDictionary class]]) {
46
+ root = parsed;
47
+ }
48
+ }
49
+ _root = root ?: @{};
50
+ _textStyles = [NSMutableDictionary new];
51
+
52
+ NSDictionary *main = [_root[@"main"] isKindOfClass:[NSDictionary class]] ? _root[@"main"] : @{};
53
+ // Container style-prop gap wins; the styles prop (defaultStyles.gap)
54
+ // supplies the themed value; unstyled floor is 0.
55
+ _gap = [self floatFrom:main key:@"gap"
56
+ fallback:[self floatFrom:_root key:@"gap" fallback:0]];
57
+ _paddingLeft = [self floatFrom:main key:@"paddingLeft" fallback:0];
58
+ _paddingRight = [self floatFrom:main key:@"paddingRight" fallback:0];
59
+ _paddingTop = [self floatFrom:main key:@"paddingTop" fallback:0];
60
+ _paddingBottom = [self floatFrom:main key:@"paddingBottom" fallback:0];
61
+ _backgroundColor = [JMDTextStyle colorFromJson:main[@"backgroundColor"]];
62
+
63
+ NSMutableArray<JMDMentionVariant *> *variants = [NSMutableArray new];
64
+ NSDictionary *mention =
65
+ [_root[@"mention"] isKindOfClass:[NSDictionary class]] ? _root[@"mention"] : nil;
66
+ NSArray *variantPairs =
67
+ [mention[@"variants"] isKindOfClass:[NSArray class]] ? mention[@"variants"] : @[];
68
+ for (id pair in variantPairs) {
69
+ if (![pair isKindOfClass:[NSArray class]] || [pair count] != 2) {
70
+ continue;
71
+ }
72
+ NSString *patternString = [pair[0] isKindOfClass:[NSString class]] ? pair[0] : nil;
73
+ if (patternString == nil) {
74
+ continue;
75
+ }
76
+ NSRegularExpression *pattern =
77
+ [NSRegularExpression regularExpressionWithPattern:patternString options:0 error:nil];
78
+ if (pattern == nil) {
79
+ continue;
80
+ }
81
+ [variants addObject:[[JMDMentionVariant alloc]
82
+ initWithPattern:pattern
83
+ style:[JMDTextStyle fromJson:pair[1]]]];
84
+ }
85
+ _mentionVariants = variants;
86
+ }
87
+ return self;
88
+ }
89
+
90
+ - (CGFloat)floatFrom:(NSDictionary *)dict key:(NSString *)key fallback:(CGFloat)fallback {
91
+ NSNumber *value = [dict[key] isKindOfClass:[NSNumber class]] ? dict[key] : nil;
92
+ return value != nil ? value.doubleValue : fallback;
93
+ }
94
+
95
+ - (nullable NSDictionary *)rawSectionFor:(NSString *)key {
96
+ return [_root[key] isKindOfClass:[NSDictionary class]] ? _root[key] : nil;
97
+ }
98
+
99
+ - (nullable JMDTextStyle *)textStyleFor:(NSString *)key {
100
+ @synchronized(self) {
101
+ id cached = _textStyles[key];
102
+ if (cached != nil) {
103
+ return cached == NSNull.null ? nil : cached;
104
+ }
105
+ JMDTextStyle *style = [JMDTextStyle fromJson:_root[key]];
106
+ _textStyles[key] = style ?: (id)NSNull.null;
107
+ return style;
108
+ }
109
+ }
110
+
111
+ @end
@@ -0,0 +1,25 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /// One element's text style as it arrived in stylesJson; nil fields inherit.
6
+ @interface JMDTextStyle : NSObject
7
+
8
+ @property (nonatomic, readonly, nullable) NSNumber *fontSize;
9
+ @property (nonatomic, readonly, nullable) NSNumber *fontWeight; // 100-900
10
+ @property (nonatomic, readonly, nullable) NSString *fontFamily;
11
+ @property (nonatomic, readonly, nullable) NSNumber *lineHeight;
12
+ @property (nonatomic, readonly, nullable) UIColor *color;
13
+ @property (nonatomic, readonly, nullable) NSArray<NSString *> *fontVariant;
14
+ @property (nonatomic, readonly, nullable) UIColor *textDecorationColor;
15
+ @property (nonatomic, readonly, nullable) NSString *textDecorationLine;
16
+ @property (nonatomic, readonly, nullable) NSString *textDecorationStyle;
17
+ @property (nonatomic, readonly, nullable) UIColor *backgroundColor;
18
+
19
+ + (nullable instancetype)fromJson:(nullable NSDictionary *)json;
20
+
21
+ + (nullable UIColor *)colorFromJson:(nullable id)value;
22
+
23
+ @end
24
+
25
+ NS_ASSUME_NONNULL_END