Add rich-text-editor.swift
This commit is contained in:
227
rich-text-editor.swift
Normal file
227
rich-text-editor.swift
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
//
|
||||||
|
// TextDataApiModelConverter.swift
|
||||||
|
// SttDictionary.iOS.NextGen
|
||||||
|
//
|
||||||
|
// Created by Peter Standret on 9/28/19.
|
||||||
|
// Copyright © 2019 Peter Standret. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import STT
|
||||||
|
|
||||||
|
fileprivate extension UIFont {
|
||||||
|
|
||||||
|
func withTraits(_ traits: UIFontDescriptor.SymbolicTraits) -> UIFont {
|
||||||
|
|
||||||
|
if let fontDescriptor = fontDescriptor.withSymbolicTraits(traits) {
|
||||||
|
return UIFont(descriptor: fontDescriptor, size: pointSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension String {
|
||||||
|
|
||||||
|
func substring(location: Int, lenght: Int) -> Substring {
|
||||||
|
let startIndex = self.index(self.startIndex, offsetBy: location)
|
||||||
|
let endIndex = self.index(self.startIndex, offsetBy: location + lenght)
|
||||||
|
return self[startIndex..<endIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TDConverterParameter {
|
||||||
|
let isOpenCloseDeletion: Bool
|
||||||
|
let rawOutput: Bool
|
||||||
|
|
||||||
|
static let defaultEditing = TDConverterParameter(isOpenCloseDeletion: false, rawOutput: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TextDataApiModelConverter: ConverterType {
|
||||||
|
|
||||||
|
enum Const {
|
||||||
|
|
||||||
|
static let defaultAttributes: [NSAttributedString.Key: Any] = [
|
||||||
|
.foregroundColor: Asset.NightColors.textColor.color,
|
||||||
|
.font: UIFont.systemFont(ofSize: 18, weight: .regular)
|
||||||
|
]
|
||||||
|
|
||||||
|
static let defaultCLDAttributes: [NSAttributedString.Key: Any] = [
|
||||||
|
.foregroundColor: Asset.NightColors.mainYellow.color,
|
||||||
|
.font: UIFont.systemFont(ofSize: 18, weight: .bold)
|
||||||
|
]
|
||||||
|
|
||||||
|
static let closeDeletionDelimeter = "([{]{2})(.*?)([}]{2}|$)"
|
||||||
|
static let closeHintDelimeter = "::"
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert(value: TextDataApiModel, parameter: Any?) -> NSAttributedString {
|
||||||
|
|
||||||
|
let parserData = parameter as! TDConverterParameter
|
||||||
|
|
||||||
|
let result = NSMutableAttributedString(
|
||||||
|
string: value.value,
|
||||||
|
attributes: Const.defaultAttributes
|
||||||
|
)
|
||||||
|
|
||||||
|
for data in value.metaData {
|
||||||
|
|
||||||
|
// range
|
||||||
|
let start = data.range.location
|
||||||
|
let length = data.range.length
|
||||||
|
let range = NSRange(location: start, length: length)
|
||||||
|
|
||||||
|
var attributes = Const.defaultAttributes
|
||||||
|
|
||||||
|
attributes[.foregroundColor] = getColor(data: data)
|
||||||
|
attributes[.font] = UIFont.systemFont(ofSize: 18, weight: getFontWeight(data: data))
|
||||||
|
|
||||||
|
if data.fontDecoration == .underline {
|
||||||
|
attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.fontStyle == .italic {
|
||||||
|
attributes[.font] = (attributes[.font] as! UIFont).withTraits(.traitItalic)
|
||||||
|
}
|
||||||
|
else if data.fontStyle == .monospaced {
|
||||||
|
attributes[.font] = UIFont.monospacedSystemFont(ofSize: 18, weight: getFontWeight(data: data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscript or superscript
|
||||||
|
if data.textType != .normal {
|
||||||
|
attributes[.font] = (attributes[.font] as! UIFont).withSize(10)
|
||||||
|
attributes[.baselineOffset] = data.textType == .subScript ? -7 : 7
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setAttributes(attributes, range: range)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parserData.rawOutput {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFormattedString(from: result, parserData: parserData)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getFormattedString(
|
||||||
|
from string: NSAttributedString,
|
||||||
|
parserData: TDConverterParameter
|
||||||
|
) -> NSAttributedString {
|
||||||
|
|
||||||
|
let closeDeletionRanges = string.string.ranges(for: Const.closeDeletionDelimeter)
|
||||||
|
guard !closeDeletionRanges.isEmpty else {
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = NSMutableAttributedString()
|
||||||
|
|
||||||
|
var previusEnd = 0
|
||||||
|
for range in closeDeletionRanges {
|
||||||
|
if previusEnd < range.location {
|
||||||
|
result.append(string.attributedSubstring(from:
|
||||||
|
NSRange(location: previusEnd, length: range.location - previusEnd))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let closeDeletion = string.attributedSubstring(
|
||||||
|
from: NSRange(location: range.location, length: range.length)
|
||||||
|
)
|
||||||
|
if let delimeterRange = closeDeletion.string.ranges(for: Const.closeHintDelimeter).first {
|
||||||
|
let hint = closeDeletion.attributedSubstring(from:
|
||||||
|
NSRange(location: 2, length: delimeterRange.location - 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
let value = closeDeletion.attributedSubstring(from:
|
||||||
|
NSRange(
|
||||||
|
location: delimeterRange.location + delimeterRange.length,
|
||||||
|
length: closeDeletion.length - delimeterRange.location - delimeterRange.length - 2
|
||||||
|
))
|
||||||
|
|
||||||
|
let valueToAppend = parserData.isOpenCloseDeletion ? value : hint
|
||||||
|
|
||||||
|
let startLocation = result.length
|
||||||
|
result.append(valueToAppend)
|
||||||
|
result.setAttributes(
|
||||||
|
Const.defaultCLDAttributes,
|
||||||
|
range: NSRange(location: startLocation, length: valueToAppend.length)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if parserData.isOpenCloseDeletion {
|
||||||
|
let valueToAppend = closeDeletion.attributedSubstring(from:
|
||||||
|
NSRange(location: 2, length: closeDeletion.length - 4)
|
||||||
|
)
|
||||||
|
|
||||||
|
let startLocation = result.length
|
||||||
|
result.append(valueToAppend)
|
||||||
|
result.setAttributes(
|
||||||
|
Const.defaultCLDAttributes,
|
||||||
|
range: NSRange(location: startLocation, length: valueToAppend.length)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.append(NSAttributedString(string: "[...]", attributes: Const.defaultCLDAttributes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previusEnd = range.location + range.length
|
||||||
|
}
|
||||||
|
|
||||||
|
if previusEnd < string.length {
|
||||||
|
result.append(string.attributedSubstring(from:
|
||||||
|
NSRange(location: previusEnd, length: string.length - previusEnd))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getColor(data: TextMetaDataApiModel) -> UIColor {
|
||||||
|
|
||||||
|
switch data.colorType {
|
||||||
|
case .default: return Asset.NightColors.textColor.color
|
||||||
|
case .highlihted: return Asset.NightColors.mainYellow.color
|
||||||
|
case .custom:
|
||||||
|
if let customColor = data.color {
|
||||||
|
return UIColor(
|
||||||
|
red: CGFloat(CFloat(customColor.red)) / 255.0,
|
||||||
|
green: CGFloat(CFloat(customColor.green)) / 255.0,
|
||||||
|
blue: CGFloat(CFloat(customColor.blue)) / 255.0,
|
||||||
|
alpha: CGFloat(CFloat(customColor.alpha)) / 255.0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return Asset.NightColors.textColor.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getFontWeight(data: TextMetaDataApiModel) -> UIFont.Weight {
|
||||||
|
switch data.fontWeight {
|
||||||
|
case .black: return .black
|
||||||
|
case .regular: return .regular
|
||||||
|
case .thin: return .thin
|
||||||
|
case .medium: return .medium
|
||||||
|
case .bold: return .bold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TextDataApiModelConverterCombiner: ConverterType {
|
||||||
|
|
||||||
|
let converter = TextDataApiModelConverter()
|
||||||
|
|
||||||
|
func convert(value: [TextDataApiModel], parameter: Any?) -> NSAttributedString {
|
||||||
|
|
||||||
|
let parserData = parameter as! TDConverterParameter
|
||||||
|
let result = NSMutableAttributedString()
|
||||||
|
|
||||||
|
for (index, text) in value.enumerated() {
|
||||||
|
result.append(converter.convert(value: text, parameter: parserData))
|
||||||
|
if index < value.count - 1 {
|
||||||
|
result.append(NSAttributedString(string: "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user