From c70945425e4c430684421dfb3d14afac9cdc062f Mon Sep 17 00:00:00 2001 From: Angelika Serwa Date: Wed, 1 Jul 2026 14:48:54 +0200 Subject: [PATCH] feat(ios): add line break rendering Render hard line breaks as U+2028 inside paragraphs so TextKit preserves intra-paragraph breaks without starting new blocks. Co-authored-by: Cursor --- .../Rendering/RendererFactory.swift | 2 ++ .../Rendering/Renderers/LineBreakRenderer.swift | 13 +++++++++++++ .../EnrichedMarkdownTests/RendererTests.swift | 15 +++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 packages/ios-enriched-markdown/Sources/EnrichedMarkdown/Rendering/Renderers/LineBreakRenderer.swift diff --git a/packages/ios-enriched-markdown/Sources/EnrichedMarkdown/Rendering/RendererFactory.swift b/packages/ios-enriched-markdown/Sources/EnrichedMarkdown/Rendering/RendererFactory.swift index d17cbd612..ed424c4da 100644 --- a/packages/ios-enriched-markdown/Sources/EnrichedMarkdown/Rendering/RendererFactory.swift +++ b/packages/ios-enriched-markdown/Sources/EnrichedMarkdown/Rendering/RendererFactory.swift @@ -43,6 +43,8 @@ final class RendererFactory { return HeadingRenderer(factory: self, config: config) case .link: return LinkRenderer(factory: self, config: config) + case .lineBreak: + return LineBreakRenderer() default: return childrenOnlyRenderer } diff --git a/packages/ios-enriched-markdown/Sources/EnrichedMarkdown/Rendering/Renderers/LineBreakRenderer.swift b/packages/ios-enriched-markdown/Sources/EnrichedMarkdown/Rendering/Renderers/LineBreakRenderer.swift new file mode 100644 index 000000000..1a7d53244 --- /dev/null +++ b/packages/ios-enriched-markdown/Sources/EnrichedMarkdown/Rendering/Renderers/LineBreakRenderer.swift @@ -0,0 +1,13 @@ +import UIKit + +final class LineBreakRenderer: NodeRenderer { + private static let lineSeparator = "\u{2028}" + + func render(node: MarkdownASTNode, into output: NSMutableAttributedString, context: RenderContext) { + let lineBreak = NSAttributedString( + string: Self.lineSeparator, + attributes: context.getTextAttributes() + ) + output.append(lineBreak) + } +} diff --git a/packages/ios-enriched-markdown/Tests/EnrichedMarkdownTests/RendererTests.swift b/packages/ios-enriched-markdown/Tests/EnrichedMarkdownTests/RendererTests.swift index 8812eeb2e..8f0ec4c93 100644 --- a/packages/ios-enriched-markdown/Tests/EnrichedMarkdownTests/RendererTests.swift +++ b/packages/ios-enriched-markdown/Tests/EnrichedMarkdownTests/RendererTests.swift @@ -300,6 +300,21 @@ final class RendererTests: XCTestCase { A paragraph with a [link](https://example.com) and **bold** nearby. """ + + + func testHardLineBreakUsesLineSeparator() { + let result = MarkdownRenderer.render("Line one \nLine two", config: config) + XCTAssertTrue(result.string.contains("Line one")) + XCTAssertTrue(result.string.contains("Line two")) + XCTAssertTrue(result.string.contains("\u{2028}")) + + let separatorRange = (result.string as NSString).range(of: "\u{2028}") + XCTAssertNotEqual(separatorRange.location, NSNotFound) + + var effectiveRange = NSRange() + let attributes = result.attributes(at: separatorRange.location, effectiveRange: &effectiveRange) + XCTAssertNotNil(attributes[.font] as? UIFont) + } } private extension String {