Add rich-text-editor.swift

This commit is contained in:
2025-04-28 18:31:45 +03:00
parent 09b0191d44
commit 776bf5d9c6

227
rich-text-editor.swift Normal file
View 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
}
}