react-native-enriched-markdown 0.1.0 → 0.2.0
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 -0
- package/README.md +551 -0
- package/ReactNativeEnrichedMarkdown.podspec +27 -0
- package/android/build.gradle +101 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +54 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +26 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +33 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +82 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1388 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp +16 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h +20 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/baseline-prof.txt +65 -0
- package/android/src/main/cpp/jni-adapter.cpp +220 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +270 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +173 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextPackage.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +385 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +279 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLongPressEvent.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkPressEvent.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +31 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +62 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +166 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +84 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +104 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +70 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +68 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +105 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +59 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +76 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +103 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +80 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +109 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +86 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrikethroughRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +45 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/UnderlineRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +136 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +135 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +180 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +196 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/HeadingSpan.kt +38 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +321 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +76 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +87 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrikethroughSpan.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +37 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/TextSpan.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ThematicBreakSpan.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +11 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +54 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeStyle.kt +21 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/EmphasisStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineImageStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +57 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrongStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +211 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +92 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/UnderlineStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/AsyncDrawable.kt +91 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +827 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/LinkLongPressMovementMethod.kt +121 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +375 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/SelectionActionMode.kt +139 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +183 -0
- package/android/src/main/jni/CMakeLists.txt +70 -0
- package/android/src/main/jni/EnrichedMarkdownTextSpec.cpp +21 -0
- package/android/src/main/jni/EnrichedMarkdownTextSpec.h +25 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextComponentDescriptor.h +29 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.cpp +45 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.h +21 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +20 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +37 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +22 -0
- package/cpp/md4c/md4c.c +6492 -0
- package/cpp/md4c/md4c.h +402 -0
- package/cpp/parser/MD4CParser.cpp +327 -0
- package/cpp/parser/MD4CParser.hpp +27 -0
- package/cpp/parser/MarkdownASTNode.hpp +51 -0
- package/ios/EnrichedMarkdownText.h +18 -0
- package/ios/EnrichedMarkdownText.mm +1401 -0
- package/ios/attachments/EnrichedMarkdownImageAttachment.h +23 -0
- package/ios/attachments/EnrichedMarkdownImageAttachment.m +185 -0
- package/ios/attachments/ThematicBreakAttachment.h +15 -0
- package/ios/attachments/ThematicBreakAttachment.m +33 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +33 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +82 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1388 -0
- package/ios/generated/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +20 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
- package/ios/generated/EnrichedMarkdownTextSpec/States.cpp +16 -0
- package/ios/generated/EnrichedMarkdownTextSpec/States.h +20 -0
- package/ios/internals/EnrichedMarkdownTextComponentDescriptor.h +19 -0
- package/ios/internals/EnrichedMarkdownTextShadowNode.h +43 -0
- package/ios/internals/EnrichedMarkdownTextShadowNode.mm +85 -0
- package/ios/internals/EnrichedMarkdownTextState.h +24 -0
- package/ios/parser/MarkdownASTNode.h +35 -0
- package/ios/parser/MarkdownASTNode.m +32 -0
- package/ios/parser/MarkdownParser.h +17 -0
- package/ios/parser/MarkdownParser.mm +42 -0
- package/ios/parser/MarkdownParserBridge.mm +120 -0
- package/ios/renderer/AttributedRenderer.h +11 -0
- package/ios/renderer/AttributedRenderer.m +152 -0
- package/ios/renderer/BlockquoteRenderer.h +7 -0
- package/ios/renderer/BlockquoteRenderer.m +160 -0
- package/ios/renderer/CodeBlockRenderer.h +10 -0
- package/ios/renderer/CodeBlockRenderer.m +90 -0
- package/ios/renderer/CodeRenderer.h +10 -0
- package/ios/renderer/CodeRenderer.m +60 -0
- package/ios/renderer/EmphasisRenderer.h +6 -0
- package/ios/renderer/EmphasisRenderer.m +96 -0
- package/ios/renderer/HeadingRenderer.h +7 -0
- package/ios/renderer/HeadingRenderer.m +105 -0
- package/ios/renderer/ImageRenderer.h +12 -0
- package/ios/renderer/ImageRenderer.m +83 -0
- package/ios/renderer/LinkRenderer.h +7 -0
- package/ios/renderer/LinkRenderer.m +69 -0
- package/ios/renderer/ListItemRenderer.h +16 -0
- package/ios/renderer/ListItemRenderer.m +103 -0
- package/ios/renderer/ListRenderer.h +13 -0
- package/ios/renderer/ListRenderer.m +70 -0
- package/ios/renderer/NodeRenderer.h +8 -0
- package/ios/renderer/ParagraphRenderer.h +7 -0
- package/ios/renderer/ParagraphRenderer.m +80 -0
- package/ios/renderer/RenderContext.h +105 -0
- package/ios/renderer/RenderContext.m +312 -0
- package/ios/renderer/RendererFactory.h +12 -0
- package/ios/renderer/RendererFactory.m +116 -0
- package/ios/renderer/StrikethroughRenderer.h +6 -0
- package/ios/renderer/StrikethroughRenderer.m +40 -0
- package/ios/renderer/StrongRenderer.h +6 -0
- package/ios/renderer/StrongRenderer.m +83 -0
- package/ios/renderer/TextRenderer.h +6 -0
- package/ios/renderer/TextRenderer.m +16 -0
- package/ios/renderer/ThematicBreakRenderer.h +5 -0
- package/ios/renderer/ThematicBreakRenderer.m +53 -0
- package/ios/renderer/UnderlineRenderer.h +6 -0
- package/ios/renderer/UnderlineRenderer.m +39 -0
- package/ios/styles/StyleConfig.h +274 -0
- package/ios/styles/StyleConfig.mm +1806 -0
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -0
- package/ios/utils/BlockquoteBorder.h +20 -0
- package/ios/utils/BlockquoteBorder.m +92 -0
- package/ios/utils/CodeBackground.h +19 -0
- package/ios/utils/CodeBackground.m +191 -0
- package/ios/utils/CodeBlockBackground.h +17 -0
- package/ios/utils/CodeBlockBackground.m +82 -0
- package/ios/utils/EditMenuUtils.h +22 -0
- package/ios/utils/EditMenuUtils.m +118 -0
- package/ios/utils/FontUtils.h +25 -0
- package/ios/utils/FontUtils.m +27 -0
- package/ios/utils/HTMLGenerator.h +20 -0
- package/ios/utils/HTMLGenerator.m +793 -0
- package/ios/utils/LastElementUtils.h +53 -0
- package/ios/utils/ListMarkerDrawer.h +15 -0
- package/ios/utils/ListMarkerDrawer.m +127 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +45 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
- package/ios/utils/MarkdownExtractor.h +17 -0
- package/ios/utils/MarkdownExtractor.m +308 -0
- package/ios/utils/ParagraphStyleUtils.h +21 -0
- package/ios/utils/ParagraphStyleUtils.m +111 -0
- package/ios/utils/PasteboardUtils.h +36 -0
- package/ios/utils/PasteboardUtils.m +134 -0
- package/ios/utils/RTFExportUtils.h +24 -0
- package/ios/utils/RTFExportUtils.m +297 -0
- package/ios/utils/RuntimeKeys.h +38 -0
- package/ios/utils/RuntimeKeys.m +11 -0
- package/ios/utils/TextViewLayoutManager.h +14 -0
- package/ios/utils/TextViewLayoutManager.mm +113 -0
- package/lib/module/EnrichedMarkdownText.js +65 -0
- package/lib/module/EnrichedMarkdownText.js.map +1 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +210 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalizeMarkdownStyle.js +384 -0
- package/lib/module/normalizeMarkdownStyle.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts +183 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +185 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts +6 -0
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -0
- package/package.json +186 -1
- package/react-native.config.js +13 -0
- package/src/EnrichedMarkdownText.tsx +280 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +210 -0
- package/src/index.tsx +10 -0
- package/src/normalizeMarkdownStyle.ts +423 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
#import "HTMLGenerator.h"
|
|
2
|
+
#import "BlockquoteBorder.h"
|
|
3
|
+
#import "CodeBackground.h"
|
|
4
|
+
#import "EnrichedMarkdownImageAttachment.h"
|
|
5
|
+
#import "LastElementUtils.h"
|
|
6
|
+
#import "ListItemRenderer.h"
|
|
7
|
+
#import "RenderContext.h"
|
|
8
|
+
#import "RuntimeKeys.h"
|
|
9
|
+
#import "StyleConfig.h"
|
|
10
|
+
#import "ThematicBreakAttachment.h"
|
|
11
|
+
|
|
12
|
+
static NSString *const kObjectReplacementChar = @"\uFFFC";
|
|
13
|
+
static const CGFloat kBlockquoteVerticalPadding = 8.0;
|
|
14
|
+
static const CGFloat kBlockquoteParagraphSpacing = 4.0;
|
|
15
|
+
static const CGFloat kNestedBlockquoteTopMargin = 8.0;
|
|
16
|
+
static const CGFloat kDefaultListIndent = 24.0;
|
|
17
|
+
static const CGFloat kCodePadding = 2.0;
|
|
18
|
+
static const CGFloat kCodeBorderRadius = 4.0;
|
|
19
|
+
|
|
20
|
+
#pragma mark - Paragraph Types
|
|
21
|
+
|
|
22
|
+
typedef NS_ENUM(NSInteger, ParagraphType) {
|
|
23
|
+
ParagraphTypeNormal,
|
|
24
|
+
ParagraphTypeHeading1,
|
|
25
|
+
ParagraphTypeHeading2,
|
|
26
|
+
ParagraphTypeHeading3,
|
|
27
|
+
ParagraphTypeHeading4,
|
|
28
|
+
ParagraphTypeHeading5,
|
|
29
|
+
ParagraphTypeHeading6,
|
|
30
|
+
ParagraphTypeCodeBlock,
|
|
31
|
+
ParagraphTypeBlockquote,
|
|
32
|
+
ParagraphTypeListItemUnordered,
|
|
33
|
+
ParagraphTypeListItemOrdered
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
typedef struct {
|
|
37
|
+
NSRange range;
|
|
38
|
+
ParagraphType type;
|
|
39
|
+
NSInteger depth;
|
|
40
|
+
NSInteger listNumber;
|
|
41
|
+
} ParagraphData;
|
|
42
|
+
|
|
43
|
+
#pragma mark - Cached Style Config
|
|
44
|
+
|
|
45
|
+
/// Pre-fetched style values to avoid repeated StyleConfig method calls
|
|
46
|
+
@interface CachedStyles : NSObject
|
|
47
|
+
@property (nonatomic, copy) NSString *paragraphColor;
|
|
48
|
+
@property (nonatomic, copy) NSString *strongColor;
|
|
49
|
+
@property (nonatomic, copy) NSString *emphasisColor;
|
|
50
|
+
@property (nonatomic, copy) NSString *linkColor;
|
|
51
|
+
@property (nonatomic, copy) NSString *codeColor;
|
|
52
|
+
@property (nonatomic, copy) NSString *codeBackgroundColor;
|
|
53
|
+
@property (nonatomic, copy) NSString *codeBlockColor;
|
|
54
|
+
@property (nonatomic, copy) NSString *codeBlockBackgroundColor;
|
|
55
|
+
@property (nonatomic, copy) NSString *blockquoteColor;
|
|
56
|
+
@property (nonatomic, copy) NSString *blockquoteBackgroundColor;
|
|
57
|
+
@property (nonatomic, copy) NSString *blockquoteBorderColor;
|
|
58
|
+
@property (nonatomic, copy) NSString *listStyleColor;
|
|
59
|
+
@property (nonatomic, copy) NSString *h1Color;
|
|
60
|
+
@property (nonatomic, copy) NSString *h2Color;
|
|
61
|
+
@property (nonatomic, copy) NSString *h3Color;
|
|
62
|
+
@property (nonatomic, copy) NSString *h4Color;
|
|
63
|
+
@property (nonatomic, copy) NSString *h5Color;
|
|
64
|
+
@property (nonatomic, copy) NSString *h6Color;
|
|
65
|
+
@property (nonatomic) CGFloat paragraphFontSize;
|
|
66
|
+
@property (nonatomic) CGFloat paragraphMarginBottom;
|
|
67
|
+
@property (nonatomic) CGFloat codeBlockFontSize;
|
|
68
|
+
@property (nonatomic) CGFloat codeBlockPadding;
|
|
69
|
+
@property (nonatomic) CGFloat codeBlockBorderRadius;
|
|
70
|
+
@property (nonatomic) CGFloat codeBlockMarginBottom;
|
|
71
|
+
@property (nonatomic) CGFloat blockquoteFontSize;
|
|
72
|
+
@property (nonatomic) CGFloat blockquoteBorderWidth;
|
|
73
|
+
@property (nonatomic) CGFloat blockquoteMarginBottom;
|
|
74
|
+
@property (nonatomic) CGFloat blockquoteGapWidth;
|
|
75
|
+
@property (nonatomic) CGFloat listStyleFontSize;
|
|
76
|
+
@property (nonatomic) CGFloat listStyleMarginBottom;
|
|
77
|
+
@property (nonatomic) CGFloat listStyleMarginLeft;
|
|
78
|
+
@property (nonatomic) CGFloat imageMarginBottom;
|
|
79
|
+
@property (nonatomic) CGFloat imageBorderRadius;
|
|
80
|
+
@property (nonatomic) CGFloat h1FontSize;
|
|
81
|
+
@property (nonatomic) CGFloat h2FontSize;
|
|
82
|
+
@property (nonatomic) CGFloat h3FontSize;
|
|
83
|
+
@property (nonatomic) CGFloat h4FontSize;
|
|
84
|
+
@property (nonatomic) CGFloat h5FontSize;
|
|
85
|
+
@property (nonatomic) CGFloat h6FontSize;
|
|
86
|
+
@property (nonatomic) CGFloat h1MarginBottom;
|
|
87
|
+
@property (nonatomic) CGFloat h2MarginBottom;
|
|
88
|
+
@property (nonatomic) CGFloat h3MarginBottom;
|
|
89
|
+
@property (nonatomic) CGFloat h4MarginBottom;
|
|
90
|
+
@property (nonatomic) CGFloat h5MarginBottom;
|
|
91
|
+
@property (nonatomic) CGFloat h6MarginBottom;
|
|
92
|
+
@property (nonatomic, copy) NSString *h1FontWeight;
|
|
93
|
+
@property (nonatomic, copy) NSString *h2FontWeight;
|
|
94
|
+
@property (nonatomic, copy) NSString *h3FontWeight;
|
|
95
|
+
@property (nonatomic, copy) NSString *h4FontWeight;
|
|
96
|
+
@property (nonatomic, copy) NSString *h5FontWeight;
|
|
97
|
+
@property (nonatomic, copy) NSString *h6FontWeight;
|
|
98
|
+
@property (nonatomic) BOOL linkUnderline;
|
|
99
|
+
@property (nonatomic, copy) NSString *thematicBreakColor;
|
|
100
|
+
@property (nonatomic) CGFloat thematicBreakHeight;
|
|
101
|
+
@property (nonatomic) CGFloat thematicBreakMarginTop;
|
|
102
|
+
@property (nonatomic) CGFloat thematicBreakMarginBottom;
|
|
103
|
+
@property (nonatomic, copy) NSString *strikethroughColor;
|
|
104
|
+
@property (nonatomic, copy) NSString *underlineColor;
|
|
105
|
+
@end
|
|
106
|
+
|
|
107
|
+
@implementation CachedStyles
|
|
108
|
+
@end
|
|
109
|
+
|
|
110
|
+
#pragma mark - Generator State
|
|
111
|
+
|
|
112
|
+
@interface GeneratorState : NSObject
|
|
113
|
+
@property (nonatomic) NSInteger currentListDepth;
|
|
114
|
+
@property (nonatomic) NSInteger currentBlockquoteDepth;
|
|
115
|
+
@property (nonatomic) BOOL inBlockquote;
|
|
116
|
+
@property (nonatomic) BOOL inCodeBlock;
|
|
117
|
+
@property (nonatomic) BOOL previousWasCodeBlock;
|
|
118
|
+
@property (nonatomic) BOOL previousWasBlockquote;
|
|
119
|
+
@property (nonatomic, strong) NSMutableArray<NSNumber *> *openListTypes;
|
|
120
|
+
@end
|
|
121
|
+
|
|
122
|
+
@implementation GeneratorState
|
|
123
|
+
- (instancetype)init
|
|
124
|
+
{
|
|
125
|
+
self = [super init];
|
|
126
|
+
if (self) {
|
|
127
|
+
_currentListDepth = -1;
|
|
128
|
+
_currentBlockquoteDepth = -1;
|
|
129
|
+
_openListTypes = [NSMutableArray array];
|
|
130
|
+
}
|
|
131
|
+
return self;
|
|
132
|
+
}
|
|
133
|
+
@end
|
|
134
|
+
|
|
135
|
+
#pragma mark - Color Conversion
|
|
136
|
+
|
|
137
|
+
static NSString *colorToCSS(UIColor *color)
|
|
138
|
+
{
|
|
139
|
+
if (!color)
|
|
140
|
+
return @"inherit";
|
|
141
|
+
|
|
142
|
+
CGFloat r = 0, g = 0, b = 0, a = 1;
|
|
143
|
+
[color getRed:&r green:&g blue:&b alpha:&a];
|
|
144
|
+
|
|
145
|
+
if (a < 1.0) {
|
|
146
|
+
return [NSString stringWithFormat:@"rgba(%.0f, %.0f, %.0f, %.2f)", r * 255, g * 255, b * 255, a];
|
|
147
|
+
}
|
|
148
|
+
return [NSString stringWithFormat:@"#%02X%02X%02X", (int)(r * 255), (int)(g * 255), (int)(b * 255)];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#pragma mark - HTML Escaping
|
|
152
|
+
|
|
153
|
+
static void appendEscapedHTML(NSMutableString *output, NSString *text)
|
|
154
|
+
{
|
|
155
|
+
NSUInteger length = text.length;
|
|
156
|
+
if (length == 0)
|
|
157
|
+
return;
|
|
158
|
+
|
|
159
|
+
for (NSUInteger i = 0; i < length; i++) {
|
|
160
|
+
unichar c = [text characterAtIndex:i];
|
|
161
|
+
switch (c) {
|
|
162
|
+
case '&':
|
|
163
|
+
[output appendString:@"&"];
|
|
164
|
+
break;
|
|
165
|
+
case '<':
|
|
166
|
+
[output appendString:@"<"];
|
|
167
|
+
break;
|
|
168
|
+
case '>':
|
|
169
|
+
[output appendString:@">"];
|
|
170
|
+
break;
|
|
171
|
+
case '"':
|
|
172
|
+
[output appendString:@"""];
|
|
173
|
+
break;
|
|
174
|
+
case '\'':
|
|
175
|
+
[output appendString:@"'"];
|
|
176
|
+
break;
|
|
177
|
+
default:
|
|
178
|
+
[output appendFormat:@"%C", c];
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static NSString *escapeHTML(NSString *text)
|
|
185
|
+
{
|
|
186
|
+
// Fast path: skip if no special chars
|
|
187
|
+
static NSCharacterSet *escapeChars = nil;
|
|
188
|
+
static dispatch_once_t onceToken;
|
|
189
|
+
dispatch_once(&onceToken, ^{ escapeChars = [NSCharacterSet characterSetWithCharactersInString:@"&<>\"'"]; });
|
|
190
|
+
|
|
191
|
+
if ([text rangeOfCharacterFromSet:escapeChars].location == NSNotFound) {
|
|
192
|
+
return text;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
NSMutableString *escaped = [NSMutableString stringWithCapacity:text.length + 16];
|
|
196
|
+
appendEscapedHTML(escaped, text);
|
|
197
|
+
return escaped;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#pragma mark - Font Weight Conversion
|
|
201
|
+
|
|
202
|
+
static NSString *fontWeightToCSS(NSString *fontWeight)
|
|
203
|
+
{
|
|
204
|
+
if (!fontWeight || fontWeight.length == 0)
|
|
205
|
+
return @"normal";
|
|
206
|
+
|
|
207
|
+
if ([fontWeight integerValue] > 0)
|
|
208
|
+
return fontWeight;
|
|
209
|
+
|
|
210
|
+
static NSDictionary *weightMap = nil;
|
|
211
|
+
static dispatch_once_t onceToken;
|
|
212
|
+
dispatch_once(&onceToken, ^{ weightMap = @{@"bold" : @"700", @"semibold" : @"600", @"medium" : @"500"}; });
|
|
213
|
+
|
|
214
|
+
NSString *mapped = weightMap[fontWeight.lowercaseString];
|
|
215
|
+
return mapped ?: @"normal";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#pragma mark - Style Caching
|
|
219
|
+
|
|
220
|
+
static CachedStyles *cacheStyles(StyleConfig *styleConfig)
|
|
221
|
+
{
|
|
222
|
+
CachedStyles *cache = [[CachedStyles alloc] init];
|
|
223
|
+
|
|
224
|
+
cache.paragraphColor = colorToCSS([styleConfig paragraphColor]);
|
|
225
|
+
cache.strongColor = colorToCSS([styleConfig strongColor]);
|
|
226
|
+
cache.emphasisColor = colorToCSS([styleConfig emphasisColor]);
|
|
227
|
+
cache.linkColor = colorToCSS([styleConfig linkColor]);
|
|
228
|
+
cache.codeColor = colorToCSS([styleConfig codeColor]);
|
|
229
|
+
cache.codeBackgroundColor = colorToCSS([styleConfig codeBackgroundColor]);
|
|
230
|
+
cache.codeBlockColor = colorToCSS([styleConfig codeBlockColor]);
|
|
231
|
+
cache.codeBlockBackgroundColor = colorToCSS([styleConfig codeBlockBackgroundColor]);
|
|
232
|
+
cache.blockquoteColor = colorToCSS([styleConfig blockquoteColor]);
|
|
233
|
+
cache.blockquoteBackgroundColor = colorToCSS([styleConfig blockquoteBackgroundColor]);
|
|
234
|
+
cache.blockquoteBorderColor = colorToCSS([styleConfig blockquoteBorderColor]);
|
|
235
|
+
cache.listStyleColor = colorToCSS([styleConfig listStyleColor]);
|
|
236
|
+
cache.h1Color = colorToCSS([styleConfig h1Color]);
|
|
237
|
+
cache.h2Color = colorToCSS([styleConfig h2Color]);
|
|
238
|
+
cache.h3Color = colorToCSS([styleConfig h3Color]);
|
|
239
|
+
cache.h4Color = colorToCSS([styleConfig h4Color]);
|
|
240
|
+
cache.h5Color = colorToCSS([styleConfig h5Color]);
|
|
241
|
+
cache.h6Color = colorToCSS([styleConfig h6Color]);
|
|
242
|
+
|
|
243
|
+
cache.paragraphFontSize = [styleConfig paragraphFontSize];
|
|
244
|
+
cache.paragraphMarginBottom = [styleConfig paragraphMarginBottom];
|
|
245
|
+
cache.codeBlockFontSize = [styleConfig codeBlockFontSize];
|
|
246
|
+
cache.codeBlockPadding = [styleConfig codeBlockPadding];
|
|
247
|
+
cache.codeBlockBorderRadius = [styleConfig codeBlockBorderRadius];
|
|
248
|
+
cache.codeBlockMarginBottom = [styleConfig codeBlockMarginBottom];
|
|
249
|
+
cache.blockquoteFontSize = [styleConfig blockquoteFontSize];
|
|
250
|
+
cache.blockquoteBorderWidth = [styleConfig blockquoteBorderWidth];
|
|
251
|
+
cache.blockquoteMarginBottom = [styleConfig blockquoteMarginBottom];
|
|
252
|
+
cache.blockquoteGapWidth = [styleConfig blockquoteGapWidth];
|
|
253
|
+
cache.listStyleFontSize = [styleConfig listStyleFontSize];
|
|
254
|
+
cache.listStyleMarginBottom = [styleConfig listStyleMarginBottom];
|
|
255
|
+
cache.listStyleMarginLeft = [styleConfig listStyleMarginLeft];
|
|
256
|
+
cache.imageMarginBottom = [styleConfig imageMarginBottom];
|
|
257
|
+
cache.imageBorderRadius = [styleConfig imageBorderRadius];
|
|
258
|
+
cache.h1FontSize = [styleConfig h1FontSize];
|
|
259
|
+
cache.h2FontSize = [styleConfig h2FontSize];
|
|
260
|
+
cache.h3FontSize = [styleConfig h3FontSize];
|
|
261
|
+
cache.h4FontSize = [styleConfig h4FontSize];
|
|
262
|
+
cache.h5FontSize = [styleConfig h5FontSize];
|
|
263
|
+
cache.h6FontSize = [styleConfig h6FontSize];
|
|
264
|
+
cache.h1MarginBottom = [styleConfig h1MarginBottom];
|
|
265
|
+
cache.h2MarginBottom = [styleConfig h2MarginBottom];
|
|
266
|
+
cache.h3MarginBottom = [styleConfig h3MarginBottom];
|
|
267
|
+
cache.h4MarginBottom = [styleConfig h4MarginBottom];
|
|
268
|
+
cache.h5MarginBottom = [styleConfig h5MarginBottom];
|
|
269
|
+
cache.h6MarginBottom = [styleConfig h6MarginBottom];
|
|
270
|
+
cache.h1FontWeight = fontWeightToCSS([styleConfig h1FontWeight]);
|
|
271
|
+
cache.h2FontWeight = fontWeightToCSS([styleConfig h2FontWeight]);
|
|
272
|
+
cache.h3FontWeight = fontWeightToCSS([styleConfig h3FontWeight]);
|
|
273
|
+
cache.h4FontWeight = fontWeightToCSS([styleConfig h4FontWeight]);
|
|
274
|
+
cache.h5FontWeight = fontWeightToCSS([styleConfig h5FontWeight]);
|
|
275
|
+
cache.h6FontWeight = fontWeightToCSS([styleConfig h6FontWeight]);
|
|
276
|
+
cache.linkUnderline = [styleConfig linkUnderline];
|
|
277
|
+
cache.thematicBreakColor = colorToCSS([styleConfig thematicBreakColor]);
|
|
278
|
+
cache.thematicBreakHeight = [styleConfig thematicBreakHeight];
|
|
279
|
+
cache.thematicBreakMarginTop = [styleConfig thematicBreakMarginTop];
|
|
280
|
+
cache.thematicBreakMarginBottom = [styleConfig thematicBreakMarginBottom];
|
|
281
|
+
cache.strikethroughColor = colorToCSS([styleConfig strikethroughColor]);
|
|
282
|
+
cache.underlineColor = colorToCSS([styleConfig underlineColor]);
|
|
283
|
+
|
|
284
|
+
return cache;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#pragma mark - Font Helpers
|
|
288
|
+
|
|
289
|
+
static BOOL isMonospaceFont(UIFont *font)
|
|
290
|
+
{
|
|
291
|
+
if (!font)
|
|
292
|
+
return NO;
|
|
293
|
+
NSString *fontName = font.fontName.lowercaseString;
|
|
294
|
+
return [fontName containsString:@"menlo"] || [fontName containsString:@"courier"] ||
|
|
295
|
+
[fontName containsString:@"monaco"] || [fontName containsString:@"consolas"];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
static BOOL isCodeSpan(NSDictionary *attrs, BOOL isCodeBlock)
|
|
299
|
+
{
|
|
300
|
+
if (isCodeBlock)
|
|
301
|
+
return NO;
|
|
302
|
+
|
|
303
|
+
NSNumber *codeAttr = attrs[CodeAttributeName];
|
|
304
|
+
if ([codeAttr boolValue])
|
|
305
|
+
return YES;
|
|
306
|
+
|
|
307
|
+
return isMonospaceFont(attrs[NSFontAttributeName]);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#pragma mark - Paragraph Type Detection
|
|
311
|
+
|
|
312
|
+
static ParagraphType getParagraphType(NSDictionary *attrs)
|
|
313
|
+
{
|
|
314
|
+
NSNumber *isCodeBlock = attrs[CodeBlockAttributeName];
|
|
315
|
+
if ([isCodeBlock boolValue])
|
|
316
|
+
return ParagraphTypeCodeBlock;
|
|
317
|
+
|
|
318
|
+
NSString *markdownType = attrs[MarkdownTypeAttributeName];
|
|
319
|
+
if (markdownType) {
|
|
320
|
+
if ([markdownType hasPrefix:@"heading-"]) {
|
|
321
|
+
NSInteger level = [[markdownType substringFromIndex:8] integerValue];
|
|
322
|
+
if (level >= 1 && level <= 6) {
|
|
323
|
+
return ParagraphTypeHeading1 + (level - 1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if ([markdownType isEqualToString:@"code-block"])
|
|
327
|
+
return ParagraphTypeCodeBlock;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
NSNumber *blockquoteDepth = attrs[BlockquoteDepthAttributeName];
|
|
331
|
+
if (blockquoteDepth && [blockquoteDepth integerValue] >= 0)
|
|
332
|
+
return ParagraphTypeBlockquote;
|
|
333
|
+
|
|
334
|
+
NSNumber *listDepth = attrs[ListDepthAttribute];
|
|
335
|
+
if (listDepth && [listDepth integerValue] >= 0) {
|
|
336
|
+
NSNumber *listType = attrs[ListTypeAttribute];
|
|
337
|
+
if (listType && [listType integerValue] == ListTypeOrdered)
|
|
338
|
+
return ParagraphTypeListItemOrdered;
|
|
339
|
+
return ParagraphTypeListItemUnordered;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return ParagraphTypeNormal;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#pragma mark - Paragraph Collection
|
|
346
|
+
|
|
347
|
+
static NSData *collectParagraphsData(NSAttributedString *attributedString, NSUInteger *outCount)
|
|
348
|
+
{
|
|
349
|
+
NSString *string = attributedString.string;
|
|
350
|
+
NSMutableData *data = [NSMutableData dataWithCapacity:string.length / 20 * sizeof(ParagraphData)];
|
|
351
|
+
NSUInteger currentIndex = 0;
|
|
352
|
+
NSUInteger count = 0;
|
|
353
|
+
|
|
354
|
+
while (currentIndex < string.length) {
|
|
355
|
+
NSRange lineRange = [string lineRangeForRange:NSMakeRange(currentIndex, 0)];
|
|
356
|
+
|
|
357
|
+
ParagraphData para = {.range = lineRange, .type = ParagraphTypeNormal, .depth = 0, .listNumber = 1};
|
|
358
|
+
|
|
359
|
+
if (lineRange.location < attributedString.length) {
|
|
360
|
+
NSDictionary *attrs = [attributedString attributesAtIndex:lineRange.location effectiveRange:NULL];
|
|
361
|
+
para.type = getParagraphType(attrs);
|
|
362
|
+
|
|
363
|
+
NSNumber *listDepth = attrs[ListDepthAttribute];
|
|
364
|
+
NSNumber *blockquoteDepth = attrs[BlockquoteDepthAttributeName];
|
|
365
|
+
NSNumber *listNumber = attrs[ListItemNumberAttribute];
|
|
366
|
+
|
|
367
|
+
para.depth = listDepth ? [listDepth integerValue] : (blockquoteDepth ? [blockquoteDepth integerValue] : 0);
|
|
368
|
+
para.listNumber = listNumber ? [listNumber integerValue] : 1;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
[data appendBytes:¶ length:sizeof(ParagraphData)];
|
|
372
|
+
count++;
|
|
373
|
+
currentIndex = NSMaxRange(lineRange);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
*outCount = count;
|
|
377
|
+
return data;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
#pragma mark - Heading Helpers
|
|
381
|
+
|
|
382
|
+
static NSInteger headingLevel(ParagraphType type)
|
|
383
|
+
{
|
|
384
|
+
if (type >= ParagraphTypeHeading1 && type <= ParagraphTypeHeading6) {
|
|
385
|
+
return type - ParagraphTypeHeading1 + 1;
|
|
386
|
+
}
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
#pragma mark - Container Closing Helpers
|
|
391
|
+
|
|
392
|
+
static void closeBlockquotes(NSMutableString *html, GeneratorState *state)
|
|
393
|
+
{
|
|
394
|
+
while (state.currentBlockquoteDepth >= 0) {
|
|
395
|
+
[html appendString:@"</blockquote>"];
|
|
396
|
+
state.currentBlockquoteDepth--;
|
|
397
|
+
}
|
|
398
|
+
state.inBlockquote = NO;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
static void closeLists(NSMutableString *html, GeneratorState *state)
|
|
402
|
+
{
|
|
403
|
+
while (state.openListTypes.count > 0) {
|
|
404
|
+
NSString *closeTag = ([state.openListTypes.lastObject integerValue] == 1) ? @"</ol>" : @"</ul>";
|
|
405
|
+
[html appendString:closeTag];
|
|
406
|
+
[state.openListTypes removeLastObject];
|
|
407
|
+
}
|
|
408
|
+
state.currentListDepth = -1;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
static void closeCodeBlock(NSMutableString *html, GeneratorState *state)
|
|
412
|
+
{
|
|
413
|
+
if (state.inCodeBlock) {
|
|
414
|
+
[html appendString:@"</code></pre>"];
|
|
415
|
+
state.inCodeBlock = NO;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
#pragma mark - Inline Content Generation
|
|
420
|
+
|
|
421
|
+
static void generateInlineHTML(NSMutableString *html, NSAttributedString *attributedString, NSRange range,
|
|
422
|
+
CachedStyles *styles, BOOL isCodeBlock)
|
|
423
|
+
{
|
|
424
|
+
NSString *string = attributedString.string;
|
|
425
|
+
|
|
426
|
+
[attributedString
|
|
427
|
+
enumerateAttributesInRange:range
|
|
428
|
+
options:0
|
|
429
|
+
usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange attrRange, BOOL *stop) {
|
|
430
|
+
NSString *text = [string substringWithRange:attrRange];
|
|
431
|
+
|
|
432
|
+
if ([text isEqualToString:@"\n"])
|
|
433
|
+
return;
|
|
434
|
+
|
|
435
|
+
if ([text containsString:kObjectReplacementChar]) {
|
|
436
|
+
id attachment = attrs[NSAttachmentAttributeName];
|
|
437
|
+
if ([attachment isKindOfClass:[EnrichedMarkdownImageAttachment class]]) {
|
|
438
|
+
EnrichedMarkdownImageAttachment *img = (EnrichedMarkdownImageAttachment *)attachment;
|
|
439
|
+
if (img.imageURL) {
|
|
440
|
+
if (img.isInline) {
|
|
441
|
+
[html appendFormat:
|
|
442
|
+
@"<img src=\"%@\" style=\"height: 1.2em; width: auto; "
|
|
443
|
+
@"vertical-align: -0.2em;\">",
|
|
444
|
+
img.imageURL];
|
|
445
|
+
} else {
|
|
446
|
+
[html appendFormat:
|
|
447
|
+
@"</p><div style=\"margin-bottom: %.0fpx;\"><img src=\"%@\" "
|
|
448
|
+
@"style=\"max-width: 100%%; border-radius: %.0fpx;\"></div><p>",
|
|
449
|
+
styles.imageMarginBottom, img.imageURL, styles.imageBorderRadius];
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
} else if ([attachment isKindOfClass:[ThematicBreakAttachment class]]) {
|
|
453
|
+
[html appendFormat:
|
|
454
|
+
@"</p><hr style=\"border: none; border-top: %.0fpx solid %@; "
|
|
455
|
+
@"margin: %.0fpx 0 %.0fpx 0;\"><p>",
|
|
456
|
+
styles.thematicBreakHeight, styles.thematicBreakColor,
|
|
457
|
+
styles.thematicBreakMarginTop, styles.thematicBreakMarginBottom];
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
UIFont *font = attrs[NSFontAttributeName];
|
|
463
|
+
NSNumber *underline = attrs[NSUnderlineStyleAttributeName];
|
|
464
|
+
NSNumber *strikethrough = attrs[NSStrikethroughStyleAttributeName];
|
|
465
|
+
id linkAttr = attrs[NSLinkAttributeName];
|
|
466
|
+
BOOL isCode = isCodeSpan(attrs, isCodeBlock);
|
|
467
|
+
|
|
468
|
+
BOOL isBold = NO, isItalic = NO;
|
|
469
|
+
if (font) {
|
|
470
|
+
UIFontDescriptorSymbolicTraits traits = font.fontDescriptor.symbolicTraits;
|
|
471
|
+
isBold = (traits & UIFontDescriptorTraitBold) != 0;
|
|
472
|
+
isItalic = (traits & UIFontDescriptorTraitItalic) != 0;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (linkAttr) {
|
|
476
|
+
NSString *href =
|
|
477
|
+
[linkAttr isKindOfClass:[NSURL class]] ? [(NSURL *)linkAttr absoluteString] : linkAttr;
|
|
478
|
+
[html appendFormat:@"<a href=\"%@\" style=\"color: %@; text-decoration: %@;\">",
|
|
479
|
+
escapeHTML(href), styles.linkColor,
|
|
480
|
+
styles.linkUnderline ? @"underline" : @"none"];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (isCode) {
|
|
484
|
+
[html appendFormat:
|
|
485
|
+
@"<code style=\"background-color: %@; color: %@; "
|
|
486
|
+
@"padding: %.0fpx %.0fpx; border-radius: %.0fpx; "
|
|
487
|
+
@"font-size: 0.7em; font-family: Menlo, Monaco, Consolas, monospace;\">",
|
|
488
|
+
styles.codeBackgroundColor, styles.codeColor, kCodePadding, kCodePadding * 2,
|
|
489
|
+
kCodeBorderRadius];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (isBold) {
|
|
493
|
+
if (styles.strongColor && ![styles.strongColor isEqualToString:@"inherit"]) {
|
|
494
|
+
[html appendFormat:@"<strong style=\"color: %@;\">", styles.strongColor];
|
|
495
|
+
} else {
|
|
496
|
+
[html appendString:@"<strong>"];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (isItalic) {
|
|
501
|
+
if (styles.emphasisColor && ![styles.emphasisColor isEqualToString:@"inherit"]) {
|
|
502
|
+
[html appendFormat:@"<em style=\"color: %@;\">", styles.emphasisColor];
|
|
503
|
+
} else {
|
|
504
|
+
[html appendString:@"<em>"];
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if ([strikethrough integerValue] > 0) {
|
|
509
|
+
if (styles.strikethroughColor && ![styles.strikethroughColor isEqualToString:@"inherit"]) {
|
|
510
|
+
[html appendFormat:@"<s style=\"text-decoration-color: %@;\">", styles.strikethroughColor];
|
|
511
|
+
} else {
|
|
512
|
+
[html appendString:@"<s>"];
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if ([underline integerValue] > 0 && !linkAttr) {
|
|
516
|
+
if (styles.underlineColor && ![styles.underlineColor isEqualToString:@"inherit"]) {
|
|
517
|
+
[html appendFormat:@"<u style=\"text-decoration-color: %@;\">", styles.underlineColor];
|
|
518
|
+
} else {
|
|
519
|
+
[html appendString:@"<u>"];
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
[html appendString:escapeHTML(text)];
|
|
524
|
+
|
|
525
|
+
// Reverse order
|
|
526
|
+
if ([underline integerValue] > 0 && !linkAttr)
|
|
527
|
+
[html appendString:@"</u>"];
|
|
528
|
+
if ([strikethrough integerValue] > 0)
|
|
529
|
+
[html appendString:@"</s>"];
|
|
530
|
+
if (isItalic)
|
|
531
|
+
[html appendString:@"</em>"];
|
|
532
|
+
if (isBold)
|
|
533
|
+
[html appendString:@"</strong>"];
|
|
534
|
+
if (isCode)
|
|
535
|
+
[html appendString:@"</code>"];
|
|
536
|
+
if (linkAttr)
|
|
537
|
+
[html appendString:@"</a>"];
|
|
538
|
+
}];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
#pragma mark - Block Handlers
|
|
542
|
+
|
|
543
|
+
static void handleCodeBlock(NSMutableString *html, NSMutableString *inlineContent, CachedStyles *styles,
|
|
544
|
+
GeneratorState *state)
|
|
545
|
+
{
|
|
546
|
+
if (!state.inCodeBlock) {
|
|
547
|
+
state.inCodeBlock = YES;
|
|
548
|
+
[html appendFormat:
|
|
549
|
+
@"<pre style=\"background-color: %@; padding: %.0fpx; border-radius: %.0fpx; "
|
|
550
|
+
@"margin: 0 0 %.0fpx 0; overflow-x: auto;\"><code style=\"font-family: Menlo, Monaco, "
|
|
551
|
+
@"Consolas, monospace; font-size: %.0fpx; color: %@;\">",
|
|
552
|
+
styles.codeBlockBackgroundColor, styles.codeBlockPadding, styles.codeBlockBorderRadius,
|
|
553
|
+
styles.codeBlockMarginBottom, styles.codeBlockFontSize, styles.codeBlockColor];
|
|
554
|
+
} else if (state.previousWasCodeBlock) {
|
|
555
|
+
[html appendString:@"\n"];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
[html appendString:inlineContent];
|
|
559
|
+
state.previousWasCodeBlock = YES;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
static void handleBlockquote(NSMutableString *html, ParagraphData *para, NSMutableString *inlineContent,
|
|
563
|
+
CachedStyles *styles, GeneratorState *state)
|
|
564
|
+
{
|
|
565
|
+
NSInteger depth = para->depth;
|
|
566
|
+
|
|
567
|
+
// Reset if starting a new blockquote block
|
|
568
|
+
if (!state.previousWasBlockquote && state.inBlockquote) {
|
|
569
|
+
closeBlockquotes(html, state);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
while (state.currentBlockquoteDepth > depth) {
|
|
573
|
+
[html appendString:@"</blockquote>"];
|
|
574
|
+
state.currentBlockquoteDepth--;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
while (state.currentBlockquoteDepth < depth) {
|
|
578
|
+
state.currentBlockquoteDepth++;
|
|
579
|
+
state.inBlockquote = YES;
|
|
580
|
+
|
|
581
|
+
if (state.currentBlockquoteDepth == 0) {
|
|
582
|
+
[html appendFormat:
|
|
583
|
+
@"<blockquote style=\"background-color: %@; border-left: %.0fpx solid %@; "
|
|
584
|
+
@"padding: %.0fpx %.0fpx; margin: 0 0 %.0fpx 0; border-radius: 0 8px 8px 0;\">",
|
|
585
|
+
styles.blockquoteBackgroundColor, styles.blockquoteBorderWidth, styles.blockquoteBorderColor,
|
|
586
|
+
kBlockquoteVerticalPadding, styles.blockquoteGapWidth, styles.blockquoteMarginBottom];
|
|
587
|
+
} else {
|
|
588
|
+
[html appendFormat:
|
|
589
|
+
@"<blockquote style=\"border-left: %.0fpx solid %@; padding-left: %.0fpx; "
|
|
590
|
+
@"margin: %.0fpx 0 0 0;\">",
|
|
591
|
+
styles.blockquoteBorderWidth, styles.blockquoteBorderColor, styles.blockquoteGapWidth,
|
|
592
|
+
kNestedBlockquoteTopMargin];
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
[html appendFormat:@"<p style=\"margin: 0 0 %.0fpx 0; color: %@; font-size: %.0fpx;\">%@</p>",
|
|
597
|
+
kBlockquoteParagraphSpacing, styles.blockquoteColor, styles.blockquoteFontSize, inlineContent];
|
|
598
|
+
|
|
599
|
+
state.previousWasBlockquote = YES;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
static void handleListItem(NSMutableString *html, ParagraphData *para, NSMutableString *inlineContent,
|
|
603
|
+
CachedStyles *styles, GeneratorState *state)
|
|
604
|
+
{
|
|
605
|
+
NSInteger depth = para->depth;
|
|
606
|
+
BOOL isOrdered = (para->type == ParagraphTypeListItemOrdered);
|
|
607
|
+
NSInteger listTypeValue = isOrdered ? 1 : 0;
|
|
608
|
+
|
|
609
|
+
while (state.currentListDepth > depth) {
|
|
610
|
+
NSString *closeTag = ([state.openListTypes.lastObject integerValue] == 1) ? @"</ol>" : @"</ul>";
|
|
611
|
+
[html appendString:closeTag];
|
|
612
|
+
[state.openListTypes removeLastObject];
|
|
613
|
+
state.currentListDepth--;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// List type change at same depth (ul <-> ol)
|
|
617
|
+
if (state.currentListDepth == depth && state.openListTypes.count > 0) {
|
|
618
|
+
NSInteger currentType = [state.openListTypes.lastObject integerValue];
|
|
619
|
+
if (currentType != listTypeValue) {
|
|
620
|
+
NSString *closeTag = (currentType == 1) ? @"</ol>" : @"</ul>";
|
|
621
|
+
[html appendString:closeTag];
|
|
622
|
+
[state.openListTypes removeLastObject];
|
|
623
|
+
state.currentListDepth--;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
CGFloat indent = styles.listStyleMarginLeft > 0 ? styles.listStyleMarginLeft : kDefaultListIndent;
|
|
628
|
+
|
|
629
|
+
while (state.currentListDepth < depth) {
|
|
630
|
+
state.currentListDepth++;
|
|
631
|
+
if (isOrdered) {
|
|
632
|
+
[html appendFormat:@"<ol style=\"margin: 0; padding-left: %.0fpx;\">", indent];
|
|
633
|
+
} else {
|
|
634
|
+
[html appendFormat:@"<ul style=\"margin: 0; padding-left: %.0fpx; list-style-type: disc;\">", indent];
|
|
635
|
+
}
|
|
636
|
+
[state.openListTypes addObject:@(listTypeValue)];
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
[html appendFormat:@"<li style=\"margin-bottom: %.0fpx; color: %@; font-size: %.0fpx;\">%@</li>",
|
|
640
|
+
styles.listStyleMarginBottom, styles.listStyleColor, styles.listStyleFontSize, inlineContent];
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
static void handleHeading(NSMutableString *html, ParagraphData *para, NSMutableString *inlineContent,
|
|
644
|
+
CachedStyles *styles)
|
|
645
|
+
{
|
|
646
|
+
NSInteger level = headingLevel(para->type);
|
|
647
|
+
CGFloat fontSize, marginBottom;
|
|
648
|
+
NSString *fontWeight, *color;
|
|
649
|
+
|
|
650
|
+
switch (level) {
|
|
651
|
+
case 1:
|
|
652
|
+
fontSize = styles.h1FontSize;
|
|
653
|
+
fontWeight = styles.h1FontWeight;
|
|
654
|
+
color = styles.h1Color;
|
|
655
|
+
marginBottom = styles.h1MarginBottom;
|
|
656
|
+
break;
|
|
657
|
+
case 2:
|
|
658
|
+
fontSize = styles.h2FontSize;
|
|
659
|
+
fontWeight = styles.h2FontWeight;
|
|
660
|
+
color = styles.h2Color;
|
|
661
|
+
marginBottom = styles.h2MarginBottom;
|
|
662
|
+
break;
|
|
663
|
+
case 3:
|
|
664
|
+
fontSize = styles.h3FontSize;
|
|
665
|
+
fontWeight = styles.h3FontWeight;
|
|
666
|
+
color = styles.h3Color;
|
|
667
|
+
marginBottom = styles.h3MarginBottom;
|
|
668
|
+
break;
|
|
669
|
+
case 4:
|
|
670
|
+
fontSize = styles.h4FontSize;
|
|
671
|
+
fontWeight = styles.h4FontWeight;
|
|
672
|
+
color = styles.h4Color;
|
|
673
|
+
marginBottom = styles.h4MarginBottom;
|
|
674
|
+
break;
|
|
675
|
+
case 5:
|
|
676
|
+
fontSize = styles.h5FontSize;
|
|
677
|
+
fontWeight = styles.h5FontWeight;
|
|
678
|
+
color = styles.h5Color;
|
|
679
|
+
marginBottom = styles.h5MarginBottom;
|
|
680
|
+
break;
|
|
681
|
+
case 6:
|
|
682
|
+
fontSize = styles.h6FontSize;
|
|
683
|
+
fontWeight = styles.h6FontWeight;
|
|
684
|
+
color = styles.h6Color;
|
|
685
|
+
marginBottom = styles.h6MarginBottom;
|
|
686
|
+
break;
|
|
687
|
+
default:
|
|
688
|
+
fontSize = 16;
|
|
689
|
+
fontWeight = @"normal";
|
|
690
|
+
color = @"inherit";
|
|
691
|
+
marginBottom = 0;
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
[html appendFormat:@"<h%ld style=\"font-size: %.0fpx; font-weight: %@; color: %@; margin: 0 0 %.0fpx 0;\">%@</h%ld>",
|
|
696
|
+
(long)level, fontSize, fontWeight, color, marginBottom, inlineContent, (long)level];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
static void handleParagraph(NSMutableString *html, NSMutableString *inlineContent, CachedStyles *styles)
|
|
700
|
+
{
|
|
701
|
+
[html appendFormat:@"<p style=\"margin: 0 0 %.0fpx 0; color: %@; font-size: %.0fpx;\">%@</p>",
|
|
702
|
+
styles.paragraphMarginBottom, styles.paragraphColor, styles.paragraphFontSize, inlineContent];
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
#pragma mark - Main Generator
|
|
706
|
+
|
|
707
|
+
NSString *_Nullable generateHTML(NSAttributedString *attributedString, StyleConfig *styleConfig)
|
|
708
|
+
{
|
|
709
|
+
if (!attributedString || attributedString.length == 0)
|
|
710
|
+
return nil;
|
|
711
|
+
|
|
712
|
+
CachedStyles *styles = cacheStyles(styleConfig);
|
|
713
|
+
|
|
714
|
+
NSMutableString *html = [NSMutableString stringWithCapacity:attributedString.length * 2];
|
|
715
|
+
[html appendString:
|
|
716
|
+
@"<!DOCTYPE html><html><head><meta charset=\"UTF-8\"></head><body "
|
|
717
|
+
@"style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\">"];
|
|
718
|
+
|
|
719
|
+
NSUInteger paragraphCount = 0;
|
|
720
|
+
NSData *paragraphsData = collectParagraphsData(attributedString, ¶graphCount);
|
|
721
|
+
ParagraphData *paragraphs = (ParagraphData *)paragraphsData.bytes;
|
|
722
|
+
|
|
723
|
+
GeneratorState *state = [[GeneratorState alloc] init];
|
|
724
|
+
NSMutableString *inlineBuffer = [NSMutableString stringWithCapacity:256];
|
|
725
|
+
|
|
726
|
+
for (NSUInteger i = 0; i < paragraphCount; i++) {
|
|
727
|
+
ParagraphData *para = ¶graphs[i];
|
|
728
|
+
|
|
729
|
+
NSString *content = [[attributedString.string substringWithRange:para->range]
|
|
730
|
+
stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
|
731
|
+
|
|
732
|
+
if (content.length == 0) {
|
|
733
|
+
state.previousWasBlockquote = NO;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
[inlineBuffer setString:@""];
|
|
738
|
+
NSRange contentRange = para->range;
|
|
739
|
+
if (contentRange.length > 0 &&
|
|
740
|
+
[[attributedString.string substringWithRange:NSMakeRange(NSMaxRange(contentRange) - 1, 1)]
|
|
741
|
+
isEqualToString:@"\n"]) {
|
|
742
|
+
contentRange.length--;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
BOOL isCodeBlockPara = (para->type == ParagraphTypeCodeBlock);
|
|
746
|
+
generateInlineHTML(inlineBuffer, attributedString, contentRange, styles, isCodeBlockPara);
|
|
747
|
+
|
|
748
|
+
if (isCodeBlockPara) {
|
|
749
|
+
handleCodeBlock(html, inlineBuffer, styles, state);
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (state.inCodeBlock) {
|
|
754
|
+
closeCodeBlock(html, state);
|
|
755
|
+
}
|
|
756
|
+
state.previousWasCodeBlock = NO;
|
|
757
|
+
|
|
758
|
+
if (para->type == ParagraphTypeBlockquote) {
|
|
759
|
+
handleBlockquote(html, para, inlineBuffer, styles, state);
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (state.inBlockquote) {
|
|
764
|
+
closeBlockquotes(html, state);
|
|
765
|
+
}
|
|
766
|
+
state.previousWasBlockquote = NO;
|
|
767
|
+
|
|
768
|
+
if (para->type == ParagraphTypeListItemUnordered || para->type == ParagraphTypeListItemOrdered) {
|
|
769
|
+
handleListItem(html, para, inlineBuffer, styles, state);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (state.currentListDepth >= 0) {
|
|
774
|
+
closeLists(html, state);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
NSInteger hLevel = headingLevel(para->type);
|
|
778
|
+
if (hLevel > 0) {
|
|
779
|
+
handleHeading(html, para, inlineBuffer, styles);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
handleParagraph(html, inlineBuffer, styles);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
closeCodeBlock(html, state);
|
|
787
|
+
closeBlockquotes(html, state);
|
|
788
|
+
closeLists(html, state);
|
|
789
|
+
|
|
790
|
+
[html appendString:@"</body></html>"];
|
|
791
|
+
|
|
792
|
+
return html;
|
|
793
|
+
}
|