This website is meant to be read and understood quickly by humans, but is only fully parsable, on a technical level, with the aid of an AI system. Read why →
Loop MMT
Never-Clip Title Sizingfit← all gifts

forest-title-fit

Pick the largest font size at which a title still fits a fixed width — and never clip, never ellipsize: if it can't fit even at the floor, it wraps on spaces instead. A general shrink-to-fit UI primitive with the font-measuring step injected as a seam, so the fitting logic is pure and testable without a browser.

The honest edge
It sizes to the measure() you inject — only as accurate as your measurer (a webfont still loading measures as its fallback). It searches integer sizes and breaks on whitespace, not hyphens; a single word wider than the box overflows visibly, by design. It computes sizes and lines; the caller renders.
Run it
node forest-title-fit.js --demo test_forest-title-fit.js (23/23) JavaScript, zero dependencies (Node + browser)
The code — every file that ships
forest-title-fit.js172 lineson GitHub →
#!/usr/bin/env node
// SPDX-License-Identifier: MIT
"use strict";
/* forest-title-fit.js — shrink-to-fit-never-clip title sizing.

   The sign-painter's problem: a title must fit a fixed width, at the largest size
   that still fits, and it must NEVER clip and NEVER get an ellipsis — if it can't
   fit on one line even at the floor size, it wraps or the caller is told, but a
   character is never silently cut. This is a general UI primitive: a card title, a
   nav label, a chart caption, a poster headline.

   THE SEAM. Measuring rendered text needs a font engine, which lives in the DOM
   (`canvas.getContext('2d').measureText`). To keep this primitive PURE and testable
   without a browser, the measuring function is INJECTED, not imported: every entry
   point takes a `measure(text, fontSize) -> width` callback. In a browser you pass
   a one-line canvas-backed measurer (see makeCanvasMeasure below, guarded so it is
   defined only when a DOM exists); in a test you pass a deterministic stub. The
   fitting LOGIC — the binary search for the largest fitting size, the floor, the
   never-clip guarantee — is the same pure function in both worlds.

   No DOM access in the core, no filesystem, no network. Node (module.exports) or
   browser (window.LoopGifts.titleFit). */

/* fitFontSize(text, width, opts) -> {fontSize, fits, width: measuredWidth, floored}
   Find the largest integer font size in [min, max] whose single-line measured width
   is <= the available width.
     text    : the string to fit
     width   : available width in the same unit measure() returns
     opts.measure : REQUIRED (text, fontSize) -> width. The seam.
     opts.min : floor font size (default 8). Never returns below this.
     opts.max : ceiling font size (default 96).
   Return:
     fontSize : the chosen size (>= min always — never clips by shrinking past floor)
     fits     : true iff the text fits at the returned size (false => it overflows
                even at the floor; the caller decides to wrap — see wrapToWidth)
     width    : measured width at the returned size
     floored  : true iff the search bottomed out at min without fitting */
function fitFontSize(text, width, opts) {
  opts = opts || {};
  var measure = opts.measure;
  if (typeof measure !== "function")
    throw new Error("fitFontSize: opts.measure(text, fontSize) is required (the seam)");
  var min = (opts.min === undefined) ? 8 : opts.min;
  var max = (opts.max === undefined) ? 96 : opts.max;
  if (min > max) throw new Error("fitFontSize: min > max");
  if (typeof width !== "number" || width <= 0) throw new Error("fitFontSize: width must be > 0");

  // Largest size whose width <= available. Binary search over integer sizes.
  var lo = min, hi = max, best = null;
  while (lo <= hi) {
    var mid = Math.floor((lo + hi) / 2);
    var w = measure(text, mid);
    if (w <= width) { best = mid; lo = mid + 1; }  // fits — try bigger
    else { hi = mid - 1; }                          // too wide — go smaller
  }
  if (best !== null) {
    return { fontSize: best, fits: true, width: measure(text, best), floored: false };
  }
  // Nothing fit, not even min. Never clip: return the floor and report !fits.
  return { fontSize: min, fits: false, width: measure(text, min), floored: true };
}

/* wrapToWidth(text, width, fontSize, measure) -> [line, line, ...]
   Greedy word-wrap so no line's measured width exceeds `width` at `fontSize`.
   The never-clip fallback when fitFontSize reports !fits: instead of cutting or
   ellipsizing, break on spaces. A single word wider than `width` is placed on its
   own line intact (still never clipped — overflow is visible, not hidden). */
function wrapToWidth(text, width, fontSize, measure) {
  if (typeof measure !== "function")
    throw new Error("wrapToWidth: measure(text, fontSize) is required");
  if (typeof width !== "number" || width <= 0) throw new Error("wrapToWidth: width must be > 0");
  var words = String(text).split(/\s+/).filter(function (w) { return w.length > 0; });
  var lines = [], cur = "";
  for (var i = 0; i < words.length; i++) {
    var candidate = cur ? (cur + " " + words[i]) : words[i];
    if (measure(candidate, fontSize) <= width || cur === "") {
      // fits, OR the line is empty (a too-long single word goes on its own line intact)
      if (measure(candidate, fontSize) <= width) { cur = candidate; }
      else { lines.push(words[i]); cur = ""; }     // lone oversized word: own line
    } else {
      lines.push(cur); cur = words[i];
    }
  }
  if (cur) lines.push(cur);
  return lines.length ? lines : [""];
}

/* fitTitle(text, width, opts) -> {fontSize, lines, fits, floored}
   The convenience entry: try to fit on ONE line at the largest size; if it can't
   fit even at the floor, keep the floor size and WRAP (never clip, never ellipsize).
   Returns the chosen size and the line array to render. */
function fitTitle(text, width, opts) {
  var f = fitFontSize(text, width, opts);
  if (f.fits) return { fontSize: f.fontSize, lines: [String(text)], fits: true, floored: false };
  var measure = (opts || {}).measure;
  return {
    fontSize: f.fontSize,
    lines: wrapToWidth(text, width, f.fontSize, measure),
    fits: false,
    floored: f.floored
  };
}

/* makeCanvasMeasure(fontFamily) -> measure(text, fontSize) using a real canvas.
   Defined only in a browser (guarded); this is the DOM half kept OUT of the pure
   core. In Node it is a no-op that throws if called, so the seam stays explicit. */
function makeCanvasMeasure(fontFamily) {
  fontFamily = fontFamily || "sans-serif";
  if (typeof document === "undefined")
    return function () { throw new Error("makeCanvasMeasure: no DOM (pass your own measure in Node)"); };
  var ctx = document.createElement("canvas").getContext("2d");
  return function (text, fontSize) {
    ctx.font = fontSize + "px " + fontFamily;
    return ctx.measureText(String(text)).width;
  };
}

// ---- dual-runtime export ---------------------------------------------------
if (typeof window !== "undefined") {
  window.LoopGifts = window.LoopGifts || {};
  window.LoopGifts.titleFit = {
    fitFontSize: fitFontSize, wrapToWidth: wrapToWidth,
    fitTitle: fitTitle, makeCanvasMeasure: makeCanvasMeasure
  };
}
if (typeof module !== "undefined" && module.exports) {
  module.exports = {
    fitFontSize: fitFontSize, wrapToWidth: wrapToWidth,
    fitTitle: fitTitle, makeCanvasMeasure: makeCanvasMeasure
  };
}

// ---- CLI (value-arg + --demo; reads NO files) ------------------------------
if (typeof require !== "undefined" && require.main === module) {
  var args = process.argv.slice(2);
  // A deterministic demo measurer: width = chars * fontSize * 0.6 (a monospace-ish model).
  function demoMeasure(text, fontSize) { return String(text).length * fontSize * 0.6; }

  function printDemo() {
    var width = 300;
    var cases = ["OK", "A Longer Title Here", "SupercalifragilisticexpialidociousUnbreakableWord"];
    process.stdout.write("# forest-title-fit demo (available width = " + width + ", monospace-ish model)\n");
    for (var i = 0; i < cases.length; i++) {
      var r = fitTitle(cases[i], width, { measure: demoMeasure, min: 8, max: 72 });
      process.stdout.write(
        JSON.stringify(cases[i]) + " -> size " + r.fontSize +
        ", " + r.lines.length + " line(s), fits=" + r.fits +
        (r.floored ? " (floored)" : "") + "\n");
      for (var k = 0; k < r.lines.length; k++)
        process.stdout.write("    | " + r.lines[k] + "\n");
    }
    process.exit(0);
  }

  if (args[0] === "--help" || args[0] === "-h") {
    process.stdout.write(
      "forest-title-fit — shrink-to-fit-never-clip title sizing\n\n" +
      "  node forest-title-fit.js --demo           fit a few sample titles to a fixed width\n" +
      "  node forest-title-fit.js '<title>' [width] fit one title (uses a monospace-ish model)\n\n" +
      "Library: fitFontSize / fitTitle / wrapToWidth, each taking an injected\n" +
      "measure(text, fontSize) seam. makeCanvasMeasure() supplies the browser one.\n" +
      "Pure — no DOM in the core, no files, no network. Exit 0.\n");
    process.exit(0);
  }
  if (args[0] === "--demo" || args.length === 0) { printDemo(); }
  else {
    var w = args[1] ? parseInt(args[1], 10) : 300;
    var res = fitTitle(args[0], w, { measure: demoMeasure, min: 8, max: 72 });
    process.stdout.write(JSON.stringify(res, null, 2) + "\n");
    process.exit(0);
  }
}
test_forest-title-fit.js93 lineson GitHub →
#!/usr/bin/env node
// SPDX-License-Identifier: MIT
/* test_forest-title-fit.js — proves the title-fit primitive implements its contract.

   THE ORACLE. There is no stdlib that says "is this the largest font that fits?" —
   so the oracle is CURATED VECTORS against a DETERMINISTIC injected measurer
   (width = chars * fontSize * k), which makes the expected size exactly computable:
   the largest integer size s with len*s*k <= width is floor(width/(len*k)). We
   assert: (1) fitFontSize returns exactly that size and reports fits=true; (2) it
   picks the LARGEST fitting size (size+1 would overflow); (3) when nothing fits even
   at the floor it returns the floor with fits=false and floored=true — it NEVER
   returns below min (never clips by shrinking past the floor); (4) wrapToWidth never
   emits a line wider than width, breaks on spaces, and keeps a too-long single word
   intact on its own line (never clips); (5) fitTitle wraps at the floor instead of
   ellipsizing. Plus determinism and a mutation-bite (a constant-size core fails).
   Exit 0 = all pass; exit 1 = failure. stdlib only. */
"use strict";
var m = require("./forest-title-fit.js");

var pass = 0, fail = 0;
function ok(label, cond) { if (cond) { pass++; } else { fail++; console.error("FAIL  " + label); } }
function eq(label, a, b) { ok(label + " (=" + JSON.stringify(b) + ")", a === b); }
function threw(label, fn) { var d = false; try { fn(); } catch (e) { d = true; } ok(label + " (throws)", d); }

// deterministic measurer: width = chars * fontSize * k
var K = 0.6;
function measure(text, fontSize) { return String(text).length * fontSize * K; }
function largestFit(len, width) { return Math.floor(width / (len * K)); }

// ---- 1. fitFontSize returns exactly the largest fitting size ---------------
var r1 = m.fitFontSize("ABCD", 300, { measure: measure, min: 8, max: 96 });
var expect1 = largestFit(4, 300); // floor(300/2.4) = 125 -> capped at max 96
expect1 = Math.min(expect1, 96);
eq("largest fitting size (capped at max)", r1.fontSize, expect1);
ok("reports fits", r1.fits === true);

// a case where the true optimum is below the cap
var r2 = m.fitFontSize("ABCDEFGHIJ", 300, { measure: measure, min: 8, max: 96 });
var expect2 = largestFit(10, 300); // floor(300/6) = 50
eq("largest fitting size (uncapped)", r2.fontSize, expect2);
// and prove it's the LARGEST: size+1 overflows
ok("size+1 overflows (it's truly largest)", measure("ABCDEFGHIJ", r2.fontSize + 1) > 300);
ok("chosen size fits", measure("ABCDEFGHIJ", r2.fontSize) <= 300);

// ---- 2. never clips: floor when nothing fits -------------------------------
var r3 = m.fitFontSize("aVeryLongUnbreakableTitleString", 30, { measure: measure, min: 8, max: 96 });
ok("floored size == min", r3.fontSize === 8);
ok("reports !fits when even floor overflows", r3.fits === false);
ok("reports floored", r3.floored === true);
ok("NEVER returns below min", r3.fontSize >= 8);

// ---- 3. input guards -------------------------------------------------------
threw("requires measure seam", function () { m.fitFontSize("x", 100, {}); });
threw("rejects non-positive width", function () { m.fitFontSize("x", 0, { measure: measure }); });
threw("rejects min > max", function () { m.fitFontSize("x", 100, { measure: measure, min: 50, max: 10 }); });

// ---- 4. wrapToWidth: never a line wider than width, never clips -------------
var lines = m.wrapToWidth("one two three four five six", 60, 10, measure); // each word small
for (var i = 0; i < lines.length; i++)
  ok("wrap line " + i + " within width", measure(lines[i], 10) <= 60 || lines[i].split(/\s+/).length === 1);
ok("wrap breaks into multiple lines", lines.length > 1);
ok("wrap preserves all words", lines.join(" ").split(/\s+/).sort().join(",") ===
   "five,four,one,six,three,two");

// a single oversized word goes on its own line, intact (never clipped)
var wl = m.wrapToWidth("tiny enormouslylongwordthatcannotfit tiny", 40, 10, measure);
ok("oversized word kept intact", wl.indexOf("enormouslylongwordthatcannotfit") !== -1);

// ---- 5. fitTitle: wraps at floor instead of ellipsizing --------------------
var t1 = m.fitTitle("Short", 300, { measure: measure, min: 8, max: 72 });
ok("short title fits on one line", t1.fits === true && t1.lines.length === 1);
var t2 = m.fitTitle("several words that will not fit on one single line here", 60,
                    { measure: measure, min: 8, max: 72 });
ok("overflowing title wraps, does not ellipsize", t2.lines.length > 1);
ok("no ellipsis introduced", t2.lines.join(" ").indexOf("\u2026") === -1 &&
   t2.lines.join(" ").indexOf("...") === -1);

// ---- 6. determinism --------------------------------------------------------
ok("fitFontSize is deterministic",
   m.fitFontSize("ABCDE", 200, { measure: measure }).fontSize ===
   m.fitFontSize("ABCDE", 200, { measure: measure }).fontSize);

// ---- 7. mutation-bite ------------------------------------------------------
// A degenerate core that returned a constant size regardless of text/width would
// give the same size for a short title in a wide box and a long title in a narrow
// box. Prove they DIFFER.
var wide = m.fitFontSize("Hi", 400, { measure: measure, min: 8, max: 96 }).fontSize;
var narrow = m.fitFontSize("A much longer title", 80, { measure: measure, min: 8, max: 96 }).fontSize;
ok("MUTATION-BITE: size responds to text length and width", wide !== narrow && wide > narrow);

// ---- report ----------------------------------------------------------------
console.log((fail === 0 ? "PASS" : "FAIL") + "  " + pass + "/" + (pass + fail));
process.exit(fail === 0 ? 0 : 1);
Take the whole folder → MIT JavaScript, zero dependencies (Node + browser)