diff --git a/Clop/ClopApp.swift b/Clop/ClopApp.swift index d65c168..38165a9 100644 --- a/Clop/ClopApp.swift +++ b/Clop/ClopApp.swift @@ -31,6 +31,23 @@ class WindowManager: ObservableObject { } let WM = WindowManager() +extension NSPasteboard { + func debug() { + #if DEBUG + print(name.rawValue) + guard let pasteboardItems else { + print("No items") + return + } + pasteboardItems.forEach { item in + item.types.filter { ![NSPasteboard.PasteboardType.rtf, NSPasteboard.PasteboardType(rawValue: "public.utf16-external-plain-text")].contains($0) }.forEach { type in + print(type.rawValue + " " + (item.string(forType: type) ?! String(describing: item.propertyList(forType: type) ?? item.data(forType: type) ?? ""))) + } + } + #endif + } +} + class AppDelegate: LowtechProAppDelegate { var didBecomeActiveAtLeastOnce = false @@ -61,26 +78,19 @@ class AppDelegate: LowtechProAppDelegate { } let drag = NSPasteboard(name: .drag) + drag.debug() guard self.lastDragChangeCount != drag.changeCount else { return } DM.dropped = false self.lastDragChangeCount = drag.changeCount - guard let items = drag.pasteboardItems, !items.contains(where: { $0.types.set.hasElements(from: [.promise, .promisedFileName, .promisedFileURL, .promisedSuggestedFileName, .promisedMetadata, .finderNode]) }) else { + guard let items = drag.pasteboardItems, !items.contains(where: { $0.types.set.hasElements(from: [.promise, .promisedFileName, .promisedFileURL, .promisedSuggestedFileName, .promisedMetadata]) }) else { DM.itemsToOptimise = [] self.draggingSet.send(true) return } - #if DEBUG - items.forEach { item in - item.types.filter { ![NSPasteboard.PasteboardType.rtf, NSPasteboard.PasteboardType(rawValue: "public.utf16-external-plain-text")].contains($0) }.forEach { type in - print(type.rawValue + " " + (item.string(forType: type) ?! String(describing: item.propertyList(forType: type) ?? item.data(forType: type) ?? ""))) - } - } - #endif - let toOptimise: [ClipboardType] = items.compactMap { item -> ClipboardType? in let types = item.types if types.contains(.fileURL), let url = item.string(forType: .fileURL)?.url, diff --git a/Clop/ClopShortcuts.swift b/Clop/ClopShortcuts.swift index ca4a1ef..5e0b313 100644 --- a/Clop/ClopShortcuts.swift +++ b/Clop/ClopShortcuts.swift @@ -284,24 +284,39 @@ struct CropPDFIntent: AppIntent { static var parameterSummary: some ParameterSummary { When(\.$overwrite, .equalTo, true, { - Summary("Crop \(\.$item) for \(\.$aspectRatio) and \(\.$overwrite)") { - \.$hideFloatingResult - } + When(\.$usePaperSize, .equalTo, true, { + Summary("Crop \(\.$item) \(\.$usePaperSize) \(\.$paperSize) and \(\.$overwrite)") { \.$pageLayout } + }, otherwise: { + Summary("Crop \(\.$item) \(\.$usePaperSize) \(\.$device) and \(\.$overwrite)") { \.$pageLayout } + }) }, otherwise: { - Summary("Crop \(\.$item) for \(\.$aspectRatio) and \(\.$overwrite) \(\.$output)") { - \.$hideFloatingResult - } + When(\.$usePaperSize, .equalTo, true, { + Summary("Crop \(\.$item) \(\.$usePaperSize) \(\.$paperSize) and \(\.$overwrite) \(\.$output)") { \.$pageLayout } + }, otherwise: { + Summary("Crop \(\.$item) \(\.$usePaperSize) \(\.$device) and \(\.$overwrite) \(\.$output)") { \.$pageLayout } + }) }) } @Parameter(title: "PDF") var item: IntentFile - @Parameter(title: "Hide floating result") - var hideFloatingResult: Bool + @Parameter(title: "Paper size or device", displayName: .init(true: "to paper size", false: "for device")) + var usePaperSize: Bool + + @Parameter(title: "Page layout", description: """ + Allows forcing a page layout on all PDF pages: + auto: Crop pages based on their longest edge, so that horizontal pages stay horizontal and vertical pages stay vertical + portrait: Force all pages to be cropped to vertical or portrait layout + landscape: Force all pages to be cropped to horizontal or landscape layout + """, default: PageLayout.auto) + var pageLayout: PageLayout - @Parameter(title: "Aspect ratio") - var aspectRatio: Double + @Parameter(title: "Paper", default: PaperSize.a4) + var paperSize: PaperSize? + + @Parameter(title: "Device", default: Device.iPadAir) + var device: Device? @Parameter(title: "Output path", description: "Where to save the cropped PDF (defaults to modifying the PDF in place).") var output: String? @@ -311,6 +326,10 @@ struct CropPDFIntent: AppIntent { @MainActor func perform() async throws -> some IntentResult & ReturnsValue { + guard let aspectRatio = usePaperSize ? paperSize?.aspectRatio : device?.aspectRatio else { + throw IntentError.message("Invalid aspect ratio") + } + let url = item.url guard let pdf = PDFDocument(url: url) else { throw IntentError.message("Couldn't parse PDF") @@ -322,7 +341,7 @@ struct CropPDFIntent: AppIntent { } log.debug("Cropping \(pdf.documentURL?.path ?? "PDF") to aspect ratio \(aspectRatio)") - pdf.cropTo(aspectRatio: aspectRatio) + pdf.cropTo(aspectRatio: aspectRatio, alwaysPortrait: pageLayout == .portrait, alwaysLandscape: pageLayout == .landscape) log.debug("Writing PDF to \(outputURL.path)") pdf.write(to: outputURL) @@ -553,3 +572,113 @@ struct OptimiseURLIntent: AppIntent { } } } + +extension PageLayout: AppEnum { + static var typeDisplayRepresentation: TypeDisplayRepresentation { + "Page Layout" + } + + static var caseDisplayRepresentations: [PageLayout: DisplayRepresentation] { + [ + .auto: DisplayRepresentation( + title: "Auto", + subtitle: "Crop pages based on their longest edge", + image: .init(systemName: "sparkles.rectangle.stack.fill") + ), + .portrait: DisplayRepresentation( + title: "Portrait", + subtitle: "Force all pages to be vertical", + image: .init(systemName: "rectangle.portrait.arrowtriangle.2.inward") + ), + .landscape: DisplayRepresentation( + title: "Landscape", + subtitle: "Force all pages to be horizontal", + image: .init(systemName: "rectangle.arrowtriangle.2.inward") + ), + ] + } +} + +extension Device: AppEnum { + static var typeDisplayRepresentation: TypeDisplayRepresentation { + "Device" + } + + static var caseDisplayRepresentations: [Device: DisplayRepresentation] { + [ + .iPhone15ProMax: "iPhone 15 Pro Max", .iPhone15Pro: "iPhone 15 Pro", .iPhone15Plus: "iPhone 15 Plus", .iPhone15: "iPhone 15", + .iPadPro: "iPad Pro", .iPadPro6129Inch: "iPad Pro 6 12.9inch", .iPadPro611Inch: "iPad Pro 6 11inch", + .iPad: "iPad", .iPad10: "iPad 10", + .iPhone14Plus: "iPhone 14 Plus", .iPhone14ProMax: "iPhone 14 Pro Max", .iPhone14Pro: "iPhone 14 Pro", .iPhone14: "iPhone 14", + .iPhoneSe3: "iPhone SE 3", + .iPadAir: "iPad Air", .iPadAir5: "iPad Air 5", + .iPhone13: "iPhone 13", .iPhone13Mini: "iPhone 13 mini", .iPhone13ProMax: "iPhone 13 Pro Max", .iPhone13Pro: "iPhone 13 Pro", + .iPad9: "iPad 9", .iPadPro5129Inch: "iPad Pro 5 12.9inch", .iPadPro511Inch: "iPad Pro 5 11inch", .iPadAir4: "iPad Air 4", + .iPhone12: "iPhone 12", .iPhone12Mini: "iPhone 12 mini", .iPhone12ProMax: "iPhone 12 Pro Max", .iPhone12Pro: "iPhone 12 Pro", + .iPad8: "iPad 8", + .iPhoneSe2: "iPhone SE 2", + .iPadPro4129Inch: "iPad Pro 4 12.9inch", .iPadPro411Inch: "iPad Pro 4 11inch", + .iPad7: "iPad 7", + .iPhone11ProMax: "iPhone 11 Pro Max", .iPhone11Pro: "iPhone 11 Pro", .iPhone11: "iPhone 11", + .iPodTouch7: "iPod touch 7", + .iPadMini: "iPad mini", .iPadMini6: "iPad mini 6", .iPadMini5: "iPad mini 5", .iPadAir3: "iPad Air 3", .iPadPro3129Inch: "iPad Pro 3 12.9inch", .iPadPro311Inch: "iPad Pro 3 11inch", + .iPhoneXr: "iPhone XR", .iPhoneXsMax: "iPhone XS Max", .iPhoneXs: "iPhone XS", + .iPad6: "iPad 6", + .iPhoneX: "iPhone X", .iPhone8Plus: "iPhone 8 Plus", .iPhone8: "iPhone 8", + .iPadPro2129Inch: "iPad Pro 2 12.9inch", + .iPadPro2105Inch: "iPad Pro 2 10.5inch", + .iPad5: "iPad 5", + .iPhone7Plus: "iPhone 7 Plus", + .iPhone7: "iPhone 7", + .iPhoneSe1: "iPhone SE 1", + .iPadPro197Inch: "iPad Pro 1 9.7inch", + .iPadPro1129Inch: "iPad Pro 1 12.9inch", + .iPhone6SPlus: "iPhone 6s Plus", + .iPhone6S: "iPhone 6s", + .iPadMini4: "iPad mini 4", + .iPodTouch6: "iPod touch 6", + .iPadAir2: "iPad Air 2", + .iPadMini3: "iPad mini 3", + .iPhone6Plus: "iPhone 6 Plus", + .iPhone6: "iPhone 6", + .iPadMini2: "iPad mini 2", + .iPadAir1: "iPad Air 1", + .iPhone5C: "iPhone 5C", + .iPhone5S: "iPhone 5S", + .iPad4: "iPad 4", + .iPodTouch5: "iPod touch 5", + .iPhone5: "iPhone 5", + .iPad3: "iPad 3", + .iPhone4S: "iPhone 4S", + .iPad2: "iPad 2", + .iPodTouch4: "iPod touch 4", + .iPhone4: "iPhone 4", + ] + } +} + +extension PaperSize: AppEnum { + static var typeDisplayRepresentation: TypeDisplayRepresentation { + "Paper Size" + } + + static var caseDisplayRepresentations: [PaperSize: DisplayRepresentation] { + [ + .a0: "A0", .a1: "A1", .a2: "A2", .a3: "A3", .a4: "A4", .a5: "A5", .a6: "A6", .a7: "A7", .a8: "A8", .a9: "A9", .a10: "A10", .a11: "A11", .a12: "A12", .a13: "A13", + ._2A0: "2A0", ._4A0: "4A0", .a0plus: "A0+", .a1plus: "A1+", .a3plus: "A3+", + .b0: "B0", .b1: "B1", .b2: "B2", .b3: "B3", .b4: "B4", .b5: "B5", .b6: "B6", .b7: "B7", .b8: "B8", .b9: "B9", .b10: "B10", .b11: "B11", .b12: "B12", .b13: "B13", + .b0plus: "B0+", .b1plus: "B1+", .b2plus: "B2+", .letter: "Letter", + .legal: "Legal", .tabloid: "Tabloid", .ledger: "Ledger", .juniorLegal: "Junior Legal", .halfLetter: "Half Letter", .governmentLetter: "Government Letter", .governmentLegal: "Government Legal", + .ansiA: "ANSI A", .ansiB: "ANSI B", .ansiC: "ANSI C", .ansiD: "ANSI D", .ansiE: "ANSI E", .archA: "Arch A", + .archB: "Arch B", .archC: "Arch C", .archD: "Arch D", .archE: "Arch E", .archE1: "Arch E1", .archE2: "Arch E2", .archE3: "Arch E3", .passport: "Passport", + ._2R: "2R", .ldDsc: "LD, DSC", ._3RL: "3R, L", .lw: "LW", .kgd: "KGD", ._4RKg: "4R, KG", ._2LdDscw: "2LD, DSCW", ._5R2L: "5R, 2L", ._2Lw: "2LW", ._6R: "6R", ._8R6P: "8R, 6P", .s8R6Pw: "S8R, 6PW", ._11R: "11R", + .a3SuperB: "A3+ Super B", + .berliner: "Berliner", .broadsheet: "Broadsheet", .usBroadsheet: "US Broadsheet", .britishBroadsheet: "British Broadsheet", .southAfricanBroadsheet: "South African Broadsheet", + .ciner: "Ciner", .compact: "Compact", .nordisch: "Nordisch", .rhenish: "Rhenish", .swiss: "Swiss", + .newspaperTabloid: "Newspaper Tabloid", .canadianTabloid: "Canadian Tabloid", .norwegianTabloid: "Norwegian Tabloid", .newYorkTimes: "New York Times", .wallStreetJournal: "Wall Street Journal", + .folio: "Folio", .quarto: "Quarto", .imperialOctavo: "Imperial Octavo", .superOctavo: "Super Octavo", .royalOctavo: "Royal Octavo", .mediumOctavo: "Medium Octavo", .octavo: "Octavo", .crownOctavo: "Crown Octavo", + ._12Mo: "12mo", ._16Mo: "16mo", ._18Mo: "18mo", ._32Mo: "32mo", ._48Mo: "48mo", ._64Mo: "64mo", + .aFormat: "A Format", .bFormat: "B Format", .cFormat: "C Format", + ] + } +} diff --git a/Clop/PDF.swift b/Clop/PDF.swift index e796641..7034f44 100644 --- a/Clop/PDF.swift +++ b/Clop/PDF.swift @@ -178,12 +178,12 @@ class PDF: Optimisable { lazy var document: PDFDocument? = PDFDocument(url: path.url) @discardableResult - func cropTo(aspectRatio: Double, saveTo newPath: FilePath? = nil) -> Bool { + func cropTo(aspectRatio: Double, alwaysPortrait: Bool = false, alwaysLandscape: Bool = false, saveTo newPath: FilePath? = nil) -> Bool { guard let document else { return false } - document.cropTo(aspectRatio: aspectRatio) + document.cropTo(aspectRatio: aspectRatio, alwaysPortrait: alwaysPortrait, alwaysLandscape: alwaysLandscape) return document.write(to: newPath?.url ?? path.url) } diff --git a/ClopCLI/main.swift b/ClopCLI/main.swift index 397f38c..8d64f61 100644 --- a/ClopCLI/main.swift +++ b/ClopCLI/main.swift @@ -33,6 +33,7 @@ func isURLOptimisable(_ url: URL, type: UTType? = nil) -> Bool { return IMAGE_VIDEO_FORMATS.contains(type) || type == .pdf } +extension PageLayout: ExpressibleByArgument {} extension FilePath: ExpressibleByArgument { public init?(argument: String) { guard FileManager.default.fileExists(atPath: argument) else { @@ -268,6 +269,14 @@ struct Clop: ParsableCommand { @Flag(name: .long, help: "List possible paper sizes that can be passed to --paper-size") var listPaperSizes = false + @Option(help: """ + Allows forcing a page layout on all PDF pages: + auto: Crop pages based on their longest edge, so that horizontal pages stay horizontal and vertical pages stay vertical + portrait: Force all pages to be cropped to vertical or portrait layout + landscape: Force all pages to be cropped to horizontal or landscape layout + """) + var pageLayout = PageLayout.auto + @Option(name: .shortAndLong, help: "Output file path (defaults to modifying the PDF in place). In case of cropping multiple files, this needs to be a folder.") var output: String? = nil @@ -362,7 +371,7 @@ struct Clop: ParsableCommand { for pdf in foundPDFs.compactMap({ PDFDocument(url: $0.url) }) { print("Cropping \(pdf.documentURL!.path) to aspect ratio \(ratio!)", terminator: outputDir == nil ? "\n" : "") - pdf.cropTo(aspectRatio: ratio) + pdf.cropTo(aspectRatio: ratio, alwaysPortrait: pageLayout == .portrait, alwaysLandscape: pageLayout == .landscape) if let outputDir { let output = outputDir.appending(pdf.documentURL!.lastPathComponent) diff --git a/ReleaseNotes/2.2.5.md b/ReleaseNotes/2.2.5.md index 435192d..851c712 100644 --- a/ReleaseNotes/2.2.5.md +++ b/ReleaseNotes/2.2.5.md @@ -47,6 +47,14 @@ if (![clop waitForClopToBeAvailableFor:5]) { *This is not exactly a feature inside Clop, but I thought it might be a good idea to let people know in case someone is an app developer and wants to integrate Clop in their app.* +## Improvements + +- Add "Crop PDF" Shortcut +- Add `--output` parameter for the CLI +- ADd `--aggressive` parameter in the CLI commands where it was missing +- ADd `--page-layout` parameter for the `crop-pdf` CLI command +- Add `Output path` parameter for Shortcuts + ## Fixes - Fix `--playback-speed-factor` CLI option diff --git a/Releases/appcast.xml b/Releases/appcast.xml index b8db3ed..41ededd 100644 --- a/Releases/appcast.xml +++ b/Releases/appcast.xml @@ -10,9 +10,9 @@ 2.2.5 13.0 https://files.lowtechguys.com/ReleaseNotes/Clop-2.2.5.html - + - + diff --git a/Shared.swift b/Shared.swift index 3e79d09..670ed93 100644 --- a/Shared.swift +++ b/Shared.swift @@ -217,9 +217,9 @@ extension URL { import PDFKit extension NSSize { - func cropTo(aspectRatio: Double) -> NSRect { - let sizeAspectRatio = self.aspectRatio - if sizeAspectRatio > aspectRatio { + func cropToPortrait(aspectRatio: Double) -> NSRect { + let selfAspectRatio = width / height + if selfAspectRatio > aspectRatio { let width = height * aspectRatio let x = (self.width - width) / 2 return NSRect(x: x, y: 0, width: width, height: height) @@ -230,6 +230,33 @@ extension NSSize { } } + func cropToLandscape(aspectRatio: Double) -> NSRect { + let selfAspectRatio = height / width + if selfAspectRatio > aspectRatio { + let height = width * aspectRatio + let y = (self.height - height) / 2 + return NSRect(x: 0, y: y, width: width, height: height) + } else { + let width = height / aspectRatio + let x = (self.width - width) / 2 + return NSRect(x: x, y: 0, width: width, height: height) + } + } + + var isLandscape: Bool { width > height } + var isPortrait: Bool { width < height } + + func cropTo(aspectRatio: Double, alwaysPortrait: Bool = false, alwaysLandscape: Bool = false) -> NSRect { + if alwaysPortrait { + cropToPortrait(aspectRatio: aspectRatio) + } else if alwaysLandscape { + cropToLandscape(aspectRatio: aspectRatio) + } else { + isLandscape ? cropToLandscape(aspectRatio: aspectRatio) : cropToPortrait(aspectRatio: aspectRatio) + } + + } + var evenSize: NSSize { var w = Int(width.rounded()) w = w + w % 2 @@ -241,14 +268,21 @@ extension NSSize { } } +@frozen +enum PageLayout: String, Codable, CaseIterable, Sendable { + case portrait + case landscape + case auto +} + extension PDFDocument { - func cropTo(aspectRatio: Double) { + func cropTo(aspectRatio: Double, alwaysPortrait: Bool = false, alwaysLandscape: Bool = false) { guard pageCount > 0 else { return } for i in 0 ..< pageCount { let page = page(at: i)! let size = page.bounds(for: .mediaBox).size - let cropRect = size.cropTo(aspectRatio: aspectRatio) + let cropRect = size.cropTo(aspectRatio: aspectRatio, alwaysPortrait: alwaysPortrait, alwaysLandscape: alwaysLandscape) page.setBounds(cropRect, for: .cropBox) } } @@ -518,3 +552,201 @@ extension NSSize { CropSize(width: width.evenInt, height: height.evenInt, name: name, longEdge: longEdge) } } + +enum Device: String, Codable, Sendable, CaseIterable { + case iPhone15ProMax = "iPhone 15 Pro Max" + case iPhone15Pro = "iPhone 15 Pro" + case iPhone15Plus = "iPhone 15 Plus" + case iPhone15 = "iPhone 15" + case iPadPro = "iPad Pro" + case iPadPro6129Inch = "iPad Pro 6 12.9inch" + case iPadPro611Inch = "iPad Pro 6 11inch" + case iPad + case iPad10 = "iPad 10" + case iPhone14Plus = "iPhone 14 Plus" + case iPhone14ProMax = "iPhone 14 Pro Max" + case iPhone14Pro = "iPhone 14 Pro" + case iPhone14 = "iPhone 14" + case iPhoneSe3 = "iPhone SE 3" + case iPadAir = "iPad Air" + case iPadAir5 = "iPad Air 5" + case iPhone13 = "iPhone 13" + case iPhone13Mini = "iPhone 13 mini" + case iPhone13ProMax = "iPhone 13 Pro Max" + case iPhone13Pro = "iPhone 13 Pro" + case iPad9 = "iPad 9" + case iPadPro5129Inch = "iPad Pro 5 12.9inch" + case iPadPro511Inch = "iPad Pro 5 11inch" + case iPadAir4 = "iPad Air 4" + case iPhone12 = "iPhone 12" + case iPhone12Mini = "iPhone 12 mini" + case iPhone12ProMax = "iPhone 12 Pro Max" + case iPhone12Pro = "iPhone 12 Pro" + case iPad8 = "iPad 8" + case iPhoneSe2 = "iPhone SE 2" + case iPadPro4129Inch = "iPad Pro 4 12.9inch" + case iPadPro411Inch = "iPad Pro 4 11inch" + case iPad7 = "iPad 7" + case iPhone11ProMax = "iPhone 11 Pro Max" + case iPhone11Pro = "iPhone 11 Pro" + case iPhone11 = "iPhone 11" + case iPodTouch7 = "iPod touch 7" + case iPadMini = "iPad mini" + case iPadMini6 = "iPad mini 6" + case iPadMini5 = "iPad mini 5" + case iPadAir3 = "iPad Air 3" + case iPadPro3129Inch = "iPad Pro 3 12.9inch" + case iPadPro311Inch = "iPad Pro 3 11inch" + case iPhoneXr = "iPhone XR" + case iPhoneXsMax = "iPhone XS Max" + case iPhoneXs = "iPhone XS" + case iPad6 = "iPad 6" + case iPhoneX = "iPhone X" + case iPhone8Plus = "iPhone 8 Plus" + case iPhone8 = "iPhone 8" + case iPadPro2129Inch = "iPad Pro 2 12.9inch" + case iPadPro2105Inch = "iPad Pro 2 10.5inch" + case iPad5 = "iPad 5" + case iPhone7Plus = "iPhone 7 Plus" + case iPhone7 = "iPhone 7" + case iPhoneSe1 = "iPhone SE 1" + case iPadPro197Inch = "iPad Pro 1 9.7inch" + case iPadPro1129Inch = "iPad Pro 1 12.9inch" + case iPhone6SPlus = "iPhone 6s Plus" + case iPhone6S = "iPhone 6s" + case iPadMini4 = "iPad mini 4" + case iPodTouch6 = "iPod touch 6" + case iPadAir2 = "iPad Air 2" + case iPadMini3 = "iPad mini 3" + case iPhone6Plus = "iPhone 6 Plus" + case iPhone6 = "iPhone 6" + case iPadMini2 = "iPad mini 2" + case iPadAir1 = "iPad Air 1" + case iPhone5C = "iPhone 5C" + case iPhone5S = "iPhone 5S" + case iPad4 = "iPad 4" + case iPodTouch5 = "iPod touch 5" + case iPhone5 = "iPhone 5" + case iPad3 = "iPad 3" + case iPhone4S = "iPhone 4S" + case iPad2 = "iPad 2" + case iPodTouch4 = "iPod touch 4" + case iPhone4 = "iPhone 4" + + var aspectRatio: Double { + DEVICE_SIZES[rawValue]!.aspectRatio + } + +} + +enum PaperSize: String, Codable, Sendable, CaseIterable { + case a0 = "A0" + case a1 = "A1" + case a2 = "A2" + case a3 = "A3" + case a4 = "A4" + case a5 = "A5" + case a6 = "A6" + case a7 = "A7" + case a8 = "A8" + case a9 = "A9" + case a10 = "A10" + case a11 = "A11" + case a12 = "A12" + case a13 = "A13" + case _2A0 = "2A0" + case _4A0 = "4A0" + case a0plus = "A0+" + case a1plus = "A1+" + case a3plus = "A3+" + case b0 = "B0" + case b1 = "B1" + case b2 = "B2" + case b3 = "B3" + case b4 = "B4" + case b5 = "B5" + case b6 = "B6" + case b7 = "B7" + case b8 = "B8" + case b9 = "B9" + case b10 = "B10" + case b11 = "B11" + case b12 = "B12" + case b13 = "B13" + case b0plus = "B0+" + case b1plus = "B1+" + case b2plus = "B2+" + case letter = "Letter" + case legal = "Legal" + case tabloid = "Tabloid" + case ledger = "Ledger" + case juniorLegal = "Junior Legal" + case halfLetter = "Half Letter" + case governmentLetter = "Government Letter" + case governmentLegal = "Government Legal" + case ansiA = "ANSI A" + case ansiB = "ANSI B" + case ansiC = "ANSI C" + case ansiD = "ANSI D" + case ansiE = "ANSI E" + case archA = "Arch A" + case archB = "Arch B" + case archC = "Arch C" + case archD = "Arch D" + case archE = "Arch E" + case archE1 = "Arch E1" + case archE2 = "Arch E2" + case archE3 = "Arch E3" + case passport = "Passport" + case _2R = "2R" + case ldDsc = "LD, DSC" + case _3RL = "3R, L" + case lw = "LW" + case kgd = "KGD" + case _4RKg = "4R, KG" + case _2LdDscw = "2LD, DSCW" + case _5R2L = "5R, 2L" + case _2Lw = "2LW" + case _6R = "6R" + case _8R6P = "8R, 6P" + case s8R6Pw = "S8R, 6PW" + case _11R = "11R" + case a3SuperB = "A3+ Super B" + case berliner = "Berliner" + case broadsheet = "Broadsheet" + case usBroadsheet = "US Broadsheet" + case britishBroadsheet = "British Broadsheet" + case southAfricanBroadsheet = "South African Broadsheet" + case ciner = "Ciner" + case compact = "Compact" + case nordisch = "Nordisch" + case rhenish = "Rhenish" + case swiss = "Swiss" + case newspaperTabloid = "Newspaper Tabloid" + case canadianTabloid = "Canadian Tabloid" + case norwegianTabloid = "Norwegian Tabloid" + case newYorkTimes = "New York Times" + case wallStreetJournal = "Wall Street Journal" + case folio = "Folio" + case quarto = "Quarto" + case imperialOctavo = "Imperial Octavo" + case superOctavo = "Super Octavo" + case royalOctavo = "Royal Octavo" + case mediumOctavo = "Medium Octavo" + case octavo = "Octavo" + case crownOctavo = "Crown Octavo" + case _12Mo = "12mo" + case _16Mo = "16mo" + case _18Mo = "18mo" + case _32Mo = "32mo" + case _48Mo = "48mo" + case _64Mo = "64mo" + case aFormat = "A Format" + case bFormat = "B Format" + case cFormat = "C Format" + + var aspectRatio: Double { + PAPER_SIZES[rawValue]!.aspectRatio + } + +}