Skip to content

Commit 6026f54

Browse files
committed
Added Skencil (spl) support
1 parent 28dedf5 commit 6026f54

File tree

8 files changed

+239
-0
lines changed

8 files changed

+239
-0
lines changed

Quicklook/Palette Viewer/Palette Viewer/Info.plist

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,20 @@
572572
<key>NSDocumentClass</key>
573573
<string>$(PRODUCT_MODULE_NAME).Document</string>
574574
</dict>
575+
<dict>
576+
<key>CFBundleTypeName</key>
577+
<string>Skencil Palette</string>
578+
<key>CFBundleTypeRole</key>
579+
<string>Editor</string>
580+
<key>LSHandlerRank</key>
581+
<string>Default</string>
582+
<key>LSItemContentTypes</key>
583+
<array>
584+
<string>public.dagronf.colorpalette.palette.skencil</string>
585+
</array>
586+
<key>NSDocumentClass</key>
587+
<string>$(PRODUCT_MODULE_NAME).Document</string>
588+
</dict>
575589
</array>
576590
<key>UTExportedTypeDeclarations</key>
577591
<array>
@@ -1263,6 +1277,25 @@
12631277
</array>
12641278
</dict>
12651279
</dict>
1280+
<dict>
1281+
<key>UTTypeConformsTo</key>
1282+
<array>
1283+
<string>public.text</string>
1284+
</array>
1285+
<key>UTTypeDescription</key>
1286+
<string>Skencil Palette</string>
1287+
<key>UTTypeIcons</key>
1288+
<dict/>
1289+
<key>UTTypeIdentifier</key>
1290+
<string>public.dagronf.colorpalette.palette.skencil</string>
1291+
<key>UTTypeTagSpecification</key>
1292+
<dict>
1293+
<key>public.filename-extension</key>
1294+
<array>
1295+
<string>spl</string>
1296+
</array>
1297+
</dict>
1298+
</dict>
12661299
</array>
12671300
<key>UTImportedTypeDeclarations</key>
12681301
<array>

Quicklook/Palette Viewer/PaletteViewerPaletteThumbnailGenerator/Info.plist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<string>public.dagronf.colorpalette.palette.sk1</string>
3939
<string>public.dagronf.colorpalette.palette.koffice</string>
4040
<string>public.dagronf.colorpalette.palette.homesite</string>
41+
<string>public.dagronf.colorpalette.palette.skencil</string>
4142
</array>
4243
<key>QLThumbnailMinimumDimension</key>
4344
<integer>0</integer>

Quicklook/Palette Viewer/PaletteViewerQuickLookPlugin/Info.plist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<string>public.dagronf.colorpalette.palette.sk1</string>
4141
<string>public.dagronf.colorpalette.palette.koffice</string>
4242
<string>public.dagronf.colorpalette.palette.homesite</string>
43+
<string>public.dagronf.colorpalette.palette.skencil</string>
4344
</array>
4445
<key>QLSupportsSearchableItems</key>
4546
<false/>

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Supports the following :-
5353
* SK1 Color Palette (`.skp`) ***Very basic support***
5454
* KOffice Color Palette (`.colors`)
5555
* Homesite Palette (`.hpl`)
56+
* Skencil Palette (`.spl`)
5657

5758
## Supported gradient formats
5859

@@ -129,6 +130,7 @@ Some features :-
129130
|`PAL.Coder.SimplePaletteCoder` | Simple Palette (.color-palette) v0.1 |
130131
|`PAL.Coder.SketchPalette` | Sketch Palette (.sketchpalette) |
131132
|`PAL.Coder.SKP` | SK1 Color Palette (.skp) |
133+
|`PAL.Coder.Skencil` | Skencil Color Palette (.spl) |
132134
|`PAL.Coder.SVG` | SVG image file (.svg) |
133135
|`PAL.Coder.SwatchbookerCoder | Swatchbooker Palette (.sbz) |
134136
|`PAL.Coder.VGA24BitPaletteCoder` | 24-bit VGA palette (.pal) |
@@ -225,6 +227,7 @@ let paletteData = palette.export(format: .ase)
225227
| `PAL.Coder.RIFF` | Binary ||||||| RGB only ||
226228
| `PAL.Coder.ScribusXMLPaletteCoder` | XML ||||||| RGB/CMYK only ||
227229
| `PAL.Coder.SimplePaletteCoder` | JSON ||||||| RGB only ||
230+
| `PAL.Coder.SkencilPalette` | Text ||||||| RGB only ||
228231
| `PAL.Coder.SketchPalette` | XML ||||||| RGB only ||
229232
| `PAL.Coder.SKP` | Text ||||||| RGB only ||
230233
| `PAL.Coder.SVG` | SVG text ||||||| RGB only ||

Sources/ColorPaletteCodable/palette/PAL+PaletteFormat.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public extension PAL {
6161
case xara // Xara palette file (.jcw)
6262
case koffice // KOffice palette file (.colors)
6363
case hpl // Homesite Palette file (.hpl)
64+
case skencil // Skencil Palette file (.spl)
6465

6566
// This needs to go last, so it doesn't override the other PAL types
6667
case vga24bit // 24-bit RGB VGA (3 bytes RRGGBB)
@@ -113,6 +114,7 @@ public extension PAL.PaletteFormat {
113114
case .xara : return PAL.Coder.JCW()
114115
case .koffice : return PAL.Coder.KOffice()
115116
case .hpl : return PAL.Coder.HPL()
117+
case .skencil : return PAL.Coder.Skencil()
116118
}
117119
}
118120
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//
2+
// Copyright © 2025 Darren Ford. All rights reserved.
3+
//
4+
// MIT license
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7+
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
8+
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
9+
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in all copies or substantial
12+
// portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
15+
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
16+
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18+
//
19+
20+
import DSFRegex
21+
import Foundation
22+
23+
// ##Sketch RGBPalette 0
24+
// 0.000000 0.000000 0.000000 Black
25+
// 0.100000 0.100000 0.100000 90% Black
26+
// 0.200000 0.200000 0.200000 80% Black
27+
// 0.300000 0.300000 0.300000 70% Black
28+
// 0.400000 0.400000 0.400000 60% Black
29+
30+
public extension PAL.Coder {
31+
struct Skencil: PAL_PaletteCoder {
32+
public let format: PAL.PaletteFormat = .skencil
33+
public let name = "Skencil Palette"
34+
public static var utTypeString: String = "public.dagronf.colorpalette.palette.skencil"
35+
public let fileExtension = ["spl"]
36+
}
37+
}
38+
39+
public extension PAL.Coder.Skencil {
40+
func decode(from inputStream: InputStream) throws -> PAL.Palette {
41+
// Load a string from the input stream
42+
guard let decoded = String.decode(from: inputStream) else {
43+
ColorPaletteLogger.log(.error, "Skencil coder: Unexpected text encoding")
44+
throw PAL.CommonError.invalidFormat
45+
}
46+
let content = decoded.text
47+
48+
let lines = content.lines
49+
guard
50+
lines.count > 0,
51+
lines[0].contains("##Sketch RGBPalette 0")
52+
else {
53+
throw PAL.CommonError.invalidFormat
54+
}
55+
56+
var palette = PAL.Palette(format: self.format)
57+
58+
// Regex for the color line(s)
59+
let colorRegex = try DSFRegex(#"^\s*(\d*\.\d+|\d+)\s+(\d*\.\d+|\d+)\s+(\d*\.\d+|\d+)\s+(.*)$"#)
60+
61+
for line in lines.dropFirst() {
62+
let lineStr = String(line)
63+
64+
if let match = colorRegex.matches(for: lineStr).matches.first {
65+
let rs = lineStr[match.captures[0]]
66+
let gs = lineStr[match.captures[1]]
67+
let bs = lineStr[match.captures[2]]
68+
let ss = lineStr[match.captures[3]]
69+
70+
guard
71+
let rv = Double(rs),
72+
let gv = Double(gs),
73+
let bv = Double(bs)
74+
else {
75+
continue
76+
}
77+
78+
let sv = ss.trimmingCharacters(in: .whitespacesAndNewlines)
79+
80+
let re = max(0, min(1, rv))
81+
let ge = max(0, min(1, gv))
82+
let be = max(0, min(1, bv))
83+
84+
let c = try PAL.Color(colorSpace: .RGB, colorComponents: [re, ge, be], name: sv)
85+
palette.colors.append(c)
86+
}
87+
}
88+
return palette
89+
}
90+
}
91+
92+
private let __formatter = NumberFormatter(
93+
minimumFractionDigits: 6,
94+
maximumFractionDigits: 6,
95+
decimalSeparator: "."
96+
)
97+
98+
public extension PAL.Coder.Skencil {
99+
func encode(_ palette: PAL.Palette) throws -> Data {
100+
101+
var result = "##Sketch RGBPalette 0\n"
102+
let flattenedColors = try palette.allColors().map { try $0.converted(to: .RGB) }
103+
104+
flattenedColors.forEach { color in
105+
if
106+
let rs = __formatter.string(for: color._r),
107+
let gs = __formatter.string(for: color._g),
108+
let bs = __formatter.string(for: color._b)
109+
{
110+
result += "\(rs) \(gs) \(bs)"
111+
if color.name.count > 0 {
112+
result += " \(color.name)"
113+
}
114+
result += "\n"
115+
}
116+
}
117+
118+
guard let data = result.data(using: .utf8) else {
119+
throw PAL.CommonError.unsupportedColorSpace
120+
}
121+
return data
122+
}
123+
}
124+
125+
// MARK: - UTType identifiers
126+
127+
#if canImport(UniformTypeIdentifiers)
128+
import UniformTypeIdentifiers
129+
@available(macOS 11, iOS 14, tvOS 14, watchOS 7, *)
130+
public extension UTType {
131+
static let skencilPalette = UTType(PAL.Coder.Skencil.utTypeString)!
132+
}
133+
#endif
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
@testable import ColorPaletteCodable
2+
import XCTest
3+
4+
import Foundation
5+
6+
class SkencilPaletteTests: XCTestCase {
7+
func testBasicLoad() throws {
8+
let palette = try loadResourcePalette(named: "mini.spl")
9+
10+
XCTAssertEqual(23, palette.colors.count)
11+
12+
XCTAssertEqual("Black", palette.colors[0].name)
13+
XCTAssertEqual("#000000ff", try palette.colors[0].hexRGBA(hashmark: true))
14+
15+
XCTAssertEqual("Green", palette.colors[12].name)
16+
XCTAssertEqual("#00ff00ff", try palette.colors[12].hexRGBA(hashmark: true))
17+
18+
XCTAssertEqual("Dark Yellow", palette.colors[22].name)
19+
XCTAssertEqual("#808000ff", try palette.colors[22].hexRGBA(hashmark: true))
20+
}
21+
22+
func testBasicSaveReload() throws {
23+
let paletteURL = try XCTUnwrap(Bundle.module.url(forResource: "Default", withExtension: "gpl"))
24+
let palette = try PAL.Palette.Decode(from: paletteURL)
25+
XCTAssertEqual(23, palette.colors.count)
26+
27+
let saved = try PAL.Coder.Skencil().encode(palette)
28+
29+
let loaded = try PAL.Coder.Skencil().decode(from: saved)
30+
XCTAssertEqual(23, loaded.colors.count)
31+
32+
XCTAssertEqual(loaded.colors[11].name, "Dark Yellow")
33+
XCTAssertEqual(loaded.colors[11].colorComponents[0], 0.509803921, accuracy: 0.0001)
34+
XCTAssertEqual(loaded.colors[11].colorComponents[1], 0.498039215, accuracy: 0.0001)
35+
XCTAssertEqual(loaded.colors[11].colorComponents[2], 0.0, accuracy: 0.01)
36+
37+
XCTAssertEqual(loaded.colors[13].name, "Gray 10%")
38+
XCTAssertEqual(loaded.colors[13].colorComponents[0], 0.1, accuracy: 0.01)
39+
XCTAssertEqual(loaded.colors[13].colorComponents[1], 0.1, accuracy: 0.01)
40+
XCTAssertEqual(loaded.colors[13].colorComponents[2], 0.1, accuracy: 0.01)
41+
}
42+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
##Sketch RGBPalette 0
2+
0.000000 0.000000 0.000000 Black
3+
0.100000 0.100000 0.100000 90% Black
4+
0.200000 0.200000 0.200000 80% Black
5+
0.300000 0.300000 0.300000 70% Black
6+
0.400000 0.400000 0.400000 60% Black
7+
0.500000 0.500000 0.500000 50% Black
8+
0.600000 0.600000 0.600000 40% Black
9+
0.700000 0.700000 0.700000 30% Black
10+
0.800000 0.800000 0.800000 20% Black
11+
0.900000 0.900000 0.900000 10% Black
12+
1.000000 1.000000 1.000000 White
13+
1.000000 0.000000 0.000000 Red
14+
0.000000 1.000000 0.000000 Green
15+
0.000000 0.000000 1.000000 Blue
16+
0.000000 1.000000 1.000000 Cyan
17+
1.000000 0.000000 1.000000 Magenta
18+
1.000000 1.000000 0.000000 Yellow
19+
0.500000 0.000000 0.000000 Dark Red
20+
0.000000 0.500000 0.000000 Dark Green
21+
0.000000 0.000000 0.500000 Dark Blue
22+
0.000000 0.500000 0.500000 Dark Cyan
23+
0.500000 0.000000 0.500000 Dark Magenta
24+
0.500000 0.500000 0.000000 Dark Yellow

0 commit comments

Comments
 (0)