0

I'm trying to implement pagination using NSLayoutManager and multiple text containers. Creating NSTextView/UITextView instances works as expected, and the text flows from text view to another.

enter image description here

However, I'd like to force a page break, ie. determine myself where to break onto the next container. I'm working with parsed content, so I can't insert any additional ASCII control characters into the text.

I tried subclassing NSLayoutManager and overriding textContainer(glyphIndex:, effectiveRange:), and here's a very brute-force example:

override func textContainer(forGlyphAt glyphIndex: Int, effectiveRange effectiveGlyphRange: NSRangePointer?) -> NSTextContainer? {
    if glyphIndex > 100 {
        return self.textContainers[1]
    } else {
        return super.textContainer(forGlyphAt: glyphIndex, effectiveRange: effectiveGlyphRange)
    }
}

I'd expect this to move any glyphs after 100th index onto the second container, but the results are weird: enter image description here

I suppose I'd have to subclass NSTextContainer and tell the layout manager that it's already full of text. It has a method called lineFragmentRect(forProposedRect:at:writingDirection:remaining:) but the documentation is very sparse, and I can't find any working examples.

Existing documentation around displaying text is very outdated, so any ideas or hints are welcome. I'm very confused about this, but still hopeful there is a simple way of telling the layout manager where to cut off content in each container.

One possible solution

NSTextContainer.exclusionPaths could be used to rule out the rest of the possible space in containers.

let glyphIndex = layoutManager.glyphIndexForCharacter(at: 100)
var rect = layoutManager.lineFragmentRect(forGlyphAt: glyphIndex, effectiveRange: nil)

// Get the line range and used rect
var lineRange = NSMakeRange(NSNotFound, 0)
var usedRect = layoutManager.lineFragmentUsedRect(forGlyphAt: gi, effectiveRange: NSRangePointer(&lineRange))

// Calculate the remainder of the line
let remainder = NSRange(location: glyphIndex, length: NSMaxRange(lineRange) - glyphIndex)

var rectCount:Int = 0
var breakRects = layoutManager.rectArray(forGlyphRange: remainder, withinSelectedGlyphRange: remainder, in: textContainer1, rectCount: UnsafeMutablePointer(&rectCount))

// Create the rect for the remainder of the line
var lineRect = breakRect!.pointee
lineRect.size.width = textContainer1.size.width - lineRect.origin.x
// Then create a rect to cover up the rest
var coverRest = NSMakeRect(0, lineRect.origin.y + lineRect.height, textContainer1.size.width, CGFloat.greatestFiniteMagnitude)

// Add exclusion paths
textContainer1.exclusionPaths = [NSBezierPath(rect: lineRect), NSBezierPath(rect: coverRest)]

This results in expected behavior: Text breaking as expected

This requires a ton of calculations, and text containers with exclusion paths are noticeably slower. The app could potentially have hundreds of text views, which makes this quite inefficient.

Tritonal
  • 607
  • 4
  • 16
  • 1
    Have you looked at this article/tutorial? https://www.kodeco.com/578-core-text-tutorial-for-ios-making-a-magazine-app ... you might be able to "page-break" at, as in your example, every 100 glyphs. – DonMag Dec 08 '22 at 21:59
  • I had *hoped* I didn't have to write my own Core Text framesetting because it's really poorly documented and I'm relying heavily on some other `NSLayoutManager` stuff, but this article makes it seem pretty straightforward. Thanks for the tip! – Tritonal Dec 08 '22 at 22:23

0 Answers0