Files
labelmaker/CLAUDE.md
2025-10-14 18:15:41 -04:00

153 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Labelmaker is a Phoenix LiveView web application that generates text-based images via URL. Users can visit `labelmaker.xyz/Your Text` to instantly get an image with their text, with various customization options (font, color, outline, size). The app is designed for creating decals in Tabletop Simulator.
## Technology Stack
- **Elixir + Phoenix Framework** (Phoenix 1.7+ with LiveView)
- **ImageMagick** (`magick` command) for image generation
- **Tailwind CSS** for styling
- **esbuild** for JavaScript bundling
## Common Commands
### Development
```sh
# Install dependencies and set up assets
mix setup
# Start Phoenix server (runs on http://localhost:4000)
mix phx.server
# Start server with interactive Elixir shell
iex -S mix phx.server
```
### Testing
```sh
# Run all tests
mix test
# Run a specific test file
mix test test/labelmaker_web/controllers/label_controller_test.exs
# Run a specific test
mix test test/labelmaker_web/controllers/label_controller_test.exs:42
```
### Assets
```sh
# Install asset build tools (Tailwind, esbuild)
mix assets.setup
# Build assets for development
mix assets.build
# Build assets for production (minified + digest)
mix assets.deploy
```
### Docker
```sh
docker build -t labelmaker .
docker run -p 4000:4000 labelmaker
```
## Architecture
### Request Flow
1. **Homepage (`/`)**: `LabelmakerWeb.Home` LiveView provides an interactive form where users can preview text with different styling options
2. **Image Generation (`/:label`)**: `LabelmakerWeb.LabelController.show/2` handles dynamic image generation
### Core Components
**`LabelmakerWeb.LabelController`** (lib/labelmaker_web/controllers/label_controller.ex)
- Main entry point for image generation requests
- Takes URL path (label text) and query parameters (color, font, outline, size, width, height, align)
- Generates SHA256 hash from parameters to create unique filename for caching
- Checks if cached image exists; if not, builds ImageMagick command and generates image
- Stores generated images in `priv/static/labels/`
- Returns image as PNG response
**`LabelmakerWeb.Tools`** (lib/labelmaker_web/tools.ex)
- Contains parameter processing and validation logic
- `process_parameters/1`: Validates and normalizes user input against permitted values
- Handles two sizing modes:
- **Font mode** (default): Uses `label:` with `-pointsize` for natural text sizing
- **Width x Height mode**: Uses `caption:` with `-size WxH` for fixed dimensions
- `generate_link/1`: Creates URLs based on current parameters and sizing mode
- `process_label/1`: Handles `\n` to actual newline conversion for multi-line labels
- Input validation: filters colors, fonts, sizes, outlines against permitted lists
**`LabelmakerWeb.Constants`** (lib/labelmaker_web/constants.ex)
- Single source of truth for all permitted values and defaults
- Defines available colors, fonts, sizes, alignments
- Font map supports shortcuts (e.g., "h" → "Helvetica", "cs" → "Comic-Sans-MS")
- Max dimensions: 1024x1024 pixels
- Max label length: 1024 characters
- Available fonts: Comic Sans, Courier, Georgia, Helvetica, Impact, Verdana
**`LabelmakerWeb.Home`** (lib/labelmaker_web/live/home.ex)
- LiveView for homepage with interactive preview
- Handles real-time updates via `handle_event/3`:
- `update_label`: Updates label text and styling parameters
- `update_preview`: Changes preview background (gradient/solid)
- `update_sizing`: Toggles between font size mode and width×height mode
- `update_alignment`: Changes text alignment (left/center/right)
- Redirects to `/:label` route when user submits the form
**`LabelmakerWeb.RadioComponent`** (lib/labelmaker_web/live/components/radio_component.ex)
- Reusable Phoenix Component for radio button groups
- Used in the homepage form for alignment selection
### ImageMagick Command Construction
The controller builds ImageMagick commands programmatically:
1. **Basic settings**: background, fill color, font
2. **Outline settings**: stroke color and width (if not "none")
3. **Size settings**: Either pointsize + `label:` OR width×height + `caption:`
4. **Final settings**: Adds metadata comment and output filepath
Example generated command:
```sh
magick -background none -fill black -font Helvetica -stroke white -strokewidth 1 -pointsize 72 label:Hello World output.png
```
### Sizing Modes
The app supports two distinct sizing modes (controlled by `sizing` parameter):
- **Font mode** (`sizing=font`): ImageMagick calculates image size based on font size; natural text rendering
- **Width × Height mode** (`sizing=wxh`): Fixed canvas dimensions with text wrapped/aligned within bounds
### Parameter Processing
All parameters flow through `Tools.process_parameters/1`:
1. Merge with defaults from `Constants.defaults()`
2. Process label (convert `\n` escapes)
3. Validate each parameter against permitted lists
4. Filter out invalid values
5. Calculate derived values (preview_height, rows, link)
6. Return merged map with all parameters
### Caching Strategy
Images are cached using SHA256 hash of the processed options map:
- Hash includes: label, color, font, outline, size, width, height, align
- Cached files stored in `priv/static/labels/`
- Before generating, checks if file exists
- No expiration mechanism (cache persists until manually cleared)
## Important Notes
- **ImageMagick dependency**: The `magick` command must be available in PATH
- **Alignment/Gravity mapping**: User-facing "left/center/right" maps to ImageMagick "west/center/east" gravity values (see `Tools.process_gravity/1`)
- **URL escaping**: Special characters in labels must be URL-encoded; `\n` is processed as literal newline
- **Main branch**: This repo uses `trunk` as the main branch (not `main` or `master`)