Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 25 additions & 21 deletions KatexMathView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,42 @@
import UIKit
import WebKit

class KatexMathView: WKWebView {

public class KatexMathView: WKWebView {
public var onLoaded: (CGFloat)-> Void = { _ in }

func loadLatex(_ content: String ) {

#if SPM_PACKAGE
guard let path = Bundle.module.url(forResource: "katex/index", withExtension: "html")?.path else {
fatalError()
}
#else
guard let path = Bundle.main.path(forResource: "katex/index", ofType: "html") else {
fatalError()
}
#endif
self.configuration.preferences.javaScriptEnabled = true
self.scrollView.isScrollEnabled = false
self.scrollView.bounces = false
self.navigationDelegate = self

self.isOpaque = false
self.backgroundColor = UIColor.clear
self.scrollView.backgroundColor = UIColor.clear

let htmlContent = getHtml(content, path)

self.loadHTMLString(htmlContent, baseURL: URL(fileURLWithPath: path))
}

func getHtml(_ htmlContent: String, _ path: String) -> String {

var htmlString = try! String(contentsOfFile: path, encoding: .utf8)

var content = htmlContent
let delimitter = "$"
let startTexTag = "<span class=\"tex\">"
let endTexTag = "</span>"

var first = true

while content.contains(delimitter) {
let tag: String = first ? startTexTag : endTexTag
if let range = content.range(of: delimitter) {
Expand All @@ -50,24 +54,24 @@ class KatexMathView: WKWebView {
htmlString = htmlString.replacingOccurrences(of: "$LATEX$", with: content)
return htmlString
}



}

extension KatexMathView : WKNavigationDelegate {

public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
self.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
self.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (height, error) in
self.frame.size.height = height as! CGFloat
self.layoutIfNeeded()
self.onLoaded(self.frame.size.height)
})
}

})
}

}

}
78 changes: 78 additions & 0 deletions KatexView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import SwiftUI
import WebKit

public struct KatexView: View {
private let latex: String

@State
private var contentHeight: CGFloat = 0

@State
private var isLoaded = false

public init(latex: String) {
self.latex = latex
}

public static func resetHeightCache() {
KatexMathViewRepresentable.heightCache = [Int: CGFloat]()
}

public var body: some View {
ZStack {
KatexMathViewRepresentable(latex: latex, contentHeight: $contentHeight, isLoaded: $isLoaded)
.frame(height: contentHeight)
if !isLoaded {
ProgressView()
}
}.frame(maxWidth: .infinity)
}
}

struct KatexMathViewRepresentable: UIViewRepresentable {
// Caching heights prevents jumpy UI since the height calculation is
// delayed only the first time the latex block is rendered.
static var heightCache = [Int: CGFloat]()

private let latex: String

private let uiView = KatexMathView()

@Binding var contentHeight: CGFloat
@Binding var isLoaded: Bool

init(latex: String, contentHeight: Binding<CGFloat>, isLoaded: Binding<Bool>) {
self.latex = latex
self._contentHeight = contentHeight
self._isLoaded = isLoaded
}

func katexMathView(_ katexMathView: (KatexMathView) -> Void) -> KatexMathViewRepresentable {
katexMathView(uiView)
return self
}

func makeUIView(context: Context) -> KatexMathView {
uiView.onLoaded = { height in
isLoaded = true
contentHeight = height
var hasher = Hasher()
hasher.combine(latex)
Self.heightCache[hasher.finalize()] = height
}
return uiView
}

public func updateUIView(_ uiView: KatexMathView, context: Context) {
var hasher = Hasher()
hasher.combine(latex)
let hash = hasher.finalize()

if let height = Self.heightCache[hash] {
DispatchQueue.main.async {
contentHeight = height
}
}
uiView.loadLatex(latex)
}
}
33 changes: 33 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
name: "KatexMathView",
platforms: [.iOS(.v14), .macOS(.v11)],
products: [
.library(
name: "KatexMathView",
targets: ["KatexMathView"])
],
targets: [
.target(
name: "KatexMathView",
path: ".",
exclude: [
"Example",
"README.md",
],
sources: [
"KatexMathView.swift",
"KatexView.swift",
],
resources: [
.copy("katex")
],
swiftSettings: [
.define("SPM_PACKAGE")
]
)
]
)
10 changes: 9 additions & 1 deletion katex/katex.css
Original file line number Diff line number Diff line change
Expand Up @@ -1022,5 +1022,13 @@
img {
max-width:100% !important;
width:100% !important;

}

* {
-webkit-touch-callout: none;
-webkit-user-select: none;
}

:root {
color-scheme: light dark;
}
11 changes: 10 additions & 1 deletion katex/katex.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.