-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
1,176 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,17 @@ | ||
# ``EasyRichText`` | ||
|
||
A simple library & format for simple rich text. | ||
A set of tools and helpers for building your own rich text editor and encoding upon. | ||
|
||
## Overview | ||
|
||
This package is a collection of two libraries: | ||
|
||
- **EasyRichText**: Utilities and helpers for creating your own rich text format, converting between old `NSAttributedString`, new `AttributedString`, and your rich text format. | ||
- [**EasyRichTextUI**](./EasyRichTextUI): A SwiftUI view wrapping `UITextView` / `NSTextView` for editing rich text. A context object is provided for writing your own UI for common style operations. | ||
|
||
## Topics | ||
|
||
### Codable Models | ||
### Essentials | ||
|
||
- ``ERTTextSegment`` | ||
- <doc:Features> | ||
- ``ERTRichText`` | ||
|
||
### Styling | ||
|
||
- ``ERTTextSegment/Style`` | ||
- ``ERTTextSegment/Color`` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Features | ||
|
||
Distinct style component for a segment of text. | ||
|
||
## Overview | ||
|
||
In EasyRichText, rich texts are expressed as an array of text segments (*runs*) with different styles (*features*) attached to each of the segments. | ||
|
||
Features are the smallest unit of style in EasyRichText. They are used to represent a single style property, such as font, color, or underline. Each feature is represented by a `struct` conforming to `ERTFeature` protocol. | ||
|
||
## Topics | ||
|
||
### Basic Protocols | ||
|
||
- ``ERTFeature`` | ||
- ``ERTSingleKeyFeature`` | ||
- ``ERTSymbolicTraitFeature-fpvl`` | ||
|
||
### Font Features | ||
|
||
- ``ERTBoldFeature`` | ||
- ``ERTItalicFeature`` | ||
- ``ERTUnderlineFeature`` | ||
|
||
### Color Features | ||
|
||
- ``ERTForegroundColorFeature`` | ||
- ``ERTBackgroundColorFeature`` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// | ||
// ERTAttributedStringBridge.swift | ||
// RichTextTest | ||
// | ||
// Created by Shibo Lyu on 2024/2/2. | ||
// | ||
|
||
import Foundation | ||
import SwiftUI | ||
|
||
@_spi(Internal) public extension NSAttributedString.Key { | ||
static let ertSynthesizedItalic = Self("sb.lao.packages.easyrichtext.SynthesizedItalic") | ||
} | ||
|
||
public struct ERTAttributedStringBridge { | ||
public static var `default` = ERTAttributedStringBridge() | ||
|
||
public var bridgeColor: Bool | ||
public var bridgeInternalAttributes: Bool | ||
|
||
public init(bridgeColor: Bool = true, bridgeInternalAttributes: Bool = true) { | ||
self.bridgeColor = bridgeColor | ||
self.bridgeInternalAttributes = bridgeInternalAttributes | ||
} | ||
|
||
public func nsAttributedString(for attributedString: AttributedString) -> NSAttributedString { | ||
let mutableString = NSMutableAttributedString() | ||
|
||
for run in attributedString.runs { | ||
let mutableSubstring = NSMutableAttributedString(AttributedString(attributedString[run.range])) | ||
if bridgeInternalAttributes { | ||
if run.synthesizedItalic ?? false { | ||
mutableSubstring.addAttribute(.ertSynthesizedItalic, value: true, range: NSRange(location: 0, length: mutableSubstring.length)) | ||
} | ||
} | ||
mutableString.append(mutableSubstring) | ||
} | ||
|
||
return mutableString | ||
} | ||
|
||
public func attributedString(for nsAttributedString: NSAttributedString) -> AttributedString { | ||
var attributedString = AttributedString() | ||
|
||
nsAttributedString.enumerateAttributes(in: .init(location: 0, length: nsAttributedString.length)) { attributes, range, _ in | ||
let substring = nsAttributedString.attributedSubstring(from: range) | ||
var attributedSubstring = AttributedString(substring) | ||
|
||
if bridgeInternalAttributes { | ||
if let synthesizedItalicValue = attributes[.ertSynthesizedItalic], let synthesizedItalic = synthesizedItalicValue as? Bool, synthesizedItalic { | ||
attributedSubstring.synthesizedItalic = true | ||
} | ||
} | ||
attributedString += attributedSubstring | ||
} | ||
|
||
if bridgeColor { | ||
#if canImport(AppKit) | ||
for run in attributedString.runs { | ||
if let nsColor = run.attributes.appKit.foregroundColor { | ||
attributedString[run.range].swiftUI.foregroundColor = Color(nsColor: nsColor) | ||
} | ||
if let nsColor = run.attributes.appKit.backgroundColor { | ||
attributedString[run.range].swiftUI.backgroundColor = Color(nsColor: nsColor) | ||
} | ||
} | ||
#elseif canImport(UIKit) | ||
for run in attributedString.runs { | ||
if let uiColor = run.attributes.uiKit.foregroundColor { | ||
attributedString[run.range].swiftUI.foregroundColor = Color(uiColor: uiColor) | ||
} | ||
if let uiColor = run.attributes.uiKit.backgroundColor { | ||
attributedString[run.range].swiftUI.backgroundColor = Color(uiColor: uiColor) | ||
} | ||
} | ||
#endif | ||
} | ||
|
||
print("ERTAttributedStringBridge attributedString: NSAttributedString: \(nsAttributedString), AttributedString: \(attributedString)") | ||
|
||
return attributedString | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// | ||
// ERTAttributes.swift | ||
// RichTextTest | ||
// | ||
// Created by Shibo Lyu on 2024/2/2. | ||
// | ||
|
||
import Foundation | ||
|
||
@_spi(Internal) public struct ERTAttributes: AttributeScope { | ||
@_spi(Internal) let synthesizedItalic: ERTItalicSynthesizer.SynthesizedItalicKey | ||
} | ||
|
||
@_spi(Internal) public extension AttributeDynamicLookup { | ||
subscript<T: AttributedStringKey>(dynamicMember keyPath: KeyPath<ERTAttributes, T>) -> T { | ||
return self[T.self] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// | ||
// ERTFontUtils.swift | ||
// RichTextTest | ||
// | ||
// Created by Shibo Lyu on 2024/2/1. | ||
// | ||
|
||
import Foundation | ||
#if canImport(AppKit) | ||
import AppKit | ||
#elseif canImport(UIKit) | ||
import UIKit | ||
#endif | ||
|
||
public struct ERTFontUtils { | ||
public static let `default` = ERTFontUtils() | ||
|
||
#if canImport(AppKit) | ||
public func font(_ font: NSFont, with descriptor: NSFontDescriptor) -> NSFont { | ||
return .init(descriptor: descriptor, size: font.pointSize) ?? font | ||
} | ||
|
||
public func font(_ font: NSFont, modifyingDescriptor: (NSFontDescriptor) -> NSFontDescriptor) -> NSFont { | ||
return self.font(font, with: modifyingDescriptor(font.fontDescriptor)) | ||
} | ||
|
||
public func font(_ font: NSFont, with symbolicTraits: NSFontDescriptor.SymbolicTraits) -> NSFont { | ||
return self.font(font, with: font.fontDescriptor.withSymbolicTraits(symbolicTraits)) | ||
} | ||
|
||
public func font(_ font: NSFont, modifySymbolicTraits: (inout NSFontDescriptor.SymbolicTraits) -> Void) -> NSFont { | ||
var traits = font.fontDescriptor.symbolicTraits | ||
modifySymbolicTraits(&traits) | ||
return self.font(font, with: traits) | ||
} | ||
#elseif canImport(UIKit) | ||
public func font(_ font: UIFont, with descriptor: UIFontDescriptor) -> UIFont { | ||
return .init(descriptor: descriptor, size: font.pointSize) | ||
} | ||
|
||
public func font(_ font: UIFont, modifyingDescriptor: (UIFontDescriptor) -> UIFontDescriptor) -> UIFont { | ||
return self.font(font, with: modifyingDescriptor(font.fontDescriptor)) | ||
} | ||
|
||
public func font(_ font: UIFont, with symbolicTraits: UIFontDescriptor.SymbolicTraits) -> UIFont { | ||
return self.font(font, with: font.fontDescriptor.withSymbolicTraits(symbolicTraits) ?? font.fontDescriptor) | ||
} | ||
|
||
public func font(_ font: UIFont, modifySymbolicTraits: (inout UIFontDescriptor.SymbolicTraits) -> Void) -> UIFont { | ||
var traits = font.fontDescriptor.symbolicTraits | ||
modifySymbolicTraits(&traits) | ||
return self.font(font, with: traits) | ||
} | ||
#endif | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// | ||
// ERTItalicSynthesizer.swift | ||
// RichTextTest | ||
// | ||
// Created by Shibo Lyu on 2024/1/31. | ||
// | ||
|
||
import Foundation | ||
#if canImport(AppKit) | ||
import AppKit | ||
#elseif canImport(UIKit) | ||
import UIKit | ||
#endif | ||
|
||
public struct ERTItalicSynthesizer { | ||
public struct SynthesizedItalicKey: AttributedStringKey { | ||
public typealias Value = Bool | ||
public static let name = "sb.lao.packages.easyrichtext.SynthesizedItalic" | ||
} | ||
|
||
public static let `default` = ERTItalicSynthesizer() | ||
|
||
public var fontUtils: ERTFontUtils | ||
#if canImport(AppKit) | ||
public var matrix: AffineTransform | ||
|
||
public init(matrix: AffineTransform = .init(m11: 1, m12: 0, m21: 0.2, m22: 1, tX: 0, tY: 0), fontUtils: ERTFontUtils = .default) { | ||
self.matrix = matrix | ||
self.fontUtils = fontUtils | ||
} | ||
#elseif canImport(UIKit) | ||
public var matrix: CGAffineTransform | ||
|
||
public init(matrix: CGAffineTransform = .init(a: 1, b: 0, c: 0.2, d: 1, tx: 0, ty: 0), fontUtils: ERTFontUtils = .default) { | ||
self.matrix = matrix | ||
self.fontUtils = fontUtils | ||
} | ||
#endif | ||
|
||
#if canImport(AppKit) | ||
@_spi(Internal) public func synthesize(_ font: NSFont) -> NSFont { | ||
fontUtils.font(font) { descriptor in | ||
let newTraits = font.fontDescriptor.symbolicTraits.subtracting(.italic) | ||
let newDescriptor = font.fontDescriptor.withSymbolicTraits(newTraits).withMatrix(matrix) | ||
return newDescriptor | ||
} | ||
} | ||
#elseif canImport(UIKit) | ||
@_spi(Internal) public func synthesize(_ font: UIFont) -> UIFont { | ||
fontUtils.font(font) { descriptor in | ||
let newTraits = font.fontDescriptor.symbolicTraits.subtracting(.traitItalic) | ||
let newDescriptor = font.fontDescriptor.withSymbolicTraits(newTraits)?.withMatrix(matrix) | ||
return newDescriptor ?? descriptor | ||
} | ||
} | ||
#endif | ||
|
||
#if canImport(AppKit) | ||
func desynthesize(_ font: NSFont) -> NSFont { | ||
fontUtils.font(font) { descriptor in | ||
let newTraits = font.fontDescriptor.symbolicTraits.union(.italic) | ||
let newDescriptor = font.fontDescriptor.withSymbolicTraits(newTraits).withMatrix(.identity) | ||
return newDescriptor | ||
} | ||
} | ||
#elseif canImport(UIKit) | ||
func desynthesize(_ font: UIFont) -> UIFont { | ||
fontUtils.font(font) { descriptor in | ||
let newTraits = font.fontDescriptor.symbolicTraits.union(.traitItalic) | ||
let newDescriptor = font.fontDescriptor.withSymbolicTraits(newTraits)?.withMatrix(.identity) | ||
return newDescriptor ?? descriptor | ||
} | ||
} | ||
#endif | ||
|
||
public func synthesize(_ attributedString: AttributedString) -> AttributedString { | ||
var attributedString = attributedString | ||
|
||
for run in attributedString.runs { | ||
#if canImport(AppKit) | ||
guard let font = run.appKit.font, font.fontDescriptor.symbolicTraits.contains(.italic) else { continue } | ||
#elseif canImport(UIKit) | ||
guard let font = run.uiKit.font, font.fontDescriptor.symbolicTraits.contains(.traitItalic) else { continue } | ||
#endif | ||
|
||
attributedString[run.range].font = synthesize(font) | ||
attributedString[run.range].synthesizedItalic = true | ||
} | ||
|
||
return attributedString | ||
} | ||
|
||
public func desynthesize(_ attributedString: AttributedString) -> AttributedString { | ||
var attributedString = attributedString | ||
|
||
for run in attributedString.runs { | ||
guard attributedString[run.range].synthesizedItalic ?? false else { continue } | ||
|
||
#if canImport(AppKit) | ||
guard let font: NSFont = run.font else { continue } | ||
#elseif canImport(UIKit) | ||
guard let font: UIFont = run.font else { continue } | ||
#endif | ||
|
||
attributedString[run.range].font = desynthesize(font) | ||
attributedString[run.range].synthesizedItalic = false | ||
} | ||
|
||
return attributedString | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.