icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

commit 5001ec05889189ad6f7463133ec7d80e5bb4da34
parent 2c9154ea19a8d2bf0878dcdc41467af482242fa9
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date:   Mon, 21 Sep 2026 07:30:54 -0300

appicon: arrange the oracle the way a shipping icon is arranged

The first fixture crashed actool, which the note about that crash had said
adding lighting or a blend mode would avoid. It does not: the same values
across two groups still fall over, so the note now says what was seen.

Diffstat:
Mappicon/appicon.go | 26++++++++++++++++----------
Mappicon/oracle_darwin_test.go | 72++++++++++++++++++++++++++++++++++++++++++------------------------------
2 files changed, 58 insertions(+), 40 deletions(-)

diff --git a/appicon/appicon.go b/appicon/appicon.go @@ -13,17 +13,23 @@ // # A crash worth knowing about // // actool from Xcode 26.6 crashes, rather than reporting a problem, on some -// groups that specialise translucency. A group holding only a shadow and -// translucency specialised for the default appearance and for tinted brings -// it down with an exception from inside its own asset selection. The same -// group compiles once it also carries lighting, a blend mode, or a -// translucency for dark, and so does the equivalent group taken from a -// shipping app. +// bundles that specialise translucency for the default appearance and for +// tinted. It falls over with an exception from inside its own asset +// selection, naming nothing. // -// The trigger is not fully characterised, so this package writes what it is -// given and leaves the judgement to the compiler. A bundle that fails to -// compile with an exception rather than an error is worth reshaping before -// it is worth debugging. +// Two arrangements are known to bring it down: a group carrying only a +// shadow and that pair of translucencies, and a bundle spreading the pair +// and the layer they apply to across two groups. Two are known to compile: +// the same values gathered into one group with the layer they apply to, +// which is how a shipping app arranges them, and the pair with a +// translucency for dark added. +// +// The trigger is not characterised beyond that, and it is not simply the +// group's own fields: a group carrying lighting and a blend mode alongside +// the pair compiles on its own and crashes in a bundle of two groups. So +// this package writes what it is given and leaves the judgement to the +// compiler. A bundle that fails with an exception rather than an error is +// worth rearranging before it is worth debugging. package appicon import ( diff --git a/appicon/oracle_darwin_test.go b/appicon/oracle_darwin_test.go @@ -97,47 +97,59 @@ func TestActoolTakesASingleLayer(t *testing.T) { } // TestActoolTakesTheAppearanceFields covers the fields that vary by -// appearance together, in the arrangement a shipping app uses. They have -// nowhere else to be checked: a manifest actool ignores looks exactly like -// one it honours. +// appearance together, arranged the way a shipping app arranges them: one +// group holding the specialised values and the layer they apply to. Spread +// across two groups the same values crash actool, which is the arrangement +// the package documentation warns about. func TestActoolTakesTheAppearanceFields(t *testing.T) { - material := 1.0 bundle := written(t, "Composed", Bundle{ Fills: []Specialized[Fill]{ {Value: NamedFill("system-light")}, {Appearance: AppearanceDark, Value: NamedFill("system-dark")}, }, + Groups: []Group{{ + BlendModes: []Specialized[string]{{Appearance: AppearanceTinted, Value: "normal"}}, + Lighting: "individual", + Specular: true, + Shadow: &Shadow{Kind: "layer-color", Opacity: 0.5}, + Translucencies: []Specialized[Translucency]{ + {Value: Translucency{Enabled: true, Value: 0.84}}, + {Appearance: AppearanceTinted, Value: Translucency{Enabled: false, Value: 0.84}}, + }, + Layers: []Layer{{ + Name: "Front", + Image: layer(210, color.NRGBA{R: 40, G: 70, B: 200, A: 255}), + Glass: true, + Position: &Position{Scale: 1.24, Translation: [2]float64{0, 0}}, + Fills: []Specialized[Fill]{ + {Appearance: AppearanceDark, Value: NamedFill("automatic")}, + {Appearance: AppearanceTinted, Value: GradientFill( + "display-p3:0.90000,0.90000,0.90000,0.83000", + "srgb:1.00000,1.00000,1.00000,0.41987", + )}, + }, + BlendModes: []Specialized[string]{{Appearance: AppearanceDark, Value: "lighten"}}, + }}, + }}, + }) + compiled(t, compile(t, bundle)) +} + +// TestActoolTakesSeveralGroups covers a stack of groups, which is how an +// icon with a background and a mark on top is built. +func TestActoolTakesSeveralGroups(t *testing.T) { + bundle := written(t, "Stacked", Bundle{ Groups: []Group{ { - BlendModes: []Specialized[string]{{Appearance: AppearanceTinted, Value: "normal"}}, - BlurMaterials: []Specialized[float64]{{Value: material}}, - Lighting: "individual", - Specular: true, - Shadow: &Shadow{Kind: "layer-color", Opacity: 0.5}, - Translucencies: []Specialized[Translucency]{ - {Value: Translucency{Enabled: true, Value: 0.84}}, - {Appearance: AppearanceTinted, Value: Translucency{Enabled: false, Value: 0.84}}, - }, - Layers: []Layer{{ - Name: "Back", - Image: layer(210, color.NRGBA{R: 40, G: 70, B: 200, A: 255}), - }}, + Layers: []Layer{{Name: "Back", Image: layer(210, color.NRGBA{R: 40, G: 70, B: 200, A: 255})}}, + Shadow: &Shadow{Kind: ShadowNeutral, Opacity: 0.5}, }, { Layers: []Layer{{ - Name: "Front", - Image: layer(130, color.NRGBA{R: 240, G: 240, B: 250, A: 255}), - Glass: true, - Hidden: false, - Position: &Position{Scale: 1.24, Translation: [2]float64{0, -12}}, - Fills: []Specialized[Fill]{ - {Appearance: AppearanceDark, Value: NamedFill("automatic")}, - {Appearance: AppearanceTinted, Value: GradientFill( - "display-p3:0.90000,0.90000,0.90000,0.83000", - "srgb:1.00000,1.00000,1.00000,0.41987", - )}, - }, - BlendModes: []Specialized[string]{{Appearance: AppearanceDark, Value: "lighten"}}, + Name: "Front", + Image: layer(130, color.NRGBA{R: 240, G: 240, B: 250, A: 255}), + Glass: true, + Hidden: false, }}, }, },