CVResumeAppearance.swift 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. //
  2. // CVResumeAppearance.swift
  3. // App for Indeed
  4. //
  5. // Shared résumé colours for gallery thumbnails and filled CV preview/export.
  6. // Résumés always use a print-style light palette regardless of app light/dark mode.
  7. //
  8. import AppKit
  9. @MainActor
  10. enum CVResumeAppearance {
  11. struct Colors {
  12. let paper: NSColor
  13. let ink: NSColor
  14. let muted: NSColor
  15. let rule: NSColor
  16. let cardBackground: NSColor
  17. let sidebarTint: NSColor
  18. let accentRed: NSColor
  19. let accentBlue: NSColor
  20. }
  21. /// Print-style palette — always light, even when the app chrome is in dark mode.
  22. static func colors() -> Colors {
  23. Colors(
  24. paper: NSColor.white,
  25. ink: NSColor(srgbRed: 38 / 255, green: 50 / 255, blue: 71 / 255, alpha: 1),
  26. muted: NSColor(srgbRed: 110 / 255, green: 118 / 255, blue: 132 / 255, alpha: 1),
  27. rule: NSColor(srgbRed: 228 / 255, green: 232 / 255, blue: 240 / 255, alpha: 1),
  28. cardBackground: NSColor.white,
  29. sidebarTint: NSColor(srgbRed: 244 / 255, green: 246 / 255, blue: 250 / 255, alpha: 1),
  30. accentRed: NSColor(srgbRed: 207 / 255, green: 67 / 255, blue: 50 / 255, alpha: 1),
  31. accentBlue: AppDashboardTheme.brandBlue
  32. )
  33. }
  34. /// Slight paper tint by layout variant (gallery + filled CV use the same rule).
  35. static func paperBackground(variant: Int, base: NSColor) -> NSColor {
  36. switch variant % 5 {
  37. case 0: return base
  38. case 1: return NSColor(srgbRed: 0.995, green: 0.992, blue: 0.985, alpha: 1)
  39. case 2: return NSColor(srgbRed: 0.96, green: 0.99, blue: 1, alpha: 1)
  40. case 3: return NSColor(srgbRed: 0.99, green: 0.99, blue: 0.99, alpha: 1)
  41. default: return NSColor(srgbRed: 0.99, green: 0.98, blue: 0.995, alpha: 1)
  42. }
  43. }
  44. static func accentColor(for template: CVTemplate) -> NSColor {
  45. let palette = colors()
  46. switch template.accent {
  47. case .redUnderline, .redBar:
  48. return palette.accentRed
  49. case .blueBar:
  50. return template.themeColor
  51. case .none:
  52. return template.themeColor.blended(withFraction: 0.5, of: palette.ink) ?? template.themeColor
  53. }
  54. }
  55. static func sectionHeadingColor(for template: CVTemplate) -> NSColor {
  56. accentColor(for: template)
  57. }
  58. // MARK: - Document layout (RTL for Arabic / Hebrew)
  59. static var isRightToLeft: Bool {
  60. AppLayoutDirection.isRightToLeft
  61. }
  62. static var documentInterfaceLayoutDirection: NSUserInterfaceLayoutDirection {
  63. isRightToLeft ? .rightToLeft : .leftToRight
  64. }
  65. static var documentTextAlignment: NSTextAlignment {
  66. isRightToLeft ? .right : .left
  67. }
  68. static var documentWritingDirection: NSWritingDirection {
  69. isRightToLeft ? .rightToLeft : .leftToRight
  70. }
  71. static func resolvedDocumentAlignment(_ alignment: NSTextAlignment) -> NSTextAlignment {
  72. alignment == .left ? documentTextAlignment : alignment
  73. }
  74. static func applyDocumentLayout(to view: NSView) {
  75. view.userInterfaceLayoutDirection = documentInterfaceLayoutDirection
  76. for child in view.subviews {
  77. applyDocumentLayout(to: child)
  78. }
  79. }
  80. static func documentParagraphStyle(
  81. alignment: NSTextAlignment,
  82. lineBreakMode: NSLineBreakMode = .byWordWrapping
  83. ) -> NSParagraphStyle {
  84. let paragraph = NSMutableParagraphStyle()
  85. paragraph.alignment = resolvedDocumentAlignment(alignment)
  86. paragraph.baseWritingDirection = documentWritingDirection
  87. paragraph.lineBreakMode = lineBreakMode
  88. return paragraph
  89. }
  90. static func applyDocumentTextLayout(to field: NSTextField, alignment: NSTextAlignment? = nil) {
  91. let sourceAlignment = alignment ?? field.alignment
  92. let resolved = resolvedDocumentAlignment(sourceAlignment)
  93. field.alignment = resolved
  94. field.baseWritingDirection = documentWritingDirection
  95. let lineBreak: NSLineBreakMode = field.maximumNumberOfLines == 0 ? .byWordWrapping : .byTruncatingTail
  96. field.attributedStringValue = NSAttributedString(string: field.stringValue, attributes: [
  97. .font: field.font ?? .systemFont(ofSize: 12),
  98. .foregroundColor: field.textColor ?? .labelColor,
  99. .paragraphStyle: documentParagraphStyle(alignment: resolved, lineBreakMode: lineBreak)
  100. ])
  101. }
  102. }