| 1 | import Foundation |
| 2 | import Testing |
| 3 | @testable import DocsKit |
| 4 | #if os(macOS) |
| 5 | import AppKit |
| 6 | #else |
| 7 | import UIKit |
| 8 | #endif |
| 9 | |
| 10 | @Test func letterGeometryAndNarrowReflow() { |
| 11 | let desktop = DocsPageGeometry(viewportWidth: 800, paged: true) |
| 12 | #expect(desktop.paperWidth == 612) |
| 13 | #expect(desktop.textWidth == 468) |
| 14 | #expect(desktop.bodyHeight == 648) |
| 15 | let phone = DocsPageGeometry(viewportWidth: 320, paged: true) |
| 16 | #expect(phone.textWidth >= 256) |
| 17 | let crossing = desktop.lineRect(CGRect(x: 0, y: 710, width: 800, height: 20)) |
| 18 | #expect(crossing.minY == 888) |
| 19 | let continuous = DocsPageGeometry(viewportWidth: 800, paged: false) |
| 20 | #expect(continuous.lineRect(CGRect(x: 0, y: 710, width: 800, height: 20)).minY == 710) |
| 21 | } |
| 22 | |
| 23 | @MainActor @Test func nativePaginationMovesGlyphsWithoutChangingTextOffsets() { |
| 24 | let source = String(repeating: "Wrapped text العربية 日本語 🧪 preserves offsets. ", count: 100) |
| 25 | + "\n" + String(repeating: "Native paging preserves every character and edit offset.\n", count: 100) |
| 26 | let storage = NSTextStorage(string: source) |
| 27 | #if os(macOS) |
| 28 | storage.addAttribute(.font, value: NSFont.systemFont(ofSize: 12), range: NSRange(location: 0, length: storage.length)) |
| 29 | #else |
| 30 | storage.addAttribute(.font, value: UIFont.systemFont(ofSize: 12), range: NSRange(location: 0, length: storage.length)) |
| 31 | #endif |
| 32 | let manager = NSLayoutManager() |
| 33 | let container = DocsPageTextContainer(size: CGSize(width: 800, height: 1_000_000)) |
| 34 | container.lineFragmentPadding = 0 |
| 35 | container.geometry = DocsPageGeometry(viewportWidth: 800, paged: true) |
| 36 | storage.addLayoutManager(manager) |
| 37 | manager.addTextContainer(container) |
| 38 | manager.ensureLayout(for: container) |
| 39 | var secondPageGlyph: Int? |
| 40 | manager.enumerateLineFragments(forGlyphRange: NSRange(location: 0, length: manager.numberOfGlyphs)) { rect, _, _, glyphs, _ in |
| 41 | let page = container.geometry.page(at: rect.minY) |
| 42 | let top = CGFloat(page) * container.geometry.stride + DocsPageGeometry.verticalMargin |
| 43 | #expect(rect.minY >= top) |
| 44 | #expect(rect.maxY <= top + container.geometry.bodyHeight + 0.1) |
| 45 | #expect(rect.minX >= container.geometry.textX) |
| 46 | #expect(rect.maxX <= container.geometry.textX + container.geometry.textWidth + 0.1) |
| 47 | if page > 0 && secondPageGlyph == nil { secondPageGlyph = glyphs.location } |
| 48 | } |
| 49 | #expect(secondPageGlyph != nil) |
| 50 | #expect(storage.string == source) |
| 51 | let location = manager.characterIndexForGlyph(at: secondPageGlyph ?? 0) |
| 52 | #expect(location > 0 && location < source.utf16.count) |
| 53 | storage.replaceCharacters(in: NSRange(location: location, length: 0), with: "🧪") |
| 54 | #expect((storage.string as NSString).substring(with: NSRange(location: location, length: 2)) == "🧪") |
| 55 | container.geometry.paged = false |
| 56 | manager.ensureLayout(for: container) |
| 57 | #expect(storage.length == source.utf16.count + 2) |
| 58 | } |
| 59 | |