diff --git a/Art/images/data_razor.png b/Art/images/data_razor.png new file mode 100644 index 0000000..6c5d4c5 Binary files /dev/null and b/Art/images/data_razor.png differ diff --git a/Art/paths.pcvd b/Art/paths.pcvd index abcbe20..c4c9420 100644 Binary files a/Art/paths.pcvd and b/Art/paths.pcvd differ diff --git a/Package.swift b/Package.swift index 299ce03..5b54e82 100644 --- a/Package.swift +++ b/Package.swift @@ -36,7 +36,7 @@ let package = Package( // A microframework for cleaning handling image conversion .package( url: "https://github.com/dagronf/SwiftImageReadWrite", - .upToNextMinor(from: "1.6.1") + .upToNextMinor(from: "1.7.2") ), ], targets: [ diff --git a/README.md b/README.md index 9f358e2..f508c4d 100644 --- a/README.md +++ b/README.md @@ -379,6 +379,7 @@ however you can supply a `PixelShape` object to custom-draw the data. There are | |"flower"|`QRCode.PixelShape.Flower`|A 'flower' style| | |"horizontal"|`QRCode.PixelShape.Horizontal`|The pixels are horizonally joined to make continuous horizontal bars| | |"pointy"|`QRCode.PixelShape.Pointy`|A 'pointy' style| +| |"razor"|`QRCode.PixelShape.Razor`| A 'razor' style| | |"roundedEndIndent"|`QRCode.PixelShape.RoundedEndIndent`|Rounded path with circular indented ends| | |"roundedPath"|`QRCode.PixelShape.RoundedPath`|A smooth rounded-edge path| | |"roundedRect"|`QRCode.PixelShape.RoundedRect`|A basic rounded rectangle pixel with configurable radius| diff --git a/Sources/QRCode/styles/data/QRCodePixelShapeFactory.swift b/Sources/QRCode/styles/data/QRCodePixelShapeFactory.swift index c670501..a5a10d8 100644 --- a/Sources/QRCode/styles/data/QRCodePixelShapeFactory.swift +++ b/Sources/QRCode/styles/data/QRCodePixelShapeFactory.swift @@ -37,12 +37,13 @@ import Foundation QRCode.PixelShape.Vertical.self, QRCode.PixelShape.Flower.self, QRCode.PixelShape.Horizontal.self, - QRCode.PixelShape.RoundedPath.self, QRCode.PixelShape.Pointy.self, QRCode.PixelShape.CurvePixel.self, + QRCode.PixelShape.Razor.self, + QRCode.PixelShape.RoundedEndIndent.self, + QRCode.PixelShape.RoundedPath.self, QRCode.PixelShape.Sharp.self, QRCode.PixelShape.Star.self, - QRCode.PixelShape.RoundedEndIndent.self, QRCode.PixelShape.Shiny.self, ].sorted(by: { a, b in a.Title < b.Title }) super.init() diff --git a/Sources/QRCode/styles/data/QRCodePixelShapeRazor.swift b/Sources/QRCode/styles/data/QRCodePixelShapeRazor.swift new file mode 100644 index 0000000..3031284 --- /dev/null +++ b/Sources/QRCode/styles/data/QRCodePixelShapeRazor.swift @@ -0,0 +1,191 @@ +// +// QRCodePixelShapeRazor.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 Foundation +import CoreGraphics + +public extension QRCode.PixelShape { + @objc(QRCodePixelShapeRazor) class Razor: NSObject, QRCodePixelShapeGenerator { + /// The generator name + @objc public static let Name: String = "razor" + /// The generator title + @objc public static var Title: String { "Razor" } + /// Create an instance of this path generator with the specified settings + @objc public static func Create(_ settings: [String: Any]?) -> any QRCodePixelShapeGenerator { Razor() } + + /// Make a copy of the object + @objc public func copyShape() -> any QRCodePixelShapeGenerator { Razor() } + + static let templateSquare: CGPath = { + CGPath(rect: CGRect(origin: .zero, size: .init(width: 10, height: 10)), transform: nil) + }() + + static let templatePointingDown: CGPath = { + let p = CGMutablePath() + p.move(to: CGPoint(x: 0, y: 0)) + p.line(to: CGPoint(x: 10, y: 0)) + p.line(to: CGPoint(x: 10, y: 10)) + p.curve(to: CGPoint(x: 2, y: 8), controlPoint1: CGPoint(x: 10, y: 10), controlPoint2: CGPoint(x: 4.5, y: 9.5)) + p.curve(to: CGPoint(x: 0, y: 4), controlPoint1: CGPoint(x: -0.5, y: 6.5), controlPoint2: CGPoint(x: 0, y: 4)) + p.line(to: CGPoint(x: 0, y: 0)) + p.close() + return p + }() + + static let templatePointingUp: CGPath = { + let p = CGMutablePath() + p.move(to: CGPoint(x: 0, y: 0)) + p.curve(to: CGPoint(x: 8, y: 2), controlPoint1: CGPoint(x: 0, y: 0), controlPoint2: CGPoint(x: 5.5, y: 0.5)) + p.curve(to: CGPoint(x: 10, y: 6), controlPoint1: CGPoint(x: 10.5, y: 3.5), controlPoint2: CGPoint(x: 10, y: 6)) + p.line(to: CGPoint(x: 10, y: 10)) + p.line(to: CGPoint(x: 0, y: 10)) + p.line(to: CGPoint(x: 0, y: 0)) + p.close() + return p + }() + + static let templatePointingRight: CGPath = { + let p = CGMutablePath() + p.move(to: CGPoint(x: 0, y: 0)) + p.line(to: CGPoint(x: 10, y: 0)) + p.curve(to: CGPoint(x: 8, y: 8), controlPoint1: CGPoint(x: 10, y: 0), controlPoint2: CGPoint(x: 9.5, y: 5.5)) + p.curve(to: CGPoint(x: 4, y: 10), controlPoint1: CGPoint(x: 6.5, y: 10.5), controlPoint2: CGPoint(x: 4, y: 10)) + p.line(to: CGPoint(x: 0, y: 10)) + p.line(to: CGPoint(x: 0, y: 0)) + p.close() + return p + }() + + static let templatePointingLeft: CGPath = { + let p = CGMutablePath() + p.move(to: CGPoint(x: 2, y: 2)) + p.curve(to: CGPoint(x: 6, y: 0), controlPoint1: CGPoint(x: 3.5, y: -0.5), controlPoint2: CGPoint(x: 6, y: 0)) + p.line(to: CGPoint(x: 10, y: 0)) + p.line(to: CGPoint(x: 10, y: 10)) + p.line(to: CGPoint(x: 0, y: 10)) + p.curve(to: CGPoint(x: 2, y: 2), controlPoint1: CGPoint(x: 0, y: 10), controlPoint2: CGPoint(x: 0.5, y: 4.5)) + + p.close() + return p + }() + } +} + +public extension QRCode.PixelShape.Razor { + /// Generate a CGPath from the matrix contents + /// - Parameters: + /// - matrix: The matrix to generate + /// - size: The size of the resulting CGPath + /// - Returns: A path + @objc func generatePath(from matrix: BoolMatrix, size: CGSize) -> CGPath { + let dx = size.width / CGFloat(matrix.dimension) + let dy = size.height / CGFloat(matrix.dimension) + let dm = min(dx, dy) + + let xoff = (size.width - (CGFloat(matrix.dimension) * dm)) / 2.0 + let yoff = (size.height - (CGFloat(matrix.dimension) * dm)) / 2.0 + + // The scale required to convert our template paths to output path size + let w = QRCode.PixelShape.RoundedPath.DefaultSize.width + let scaleTransform = CGAffineTransform(scaleX: dm / w, y: dm / w) + + let path = CGMutablePath() + + for row in 0 ..< matrix.dimension { + for col in 0 ..< matrix.dimension { + guard matrix[row, col] == true else { continue } + + let hasLeft: Bool = { + if col == 0 { return false } + return (col - 1) >= 0 ? matrix[row, col - 1] : false + }() + let hasRight: Bool = { + if col == (matrix.dimension - 1) { return false } + return (col + 1) < matrix.dimension ? matrix[row, col + 1] : false + }() + let hasTop: Bool = { + if row == 0 { return false } + return (row - 1) >= 0 ? matrix[row - 1, col] : false + }() + let hasBottom: Bool = { + if row == (matrix.dimension - 1) { return false } + return (row + 1) < matrix.dimension ? matrix[row + 1, col] : false + }() + + let translate = CGAffineTransform(translationX: CGFloat(col) * dm + xoff, y: CGFloat(row) * dm + yoff) + + if !hasLeft, !hasRight, !hasTop, !hasBottom { + // isolated block + path.addPath( + Self.templateSquare, + transform: scaleTransform.concatenating(translate) + ) + } + else if !hasLeft, !hasRight, !hasTop, hasBottom { + // pointing up block + path.addPath( + Self.templatePointingUp, + transform: scaleTransform.concatenating(translate) + ) + } + else if !hasLeft, !hasRight, !hasBottom, hasTop { + // pointing down block + path.addPath( + Self.templatePointingDown, + transform: scaleTransform.concatenating(translate) + ) + } + else if !hasTop, !hasRight, !hasBottom, hasLeft { + // pointing right block + path.addPath( + Self.templatePointingRight, + transform: scaleTransform.concatenating(translate) + ) + } + else if !hasTop, !hasLeft, !hasBottom, hasRight { + // pointing left block + path.addPath( + Self.templatePointingLeft, + transform: scaleTransform.concatenating(translate) + ) + } + else { + path.addPath( + Self.templateSquare, + transform: scaleTransform.concatenating(translate) + ) + } + } + } + return path + } +} + +// MARK: - Settings + +public extension QRCode.PixelShape.Razor { + /// Does the shape generator support setting values for a particular key? + @objc func supportsSettingValue(forKey key: String) -> Bool { false } + /// Returns a storable representation of the shape handler + @objc func settings() -> [String: Any] { return [:] } + /// Set a configuration value for a particular setting string + @objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool { false } +} diff --git a/Sources/QRCode/views/QRCodeView.swift b/Sources/QRCode/views/QRCodeView.swift index ad71e9e..ad03950 100644 --- a/Sources/QRCode/views/QRCodeView.swift +++ b/Sources/QRCode/views/QRCodeView.swift @@ -102,7 +102,7 @@ import UIKit self.rebuildQRCode() } -#if os(iOS) || os(visionOS) || os(tvOS) +#if os(iOS) public override func didMoveToWindow() { super.didMoveToWindow() if self.supportsDrag { @@ -370,7 +370,7 @@ extension QRCodeView: NSPasteboardItemDataProvider { } #endif -#if os(iOS) || os(tvOS) || os(visionOS) +#if os(iOS) extension QRCodeView: UIDragInteractionDelegate { public func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {