1import UIKit
2
3func 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.
11let font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular)
12let characterWidth = ("M" as NSString).size(withAttributes: [.font: font]).width
13let fixtures = ["漢字表示", "e\u{301}", "👨‍👩‍👧‍👦", "مرحبا بالعالم ", "\tvalue "]
14var cases = 0
15for 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}
49let ascii = String(repeating: "const value = 123; ", count: 100)
50let layout = ReviewDiffCodeLayout(text: ascii, font: font, width: 280, characterWidth: characterWidth)
51let string = ascii as NSString
52var reconstructed = ""
53for (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}
59check(reconstructed == ascii)
60print("Passed \(cases) Unicode/width cases, color-update geometry, and ASCII coverage")