threadsafe MathImage and MTMathImag concurrent testScripts.
This commit is contained in:
@@ -47,7 +47,7 @@ public enum MathFont: String, CaseIterable {
|
||||
BundleManager.manager.obtainRawMathTable(font: self)
|
||||
}
|
||||
|
||||
//Note: Below code are no longer supported as UIFont/NSFont are not threadsafe and not used in SwiftMath.
|
||||
//Note: Below code are no longer supported, unable to tell if UIFont/NSFont is threadsafe, not used in SwiftMath.
|
||||
// #if os(iOS)
|
||||
// public func uiFont(withSize size: CGFloat) -> UIFont? {
|
||||
// UIFont(name: fontName, size: size)
|
||||
|
||||
179
Tests/SwiftMathTests/MathImageTests.swift
Normal file
179
Tests/SwiftMathTests/MathImageTests.swift
Normal file
@@ -0,0 +1,179 @@
|
||||
//
|
||||
// MathImageTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Peter Tang on 18/9/2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import SwiftMath
|
||||
|
||||
final class MathImageTests: XCTestCase {
|
||||
func testMathImageScript() throws {
|
||||
let latex = Latex.samples.randomElement()!
|
||||
let mathfont = MathFont.allCases.randomElement()!
|
||||
let fontsize = CGFloat.random(in: 24 ... 36)
|
||||
let result = SwiftMathImageResult.useMathImage(latex: latex, font: mathfont, fontSize: fontsize)
|
||||
XCTAssertNil(result.error)
|
||||
XCTAssertNotNil(result.image)
|
||||
if result.error == nil, let image = result.image, let imageData = image.pngData() {
|
||||
safeImage(fileName: "test", pngData: imageData)
|
||||
let fileUrl = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
print("completed, check \(fileUrl.path) image-test.png =================")
|
||||
}
|
||||
}
|
||||
func safeImage(fileName: String, pngData: Data) {
|
||||
let imageFileURL = URL(fileURLWithPath: NSTemporaryDirectory().appending("image-\(fileName).png"))
|
||||
try? pngData.write(to: imageFileURL, options: [.atomicWrite])
|
||||
}
|
||||
private let executionQueue = DispatchQueue.main // DispatchQueue(label: "com.swiftmath.mathbundle", attributes: .concurrent)
|
||||
private let executionGroup = DispatchGroup()
|
||||
|
||||
let totalCases = 50
|
||||
var testCount = 0
|
||||
|
||||
func testConcurrentMathImageScript() throws {
|
||||
var latex: String { Latex.samples.randomElement()! }
|
||||
var mathfont: MathFont { MathFont.allCases.randomElement()! }
|
||||
var size: CGFloat { CGFloat.random(in: 20 ... 40) }
|
||||
for caseNumber in 0 ..< totalCases {
|
||||
// if caseNumber % 2 == 0 {
|
||||
// helperConcurrentMathImage(caseNumber, latex: latex, mathfont: mathfont, fontsize: size, in: executionGroup, on: executionQueue)
|
||||
// } else {
|
||||
// helperConcurrentMTMathImage(caseNumber, latex: latex, mathfont: mathfont, fontsize: size, in: executionGroup, on: executionQueue)
|
||||
// }
|
||||
helperConcurrentMTMathImage(caseNumber, latex: latex, mathfont: mathfont, fontsize: size, in: executionGroup, on: executionQueue)
|
||||
}
|
||||
executionGroup.notify(queue: .main) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
XCTAssertEqual(self.testCount, totalCases)
|
||||
let fileUrl = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
print("\(self.testCount) completed, check \(fileUrl.path) =================")
|
||||
}
|
||||
}
|
||||
func helperConcurrentMathImage(_ count: Int, latex: String, mathfont: MathFont, fontsize: CGFloat, in group: DispatchGroup, on queue: DispatchQueue) {
|
||||
let workitem = DispatchWorkItem { [weak self] in
|
||||
let result = SwiftMathImageResult.useMTMathImage(latex: latex, font: mathfont, fontSize: fontsize)
|
||||
XCTAssertNil(result.error)
|
||||
XCTAssertNotNil(result.image)
|
||||
if result.error == nil, let image = result.image, let imageData = image.pngData() {
|
||||
self?.safeImage(fileName: "test-\(count)", pngData: imageData)
|
||||
}
|
||||
}
|
||||
workitem.notify(queue: .main) { [weak self] in
|
||||
// print("\(Thread.isMainThread ? "main" : "global") completed .....")
|
||||
self?.testCount += 1
|
||||
}
|
||||
queue.async(group: group, execute: workitem)
|
||||
}
|
||||
func helperConcurrentMTMathImage(_ count: Int, latex: String, mathfont: MathFont, fontsize: CGFloat, in group: DispatchGroup, on queue: DispatchQueue) {
|
||||
let workitem = DispatchWorkItem { [weak self] in
|
||||
let result = SwiftMathImageResult.useMathImage(latex: latex, font: mathfont, fontSize: fontsize)
|
||||
XCTAssertNil(result.error)
|
||||
XCTAssertNotNil(result.image)
|
||||
if result.error == nil, let image = result.image, let imageData = image.pngData() {
|
||||
self?.safeImage(fileName: "test-\(count)", pngData: imageData)
|
||||
}
|
||||
}
|
||||
workitem.notify(queue: .main) { [weak self] in
|
||||
// print("\(Thread.isMainThread ? "main" : "global") completed .....")
|
||||
self?.testCount += 1
|
||||
}
|
||||
queue.async(group: group, execute: workitem)
|
||||
}
|
||||
}
|
||||
public struct SwiftMathImageResult {
|
||||
let error: NSError?
|
||||
let image: MTImage?
|
||||
}
|
||||
extension SwiftMathImageResult {
|
||||
public static func useMTMathImage(latex: String, font: MathFont, fontSize: CGFloat, textColor: MTColor = MTColor.black) -> SwiftMathImageResult {
|
||||
let alignment = MTTextAlignment.left
|
||||
let formatter = MTMathImage(latex: latex, fontSize: fontSize - 1.0,
|
||||
textColor: textColor,
|
||||
labelMode: .text, textAlignment: alignment)
|
||||
formatter.font = font.mtfont(size: fontSize)
|
||||
let (error, image) = formatter.asImage()
|
||||
return SwiftMathImageResult(error: error, image: image)
|
||||
}
|
||||
public static func useMathImage(latex: String, font: MathFont, fontSize: CGFloat, textColor: MTColor = MTColor.black) -> SwiftMathImageResult {
|
||||
let alignment = MTTextAlignment.left
|
||||
var formatter = MathImage(latex: latex, fontSize: fontSize - 1.0,
|
||||
textColor: textColor,
|
||||
labelMode: .text, textAlignment: alignment)
|
||||
formatter.font = font
|
||||
let (error, image) = formatter.asImage()
|
||||
return SwiftMathImageResult(error: error, image: image)
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
extension NSBitmapImageRep {
|
||||
var png: Data? { representation(using: .png, properties: [:]) }
|
||||
}
|
||||
extension Data {
|
||||
var bitmap: NSBitmapImageRep? { NSBitmapImageRep(data: self) }
|
||||
}
|
||||
extension NSImage {
|
||||
func pngData() -> Data? {
|
||||
tiffRepresentation?.bitmap?.png
|
||||
}
|
||||
}
|
||||
#endif
|
||||
enum Latex {
|
||||
static let samples: [String] = [
|
||||
#"(a_1 + a_2)^2 = a_1^2 + 2a_1a_2 + a_2^2"#,
|
||||
#"x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}"#,
|
||||
#"\sigma = \sqrt{\frac{1}{N}\sum_{i=1}^N (x_i - \mu)^2}"#,
|
||||
#"\neg(P\land Q) \iff (\neg P)\lor(\neg Q)"#,
|
||||
#"\cos(\theta + \varphi) = \cos(\theta)\cos(\varphi) - \sin(\theta)\sin(\varphi)"#,
|
||||
#"\lim_{x\to\infty}\left(1 + \frac{k}{x}\right)^x = e^k"#,
|
||||
#"f(x) = \int\limits_{-\infty}^\infty\hat f(\xi)\,e^{2 \pi i \xi x}\,\mathrm{d}\xi"#,
|
||||
#"{n \brace k} = \frac{1}{k!}\sum_{j=0}^k (-1)^{k-j}\binom{k}{j}(k-j)^n"#,
|
||||
#"\int_{-\infty}^{\infty} \! e^{-x^2} dx = \sqrt{\pi}"#,
|
||||
#"\frac{1}{n}\sum_{i=1}^{n}x_i \geq \sqrt[n]{\prod_{i=1}^{n}x_i}"#,
|
||||
#"\left(\sum_{k=1}^n a_k b_k \right)^2 \le \left(\sum_{k=1}^n a_k^2\right)\left(\sum_{k=1}^n b_k^2\right)"#,
|
||||
#"\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)"#,
|
||||
#"i\hbar\frac{\partial}{\partial t}\mathbf\Psi(\mathbf{x},t) = -\frac{\hbar}{2m}\nabla^2\mathbf\Psi(\mathbf{x},t) + V(\mathbf{x})\mathbf\Psi(\mathbf{x},t)"#,
|
||||
#"""
|
||||
\begin{gather}
|
||||
\dot{x} = \sigma(y-x) \\
|
||||
\dot{y} = \rho x - y - xz \\
|
||||
\dot{z} = -\beta z + xy"
|
||||
\end{gather}
|
||||
"""#,
|
||||
#"""
|
||||
\vec \bf V_1 \times \vec \bf V_2 = \begin{vmatrix}
|
||||
\hat \imath &\hat \jmath &\hat k \\
|
||||
\frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\
|
||||
\frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0
|
||||
\end{vmatrix}
|
||||
"""#,
|
||||
#"""
|
||||
\begin{eqalign}
|
||||
\nabla \cdot \vec{\bf E} & = \frac {\rho} {\varepsilon_0} \\
|
||||
\nabla \cdot \vec{\bf B} & = 0 \\
|
||||
\nabla \times \vec{\bf E} &= - \frac{\partial\vec{\bf B}}{\partial t} \\
|
||||
\nabla \times \vec{\bf B} & = \mu_0\vec{\bf J} + \mu_0\varepsilon_0 \frac{\partial\vec{\bf E}}{\partial t}
|
||||
\end{eqalign}
|
||||
"""#,
|
||||
#"\log_b(x) = \frac{\log_a(x)}{\log_a(b)}"#,
|
||||
#"""
|
||||
\begin{pmatrix}
|
||||
a & b\\ c & d
|
||||
\end{pmatrix}
|
||||
\begin{pmatrix}
|
||||
\alpha & \beta \\ \gamma & \delta
|
||||
\end{pmatrix} =
|
||||
\begin{pmatrix}
|
||||
a\alpha + b\gamma & a\beta + b \delta \\
|
||||
c\alpha + d\gamma & c\beta + d \delta
|
||||
\end{pmatrix}
|
||||
"""#,
|
||||
#"""
|
||||
\frak Q(\lambda,\hat{\lambda}) =
|
||||
-\frac{1}{2} \mathbb P(O \mid \lambda ) \sum_s \sum_m \sum_t \gamma_m^{(s)} (t) +\\
|
||||
\quad \left( \log(2 \pi ) + \log \left| \cal C_m^{(s)} \right| +
|
||||
\left( o_t - \hat{\mu}_m^{(s)} \right) ^T \cal C_m^{(s)-1} \right)
|
||||
"""#
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user