LatexParserProtocol.swift 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. //
  2. // LatexParserProtocol.swift
  3. // RichTextView
  4. //
  5. // Created by Ahmed Elkady on 2018-11-19.
  6. // Copyright © 2018 Top Hat. All rights reserved.
  7. //
  8. import Down
  9. import iosMath
  10. public protocol LatexParserProtocol: AnyObject {
  11. func extractLatex(from input: String, textColor: UIColor, baselineOffset: CGFloat, fontSize: CGFloat, height: CGFloat?) -> NSAttributedString?
  12. }
  13. extension LatexParserProtocol {
  14. public func extractLatex(from input: String, textColor: UIColor, baselineOffset: CGFloat, fontSize: CGFloat, height: CGFloat?) -> NSAttributedString? {
  15. let latexInput = self.extractLatexStringInsideTags(from: input)
  16. var mathImage: UIImage?
  17. if Thread.isMainThread {
  18. mathImage = self.setupMathLabelAndGetImage(from: latexInput, textColor: textColor, fontSize: height ?? fontSize)
  19. } else {
  20. DispatchQueue.main.sync {
  21. mathImage = self.setupMathLabelAndGetImage(from: latexInput, textColor: textColor, fontSize: height ?? fontSize)
  22. }
  23. }
  24. guard let image = mathImage else {
  25. return nil
  26. }
  27. let imageOffset = self.getLatexOffset(fromInput: input, image: image, fontSize: fontSize)
  28. let textAttachment = NSTextAttachment()
  29. textAttachment.image = image
  30. textAttachment.bounds = CGRect(
  31. x: 0,
  32. y: baselineOffset - imageOffset,
  33. width: textAttachment.image?.size.width ?? 0,
  34. height: textAttachment.image?.size.height ?? 0
  35. )
  36. let latexString = NSMutableAttributedString(attachment: textAttachment)
  37. latexString.addAttribute(.baselineOffset, value: baselineOffset - imageOffset, range: NSRange(location: 0, length: latexString.length))
  38. return latexString
  39. }
  40. // MARK: - Helpers
  41. public func extractLatexStringInsideTags(from input: String) -> String {
  42. let mathTagName = RichTextParser.ParserConstants.mathTagName
  43. return input.getSubstring(inBetween: "[\(mathTagName)]", and: "[/\(mathTagName)]") ?? input
  44. }
  45. private func setupMathLabelAndGetImage(from input: String, textColor: UIColor, fontSize: CGFloat) -> UIImage? {
  46. let label = MTMathUILabel()
  47. label.textColor = textColor
  48. label.latex = input
  49. label.fontSize = fontSize
  50. var newFrame = label.frame
  51. newFrame.size = label.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
  52. label.frame = newFrame
  53. return self.getImage(from: label)
  54. }
  55. private func getImage(from label: MTMathUILabel) -> UIImage? {
  56. UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0.0)
  57. guard let context = UIGraphicsGetCurrentContext() else {
  58. return nil
  59. }
  60. let verticalFlip = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: label.frame.size.height)
  61. context.concatenate(verticalFlip)
  62. label.layer.render(in: context)
  63. guard let image = UIGraphicsGetImageFromCurrentImageContext(), let cgImage = image.cgImage else {
  64. return nil
  65. }
  66. UIGraphicsEndImageContext()
  67. return UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: image.imageOrientation)
  68. }
  69. private func getLatexOffset(fromInput input: String, image: UIImage, fontSize: CGFloat) -> CGFloat {
  70. let defaultSubScriptOffset = RichTextParser.ParserConstants.defaultSubScriptOffset
  71. let imageOffset = max((image.size.height - fontSize)/2, 0)
  72. let subscriptOffset: CGFloat = input.contains(RichTextParser.ParserConstants.latexSubscriptCharacter) ? defaultSubScriptOffset : 0
  73. return max(subscriptOffset, imageOffset)
  74. }
  75. }
  76. public class LatexParser: LatexParserProtocol {
  77. public init() {}
  78. }