Repository navigation
Designer Overview
English · Español
The card designer (the "Diseñar" view, the 2.x card editor) turns the mechanical data of a real Magic card plus a few thematic choices (name, flavour text, artwork, frame, footer) into a freshly painted proxy card. It lives in a pure Rust engine (proxy-core::diseno), a thin app layer that feeds it, and a React view that only edits the data and paints previews.
This page defines the concepts and shows how the pieces fit. The details are split across Designer-Engine, Designer-Composition, Frame-Packs, Designer-App-Layer and Designer-UI.
- It paints cards from data: one face of one card per call, at any pixel width, in a given design language (any of the 15
DesignLangcodes, see Design languages). The same call serves the on-screen preview, thumbnails, PNG export and the PDF (see Imaging-and-PDF). - It does not edit the mechanical text of the card. Cost, type line, rules text, power/toughness, rarity and original name are a snapshot of Scryfall data (
CardData) that the form never edits. The only thing the user writes is thematic: name, flavour, artwork, frame, footer. Rules can be overridden by hand (FaceTexts.rules_override), which marks the card as "hand-edited" with anAviso::ReglasEditadas. - It is deterministic. Nothing reads system fonts; only the fonts loaded into the engine are used, so a card renders identically on every machine.
renderis a pure function of its inputs (src-tauri/crates/proxy-core/tests/dorados.rs→dos_renders_iguales_dan_los_mismos_bytes). - It follows the project rule "heavy work lives in Rust": React never lays out text or computes geometry (see Architecture).
All of these are defined in src-tauri/crates/proxy-core/src/diseno/modelo.rs and exported to TypeScript through ts-rs (src/bindings/).
| Concept | Type | What it holds |
|---|---|---|
| Card design | CardDesign |
The whole design of one card: style, optional estilo_pack / variante_pack, optional own frame, one FaceDesign per face, before (how to restore the original card), the card snapshot, footer, options, emblem, collection id, thumb_key. Stored in DeckCard.design (optional; absent for non-designed cards). |
| Mechanical snapshot |
CardData / FaceData
|
Scryfall's layout, keywords, rarity, frame effects, colours, colour identity, cmc, set, collector number, artist, and per face: name, mana cost, type line and rules per language (ByLang<String>), printed Spanish name (printed_name_es) and printed names in the other languages (printed_names), colours, power, toughness, loyalty, defense. Also oversized, vanguard modifiers, meld_arriba, sin_texto_es / sin_texto (languages without a printing: their text is the English Oracle text). |
| Thematic face |
FaceDesign / FaceTexts / ArtRef
|
Per face: optional artwork (ArtRef { image, source, framing }) and texts per language (ByLang<FaceTexts>: name, flavor, rules_override) and, on cards with two printed faces, optional own style and settings (ajustes: AjustesCara: style, estilo_pack, variante_pack, frame, emblem, options). |
| Collection | Collection |
A reusable group look: default style, frame, emblem, set code, rarity letter, year, artist, credit, universe, original-name placement, footer template per language, title/rules font, title squeeze limits, font weights, fine tuning. Stored in the presets table with kind = collection (see the doc comment on Collection). |
| Frame options | FrameOptions |
pack (builtin or an imported pack id), color (FrameColor: Auto, Palette, Gradient, Custom), crown (OnOffAuto), rules_box (RulesBox: Auto, Dark, Light, Tint). |
| Fine tuning | FineTuning |
Millimetre offsets per text, rules-box opacity, art/card corner radii, art fade at the bottom of the illustration, rules-box height, per-text size multipliers, line-spacing multipliers. Lives on the collection or on the card. Always passed through FineTuning::limitado(). |
| Warning | Aviso |
A typed, language-neutral problem report (text does not fit, font missing, symbols missing, pack lacks a layout…). Rust never writes prose; the UI translates by kind (see IPC-and-Error-Handling). |
| Effective design | DisenoEfectivo |
The design with the collection and factory defaults resolved. This is what the painter consumes and what the render hash is made from. |
DesignStyle has four built-in styles: Borderless ("despejado": art over the whole card), Extended, Classic (art window plus full frame) and FullArt (landscape basics). "Token" is not a style but a layout every style knows how to paint. A frame pack may add own styles with other ids (CardDesign.estilo_pack), which inherit from one of the four (see Frame-Packs).
efectivo(d, coleccion) in src-tauri/crates/proxy-core/src/diseno/efectivo.rs resolves every overridable value in this order:
- the value set on the card (
CardDesign.frame,options.*,footer.*,emblem), - else the value of the card's collection (only if
d.collectionmatches the collection's id), - else the factory default (constants in
modelo.rs:ESTRECHADO_DEFECTO,ESTRECHADO_BASE_DEFECTO,PIE_DEFECTO,FrameOptions::default()).
Why: changing a collection (say, its frame colour) must restyle all its cards at once, so a card stores frame: None unless the user explicitly gave it its own frame. Resolution details worth knowing:
- Footer fields come one by one (
numberalways from the card;rarity,set_code,artist,credit,universe,yearcard > collection > derived from the Scryfall data). The footer template is per language and an empty one falls back toPIE_DEFECTO. -
original_name:Autoon the card defers to the collection, andAutothere too meansFooter. The result is neverAuto. - Title squeeze (
squeeze_min, clamped to 55-75 %) and the always-applied minimum (squeeze_base, clamped betweensqueeze_minand 1.0). - Font weights from the collection are clamped to 100-900.
- If
d.collectionnames a collection that no longer exists,coleccion_perdidais set and the factory values are used (the app addsAviso::ColeccionPerdidainsrc-tauri/src/diseno/vista.rs→avisos).
huella(e, lang, cara, recursos) is a SHA-256 over the JSON of the effective design, the language, the face index, a string fingerprint of the resources (fonts, pack, symbols) and VERSION_MOTOR (an integer in src-tauri/crates/proxy-core/src/diseno/mod.rs).
- Because the effective design is hashed, editing a collection changes the hash of every card that uses it, and a stale render is never served.
-
efectivoclearsthumb_keyand resetsstatustoDraftin its copy, so bookkeeping that does not change pixels does not change the hash. - Bumping
VERSION_MOTORinvalidates every cached render and every stored thumbnail (CardDesign.thumb_keymismatch triggersdesign_rerender). - Only the painted language enters the hash (
solo_su_idioma): editing the French name does not re-render the Spanish card. If the design text contains an emoji,|emoji-notois added so a change of emoji font only re-renders those cards. See Designer-Engine. - Gotcha: a new optional field on
CardDesignmust useskip_serializing_if = "Option::is_none", otherwise the JSON of every existing design changes and so does its hash. The comments onestilo_packandvariante_packsay exactly this.
The design language is per deck (PrintSettings.design_lang: English for new decks; decks saved before the field existed read as Spanish through #[serde(default)]) and is independent of the interface language. DesignLang has 15 values with Scryfall's codes, in the order of the app's language table (src/lib/idiomas.ts): es, en, fr, de, it, pt (Brazil), ro, pl, nl, ru, uk, ja, ko, zhs, zht.
- Every per-language value is a
ByLang<T>:esandenalways, the others only when present. A design that only uses Spanish and English is saved byte for byte as in 2.5.x, which keeps reading it; unknown codes from newer versions are ignored. - Spanish and English behave exactly as before (same hashes, same pixels). Other languages do not borrow the English thematic texts: without their own, the card shows the official printed name and rules.
- Romanian, Polish, Dutch and Ukrainian have no Scryfall printings (
DesignLang::impresas()is false): the rules are the English Oracle text and the card carriesAviso::SinTextoOficial; type lines and the engine's fixed words come from offline tables. - Japanese, Korean and Chinese need a downloaded font (Noto Sans JP/KR/SC/TC); until it is there the card carries
Aviso::FaltanLetras. - Choosing a new language completes the deck's designs (fetching the printing per card) through the app; see Designer-App-Layer.
Details of the model and engine side: Designer-Engine.
flowchart LR
subgraph UI["React: src/features/disenar"]
Form["formulario.tsx, ajustes-finos.tsx"]
Prev["vista-previa.tsx, arte-en-vivo.tsx"]
end
subgraph App["Tauri app: src-tauri/src/diseno + commands/diseno.rs"]
Draft["borrador.rs: Scryfall to CardData"]
Res["recursos.rs: fonts, symbols, packs, per-thread Motor"]
Paint["pintar.rs / vista.rs: efectivo + huella + render"]
end
subgraph Core["proxy-core::diseno (pure)"]
Eff["efectivo"]
Rend["render + componer"]
Txt["texto + pintar + simbolos"]
PackM["pack"]
end
Form -- "call()" --> Draft
Prev -- "pbimg://.../d/huella/ancho" --> Paint
Draft --> Eff
Res --> Rend
Paint --> Eff --> Rend --> Txt
Rend --> PackM
Paint --> PDF["PDF export (Imaging-and-PDF)"]
-
Data: a designed card is an ordinary
DeckCardwithdesign: Some(CardDesign).CardDesign::blobs()lists every image the design depends on (art, "crop from card" source, own emblem, the pre-design image, a custom back) so the app can protect them from blob garbage collection (blob_refs; see Storage-and-Blobs).beforestores what is needed to undo the design (BeforeDesign: source, front, back, edits). -
Preview: the UI asks for an image by hash and width through the
pbimg://protocol (…/d/<huella>/<ancho>); the app resolves the hash to a registered effective design and renders it (src-tauri/src/diseno/vista.rs). See Live-Preview. -
Artwork dragging does not wait for Rust: the app paints the card once without the artwork (
render_capa→CapaVista; on composed cards the whole card with that part's window transparent and its rotation) and React places the artwork underneath with the same maths aspintar_arte. See Designer-UI. - PDF: the exporter paints each designed card from the design at the requested size and DPI instead of embedding a bitmap. See Imaging-and-PDF and Export.
-
Big cards (planes, schemes, Vanguard) get their own sheets in the
PagePlan, because their size is 88.9 x 127 mm (medida_grande). See Layout-Engine and Designer-Composition. -
Resources (Scryfall symbol SVGs, imported fonts, frame packs) live under
app_data_dir(simbolos/,fuentes/,marcos/<id>/,letras-cjk/) and are loaded bysrc-tauri/src/diseno/recursos.rs. Their combined fingerprint (Instantanea.huella) enters the render hash. Details in Designer-App-Layer.
| Module | Responsibility | Page |
|---|---|---|
modelo.rs |
CardDesign, CardData, Collection, FineTuning, Aviso, constants |
this page |
efectivo.rs |
efectivo, DisenoEfectivo, huella
|
this page |
borrador.rs |
pure text helpers: name substitution in rules, effective name/rules, footer numbering | Designer-Engine |
letras.rs |
font catalogue (Letras) with no system fonts; Latin/Cyrillic and CJK fallbacks |
Designer-Engine |
texto.rs |
rich text, shaping/layout with parley, fit-to-box |
Designer-Engine |
pintar.rs |
glyph outlines to pixels with tiny-skia
|
Designer-Engine |
simbolos.rs |
mana symbol SVGs and pack-own symbols | Designer-Engine |
render.rs |
paints one face on any canvas size (pintar_cara) |
Designer-Engine |
pack.rs |
frame pack format, validation, builtin/Original definitions | Frame-Packs |
componer.rs |
split, aftermath, flip, battle, meld, big cards | Designer-Composition |
maquetacion.rs |
Scryfall layout to Maquetacion / Cuerpo
|
Designer-Composition |
estructura.rs |
parses rules text into abilities, chapters, levels (level words and headers in every printed language), and the dungeon floor plan | Designer-Composition |
textos_motor.rs |
fixed words the engine paints per design language (level word, Fuse reminder, footer language code) | Designer-Engine |
tipos_idiomas.rs |
offline type lines in the languages other than Spanish | Designer-Engine |
emoji.rs |
colour emoji (Noto COLRv1) painting | Designer-Engine |
cuerpos.rs |
section bodies with badges, adventure columns, dungeon map | Designer-Composition |
Public error type: DisenoError (LetraNoValida, SimboloNoValido, Lienzo). Constants: VERSION_DISENO (1, format of CardDesign), VERSION_MOTOR (engine version), MM_POR_PT.
proxy-core stays pure (no Tauri, no network, no database), which is why the engine receives images through the Imagenes trait and symbols/fonts/packs as already-loaded values.
| Goal | Start at |
|---|---|
| A new field on a design |
modelo.rs (add with #[serde(default, skip_serializing_if…)]), then efectivo.rs if it can be inherited from a collection, then render.rs; run pnpm bindings. |
| A new card layout |
maquetacion.rs::maquetacion, componer.rs::render, the pack layouts, and a case in tests/layouts.rs. See Designer-Composition. |
| A new frame look | a pack, not code. See Frame-Packs. |
| Pixels changed on purpose | bump VERSION_MOTOR and refresh the golden images. |
Use and build · Uso y desarrollo
- User-Guide · Guía de usuario
- Development-Setup · Preparación del entorno de desarrollo
- Testing-and-CI · Tests y CI
- Releases-and-Auto-Updates · Versiones y actualizaciones automáticas
- Conventions · Convenciones
Architecture · Arquitectura
- Architecture · Arquitectura
- IPC-and-Error-Handling · IPC y manejo de errores
- Rust-App-Layer · Capa de aplicación Rust
Data · Datos
- Deck-Model · Modelo del mazo
- Storage-and-Blobs · Almacenamiento y blobs
- Deck-Package-Format · Formato del paquete de mazo
- Deck-Library · Biblioteca de mazos
Getting cards · Conseguir cartas
- Decklist-Parsing · Parseo de la lista del mazo
- Scryfall-Integration · Integración con Scryfall
- External-Sources · Fuentes externas
- Card-Identification-and-OCR · Identificación de cartas y OCR
Layout, images, PDF · Maquetación, imagen y PDF
- Layout-Engine · Motor de maquetación
- Imaging-and-PDF · Imagen y PDF
- Export · Exportación
- Live-Preview · Vista previa en vivo
- Statistics · Estadísticas
Card designer · Diseñador de cartas
- Designer-Overview · Visión general del diseñador
- Designer-Engine · Motor del diseñador
- Designer-Composition · Composición del diseñador
- Frame-Packs · Packs de marcos
- Designer-App-Layer · Capa de aplicación del diseñador
- Designer-UI · Interfaz del diseñador
- Card-Editor-Dialog · Diálogo Editar carta
Game table · Mesa de juego
Frontend · Frontend
- Frontend-Architecture · Arquitectura del frontend
- State-Stores · Stores de estado
- Internationalization · Internacionalización
- Themes-and-Skins · Temas y skins
- Settings-and-Shell · Ajustes y shell
- Tutorials · Tutoriales
- Decklist-Panel · Panel de la lista del mazo
- Add-Card-and-Variants · Añadir carta y variantes
- Set-Browser · Explorador de sets
Reference · Referencia