Skip to content

Exportación

Pablo Martínez López edited this page Oct 29, 2026 · 9 revisions

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).

Exportando

Superficie pública

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 }.

Flujo de principio a fin

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
Loading

Lado de la pantalla (src/features/export/acciones.ts)

  1. pedirDestino pregunta dónde guardar mediante guardarParaExportar de src/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.
  2. export-panel.tsx llama a export_estimate; por encima de AVISO_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».
  3. exportarA(destino, t) lee la instantánea del mazo del store del mazo y lanza { code: "Busy" } si useTrabajo ya tiene un trabajo. Marca el trabajo como iniciado y luego llama a lanzarTrabajo.
  4. Al terminar: un error Cancelled muestra «Exportación cancelada»; cualquier otro error se lanza (quien llama muestra mensaje(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…»).

Lado de Rust (exportar)

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:

  1. Plan. plan(deck) (la misma llamada que usa la vista previa; Motor de maquetación).
  2. 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).
  3. Hoja de pedido (encargo). Para cada casilla ocupada de cada página, construye un RenderSpec con render::spec_de_casilla. Si la carta tiene diseño, DisenosDelDeck::referencia da un DisenoRef { 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éase cara_de_casilla), se vacían los edits y edge de la especificación (las cartas diseñadas no reciben retoques ni oscurecido de bordes) y se rellena diseno. Las especificaciones se indexan por RenderSpec::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_casilla asocia (page, slot) con la clave.
  4. Preparar. Un pool de rayon de min(available_parallelism, 8) hilos renderiza cada especificación distinta: render_para_pdf para las imágenes normales, o render_diseno_para_pdf para las diseñadas (pintar(...) con Salida::Impresion al tamaño exacto de corte en píxeles para los dpi elegidos, y luego render_desde_imagen para el sangrado y la codificación). Cada tarea comprueba primero la bandera de cancelación (devuelve ErrorCode::Cancelled) y emite progreso Prepare (done, total de imágenes distintas).
  5. Imágenes para krilla. Los bytes renderizados pasan a ser krilla::image::Image (from_jpeg o from_png).
  6. 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_done emite progreso Compose y devuelve !cancelled. Tras componer un trozo se emite progreso Write y escribir_atomico lo 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.

Cancelación y escrituras atómicas

  • Cancelar activa el AtomicBool compartido; las tareas de preparación y la retrollamada por página de compose lo observan, y el trabajo devuelve PdfError::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, hace sync_all y luego renombra sobre el destino. Una exportación cancelada o fallida nunca deja, por tanto, un fichero a medio escribir con el nombre real (test cancelar_no_toca_el_destino).
  • Trampa: los contadores de progreso no son perfectamente uniformes. Prepare cuenta imágenes distintas, Compose cuenta páginas de todos los trozos, Write es el índice del trozo (t) sobre el número de trozos, así que nunca llega al 100 % antes de done.

División del PDF

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.

Estimación

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.

Panel de ajustes (src/features/export/export-panel.tsx)

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
PDF 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.

Valores de fábrica y su porqué (impl Default for PrintSettings)

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.

Tamaños de carta

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_3x3 de layout/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,7 mm. 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.

Alineación de filas y hoja de calibración (alinear-filas.tsx)

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 (presets.tsx)

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.

Otros formatos de exportación (menu-exportar.tsx)

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.decklistText y 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 de cuentan.
  • Formatos: Moxfield 1 Sol Ring (CMM) 410 con SIDEBOARD:; Arena con cabeceras Commander / Companion / Deck / Sideboard y nombres de la cara frontal; MTGO 1 Name con el comandante y el banquillo tras una línea en blanco; texto plano 1 Name.

Modos de fallo

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»).

Tests

  • 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_escribe y un test de principio a fin), tests de src-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).

Por dónde empezar para ampliarlo

  • 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 en export-panel.tsx, úsalo en layout::plan o render::spec_de_casilla y añade la etiqueta a src/i18n/en/.
  • Nuevo formato de lista: añade una variante de FormatoLista y una rama en exportar(), y luego una entrada en COPIAR en menu-exportar.tsx.

Véase también

Documentation · Documentación

Language: English · Español

Use and build · Uso y desarrollo

Architecture · Arquitectura

Data · Datos

Getting cards · Conseguir cartas

Layout, images, PDF · Maquetación, imagen y PDF

Card designer · Diseñador de cartas

Game table · Mesa de juego

Frontend · Frontend

Reference · Referencia

Clone this wiki locally