<?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: S B</title>
    <description>The latest articles on DEV Community by S B (@s_b_338d557aee06b6436d328).</description>
    <link>https://dev.to/s_b_338d557aee06b6436d328</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%2F4154027%2Fbc99d80a-59b2-48f1-ae04-d7e1d654c4b4.png</url>
      <title>DEV Community: S B</title>
      <link>https://dev.to/s_b_338d557aee06b6436d328</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/s_b_338d557aee06b6436d328"/>
    <language>en</language>
    <item>
      <title>How I built a free PDF editor that keeps your fonts and never uploads your file</title>
      <dc:creator>S B</dc:creator>
      <pubDate>Thu, 08 Oct 2026 10:21:03 +0000</pubDate>
      <link>https://dev.to/s_b_338d557aee06b6436d328/how-i-built-a-free-pdf-editor-that-keeps-your-fonts-and-never-uploads-your-file-30c6</link>
      <guid>https://dev.to/s_b_338d557aee06b6436d328/how-i-built-a-free-pdf-editor-that-keeps-your-fonts-and-never-uploads-your-file-30c6</guid>
      <description>&lt;p&gt;You click a line in a PDF, type, and download. The new text uses the same font, the old words are really gone (not hidden under a white box), and the file never leaves your browser. Here is how the &lt;a href="https://aivdo.io/pdf-editor" rel="noopener noreferrer"&gt;Aivdo PDF editor&lt;/a&gt; does it, and the bugs I hit on the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why most free PDF editors get this wrong
&lt;/h2&gt;

&lt;p&gt;A PDF is not a document with paragraphs. It is a list of drawing instructions: "use font F2 at 11 pt, move to x=72 y=730, draw these glyph codes". There is no line, no word, no "edit" built in.&lt;/p&gt;

&lt;p&gt;So most browser editors do the easy thing: draw a white box over the old line and type new text on top in Helvetica. That fails in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The font changes.&lt;/strong&gt; Your Calibri invoice suddenly has one Helvetica line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The old text is still there.&lt;/strong&gt; It is hidden visually, but search, copy-paste and screen readers still find it. On an invoice, that is a real problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted both fixed, with the PDF processed entirely in the browser so nothing is ever uploaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: turning text runs into clickable lines
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://mozilla.github.io/pdf.js/" rel="noopener noreferrer"&gt;pdf.js&lt;/a&gt; renders each page to a canvas and &lt;code&gt;getTextContent()&lt;/code&gt; returns text runs: a string, a transform matrix (position and size in PDF units) and a width. A run is often a single word or even a few letters, so the editor merges runs into lines when they share a font, size and baseline and sit close together.&lt;/p&gt;

&lt;p&gt;Each line becomes a transparent &lt;code&gt;div&lt;/code&gt; placed over the canvas. Hover shows an outline, click makes it editable. The background and text colour are sampled from the rendered canvas so the editing box blends in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The table-cell gotcha.&lt;/strong&gt; My first version merged a whole table row into one line, so editing "1,200" replaced "North 1,200 1,450". The cause: the PDF pads table cells with a single wide space run (118 pt wide). Treating whitespace-only runs as separators, and only joining runs with a word-sized gap, fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The colour gotcha.&lt;/strong&gt; White text on a red banner came back red-on-red. Sampling around the box picked up the white page outside the banner. Now the background is the most common colour inside the box, and the text colour is the average of the highest-contrast pixels, which ignores the soft anti-aliased edges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: recognising the font
&lt;/h2&gt;

&lt;p&gt;The obvious idea is to reuse the font embedded in the PDF. It rarely works. Almost every PDF embeds a &lt;strong&gt;subset&lt;/strong&gt;: only the letters it already uses (my test invoice carried 23 glyphs of Carlito), often stored under private codes instead of real characters. Type a new letter and it simply is not there.&lt;/p&gt;

&lt;p&gt;So the editor reads the font name (for example &lt;code&gt;BCDEEE+Calibri-Bold&lt;/code&gt;), strips the subset prefix, works out family and style, and picks the best real font in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A font file the user uploaded (.ttf or .otf).&lt;/li&gt;
&lt;li&gt;The same font installed on the user's computer, via the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/queryLocalFonts" rel="noopener noreferrer"&gt;Local Font Access API&lt;/a&gt; (Chrome and Edge, with permission).&lt;/li&gt;
&lt;li&gt;A complete (non-subset) font embedded in the PDF.&lt;/li&gt;
&lt;li&gt;A bundled open-licence font with &lt;strong&gt;the same letter widths&lt;/strong&gt; as the original.&lt;/li&gt;
&lt;li&gt;DejaVu Sans, only for individual characters the chosen font lacks (₹, ₽, Greek, Cyrillic).&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Font in the PDF&lt;/th&gt;
&lt;th&gt;Bundled match&lt;/th&gt;
&lt;th&gt;Same letter widths&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Arial, Helvetica&lt;/td&gt;
&lt;td&gt;Liberation Sans&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Times New Roman, Times&lt;/td&gt;
&lt;td&gt;Liberation Serif&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Courier New, Courier&lt;/td&gt;
&lt;td&gt;Liberation Mono&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calibri&lt;/td&gt;
&lt;td&gt;Carlito&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cambria&lt;/td&gt;
&lt;td&gt;Caladea&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anything else&lt;/td&gt;
&lt;td&gt;Closest serif, sans or mono&lt;/td&gt;
&lt;td&gt;No: user can add the exact font&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Matching letter widths matters more than looks: an edited line takes the same space as before, so it does not run into the next column. The UI shows this as a coloured dot: green for exact or same-width fonts, amber for a look-alike, with a one-click &lt;strong&gt;Use exact font&lt;/strong&gt; button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: really removing the old words
&lt;/h2&gt;

&lt;p&gt;This is the part most editors skip. With &lt;a href="https://pdf-lib.js.org/" rel="noopener noreferrer"&gt;pdf-lib&lt;/a&gt; I decompress the page's content stream and walk it operator by operator, tracking where every piece of text lands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cm&lt;/code&gt;, &lt;code&gt;q&lt;/code&gt;, &lt;code&gt;Q&lt;/code&gt; for the graphics transform&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BT&lt;/code&gt;, &lt;code&gt;Tm&lt;/code&gt;, &lt;code&gt;Td&lt;/code&gt;, &lt;code&gt;TD&lt;/code&gt;, &lt;code&gt;T*&lt;/code&gt;, &lt;code&gt;TL&lt;/code&gt; for the text position&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tf&lt;/code&gt;, &lt;code&gt;Tc&lt;/code&gt;, &lt;code&gt;Tw&lt;/code&gt;, &lt;code&gt;Tz&lt;/code&gt; for font, size and spacing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tj&lt;/code&gt;, &lt;code&gt;TJ&lt;/code&gt;, &lt;code&gt;'&lt;/code&gt;, &lt;code&gt;"&lt;/code&gt; for the text itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a show operator starts on an edited line, I cannot simply delete it. Anything drawn after it in the same text block would slide left, because the pen position moves by the width of what was drawn. So the operator is replaced with an empty &lt;code&gt;TJ&lt;/code&gt; that moves the pen by exactly the same amount:&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="c1"&gt;// width of the original string in text-space units, from the font's /Widths&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;glyphWidths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;hScale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// "[ -n ] TJ" draws nothing but advances the pen by n/1000 em&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adv&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;hScale&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;] TJ`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The widths come from the font dictionary (&lt;code&gt;/Widths&lt;/code&gt;, or &lt;code&gt;/W&lt;/code&gt; for Type0 fonts). The 14 standard fonts such as Helvetica carry no widths in the file at all, so those come from pdf-lib's built-in metrics. The new text is then drawn with the chosen font at the original baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: the editor checks its own work
&lt;/h2&gt;

&lt;p&gt;Rewriting content streams is risky, so every save is verified before the user gets the file. The editor reopens the new PDF with pdf.js, confirms the page count, checks that every line the user did not touch is still present, and renders each changed page.&lt;/p&gt;

&lt;p&gt;If anything fails, it rebuilds the file the conservative way: the original text stays and is covered with a box in the sampled background colour. The user always gets a correct-looking file. Fonts it cannot parse (Type3, unusual encodings) and encrypted PDFs take that safe path, or get a plain-language message instead of a broken download.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing on 11 real-world PDF types
&lt;/h2&gt;

&lt;p&gt;All 11 pass. Each output was checked outside the browser with qpdf, Poppler and pypdf: valid file, same page count, new text present, old text gone, untouched text intact.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test PDF&lt;/th&gt;
&lt;th&gt;What it proved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Office invoice (LibreOffice, subset fonts)&lt;/td&gt;
&lt;td&gt;Same font kept, ₹ added, old text removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Letter in Helvetica and Times (not embedded)&lt;/td&gt;
&lt;td&gt;Standard-14 widths work, added text lands where clicked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;150-page report&lt;/td&gt;
&lt;td&gt;Opens in about 2 s, edit on page 120, saves in under 1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fillable job-application form&lt;/td&gt;
&lt;td&gt;Text, dropdown, checkbox, radio and multi-line values saved; form still fillable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Newsletter: red banner, table, image, two columns&lt;/td&gt;
&lt;td&gt;White banner text kept, one table cell edited alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanned page&lt;/td&gt;
&lt;td&gt;Clear "no text" message; Add text and White-out work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password-protected&lt;/td&gt;
&lt;td&gt;Friendly "remove the password first" message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edit-restricted&lt;/td&gt;
&lt;td&gt;Friendly message instead of a broken file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Euro, rouble, rupee, Cyrillic, Greek&lt;/td&gt;
&lt;td&gt;Correct characters via per-letter fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotated page&lt;/td&gt;
&lt;td&gt;Edit lands in the right place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30 full-page photos&lt;/td&gt;
&lt;td&gt;Fast on a heavy file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Looking at the rendered results mattered as much as the automated checks: the red-on-red banner and the merged table row both passed the text checks before I saw them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas if you build one
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;pdf-lib's font subsetter drops glyphs&lt;/strong&gt; in some fonts (Carlito lost most letters). Embedding the full font is reliable; it adds roughly 0.4 to 0.8 MB per font used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background tabs stall saving.&lt;/strong&gt; pdf-lib parses and saves in small timed steps, and browsers throttle timers in hidden tabs. Passing &lt;code&gt;parseSpeed: ParseSpeeds.Fastest&lt;/code&gt; and &lt;code&gt;objectsPerTick: Infinity&lt;/code&gt; fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pdf.js page rendering waits for animation frames,&lt;/strong&gt; which never fire in a hidden tab. For off-screen verification renders, &lt;code&gt;intent: 'print'&lt;/code&gt; avoids that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clicking with the "add text" tool loses focus&lt;/strong&gt; to the browser's own mousedown handling. Starting the edit a moment after the click fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not reuse a subset font for new text,&lt;/strong&gt; even if its cmap claims a character exists: the codes are often private, and you get "?" boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not give your page's &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; and your tool buttons the same &lt;code&gt;data-&lt;/code&gt; attribute.&lt;/strong&gt; A click anywhere selected a tool called "pdfedit". An hour I will not get back.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The editor is free, needs no sign-up, adds no watermark and runs entirely in your browser: &lt;a href="https://aivdo.io/pdf-editor" rel="noopener noreferrer"&gt;aivdo.io/pdf-editor&lt;/a&gt;. Drop a PDF, click any line, type, download. It also fills forms, adds text and has a white-out tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; pdf.js 3.11 for rendering and text extraction, pdf-lib 1.17 with @pdf-lib/fontkit for writing and font embedding, a small hand-written content-stream parser for text removal, and Liberation, Carlito, Caladea and DejaVu fonts (open licences). No server, no build step.&lt;/p&gt;

&lt;p&gt;If you try it on a PDF that breaks, I would genuinely like to hear about it in the comments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>pdf</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built a free file compressor that never uploads your files, and checks its own quality</title>
      <dc:creator>S B</dc:creator>
      <pubDate>Thu, 01 Oct 2026 11:13:50 +0000</pubDate>
      <link>https://dev.to/s_b_338d557aee06b6436d328/i-built-a-free-file-compressor-that-never-uploads-your-files-and-checks-its-own-quality-25d9</link>
      <guid>https://dev.to/s_b_338d557aee06b6436d328/i-built-a-free-file-compressor-that-never-uploads-your-files-and-checks-its-own-quality-25d9</guid>
      <description>&lt;p&gt;Every week I compress something: screenshots for a deck, a scanned PDF that bounces off an email limit, a phone video for a client. Every time, the options were the same. Upload my file to a site I don't know, wait, and hope the result doesn't look worse. So I built &lt;a href="https://aivdo.io" rel="noopener noreferrer"&gt;Avido&lt;/a&gt;, a compressor that runs entirely in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your files never leave your device
&lt;/h2&gt;

&lt;p&gt;Avido is a static website. All the compression happens on your machine using WebAssembly builds of the same codecs professionals use: MozJPEG for photos, libimagequant (the engine behind pngquant) and OxiPNG for PNGs, libwebp and AVIF for modern formats, and Gifsicle for animated GIFs. Videos use your device's hardware encoder through the WebCodecs API. There is no upload, so there is no waiting and nothing to leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  It measures quality instead of guessing
&lt;/h2&gt;

&lt;p&gt;Most compressors ask you to pick a quality number like 75. The right number is different for every image. Avido's Smart mode encodes each image several times and scores each attempt against the original with SSIM, a perceptual similarity measure. It keeps the smallest file that still clears your fidelity target. If nothing smaller looks as good, you get your original back.&lt;/p&gt;

&lt;p&gt;In my tests, a 367 KB PNG screenshot dropped to 110 KB (70% smaller) and a 2.4 MB PNG illustration to about 200 KB (over 90% smaller), with no visible difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video: codec agents
&lt;/h2&gt;

&lt;p&gt;For video, Avido runs one "agent" per codec your device supports: H.264, HEVC and AV1. Each agent encodes short samples from the start, middle and end of your video at different bitrates, decodes them, and compares frames to the original. The lowest bitrate that passes wins, and only then is the full video encoded. If you need maximum compatibility, you can lock it to H.264.&lt;/p&gt;

&lt;h2&gt;
  
  
  PDFs without blurry text
&lt;/h2&gt;

&lt;p&gt;Most of a PDF's weight is embedded photos and scans. Avido recompresses only those images and packs the file's structure. Text and vector graphics are never turned into pictures, so they stay sharp and selectable. A 1.7 MB test document with two large photos came out at 130 KB.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI upscaling, also on-device
&lt;/h2&gt;

&lt;p&gt;The same page can enlarge images 2× or 4×, and low-resolution videos 2×, with an ESRGAN neural network running on your graphics chip through TensorFlow.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch everything
&lt;/h2&gt;

&lt;p&gt;Drop a whole folder of hundreds of files. Up to three compress in parallel, with pause, retry and a time estimate, and the ZIP keeps your folder structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned building it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The browser is now a serious media workstation. WebAssembly codecs and WebCodecs hardware encoding are fast enough for real work.&lt;/li&gt;
&lt;li&gt;Measuring beats guessing. A perceptual check per file saves more bytes than any fixed quality setting, and it protects against the occasional ugly result.&lt;/li&gt;
&lt;li&gt;Privacy is a feature people notice. "Nothing is uploaded" is the first thing testers mention.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avido is free, with no signup and no watermark: &lt;a href="https://aivdo.io" rel="noopener noreferrer"&gt;aivdo.io&lt;/a&gt;. I'd love feedback, especially files where it does worse than the tool you use today.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;— Shub, maker of Avido&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>webassembly</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
