<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: decksmith</title>
    <description>The latest articles on DEV Community by decksmith (@decksmith).</description>
    <link>https://dev.to/decksmith</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4115047%2F51ad2930-d1bd-4c0f-b648-006592f3d2b5.png</url>
      <title>DEV Community: decksmith</title>
      <link>https://dev.to/decksmith</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/decksmith"/>
    <language>en</language>
    <item>
      <title>Which CSS variable made this color? Ask the browser three times instead of guessing from the value</title>
      <dc:creator>decksmith</dc:creator>
      <pubDate>Thu, 05 Nov 2026 06:09:29 +0000</pubDate>
      <link>https://dev.to/decksmith/which-css-variable-made-this-color-ask-the-browser-three-times-instead-of-guessing-from-the-value-2lgl</link>
      <guid>https://dev.to/decksmith/which-css-variable-made-this-color-ask-the-browser-three-times-instead-of-guessing-from-the-value-2lgl</guid>
      <description>&lt;p&gt;One of my color themes sets three CSS variables to the exact same value: &lt;code&gt;--ink&lt;/code&gt;, &lt;code&gt;--accent&lt;/code&gt; and the inverse background are all &lt;code&gt;#1F2BE0&lt;/code&gt;. That was fine for rendering. It stopped being fine when I needed to go from a computed color back to the variable it came from.&lt;/p&gt;

&lt;p&gt;The situation: I measure a page once, in a browser, and store the result. Later, a user swaps in their own palette, and I need to repaint the stored result without a browser (the code that does it runs on a server with no headless browser). &lt;code&gt;getComputedStyle&lt;/code&gt; only gives you &lt;code&gt;rgb(31, 43, 224)&lt;/code&gt;. The variable name is gone.&lt;/p&gt;

&lt;p&gt;I considered three ways to get it back.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look the value up in the theme's variable table. With my cobalt theme that returns three candidates for every blue thing on the page, so it's a coin flip. It also can't see through &lt;code&gt;color-mix()&lt;/code&gt;, because a mixed color matches nothing in the table.&lt;/li&gt;
&lt;li&gt;Walk the CSS declarations and read which &lt;code&gt;var()&lt;/code&gt; each element uses. That means reimplementing the cascade, inheritance, &lt;code&gt;color-mix()&lt;/code&gt; and gradients. I decided I'd rather not own a second CSS engine.&lt;/li&gt;
&lt;li&gt;Measure again with every variable set to a different marker color, and see which elements move with which marker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I went with the third one. A minimal version of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--ink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--accent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--bg-inverse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--paper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;markerSets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hsl(7 72% 44%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hsl(97 72% 44%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hsl(187 72% 44%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hsl(277 72% 44%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hsl(151 58% 62%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hsl(241 58% 62%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hsl(331 58% 62%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hsl(61 58% 62%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// run 1: theme as is. Runs 2 and 3: inject :root{ --ink: marker; ... } after the theme&lt;/span&gt;
&lt;span class="c1"&gt;// then read getComputedStyle for every element and for every variable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran this against a four-element page in Chrome 156: a heading using &lt;code&gt;--ink&lt;/code&gt;, two boxes using &lt;code&gt;--accent&lt;/code&gt; and &lt;code&gt;--bg-inverse&lt;/code&gt;, and a box using &lt;code&gt;color-mix(in srgb, var(--accent) 30%, var(--paper))&lt;/code&gt;. The first three all compute to &lt;code&gt;rgb(31, 43, 224)&lt;/code&gt; in the untouched run, so a value lookup matches three variables for each of them. With markers, the heading follows &lt;code&gt;--ink&lt;/code&gt; in both marked runs, the first box follows &lt;code&gt;--accent&lt;/code&gt;, the second follows &lt;code&gt;--bg-inverse&lt;/code&gt;. Each one is unambiguous after two extra runs.&lt;/p&gt;

&lt;p&gt;The mixed box matches no variable in any run, which is the signal that it's a mix. Solving each channel against the two marker values gave a weight of 0.296 to 0.302 for &lt;code&gt;--accent&lt;/code&gt; and the rest for &lt;code&gt;--paper&lt;/code&gt;, which is the 30% I wrote in the CSS.&lt;/p&gt;

&lt;p&gt;Two details that mattered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use two marker sets rather than one, so a coincidence in one run has to repeat in the next. My real code accepts an assignment only if it fits the observed colors in all three runs.&lt;/li&gt;
&lt;li&gt;If the theme sets a variable to &lt;code&gt;transparent&lt;/code&gt;, give that variable a transparent marker. An opaque marker makes a previously invisible box visible, the page changes shape, and the runs no longer line up element by element.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I haven't handled: colors that aren't driven by a variable at all (an image, a hard-coded hex) simply never move, and I treat them as constants. I haven't tested anything outside Chromium.&lt;/p&gt;

&lt;p&gt;Has anyone solved this without a browser, for example by parsing the stylesheet with something like &lt;code&gt;postcss&lt;/code&gt; and resolving &lt;code&gt;var()&lt;/code&gt; and &lt;code&gt;color-mix()&lt;/code&gt; yourself? I'm curious how far that gets before the cascade bites.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance. The claims about the code were checked against the project source and a small test run in Chrome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Embedding a font in a PPTX: the file that LibreOffice used, and the two it silently ignored</title>
      <dc:creator>decksmith</dc:creator>
      <pubDate>Wed, 21 Oct 2026 06:03:42 +0000</pubDate>
      <link>https://dev.to/decksmith/embedding-a-font-in-a-pptx-the-file-that-libreoffice-used-and-the-two-it-silently-ignored-2m0a</link>
      <guid>https://dev.to/decksmith/embedding-a-font-in-a-pptx-the-file-that-libreoffice-used-and-the-two-it-silently-ignored-2m0a</guid>
      <description>&lt;p&gt;I've been writing a PPTX exporter, and I wanted the font to travel with the file. When the person opening it doesn't have the font installed, the text falls back to some default and the line breaks drift.&lt;/p&gt;

&lt;p&gt;PPTX can carry fonts. The part is called a fntdata file, and from the spec and a few open source implementations I pieced together that it should be an EOT wrapper (Embedded OpenType) around the TTF. Before writing any real code I ran one slide through four variants to see which one the renderer would honor.&lt;/p&gt;

&lt;p&gt;The setup: one chart slide that uses VT323, a font I did not have installed on this computer. I rendered each variant to PDF with LibreOffice 26.8.0.3 and ran &lt;code&gt;pdffonts&lt;/code&gt; to see which font actually got drawn.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I put in the file&lt;/th&gt;
&lt;th&gt;Font in the PDF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Fallback fonts (FrankRuhlHofshi-Bold, LinuxLibertineG)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EOT with Flags 0, plus &lt;code&gt;embedTrueTypeFonts="1"&lt;/code&gt; on the presentation&lt;/td&gt;
&lt;td&gt;VT323-Regular&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The raw TTF as the fntdata, plus &lt;code&gt;embedTrueTypeFonts="1"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Fallback, silently ignored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EOT with Flags 0, no &lt;code&gt;embedTrueTypeFonts&lt;/code&gt; attribute&lt;/td&gt;
&lt;td&gt;Fallback, silently ignored&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Only the second row worked, the one where both pieces were present. In the other two attempts the text fell back to a substitute font, the same outcome as embedding nothing at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fijrl81kvm80fw1x7b8l9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fijrl81kvm80fw1x7b8l9.png" alt="The same chart slide converted to PDF by LibreOffice: serif fallback numbers without embedding, VT323 numbers with the EOT embedded" width="800" height="336"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same slide, converted to PDF by LibreOffice 26.8.0.3. Top: nothing embedded. Bottom: EOT with Flags 0 plus &lt;code&gt;embedTrueTypeFonts="1"&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few details that cost me time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The EOT header is the version 0x00020002 layout from the W3C submission. Flags 0 means no MicroType Express compression and no XOR. I couldn't find a public compressor for MicroType Express, so uncompressed it is.&lt;/li&gt;
&lt;li&gt;LibreOffice's own PPTX export writes the same thing (EOT, Flags 0, TTF appended as is), and it also writes &lt;code&gt;embedTrueTypeFonts="1"&lt;/code&gt;. Its import code only reads the embedded font list when that attribute is true, and always treats the data as EOT. That matches what the table shows.&lt;/li&gt;
&lt;li&gt;I opened a real fntdata from Apache POI's test data. Its header says Flags 0x5, which is subset plus compressed, so whatever Office writes is not what I'm writing. I can't prove that file came from PowerPoint itself, so I'm only using it as a header reference.&lt;/li&gt;
&lt;li&gt;Variable fonts need care. I pinned the weight to make a static instance, then rewrote the name table so the family name in the font, the family name in the EOT header and the &lt;code&gt;typeface&lt;/code&gt; attribute all matched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On size, the font is the expensive part. Pretendard Regular after deflate, which is roughly what the zip does to it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Characters kept&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Whole font&lt;/td&gt;
&lt;td&gt;1,117,566 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All 11,172 Hangul syllables plus ASCII and Latin-1&lt;/td&gt;
&lt;td&gt;931,592 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The 2,350 common Hangul syllables plus ASCII and Latin-1&lt;/td&gt;
&lt;td&gt;233,993 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only the 30 characters used on the slide&lt;/td&gt;
&lt;td&gt;4,619 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I went with the 2,350 set. On a test over 41 themes with 177 layouts each, the subset lost zero characters out of 64,465 (font, character) pairs. The catch is that if the recipient types a syllable outside those 2,350, only that character falls back to the default font. Microsoft's documentation says the same about subset embedding, that it limits editing.&lt;/p&gt;

&lt;p&gt;Here is what I did not check. I don't have PowerPoint on this computer, so everything above is LibreOffice behavior. I don't know whether PowerPoint accepts an uncompressed EOT. Comments in one open source project say a raw TTF makes Windows PowerPoint fall back to Calibri, and the same comments mention PowerPoint rejecting a whole file as corrupt after an XOR obfuscation attempt on a font part. Those are their observations, not mine, so I'm keeping the feature off by default until someone opens the file in PowerPoint on a machine without the font. Keynote and PowerPoint for the web are also untested.&lt;/p&gt;

&lt;p&gt;If you've opened an uncompressed (Flags 0) EOT fntdata in Windows or Mac PowerPoint on a machine without the font, did it load the font, or did it quietly fall back?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance and checked by me against my experiment notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>powerpoint</category>
      <category>fonts</category>
      <category>ooxml</category>
      <category>programming</category>
    </item>
    <item>
      <title>Seven signs a slide deck was made by AI</title>
      <dc:creator>decksmith</dc:creator>
      <pubDate>Mon, 14 Sep 2026 10:31:47 +0000</pubDate>
      <link>https://dev.to/decksmith/seven-signs-a-slide-deck-was-made-by-ai-1828</link>
      <guid>https://dev.to/decksmith/seven-signs-a-slide-deck-was-made-by-ai-1828</guid>
      <description>&lt;p&gt;I render slide layouts at 1920x1080 and run automated checks on every one of them: content outside the canvas, text clipped by its container, content running into the footer, overlapping elements, and elements hidden behind others.&lt;/p&gt;

&lt;p&gt;After a while I noticed the failures weren't random. The same patterns kept coming back, and they matched what makes a generated deck feel off when you sit through it. None of them are about taste. They are decisions made in the wrong order.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Everything is centered
&lt;/h2&gt;

&lt;p&gt;Cover centered, body centered, closing centered. Centered text never tells the reader where to start. A cover with one line can stay in the center. Once there are two blocks of information, left align and make one of them clearly bigger than the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Three cards, whatever the content
&lt;/h2&gt;

&lt;p&gt;Two items become three cards. Five items become three cards. The layout got picked before anyone counted the items, and three across is the arrangement most templates lead with, so that is what comes back by default.&lt;/p&gt;

&lt;p&gt;Count first, then pick an arrangement built for that count. Quick test: cover the slide and say how many things it compares. If that number isn't the number of boxes on screen, the form is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The title is a sentence
&lt;/h2&gt;

&lt;p&gt;"We improved customer satisfaction through a data-driven approach" is not a title. It's the script, and it competes with the person speaking. Cut it to a noun phrase and lead with the number: "Satisfaction up 32%". If a title needs more than about 40 characters, two messages are probably sharing one slide.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Numbers arrive without a source
&lt;/h2&gt;

&lt;p&gt;A market size in 90pt type, no year, no idea who produced it. Models are good at filling in plausible figures and just as good at leaving out where they came from. Put the year and source on the same slide, even in small type. A blank space is cheaper than a wrong number.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Decoration that means nothing
&lt;/h2&gt;

&lt;p&gt;A gradient behind the text, an icon on every bullet. Decoration exists to pull the eye somewhere, so when everything is decorated, nothing pulls. Use one accent color per slide and spend it once. Gradients also shift the value behind text, so contrast stops being predictable and small type suffers first.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Text that clips or overflows
&lt;/h2&gt;

&lt;p&gt;A title wraps to two lines and pushes the cards below it off the edge. This is a verification problem, not a design one. Generated slides usually look fine as code and have never been rendered at real size.&lt;/p&gt;

&lt;p&gt;This is also the easiest one to catch with a script. Render the slide in a headless browser at the size you'll present at, then compare each element's bounding box with its container. For hidden content, &lt;code&gt;document.elementFromPoint&lt;/code&gt; at the element's center tells you whether something else is sitting on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Slides drift from each other
&lt;/h2&gt;

&lt;p&gt;Slide three and slide seven have different margins, and the body text changes size somewhere in between. Building one slide at a time means every decision is made fresh and lands on a slightly different value. Nobody in the audience names this one. They just remember the deck as careless.&lt;/p&gt;

&lt;p&gt;The fix is the same one you'd use in a web app: pull spacing and type sizes into CSS variables and let every slide read from them, so the whole deck keeps one rhythm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order behind all seven
&lt;/h2&gt;

&lt;p&gt;The usual sequence is to write everything, look for a layout, then shrink the text or stretch the box when it doesn't fit. Reverse it. Decide the one thing the slide has to say, count the items, pick an arrangement for that count, then write to the space you have. Most of the list above never gets a chance to happen.&lt;/p&gt;

&lt;p&gt;What else do you keep seeing in generated decks? I built this list from render failures, so it leans toward things a script can detect. I suspect the tells that matter most are ones no checker would catch.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>design</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
