Progress on drawing the Word Counting Calendar programmatically from parameter data. Getting closer!


I felt a lot better on Sunday, so I made up for lost time by working on the PDF generator for Word Counting Calendars! The cell drawing uses a constructed grid based on key subjects, which means that I can change the base grid size (used for drawing the blocks) and the labels (both font and size) and the cell will redraw correctly.


The rendering order starts in the upper left, inset by a MARGIN. The DAY is drawn and the width is used to index to the next column, which is the alignment point for the LABELS and DIVIDER LINES before the PYRAMID is drawn. The size of the pyramid itself is determined by the BLOCK DIMENSION, which specifies the size of each individual BLOCK as well as the strokes, gaps, and proportional vertical spaces across the cell. Each cell also is drawn using a base color specified in a color table, using various opacity levels to achieve the unified color look.
Here's the parameterization that determines the cell's layout:
/// COLORS ///
const clr_default = [
cmyk(0.7423, 0.2818, 1, 0.1399),
cmyk(0.74, 0.0, 0.1, 0),
cmyk(0.57, 0.49, 0.59, 0.23),
cmyk(0.0, 0.55, 0.83, 0)
];
const clr_cell = R(clr_default);
/// PARAMETERS ///
const DIM = 8; // 8 pt is unit base
const DSIZE = 13; // fontsize of DAY
const LSIZE = 9; // fontsize of LABELS
const SSIZE = 5.5; // fontsize of SPECIAL
/// DERIVED PARAMETERS ///
const MRGN = DIM / 2;
const SWI = DIM / 8; // stroke width
const INS = SWI / 2; // stroke inset
const GAP = SWI * 3; // gap between boxes
The actual drawing code uses these values for spacing elements. There's a separate bit of code that precalculates the critical dimensions of elements:
// CALCULATE DIMENSIONS OF DYNAMIC ELEMENTS
const dayString = String(DAY).padStart(2, '0');
const DWIDTH = font.widthOfTextAtSize(dayString, DSIZE);
const DHEIGHT = font.heightAtSize(DSIZE, { descender: false });
const LABELS = ['2500+', '2250', '1750'];
let LWIDTH = 0; // text labels
for (let label of LABELS) {
const tw = font.widthOfTextAtSize(label, LSIZE);
if (tw > LWIDTH) LWIDTH = tw;
}
const LHEIGHT = font.heightAtSize(LSIZE, { descender: false });
let PWIDTH = (PSIZE - 1) * (DIM + GAP) + DIM; // pyramid
const PHEIGHT = PWIDTH;
const CELLW = MRGN + DWIDTH + GAP + LWIDTH + PWIDTH + MRGN;
const CELLH = MRGN + DHEIGHT + DIM + PHEIGHT + DIM + MRGN;
const SHEIGHT = font.heightAtSize(SSIZE, { descender: false });
const SWIDTH = font.widthOfTextAtSize(SPECIAL, SSIZE);
It's a bit fragile and janky as this is prototype code; I'll be cleaning it up later once I figure out the best way to write these kind of layout-based calculations more cleanly as components that have some kind of layout manager specific to my form design.
It was nice to spend several hours over the weekend getting this to work. I started with just this:


Seems like good progress to me!
2025 Building Challenge Posts
Making an URSYS App Example
Adding Typescript support to Eleventy
Review of Old Design Work
Improving my Eleventy Atom Feeds
Managing a Productivity Crash
Activity Bingo Board: Layout with Affinity Designer
ETP 5885 Notebook Press Run Prep
Activity Bingo Board Revisions
ETP 5885 Notebook Press Tour
A Silly Pass at Logo Design
Unprofessional Business Cards
Word Counting Calendar PDF Quickie Reuse
Word Counting Calendar PDF Now Available!
Word Counting Calendar Preparing to Code
Word Counting Calendar Simple Beginnings
Articulating Friendship
First skip day due to day trip to Concord, etc.
Making a PDF-LIB Reference
Word Counting Calendar Drawing Blocks
Minimum Progress Despite Nausea
Word Counting Calendar Drawing Blocks II
Writing A Mythical Magical Adventure Cat Primer
Word Counting Calendar Drawing Days
Word Counting Calendar Drawing Spaces
A Restorative Visit to the North Shore
Word Counting Calendar: Alpha Release!
ETP 5885 Notebook Production Update!
Personal Cards Revisited
11/21 - Visiting an Old Friend in Beverly, MA
Experimental Collaboration
Short Productive Sprint Day
Thanksgiving Reset Break
ETP 5885 Notebook back on Amazon!
ETP 365 Day Journal Updated for 2026!
Making a Freelance Services Page
BUILD CHALLENGE COMMENTARY
I'm glad that the 3-4 day period of lethargy/nausea has passed, and I should get another couple of weeks of relatively good progress in. This is day 19, so there are just about two calendar weeks left before the 30-DAY BUILD CHALLENGE is completed.
So far, I think I've discovered a few things already:
- I do like making things to share every day, even though they aren't always a finished project.
- I've been working on different things (e.g. business cards, planning docs) as well as this software, but they're all related to delivering something that could serve my business needs. So, that's great! It seems sustainable so far.
- The blogging process, including tooling, has stabilized! This means that writing online feels as effortless as it used to on early Wordpress 1.0!
There is an element of joy and whimsy that still seems missing, though. I'm hoping I stumble upon it in the next couple of weeks.
BONUS ACHIEVEMENTS
I had a look at ollama, which is a way to download and run open source Large Language Models (e.g. "AI Chat Bots") locally if you have enough memory in your computer. I happen to have an Apple Macbook Pro M1 which has unified memory, so I'm able to run the gpt-oss-20b model in about 15GB; it's roughly comparable to one of the "mini" models that are useful for doing document processing. By running these locally, I avoid incurring the cost of using a service that charges by the token.
Also, I am still totally obsessed by K-Pop Demon Hunters. It totally lights up my bitey little autistic heart.
Skip Day 02! Minimum Progress Despite Nausea
BUILD 20/30: Writing A Mythical Magical Adventure Cat Primer
We chat about personal projects and challenges on the DS|CAFE Community Discord Server every day. Come visit! Maybe you'll make some friends!
You can reach me at Mastodon or Bluesky. Or subscribe to the blog feed to stay up-to-date.