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