Compare commits

..

10 commits

22 changed files with 1025 additions and 4 deletions

38
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-${{ matrix.ubuntu }}
strategy:
matrix:
ubuntu: ["22.04", "24.04"]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install test deps
run: |
pip install pytest
sudo apt-get install -y bats shellcheck inkscape
- name: Python unit tests
run: pytest tests/test_merge_prefs.py -v
- name: Shellcheck
run: |
shellcheck install.sh
shellcheck uninstall.sh
shellcheck scripts/detect_platform.sh
- name: Bats integration tests
run: bats tests/test_install.bats

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ __pycache__/
.venv/ .venv/
*.bak *.bak
tests/fixtures/prefs-missing/.keep tests/fixtures/prefs-missing/.keep
.coverage

82
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,82 @@
# Contributing to Illuscape
Thank you for helping make Inkscape more accessible to Illustrator migrants.
## How to add a keyboard shortcut
1. Open `config/keys/illustrator-cc.xml` and/or `config/keys/illustrator-cs6.xml`
2. Add a `<bind>` element:
```xml
<bind key="KEY" action="INKSCAPE_ACTION" modifiers="Primary,Shift" />
```
Valid modifiers: `Primary` (Ctrl/Cmd), `Shift`, `Alt`. Omit `modifiers=""` for bare keys.
3. Find Inkscape action names via `inkscape --action-list` on your system
4. Update the shortcut table in `README.md`
5. Update the shortcut comparison table in the spec if the shortcut differs between CC and CS6
## How to add a color palette
1. Create `config/palettes/YourPalette.gpl` following GPL format:
```
GIMP Palette
Name: Your Palette Name
Columns: 8
#
255 0 0 Red
...
```
2. Add the filename to `uninstall.sh`'s `remove_files()` function so it gets cleaned up
3. The palette will be installed automatically by `install.sh` (it copies all `*.gpl` files)
## How to add a document template
1. Create an SVG at `config/templates/YourTemplate.svg`
2. Include a `sodipodi:namedview` element with appropriate canvas settings
3. Add the filename to `uninstall.sh`'s `remove_files()` function
4. The template will be installed automatically by `install.sh` (copies all `*.svg` files)
## How to add a preference
Preferences live in `config/preferences-patch.xml`. The XML merge algorithm (in
`scripts/merge_prefs.py`) does an upsert: it walks the patch tree, matches nodes
by their `id` attribute, and sets only the attributes present in the patch. It
never deletes nodes or attributes.
To add a new preference:
1. Find the relevant node in Inkscape's `preferences.xml` on a running install
2. Add the node/attribute to `config/preferences-patch.xml` with the correct `id` values
3. Write a test in `tests/test_merge_prefs.py` that verifies the attribute is set
4. Run `conda run -n cf python3 -m pytest tests/test_merge_prefs.py -v` to verify
## Running tests
```bash
# Python unit tests (merge algorithm)
conda run -n cf python3 -m pytest tests/test_merge_prefs.py -v
# Integration tests (requires bats-core)
bats tests/test_install.bats
# Validate all config files
conda run -n cf python3 -c "
from xml.etree import ElementTree as ET
from pathlib import Path
for f in [*Path('config/keys').glob('*.xml'), *Path('config/templates').glob('*.svg'),
*Path('config/symbols').glob('*.svg'), Path('config/preferences-patch.xml')]:
ET.parse(f)
print(f'OK {f}')
"
```
## Commit format
```
feat: add <shortcut/palette/template>
fix: correct <behavior>
test: add test for <scenario>
docs: update <section>
```
## License
By contributing, you agree that your contributions are released under the MIT license.

View file

@ -6,6 +6,26 @@ Illuscape patches Inkscape's config to match Illustrator's keyboard shortcuts,
workspace layout, scroll/zoom behavior, color palettes, and document templates — workspace layout, scroll/zoom behavior, color palettes, and document templates —
without modifying Inkscape itself. without modifying Inkscape itself.
## What it does
| Feature | Detail |
|---------|--------|
| Keyboard shortcuts | Adobe Illustrator CC or CS6 presets — your choice at install |
| Scroll/zoom | Mouse wheel pans (not zooms); Ctrl+wheel zooms — matching Illustrator |
| Canvas | White background, cyan guides, rubber-band selection (touching, not enclosed) |
| Node handles | Square handles, rotation handles hidden until needed |
| Palettes | Illustrator Defaults, Grays, and Earth Tones swatches |
| Templates | Letter, A4, Web 1920x1080, Web 1280x720, Print CMYK Letter, Print CMYK A4 |
| Symbols | Common symbol library (arrows, checkmark, star, cross) |
| Recent files | 20 (matching Illustrator; Inkscape default is 4) |
| Single-window mode | On by default |
## Requirements
- Inkscape 1.2 or later
- Python 3.8+
- Bash 4+
## Install ## Install
```bash ```bash
@ -14,7 +34,21 @@ cd illuscape
./install.sh ./install.sh
``` ```
Follow the prompt to select your Illustrator era (CC or CS6), then open Inkscape. Follow the prompt to choose your Illustrator era (CC or CS6), then open Inkscape.
### Non-interactive (for scripts / MenagerieOS)
```bash
./install.sh --preset=cc --yes
# or
./install.sh --preset=cs6 --yes
```
### Supported Inkscape installs
- Native (apt, pacman, etc.)
- Flatpak (`org.inkscape.Inkscape`)
- Snap
## Uninstall ## Uninstall
@ -22,8 +56,46 @@ Follow the prompt to select your Illustrator era (CC or CS6), then open Inkscape
./uninstall.sh ./uninstall.sh
``` ```
Your original Inkscape config is restored from backup. Your original Inkscape config is restored from the backup created at install time.
## Tool mapping from Illustrator to Inkscape
| Illustrator tool | Key | Inkscape equivalent |
|-----------------|-----|---------------------|
| Selection | V | Selection tool |
| Direct Selection | A | Node tool |
| Pen | P | Pen tool |
| Pencil | N | Pencil tool |
| Paintbrush | B | Calligraphy tool |
| Type | T | Text tool |
| Rectangle | M | Rectangle tool |
| Ellipse | L | Ellipse tool |
| Rotate | R | Transform > Rotate |
| Scale | S | Transform > Scale |
| Reflect | O | Transform > Flip |
| Gradient | G | Gradient tool |
| **Mesh** | **U** | **Mesh Gradient tool (direct equivalent)** |
| Eyedropper | I | Dropper tool |
| Zoom | Z | Zoom tool |
| Hand | H / Space | Pan (Space held) |
| Scissors | C | Scissors tool |
| Eraser | Shift+E | Eraser tool |
| Blob Brush | Shift+B | Spray tool (paint mode) |
| Symbol Sprayer | Shift+S | Spray tool (clone mode) |
| Live Paint | K | Paint Bucket tool |
## Planned extensions (post-v1)
- Artboards panel
- Character Styles
- Recolor Artwork
- Object Styles
- Export for Screens (1x/2x/3x batch)
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
## License ## License
MIT MIT — see [LICENSE](LICENSE).

9
assets/illuscape.svg Normal file
View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Illuscape app icon source. Replace with final artwork before v1.0 release. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<rect width="512" height="512" rx="80" fill="#1a1a2e"/>
<rect x="200" y="80" width="112" height="24" rx="4" fill="#e8c547"/>
<rect x="232" y="104" width="48" height="304" rx="4" fill="#e8c547"/>
<rect x="200" y="408" width="112" height="24" rx="4" fill="#e8c547"/>
<path d="M320 200 L380 140 L340 180 Z" fill="#4a9eff" opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 563 B

View file

@ -0,0 +1,12 @@
[Desktop Entry]
Name=Illuscape
GenericName=Vector Graphics Editor
Comment=Inkscape configured for Adobe Illustrator migrants
Exec=inkscape %F
Icon=illuscape
Terminal=false
Type=Application
MimeType=image/svg+xml;image/svg+xml-compressed;application/illustrator;
Categories=Graphics;VectorGraphics;
Keywords=vector;SVG;draw;illustrator;
StartupNotify=true

19
assets/splash.svg Normal file
View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Illuscape splash screen source. Replace with final artwork before v1.0 release. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 400" width="600" height="400">
<rect width="600" height="400" fill="#1a1a2e"/>
<g stroke="#2a2a4e" stroke-width="1">
<line x1="0" y1="100" x2="600" y2="100"/>
<line x1="0" y1="200" x2="600" y2="200"/>
<line x1="0" y1="300" x2="600" y2="300"/>
<line x1="150" y1="0" x2="150" y2="400"/>
<line x1="300" y1="0" x2="300" y2="400"/>
<line x1="450" y1="0" x2="450" y2="400"/>
</g>
<text x="300" y="200" font-family="sans-serif" font-size="72" font-weight="bold"
fill="#e8c547" text-anchor="middle" dominant-baseline="middle">Illuscape</text>
<text x="300" y="260" font-family="sans-serif" font-size="20"
fill="#8888aa" text-anchor="middle">Inkscape for Illustrator migrants</text>
<text x="580" y="385" font-family="monospace" font-size="13"
fill="#4a4a6a" text-anchor="end">v0.1.0</text>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,34 @@
GIMP Palette
Name: Illustrator Defaults
Columns: 8
#
0 0 0 Registration Black
0 0 0 Black
255 255 255 White
255 0 0 CMYK Red
0 0 255 CMYK Blue
0 255 0 CMYK Green
0 255 255 CMYK Cyan
255 0 255 CMYK Magenta
255 255 0 CMYK Yellow
255 153 0 CMYK Orange
128 0 128 CMYK Purple
128 64 0 CMYK Brown
255 192 203 Pink
255 165 0 Orange
128 128 0 Olive
0 128 128 Teal
0 0 128 Navy
128 0 0 Maroon
128 128 128 50% Gray
192 192 192 Silver
255 255 153 Light Yellow
153 255 153 Light Green
153 204 255 Light Blue
255 153 153 Light Red
204 153 255 Light Purple
255 204 153 Light Orange
153 255 255 Light Cyan
255 153 255 Light Magenta
240 240 240 Near White
16 16 16 Near Black

View file

@ -0,0 +1,24 @@
GIMP Palette
Name: Illustrator Earth Tones
Columns: 8
#
101 67 33 Dark Brown
139 90 43 Brown
160 120 60 Warm Brown
184 148 84 Tan
210 180 140 Burlywood
222 184 135 Wheat
245 222 179 Navajo White
255 248 220 Cornsilk
188 143 143 Rosy Brown
139 119 101 Warm Gray
107 84 63 Coffee
85 60 42 Espresso
156 102 31 Ochre
184 134 11 Dark Goldenrod
218 165 32 Goldenrod
238 203 173 Peach Puff
95 117 87 Sage
107 142 35 Olive Drab
85 107 47 Dark Olive
46 79 48 Forest Green

View file

@ -0,0 +1,15 @@
GIMP Palette
Name: Illustrator Grays
Columns: 10
#
255 255 255 White
230 230 230 10% Gray
204 204 204 20% Gray
179 179 179 30% Gray
153 153 153 40% Gray
128 128 128 50% Gray
102 102 102 60% Gray
77 77 77 70% Gray
51 51 51 80% Gray
26 26 26 90% Gray
0 0 0 Black

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Illuscape common symbols library.
Accessible in Inkscape via Object > Symbols > Illuscape Common.
-->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="illuscape-symbols"
style="display:none">
<title>Illuscape Common Symbols</title>
<symbol id="illuscape-arrow-right" viewBox="0 0 24 24" inkscape:label="Arrow Right">
<title>Arrow Right</title>
<path d="M4 12h16M14 6l6 6-6 6" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</symbol>
<symbol id="illuscape-arrow-left" viewBox="0 0 24 24" inkscape:label="Arrow Left">
<title>Arrow Left</title>
<path d="M20 12H4M10 18l-6-6 6-6" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</symbol>
<symbol id="illuscape-check" viewBox="0 0 24 24" inkscape:label="Checkmark">
<title>Checkmark</title>
<path d="M4 12l5 5L20 7" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</symbol>
<symbol id="illuscape-cross" viewBox="0 0 24 24" inkscape:label="Cross">
<title>Cross</title>
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2"
stroke-linecap="round" fill="none"/>
</symbol>
<symbol id="illuscape-star" viewBox="0 0 24 24" inkscape:label="Star">
<title>Star</title>
<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

33
config/templates/A4.svg Normal file
View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- A4 portrait -->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="793.7008"
height="1122.5197"
viewBox="0 0 793.7008 1122.5197"
version="1.1"
id="svg-a4">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="1.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:snap-global="true"
inkscape:snap-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-to-guides="true"
inkscape:guide-bbox="true"
units="mm">
</sodipodi:namedview>
<title>A4</title>
<g inkscape:label="Background" inkscape:groupmode="layer" id="layer-background" style="display:inline" />
<g inkscape:label="Guides" inkscape:groupmode="layer" id="layer-guides" style="display:inline" />
<g inkscape:label="Artwork" inkscape:groupmode="layer" id="layer-artwork" style="display:inline" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- US Letter portrait -->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="816.0000"
height="1056.0000"
viewBox="0 0 816.0000 1056.0000"
version="1.1"
id="svg-letter">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="1.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="in"
showgrid="false"
inkscape:snap-global="true"
inkscape:snap-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-to-guides="true"
inkscape:guide-bbox="true"
units="in">
</sodipodi:namedview>
<title>Letter</title>
<g inkscape:label="Background" inkscape:groupmode="layer" id="layer-background" style="display:inline" />
<g inkscape:label="Guides" inkscape:groupmode="layer" id="layer-guides" style="display:inline" />
<g inkscape:label="Artwork" inkscape:groupmode="layer" id="layer-artwork" style="display:inline" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- A4 with 3mm bleed. Note: Inkscape is RGB-native; set CMYK values manually. -->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="793.7008"
height="1122.5197"
viewBox="0 0 793.7008 1122.5197"
version="1.1"
id="svg-print-cmyk-a4">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="1.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:snap-global="true"
inkscape:snap-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-to-guides="true"
inkscape:guide-bbox="true"
units="mm">
<sodipodi:guide position="-11.3386,11.3386" orientation="1,0" />
<sodipodi:guide position="805.0394,11.3386" orientation="1,0" />
<sodipodi:guide position="11.3386,-11.3386" orientation="0,1" />
<sodipodi:guide position="11.3386,1133.8583" orientation="0,1" />
</sodipodi:namedview>
<title>Print-CMYK-A4</title>
<g inkscape:label="Background" inkscape:groupmode="layer" id="layer-background" style="display:inline" />
<g inkscape:label="Guides" inkscape:groupmode="layer" id="layer-guides" style="display:inline" />
<g inkscape:label="Artwork" inkscape:groupmode="layer" id="layer-artwork" style="display:inline" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- US Letter with 3mm bleed. Note: Inkscape is RGB-native; set CMYK values manually. -->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="816.0000"
height="1056.0000"
viewBox="0 0 816.0000 1056.0000"
version="1.1"
id="svg-print-cmyk-letter">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="1.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="in"
showgrid="false"
inkscape:snap-global="true"
inkscape:snap-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-to-guides="true"
inkscape:guide-bbox="true"
units="in">
<sodipodi:guide position="-11.3386,11.3386" orientation="1,0" />
<sodipodi:guide position="827.3386,11.3386" orientation="1,0" />
<sodipodi:guide position="11.3386,-11.3386" orientation="0,1" />
<sodipodi:guide position="11.3386,1067.3386" orientation="0,1" />
</sodipodi:namedview>
<title>Print-CMYK-Letter</title>
<g inkscape:label="Background" inkscape:groupmode="layer" id="layer-background" style="display:inline" />
<g inkscape:label="Guides" inkscape:groupmode="layer" id="layer-guides" style="display:inline" />
<g inkscape:label="Artwork" inkscape:groupmode="layer" id="layer-artwork" style="display:inline" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- HD web canvas (smaller) -->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1280.0000"
height="720.0000"
viewBox="0 0 1280.0000 720.0000"
version="1.1"
id="svg-web-1280x720">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="1.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="false"
inkscape:snap-global="true"
inkscape:snap-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-to-guides="true"
inkscape:guide-bbox="true"
units="px">
</sodipodi:namedview>
<title>Web-1280x720</title>
<g inkscape:label="Background" inkscape:groupmode="layer" id="layer-background" style="display:inline" />
<g inkscape:label="Guides" inkscape:groupmode="layer" id="layer-guides" style="display:inline" />
<g inkscape:label="Artwork" inkscape:groupmode="layer" id="layer-artwork" style="display:inline" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- HD web canvas -->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1920.0000"
height="1080.0000"
viewBox="0 0 1920.0000 1080.0000"
version="1.1"
id="svg-web-1920x1080">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="1.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="false"
inkscape:snap-global="true"
inkscape:snap-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-to-guides="true"
inkscape:guide-bbox="true"
units="px">
</sodipodi:namedview>
<title>Web-1920x1080</title>
<g inkscape:label="Background" inkscape:groupmode="layer" id="layer-background" style="display:inline" />
<g inkscape:label="Guides" inkscape:groupmode="layer" id="layer-guides" style="display:inline" />
<g inkscape:label="Artwork" inkscape:groupmode="layer" id="layer-artwork" style="display:inline" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

185
install.sh Executable file
View file

@ -0,0 +1,185 @@
#!/usr/bin/env bash
# Illuscape installer
# Usage: ./install.sh [--preset=cc|cs6] [--yes]
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PRESET=""
AUTO_YES=0
# ── Parse args ──────────────────────────────────────────────────────────────
for arg in "$@"; do
case "$arg" in
--preset=cc|--preset=cs6) PRESET="${arg#--preset=}" ;;
--yes|-y) AUTO_YES=1 ;;
--help|-h)
echo "Usage: ./install.sh [--preset=cc|cs6] [--yes]"
echo " --preset=cc Illustrator CC shortcuts (default)"
echo " --preset=cs6 Illustrator CS6 shortcuts"
echo " --yes Skip confirmation prompts"
exit 0 ;;
*) echo "Unknown argument: $arg"; exit 1 ;;
esac
done
# ── Check dependencies ───────────────────────────────────────────────────────
check_deps() {
if ! command -v inkscape &>/dev/null; then
echo "✗ Inkscape is not installed. Install Inkscape first, then run Illuscape."
exit 1
fi
local version
version=$(inkscape --version 2>/dev/null | grep -oP '\d+\.\d+' | head -1)
local major minor
IFS='.' read -r major minor <<< "$version"
# Skip version check if regex couldn't match (non-standard version string)
if [[ -n "$major" ]] && { (( major < 1 )) || (( major == 1 && minor < 2 )); }; then
echo "⚠ Inkscape $version detected. Illuscape targets Inkscape 1.2+."
echo " Some settings may not apply correctly. Continue anyway? [y/N]"
[[ $AUTO_YES == 1 ]] || { read -r ans; [[ "$ans" =~ ^[Yy]$ ]] || exit 0; }
fi
if ! command -v python3 &>/dev/null; then
echo "✗ python3 is required. Install it and try again."
exit 1
fi
}
# ── Detect platform ──────────────────────────────────────────────────────────
detect_config() {
source "$SCRIPT_DIR/scripts/detect_platform.sh"
echo "→ Inkscape config: $INKSCAPE_CONFIG ($INKSCAPE_INSTALL_METHOD)"
}
# ── Backup ───────────────────────────────────────────────────────────────────
backup_config() {
local backup_dir="${INKSCAPE_CONFIG}.bak-illuscape-$(date +%Y%m%d-%H%M%S)"
# If any illuscape backup already exists, skip (idempotent)
if ls "${INKSCAPE_CONFIG}".bak-illuscape-* &>/dev/null; then
echo "→ Backup already exists — skipping (idempotent)"
return 0
fi
if [[ -d "$INKSCAPE_CONFIG" ]]; then
cp -r "$INKSCAPE_CONFIG" "$backup_dir"
echo "→ Backed up to: $backup_dir"
else
echo "→ No existing config to back up (fresh install)"
fi
}
# ── Prompt for preset ────────────────────────────────────────────────────────
choose_preset() {
[[ -n "$PRESET" ]] && return 0
echo ""
echo "Which Illustrator era are you coming from?"
echo " [1] CC — current Creative Cloud (default)"
echo " [2] CS6 — last perpetual license"
printf "Choice [1]: "
if [[ $AUTO_YES == 1 ]]; then
echo "1 (auto)"
PRESET="cc"
return 0
fi
read -r choice
case "${choice:-1}" in
2) PRESET="cs6" ;;
*) PRESET="cc" ;;
esac
echo "→ Using preset: $PRESET"
}
# ── Merge preferences ────────────────────────────────────────────────────────
merge_prefs() {
local prefs_file="$INKSCAPE_CONFIG/preferences.xml"
python3 "$SCRIPT_DIR/scripts/merge_prefs.py" \
--prefs "$prefs_file" \
--patch "$SCRIPT_DIR/config/preferences-patch.xml" \
--preset "$PRESET"
}
# ── Copy config files ────────────────────────────────────────────────────────
copy_files() {
mkdir -p \
"$INKSCAPE_CONFIG/keys" \
"$INKSCAPE_CONFIG/palettes" \
"$INKSCAPE_CONFIG/templates" \
"$INKSCAPE_CONFIG/symbols" \
"$INKSCAPE_CONFIG/splashscreens"
cp "$SCRIPT_DIR/config/keys/illustrator-${PRESET}.xml" \
"$INKSCAPE_CONFIG/keys/"
cp "$SCRIPT_DIR/config/palettes/"*.gpl "$INKSCAPE_CONFIG/palettes/"
cp "$SCRIPT_DIR/config/templates/"*.svg "$INKSCAPE_CONFIG/templates/"
cp "$SCRIPT_DIR/config/symbols/"*.svg "$INKSCAPE_CONFIG/symbols/"
echo "→ Config files copied"
}
# ── Install desktop launcher + icons (Linux native only) ─────────────────────
install_desktop() {
[[ "$INKSCAPE_INSTALL_METHOD" != "native" ]] && return 0
local app_dir="$HOME/.local/share/applications"
local icon_src="$SCRIPT_DIR/assets/illuscape.svg"
local splash_src="$SCRIPT_DIR/assets/splash.svg"
local icon_sizes=(16 32 48 64 128 256 512)
mkdir -p "$app_dir"
cp "$SCRIPT_DIR/assets/org.inkscape.Inkscape.desktop" "$app_dir/"
# Rasterize icon at each size (prefer rsvg-convert, fall back to Inkscape)
for size in "${icon_sizes[@]}"; do
local icon_dir="$HOME/.local/share/icons/hicolor/${size}x${size}/apps"
mkdir -p "$icon_dir"
if command -v rsvg-convert &>/dev/null; then
rsvg-convert -w "$size" -h "$size" "$icon_src" \
-o "$icon_dir/illuscape.png" 2>/dev/null && continue
fi
if command -v inkscape &>/dev/null; then
# Inkscape 1.x uses --export-filename + --export-type (--export-png removed in 1.0)
inkscape --export-filename="$icon_dir/illuscape.png" \
--export-type=png \
--export-width="$size" --export-height="$size" \
"$icon_src" 2>/dev/null && continue
fi
echo "⚠ Could not rasterize icon at ${size}px (install rsvg-convert for icons)"
done
# Splash screen (600x400)
if command -v rsvg-convert &>/dev/null; then
rsvg-convert -w 600 -h 400 "$splash_src" \
-o "$INKSCAPE_CONFIG/splashscreens/illuscape-splash.png" 2>/dev/null || true
fi
update-desktop-database "$app_dir" 2>/dev/null || true
echo "→ Desktop launcher installed"
}
# ── Main ──────────────────────────────────────────────────────────────────────
main() {
echo "╔══════════════════════════════╗"
echo "║ Illuscape Installer ║"
echo "╚══════════════════════════════╝"
echo ""
check_deps
detect_config
backup_config
choose_preset
merge_prefs
copy_files
install_desktop
echo ""
echo "✓ Illuscape installed (preset: $PRESET)"
echo ""
echo " Open Inkscape to start using your Illustrator-style workspace."
echo " To uninstall: ./uninstall.sh"
echo ""
}
main

72
scripts/gen_templates.py Normal file
View file

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Generate Illuscape document templates."""
from pathlib import Path
TEMPLATES_DIR = Path("config/templates")
def mm_to_px(mm: float) -> float:
return mm * 3.7795275591 # 96 dpi
def in_to_px(inches: float) -> float:
return inches * 96.0
def svg(name: str, width_px: float, height_px: float,
units: str, bleed_mm: float = 0.0, note: str = "") -> str:
import math
bleed_px = mm_to_px(bleed_mm)
guides = ""
if bleed_mm > 0:
guides = f"""
<sodipodi:guide position="{-bleed_px:.4f},{bleed_px:.4f}" orientation="1,0" />
<sodipodi:guide position="{width_px + bleed_px:.4f},{bleed_px:.4f}" orientation="1,0" />
<sodipodi:guide position="{bleed_px:.4f},{-bleed_px:.4f}" orientation="0,1" />
<sodipodi:guide position="{bleed_px:.4f},{height_px + bleed_px:.4f}" orientation="0,1" />"""
comment = f"<!-- {note} -->" if note else ""
return f"""<?xml version="1.0" encoding="UTF-8"?>
{comment}
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="{width_px:.4f}"
height="{height_px:.4f}"
viewBox="0 0 {width_px:.4f} {height_px:.4f}"
version="1.1"
id="svg-{name.lower().replace(' ', '-')}">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="1.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="{units}"
showgrid="false"
inkscape:snap-global="true"
inkscape:snap-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-to-guides="true"
inkscape:guide-bbox="true"
units="{units}">{guides}
</sodipodi:namedview>
<title>{name}</title>
<g inkscape:label="Background" inkscape:groupmode="layer" id="layer-background" style="display:inline" />
<g inkscape:label="Guides" inkscape:groupmode="layer" id="layer-guides" style="display:inline" />
<g inkscape:label="Artwork" inkscape:groupmode="layer" id="layer-artwork" style="display:inline" />
</svg>
"""
specs = [
("Letter", in_to_px(8.5), in_to_px(11), "in", 0, "US Letter portrait"),
("A4", mm_to_px(210), mm_to_px(297), "mm", 0, "A4 portrait"),
("Web-1920x1080", 1920, 1080, "px", 0, "HD web canvas"),
("Web-1280x720", 1280, 720, "px", 0, "HD web canvas (smaller)"),
("Print-CMYK-Letter", in_to_px(8.5), in_to_px(11), "in", 3, "US Letter with 3mm bleed. Note: Inkscape is RGB-native; set CMYK values manually."),
("Print-CMYK-A4", mm_to_px(210), mm_to_px(297), "mm", 3, "A4 with 3mm bleed. Note: Inkscape is RGB-native; set CMYK values manually."),
]
for name, w, h, units, bleed, note in specs:
path = TEMPLATES_DIR / f"{name}.svg"
path.write_text(svg(name, w, h, units, bleed, note))
print(f"{path}")

View file

@ -95,7 +95,7 @@ def merge(prefs_path: Path, patch_path: Path, preset: str) -> None:
_merge_nodes(live_root, patch_root) _merge_nodes(live_root, patch_root)
_apply_preset(live_root, preset) _apply_preset(live_root, preset)
live_tree.write(str(prefs_path), encoding="unicode", xml_declaration=True) _atomic_write(live_tree, prefs_path)
def main() -> None: def main() -> None:

135
tests/test_install.bats Normal file
View file

@ -0,0 +1,135 @@
#!/usr/bin/env bats
# Integration tests for install.sh and uninstall.sh
# Requires: bats-core (https://github.com/bats-core/bats-core)
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
setup() {
# Each test gets a fresh temp home directory
export TEST_HOME
TEST_HOME="$(mktemp -d)"
export HOME="$TEST_HOME"
export XDG_CONFIG_HOME="$TEST_HOME/.config"
# Inject a fake inkscape so check_deps passes without the real binary
local fake_bin="$TEST_HOME/bin"
mkdir -p "$fake_bin"
printf '#!/bin/sh\necho "Inkscape 1.3.2"\n' > "$fake_bin/inkscape"
chmod +x "$fake_bin/inkscape"
export PATH="$fake_bin:$PATH"
}
teardown() {
rm -rf "$TEST_HOME"
}
_run_install() {
bash "$REPO_ROOT/install.sh" --preset="${1:-cc}" --yes 2>&1
}
_config_root() {
echo "$TEST_HOME/.config/inkscape"
}
# ── Backup ────────────────────────────────────────────────────────────────────
@test "backup is created on first install" {
mkdir -p "$(_config_root)"
echo '<inkscape version="1"/>' > "$(_config_root)/preferences.xml"
_run_install cc
local backup
backup=$(ls -1d "$(_config_root)".bak-illuscape-* 2>/dev/null | head -1)
[ -n "$backup" ]
[ -f "$backup/preferences.xml" ]
}
@test "second install does not overwrite backup" {
mkdir -p "$(_config_root)"
echo '<inkscape version="1"/>' > "$(_config_root)/preferences.xml"
_run_install cc
sleep 1
_run_install cc
local backup_count
backup_count=$(ls -1d "$(_config_root)".bak-illuscape-* 2>/dev/null | wc -l)
[ "$backup_count" -eq 1 ]
}
# ── Keys ──────────────────────────────────────────────────────────────────────
@test "CC preset installs illustrator-cc.xml" {
_run_install cc
[ -f "$(_config_root)/keys/illustrator-cc.xml" ]
}
@test "CS6 preset installs illustrator-cs6.xml" {
_run_install cs6
[ -f "$(_config_root)/keys/illustrator-cs6.xml" ]
}
@test "CC preset does not install cs6 key file" {
_run_install cc
[ ! -f "$(_config_root)/keys/illustrator-cs6.xml" ]
}
# ── Palettes ──────────────────────────────────────────────────────────────────
@test "all palette files are installed" {
_run_install cc
[ -f "$(_config_root)/palettes/Illustrator-Defaults.gpl" ]
[ -f "$(_config_root)/palettes/Illustrator-Grays.gpl" ]
[ -f "$(_config_root)/palettes/Illustrator-Earth.gpl" ]
}
# ── Templates ─────────────────────────────────────────────────────────────────
@test "all template files are installed" {
_run_install cc
[ -f "$(_config_root)/templates/Letter.svg" ]
[ -f "$(_config_root)/templates/A4.svg" ]
[ -f "$(_config_root)/templates/Web-1920x1080.svg" ]
[ -f "$(_config_root)/templates/Web-1280x720.svg" ]
[ -f "$(_config_root)/templates/Print-CMYK-Letter.svg" ]
[ -f "$(_config_root)/templates/Print-CMYK-A4.svg" ]
}
# ── Preferences merge ─────────────────────────────────────────────────────────
@test "preferences.xml is created when missing" {
_run_install cc
[ -f "$(_config_root)/preferences.xml" ]
}
@test "CC preset sets px units in preferences.xml" {
_run_install cc
grep -q 'doc="px"' "$(_config_root)/preferences.xml"
}
@test "CS6 preset sets pt units in preferences.xml" {
_run_install cs6
grep -q 'doc="pt"' "$(_config_root)/preferences.xml"
}
# ── Noninteractive ────────────────────────────────────────────────────────────
@test "--preset flag skips interactive prompt" {
# If prompt were shown, it would hang — the test itself proves it didn't
run bash "$REPO_ROOT/install.sh" --preset=cc --yes
[ "$status" -eq 0 ]
}
# ── Uninstall ─────────────────────────────────────────────────────────────────
@test "uninstall restores preferences.xml from backup" {
mkdir -p "$(_config_root)"
echo '<inkscape version="1"><group id="original"/></inkscape>' \
> "$(_config_root)/preferences.xml"
_run_install cc
bash "$REPO_ROOT/uninstall.sh"
grep -q 'id="original"' "$(_config_root)/preferences.xml"
}
@test "uninstall removes palette files" {
_run_install cc
bash "$REPO_ROOT/uninstall.sh"
[ ! -f "$(_config_root)/palettes/Illustrator-Defaults.gpl" ]
}

74
uninstall.sh Executable file
View file

@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Illuscape uninstaller
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/scripts/detect_platform.sh"
echo "╔══════════════════════════════╗"
echo "║ Illuscape Uninstaller ║"
echo "╚══════════════════════════════╝"
echo ""
echo "→ Inkscape config: $INKSCAPE_CONFIG"
# ── Find backup ───────────────────────────────────────────────────────────────
BACKUP=$(ls -1d "${INKSCAPE_CONFIG}".bak-illuscape-* 2>/dev/null | sort | tail -1 || true)
if [[ -z "$BACKUP" ]]; then
echo "⚠ No Illuscape backup found at ${INKSCAPE_CONFIG}.bak-illuscape-*"
echo " Cannot restore original preferences.xml automatically."
echo " Removing only Illuscape-installed files."
else
echo "→ Restoring preferences.xml from: $BACKUP"
cp "$BACKUP/preferences.xml" "$INKSCAPE_CONFIG/preferences.xml" 2>/dev/null || true
fi
# ── Remove Illuscape-installed files ─────────────────────────────────────────
remove_files() {
# Keys
rm -f "$INKSCAPE_CONFIG/keys/illustrator-cc.xml"
rm -f "$INKSCAPE_CONFIG/keys/illustrator-cs6.xml"
# Palettes
rm -f "$INKSCAPE_CONFIG/palettes/Illustrator-Defaults.gpl"
rm -f "$INKSCAPE_CONFIG/palettes/Illustrator-Grays.gpl"
rm -f "$INKSCAPE_CONFIG/palettes/Illustrator-Earth.gpl"
# Templates
rm -f "$INKSCAPE_CONFIG/templates/Letter.svg"
rm -f "$INKSCAPE_CONFIG/templates/A4.svg"
rm -f "$INKSCAPE_CONFIG/templates/Web-1920x1080.svg"
rm -f "$INKSCAPE_CONFIG/templates/Web-1280x720.svg"
rm -f "$INKSCAPE_CONFIG/templates/Print-CMYK-Letter.svg"
rm -f "$INKSCAPE_CONFIG/templates/Print-CMYK-A4.svg"
# Symbols
rm -f "$INKSCAPE_CONFIG/symbols/illuscape-common.svg"
# Splash
rm -f "$INKSCAPE_CONFIG/splashscreens/illuscape-splash.png"
echo "→ Illuscape config files removed"
}
remove_desktop() {
[[ "$INKSCAPE_INSTALL_METHOD" != "native" ]] && return 0
rm -f "$HOME/.local/share/applications/org.inkscape.Inkscape.desktop"
for size in 16 32 48 64 128 256 512; do
rm -f "$HOME/.local/share/icons/hicolor/${size}x${size}/apps/illuscape.png"
done
update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
echo "→ Desktop launcher removed"
}
remove_files
remove_desktop
echo ""
echo "✓ Illuscape uninstalled."
echo ""
echo " Note: Your Inkscape preferences may have changed since Illuscape was"
echo " installed. If anything looks wrong, review:"
echo " $INKSCAPE_CONFIG/preferences.xml"
echo ""