{"id":316,"date":"2026-11-13T09:15:07","date_gmt":"2026-11-13T09:15:07","guid":{"rendered":"https:\/\/joanruedapauweb.com\/blog\/?p=316"},"modified":"2026-11-13T09:34:28","modified_gmt":"2026-11-13T09:34:28","slug":"una-q-de-quake-en-mi-editor","status":"publish","type":"post","link":"https:\/\/joanruedapauweb.com\/blog\/index.php\/2026\/11\/13\/una-q-de-quake-en-mi-editor\/","title":{"rendered":"Una Q de Quake en mi editor"},"content":{"rendered":"\n<h1 class=\"wp-block-heading has-medium-font-size\"><em>C\u00f3mo hice Quake Q, una extensi\u00f3n de Visual Studio Code que dibuja la letra Q como el logo de Quake 1 sin cambiar ni un car\u00e1cter del texto.<\/em><\/h1>\n\n\n\n<p class=\"has-text-align-center wp-block-paragraph\"><a href=\"https:\/\/github.com\/cefaloide\/quake-vscode\">https:\/\/github.com\/cefaloide\/quake-vscode<\/a><\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"256\" height=\"256\" src=\"https:\/\/joanruedapauweb.com\/blog\/wp-content\/uploads\/2026\/11\/icon.png\" alt=\"\" class=\"wp-image-328\" style=\"width:128px;height:auto\" srcset=\"https:\/\/joanruedapauweb.com\/blog\/wp-content\/uploads\/2026\/11\/icon.png 256w, https:\/\/joanruedapauweb.com\/blog\/wp-content\/uploads\/2026\/11\/icon-150x150.png 150w\" sizes=\"auto, (max-width: 256px) 100vw, 256px\" \/><\/figure>\n<\/div>\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/joanruedapauweb.com\/blog\/wp-content\/uploads\/2026\/11\/screenshot.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"361\" src=\"https:\/\/joanruedapauweb.com\/blog\/wp-content\/uploads\/2026\/11\/screenshot-1024x361.png\" alt=\"\" class=\"wp-image-327\" srcset=\"https:\/\/joanruedapauweb.com\/blog\/wp-content\/uploads\/2026\/11\/screenshot-1024x361.png 1024w, https:\/\/joanruedapauweb.com\/blog\/wp-content\/uploads\/2026\/11\/screenshot-300x106.png 300w, https:\/\/joanruedapauweb.com\/blog\/wp-content\/uploads\/2026\/11\/screenshot-768x271.png 768w, https:\/\/joanruedapauweb.com\/blog\/wp-content\/uploads\/2026\/11\/screenshot.png 1524w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">La idea<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Quake (1996) es uno de esos juegos que se te quedan grabados, y su logo tambi\u00e9n: un anillo abierto por arriba atravesado por un clavo. Un d\u00eda pens\u00e9 que me gustar\u00eda ver ese logo cada vez que escribo una Q en el editor. Solo esa letra; el resto del c\u00f3digo ten\u00eda que seguir igual.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Parec\u00eda tan f\u00e1cil como cambiar una fuente, pero no lo era.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El problema: VS Code no deja cambiar la fuente de una sola letra<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">En VS Code, <code>editor.fontFamily<\/code> se aplica a todo el editor, y la API de extensiones no tiene nada parecido a \u00abesta letra, con esta fuente\u00bb.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lo que s\u00ed ofrece son las <strong>decoraciones<\/strong> (<code>createTextEditorDecorationType<\/code>). Son las mismas que usan los linters para subrayar errores o GitLens para anotar l\u00edneas. Con ellas puedes cambiar el color, el fondo o el borde de un trozo de texto, y a\u00f1adir contenido antes o despu\u00e9s de \u00e9l (<code>before<\/code> y <code>after<\/code>), incluida una imagen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Con eso hab\u00eda dos caminos:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Inyectar CSS.<\/strong> El valor de la propiedad <code>textDecoration<\/code> acaba tal cual en el CSS del editor. Por eso, si escribes <code>none; font-family: 'DpQuake'<\/code>, se cuela una regla <code>font-family<\/code>. Funciona, pero es un truco no documentado y solo sirve si tienes una fuente de Quake instalada. Lo dej\u00e9 como modo opcional (<code>quakeQ.fontFamily<\/code>).<\/li>\n\n\n\n<li><strong>Dibujar encima.<\/strong> Hacer la letra transparente y pintar una imagen en su sitio. No depende de ninguna fuente instalada, as\u00ed que es el modo por defecto.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">C\u00f3mo funciona por dentro<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. Una decoraci\u00f3n que esconde la letra<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">El primer paso es una decoraci\u00f3n que vuelve transparente la letra:<\/p>\n\n\n\n<pre class=\"wp-block-code has-light-gray-background-color has-background\"><code>vscode.window.createTextEditorDecorationType({\n  color: 'transparent',\n  rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed\n});\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El car\u00e1cter sigue ah\u00ed. El cursor, la selecci\u00f3n, la b\u00fasqueda y el copiar y pegar funcionan exactamente igual; la letra solo deja de verse.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La <code>q<\/code> y la <code>Q<\/code> comparten el mismo tipo de decoraci\u00f3n. La imagen de cada una se elige por rango con <code>renderOptions<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Una imagen que no ocupa sitio<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Encima de la letra transparente se pinta la imagen con un <code>before<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code has-light-gray-background-color has-background\"><code>before: {\n  contentIconPath: svgUri,\n  width: `${width}px`,\n  height: `${lineHeight}px`,\n  margin: `0 -${width - overhang}px 0 -${overhang}px`,\n  textDecoration: 'none; vertical-align: top; pointer-events: none'\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Un <code>before<\/code> normal empujar\u00eda el texto hacia la derecha. El truco est\u00e1 en los m\u00e1rgenes negativos:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El margen izquierdo retrocede lo que la imagen sobresale por la izquierda (<code>overhang<\/code>).<\/li>\n\n\n\n<li>El margen derecho retrocede el resto.<\/li>\n\n\n\n<li>Entre los dos suman exactamente el ancho de la imagen, as\u00ed que su <strong>ancho neto es cero<\/strong>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">As\u00ed la imagen se superpone a la letra transparente y las columnas no se mueven ni un p\u00edxel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>vertical-align: top<\/code> alinea la imagen con la parte de arriba de la l\u00ednea, y <code>pointer-events: none<\/code> deja que los clics pasen al texto que hay debajo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. De una cuadr\u00edcula de texto a un SVG<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Cada letra est\u00e1 dibujada como una cuadr\u00edcula de texto, donde <code>#<\/code> es un p\u00edxel encendido y <code>.<\/code> uno apagado. Esta es la <code>Q<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.#...#.\n#.....#\n#..#..#\n#..#..#\n#..#..#\n##.#.##\n.#####.\n...#...\n...#...\n...#...\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Cada glifo tiene adem\u00e1s un <code>ascent<\/code>: el n\u00famero de filas que quedan por encima de la l\u00ednea base. Las dem\u00e1s cuelgan por debajo, como el rabito de la q, y aqu\u00ed ese rabito es la punta del clavo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La funci\u00f3n <code>buildSvg<\/code> convierte la cuadr\u00edcula en un SVG:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Junta los p\u00edxeles seguidos de cada fila en un solo rect\u00e1ngulo, para que el SVG sea peque\u00f1o.<\/li>\n\n\n\n<li>Lo rellena con un degradado vertical de bronce, de <code>#d8b070<\/code> a <code>#7a4a20<\/code>, los dos colores configurables.<\/li>\n\n\n\n<li>Usa <code>shape-rendering=\"crispEdges\"<\/code> para que los bordes salgan n\u00edtidos, como en un juego de los 90.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">4. Medir sin poder medir<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La API no da las medidas reales de la fuente del editor, as\u00ed que las estimo a partir de la configuraci\u00f3n:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Ancho de un car\u00e1cter:<\/strong> unos <code>0,6 \u00d7 fontSize + letterSpacing<\/code>. Casi todas las fuentes monoespaciadas rondan las 0,6 em de ancho.<\/li>\n\n\n\n<li><strong>Alto de l\u00ednea:<\/strong> si <code>editor.lineHeight<\/code> vale 0, VS Code usa <code>1,35 \u00d7 fontSize<\/code> (1,5 en macOS). Los valores menores que 8 son multiplicadores del tama\u00f1o de letra.<\/li>\n\n\n\n<li><strong>L\u00ednea base:<\/strong> m\u00e1s o menos a mitad de la l\u00ednea m\u00e1s <code>0,33 \u00d7 fontSize<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Como la imagen se superpone a la letra en lugar de sustituirla, un error de un p\u00edxel en estas cuentas no descoloca nada. Y si con tu fuente la letra queda un poco alta o baja, se corrige con <code>quakeQ.verticalOffset<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. El tama\u00f1o del p\u00edxel<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">El tama\u00f1o de cada \u00abp\u00edxel\u00bb del dibujo es el menor de tres l\u00edmites:<\/p>\n\n\n\n<pre class=\"wp-block-code has-light-gray-background-color has-background\"><code>const pixel = Math.min(\n  width \/ (COLUMNS + 1),          \/\/ que quepa a lo ancho\n  baseline \/ glyph.ascent,        \/\/ que no se salga por arriba\n  (lineHeight - baseline) \/ descent \/\/ ni por abajo\n);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El ancho es el de un car\u00e1cter multiplicado por <code>quakeQ.scale<\/code>, que por defecto vale 1,5. As\u00ed el logo es algo m\u00e1s grande que una letra y se mete un poco en las vecinas, pero nunca se sale de la l\u00ednea.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">6. Archivos SVG con nombre de hash<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>contentIconPath<\/code> necesita la URI de un archivo. Por eso cada SVG se guarda en la carpeta de almacenamiento de la extensi\u00f3n (<code>globalStorageUri<\/code>), con un nombre sacado de su contenido: los primeros 12 caracteres de su hash SHA-1.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si el SVG no cambia, el archivo tampoco, y un mapa en memoria evita volver a escribirlo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">7. Solo lo que se ve<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La extensi\u00f3n solo busca la Q con una expresi\u00f3n regular en las l\u00edneas visibles, m\u00e1s 20 de margen por arriba y por abajo para que al hacer scroll r\u00e1pido no aparezcan letras sin decorar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El rec\u00e1lculo se hace con un retraso (debounce) de 50 ms cada vez que:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>cambian los editores visibles,<\/li>\n\n\n\n<li>haces scroll,<\/li>\n\n\n\n<li>editas el documento.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">As\u00ed un archivo de 10.000 l\u00edneas cuesta lo mismo que uno de 50.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando cambias cualquier ajuste de la extensi\u00f3n (<code>quakeQ.*<\/code>) o del editor (<code>editor.*<\/code>), como el tama\u00f1o de letra, todo se reconstruye desde cero.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El dise\u00f1o de la letra<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La letra fue lo que m\u00e1s vueltas dio:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Una q de consola.<\/strong> La primera versi\u00f3n era una <code>q<\/code> gruesa inspirada en la fuente de consola de Quake.<\/li>\n\n\n\n<li><strong>El logo vectorial.<\/strong> Prob\u00e9 a usar el logo oficial en SVG. Al final prefer\u00ed volver al pixel art, que encaja mejor con el estilo retro (adem\u00e1s, el logo es una marca registrada de id Software).<\/li>\n\n\n\n<li><strong>El logo en p\u00edxeles.<\/strong> Redibuj\u00e9 la letra con la forma del logo: un anillo y un clavo con cruceta.<\/li>\n\n\n\n<li><strong>M\u00e1s grande.<\/strong> A tama\u00f1o de editor se ve\u00eda demasiado fina, as\u00ed que a\u00f1ad\u00ed el ajuste <code>quakeQ.scale<\/code> para que ocupe algo m\u00e1s que una letra.<\/li>\n\n\n\n<li><strong>Sin cruz.<\/strong> Quit\u00e9 la cruceta del clavo, que a ese tama\u00f1o ensuciaba el dibujo.<\/li>\n\n\n\n<li><strong>Anillo redondo.<\/strong> Curv\u00e9 las puntas hacia dentro y ensanch\u00e9 la base para que pareciera un anillo y no un tridente.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Por \u00faltimo a\u00f1ad\u00ed el ajuste <code>quakeQ.letters<\/code>, para elegir si se sustituye la <code>Q<\/code> may\u00fascula (por defecto), la <code>q<\/code> min\u00fascula o las dos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Ajustes<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Ajuste<\/th><th>Por defecto<\/th><th>Para qu\u00e9 sirve<\/th><\/tr><\/thead><tbody><tr><td><code>quakeQ.enabled<\/code><\/td><td><code>true<\/code><\/td><td>Activa o desactiva el efecto<\/td><\/tr><tr><td><code>quakeQ.letters<\/code><\/td><td><code>uppercase<\/code><\/td><td><code>uppercase<\/code>, <code>lowercase<\/code> o <code>both<\/code><\/td><\/tr><tr><td><code>quakeQ.scale<\/code><\/td><td><code>1.5<\/code><\/td><td>Tama\u00f1o del logo respecto a una letra<\/td><\/tr><tr><td><code>quakeQ.color<\/code> \/ <code>quakeQ.shadowColor<\/code><\/td><td><code>#d8b070<\/code> \/ <code>#7a4a20<\/code><\/td><td>Colores del degradado<\/td><\/tr><tr><td><code>quakeQ.verticalOffset<\/code><\/td><td><code>0<\/code><\/td><td>Ajuste vertical en p\u00edxeles<\/td><\/tr><tr><td><code>quakeQ.fontFamily<\/code><\/td><td><code>\"\"<\/code><\/td><td>Usar una fuente de Quake instalada en lugar del dibujo<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Tambi\u00e9n hay un comando, <strong>Quake Q: Toggle<\/strong>, para activarla y desactivarla.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Herramientas<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>JavaScript sin compilar:<\/strong> sin paso de build, sin TypeScript y sin dependencias. Son dos archivos: <code>extension.js<\/code>, con las decoraciones y los ajustes, y <code>glyph.js<\/code>, con los dibujos.<\/li>\n\n\n\n<li><strong>Icono y captura generados con c\u00f3digo:<\/strong> se crean con Playwright a partir de la misma funci\u00f3n <code>buildSvg<\/code> que usa la extensi\u00f3n, as\u00ed que siempre coinciden con lo que se ve en el editor.<\/li>\n\n\n\n<li><strong>Empaquetado:<\/strong> con <code>vsce<\/code>.<\/li>\n\n\n\n<li><strong>Claude Code:<\/strong> desarroll\u00e9 la extensi\u00f3n con Claude Code. Yo le explicaba en espa\u00f1ol lo que quer\u00eda ver, revisaba las capturas y le ped\u00eda ajustes. En el repositorio hay un <code>CLAUDE.md<\/code> con las convenciones del proyecto: commits en formato Conventional Commits y todo el c\u00f3digo y la documentaci\u00f3n en ingl\u00e9s.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Limitaciones<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Medidas estimadas:<\/strong> no son las reales de la fuente, as\u00ed que con algunas fuentes hay que retocar <code>verticalOffset<\/code>.<\/li>\n\n\n\n<li><strong>Colores fijos:<\/strong> no cambian con el tema de VS Code, aunque el bronce se ve bien tanto en temas claros como oscuros.<\/li>\n\n\n\n<li><strong>Solapamiento:<\/strong> con <code>scale<\/code> por encima de 1, el logo se monta un poco sobre las letras de al lado.<\/li>\n\n\n\n<li><strong>Modo fuente:<\/strong> depende de un truco de CSS que una futura versi\u00f3n de VS Code podr\u00eda romper.<\/li>\n\n\n\n<li><strong>Sin tests:<\/strong> de momento no hay tests autom\u00e1ticos.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Pru\u00e9bala<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El c\u00f3digo est\u00e1 en <a href=\"https:\/\/github.com\/cefaloide\/quake-vscode\">github.com\/cefaloide\/quake-vscode<\/a>. Para probarla, abre la carpeta en VS Code y pulsa <code>F5<\/code>. Para instalarla:<\/p>\n\n\n\n<pre class=\"wp-block-code has-light-gray-background-color has-background\"><code>npx @vscode\/vsce package\ncode --install-extension quake-q-0.1.0.vsix\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Y a partir de ah\u00ed, cada Q de tu c\u00f3digo ser\u00e1 un peque\u00f1o homenaje a Quake.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>C\u00f3mo hice Quake Q, una extensi\u00f3n de Visual Studio Code que dibuja la letra Q como el logo de Quake 1 sin cambiar ni un car\u00e1cter del texto. https:\/\/github.com\/cefaloide\/quake-vscode La idea Quake (1996) es uno de esos juegos que se te quedan grabados, y su logo tambi\u00e9n: un anillo abierto por arriba atravesado por un &hellip; <a href=\"https:\/\/joanruedapauweb.com\/blog\/index.php\/2026\/11\/13\/una-q-de-quake-en-mi-editor\/\" class=\"more-link\">Sigue leyendo <span class=\"screen-reader-text\">Una Q de Quake en mi editor<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"footnotes":""},"categories":[1],"tags":[54,53],"class_list":["post-316","post","type-post","status-publish","format-standard","hentry","category-off_topic","tag-quake","tag-vscode"],"_links":{"self":[{"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/posts\/316","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/comments?post=316"}],"version-history":[{"count":14,"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/posts\/316\/revisions"}],"predecessor-version":[{"id":335,"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/posts\/316\/revisions\/335"}],"wp:attachment":[{"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/media?parent=316"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/categories?post=316"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/joanruedapauweb.com\/blog\/index.php\/wp-json\/wp\/v2\/tags?post=316"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}