88import AppKit
99import CoreText
1010
11+ /// The `Typesetter` is responsible for producing text fragments from a document range. It transforms a text line
12+ /// and attachments into a sequence of `LineFragment`s, which reflect the visual structure of the text line.
13+ ///
14+ /// This class has one primary method: ``typeset(_:documentRange:displayData:markedRanges:attachments:)``, which
15+ /// performs the typesetting algorithm and breaks content into runs using attachments.
16+ ///
17+ /// To retrieve the
1118final public class Typesetter {
1219 struct ContentRun {
1320 let range : NSRange
@@ -19,7 +26,6 @@ final public class Typesetter {
1926 }
2027 }
2128
22- public var string : NSAttributedString = NSAttributedString ( string: " " )
2329 public var documentRange : NSRange ?
2430 public var lineFragments = TextLineStorage < LineFragment > ( )
2531
@@ -34,42 +40,48 @@ final public class Typesetter {
3440 markedRanges: MarkedRanges ? ,
3541 attachments: [ AnyTextAttachment ] = [ ]
3642 ) {
37- makeString ( string: string, markedRanges: markedRanges)
43+ let string = makeString ( string: string, markedRanges: markedRanges)
3844 lineFragments. removeAll ( )
3945
4046 // Fast path
4147 if string. length == 0 {
42- typesetEmptyLine ( displayData: displayData)
48+ typesetEmptyLine ( displayData: displayData, string : string )
4349 return
4450 }
4551 let ( lines, maxHeight) = typesetLineFragments (
52+ string: string,
4653 documentRange: documentRange,
4754 displayData: displayData,
4855 attachments: attachments
4956 )
5057 lineFragments. build ( from: lines, estimatedLineHeight: maxHeight)
5158 }
5259
53- private func makeString( string: NSAttributedString , markedRanges: MarkedRanges ? ) {
60+ private func makeString( string: NSAttributedString , markedRanges: MarkedRanges ? ) -> NSAttributedString {
5461 if let markedRanges {
5562 let mutableString = NSMutableAttributedString ( attributedString: string)
5663 for markedRange in markedRanges. ranges {
5764 mutableString. addAttributes ( markedRanges. attributes, range: markedRange)
5865 }
59- self . string = mutableString
60- } else {
61- self . string = string
66+ return mutableString
6267 }
68+
69+ return string
6370 }
6471
6572 // MARK: - Create Content Lines
6673
6774 /// Breaks up the string into a series of 'runs' making up the visual content of this text line.
6875 /// - Parameters:
76+ /// - string: The string reference to use.
6977 /// - documentRange: The range in the string reference.
7078 /// - attachments: Any text attachments overlapping the string reference.
7179 /// - Returns: A series of content runs making up this line.
72- func createContentRuns( documentRange: NSRange , attachments: [ AnyTextAttachment ] ) -> [ ContentRun ] {
80+ func createContentRuns(
81+ string: NSAttributedString ,
82+ documentRange: NSRange ,
83+ attachments: [ AnyTextAttachment ]
84+ ) -> [ ContentRun ] {
7385 var attachments = attachments
7486 var currentPosition = 0
7587 let maxPosition = documentRange. length
@@ -116,11 +128,12 @@ final public class Typesetter {
116128 // MARK: - Typeset Content Runs
117129
118130 func typesetLineFragments(
131+ string: NSAttributedString ,
119132 documentRange: NSRange ,
120133 displayData: TextLine . DisplayData ,
121134 attachments: [ AnyTextAttachment ]
122135 ) -> ( lines: [ TextLineStorage < LineFragment > . BuildItem ] , maxHeight: CGFloat ) {
123- let contentRuns = createContentRuns ( documentRange: documentRange, attachments: attachments)
136+ let contentRuns = createContentRuns ( string : string , documentRange: documentRange, attachments: attachments)
124137 var context = TypesetContext ( documentRange: documentRange, displayData: displayData)
125138
126139 for run in contentRuns {
@@ -130,6 +143,7 @@ final public class Typesetter {
130143 case . string( let typesetter) :
131144 layoutTextUntilLineBreak (
132145 context: & context,
146+ string: string,
133147 range: run. range,
134148 typesetter: typesetter,
135149 displayData: displayData
@@ -148,6 +162,7 @@ final public class Typesetter {
148162
149163 func layoutTextUntilLineBreak(
150164 context: inout TypesetContext ,
165+ string: NSAttributedString ,
151166 range: NSRange ,
152167 typesetter: CTTypesetter ,
153168 displayData: TextLine . DisplayData
@@ -210,8 +225,8 @@ final public class Typesetter {
210225
211226 /// Typesets a single, 0-length line fragment.
212227 /// - Parameter displayData: Relevant information for layout estimation.
213- private func typesetEmptyLine( displayData: TextLine . DisplayData ) {
214- let typesetter = CTTypesetterCreateWithAttributedString ( self . string)
228+ private func typesetEmptyLine( displayData: TextLine . DisplayData , string : NSAttributedString ) {
229+ let typesetter = CTTypesetterCreateWithAttributedString ( string)
215230 // Insert an empty fragment
216231 let ctLine = CTTypesetterCreateLine ( typesetter, CFRangeMake ( 0 , 0 ) )
217232 let fragment = LineFragment (
0 commit comments