diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index b0efdbd..44075b4 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -14,6 +14,9 @@ jobs: build-linux: name: Build Linux runs-on: ubuntu-latest + env: + APPIMAGE_EXTRACT_AND_RUN: 1 + NO_STRIP: "true" steps: - name: Checkout repository @@ -33,11 +36,35 @@ jobs: - name: Rust Cache uses: Swatinem/rust-cache@v2 + continue-on-error: true + with: + workspaces: "src-tauri -> target" + + - name: Cache APT Packages + uses: actions/cache@v3 + with: + path: /var/cache/apt/archives + key: ${{ runner.os }}-apt-packages-v1 + restore-keys: | + ${{ runner.os }}-apt-packages- - name: Install Tauri Linux dependencies run: | - sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev patchelf xdg-utils + sudo apt-get update -qq + sudo apt-get install --no-install-recommends -y \ + libwebkit2gtk-4.1-dev \ + build-essential \ + curl wget file patchelf xdg-utils squashfs-tools zsync \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + libssl-dev \ + libxss-dev \ + libxinerama-dev \ + libxrandr-dev \ + libxxf86vm-dev \ + libxcursor-dev \ + libxi-dev + sudo apt-get install --no-install-recommends -y libfuse2 || sudo apt-get install --no-install-recommends -y libfuse2t64 || true - name: Install Node dependencies run: npm ci @@ -53,19 +80,25 @@ jobs: echo "APP_VERSION=${VERSION}-dev" >> $GITHUB_ENV fi + - name: Prepare platform-specific resources + run: | + echo "Removing Windows ArgyllCMS binaries to avoid bundling them in Linux packages..." + rm -rf src-tauri/argyll/windows-x86_64 + - name: Build Tauri App env: APPIMAGE_EXTRACT_AND_RUN: 1 - run: npm run tauri build + NO_STRIP: "true" + run: npm run tauri build -- -v --bundles deb,appimage - name: Upload Debian Package - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: iccery-linux-deb-${{ env.APP_VERSION }} path: src-tauri/target/release/bundle/deb/*.deb - name: Upload AppImage - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: iccery-linux-appimage-${{ env.APP_VERSION }} path: src-tauri/target/release/bundle/appimage/*.AppImage diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 2a49c2e..bf119ac 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -13,7 +13,7 @@ on: jobs: build-windows: name: Build Windows - runs-on: windows-latest + runs-on: windows steps: - name: Checkout repository @@ -26,15 +26,25 @@ jobs: cache: 'npm' - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - targets: x86_64-pc-windows-msvc + shell: powershell + run: | + if (!(Get-Command rustup -ErrorAction SilentlyContinue) -and !(Test-Path "$env:USERPROFILE\.cargo\bin\rustup.exe")) { + Write-Host "Downloading rustup-init.exe..." + Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile "$env:TEMP\rustup-init.exe" + & "$env:TEMP\rustup-init.exe" -y --default-toolchain stable --profile minimal + } + "$env:USERPROFILE\.cargo\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + rustup default stable + rustup target add x86_64-pc-windows-msvc - name: Rust Cache uses: Swatinem/rust-cache@v2 + continue-on-error: true + with: + workspaces: "src-tauri -> target" - name: Check sidecar binaries + shell: powershell run: | if (!(Test-Path "src-tauri\argyll\windows-x86_64\argyll-instlist.exe")) { Write-Host "Warning: Windows ArgyllCMS binaries not found. Please ensure they are downloaded/committed to src-tauri/argyll/windows-x86_64 before release." @@ -45,26 +55,34 @@ jobs: - name: Get App Version id: get_version - shell: bash + shell: powershell run: | - VERSION=$(node -p "require('./package.json').version") - if [ "${{ github.ref }}" = "refs/heads/main" ]; then - echo "APP_VERSION=${VERSION}" >> $GITHUB_ENV - else - echo "APP_VERSION=${VERSION}-dev" >> $GITHUB_ENV - fi + $VERSION = node -p "require('./package.json').version" + if ($env:GITHUB_REF -eq "refs/heads/main") { + "APP_VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + } else { + "APP_VERSION=$VERSION-dev" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + } + + - name: Prepare platform-specific resources + shell: powershell + run: | + if (Test-Path "src-tauri\argyll\linux-x86_64") { + Write-Host "Removing Linux ArgyllCMS binaries to avoid bundling them in Windows installer..." + Remove-Item -Recurse -Force "src-tauri\argyll\linux-x86_64" + } - name: Build Tauri App - run: npm run tauri build + run: npm run tauri build -- -v - name: Upload MSI Installer - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: iccery-windows-msi-${{ env.APP_VERSION }} path: src-tauri/target/release/bundle/msi/*.msi - name: Upload NSIS Installer - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: iccery-windows-exe-${{ env.APP_VERSION }} path: src-tauri/target/release/bundle/nsis/*.exe diff --git a/.gitignore b/.gitignore index 8332b90..823fe07 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,4 @@ dist-ssr *.sln *.sw? -argyll/linux-x86_64/* -argyll/macos-aarch64/* -argyll/windows-x86_64/* + diff --git a/README.md b/README.md index c991c2c..ec26c01 100644 --- a/README.md +++ b/README.md @@ -1,257 +1,98 @@ -# Printer Profiling UI Project: Comprehensive Requirements & Architecture Roadmap +# ICCery ๐ŸŽจ -This document serves as the detailed feature set roadmap and architectural blueprint for integrating ArgyllCMS into a modern, native GUI application specifically dedicated to **Printer Profiling**. The goal is to abstract the complexities of the command-line utilities into a seamless, visual, and user-friendly experience. +> Modern, cross-platform native desktop application for printer profiling, powered by ArgyllCMS. + +[![Release](https://img.shields.io/badge/version-v0.2.0-blue.svg)](https://git.i3omb.com/gronod/ICCery) +[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey.svg)](https://git.i3omb.com/gronod/ICCery) +[![Framework](https://img.shields.io/badge/framework-Tauri%20v2%20%2B%20Rust-orange.svg)](https://tauri.app) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) + +**ICCery** is a native GUI frontend designed to make creating custom ICC/ICM printer profiles seamless, visual, and reliable. It wraps the powerful color management capabilities of [ArgyllCMS](https://www.argyllcms.com/) within an intuitive, artefact-gated 5-stage wizard. --- -## 1. Architectural Overview & Licence Boundary +## Key Features -To maintain compliance with the ArgyllCMS AGPLv3 licence whilst allowing the host UI application to be closed-source, a strict inter-process communication (IPC) boundary must be maintained. +- ๐Ÿช„ **Linear 5-Stage Wizard Workflow**: + 1. **Stage 1 โ€” Patch Generation (`targen`)**: Configure RGB (driver-managed) or CMYK (RIP-managed) patch sets with custom counts, quality presets, and neutral/grey axis boosting. + 2. **Stage 2 โ€” Target Creation & Raw Printing (`printtarg`)**: Format patch targets for spectrophotometers (i1Pro, i1Pro2, ColorMunki, SpyderPrint). View high-resolution downscaled TIFF previews and print directly using native OS raw unmanaged pathways (Windows GDI uncorrected / Linux CUPS `raw`). + 3. **Stage 3 โ€” Interactive Measurement (`chartread`)**: Real-time instrument calibration prompts, interactive strip reading state machine, and a live swatch grid featuring instant per-patch CIEDE2000 ($\Delta E_{00}$) quality indicators. + 4. **Stage 4 โ€” Profile Calculation (`colprof`)**: Generate high-precision cLUT mathematical ICC/ICM profiles with configurable algorithm quality, descriptions, and copyright tagging. + 5. **Stage 5 โ€” Verification & 3D Gamut (`profcheck` + `iccgamut`)**: Comprehensive mathematical validation report (Peak, Average, RMS $\Delta E$) paired with an interactive 3D CIELAB color volume viewer and bundled sRGB reference wireframe comparison. +- ๐Ÿ›ก๏ธ **Disk Artefact Gating**: Stepper navigation strictly verifies generated artefacts on disk (`.ti1`, `.ti2`, `.ti3`, `.icc`/`.icm`), preventing out-of-order execution while preserving backward navigation. +- ๐ŸŒ **Platform-Aware**: Automatic handling of platform profile conventions (`.icm` on Windows, `.icc` on Linux/macOS) and native OS printer subsystems. +- โš–๏ธ **Clean AGPL Boundary**: Complete isolation of AGPLv3 binaries via asynchronous tokio IPC process pipelines. + +--- + +## Architectural Overview + +ICCery is built on **Tauri v2** and **Rust**, coupled with a reactive Vanilla JavaScript frontend and **Three.js** WebGL visualization: ```mermaid flowchart TD - subgraph host_app ["Host Application (Closed Source)"] - UI[Native User Interface] - State[State Machine & Orchestrator] - IO[Subprocess IPC Manager] - + subgraph Host ["ICCery Host (Tauri + Rust + Vanilla JS)"] + UI[Wizard UI & Swatch Grid] + ThreeJS[3D CIELAB Gamut Viewer] + State[Wizard State & Artefact Verifier] + PrintEngine[Raw Print Subsystem (GDI / CUPS)] + ProcMgr[Async Subprocess IPC Manager] + UI <--> State - State <--> IO + State <--> ProcMgr + ProcMgr --> ThreeJS + UI --> PrintEngine end - - subgraph argyll_cms ["ArgyllCMS (AGPLv3)"] + + subgraph Argyll ["ArgyllCMS Subprocesses (AGPLv3)"] BIN_TAR[targen] BIN_PRT[printtarg] BIN_CHR[chartread] BIN_COL[colprof] + BIN_CHK[profcheck] + BIN_GAM[iccgamut] end - - IO -- stdin/stdout/stderr pipes --> BIN_TAR - IO -- stdin/stdout/stderr pipes --> BIN_PRT - IO -- stdin/stdout/stderr pipes --> BIN_CHR - IO -- stdin/stdout/stderr pipes --> BIN_COL - - classDef hostStyle fill:#0f52ba,stroke:#000,stroke-width:2px,color:#fff; - classDef agplStyle fill:#800000,stroke:#000,stroke-width:2px,color:#fff; - - class UI,State,IO hostStyle; - class BIN_TAR,BIN_PRT,BIN_CHR,BIN_COL agplStyle; -``` -### Core IPC Guidelines -1. **No Library Linking**: The host application must never link against `libinst`, `libicc`, or any other Argyll C library. -2. **Subprocess Management**: Binaries must be invoked via OS process spawning (e.g., `std::process::Command` in Rust, `System.Diagnostics.Process` in C#). -3. **Data Passing**: Data is passed via file paths (e.g., `.ti1`, `.ti2`, `.ti3` files) and standard streams (`stdin` / `stdout`). - ---- - -## 2. Technology Stack Recommendations - -Given the requirements for a cross-platform (Windows, macOS, Linux), compiled (non-scripted), and closed-source compliant application, the following technology stacks are highly recommended: - -1. **Rust with Tauri, Slint, or Iced** - * **Pros**: Compiles to a highly optimised native binary, memory-safe, fearless concurrency for background tasks. - * **Licence**: Permissive (MIT / Apache 2.0). Fully safe for closed-source. -2. **C# with .NET (Avalonia UI or MAUI)** - * **Pros**: Strong object-oriented ecosystem, highly productive. Avalonia provides pixel-perfect identical rendering across all OSs. - * **Licence**: Permissive (MIT). Fully safe for closed-source. -3. **C++ with wxWidgets or Qt** - * **Pros**: Industry standard for cross-platform C++ UIs. Highly performant. - * **Licence**: wxWidgets uses a modified LGPL that permits static linking in closed-source apps. Qt requires dynamic linking under LGPLv3 or purchasing a commercial licence. - -*(Note: Scripted frameworks like Python/PyQt and heavy web-wrappers like Electron/React are explicitly excluded per architectural constraints).* - ---- - -## 3. The Complete Profiling Workflow - -The application must guide the user through a linear wizard-style process. - -```mermaid -sequenceDiagram - participant User - participant UI as Host UI - participant TAR as targen - participant PRT as printtarg - participant CHR as chartread - participant COL as colprof - - Note over User, COL: Stage 1: Patch Generation - User->>UI: Select RGB/CMYK & Patch Count - UI->>TAR: targen -v -d [colour_space] -f [count] target_name - TAR-->>UI: Generates .ti1 file - - Note over User, COL: Stage 2: Target Creation - User->>UI: Select Instrument & Page Size - UI->>PRT: printtarg -v -i [instr] -p [size] -t 300 target_name - PRT-->>UI: Generates .ti2 & .tif files - UI->>User: Display .tif for printing (Colour Management OFF) - - Note over User, COL: Stage 3: Measurement - User->>UI: User clicks "Start Reading" - UI->>CHR: chartread -v -u target_name - CHR-->>UI: Live JSON Stream (ROW_COLORS_JSON) - UI-->>User: Live Visual Updates (Swatches, Delta E) - CHR-->>UI: Generates .ti3 file upon completion - - Note over User, COL: Stage 4: Profile Calculation - User->>UI: Select Profile Quality & Name - UI->>COL: colprof -v -q [quality] target_name - COL-->>UI: Generates .icc / .icm profile - UI->>User: Display Gamut & Quality Report + ProcMgr -- stdin/stdout/stderr pipes --> BIN_TAR + ProcMgr -- stdin/stdout/stderr pipes --> BIN_PRT + ProcMgr -- stdin/stdout/stderr pipes --> BIN_CHR + ProcMgr -- stdin/stdout/stderr pipes --> BIN_COL + ProcMgr -- stdin/stdout/stderr pipes --> BIN_CHK + ProcMgr -- stdin/stdout/stderr pipes --> BIN_GAM ``` --- -## 4. Stage 1: Patch Set Creation (`targen`) +## Building from Source -The first step is configuring the numerical colour combinations to be printed. +### Prerequisites +- [Node.js](https://nodejs.org/) (v18 or newer) +- [Rust](https://www.rust-lang.org/) (1.78+ stable) +- Operating system dependencies: + - **Windows**: Microsoft Visual Studio C++ Build Tools & WebView2 runtime. + - **Linux (Debian/Ubuntu)**: `libwebkit2gtk-4.1-dev`, `build-essential`, `curl`, `wget`, `file`, `libxdo-dev`, `libssl-dev`, `libayatana-appindicator3-dev`, `librsvg2-dev`, `libcups2-dev`. -### UI Configuration Details -* **Printer Colour Space**: Toggle between RGB (Driver-managed) or CMYK (RIP-managed). - * *Command flag*: `-d 2` (RGB) or `-d 4` (CMYK). -* **Total Patch Count**: Controls profile accuracy vs. reading time. Provide presets (e.g., "Draft (400)", "Standard (800)", "High Quality (1500)", "Ultra (2500+)") and a custom input. - * *Command flag*: `-f `. -* **Neutral Axis Boosting**: Inject extra grey/black patches for smoother B&W printing. - * *Command flag*: `-e -B `. - -### Execution -The UI launches the subprocess and monitors `stderr` for completion. +### Development Mode ```bash -targen -v -d 2 -f 800 -e 4 -B 8 my_profile +# Clone the repository +git clone https://git.i3omb.com/gronod/ICCery.git +cd ICCery + +# Install frontend dependencies +npm install + +# Run the development app +npm run tauri dev ``` ---- - -## 5. Stage 2: Target Image Generation (`printtarg`) - -Once the `.ti1` patch file exists, it is converted into a physical page layout formatted specifically for the user's measurement hardware. - -### UI Configuration Details -* **Hardware Instrument**: Dropdown list of supported devices. - * *i1Pro/i1Pro2*: `-i i1` - * *ColorMunki*: `-i CM` - * *SpyderPrint/SpectroScan*: `-i SS` -* **Page Size**: Dropdown for media dimensions. - * *Command flag*: `-p A4` or `-p Letter`. -* **Output Format**: Default to high-resolution TIFF for maximum compatibility. - * *Command flag*: `-t 300` (8-bit) or `-T 300` (16-bit). - -### Execution & Printing Instructions +### Production Build ```bash -printtarg -v -i i1 -t 300 -p A4 my_profile -``` -> [!IMPORTANT] -> The UI must explicitly warn the user: **The generated TIFF files MUST be printed with colour management completely disabled in the printer driver.** (e.g., "No Colour Adjustment" on Epson, "Off (No Colour Adjustment)" on Canon). Failure to do so ruins the profile. - ---- - -## 6. Stage 3: Interactive Measurement (`chartread`) - -This is the most complex stage for the UI, requiring real-time bi-directional communication with `chartread` utilising the `-u` stream. - -### Interaction State Machine -```mermaid -stateDiagram-v2 - [*] --> Initialize - Initialize --> AwaitCalibration: Spawn chartread -u - AwaitCalibration --> Calibrating: Send Spacebar - Calibrating --> AwaitStrip: Calibration OK - - AwaitStrip --> ReadingStrip: Wait for hardware trigger / button - ReadingStrip --> ProcessRow: Read success - ReadingStrip --> ErrorState: Read failed (Too fast/slow) - - ErrorState --> AwaitStrip: Retry - ProcessRow --> AwaitStrip: Row complete, parse JSON - ProcessRow --> Finished: Last row completed - - Finished --> [*]: .ti3 generated -``` - -### IPC Mechanics -1. **Stdout Parsing**: The UI reads `stdout` line-by-line. - * Lines matching `/^ROW_COLORS_JSON: (.*)/` are parsed as JSON to update the live patch grid and Delta E traffic lights. - * Unprefixed lines (e.g., "Hit [Space] to read strip A") trigger state changes in the UI prompt text. -2. **Stdin Injection**: The UI provides buttons (e.g., "Calibrate", "Skip") that inject keystrokes (`" \n"`, `"s\n"`) into the child process's `stdin`. - -### Visualising Delta E -For live feedback, the JSON stream contains `expected.Lab` and `measured.Lab`. The UI should: -1. Implement the **CIEDE2000 ($\Delta E_{00}$)** formula. -2. Calculate the difference for every patch as it arrives. -3. Display a traffic light indicator (e.g., Green: $\Delta E < 2$, Amber: $\Delta E < 5$, Red: $\Delta E > 5$) on the UI swatch grid to instantly flag printing or reading errors. - ---- - -## 7. Stage 4: Profile Calculation (`colprof`) - -Once reading is finished and the `.ti3` file is generated, the mathematical ICC profile generation begins. - -### UI Configuration Details -* **Profile Quality**: Controls LUT resolution and optimisation passes. - * *Command flag*: `-q l` (Low), `-q m` (Medium), `-q h` (High), `-q u` (Ultra). *Note: Ultra can take a very long time.* -* **Profile Description Name**: The internal name embedded in the ICC file that appears in Photoshop/Lightroom dropdowns. - * *Command flag*: `-D "My Profile Name"`. -* **Algorithm**: Typically `cLUT` for printers. - * *Command flag*: `-a l`. - -### Execution -This process is highly CPU intensive. The UI must launch this in an asynchronous background thread and display an indeterminate progress spinner. -```bash -colprof -v -q h -a l -D "Canon Pro-100 Luster Paper" my_profile +# Build desktop packages (MSI/NSIS on Windows, DEB/AppImage on Linux) +npm run tauri build ``` --- -## 8. Display of Results & Verification +## Licence -### Post-Processing Integration -1. **Mathematical Quality Score (`profcheck`)**: - Silently execute `profcheck` to compare the generated profile against the measurement data. - ```bash - profcheck -k my_profile.ti3 my_profile.icc - ``` - *Parse the output*: Extract "Peak error" and "Average error". A healthy profile typically has an average $\Delta E$ below 1.5. Display this clearly in the UI. -2. **3D Gamut Visualisation**: - To provide a premium feel, integrate a native 3D WebGL (if using Tauri) or OpenGL (if using C++/C#) viewer. Parse the gamut boundary data to render a rotatable 3D plot of the printer's colour volume in CIELAB space. - ---- - -## 9. Proposed ArgyllCMS Upstream Modifications - -To ensure maximum value, stability, and responsiveness in the UI, several low-level features should be developed within the ArgyllCMS codebase. These modifications primarily revolve around structured data output (JSON) to prevent the UI from relying on fragile regex string parsing of human-readable console text. - -### 9.1 Implemented Enhancements -* **`chartread -u` (JSON Streaming)**: *Already developed and merged.* We added the `-u` flag to `chartread`, which emits a real-time, flush-forced `ROW_COLORS_JSON: { ... }` stream. This solved the critical UI requirement of displaying live swatches and computing Delta E ($\Delta E$) mid-measurement without waiting for the `.ti3` file to close. - -### 9.2 Recommended Future Enhancements - -#### `targen`: Structured Progress -* **Feature**: Add a `-u` flag to emit JSON progress events during generation (e.g., `{"event": "progress", "stage": "optimising", "percent": 45}`). -* **Rationale**: Advanced patch generation (especially iterative OFPS with adaptation) can take several minutes. A structured progress stream prevents the UI from appearing frozen. - -#### `printtarg`: Structured Target Manifest -* **Feature**: Add a `-u` flag to output a JSON manifest upon completion, detailing the exact filenames generated (e.g., `target_01.tif`, `target_02.tif`), their physical dimensions, and patch counts per page. -* **Rationale**: Currently, `printtarg` generates multiple pages implicitly. A UI requires this structured manifest to reliably load and display the correct number of pages for printing without guessing the filenames based on the basename. - -#### `colprof`: Structured Profile Calculation Progress -* **Feature**: Add a `-u` flag to emit JSON progress during profile calculation (e.g., `{"event": "gamut_mapping", "percent": 75}`). -* **Rationale**: High and Ultra quality profile generation is highly CPU-intensive and time-consuming. Parsing dots (`.`) or numbers from `stdout` for progress bars is unreliable; structured progress ensures smooth UI feedback. - -#### `profcheck`: Structured Delta E Report -* **Feature**: Add a `-u` flag to output the final $\Delta E$ report in JSON format (e.g., `{"peak_de": 2.34, "avg_de": 0.51, "rms": 0.61}`). -* **Rationale**: The UI currently has to use regex to parse human-readable text (e.g., `errors(CIEDE2000): max. = ...`). A JSON payload guarantees robust extraction of quality metrics. - -#### Instrument Enumeration (Global) -* **Feature**: A flag (e.g., `chartread -u -c list`) to query connected USB instruments and return a JSON array of device ports and names. -* **Rationale**: Allows the UI to automatically populate the "Select Instrument" dropdown without relying on OS-level USB polling or brittle string parsing of `chartread -?`. - ---- - -## 10. Development Milestones - -* **Milestone 1: Project Scaffolding & State Management**: Build the core host application (Rust/C#/C++) and the non-blocking subprocess invocation abstractions to safely manage AGPL binaries. -* **Milestone 2: Target Generation Pipeline**: Implement UI forms and validation for Stages 1 & 2 (`targen` and `printtarg`), yielding printable TIFF files. -* **Milestone 3: Interactive Reading Engine**: Build the state machine, live swatch grid, $\Delta E$ math, and `chartread -u` IPC pipe handler. -* **Milestone 4: Profiling & Verification**: Implement the `colprof` execution thread and `profcheck` parsing for the final report. -* **Milestone 5: Advanced Visualisations**: Integrate 3D gamut plotting and UX polish. +ICCery is licensed under the [MIT Licence](LICENSE). ArgyllCMS binaries and source code are licensed under the GNU Affero General Public License (AGPLv3). diff --git a/ROADMAP.md b/ROADMAP.md index c991c2c..a9b2c5d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,257 +1,72 @@ -# Printer Profiling UI Project: Comprehensive Requirements & Architecture Roadmap +# ICCery Development Roadmap & Architecture Strategy -This document serves as the detailed feature set roadmap and architectural blueprint for integrating ArgyllCMS into a modern, native GUI application specifically dedicated to **Printer Profiling**. The goal is to abstract the complexities of the command-line utilities into a seamless, visual, and user-friendly experience. +This document outlines the architectural roadmap, completed milestones, and upcoming development goals for **ICCery**. --- -## 1. Architectural Overview & Licence Boundary +## 1. Architecture Summary -To maintain compliance with the ArgyllCMS AGPLv3 licence whilst allowing the host UI application to be closed-source, a strict inter-process communication (IPC) boundary must be maintained. - -```mermaid -flowchart TD - subgraph host_app ["Host Application (Closed Source)"] - UI[Native User Interface] - State[State Machine & Orchestrator] - IO[Subprocess IPC Manager] - - UI <--> State - State <--> IO - end - - subgraph argyll_cms ["ArgyllCMS (AGPLv3)"] - BIN_TAR[targen] - BIN_PRT[printtarg] - BIN_CHR[chartread] - BIN_COL[colprof] - end - - IO -- stdin/stdout/stderr pipes --> BIN_TAR - IO -- stdin/stdout/stderr pipes --> BIN_PRT - IO -- stdin/stdout/stderr pipes --> BIN_CHR - IO -- stdin/stdout/stderr pipes --> BIN_COL - - classDef hostStyle fill:#0f52ba,stroke:#000,stroke-width:2px,color:#fff; - classDef agplStyle fill:#800000,stroke:#000,stroke-width:2px,color:#fff; - - class UI,State,IO hostStyle; - class BIN_TAR,BIN_PRT,BIN_CHR,BIN_COL agplStyle; -``` - -### Core IPC Guidelines -1. **No Library Linking**: The host application must never link against `libinst`, `libicc`, or any other Argyll C library. -2. **Subprocess Management**: Binaries must be invoked via OS process spawning (e.g., `std::process::Command` in Rust, `System.Diagnostics.Process` in C#). -3. **Data Passing**: Data is passed via file paths (e.g., `.ti1`, `.ti2`, `.ti3` files) and standard streams (`stdin` / `stdout`). +ICCery is a native, cross-platform desktop application built with: +- **Backend**: Rust + Tauri v2, managing asynchronous process pipes, native printer devmode configurations (Windows GDI & Linux CUPS), and filesystem operations. +- **Frontend**: Vanilla JS (ES Modules) + HTML5/CSS3 with a modern dark theme and responsive layout. +- **Visualisation**: Three.js WebGL engine for 3D CIELAB color gamut volumes and sRGB reference comparisons. +- **Engine**: ArgyllCMS command-line utilities orchestrated over isolated standard stream IPC (`stdin`, `stdout`, `stderr`). --- -## 2. Technology Stack Recommendations +## 2. Completed Milestones -Given the requirements for a cross-platform (Windows, macOS, Linux), compiled (non-scripted), and closed-source compliant application, the following technology stacks are highly recommended: +### Milestone 1 โ€” Core Scaffolding & Subprocess Engine (`v0.1.0`) +- [x] Initialized Tauri v2 + Rust multi-threaded asynchronous process manager. +- [x] Implemented non-blocking event-driven stream pipes (`process:stdout`, `process:stderr`, `process:exit`). +- [x] Established strict AGPLv3 isolation boundary. -1. **Rust with Tauri, Slint, or Iced** - * **Pros**: Compiles to a highly optimised native binary, memory-safe, fearless concurrency for background tasks. - * **Licence**: Permissive (MIT / Apache 2.0). Fully safe for closed-source. -2. **C# with .NET (Avalonia UI or MAUI)** - * **Pros**: Strong object-oriented ecosystem, highly productive. Avalonia provides pixel-perfect identical rendering across all OSs. - * **Licence**: Permissive (MIT). Fully safe for closed-source. -3. **C++ with wxWidgets or Qt** - * **Pros**: Industry standard for cross-platform C++ UIs. Highly performant. - * **Licence**: wxWidgets uses a modified LGPL that permits static linking in closed-source apps. Qt requires dynamic linking under LGPLv3 or purchasing a commercial licence. +### Milestone 2 โ€” Patch Set & Target Generation (`v0.1.3`) +- [x] Integrated Stage 1 `targen` for RGB/CMYK patch generation with presets and neutral axis boosting. +- [x] Integrated Stage 2 `printtarg` multi-page layout generation for major spectrophotometers (i1Pro, i1Pro2, ColorMunki, SpyderPrint). +- [x] Built working directory resolution and cross-platform binary detection. -*(Note: Scripted frameworks like Python/PyQt and heavy web-wrappers like Electron/React are explicitly excluded per architectural constraints).* +### Milestone 3 โ€” Interactive Measurement & Live Delta E (`v0.1.6`) +- [x] Developed bidirectional `chartread -u` state machine with prompt-driven calibration and strip reading. +- [x] Real-time JSON stream parsing (`ROW_COLORS_JSON`) for dynamic swatch grid updates. +- [x] Implemented live CIEDE2000 ($\Delta E_{00}$) calculation with traffic light quality indicators. + +### Milestone 4 โ€” Profile Calculation & Quality Verification (`v0.1.9`) +- [x] Integrated Stage 4 `colprof` cLUT profile mathematical generation. +- [x] Integrated Stage 5 `profcheck` numerical verification report (Peak, Average, RMS $\Delta E$). +- [x] Added automated dark-theme UI with responsive status indicators. + +### Milestone 5 โ€” Native OS Printing & Previews (`v0.1.13`) +- [x] Added raw unmanaged print pipelines (Windows GDI unmanaged printing + Linux CUPS `raw` queue printing). +- [x] Added high-resolution base64 TIFF thumbnail rendering pipeline. +- [x] Optimized window constraints to 1280ร—800 minimum bounds. + +### Milestone 6 โ€” 3D Gamut Visualization & Platform Extension Handling (`v0.1.14`) +- [x] Extracted `.gam` gamut datasets via `iccgamut` and integrated 3D CIELAB WebGL viewer. +- [x] Bundled reference `sRGB.gam` wireframe overlay for comparative volume analysis. +- [x] Resolved platform profile extensions (`.icm` on Windows vs. `.icc` on Unix). + +### Milestone 7 โ€” Wizard Disk-Artefact Gating & v0.2 Ship (`v0.2.0`) +- [x] Implemented disk-artefact gating for wizard stepper navigation (`.ti1` โ†’ `.ti2` โ†’ `.ti3` โ†’ `.icc`/`.icm`). +- [x] Eliminated all hardcoded placeholder and fallback crutches across JavaScript modules. +- [x] Comprehensive documentation, release testing, and packaging automation. --- -## 3. The Complete Profiling Workflow +## 3. Future Roadmap -The application must guide the user through a linear wizard-style process. +### Milestone 8 โ€” Advanced Measurement & Workflow Enhancements (`v0.3.0`) +- [ ] **Averaging & Multi-Pass Reading**: Integrate Argyll's `average` utility to merge multiple measurement sheets for enhanced accuracy. +- [ ] **Ambient & Display Profiling**: Extend wizard beyond reflective targets to emissive displays (`dispwin`, `dispread`). +- [ ] **Preset Management**: Save and load reusable profiling recipes (paper types, patch counts, quality presets). +- [ ] **Full 3D Convex Hull in CIELAB**: Replace 2D projected Delaunay triangulation with full 3D Delaunay/Convex Hull in Lab space for complex non-convex gamuts. -```mermaid -sequenceDiagram - participant User - participant UI as Host UI - participant TAR as targen - participant PRT as printtarg - participant CHR as chartread - participant COL as colprof - - Note over User, COL: Stage 1: Patch Generation - User->>UI: Select RGB/CMYK & Patch Count - UI->>TAR: targen -v -d [colour_space] -f [count] target_name - TAR-->>UI: Generates .ti1 file - - Note over User, COL: Stage 2: Target Creation - User->>UI: Select Instrument & Page Size - UI->>PRT: printtarg -v -i [instr] -p [size] -t 300 target_name - PRT-->>UI: Generates .ti2 & .tif files - UI->>User: Display .tif for printing (Colour Management OFF) - - Note over User, COL: Stage 3: Measurement - User->>UI: User clicks "Start Reading" - UI->>CHR: chartread -v -u target_name - CHR-->>UI: Live JSON Stream (ROW_COLORS_JSON) - UI-->>User: Live Visual Updates (Swatches, Delta E) - CHR-->>UI: Generates .ti3 file upon completion - - Note over User, COL: Stage 4: Profile Calculation - User->>UI: Select Profile Quality & Name - UI->>COL: colprof -v -q [quality] target_name - COL-->>UI: Generates .icc / .icm profile - UI->>User: Display Gamut & Quality Report -``` +### Milestone 9 โ€” macOS Native Support & Enhanced Print Spooling (`v0.4.0`) +- [ ] **macOS Platform Bundle**: Build and sign universal macOS `.dmg` bundles. +- [ ] **macOS Raw Spooling**: Native CoreGraphics/CUPS raw print dialog bypass. +- [ ] **SpectroScan & Automated Table Support**: Support XY automated scanning tables in `chartread`. ---- - -## 4. Stage 1: Patch Set Creation (`targen`) - -The first step is configuring the numerical colour combinations to be printed. - -### UI Configuration Details -* **Printer Colour Space**: Toggle between RGB (Driver-managed) or CMYK (RIP-managed). - * *Command flag*: `-d 2` (RGB) or `-d 4` (CMYK). -* **Total Patch Count**: Controls profile accuracy vs. reading time. Provide presets (e.g., "Draft (400)", "Standard (800)", "High Quality (1500)", "Ultra (2500+)") and a custom input. - * *Command flag*: `-f `. -* **Neutral Axis Boosting**: Inject extra grey/black patches for smoother B&W printing. - * *Command flag*: `-e -B `. - -### Execution -The UI launches the subprocess and monitors `stderr` for completion. -```bash -targen -v -d 2 -f 800 -e 4 -B 8 my_profile -``` - ---- - -## 5. Stage 2: Target Image Generation (`printtarg`) - -Once the `.ti1` patch file exists, it is converted into a physical page layout formatted specifically for the user's measurement hardware. - -### UI Configuration Details -* **Hardware Instrument**: Dropdown list of supported devices. - * *i1Pro/i1Pro2*: `-i i1` - * *ColorMunki*: `-i CM` - * *SpyderPrint/SpectroScan*: `-i SS` -* **Page Size**: Dropdown for media dimensions. - * *Command flag*: `-p A4` or `-p Letter`. -* **Output Format**: Default to high-resolution TIFF for maximum compatibility. - * *Command flag*: `-t 300` (8-bit) or `-T 300` (16-bit). - -### Execution & Printing Instructions -```bash -printtarg -v -i i1 -t 300 -p A4 my_profile -``` -> [!IMPORTANT] -> The UI must explicitly warn the user: **The generated TIFF files MUST be printed with colour management completely disabled in the printer driver.** (e.g., "No Colour Adjustment" on Epson, "Off (No Colour Adjustment)" on Canon). Failure to do so ruins the profile. - ---- - -## 6. Stage 3: Interactive Measurement (`chartread`) - -This is the most complex stage for the UI, requiring real-time bi-directional communication with `chartread` utilising the `-u` stream. - -### Interaction State Machine -```mermaid -stateDiagram-v2 - [*] --> Initialize - Initialize --> AwaitCalibration: Spawn chartread -u - AwaitCalibration --> Calibrating: Send Spacebar - Calibrating --> AwaitStrip: Calibration OK - - AwaitStrip --> ReadingStrip: Wait for hardware trigger / button - ReadingStrip --> ProcessRow: Read success - ReadingStrip --> ErrorState: Read failed (Too fast/slow) - - ErrorState --> AwaitStrip: Retry - ProcessRow --> AwaitStrip: Row complete, parse JSON - ProcessRow --> Finished: Last row completed - - Finished --> [*]: .ti3 generated -``` - -### IPC Mechanics -1. **Stdout Parsing**: The UI reads `stdout` line-by-line. - * Lines matching `/^ROW_COLORS_JSON: (.*)/` are parsed as JSON to update the live patch grid and Delta E traffic lights. - * Unprefixed lines (e.g., "Hit [Space] to read strip A") trigger state changes in the UI prompt text. -2. **Stdin Injection**: The UI provides buttons (e.g., "Calibrate", "Skip") that inject keystrokes (`" \n"`, `"s\n"`) into the child process's `stdin`. - -### Visualising Delta E -For live feedback, the JSON stream contains `expected.Lab` and `measured.Lab`. The UI should: -1. Implement the **CIEDE2000 ($\Delta E_{00}$)** formula. -2. Calculate the difference for every patch as it arrives. -3. Display a traffic light indicator (e.g., Green: $\Delta E < 2$, Amber: $\Delta E < 5$, Red: $\Delta E > 5$) on the UI swatch grid to instantly flag printing or reading errors. - ---- - -## 7. Stage 4: Profile Calculation (`colprof`) - -Once reading is finished and the `.ti3` file is generated, the mathematical ICC profile generation begins. - -### UI Configuration Details -* **Profile Quality**: Controls LUT resolution and optimisation passes. - * *Command flag*: `-q l` (Low), `-q m` (Medium), `-q h` (High), `-q u` (Ultra). *Note: Ultra can take a very long time.* -* **Profile Description Name**: The internal name embedded in the ICC file that appears in Photoshop/Lightroom dropdowns. - * *Command flag*: `-D "My Profile Name"`. -* **Algorithm**: Typically `cLUT` for printers. - * *Command flag*: `-a l`. - -### Execution -This process is highly CPU intensive. The UI must launch this in an asynchronous background thread and display an indeterminate progress spinner. -```bash -colprof -v -q h -a l -D "Canon Pro-100 Luster Paper" my_profile -``` - ---- - -## 8. Display of Results & Verification - -### Post-Processing Integration -1. **Mathematical Quality Score (`profcheck`)**: - Silently execute `profcheck` to compare the generated profile against the measurement data. - ```bash - profcheck -k my_profile.ti3 my_profile.icc - ``` - *Parse the output*: Extract "Peak error" and "Average error". A healthy profile typically has an average $\Delta E$ below 1.5. Display this clearly in the UI. -2. **3D Gamut Visualisation**: - To provide a premium feel, integrate a native 3D WebGL (if using Tauri) or OpenGL (if using C++/C#) viewer. Parse the gamut boundary data to render a rotatable 3D plot of the printer's colour volume in CIELAB space. - ---- - -## 9. Proposed ArgyllCMS Upstream Modifications - -To ensure maximum value, stability, and responsiveness in the UI, several low-level features should be developed within the ArgyllCMS codebase. These modifications primarily revolve around structured data output (JSON) to prevent the UI from relying on fragile regex string parsing of human-readable console text. - -### 9.1 Implemented Enhancements -* **`chartread -u` (JSON Streaming)**: *Already developed and merged.* We added the `-u` flag to `chartread`, which emits a real-time, flush-forced `ROW_COLORS_JSON: { ... }` stream. This solved the critical UI requirement of displaying live swatches and computing Delta E ($\Delta E$) mid-measurement without waiting for the `.ti3` file to close. - -### 9.2 Recommended Future Enhancements - -#### `targen`: Structured Progress -* **Feature**: Add a `-u` flag to emit JSON progress events during generation (e.g., `{"event": "progress", "stage": "optimising", "percent": 45}`). -* **Rationale**: Advanced patch generation (especially iterative OFPS with adaptation) can take several minutes. A structured progress stream prevents the UI from appearing frozen. - -#### `printtarg`: Structured Target Manifest -* **Feature**: Add a `-u` flag to output a JSON manifest upon completion, detailing the exact filenames generated (e.g., `target_01.tif`, `target_02.tif`), their physical dimensions, and patch counts per page. -* **Rationale**: Currently, `printtarg` generates multiple pages implicitly. A UI requires this structured manifest to reliably load and display the correct number of pages for printing without guessing the filenames based on the basename. - -#### `colprof`: Structured Profile Calculation Progress -* **Feature**: Add a `-u` flag to emit JSON progress during profile calculation (e.g., `{"event": "gamut_mapping", "percent": 75}`). -* **Rationale**: High and Ultra quality profile generation is highly CPU-intensive and time-consuming. Parsing dots (`.`) or numbers from `stdout` for progress bars is unreliable; structured progress ensures smooth UI feedback. - -#### `profcheck`: Structured Delta E Report -* **Feature**: Add a `-u` flag to output the final $\Delta E$ report in JSON format (e.g., `{"peak_de": 2.34, "avg_de": 0.51, "rms": 0.61}`). -* **Rationale**: The UI currently has to use regex to parse human-readable text (e.g., `errors(CIEDE2000): max. = ...`). A JSON payload guarantees robust extraction of quality metrics. - -#### Instrument Enumeration (Global) -* **Feature**: A flag (e.g., `chartread -u -c list`) to query connected USB instruments and return a JSON array of device ports and names. -* **Rationale**: Allows the UI to automatically populate the "Select Instrument" dropdown without relying on OS-level USB polling or brittle string parsing of `chartread -?`. - ---- - -## 10. Development Milestones - -* **Milestone 1: Project Scaffolding & State Management**: Build the core host application (Rust/C#/C++) and the non-blocking subprocess invocation abstractions to safely manage AGPL binaries. -* **Milestone 2: Target Generation Pipeline**: Implement UI forms and validation for Stages 1 & 2 (`targen` and `printtarg`), yielding printable TIFF files. -* **Milestone 3: Interactive Reading Engine**: Build the state machine, live swatch grid, $\Delta E$ math, and `chartread -u` IPC pipe handler. -* **Milestone 4: Profiling & Verification**: Implement the `colprof` execution thread and `profcheck` parsing for the final report. -* **Milestone 5: Advanced Visualisations**: Integrate 3D gamut plotting and UX polish. +### Milestone 10 โ€” Enterprise Colour Workflow (`v1.0.0`) +- [ ] **Custom CGATS Export**: Export and import industry-standard `.ti3` / `.txt` CGATS datasets. +- [ ] **Batch Verification & Drift Tracking**: Track printer drift over time by comparing periodic verification measurements against a baseline profile. +- [ ] **Multi-Language Localization**: Full UI internationalization (English, German, French, Japanese). diff --git a/package-lock.json b/package-lock.json index 8e370dc..ab10594 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,29 +1,16 @@ { - "name": "tmp_tauri", - "version": "0.1.0", + "name": "iccery", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "tmp_tauri", - "version": "0.1.0", - "dependencies": { - "@tauri-apps/plugin-dialog": "^2.7.2" - }, + "name": "iccery", + "version": "0.1.4", "devDependencies": { "@tauri-apps/cli": "^2" } }, - "node_modules/@tauri-apps/api": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", - "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, "node_modules/@tauri-apps/cli": { "version": "2.11.4", "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", @@ -240,15 +227,6 @@ "engines": { "node": ">= 10" } - }, - "node_modules/@tauri-apps/plugin-dialog": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.2.tgz", - "integrity": "sha512-pX0IGm1I3I6wc+zeKYcq1GSqogK6okCNX5fOdaNU5ab1AjGS6l1E5wFNjEb7meg7ZFSp0JUs+0jQGQNyOvLrsg==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.11.0" - } } } } diff --git a/package.json b/package.json index 91dc9c1..794e7fc 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,12 @@ { - "name": "tmp_tauri", + "name": "iccery", "private": true, - "version": "0.1.0", + "version": "0.2.0", "type": "module", "scripts": { "tauri": "tauri" }, "devDependencies": { "@tauri-apps/cli": "^2" - }, - "dependencies": { - "@tauri-apps/plugin-dialog": "^2.7.2" } -} +} \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 0ab1ad3..f8602d6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -47,137 +47,6 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "async-signal" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - [[package]] name = "atk" version = "0.18.2" @@ -273,19 +142,6 @@ dependencies = [ "objc2", ] -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - [[package]] name = "brotli" version = "8.0.4" @@ -334,6 +190,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.12.1" @@ -475,15 +337,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "cookie" version = "0.18.2" @@ -567,6 +420,12 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -885,33 +744,6 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -939,32 +771,18 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - [[package]] name = "fastrand" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + [[package]] name = "fdeflate" version = "0.3.7" @@ -1080,19 +898,6 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.34" @@ -1423,6 +1228,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1447,12 +1263,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" @@ -1577,16 +1387,17 @@ dependencies = [ [[package]] name = "iccery" -version = "0.1.0" +version = "0.1.4" dependencies = [ "base64 0.22.1", + "image", "serde", "serde_json", "tauri", "tauri-build", "tauri-plugin-dialog", - "tauri-plugin-opener", "tokio", + "windows", ] [[package]] @@ -1709,6 +1520,19 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "tiff", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1747,25 +1571,6 @@ version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - [[package]] name = "itoa" version = "1.0.18" @@ -1994,12 +1799,6 @@ dependencies = [ "libc", ] -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - [[package]] name = "litemap" version = "0.8.3" @@ -2074,6 +1873,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "muda" version = "0.19.3" @@ -2364,33 +2173,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "open" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cfef937e9c486488c7e3d949ae31c0f1d06bdacd75b99c086cb35356e30408" -dependencies = [ - "dunce", - "is-wsl", - "libc", -] - [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - [[package]] name = "pango" version = "0.18.3" @@ -2416,12 +2204,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - [[package]] name = "parking_lot" version = "0.12.5" @@ -2510,17 +2292,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - [[package]] name = "pkg-config" version = "0.3.34" @@ -2566,20 +2337,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - [[package]] name = "portable-atomic" version = "1.15.0" @@ -2678,6 +2435,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.41.0" @@ -2856,19 +2625,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - [[package]] name = "rustversion" version = "1.0.23" @@ -3578,28 +3334,6 @@ dependencies = [ "url", ] -[[package]] -name = "tauri-plugin-opener" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" -dependencies = [ - "dunce", - "glob", - "objc2-app-kit", - "objc2-foundation", - "open", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.20", - "url", - "windows", - "zbus", -] - [[package]] name = "tauri-runtime" version = "2.11.3" @@ -3700,19 +3434,6 @@ dependencies = [ "toml 1.1.4+spec-1.1.0", ] -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - [[package]] name = "tendril" version = "0.5.1" @@ -3762,6 +3483,20 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.55" @@ -3826,25 +3561,12 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", - "tokio-macros", "windows-sys 0.61.2", ] -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - [[package]] name = "tokio-util" version = "0.7.19" @@ -4030,21 +3752,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "tracing-core" version = "0.1.36" @@ -4094,17 +3804,6 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" -[[package]] -name = "uds_windows" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" -dependencies = [ - "memoffset", - "tempfile", - "windows-sys 0.61.2", -] - [[package]] name = "unic-char-property" version = "0.9.0" @@ -4437,6 +4136,12 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "winapi" version = "0.3.9" @@ -4997,73 +4702,23 @@ dependencies = [ ] [[package]] -name = "zbus" -version = "5.19.0" +name = "zerocopy" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "libc", - "ordered-stream", - "rustix", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow 1.0.4", - "zbus_macros", - "zbus_names", - "zvariant", + "zerocopy-derive", ] [[package]] -name = "zbus_macros" -version = "5.19.0" +name = "zerocopy-derive" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ - "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 3.0.3", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" -dependencies = [ - "serde", - "winnow 1.0.4", - "zvariant", -] - -[[package]] -name = "zcheapstr" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" -dependencies = [ - "serde", + "syn 2.0.119", ] [[package]] @@ -5127,42 +4782,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] -name = "zvariant" -version = "5.15.0" +name = "zune-core" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow 1.0.4", - "zcheapstr", - "zvariant_derive", - "zvariant_utils", -] +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" [[package]] -name = "zvariant_derive" -version = "5.15.0" +name = "zune-jpeg" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 3.0.3", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 3.0.3", - "winnow 1.0.4", + "zune-core", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 060753c..6c9a4d0 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "iccery" -version = "0.1.0" -description = "A Tauri App" -authors = ["you"] +version = "0.2.0" +description = "Modern Printer Profiling UI frontend for ArgyllCMS" +authors = ["Gordon"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -19,10 +19,23 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } -tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["process", "io-util", "sync", "time", "rt-multi-thread"] } tauri-plugin-dialog = "2.7.2" base64 = "0.22" +image = { version = "0.25", default-features = false, features = ["png", "tiff"] } +[target.'cfg(windows)'.dependencies] +windows = { version = "0.61", features = [ + "Win32_Foundation", + "Win32_Graphics_Gdi", + "Win32_Graphics_Printing", + "Win32_UI_WindowsAndMessaging", +] } + +[profile.release] +panic = "abort" +codegen-units = 1 +lto = "thin" +strip = true diff --git a/src-tauri/argyll/linux-x86_64/applycal b/src-tauri/argyll/linux-x86_64/applycal index 137fd91..a012805 100755 Binary files a/src-tauri/argyll/linux-x86_64/applycal and b/src-tauri/argyll/linux-x86_64/applycal differ diff --git a/src-tauri/argyll/linux-x86_64/average b/src-tauri/argyll/linux-x86_64/average index 77fc132..a7e8be0 100755 Binary files a/src-tauri/argyll/linux-x86_64/average and b/src-tauri/argyll/linux-x86_64/average differ diff --git a/src-tauri/argyll/linux-x86_64/cb2ti3 b/src-tauri/argyll/linux-x86_64/cb2ti3 index bfb8d89..2fe700a 100755 Binary files a/src-tauri/argyll/linux-x86_64/cb2ti3 and b/src-tauri/argyll/linux-x86_64/cb2ti3 differ diff --git a/src-tauri/argyll/linux-x86_64/cctiff b/src-tauri/argyll/linux-x86_64/cctiff index 85cab87..1663536 100755 Binary files a/src-tauri/argyll/linux-x86_64/cctiff and b/src-tauri/argyll/linux-x86_64/cctiff differ diff --git a/src-tauri/argyll/linux-x86_64/ccxxmake b/src-tauri/argyll/linux-x86_64/ccxxmake index dd02000..a298d46 100755 Binary files a/src-tauri/argyll/linux-x86_64/ccxxmake and b/src-tauri/argyll/linux-x86_64/ccxxmake differ diff --git a/src-tauri/argyll/linux-x86_64/chartread b/src-tauri/argyll/linux-x86_64/chartread index 9c7abef..ee0249b 100755 Binary files a/src-tauri/argyll/linux-x86_64/chartread and b/src-tauri/argyll/linux-x86_64/chartread differ diff --git a/src-tauri/argyll/linux-x86_64/collink b/src-tauri/argyll/linux-x86_64/collink index 5bdab19..30f9cf0 100755 Binary files a/src-tauri/argyll/linux-x86_64/collink and b/src-tauri/argyll/linux-x86_64/collink differ diff --git a/src-tauri/argyll/linux-x86_64/colprof b/src-tauri/argyll/linux-x86_64/colprof index 04f0cf1..a00a0bc 100755 Binary files a/src-tauri/argyll/linux-x86_64/colprof and b/src-tauri/argyll/linux-x86_64/colprof differ diff --git a/src-tauri/argyll/linux-x86_64/colverify b/src-tauri/argyll/linux-x86_64/colverify index 84a73ec..bc67857 100755 Binary files a/src-tauri/argyll/linux-x86_64/colverify and b/src-tauri/argyll/linux-x86_64/colverify differ diff --git a/src-tauri/argyll/linux-x86_64/cxf2ti3 b/src-tauri/argyll/linux-x86_64/cxf2ti3 index bc98def..5850bdf 100755 Binary files a/src-tauri/argyll/linux-x86_64/cxf2ti3 and b/src-tauri/argyll/linux-x86_64/cxf2ti3 differ diff --git a/src-tauri/argyll/linux-x86_64/dispcal b/src-tauri/argyll/linux-x86_64/dispcal index a1a2292..b64900d 100755 Binary files a/src-tauri/argyll/linux-x86_64/dispcal and b/src-tauri/argyll/linux-x86_64/dispcal differ diff --git a/src-tauri/argyll/linux-x86_64/dispread b/src-tauri/argyll/linux-x86_64/dispread index ffadc1b..8435b87 100755 Binary files a/src-tauri/argyll/linux-x86_64/dispread and b/src-tauri/argyll/linux-x86_64/dispread differ diff --git a/src-tauri/argyll/linux-x86_64/dispwin b/src-tauri/argyll/linux-x86_64/dispwin index c98ab2b..03f524a 100755 Binary files a/src-tauri/argyll/linux-x86_64/dispwin and b/src-tauri/argyll/linux-x86_64/dispwin differ diff --git a/src-tauri/argyll/linux-x86_64/extracticc b/src-tauri/argyll/linux-x86_64/extracticc index f71fd3b..f19dd61 100755 Binary files a/src-tauri/argyll/linux-x86_64/extracticc and b/src-tauri/argyll/linux-x86_64/extracticc differ diff --git a/src-tauri/argyll/linux-x86_64/extractttag b/src-tauri/argyll/linux-x86_64/extractttag index 3249499..99c348c 100755 Binary files a/src-tauri/argyll/linux-x86_64/extractttag and b/src-tauri/argyll/linux-x86_64/extractttag differ diff --git a/src-tauri/argyll/linux-x86_64/fakeCMY b/src-tauri/argyll/linux-x86_64/fakeCMY index 693f00e..7251d77 100755 Binary files a/src-tauri/argyll/linux-x86_64/fakeCMY and b/src-tauri/argyll/linux-x86_64/fakeCMY differ diff --git a/src-tauri/argyll/linux-x86_64/fakeread b/src-tauri/argyll/linux-x86_64/fakeread index 2d8af4d..68a0f03 100755 Binary files a/src-tauri/argyll/linux-x86_64/fakeread and b/src-tauri/argyll/linux-x86_64/fakeread differ diff --git a/src-tauri/argyll/linux-x86_64/greytiff b/src-tauri/argyll/linux-x86_64/greytiff index 4d192de..029b90a 100755 Binary files a/src-tauri/argyll/linux-x86_64/greytiff and b/src-tauri/argyll/linux-x86_64/greytiff differ diff --git a/src-tauri/argyll/linux-x86_64/iccdump b/src-tauri/argyll/linux-x86_64/iccdump index 7620cc9..9018a4c 100755 Binary files a/src-tauri/argyll/linux-x86_64/iccdump and b/src-tauri/argyll/linux-x86_64/iccdump differ diff --git a/src-tauri/argyll/linux-x86_64/iccgamut b/src-tauri/argyll/linux-x86_64/iccgamut index ec75224..68fccc8 100755 Binary files a/src-tauri/argyll/linux-x86_64/iccgamut and b/src-tauri/argyll/linux-x86_64/iccgamut differ diff --git a/src-tauri/argyll/linux-x86_64/icclu b/src-tauri/argyll/linux-x86_64/icclu index 4c4f30c..adc545c 100755 Binary files a/src-tauri/argyll/linux-x86_64/icclu and b/src-tauri/argyll/linux-x86_64/icclu differ diff --git a/src-tauri/argyll/linux-x86_64/iccvcgt b/src-tauri/argyll/linux-x86_64/iccvcgt index 6e5dbbb..31e5f36 100755 Binary files a/src-tauri/argyll/linux-x86_64/iccvcgt and b/src-tauri/argyll/linux-x86_64/iccvcgt differ diff --git a/src-tauri/argyll/linux-x86_64/illumread b/src-tauri/argyll/linux-x86_64/illumread index 7b709ff..efa738e 100755 Binary files a/src-tauri/argyll/linux-x86_64/illumread and b/src-tauri/argyll/linux-x86_64/illumread differ diff --git a/src-tauri/argyll/linux-x86_64/instlist b/src-tauri/argyll/linux-x86_64/instlist index 94b86ca..7f9363a 100755 Binary files a/src-tauri/argyll/linux-x86_64/instlist and b/src-tauri/argyll/linux-x86_64/instlist differ diff --git a/src-tauri/argyll/linux-x86_64/invprofcheck b/src-tauri/argyll/linux-x86_64/invprofcheck index a93baa7..0ab4e5e 100755 Binary files a/src-tauri/argyll/linux-x86_64/invprofcheck and b/src-tauri/argyll/linux-x86_64/invprofcheck differ diff --git a/src-tauri/argyll/linux-x86_64/kodak2ti3 b/src-tauri/argyll/linux-x86_64/kodak2ti3 index 4b06bbe..d77305c 100755 Binary files a/src-tauri/argyll/linux-x86_64/kodak2ti3 and b/src-tauri/argyll/linux-x86_64/kodak2ti3 differ diff --git a/src-tauri/argyll/linux-x86_64/ls2ti3 b/src-tauri/argyll/linux-x86_64/ls2ti3 index b7a4239..57caa28 100755 Binary files a/src-tauri/argyll/linux-x86_64/ls2ti3 and b/src-tauri/argyll/linux-x86_64/ls2ti3 differ diff --git a/src-tauri/argyll/linux-x86_64/mppcheck b/src-tauri/argyll/linux-x86_64/mppcheck index 5c6d91e..cf9a82a 100755 Binary files a/src-tauri/argyll/linux-x86_64/mppcheck and b/src-tauri/argyll/linux-x86_64/mppcheck differ diff --git a/src-tauri/argyll/linux-x86_64/mpplu b/src-tauri/argyll/linux-x86_64/mpplu index 0b6220d..8fe5a23 100755 Binary files a/src-tauri/argyll/linux-x86_64/mpplu and b/src-tauri/argyll/linux-x86_64/mpplu differ diff --git a/src-tauri/argyll/linux-x86_64/mppprof b/src-tauri/argyll/linux-x86_64/mppprof index e12a33a..c364d9b 100755 Binary files a/src-tauri/argyll/linux-x86_64/mppprof and b/src-tauri/argyll/linux-x86_64/mppprof differ diff --git a/src-tauri/argyll/linux-x86_64/oeminst b/src-tauri/argyll/linux-x86_64/oeminst index a9cb8d4..f33ecc3 100755 Binary files a/src-tauri/argyll/linux-x86_64/oeminst and b/src-tauri/argyll/linux-x86_64/oeminst differ diff --git a/src-tauri/argyll/linux-x86_64/printcal b/src-tauri/argyll/linux-x86_64/printcal index 91070ef..836f714 100755 Binary files a/src-tauri/argyll/linux-x86_64/printcal and b/src-tauri/argyll/linux-x86_64/printcal differ diff --git a/src-tauri/argyll/linux-x86_64/printtarg b/src-tauri/argyll/linux-x86_64/printtarg index 8581d51..4beb455 100755 Binary files a/src-tauri/argyll/linux-x86_64/printtarg and b/src-tauri/argyll/linux-x86_64/printtarg differ diff --git a/src-tauri/argyll/linux-x86_64/profcheck b/src-tauri/argyll/linux-x86_64/profcheck index 8ca2012..5b08d9a 100755 Binary files a/src-tauri/argyll/linux-x86_64/profcheck and b/src-tauri/argyll/linux-x86_64/profcheck differ diff --git a/src-tauri/argyll/linux-x86_64/refine b/src-tauri/argyll/linux-x86_64/refine index 756fa13..55fd804 100755 Binary files a/src-tauri/argyll/linux-x86_64/refine and b/src-tauri/argyll/linux-x86_64/refine differ diff --git a/src-tauri/argyll/linux-x86_64/revfix b/src-tauri/argyll/linux-x86_64/revfix index ec0b77a..1ff6cc2 100755 Binary files a/src-tauri/argyll/linux-x86_64/revfix and b/src-tauri/argyll/linux-x86_64/revfix differ diff --git a/src-tauri/argyll/linux-x86_64/scanin b/src-tauri/argyll/linux-x86_64/scanin index 85619cc..5cf4307 100755 Binary files a/src-tauri/argyll/linux-x86_64/scanin and b/src-tauri/argyll/linux-x86_64/scanin differ diff --git a/src-tauri/argyll/linux-x86_64/spec2cie b/src-tauri/argyll/linux-x86_64/spec2cie index 13ae188..e6e4f0b 100755 Binary files a/src-tauri/argyll/linux-x86_64/spec2cie and b/src-tauri/argyll/linux-x86_64/spec2cie differ diff --git a/src-tauri/argyll/linux-x86_64/specplot b/src-tauri/argyll/linux-x86_64/specplot index 173c139..655b576 100755 Binary files a/src-tauri/argyll/linux-x86_64/specplot and b/src-tauri/argyll/linux-x86_64/specplot differ diff --git a/src-tauri/argyll/linux-x86_64/splitti3 b/src-tauri/argyll/linux-x86_64/splitti3 index 97a8b46..8ad5a9c 100755 Binary files a/src-tauri/argyll/linux-x86_64/splitti3 and b/src-tauri/argyll/linux-x86_64/splitti3 differ diff --git a/src-tauri/argyll/linux-x86_64/spotread b/src-tauri/argyll/linux-x86_64/spotread index 3a9b7e1..9d505ea 100755 Binary files a/src-tauri/argyll/linux-x86_64/spotread and b/src-tauri/argyll/linux-x86_64/spotread differ diff --git a/src-tauri/argyll/linux-x86_64/synthcal b/src-tauri/argyll/linux-x86_64/synthcal index 93c4043..a6845c6 100755 Binary files a/src-tauri/argyll/linux-x86_64/synthcal and b/src-tauri/argyll/linux-x86_64/synthcal differ diff --git a/src-tauri/argyll/linux-x86_64/synthread b/src-tauri/argyll/linux-x86_64/synthread index 721b533..0635beb 100755 Binary files a/src-tauri/argyll/linux-x86_64/synthread and b/src-tauri/argyll/linux-x86_64/synthread differ diff --git a/src-tauri/argyll/linux-x86_64/targen b/src-tauri/argyll/linux-x86_64/targen index 7c39b09..aa35e9d 100755 Binary files a/src-tauri/argyll/linux-x86_64/targen and b/src-tauri/argyll/linux-x86_64/targen differ diff --git a/src-tauri/argyll/linux-x86_64/tiffgamut b/src-tauri/argyll/linux-x86_64/tiffgamut index 100d01c..c226656 100755 Binary files a/src-tauri/argyll/linux-x86_64/tiffgamut and b/src-tauri/argyll/linux-x86_64/tiffgamut differ diff --git a/src-tauri/argyll/linux-x86_64/timage b/src-tauri/argyll/linux-x86_64/timage index a519a54..9fe3bcd 100755 Binary files a/src-tauri/argyll/linux-x86_64/timage and b/src-tauri/argyll/linux-x86_64/timage differ diff --git a/src-tauri/argyll/linux-x86_64/txt2ti3 b/src-tauri/argyll/linux-x86_64/txt2ti3 index a59ed24..3fcf478 100755 Binary files a/src-tauri/argyll/linux-x86_64/txt2ti3 and b/src-tauri/argyll/linux-x86_64/txt2ti3 differ diff --git a/src-tauri/argyll/linux-x86_64/viewgam b/src-tauri/argyll/linux-x86_64/viewgam index e941f57..5a6863a 100755 Binary files a/src-tauri/argyll/linux-x86_64/viewgam and b/src-tauri/argyll/linux-x86_64/viewgam differ diff --git a/src-tauri/argyll/linux-x86_64/xicclu b/src-tauri/argyll/linux-x86_64/xicclu index 9024402..86d8aee 100755 Binary files a/src-tauri/argyll/linux-x86_64/xicclu and b/src-tauri/argyll/linux-x86_64/xicclu differ diff --git a/src-tauri/argyll/windows-x86_64/License.txt b/src-tauri/argyll/windows-x86_64/License.txt new file mode 100644 index 0000000..d56d0b6 --- /dev/null +++ b/src-tauri/argyll/windows-x86_64/License.txt @@ -0,0 +1,662 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. + diff --git a/src-tauri/argyll/windows-x86_64/applycal.exe b/src-tauri/argyll/windows-x86_64/applycal.exe new file mode 100755 index 0000000..21535d8 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/applycal.exe differ diff --git a/src-tauri/argyll/windows-x86_64/average.exe b/src-tauri/argyll/windows-x86_64/average.exe new file mode 100755 index 0000000..5f96da5 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/average.exe differ diff --git a/src-tauri/argyll/windows-x86_64/cb2ti3.exe b/src-tauri/argyll/windows-x86_64/cb2ti3.exe new file mode 100755 index 0000000..8dfd131 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/cb2ti3.exe differ diff --git a/src-tauri/argyll/windows-x86_64/cctiff.exe b/src-tauri/argyll/windows-x86_64/cctiff.exe new file mode 100755 index 0000000..9872699 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/cctiff.exe differ diff --git a/src-tauri/argyll/windows-x86_64/ccxxmake.exe b/src-tauri/argyll/windows-x86_64/ccxxmake.exe new file mode 100755 index 0000000..02d121d Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/ccxxmake.exe differ diff --git a/src-tauri/argyll/windows-x86_64/chartread.exe b/src-tauri/argyll/windows-x86_64/chartread.exe new file mode 100755 index 0000000..d2f511f Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/chartread.exe differ diff --git a/src-tauri/argyll/windows-x86_64/collink.exe b/src-tauri/argyll/windows-x86_64/collink.exe new file mode 100755 index 0000000..736d5b4 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/collink.exe differ diff --git a/src-tauri/argyll/windows-x86_64/colprof.exe b/src-tauri/argyll/windows-x86_64/colprof.exe new file mode 100755 index 0000000..bcaf38c Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/colprof.exe differ diff --git a/src-tauri/argyll/windows-x86_64/colverify.exe b/src-tauri/argyll/windows-x86_64/colverify.exe new file mode 100755 index 0000000..3710f9a Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/colverify.exe differ diff --git a/src-tauri/argyll/windows-x86_64/com.argyllcms.metainfo.xml b/src-tauri/argyll/windows-x86_64/com.argyllcms.metainfo.xml new file mode 100644 index 0000000..8e3a02c --- /dev/null +++ b/src-tauri/argyll/windows-x86_64/com.argyllcms.metainfo.xml @@ -0,0 +1,55 @@ + + + com.argyllcms + MIT + argyll + Color Management System, calibrator and profiler + +

Argyll is an experimental, open source, ICC compatible color + management system. It supports accurate ICC profile creation for + scanners, CMYK printers, film recorders and calibration and + profiling of displays. Spectral sample data is supported, + allowing a selection of illuminants observer types, and paper + fluorescent whitener additive compensation. Profiles can also + incorporate source specific gamut mappings for perceptual and + saturation intents. Gamut mapping and profile linking uses the + CIECAM02 appearance model, a unique gamut mapping algorithm, and a + wide selection of rendering intents. It also includes code for the + fastest portable 8 bit raster color conversion engine available + anywhere, as well as support for fast, fully accurate 16 bit + conversion. Device color gamuts can also be viewed and compared + using a VRML viewer.

+
+ + usb:vv04dbp005b* + usb:vv0670p0001* + usb:vv0765p5001* + usb:vv0765p5010* + usb:vv0765p5020* + usb:vv0765p6003* + usb:vv0765p6008* + usb:vv0765p6009* + usb:vv0765pd020* + usb:vv0765pd092* + usb:vv0765pd094* + usb:vv0971p2000* + usb:vv0971p2001* + usb:vv0971p2003* + usb:vv0971p2004* + usb:vv0971p2005* + usb:vv0971p2006* + usb:vv0971p2007* + usb:vv085cp0100* + usb:vv085cp0200* + usb:vv085cp0300* + usb:vv085cp0400* + usb:vv085cp0500* + usb:vv085cp0a00* + usb:vv085cp0a0a* + usb:vv04d8pf8da* + usb:vv273fp1001* + usb:vv273fp1004* + usb:vv273fp1002* + usb:vv2457p4000* + +
diff --git a/src-tauri/argyll/windows-x86_64/cxf2ti3.exe b/src-tauri/argyll/windows-x86_64/cxf2ti3.exe new file mode 100755 index 0000000..ffb52b2 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/cxf2ti3.exe differ diff --git a/src-tauri/argyll/windows-x86_64/dispcal.exe b/src-tauri/argyll/windows-x86_64/dispcal.exe new file mode 100755 index 0000000..9d1c796 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/dispcal.exe differ diff --git a/src-tauri/argyll/windows-x86_64/dispread.exe b/src-tauri/argyll/windows-x86_64/dispread.exe new file mode 100755 index 0000000..f59e0d2 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/dispread.exe differ diff --git a/src-tauri/argyll/windows-x86_64/dispwin.exe b/src-tauri/argyll/windows-x86_64/dispwin.exe new file mode 100755 index 0000000..b3ee71a Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/dispwin.exe differ diff --git a/src-tauri/argyll/windows-x86_64/extracticc.exe b/src-tauri/argyll/windows-x86_64/extracticc.exe new file mode 100755 index 0000000..03c0ff3 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/extracticc.exe differ diff --git a/src-tauri/argyll/windows-x86_64/extractttag.exe b/src-tauri/argyll/windows-x86_64/extractttag.exe new file mode 100755 index 0000000..b4fe634 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/extractttag.exe differ diff --git a/src-tauri/argyll/windows-x86_64/fakeCMY.exe b/src-tauri/argyll/windows-x86_64/fakeCMY.exe new file mode 100755 index 0000000..cdf063f Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/fakeCMY.exe differ diff --git a/src-tauri/argyll/windows-x86_64/fakeread.exe b/src-tauri/argyll/windows-x86_64/fakeread.exe new file mode 100755 index 0000000..5bd8aba Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/fakeread.exe differ diff --git a/src-tauri/argyll/windows-x86_64/greytiff.exe b/src-tauri/argyll/windows-x86_64/greytiff.exe new file mode 100755 index 0000000..c83b82d Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/greytiff.exe differ diff --git a/src-tauri/argyll/windows-x86_64/iccdump.exe b/src-tauri/argyll/windows-x86_64/iccdump.exe new file mode 100755 index 0000000..96806ff Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/iccdump.exe differ diff --git a/src-tauri/argyll/windows-x86_64/iccgamut.exe b/src-tauri/argyll/windows-x86_64/iccgamut.exe new file mode 100755 index 0000000..c09ced9 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/iccgamut.exe differ diff --git a/src-tauri/argyll/windows-x86_64/icclu.exe b/src-tauri/argyll/windows-x86_64/icclu.exe new file mode 100755 index 0000000..8930996 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/icclu.exe differ diff --git a/src-tauri/argyll/windows-x86_64/iccvcgt.exe b/src-tauri/argyll/windows-x86_64/iccvcgt.exe new file mode 100755 index 0000000..6b7023b Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/iccvcgt.exe differ diff --git a/src-tauri/argyll/windows-x86_64/illumread.exe b/src-tauri/argyll/windows-x86_64/illumread.exe new file mode 100755 index 0000000..27b9228 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/illumread.exe differ diff --git a/src-tauri/argyll/windows-x86_64/instlist.exe b/src-tauri/argyll/windows-x86_64/instlist.exe new file mode 100755 index 0000000..78cf982 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/instlist.exe differ diff --git a/src-tauri/argyll/windows-x86_64/invprofcheck.exe b/src-tauri/argyll/windows-x86_64/invprofcheck.exe new file mode 100755 index 0000000..c973398 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/invprofcheck.exe differ diff --git a/src-tauri/argyll/windows-x86_64/kodak2ti3.exe b/src-tauri/argyll/windows-x86_64/kodak2ti3.exe new file mode 100755 index 0000000..a524780 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/kodak2ti3.exe differ diff --git a/src-tauri/argyll/windows-x86_64/ls2ti3.exe b/src-tauri/argyll/windows-x86_64/ls2ti3.exe new file mode 100755 index 0000000..1b2c3a4 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/ls2ti3.exe differ diff --git a/src-tauri/argyll/windows-x86_64/mppcheck.exe b/src-tauri/argyll/windows-x86_64/mppcheck.exe new file mode 100755 index 0000000..de1409c Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/mppcheck.exe differ diff --git a/src-tauri/argyll/windows-x86_64/mpplu.exe b/src-tauri/argyll/windows-x86_64/mpplu.exe new file mode 100755 index 0000000..42a687b Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/mpplu.exe differ diff --git a/src-tauri/argyll/windows-x86_64/mppprof.exe b/src-tauri/argyll/windows-x86_64/mppprof.exe new file mode 100755 index 0000000..f002911 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/mppprof.exe differ diff --git a/src-tauri/argyll/windows-x86_64/oeminst.exe b/src-tauri/argyll/windows-x86_64/oeminst.exe new file mode 100755 index 0000000..817fc4b Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/oeminst.exe differ diff --git a/src-tauri/argyll/windows-x86_64/printcal.exe b/src-tauri/argyll/windows-x86_64/printcal.exe new file mode 100755 index 0000000..df92eed Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/printcal.exe differ diff --git a/src-tauri/argyll/windows-x86_64/printtarg.exe b/src-tauri/argyll/windows-x86_64/printtarg.exe new file mode 100755 index 0000000..22d53b8 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/printtarg.exe differ diff --git a/src-tauri/argyll/windows-x86_64/profcheck.exe b/src-tauri/argyll/windows-x86_64/profcheck.exe new file mode 100755 index 0000000..70cbb18 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/profcheck.exe differ diff --git a/src-tauri/argyll/windows-x86_64/refine.exe b/src-tauri/argyll/windows-x86_64/refine.exe new file mode 100755 index 0000000..3818e98 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/refine.exe differ diff --git a/src-tauri/argyll/windows-x86_64/revfix.exe b/src-tauri/argyll/windows-x86_64/revfix.exe new file mode 100755 index 0000000..8fd43f9 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/revfix.exe differ diff --git a/src-tauri/argyll/windows-x86_64/scanin.exe b/src-tauri/argyll/windows-x86_64/scanin.exe new file mode 100755 index 0000000..f1c2f93 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/scanin.exe differ diff --git a/src-tauri/argyll/windows-x86_64/spec2cie.exe b/src-tauri/argyll/windows-x86_64/spec2cie.exe new file mode 100755 index 0000000..b17aa80 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/spec2cie.exe differ diff --git a/src-tauri/argyll/windows-x86_64/specplot.exe b/src-tauri/argyll/windows-x86_64/specplot.exe new file mode 100755 index 0000000..926d7e1 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/specplot.exe differ diff --git a/src-tauri/argyll/windows-x86_64/splitti3.exe b/src-tauri/argyll/windows-x86_64/splitti3.exe new file mode 100755 index 0000000..fdd4c46 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/splitti3.exe differ diff --git a/src-tauri/argyll/windows-x86_64/spotread.exe b/src-tauri/argyll/windows-x86_64/spotread.exe new file mode 100755 index 0000000..b0d1429 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/spotread.exe differ diff --git a/src-tauri/argyll/windows-x86_64/synthcal.exe b/src-tauri/argyll/windows-x86_64/synthcal.exe new file mode 100755 index 0000000..652cf0a Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/synthcal.exe differ diff --git a/src-tauri/argyll/windows-x86_64/synthread.exe b/src-tauri/argyll/windows-x86_64/synthread.exe new file mode 100755 index 0000000..8634109 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/synthread.exe differ diff --git a/src-tauri/argyll/windows-x86_64/targen.exe b/src-tauri/argyll/windows-x86_64/targen.exe new file mode 100755 index 0000000..f7c1915 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/targen.exe differ diff --git a/src-tauri/argyll/windows-x86_64/tiffgamut.exe b/src-tauri/argyll/windows-x86_64/tiffgamut.exe new file mode 100755 index 0000000..2993947 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/tiffgamut.exe differ diff --git a/src-tauri/argyll/windows-x86_64/timage.exe b/src-tauri/argyll/windows-x86_64/timage.exe new file mode 100755 index 0000000..0a524d4 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/timage.exe differ diff --git a/src-tauri/argyll/windows-x86_64/txt2ti3.exe b/src-tauri/argyll/windows-x86_64/txt2ti3.exe new file mode 100755 index 0000000..ebe9101 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/txt2ti3.exe differ diff --git a/src-tauri/argyll/windows-x86_64/viewgam.exe b/src-tauri/argyll/windows-x86_64/viewgam.exe new file mode 100755 index 0000000..42165e8 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/viewgam.exe differ diff --git a/src-tauri/argyll/windows-x86_64/xicclu.exe b/src-tauri/argyll/windows-x86_64/xicclu.exe new file mode 100755 index 0000000..00f3e59 Binary files /dev/null and b/src-tauri/argyll/windows-x86_64/xicclu.exe differ diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 778bfb5..e895c6b 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -5,7 +5,6 @@ "windows": ["main"], "permissions": [ "core:default", - "opener:default", "dialog:default" ] } diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 6be5e50..aacddbc 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index e81bece..5e3036a 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index a437dd5..28ff3fe 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png new file mode 100644 index 0000000..f4f523e Binary files /dev/null and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index 0ca4f27..278560d 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index b81f820..67caec9 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index 624c7bf..44195cd 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index c021d2b..3f8e04b 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 6219700..ea87d5f 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index f9bc048..4a389b2 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index d5fbfb2..0b7c8c8 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index 63440d7..5bc4336 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index f3f705a..aa9e423 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index 4556388..1e6ecae 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..42d18fa Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..5b09109 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..91be58e Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..6970104 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a454829 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..7df4870 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..fcbff2d Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..e0ad14c Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d335260 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..241168d Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..88a62e6 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..adedee3 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..5d96f65 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..df15b03 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..45d166a Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index 12a5bce..2f0adc2 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index b3636e4..66fa412 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index e1cd261..7cb7771 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..d984d6e Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..f42f9ce Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..f42f9ce Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..ae8599c Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..45b137b Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..f447c0a Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..f447c0a Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..40494ff Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..f42f9ce Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..9084114 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..9084114 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..356f6f7 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..f12e533 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..356f6f7 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..4bbb706 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..5e726f6 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..f89a454 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..b24d1f8 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/src-tauri/icons/nsis-header.bmp b/src-tauri/icons/nsis-header.bmp new file mode 100644 index 0000000..c1f8368 Binary files /dev/null and b/src-tauri/icons/nsis-header.bmp differ diff --git a/src-tauri/icons/nsis-sidebar.bmp b/src-tauri/icons/nsis-sidebar.bmp new file mode 100644 index 0000000..b34942e Binary files /dev/null and b/src-tauri/icons/nsis-sidebar.bmp differ diff --git a/src-tauri/icons/wix-banner.bmp b/src-tauri/icons/wix-banner.bmp new file mode 100644 index 0000000..c4aae7a Binary files /dev/null and b/src-tauri/icons/wix-banner.bmp differ diff --git a/src-tauri/icons/wix-dialog.bmp b/src-tauri/icons/wix-dialog.bmp new file mode 100644 index 0000000..7eba95e Binary files /dev/null and b/src-tauri/icons/wix-dialog.bmp differ diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 65b4f2b..7535eaf 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -75,12 +75,96 @@ pub fn get_app_info() -> AppInfo { } } +pub fn resolve_safe_cwd(app: &AppHandle, cwd_input: &str) -> Result { + if !cwd_input.trim().is_empty() { + let p = std::path::Path::new(cwd_input.trim()); + if p.is_dir() { + return Ok(p.to_string_lossy().to_string()); + } + } + + app.path() + .document_dir() + .or_else(|_| app.path().home_dir()) + .or_else(|_| app.path().app_data_dir()) + .map(|p| p.to_string_lossy().to_string()) + .map_err(|e| format!("Failed to resolve default working directory: {}", e)) +} + +#[tauri::command] +pub fn get_default_working_dir(app: AppHandle) -> Result { + resolve_safe_cwd(&app, "") +} + #[tauri::command] pub async fn detect_instruments(app: AppHandle, state: State<'_, ProcessManager>) -> Result<(), String> { let binary = resolve_binary(app.clone(), "instlist".to_string()).await?; state.spawn(app, "instlist".to_string(), binary, vec![], None).await } +pub fn resolve_profile_extension(cwd: &str, basename: &str) -> (String, std::path::PathBuf) { + let p_icc = std::path::Path::new(cwd).join(format!("{}.icc", basename)); + let p_icm = std::path::Path::new(cwd).join(format!("{}.icm", basename)); + + if p_icm.exists() && !p_icc.exists() { + ("icm".to_string(), p_icm) + } else if p_icc.exists() { + ("icc".to_string(), p_icc) + } else { + #[cfg(windows)] + { + ("icm".to_string(), p_icm) + } + #[cfg(not(windows))] + { + ("icc".to_string(), p_icc) + } + } +} + +#[derive(Serialize)] +pub struct StageStatus { + pub stage1_complete: bool, + pub stage2_complete: bool, + pub stage3_complete: bool, + pub stage4_complete: bool, + pub profile_path: Option, +} + +#[tauri::command] +pub fn verify_stage_artefacts(cwd: String, basename: String) -> StageStatus { + if cwd.trim().is_empty() || basename.trim().is_empty() { + return StageStatus { + stage1_complete: false, + stage2_complete: false, + stage3_complete: false, + stage4_complete: false, + profile_path: None, + }; + } + + let base_path = std::path::Path::new(&cwd); + let ti1 = base_path.join(format!("{}.ti1", basename)).exists(); + let ti2 = base_path.join(format!("{}.ti2", basename)).exists(); + let ti3 = base_path.join(format!("{}.ti3", basename)).exists(); + let (_, prof_p) = resolve_profile_extension(&cwd, &basename); + let prof_exists = prof_p.exists(); + + StageStatus { + stage1_complete: ti1, + stage2_complete: ti2, + stage3_complete: ti3, + stage4_complete: prof_exists, + profile_path: if prof_exists { Some(prof_p.to_string_lossy().to_string()) } else { None }, + } +} + +#[tauri::command] +pub fn get_profile_path(cwd: String, basename: String) -> String { + let (_, path) = resolve_profile_extension(&cwd, &basename); + path.to_string_lossy().to_string() +} + #[tauri::command] pub async fn extract_gamut( app: AppHandle, @@ -89,36 +173,66 @@ pub async fn extract_gamut( ) -> Result<(), String> { let binary = resolve_binary(app.clone(), "iccgamut".to_string()).await?; let path = std::path::Path::new(&icc_path); - let basename = path.file_stem().unwrap().to_str().unwrap(); - let parent_dir = path.parent().map(|p| p.to_str().unwrap()).unwrap_or(""); + let basename = path.file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| "profile".to_string()); + let parent_dir = path.parent().map(|p| p.to_string_lossy().to_string()).unwrap_or_default(); - let mut args = vec!["-w".to_string()]; - if !parent_dir.is_empty() { - args.push("-d".to_string()); - args.push(parent_dir.to_string()); - } - args.push(icc_path.clone()); + // Auto-detect .icc vs .icm if the specified path does not exist directly + let resolved_path = if path.exists() { + icc_path.clone() + } else { + let alt = if icc_path.ends_with(".icc") { + icc_path.replace(".icc", ".icm") + } else if icc_path.ends_with(".icm") { + icc_path.replace(".icm", ".icc") + } else { + icc_path.clone() + }; + if std::path::Path::new(&alt).exists() { + alt + } else { + icc_path.clone() + } + }; + + let args = vec![ + "-v".to_string(), + "-d".to_string(), + "50.0".to_string(), + resolved_path, + ]; + let cwd = if parent_dir.is_empty() { + resolve_safe_cwd(&app, "").ok() + } else { + Some(parent_dir) + }; let id = format!("iccgamut_{}", basename); - state.spawn(app, id, binary, args, None).await + state.spawn(app, id, binary, args, cwd).await } #[derive(Debug, Deserialize, Serialize)] pub struct TargenConfig { pub colour_space: String, + #[serde(default)] pub patch_count: u32, pub white_patches: Option, pub black_patches: Option, + pub total_patches: Option, pub basename: String, pub cwd: String, } +fn default_dpi() -> u32 { + 300 +} + #[derive(Debug, Deserialize, Serialize)] pub struct PrinttargConfig { pub instrument: String, // One of: "i1", "p3", "CM", "SS", "20", "22", "41", "51" pub page_size: String, // One of: "A4", "A4R", "A3", "A2", "Letter", "LetterR", "Legal", "4x6", "11x17", or "WWWxHHH" pub bit_depth: u8, // 8 or 16 - pub dpi: u32, // TIFF resolution, e.g. 100, 200, 300 + #[serde(default = "default_dpi")] + pub dpi: u32, // TIFF resolution, defaults to 300 DPI pub basename: String, // Must match the .ti1 basename from Stage 1 pub cwd: String, // Working directory where the .ti1 file resides } @@ -135,8 +249,16 @@ pub fn build_targen_args(config: &TargenConfig) -> Vec { args.push("2".to_string()); // Default to RGB } - args.push("-f".to_string()); - args.push(config.patch_count.to_string()); + let patches = if config.patch_count > 0 { + config.patch_count + } else { + config.total_patches.unwrap_or(0) + }; + + if patches > 0 { + args.push("-f".to_string()); + args.push(patches.to_string()); + } if let Some(white) = config.white_patches { args.push("-e".to_string()); @@ -182,14 +304,7 @@ pub async fn run_targen( let binary = resolve_binary(app.clone(), "targen".to_string()).await?; let args = build_targen_args(&config); let id = format!("targen_{}", config.basename); - - let cwd = if config.cwd.trim().is_empty() { - None - } else { - Some(config.cwd.clone()) - }; - - println!("DEBUG RUN_TARGEN: binary='{}' args={:?} cwd={:?}", binary, args, cwd); + let cwd = Some(resolve_safe_cwd(&app, &config.cwd)?); state.spawn(app, id, binary, args, cwd).await } @@ -203,12 +318,7 @@ pub async fn run_printtarg( let binary = resolve_binary(app.clone(), "printtarg".to_string()).await?; let args = build_printtarg_args(&config); let id = format!("printtarg_{}", config.basename); - - let cwd = if config.cwd.trim().is_empty() { - None - } else { - Some(config.cwd.clone()) - }; + let cwd = Some(resolve_safe_cwd(&app, &config.cwd)?); state.spawn(app, id, binary, args, cwd).await } @@ -221,6 +331,32 @@ pub async fn read_file_base64(path: String) -> Result { Ok(base64::engine::general_purpose::STANDARD.encode(&bytes)) } +#[tauri::command] +pub async fn read_tiff_preview_png(path: String) -> Result { + use std::fs; + use std::io::Cursor; + use base64::Engine; + use image::ImageFormat; + + let file_bytes = fs::read(&path).map_err(|e| format!("Failed to read {}: {}", path, e))?; + let img = image::load_from_memory_with_format(&file_bytes, ImageFormat::Tiff) + .map_err(|e| format!("Failed to decode TIFF {}: {}", path, e))?; + + let max_dim = std::cmp::max(img.width(), img.height()); + let processed_img = if max_dim > 1200 { + img.resize(1200, 1200, image::imageops::FilterType::Lanczos3) + } else { + img + }; + + let mut png_bytes = Cursor::new(Vec::new()); + processed_img + .write_to(&mut png_bytes, ImageFormat::Png) + .map_err(|e| format!("Failed to encode PNG for {}: {}", path, e))?; + + Ok(base64::engine::general_purpose::STANDARD.encode(png_bytes.into_inner())) +} + #[derive(Debug, Deserialize, Serialize)] pub struct ChartreadConfig { pub basename: String, @@ -244,22 +380,18 @@ pub async fn run_chartread( let binary = resolve_binary(app.clone(), "chartread".to_string()).await?; let args = build_chartread_args(&config); let id = format!("chartread_{}", config.basename); - - let cwd = if config.cwd.trim().is_empty() { - None - } else { - Some(config.cwd.clone()) - }; + let cwd = Some(resolve_safe_cwd(&app, &config.cwd)?); state.spawn(app, id, binary, args, cwd).await } #[derive(Debug, Deserialize, Serialize)] pub struct ColprofConfig { - pub quality: String, pub algorithm: String, - pub description: String, + pub quality: String, + pub intent: Option, pub copyright: Option, + pub description: Option, pub basename: String, pub cwd: String, } @@ -267,19 +399,30 @@ pub struct ColprofConfig { pub fn build_colprof_args(config: &ColprofConfig) -> Vec { let mut args = vec![ "-v".to_string(), - "-u".to_string(), - "-q".to_string(), - config.quality.clone(), "-a".to_string(), config.algorithm.clone(), - "-D".to_string(), - config.description.clone(), + "-q".to_string(), + config.quality.clone(), ]; - if let Some(copyright) = &config.copyright { - if !copyright.trim().is_empty() { + if let Some(ref intent) = config.intent { + if !intent.trim().is_empty() { + args.push("-p".to_string()); + args.push(intent.clone()); + } + } + + if let Some(ref cr) = config.copyright { + if !cr.trim().is_empty() { args.push("-C".to_string()); - args.push(copyright.clone()); + args.push(cr.clone()); + } + } + + if let Some(ref desc) = config.description { + if !desc.trim().is_empty() { + args.push("-D".to_string()); + args.push(desc.clone()); } } @@ -296,12 +439,7 @@ pub async fn run_colprof( let binary = resolve_binary(app.clone(), "colprof".to_string()).await?; let args = build_colprof_args(&config); let id = format!("colprof_{}", config.basename); - - let cwd = if config.cwd.trim().is_empty() { - None - } else { - Some(config.cwd.clone()) - }; + let cwd = Some(resolve_safe_cwd(&app, &config.cwd)?); state.spawn(app, id, binary, args, cwd).await } @@ -317,7 +455,7 @@ pub fn build_profcheck_args(config: &ProfcheckConfig) -> Vec { vec![ "-v".to_string(), "-k".to_string(), - "-u".to_string(), + "-s".to_string(), config.ti3_path.clone(), config.icc_path.clone(), ] @@ -327,21 +465,182 @@ pub fn build_profcheck_args(config: &ProfcheckConfig) -> Vec { pub async fn run_profcheck( app: AppHandle, state: State<'_, ProcessManager>, - config: ProfcheckConfig, + mut config: ProfcheckConfig, ) -> Result<(), String> { let binary = resolve_binary(app.clone(), "profcheck".to_string()).await?; + + // Auto-detect .icc vs .icm if the specified icc_path does not exist directly + if !std::path::Path::new(&config.icc_path).exists() { + let alt = if config.icc_path.ends_with(".icc") { + config.icc_path.replace(".icc", ".icm") + } else if config.icc_path.ends_with(".icm") { + config.icc_path.replace(".icm", ".icc") + } else { + config.icc_path.clone() + }; + if std::path::Path::new(&alt).exists() { + config.icc_path = alt; + } + } + let args = build_profcheck_args(&config); let id = format!("profcheck_{}", config.ti3_path); - - let cwd = if config.cwd.trim().is_empty() { - None - } else { - Some(config.cwd.clone()) - }; + let cwd = Some(resolve_safe_cwd(&app, &config.cwd)?); state.spawn(app, id, binary, args, cwd).await } +#[tauri::command] +pub async fn get_windows_printers() -> Result, String> { + #[cfg(windows)] + { + crate::print::windows::get_printers().map(|list| list.into_iter().map(|p| p.name).collect()) + } + #[cfg(not(windows))] + { + Err("Windows native printing is only supported on Windows.".to_string()) + } +} + +#[tauri::command] +pub async fn print_target_windows( + state: State<'_, crate::print::PrinterDevModeStore>, + printer_name: String, + tiff_path: String, + options: Option, +) -> Result<(), String> { + #[cfg(windows)] + { + crate::print::windows::print_target(&printer_name, &tiff_path, options.as_ref(), Some(&state)) + } + #[cfg(not(windows))] + { + let _ = (state, printer_name, tiff_path, options); + Err("Windows native printing is only supported on Windows.".to_string()) + } +} + +#[tauri::command] +pub async fn get_cups_printers() -> Result, String> { + #[cfg(unix)] + { + crate::print::unix::get_printers() + } + #[cfg(not(unix))] + { + Err("CUPS printing is only supported on macOS and Linux.".to_string()) + } +} + +#[tauri::command] +pub async fn print_target_cups( + printer_name: String, + tiff_path: String, + options: Option, +) -> Result<(), String> { + #[cfg(unix)] + { + crate::print::unix::print_target( + &printer_name, + &tiff_path, + options.as_ref(), + ) + } + #[cfg(not(unix))] + { + let _ = (printer_name, tiff_path, options); + Err("CUPS printing is only supported on macOS and Linux.".to_string()) + } +} + +#[tauri::command] +pub async fn get_printers() -> Result, String> { + #[cfg(windows)] + { + crate::print::windows::get_printers() + } + #[cfg(unix)] + { + crate::print::unix::get_printers() + } + #[cfg(not(any(windows, unix)))] + { + Err("Native printer enumeration is not supported on this platform.".to_string()) + } +} + +#[tauri::command] +pub async fn get_printer_capabilities( + printer_name: String, +) -> Result { + #[cfg(windows)] + { + crate::print::windows::get_printer_capabilities(&printer_name) + } + #[cfg(unix)] + { + crate::print::unix::get_printer_capabilities(&printer_name) + } + #[cfg(not(any(windows, unix)))] + { + let _ = printer_name; + Err("Printer capabilities query is not supported on this platform.".to_string()) + } +} + +#[tauri::command] +pub async fn show_printer_properties( + state: State<'_, crate::print::PrinterDevModeStore>, + printer_name: String, +) -> Result<(), String> { + #[cfg(windows)] + { + crate::print::windows::show_printer_properties(&printer_name, &state) + } + #[cfg(unix)] + { + let _ = (state, printer_name); + Ok(()) + } + #[cfg(not(any(windows, unix)))] + { + let _ = (state, printer_name); + Err("Printer properties dialog is not supported on this platform.".to_string()) + } +} + +#[tauri::command] +pub async fn print_target_native( + state: State<'_, crate::print::PrinterDevModeStore>, + printer_name: String, + tiff_path: String, + options: Option, +) -> Result<(), String> { + #[cfg(windows)] + { + crate::print::windows::print_target( + &printer_name, + &tiff_path, + options.as_ref(), + Some(&state), + ) + } + #[cfg(unix)] + { + let _ = state; + crate::print::unix::print_target( + &printer_name, + &tiff_path, + options.as_ref(), + ) + } + #[cfg(not(any(windows, unix)))] + { + let _ = (state, printer_name, tiff_path, options); + Err("Native raw printing is not supported on this platform.".to_string()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -353,6 +652,7 @@ mod tests { patch_count: 800, white_patches: Some(4), black_patches: None, + total_patches: None, basename: "my_profile".to_string(), cwd: "/tmp".to_string(), }; @@ -367,6 +667,7 @@ mod tests { patch_count: 1500, white_patches: None, black_patches: Some(8), + total_patches: None, basename: "cmyk_profile".to_string(), cwd: "/tmp".to_string(), }; @@ -374,6 +675,21 @@ mod tests { assert_eq!(args, vec!["-v", "-d", "4", "-f", "1500", "-B", "8", "cmyk_profile"]); } + #[test] + fn test_build_targen_args_total_patches_fallback() { + let config = TargenConfig { + colour_space: "rgb".to_string(), + patch_count: 0, + white_patches: None, + black_patches: None, + total_patches: Some(400), + basename: "draft_profile".to_string(), + cwd: "/tmp".to_string(), + }; + let args = build_targen_args(&config); + assert_eq!(args, vec!["-v", "-d", "2", "-f", "400", "draft_profile"]); + } + #[test] fn test_build_printtarg_args_i1_a4_8bit() { let config = PrinttargConfig { @@ -431,7 +747,8 @@ mod tests { let config = ColprofConfig { quality: "h".to_string(), algorithm: "l".to_string(), - description: "My Profile".to_string(), + intent: None, + description: Some("My Profile".to_string()), copyright: Some("2026 ACME".to_string()), basename: "my_profile".to_string(), cwd: "/home/user".to_string(), @@ -439,7 +756,7 @@ mod tests { let args = build_colprof_args(&config); assert_eq!( args, - vec!["-v", "-u", "-q", "h", "-a", "l", "-D", "My Profile", "-C", "2026 ACME", "my_profile"] + vec!["-v", "-a", "l", "-q", "h", "-C", "2026 ACME", "-D", "My Profile", "my_profile"] ); } @@ -451,6 +768,6 @@ mod tests { cwd: "/home/user".to_string(), }; let args = build_profcheck_args(&config); - assert_eq!(args, vec!["-v", "-k", "-u", "my_profile.ti3", "my_profile.icc"]); + assert_eq!(args, vec!["-v", "-k", "-s", "my_profile.ti3", "my_profile.icc"]); } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4b1c93d..dd951e9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,28 +1,41 @@ mod commands; mod events; +mod print; mod process_manager; mod settings; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_dialog::init()) .manage(process_manager::ProcessManager::new()) + .manage(print::PrinterDevModeStore::new()) .invoke_handler(tauri::generate_handler![ commands::spawn_process, commands::get_app_info, + commands::get_default_working_dir, commands::send_stdin, commands::kill_process, commands::resolve_binary, commands::detect_instruments, + commands::get_profile_path, + commands::verify_stage_artefacts, commands::extract_gamut, commands::run_targen, commands::run_printtarg, commands::read_file_base64, + commands::read_tiff_preview_png, commands::run_chartread, commands::run_colprof, commands::run_profcheck, + commands::get_windows_printers, + commands::print_target_windows, + commands::get_cups_printers, + commands::print_target_cups, + commands::get_printers, + commands::get_printer_capabilities, + commands::show_printer_properties, + commands::print_target_native, settings::load_settings, settings::save_settings, ]) diff --git a/src-tauri/src/print/mod.rs b/src-tauri/src/print/mod.rs new file mode 100644 index 0000000..2c09492 --- /dev/null +++ b/src-tauri/src/print/mod.rs @@ -0,0 +1,72 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Printer { + pub name: String, + pub status: String, + pub is_default: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PrinterTray { + pub id: u16, + pub name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PrinterPaperSize { + pub id: u16, + pub name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PrinterCapabilities { + pub trays: Vec, + pub paper_sizes: Vec, + pub supports_orientation: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] +pub struct PrintOptions { + pub paper_source: Option, + pub orientation: Option, + pub paper_size: Option, + pub ppd_uncorrected_passthrough: Option, +} + +#[derive(Clone, Default)] +#[allow(dead_code)] +pub struct PrinterDevModeStore { + pub devmodes: Arc>>>, +} + +#[allow(dead_code)] +impl PrinterDevModeStore { + pub fn new() -> Self { + Self { + devmodes: Arc::new(Mutex::new(HashMap::new())), + } + } + + pub fn get(&self, printer_name: &str) -> Option> { + let map = self.devmodes.lock().ok()?; + map.get(printer_name).cloned() + } + + pub fn set(&self, printer_name: &str, devmode: Vec) { + if let Ok(mut map) = self.devmodes.lock() { + map.insert(printer_name.to_string(), devmode); + } + } +} + +#[cfg(windows)] +pub mod windows; + +#[cfg(any(unix, test))] +pub mod unix; + +#[cfg(test)] +mod tests; diff --git a/src-tauri/src/print/tests.rs b/src-tauri/src/print/tests.rs new file mode 100644 index 0000000..b0deef7 --- /dev/null +++ b/src-tauri/src/print/tests.rs @@ -0,0 +1,246 @@ +#[cfg(test)] +mod integration_tests { + use crate::print::{ + PrintOptions, Printer, PrinterCapabilities, PrinterDevModeStore, PrinterPaperSize, + PrinterTray, + }; + use crate::print::unix::{build_lp_args, parse_lpstat_d, parse_lpstat_e, parse_lpstat_p, print_target}; + + #[test] + fn test_printer_model_json_serialization() { + let printer = Printer { + name: "Epson-Stylus-Pro-4900".to_string(), + status: "Idle".to_string(), + is_default: true, + }; + + let json = serde_json::to_string(&printer).expect("Failed to serialize printer"); + assert!(json.contains(r#""name":"Epson-Stylus-Pro-4900""#)); + assert!(json.contains(r#""status":"Idle""#)); + assert!(json.contains(r#""is_default":true"#)); + + let deserialized: Printer = serde_json::from_str(&json).expect("Failed to deserialize printer"); + assert_eq!(deserialized, printer); + } + + #[test] + fn test_printer_capabilities_serialization() { + let caps = PrinterCapabilities { + trays: vec![ + PrinterTray { + id: 1, + name: "Auto Sheet Feeder".to_string(), + }, + PrinterTray { + id: 2, + name: "Rear Manual Feed".to_string(), + }, + ], + paper_sizes: vec![ + PrinterPaperSize { + id: 9, + name: "A4 (210 x 297 mm)".to_string(), + }, + PrinterPaperSize { + id: 1, + name: "Letter (8.5 x 11 in)".to_string(), + }, + ], + supports_orientation: true, + }; + + let json = serde_json::to_string(&caps).expect("Failed to serialize capabilities"); + assert!(json.contains("Auto Sheet Feeder")); + assert!(json.contains("Rear Manual Feed")); + assert!(json.contains("A4 (210 x 297 mm)")); + + let deserialized: PrinterCapabilities = + serde_json::from_str(&json).expect("Failed to deserialize capabilities"); + assert_eq!(deserialized, caps); + } + + #[test] + fn test_print_options_serialization() { + let opts = PrintOptions { + paper_source: Some(2), + orientation: Some("landscape".to_string()), + paper_size: Some("A4".to_string()), + ppd_uncorrected_passthrough: Some(false), + }; + + let json = serde_json::to_string(&opts).expect("Failed to serialize PrintOptions"); + let deserialized: PrintOptions = + serde_json::from_str(&json).expect("Failed to deserialize PrintOptions"); + assert_eq!(deserialized.paper_source, Some(2)); + assert_eq!(deserialized.orientation.as_deref(), Some("landscape")); + assert_eq!(deserialized.paper_size.as_deref(), Some("A4")); + } + + #[test] + fn test_devmode_store_concurrency() { + let store = PrinterDevModeStore::new(); + assert!(store.get("Epson-P900").is_none()); + + let fake_devmode = vec![1, 2, 3, 4, 5]; + store.set("Epson-P900", fake_devmode.clone()); + assert_eq!(store.get("Epson-P900"), Some(fake_devmode)); + } + + #[test] + fn test_auto_scaler_proportional_fit_math() { + // Page printable area: 4800 x 6800 device units + let page_w = 4800f64; + let page_h = 6800f64; + + // Image: 2400 x 3000 (aspect ratio 0.8) + let img_w = 2400f64; + let img_h = 3000f64; + + let scale_x = page_w / img_w; // 2.0 + let scale_y = page_h / img_h; // 2.2666 + let scale = scale_x.min(scale_y); // 2.0 + + let dest_w = (img_w * scale).floor() as i32; + let dest_h = (img_h * scale).floor() as i32; + + assert_eq!(dest_w, 4800); + assert_eq!(dest_h, 6000); + assert!(dest_w <= page_w as i32); + assert!(dest_h <= page_h as i32); + + let dest_x = (page_w as i32 - dest_w) / 2; + let dest_y = (page_h as i32 - dest_h) / 2; + + assert_eq!(dest_x, 0); + assert_eq!(dest_y, 400); // Vertically centered + } + + #[test] + fn test_lpstat_e_multiline_and_whitespace_handling() { + let sample = " \n Epson_SureColor_P900 \n\n Canon_imagePROGRAF_PRO_1000 \n\n"; + let parsed = parse_lpstat_e(sample); + assert_eq!(parsed, vec!["Epson_SureColor_P900", "Canon_imagePROGRAF_PRO_1000"]); + } + + #[test] + fn test_lpstat_p_various_printer_states() { + let sample = "\ +printer Canon_PRO-1000 is idle. enabled since Fri 12 Jun 2026\n\ +printer Epson_P900 is printing. enabled since Sat 13 Jun 2026\n\ +printer HP_Z9 disabled since Sun 14 Jun 2026 - offline\n\ +printer DNP_DS620 stopped since Mon 15 Jun 2026 - out of ribbon\n\ +printer Custom_Queue unknown state\n\ +"; + let parsed = parse_lpstat_p(sample); + assert_eq!( + parsed, + vec![ + ("Canon_PRO-1000".to_string(), "Idle".to_string()), + ("Epson_P900".to_string(), "Printing".to_string()), + ("HP_Z9".to_string(), "Stopped".to_string()), + ("DNP_DS620".to_string(), "Stopped".to_string()), + ("Custom_Queue".to_string(), "Unknown".to_string()), + ] + ); + } + + #[test] + fn test_lpstat_d_with_trailing_spaces_and_tabs() { + let sample = "system default destination: Canon_PRO-1000\t \n"; + assert_eq!(parse_lpstat_d(sample), Some("Canon_PRO-1000".to_string())); + } + + #[test] + fn test_multi_page_batch_spool_arguments() { + let pages = vec!["target_01.tif", "target_02.tif", "target_03.tif"]; + let printer = "Epson_Stylus_SX420W"; + + for (_idx, page) in pages.iter().enumerate() { + let path = format!("/tmp/profiling_run/{}", page); + let args_raw = build_lp_args(printer, &path, None); + assert_eq!(args_raw[0], "-d"); + assert_eq!(args_raw[1], printer); + assert_eq!(args_raw[2], "-t"); + assert_eq!(args_raw[3], format!("ICCery Target - {}", page)); + assert_eq!(args_raw[4], "-o"); + assert_eq!(args_raw[5], "raw"); + assert_eq!(args_raw[6], path); + + let opts = PrintOptions { + ppd_uncorrected_passthrough: Some(true), + ..Default::default() + }; + let args_fallback = build_lp_args(printer, &path, Some(&opts)); + assert_eq!(args_fallback[4], "-o"); + assert_eq!(args_fallback[5], "ColorModel=Gray"); + assert_eq!(args_fallback[6], "-o"); + assert_eq!(args_fallback[7], "cm-calibration"); + assert_eq!(args_fallback[8], path); + } + } + + #[test] + fn test_print_target_nonexistent_file_handling() { + let res = print_target("Test_Printer", "/non/existent/path/target_page_99.tif", None); + assert!(res.is_err()); + let err = res.unwrap_err(); + assert!(err.contains("Target TIFF file not found")); + } + + #[cfg(windows)] + #[test] + fn test_windows_devmode_preservation_and_overrides() { + use windows::Win32::Graphics::Gdi::DEVMODEW; + use crate::print::windows::apply_print_options_to_devmode; + + let devmode_size = std::mem::size_of::() + 128; // Public DEVMODE + 128 bytes private OEM extra + let mut buffer = vec![0u8; devmode_size]; + + // Simulate OEM private driver payload (e.g. Epson Print Preview flag in private offset) + buffer[std::mem::size_of::() + 10] = 0xAA; + buffer[std::mem::size_of::() + 11] = 0x55; + + let p_devmode = buffer.as_mut_ptr() as *mut DEVMODEW; + unsafe { + (*p_devmode).dmSize = std::mem::size_of::() as u16; + (*p_devmode).dmDriverExtra = 128; + } + + // Store into PrinterDevModeStore + let store = PrinterDevModeStore::new(); + store.set("EPSON-Test-Printer", buffer.clone()); + + let retrieved = store.get("EPSON-Test-Printer").expect("Failed to get cached DEVMODE"); + assert_eq!(retrieved.len(), devmode_size); + // Verify private OEM payload is preserved + assert_eq!(retrieved[std::mem::size_of::() + 10], 0xAA); + assert_eq!(retrieved[std::mem::size_of::() + 11], 0x55); + + // Apply UI overrides + let opts = PrintOptions { + paper_source: Some(3), + orientation: Some("landscape".to_string()), + paper_size: None, + ppd_uncorrected_passthrough: None, + }; + + let mut work_buf = retrieved; + let p_work_devmode = work_buf.as_mut_ptr() as *mut DEVMODEW; + apply_print_options_to_devmode(p_work_devmode, Some(&opts)); + + unsafe { + // Verify ICM bypass is applied + assert_eq!((*p_work_devmode).dmICMMethod, 1); // DMICMMETHOD_NONE = 1 + // Verify tray override + assert_eq!((*p_work_devmode).Anonymous1.Anonymous1.dmDefaultSource, 3); + // Verify orientation override + assert_eq!((*p_work_devmode).Anonymous1.Anonymous1.dmOrientation, 2); // DMORIENT_LANDSCAPE = 2 + } + + // Verify private OEM payload was NOT corrupted or overwritten + assert_eq!(work_buf[std::mem::size_of::() + 10], 0xAA); + assert_eq!(work_buf[std::mem::size_of::() + 11], 0x55); + } +} + + diff --git a/src-tauri/src/print/unix.rs b/src-tauri/src/print/unix.rs new file mode 100644 index 0000000..b13edd8 --- /dev/null +++ b/src-tauri/src/print/unix.rs @@ -0,0 +1,471 @@ +#![allow(dead_code)] + +use std::path::Path; +use crate::print::{ + PrintOptions, Printer, PrinterCapabilities, PrinterPaperSize, PrinterTray, +}; + +/// Parse `lpstat -e` output into a list of printer destination names. +pub fn parse_lpstat_e(output: &str) -> Vec { + output + .lines() + .map(|l| l.trim().to_string()) + .filter(|l| !l.is_empty()) + .collect() +} + +/// Parse `lpstat -d` output to determine the default system printer destination. +pub fn parse_lpstat_d(output: &str) -> Option { + for line in output.lines() { + let trimmed = line.trim(); + if let Some(rest) = trimmed.strip_prefix("system default destination:") { + let dest = rest.trim(); + if !dest.is_empty() { + return Some(dest.to_string()); + } + } + } + None +} + +/// Parse `lpstat -p` output to extract printer names and status indicators. +pub fn parse_lpstat_p(output: &str) -> Vec<(String, String)> { + let mut results = Vec::new(); + for line in output.lines() { + let trimmed = line.trim(); + if let Some(rest) = trimmed.strip_prefix("printer ") { + let mut parts = rest.split_whitespace(); + if let Some(name) = parts.next() { + let name = name.to_string(); + let lower = rest.to_lowercase(); + let status = if lower.contains("is idle") || lower.contains("idle") { + "Idle".to_string() + } else if lower.contains("is printing") || lower.contains("now printing") || lower.contains("printing") { + "Printing".to_string() + } else if lower.contains("disabled") || lower.contains("stopped") { + "Stopped".to_string() + } else { + "Unknown".to_string() + }; + results.push((name, status)); + } + } + } + results +} + +/// Merge printer lists from `lpstat -e`, `lpstat -p`, and `lpstat -d`. +pub fn merge_printer_info( + destinations: &[String], + statuses: &[(String, String)], + default_dest: Option<&str>, +) -> Vec { + use std::collections::HashMap; + + let status_map: HashMap<&str, &str> = statuses + .iter() + .map(|(name, status)| (name.as_str(), status.as_str())) + .collect(); + + let mut seen = std::collections::HashSet::new(); + let mut result = Vec::new(); + + for name in destinations { + if seen.insert(name.clone()) { + let status = status_map.get(name.as_str()).copied().unwrap_or("Idle"); + let is_default = default_dest.map_or(false, |d| d == name); + result.push(Printer { + name: name.clone(), + status: status.to_string(), + is_default, + }); + } + } + + for (name, status) in statuses { + if seen.insert(name.clone()) { + let is_default = default_dest.map_or(false, |d| d == name); + result.push(Printer { + name: name.clone(), + status: status.clone(), + is_default, + }); + } + } + + result +} + +/// Query available CUPS printers, connection statuses, and default destination. +pub fn get_printers() -> Result, String> { + let e_output = std::process::Command::new("lpstat") + .arg("-e") + .output(); + let destinations = match e_output { + Ok(out) if out.status.success() => { + parse_lpstat_e(&String::from_utf8_lossy(&out.stdout)) + } + _ => Vec::new(), + }; + + let p_output = std::process::Command::new("lpstat") + .arg("-p") + .output(); + let statuses = match p_output { + Ok(out) => { + parse_lpstat_p(&String::from_utf8_lossy(&out.stdout)) + } + Err(e) => { + if destinations.is_empty() { + return Err(format!("Failed to execute 'lpstat': {}", e)); + } + Vec::new() + } + }; + + let d_output = std::process::Command::new("lpstat") + .arg("-d") + .output(); + let default_dest = match d_output { + Ok(out) => parse_lpstat_d(&String::from_utf8_lossy(&out.stdout)), + _ => None, + }; + + Ok(merge_printer_info(&destinations, &statuses, default_dest.as_deref())) +} + +/// Parse `lpoptions -p -l` output into trays and paper sizes. +pub fn parse_lpoptions_l(output: &str) -> (Vec, Vec) { + let mut trays = Vec::new(); + let mut paper_sizes = Vec::new(); + + for line in output.lines() { + let trimmed = line.trim(); + if trimmed.is_empty() { + continue; + } + + if let Some((key_part, values_part)) = trimmed.split_once(':') { + let key_name = key_part.split('/').next().unwrap_or("").trim(); + let values = values_part.split_whitespace(); + + if key_name.eq_ignore_ascii_case("InputSlot") || key_name.eq_ignore_ascii_case("MediaSource") { + for (idx, val) in values.enumerate() { + let clean_val = val.trim_start_matches('*'); + trays.push(PrinterTray { + id: (idx + 1) as u16, + name: clean_val.to_string(), + }); + } + } else if key_name.eq_ignore_ascii_case("PageSize") || key_name.eq_ignore_ascii_case("MediaSize") { + for (idx, val) in values.enumerate() { + let clean_val = val.trim_start_matches('*'); + paper_sizes.push(PrinterPaperSize { + id: (idx + 1) as u16, + name: clean_val.to_string(), + }); + } + } + } + } + + (trays, paper_sizes) +} + +/// Query CUPS printer capabilities via `lpoptions -p -l`. +pub fn get_printer_capabilities(printer_name: &str) -> Result { + let output = std::process::Command::new("lpoptions") + .args(["-p", printer_name, "-l"]) + .output(); + + let (trays, paper_sizes) = match output { + Ok(out) if out.status.success() => { + parse_lpoptions_l(&String::from_utf8_lossy(&out.stdout)) + } + _ => (Vec::new(), Vec::new()), + }; + + Ok(PrinterCapabilities { + trays, + paper_sizes, + supports_orientation: true, + }) +} + +/// Construct `lp` command line arguments for target printing with options. +pub fn build_lp_args( + printer_name: &str, + tiff_path: &str, + options: Option<&PrintOptions>, +) -> Vec { + let path = Path::new(tiff_path); + let title = format!( + "ICCery Target - {}", + path.file_name().and_then(|n| n.to_str()).unwrap_or("Profiling Target") + ); + + let mut args = vec![ + "-d".to_string(), + printer_name.to_string(), + "-t".to_string(), + title, + ]; + + let ppd_fallback = options + .and_then(|o| o.ppd_uncorrected_passthrough) + .unwrap_or(false); + + if ppd_fallback { + args.push("-o".to_string()); + args.push("ColorModel=Gray".to_string()); + args.push("-o".to_string()); + args.push("cm-calibration".to_string()); + } else { + args.push("-o".to_string()); + args.push("raw".to_string()); + } + + if let Some(opts) = options { + if let Some(ref orient) = opts.orientation { + args.push("-o".to_string()); + if orient.eq_ignore_ascii_case("landscape") { + args.push("orientation-requested=4".to_string()); + } else { + args.push("orientation-requested=3".to_string()); + } + } + + if let Some(ref page_size) = opts.paper_size { + if !page_size.trim().is_empty() { + args.push("-o".to_string()); + args.push(format!("PageSize={}", page_size.trim())); + } + } + } + + args.push(tiff_path.to_string()); + args +} + +/// Spool target TIFF to CUPS printer bypassing ColorSync and PPD color management. +pub fn print_target( + printer_name: &str, + tiff_path: &str, + options: Option<&PrintOptions>, +) -> Result<(), String> { + let path = Path::new(tiff_path); + if !path.exists() { + return Err(format!("Target TIFF file not found: {}", tiff_path)); + } + + let args = build_lp_args(printer_name, tiff_path, options); + + let output = std::process::Command::new("lp") + .args(&args) + .output() + .map_err(|e| format!("Failed to execute 'lp' command: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + let msg = if !stderr.trim().is_empty() { + stderr.trim().to_string() + } else if !stdout.trim().is_empty() { + stdout.trim().to_string() + } else { + format!("exited with status code {}", output.status.code().unwrap_or(-1)) + }; + return Err(format!("CUPS print job failed: {}", msg)); + } + + Ok(()) +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_lpstat_e() { + let sample = "Epson-Stylus-SX420W\nHP_Officejet_Pro_8600\n\n"; + let parsed = parse_lpstat_e(sample); + assert_eq!(parsed, vec!["Epson-Stylus-SX420W", "HP_Officejet_Pro_8600"]); + } + + #[test] + fn test_parse_lpstat_d() { + let sample_valid = "system default destination: Epson-Stylus-SX420W\n"; + assert_eq!( + parse_lpstat_d(sample_valid), + Some("Epson-Stylus-SX420W".to_string()) + ); + + let sample_none = "no system default destination\n"; + assert_eq!(parse_lpstat_d(sample_none), None); + + let sample_empty = ""; + assert_eq!(parse_lpstat_d(sample_empty), None); + } + + #[test] + fn test_parse_lpstat_p() { + let sample = "\ +printer Epson-Stylus-SX420W is idle. enabled since Thu 28 May 2026 12:52:43 BST\n\ +printer HP_LaserJet now printing HP_LaserJet-42. enabled since ...\n\ +printer Zebra_Label disabled since Wed 10 Jun 2026 - reason: out of ribbon\n\ +"; + let parsed = parse_lpstat_p(sample); + assert_eq!( + parsed, + vec![ + ("Epson-Stylus-SX420W".to_string(), "Idle".to_string()), + ("HP_LaserJet".to_string(), "Printing".to_string()), + ("Zebra_Label".to_string(), "Stopped".to_string()), + ] + ); + } + + #[test] + fn test_merge_printer_info() { + let destinations = vec![ + "Epson-Stylus-SX420W".to_string(), + "HP_LaserJet".to_string(), + "Virtual_PDF".to_string(), + ]; + let statuses = vec![ + ("Epson-Stylus-SX420W".to_string(), "Idle".to_string()), + ("HP_LaserJet".to_string(), "Printing".to_string()), + ("Zebra_Label".to_string(), "Stopped".to_string()), + ]; + let default_dest = Some("Epson-Stylus-SX420W"); + + let merged = merge_printer_info(&destinations, &statuses, default_dest); + assert_eq!(merged.len(), 4); + + assert_eq!( + merged[0], + Printer { + name: "Epson-Stylus-SX420W".to_string(), + status: "Idle".to_string(), + is_default: true, + } + ); + assert_eq!( + merged[1], + Printer { + name: "HP_LaserJet".to_string(), + status: "Printing".to_string(), + is_default: false, + } + ); + assert_eq!( + merged[2], + Printer { + name: "Virtual_PDF".to_string(), + status: "Idle".to_string(), + is_default: false, + } + ); + assert_eq!( + merged[3], + Printer { + name: "Zebra_Label".to_string(), + status: "Stopped".to_string(), + is_default: false, + } + ); + } + + #[test] + fn test_parse_lpoptions_l() { + let sample = "\ +PageSize/Media Size: *A4 Letter Legal A3\n\ +InputSlot/Media Source: *Auto Upper Lower Rear Manual\n\ +Duplex/2-Sided Printing: *None DuplexNoTumble DuplexTumble\n\ +"; + let (trays, paper_sizes) = parse_lpoptions_l(sample); + assert_eq!(trays.len(), 5); + assert_eq!(trays[0].name, "Auto"); + assert_eq!(trays[1].name, "Upper"); + assert_eq!(trays[4].name, "Manual"); + + assert_eq!(paper_sizes.len(), 4); + assert_eq!(paper_sizes[0].name, "A4"); + assert_eq!(paper_sizes[1].name, "Letter"); + } + + #[test] + fn test_build_lp_args_raw() { + let args = build_lp_args("Epson-Stylus-SX420W", "/tmp/target.tif", None); + assert_eq!( + args, + vec![ + "-d", + "Epson-Stylus-SX420W", + "-t", + "ICCery Target - target.tif", + "-o", + "raw", + "/tmp/target.tif" + ] + ); + } + + #[test] + fn test_build_lp_args_ppd_fallback() { + let opts = PrintOptions { + ppd_uncorrected_passthrough: Some(true), + ..Default::default() + }; + let args = build_lp_args("Epson-Stylus-SX420W", "/tmp/target.tif", Some(&opts)); + assert_eq!( + args, + vec![ + "-d", + "Epson-Stylus-SX420W", + "-t", + "ICCery Target - target.tif", + "-o", + "ColorModel=Gray", + "-o", + "cm-calibration", + "/tmp/target.tif" + ] + ); + } + + #[test] + fn test_build_lp_args_with_orientation_and_size() { + let opts = PrintOptions { + orientation: Some("landscape".to_string()), + paper_size: Some("A4".to_string()), + ppd_uncorrected_passthrough: Some(false), + ..Default::default() + }; + let args = build_lp_args("Epson-Stylus-SX420W", "/tmp/target.tif", Some(&opts)); + assert_eq!( + args, + vec![ + "-d", + "Epson-Stylus-SX420W", + "-t", + "ICCery Target - target.tif", + "-o", + "raw", + "-o", + "orientation-requested=4", + "-o", + "PageSize=A4", + "/tmp/target.tif" + ] + ); + } + + #[test] + fn test_print_target_file_not_found() { + let res = print_target("Epson-Stylus-SX420W", "/non/existent/target.tif", None); + assert!(res.is_err()); + assert!(res.unwrap_err().contains("not found")); + } +} diff --git a/src-tauri/src/print/windows.rs b/src-tauri/src/print/windows.rs new file mode 100644 index 0000000..d5e6866 --- /dev/null +++ b/src-tauri/src/print/windows.rs @@ -0,0 +1,581 @@ +#![allow(non_snake_case, dead_code)] + +use std::ffi::{c_void, OsStr}; +use std::os::windows::ffi::OsStrExt; +use std::path::Path; +use windows::core::PCWSTR; +use windows::Win32::Graphics::Gdi::{ + CreateDCW, DeleteDC, GetDeviceCaps, StretchDIBits, + BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DEVMODEW, DEVMODE_FIELD_FLAGS, + DIB_RGB_COLORS, HDC, HORZRES, LOGPIXELSX, LOGPIXELSY, RGBQUAD, SRCCOPY, VERTRES, +}; +use windows::Win32::Graphics::Printing::{ + ClosePrinter, DocumentPropertiesW, EnumPrintersW, OpenPrinterW, + PRINTER_ENUM_CONNECTIONS, PRINTER_ENUM_LOCAL, PRINTER_INFO_1W, PRINTER_INFO_4W, + PRINTER_HANDLE, +}; +use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow; + +use crate::print::{ + PrintOptions, Printer, PrinterCapabilities, PrinterDevModeStore, PrinterPaperSize, PrinterTray, +}; + +#[repr(C)] +struct DOCINFOW { + cbSize: i32, + lpszDocName: PCWSTR, + lpszOutput: PCWSTR, + lpszDatatype: PCWSTR, + fwType: u32, +} + +extern "system" { + fn SetICMMode(hdc: HDC, mode: i32) -> i32; + fn StartDocW(hdc: HDC, lpdi: *const DOCINFOW) -> i32; + fn StartPage(hdc: HDC) -> i32; + fn EndPage(hdc: HDC) -> i32; + fn EndDoc(hdc: HDC) -> i32; + fn DeviceCapabilitiesW( + pdevicename: PCWSTR, + pport: PCWSTR, + fwcapability: u16, + poutput: *mut u16, + pdevmode: *const DEVMODEW, + ) -> i32; +} + +const ICM_OFF: i32 = 1; +const DM_IN_PROMPT: u32 = 4; +const DM_IN_BUFFER: u32 = 8; +const DM_OUT_BUFFER: u32 = 2; +const DM_ORIENTATION: u32 = 0x00000001; +const DM_DEFAULTSOURCE: u32 = 0x00000200; +const DM_ICMMETHOD: u32 = 0x00800000; +const DMICMMETHOD_NONE: u32 = 1; +const DMORIENT_PORTRAIT: i16 = 1; +const DMORIENT_LANDSCAPE: i16 = 2; + +const DC_PAPERS: u16 = 2; +const DC_BINS: u16 = 6; +const DC_BINNAMES: u16 = 12; +const DC_PAPERNAMES: u16 = 16; +const IDOK: i32 = 1; + +fn to_wide(s: &str) -> Vec { + OsStr::new(s).encode_wide().chain(std::iter::once(0)).collect() +} + +fn extract_null_terminated_string(wide_slice: &[u16]) -> String { + let len = wide_slice.iter().position(|&c| c == 0).unwrap_or(wide_slice.len()); + String::from_utf16_lossy(&wide_slice[..len]).trim().to_string() +} + +/// Enumerate available printers on the system +pub fn get_printers() -> Result, String> { + unsafe { + let flags = PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS; + let mut bytes_needed = 0u32; + let mut count = 0u32; + + let _ = EnumPrintersW( + flags, + PCWSTR::null(), + 4, + None, + &mut bytes_needed, + &mut count, + ); + + if bytes_needed == 0 { + let _ = EnumPrintersW( + flags, + PCWSTR::null(), + 1, + None, + &mut bytes_needed, + &mut count, + ); + + if bytes_needed == 0 { + return Ok(Vec::new()); + } + + let mut buffer = vec![0u8; bytes_needed as usize]; + let res = EnumPrintersW( + flags, + PCWSTR::null(), + 1, + Some(&mut buffer), + &mut bytes_needed, + &mut count, + ); + + if res.is_err() { + return Err("Failed to enumerate printers (Level 1)".to_string()); + } + + let p_info = buffer.as_ptr() as *const PRINTER_INFO_1W; + let mut printers = Vec::new(); + for i in 0..count as usize { + let info = *p_info.add(i); + if !info.pName.is_null() { + let name = info.pName.to_string().map_err(|e| e.to_string())?; + printers.push(Printer { + name, + status: "Ready".to_string(), + is_default: false, + }); + } + } + return Ok(printers); + } + + let mut buffer = vec![0u8; bytes_needed as usize]; + let res = EnumPrintersW( + flags, + PCWSTR::null(), + 4, + Some(&mut buffer), + &mut bytes_needed, + &mut count, + ); + + if res.is_err() { + return Err("Failed to enumerate printers (Level 4)".to_string()); + } + + let p_info = buffer.as_ptr() as *const PRINTER_INFO_4W; + let mut printers = Vec::new(); + for i in 0..count as usize { + let info = *p_info.add(i); + if !info.pPrinterName.is_null() { + let name = info.pPrinterName.to_string().map_err(|e| e.to_string())?; + printers.push(Printer { + name, + status: "Ready".to_string(), + is_default: false, + }); + } + } + Ok(printers) + } +} + +/// Query hardware capabilities (paper trays and paper sizes) for a given printer +pub fn get_printer_capabilities(printer_name: &str) -> Result { + let printer_wide = to_wide(printer_name); + + unsafe { + // Query trays (bins) + let num_bins = DeviceCapabilitiesW( + PCWSTR(printer_wide.as_ptr()), + PCWSTR::null(), + DC_BINS, + std::ptr::null_mut(), + std::ptr::null(), + ); + + let mut trays = Vec::new(); + if num_bins > 0 { + let mut bin_ids = vec![0u16; num_bins as usize]; + let mut bin_names_raw = vec![0u16; num_bins as usize * 24]; + + let res_ids = DeviceCapabilitiesW( + PCWSTR(printer_wide.as_ptr()), + PCWSTR::null(), + DC_BINS, + bin_ids.as_mut_ptr(), + std::ptr::null(), + ); + + let res_names = DeviceCapabilitiesW( + PCWSTR(printer_wide.as_ptr()), + PCWSTR::null(), + DC_BINNAMES, + bin_names_raw.as_mut_ptr(), + std::ptr::null(), + ); + + if res_ids > 0 && res_names > 0 { + for i in 0..num_bins as usize { + let id = bin_ids[i]; + let name_slice = &bin_names_raw[i * 24..(i + 1) * 24]; + let name = extract_null_terminated_string(name_slice); + let display_name = if name.is_empty() { + format!("Tray {}", id) + } else { + name + }; + trays.push(PrinterTray { + id, + name: display_name, + }); + } + } + } + + // Query paper sizes + let num_papers = DeviceCapabilitiesW( + PCWSTR(printer_wide.as_ptr()), + PCWSTR::null(), + DC_PAPERS, + std::ptr::null_mut(), + std::ptr::null(), + ); + + let mut paper_sizes = Vec::new(); + if num_papers > 0 { + let mut paper_ids = vec![0u16; num_papers as usize]; + let mut paper_names_raw = vec![0u16; num_papers as usize * 64]; + + let res_ids = DeviceCapabilitiesW( + PCWSTR(printer_wide.as_ptr()), + PCWSTR::null(), + DC_PAPERS, + paper_ids.as_mut_ptr(), + std::ptr::null(), + ); + + let res_names = DeviceCapabilitiesW( + PCWSTR(printer_wide.as_ptr()), + PCWSTR::null(), + DC_PAPERNAMES, + paper_names_raw.as_mut_ptr(), + std::ptr::null(), + ); + + if res_ids > 0 && res_names > 0 { + for i in 0..num_papers as usize { + let id = paper_ids[i]; + let name_slice = &paper_names_raw[i * 64..(i + 1) * 64]; + let name = extract_null_terminated_string(name_slice); + let display_name = if name.is_empty() { + format!("Paper Size {}", id) + } else { + name + }; + paper_sizes.push(PrinterPaperSize { + id, + name: display_name, + }); + } + } + } + + Ok(PrinterCapabilities { + trays, + paper_sizes, + supports_orientation: true, + }) + } +} + +/// Open the native modal Printer Properties / Preferences dialog and retain DEVMODE changes +pub fn show_printer_properties( + printer_name: &str, + devmode_store: &PrinterDevModeStore, +) -> Result<(), String> { + let printer_wide = to_wide(printer_name); + + unsafe { + let mut h_printer = PRINTER_HANDLE::default(); + let open_res = OpenPrinterW(PCWSTR(printer_wide.as_ptr()), &mut h_printer, None); + if open_res.is_err() || h_printer.Value.is_null() { + return Err(format!("Failed to open printer '{}'", printer_name)); + } + + let parent_hwnd = GetForegroundWindow(); + + let devmode_size = DocumentPropertiesW( + Some(parent_hwnd), + h_printer, + PCWSTR(printer_wide.as_ptr()), + None, + None, + 0, + ); + + if devmode_size <= 0 { + let _ = ClosePrinter(h_printer); + return Err("Failed to query DEVMODE size for printer properties".to_string()); + } + + let mut in_buf = devmode_store + .get(printer_name) + .unwrap_or_else(|| vec![0u8; devmode_size as usize]); + + if in_buf.len() < devmode_size as usize { + in_buf.resize(devmode_size as usize, 0); + } + + // Populate initial DEVMODE if empty + if in_buf.iter().all(|&b| b == 0) { + let _ = DocumentPropertiesW( + Some(parent_hwnd), + h_printer, + PCWSTR(printer_wide.as_ptr()), + Some(in_buf.as_mut_ptr() as *mut DEVMODEW), + None, + DM_OUT_BUFFER, + ); + } + + let mut out_buf = vec![0u8; devmode_size as usize]; + + let res = DocumentPropertiesW( + Some(parent_hwnd), + h_printer, + PCWSTR(printer_wide.as_ptr()), + Some(out_buf.as_mut_ptr() as *mut DEVMODEW), + Some(in_buf.as_ptr() as *const DEVMODEW), + DM_IN_PROMPT | DM_IN_BUFFER | DM_OUT_BUFFER, + ); + + let _ = ClosePrinter(h_printer); + + if res == IDOK { + devmode_store.set(printer_name, out_buf); + Ok(()) + } else if res == 2 { + // Cancelled by user + Ok(()) + } else { + Err("Printer properties dialog was dismissed or encountered an error".to_string()) + } + } +} + +/// Apply ICM bypass and user options onto an active DEVMODE structure +pub fn apply_print_options_to_devmode( + p_devmode: *mut DEVMODEW, + options: Option<&PrintOptions>, +) { + unsafe { + // Apply strict ICM bypass + (*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_ICMMETHOD); + (*p_devmode).dmICMMethod = DMICMMETHOD_NONE; + + // Apply UI options if specified + if let Some(opts) = options { + if let Some(tray_id) = opts.paper_source { + (*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_DEFAULTSOURCE); + (*p_devmode).Anonymous1.Anonymous1.dmDefaultSource = tray_id as i16; + } + + if let Some(ref orient) = opts.orientation { + (*p_devmode).dmFields |= DEVMODE_FIELD_FLAGS(DM_ORIENTATION); + if orient.eq_ignore_ascii_case("landscape") { + (*p_devmode).Anonymous1.Anonymous1.dmOrientation = DMORIENT_LANDSCAPE; + } else { + (*p_devmode).Anonymous1.Anonymous1.dmOrientation = DMORIENT_PORTRAIT; + } + } + } + } +} + +/// Print target TIFF file to printer with GDI ICM bypass, DEVMODE overrides, and auto-fit scaling +pub fn print_target( + printer_name: &str, + tiff_path: &str, + options: Option<&PrintOptions>, + devmode_store: Option<&PrinterDevModeStore>, +) -> Result<(), String> { + let path = Path::new(tiff_path); + if !path.exists() { + return Err(format!("Target TIFF file not found: {}", tiff_path)); + } + + // Load and convert the TIFF image + let dyn_img = image::open(path).map_err(|e| format!("Failed to open TIFF image: {}", e))?; + let rgb_img = dyn_img.to_rgb8(); + let img_width = rgb_img.width(); + let img_height = rgb_img.height(); + + if img_width == 0 || img_height == 0 { + return Err("Image has zero dimensions".to_string()); + } + + // Prepare 24-bit BGR DIB buffer with 4-byte row stride alignment + let row_stride = ((img_width as usize * 3 + 3) / 4) * 4; + let mut dib_bytes = vec![0u8; row_stride * img_height as usize]; + + for y in 0..img_height as usize { + for x in 0..img_width as usize { + let pixel = rgb_img.get_pixel(x as u32, y as u32); + let dst_idx = y * row_stride + x * 3; + dib_bytes[dst_idx] = pixel[2]; // Blue + dib_bytes[dst_idx + 1] = pixel[1]; // Green + dib_bytes[dst_idx + 2] = pixel[0]; // Red + } + } + + let printer_wide = to_wide(printer_name); + let doc_name = format!( + "ICCery Target - {}", + path.file_name().and_then(|n| n.to_str()).unwrap_or("Profiling Target") + ); + let doc_name_wide = to_wide(&doc_name); + + unsafe { + // Open printer handle + let mut h_printer = PRINTER_HANDLE::default(); + let open_res = OpenPrinterW(PCWSTR(printer_wide.as_ptr()), &mut h_printer, None); + if open_res.is_err() || h_printer.Value.is_null() { + return Err(format!("Failed to open printer '{}'", printer_name)); + } + + // Configure DEVMODE + let devmode_size = DocumentPropertiesW( + None, + h_printer, + PCWSTR(printer_wide.as_ptr()), + None, + None, + 0, + ); + + let mut devmode_buf = if devmode_size > 0 { + let mut buf = if let Some(cached) = devmode_store.and_then(|s| s.get(printer_name)) { + let mut b = cached; + if b.len() < devmode_size as usize { + b.resize(devmode_size as usize, 0); + } + b + } else { + let mut b = vec![0u8; devmode_size as usize]; + let p_devmode = b.as_mut_ptr() as *mut DEVMODEW; + let _ = DocumentPropertiesW( + None, + h_printer, + PCWSTR(printer_wide.as_ptr()), + Some(p_devmode), + None, + DM_OUT_BUFFER, + ); + b + }; + + let p_devmode = buf.as_mut_ptr() as *mut DEVMODEW; + apply_print_options_to_devmode(p_devmode, options); + + Some(buf) + } else { + None + }; + + let p_devmode_ptr = devmode_buf + .as_mut() + .map(|b| b.as_mut_ptr() as *const DEVMODEW); + + // Create printer Device Context (DC) + let hdc = CreateDCW( + PCWSTR::null(), + PCWSTR(printer_wide.as_ptr()), + PCWSTR::null(), + p_devmode_ptr, + ); + + if hdc.is_invalid() { + let _ = ClosePrinter(h_printer); + return Err(format!("Failed to create Device Context for printer '{}'", printer_name)); + } + + // STRICT ICM BYPASS: Turn off Windows GDI Image Color Management + let _ = SetICMMode(hdc, ICM_OFF); + + // Start Document + let doc_info = DOCINFOW { + cbSize: std::mem::size_of::() as i32, + lpszDocName: PCWSTR(doc_name_wide.as_ptr()), + lpszOutput: PCWSTR::null(), + lpszDatatype: PCWSTR::null(), + fwType: 0, + }; + + let start_doc_res = StartDocW(hdc, &doc_info); + if start_doc_res <= 0 { + let _ = DeleteDC(hdc); + let _ = ClosePrinter(h_printer); + return Err("Failed to start print document (StartDocW)".to_string()); + } + + // Start Page + let start_page_res = StartPage(hdc); + if start_page_res <= 0 { + let _ = EndDoc(hdc); + let _ = DeleteDC(hdc); + let _ = ClosePrinter(h_printer); + return Err("Failed to start print page (StartPage)".to_string()); + } + + // Query printable device dimensions + let dpi_x = GetDeviceCaps(Some(hdc), LOGPIXELSX); + let dpi_y = GetDeviceCaps(Some(hdc), LOGPIXELSY); + let page_width = GetDeviceCaps(Some(hdc), HORZRES); + let page_height = GetDeviceCaps(Some(hdc), VERTRES); + + // AUTOMATIC BEHIND-THE-SCENES PAGE-FIT SCALER: + // Proportionally scale the target TIFF to fill the available physical printable area + let scale_x = page_width as f64 / img_width as f64; + let scale_y = page_height as f64 / img_height as f64; + let scale = scale_x.min(scale_y); + + let dest_width = (img_width as f64 * scale).floor() as i32; + let dest_height = (img_height as f64 * scale).floor() as i32; + + let dest_x = (page_width - dest_width).max(0) / 2; + let dest_y = (page_height - dest_height).max(0) / 2; + + // Build BITMAPINFO structure for top-down 24-bit DIB + let bmi = BITMAPINFO { + bmiHeader: BITMAPINFOHEADER { + biSize: std::mem::size_of::() as u32, + biWidth: img_width as i32, + biHeight: -(img_height as i32), // Negative height denotes top-down DIB + biPlanes: 1, + biBitCount: 24, + biCompression: BI_RGB.0, + biSizeImage: dib_bytes.len() as u32, + biXPelsPerMeter: ((dpi_x as f64 / 0.0254).round()) as i32, + biYPelsPerMeter: ((dpi_y as f64 / 0.0254).round()) as i32, + biClrUsed: 0, + biClrImportant: 0, + }, + bmiColors: [RGBQUAD::default()], + }; + + // Draw unmanaged DIB to printer device context + let lines_drawn = StretchDIBits( + hdc, + dest_x, + dest_y, + dest_width, + dest_height, + 0, + 0, + img_width as i32, + img_height as i32, + Some(dib_bytes.as_ptr() as *const c_void), + &bmi, + DIB_RGB_COLORS, + SRCCOPY, + ); + + if lines_drawn <= 0 { + let _ = EndPage(hdc); + let _ = EndDoc(hdc); + let _ = DeleteDC(hdc); + let _ = ClosePrinter(h_printer); + return Err("Failed to draw DIB bitmap to printer DC (StretchDIBits)".to_string()); + } + + // Finish Page and Document + let _ = EndPage(hdc); + let _ = EndDoc(hdc); + let _ = DeleteDC(hdc); + let _ = ClosePrinter(h_printer); + + Ok(()) + } +} diff --git a/src-tauri/src/process_manager.rs b/src-tauri/src/process_manager.rs index eb8d715..ed31858 100644 --- a/src-tauri/src/process_manager.rs +++ b/src-tauri/src/process_manager.rs @@ -1,26 +1,32 @@ use std::collections::HashMap; use std::process::Stdio; +use std::sync::Arc; +use tauri::AppHandle; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::process::{Child, Command}; use tokio::sync::Mutex; -use tauri::AppHandle; use crate::events::{emit_error, emit_stderr, emit_stdout}; -use std::sync::Arc; - pub struct ProcessManager { - processes: Mutex>>>, + processes: Arc>>>>, } impl ProcessManager { pub fn new() -> Self { Self { - processes: Mutex::new(HashMap::new()), + processes: Arc::new(Mutex::new(HashMap::new())), } } - pub async fn spawn(&self, app: AppHandle, id: String, binary: String, args: Vec, cwd: Option) -> Result<(), String> { + pub async fn spawn( + &self, + app: AppHandle, + id: String, + binary: String, + args: Vec, + cwd: Option, + ) -> Result<(), String> { let mut command = Command::new(&binary); command.args(args); if let Some(dir) = cwd { @@ -30,11 +36,17 @@ impl ProcessManager { command.stderr(Stdio::piped()); command.stdin(Stdio::piped()); + #[cfg(windows)] + { + const CREATE_NO_WINDOW: u32 = 0x08000000; + command.creation_flags(CREATE_NO_WINDOW); + } + match command.spawn() { Ok(mut child) => { let stdout = child.stdout.take().expect("Failed to open stdout"); let stderr = child.stderr.take().expect("Failed to open stderr"); - + let id_clone = id.clone(); let app_clone = app.clone(); tokio::spawn(async move { @@ -58,24 +70,34 @@ impl ProcessManager { emit_stderr(&app_clone2, &id_clone2, line); } }); - + let child_arc = Arc::new(Mutex::new(child)); - let mut processes = self.processes.lock().await; - processes.insert(id.clone(), child_arc.clone()); - + { + let mut processes = self.processes.lock().await; + processes.insert(id.clone(), child_arc.clone()); + } + let id_clone_exit = id.clone(); let app_clone_exit = app.clone(); + let processes_clone = self.processes.clone(); tokio::spawn(async move { - loop { - tokio::time::sleep(std::time::Duration::from_millis(100)).await; + let exit_code = { let mut c = child_arc.lock().await; - if let Ok(Some(status)) = c.try_wait() { - crate::events::emit_exit(&app_clone_exit, &id_clone_exit, status.code().unwrap_or(1)); - break; + match c.wait().await { + Ok(status) => status.code().unwrap_or(0), + Err(_) => 1, } + }; + + // Reap child from process manager map upon exit + { + let mut processes = processes_clone.lock().await; + processes.remove(&id_clone_exit); } + + crate::events::emit_exit(&app_clone_exit, &id_clone_exit, exit_code); }); - + Ok(()) } Err(e) => { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 0750aaf..d5b06f5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ICCery", - "version": "0.1.0", + "version": "0.2.0", "identifier": "com.gronod.iccery", "build": { "frontendDist": "../src" @@ -11,8 +11,10 @@ "windows": [ { "title": "ICCery", - "width": 800, - "height": 600 + "width": 1280, + "height": 800, + "minWidth": 1100, + "minHeight": 700 } ], "security": { @@ -24,6 +26,7 @@ "targets": "all", "icon": [ "icons/32x32.png", + "icons/64x64.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", @@ -31,6 +34,17 @@ ], "resources": [ "argyll/**/*" - ] + ], + "windows": { + "wix": { + "bannerPath": "icons/wix-banner.bmp", + "dialogImagePath": "icons/wix-dialog.bmp" + }, + "nsis": { + "installerIcon": "icons/icon.ico", + "headerImage": "icons/nsis-header.bmp", + "sidebarImage": "icons/nsis-sidebar.bmp" + } + } } -} +} \ No newline at end of file diff --git a/src/assets/ICCery-logo.svg b/src/assets/ICCery-logo.svg new file mode 100644 index 0000000..2b7b7e2 --- /dev/null +++ b/src/assets/ICCery-logo.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ICCery + diff --git a/src/assets/app-icon.svg b/src/assets/app-icon.svg new file mode 100644 index 0000000..917269a --- /dev/null +++ b/src/assets/app-icon.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/sRGB.gam b/src/assets/sRGB.gam new file mode 100644 index 0000000..993c5b5 --- /dev/null +++ b/src/assets/sRGB.gam @@ -0,0 +1,1379 @@ +GAMUT + +DESCRIPTOR "Argyll Gamut surface poligon data" +ORIGINATOR "Argyll CMS gamut library" +CREATED "Tue Aug 25 10:22:46 2026" +COLOR_REP "LAB" +GAMUT_CENTER "50.000000 0.000000 0.000000" +CSPACE_WHITE "100.000000 -2.387816 -19.404026" +GAMUT_WHITE "99.998874 -2.387789 -19.403808" +CSPACE_BLACK "0.000000 0.000000 0.000000" +GAMUT_BLACK "0.000000 0.000000 0.000000" +CUSP_RED "53.237382 78.287871 62.148058" +CUSP_YELLOW "97.137321 -23.769791 84.721311" +CUSP_GREEN "87.733928 -87.887785 73.898355" +CUSP_CYAN "91.113320 -50.031832 -33.437063" +CUSP_BLUE "32.301170 77.838134 -126.416203" +CUSP_MAGENTA "60.323069 96.208467 -79.513768" +# First come the triangle verticy location + +NUMBER_OF_FIELDS 4 +BEGIN_DATA_FORMAT +VERTEX_NO LAB_L LAB_A LAB_B +END_DATA_FORMAT + +NUMBER_OF_SETS 448 +BEGIN_DATA +0 53.23738 78.28787 62.14806 +1 87.73393 -87.88779 73.89835 +2 32.30117 77.83813 -126.4162 +3 97.13732 -23.76979 84.72131 +4 60.32307 96.20847 -79.51377 +5 92.08525 -14.24625 81.54220 +6 53.35447 78.60413 50.79253 +7 83.87564 2.280649 76.53540 +8 80.69498 9.066293 74.66362 +9 77.59264 15.90655 72.88418 +10 57.93092 64.97051 63.46530 +11 58.76878 62.66166 63.75963 +12 53.57668 79.20211 36.15187 +13 89.34786 -74.05441 75.78451 +14 71.68937 29.56313 69.65578 +15 65.07949 45.92003 66.37764 +16 73.12190 26.16928 70.41736 +17 66.31765 42.76656 66.95748 +18 53.65394 79.40935 32.13248 +19 90.79613 -62.92803 77.46661 +20 91.08723 -60.81567 77.80354 +21 53.94195 80.17896 19.76368 +22 92.04631 -54.11558 78.91087 +23 54.05752 80.48649 15.59671 +24 92.39458 -51.77448 79.31196 +25 95.22381 -34.29738 82.55042 +26 54.46471 81.56421 3.147035 +27 55.15354 83.36736 -13.13741 +28 58.23555 91.15715 -58.67024 +29 56.25383 86.19822 -32.74892 +30 43.82204 44.39999 -107.3457 +31 88.00240 -84.44335 56.17783 +32 53.79934 90.68751 -90.31915 +33 52.74215 89.83989 -92.07732 +34 88.28748 -80.88480 41.84045 +35 91.11332 -50.03183 -33.43706 +36 50.66167 88.21685 -95.54287 +37 55.47044 15.53048 -88.43226 +38 70.25635 -15.11766 -65.05040 +39 88.51724 -78.08693 32.21627 +40 49.64029 87.44353 -97.24689 +41 50.24043 27.91848 -96.87445 +42 90.56657 -55.46975 -23.06920 +43 12.35755 45.69866 -74.21878 +44 13.42559 47.41983 -77.01411 +45 90.39594 -57.21271 -19.59352 +46 67.01897 -70.33719 59.14136 +47 89.11856 -71.03844 11.82216 +48 74.03954 -22.07350 -59.19191 +49 90.07208 -60.58426 -12.62126 +50 49.46805 -55.46731 46.63837 +51 64.65829 -3.983664 -73.81222 +52 75.93542 -25.44286 -56.27611 +53 62.76463 -0.333769 -76.80409 +54 5.923683 34.08038 -56.46335 +55 89.62939 -65.33530 -2.130596 +56 2.837725 19.48753 -41.27759 +57 15.14731 -26.38932 20.02350 +58 28.15269 -37.40803 31.45365 +59 17.84328 -28.67346 22.94881 +60 30.63103 -39.50779 33.21918 +61 85.42860 -41.27479 -41.88407 +62 2.264561 15.55144 -36.92225 +63 87.32555 -44.25171 -39.05084 +64 3.487739 23.76854 -45.40603 +65 35.09873 57.77819 45.86665 +66 26.18016 47.69382 36.88900 +67 9.565408 -19.61366 13.12312 +68 1.765340 12.12314 -32.33317 +69 24.64917 45.96270 35.11541 +70 99.99887 -2.368094 -19.42243 +71 0.00000 0.00000 0.00000 +72 73.84328 58.41718 -58.35473 +73 89.01410 21.35955 -35.43485 +74 90.53891 17.92010 -33.18221 +75 92.08034 14.49254 -30.91473 +76 77.62019 48.68730 -52.56424 +77 13.46979 33.32198 20.41046 +78 86.02128 28.25508 -39.88383 +79 11.79010 31.42273 17.96859 +80 0.976169 6.703653 -22.41022 +81 78.94902 45.34606 -50.54002 +82 97.40326 -21.64644 65.83800 +83 84.55614 31.70238 -42.07507 +84 97.87024 -17.99013 42.20060 +85 99.53319 -5.650714 -9.059997 +86 99.24797 -7.696724 -2.125014 +87 99.11290 -8.675347 1.345406 +88 98.62197 -12.28656 15.20945 +89 99.38806 -6.688337 -5.594113 +90 98.98277 -9.624138 4.816620 +91 98.73735 -11.43000 11.75069 +92 98.85759 -10.54257 8.286179 +93 98.39120 0.957317 -21.73439 +94 0.679866 4.668850 -17.05062 +95 1.282928 -2.661895 1.764970 +96 0.129513 0.889405 -3.341660 +97 6.734616 -13.97338 9.265051 +98 5.199688 21.73802 7.977318 +99 99.52551 -4.488846 -20.16416 +100 98.18088 -10.71721 -22.27412 +101 10.15105 -14.53865 -3.319695 +102 3.938488 17.17106 6.042395 +103 1.664400 -0.998752 2.350220 +104 9.951148 -16.30966 1.630954 +105 93.99286 -32.42990 -28.87434 +106 10.39826 -12.29228 -8.240623 +107 93.73185 -33.92167 -29.28710 +108 0.825811 4.714559 -10.87949 +109 9.796380 -17.65318 6.241842 +110 8.071554 -4.789824 -17.08635 +111 1.547389 -0.845764 -5.058574 +112 7.646280 28.27220 -11.70984 +113 7.710785 -7.269724 -12.26130 +114 4.531049 -8.243161 2.713690 +115 10.19545 29.79051 12.06211 +116 8.489888 -1.807740 -21.73665 +117 10.69333 -9.569632 -13.07176 +118 15.40892 -23.12967 8.665202 +119 2.591700 3.044100 3.772876 +120 5.703828 26.49037 -25.86414 +121 3.285427 6.068621 4.837187 +122 3.595860 1.268164 -18.47215 +123 6.203050 28.34728 -30.40435 +124 4.382828 20.02936 -5.422344 +125 1.438284 6.595400 -1.333755 +126 20.91115 -7.754914 26.97127 +127 20.27872 -11.23640 26.16018 +128 13.07359 -16.15192 -4.356306 +129 19.75035 24.39195 -61.99173 +130 20.43478 27.07432 -65.41167 +131 7.513566 -10.57730 10.46011 +132 5.081402 -4.463716 -10.68033 +133 5.377705 -2.428913 -15.78637 +134 21.61150 -4.222775 27.85812 +135 7.464250 30.09177 -28.31670 +136 23.48754 27.82838 -69.36887 +137 19.23150 -17.78240 24.79616 +138 30.72092 34.40688 -83.56651 +139 21.24832 7.098725 28.18333 +140 22.37545 -0.689668 28.81160 +141 28.48420 11.23173 -56.81067 +142 5.221417 14.44958 7.807366 +143 27.77366 37.12635 37.13350 +144 3.555104 17.20435 -12.53467 +145 19.39693 11.38805 26.46904 +146 47.69490 -47.30841 18.78004 +147 6.482617 18.55770 9.742288 +148 36.73982 11.47773 -65.27615 +149 4.202949 18.92348 -0.781149 +150 62.93561 -64.00783 42.28715 +151 31.67616 21.19027 38.39296 +152 65.12867 -65.33676 41.60032 +153 4.665997 -7.316435 -0.768194 +154 17.39142 22.45460 24.82553 +155 20.90425 -25.78611 6.307888 +156 30.74844 -38.00092 25.43964 +157 26.66549 -0.0907351 -41.71491 +158 13.91397 34.50998 3.577564 +159 20.33466 3.625928 27.01590 +160 4.068001 18.04872 2.700735 +161 15.90574 -20.09466 21.08254 +162 30.98258 -35.10897 14.50448 +163 35.98428 -37.59082 12.34234 +164 11.88289 -5.858106 16.57264 +165 10.67036 -13.02915 14.77963 +166 33.49846 -36.37075 13.41920 +167 32.29113 14.26994 38.16461 +168 47.73146 -1.699098 -60.36746 +169 30.09398 36.68870 39.02219 +170 26.37535 -26.46001 -0.308204 +171 28.98639 -30.35030 32.42729 +172 15.46523 8.927534 21.70760 +173 20.77851 -27.26990 10.70253 +174 45.39094 -0.217804 -59.91525 +175 17.88513 -0.883742 -32.12110 +176 14.74711 23.14890 21.53339 +177 72.35878 -60.47868 11.85376 +178 91.67496 -32.25284 -32.33429 +179 92.20045 -29.29361 -31.50100 +180 92.48407 -27.72691 -31.05153 +181 77.63971 34.10040 -52.78188 +182 83.30741 27.60819 -44.07625 +183 13.76159 34.10536 8.718749 +184 75.54892 35.67697 -56.03791 +185 43.72381 -36.17902 -2.844653 +186 76.39551 30.21868 -54.79453 +187 76.21464 20.90006 -55.22162 +188 13.64415 33.79146 13.43158 +189 78.87199 21.14692 -51.06609 +190 87.32002 15.09810 -38.13399 +191 67.26989 26.66099 -69.28892 +192 38.66394 62.86364 10.75828 +193 35.19656 58.04239 36.57309 +194 83.65310 20.03624 -43.67447 +195 90.66601 -12.51473 -33.53344 +196 66.21972 13.60448 -71.12634 +197 89.57944 -17.91095 -35.26060 +198 37.26335 61.33582 8.675190 +199 23.91933 -23.30291 -3.733546 +200 2.682365 13.39937 -13.90632 +201 98.13709 -14.63988 14.48116 +202 74.35996 -7.603405 -58.51742 +203 74.68159 15.36010 -57.70693 +204 37.13105 60.98447 13.13676 +205 35.71721 59.43591 11.05708 +206 75.17492 17.17678 -56.90661 +207 27.77639 49.62372 31.59393 +208 67.91145 44.21902 -68.02449 +209 65.35662 20.31670 -72.42599 +210 11.42291 6.317206 16.36066 +211 88.37362 9.567093 -36.62598 +212 3.319578 15.58691 -7.053573 +213 88.56494 -11.00468 -36.68412 +214 74.63991 -6.294046 -58.06294 +215 27.98927 50.19579 18.84953 +216 31.00037 -16.59208 34.74828 +217 65.71297 30.38908 -71.73081 +218 36.82309 60.16166 26.36287 +219 67.26234 49.39310 -68.98901 +220 88.19055 -12.81586 -37.28068 +221 74.59533 4.811492 -57.98787 +222 74.20986 3.198571 -58.61370 +223 16.10752 19.55477 23.06607 +224 37.41096 61.72633 4.231548 +225 13.31752 -13.69400 -9.151914 +226 26.39913 48.28369 21.36279 +227 87.86746 7.522807 -37.43259 +228 98.90170 -7.761468 -17.43958 +229 35.24765 62.42859 -36.12594 +230 31.75068 -3.266419 -42.71249 +231 28.01680 52.51032 -19.93533 +232 59.44113 -50.08606 6.299467 +233 33.94732 61.18875 -38.24646 +234 96.00062 -29.01119 61.31600 +235 88.10144 -1.914353 -37.23781 +236 94.56869 -35.07831 27.10537 +237 95.28485 -31.26178 31.65535 +238 23.35841 44.90028 17.21548 +239 2.266959 10.54665 -3.751327 +240 28.09646 50.48229 14.21560 +241 34.34219 60.15362 -23.87512 +242 37.29636 63.54341 -23.72363 +243 26.33149 50.38084 -17.82716 +244 95.98191 -27.88023 39.51939 +245 2.446838 11.78193 -8.392522 +246 13.92629 -8.181595 -18.46406 +247 94.87371 -33.59099 31.05815 +248 98.29312 -11.03405 -14.68680 +249 95.37605 -30.49052 28.28770 +250 94.77740 -35.63092 53.89733 +251 27.50338 51.19254 -11.10843 +252 98.45214 -9.864210 -18.14280 +253 29.98711 53.20877 2.465737 +254 28.53656 51.64811 0.250181 +255 32.13856 -0.522276 -46.54584 +256 48.32618 -40.06980 -0.680740 +257 11.85507 -0.428976 -26.77594 +258 9.504890 10.87540 13.86657 +259 93.81622 -39.51807 26.00116 +260 74.52452 -61.48449 10.94735 +261 97.70015 -14.30301 -11.90641 +262 31.55372 -13.38783 35.37864 +263 10.42073 2.579694 14.87232 +264 97.44691 -17.41806 6.142709 +265 97.55030 -15.43866 -8.437139 +266 94.36246 -38.06815 53.33448 +267 13.04887 34.71996 -13.88170 +268 12.75360 33.96459 -9.020249 +269 93.39587 -43.35149 42.52101 +270 33.25497 17.34379 39.19856 +271 8.715568 -5.149986 12.30012 +272 97.78216 -16.07696 10.30740 +273 20.20364 -5.467953 -28.48208 +274 97.40563 -16.54334 -4.963766 +275 26.61730 51.10743 -22.18650 +276 34.30220 62.06052 -42.18343 +277 91.00758 -56.66178 11.02375 +278 70.70544 -53.57729 -1.864092 +279 14.10349 35.00912 -1.521011 +280 30.98682 57.86378 -38.52871 +281 9.512529 -1.287457 13.50842 +282 93.32611 -43.99792 45.67200 +283 94.01887 -37.70669 19.16257 +284 13.60171 -11.02093 -13.85885 +285 97.27410 -16.40380 -12.56932 +286 8.043309 -8.267045 11.27282 +287 93.43482 -40.88541 14.67685 +288 93.77435 -38.83760 15.18622 +289 90.89224 -57.84543 14.48903 +290 97.26612 -17.61638 -1.488146 +291 92.20520 -50.48310 34.11207 +292 91.36553 -55.25143 22.38978 +293 93.03680 -42.60741 6.773126 +294 92.86103 -42.47785 -4.573553 +295 96.97734 -18.69401 -5.625079 +296 24.91315 48.95145 -20.10400 +297 93.22558 -41.81436 10.72090 +298 92.74040 -42.78198 -8.474425 +299 97.13171 -18.65765 1.989240 +300 91.12239 -56.93396 22.03022 +301 92.69355 -47.79108 41.53138 +302 92.89173 -41.39279 -11.95982 +303 93.21108 -38.50587 -18.91533 +304 97.00239 -19.66648 5.465989 +305 93.04862 -39.96704 -15.44071 +306 91.52506 -54.47515 26.16916 +307 33.23674 -12.70974 -31.45839 +308 34.01268 10.28814 39.10684 +309 33.49186 -3.503987 37.56308 +310 11.50229 33.18357 -16.37708 +311 29.30833 55.73847 -36.66815 +312 8.335318 7.298841 12.08963 +313 91.90437 -52.48325 33.67877 +314 24.12544 -21.15564 -8.183776 +315 96.86083 -9.866071 16.50597 +316 77.95635 -13.90732 70.77213 +317 9.421928 32.61559 -30.32216 +318 62.64612 -31.14253 57.93254 +319 60.40093 -39.80946 -16.46213 +320 48.42654 -15.59406 -42.63749 +321 6.404035 -0.402050 9.127567 +322 7.276775 3.402146 10.46652 +323 48.84672 -34.55524 -12.53862 +324 61.81548 -36.45738 56.97924 +325 59.94608 24.49026 60.21842 +326 6.558180 11.46464 9.646398 +327 61.42550 11.70671 60.16647 +328 48.14355 -45.40177 46.12738 +329 49.32816 -19.97056 48.49804 +330 69.33594 -43.88677 -19.45580 +331 48.82496 -22.91644 47.92431 +332 47.73441 19.14651 50.39953 +333 49.07231 31.10492 52.63467 +334 49.32683 15.43808 51.26434 +335 45.27055 19.94972 48.60077 +336 39.82562 28.05534 45.30432 +337 48.13794 -18.00573 -38.88927 +338 70.36580 -33.95143 -37.58748 +339 38.79589 25.27193 44.22368 +340 25.20039 -11.21379 -25.49234 +341 49.77231 -4.182637 49.95564 +342 31.38055 -33.09592 34.09503 +343 22.54416 -9.789864 -24.85476 +344 35.74211 -14.05042 -32.06197 +345 37.37400 28.84882 43.53951 +346 47.71964 34.51697 51.98748 +347 34.91799 -21.38088 -19.94736 +348 49.04452 -32.55371 -16.46482 +349 34.26401 -13.90936 37.41112 +350 38.45770 31.53684 44.66277 +351 70.14358 -36.02027 -33.99700 +352 37.41040 -22.57751 -20.67675 +353 49.71131 -26.14210 -28.10082 +354 24.35665 -18.85029 -12.59794 +355 24.89436 -13.85676 -21.26108 +356 49.47680 -28.34128 -24.24881 +357 22.22683 -12.49970 -20.56104 +358 24.61284 -16.41120 -16.95794 +359 35.44970 -16.55937 -28.07054 +360 21.07184 47.77452 -45.56465 +361 23.34524 51.15712 -51.13708 +362 35.17491 -19.00693 -24.03086 +363 87.06072 9.370494 7.187899 +364 88.52195 5.174662 12.87112 +365 90.12211 1.800273 14.97607 +366 87.61671 12.88924 -7.151265 +367 82.81879 9.180387 41.93159 +368 85.62945 13.62353 1.468319 +369 86.93636 8.570999 10.77074 +370 31.85722 61.79949 -59.60587 +371 89.33123 10.42784 -8.524011 +372 87.46891 11.96257 -3.571796 +373 85.49520 12.78818 5.074162 +374 88.76889 6.798596 5.741566 +375 85.76969 14.49072 -2.135386 +376 85.36696 11.98546 8.677448 +377 92.22258 1.787498 2.973154 +378 90.62339 5.158576 0.784580 +379 91.96858 0.0543591 10.04090 +380 80.27421 19.38626 17.09611 +381 79.28559 25.73400 0.430351 +382 86.81771 7.803842 14.34869 +383 91.73611 -1.550446 17.08429 +384 81.21472 24.81315 -8.495535 +385 91.21404 9.016163 -13.38990 +386 82.75019 8.740551 45.22326 +387 81.18401 12.15713 43.52679 +388 92.09286 0.905013 6.508738 +389 78.77087 22.82519 15.14776 +390 92.35770 2.700996 -0.561588 +391 79.58249 27.38259 -6.915997 +392 81.50122 14.12340 29.79385 +393 83.37224 25.16023 -20.83636 +394 85.91600 15.38956 -5.735542 +395 83.94803 16.21828 2.971552 +396 83.14824 11.27243 28.15276 +397 79.74100 28.25421 -10.57715 +398 71.32551 47.57174 -18.68964 +399 90.90744 7.026770 -6.313611 +400 81.54185 26.64941 -15.76232 +401 78.88980 23.50318 11.47322 +402 85.04753 22.73359 -22.24328 +403 93.96598 -0.651517 1.647721 +404 93.47278 -4.094151 15.70088 +405 70.07438 50.78030 -20.50606 +406 85.22966 23.78332 -25.80187 +407 83.55337 26.18182 -24.41524 +408 12.49315 37.63989 -40.46678 +409 93.11594 7.722502 -18.17200 +410 79.01517 24.21397 7.793819 +411 89.90427 0.315521 22.02200 +412 92.95320 6.659334 -14.66383 +413 81.59492 14.69861 26.24226 +414 69.07035 46.34499 2.218811 +415 78.65839 22.18071 18.80973 +416 94.70243 4.342196 -15.90200 +417 77.68103 28.39026 2.114921 +418 81.41337 13.58186 33.30720 +419 80.16523 18.74256 20.72951 +420 69.53654 48.42463 -9.213966 +421 87.93016 14.83435 -14.28997 +422 87.83105 0.527343 37.31729 +423 70.49374 43.77068 0.154891 +424 94.46153 -12.13477 41.60092 +425 69.85356 40.76189 19.17483 +426 65.82409 56.94787 -10.42383 +427 85.87591 1.556436 51.72802 +428 74.82919 24.13317 53.16703 +429 8.603005 16.24850 -42.11586 +430 76.36188 20.99314 51.57010 +431 68.79887 45.11718 9.878961 +432 69.96660 41.29868 15.38241 +433 63.04901 60.16254 2.151702 +434 72.28298 32.67149 33.56793 +435 63.53064 61.96164 -9.575327 +436 73.19295 46.96005 -27.89996 +437 62.77183 59.11453 10.03247 +438 20.37149 48.00729 -51.43156 +439 73.40029 47.90326 -31.55172 +440 72.06443 31.53639 44.02758 +441 68.34758 43.04775 25.12152 +442 70.90211 36.02996 31.97182 +443 62.64618 58.63630 13.97813 +444 70.82030 35.62091 35.58479 +445 63.48416 54.93669 23.14435 +446 62.42032 57.77172 21.84206 +447 64.73492 66.34472 -32.42670 +END_DATA + + +# And then come the triangles + +NUMBER_OF_FIELDS 3 +BEGIN_DATA_FORMAT +VERTEX_0 VERTEX_1 VERTEX_2 +END_DATA_FORMAT + +NUMBER_OF_SETS 892 +BEGIN_DATA +13 1 46 +6 0 65 +4 32 72 +32 33 72 +25 3 82 +70 93 99 +99 93 100 +95 71 103 +63 35 107 +97 67 109 +104 97 109 +96 71 111 +108 96 111 +97 104 114 +104 101 114 +79 98 115 +110 113 117 +113 106 117 +67 57 118 +109 67 118 +104 109 118 +103 71 119 +62 56 120 +68 62 120 +119 71 121 +80 94 122 +116 80 122 +110 116 122 +64 54 123 +56 64 123 +120 56 123 +71 96 125 +96 108 125 +121 71 125 +102 121 125 +101 104 128 +106 101 128 +43 54 129 +54 64 129 +44 43 130 +43 129 130 +97 114 131 +106 113 132 +94 108 132 +108 111 132 +110 122 133 +122 94 133 +94 132 133 +113 110 133 +132 113 133 +120 123 135 +2 44 136 +44 130 136 +130 129 136 +59 57 137 +30 2 138 +2 136 138 +136 129 141 +138 136 141 +121 102 142 +69 66 143 +68 120 144 +120 112 144 +112 124 144 +60 50 146 +102 98 147 +142 102 147 +37 41 148 +41 30 148 +30 138 148 +138 141 148 +124 98 149 +50 46 150 +146 50 150 +1 31 152 +46 1 152 +150 46 152 +31 34 152 +34 146 152 +146 150 152 +71 95 153 +111 71 153 +101 106 153 +114 101 153 +95 114 153 +132 111 153 +106 132 153 +77 69 154 +69 143 154 +143 151 154 +151 145 154 +58 60 156 +141 129 157 +115 98 158 +139 140 159 +140 134 159 +98 102 160 +149 98 160 +127 137 161 +57 67 161 +137 57 161 +59 58 162 +58 156 162 +60 146 163 +156 60 163 +126 127 164 +134 126 164 +159 134 164 +67 97 165 +97 131 165 +127 161 165 +164 127 165 +161 67 165 +162 156 166 +156 163 166 +139 145 167 +145 151 167 +51 53 168 +53 37 168 +66 65 169 +143 66 169 +151 143 169 +162 166 170 +166 163 170 +60 58 171 +58 59 171 +59 137 171 +145 139 172 +139 159 172 +57 59 173 +118 57 173 +155 118 173 +59 162 173 +162 170 173 +170 155 173 +37 148 174 +168 37 174 +62 68 175 +56 62 175 +98 79 176 +147 98 176 +79 77 176 +77 154 176 +39 47 177 +61 63 178 +63 107 178 +107 105 178 +61 178 179 +178 105 179 +105 100 180 +179 105 180 +83 81 181 +78 83 182 +79 115 183 +115 158 183 +81 76 184 +181 81 184 +170 163 185 +83 181 186 +181 184 186 +77 79 188 +79 183 188 +182 83 189 +83 186 189 +186 187 189 +74 73 190 +187 186 191 +23 21 192 +12 6 193 +6 65 193 +65 66 193 +73 78 194 +78 182 194 +182 189 194 +190 73 194 +100 93 195 +37 53 196 +61 179 197 +179 180 197 +180 100 197 +100 195 197 +52 61 197 +26 23 198 +23 192 198 +104 118 199 +118 155 199 +128 104 199 +155 170 199 +94 80 200 +108 94 200 +80 68 200 +68 144 200 +38 48 202 +194 189 203 +21 18 204 +192 21 204 +198 192 205 +192 204 205 +187 191 206 +191 203 206 +189 187 206 +203 189 206 +193 66 207 +184 76 208 +2 30 208 +186 184 208 +41 37 209 +191 41 209 +37 196 209 +196 203 209 +203 191 209 +75 74 211 +74 190 211 +124 149 212 +144 124 212 +51 38 214 +38 202 214 +202 48 214 +137 127 216 +171 137 216 +30 41 217 +41 191 217 +208 30 217 +191 186 217 +186 208 217 +18 12 218 +12 193 218 +204 18 218 +215 204 218 +193 207 218 +36 40 219 +33 36 219 +72 33 219 +76 72 219 +208 76 219 +40 2 219 +2 208 219 +48 52 220 +52 197 220 +197 195 220 +195 213 220 +213 214 220 +214 48 220 +196 53 221 +203 196 221 +53 51 222 +221 53 222 +51 214 222 +154 145 223 +145 172 223 +176 154 223 +27 26 224 +26 198 224 +106 128 225 +66 69 226 +207 66 226 +215 218 226 +218 207 226 +75 211 227 +190 194 227 +211 190 227 +194 203 227 +203 221 227 +28 29 229 +34 39 232 +146 34 232 +39 177 232 +25 82 234 +82 84 234 +195 93 235 +213 195 235 +221 222 235 +222 214 235 +93 75 235 +75 227 235 +214 213 235 +227 221 235 +183 158 238 +188 183 238 +215 226 238 +69 77 238 +226 69 238 +77 188 238 +102 125 239 +160 102 239 +149 160 239 +212 149 239 +125 108 239 +205 204 240 +204 215 240 +215 238 240 +29 27 242 +229 29 242 +241 229 242 +84 237 244 +200 144 245 +144 212 245 +108 200 245 +212 239 245 +239 108 245 +116 110 246 +110 117 246 +85 70 248 +70 228 248 +88 201 249 +84 88 249 +237 84 249 +236 247 249 +247 237 249 +24 25 250 +25 234 250 +234 84 250 +84 244 250 +27 224 251 +242 27 251 +231 241 251 +241 242 251 +243 231 251 +70 99 252 +228 70 252 +99 100 252 +100 248 252 +248 228 252 +198 205 253 +224 198 253 +205 240 253 +238 158 254 +240 238 254 +253 240 254 +251 224 254 +224 253 254 +148 141 255 +141 157 255 +157 230 255 +174 148 255 +230 174 255 +163 146 256 +185 163 256 +146 232 256 +68 80 257 +80 116 257 +175 68 257 +116 246 257 +172 210 258 +223 172 258 +147 176 258 +176 223 258 +247 236 259 +177 47 260 +85 248 261 +127 126 262 +216 127 262 +126 134 262 +159 164 263 +172 159 263 +210 172 263 +92 90 264 +91 92 264 +89 85 265 +85 261 265 +22 24 266 +24 250 266 +250 244 266 +112 267 268 +267 251 268 +244 237 269 +237 247 269 +247 259 269 +167 151 270 +88 91 272 +91 264 272 +201 88 272 +249 201 272 +157 175 273 +175 257 273 +257 246 273 +86 89 274 +89 265 274 +241 231 275 +231 243 275 +4 28 276 +28 229 276 +229 233 276 +32 4 276 +55 47 277 +55 49 278 +47 55 278 +256 232 278 +260 47 278 +232 177 278 +177 260 278 +98 124 279 +158 98 279 +124 112 279 +251 254 279 +254 158 279 +112 268 279 +268 251 279 +276 233 280 +263 164 281 +164 271 281 +13 19 282 +19 20 282 +20 22 282 +22 266 282 +266 244 282 +244 269 282 +259 236 283 +236 249 283 +249 272 283 +117 106 284 +106 225 284 +246 117 284 +100 105 285 +248 100 285 +261 248 285 +265 261 285 +164 165 286 +165 131 286 +114 95 286 +95 271 286 +131 114 286 +271 164 286 +287 283 288 +277 47 289 +87 86 290 +86 274 290 +31 1 291 +287 289 292 +55 277 293 +49 55 294 +55 293 294 +274 265 295 +265 285 295 +290 274 295 +243 251 296 +251 267 296 +275 243 296 +277 289 297 +289 287 297 +293 277 297 +287 288 297 +45 49 298 +49 294 298 +90 87 299 +87 290 299 +264 90 299 +290 295 299 +295 294 299 +47 39 300 +39 292 300 +289 47 300 +292 289 300 +1 13 301 +13 282 301 +282 269 301 +269 259 301 +259 291 301 +291 1 301 +45 298 302 +294 295 302 +298 294 302 +35 42 303 +107 35 303 +105 107 303 +285 105 303 +295 285 303 +272 264 304 +283 272 304 +288 283 304 +294 293 304 +299 294 304 +264 299 304 +297 288 304 +293 297 304 +42 45 305 +45 302 305 +303 42 305 +302 295 305 +295 303 305 +39 34 306 +259 283 306 +283 287 306 +287 292 306 +291 259 306 +292 39 306 +230 157 307 +157 273 307 +140 139 308 +139 167 308 +167 270 308 +134 140 309 +262 134 309 +140 308 309 +112 120 310 +120 135 310 +267 112 310 +296 267 310 +229 241 311 +241 275 311 +233 229 311 +280 233 311 +258 210 312 +210 263 312 +34 31 313 +31 291 313 +291 306 313 +306 34 313 +199 170 314 +128 199 314 +225 128 314 +88 84 315 +5 3 316 +7 5 316 +123 54 317 +135 123 317 +296 310 317 +310 135 317 +3 25 318 +316 3 318 +25 24 318 +24 22 318 +278 49 319 +256 278 319 +38 51 320 +51 168 320 +168 174 320 +174 230 320 +95 103 321 +271 95 321 +281 271 321 +103 119 321 +119 121 322 +321 119 322 +263 281 322 +281 321 322 +312 263 322 +121 312 322 +185 256 323 +256 319 323 +19 13 324 +13 46 324 +46 50 324 +20 19 324 +22 20 324 +318 22 324 +17 14 325 +142 147 326 +147 258 326 +121 142 326 +312 121 326 +258 312 326 +9 8 327 +16 9 327 +50 60 328 +324 50 328 +316 318 329 +42 35 330 +45 42 330 +49 45 330 +319 49 330 +318 324 331 +329 318 331 +324 328 331 +17 325 332 +15 17 333 +17 332 333 +14 16 334 +325 14 334 +16 327 334 +332 325 334 +308 270 335 +333 332 335 +332 334 335 +334 308 335 +333 335 336 +52 48 337 +48 38 337 +38 320 337 +61 52 338 +270 151 339 +335 270 339 +336 335 339 +307 273 340 +8 7 341 +327 8 341 +7 316 341 +316 329 341 +334 327 341 +309 308 341 +308 334 341 +60 171 342 +171 216 342 +328 60 342 +331 328 342 +273 246 343 +340 273 343 +230 307 344 +320 230 344 +337 320 344 +151 169 345 +339 151 345 +336 339 345 +11 15 346 +15 333 346 +333 336 346 +170 185 347 +314 170 347 +323 319 348 +262 309 349 +216 262 349 +329 331 349 +309 341 349 +341 329 349 +331 342 349 +342 216 349 +65 0 350 +336 345 350 +169 65 350 +345 169 350 +0 10 350 +346 336 350 +10 11 350 +11 346 350 +35 63 351 +330 35 351 +63 61 351 +61 338 351 +319 330 351 +185 323 352 +347 185 352 +323 348 352 +52 337 353 +338 52 353 +351 338 353 +284 225 354 +225 314 354 +314 347 354 +340 343 355 +348 319 356 +319 351 356 +351 353 356 +352 348 356 +353 352 356 +246 284 357 +343 246 357 +355 343 357 +354 347 358 +347 355 358 +284 354 358 +357 284 358 +355 357 358 +344 307 359 +337 344 359 +353 337 359 +307 340 359 +275 296 360 +311 275 360 +44 2 361 +280 311 361 +311 360 361 +347 352 362 +352 353 362 +353 359 362 +340 355 362 +359 340 362 +355 347 362 +33 32 370 +32 276 370 +36 33 370 +40 36 370 +2 40 370 +361 2 370 +276 280 370 +280 361 370 +366 371 372 +364 369 374 +369 363 374 +363 373 374 +373 368 374 +372 368 375 +363 369 376 +373 363 376 +368 372 378 +374 368 378 +377 374 378 +92 91 379 +365 364 379 +364 374 379 +364 365 382 +369 364 382 +376 369 382 +380 376 382 +7 8 386 +8 9 387 +386 8 387 +367 386 387 +90 92 388 +92 379 388 +374 377 388 +379 374 388 +89 86 390 +85 89 390 +377 378 390 +384 381 391 +366 372 394 +372 375 394 +375 381 394 +381 384 394 +368 373 395 +375 368 395 +381 375 395 +373 376 395 +384 391 397 +70 85 399 +372 371 399 +378 372 399 +371 385 399 +85 390 399 +390 378 399 +384 397 400 +376 380 401 +395 376 401 +380 389 401 +73 74 402 +74 75 402 +400 393 402 +86 87 403 +87 90 403 +90 388 403 +388 377 403 +377 390 403 +390 86 403 +88 315 404 +315 383 404 +91 88 404 +379 91 404 +365 379 404 +383 365 404 +78 73 406 +73 402 406 +83 78 407 +393 400 407 +78 406 407 +402 393 407 +406 402 407 +54 43 408 +43 360 408 +317 54 408 +296 317 408 +360 296 408 +75 93 409 +381 395 410 +395 401 410 +315 84 411 +383 315 411 +382 365 411 +396 382 411 +365 383 411 +399 385 412 +385 409 412 +409 93 412 +382 396 413 +396 392 413 +389 380 415 +93 70 416 +412 93 416 +70 399 416 +399 412 416 +391 381 417 +381 410 417 +367 387 418 +392 396 418 +396 367 418 +380 382 419 +382 413 419 +413 392 419 +415 380 419 +397 391 420 +398 397 420 +405 398 420 +371 366 421 +385 371 421 +402 75 421 +366 394 421 +394 384 421 +384 400 421 +400 402 421 +75 409 421 +409 385 421 +3 5 422 +367 396 422 +396 411 422 +391 417 423 +420 391 423 +414 420 423 +82 3 424 +3 422 424 +84 82 424 +411 84 424 +422 411 424 +401 389 425 +389 415 425 +27 29 426 +29 405 426 +405 420 426 +420 414 426 +5 7 427 +422 5 427 +7 386 427 +386 367 427 +367 422 427 +64 56 429 +129 64 429 +157 129 429 +56 175 429 +175 157 429 +9 16 430 +387 9 430 +16 14 430 +14 428 430 +418 387 430 +414 423 431 +423 417 431 +410 401 432 +401 425 432 +417 410 432 +431 417 432 +26 27 433 +426 414 433 +415 419 434 +419 392 434 +27 426 435 +426 433 435 +433 27 435 +397 398 436 +400 397 436 +398 405 436 +407 400 436 +23 26 437 +26 433 437 +414 431 437 +433 414 437 +43 44 438 +44 361 438 +360 43 438 +361 360 438 +28 4 439 +436 28 439 +4 72 439 +81 83 439 +83 407 439 +407 436 439 +72 76 439 +76 81 439 +17 15 440 +14 17 440 +428 14 440 +392 418 440 +434 392 440 +418 430 440 +430 428 440 +0 6 441 +6 12 441 +425 415 442 +415 434 442 +10 0 442 +0 441 442 +441 425 442 +11 10 442 +21 23 443 +23 437 443 +437 431 443 +434 440 444 +440 15 444 +15 11 444 +11 442 444 +442 434 444 +12 18 445 +432 425 445 +425 441 445 +441 12 445 +431 432 445 +18 21 446 +445 18 446 +21 443 446 +443 431 446 +431 445 446 +29 28 447 +405 29 447 +28 436 447 +436 405 447 +END_DATA diff --git a/src/index.html b/src/index.html index 5ef676d..4af00bd 100644 --- a/src/index.html +++ b/src/index.html @@ -4,6 +4,7 @@ ICCery + @@ -14,8 +15,14 @@
- -
-
- -
- - -
-
-
- - -
+ +
+ +
- + + + + @@ -227,10 +279,10 @@
- + @@ -286,10 +338,10 @@ - + @@ -333,10 +385,10 @@ - + @@ -363,6 +415,9 @@