#if os(macOS) import AppKit extension NSAttributedString.Key { static let docsTopBlock = NSAttributedString.Key("OficinaDocsTopBlock") static let docsMarginBefore = NSAttributedString.Key("OficinaDocsMarginBefore") static let docsMarginAfter = NSAttributedString.Key("OficinaDocsMarginAfter") } /// Mirrors pages.mjs: keep top-level blocks intact and put a visual separator /// before the next block exceeding the 648pt text budget. All offsets are layout /// only; an oversized paragraph/list is never split or rewritten to make a page. @MainActor enum DocumentPageLayout { struct Result { var starts: [Int: CGFloat] = [:] var separators: [CGFloat] = [] } /// Mapping IDs, formatting marks and colors do not affect glyph positions. /// Retain only layout inputs so metadata acknowledgements reuse pagination. static func measurementContent(_ storage: NSAttributedString) -> NSAttributedString { let keys: Set = [.font, .paragraphStyle, .kern, .ligature, .baselineOffset, .attachment, .expansion, .obliqueness, .writingDirection, .verticalGlyphForm, .superscript, .docsBaseFont, .docsLeading, .docsCodeStart, .docsFirstMargin, .docsTopBlock, .docsMarginBefore, .docsMarginAfter] let result = NSMutableAttributedString(string: storage.string) storage.enumerateAttributes(in: NSRange(location: 0, length: storage.length)) { attributes, range, _ in result.setAttributes(attributes.filter { keys.contains($0.key) }, range: range) } return result } static func measure(storage: NSTextStorage, manager: NSLayoutManager, container: NSTextContainer) -> Result { var result = Result() var pageTop: CGFloat? var added: CGFloat = 0 var previousMargin: CGFloat = 0 storage.enumerateAttribute(.docsTopBlock, in: NSRange(location: 0, length: storage.length)) { value, range, _ in guard value != nil else { return } let glyphs = manager.glyphRange(forCharacterRange: range, actualCharacterRange: nil) var top = CGFloat.greatestFiniteMagnitude var bottom: CGFloat = 0 manager.enumerateLineFragments(forGlyphRange: glyphs) { rect, used, _, _, _ in top = min(top, rect.minY); bottom = max(bottom, used.maxY) } guard bottom > top else { return } let before = storage.attribute(.docsMarginBefore, at: range.location, effectiveRange: nil) as? CGFloat ?? 0 let after = storage.attribute(.docsMarginAfter, at: range.location, effectiveRange: nil) as? CGFloat ?? 0 defer { previousMargin = after } if let currentTop = pageTop { if bottom - currentTop > 648 { // Collapse margins independently on either side of the gap. let collapsed = max(previousMargin, before) let separator = top + added - collapsed + max(previousMargin, 12) let extra = max(previousMargin, 12) + 18 + max(before, 12) - collapsed added += extra result.starts[range.location] = top + added result.separators.append(separator) pageTop = top } } else { pageTop = top } } return result } } #endif