El tamaño sí importa

Un poco de contexto

Resulta que estoy haciendo una interfaz gráfica para Neovim. Hay millón, sí, ya sé, pero cada una tiene su quilombo. Y mi idea igual no es hacer la "interfaz gráfica definitiva", sino aprender en el proceso.

<nerd> Vengo aprendiendo un montón, ya que estamos, es re divertido este "proyecto mascota". </nerd>

El escollo o tema más importante que me encontré hasta ahora que no sé cómo resolver (estoy cada vez más convencido de que no hay una "manera correcta" de resolverlo) es cómo manejar los caracteres anchos en una grilla de caracteres monoespaciados.

A ver, vamos por partes (como diría mi amigo Jack).

Estoy haciendo una interfaz gráfica para Neovim. Neovim es un editor de textos muy usado para programar (entre otras muchas funciones) y tiene una interfaz muy limpia originalmente pensada para la terminal. Y las terminales (la mayoría, anywyay) usan tipografías monoespaciadas.

Una tipografía monoespaciada es aquella donde sus caracteres ocupan el mismo ancho. Si estás leyendo esto en mi blog, vas a ver que la tipografía está orientada a la "facilidad de lectura de textos" y los caracteres tienen distinto ancho. Mirá el ancho de las i y las m en la siguiente secuencia: mimimim. Y compará con la siguiente secuencia donde estoy forzando que use una tipografía monoespaciada: mimimim`. La elección para estos ejemplos de la m y la i no es casual, la i es bastante finita, y se considera que la M tiene el ancho completo.

Las tipografías monoespaciadas son muy importantes para programar en casi cualquier lenguaje. Este código "tiene sentido":

def test_levels_assert_ok_exception(logs):
    try:
        raise ValueError("test error")
    except ValueError:
        logger.exception("test message")
    assert "test.message" in logs.error
    assert "ValueError" in logs.error
    assert "test.error" in logs.error

El mismo código en una tipografía de ancho variable queda una porquería (especialmente en Python donde la indentanción importa, pero quedaría igual de feo en cualquier otro lenguaje).

El problema

Todo muy lindo. Pero ahora vamos a la realidad. En el planeta tenemos un montón de caracteres que son más anchos que una M (lo dejo escrito así genérico porque no queremos caernos en un agujero de conejo). Y no sólo caracteres de idiomas escritos. Unicode cubre todo eso pero también, por ejemplo, emoticones o dibujitos de todo tipo.

Vamos al caso de estos 3 caracteres: el "FULL STOP" (o más conocido "punto"), "HEAVY MULTIPLICATION X", y "CJK UNIFIED IDEOGRAPH-6614": . ✖ 昔 -- o en monoespaciado: . ✖  昔.

Los nombre en mayúsculas que puse en la oración anterior son los nombres formales que le da Unicode a esos caracteres. Y en relación a lo que venimos hablando, Unicode nos da también un dato importante: el "ancho inherente" del carácter, un tema para nada trivial, al punto que Unicode tiene todo un anexo al respecto.

Como gran parte de la especificación de Unicode está dentro de Python, podemos ver sencillamente esos valores para los caracteres en cuestión:

>>> unicodedata.east_asian_width(".")
'Na'
>>> unicodedata.east_asian_width("✖")
'N'
>>> unicodedata.east_asian_width("昔")
'W'

¿Y qué significa eso? No voy a entrar en todo el detalle, ahí ya les dejé el Anexo si quieren explorar. Pero básicamente Unicode nos dice que hay caracteres "wide", "fullwidth", "narrow", "halfwidth", "ambiguous", y "neutrals". Un quilombo. Que podemos reducir un poco haciendo una simplificación: tomamos algunos como anchos y otros como angostos.

Volviendo a los tres casos nuestros, el punto es Na (angosto), la cruz pesada es N (neutra), y el ideograma es W (ancho). Y más allá de esas características "formales", se puede notar visualmente que los anchos no son los mismos.

La dificultad real que me llevó a estudiar todo esto, en el contexto de hacer una interfaz gráfica a Neovim, es: ¿cómo meto caracteres anchos en lo que a priori sería una grilla regular? ¿qué hago cuando el carácter ancho me rompe la columna? ¿hay algo que se puede hacer que tenga sentido o que se considere "correcto"?

La exploración

¿Qué hacen otros editores con este bardo?

Los tres que estuve estudiando son Neovim mismo en la terminal, neovim-qt (una interfaz gráfica hecha en Qt que se comporta muy muy parecida a Neovim en la terminal), y Visual Studio Code. Para simplificar, de los dos primeros voy a mostrar sólo a neovim-qt, porque se comportan igual.

Miren lo que hace neovim-qt (o Neovim mismo en la terminal):

Screenshot y ampliado de cómo se comporta neovim-qt

La primer línea tiene al . como referencia; sabemos que es angosto y cómo debería comportarse. En la segunda línea tenemos el ideograma: como Unicode dice que es ancho, ocupa dos espacios; fíjense que el ideograma ocupa todo el ancho del par .P de arriba, y luego la P está encolumnada con la y de arriba. En la tercer línea tenemos la cruz pesada: Unicode dice que es angosta, pero el dibujo ocupa un montón! Mala suerte, en este caso se respeta el ancho, mirá como la P está encolumnada con la P de la primer línea, pero el problema es que el carácter excedido en ancho queda pisado por la letra siguiente.

Por otro lado, esto es lo que hace Visual Studio Code:

Screenshot y ampliado de cómo se comporta VSC

En la primera línea no hay sorpresas. En la segunda vemos que el ideograma tiene el ancho que ocupa, a nivel dibujo, pero luego el espacio contra la P no está exagerado: esto es porque VSCode no hizo que el ideograma ocupara "dos espacios", sino sólo lo que ocupó el dibujo en sí. Esto también lo vemos en la tercer línea, donde más allá que Unicode diga que es un carácter angosto, VSCode dibuja la cruz pesada al ancho que tenga y luego sigue con el resto del texto. Claramente esta forma de renderizar el texto queda más lindo, pero rompe totalmente las columnas: fíjense como se pierde la alineación vertical entre las tres líneas.

¿Hay valor en mantener en lo posible la alineación de las columnas? neovim-qt mismo en algún punto la rompe porque si ponés un carácter en dos espacios, parece todo ordenadito pero realmente las columnas están rotas, aunque no se nota tanto.

Entonces, ¿qué hago?

Por lo pronto, puedo implementar cualquiera de las dos soluciones que vimos recién.

La primera, como nvim-qt, con los anchos que indica Unicode:

Ocupando uno o dos espacios, según su ancho

(tengo esos puntitos azules porque todavía tengo en desarrollo todo lo que es "dibujar la grilla", después los voy a sacar)

Un detalle: a diferencia de nvim-qt, en vez de que el caracter de después tape completamente al anterior, estoy haciendo que los dibujos de los caracteres se superpongan... creo que queda mejor, pero no es definitivo.

La segunda solución, como VSCode, con el tamaño natural de los caracteres que se escapan del ancho angosto:

A lo que ocupe el glifo, si se escapa de lo angosto

Hay una tercera opción, que se le ocurrió a Felipe, que se basa en el ancho real de cada glifo, pero luego ajustando a que ocupe uno o dos espacios según corresponda. Esta tiene la ventaja que todos los caracteres se verán bien (como en VSCode), y que las columnas parecen ordenaditas (como en neovim-qt), aunque sufre el mismo problema de que las columnas no están realmente alineadas.

Acomodando los anchos en cantidad de espacios fijos

Y una cuarta opción también, idea mía: llevar todo todo a un sólo espacio. La ventaja es indiscutible: al tener siempre un carácter por espacio, la grilla queda perfecta a nivel alineación de columnas. Pero los caracteres al achicarse pierden mucho detalle, y creo que al final no es práctico.

Todo a un sólo espacio

Cabe acotar que Neovim espera que la GUI funcione como la primer manera ("unicode"), porque sino se rompen otras cosas que el editor dibuja "alrededor de la grilla del código"; en la siguiente imagen (del segundo caso, "natural") se puede ver qué mal que queda la barra vertical de la derecha que marca 99 columnas, y cómo se desplaza toda la línea que tiene la cruz pesada al principio:

Neovim espera que la grilla se comporte de una manera específica

Si voy a mantener que Neovim haga esos dibujos de alrededor, tengo que hacer que la GUI se comporte sí o sí de la primer manera ("unicode"), pero quizás en un futuro haga yo mismo desde la GUI esos dibujos de "asistencia", lo cual me liberaría a dibujar los anchos como yo quiera (y esos otros dibujos quedarían más elegantes, por ejemplo la línea que marca el límite de columnas que sea una línea, y no un caracter pintado).

Conclusiones

No tengo una decisión tomada. No me parece que haya una forma que sea claramente mejor que el resto. Todas tienen algún problema.

Pero después de todo este análisis, lo próximo que voy a hacer es usar el modo "unicode", el que usa nvim-qt y que es mejor soportado por Neovim, ya que en la primera etapa (al menos) voy a mantener los "dibujos de asistencia de alrededor" hechos por Neovim mismo, así que no quiero romper eso.

Tampoco me queda claro que las otras opciones sean mejores. Neovim eligió ese modo por alguna razón, aunque quizás esa razón no sea la más importante en este momento/contexto (quizás porque Vim hacía lo mismo, o quizás porque la interfaz primaria es la terminal).

VSCode eligió la otra solución, la "natural", pero que queden las columnas levemente desalineadas es horrible. Aunque quizás eso no sea un problema ya que al final les hispanoparlantes usamos normalmente caracteres "angostos", especialmente para programar... pero después metiste un emoji y perdiste.

En fin. Si tienen más info sobre este tema, es bienvenida. ¡Gracias!

Comentarios Imprimir

Películas, cortitas y al pie

Sin mucha ceremonia, y con las series ni voy a meterme, pero hace rato que no actualizaba la lista de películas, así que acá vamos...

Darío Grandinetti, Mercedes Morán, Jorge Marrale.

Eso sí, un montón de nuevas películas anotadas:

  • Caro diario: (1993; Comedia, Drama) Film dividido en tres episodios, que refleja la vida y opiniones de Nanni Moretti. «En mi Vespa» es una aproximación a la vida cotidiana de Roma durante el mes de agosto; en "Islas" visita a Gerardo, un amigo que lleva once años viviendo en Lipari; juntos recorren otras íslas como Salina, Stromboli, Panarea y Alicudi. En "Médicos" el director rueda su propia quimioterapia y su recorrido por hospitales y especialistas incapaces de diagnosticarle la causa de unos insoportables picores. [D: Riccardo Milani, Nanni Moretti; A: Nanni Moretti, Renato Carpentieri, Antonio Neiwiller, Claudia Della Seta, Lorenzo Alessandri]

  • F1 The Movie: (2025; Acción, Drama) El mítico piloto Sonny Hayes vuelve de su retiro, persuadido para liderar un equipo de Fórmula 1 en apuros y guiar a su joven promesa, en busca de una nueva oportunidad de éxito. [D: Joseph Kosinski, Kailyn Dabkowski; A: Brad Pitt, Damson Idris, Kerry Condon, Javier Bardem, Kim Bodnia]

  • Mission: Impossible - The Final Reckoning: (2025; Acción, Aventura, Suspense) El agente Ethan Hunt continúa su misión de impedir que Gabriel controle el tecnológicamente omnipotente programa de IA conocido como "the Entity". [D: Rebecca Sheridan, Christopher McQuarrie; A: Tom Cruise, Hayley Atwell, Ving Rhames, Simon Pegg, Esai Morales]

  • The Gorge: (2025; Romance, Ciencia ficción, Suspense) Mandan a dos operativos de élite a vigilar lados opuestos de un misterioso abismo y allí intiman desde la distancia, pero deberán aunar fuerzas para sobrevivir al mal que esconde el abismo. [D: Scott Derrickson, Jeff Habberstad; A: Miles Teller, Anya Taylor-Joy, Sigourney Weaver, Sope Dirisu, William Houston]

  • The Amateur: (2025; Suspense, Acción) Charlie Heller es un brillante pero introvertido decodificador de la CIA que trabaja en una oficina en el sótano de la sede de Langley. Su vida cambia radicalmente cuando su esposa muere en un ataque terrorista en Londres. Cuando sus supervisores se niegan a tomar cartas en el asunto, toma las riendas y se embarca en un peligroso viaje por todo el mundo para localizar a los responsables. Su inteligencia será el arma definitiva para escapar y llevar a cabo su venganza. [D: James Hawes, Svetlana Punte; A: Rami Malek, Holt McCallany, Danny Sapani, Rachel Brosnahan, Michael Stuhlbarg]

  • Titane: (2021; Drama, Suspense, Terror) Un joven con la cara magullada es descubierto en un aeropuerto. Dice llamarse Adrien Legrand, un niño que desapareció hace 10 años. Para su padre, Vincent, esto supone el final de una larga pesadilla y lo lleva a casa. Simultáneamente, se suceden una serie de horribles asesinatos en la región. [D: Bénédicte Kermadec, Claire Corbetta-Doll; A: Vincent Lindon, Agathe Rousselle, Garance Marillier, Laïs Salameh, Mara Cissé]

  • Anora: (2024; Drama, Comedia, Romance) Anora, una joven prostituta de Brooklyn, tiene la oportunidad de vivir una historia de Cenicienta cuando conoce e impulsivamente se casa con el hijo de un oligarca. Cuando la noticia llega a Rusia, su cuento de hadas se ve amenazado, ya que los padres parten hacia Nueva York para intentar conseguir la anulación del matrimonio. [D: Sean Baker, Albert Rudnitsky; A: Mikey Madison, Марк Эйдельштейн, Юра Борисов, Karren Karagulian, Vache Tovmasyan]

  • Civil War: (2024; Bélica, Acción, Drama) En un futuro cercano, donde América está sumida en una cruenta guerra civil, un equipo de periodistas y fotógrafos de guerra emprenderá un viaje por carretera en dirección a Washington DC. Su misión: llegar antes de que las fuerzas rebeldes asalten la Casa Blanca y arrebaten el control al presidente de Estados Unidos. [D: Amber Harley, Craig Comstock; A: Kirsten Dunst, Wagner Moura, Cailee Spaeny, Stephen McKinley Henderson, 李志傑]

  • Levels: (2024; Acción, Ciencia ficción, Suspense) [D: Adam Stern; A: Cara Gee, Peter Mooney, Aaron Abrams, David Hewlett, Amanda Tapping]

  • Love Lies Bleeding: (2024; Crimen, Romance, Suspense) Jackie está decidida a triunfar como culturista y se dirige a Las Vegas para participar en una competición. En su camino, pasa por un pequeño pueblo de Nuevo México donde conoce a Lou, la solitaria gerente del gimnasio local. El padre de Lou es traficante de armas y lleva las riendas de un sindicato del crimen. Jackie y Lou se enamoran. Pero su relación provoca violencia y ambas se ven inmersas en las maquinaciones de la familia de Lou. [D: Rose Glass, Susan E. Fiore; A: Kristen Stewart, Katy O'Brian, Ed Harris, Dave Franco, Jena Malone]

  • Sinners: (2025; Terror, Suspense) Tratando de dejar atrás sus problemáticas vidas, dos hermanos gemelos regresan a su pueblo natal para empezar de nuevo, solo para descubrir que un mal aún mayor les espera para darles la bienvenida. [D: Ryan Coogler, Steve Gehrke; A: Michael B. Jordan, Miles Caton, Hailee Steinfeld, Wunmi Mosaku, Jack O'Connell]

  • The Accountant²: (2025; Crimen, Suspense, Acción) Cuando un viejo conocido es asesinado, Wolff se ve obligado a resolver el caso. Al darse cuenta de que son necesarias medidas más extremas, Wolff recluta a su hermano Brax, distanciado y muy letal, para que le ayude. En colaboración con Marybeth Medina, descubren una conspiración mortal y se convierten en objetivo de una despiadada red de asesinos que no se detendrán ante nada para mantener sus secretos enterrados. [D: Gavin O'Connor, Mariela Comitini; A: Ben Affleck, Jon Bernthal, Cynthia Addai-Robinson, J.K. Simmons, Allison Robertson]

  • The Assessment: (2025; Ciencia ficción, Drama, Suspense) En un futuro cercano, en el que está estrictamente controlado tener hijos, los siete días de evaluación de una pareja para poder ser padres se convierten en una pesadilla psicológica, que les obliga a cuestionarse los principios básicos de su sociedad y lo que significa ser humanos. [D: Fleur Fortuné, Marino Darés; A: Elizabeth Olsen, Himesh Patel, Alicia Vikander, Minnie Driver, Indira Varma]

  • The Brutalist: (2024; Drama) Cuando el visionario arquitecto László Toth y su esposa Erzsébet huyen de la Europa de posguerra en 1947 para reconstruir su legado y ver el nacimiento de la América moderna, sus vidas cambian a causa de un misterioso y adinerado cliente. [D: Brady Corbet, Krisztina Szigeti; A: Adrien Brody, Felicity Jones, Guy Pearce, Joe Alwyn, Raffey Cassidy]

  • The Gift: (2015; Suspense, Misterio, Drama) Las vidas de un joven matrimonio se verán totalmente alteradas después de que un conocido del pasado del marido comience a dejarles misteriosos regalos y se revele un horrible secreto tras veinte años. [D: Michael J. Moore, Matt Haggerty; A: Jason Bateman, Rebecca Hall, Joel Edgerton, Allison Tolman, Tim Griffin]

  • The Substance: (2024; Terror, Ciencia ficción) 'Tú, pero mejor en todos los sentidos'. Esa es la promesa de la sustancia, un producto revolucionario basado en la división celular, que crea un alter ego más joven, más bello, más perfecto. [D: Matthieu de la Mortière, Anne Juin; A: Demi Moore, Margaret Qualley, Dennis Quaid, Edward Hamilton-Clark, Gore Abrams]

  • TRON: Ares: (2025; Ciencia ficción, Aventura) Tercera entrega de la saga "TRON". Cuenta la historia de Ares, un programa muy sofisticado que se envía desde el mundo digital al mundo real en una misión peligrosa y que va a representar el primer encuentro de la humanidad con seres creados por la IA. [D: Joachim Rønning, Donald Sparks; A: Jared Leto, Greta Lee, Evan Peters, Hasan Minhaj, Jodie Turner-Smith]

Predestination

Finalmente, el conteo de pendientes por fecha:

(Dic-2017)    5   2   1
(May-2018)   17   9   2
(Sep-2018)   12  10   3   1
(Mar-2019)   13  13  12   2   1   1
(Ago-2019)   10  10  10   7   2   1
(Feb-2020)        8   8   8   8   1
(Ago-2020)            9   9   9   4
(Ene-2021)                5   5   5   2
(Sep-2021)                    5   5   3
(Sep-2022)                       20  19   5   2
(Jun-2023)                            7   5   2   1
(Abr-2024)                               16  15   7
(Oct-2024)                                   11  11
(May-2025)                                       17
Total:       57  52  45  32  30  37  31  26  30  36
Comentarios Imprimir