Repository navigation
Exportación
English · Español
Exportar convierte el mazo en pantalla en uno o varios PDF listos para imprimir, y también exporta el mazo como lista de texto (Moxfield, Arena, MTGO, texto plano) o como paquete .pbdeck. La exportación a PDF es un trabajo en segundo plano largo, paralelo y cancelable, con eventos de progreso, que nunca deja un fichero a medio escribir con el nombre definitivo.
Código fuente: src-tauri/src/commands/export.rs (trabajo), src-tauri/src/jobs.rs (ids y banderas de cancelación), src/features/export/ (panel derecho, alineación de filas, presets, menú de «más formas de exportar»), src/lib/trabajos.ts y src/stores/trabajo-store.ts (la fontanería del trabajo en la pantalla), src-tauri/crates/proxy-core/src/decklist/exportar.rs (texto de la lista). Los ajustes viven en PrintSettings (src-tauri/crates/proxy-core/src/model.rs).

Comandos de Tauri (registrados en src-tauri/src/lib.rs):
| Comando | Dónde | Qué hace |
|---|---|---|
export_pdf(deck, path) -> u32 |
commands/export.rs |
Inicia el trabajo en una tarea bloqueante y devuelve el job_id de inmediato. Progreso en export://progress, resultado en export://done. |
export_cancel(job_id) -> bool |
igual | Activa la bandera de cancelación del trabajo (Jobs::cancelar); false si el trabajo ya no existe. |
export_estimate(deck) -> ExportEstimate { bytes, images, pages } |
igual | Tamaño aproximado del PDF antes de exportar (véase más abajo). |
card_export_image(card, back, settings, deck_back, path) -> String |
commands/imagen.rs |
Guarda una cara de una carta como imagen (clic derecho → «Exportar imagen»): la misma imagen que llevaría el PDF (retoques, oscurecido, cartas diseñadas pintadas por el motor) a los PPP de la calidad del PDF y sin sangrado. El formato sale de la extensión (png, jpg, webp, tif, bmp; ninguna → PNG; otra → InvalidInput). El reverso es el propio de la carta, la otra cara de una DFC o el del deck. |
export_calibration(settings, path, texts) -> String |
igual | Escribe la hoja de calibración duplex (Imagen y PDF). |
export_open(path), export_reveal(path)
|
igual | Abren un fichero exportado con el visor del sistema / lo muestran en su carpeta. Solo se aceptan ficheros .pdf y .pbdeck que existan (exportado()); si no, InvalidInput. |
deck_list_text(deck, format) -> String |
commands/decklist.rs |
El mazo como texto de lista para copiar. |
deck_list_save(deck, format, path) |
igual | Escribe la lista en un .txt (la ruta debe ser absoluta y acabar en .txt). |
deck_export_package(id, path) -> u64 |
commands/library.rs |
Guarda el .pbdeck a partir del mazo almacenado (Formato del paquete de mazo). |
preset_list / preset_save / preset_delete |
commands/library.rs, store/presets.rs
|
Presets de impresión con nombre (véase «Presets» más abajo). |
Eventos (tipos en src/bindings/, declarados en src/lib/ipc.ts):
-
export://progress:ExportProgressEvent { jobId, phase: "prepare" | "compose" | "write", done, total }. -
export://done:ExportDoneEvent { jobId, result: ExportResult | null, error: AppError | null }.ExportResult { paths, pages, sheets, bytes }.
sequenceDiagram
participant UI as export-panel.tsx
participant A as acciones.ts (exportarA)
participant T as lanzarTrabajo
participant R as export_pdf (Rust)
participant J as exportar() worker
UI->>A: pedirDestino (diálogo de guardar)
UI->>R: export_estimate(deck)
alt estimación > 500 MB
UI->>UI: diálogo de aviso (bajar a JPEG 90 / 300 dpi / exportar igualmente)
end
A->>T: lanzarTrabajo("export://progress","export://done", call export_pdf)
T->>T: primero escucha, luego lanza
T->>R: export_pdf(instantánea del mazo, ruta)
R-->>T: job_id
R->>J: spawn_blocking
J-->>UI: export://progress (prepare n/total)
J-->>UI: export://progress (compose página/total)
J-->>UI: export://progress (write)
J-->>T: export://done
T-->>A: resultado o error
A-->>UI: aviso con Abrir / Mostrar en carpeta
-
pedirDestinopregunta dónde guardar medianteguardarParaExportardesrc/lib/dialogos.ts(que recuerda en Rust la última carpeta de exportación). Nombre por defecto:nombreSeguro(deck.name, "pdf"), que sustituye los caracteres que rechazan los sistemas de ficheros. -
export-panel.tsxllama aexport_estimate; por encima deAVISO_BYTES(500 MiB) muestra un diálogo con tres opciones: «Bajar a JPEG 90», «Bajar a 300 PPP» (ambas cambian los ajustes del mazo y luego exportan) o «Exportar igualmente». -
exportarA(destino, t)lee la instantánea del mazo del store del mazo y lanza{ code: "Busy" }siuseTrabajoya tiene un trabajo. Marca el trabajo como iniciado y luego llama alanzarTrabajo. - Al terminar: un error
Cancelledmuestra «Exportación cancelada»; cualquier otro error se lanza (quien llama muestramensaje(e)en un aviso); si va bien, se muestra un aviso (toast) con el tamaño, la ruta, «Abrir» (export_open) y «Mostrar en carpeta» (export_reveal).
Decisión: el mazo que se envía es la instantánea en memoria, no lo que hay en la base de datos. Motivo: el autoguardado puede ir con un retraso de hasta unos 800 ms, y quien cambia un ajuste y exporta enseguida debe obtener ese ajuste.
lanzarTrabajo (src/lib/trabajos.ts): se suscribe a los eventos de progreso y final ANTES de invocar el comando, almacena en un búfer lo que llegue antes de conocerse el job_id y luego lo reproduce filtrado por jobId. Motivo: un trabajo corto puede acabar antes de que se resuelva la promesa de invoke; suscribirse después perdería el evento final. useTrabajo (trabajo-store.ts) guarda el único trabajo en marcha (tipo: "resolver" | "exportar", jobId, fase, hechas, total); el panel muestra un spinner, «n/total», una barra Progress y un botón de cancelar (cancelarTrabajo en src/features/decklist/acciones.ts llama a export_cancel en las exportaciones). Etiquetas de fase: el mapa FASES (prepare, compose, write a «Preparando imágenes…», «Componiendo páginas…», «Escribiendo el PDF…»).
export_pdf registra un trabajo (state.jobs.alta() -> (job_id, Arc<AtomicBool>)), marca la app como ocupada con state.trabajo() (así, por ejemplo, el actualizador automático no reiniciará a mitad de una exportación, véase Versiones y actualizaciones automáticas), ejecuta exportar(...) en tauri::async_runtime::spawn_blocking dentro de catch_unwind, siempre elimina el trabajo (jobs.baja) y emite export://done con el resultado o con el AppError (un pánico pasa a Internal { detalle: "pánico" }). Motivo: sin export://done la pantalla se queda para siempre con el último progreso y «Cancelar» no tiene nada que parar.
exportar(state, deck, destino, cancelado, progreso) hace:
-
Plan.
plan(deck)(la misma llamada que usa la vista previa; Motor de maquetación). -
Diseños.
DisenosDelDeck::de(state, deck)(src-tauri/src/diseno/pintar.rs) calcula una sola vez por trabajo el diseño efectivo de cada carta diseñada (carta > colección > fábrica). -
Hoja de pedido (
encargo). Para cada casilla ocupada de cada página, construye unRenderSpecconrender::spec_de_casilla. Si la carta tiene diseño,DisenosDelDeck::referenciada unDisenoRef { huella, cara, lang }(qué cara del diseño muestra esta casilla: el anverso, o la cara 1 de una carta diseñada de doble cara como reverso o como carta aparte; véasecara_de_casilla), se vacían loseditsyedgede la especificación (las cartas diseñadas no reciben retoques ni oscurecido de bordes) y se rellenadiseno. Las especificaciones se indexan porRenderSpec::key(), así que las casillas idénticas (p. ej. 4 copias de la misma carta, o el mismo reverso en cada hoja) se renderizan una sola vez.por_casillaasocia(page, slot)con la clave. -
Preparar. Un pool de rayon de
min(available_parallelism, 8)hilos renderiza cada especificación distinta:render_para_pdfpara las imágenes normales, orender_diseno_para_pdfpara las diseñadas (pintar(...)conSalida::Impresional tamaño exacto de corte en píxeles para los dpi elegidos, y luegorender_desde_imagenpara el sangrado y la codificación). Cada tarea comprueba primero la bandera de cancelación (devuelveErrorCode::Cancelled) y emite progresoPrepare(done, total de imágenes distintas). -
Imágenes para krilla. Los bytes renderizados pasan a ser
krilla::image::Image(from_jpegofrom_png). -
Componer y escribir, por cada trozo de salida (véase la división):
compose(&sub_plan, &cutGuides, PdfMeta{title: deck.name, document_id: "<hash>-<chunk>"}, image_for_slot, page_done).page_doneemite progresoComposey devuelve!cancelled. Tras componer un trozo se emite progresoWriteyescribir_atomicolo escribe.
document_id sale de huella_documento: SHA-256 del plan serializado más las claves casilla-especificación ordenadas, primeros 16 bytes en hex. El mismo mazo con los mismos ajustes da PDF idénticos byte a byte.
- Cancelar activa el
AtomicBoolcompartido; las tareas de preparación y la retrollamada por página decomposelo observan, y el trabajo devuelvePdfError::Cancelled/ErrorCode::Cancelled, que la pantalla trata como un resultado normal de «cancelado», no como un fallo. -
escribir_atomico(src-tauri/src/store/blobs.rs) escribe en<name>.<ext>.part, hacesync_ally luego renombra sobre el destino. Una exportación cancelada o fallida nunca deja, por tanto, un fichero a medio escribir con el nombre real (testcancelar_no_toca_el_destino). - Trampa: los contadores de progreso no son perfectamente uniformes.
Preparecuenta imágenes distintas,Composecuenta páginas de todos los trozos,Writees el índice del trozo (t) sobre el número de trozos, así que nunca llega al 100 % antes dedone.
PrintSettings.splitEvery (Option<u32>, en la interfaz «Partir cada» N «hojas»; vacío significa un solo fichero). trozos(plan, n) agrupa las páginas por page.sheet / n, de modo que el anverso y el reverso de una hoja siempre quedan en el mismo fichero. Nombres de salida: ruta_trozo conserva la ruta elegida cuando hay un solo fichero; si no, <stem> (1).pdf, <stem> (2).pdf... junto a ella. ExportResult.paths lista todos los ficheros escritos. Test partir_cada_dos_hojas. Una cancelación tras escribir algunos trozos deja esos trozos anteriores (cada uno se escribió de forma atómica); Deducido de la estructura del bucle en exportar.
export_estimate vuelve a ejecutar plan y encargo(.., None) (sin referencias de diseño, de modo que las cartas diseñadas se cuentan por su blob de miniatura guardado) y multiplica pdf::estimate::bytes_por_imagen(...) por el número de especificaciones distintas. Es una aproximación, usada solo para avisar por encima de 500 MiB. Test la_estimacion_cuenta_imagenes_distintas.
Cada control escribe en deck.settings mediante useDeck.setAjustes, de modo que cada cambio se guarda con el mazo, se puede deshacer con Ctrl+Z y dispara un recálculo del plan (100 ms de espera, Motor de maquetación). Secciones:
| Sección | Controles | Campos de ajustes |
|---|---|---|
| Calidad 300/600/900, formato de imagen JPEG/PNG, calidad JPEG (60-100), partir cada N hojas |
quality, encoding {format, jpegQuality}, splitEvery
|
|
| Doble cara | Apagado / dúplex, intercalar pares de DFC, reverso de las cartas grandes (solo cuando el plan tiene bigGrid), hoja de calibración |
duplex, interleaveDfc, bigCardsBack
|
| Página | Papel, apaisado, tamaño de carta (63 x 88, 63,5 x 88,9 o 65 x 90 mm, con un texto de ayuda sobre el último), margen mínimo |
paper, landscape, cardSize, minMarginMm
|
| Separación y sangrado | Separación, sangrado (0-5 mm), relleno del sangrado (repetir el borde / espejo), rellenar las esquinas, sangrado en los reversos |
gapMm, bleedMm, bleedMode, fillCorners, bleedOnBacks
|
| Guías de corte | Dibujar guías, color, grosor (px), separación del borde, longitud | cutGuides {enabled, color, widthPx, offsetMm, lengthMm} |
| Oscurecer bordes (plegada) | Modo, cantidad, ancho de la banda, detección automática, aplicar a Scryfall / imágenes propias / reversos | edgeDarkening |
| Alinear filas (plegada) | Desplazamientos X/Y por fila de anverso/reverso, medir y corregir | rowOffsets |
| Presets de impresión (plegada) | Guardar/cargar conjuntos con nombre | todo PrintSettings
|
Una nota al pie recuerda al usuario que imprima al 100 % (sin «ajustar a la página») para que las medidas salgan exactas.
| Ajuste | Valor por defecto | Justificación |
|---|---|---|
| paper / landscape | A4 / vertical | El caso común. |
| cardSize | 63 x 88 mm | Tamaño oficial; la opción de 63,5 x 88,9 es el tamaño poker de 2,5 x 3,5 pulgadas, y 65 x 90 llena una funda (abajo). |
| minMarginMm | 5 | Borde no imprimible típico; el margen efectivo es max(min, bleed). |
| gapMm | 2 | Una separación en blanco, como en la herramienta de referencia: cada carta conserva su tamaño exacto (cabe en fundas) y el sangrado no cubre la separación. |
| bleedMm | 0 | Separación en lugar de sangrado por defecto; el sangrado sigue disponible. |
| bleedOnBacks | encendido | Los reversos coinciden con los anversos cuando se usa sangrado. |
| fillCorners | apagado | Las esquinas transparentes se imprimen del color del papel; corners::fill solo cuando el usuario lo activa. |
| cutGuides.enabled | apagado | Hojas limpias por defecto; al activarlas, color #00c853, 0,5 px, separación 0, longitud 5 mm. |
| edgeDarkening | desactivado | Solo para escaneos viejos; cantidad 80, banda 8 %, detección automática activada, se aplica solo a imágenes de Scryfall. |
| duplex | apagado | La mayoría imprime a una cara. |
| quality / encoding | 600 dpi / JPEG 95 | Valor por defecto de calidad de impresión con un tamaño razonable. |
| designLang |
En (mazos guardados sin él: Es) |
Idioma en que se pintan las cartas diseñadas (DesignLang). Cualquiera de los 15 idiomas; se cambia con el selector «Idioma de las cartas diseñadas» (SelectorIdioma, bandera y nombre propio) de la sección PDF del panel de exportar (y con el de la vista previa de Diseñar); un preset de impresión no lo lleva. El cambio pasa por ponerIdiomaDiseno (src/features/disenar/renumerar.ts): en un idioma que no sea el español ni el inglés lanza antes completarIdioma (trabajo design_complete_lang: baja la letra de japonés, coreano o chino si hace falta y la impresión de cada carta diseñada a la que le falte ese idioma) y luego rehace las miniaturas de las cartas diseñadas (design_rerender), porque la hoja enseña esas. Véase Capa de aplicación del diseñador. |
El test los_ajustes_por_defecto_son_los_de_fabricard de model.rs fija varios de ellos.
El selector ofrece tres constantes CardSize de src-tauri/crates/proxy-core/src/model.rs (el mapa TAMANOS del panel, que se reconoce de vuelta por widthMm):
| Opción | Constante | Uso |
|---|---|---|
| 63 x 88 mm |
CardSize::MTG (por defecto) |
El tamaño que publica Wizards. |
| 63,5 x 88,9 mm | CardSize::POKER |
2,5 x 3,5 pulgadas. |
| 65 x 90 mm | CardSize::FUNDA |
Llena justa una funda estándar. |
Por qué 65 x 90: ocupa lo mismo que 63 x 88 impreso con 1 mm de sangrado, pero con la imagen entera a ese tamaño en vez de con su borde repetido o en espejo, que es donde el sangrado enseña sus defectos (el borde repetido de un escaneo, una franja en espejo). Lo dice el texto de ayuda del selector («65 × 90 mm llena justa una funda: es como 1 mm de sangrado, pero con la imagen entera a ese tamaño en vez del borde repetido.»). La imagen se estira a la casilla como cualquier otra (imaging::frame, véase Imagen y PDF): la proporción de 65 x 90 se aparta de la de 63 x 88 menos de un 1 %. Las cartas diseñadas se pintan a ese tamaño desde su diseño (la geometría del marco se estira lo mismo; véase Motor del diseñador).
Cuántas caben con la separación de 2 mm y el margen mínimo de 5 mm de fábrica (Motor de maquetación):
- A4 vertical: siguen siendo 3 x 3 (test
a4_vertical_a_la_medida_de_la_funda_siguen_siendo_3x3delayout/grid.rs). - US Letter vertical: solo 3 x 2, porque tres filas piden
3 x 90 + 2 x separación + 2 x margen <= 279,4, es decir,margen + separación <= 4,7mm. Bajar el margen mínimo a 2,7 mm (con la separación de 2 mm), o la separación a 0 con un margen de hasta 4,7 mm, vuelve a dar 3 x 3, si la impresora llega a imprimir tan cerca del borde.
El test del PDF la_carta_sale_en_el_pdf_con_su_tamano_en_milimetros (pdf/compose.rs) comprueba los tres tamaños, a una cara y a doble cara.
AlinearFilas muestra una tabla X/Y por fila para «Frente» y «Reverso» (rango -20..20 mm, paso 0,1). Los desplazamientos del reverso se introducen en el marco del ANVERSO, indexados por la fila del anverso; Rust los aplica antes del espejo (Motor de maquetación). «Corregir con lo medido» abre un diálogo donde el usuario teclea, por fila, la desviación leída en la hoja de calibración (derecha y abajo en positivo); aplicar la resta de los desplazamientos de reverso existentes (redondeado a 2 decimales). «Restablecer» vacía ambas listas. exportarCalibracion guarda la hoja de export_calibration, pasando el título y cuatro líneas de instrucciones traducidos (Rust no escribe prosa; véase Internacionalización).
Presets es un control genérico de «cargar / guardar / borrar» sobre preset_list, preset_save, preset_delete, con PresetKind (borrar pregunta antes, «¿Borrar el preset «{n}»?»): frontOffsets, backOffsets (JSON Offset[]) y print (JSON de todo PrintSettings). Viven en la base de datos de la biblioteca local (tabla presets, store/presets.rs), no en el mazo, así que valen para cualquier mazo. Cargar un preset de impresión lo mezcla sobre los ajustes actuales ({ ...actual, ...s }) tras comprobar que tiene un campo paper; un JSON corrupto muestra «Ese preset está dañado». Trampa: como se hace con spread, un preset guardado por una versión anterior carece de los campos más nuevos y conserva para ellos los valores actuales.
Sin ninguna carta de verdad en el deck (los huecos no cuentan) el botón «Exportar PDF» está desactivado y su tooltip dice «El deck está vacío.»: no hay nada que imprimir. El botón va partido: los tres puntos (MenuExportar) ofrecen:
| Elemento | Qué hace |
|---|---|
| Copiar la lista: Para Moxfield / Para Arena / Para MTGO / En texto plano |
deck_list_text(deck, format) y luego portapapeles; si el resultado está vacío, muestra un aviso informativo en su lugar. |
| Descargar para MTGO (.txt) | Diálogo de guardar y luego deck_list_save(deck, "mtgo", path). |
| Deck con sus imágenes (.pbdeck) |
guardarAhora() (vacía el autoguardado), diálogo de guardar y luego deck_export_package(id, path). |
El texto de la lista lo escribe proxy_core::decklist::exportar (FormatoLista::{Moxfield, Arena, Mtgo, Texto}):
- El origen son las cartas del mazo (lo que se imprime), fusionando las copias de la misma impresión (nombre + set + número de coleccionista) por orden de aparición, no el texto pegado.
- Las secciones (comandante, compañero, banquillo) no se guardan por carta, así que se recuperan volviendo a analizar
deck.decklistTexty emparejando por nombre (también por el nombre de la cara frontal en las cartas de doble cara). - Se omiten las fichas (por origen o por línea de tipo), las casillas en blanco y las cartas propias vinculadas a una fuente externa de fichas.
- Marcas:
cuentan(&deck.cards)descarta las cartas opcionales y las variantes de diseño extra; las cartas marcadas como banquillo van al banquillo (sideboard). Véase Estadísticas para la regla decuentan. - Formatos: Moxfield
1 Sol Ring (CMM) 410conSIDEBOARD:; Arena con cabecerasCommander/Companion/Deck/Sideboardy nombres de la cara frontal; MTGO1 Namecon el comandante y el banquillo tras una línea en blanco; texto plano1 Name.
| Situación | Comportamiento |
|---|---|
| La carta no cabe en el papel / ajustes no válidos |
plan falla: AppError InvalidInput con motivo. |
| Falta un blob |
ErrorCode::NotFound de leer_blob. |
| Imagen incorrecta |
ErrorCode::Image (motivo=too_large o detalle). |
| Error al escribir el PDF |
ErrorCode::Pdf con detalle. |
| Cancelar |
ErrorCode::Cancelled, que la interfaz trata como «Exportación cancelada». |
| Hay otro trabajo en marcha | La interfaz lanza Busy antes de llamar a Rust. |
| Diseño perdido entre la planificación y el renderizado |
ErrorCode::Internal («diseno perdido»). |
- Rust: tests de
src-tauri/src/commands/export.rs(exportar_veinte_cartas_en_duplex,exportar_cartas_disenadas,una_dfc_disenada_sale_con_cada_cara_en_su_sitio,un_plano_disenado_sale_grande_en_su_hoja,cancelar_no_toca_el_destino,partir_cada_dos_hojas,la_estimacion_cuenta_imagenes_distintas,la_hoja_de_calibracion_se_escribey un test de principio a fin), tests desrc-tauri/crates/proxy-core/src/decklist/exportar.rs,src-tauri/src/jobs.rs. - Frontend:
src/features/export/__tests__/acciones.test.ts(bus de eventos simulado).
- Nueva opción de impresión: añade el campo a
PrintSettings(con#[serde(default)]si los mazos antiguos deben seguir abriéndose), regenera los bindings, añade un control enexport-panel.tsx, úsalo enlayout::planorender::spec_de_casillay añade la etiqueta asrc/i18n/en/. - Nuevo formato de lista: añade una variante de
FormatoListay una rama enexportar(), y luego una entrada enCOPIARenmenu-exportar.tsx.
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
- Icons · Iconos
- 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