-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 691f03f
Showing
16 changed files
with
778 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
I do not intend to merge pull requests with something else than small bug fixes. | ||
|
||
Do not hesitate to fork this project and adapt it to your own needs. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
xcuserdata/ | ||
DerivedData/ | ||
.swiftpm/configuration/registries.json | ||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
.netrc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2019 Daniel Griesser <daniel.griesser.86@gmail.com> | ||
Copyright (c) 2023 Vincent Isambart | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"pins" : [ | ||
{ | ||
"identity" : "swift-argument-parser", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-argument-parser.git", | ||
"state" : { | ||
"revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", | ||
"version" : "1.2.3" | ||
} | ||
} | ||
], | ||
"version" : 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// swift-tools-version: 5.9 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Swadgics", | ||
// macOS 13 to be able to use `Regex`. | ||
// (It should not be hard to support older versions of macOS using `NSRegularExpression` if needed) | ||
platforms: [.macOS(.v13)], | ||
products: [ | ||
.executable(name: "swadgics", targets: ["Swadgics"]), | ||
], | ||
dependencies: [ | ||
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.3"), | ||
], | ||
targets: [ | ||
.executableTarget( | ||
name: "Swadgics", | ||
dependencies: [ | ||
.product(name: "ArgumentParser", package: "swift-argument-parser"), | ||
], | ||
resources: [ | ||
.embedInCode("Resources/alpha_badge_dark.png"), | ||
.embedInCode("Resources/alpha_badge_light.png"), | ||
.embedInCode("Resources/beta_badge_dark.png"), | ||
.embedInCode("Resources/beta_badge_light.png"), | ||
] | ||
), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Swadgics | ||
|
||
Partial reimplementation of [badge](https://github.com/HazAT/badge) in Swift using Core Graphics (Swadgics = Sw(ift) + (b)adg(e) + (Core Graph)ics). | ||
|
||
Its main advantage is that it does not require ImageMagick, GraphicsMagick, or RSVG, and that at runtime it does not use the network. As it uses Core Graphics it needs to be run on macOS, but for iOS applications that should not be much of a limitation. | ||
|
||
Instead of sending pull requests to add new features, do not hesitate to fork it and adapt it to your own needs. | ||
|
||
This project under the MIT license. The files under `Sources/Resources/` have been copied as-is from [badge](https://github.com/HazAT/badge) but were already under MIT license. | ||
|
||
## Differences | ||
|
||
It handles many of the flags of [badge](https://github.com/HazAT/badge) but the generated image will not be exactly the same. | ||
|
||
Also, Swadgics will not automatically search for icons ([badge](https://github.com/HazAT/badge) looks at `./**/*.appiconset/*.{png,PNG}`), and does not take a `--glob` option. You have to give it the path to the images to modify. You can use your shell's glob features of course. | ||
|
||
```console | ||
$ swadgics badge --shield "Version-0.0.3-blue" --dark --shield_geometry "+0+25%" --shield_scale 0.75 path/to/my/icon.png | ||
``` | ||
|
||
WARNING: As in [badge](https://github.com/HazAT/badge), the files are modified in place so make sure to make a copy before applying Swadgics on them. However, for testing purpose, when only once input file is specified, you can use `--output-file` to specify the file path to output to. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import CoreGraphics | ||
|
||
extension CGColorSpace { | ||
// Unfortunately, `sRGB` is already taken for the name of the sRGB color space. | ||
static let sRGBColorSpace = CGColorSpace(name: CGColorSpace.sRGB)! | ||
// `ColorSpace` suffix for uniformity. | ||
static let grayscaleColorSpace = CGColorSpaceCreateDeviceGray() | ||
} | ||
|
||
extension CGColor { | ||
/// sRGB color from 3 0-255 8-bit values (plus an optional alpha). | ||
static func sRGB8(_ red: UInt8, _ green: UInt8, _ blue: UInt8, alpha: CGFloat = 1.0) -> CGColor { | ||
CGColor(srgbRed: CGFloat(red) / 0xff, green: CGFloat(green) / 0xff, blue: CGFloat(blue) / 0xff, alpha: alpha) | ||
} | ||
} | ||
|
||
extension CGImage { | ||
var hasAlpha: Bool { | ||
switch alphaInfo { | ||
case .none, .noneSkipLast, .noneSkipFirst: | ||
return false | ||
default: | ||
return true | ||
} | ||
} | ||
} | ||
|
||
extension CGContext { | ||
static func makeGrayscaleContext(size: CGSize) -> CGContext { | ||
CGContext( | ||
data: nil, | ||
width: Int(size.width.rounded(.up)), | ||
height: Int(size.height.rounded(.up)), | ||
bitsPerComponent: 8, | ||
bytesPerRow: 0, | ||
space: CGColorSpace.grayscaleColorSpace, | ||
// `CGImageAlphaInfo` is part of `CGBitmapInfo`. | ||
// Unfortunately, the grayscale color space only seems to support no alpha or alpha only. | ||
// https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB | ||
bitmapInfo: CGImageAlphaInfo.none.rawValue | ||
)! | ||
} | ||
|
||
static func makeSRBGContext(size: CGSize) -> CGContext { | ||
CGContext( | ||
data: nil, | ||
width: Int(size.width.rounded(.up)), | ||
height: Int(size.height.rounded(.up)), | ||
bitsPerComponent: 8, | ||
bytesPerRow: 0, | ||
space: CGColorSpace.sRGBColorSpace, | ||
// `CGImageAlphaInfo` is part of `CGBitmapInfo`. | ||
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | ||
)! | ||
} | ||
|
||
/// Add a rounded rect to the current path of the context. | ||
func addRoundedRect(_ rect: CGRect, radius: CGFloat) { | ||
move(to: CGPoint(x: rect.minX, y: rect.midY)) | ||
addArc( | ||
tangent1End: CGPoint(x: rect.minX, y: rect.minY), | ||
tangent2End: CGPoint(x: rect.midX, y: rect.minY), | ||
radius: radius | ||
) | ||
addArc( | ||
tangent1End: CGPoint(x: rect.maxX, y: rect.minY), | ||
tangent2End: CGPoint(x: rect.maxX, y: rect.midY), | ||
radius: radius | ||
) | ||
addArc( | ||
tangent1End: CGPoint(x: rect.maxX, y: rect.maxY), | ||
tangent2End: CGPoint(x: rect.midX, y: rect.maxY), | ||
radius: radius | ||
) | ||
addArc( | ||
tangent1End: CGPoint(x: rect.minX, y: rect.maxY), | ||
tangent2End: CGPoint(x: rect.minX, y: rect.midY), | ||
radius: radius | ||
) | ||
closePath() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import CoreText | ||
|
||
extension CTFont { | ||
var ascent: CGFloat { CTFontGetAscent(self) } | ||
var descent: CGFloat { CTFontGetDescent(self) } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Partial implementation of ImageMagick gravity and geometry concepts. | ||
|
||
import ArgumentParser | ||
import CoreGraphics | ||
|
||
enum Gravity: String, ExpressibleByArgument { | ||
case northWest = "NorthWest" | ||
case north = "North" | ||
case northEast = "NorthEast" | ||
case west = "West" | ||
case center = "Center" | ||
case east = "East" | ||
case southWest = "SouthWest" | ||
case south = "South" | ||
case southEast = "SouthEast" | ||
|
||
func objectCenter(objectSize: CGSize, canvasSize: CGSize, geometry: Geometry?) -> CGPoint { | ||
var centerX, centerY: CGFloat | ||
switch self { | ||
case .northWest, .west, .southWest: | ||
centerX = 0 | ||
case .north, .center, .south: | ||
centerX = canvasSize.width / 2 | ||
case .northEast, .east, .southEast: | ||
centerX = canvasSize.width | ||
} | ||
switch self { | ||
case .southWest, .south, .southEast: | ||
centerY = 0 | ||
case .west, .center, .east: | ||
centerY = canvasSize.height / 2 | ||
case .northWest, .north, .northEast: | ||
centerY = canvasSize.height | ||
} | ||
|
||
if let geometry { | ||
switch geometry.x { | ||
case .pixels(let x): | ||
centerX += x | ||
case .percent(let x): | ||
centerX += canvasSize.width * x / 100 | ||
} | ||
// Geometry's `y` is inverted compared to Core Graphics | ||
switch geometry.y { | ||
case .pixels(let y): | ||
centerY += -y | ||
case .percent(let y): | ||
centerY += canvasSize.width * -y / 100 | ||
} | ||
} | ||
|
||
let halfObjectSize = CGSize( | ||
width: objectSize.width / 2, | ||
height: objectSize.height / 2 | ||
) | ||
|
||
if objectSize.width <= canvasSize.width { | ||
if centerX - halfObjectSize.width < 0 { | ||
centerX = halfObjectSize.width | ||
} else if centerX + halfObjectSize.width > canvasSize.width { | ||
centerX = canvasSize.width - halfObjectSize.width | ||
} | ||
} else { | ||
switch self { | ||
case .northWest, .west, .southWest: | ||
centerX = halfObjectSize.width | ||
case .north, .center, .south: | ||
centerX = canvasSize.width / 2 | ||
case .northEast, .east, .southEast: | ||
centerX = canvasSize.width - halfObjectSize.width | ||
} | ||
} | ||
|
||
if objectSize.height <= canvasSize.height { | ||
if centerY - halfObjectSize.height < 0 { | ||
centerY = halfObjectSize.height | ||
} else if centerY + halfObjectSize.height > canvasSize.height { | ||
centerY = canvasSize.height - halfObjectSize.height | ||
} | ||
} else { | ||
switch self { | ||
case .southWest, .south, .southEast: | ||
centerY = halfObjectSize.height | ||
case .west, .center, .east: | ||
centerY = canvasSize.height / 2 | ||
case .northWest, .north, .northEast: | ||
centerY = canvasSize.height - halfObjectSize.height | ||
} | ||
} | ||
return CGPoint(x: centerX, y: centerY) | ||
} | ||
} | ||
|
||
struct Geometry: ExpressibleByArgument { | ||
var x: Offset | ||
var y: Offset | ||
|
||
enum Offset { | ||
case pixels(CGFloat) | ||
case percent(CGFloat) | ||
} | ||
|
||
private static let geometryRegex = try! Regex(#"([+\-]\d+)(%?)([+\-]\d+)(%?)"#, as: (Substring, Substring, Substring, Substring, Substring).self) | ||
|
||
init?(argument: String) { | ||
guard let match = try! Self.geometryRegex.wholeMatch(in: argument) else { return nil } | ||
let (_, x, percentX, y, percentY) = match.output | ||
if percentX.isEmpty { | ||
self.x = .pixels(Double(x)!) | ||
} else { | ||
self.x = .percent(Double(x)!) | ||
} | ||
if percentY.isEmpty { | ||
self.y = .pixels(Double(y)!) | ||
} else { | ||
self.y = .percent(Double(y)!) | ||
} | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.