From 607dd32d478a2012253c38abecbe6fad71817cec Mon Sep 17 00:00:00 2001 From: Darren Ford Date: Mon, 16 Dec 2024 12:34:02 +1100 Subject: [PATCH] Replaced 'isFlipped' settings key with 'flip' This provides more flexibility for shapes that support flipping, as it supports horizontally, vertically and both There was no point having two different flip settings. Because of this, I've removed 'roundedPointingOut' because the same shape can be generated using 'roundedPointingIn' and flipping both axes --- ..._roundedouter.png => eye_roundedOuter.png} | Bin ...intingin.png => eye_roundedPointingIn.png} | Bin Art/images/eye_roundedPointingOut.png | Bin 1434 -> 0 bytes .../{pupil_Circle.png => pupil_circle.png} | Bin Art/images/{pupil_Leaf.png => pupil_leaf.png} | Bin ...oundedOuter.png => pupil_roundedOuter.png} | Bin ...tingIn.png => pupil_roundedPointingIn.png} | Bin Art/images/pupil_roundedPointingOut.png | Bin 730 -> 0 bytes ...{pupil_Squircle.png => pupil_squircle.png} | Bin DSF_QRCode.podspec | 2 +- .../Cocoapods Mac Test/ContentView.swift | 6 +- .../Cocoapods Test/ContentView.swift | 6 +- Demo/Cocoapods Test/Podfile.lock | 10 +- .../QRCode 3D AppKit/EyeStylesView.swift | 36 ++-- .../QRCode 3D AppKit/PupilStylesView.swift | 32 +-- .../QRCodeView Demo/ViewController.swift | 2 +- .../QRCode 3D/settings/EyeSettingsView.swift | 16 +- .../settings/PupilSettingsView.swift | 19 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../shape-configuration/eye-styles.md | 9 +- .../shape-configuration/pupil-styles.md | 3 +- README.md | 27 +-- Sources/QRCode/QRCode+Keys.swift | 6 +- Sources/QRCode/QRCode+Types.swift | 10 + .../QRCode/styles/eye/QRCodeEyeShapeEye.swift | 55 ++++-- .../styles/eye/QRCodeEyeShapeFactory.swift | 3 +- .../styles/eye/QRCodeEyeShapeHeadlight.swift | 99 ++++++++-- .../eye/QRCodeEyeShapeRoundedPointingIn.swift | 176 ++++++++++++----- .../QRCodeEyeShapeRoundedPointingOut.swift | 89 --------- .../styles/eye/QRCodeEyeShapeTeardrop.swift | 159 +++++++++++---- .../QRCode/styles/eye/QRCodeEyeShapeUFO.swift | 186 +++++++++++------- .../styles/pupil/QRCodePupilShapeBlobby.swift | 81 ++++---- .../styles/pupil/QRCodePupilShapeCRT.swift | 33 ++-- .../pupil/QRCodePupilShapeCrossCurved.swift | 47 ++--- .../pupil/QRCodePupilShapeExplode.swift | 19 +- .../pupil/QRCodePupilShapeFactory.swift | 1 - .../styles/pupil/QRCodePupilShapeForest.swift | 4 +- .../pupil/QRCodePupilShapeHexagonLeaf.swift | 93 +++++---- .../styles/pupil/QRCodePupilShapeLeaf.swift | 78 +++++--- .../pupil/QRCodePupilShapeRoundedOuter.swift | 2 +- .../QRCodePupilShapeRoundedPointing.swift | 123 ++++++++++++ .../QRCodePupilShapeRoundedPointingIn.swift | 63 ------ .../QRCodePupilShapeRoundedPointingOut.swift | 63 ------ .../styles/pupil/QRCodePupilShapeUFO.swift | 115 ++++++----- Tests/QRCodeTests/QRCodeDocGenerator.swift | 111 ++++++++++- Tests/QRCodeTests/QRCodeEyeShapeTests.swift | 12 +- 46 files changed, 1109 insertions(+), 691 deletions(-) rename Art/images/{eye_roundedouter.png => eye_roundedOuter.png} (100%) rename Art/images/{eye_roundedpointingin.png => eye_roundedPointingIn.png} (100%) delete mode 100644 Art/images/eye_roundedPointingOut.png rename Art/images/{pupil_Circle.png => pupil_circle.png} (100%) rename Art/images/{pupil_Leaf.png => pupil_leaf.png} (100%) rename Art/images/{pupil_RoundedOuter.png => pupil_roundedOuter.png} (100%) rename Art/images/{pupil_RoundedPointingIn.png => pupil_roundedPointingIn.png} (100%) delete mode 100644 Art/images/pupil_roundedPointingOut.png rename Art/images/{pupil_Squircle.png => pupil_squircle.png} (100%) delete mode 100644 Sources/QRCode/styles/eye/QRCodeEyeShapeRoundedPointingOut.swift create mode 100644 Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointing.swift delete mode 100644 Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointingIn.swift delete mode 100644 Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointingOut.swift diff --git a/Art/images/eye_roundedouter.png b/Art/images/eye_roundedOuter.png similarity index 100% rename from Art/images/eye_roundedouter.png rename to Art/images/eye_roundedOuter.png diff --git a/Art/images/eye_roundedpointingin.png b/Art/images/eye_roundedPointingIn.png similarity index 100% rename from Art/images/eye_roundedpointingin.png rename to Art/images/eye_roundedPointingIn.png diff --git a/Art/images/eye_roundedPointingOut.png b/Art/images/eye_roundedPointingOut.png deleted file mode 100644 index 573193a88dc562c705cf8f93bec36d3f69160cd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1434 zcma)+dpOez7{_O}874Zpl~^7*c!XulWtwR&b>tGK45?qqiCL)11#OZx`4L%@*i*S+ znXf%kb8^*GS@0WGX@pedK7r-8-7)fuIl=89-OH5CPV-Tr}*C`y!+iPp4U z-6S1;{Z;XZCkL$Ecnx|8P*5fQYdb5h(N27y6qSjb?S(Z~?=&|rimqjnitMs-z_1%L zWl{s;xeFOh z-I7g6rWT)bCc8B|QJlQH!|yVmbIBXeuTBJb)+3sal){xewszFu9#0>_hgHUoxu60n zEl1-uGAc+Ng=D~zAiSR^rXHjebiyV3DaHmN5F}n$nkSZ^C^0*$VAJ6EpB_WQ&qv&~oZ7)-Q25k)?)EU!IhJYWyV=YFaNd)*K0O#QlWRT3^rlzgMsQ>aC%pp!I_*x_XWT(0w*Om31KnG;lyvRVla zGar{#@!8rs!{C4lXHe&@V!OX>KfN!6QJ@Hh(=DB%ZT$@!FLUKna~MMUx^>QnV#_UH zbDFT9%rehOS^gIC>CJ(yP{yboaxm+PwN>9<)5+fw@cF8~GG1SJvOCe_hLQLS|o9qoEy9R_sRRf-xQP#Zjq# zAOszRLasE{ZYt7Y>=sybQ1sz+R9fK;tuUV#M9yQ{4d~F6wI!+@n{8mn^6ZC8gih&i zH}$8Xcb#iS+0|Khn#eEX6w>`Z7c3~3EYi)MO^u(Vo+R6zQZai*tn)P#c%lMD2o3-4 za9X|^lhJ`I#XmFtnZG@PuPpuepl{jr;>2>FA1xd_^fk4$3xzJa-dz}qR-KHvZKD&} zahe|Fa0K@*>-^2DgW%t_T5bmOlM@{kuXR&(7?zH zWlZKm;ljm%j6X%;o8H=0JbLu^yOs(+Y&%6HFG?mw%mW?!mpbk)i! zXA{f(azQeJE2oT2Omg;19-X`Lt^O1I>E}dV#!Z=F`IY4-Vfu;Jv3& zy^@*#1KYb-ymwe}x);r7$X>xC^(Pz=7zi!$Mttu?O@B-({IFof!T#Q6cUNA&-}lWb zs;(rgO_1KBS5%OG`@!Ui&FMGU_V>xuoZGU!!;R1HY`WwfrK$~P+j-`RGVD2J^dpSn z!*Q9iy{RS)5z)6;{kfRzlEnXLa9FIK6q+y1%XIB(*g=N}ekr@+kg)i?+|T*! z%-P1>50*;t{aG~Osi-!?jRypX;cd|MkA`*}D7db7$MvdCbp?Wx8H{ o=8bH5kZE 'MIT', :file => 'LICENSE' } diff --git a/Demo/Cocoapods Test/Cocoapods Mac Test/ContentView.swift b/Demo/Cocoapods Test/Cocoapods Mac Test/ContentView.swift index 7c331e3c..6263df92 100644 --- a/Demo/Cocoapods Test/Cocoapods Mac Test/ContentView.swift +++ b/Demo/Cocoapods Test/Cocoapods Mac Test/ContentView.swift @@ -11,7 +11,7 @@ import QRCode struct ContentView: View { @State var content = "Hello, world!" - let style1 = QRCode.PixelShape.RoundedPath(cornerRadiusFraction: 0.8, hasInnerCorners: true) + let pixelShape = QRCode.PixelShape.RoundedPath(cornerRadiusFraction: 0.8, hasInnerCorners: true) var body: some View { VStack { @@ -20,9 +20,9 @@ struct ContentView: View { Divider() QRCodeViewUI( content: content, - pixelStyle: style1, additionalQuietZonePixels: 3, - backgroundFractionalCornerRadius: 2 + backgroundFractionalCornerRadius: 2, + onPixelShape: pixelShape ) .frame(minWidth: 300, minHeight: 300) } diff --git a/Demo/Cocoapods Test/Cocoapods Test/ContentView.swift b/Demo/Cocoapods Test/Cocoapods Test/ContentView.swift index c7708b7d..6fe4fd80 100644 --- a/Demo/Cocoapods Test/Cocoapods Test/ContentView.swift +++ b/Demo/Cocoapods Test/Cocoapods Test/ContentView.swift @@ -12,7 +12,7 @@ import QRCode struct ContentView: View { @State var content = "Hello, world!" - let style1 = QRCode.PixelShape.RoundedPath(cornerRadiusFraction: 0.8, hasInnerCorners: true) + let pixelShape = QRCode.PixelShape.RoundedPath(cornerRadiusFraction: 0.8, hasInnerCorners: true) var body: some View { VStack { @@ -21,9 +21,9 @@ struct ContentView: View { Divider() QRCodeViewUI( content: content, - pixelStyle: style1, additionalQuietZonePixels: 3, - backgroundFractionalCornerRadius: 2 + backgroundFractionalCornerRadius: 2, + onPixelShape: pixelShape ) .frame(minWidth: 300, minHeight: 300) } diff --git a/Demo/Cocoapods Test/Podfile.lock b/Demo/Cocoapods Test/Podfile.lock index 50879a04..718b3594 100644 --- a/Demo/Cocoapods Test/Podfile.lock +++ b/Demo/Cocoapods Test/Podfile.lock @@ -1,8 +1,8 @@ PODS: - - DSF_QRCode (20.4.1): + - DSF_QRCode (23.0.0): - SwiftImageReadWrite (~> 1.9.1) - SwiftQRCodeGenerator (~> 2.0.2) - - SwiftImageReadWrite (1.9.1) + - SwiftImageReadWrite (1.9.2) - SwiftQRCodeGenerator (2.0.2) DEPENDENCIES: @@ -18,10 +18,10 @@ EXTERNAL SOURCES: :path: "../../" SPEC CHECKSUMS: - DSF_QRCode: 7a8a264f95ad20544c87d60cf7793f1f699fe8fa - SwiftImageReadWrite: b06cb9bcdc56877c555fbb31bbfca98420982531 + DSF_QRCode: 5a85047ccf37fca1cf6c57333131d18d9c8d23f8 + SwiftImageReadWrite: f58e807c5e3e211b788b5e058c4da464096ed430 SwiftQRCodeGenerator: cc02fed209335064d0b0dd61b2a0874b9bc6bd5a PODFILE CHECKSUM: 8bc17f7e1b88bd77e3f248afa8c852e64fc65d8d -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/Demo/QRCodeView Demo/macOS/QRCode 3D AppKit/EyeStylesView.swift b/Demo/QRCodeView Demo/macOS/QRCode 3D AppKit/EyeStylesView.swift index 8ea79557..81b7b552 100644 --- a/Demo/QRCodeView Demo/macOS/QRCode 3D AppKit/EyeStylesView.swift +++ b/Demo/QRCodeView Demo/macOS/QRCode 3D AppKit/EyeStylesView.swift @@ -11,9 +11,12 @@ import AppKit import DSFAppKitBuilder import QRCode import DSFValueBinders +import DSFMenuBuilder class EyeStylesView: Element { + let flipTypes = ValueBinder(["none", "horizontally", "vertically", "both"]) + init(qrCode: Observable, _ updateBlock: @escaping () -> Void ) { self.qrCodeObject = qrCode self.updateBlock = updateBlock @@ -63,7 +66,7 @@ class EyeStylesView: Element { HDivider() - VStack(spacing: 4) { + VStack(spacing: 8) { HStack { Label("Radius:").font(.callout) @@ -75,16 +78,22 @@ class EyeStylesView: Element { self?.update() } } + HStack { - Label("Flipped:").font(.callout) - Toggle() - .controlSize(.small) - .bindOnOff(eyeFlipped) - .bindIsEnabled(eyeFlippedEnabled) - .onChange(of: eyeFlipped) { [weak self] newValue in - _ = self?.qrCode.design.shape.eye.setSettingValue(newValue, forKey: QRCode.SettingsKey.isFlipped) - self?.update() - } + Label("Flip:").font(.callout) + PopupButton { + MenuItem("none") + MenuItem("horizontally") + MenuItem("vertically") + MenuItem("both") + } + .bindSelection(eyeFlipped) + .bindIsEnabled(eyeFlippedEnabled) + .onChange { [weak self] popupIndex in + let newVal = QRCode.Flip(rawValue: popupIndex) ?? .none + _ = self?.qrCode.design.shape.eye.setSettingValue(newVal.rawValue, forKey: QRCode.SettingsKey.flip) + self?.update() + } EmptyView() } @@ -101,7 +110,6 @@ class EyeStylesView: Element { .bindIsEnabled(eyeSelectedCornersEnabled) EmptyView() } - .contentHugging(h: 1) } } .edgeInsets(top: 4, left: 4, bottom: 4, right: 4) @@ -111,8 +119,8 @@ class EyeStylesView: Element { self.eyeCornerRadiusEnabled.wrappedValue = item.supportsSettingValue(forKey: QRCode.SettingsKey.cornerRadiusFraction) self.eyeCornerRadius.wrappedValue = item.settingsValue(forKey: QRCode.SettingsKey.cornerRadiusFraction) ?? 0.65 - self.eyeFlippedEnabled.wrappedValue = item.supportsSettingValue(forKey: QRCode.SettingsKey.isFlipped) - self.eyeFlipped.wrappedValue = item.settingsValue(forKey: QRCode.SettingsKey.isFlipped) ?? false + self.eyeFlippedEnabled.wrappedValue = item.supportsSettingValue(forKey: QRCode.SettingsKey.flip) + self.eyeFlipped.wrappedValue = item.settingsValue(forKey: QRCode.SettingsKey.flip) ?? 0 self.eyeSelectedCornersEnabled.wrappedValue = item.supportsSettingValue(forKey: QRCode.SettingsKey.corners) if let value: Int = item.settingsValue(forKey: QRCode.SettingsKey.corners) { @@ -147,7 +155,7 @@ class EyeStylesView: Element { private lazy var eyeCornerRadius = ValueBinder(0.65) private var eyeCornerRadiusEnabled = ValueBinder(false) - private lazy var eyeFlipped = ValueBinder(false) + private lazy var eyeFlipped = ValueBinder(0) private var eyeFlippedEnabled = ValueBinder(false) private lazy var eyeSelectedCorners = ValueBinder(NSSet()) { newValue in diff --git a/Demo/QRCodeView Demo/macOS/QRCode 3D AppKit/PupilStylesView.swift b/Demo/QRCodeView Demo/macOS/QRCode 3D AppKit/PupilStylesView.swift index 7751f100..f6ef7790 100644 --- a/Demo/QRCodeView Demo/macOS/QRCode 3D AppKit/PupilStylesView.swift +++ b/Demo/QRCodeView Demo/macOS/QRCode 3D AppKit/PupilStylesView.swift @@ -11,6 +11,7 @@ import AppKit import DSFAppKitBuilder import QRCode import DSFValueBinders +import DSFMenuBuilder class PupilStylesView: Element { @@ -75,17 +76,22 @@ class PupilStylesView: Element { } HStack { - Label("Flipped:").font(.callout) - Toggle() - .controlSize(.small) - .bindOnOff(pupilFlipped) - .bindIsEnabled(pupilFlippedEnabled) + Label("Flip:").font(.callout) + PopupButton { + MenuItem("none") + MenuItem("horizontally") + MenuItem("vertically") + MenuItem("both") + } + .bindSelection(pupilFlipped) + .bindIsEnabled(pupilFlippedEnabled) + .onChange { [weak self] popupIndex in + let newVal = QRCode.Flip(rawValue: popupIndex) ?? .none + _ = self?.qrCode.design.shape.pupil?.setSettingValue(newVal.rawValue, forKey: QRCode.SettingsKey.flip) + self?.update() + } EmptyView() } - .onChange(of: pupilFlipped) { [weak self] newValue in - _ = self?.qrCode.design.shape.pupil?.setSettingValue(newValue, forKey: QRCode.SettingsKey.isFlipped) - self?.update() - } HStack { Label("Inner corners:").font(.callout) @@ -115,12 +121,14 @@ class PupilStylesView: Element { } } } + .hugging(h: 1) .edgeInsets(top: 4, left: 4, bottom: 4, right: 4) private func sync() { let item = qrCode.design.shape.actualPupilShape - self.pupilFlippedEnabled.wrappedValue = item.supportsSettingValue(forKey: QRCode.SettingsKey.isFlipped) - self.pupilFlipped.wrappedValue = item.settingsValue(forKey: QRCode.SettingsKey.isFlipped) ?? false + + self.pupilFlippedEnabled.wrappedValue = item.supportsSettingValue(forKey: QRCode.SettingsKey.flip) + self.pupilFlipped.wrappedValue = item.settingsValue(forKey: QRCode.SettingsKey.flip) ?? 0 self.pupilHasInnerCornersEnabled.wrappedValue = item.supportsSettingValue(forKey: QRCode.SettingsKey.hasInnerCorners) self.pupilHasInnerCorners.wrappedValue = item.settingsValue(forKey: QRCode.SettingsKey.hasInnerCorners) ?? false @@ -160,7 +168,7 @@ class PupilStylesView: Element { private lazy var pupilSelection: ValueBinder = ValueBinder(0) - private lazy var pupilFlipped = ValueBinder(false) + private lazy var pupilFlipped = ValueBinder(0) private var pupilFlippedEnabled = ValueBinder(false) private lazy var pupilHasInnerCorners = ValueBinder(false) diff --git a/Demo/QRCodeView Demo/macOS/QRCodeView Demo/ViewController.swift b/Demo/QRCodeView Demo/macOS/QRCodeView Demo/ViewController.swift index 6988d685..6f7171f9 100644 --- a/Demo/QRCodeView Demo/macOS/QRCodeView Demo/ViewController.swift +++ b/Demo/QRCodeView Demo/macOS/QRCodeView Demo/ViewController.swift @@ -25,7 +25,7 @@ class ViewController: NSViewController { q2.rebuildQRCode() q3.design.shape.onPixels = QRCode.PixelShape.RoundedPath() - q3.design.shape.eye = QRCode.EyeShape.RoundedPointingIn() + q3.design.shape.eye = QRCode.EyeShape.RoundedPointingIn() q3.rebuildQRCode() let gr = QRCode.FillStyle.RadialGradient( diff --git a/Demo/QRCodeView Demo/swiftui/QRCode 3D/settings/EyeSettingsView.swift b/Demo/QRCodeView Demo/swiftui/QRCode 3D/settings/EyeSettingsView.swift index 984cb6a4..9f09cc3d 100644 --- a/Demo/QRCodeView Demo/swiftui/QRCode 3D/settings/EyeSettingsView.swift +++ b/Demo/QRCodeView Demo/swiftui/QRCode 3D/settings/EyeSettingsView.swift @@ -43,7 +43,7 @@ struct EyeSettingsView: View { @State var radiusFraction: CGFloat = 0 @State var supportsRadiusFraction = false - @State var flipped = false + @State var flipped = QRCode.Flip.none @State var supportsFlipped = false @State var corners = QRCode.Corners.all @@ -85,12 +85,15 @@ struct EyeSettingsView: View { document.objectWillChange.send() } - Toggle(isOn: $flipped) { - Text("flipped") + Picker("flip", selection: $flipped) { + Text("none").tag(QRCode.Flip.none) + Text("horizontally").tag(QRCode.Flip.horizontally) + Text("vertically").tag(QRCode.Flip.vertically) + Text("both").tag(QRCode.Flip.both) } .disabled(!supportsFlipped) .onChange(of: flipped) { newValue in - _ = document.qrcode.design.shape.eye.setSettingValue(newValue, forKey: QRCode.SettingsKey.isFlipped) + _ = document.qrcode.design.shape.eye.setSettingValue(newValue.rawValue, forKey: QRCode.SettingsKey.flip) document.objectWillChange.send() } @@ -116,8 +119,9 @@ struct EyeSettingsView: View { supportsRadiusFraction = generator.supportsSettingValue(forKey: QRCode.SettingsKey.cornerRadiusFraction) radiusFraction = generator.settingsValue(forKey: QRCode.SettingsKey.cornerRadiusFraction) ?? 0 - supportsFlipped = generator.supportsSettingValue(forKey: QRCode.SettingsKey.isFlipped) - flipped = generator.settingsValue(forKey: QRCode.SettingsKey.isFlipped) ?? false + supportsFlipped = generator.supportsSettingValue(forKey: QRCode.SettingsKey.flip) + let fVal = generator.settingsValue(forKey: QRCode.SettingsKey.flip) ?? 0 + flipped = QRCode.Flip(rawValue: fVal) ?? .none supportsCorners = generator.supportsSettingValue(forKey: QRCode.SettingsKey.corners) let c: Int = generator.settingsValue(forKey: QRCode.SettingsKey.corners) ?? 0 diff --git a/Demo/QRCodeView Demo/swiftui/QRCode 3D/settings/PupilSettingsView.swift b/Demo/QRCodeView Demo/swiftui/QRCode 3D/settings/PupilSettingsView.swift index 04a6ed8f..4fc9edc8 100644 --- a/Demo/QRCodeView Demo/swiftui/QRCode 3D/settings/PupilSettingsView.swift +++ b/Demo/QRCodeView Demo/swiftui/QRCode 3D/settings/PupilSettingsView.swift @@ -34,7 +34,7 @@ struct PupilSettingsView: View { @EnvironmentObject var document: QRCode_3DDocument @State var generator: QRCodePupilShapeGenerator = QRCode.PupilShape.Square() - @State var flipped = false + @State var flipped: QRCode.Flip = .none @State var supportsFlipped = false @State var hasInnerCorners = false @@ -63,20 +63,22 @@ struct PupilSettingsView: View { Divider() Form { - //VStack(alignment: .leading, spacing: 6) { LabeledContent("style") { StyleSelectorView(current: document.qrcode.design.style.actualPupilStyle) { newFill in document.qrcode.design.style.pupil = newFill document.objectWillChange.send() } } - - Toggle(isOn: $flipped) { - Text("flipped") + + Picker("flip", selection: $flipped) { + Text("none").tag(QRCode.Flip.none) + Text("horizontally").tag(QRCode.Flip.horizontally) + Text("vertically").tag(QRCode.Flip.vertically) + Text("both").tag(QRCode.Flip.both) } .disabled(!supportsFlipped) .onChange(of: flipped) { newValue in - _ = document.qrcode.design.shape.pupil?.setSettingValue(newValue, forKey: QRCode.SettingsKey.isFlipped) + _ = document.qrcode.design.shape.pupil?.setSettingValue(newValue.rawValue, forKey: QRCode.SettingsKey.flip) document.objectWillChange.send() } @@ -109,8 +111,9 @@ struct PupilSettingsView: View { func sync() { generator = document.qrcode.design.shape.actualPupilShape - supportsFlipped = generator.supportsSettingValue(forKey: QRCode.SettingsKey.isFlipped) - flipped = generator.settingsValue(forKey: QRCode.SettingsKey.isFlipped) ?? false + supportsFlipped = generator.supportsSettingValue(forKey: QRCode.SettingsKey.flip) + let fVal = generator.settingsValue(forKey: QRCode.SettingsKey.flip) ?? 0 + flipped = QRCode.Flip(rawValue: fVal) ?? .none supportsHasInnerCorners = generator.supportsSettingValue(forKey: QRCode.SettingsKey.hasInnerCorners) hasInnerCorners = generator.settingsValue(forKey: QRCode.SettingsKey.hasInnerCorners) ?? false diff --git a/Demo/WatchQR/WatchQR.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Demo/WatchQR/WatchQR.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 017a9a0f..59b7dbec 100644 --- a/Demo/WatchQR/WatchQR.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Demo/WatchQR/WatchQR.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/dagronf/SwiftImageReadWrite", "state": { "branch": null, - "revision": "016a9cb842038e4078a91e506e35cef4f4ff76ff", - "version": "1.7.2" + "revision": "42ace2412279f18bc264bc306e96b51c36e12a33", + "version": "1.9.2" } } ] diff --git a/Documentation/shape-configuration/eye-styles.md b/Documentation/shape-configuration/eye-styles.md index 68dbbfc8..1f350fbd 100644 --- a/Documentation/shape-configuration/eye-styles.md +++ b/Documentation/shape-configuration/eye-styles.md @@ -11,16 +11,15 @@ | | __dotDragVertical__ | `QRCode.EyeShape.DotDragVertical` | _none_ | | | __edges__ | `QRCode.EyeShape.Edges` | • __Corner radius__
| | | __explode__ | `QRCode.EyeShape.Explode` | _none_ | -| | __eye__ | `QRCode.EyeShape.Eye` | • __Flippable__
| +| | __eye__ | `QRCode.EyeShape.Eye` | • __Flippable__
• __Configurable eye corners__
| | | __fireball__ | `QRCode.EyeShape.Fireball` | _none_ | -| | __headlight__ | `QRCode.EyeShape.Headlight` | _none_ | +| | __headlight__ | `QRCode.EyeShape.Headlight` | • __Flippable__
| | | __leaf__ | `QRCode.EyeShape.Leaf` | _none_ | | | __peacock__ | `QRCode.EyeShape.Peacock` | _none_ | | | __pinch__ | `QRCode.EyeShape.Pinch` | _none_ | | | __pixels__ | `QRCode.EyeShape.Pixels` | • __Corner radius__
| | | __roundedOuter__ | `QRCode.EyeShape.RoundedOuter` | • __Flippable__
| -| | __roundedPointingIn__ | `QRCode.EyeShape.RoundedPointingIn` | _none_ | -| | __roundedPointingOut__ | `QRCode.EyeShape.RoundedPointingOut` | _none_ | +| | __roundedPointingIn__ | `QRCode.EyeShape.RoundedPointingIn` | • __Flippable__
| | | __roundedRect__ | `QRCode.EyeShape.RoundedRect` | • __Corner radius__
| | | __shield__ | `QRCode.EyeShape.Shield` | • __Configurable corners__
| | | __spikyCircle__ | `QRCode.EyeShape.SpikyCircle` | _none_ | @@ -28,6 +27,6 @@ | | __squarePeg__ | `QRCode.EyeShape.SquarePeg` | _none_ | | | __squircle__ | `QRCode.EyeShape.Squircle` | _none_ | | | __surroundingBars__ | `QRCode.EyeShape.SurroundingBars` | _none_ | -| | __teardrop__ | `QRCode.EyeShape.Teardrop` | _none_ | +| | __teardrop__ | `QRCode.EyeShape.Teardrop` | • __Flippable__
| | | __ufo__ | `QRCode.EyeShape.UFO` | • __Flippable__
| | | __usePixelShape__ | `QRCode.EyeShape.UsePixelShape` | _none_ | diff --git a/Documentation/shape-configuration/pupil-styles.md b/Documentation/shape-configuration/pupil-styles.md index 539e9912..e071c8d8 100644 --- a/Documentation/shape-configuration/pupil-styles.md +++ b/Documentation/shape-configuration/pupil-styles.md @@ -24,8 +24,7 @@ | | __pinch__ | `QRCode.PixelShape.Pinch` | _none_ | | | __pixels__ | `QRCode.PixelShape.Pixels` | • __Corner radius__
| | | __roundedOuter__ | `QRCode.PixelShape.RoundedOuter` | • __Flippable__
| -| | __roundedPointingIn__ | `QRCode.PixelShape.RoundedPointingIn` | _none_ | -| | __roundedPointingOut__ | `QRCode.PixelShape.RoundedPointingOut` | _none_ | +| | __roundedPointingIn__ | `QRCode.PixelShape.RoundedPointingIn` | • __Flippable__
| | | __roundedRect__ | `QRCode.PixelShape.RoundedRect` | • __Corner radius__
| | | __seal__ | `QRCode.PixelShape.Seal` | _none_ | | | __shield__ | `QRCode.PixelShape.Shield` | • __Configurable corners__
| diff --git a/README.md b/README.md index 640ad648..10596052 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,7 @@ More details and configuration options about pixel styles [can be found here](./ You can provide an `EyeShape` object to style just the eyes of the generated qr code. There are built-in generators for square, circle, rounded rectangle, and more. - + More details and configuration options about eye styles [can be found here](./Documentation/shape-configuration/eye-styles.md) @@ -379,7 +379,7 @@ You can provide an override to the default `EyeShape` pupil shape to change just If you don't override the pupil shape, it defaults to the eye shape's pupil shape. - + More details and configuration options about eye styles [can be found here](./Documentation/shape-configuration/pupil-styles.md) @@ -1194,9 +1194,9 @@ OPTIONS: --all-pixel-shapes Print all the available pixel shapes. -d, --on-pixel-shape - The onPixels shape to use. Available shapes are arrow, blob, crt, circle, circuit, curvePixel, donut, flower, - heart, horizontal, pointy, razor, roundedEndIndent, roundedPath, roundedRect, sharp, shiny, square, squircle, - star, vertical, vortex, wave. + The onPixels shape to use. Available shapes are grid2x2, grid3x3, grid4x4, abstract, arrow, blob, crt, circle, + circuit, curvePixel, donut, flower, heart, horizontal, pointy, razor, roundedEndIndent, roundedPath, roundedRect, + sharp, shiny, spikyCircle, square, squircle, star, vertical, vortex, wave. -n, --on-pixel-inset-fraction The spacing around each individual pixel in the onPixels section -r, --on-pixel-shape-corner-radius @@ -1206,18 +1206,19 @@ OPTIONS: --all-eye-shapes Print all the available eye shapes. -e, --eye-shape - The eye shape to use. Available shapes are crt, circle, corneredPixels, edges, fireball, barsHorizontal, leaf, - peacock, pinch, pixels, roundedOuter, roundedPointingIn, roundedPointingOut, roundedRect, shield, square, - squarePeg, squircle, teardrop, ufo, usePixelShape, barsVertical. + The eye shape to use. Available shapes are crt, circle, corneredPixels, dotDragHorizontal, dotDragVertical, edges, + explode, eye, fireball, headlight, barsHorizontal, leaf, peacock, pinch, pixels, roundedPointingIn, roundedOuter, + roundedRect, shield, spikyCircle, square, squarePeg, squircle, surroundingBars, teardrop, ufo, usePixelShape, + barsVertical. --eye-shape-corner-radius The fractional (0 ... 1) corner radius to use for the eye shape IF the eye shape supports it. --all-pupil-shapes Print all the available pupil shapes. -p, --pupil-shape - The pupil shape to use. Available shapes are blobby, crt, circle, corneredPixels, cross, crossCurved, edges, - hexagonLeaf, barsHorizontal, leaf, pinch, pixels, roundedOuter, roundedPointingIn, roundedPointingOut, - roundedRect, seal, shield, square, barsHorizontalSquare, barsVerticalSquare, squircle, teardrop, ufo, - usePixelShape, barsVertical. + The pupil shape to use. Available shapes are blade, blobby, crt, circle, corneredPixels, cross, crossCurved, + dotDragHorizontal, dotDragVertical, edges, explode, forest, hexagonLeaf, barsHorizontal, leaf, orbits, pinch, pixels, + roundedPointingIn, roundedOuter, roundedRect, seal, shield, spikyCircle, square, barsHorizontalSquare, + barsVerticalSquare, squircle, teardrop, ufo, usePixelShape, barsVertical. --pupil-shape-corner-radius The fractional (0 ... 1) corner radius to apply to the pupil shape IF the pupil shape supports it. --bg-color The background color to use (format r,g,b,a - 1.0,0.5,0.5,1.0) @@ -1227,7 +1228,7 @@ OPTIONS: --pupil-color The pupil color to use (format r,g,b,a - 1.0,0.5,0.5,1.0) -h, --help Show help information. - ``` +``` ### Example diff --git a/Sources/QRCode/QRCode+Keys.swift b/Sources/QRCode/QRCode+Keys.swift index 10a2d1d4..6ab74cec 100644 --- a/Sources/QRCode/QRCode+Keys.swift +++ b/Sources/QRCode/QRCode+Keys.swift @@ -46,14 +46,14 @@ public extension QRCode { /// Settings key for 'additionalQuietSpace' @objc public static let additionalQuietZonePixels = "additionalQuietZonePixels" - /// Settings key for 'isFlipped' - @objc public static let isFlipped = "isFlipped" +// /// Settings key for 'isFlipped' +// @objc public static let isFlipped = "isFlipped" /// Settings for eye inner style @objc public static let eyeInnerStyle = "eyeInnerStyle" /// Settings key for 'Flip' (QRCode.Flip) - @objc public static let flip = "Flip" + @objc public static let flip = "flip" } } diff --git a/Sources/QRCode/QRCode+Types.swift b/Sources/QRCode/QRCode+Types.swift index 5300295a..7cdee3a1 100644 --- a/Sources/QRCode/QRCode+Types.swift +++ b/Sources/QRCode/QRCode+Types.swift @@ -53,5 +53,15 @@ public extension QRCode { case vertically = 2 /// Flip along both axes case both = 3 + + /// String representation + var name: String { + switch self { + case .none: return "none" + case .horizontally: return "horizontally" + case .vertically: return "vertically" + case .both: return "both" + } + } } } diff --git a/Sources/QRCode/styles/eye/QRCodeEyeShapeEye.swift b/Sources/QRCode/styles/eye/QRCodeEyeShapeEye.swift index f7e83e74..8c816b54 100644 --- a/Sources/QRCode/styles/eye/QRCodeEyeShapeEye.swift +++ b/Sources/QRCode/styles/eye/QRCodeEyeShapeEye.swift @@ -43,8 +43,8 @@ public extension QRCode.EyeShape { QRCode.EyeShape.Eye(settings: settings) } - /// Is the eye shape vertically flipped? - @objc public var isFlipped: Bool = false + /// Is the eye shape flipped + @objc public var flip: QRCode.Flip = .none /// The inner curve state for the eye @objc public var eyeInnerStyle: Style = .both @@ -53,8 +53,8 @@ public extension QRCode.EyeShape { /// - Parameters: /// - isFlipped: If true, flip the eye on the horizontal axis /// - eyeInnerStyle: The eye's inner style - @objc public init(isFlipped: Bool = false, eyeInnerStyle: Style = .both) { - self.isFlipped = isFlipped + @objc public init(flip: QRCode.Flip = .none, eyeInnerStyle: Style = .both) { + self.flip = flip self.eyeInnerStyle = eyeInnerStyle super.init() } @@ -74,7 +74,7 @@ public extension QRCode.EyeShape { /// Reset the eye shape generator back to defaults @objc public func reset() { - self.isFlipped = false + self.flip = .none self.eyeInnerStyle = .both } @@ -88,25 +88,44 @@ public extension QRCode.EyeShape { } }() - if self.isFlipped { - return CGPath.make { n in + switch self.flip { + case .none: + return core + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(core, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in n.addPath(core, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) - n.closeSubpath() + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(core, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) } } - return core } /// The eye's background path @objc public func eyeBackgroundPath() -> CGPath { let core = self.fullBackground() - if self.isFlipped == false { - return CGPath.make { n in + + switch self.flip { + case .none: + return core + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(core, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in n.addPath(core, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) - n.closeSubpath() + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(core, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) } } - return core } private static let _defaultPupil = QRCode.PupilShape.Circle() @@ -118,17 +137,18 @@ public extension QRCode.EyeShape { public extension QRCode.EyeShape.Eye { @objc func settings() -> [String: Any] { [ - QRCode.SettingsKey.isFlipped: self.isFlipped, + QRCode.SettingsKey.flip: self.flip.rawValue, QRCode.SettingsKey.eyeInnerStyle: self.eyeInnerStyle.rawValue, ] } @objc func supportsSettingValue(forKey key: String) -> Bool { - key == QRCode.SettingsKey.isFlipped || key == QRCode.SettingsKey.eyeInnerStyle + key == QRCode.SettingsKey.flip || key == QRCode.SettingsKey.eyeInnerStyle } @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { - if key == QRCode.SettingsKey.isFlipped { - self.isFlipped = BoolValue(value) ?? false + if key == QRCode.SettingsKey.flip, + let value = IntValue(value) { + self.flip = QRCode.Flip(rawValue: value) ?? .none } else if key == QRCode.SettingsKey.eyeInnerStyle { let value = IntValue(value) ?? 0 @@ -218,6 +238,7 @@ private extension QRCode.EyeShape.Eye { safeZonePath.line(to: CGPoint(x: 90, y: 90)) safeZonePath.close() } + .applyingTransform(CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) } } diff --git a/Sources/QRCode/styles/eye/QRCodeEyeShapeFactory.swift b/Sources/QRCode/styles/eye/QRCodeEyeShapeFactory.swift index 1c469c8a..d5a887fa 100644 --- a/Sources/QRCode/styles/eye/QRCodeEyeShapeFactory.swift +++ b/Sources/QRCode/styles/eye/QRCodeEyeShapeFactory.swift @@ -68,7 +68,6 @@ import Foundation QRCode.EyeShape.Circle.self, QRCode.EyeShape.Edges.self, QRCode.EyeShape.RoundedRect.self, - QRCode.EyeShape.RoundedPointingIn.self, QRCode.EyeShape.Squircle.self, QRCode.EyeShape.RoundedOuter.self, QRCode.EyeShape.Square.self, @@ -77,7 +76,7 @@ import Foundation QRCode.EyeShape.BarsHorizontal.self, QRCode.EyeShape.Pixels.self, QRCode.EyeShape.CorneredPixels.self, - QRCode.EyeShape.RoundedPointingOut.self, + QRCode.EyeShape.RoundedPointingIn.self, QRCode.EyeShape.Shield.self, QRCode.EyeShape.UsePixelShape.self, QRCode.EyeShape.Teardrop.self, diff --git a/Sources/QRCode/styles/eye/QRCodeEyeShapeHeadlight.swift b/Sources/QRCode/styles/eye/QRCodeEyeShapeHeadlight.swift index febbcb06..228ef04e 100644 --- a/Sources/QRCode/styles/eye/QRCodeEyeShapeHeadlight.swift +++ b/Sources/QRCode/styles/eye/QRCodeEyeShapeHeadlight.swift @@ -25,30 +25,107 @@ public extension QRCode.EyeShape { @objc(QRCodeEyeShapeHeadlight) class Headlight: NSObject, QRCodeEyeShapeGenerator { @objc public static let Name = "headlight" @objc public static var Title: String { "Headlight" } - @objc public static func Create(_ settings: [String: Any]?) -> any QRCodeEyeShapeGenerator { Headlight() } + @objc public static func Create(_ settings: [String: Any]?) -> any QRCodeEyeShapeGenerator { + Headlight(settings: settings) + } + + /// Flip the eye shape + @objc public var flip: QRCode.Flip = .none + + /// Create a headlight eye generator + @objc public init(flip: QRCode.Flip = .none) { + self.flip = flip + super.init() + } + + @objc public init(settings: [String: Any]?) { + super.init() + settings?.forEach { (key: String, value: Any) in + _ = self.setSettingValue(value, forKey: key) + } + } /// Make a copy of the object @objc public func copyShape() -> any QRCodeEyeShapeGenerator { Headlight() } /// Reset the eye shape generator back to defaults - @objc public func reset() { } - - // Has no configurable settings - @objc public func settings() -> [String: Any] { return [:] } - @objc public func supportsSettingValue(forKey key: String) -> Bool { false } - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { false } + @objc public func reset() { + self.flip = .none + } /// The eye path - @objc public func eyePath() -> CGPath { eyeShape__ } + @objc public func eyePath() -> CGPath { + switch self.flip { + case .none: + return eyePath__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyePath__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyePath__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyePath__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } + } + } /// The background path for the eye - @objc public func eyeBackgroundPath() -> CGPath { eyeBackgroundShape__ } + public func eyeBackgroundPath() -> CGPath { + switch self.flip { + case .none: + return eyeBackgroundPath__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundPath__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundPath__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundPath__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } + } + } private static let _defaultPupil = QRCode.PupilShape.Circle() public func defaultPupil() -> any QRCodePupilShapeGenerator { Self._defaultPupil } } } -private let eyeBackgroundShape__: CGPath = +public extension QRCode.EyeShape.Headlight { + @objc func settings() -> [String: Any] { + [QRCode.SettingsKey.flip: self.flip.rawValue] + } + + /// Returns true if the generator supports settings values for the given key + @objc func supportsSettingValue(forKey key: String) -> Bool { + key == QRCode.SettingsKey.flip + } + + /// Set the key's value in the generator + /// - Parameters: + /// - value: The value to set + /// - key: The setting key + /// - Returns: True if the setting was able to be change, false otherwise + @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { + if key == QRCode.SettingsKey.flip, + let which = IntValue(value) + { + self.flip = QRCode.Flip(rawValue: which) ?? .none + return true + } + return false + } +} + +// MARK: - Paths + +private let eyeBackgroundPath__: CGPath = CGPath.make { headlightEyeBackgroundPath in headlightEyeBackgroundPath.move(to: CGPoint(x: 0, y: 0)) headlightEyeBackgroundPath.line(to: CGPoint(x: 90, y: 0)) @@ -59,7 +136,7 @@ private let eyeBackgroundShape__: CGPath = headlightEyeBackgroundPath.close() } -private let eyeShape__: CGPath = { +private let eyePath__: CGPath = { CGPath.make { headlightEyeOuterPath in headlightEyeOuterPath.move(to: CGPoint(x: 45, y: 70)) headlightEyeOuterPath.curve(to: CGPoint(x: 20, y: 45), controlPoint1: CGPoint(x: 31.19, y: 70), controlPoint2: CGPoint(x: 20, y: 58.81)) diff --git a/Sources/QRCode/styles/eye/QRCodeEyeShapeRoundedPointingIn.swift b/Sources/QRCode/styles/eye/QRCodeEyeShapeRoundedPointingIn.swift index 405f0bca..2093faf8 100644 --- a/Sources/QRCode/styles/eye/QRCodeEyeShapeRoundedPointingIn.swift +++ b/Sources/QRCode/styles/eye/QRCodeEyeShapeRoundedPointingIn.swift @@ -1,6 +1,4 @@ // -// QRCodeEyeStyleLeaf.swift -// // Copyright © 2024 Darren Ford. All rights reserved. // // MIT license @@ -26,69 +24,157 @@ public extension QRCode.EyeShape { /// A 'rounded rect with a pointy bit facing inwards' style eye design @objc(QRCodeEyeShapeRoundedPointingIn) class RoundedPointingIn: NSObject, QRCodeEyeShapeGenerator { @objc public static let Name = "roundedPointingIn" - @objc public static var Title: String { "Rounded pointing in" } + @objc public static var Title: String { "Rounded Pointing In" } @objc public static func Create(_ settings: [String: Any]?) -> any QRCodeEyeShapeGenerator { - return QRCode.EyeShape.RoundedPointingIn() + QRCode.EyeShape.RoundedPointingIn(settings: settings) + } + + /// Flip the eye shape + @objc public var flip: QRCode.Flip = .none { + didSet { + _ = self.generator_.setSettingValue(self.flip.rawValue, forKey: QRCode.SettingsKey.flip) + } + } + + /// Create a roundedPointing eye shape + /// - Parameter flip: The flip state for the eye + @objc public init(flip: QRCode.Flip = .none) { + self.flip = flip + super.init() + + // Push the setting down to the pupil + self.generator_.flip = self.flip + } + + /// Create a eye shape using the specified settings + @objc public init(settings: [String: Any]?) { + super.init() + settings?.forEach { (key: String, value: Any) in + _ = self.setSettingValue(value, forKey: key) + } } - - @objc public func settings() -> [String: Any] { return [:] } - @objc public func supportsSettingValue(forKey key: String) -> Bool { false } - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { false } /// Make a copy of the object @objc public func copyShape() -> any QRCodeEyeShapeGenerator { - return Self.Create(self.settings()) + RoundedPointingIn(flip: self.flip) } /// Reset the eye shape generator back to defaults - @objc public func reset() { } + @objc public func reset() { + self.flip = .none + } + /// Returns a path representation of the eye outer public func eyePath() -> CGPath { - let tearEyePath = CGMutablePath() - tearEyePath.move(to: CGPoint(x: 57, y: 20)) - tearEyePath.addLine(to: CGPoint(x: 33, y: 20)) - tearEyePath.addCurve(to: CGPoint(x: 20, y: 33), control1: CGPoint(x: 25.82, y: 20), control2: CGPoint(x: 20, y: 25.82)) - tearEyePath.addLine(to: CGPoint(x: 20, y: 57)) - tearEyePath.addCurve(to: CGPoint(x: 33, y: 70), control1: CGPoint(x: 20, y: 64.18), control2: CGPoint(x: 25.82, y: 70)) - tearEyePath.addLine(to: CGPoint(x: 70, y: 70)) - tearEyePath.addLine(to: CGPoint(x: 70, y: 33)) - tearEyePath.addCurve(to: CGPoint(x: 57, y: 20), control1: CGPoint(x: 70, y: 25.82), control2: CGPoint(x: 64.18, y: 20)) - tearEyePath.close() - tearEyePath.move(to: CGPoint(x: 80, y: 33)) - tearEyePath.addLine(to: CGPoint(x: 80, y: 80)) - tearEyePath.addLine(to: CGPoint(x: 33, y: 80)) - tearEyePath.addCurve(to: CGPoint(x: 10, y: 57), control1: CGPoint(x: 20.3, y: 80), control2: CGPoint(x: 10, y: 69.7)) - tearEyePath.addLine(to: CGPoint(x: 10, y: 33)) - tearEyePath.addCurve(to: CGPoint(x: 33, y: 10), control1: CGPoint(x: 10, y: 20.3), control2: CGPoint(x: 20.3, y: 10)) - tearEyePath.addLine(to: CGPoint(x: 57, y: 10)) - tearEyePath.addCurve(to: CGPoint(x: 80, y: 33), control1: CGPoint(x: 69.7, y: 10), control2: CGPoint(x: 80, y: 20.3)) - tearEyePath.close() - return tearEyePath + switch self.flip { + case .none: + return eyeShape__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeShape__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeShape__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeShape__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } + } } @objc public func eyeBackgroundPath() -> CGPath { - let tearEye2Path = CGMutablePath() - tearEye2Path.move(to: CGPoint(x: 90, y: 60.43)) - tearEye2Path.line(to: CGPoint(x: 90, y: 0)) - tearEye2Path.line(to: CGPoint(x: 29.57, y: 0)) - tearEye2Path.curve(to: CGPoint(x: 0, y: 29.57), controlPoint1: CGPoint(x: 13.24, y: 0), controlPoint2: CGPoint(x: 0, y: 13.24)) - tearEye2Path.line(to: CGPoint(x: 0, y: 60.43)) - tearEye2Path.curve(to: CGPoint(x: 29.57, y: 90), controlPoint1: CGPoint(x: 0, y: 76.76), controlPoint2: CGPoint(x: 13.24, y: 90)) - tearEye2Path.line(to: CGPoint(x: 60.43, y: 90)) - tearEye2Path.curve(to: CGPoint(x: 90, y: 60.43), controlPoint1: CGPoint(x: 76.76, y: 90), controlPoint2: CGPoint(x: 90, y: 76.76)) - tearEye2Path.close() - return tearEye2Path + switch self.flip { + case .none: + return eyeBackgroundShape__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundShape__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundShape__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundShape__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } + } } - private static let generator_ = QRCode.PupilShape.RoundedPointingIn() - public func defaultPupil() -> any QRCodePupilShapeGenerator { Self.generator_ } + private let generator_ = QRCode.PupilShape.RoundedPointingIn() + public func defaultPupil() -> any QRCodePupilShapeGenerator { self.generator_ } + } +} + +public extension QRCode.EyeShape.RoundedPointingIn { + @objc func settings() -> [String: Any] { + [QRCode.SettingsKey.flip: self.flip.rawValue] + } + + /// Returns true if the generator supports settings values for the given key + @objc func supportsSettingValue(forKey key: String) -> Bool { + key == QRCode.SettingsKey.flip + } + + /// Set the key's value in the generator + /// - Parameters: + /// - value: The value to set + /// - key: The setting key + /// - Returns: True if the setting was able to be change, false otherwise + @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { + if key == QRCode.SettingsKey.flip, + let which = IntValue(value) + { + self.flip = QRCode.Flip(rawValue: which) ?? .none + return true + } + return false } } public extension QRCodeEyeShapeGenerator where Self == QRCode.EyeShape.RoundedPointingIn { - /// Create a 'roundedPointingIn' eye shape generator + /// Create a 'roundedPointing' eye shape generator /// - Returns: An eye shape generator - @inlinable static func roundedPointingIn() -> QRCodeEyeShapeGenerator { + @inlinable static func roundedPointing() -> QRCodeEyeShapeGenerator { QRCode.EyeShape.RoundedPointingIn() } } + +// MARK: - Paths + +private let eyeShape__: CGPath = + CGPath.make { tearEyePath in + tearEyePath.move(to: CGPoint(x: 57, y: 20)) + tearEyePath.addLine(to: CGPoint(x: 33, y: 20)) + tearEyePath.addCurve(to: CGPoint(x: 20, y: 33), control1: CGPoint(x: 25.82, y: 20), control2: CGPoint(x: 20, y: 25.82)) + tearEyePath.addLine(to: CGPoint(x: 20, y: 57)) + tearEyePath.addCurve(to: CGPoint(x: 33, y: 70), control1: CGPoint(x: 20, y: 64.18), control2: CGPoint(x: 25.82, y: 70)) + tearEyePath.addLine(to: CGPoint(x: 70, y: 70)) + tearEyePath.addLine(to: CGPoint(x: 70, y: 33)) + tearEyePath.addCurve(to: CGPoint(x: 57, y: 20), control1: CGPoint(x: 70, y: 25.82), control2: CGPoint(x: 64.18, y: 20)) + tearEyePath.close() + tearEyePath.move(to: CGPoint(x: 80, y: 33)) + tearEyePath.addLine(to: CGPoint(x: 80, y: 80)) + tearEyePath.addLine(to: CGPoint(x: 33, y: 80)) + tearEyePath.addCurve(to: CGPoint(x: 10, y: 57), control1: CGPoint(x: 20.3, y: 80), control2: CGPoint(x: 10, y: 69.7)) + tearEyePath.addLine(to: CGPoint(x: 10, y: 33)) + tearEyePath.addCurve(to: CGPoint(x: 33, y: 10), control1: CGPoint(x: 10, y: 20.3), control2: CGPoint(x: 20.3, y: 10)) + tearEyePath.addLine(to: CGPoint(x: 57, y: 10)) + tearEyePath.addCurve(to: CGPoint(x: 80, y: 33), control1: CGPoint(x: 69.7, y: 10), control2: CGPoint(x: 80, y: 20.3)) + tearEyePath.close() + } + +private let eyeBackgroundShape__: CGPath = + CGPath.make { tearEye2Path in + tearEye2Path.move(to: CGPoint(x: 90, y: 60.43)) + tearEye2Path.line(to: CGPoint(x: 90, y: 0)) + tearEye2Path.line(to: CGPoint(x: 29.57, y: 0)) + tearEye2Path.curve(to: CGPoint(x: 0, y: 29.57), controlPoint1: CGPoint(x: 13.24, y: 0), controlPoint2: CGPoint(x: 0, y: 13.24)) + tearEye2Path.line(to: CGPoint(x: 0, y: 60.43)) + tearEye2Path.curve(to: CGPoint(x: 29.57, y: 90), controlPoint1: CGPoint(x: 0, y: 76.76), controlPoint2: CGPoint(x: 13.24, y: 90)) + tearEye2Path.line(to: CGPoint(x: 60.43, y: 90)) + tearEye2Path.curve(to: CGPoint(x: 90, y: 60.43), controlPoint1: CGPoint(x: 76.76, y: 90), controlPoint2: CGPoint(x: 90, y: 76.76)) + tearEye2Path.close() + } diff --git a/Sources/QRCode/styles/eye/QRCodeEyeShapeRoundedPointingOut.swift b/Sources/QRCode/styles/eye/QRCodeEyeShapeRoundedPointingOut.swift deleted file mode 100644 index 21fb6071..00000000 --- a/Sources/QRCode/styles/eye/QRCodeEyeShapeRoundedPointingOut.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// QRCodeEyeStyleLeaf.swift -// -// Copyright © 2024 Darren Ford. All rights reserved. -// -// MIT license -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or substantial -// portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import CoreGraphics -import Foundation - -public extension QRCode.EyeShape { - /// A 'rounded rect with a pointy bit facing inwards' style eye design - @objc(QRCodeEyeShapeRoundedPointingOut) class RoundedPointingOut: NSObject, QRCodeEyeShapeGenerator { - @objc public static let Name = "roundedPointingOut" - @objc public static var Title: String { "Rounded pointing out" } - @objc public static func Create(_ settings: [String: Any]?) -> any QRCodeEyeShapeGenerator { - return QRCode.EyeShape.RoundedPointingOut() - } - - @objc public func settings() -> [String: Any] { return [:] } - @objc public func supportsSettingValue(forKey key: String) -> Bool { false } - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { false } - - /// Make a copy of the object - @objc public func copyShape() -> any QRCodeEyeShapeGenerator { - return Self.Create(self.settings()) - } - - /// Reset the eye shape generator back to defaults - @objc public func reset() { } - - public func eyePath() -> CGPath { - let tearEyePath = CGMutablePath() - tearEyePath.move(to: CGPoint(x: 33, y: 70)) - tearEyePath.line(to: CGPoint(x: 57, y: 70)) - tearEyePath.curve(to: CGPoint(x: 70, y: 57), controlPoint1: CGPoint(x: 64.18, y: 70), controlPoint2: CGPoint(x: 70, y: 64.18)) - tearEyePath.line(to: CGPoint(x: 70, y: 33)) - tearEyePath.curve(to: CGPoint(x: 57, y: 20), controlPoint1: CGPoint(x: 70, y: 25.82), controlPoint2: CGPoint(x: 64.18, y: 20)) - tearEyePath.line(to: CGPoint(x: 20, y: 20)) - tearEyePath.line(to: CGPoint(x: 20, y: 57)) - tearEyePath.curve(to: CGPoint(x: 33, y: 70), controlPoint1: CGPoint(x: 20, y: 64.18), controlPoint2: CGPoint(x: 25.82, y: 70)) - tearEyePath.close() - tearEyePath.move(to: CGPoint(x: 10, y: 57)) - tearEyePath.line(to: CGPoint(x: 10, y: 10)) - tearEyePath.line(to: CGPoint(x: 57, y: 10)) - tearEyePath.curve(to: CGPoint(x: 80, y: 33), controlPoint1: CGPoint(x: 69.7, y: 10), controlPoint2: CGPoint(x: 80, y: 20.3)) - tearEyePath.line(to: CGPoint(x: 80, y: 57)) - tearEyePath.curve(to: CGPoint(x: 57, y: 80), controlPoint1: CGPoint(x: 80, y: 69.7), controlPoint2: CGPoint(x: 69.7, y: 80)) - tearEyePath.line(to: CGPoint(x: 33, y: 80)) - tearEyePath.curve(to: CGPoint(x: 10, y: 57), controlPoint1: CGPoint(x: 20.3, y: 80), controlPoint2: CGPoint(x: 10, y: 69.7)) - tearEyePath.close() - return tearEyePath - } - - @objc public func eyeBackgroundPath() -> CGPath { - let roundedPupil = CGPath.RoundedRect( - rect: CGRect(x: 0, y: 0, width: 90, height: 90), - cornerRadius: 30, - byRoundingCorners: [.bottomRight, .topRight, .topLeft] - ) - return roundedPupil - } - - private static let generator_ = QRCode.PupilShape.RoundedPointingOut() - public func defaultPupil() -> any QRCodePupilShapeGenerator { Self.generator_ } - } -} - -public extension QRCodeEyeShapeGenerator where Self == QRCode.EyeShape.RoundedPointingOut { - /// Create a 'roundedPointingOut' eye shape generator - /// - Returns: An eye shape generator - @inlinable static func roundedPointingOut() -> QRCodeEyeShapeGenerator { - QRCode.EyeShape.RoundedPointingOut() - } -} diff --git a/Sources/QRCode/styles/eye/QRCodeEyeShapeTeardrop.swift b/Sources/QRCode/styles/eye/QRCodeEyeShapeTeardrop.swift index 82f4ccd9..f02aa7dd 100644 --- a/Sources/QRCode/styles/eye/QRCodeEyeShapeTeardrop.swift +++ b/Sources/QRCode/styles/eye/QRCodeEyeShapeTeardrop.swift @@ -28,57 +28,108 @@ public extension QRCode.EyeShape { @objc public static let Name = "teardrop" @objc public static var Title: String { "Teardrop" } @objc public static func Create(_ settings: [String: Any]?) -> any QRCodeEyeShapeGenerator { - return QRCode.EyeShape.Teardrop() + return QRCode.EyeShape.Teardrop(settings: settings) + } + + /// Flip the pupil shape + @objc public var flip: QRCode.Flip = .none { + didSet { + self._defaultPupil.flip = flip + } + } + + @objc public init(flip: QRCode.Flip = .none) { + self.flip = flip + super.init() + + self._defaultPupil.flip = flip + } + + @objc public init(settings: [String: Any]?) { + super.init() + settings?.forEach { (key: String, value: Any) in + _ = self.setSettingValue(value, forKey: key) + } } - - @objc public func settings() -> [String: Any] { return [:] } - @objc public func supportsSettingValue(forKey key: String) -> Bool { false } - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { false } /// Make a copy of the object @objc public func copyShape() -> any QRCodeEyeShapeGenerator { - return Self.Create(self.settings()) + Teardrop(flip: self.flip) } /// Reset the eye shape generator back to defaults - @objc public func reset() { } + @objc public func reset() { + self.flip = .none + } + /// The eye path public func eyePath() -> CGPath { - let eyeShapePath = CGMutablePath() - eyeShapePath.move(to: CGPoint(x: 45, y: 70)) - eyeShapePath.curve(to: CGPoint(x: 20, y: 45), controlPoint1: CGPoint(x: 31.19, y: 70), controlPoint2: CGPoint(x: 20, y: 58.81)) - eyeShapePath.line(to: CGPoint(x: 20, y: 20)) - eyeShapePath.line(to: CGPoint(x: 45, y: 20)) - eyeShapePath.curve(to: CGPoint(x: 70, y: 45), controlPoint1: CGPoint(x: 58.81, y: 20), controlPoint2: CGPoint(x: 70, y: 31.19)) - eyeShapePath.curve(to: CGPoint(x: 45, y: 70), controlPoint1: CGPoint(x: 70, y: 58.81), controlPoint2: CGPoint(x: 58.81, y: 70)) - eyeShapePath.close() - eyeShapePath.move(to: CGPoint(x: 80, y: 45)) - eyeShapePath.curve(to: CGPoint(x: 45, y: 10), controlPoint1: CGPoint(x: 80, y: 25.67), controlPoint2: CGPoint(x: 64.33, y: 10)) - eyeShapePath.line(to: CGPoint(x: 10, y: 10)) - eyeShapePath.line(to: CGPoint(x: 10, y: 45)) - eyeShapePath.curve(to: CGPoint(x: 45, y: 80), controlPoint1: CGPoint(x: 10, y: 64.33), controlPoint2: CGPoint(x: 25.67, y: 80)) - eyeShapePath.curve(to: CGPoint(x: 80, y: 45), controlPoint1: CGPoint(x: 64.33, y: 80), controlPoint2: CGPoint(x: 80, y: 64.33)) - eyeShapePath.close() - return eyeShapePath + switch self.flip { + case .none: + return eyePath__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyePath__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyePath__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyePath__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } + } } + /// The path for the background area of the eye public func eyeBackgroundPath() -> CGPath { - let eyeBackgroundPath = CGMutablePath() - eyeBackgroundPath.move(to: CGPoint(x: 90, y: 45)) - eyeBackgroundPath.curve(to: CGPoint(x: 45, y: 0), controlPoint1: CGPoint(x: 90, y: 20.15), controlPoint2: CGPoint(x: 69.85, y: 0)) - eyeBackgroundPath.line(to: CGPoint(x: 0, y: 0)) - eyeBackgroundPath.line(to: CGPoint(x: 0, y: 45)) - eyeBackgroundPath.curve(to: CGPoint(x: 45, y: 90), controlPoint1: CGPoint(x: 0, y: 69.85), controlPoint2: CGPoint(x: 20.15, y: 90)) - eyeBackgroundPath.curve(to: CGPoint(x: 90, y: 45), controlPoint1: CGPoint(x: 69.85, y: 90), controlPoint2: CGPoint(x: 90, y: 69.85)) - eyeBackgroundPath.close() - - let n = CGMutablePath() - n.addPath(eyeBackgroundPath, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) - return n + switch self.flip { + case .none: + return eyeBackgroundPath__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundPath__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundPath__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundPath__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } + } + } + + private let _defaultPupil = QRCode.PupilShape.Teardrop() + public func defaultPupil() -> any QRCodePupilShapeGenerator { self._defaultPupil } + } +} + +public extension QRCode.EyeShape.Teardrop { + @objc func settings() -> [String: Any] { + [QRCode.SettingsKey.flip: self.flip.rawValue] + } + + /// Returns true if the generator supports settings values for the given key + @objc func supportsSettingValue(forKey key: String) -> Bool { + key == QRCode.SettingsKey.flip + } + + /// Set the key's value in the generator + /// - Parameters: + /// - value: The value to set + /// - key: The setting key + /// - Returns: True if the setting was able to be change, false otherwise + @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { + if key == QRCode.SettingsKey.flip, + let which = IntValue(value) + { + self.flip = QRCode.Flip(rawValue: which) ?? .none + return true } - - private static let _defaultPupil = QRCode.PupilShape.Teardrop() - public func defaultPupil() -> any QRCodePupilShapeGenerator { Self._defaultPupil } + return false } } @@ -87,3 +138,35 @@ public extension QRCodeEyeShapeGenerator where Self == QRCode.EyeShape.Teardrop /// - Returns: An eye shape generator @inlinable static func teardrop() -> QRCodeEyeShapeGenerator { QRCode.EyeShape.Teardrop() } } + +// MARK: - Paths + +private let eyePath__: CGPath = + CGPath.make { eyeShapePath in + eyeShapePath.move(to: CGPoint(x: 45, y: 70)) + eyeShapePath.curve(to: CGPoint(x: 20, y: 45), controlPoint1: CGPoint(x: 31.19, y: 70), controlPoint2: CGPoint(x: 20, y: 58.81)) + eyeShapePath.line(to: CGPoint(x: 20, y: 20)) + eyeShapePath.line(to: CGPoint(x: 45, y: 20)) + eyeShapePath.curve(to: CGPoint(x: 70, y: 45), controlPoint1: CGPoint(x: 58.81, y: 20), controlPoint2: CGPoint(x: 70, y: 31.19)) + eyeShapePath.curve(to: CGPoint(x: 45, y: 70), controlPoint1: CGPoint(x: 70, y: 58.81), controlPoint2: CGPoint(x: 58.81, y: 70)) + eyeShapePath.close() + eyeShapePath.move(to: CGPoint(x: 80, y: 45)) + eyeShapePath.curve(to: CGPoint(x: 45, y: 10), controlPoint1: CGPoint(x: 80, y: 25.67), controlPoint2: CGPoint(x: 64.33, y: 10)) + eyeShapePath.line(to: CGPoint(x: 10, y: 10)) + eyeShapePath.line(to: CGPoint(x: 10, y: 45)) + eyeShapePath.curve(to: CGPoint(x: 45, y: 80), controlPoint1: CGPoint(x: 10, y: 64.33), controlPoint2: CGPoint(x: 25.67, y: 80)) + eyeShapePath.curve(to: CGPoint(x: 80, y: 45), controlPoint1: CGPoint(x: 64.33, y: 80), controlPoint2: CGPoint(x: 80, y: 64.33)) + eyeShapePath.close() + } + +private let eyeBackgroundPath__: CGPath = + CGPath.make { eyeBackgroundPath in + eyeBackgroundPath.move(to: CGPoint(x: 90, y: 45)) + eyeBackgroundPath.curve(to: CGPoint(x: 45, y: 0), controlPoint1: CGPoint(x: 90, y: 20.15), controlPoint2: CGPoint(x: 69.85, y: 0)) + eyeBackgroundPath.line(to: CGPoint(x: 0, y: 0)) + eyeBackgroundPath.line(to: CGPoint(x: 0, y: 45)) + eyeBackgroundPath.curve(to: CGPoint(x: 45, y: 90), controlPoint1: CGPoint(x: 0, y: 69.85), controlPoint2: CGPoint(x: 20.15, y: 90)) + eyeBackgroundPath.curve(to: CGPoint(x: 90, y: 45), controlPoint1: CGPoint(x: 69.85, y: 90), controlPoint2: CGPoint(x: 90, y: 69.85)) + eyeBackgroundPath.close() + } + .applyingTransform(CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) diff --git a/Sources/QRCode/styles/eye/QRCodeEyeShapeUFO.swift b/Sources/QRCode/styles/eye/QRCodeEyeShapeUFO.swift index 6576c3ae..cedc388d 100644 --- a/Sources/QRCode/styles/eye/QRCodeEyeShapeUFO.swift +++ b/Sources/QRCode/styles/eye/QRCodeEyeShapeUFO.swift @@ -28,102 +28,106 @@ public extension QRCode.EyeShape { @objc public static let Name = "ufo" @objc public static var Title: String { "UFO" } @objc public static func Create(_ settings: [String: Any]?) -> any QRCodeEyeShapeGenerator { - if let settings = settings { - return QRCode.EyeShape.UFO(settings: settings) + QRCode.EyeShape.UFO(settings: settings) + } + + /// Flip the pupil shape + @objc public var flip: QRCode.Flip = .none { + didSet { + self.defaultPupil__.flip = flip } - return QRCode.EyeShape.UFO() } - @objc public init(isFlipped: Bool = false) { + @objc public init(flip: QRCode.Flip = .none) { + self.flip = flip super.init() - self.isFlipped = isFlipped + + self.defaultPupil__.flip = flip } - @objc public init(settings: [String: Any]) { + @objc public init(settings: [String: Any]?) { super.init() - settings.forEach { (key: String, value: Any) in + settings?.forEach { (key: String, value: Any) in _ = self.setSettingValue(value, forKey: key) } } - @objc public func settings() -> [String: Any] { - [QRCode.SettingsKey.isFlipped: self.isFlipped] - } - @objc public func supportsSettingValue(forKey key: String) -> Bool { - key == QRCode.SettingsKey.isFlipped - } - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { - if key == QRCode.SettingsKey.isFlipped { - self.isFlipped = BoolValue(value) ?? false - } - return false - } - - /// Is the eye shape flipped? - @objc public var isFlipped = false - /// Make a copy of the object @objc public func copyShape() -> any QRCodeEyeShapeGenerator { - return QRCode.EyeShape.UFO(isFlipped: self.isFlipped) + return QRCode.EyeShape.UFO(flip: self.flip) } /// Reset the eye shape generator back to defaults @objc public func reset() { - self.isFlipped = false + self.flip = .none } public func eyePath() -> CGPath { - let eyePath = CGMutablePath() - eyePath.move(to: CGPoint(x: 70, y: 70)) - eyePath.line(to: CGPoint(x: 45, y: 70)) - eyePath.curve(to: CGPoint(x: 20, y: 45), controlPoint1: CGPoint(x: 31.19, y: 70), controlPoint2: CGPoint(x: 20, y: 58.81)) - eyePath.line(to: CGPoint(x: 20, y: 20)) - eyePath.line(to: CGPoint(x: 45, y: 20)) - eyePath.curve(to: CGPoint(x: 70, y: 45), controlPoint1: CGPoint(x: 58.81, y: 20), controlPoint2: CGPoint(x: 70, y: 31.19)) - eyePath.curve(to: CGPoint(x: 70, y: 58.44), controlPoint1: CGPoint(x: 70, y: 45), controlPoint2: CGPoint(x: 70, y: 51.89)) - eyePath.curve(to: CGPoint(x: 70, y: 70), controlPoint1: CGPoint(x: 70, y: 64.36), controlPoint2: CGPoint(x: 70, y: 70)) - eyePath.close() - eyePath.move(to: CGPoint(x: 80, y: 80)) - eyePath.curve(to: CGPoint(x: 80, y: 74.22), controlPoint1: CGPoint(x: 80, y: 80), controlPoint2: CGPoint(x: 80, y: 77.67)) - eyePath.curve(to: CGPoint(x: 80, y: 45), controlPoint1: CGPoint(x: 80, y: 64.28), controlPoint2: CGPoint(x: 80, y: 45)) - eyePath.curve(to: CGPoint(x: 45, y: 10), controlPoint1: CGPoint(x: 80, y: 25.67), controlPoint2: CGPoint(x: 64.33, y: 10)) - eyePath.line(to: CGPoint(x: 10, y: 10)) - eyePath.line(to: CGPoint(x: 10, y: 45)) - eyePath.curve(to: CGPoint(x: 45, y: 80), controlPoint1: CGPoint(x: 10, y: 64.33), controlPoint2: CGPoint(x: 25.67, y: 80)) - eyePath.line(to: CGPoint(x: 80, y: 80)) - eyePath.line(to: CGPoint(x: 80, y: 80)) - eyePath.close() - if isFlipped { - let n = CGMutablePath() - n.addPath(eyePath, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) - return n + switch self.flip { + case .none: + return eyePath__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyePath__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyePath__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyePath__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } } - return eyePath } public func eyeBackgroundPath() -> CGPath { - let safeZonePath = CGMutablePath() - safeZonePath.move(to: CGPoint(x: 90, y: 90)) - safeZonePath.curve(to: CGPoint(x: 90, y: 45), controlPoint1: CGPoint(x: 90, y: 90), controlPoint2: CGPoint(x: 90, y: 45)) - safeZonePath.curve(to: CGPoint(x: 45, y: 0), controlPoint1: CGPoint(x: 90, y: 20.15), controlPoint2: CGPoint(x: 69.85, y: 0)) - safeZonePath.line(to: CGPoint(x: 0, y: 0)) - safeZonePath.line(to: CGPoint(x: 0, y: 45)) - safeZonePath.curve(to: CGPoint(x: 45, y: 90), controlPoint1: CGPoint(x: 0, y: 69.85), controlPoint2: CGPoint(x: 20.15, y: 90)) - safeZonePath.line(to: CGPoint(x: 90, y: 90)) - safeZonePath.line(to: CGPoint(x: 90, y: 90)) - safeZonePath.close() - - if !isFlipped { - let n = CGMutablePath() - n.addPath(safeZonePath, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) - return n + switch self.flip { + case .none: + return eyeBackgroundPath__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundPath__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundPath__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(eyeBackgroundPath__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } } - return safeZonePath } - - public func defaultPupil() -> any QRCodePupilShapeGenerator { - QRCode.PupilShape.UFO(isFlipped: self.isFlipped) + + private let defaultPupil__ = QRCode.PupilShape.UFO() + public func defaultPupil() -> any QRCodePupilShapeGenerator { defaultPupil__ } + } +} + +public extension QRCode.EyeShape.UFO { + @objc func settings() -> [String: Any] { + [QRCode.SettingsKey.flip: self.flip.rawValue] + } + + /// Returns true if the generator supports settings values for the given key + @objc func supportsSettingValue(forKey key: String) -> Bool { + key == QRCode.SettingsKey.flip + } + + /// Set the key's value in the generator + /// - Parameters: + /// - value: The value to set + /// - key: The setting key + /// - Returns: True if the setting was able to be change, false otherwise + @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { + if key == QRCode.SettingsKey.flip, + let which = IntValue(value) + { + self.flip = QRCode.Flip(rawValue: which) ?? .none + return true } + return false } } @@ -131,7 +135,47 @@ public extension QRCodeEyeShapeGenerator where Self == QRCode.EyeShape.UFO { /// Create a UFO eye shape generator /// - Parameter isFlipped: if true, flips the generated shape /// - Returns: An eye shape generator - @inlinable static func ufo(isFlipped: Bool = false) -> QRCodeEyeShapeGenerator { - QRCode.EyeShape.UFO(isFlipped: isFlipped) + @inlinable static func ufo(flip: QRCode.Flip = .none) -> QRCodeEyeShapeGenerator { + QRCode.EyeShape.UFO(flip: flip) } } + + +// MARK: - Paths + +private let eyePath__: CGPath = + CGPath.make { eyePath in + eyePath.move(to: CGPoint(x: 70, y: 70)) + eyePath.line(to: CGPoint(x: 45, y: 70)) + eyePath.curve(to: CGPoint(x: 20, y: 45), controlPoint1: CGPoint(x: 31.19, y: 70), controlPoint2: CGPoint(x: 20, y: 58.81)) + eyePath.line(to: CGPoint(x: 20, y: 20)) + eyePath.line(to: CGPoint(x: 45, y: 20)) + eyePath.curve(to: CGPoint(x: 70, y: 45), controlPoint1: CGPoint(x: 58.81, y: 20), controlPoint2: CGPoint(x: 70, y: 31.19)) + eyePath.curve(to: CGPoint(x: 70, y: 58.44), controlPoint1: CGPoint(x: 70, y: 45), controlPoint2: CGPoint(x: 70, y: 51.89)) + eyePath.curve(to: CGPoint(x: 70, y: 70), controlPoint1: CGPoint(x: 70, y: 64.36), controlPoint2: CGPoint(x: 70, y: 70)) + eyePath.close() + eyePath.move(to: CGPoint(x: 80, y: 80)) + eyePath.curve(to: CGPoint(x: 80, y: 74.22), controlPoint1: CGPoint(x: 80, y: 80), controlPoint2: CGPoint(x: 80, y: 77.67)) + eyePath.curve(to: CGPoint(x: 80, y: 45), controlPoint1: CGPoint(x: 80, y: 64.28), controlPoint2: CGPoint(x: 80, y: 45)) + eyePath.curve(to: CGPoint(x: 45, y: 10), controlPoint1: CGPoint(x: 80, y: 25.67), controlPoint2: CGPoint(x: 64.33, y: 10)) + eyePath.line(to: CGPoint(x: 10, y: 10)) + eyePath.line(to: CGPoint(x: 10, y: 45)) + eyePath.curve(to: CGPoint(x: 45, y: 80), controlPoint1: CGPoint(x: 10, y: 64.33), controlPoint2: CGPoint(x: 25.67, y: 80)) + eyePath.line(to: CGPoint(x: 80, y: 80)) + eyePath.line(to: CGPoint(x: 80, y: 80)) + eyePath.close() + } + +private let eyeBackgroundPath__: CGPath = + CGPath.make { safeZonePath in + safeZonePath.move(to: CGPoint(x: 90, y: 90)) + safeZonePath.curve(to: CGPoint(x: 90, y: 45), controlPoint1: CGPoint(x: 90, y: 90), controlPoint2: CGPoint(x: 90, y: 45)) + safeZonePath.curve(to: CGPoint(x: 45, y: 0), controlPoint1: CGPoint(x: 90, y: 20.15), controlPoint2: CGPoint(x: 69.85, y: 0)) + safeZonePath.line(to: CGPoint(x: 0, y: 0)) + safeZonePath.line(to: CGPoint(x: 0, y: 45)) + safeZonePath.curve(to: CGPoint(x: 45, y: 90), controlPoint1: CGPoint(x: 0, y: 69.85), controlPoint2: CGPoint(x: 20.15, y: 90)) + safeZonePath.line(to: CGPoint(x: 90, y: 90)) + safeZonePath.line(to: CGPoint(x: 90, y: 90)) + safeZonePath.close() + } + .applyingTransform(CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeBlobby.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeBlobby.swift index eb130589..15156c70 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeBlobby.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeBlobby.swift @@ -42,49 +42,10 @@ extension QRCode.PupilShape { @objc public func setSettingValue(_: Any?, forKey _: String) -> Bool { false } /// The pupil centered in the 90x90 square - @objc public func pupilPath() -> CGPath { _path() } + @objc public func pupilPath() -> CGPath { pupilPath__ } } } -private func _path() -> CGPath { - let bezierPath = CGMutablePath() - bezierPath.move(to: CGPoint(x: 60, y: 55)) - bezierPath.curve(to: CGPoint(x: 56, y: 50.1), controlPoint1: CGPoint(x: 60, y: 52.58), controlPoint2: CGPoint(x: 58.28, y: 50.56)) - bezierPath.line(to: CGPoint(x: 56, y: 49.9)) - bezierPath.curve(to: CGPoint(x: 60, y: 45), controlPoint1: CGPoint(x: 58.28, y: 49.44), controlPoint2: CGPoint(x: 60, y: 47.42)) - bezierPath.curve(to: CGPoint(x: 56, y: 40.1), controlPoint1: CGPoint(x: 60, y: 42.58), controlPoint2: CGPoint(x: 58.28, y: 40.56)) - bezierPath.line(to: CGPoint(x: 56, y: 39.9)) - bezierPath.curve(to: CGPoint(x: 60, y: 35), controlPoint1: CGPoint(x: 58.28, y: 39.44), controlPoint2: CGPoint(x: 60, y: 37.42)) - bezierPath.curve(to: CGPoint(x: 55, y: 30), controlPoint1: CGPoint(x: 60, y: 32.24), controlPoint2: CGPoint(x: 57.76, y: 30)) - bezierPath.curve(to: CGPoint(x: 52.74, y: 30.54), controlPoint1: CGPoint(x: 54.19, y: 30), controlPoint2: CGPoint(x: 53.42, y: 30.19)) - bezierPath.curve(to: CGPoint(x: 50.1, y: 34), controlPoint1: CGPoint(x: 51.4, y: 31.22), controlPoint2: CGPoint(x: 50.41, y: 32.49)) - bezierPath.curve(to: CGPoint(x: 49.9, y: 34), controlPoint1: CGPoint(x: 50.1, y: 34), controlPoint2: CGPoint(x: 50.03, y: 34)) - bezierPath.curve(to: CGPoint(x: 45, y: 30), controlPoint1: CGPoint(x: 49.44, y: 31.72), controlPoint2: CGPoint(x: 47.42, y: 30)) - bezierPath.curve(to: CGPoint(x: 43.5, y: 30.23), controlPoint1: CGPoint(x: 44.48, y: 30), controlPoint2: CGPoint(x: 43.97, y: 30.08)) - bezierPath.curve(to: CGPoint(x: 40.1, y: 34), controlPoint1: CGPoint(x: 41.78, y: 30.77), controlPoint2: CGPoint(x: 40.46, y: 32.21)) - bezierPath.line(to: CGPoint(x: 39.9, y: 34)) - bezierPath.curve(to: CGPoint(x: 35, y: 30), controlPoint1: CGPoint(x: 39.44, y: 31.72), controlPoint2: CGPoint(x: 37.42, y: 30)) - bezierPath.curve(to: CGPoint(x: 31.47, y: 31.46), controlPoint1: CGPoint(x: 33.62, y: 30), controlPoint2: CGPoint(x: 32.37, y: 30.56)) - bezierPath.curve(to: CGPoint(x: 30, y: 35), controlPoint1: CGPoint(x: 30.56, y: 32.37), controlPoint2: CGPoint(x: 30, y: 33.62)) - bezierPath.curve(to: CGPoint(x: 34, y: 39.9), controlPoint1: CGPoint(x: 30, y: 37.42), controlPoint2: CGPoint(x: 31.72, y: 39.44)) - bezierPath.line(to: CGPoint(x: 34, y: 40.1)) - bezierPath.curve(to: CGPoint(x: 30.84, y: 42.22), controlPoint1: CGPoint(x: 32.69, y: 40.37), controlPoint2: CGPoint(x: 31.56, y: 41.15)) - bezierPath.curve(to: CGPoint(x: 30, y: 45), controlPoint1: CGPoint(x: 30.31, y: 43.01), controlPoint2: CGPoint(x: 30, y: 43.97)) - bezierPath.curve(to: CGPoint(x: 34, y: 49.9), controlPoint1: CGPoint(x: 30, y: 47.42), controlPoint2: CGPoint(x: 31.72, y: 49.44)) - bezierPath.line(to: CGPoint(x: 34, y: 50.1)) - bezierPath.curve(to: CGPoint(x: 30, y: 55), controlPoint1: CGPoint(x: 31.72, y: 50.56), controlPoint2: CGPoint(x: 30, y: 52.58)) - bezierPath.curve(to: CGPoint(x: 35, y: 60), controlPoint1: CGPoint(x: 30, y: 57.76), controlPoint2: CGPoint(x: 32.24, y: 60)) - bezierPath.curve(to: CGPoint(x: 39.9, y: 56), controlPoint1: CGPoint(x: 37.42, y: 60), controlPoint2: CGPoint(x: 39.44, y: 58.28)) - bezierPath.line(to: CGPoint(x: 40.1, y: 56)) - bezierPath.curve(to: CGPoint(x: 45, y: 60), controlPoint1: CGPoint(x: 40.56, y: 58.28), controlPoint2: CGPoint(x: 42.58, y: 60)) - bezierPath.curve(to: CGPoint(x: 49.9, y: 56), controlPoint1: CGPoint(x: 47.42, y: 60), controlPoint2: CGPoint(x: 49.44, y: 58.28)) - bezierPath.line(to: CGPoint(x: 50.1, y: 56)) - bezierPath.curve(to: CGPoint(x: 55, y: 60), controlPoint1: CGPoint(x: 50.56, y: 58.28), controlPoint2: CGPoint(x: 52.58, y: 60)) - bezierPath.curve(to: CGPoint(x: 60, y: 55), controlPoint1: CGPoint(x: 57.76, y: 60), controlPoint2: CGPoint(x: 60, y: 57.76)) - bezierPath.close() - return bezierPath -} - public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.Blobby { /// Create a blobby pupil shape generator /// - Returns: A pupil shape generator @@ -92,3 +53,43 @@ public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.Blobb QRCode.PupilShape.Blobby() } } + +// MARK: - Paths + +private let pupilPath__: CGPath = + CGPath.make { bezierPath in + bezierPath.move(to: CGPoint(x: 60, y: 55)) + bezierPath.curve(to: CGPoint(x: 56, y: 50.1), controlPoint1: CGPoint(x: 60, y: 52.58), controlPoint2: CGPoint(x: 58.28, y: 50.56)) + bezierPath.line(to: CGPoint(x: 56, y: 49.9)) + bezierPath.curve(to: CGPoint(x: 60, y: 45), controlPoint1: CGPoint(x: 58.28, y: 49.44), controlPoint2: CGPoint(x: 60, y: 47.42)) + bezierPath.curve(to: CGPoint(x: 56, y: 40.1), controlPoint1: CGPoint(x: 60, y: 42.58), controlPoint2: CGPoint(x: 58.28, y: 40.56)) + bezierPath.line(to: CGPoint(x: 56, y: 39.9)) + bezierPath.curve(to: CGPoint(x: 60, y: 35), controlPoint1: CGPoint(x: 58.28, y: 39.44), controlPoint2: CGPoint(x: 60, y: 37.42)) + bezierPath.curve(to: CGPoint(x: 55, y: 30), controlPoint1: CGPoint(x: 60, y: 32.24), controlPoint2: CGPoint(x: 57.76, y: 30)) + bezierPath.curve(to: CGPoint(x: 52.74, y: 30.54), controlPoint1: CGPoint(x: 54.19, y: 30), controlPoint2: CGPoint(x: 53.42, y: 30.19)) + bezierPath.curve(to: CGPoint(x: 50.1, y: 34), controlPoint1: CGPoint(x: 51.4, y: 31.22), controlPoint2: CGPoint(x: 50.41, y: 32.49)) + bezierPath.curve(to: CGPoint(x: 49.9, y: 34), controlPoint1: CGPoint(x: 50.1, y: 34), controlPoint2: CGPoint(x: 50.03, y: 34)) + bezierPath.curve(to: CGPoint(x: 45, y: 30), controlPoint1: CGPoint(x: 49.44, y: 31.72), controlPoint2: CGPoint(x: 47.42, y: 30)) + bezierPath.curve(to: CGPoint(x: 43.5, y: 30.23), controlPoint1: CGPoint(x: 44.48, y: 30), controlPoint2: CGPoint(x: 43.97, y: 30.08)) + bezierPath.curve(to: CGPoint(x: 40.1, y: 34), controlPoint1: CGPoint(x: 41.78, y: 30.77), controlPoint2: CGPoint(x: 40.46, y: 32.21)) + bezierPath.line(to: CGPoint(x: 39.9, y: 34)) + bezierPath.curve(to: CGPoint(x: 35, y: 30), controlPoint1: CGPoint(x: 39.44, y: 31.72), controlPoint2: CGPoint(x: 37.42, y: 30)) + bezierPath.curve(to: CGPoint(x: 31.47, y: 31.46), controlPoint1: CGPoint(x: 33.62, y: 30), controlPoint2: CGPoint(x: 32.37, y: 30.56)) + bezierPath.curve(to: CGPoint(x: 30, y: 35), controlPoint1: CGPoint(x: 30.56, y: 32.37), controlPoint2: CGPoint(x: 30, y: 33.62)) + bezierPath.curve(to: CGPoint(x: 34, y: 39.9), controlPoint1: CGPoint(x: 30, y: 37.42), controlPoint2: CGPoint(x: 31.72, y: 39.44)) + bezierPath.line(to: CGPoint(x: 34, y: 40.1)) + bezierPath.curve(to: CGPoint(x: 30.84, y: 42.22), controlPoint1: CGPoint(x: 32.69, y: 40.37), controlPoint2: CGPoint(x: 31.56, y: 41.15)) + bezierPath.curve(to: CGPoint(x: 30, y: 45), controlPoint1: CGPoint(x: 30.31, y: 43.01), controlPoint2: CGPoint(x: 30, y: 43.97)) + bezierPath.curve(to: CGPoint(x: 34, y: 49.9), controlPoint1: CGPoint(x: 30, y: 47.42), controlPoint2: CGPoint(x: 31.72, y: 49.44)) + bezierPath.line(to: CGPoint(x: 34, y: 50.1)) + bezierPath.curve(to: CGPoint(x: 30, y: 55), controlPoint1: CGPoint(x: 31.72, y: 50.56), controlPoint2: CGPoint(x: 30, y: 52.58)) + bezierPath.curve(to: CGPoint(x: 35, y: 60), controlPoint1: CGPoint(x: 30, y: 57.76), controlPoint2: CGPoint(x: 32.24, y: 60)) + bezierPath.curve(to: CGPoint(x: 39.9, y: 56), controlPoint1: CGPoint(x: 37.42, y: 60), controlPoint2: CGPoint(x: 39.44, y: 58.28)) + bezierPath.line(to: CGPoint(x: 40.1, y: 56)) + bezierPath.curve(to: CGPoint(x: 45, y: 60), controlPoint1: CGPoint(x: 40.56, y: 58.28), controlPoint2: CGPoint(x: 42.58, y: 60)) + bezierPath.curve(to: CGPoint(x: 49.9, y: 56), controlPoint1: CGPoint(x: 47.42, y: 60), controlPoint2: CGPoint(x: 49.44, y: 58.28)) + bezierPath.line(to: CGPoint(x: 50.1, y: 56)) + bezierPath.curve(to: CGPoint(x: 55, y: 60), controlPoint1: CGPoint(x: 50.56, y: 58.28), controlPoint2: CGPoint(x: 52.58, y: 60)) + bezierPath.curve(to: CGPoint(x: 60, y: 55), controlPoint1: CGPoint(x: 57.76, y: 60), controlPoint2: CGPoint(x: 60, y: 57.76)) + bezierPath.close() + } diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeCRT.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeCRT.swift index 96df20d4..f248b32f 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeCRT.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeCRT.swift @@ -42,27 +42,28 @@ extension QRCode.PupilShape { @objc public func setSettingValue(_: Any?, forKey _: String) -> Bool { false } /// The pupil centered in the 90x90 square - @objc public func pupilPath() -> CGPath { _path() } + @objc public func pupilPath() -> CGPath { pupilPath__ } } } -private func _path() -> CGPath { - let crt_pupilPath = CGMutablePath() - crt_pupilPath.move(to: CGPoint(x: 30, y: 45)) - crt_pupilPath.curve(to: CGPoint(x: 31.29, y: 31.29), controlPoint1: CGPoint(x: 30, y: 37.5), controlPoint2: CGPoint(x: 31.29, y: 31.29)) - crt_pupilPath.curve(to: CGPoint(x: 45, y: 30), controlPoint1: CGPoint(x: 31.29, y: 31.29), controlPoint2: CGPoint(x: 37.5, y: 30)) - crt_pupilPath.curve(to: CGPoint(x: 58.71, y: 31.29), controlPoint1: CGPoint(x: 52.5, y: 30), controlPoint2: CGPoint(x: 58.71, y: 31.29)) - crt_pupilPath.curve(to: CGPoint(x: 60, y: 45), controlPoint1: CGPoint(x: 58.71, y: 31.29), controlPoint2: CGPoint(x: 60, y: 37.5)) - crt_pupilPath.curve(to: CGPoint(x: 58.71, y: 58.71), controlPoint1: CGPoint(x: 60, y: 52.5), controlPoint2: CGPoint(x: 58.71, y: 58.71)) - crt_pupilPath.curve(to: CGPoint(x: 45, y: 60), controlPoint1: CGPoint(x: 58.71, y: 58.71), controlPoint2: CGPoint(x: 52.5, y: 60)) - crt_pupilPath.curve(to: CGPoint(x: 31.29, y: 58.71), controlPoint1: CGPoint(x: 37.5, y: 60), controlPoint2: CGPoint(x: 31.29, y: 58.71)) - crt_pupilPath.curve(to: CGPoint(x: 30, y: 45), controlPoint1: CGPoint(x: 31.29, y: 58.71), controlPoint2: CGPoint(x: 30, y: 52.5)) - crt_pupilPath.close() - return crt_pupilPath -} - public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.CRT { /// Create a crt pupil shape generator with curved insets /// - Returns: A pupil shape generator @inlinable static func crt() -> QRCodePupilShapeGenerator { QRCode.PupilShape.CRT() } } + +// MARK: - Paths + +private let pupilPath__: CGPath = + CGPath.make { crt_pupilPath in + crt_pupilPath.move(to: CGPoint(x: 30, y: 45)) + crt_pupilPath.curve(to: CGPoint(x: 31.29, y: 31.29), controlPoint1: CGPoint(x: 30, y: 37.5), controlPoint2: CGPoint(x: 31.29, y: 31.29)) + crt_pupilPath.curve(to: CGPoint(x: 45, y: 30), controlPoint1: CGPoint(x: 31.29, y: 31.29), controlPoint2: CGPoint(x: 37.5, y: 30)) + crt_pupilPath.curve(to: CGPoint(x: 58.71, y: 31.29), controlPoint1: CGPoint(x: 52.5, y: 30), controlPoint2: CGPoint(x: 58.71, y: 31.29)) + crt_pupilPath.curve(to: CGPoint(x: 60, y: 45), controlPoint1: CGPoint(x: 58.71, y: 31.29), controlPoint2: CGPoint(x: 60, y: 37.5)) + crt_pupilPath.curve(to: CGPoint(x: 58.71, y: 58.71), controlPoint1: CGPoint(x: 60, y: 52.5), controlPoint2: CGPoint(x: 58.71, y: 58.71)) + crt_pupilPath.curve(to: CGPoint(x: 45, y: 60), controlPoint1: CGPoint(x: 58.71, y: 58.71), controlPoint2: CGPoint(x: 52.5, y: 60)) + crt_pupilPath.curve(to: CGPoint(x: 31.29, y: 58.71), controlPoint1: CGPoint(x: 37.5, y: 60), controlPoint2: CGPoint(x: 31.29, y: 58.71)) + crt_pupilPath.curve(to: CGPoint(x: 30, y: 45), controlPoint1: CGPoint(x: 31.29, y: 58.71), controlPoint2: CGPoint(x: 30, y: 52.5)) + crt_pupilPath.close() + } diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeCrossCurved.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeCrossCurved.swift index b43ae3de..e2545dc6 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeCrossCurved.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeCrossCurved.swift @@ -45,28 +45,7 @@ public extension QRCode.PupilShape { @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { false } /// The pupil centered in the 90x90 square - @objc public func pupilPath() -> CGPath { - let crossPath = CGMutablePath() - crossPath.move(to: CGPoint(x: 30, y: 50)) - crossPath.curve(to: CGPoint(x: 34, y: 45), controlPoint1: CGPoint(x: 30, y: 50), controlPoint2: CGPoint(x: 34, y: 49)) - crossPath.curve(to: CGPoint(x: 30, y: 40), controlPoint1: CGPoint(x: 34, y: 41), controlPoint2: CGPoint(x: 30, y: 40)) - crossPath.line(to: CGPoint(x: 30, y: 30)) - crossPath.line(to: CGPoint(x: 40, y: 30)) - crossPath.curve(to: CGPoint(x: 45, y: 34), controlPoint1: CGPoint(x: 40, y: 30), controlPoint2: CGPoint(x: 41, y: 34)) - crossPath.curve(to: CGPoint(x: 50, y: 30), controlPoint1: CGPoint(x: 49, y: 34), controlPoint2: CGPoint(x: 50, y: 30)) - crossPath.line(to: CGPoint(x: 60, y: 30)) - crossPath.line(to: CGPoint(x: 60, y: 40)) - crossPath.curve(to: CGPoint(x: 56, y: 45), controlPoint1: CGPoint(x: 60, y: 40), controlPoint2: CGPoint(x: 56, y: 41)) - crossPath.curve(to: CGPoint(x: 60, y: 50), controlPoint1: CGPoint(x: 56, y: 49), controlPoint2: CGPoint(x: 60, y: 50)) - crossPath.line(to: CGPoint(x: 60, y: 60)) - crossPath.line(to: CGPoint(x: 50, y: 60)) - crossPath.curve(to: CGPoint(x: 45, y: 56), controlPoint1: CGPoint(x: 50, y: 60), controlPoint2: CGPoint(x: 49, y: 56)) - crossPath.curve(to: CGPoint(x: 40, y: 60), controlPoint1: CGPoint(x: 41, y: 56), controlPoint2: CGPoint(x: 40, y: 60)) - crossPath.line(to: CGPoint(x: 30, y: 60)) - crossPath.line(to: CGPoint(x: 30, y: 50)) - crossPath.close() - return crossPath - } + @objc public func pupilPath() -> CGPath { pupilShape__ } } } @@ -75,3 +54,27 @@ public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.Cross /// - Returns: A pupil shape generator @inlinable static func crossCurved() -> QRCodePupilShapeGenerator { QRCode.PupilShape.CrossCurved() } } + +// MARK: - Paths + +private let pupilShape__: CGPath = + CGPath.make { crossPath in + crossPath.move(to: CGPoint(x: 30, y: 50)) + crossPath.curve(to: CGPoint(x: 34, y: 45), controlPoint1: CGPoint(x: 30, y: 50), controlPoint2: CGPoint(x: 34, y: 49)) + crossPath.curve(to: CGPoint(x: 30, y: 40), controlPoint1: CGPoint(x: 34, y: 41), controlPoint2: CGPoint(x: 30, y: 40)) + crossPath.line(to: CGPoint(x: 30, y: 30)) + crossPath.line(to: CGPoint(x: 40, y: 30)) + crossPath.curve(to: CGPoint(x: 45, y: 34), controlPoint1: CGPoint(x: 40, y: 30), controlPoint2: CGPoint(x: 41, y: 34)) + crossPath.curve(to: CGPoint(x: 50, y: 30), controlPoint1: CGPoint(x: 49, y: 34), controlPoint2: CGPoint(x: 50, y: 30)) + crossPath.line(to: CGPoint(x: 60, y: 30)) + crossPath.line(to: CGPoint(x: 60, y: 40)) + crossPath.curve(to: CGPoint(x: 56, y: 45), controlPoint1: CGPoint(x: 60, y: 40), controlPoint2: CGPoint(x: 56, y: 41)) + crossPath.curve(to: CGPoint(x: 60, y: 50), controlPoint1: CGPoint(x: 56, y: 49), controlPoint2: CGPoint(x: 60, y: 50)) + crossPath.line(to: CGPoint(x: 60, y: 60)) + crossPath.line(to: CGPoint(x: 50, y: 60)) + crossPath.curve(to: CGPoint(x: 45, y: 56), controlPoint1: CGPoint(x: 50, y: 60), controlPoint2: CGPoint(x: 49, y: 56)) + crossPath.curve(to: CGPoint(x: 40, y: 60), controlPoint1: CGPoint(x: 41, y: 56), controlPoint2: CGPoint(x: 40, y: 60)) + crossPath.line(to: CGPoint(x: 30, y: 60)) + crossPath.line(to: CGPoint(x: 30, y: 50)) + crossPath.close() + } diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeExplode.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeExplode.swift index 26ec4da7..2e4ac20c 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeExplode.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeExplode.swift @@ -40,11 +40,19 @@ extension QRCode.PupilShape { @objc public func setSettingValue(_: Any?, forKey _: String) -> Bool { false } /// The pupil centered in the 90x90 square - @objc public func pupilPath() -> CGPath { _path() } + @objc public func pupilPath() -> CGPath { pupilPath__ } } } -private func _path() -> CGPath { +public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.Explode { + /// Create an explode pupil shape generator + /// - Returns: A pupil shape generator + @inlinable static func explode() -> QRCodePupilShapeGenerator { QRCode.PupilShape.Explode() } +} + +// MARK: - Paths + +private let pupilPath__: CGPath = CGPath.make { explodeeyepupilPath in explodeeyepupilPath.move(to: CGPoint(x: 32.5, y: 45.5)) explodeeyepupilPath.curve(to: CGPoint(x: 30, y: 30), controlPoint1: CGPoint(x: 32.5, y: 38), controlPoint2: CGPoint(x: 30, y: 30)) @@ -57,10 +65,3 @@ private func _path() -> CGPath { explodeeyepupilPath.curve(to: CGPoint(x: 32.5, y: 45.5), controlPoint1: CGPoint(x: 30, y: 60), controlPoint2: CGPoint(x: 32.5, y: 53)) explodeeyepupilPath.close() } -} - -public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.Explode { - /// Create an explode pupil shape generator - /// - Returns: A pupil shape generator - @inlinable static func explode() -> QRCodePupilShapeGenerator { QRCode.PupilShape.Explode() } -} diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeFactory.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeFactory.swift index 475802d7..97621e45 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeFactory.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeFactory.swift @@ -79,7 +79,6 @@ import CoreGraphics QRCode.PupilShape.Leaf.self, QRCode.PupilShape.BarsVertical.self, QRCode.PupilShape.BarsHorizontal.self, - QRCode.PupilShape.RoundedPointingOut.self, QRCode.PupilShape.Shield.self, QRCode.PupilShape.UsePixelShape.self, QRCode.PupilShape.HexagonLeaf.self, diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeForest.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeForest.swift index 463f3885..bcd79c8f 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeForest.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeForest.swift @@ -29,7 +29,9 @@ public extension QRCode.PupilShape { /// The generator title @objc public static var Title: String { "Forest" } /// Create a pupil generator with the provided settings - @objc public static func Create(_ settings: [String : Any]?) -> any QRCodePupilShapeGenerator { Forest() } + @objc public static func Create(_ settings: [String : Any]?) -> any QRCodePupilShapeGenerator { + Forest(settings: settings) + } /// Create a pupil generator /// - Parameter flip: The flip state for the pupil diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeHexagonLeaf.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeHexagonLeaf.swift index 075cf7c0..c2ce454f 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeHexagonLeaf.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeHexagonLeaf.swift @@ -31,72 +31,93 @@ extension QRCode.PupilShape { @objc public static var Title: String { "Hexagon Leaf" } /// Create a hexagon leaf pupil shape, using the specified settings @objc public static func Create(_ settings: [String : Any]?) -> any QRCodePupilShapeGenerator { - if let settings = settings { - return HexagonLeaf(settings: settings) - } - return HexagonLeaf() + HexagonLeaf(settings: settings) } /// Is the pupil shape flipped? - @objc public var isFlipped: Bool = false + @objc public var flip: QRCode.Flip = .none - @objc public init(isFlipped: Bool = false) { - self.isFlipped = isFlipped + @objc public init(flip: QRCode.Flip = .none) { + self.flip = flip super.init() } /// Create a navigator pupil shape using the specified settings - @objc public init(settings: [String: Any]) { + @objc public init(settings: [String: Any]?) { super.init() - settings.forEach { (key: String, value: Any) in + settings?.forEach { (key: String, value: Any) in _ = self.setSettingValue(value, forKey: key) } } /// Make a copy of the object @objc public func copyShape() -> any QRCodePupilShapeGenerator { - HexagonLeaf(isFlipped: self.isFlipped) + HexagonLeaf(flip: self.flip) } /// Reset the pupil shape generator back to defaults @objc public func reset() { - self.isFlipped = false + self.flip = .none } - @objc public func settings() -> [String: Any] { - [QRCode.SettingsKey.isFlipped: self.isFlipped] - } - @objc public func supportsSettingValue(forKey key: String) -> Bool { - key == QRCode.SettingsKey.isFlipped - } - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { - if key == QRCode.SettingsKey.isFlipped { - self.isFlipped = BoolValue(value) ?? false + /// The pupil centered in the 90x90 square + @objc public func pupilPath() -> CGPath { + switch self.flip { + case .none: + return pupilPath__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(pupilPath__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(pupilPath__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(pupilPath__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } } - return false } + } +} - /// The pupil centered in the 90x90 square - @objc public func pupilPath() -> CGPath { - let pupilPath = CGPath.RoundedHexagon( - rect: CGRect(x: 30, y: 30, width: 30, height: 30), - cornerRadius: 3, - byRoundingCorners: [.topMiddle, .rightMiddle, .bottomMiddle, .leftMiddle] - ) +private let pupilPath__: CGPath = + CGPath.RoundedHexagon( + rect: CGRect(x: 30, y: 30, width: 30, height: 30), + cornerRadius: 3, + byRoundingCorners: [.topMiddle, .rightMiddle, .bottomMiddle, .leftMiddle] + ) - if isFlipped { - let flipped = CGMutablePath() - flipped.addPath(pupilPath, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) - return flipped - } - return pupilPath +public extension QRCode.PupilShape.HexagonLeaf { + @objc func settings() -> [String: Any] { + [QRCode.SettingsKey.flip: self.flip.rawValue] + } + + /// Returns true if the generator supports settings values for the given key + @objc func supportsSettingValue(forKey key: String) -> Bool { + key == QRCode.SettingsKey.flip + } + + /// Set the key's value in the generator + /// - Parameters: + /// - value: The value to set + /// - key: The setting key + /// - Returns: True if the setting was able to be change, false otherwise + @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { + if key == QRCode.SettingsKey.flip, + let which = IntValue(value) + { + self.flip = QRCode.Flip(rawValue: which) ?? .none + return true } + return false } } public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.HexagonLeaf { /// Create a hexagonal leaf pupil shape generator /// - Returns: A pupil shape generator - @inlinable static func hexagonLeaf(isFlipped: Bool = false) -> QRCodePupilShapeGenerator { - QRCode.PupilShape.HexagonLeaf(isFlipped: isFlipped) + @inlinable static func hexagonLeaf(flip: QRCode.Flip = .none) -> QRCodePupilShapeGenerator { + QRCode.PupilShape.HexagonLeaf(flip: flip) } } diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeLeaf.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeLeaf.swift index 548aa0c2..6861e944 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeLeaf.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeLeaf.swift @@ -39,12 +39,12 @@ public extension QRCode.PupilShape { } /// Is the pupil shape flipped? - @objc public var isFlipped: Bool = false + @objc public var flip: QRCode.Flip = .none /// Create a pupil /// - Parameter isFlipped: Flip the shape - @objc public init(isFlipped: Bool = false) { - self.isFlipped = isFlipped + @objc public init(flip: QRCode.Flip = .none) { + self.flip = flip super.init() } @@ -58,34 +58,57 @@ public extension QRCode.PupilShape { /// Make a copy of the object @objc public func copyShape() -> any QRCodePupilShapeGenerator { - Leaf(isFlipped: self.isFlipped) + Leaf(flip: self.flip) } /// Reset the pupil shape generator back to defaults - @objc public func reset() { self.isFlipped = false } + @objc public func reset() { self.flip = .none } - @objc public func settings() -> [String : Any] { - [QRCode.SettingsKey.isFlipped: self.isFlipped] - } - @objc public func supportsSettingValue(forKey key: String) -> Bool { - key == QRCode.SettingsKey.isFlipped - } - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { - if key == QRCode.SettingsKey.isFlipped { - self.isFlipped = BoolValue(value) ?? false + /// The pupil centered in the 90x90 square + @objc public func pupilPath() -> CGPath { + switch self.flip { + case .none: + return pupilPath__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(pupilPath__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(pupilPath__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(pupilPath__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } } - return false } + } +} - /// The pupil centered in the 90x90 square - @objc public func pupilPath() -> CGPath { - let roundedPupil = CGPath.RoundedRect( - rect: CGRect(x: 30, y: 30, width: 30, height: 30), - cornerRadius: 6, - byRoundingCorners: self.isFlipped ? [.bottomRight, .topLeft] : [.topRight, .bottomLeft] - ) - return roundedPupil +public extension QRCode.PupilShape.Leaf { + @objc func settings() -> [String: Any] { + [QRCode.SettingsKey.flip: self.flip.rawValue] + } + + /// Returns true if the generator supports settings values for the given key + @objc func supportsSettingValue(forKey key: String) -> Bool { + key == QRCode.SettingsKey.flip + } + + /// Set the key's value in the generator + /// - Parameters: + /// - value: The value to set + /// - key: The setting key + /// - Returns: True if the setting was able to be change, false otherwise + @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { + if key == QRCode.SettingsKey.flip, + let which = IntValue(value) + { + self.flip = QRCode.Flip(rawValue: which) ?? .none + return true } + return false } } @@ -94,3 +117,12 @@ public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.Leaf /// - Returns: A pupil shape generator @inlinable static func leaf() -> QRCodePupilShapeGenerator { QRCode.PupilShape.Leaf() } } + +// MARK: - Paths + +private let pupilPath__: CGPath = + CGPath.RoundedRect( + rect: CGRect(x: 30, y: 30, width: 30, height: 30), + cornerRadius: 6, + byRoundingCorners: [.topRight, .bottomLeft] + ) diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedOuter.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedOuter.swift index 1a8ffd87..133e63ec 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedOuter.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedOuter.swift @@ -35,7 +35,7 @@ public extension QRCode.PupilShape { RoundedOuter(settings: settings) } - /// Flip the eye shape + /// Flip the pupil shape @objc public var flip: QRCode.Flip = .none /// Create a pupil diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointing.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointing.swift new file mode 100644 index 00000000..4bc87c62 --- /dev/null +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointing.swift @@ -0,0 +1,123 @@ +// +// Copyright © 2024 Darren Ford. All rights reserved. +// +// MIT license +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial +// portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import CoreGraphics +import Foundation + +// MARK: - Pupil shape + +public extension QRCode.PupilShape { + /// A 'rounded rect with a pointy bit facing inwards' style pupil design + @objc(QRCodePupilShapeRoundedPointing) class RoundedPointingIn: NSObject, QRCodePupilShapeGenerator { + /// The unique name for identifying the pupil shape + @objc public static var Name: String { "roundedPointingIn" } + /// The generator title + @objc public static var Title: String { "Rounded Pointing In" } + /// Create a pupil shape generator using the provided settings + @objc public static func Create(_ settings: [String : Any]?) -> any QRCodePupilShapeGenerator { + RoundedPointingIn(settings: settings) + } + + /// Flip the eye shape + @objc public var flip: QRCode.Flip = .none + + /// Create a pupil + /// - Parameter flip: The flip state for the eye + @objc public init(flip: QRCode.Flip = .none) { + self.flip = flip + super.init() + } + + /// Create a pupil shape using the specified settings + @objc public init(settings: [String: Any]?) { + super.init() + settings?.forEach { (key: String, value: Any) in + _ = self.setSettingValue(value, forKey: key) + } + } + + /// Make a copy of the object + @objc public func copyShape() -> any QRCodePupilShapeGenerator { + RoundedPointingIn(flip: self.flip) + } + /// Reset the pupil shape generator back to defaults + @objc public func reset() { + self.flip = .none + } + + /// The pupil centered in the 90x90 square + @objc public func pupilPath() -> CGPath { + let roundedPupil = CGPath.RoundedRect( + rect: CGRect(x: 30, y: 30, width: 30, height: 30), + cornerRadius: 6, + byRoundingCorners: [.topLeft, .bottomLeft, .topRight] + ) + + switch self.flip { + case .none: + return roundedPupil + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(roundedPupil, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(roundedPupil, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(roundedPupil, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } + } + } + } +} + +public extension QRCode.PupilShape.RoundedPointingIn { + /// The pupil generator settings + @objc func settings() -> [String: Any] { + [QRCode.SettingsKey.flip: self.flip.rawValue] + } + + /// Does the shape generator support setting values for a particular key? + @objc func supportsSettingValue(forKey key: String) -> Bool { + key == QRCode.SettingsKey.flip + } + + /// Set the key's value in the generator + /// - Parameters: + /// - value: The value to set + /// - key: The setting key + /// - Returns: True if the setting was able to be change, false otherwise + @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { + if key == QRCode.SettingsKey.flip, + let which = IntValue(value) + { + self.flip = QRCode.Flip(rawValue: which) ?? .none + return true + } + return false + } +} + +public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.RoundedPointingIn { + /// Create a rounded pointing pupil shape generator + /// - Returns: A pupil shape generator + @inlinable static func roundedPointing() -> QRCodePupilShapeGenerator { QRCode.PupilShape.RoundedPointingIn() } +} diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointingIn.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointingIn.swift deleted file mode 100644 index 875f4fec..00000000 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointingIn.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// QRCodePupilShapeRoundedPointingIn.swift -// -// Copyright © 2024 Darren Ford. All rights reserved. -// -// MIT license -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or substantial -// portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import CoreGraphics -import Foundation - -// MARK: - Pupil shape - -public extension QRCode.PupilShape { - /// A 'rounded rect with a pointy bit facing inwards' style pupil design - @objc(QRCodePupilShapeRoundedPointingIn) class RoundedPointingIn: NSObject, QRCodePupilShapeGenerator { - @objc public static var Name: String { "roundedPointingIn" } - /// The generator title - @objc public static var Title: String { "Rounded pointing in" } - - @objc public static func Create(_ settings: [String : Any]?) -> any QRCodePupilShapeGenerator { - RoundedPointingIn() - } - - /// Make a copy of the object - @objc public func copyShape() -> any QRCodePupilShapeGenerator { RoundedPointingIn() } - /// Reset the pupil shape generator back to defaults - @objc public func reset() { } - - @objc public func settings() -> [String : Any] { [:] } - @objc public func supportsSettingValue(forKey key: String) -> Bool { false } - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { false } - - /// The pupil centered in the 90x90 square - @objc public func pupilPath() -> CGPath { - let roundedPupil = CGPath.RoundedRect( - rect: CGRect(x: 30, y: 30, width: 30, height: 30), - cornerRadius: 6, - byRoundingCorners: [.topLeft, .bottomLeft, .topRight] - ) - return roundedPupil - } - } -} - -public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.RoundedPointingIn { - /// Create a rounded pointing in pupil shape generator - /// - Returns: A pupil shape generator - @inlinable static func roundedPointingIn() -> QRCodePupilShapeGenerator { QRCode.PupilShape.RoundedPointingIn() } -} diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointingOut.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointingOut.swift deleted file mode 100644 index 64637215..00000000 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeRoundedPointingOut.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// QRCodePupilShapeRoundedPointingOut.swift -// -// Copyright © 2024 Darren Ford. All rights reserved. -// -// MIT license -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or substantial -// portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import CoreGraphics -import Foundation - -// MARK: - Pupil shape - -public extension QRCode.PupilShape { - /// A 'rounded rect with a pointy bit facing inwards' style pupil design - @objc(QRCodePupilShapeRoundedPointingOut) class RoundedPointingOut: NSObject, QRCodePupilShapeGenerator { - @objc public static var Name: String { "roundedPointingOut" } - /// The generator title - @objc public static var Title: String { "Rounded pointing out" } - - @objc public static func Create(_ settings: [String : Any]?) -> any QRCodePupilShapeGenerator { - RoundedPointingOut() - } - - /// Make a copy of the object - @objc public func copyShape() -> any QRCodePupilShapeGenerator { RoundedPointingOut() } - /// Reset the pupil shape generator back to defaults - @objc public func reset() { } - - @objc public func settings() -> [String : Any] { [:] } - @objc public func supportsSettingValue(forKey key: String) -> Bool { false } - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { false } - - /// The pupil centered in the 90x90 square - @objc public func pupilPath() -> CGPath { - let roundedPupil = CGPath.RoundedRect( - rect: CGRect(x: 30, y: 30, width: 30, height: 30), - cornerRadius: 6, - byRoundingCorners: [.bottomRight, .bottomLeft, .topRight] - ) - return roundedPupil - } - } -} - -public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.RoundedPointingOut { - /// Create a rounded pointing out pupil shape generator - /// - Returns: A pupil shape generator - @inlinable static func roundedPointingOut() -> QRCodePupilShapeGenerator { QRCode.PupilShape.RoundedPointingOut() } -} diff --git a/Sources/QRCode/styles/pupil/QRCodePupilShapeUFO.swift b/Sources/QRCode/styles/pupil/QRCodePupilShapeUFO.swift index c4b3ca92..183cbc03 100644 --- a/Sources/QRCode/styles/pupil/QRCodePupilShapeUFO.swift +++ b/Sources/QRCode/styles/pupil/QRCodePupilShapeUFO.swift @@ -31,81 +31,92 @@ public extension QRCode.PupilShape { /// The generator title @objc public static var Title: String { "UFO" } @objc public static func Create(_ settings: [String : Any]?) -> any QRCodePupilShapeGenerator { - if let settings = settings { - return UFO(settings: settings) - } - return UFO() + UFO(settings: settings) } - @objc public init(isFlipped: Bool = false) { - self.isFlipped = isFlipped + /// Flip the pupil shape + @objc public var flip: QRCode.Flip = .none + + @objc public init(flip: QRCode.Flip = .none) { + self.flip = flip super.init() } /// Create a navigator pupil shape using the specified settings - @objc public init(settings: [String: Any]) { + @objc public init(settings: [String: Any]?) { super.init() - settings.forEach { (key: String, value: Any) in + settings?.forEach { (key: String, value: Any) in _ = self.setSettingValue(value, forKey: key) } } /// Make a copy of the object @objc public func copyShape() -> any QRCodePupilShapeGenerator { - UFO(isFlipped: self.isFlipped) + UFO(flip: self.flip) } /// Reset the pupil shape generator back to defaults @objc public func reset() { - self.isFlipped = false + self.flip = .none } - @objc public func settings() -> [String : Any] { - [QRCode.SettingsKey.isFlipped: self.isFlipped] - } - - /// Does this pupil support this setting? - /// - Parameter key: The key - /// - Returns: True if this pupil type supports this settings, false otherwise - @objc public func supportsSettingValue(forKey key: String) -> Bool { - key == QRCode.SettingsKey.isFlipped - } - - /// Set the key value for this pupil - /// - Parameters: - /// - value: The value to set - /// - key: The key - /// - Returns: True if the setting was able to be set, false otherwise - @objc public func setSettingValue(_ value: Any?, forKey key: String) -> Bool { - if key == QRCode.SettingsKey.isFlipped { - self.isFlipped = BoolValue(value) ?? false + /// The pupil centered in the 90x90 square + @objc public func pupilPath() -> CGPath { + switch self.flip { + case .none: + return pupilShape__ + case .vertically: + return CGPath.make(forceClosePath: true) { n in + n.addPath(pupilShape__, transform: .init(scaleX: -1, y: 1).translatedBy(x: -90, y: 0)) + } + case .horizontally: + return CGPath.make(forceClosePath: true) { n in + n.addPath(pupilShape__, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) + } + case .both: + return CGPath.make(forceClosePath: true) { n in + n.addPath(pupilShape__, transform: .init(scaleX: -1, y: -1).translatedBy(x: -90, y: -90)) + } } - return false } + } +} - /// Is the pupil shape flipped? - @objc public var isFlipped: Bool = false +private let pupilShape__: CGPath = + CGPath.make { pupilPath in + pupilPath.move(to: CGPoint(x: 60, y: 60)) + pupilPath.curve(to: CGPoint(x: 60, y: 45), controlPoint1: CGPoint(x: 60, y: 60), controlPoint2: CGPoint(x: 60, y: 45)) + pupilPath.curve(to: CGPoint(x: 45, y: 30), controlPoint1: CGPoint(x: 60, y: 36.72), controlPoint2: CGPoint(x: 53.28, y: 30)) + pupilPath.line(to: CGPoint(x: 30, y: 30)) + pupilPath.line(to: CGPoint(x: 30, y: 45)) + pupilPath.curve(to: CGPoint(x: 45, y: 60), controlPoint1: CGPoint(x: 30, y: 53.28), controlPoint2: CGPoint(x: 36.72, y: 60)) + pupilPath.line(to: CGPoint(x: 60, y: 60)) + pupilPath.line(to: CGPoint(x: 60, y: 60)) + pupilPath.close() + } - /// The pupil centered in the 90x90 square - @objc public func pupilPath() -> CGPath { - let pupilPath = CGMutablePath() - pupilPath.move(to: CGPoint(x: 60, y: 60)) - pupilPath.curve(to: CGPoint(x: 60, y: 45), controlPoint1: CGPoint(x: 60, y: 60), controlPoint2: CGPoint(x: 60, y: 45)) - pupilPath.curve(to: CGPoint(x: 45, y: 30), controlPoint1: CGPoint(x: 60, y: 36.72), controlPoint2: CGPoint(x: 53.28, y: 30)) - pupilPath.line(to: CGPoint(x: 30, y: 30)) - pupilPath.line(to: CGPoint(x: 30, y: 45)) - pupilPath.curve(to: CGPoint(x: 45, y: 60), controlPoint1: CGPoint(x: 30, y: 53.28), controlPoint2: CGPoint(x: 36.72, y: 60)) - pupilPath.line(to: CGPoint(x: 60, y: 60)) - pupilPath.line(to: CGPoint(x: 60, y: 60)) - pupilPath.close() +public extension QRCode.PupilShape.UFO { + @objc func settings() -> [String: Any] { + [QRCode.SettingsKey.flip: self.flip.rawValue] + } - if isFlipped { - let n = CGMutablePath() - n.addPath(pupilPath, transform: .init(scaleX: 1, y: -1).translatedBy(x: 0, y: -90)) - return n - } + /// Returns true if the generator supports settings values for the given key + @objc func supportsSettingValue(forKey key: String) -> Bool { + key == QRCode.SettingsKey.flip + } - return pupilPath + /// Set the key's value in the generator + /// - Parameters: + /// - value: The value to set + /// - key: The setting key + /// - Returns: True if the setting was able to be change, false otherwise + @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { + if key == QRCode.SettingsKey.flip, + let which = IntValue(value) + { + self.flip = QRCode.Flip(rawValue: which) ?? .none + return true } + return false } } @@ -113,7 +124,7 @@ public extension QRCodePupilShapeGenerator where Self == QRCode.PupilShape.UFO { /// Create a ufo pupil shape generator with curved insets /// - Parameter isFlipped: if true, flips the pupil shape horizontally /// - Returns: A pupil shape generator - @inlinable static func ufo(isFlipped: Bool = false) -> QRCodePupilShapeGenerator { - QRCode.PupilShape.UFO(isFlipped: isFlipped) + @inlinable static func ufo(flip: QRCode.Flip = .none) -> QRCodePupilShapeGenerator { + QRCode.PupilShape.UFO(flip: flip) } } diff --git a/Tests/QRCodeTests/QRCodeDocGenerator.swift b/Tests/QRCodeTests/QRCodeDocGenerator.swift index 0d6b74a6..97c74380 100644 --- a/Tests/QRCodeTests/QRCodeDocGenerator.swift +++ b/Tests/QRCodeTests/QRCodeDocGenerator.swift @@ -1652,35 +1652,132 @@ final class QRCodeDocGeneratorTests: XCTestCase { doc.design.style.background = QRCode.FillStyle.Solid(1, 0, 0) - markdownText += "| eye | flipped | 0 | 2 | 4 |\n" - markdownText += "|:------|:-------:|:-----:|:-----:|:-----:|\n" + let flips: [QRCode.Flip] = [.none, .horizontally, .vertically, .both] try QRCodeEyeShapeFactory.shared.all().sorted(by: { $0.name < $1.name }).forEach { generator in doc.design.shape.eye = generator doc.design.style.eyeBackground = .commonWhite - let flipped = generator.supportsSettingValue(forKey: QRCode.SettingsKey.isFlipped) ? [false, true] : [false] + markdownText += "## \(generator.name)\n\n" - try flipped.forEach { isFlipped in + markdownText += "| flipped | 0 | 2 | 4 |\n" + markdownText += "|:-------:|:-----:|:-----:|:-----:|\n" - markdownText += "|\(generator.name)|\(isFlipped)" + let flips = generator.supportsSettingValue(forKey: QRCode.SettingsKey.flip) ? flips : [.none] + + try flips.forEach { flip in + + let flipName = flip.name + + markdownText += "| \(flipName)" try [0, 2, 4].forEach { (quietZone: UInt) in markdownText += "|" - _ = doc.design.shape.eye.setSettingValue(isFlipped, forKey: QRCode.SettingsKey.isFlipped) + _ = doc.design.shape.eye.setSettingValue(flip.rawValue, forKey: QRCode.SettingsKey.flip) doc.design.additionalQuietZonePixels = quietZone let image = try doc.imageData(.png(), dimension: dimension) - let filename = "eye-background-exclusion-zone-\(generator.name)-\(quietZone)-\(isFlipped).png" + let filename = "eye-background-exclusion-zone-\(generator.name)-\(quietZone)-\(flipName).png" let link = try imageStore.store(image, filename: filename) markdownText += " " } markdownText += "|\n" } + + markdownText += "\n\n" } markdownText += "\n\n" } + + func testPupilFlips() throws { + + let flips: [QRCode.Flip] = [.horizontally, .vertically, .both] + + markdownText += "
\n\n" + + markdownText += "# Verify pupil flipping\n\n" + + let doc = try QRCode.Document(utf8String: "Validate pupil flips") + + try QRCodePupilShapeFactory.shared.all().sorted(by: { $0.name < $1.name }).forEach { generator in + doc.design.shape.pupil = generator + doc.design.style.pupil = QRCode.FillStyle.Solid(1, 0, 0) + + markdownText += "## \(generator.name)\n\n" + + let flips = generator.supportsSettingValue(forKey: QRCode.SettingsKey.flip) ? flips : [] + + markdownText += "| none | horizontally | vertically | both |\n" + markdownText += "|:-------:|:-----:|:-----:|:-----:|\n" + + let image = try doc.imageData(.svg, dimension: dimension) + let filename = "pupil-flipping-\(generator.name)-none.svg" + let link = try imageStore.store(image, filename: filename) + markdownText += "| " + + if flips.count == 0 { + markdownText += "| | |\n" + } + else + { + try flips.forEach { flip in + _ = generator.setSettingValue(flip.rawValue, forKey: QRCode.SettingsKey.flip) + + let image = try doc.imageData(.svg, dimension: dimension) + let filename = "pupil-flipping-\(generator.name)-\(flip.name).svg" + let link = try imageStore.store(image, filename: filename) + markdownText += "| " + } + markdownText += " |\n" + } + } + } + + func testEyeFlips() throws { + + let flips: [QRCode.Flip] = [.horizontally, .vertically, .both] + + markdownText += "
\n\n" + + markdownText += "# Verify eye flipping\n\n" + + let doc = try QRCode.Document(utf8String: "Validate eye flips") + + markdownText += "| name | none | horizontally | vertically | both |\n" + markdownText += "|-------|:-------:|:-----:|:-----:|:-----:|\n" + + try QRCodeEyeShapeFactory.shared.all().sorted(by: { $0.name < $1.name }).forEach { generator in + doc.design.shape.eye = generator + doc.design.style.eye = QRCode.FillStyle.Solid(1, 0, 0) + doc.design.style.eyeBackground = CGColor(red: 0, green: 1, blue: 0, alpha: 1) + + markdownText += "| \(generator.name) " + + let flips = generator.supportsSettingValue(forKey: QRCode.SettingsKey.flip) ? flips : [] + + let image = try doc.imageData(.svg, dimension: dimension) + let filename = "eye-flipping-\(generator.name)-none.svg" + let link = try imageStore.store(image, filename: filename) + markdownText += "| " + + if flips.count == 0 { + markdownText += "| | |\n" + } + else + { + try flips.forEach { flip in + _ = generator.setSettingValue(flip.rawValue, forKey: QRCode.SettingsKey.flip) + + let image = try doc.imageData(.svg, dimension: dimension) + let filename = "eye-flipping-\(generator.name)-\(flip.name).svg" + let link = try imageStore.store(image, filename: filename) + markdownText += "| " + } + markdownText += " |\n" + } + } + } } diff --git a/Tests/QRCodeTests/QRCodeEyeShapeTests.swift b/Tests/QRCodeTests/QRCodeEyeShapeTests.swift index 211a5844..e6a8074f 100644 --- a/Tests/QRCodeTests/QRCodeEyeShapeTests.swift +++ b/Tests/QRCodeTests/QRCodeEyeShapeTests.swift @@ -105,8 +105,7 @@ final class QRCodeEyeShapeConfigurationTests: XCTestCase { if g.supportsSettingValue(forKey: QRCode.SettingsKey.corners) { settings += "• __Configurable corners__
" } - if g.supportsSettingValue(forKey: QRCode.SettingsKey.isFlipped) - || g.supportsSettingValue(forKey: QRCode.SettingsKey.flip) { + if g.supportsSettingValue(forKey: QRCode.SettingsKey.flip) { settings += "• __Flippable__
" } if g.supportsSettingValue(forKey: QRCode.SettingsKey.rotationFraction) { @@ -122,6 +121,9 @@ final class QRCodeEyeShapeConfigurationTests: XCTestCase { if g.supportsSettingValue(forKey: QRCode.SettingsKey.insetGeneratorName) { settings += "  - Supports pixel inset generator
" } + if g.supportsSettingValue(forKey: QRCode.SettingsKey.eyeInnerStyle) { + settings += "• __Configurable eye corners__
" + } markdown += (settings.count > 0) ? settings : "_none_" } @@ -171,8 +173,7 @@ final class QRCodePixelShapeConfigurationTests: XCTestCase { if g.supportsSettingValue(forKey: QRCode.SettingsKey.corners) { settings += "• __Configurable corners__
" } - if g.supportsSettingValue(forKey: QRCode.SettingsKey.isFlipped) - || g.supportsSettingValue(forKey: QRCode.SettingsKey.flip) { + if g.supportsSettingValue(forKey: QRCode.SettingsKey.flip) { settings += "• __Flippable__
" } if g.supportsSettingValue(forKey: QRCode.SettingsKey.rotationFraction) { @@ -235,8 +236,7 @@ final class QRCodePupilShapeConfigurationTests: XCTestCase { if g.supportsSettingValue(forKey: QRCode.SettingsKey.corners) { settings += "• __Configurable corners__
" } - if g.supportsSettingValue(forKey: QRCode.SettingsKey.isFlipped) - || g.supportsSettingValue(forKey: QRCode.SettingsKey.flip) { + if g.supportsSettingValue(forKey: QRCode.SettingsKey.flip) { settings += "• __Flippable__
" } if g.supportsSettingValue(forKey: QRCode.SettingsKey.rotationFraction) {