Skip to content
This repository has been archived by the owner on Apr 18, 2023. It is now read-only.

Commit

Permalink
Feat new html rendering (#224)
Browse files Browse the repository at this point in the history
* feat: bumping version to 3.2.2

* added SwiftRichString dependency; adding parameter to control HTML parsing mode

* implementing HTMLRenderer

* adding XMLDynamicAttributesResolver

* pointing to SwiftRichString branch

* passing HTMLStyleParams

* some refactoring and tidy up; allow the optimized HTML rendering to be passed on the creation of the RTV

* cleanup

* resolved package

* some tidy up

* fixing tests and lint warnings; adding the files to the workspace

* updating podspec to 3.3.0

* adding SwiftRichString as a dependency

* PR fix: nicer implementation of renderHTML

* removing extra blank space
  • Loading branch information
maicon-brauwers authored Mar 1, 2022
1 parent 2e51877 commit c34d4d8
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 14 deletions.
43 changes: 43 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"object": {
"pins": [
{
"package": "Down",
"repositoryURL": "https://github.com/johnxnguyen/Down",
"state": {
"branch": null,
"revision": "f34b166be1f1db4aa8f573067e901d72f2a6be57",
"version": "0.11.0"
}
},
{
"package": "iosMath",
"repositoryURL": "https://github.com/tophatmonocle/iosMath",
"state": {
"branch": null,
"revision": "8c65dab2295100c02d9d844f6dc5646ab9d57b42",
"version": "1.1.2"
}
},
{
"package": "SnapKit",
"repositoryURL": "https://github.com/SnapKit/SnapKit",
"state": {
"branch": null,
"revision": "d458564516e5676af9c70b4f4b2a9178294f1bc6",
"version": "5.0.1"
}
},
{
"package": "SwiftRichString",
"repositoryURL": "https://github.com/tophatmonocle/SwiftRichString",
"state": {
"branch": "master",
"revision": "e0b72d5c96968d7802856d2be096202c9798e8d1",
"version": null
}
}
]
},
"version": 1
}
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ let package = Package(
.package(name: "Down", url: "https://github.com/johnxnguyen/Down", .upToNextMajor(from: "0.11.0")),
.package(name: "SnapKit", url: "https://github.com/SnapKit/SnapKit", .upToNextMajor(from: "5.0.1")),
.package(name: "iosMath", url: "https://github.com/tophatmonocle/iosMath", .upToNextMajor(from: "1.1.1")),
.package(name: "SwiftRichString", url: "https://github.com/tophatmonocle/SwiftRichString", .branch("master"))
],
targets: [
.target(
Expand All @@ -20,6 +21,7 @@ let package = Package(
.product(name: "Down", package: "Down"),
.product(name: "SnapKit", package: "SnapKit"),
.product(name: "iosMath", package: "iosMath"),
.product(name: "SwiftRichString", package: "SwiftRichString"),
],
path: "Source",
exclude: ["Info.plist"]
Expand Down
1 change: 1 addition & 0 deletions Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ target 'RichTextView' do
pod 'iosMath', :git => 'https://github.com/tophatmonocle/iosMath.git'
pod 'SwiftLint'
pod 'SnapKit'
pod 'SwiftRichString', :git => 'https://github.com/tophatmonocle/SwiftRichString.git'
end

# This works around a unit test issue introduced in Xcode 10 / Cocoapod
Expand Down
12 changes: 10 additions & 2 deletions Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ PODS:
- Quick (4.0.0)
- SnapKit (5.0.0)
- SwiftLint (0.32.0)
- SwiftRichString (3.7.2)

DEPENDENCIES:
- Down
Expand All @@ -24,6 +25,7 @@ DEPENDENCIES:
- Quick
- SnapKit
- SwiftLint
- SwiftRichString (from `https://github.com/tophatmonocle/SwiftRichString.git`)

SPEC REPOS:
trunk:
Expand All @@ -38,11 +40,16 @@ SPEC REPOS:
EXTERNAL SOURCES:
iosMath:
:git: https://github.com/tophatmonocle/iosMath.git
SwiftRichString:
:git: https://github.com/tophatmonocle/SwiftRichString.git

CHECKOUT OPTIONS:
iosMath:
:commit: 7518e763c44a82202ec36e518c5ab4b412adbdae
:git: https://github.com/tophatmonocle/iosMath.git
SwiftRichString:
:commit: e0b72d5c96968d7802856d2be096202c9798e8d1
:git: https://github.com/tophatmonocle/SwiftRichString.git

SPEC CHECKSUMS:
Down: 8babe0cafe145de188d2efa76ea938a11a5fdc6f
Expand All @@ -53,7 +60,8 @@ SPEC CHECKSUMS:
Quick: 6473349e43b9271a8d43839d9ba1c442ed1b7ac4
SnapKit: fd22d10eb9aff484d79a8724eab922c1ddf89bcf
SwiftLint: 009a898ef2a1c851f45e1b59349bf6ff2ddc990d
SwiftRichString: d61b807db1f8834c26fee92b6dd3e1be2dc5af03

PODFILE CHECKSUM: fd6952a9b41a9a41bb7e6fb036901d60f1134d46
PODFILE CHECKSUM: ecc4996936f36f5e9a7ebeadfeac1dec9b7ffc17

COCOAPODS: 1.11.2
COCOAPODS: 1.10.1
3 changes: 2 additions & 1 deletion RichTextView.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = 'RichTextView'
s.version = '3.2.2'
s.version = '3.3.0'
s.summary = 'iOS Text View that Properly Displays LaTeX, HTML, Markdown, and YouTube/Vimeo Links.'
s.description = <<-DESC
This is an iOS UIView that Properly Displays LaTeX, HTML, Markdown, and YouTube/Vimeo Links. Simply feed in an input
Expand All @@ -29,5 +29,6 @@ string with the relevant rich text surrounded by the appropriate tags and it wil
s.dependency 'Down'
s.dependency 'iosMath'
s.dependency 'SnapKit'
s.dependency 'SwiftRichString'
s.swift_version = '5.0'
end
26 changes: 25 additions & 1 deletion RichTextView.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 51;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -33,6 +33,10 @@
78E2681121A4B16900C9B2DE /* RichTextViewUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E2680F21A4B16300C9B2DE /* RichTextViewUITests.swift */; };
78E2681421A4B75800C9B2DE /* RichTextView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 781488E92194E6B600DED7A1 /* RichTextView.framework */; };
E0D33D3F751E4903439BC745 /* Pods_RichTextViewUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82CE0BC0D9A50B53001050A4 /* Pods_RichTextViewUITests.framework */; };
F28C888827C9725D003990CC /* HTMLStyleBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C888427C9725D003990CC /* HTMLStyleBuilder.swift */; };
F28C888927C9725D003990CC /* DynamicAttributesResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C888527C9725D003990CC /* DynamicAttributesResolver.swift */; };
F28C888A27C9725D003990CC /* HTMLStyleParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C888627C9725D003990CC /* HTMLStyleParams.swift */; };
F28C888B27C9725D003990CC /* HTMLRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C888727C9725D003990CC /* HTMLRenderer.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -89,6 +93,10 @@
ADEC9B28C5BC453A6A45AD55 /* Pods-RichTextView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RichTextView.release.xcconfig"; path = "Target Support Files/Pods-RichTextView/Pods-RichTextView.release.xcconfig"; sourceTree = "<group>"; };
BD74F48D045A1BFAEE356BA5 /* Pods-RichTextViewUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RichTextViewUnitTests.debug.xcconfig"; path = "Target Support Files/Pods-RichTextViewUnitTests/Pods-RichTextViewUnitTests.debug.xcconfig"; sourceTree = "<group>"; };
DE6831B3A5825165B8A981E0 /* Pods-RichTextView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RichTextView.debug.xcconfig"; path = "Target Support Files/Pods-RichTextView/Pods-RichTextView.debug.xcconfig"; sourceTree = "<group>"; };
F28C888427C9725D003990CC /* HTMLStyleBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLStyleBuilder.swift; sourceTree = "<group>"; };
F28C888527C9725D003990CC /* DynamicAttributesResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicAttributesResolver.swift; sourceTree = "<group>"; };
F28C888627C9725D003990CC /* HTMLStyleParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLStyleParams.swift; sourceTree = "<group>"; };
F28C888727C9725D003990CC /* HTMLRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLRenderer.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -149,6 +157,7 @@
7886C3B521A3540F00C11817 /* Constants */,
78322F97223C39B400C3D599 /* Delegates */,
7886C3AD21A33AE800C11817 /* Extensions */,
F28C888327C9725D003990CC /* HTML Rendering */,
781488ED2194E6B600DED7A1 /* Info.plist */,
781488EC2194E6B600DED7A1 /* RichTextView.h */,
781488F42194E6D600DED7A1 /* RichTextView.swift */,
Expand Down Expand Up @@ -275,6 +284,17 @@
name = Frameworks;
sourceTree = "<group>";
};
F28C888327C9725D003990CC /* HTML Rendering */ = {
isa = PBXGroup;
children = (
F28C888427C9725D003990CC /* HTMLStyleBuilder.swift */,
F28C888527C9725D003990CC /* DynamicAttributesResolver.swift */,
F28C888627C9725D003990CC /* HTMLStyleParams.swift */,
F28C888727C9725D003990CC /* HTMLRenderer.swift */,
);
path = "HTML Rendering";
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -545,10 +565,14 @@
files = (
7886C3AF21A33AFD00C11817 /* StringExtension.swift in Sources */,
7886C3B721A3541D00C11817 /* RichTextViewConstants.swift in Sources */,
F28C888827C9725D003990CC /* HTMLStyleBuilder.swift in Sources */,
7823D04521BACCBC0041CD51 /* WKWebViewGenerator.swift in Sources */,
78D1DC3721A49486004CA0F1 /* NSMutableAttributedStringExtension.swift in Sources */,
F28C888A27C9725D003990CC /* HTMLStyleParams.swift in Sources */,
7823D04621BACCBC0041CD51 /* UITextViewGenerator.swift in Sources */,
F28C888927C9725D003990CC /* DynamicAttributesResolver.swift in Sources */,
781488F52194E6D600DED7A1 /* RichTextView.swift in Sources */,
F28C888B27C9725D003990CC /* HTMLRenderer.swift in Sources */,
7886C3AB21A319E000C11817 /* LatexParserProtocol.swift in Sources */,
78D1DC3521A47B23004CA0F1 /* RichDataType.swift in Sources */,
78148940219536DF00DED7A1 /* RichTextParser.swift in Sources */,
Expand Down
42 changes: 42 additions & 0 deletions Source/HTML Rendering/DynamicAttributesResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// DynamicAttributesResolver.swift
//
//
// Created by Maicon Brauwers on 2022-02-24.
//

import UIKit
import SwiftRichString

class DynamicAttributesResolver: XMLDynamicAttributesResolver {

func styleForUnknownXMLTag(_ tag: String, to attributedString: inout SwiftRichString.AttributedString, attributes: [String: String]?, fromStyle: StyleXML) {
}

func applyDynamicAttributes(to attributedString: inout SwiftRichString.AttributedString, xmlStyle: XMLDynamicStyle, fromStyle: StyleXML) {
let finalStyleToApply = Style()
xmlStyle.enumerateAttributes { key, value in
switch key {
case "color": // color support
finalStyleToApply.color = Color(hexString: value)
case "style":
// THIS IS ALL HORRIBLE AND SHOULD BE REFACTOR USING A STRING SCANNER OR REGEX!!!
if value.starts(with: "color: rgb(") {
let substr = value.suffix(from: "color: rgb(".endIndex).dropLast()
let rgbValues = substr.split(separator: ",")
if rgbValues.count == 3,
let red = Float(rgbValues[0].trimmingCharacters(in: .whitespacesAndNewlines)),
let green = Float(rgbValues[1].trimmingCharacters(in: .whitespacesAndNewlines)),
let blue = Float(rgbValues[2].trimmingCharacters(in: .whitespacesAndNewlines)) {
finalStyleToApply.color = Color(red: CGFloat(red)/255, green: CGFloat(green)/255, blue: CGFloat(blue)/255, alpha: 1.0)
}
}

default:
break
}
}

attributedString.add(style: finalStyleToApply)
}
}
33 changes: 33 additions & 0 deletions Source/HTML Rendering/HTMLRenderer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// HTMLRenderer.swift
//
//
// Created by Maicon Brauwers on 2022-02-22.
//

import UIKit
import SwiftRichString

/*
* Renders an HTML string into a NSAttributedString by using the SwiftRichString dependency
*/

class HTMLRenderer {

var cachedStyles: [HTMLStyleParams: StyleXML] = [:]
static let shared = HTMLRenderer()

func renderHTML(html: String, styleParams: HTMLStyleParams) -> NSAttributedString {
let style: StyleXML

if let cachedStyle = self.cachedStyles[styleParams] {
style = cachedStyle
} else {
style = HTMLStyleBuilder().buildStyles(styleParams: styleParams)
cachedStyles[styleParams] = style
}

let htmlReplacingBr = html.replacingOccurrences(of: "<br>", with: "\n")
return htmlReplacingBr.set(style: style)
}
}
84 changes: 84 additions & 0 deletions Source/HTML Rendering/HTMLStyleBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// HTMLStyleBuilder.swift
//
//
// Created by Maicon Brauwers on 2022-02-24.
//

import SwiftRichString

// This class is responsible to create the styles that are using to style the HTML tags

struct HTMLStyleBuilder {

// swiftlint:disable function_body_length
func buildStyles(styleParams: HTMLStyleParams) -> StyleGroup {

let baseStyle = Style {
$0.font = styleParams.baseFont
$0.minimumLineHeight = 28
}

let h1Style = Style {
$0.font = styleParams.h1Font
$0.traitVariants = .bold
}

let h2Style = Style {
$0.font = styleParams.h2Font
$0.traitVariants = .bold
}

let h3Style = Style {
$0.font = styleParams.h3Font
$0.traitVariants = .bold
}

let italicStyle = baseStyle.byAdding {
$0.traitVariants = .italic
}

let boldStyle = baseStyle.byAdding {
$0.traitVariants = .bold
}

let superStyle = Style {
$0.font = styleParams.baseFont.font(size: 8.0)
$0.baselineOffset = 20.0 / 3.5
}

let subStyle = baseStyle.byAdding {
$0.font = styleParams.baseFont.font(size: 8.0)
$0.baselineOffset = -20.0 / 3.5
}

let codeStyle = baseStyle.byAdding {
$0.backColor = Color(red: 249/255, green: 249/255, blue: 249/255, alpha: 1.0)
$0.color = Color(red: 0.69, green: 0.231, blue: 0, alpha: 1)
}

let groupStyle = StyleXML(
base: baseStyle,
[
"p": baseStyle,
"div": baseStyle,
"h1": h1Style,
"h2": h2Style,
"h3": h3Style,
"i": italicStyle,
"em": italicStyle,
"italic": italicStyle,
"bold": boldStyle,
"strong": boldStyle,
"b": boldStyle,
"sub": subStyle,
"sup": superStyle,
"span": baseStyle,
"code": codeStyle
]
)
groupStyle.xmlAttributesResolver = DynamicAttributesResolver()
return groupStyle
}
// swiftlint:enable function_body_length
}
22 changes: 22 additions & 0 deletions Source/HTML Rendering/HTMLStyleParams.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// HTMLStyleParams.swift
//
//
// Created by Maicon Brauwers on 2022-02-24.
//

import UIKit

public struct HTMLStyleParams: Hashable {
let baseFont: UIFont
let h1Font: UIFont
let h2Font: UIFont
let h3Font: UIFont

public init(baseFont: UIFont, h1Font: UIFont, h2Font: UIFont, h3Font: UIFont) {
self.baseFont = baseFont
self.h1Font = h1Font
self.h2Font = h2Font
self.h3Font = h3Font
}
}
Loading

0 comments on commit c34d4d8

Please sign in to comment.