diff --git a/README.md b/README.md index a81ed50..4e9aa06 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ # Glyph Make life with TextKit better -This library is a light abstraction over TextKit. It provides some tools to make it easier and more efficient to work with. You don't even need to know whether your view using 1 or 2, and glyph will not downgrade TextKit 2 views. +Glyph adds features and abstractions for working with TextKit. It makes it easier and more efficient to work with. You don't even need to know whether your view using 1 or 2, and glyph will not downgrade TextKit 2 views. ## Installation @@ -25,13 +25,16 @@ dependencies: [ ### `NSTextContainer` Additions ```swift +func textSet(for rect: CGRect) -> IndexSet func enumerateLineFragments(for rect: CGRect, strictIntersection: Bool, block: (CGRect, NSRange, inout Bool) -> Void) +func enumerateLineFragments(in range: NSRange, block: (CGRect, NSRange, inout Bool) -> Void) ``` ### `NSTextLayoutManager` Additions ```swift func enumerateLineFragments(for rect: CGRect, options: NSTextLayoutFragment.EnumerationOptions = [], block: (CGRect, NSTextRange?, inout Bool) -> Void) +func enumerateLineFragments(in range: NSRange, options: NSTextLayoutFragment.EnumerationOptions = [], block: (CGRect, NSTextRange, inout Bool) -> Void) ``` ### `NSTextView`/`UITextView` Additions diff --git a/Sources/Glyph/NSTextContainer+Additions.swift b/Sources/Glyph/NSTextContainer+Additions.swift index 47faaf2..a293bfe 100644 --- a/Sources/Glyph/NSTextContainer+Additions.swift +++ b/Sources/Glyph/NSTextContainer+Additions.swift @@ -14,14 +14,6 @@ extension NSTextContainer { return layoutManager } - func textRange(for rect: CGRect) -> NSRange? { - guard let layoutManager = nonDowngradingLayoutManager else { return nil } - - let glyphRange = layoutManager.glyphRange(forBoundingRect: rect, in: self) - - return layoutManager.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil) - } - func tk1EnumerateLineFragments(for rect: CGRect, strictIntersection: Bool, block: (CGRect, NSRange, inout Bool) -> Void) { guard let layoutManager = nonDowngradingLayoutManager else { return } @@ -56,8 +48,6 @@ extension NSTextContainer { } textLayoutManager.enumerateLineFragments(for: rect) { fragmentRect, textRange, stop in - guard let textRange else { return } - let range = NSRange(textRange, provider: textContentManager) block(fragmentRect, range, &stop) @@ -68,6 +58,48 @@ extension NSTextContainer { tk1EnumerateLineFragments(for: rect, strictIntersection: strictIntersection, block: block) } + + public func textSet(for rect: CGRect) -> IndexSet { + var set = IndexSet() + + enumerateLineFragments(for: rect, strictIntersection: true) { _, range, _ in + set.insert(integersIn: range.lowerBound.. Void) { + if #available(macOS 12.0, iOS 15.0, *), let textLayoutManager { + guard let textContentManager = textLayoutManager.textContentManager else { + return + } + + textLayoutManager.enumerateLineFragments(in: range) { fragmentRect, textRange, stop in + let range = NSRange(textRange, provider: textContentManager) + + block(fragmentRect, range, &stop) + } + + return + } + + guard let glyphRange = layoutManager?.glyphRange(forCharacterRange: range, actualCharacterRange: nil) else { + return + } + + withoutActuallyEscaping(block) { escapingBlock in + layoutManager?.enumerateLineFragments(forGlyphRange: glyphRange) { (fragmentRect, _, _, fragmentRange, stop) in + var innerStop = false + + escapingBlock(fragmentRect, fragmentRange, &innerStop) + + stop.pointee = ObjCBool(innerStop) + } + } + } } #endif diff --git a/Sources/Glyph/NSTextLayoutManager+Additions.swift b/Sources/Glyph/NSTextLayoutManager+Additions.swift index d04da9d..e130def 100644 --- a/Sources/Glyph/NSTextLayoutManager+Additions.swift +++ b/Sources/Glyph/NSTextLayoutManager+Additions.swift @@ -7,7 +7,7 @@ import UIKit #if os(macOS) || os(iOS) || os(visionOS) @available(macOS 12.0, iOS 15.0, *) extension NSTextLayoutManager { - public func enumerateLineFragments(for rect: CGRect, options: NSTextLayoutFragment.EnumerationOptions = [], block: (CGRect, NSTextRange?, inout Bool) -> Void) { + public func enumerateLineFragments(for rect: CGRect, options: NSTextLayoutFragment.EnumerationOptions = [], block: (CGRect, NSTextRange, inout Bool) -> Void) { // if this is nil, our optmizations will have no effect let viewportRange = textViewportLayoutController.viewportRange ?? documentRange let viewportBounds = textViewportLayoutController.viewportBounds @@ -41,7 +41,7 @@ extension NSTextLayoutManager { enumerateTextLayoutFragments(from: location, options: options, using: { fragment in let frame = fragment.layoutFragmentFrame - let elementRange = fragment.textElement?.elementRange + let elementRange = fragment.rangeInElement var keepGoing: Bool @@ -61,5 +61,22 @@ extension NSTextLayoutManager { }) } + public func enumerateLineFragments(in range: NSRange, options: NSTextLayoutFragment.EnumerationOptions = [], block: (CGRect, NSTextRange, inout Bool) -> Void) { + let start = documentRange.location + guard let end = textContentManager?.location(start, offsetBy: range.length) else { + return + } + + enumerateTextLayoutFragments(from: documentRange.location, options: options) { fragment in + let frame = fragment.layoutFragmentFrame + let elementRange = fragment.rangeInElement + + var stop = false + + block(frame, elementRange, &stop) + + return stop == false && elementRange.endLocation.compare(end) == .orderedAscending + } + } } #endif diff --git a/Sources/Glyph/NSTextView+Additions.swift b/Sources/Glyph/NSTextView+Additions.swift index c35fb38..39f3463 100644 --- a/Sources/Glyph/NSTextView+Additions.swift +++ b/Sources/Glyph/NSTextView+Additions.swift @@ -22,19 +22,11 @@ extension TextView { /// Returns an IndexSet representing the content within `rect`. public func textSet(for rect: CGRect) -> IndexSet { - var set = IndexSet() - #if os(macOS) && !targetEnvironment(macCatalyst) - guard let textContainer else { - return set - } + return textContainer?.textSet(for: rect) ?? IndexSet() +#elseif os(iOS) || os(visionOS) + return textContainer.textSet(for: rect) #endif - - textContainer.enumerateLineFragments(for: rect, strictIntersection: true) { _, range, _ in - set.insert(integersIn: range.lowerBound..