# appthere-color
**Document-aware color management for Rust — pure Rust ICC transforms, CMYK, soft proofing, and print-ready color policies.**
---
`appthere-color` fills a gap in the Rust ecosystem: a color model built for **document authoring and print workflows**, rather than pixel rendering. It provides named color values, CMYK ink representation, ICC profile transforms via a pure-Rust backend ([moxcms](https://crates.io/crates/moxcms)), rendering intent selection, soft proofing configuration, and output intent policies — all the primitives a document editor, prepress tool, or print-aware renderer needs.
---
## Features
- **Pure Rust ICC transforms** via `moxcms` — no C toolchain, no `liblcms2` system dependency, cross-compiles cleanly to Android and WASM targets
- **CMYK and multi-ink color values** with f32 component precision
- **Named color spaces** — sRGB, Display P3, Adobe RGB (1998), ProPhoto RGB, CMYK (generic and profile-bound)
- **Rendering intents** — Perceptual, Relative Colorimetric, Saturation, Absolute Colorimetric
- **Soft proofing configuration** — proof profile, simulation intent, gamut warning, paper simulation
- **Document color policy** — named output intent, fallback profile, per-document color state
- **Profile-to-profile transforms** — CMYK↔RGB, RGB↔RGB, Lab↔RGB, Gray↔RGB
- `no_std` compatible (with `moxcms` backend, disable default features)
---
## Installation
```toml
[dependencies]
appthere-color = "0.1"
```
No system libraries required. Everything is pure Rust.
---
## Quick Start
### Color values
```rust
use appthere_color::{ColorValue, CmykColor, RgbColor};
// RGB color (0.0–1.0 components)
let red = ColorValue::Rgb(RgbColor::new(1.0, 0.0, 0.0));
// CMYK ink values (0.0–1.0 per channel)
let rich_black = ColorValue::Cmyk(CmykColor::new(0.60, 0.50, 0.50, 1.0));
// Lab color (D50 whitepoint)
let lab = ColorValue::Lab { l: 53.39, a: 80.09, b: 67.20 };
```
### ICC profile transforms
```rust
use appthere_color::{IccProfile, ColorTransform, RenderingIntent};
let fogra39 = IccProfile::from_bytes(include_bytes!("profiles/ISOcoated_v2_300_eci.icc"))?;
let srgb = IccProfile::srgb();
let transform = ColorTransform::new(&fogra39, &srgb, RenderingIntent::RelativeColorimetric)?;
let cmyk_pixel = [0.60f32, 0.50, 0.50, 1.0];
let rgb_pixel = transform.transform_cmyk_to_rgb(&cmyk_pixel)?;
```
### Soft proofing
```rust
use appthere_color::{ProofingConfig, GamutWarning, RenderingIntent};
let config = ProofingConfig::builder()
.output_profile(fogra39)
.simulation_intent(RenderingIntent::RelativeColorimetric)
.display_profile(srgb)
.gamut_warning(GamutWarning::Color([1.0, 0.0, 1.0])) // magenta flag
.paper_simulation(true)
.build()?;
let proofing_transform = config.build_transform()?;
```
### Document color policy
```rust
use appthere_color::{ColorPolicy, OutputIntent};
let policy = ColorPolicy::builder()
.output_intent(OutputIntent::named("FOGRA39", fogra39_profile))
.default_rendering_intent(RenderingIntent::Perceptual)
.fallback_profile(IccProfile::srgb())
.build();
// Serialize the policy into your document format's color metadata
```
---
## Color Value Types
| `RgbColor` | R, G, B | 0.0–1.0 | Linear or gamma-encoded depending on profile |
| `CmykColor` | C, M, Y, K | 0.0–1.0 | Profile-bound ink percentages |
| `LabColor` | L, a, b | L: 0–100, a/b: ±128 | D50 whitepoint |
| `XyzColor` | X, Y, Z | 0.0–1.0 (relative) | D50 |
| `GrayColor` | K | 0.0–1.0 | |
| `ColorValue` | — | — | Enum over all of the above |
---
## Rendering Intents
```rust
pub enum RenderingIntent {
Perceptual,
RelativeColorimetric,
Saturation,
AbsoluteColorimetric,
}
```
These map directly to ICC specification intents and are passed through to the `moxcms` transform engine without modification.
---
## Soft Proofing
Soft proofing simulates how a document will appear when reproduced on a specific output device (typically a press or printer). `appthere-color` models proofing as a first-class configuration object rather than a transform flag:
```rust
pub struct ProofingConfig {
pub output_profile: IccProfile,
pub simulation_intent: RenderingIntent,
pub display_profile: IccProfile,
pub display_intent: RenderingIntent,
pub gamut_warning: GamutWarning,
pub paper_simulation: bool,
}
```
Soft proofing is implemented as two chained transforms internally: source → output simulation space, then simulation space → display. This is semantically equivalent to LCMS2's proofing transform and produces the same result for standard workflows.
### Gamut warning modes
```rust
pub enum GamutWarning {
/// Do not flag out-of-gamut colors
None,
/// Replace out-of-gamut colors with a fixed indicator color
Color([f32; 3]),
/// Reduce saturation of out-of-gamut colors by a factor
Desaturate(f32),
}
```
---
## ICC Profile Support
Profiles are loaded from raw ICC bytes and backed by `moxcms`:
```rust
// From bytes (e.g. embedded in a document or file)
let profile = IccProfile::from_bytes(&icc_bytes)?;
// From a file path
let profile = IccProfile::from_file("path/to/profile.icc")?;
// Built-in virtual profiles
let srgb = IccProfile::srgb();
let linear = IccProfile::linear_srgb();
let gray = IccProfile::gray_d50();
```
Supported transform combinations mirror `moxcms` capabilities: CMYK↔RGB, RGB↔RGB, Lab↔RGB, Gray↔RGB, and any Display Class profiles up to 16 inks.
---
## Document Color Policy
A `ColorPolicy` captures the color management configuration for an entire document — the intended output condition, rendering intent, and fallback behavior when profiles are unavailable. This concept appears in PDF/X (OutputIntent), ODF (`draw:color-profile`), and EPUB (embedded ICC in images), but `appthere-color` represents it format-agnostically:
```rust
pub struct ColorPolicy {
pub output_intent: Option<OutputIntent>,
pub default_rendering_intent: RenderingIntent,
pub fallback_profile: IccProfile,
pub embedded_profiles: HashMap<String, IccProfile>,
}
```
Format-specific serialization (e.g. writing an `/OutputIntents` array into a PDF or the color profile metadata into an ODF package) is the responsibility of the document format crate that depends on `appthere-color`.
---
## Why not `lcms2`?
[`lcms2`](https://crates.io/crates/lcms2) is an excellent crate wrapping the mature Little CMS 2 library. If you need spot color (`NamedColorList`), DeviceLink profiles, or maximum compatibility with exotic real-world ICC profiles, it may be the right choice.
`appthere-color` uses `moxcms` instead because:
- **No C toolchain required** — clean cross-compilation to Android, WASM, and musl targets without a system `liblcms2`
- **Pure Rust safety guarantees** — no `unsafe` FFI surface
- **Simpler CI** — no need to manage native library availability across platforms
The tradeoff: `moxcms` does not yet support spot/named color lookups or some exotic DeviceLink profile classes. For document authoring workflows targeting standard RGB and CMYK output conditions, this is not a practical limitation.
---
## `no_std` Support
`appthere-color` is `no_std` compatible. Disable the default `std` feature:
```toml
[dependencies]
appthere-color = { version = "0.1", default-features = false }
```
ICC file loading from paths requires `std`. Profile construction from `&[u8]` and all transform operations work in `no_std` environments.
## License
Licensed under the [Apache License, Version 2.0](LICENSE).
`moxcms` is also Apache 2.0. The full dependency tree is permissively licensed.
---
## Contributing
Issues and pull requests are welcome. The scope of this crate is intentionally narrow — document-authoring color primitives and ICC transforms. Requests for PDF/ODF/EPUB serialization belong in the respective format crates.