| 1 | import UIKit |
| 2 | |
| 3 | func check(_ passed: Bool, _ message: String = "Failed layout check") { |
| 4 | if !passed { |
| 5 | FileHandle.standardError.write(Data((message + "\n").utf8)) |
| 6 | exit(1) |
| 7 | } |
| 8 | } |
| 9 | |
| 10 | // Runs the production layout against UIKit through Mac Catalyst, without launching an app. |
| 11 | let font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) |
| 12 | let characterWidth = ("M" as NSString).size(withAttributes: [.font: font]).width |
| 13 | let fixtures = ["漢字表示", "e\u{301}", "👨👩👧👦", "مرحبا بالعالم ", "\tvalue "] |
| 14 | var cases = 0 |
| 15 | for fixture in fixtures { |
| 16 | let text = String(repeating: fixture, count: 40) |
| 17 | var previousHeight = CGFloat.greatestFiniteMagnitude |
| 18 | for width: CGFloat in [180, 280, 420] { |
| 19 | let layout = ReviewDiffCodeLayout(text: text, font: font, width: width, characterWidth: characterWidth) |
| 20 | let height = layout.firstLineHeight + layout.extraHeight |
| 21 | check(height <= previousHeight, "Wider text must not require more height") |
| 22 | previousHeight = height |
| 23 | let fullRange = NSRange(location: 0, length: text.utf16.count) |
| 24 | let attributed = NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black]) |
| 25 | layout.decorate(text: attributed, highlights: [], color: .clear, version: 0) |
| 26 | let format = UIGraphicsImageRendererFormat() |
| 27 | format.scale = 1 |
| 28 | format.opaque = false |
| 29 | format.preferredRange = .standard |
| 30 | let size = CGSize(width: width + 40, height: ceil(height)) |
| 31 | let image = UIGraphicsImageRenderer(size: size, format: format).image { _ in |
| 32 | layout.draw(at: .zero, clip: CGRect(origin: .zero, size: size)) |
| 33 | } |
| 34 | let bitmap = image.cgImage! |
| 35 | let data = bitmap.dataProvider!.data! |
| 36 | let bytes = CFDataGetBytePtr(data)! |
| 37 | // Render without a viewport clip so an overflowing glyph cannot hide behind clipping. |
| 38 | for y in 0..<bitmap.height { |
| 39 | for x in Int(width) + 1..<bitmap.width { |
| 40 | check(bytes[y * bitmap.bytesPerRow + x * 4 + 3] == 0, "Ink outside viewport: \(fixture) x=\(x) y=\(y) bpp=\(bitmap.bitsPerPixel) alpha=\(bitmap.alphaInfo.rawValue)") |
| 41 | } |
| 42 | } |
| 43 | // Updating highlights and syntax colors must preserve measured geometry. |
| 44 | layout.decorate(text: attributed, highlights: [fullRange], color: .green, version: 1) |
| 45 | check(layout.firstLineHeight + layout.extraHeight == height) |
| 46 | cases += 1 |
| 47 | } |
| 48 | } |
| 49 | let ascii = String(repeating: "const value = 123; ", count: 100) |
| 50 | let layout = ReviewDiffCodeLayout(text: ascii, font: font, width: 280, characterWidth: characterWidth) |
| 51 | let string = ascii as NSString |
| 52 | var reconstructed = "" |
| 53 | for (index, start) in layout.starts.enumerated() { |
| 54 | let end = index + 1 < layout.starts.count ? layout.starts[index + 1] : string.length |
| 55 | let segment = string.substring(with: NSRange(location: start, length: end - start)) |
| 56 | check((segment as NSString).size(withAttributes: [.font: font]).width <= 280) |
| 57 | reconstructed += segment |
| 58 | } |
| 59 | check(reconstructed == ascii) |
| 60 | print("Passed \(cases) Unicode/width cases, color-update geometry, and ASCII coverage") |