Skip to content

Commit

Permalink
Support text alignment (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-divyesh-v authored Nov 25, 2024
1 parent 9e4cbdf commit 24576b6
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 22 deletions.
6 changes: 3 additions & 3 deletions Sources/RichEditorSwiftUI/Alignment/RichTextAlignment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public enum RichTextAlignment: String, CaseIterable, Codable, Equatable, Identif
case .left: self = .left
case .right: self = .right
case .center: self = .center
case .justified: self = .justified
case .justified: self = .justify
default: self = .left
}
}
Expand All @@ -36,7 +36,7 @@ public enum RichTextAlignment: String, CaseIterable, Codable, Equatable, Identif
case center

/// Justified text alignment.
case justified
case justify

/// Right text alignment.
case right
Expand Down Expand Up @@ -67,7 +67,7 @@ public extension RichTextAlignment {
case .left: .left
case .right: .right
case .center: .center
case .justified: .justified
case .justify: .justified
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ extension RichTextCoordinator {
syncContextWithTextView()
case .selectRange(let range):
setSelectedRange(to: range)
case .setAlignment(_):
//// textView.setRichTextAlignment(alignment)
return
case .setAlignment(let alignment):
textView.setRichTextAlignment(alignment)
case .setAttributedString(let string):
setAttributedString(to: string)
case .setColor(let color, let newValue):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// RichTextViewComponent+Alignment.swift
// RichEditorSwiftUI
//
// Created by Divyesh Vekariya on 25/11/24.
//

import Foundation

#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit) && !targetEnvironment(macCatalyst)
import AppKit
#endif

public extension RichTextViewComponent {

/// Get the text alignment.
var richTextAlignment: RichTextAlignment? {
guard let style = richTextParagraphStyle else { return nil }
return RichTextAlignment(style.alignment)
}

/// Set the text alignment.
func setRichTextAlignment(_ alignment: RichTextAlignment) {
if richTextAlignment == alignment { return }
let style = NSMutableParagraphStyle(
from: richTextParagraphStyle,
alignment: alignment
)
setRichTextParagraphStyle(style)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// RichTextViewComponent+Paragraph.swift
// RichEditorSwiftUI
//
// Created by Divyesh Vekariya on 25/11/24.
//

import Foundation

#if canImport(UIKit)
import UIKit
#endif

#if canImport(AppKit) && !targetEnvironment(macCatalyst)
import AppKit
#endif

public extension RichTextViewComponent {

/// Get the paragraph style.
var richTextParagraphStyle: NSMutableParagraphStyle? {
richTextAttribute(.paragraphStyle)
}

/// Set the paragraph style.
///
/// > Todo: The function currently can't handle multiple
/// selected paragraphs. If many paragraphs are selected,
/// it will only affect the first one.
func setRichTextParagraphStyle(_ style: NSParagraphStyle) {
let range = lineRange(for: selectedRange)
guard range.length > 0 else { return }
#if os(watchOS)
setRichTextAttribute(.paragraphStyle, to: style, at: range)
#else
textStorageWrapper?.addAttribute(.paragraphStyle, value: style, range: range)
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// RichTextViewComponent+Ranges.swift
// RichTextKit
//
// Created by Dominik Bucher
//

import Foundation

extension RichTextViewComponent {

var notFoundRange: NSRange {
.init(location: NSNotFound, length: 0)
}

/// Get the line range at a certain text location.
func lineRange(at location: Int) -> NSRange {
#if os(watchOS)
return notFoundRange
#else
guard
let manager = layoutManagerWrapper,
let storage = textStorageWrapper
else { return NSRange(location: NSNotFound, length: 0) }
let string = storage.string as NSString
let locationRange = NSRange(location: location, length: 0)
let lineRange = string.lineRange(for: locationRange)
return manager.characterRange(forGlyphRange: lineRange, actualGlyphRange: nil)
#endif
}

/// Get the line range for a certain text range.
func lineRange(for range: NSRange) -> NSRange {
#if os(watchOS)
return notFoundRange
#else
// Use the location-based logic if range is empty
if range.length == 0 {
return lineRange(at: range.location)
}

guard let manager = layoutManagerWrapper else {
return NSRange(location: NSNotFound, length: 0)
}

var lineRange = NSRange(location: NSNotFound, length: 0)
manager.enumerateLineFragments(
forGlyphRange: range
) { (_, _, _, glyphRange, stop) in
lineRange = glyphRange
stop.pointee = true
}

// Convert glyph range to character range
return manager.characterRange(forGlyphRange: lineRange, actualGlyphRange: nil)
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import UIKit
import AppKit
#endif

import SwiftUI

extension RichAttributes {
func toAttributes(font: FontRepresentable? = nil) -> RichTextAttributes {
var attributes: RichTextAttributes = [:]
Expand All @@ -24,6 +26,14 @@ extension RichAttributes {
)
}

if let size = size {
font = font.updateFontSize(size: CGFloat(size))
}

if let fontName = self.font {
font = font.updateFontName(with: fontName)
}

// Apply bold and italic styles
if let isBold = bold, isBold {
font = font.makeBold()
Expand All @@ -45,6 +55,19 @@ extension RichAttributes {
attributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
}

if let color {
attributes[.foregroundColor] = ColorRepresentable(Color(hex: color))
}

if let background {
attributes[.backgroundColor] = ColorRepresentable(Color(hex: background))
}

if let align {
let style = NSMutableParagraphStyle(from: nil, alignment: align)
attributes[.paragraphStyle] = style
}

// Handle indent and paragraph styles
// if let indentLevel = indent {
// let paragraphStyle = NSMutableParagraphStyle()
Expand Down
32 changes: 27 additions & 5 deletions Sources/RichEditorSwiftUI/Data/Models/RichAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public struct RichAttributes: Codable {
public let font: String?
public let color: String?
public let background: String?
public let align: RichTextAlignment?

public init(
// id: String = UUID().uuidString,
Expand All @@ -34,7 +35,8 @@ public struct RichAttributes: Codable {
size: Int? = nil,
font: String? = nil,
color: String? = nil,
background: String? = nil
background: String? = nil,
align: RichTextAlignment? = nil
) {
// self.id = id
self.bold = bold
Expand All @@ -48,6 +50,7 @@ public struct RichAttributes: Codable {
self.font = font
self.color = color
self.background = background
self.align = align
}

enum CodingKeys: String, CodingKey {
Expand All @@ -62,6 +65,7 @@ public struct RichAttributes: Codable {
case font = "font"
case color = "color"
case background = "background"
case align = "align"
}

public init(from decoder: Decoder) throws {
Expand All @@ -78,6 +82,7 @@ public struct RichAttributes: Codable {
self.font = try values.decodeIfPresent(String.self, forKey: .font)
self.color = try values.decodeIfPresent(String.self, forKey: .color)
self.background = try values.decodeIfPresent(String.self, forKey: .background)
self.align = try values.decodeIfPresent(RichTextAlignment.self, forKey: .align)
}
}

Expand All @@ -95,6 +100,7 @@ extension RichAttributes: Hashable {
hasher.combine(font)
hasher.combine(color)
hasher.combine(background)
hasher.combine(align)
}
}

Expand All @@ -114,6 +120,7 @@ extension RichAttributes: Equatable {
&& lhs.font == rhs.font
&& lhs.color == rhs.color
&& lhs.background == rhs.background
&& lhs.align == rhs.align
)
}
}
Expand All @@ -129,7 +136,8 @@ extension RichAttributes {
size: Int? = nil,
font: String? = nil,
color: String? = nil,
background: String? = nil
background: String? = nil,
align: RichTextAlignment? = nil
) -> RichAttributes {
return RichAttributes(
bold: (bold != nil ? bold! : self.bold),
Expand All @@ -142,7 +150,8 @@ extension RichAttributes {
size: (size != nil ? size! : self.size),
font: (font != nil ? font! : self.font),
color: (color != nil ? color! : self.color),
background: (background != nil ? background! : self.background)
background: (background != nil ? background! : self.background),
align: (align != nil ? align! : self.align)
)
}

Expand All @@ -163,7 +172,8 @@ extension RichAttributes {
size: (att.size != nil ? (byAdding ? att.size! : nil) : self.size),
font: (att.font != nil ? (byAdding ? att.font! : nil) : self.font),
color: (att.color != nil ? (byAdding ? att.color! : nil) : self.color),
background: (att.background != nil ? (byAdding ? att.background! : nil) : self.background)
background: (att.background != nil ? (byAdding ? att.background! : nil) : self.background),
align: (att.align != nil ? (byAdding ? att.align! : nil) : self.align)
)
}
}
Expand Down Expand Up @@ -201,6 +211,9 @@ extension RichAttributes {
if let background = background {
styles.append(.background(.init(hex: background)))
}
if let align = align {
styles.append(.align(align))
}
return styles
}

Expand Down Expand Up @@ -236,6 +249,9 @@ extension RichAttributes {
if let background = background {
styles.insert(.background(Color(hex: background)))
}
if let align = align {
styles.insert(.align(align))
}
return styles
}
}
Expand Down Expand Up @@ -275,6 +291,8 @@ extension RichAttributes {
return color == colorItem?.hexString
case .background(let color):
return background == color?.hexString
case .align(let alignment):
return align == alignment
}
}
}
Expand All @@ -296,6 +314,7 @@ internal func getRichAttributesFor(styles: [RichTextSpanStyle]) -> RichAttribute
var font: String? = nil
var color: String? = nil
var background: String? = nil
var align: RichTextAlignment? = nil

for style in styles {
switch style {
Expand Down Expand Up @@ -332,6 +351,8 @@ internal func getRichAttributesFor(styles: [RichTextSpanStyle]) -> RichAttribute
color = textColor?.hexString
case .background(let backgroundColor):
background = backgroundColor?.hexString
case .align(let alignment):
align = alignment
}
}
return RichAttributes(bold: bold,
Expand All @@ -344,6 +365,7 @@ internal func getRichAttributesFor(styles: [RichTextSpanStyle]) -> RichAttribute
size: size,
font: font,
color: color,
background: background
background: background,
align: align
)
}
13 changes: 8 additions & 5 deletions Sources/RichEditorSwiftUI/Format/RichTextFormat+Sidebar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,21 @@ public extension RichTextFormat {

Divider()

// SidebarSection {
// alignmentPicker(value: $context.textAlignment)
SidebarSection {
alignmentPicker(value: $context.textAlignment)
.onChangeBackPort(of: context.textAlignment) { newValue in
context.updateStyle(style: .align(newValue))
}
// HStack {
// lineSpacingPicker(for: context)
// }
// HStack {
// indentButtons(for: context, greedy: true)
// superscriptButtons(for: context, greedy: true)
// }
// }
//
// Divider()
}

Divider()

if hasColorPickers {
SidebarSection {
Expand Down
Loading

0 comments on commit 24576b6

Please sign in to comment.