DEV Community

Retro-imprimiendo

Ya he escrito varias veces sobre mis (des)aventuras tratando de utilizar una impresora con mi Sinclair Spectrum 128k.

Bien, pero... ¿cómo se imprimía en papel en aquella época? ¿Se puede seguir imprimiendo así? Bueno... Es una historia muy larga. ¿Alguna vez te has fijado en los códigos ASCII por debajo del espacio en blanco (el #32)?

DEC HEX Símbolo Descripción
0 0 NUL Carácter Nulo
1 1 SOH Inicio de Encabezado
2 2 STX Inicio de Texto
3 3 ETX Fin de Texto
4 4 EOT Fin de Transmisión
5 5 ENQ Consulta
6 6 ACK Acuse de recibo
7 7 BEL Timbre
8 8 BS Retroceso (borrar)
9 9 HT Tabulación
10 0A LF Salto de línea
11 0B VT Tabulación Vertical
12 0C FF Avance de página
13 0D CR Retorno de carro
14 0E SO Desactivar mayúsculas
15 0F SI Activar mayúsculas
16 10 DLE Escape enlace datos
17 11 DC1 Ctrl. dispositivo 1
18 12 DC2 Ctrl. dispositivo 2
19 13 DC3 Ctrl. dispositivo 3
20 14 DC4 Ctrl. dispositivo 4
21 15 NAK ACK negativo
22 16 SYN Síncronía en espera
23 17 ETB Fin bloque
24 18 CAN Cancelar
25 19 Fin del medio
26 1A Substitución
27 1B ESC Escape
28 1C FS Separador de archivos
29 1D GS Separador de grupo
30 1E RS Separador de registro
31 1F US Separador de unidad

Los primeros ordendores no tenían pantallas, sino que se comunicaban con el usuario a través de una impresora, que funcionaba como una especie de teletipo. La conexión se realizaba a través de la interfaz serie RS-232. Es cierto; no existía USB en aquella época. Esta conexión serie es la responsable de la necesidad de códigos de caracteres como #4 (fin de la transmisión), #6 (ACK o acuse de recibo).

Para el manejo de la impresora son los códigos #10 (salto de línea), #13 (retorno de carro), o #7 (la campana). ¿En qué se inspiraron para estas impresoras? Pues en las máquinas de escribir de la época.

Mención aparte merecen los códigos #13 y #10. Si vemos un archivo de texto en Windows o MS-DOS con un visor hexadecimal, veremos cada cambio de línea representado con los códigos 0A0D, es decir LF y CR. Y es que, aquellas impresoras funcionaban con un carro que llevaba una cabecera de matriz de puntos (de ahí que se las llamara impresoras matriciales o dot matrix printers). Y, sí, en este tipo de impresoras no solo era necesario hacer avanzar el papel (cosa que se hacía con un rodillo, como en las máquinas de escribir, solo que automatizado con un motor), sino también llevar el carro hacia la izquierda. ¿Por qué no es así en UNIX/LINUX? Los desarrolladores decidieron que dedicar dos caracteres por cada cambio de línea era excesivo, una vez descartados aquellos teletipos y asumidos los terminales, así que lo simplificaron a solo LF.

Por cierto, es importante tener en cuenta que los terminales no eran ordenadores, sino solo proporcionaban una pantalla para ver la salida del ordenador, y un teclado integrado para introducir datos en él.

Hoy por hoy, la necesidad de un protocolo serie ha sida eliminada de cuajo al tener USB, que permite conexiones y desconexiones en caliente, mucha más velocidad, y otras ventajas, como la autoconfiguración, también llamada Plug & Play. Además las impresoras ya no utilizan estos caracteres especiales para saber cómo imprimir texto, sino que utilizan PostScript, por lo que, hoy por hoy, estos 31 caracteres (con algunas excepciones, como el #27 (escape), o el #8 (borrado a la izquierda), no se utilizan.

Así, imprimir un archivo podía hacerse tan fácil como volcar un archivo en un archivo "especial". Y es que, todos los sistemas operativos actuales siguen la metáfora todo es un archivo. Es decir, en lugar de tratar cada dispositivo como un caso especial, se trata de obtener datos de él haciendo como que se lee de un archivo, y enviarle datos haciendo como que se escribe en un archivo. Todos los programadores sabemos que, en un programa en C, los descriptores de archivo 0 y 1 se corresponden, respectivamente, con el teclado y la pantalla. Es decir, el archivo #0 es un archivo de solo lectura, y leyendo de él se obtiene la entrada del usuario. El archivo #1 es de solo escritura, y escribiendo en él se muestra texto en la pantalla del usuario.

Muchos dispositivos requieren sin embargo un manejo más detallado, para lo que existe la llamada al sistema operativo IOCTL, que es capaz de enviar códigos y datos de manera especial para acceder a características de bajo nivel.

En cualquier caso, en Windows y en MS-DOS se puede imprimir un archivo de texto archivo.txt en la impresora por defecto utilizando:

$ type archivo.txt > prn
Enter fullscreen mode Exit fullscreen mode

Sí, PRN es un archivo especial que representa a la impresora, y es la razón por la que no se puede crear un archivo de nombre prn en MS-DOS o Windows.

En UNIX/LINUX, el comando es ligeramente distinto, pero el resultado es el mismo.

$ cat archivo.txt > /dev/lp0
Enter fullscreen mode Exit fullscreen mode

El comando cat vuelva el archivo, igual que type en MS_DOS o Windows. El carácter > indica que la salida del anterior comando se va a redirigir, de la pantalla a otro dispositivo. PRN es el dispositivo de la impresora en MS_DOS/Windows, mientras que en UNIX solía ser /dev/lp0, pero en tiempos modernos es /dev/usb/lp0.

He escrito print.py, una pequeña utilidad que permite volcar datos en texto plano directamente a la impresora por defecto. Admite algunos códigos, como los siguientes:

^[  : chr(s) 27
^v  : chr(s) 12
^l  : chr(s) 10
^r  : chr(s) 13
Enter fullscreen mode Exit fullscreen mode

Si quieremos enviar un texto a la impresora, podemos hacer lo siguiente:

$ python print.py --send "hola!^v"
Enter fullscreen mode Exit fullscreen mode

El comando anterior vuelca el mensaje hola en la impresora, que lo imprime, y a continuación manda el código ^v, que se corresponde con el carácter #12, y realiza un FF (form feed), o alimentación de página, lo que hará que salga el papel.

Si preferimos imprimir un archivo de texto, podemos hacerlo con:

$ python print.py --file datos.txt
Enter fullscreen mode Exit fullscreen mode

¿Cuál es el corazón del programa? Pues nada, abrir un archivo en modo texto y escribir en él.

class Printer:
    # más cosas...

    @property
    def device(self):
        return self.__dev
    ...

    def send(self, data: str):
        """Sends new data to the printer.
            :param data: the data to send to the printer, as str.
        """
        with open(self.device, "wt") as dev:
            dev.write(data)
        ...
    ...

    def sendfile(self, filename: str):
        """Sends a whole file to the printer.
            :param filename: the path of the file to print.
        """
        with open(filename, "rt") as f:
            for l in f:
                self.send(l.rstrip())
                self.send(interpret_data("^r^l"))
            ...
        ...

        self.send(interpret_data("^v"))
    ...
Enter fullscreen mode Exit fullscreen mode

Para terminar... ¿se puede comprar hoy en día una impresora matricial? La respuesta es sí: desde impresoras "normales" hasta impresoras de tickets para comercios.

Continuará.

Top comments (0)