import Foundation /// **What the running head and foot say** — "page footer/header", the card's fifth bullet, as four /// toggles composed into two lines. /// /// ### Why the pieces are composed here and not drawn here /// /// Which pieces appear, in what order, and how a line reads when only some of them are switched on is a /// *decision*, and decisions live in the pure layer where a test can hold them (`PrintDocumentBuilder`'s /// own argument). Where the ink lands is `PrintDocumentView`'s. /// /// ### The date and the page number arrive pre-formatted /// /// Both are strings the caller supplies rather than a `Date` and an `Int` this type formats. That keeps /// every rule below **locale-free and therefore testable**: "Page 3 of 7" and "9 Aug 2026 at 14:30" are /// the caller's renderings of values the system formats differently in every region, and a rule that /// baked them in would be a rule whose test only passed in one place. /// /// ### Leading and trailing, not left and right /// /// The two slots are named by reading order because that is what they are: the view places them at the /// two ends of the measure, which a right-to-left interface swaps. Nothing here knows which end is /// which. public enum PrintRunningHead { /// A line of the running head or foot, as a pair of ends. Either may be empty; both empty means the /// line does not print at all, which is what `isEmpty` is for and what lets the view reclaim the /// space rather than leaving a band of blank paper. public struct Line: Sendable, Equatable { public var leading: String public var trailing: String public var isEmpty: Bool { leading.isEmpty && trailing.isEmpty } public init(leading: String = "", trailing: String = "") { self.leading = leading self.trailing = trailing } } /// The running head: the board's name at the leading end, the print's date at the trailing end. /// /// The board's name leads because it is what the reader is looking for when they pick the sheet up; /// the date trails because it answers a question they ask second. A board with no title contributes /// nothing rather than the word "Untitled" — the running head is context, and inventing context is /// worse than having none. public static func header(options: PrintOptions, boardTitle: String, dateText: String) -> Line { Line( leading: options.headerShowsBoardTitle ? boardTitle : "", trailing: options.headerShowsPrintDate ? dateText : "" ) } /// The running foot: the user's own line at the leading end, the folio at the trailing end — where /// a book puts it. /// /// The custom line is read through `normalized`, so a toggle left on over an emptied field prints /// nothing rather than an indent of blank space (`PrintOptions.normalized`). public static func footer(options rawOptions: PrintOptions, pageText: String) -> Line { let options = rawOptions.normalized return Line( leading: options.footerShowsCustomLine ? options.footerCustomLine : "", trailing: options.footerShowsPageNumbers ? pageText : "" ) } /// "Page 3 of 7" — the folio's wording, in one place because the view draws it and a summary line /// in the print panel describes it. public static func pageText(page: Int, of pageCount: Int) -> String { "Page \(page) of \(pageCount)" } }