139 / Input, output and data

A PDF that traces a pre-printed form

An ERP fills forms that the business already prints on paper with a frame, shaded headings and side labels, and labels set in Courier. The program has to draw on top of what the paper already carries. This is the second phase of std/pdf, which writes a real PDF in pure Nyx.

139-pdf-preimpreso.nxSource →
// Nyx by Example: un documento que calca una hoja preimpresa (std/pdf, fase 2).
//
// El caso real: un ERP llena formularios que el negocio ya imprime en papel con
// marco, encabezados sombreados y rótulos laterales; y etiquetas con texto en
// Courier. Hace falta dibujar encima lo mismo que trae el papel.
//
// Lo que muestra:
//   - el grosor de línea (d.line_width, en mm): marco grueso, divisiones finas;
//   - sombreado en gris (d.gray) y en color (d.color): el encabezado de una
//     tabla, y la vuelta a negro para escribir encima;
//   - las 12 fuentes de texto estándar: Times y Courier en negrita y cursiva;
//   - texto girado (d.text_rotated) para el rótulo del margen: 90 grados sube
//     por el borde izquierdo, 270 baja;
//   - que el estado gráfico vale PARA LO QUE SE DIBUJA DESPUÉS en esa página, y
//     cada página nueva arranca otra vez en negro con el trazo de 1 pt.
//
// Correr: nyx run -- preimpreso.pdf   (sin argumento solo informa el tamaño)

import "std/pdf"
import "std/error"

fn preimpreso() -> Result<String, Error> {
    var d: Pdf = pdf_new(PdfPageSize.Letter, 0.0, 0.0)

    // Marco exterior grueso (0,6 mm) y divisiones finas (0,15 mm).
    d.line_width(0.6)
    d.rect(15.0, 15.0, 185.9, 120.0, false)
    d.line_width(0.15)
    d.line(15.0, 45.0, 200.9, 45.0)
    d.line(15.0, 55.0, 200.9, 55.0)
    d.line(110.0, 45.0, 110.0, 135.0)

    // Encabezado sombreado en gris claro: se rellena primero y el texto va encima,
    // en negro (el relleno y el texto comparten color, así que se vuelve a negro).
    d.gray(0.85)
    d.rect(15.3, 45.3, 185.3, 9.4, true)
    d.gray(0.0)
    d.text(18.0, 51.5, "Concepto", PdfFont.HelveticaBold, 10.0)
    d.text(112.0, 51.5, "Observación", PdfFont.HelveticaBoldOblique, 10.0)

    // Título en Times, con un filete de color debajo.
    d.text(18.0, 28.0, "Orden de entrega", PdfFont.TimesBold, 20.0)
    d.text(18.0, 38.0, "Original para el cliente", PdfFont.TimesItalic, 11.0)
    d.color(0, 90, 160)
    d.line_width(0.8)
    d.line(18.0, 31.0, 90.0, 31.0)
    d.color(0, 0, 0)

    // Filas con Courier: ancho fijo, las columnas de cifras quedan alineadas.
    d.line_width(0.15)
    d.text(18.0, 63.0, "0001  Tornillo 1/4 x 2", PdfFont.Courier, 10.0)
    d.text(18.0, 69.0, "0002  Tuerca hex 1/4", PdfFont.Courier, 10.0)
    d.text(18.0, 75.0, "0003  Arandela plana", PdfFont.CourierOblique, 10.0)
    d.text(18.0, 81.0, "TOTAL 3 renglones", PdfFont.CourierBold, 10.0)
    d.text(112.0, 63.0, "Entregar en almacén", PdfFont.TimesRoman, 10.0)
    d.text(112.0, 69.0, "Frágil", PdfFont.TimesBoldItalic, 10.0)

    // Rótulo del margen izquierdo, girado 90 grados: arranca abajo (y = 130) y
    // sube. En el margen derecho, el mismo texto girado 270 baja.
    d.text_rotated(10.0, 130.0, "ORIGINAL - CLIENTE", PdfFont.Helvetica, 8.0, 90.0)
    d.text_rotated(206.0, 20.0, "ORIGINAL - CLIENTE", PdfFont.Helvetica, 8.0, 270.0)

    // Segunda página: vuelve a empezar en negro con el trazo por omisión.
    d.add_page(PdfPageSize.Letter, 0.0, 0.0)
    d.text(18.0, 25.0, "Copia sin sombreado", PdfFont.CourierBoldOblique, 12.0)
    d.line(18.0, 28.0, 120.0, 28.0)

    return d.bytes()
}

fn main() -> int {
    match preimpreso() {
        Result.Ok(pdf) => {
            let args: Array = get_args()
            if args.length() > 1 {
                let ruta: String = args[1]
                write_file(ruta, pdf)
                print("preimpreso escrito en " + ruta + " (" + int_to_string(pdf.length()) + " bytes)")
            } else {
                print("preimpreso: " + int_to_string(pdf.length()) + " bytes, empieza con " + pdf.substring(0, 8))
                print("para escribirlo: nyx run -- preimpreso.pdf")
            }
            return 0
        }
        Result.Err(e) => {
            print("no se pudo armar el preimpreso: " + error_to_string(e))
            return 1
        }
    }
}
Outputstdout
preimpreso: 13458 bytes, empieza con %PDF-1.4
para escribirlo: nyx run -- preimpreso.pdf

How it works

The recipe uses line width (d.line_width, in mm) for a thick frame and thin dividers; gray (d.gray) and color (d.color) fills for a table header, followed by a return to black to write on top; all 12 standard text fonts, with Times and Courier in bold and italic; and rotated text (d.text_rotated) for the margin label — 90 degrees climbs the left edge, 270 descends.

The graphic state applies to WHAT IS DRAWN AFTER it on that page, and every new page starts again in black with the 1 pt stroke. Run it with nyx run -- preimpreso.pdf to write the file; with no argument it only reports the size and the %PDF-1.4 header.