feat: add support for apple color list / .clr files (#87)
* feat: add support for apple color list / .clr files * style: format with swift-format * refactor: minor changes * refactor: simplify `hexToRGBA` * fix: use `red` not `calibratedRed` parameter for NSColor * refactor: clean up arg handling * ci(release-please): use macos runner, setup swift * fix: import Buffer type * fix: require `COMPILE_APPLE_COLOR_LIST=1` * ci(test): switch to macos & build everything * ci: upload palettes as artifact * Update .github/workflows/test.yml Co-authored-by: uncenter <47499684+uncenter@users.noreply.github.com> --------- Co-authored-by: sgoudham <sgoudham@gmail.com> Co-authored-by: Hammy <58985301+sgoudham@users.noreply.github.com>
This commit is contained in:
parent
628a04a0ee
commit
88e2795c5d
|
@ -30,7 +30,7 @@ jobs:
|
|||
tag_name: ${{ steps.release.outputs.tag_name }}
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-latest
|
||||
needs: release-please
|
||||
if: ${{ needs.release-please.outputs.release_created || github.event.inputs.force_release }}
|
||||
steps:
|
||||
|
@ -43,8 +43,14 @@ jobs:
|
|||
node-version: "lts/*"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- uses: swift-actions/setup-swift@v2
|
||||
with:
|
||||
swift-version: "5"
|
||||
|
||||
- name: Build
|
||||
run: deno task build
|
||||
env:
|
||||
COMPILE_APPLE_COLOR_LIST: 1
|
||||
|
||||
- name: Publish NPM package
|
||||
working-directory: dist/npm
|
||||
|
|
|
@ -8,13 +8,17 @@ on:
|
|||
jobs:
|
||||
main:
|
||||
name: Lint and test
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: nekowinston/setup-deno@v1
|
||||
|
||||
- uses: swift-actions/setup-swift@v2
|
||||
with:
|
||||
swift-version: "5"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
|
@ -28,5 +32,13 @@ jobs:
|
|||
- name: Test
|
||||
run: deno test --doc
|
||||
|
||||
- name: Test Deno dnt
|
||||
run: deno task build:npm
|
||||
- name: Build
|
||||
run: deno task build
|
||||
env:
|
||||
COMPILE_APPLE_COLOR_LIST: 1
|
||||
|
||||
- name: Upload Built Palette Formats
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "Catppuccin Palette Formats"
|
||||
path: dist/palettes
|
||||
|
|
|
@ -95,6 +95,7 @@ Please use the respective files in [the latest GitHub Release](https://github.co
|
|||
| Adobe Suite, Affinity Suite, Sip | `ase/` |
|
||||
| Aseprite, Gimp, Inkscape, Krita | `gimp/` |
|
||||
| Procreate | `procreate/` |
|
||||
| Apple Color List (.clr) | `clr/` |
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,9 +2,14 @@
|
|||
"version": "3",
|
||||
"packages": {
|
||||
"specifiers": {
|
||||
"npm:@types/node": "npm:@types/node@18.16.19",
|
||||
"npm:chalk@5": "npm:chalk@5.3.0"
|
||||
},
|
||||
"npm": {
|
||||
"@types/node@18.16.19": {
|
||||
"integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==",
|
||||
"dependencies": {}
|
||||
},
|
||||
"chalk@5.3.0": {
|
||||
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
|
||||
"dependencies": {}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { join } from "std/path/mod.ts";
|
|||
import { flavors } from "@catppuccin/palette";
|
||||
import {
|
||||
generateAse,
|
||||
generateClrJson,
|
||||
generateGimp,
|
||||
generatePng,
|
||||
generateProcreate,
|
||||
|
@ -14,14 +15,14 @@ const ROOT = new URL("../dist/palettes", import.meta.url).pathname;
|
|||
await emptyDir(ROOT);
|
||||
|
||||
await Promise.all(
|
||||
["ase", "gimp", "procreate", "png", "sip"].map((folder) =>
|
||||
["ase", "gimp", "procreate", "png", "sip", "clr"].map((folder) =>
|
||||
ensureDir(join(ROOT, folder))
|
||||
),
|
||||
);
|
||||
|
||||
Promise.all(
|
||||
Object.entries(flavors).flatMap(async ([name, { colors }]) => {
|
||||
const fname = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
Object.entries(flavors).flatMap(async ([identifier, { name, colors }]) => {
|
||||
const fname = identifier.charAt(0).toUpperCase() + identifier.slice(1);
|
||||
|
||||
await Deno.writeFile(
|
||||
join(ROOT, `ase/${fname}.ase`),
|
||||
|
@ -43,5 +44,30 @@ Promise.all(
|
|||
join(ROOT, `sip/${fname}.palette`),
|
||||
generateSip(fname, colors),
|
||||
);
|
||||
|
||||
if (Deno.env.get("COMPILE_APPLE_COLOR_LIST") === "1") {
|
||||
const clrJson = join(ROOT, `clr/${fname}.json`);
|
||||
await Deno.writeTextFile(
|
||||
clrJson,
|
||||
generateClrJson(fname, colors),
|
||||
);
|
||||
|
||||
const cmd = new Deno.Command("swift", {
|
||||
args: [
|
||||
join(import.meta.dirname!, "./builders/palettes/json-to-clr.swift"),
|
||||
clrJson,
|
||||
join(ROOT, `clr/${name}.clr`),
|
||||
],
|
||||
});
|
||||
const { code, stderr, stdout } = await cmd.output();
|
||||
const td = new TextDecoder();
|
||||
if (code === 0) {
|
||||
console.log(td.decode(stdout).trim());
|
||||
} else {
|
||||
throw new Error(td.decode(stderr));
|
||||
}
|
||||
|
||||
await Deno.remove(clrJson);
|
||||
}
|
||||
}),
|
||||
).then(() => Deno.exit(0));
|
||||
|
|
|
@ -7,3 +7,4 @@ export { generateGimp } from "./palettes/gimp.ts";
|
|||
export { generatePng } from "./palettes/png.ts";
|
||||
export { generateProcreate } from "./palettes/procreate.ts";
|
||||
export { generateSip } from "./palettes/sip.ts";
|
||||
export { generateClrJson } from "./palettes/clr.ts";
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import type { CatppuccinColors } from "@catppuccin/palette";
|
||||
|
||||
export const generateClrJson = (
|
||||
_name: string,
|
||||
palette: CatppuccinColors,
|
||||
): string => {
|
||||
const data: Record<string, { hex: string; order: number }> = Object
|
||||
.fromEntries(
|
||||
Object.entries(palette).map(([_, { name, hex, order }]) => {
|
||||
return [name, { hex, order }];
|
||||
}),
|
||||
);
|
||||
|
||||
return JSON.stringify(data);
|
||||
};
|
|
@ -0,0 +1,57 @@
|
|||
import AppKit
|
||||
import Foundation
|
||||
|
||||
struct ColorProperties: Decodable {
|
||||
let hex: String
|
||||
let order: Int
|
||||
}
|
||||
|
||||
typealias ColorList = [String: ColorProperties]
|
||||
|
||||
func hexToRGBA(_ color: String) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
|
||||
var hexColor = String(color.dropFirst())
|
||||
|
||||
let r = CGFloat(Int(hexColor.prefix(2), radix: 16) ?? 0) / 255
|
||||
let g = CGFloat(Int(hexColor.dropFirst(2).prefix(2), radix: 16) ?? 0) / 255
|
||||
let b = CGFloat(Int(hexColor.dropFirst(4).prefix(2), radix: 16) ?? 0) / 255
|
||||
return (r, g, b)
|
||||
}
|
||||
|
||||
func convertJSONToCLR(inputFilePath: String, outputFilePath: String) {
|
||||
let url = URL(fileURLWithPath: inputFilePath)
|
||||
guard let data = try? Data(contentsOf: url),
|
||||
let colorList = try? JSONDecoder().decode(ColorList.self, from: data)
|
||||
else {
|
||||
print("Failed to read or parse JSON file.")
|
||||
return
|
||||
}
|
||||
|
||||
let sortedColors = colorList.sorted { (lhs, rhs) -> Bool in
|
||||
return lhs.value.order < rhs.value.order
|
||||
}
|
||||
|
||||
let paletteName = url.deletingPathExtension().lastPathComponent
|
||||
let nsColorList = NSColorList(name: paletteName)
|
||||
|
||||
for (name, properties) in sortedColors {
|
||||
let hex = properties.hex
|
||||
let color = hexToRGBA(hex)
|
||||
nsColorList.setColor(
|
||||
NSColor(red: color.r, green: color.g, blue: color.b, alpha: 1), forKey: name)
|
||||
}
|
||||
|
||||
let clrFilePath = URL(fileURLWithPath: outputFilePath)
|
||||
do {
|
||||
try nsColorList.write(to: clrFilePath)
|
||||
print("Successfully saved palette to \(outputFilePath).")
|
||||
} catch {
|
||||
print("Failed to save color palette: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
if CommandLine.argc != 3 {
|
||||
print("\(CommandLine.arguments[0]): Not enough arguments provided (expected input and output filenames)")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
convertJSONToCLR(inputFilePath: CommandLine.arguments[1], outputFilePath: CommandLine.arguments[2])
|
|
@ -1,9 +1,11 @@
|
|||
import type { Buffer } from "node:buffer";
|
||||
|
||||
type ColorSpace = "rgb" | "hsl" | "hsv" | "hwb" | "xyz" | "lab" | "lch";
|
||||
type Colors = [number[], ColorSpace][];
|
||||
|
||||
export function readSwatchesFile(
|
||||
data: string | Uint8Array | ArrayBuffer | Blob,
|
||||
colorSpace?: ColorSpace
|
||||
colorSpace?: ColorSpace,
|
||||
): Promise<{
|
||||
name: string;
|
||||
colors: Colors;
|
||||
|
@ -21,11 +23,11 @@ type SwatchReturnType = {
|
|||
};
|
||||
|
||||
export function createSwatchesFile<
|
||||
F extends keyof SwatchReturnType | undefined = undefined
|
||||
F extends keyof SwatchReturnType | undefined = undefined,
|
||||
>(
|
||||
name: string,
|
||||
colors: Colors,
|
||||
format?: F
|
||||
format?: F,
|
||||
): Promise<F extends keyof SwatchReturnType ? SwatchReturnType[F] : Uint8Array>;
|
||||
|
||||
interface ProcreateSwatchesError extends Error {
|
||||
|
|
Loading…
Reference in New Issue