<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged hacktoberfest</title>
<link>https://coderlegion.com/tag/hacktoberfest</link>
<description>Powered by Question2Answer</description>
<item>
<title>I put 64 file tools and an open-weight AI model in a browser tab. Nothing gets uploaded.</title>
<link>https://coderlegion.com/31508/i-put-64-file-tools-and-an-open-weight-ai-model-in-a-browser-tab-nothing-gets-uploaded</link>
<description>&lt;p&gt;Right after I shared Fizzdoc publicly, I opened it on my own Android phone and split a PDF.&lt;/p&gt;
&lt;p&gt;It took six minutes.&lt;/p&gt;
&lt;p&gt;Six. On my laptop the same kind of job took about a second. The link was already out in public, so somewhere out there, people were staring at a progress bar that didn't exist and thinking &quot;nice project, Rishab.&quot;&lt;/p&gt;
&lt;p&gt;I'll tell you how I fixed it. But first, what this thing even is, and why I think it fits this year's Hacktoberfest theme, &quot;AI belongs to everyone&quot;, better than I planned.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/kingrishabdugar/fizzdoc/main/docs/assets/hero.png&quot; alt=&quot;Fizzdoc home page&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-is-fizzdoc-&quot;&gt;What is Fizzdoc?&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://fizzdoc.com&quot;&gt;Fizzdoc&lt;/a&gt; is 64 tools for PDF, Word, Excel, PowerPoint, images and audio. Merge, compress, convert, edit, redact, OCR, cut an MP3, turn a voice note into text. The usual suspects, in 16 languages.&lt;/p&gt;
&lt;p&gt;None of it uploads your file, though. There's no backend. Not a small one, not a &quot;we only keep it for an hour&quot; one. Nothing. It's a static site, and every job runs inside your browser tab, on your own device.&lt;/p&gt;
&lt;p&gt;Most &quot;free PDF&quot; sites work like this: you send them your passport scan (or your Aadhaar card, if you're in India), they send you back a slightly smaller one, and you hope for the best. That always felt like a strange deal for jobs a phone can do on its own. So I built the other version.&lt;/p&gt;
&lt;p&gt;Oh, and it runs an open-weight AI model (Whisper) right in the tab to turn audio into text. No API key, no GPU, no server. If AI belongs to everyone, it should also run on everyone's phone.&lt;/p&gt;
&lt;p&gt;It's open source (Apache-2.0): &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc&quot;&gt;github.com/kingrishabdugar/fizzdoc&lt;/a&gt;. Everything in this post is in that repo, and every number comes from the code or something I actually measured.&lt;/p&gt;
&lt;h3 id=&quot;where-fizzdoc-fits&quot;&gt;Where Fizzdoc fits&lt;/h3&gt;
&lt;p&gt;The open-source world already has great PDF tools, and I've used several. Some are big platforms you self-host with Docker and run for a whole team, with pipelines, APIs and logins. Some are lean, browser-only PDF kits. Both are good at what they do.&lt;/p&gt;
&lt;p&gt;Fizzdoc makes a different bet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nothing to install. Not even Docker.&lt;/strong&gt; Open a link on any phone or laptop and it works. That matters when the person who needs to unlock a PDF is your dad, not your homelab.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not just PDF.&lt;/strong&gt; 30 PDF tools, 14 for images, 14 for Word, Excel and PowerPoint, and 6 for audio, including speech to text and subtitles. Plus developer odds and ends like Excel to JSON and Mermaid to PNG.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your language, not just English.&lt;/strong&gt; 16 languages, from Spanish, French, German and Portuguese to Hindi, Bengali, Tamil and Telugu, each with its own URLs, so people can find it by searching the way they actually talk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built for cheap phones first.&lt;/strong&gt; More on that below, because I learned it the hard way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Privacy you can verify.&lt;/strong&gt; A strict CSP and a test that fails if any request leaves the site.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need automation pipelines or an admin panel for your company, a self-hosted platform is the right call. If you need to shrink a photo to 50 KB for a government form on your phone in the next 30 seconds, that's what this is for.&lt;/p&gt;
&lt;h3 id=&quot;the-whole-thing-on-one-slide&quot;&gt;The whole thing on one slide&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0bk2ipjt7mxyyqq613yc.png&quot; alt=&quot;How Fizzdoc is built: build time, static host, browser tab&quot;&gt;&lt;/p&gt;
&lt;p&gt;Three layers, and only the last one ever touches your file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build time.&lt;/strong&gt; One TypeScript file, &lt;code&gt;src/site.ts&lt;/code&gt;, describes all 64 tools: slug, formats, options, page text. A renderer turns that into about 1,050 plain HTML pages (64 tools × 16 languages, plus a few extras), with the sitemap and &lt;code&gt;llms.txt&lt;/code&gt; thrown in. Adding a tool is one registry entry, one engine function and tests. The pages write themselves, which is the only kind of page I enjoy writing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hosting.&lt;/strong&gt; Cloudflare Pages. It serves files and answers GET requests. That's its entire personality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your browser.&lt;/strong&gt; A small script handles the boring parts: picking files, options, progress, download. It has no idea what a PDF is. When you press the button, it borrows a specialist:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Engine &lt;/th&gt;&lt;th&gt;Job &lt;/th&gt;&lt;th&gt;Download (gzipped)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;qpdf (C++, compiled to WebAssembly) &lt;/td&gt;&lt;td&gt;merge, split, rotate, passwords &lt;/td&gt;&lt;td&gt;~440 KB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;pdf.js &lt;/td&gt;&lt;td&gt;drawing pages, reading text &lt;/td&gt;&lt;td&gt;~390 KB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;pdf-lib &lt;/td&gt;&lt;td&gt;page numbers, watermarks, building PDFs &lt;/td&gt;&lt;td&gt;small&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tesseract.js &lt;/td&gt;&lt;td&gt;OCR &lt;/td&gt;&lt;td&gt;~5.8 MB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Whisper tiny &lt;/td&gt;&lt;td&gt;speech to text &lt;/td&gt;&lt;td&gt;~58 MB, once&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fflate &lt;/td&gt;&lt;td&gt;Word, Excel, PowerPoint (secretly ZIP files) &lt;/td&gt;&lt;td&gt;small&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Mermaid &lt;/td&gt;&lt;td&gt;diagrams to PNG/SVG &lt;/td&gt;&lt;td&gt;per diagram type&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;-just-use-a-server-bro-&quot;&gt;&quot;Just use a server, bro&quot;&lt;/h3&gt;
&lt;p&gt;I know. A server would have been easier. I chose pain for three reasons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can check it.&lt;/strong&gt; &quot;We delete your files&quot; is a promise. This is a rule your browser enforces:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;default-src 'self';
script-src 'self' 'wasm-unsafe-eval';
connect-src 'self';
img-src 'self' data: blob:;
object-src 'none'; base-uri 'none'; form-action 'none'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;connect-src 'self'&lt;/code&gt; means the page can only talk to fizzdoc.com, and fizzdoc.com only serves the website. Open DevTools, go to Network, run a job, and watch nothing interesting happen. The test suite does the same thing on every pull request and fails if a single request goes anywhere else.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's free to run.&lt;/strong&gt; A file-processing server gets more expensive with every user. A static site doesn't care how popular it gets. That's the only reason I can say &quot;free&quot; without a pricing page hiding behind it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No waiting in line.&lt;/strong&gt; No upload bar, no &quot;processing on our servers&quot;, no download. Your file goes from your disk to your RAM and back.&lt;/p&gt;
&lt;p&gt;The catch is obvious: your device does the work, and not everyone has a laptop. Which brings us back to my six-minute disaster.&lt;/p&gt;
&lt;h3 id=&quot;the-android-bug&quot;&gt;The Android bug&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/qpdf/qpdf&quot;&gt;qpdf&lt;/a&gt; runs on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/emscripten-core/emscripten&quot;&gt;Emscripten&lt;/a&gt;, which gives it a small pretend file system. I was handing it your file through &lt;strong&gt;WORKERFS&lt;/strong&gt;, which reads the file lazily, only when qpdf asks for a piece. Sounds efficient, very &quot;don't load what you don't need&quot;, and I was proud of it.&lt;/p&gt;
&lt;p&gt;What I didn't know: qpdf doesn't ask for a few big pieces. It asks for lots and lots of tiny ones. On a 10 MB, 28-page test PDF, I counted &lt;strong&gt;1,448 separate reads&lt;/strong&gt;: 434 just to count the pages, and 1,014 more for the split.&lt;/p&gt;
&lt;p&gt;On a laptop, 1,448 small reads is nothing. On Android, reading a picked file in small pieces is slow, and 1,448 slow things in a row add up to minutes. My &quot;efficient&quot; design was death by a thousand paper cuts (literally, since it was a PDF).&lt;/p&gt;
&lt;p&gt;The fix was almost embarrassing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// qpdf reads its input in thousands of small pieces, and on Android every small read
// of a picked file is slow. Only inputs too big to copy safely are still read lazily.
const COPY_UP_TO = 128 * 1024 * 1024;

if (totalSize &amp;lt;= COPY_UP_TO) await copyIn(q, files, run); // read once, in order, into memory
else q.FS.mount(q.WORKERFS, { blobs }, '/in');            // huge files: no second copy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/j87ktfnpwl52ai3a3sml.png&quot; alt=&quot;How a PDF gets into memory: budget check, then copy or lazy read&quot;&gt;&lt;/p&gt;
&lt;p&gt;If your files add up to 128 MB or less, the worker reads them once, start to finish, into memory. All those tiny reads now hit RAM, which doesn't care. Same test file: 1,448 reads became zero, and even on my laptop the split went from 1.4 s to 0.25 s. So the bug was slowing everyone down; phones were just louder about it.&lt;/p&gt;
&lt;p&gt;Above 128 MB, making a second full copy could run a phone out of memory, so big files still get the lazy path. Slower, but it finishes. I tested that with a 140 MB PDF, and yes, I waited.&lt;/p&gt;
&lt;h3 id=&quot;being-nice-to-a-8-000-phone&quot;&gt;Being nice to a ₹8,000 phone&lt;/h3&gt;
&lt;p&gt;That bug taught me to design for the cheapest phone first, not my laptop. This is what changed because of it.&lt;/p&gt;
&lt;h4 id=&quot;load-almost-nothing&quot;&gt;Load almost nothing&lt;/h4&gt;
&lt;p&gt;The first page load is about &lt;strong&gt;15 KB of gzipped JavaScript and 7 KB of CSS&lt;/strong&gt;. No framework. Every page is prerendered HTML, so you can read it before any script runs (Google can too).&lt;/p&gt;
&lt;p&gt;The Inter font is split into 7 &lt;code&gt;unicode-range&lt;/code&gt; pieces with &lt;code&gt;font-display: swap&lt;/code&gt;, so your browser only downloads the characters a page actually uses, and shows text in a system font while it waits. A tiny &lt;code&gt;theme.js&lt;/code&gt; sets dark mode before the first paint, so no white flash at 2 a.m. Hashed files are cached for a year, so the second visit is mostly just HTML.&lt;/p&gt;
&lt;h4 id=&quot;load-engines-only-when-asked&quot;&gt;Load engines only when asked&lt;/h4&gt;
&lt;p&gt;Each tool's engine hides behind a dynamic &lt;code&gt;import()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;'compress-pdf': async (f, o) =&amp;gt; (await import('./engine/compress')).compressPdf(f[0], { ... }),
'ocr-pdf':      async (f) =&amp;gt; (await import('./engine/ocr')).ocrPdf(f[0], progressLabel),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If all you do is rotate a PDF, you never download the OCR engine or a 58 MB speech model. Mermaid is even pickier: every diagram type is its own chunk, so drawing a flowchart doesn't drag in the code for sequence diagrams.&lt;/p&gt;
&lt;h4 id=&quot;check-the-budget-before-you-spend-it&quot;&gt;Check the budget before you spend it&lt;/h4&gt;
&lt;p&gt;Before a job starts, the page does a quick sanity check:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// 128 MiB per GiB of reported device memory, capped at 1 GiB.
function inputBudget() {
  const gib = navigator.deviceMemory;
  return gib ? Math.min(gib * 128, 1024) * MiB : 512 * MiB;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A 4 GB phone gets 512 MiB, an 8 GB laptop gets 1 GiB. Firefox and Safari don't report device memory, so they get a sensible 512 MiB. The cap exists because qpdf's WebAssembly memory tops out at 2 GiB and a job needs room for more than its input.&lt;/p&gt;
&lt;p&gt;Is it a perfect measurement? No, and the code comment admits that. But &quot;this file is too big for this device&quot; is a much better message than a tab that quietly dies after five minutes.&lt;/p&gt;
&lt;h4 id=&quot;show-real-progress&quot;&gt;Show real progress&lt;/h4&gt;
&lt;p&gt;A spinner on a slow phone is basically a &quot;maybe&quot; button. So the progress bar is real.&lt;/p&gt;
&lt;p&gt;The first 30% is the file being read into memory, byte by byte. The other 70% comes from qpdf itself. qpdf has a &lt;code&gt;--progress&lt;/code&gt; flag that prints lines like &lt;code&gt;write progress: 42%&lt;/code&gt;. This WebAssembly build ignores the option meant to catch that output, so it just goes to &lt;code&gt;console.log&lt;/code&gt;. Okay then, I'll grab it from there:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;console.log = (...args) =&amp;gt; {
  const pct = /: write progress: (\d+)%$/.exec(String(args[0]))?.[1];
  if (pct === undefined) return log(...args);
  post({ type: 'progress', fraction: READ_SHARE + ((1 - READ_SHARE) * Number(pct)) / 100 });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Is hijacking &lt;code&gt;console.log&lt;/code&gt; elegant? Absolutely not. Does the bar move now? It does. I sleep fine.&lt;/p&gt;
&lt;h4 id=&quot;one-job-one-worker-then-gone&quot;&gt;One job, one worker, then gone&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/kxguikttab6kqgem8a6m.png&quot; alt=&quot;Life of a job: worker per job, terminate to clean up&quot;&gt;&lt;/p&gt;
&lt;p&gt;Every qpdf job gets its own fresh Web Worker, so the page stays smooth while it works. When the job ends, or you hit Cancel, the page calls &lt;code&gt;worker.terminate()&lt;/code&gt;. That one line throws away the file, any password you typed, and the engine's whole memory, so the cancel button doubles as cleanup and leak prevention. No state survives between jobs, so memory doesn't slowly creep up while you merge your way through tax season.&lt;/p&gt;
&lt;p&gt;Before running, the worker also asks qpdf what's in the file: bookmarks, forms, signatures, encryption. If you merge a signed PDF, Fizzdoc tells you the signature won't be valid anymore, instead of letting you find out from your CA.&lt;/p&gt;
&lt;h4 id=&quot;only-draw-what-s-on-screen&quot;&gt;Only draw what's on screen&lt;/h4&gt;
&lt;p&gt;The editor and the redaction tool show every page of your PDF. On a 200-page file that could mean 200 canvases fighting for memory. Instead, a page is drawn only when it's about to scroll into view:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const io = new IntersectionObserver(
  (entries) =&amp;gt; { for (const e of entries) if (e.isIntersecting) renderPage(pages[e.target.dataset.index]); },
  { root: scroller, rootMargin: '600px 0px' },
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tools that must touch every page (PDF to JPG, scanned PDF, OCR) go one page at a time and clean each page up before moving on.&lt;/p&gt;
&lt;h4 id=&quot;do-less-work&quot;&gt;Do less work&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MP3 and M4A are cut without re-encoding.&lt;/strong&gt; An MP3 is a row of little frames, each saying how long it is. Cutting is just picking the frames between two timestamps. No decoding, no quality loss, and fast even on a phone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compression never makes things bigger.&lt;/strong&gt; Each image inside a PDF is kept only if the new version is smaller. If the whole file doesn't shrink, you get your original back, not a &quot;compressed&quot; file that's 3% larger. (We've all seen that one.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&quot;Compress image to 50 KB&quot; is smart about it.&lt;/strong&gt; It keeps the photo large and lowers quality first, never below 50% while it's 1000 px or wider, using a 6-step binary search. Only if that can't fit does it shrink the size. Roughly how messaging apps do it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;and-when-it-s-slow-anyway-say-so&quot;&gt;And when it's slow anyway, say so&lt;/h4&gt;
&lt;p&gt;While a job runs, the page tells you speed depends on your device. If something takes over 20 seconds or fails, a &quot;Slow or not working? Report it on GitHub&quot; link lights up. It opens a ready-made issue with the tool, browser, time taken, file size, and the device's RAM and CPU cores. Never the file, never its name. That link is how I find the next bug instead of guessing.&lt;/p&gt;
&lt;p&gt;What's still not great: PDF to JPG keeps every page image in memory until the ZIP is built (a 1,000-page PDF on a phone would be a bad day), some jobs still run on the main thread, and Whisper runs on a single thread. Speaking of which.&lt;/p&gt;
&lt;h3 id=&quot;speech-to-text-without-a-server&quot;&gt;Speech to text, without a server&lt;/h3&gt;
&lt;p&gt;This was the feature I wasn't sure a static site could pull off.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/6uflsc2txovtu1c3y9we.png&quot; alt=&quot;Speech to text pipeline: download once, cache, decode, 30 s windows&quot;&gt;&lt;/p&gt;
&lt;p&gt;Fizzdoc runs Whisper tiny (8-bit quantized) with transformers.js and ONNX Runtime, on the CPU, inside a worker. It took three fights to get there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare Pages allows 25 MiB per file.&lt;/strong&gt; The model is bigger. So a build script chops the model into parts. The first time you use the tool, the browser downloads the parts, glues them back together and keeps them in Cache Storage. After that it starts in seconds, even offline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My own security rules blocked it.&lt;/strong&gt; ONNX Runtime likes to load its WebAssembly through a &lt;code&gt;blob:&lt;/code&gt; URL, which my CSP refuses. So the worker reads the file from the cache itself and hands it over directly. Remote model loading is switched off too, so it can only read from fizzdoc.com.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nobody trusts a spinner for 58 MB.&lt;/strong&gt; The first run says &quot;Preparing… one-time setup: 23 of 58 MB&quot; with a real byte counter, and tells you it only happens once.&lt;/p&gt;
&lt;p&gt;Audio is turned into 16 kHz mono (what Whisper expects) and fed in 30-second windows with 5 seconds of overlap, so words at the edges don't get chopped in half.&lt;/p&gt;
&lt;p&gt;Whisper tiny is, well, tiny. Clear English comes out well. Hinglish, sadly, does not. Give it Hindi and English in the same sentence and it answers with great confidence and very little accuracy. The tool now says it works best on clear English. Multi-threading would help speed, but it needs cross-origin isolation, and my first attempt made the worker hang. So it's single-threaded for now. If you've solved this, please come find me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/js47lvxn8sgv6bnsux5k.png&quot; alt=&quot;Audio to Text page&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;smaller-decisions-i-d-defend-in-a-code-review&quot;&gt;Smaller decisions I'd defend in a code review&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Redaction that actually redacts.&lt;/strong&gt; Drawing a black box over text doesn't remove it. People still do that, and people still copy the text out from under the box. Fizzdoc turns each page into an image with the boxes burned in, then builds a new PDF from those images. The text is really gone. The trade-off is the result isn't selectable text, and the tool says so. There's also an optional &quot;find personal info&quot; button that marks emails, UPI IDs, phone numbers, PAN, IFSC, IBAN and long ID numbers. It's plain regex, not AI, and you check every mark before saving.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Editing text in place, in the PDF's own font.&lt;/strong&gt; Click a line in a PDF and retype it. On save, Fizzdoc parses the page's content stream, tracks the text state (matrices, font, spacing) to find the exact operators that draw that line, and rewrites them using the font that's already embedded in the file. Same font, same size, same colour, no white box. The catch: embedded fonts are usually subsets, so you can only use letters the file already contains. Change &quot;Rs 4,250&quot; to &quot;Rs 2,450&quot;? Easy. Type a &quot;Z&quot; into a PDF that never had one? The glyph simply isn't there, so that line falls back to covering the old text and drawing new text in the closest standard font. Hindi and Arabic always take the fallback too, because their glyphs are drawn in a different order than they're typed, and I'd rather be boring than garbled.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0njstg0epqbd56v90zy2.png&quot; alt=&quot;Before and after: three lines edited, same fonts and colours&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Word to PDF via the print dialog.&lt;/strong&gt; Drawing Hindi, Tamil or Arabic correctly inside a JavaScript PDF writer is a whole career. Every browser already ships a great PDF writer: Print → Save as PDF. So Fizzdoc turns your .docx into clean HTML and opens the print dialog. One extra click, and every script comes out right. Lazy, yes, but it works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;qpdf instead of a JavaScript PDF library.&lt;/strong&gt; I started with pdf-lib for merging. It works, but copying pages that way drops bookmarks, form fields and internal links. qpdf rewrites the file's structure instead, and has been doing it reliably for years.&lt;/p&gt;
&lt;h3 id=&quot;bonus-your-coding-agent-can-use-it-too&quot;&gt;Bonus: your coding agent can use it too&lt;/h3&gt;
&lt;p&gt;Hacktoberfest is big on agents this year, so here's a small one. The repo has an MCP server (in &lt;code&gt;/mcp&lt;/code&gt;) that runs the same qpdf and audio engines locally. Hook it up to Claude Code, Codex or Cursor and you can say &quot;merge these PDFs&quot; or &quot;cut the first 30 seconds of this MP3&quot;, and the agent does it on your machine. No upload, no throwaway Python script, and it never overwrites your files.&lt;/p&gt;
&lt;h3 id=&quot;lots-of-languages-one-source-file&quot;&gt;Lots of languages, one source file&lt;/h3&gt;
&lt;p&gt;Each language has real URLs (&lt;code&gt;/hi/merge-pdf/&lt;/code&gt;, &lt;code&gt;/ta/compress-pdf/&lt;/code&gt;), translated buttons, FAQs and error messages, and hreflang links between them: Hindi, Bengali, Marathi, Tamil, Telugu, Spanish, Portuguese, French, German, Italian, Dutch, Polish, Turkish, Indonesian, Vietnamese and English.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/kingrishabdugar/fizzdoc/main/docs/assets/lang.gif&quot; alt=&quot;Fizzdoc in Hindi&quot;&gt;&lt;/p&gt;
&lt;p&gt;Because every page is generated, the tests can check all ~1,050 of them for their CSP, title, description, canonical link, a single &lt;code&gt;h1&lt;/code&gt; and valid structured data. I'm not checking 1,050 pages by hand.&lt;/p&gt;
&lt;h3 id=&quot;tests-because-i-like-sleeping&quot;&gt;Tests, because I like sleeping&lt;/h3&gt;
&lt;p&gt;209 unit tests and 1,087 browser tests. Engines are plain functions (bytes in, bytes out), so most run in Node against small weird files: encrypted PDFs, forms, bookmarks, MP3s. The browser tests click through the real UI, including the &quot;no request leaves the site&quot; check. CI runs both on every pull request, and nothing merges red.&lt;/p&gt;
&lt;h3 id=&quot;come-build-it-with-me&quot;&gt;Come build it with me&lt;/h3&gt;
&lt;p&gt;Right now Fizzdoc is one person and a lot of tabs. There's plenty left, and some of it is really fun engineering. Hacktoberfest doesn't count pull requests anymore, which honestly suits me. I'd much rather have one good PR that someone understands than ten that just exist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good first issues&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/17&quot;&gt;Crop PDF&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/18&quot;&gt;Flatten PDF&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/19&quot;&gt;Split every N pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/6&quot;&gt;Pick the DPI for PDF → JPG&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/8&quot;&gt;watermark position and colour&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/22&quot;&gt;Word → Markdown / HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Translations: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/1&quot;&gt;Japanese&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/3&quot;&gt;Korean, Gujarati, Kannada, Malayalam, Punjabi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bigger ones&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/10&quot;&gt;Sign PDF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/13&quot;&gt;OCR in more languages&lt;/a&gt;, downloaded on demand like Whisper&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/11&quot;&gt;Install it as an offline app (PWA)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/12&quot;&gt;Page thumbnails with drag and drop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/42&quot;&gt;WAV, FLAC, OGG and OPUS&lt;/a&gt; in the audio tools&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/44&quot;&gt;Remove image background&lt;/a&gt;, on the device&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/5&quot;&gt;Right-to-left languages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Problems I'd love your brain on&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Faster Whisper: threads (COOP/COEP without the hang) or WebGPU. If you've shipped either, tell me everything.&lt;/li&gt;
&lt;li&gt;A bigger or multilingual open-weight speech model that still fits a phone, so Hinglish stops being a comedy show.&lt;/li&gt;
&lt;li&gt;More MCP tools: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues/61&quot;&gt;the image, OCR, redaction and Office engines in Node&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Streaming the ZIP for PDF to JPG, so huge PDFs don't sit in memory.&lt;/li&gt;
&lt;li&gt;Real text deletion in Edit PDF, rewriting the content stream instead of covering it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Setup takes about as long as reading this sentence twice:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git clone https://github.com/kingrishabdugar/fizzdoc
cd fizzdoc
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CONTRIBUTING.md shows how a tool goes from the registry to its engine. Small pull requests get reviewed fastest. Not sure where to start? Comment on any &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc/issues&quot;&gt;issue&lt;/a&gt; and I'll help you pick one.&lt;/p&gt;
&lt;h3 id=&quot;that-s-it&quot;&gt;That's it&lt;/h3&gt;
&lt;p&gt;Try it at &lt;a rel=&quot;nofollow&quot; href=&quot;https://fizzdoc.com&quot;&gt;fizzdoc.com&lt;/a&gt;. Throw your weirdest PDF at it. If it's slow on your phone, hit the report link and tell me which phone, so I can have another humbling morning.&lt;/p&gt;
&lt;p&gt;And if you think it's useful, a star on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kingrishabdugar/fizzdoc&quot;&gt;kingrishabdugar/fizzdoc&lt;/a&gt; helps more people find it. It also makes me unreasonably happy.&lt;/p&gt;
&lt;p&gt;Thanks for reading.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31508/i-put-64-file-tools-and-an-open-weight-ai-model-in-a-browser-tab-nothing-gets-uploaded</guid>
<pubDate>Sat, 17 Oct 2026 11:18:58 +0000</pubDate>
</item>
<item>
<title>Hacktoberfest Stopped Counting PRs. So What Counts as a Contribution Now?</title>
<link>https://coderlegion.com/29617/hacktoberfest-stopped-counting-prs-so-what-counts-as-a-contribution-now</link>
<description>&lt;p&gt;Hacktoberfest 2026 no longer rewards pull requests, and I think the change forces a useful question on all of us: if a contribution is not a merged diff, what is it?&lt;/p&gt;
&lt;p&gt;The organisers' reason is short. Their FAQ says it is &quot;easier than ever to submit low-effort spam PRs to projects,&quot; so they listened to maintainers and stopped incentivising them. This year MLH and DEV run the month with DigitalOcean presenting, under the theme &quot;AI Belongs to Everyone.&quot; You earn stickers by attending local Fests, joining Global Hack Week (October 9 to 15), entering DEV challenges and similar activities.&lt;/p&gt;
&lt;p&gt;The history makes the decision easy to understand. The first Hacktoberfest in 2014 had 676 participants and a simple rule: four pull requests, one t-shirt. By 2020 there were nearly 170,000 sign-ups and more than 621,000 pull requests, enough README spam that maintainers compared it to a denial-of-service attack. The rules tightened to opt-in repositories and maintainer acceptance. Last year the bar went from four accepted PRs to six. Then AI tools made a plausible patch cost seconds to produce, while reviewing one costs what it always did. Counting stopped meaning anything.&lt;/p&gt;
&lt;p&gt;I have been involved in open source since 1999, much of it teaching people to use Linux and Git and running infrastructure on them. Very little of the work that keeps a project healthy would ever show up in a PR count: reproducing a bug on a different machine, answering the same question for the fifth time, writing down how something behaved so the next person does not have to find out the hard way, or teaching someone to use the tools in the first place.&lt;/p&gt;
&lt;p&gt;A 2024 Harvard Business School study estimated that 96 percent of open source's demand-side value comes from 5 percent of developers. That small group's scarce resource is attention. The most useful contributions spend less of it, or add to it.&lt;/p&gt;
&lt;p&gt;So here is my working list of what counts this October, offered as a starting point rather than a rule:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reproducing a reported bug on the current release, and saying so on the issue&lt;/li&gt;
&lt;li&gt;reducing a failing test to its smallest case&lt;/li&gt;
&lt;li&gt;fixing documentation that misled you, while you still remember how&lt;/li&gt;
&lt;li&gt;reviewing someone else's pull request carefully and leaving specific questions&lt;/li&gt;
&lt;li&gt;disclosing the AI tools you used and which parts of a change they touched, in the spirit of the Linux kernel's rule: &quot;You are expected to understand and to be able to defend everything you submit&quot;&lt;/li&gt;
&lt;li&gt;building something with an open-weight model and writing down the exact model, digest and licence, plus what the release did not tell you&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last one connects to this year's theme. Open weights let you run, test and fine-tune a model on your own hardware. Most releases do not publish training data or full training code, so you cannot reproduce or audit how the model was made. The OSI's Open Source AI Definition asks for both. Knowing where a model's openness ends is part of using it responsibly.&lt;/p&gt;
&lt;p&gt;I would like to hear from people here, especially anyone who maintains a project. What kind of contribution helps you most, and what would you want a Hacktoberfest participant to do this month that no counter would ever see?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Sources: &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.mlh.com/everything-you-need-to-know-about-hacktoberfest-2026-ai-belongs-to-everyone-mdk&quot;&gt;Hacktoberfest 2026 (MLH)&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://hacktoberfest.com/questions/&quot;&gt;Hacktoberfest FAQ&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.digitalocean.com/blog/hacktoberfest-recap2020&quot;&gt;DigitalOcean 2020 recap&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.digitalocean.com/blog/hacktoberfest-2025-wrapup&quot;&gt;DigitalOcean 2025 wrap-up&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.hbs.edu/faculty/Pages/item.aspx?num=65230&quot;&gt;HBS: The Value of Open Source Software&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.kernel.org/process/generated-content.html&quot;&gt;Linux kernel guidelines for tool-generated content&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://opensource.org/ai/open-source-ai-definition&quot;&gt;OSI Open Source AI Definition 1.0&lt;/a&gt;&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/29617/hacktoberfest-stopped-counting-prs-so-what-counts-as-a-contribution-now</guid>
<pubDate>Sat, 03 Oct 2026 12:42:47 +0000</pubDate>
</item>
<item>
<title>Hacktoberfest 2024: as a newbie contributer</title>
<link>https://coderlegion.com/434/hacktoberfest-2024-as-a-newbie-contributer</link>
<description>&lt;p&gt;Hey there! I'm Candy and I'm here to share my experience as a beginner contributor in Hacktoberfest 2024!&lt;/p&gt;
&lt;h3 id=&quot;open-source&quot;&gt;Open Source&lt;/h3&gt;
&lt;p&gt;Before this, I didn't know what was open source. I mean, I definitely heard of it, but I had questions: &lt;em&gt;&quot;What exactly is open source? How do I contribute?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Looking at tutorials and examples, I learned about open source- okay not really, but I had a basic understanding of my.. umm &lt;em&gt;job&lt;/em&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-the-terminal-&quot;&gt;What the terminal?&lt;/h3&gt;
&lt;p&gt;I have 0% experience with terminal, I mean like &lt;em&gt;Git&lt;/em&gt; and all.. you know. I went through the tutorials but it just simply &lt;em&gt;wouldn't work&lt;/em&gt; and I absolutely understood nothing.&lt;/p&gt;
&lt;p&gt;Okay yea then I learned that you could just fork and edit the stuff online.. useful.&lt;/p&gt;
&lt;h3 id=&quot;pull-requests-aargh-&quot;&gt;Pull Requests • Aargh! ✨&lt;/h3&gt;
&lt;p&gt;Here's some picks of the pull requests I made:&lt;/p&gt;
&lt;h4 id=&quot;chaosweb&quot;&gt;ChaosWeb&lt;/h4&gt;
&lt;p&gt;Their website : &lt;a rel=&quot;nofollow&quot; href=&quot;https://chaosweb.vercel.app/&quot;&gt;chaosweb.vercel.app/&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Their repo : &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/vansh-codes/ChaosWeb&quot;&gt;github.com/vansh-codes/ChaosWeb&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eip3xpucyja4lmw9wo0t.png&quot; alt=&quot;screenshotOfTest&quot;&gt;&lt;/p&gt;
&lt;p&gt;Voilà! After my contribution, ChaosWeb looked like this—pure chaos (not really). But then they added more pages and converted it to React.&lt;/p&gt;
&lt;p&gt;My contribution was a script that would play loud, annoying✨ music that's triggered when a button/link or a key is pressed. You can def see my testing &lt;a rel=&quot;nofollow&quot; href=&quot;https://codingkatty.github.io/ChaosWeb/&quot;&gt;here&lt;/a&gt; if you're interested. Beware of loud noise.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;C0MmEnTs&lt;/em&gt;: they rlly should add an epilepsy warning.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;enyanjyoti&quot;&gt;Enyanjyoti&lt;/h4&gt;
&lt;p&gt;Oh well, I didn't really realize it was React before I started editing. At that time, I haven't really &lt;em&gt;touched&lt;/em&gt; React, like how do you even run it!?&lt;/p&gt;
&lt;p&gt;Immediately after that, I started a React ⚛️ course. I didn't know it was &lt;em&gt;this&lt;/em&gt; popular!!&lt;/p&gt;
&lt;p&gt;Their website : &lt;a rel=&quot;nofollow&quot; href=&quot;https://enyanjyoti.vercel.app/&quot;&gt;enyanjyoti.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Their repo : &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Vin205/Enyanjyoti&quot;&gt;github.com/Vin205/Enyanjyoti&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Oh well thankfully my job was easy, just creating a new favicon. I def did not just use &lt;em&gt;Scratch&lt;/em&gt; tho...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nviqsbyz8r926a7mgdy1.png&quot; alt=&quot;favicon&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://scratch.mit.edu&quot;&gt;Scratch&lt;/a&gt; website was def what I needed for this job. The costumes made there are vector and can be exported into .svg format.&lt;/p&gt;
&lt;p&gt;Okay speaking about the React thing, I was confused as of &lt;em&gt;what file&lt;/em&gt; should I insert the favicon, and the worst thing would just be that I couldn't preview it!&lt;/p&gt;
&lt;p&gt;I'd just add it to the index.html, and submitted it without even checking. The next day, it got merged, had my &lt;em&gt;fingers crossed&lt;/em&gt; , as I opened the website. It turned out to be correct! The favicon was updated there.&lt;/p&gt;
&lt;h3 id=&quot;confucius-tion-conclusion-&quot;&gt;Confucius-tion (Conclusion)&lt;/h3&gt;
&lt;p&gt;“min er hao xue, bu chi xia wen” ——Confucius. It means that we should always learn and don't be afraid to ask. I found this idiom resonating deeply throughout my Hacktoberfest journey. Whether I was contributing to different repositories or collaborating with developers, I learned by asking questions and gaining insights from others. From seeking help to eventually helping others, this experience has been all about growth through curiosity and sharing knowledge. ✨&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/434/hacktoberfest-2024-as-a-newbie-contributer</guid>
<pubDate>Wed, 16 Oct 2024 07:24:35 +0000</pubDate>
</item>
</channel>
</rss>