Skip to content

Releases: donkanishka/etch-font-manager

v1.0.2 — Fonts kept safely apart

Choose a tag to compare

@donkanishka donkanishka released this 06 Sep 19:28
10b8cb8

Security fixes

  • Multisite isolation: each site now has its own fonts and generated CSS. Mapped legacy fonts are copied safely without removing shared originals or overwriting shared CSS. Interrupted migrations retry, and new-site creation waits until its tables exist. Single-site paths are unchanged.
  • Bounded WOFF reconstruction: validate the complete layout before allocation, cap reconstructed output at 64 MiB, process tables sequentially, reject excess output, and feed the native decompressor in 1 KiB chunks.

Verification

All five CI jobs passed: PHP 7.4, PHP 8.3, coding standards, JavaScript/boot/translation checks (including 40 WOFF regressions), and a real disposable WordPress 7.0 multisite test. Chrome checks also confirmed valid reconstruction and oversized-output rejection.

Notes: WOFFs reconstructing beyond 64 MiB are not converted; use TTF, OTF or WOFF2. This output limit is not a total browser-memory cap. Multisite migration requires local filesystem locking and hard-link support; failed copies retry.

Download etch-font-manager.zip below, or update from WordPress once the update manifest refreshes.

v1.0.1 — Counts that stay round

Choose a tag to compare

@donkanishka donkanishka released this 04 Sep 05:55
4aaa4fd

Fixed

  • The active filter count in Google Fonts now uses the same 20px circular badge geometry as the other counters.
  • Added a little more space between the Filters label and its count.

Measured live at 20 by 20px with a 6px label gap.

v1.0.0 — Fonts that belong to your site

Choose a tag to compare

@donkanishka donkanishka released this 03 Sep 11:20
8ed5b51

First stable release.

The code is byte-identical to 0.36.4. Nothing was written for the occasion — this is the build that was
installed, run across every screen and reported clean, with the version number changed to say so. The 0.x
numbering existed because only its author had ever installed it. That stopped being true, so the number stopped
claiming otherwise.

What it does

Self-hosted fonts, inside the Etch builder. Upload TTF, OTF, WOFF or WOFF2, or install from Google Fonts
without your visitors' browsers ever calling Google. The files live in wp-content/fonts on your own server
and the stylesheet is yours.

WOFF2 conversion in the browser. TTF, OTF and WOFF are compressed locally before upload — typically 40 to
65% smaller, measured at 65% on Source Code Pro — so the font is never sent anywhere but your own site. Only the
container changes: glyphs, variable axes and OpenType features are untouched.

Typography tokens. Publish a family as the site's heading or body font under the names Etch documents and
Automatic.css reads, so a framework picks it up without you writing a rule.

Per-family delivery. font-display, a fallback stack, a selector list to apply the family to, metric
overrides that hold the line height while the font loads, and preload for the cut a page renders first —
measured starting its request roughly a second before a font discovered through CSS.

Variable fonts. Axes are read from the file itself, including from a WOFF2 that has to be decompressed to
look inside. A tester for tuning an instance, and a font-variation-settings value the site actually uses.

A library that behaves like one. Enable, disable, trash and restore without losing files. A trash that is
genuinely reversible. Import and export with a real dry run that writes nothing. And if you ever remove the
plugin, the fonts and the stylesheet stay unless you ask for them to go.

Requirements

WordPress 6.0+, PHP 7.4+, and Etch. Updates ship through GitHub releases: the plugin reads a manifest and offers
the update in wp-admin like any other plugin.

Thanks

Every release from 0.20.0 onward was shaped by someone pointing at a screenshot and saying what was wrong with
it. The last four came out of exactly that, including a converter that could not load, an install that quietly
replaced font weights, and a card that was crushing its own family name.

v0.36.4 — The name gets its head back

Choose a tag to compare

@donkanishka donkanishka released this 03 Sep 11:11
49e6afb

A family card was crushing its own name to make room for two labels.

Fixed

Role badges wrapped when the family name was long. They sat in the card head as siblings of the title with
nothing stopping the flex row from squeezing them, so a two-word badge such as Body text broke across two
lines and stood 42px tall against its 26px neighbour — and since the tallest item sets the head height, the
whole row lost its alignment. A badge is a label: it now never wraps and never shrinks.

Typing a fallback stack left the Generated CSS box showing the previous one. The field updated the family
and the save bar but never refreshed the preview, while picking the same stack from Common stacks did — so one
field disagreed with itself depending on how it was filled in. It now refreshes that box in place, the way
Apply to already did.

Changed

The card's state badges moved to their own row under the head. Stopping the wrap fixed the break but not the
cause: with three action buttons beside it, a name as ordinary as Poppins was still truncated to
Poppi.... Disabled, both role badges, No files and Files missing now sit together on a row below
the title.

Measured: the title goes from 132px to 353px and stops truncating. The footer keeps the 259px the
weights list needs — which putting the badges there would have taken, and nine static weights fills that by a
single pixel.

The row is only rendered when something is on it, so an ordinary family card is exactly as tall as before. Cards
carrying state grow by 34px, and a grid row still lines up because the footer is pinned with
margin-block-start: auto and the row stretches to its tallest card. Checked with a mixed grid — cards with
and without badges all measuring 182px — and in the row layout as well.

v0.36.3 — What a fresh install showed

Choose a tag to compare

@donkanishka donkanishka released this 03 Sep 10:40
569b21b

Three faults found by working through every screen of a brand new site with no fonts on it. One is a
regression I shipped in 0.36.2, so anyone on that release has a library quietly showing the wrong preview.

Fixed

Opening Google Fonts changed the library's preview. 0.36.2 made that screen prefer Latin over Auto, which is
right for browsing two thousand mixed-script families. But it did it by writing Latin into shared state, and
nothing put it back — so the font library stopped using Auto for the rest of the session, and permanently as soon
as anything saved preferences.

That removes a signal the panel exists to give. A Latin pangram renders identically whether or not a family
actually carries the glyphs, so Auto — each family in its own script — is what makes a missing subset visible at
a glance. The preference is now computed per view rather than stored: Google Fonts shows Latin, the library shows
each family in its own script, and only a deliberate choice changes either.

A font already in the library could be converted and uploaded all over again. The guard that catches a
re-picked file compared it against the library by raw name, but the server stores names through
sanitize_file_name() — so Atlas Test Sans Regular.ttf sits on disk as Atlas-Test-Sans-Regular.woff2
and the comparison never matched. Picking it again spent a conversion and a whole upload before the server turned
it away, which on a re-picked folder is minutes rather than seconds. Both sides are normalised now, for the
same-file case and for the WOFF2 twin of a convertible one.

Same root cause as the missing-family bug fixed in 0.36.2: the panel trusting a name the server rewrites.

The Generated CSS preview showed a fallback the stylesheet does not contain. familyStack() always
appended , sans-serif and ignored the family's own fallback, while family_stack() on the server uses that
fallback and writes nothing when it is empty. The preview claimed a generic that was never written, and would
have shown sans-serif for a family whose stack was set to Georgia, serif. The two now agree, checked
against five records including metric-matched ones.

Third time the preview has drifted from the generator.

What the same pass confirmed working

On a fresh install, from an empty library: the WOFF2 converter (TTF 210,312 → 73,816 bytes, 65% smaller),
Google Fonts search, filters, pagination and the type tester, install with chosen weights and subsets, the
0.36.2 Reinstall guard opening on what is actually installed and confirming before dropping anything, duplicate
detection by file contents, enable, disable, trash, restore, the derived dirty flag, settings including inline
CSS, the export and import dry run, preload, and the typography tokens reaching the front end — body computes
to the installed family and only the preloaded cut is fetched.

Fifty checks passed; these three did not.

v0.36.2 — The converter your browser can actually reach

Choose a tag to compare

@donkanishka donkanishka released this 03 Sep 05:12
0d2cc9b

If you installed 0.36.1 and found conversion still broken, this is why. The converter in 0.36.1 was
correct. Your browser never loaded it.

Fixed

Nothing in the converter's directory carried a cache key. The worker, the Emscripten glue and the binary are
a matched set — the glue holds the import table the binary declares — and none of the three was versioned, so a
browser that had run an earlier release kept whichever copies it already had. Measured on a real install after
0.36.1: a current 815,926-byte binary paired with an 11,437-byte glue from 0.35.0, which cannot boot.

All three now carry the binary's own modification time. The panel puts it on the worker URL and the worker
forwards the same query to importScripts() and to locateFile() — necessary, because resolving a bare
filename against the worker's own URL drops the query. Versioning only the worker was tested and still failed
with module[entry] is not a function.

Reinstall could quietly take weights away. The install replaces a family's variant list rather than
merging into it, while the Google Fonts card chose its own defaults from the catalogue: the variable cut
whenever one existed, and weights 400 and 700. A family installed as five static weights therefore opened with
Variable ticked, and pressing Reinstall replaced all five files with one — silently. An installed family
now opens on what it actually has, so Reinstall means the same again, and changing the selection asks first
and names the variants that would go.

An upload whose file name the server rewrote never got a family. The panel adopted each file by the name it
sent rather than the name that was written. sanitize_file_name() turns spaces into dashes, so
Acme Sans Regular.ttf lands as Acme-Sans-Regular.ttf, and the adopt step skips a name it cannot find
without saying so. The font uploaded, appeared under Font files, and no family was ever created — which is why
only some uploads seemed to work.

Add variant scrolled away from the row it had just made. Generated CSS sits above the variants table and
grows with each face until it reaches its 320px cap, pushing the table down by however much it gained — 278px on
a family with one variant. Nothing moves once that block is capped, which is why smaller families did this and
larger ones never did.

Changed

  • Google Fonts opens on Latin rather than Auto, so families in different scripts are compared against the
    same sample. The library keeps Auto, where each family previewing in its own script is what makes a missing
    subset visible. Applied only until you touch the script row, and remembered after that.
  • The Font files toolbar drops its Format label, kept as the group's accessible name. Weights and
    Subsets keep theirs, where the tokens mean nothing alone.
  • A WOFF2 upload says it was left alone instead of passing without comment.

Also

CI now runs no-undef over the panel and the worker. It would have caught both runtime breaks this project has
shipped: 0.34.0's read-before-assignment and 0.36.0's reference to a variable the same release removed. Neither
node --check nor the boot check can see those.

Verified

The shipped pair was checked before this was written: booted in a real browser worker with both entry points
present, and Source Code Pro converted 210,312 → 73,816 bytes, 65% smaller, signature wOF2.

v0.36.1 — The converter comes back

Choose a tag to compare

@donkanishka donkanishka released this 02 Sep 15:42
afb5022

A hotfix. 0.36.0 shipped with no working font converter, and with three sections missing from every Google family's editor. Both were introduced by that release.

Fixed

  • The WebAssembly module could not load in a browser at all. woff2_dec.cc calls assert() and the encoder sources never did, so compiling the decoder in gave the binary a sixth import, __assert_fail, which the glue leaves undefined at -sASSERTIONS=0. A browser refuses to link a module whose import is not callable, so it died on boot and took the converter, the WOFF2 decoder and the metrics reader with it. Rebuilt with -DNDEBUG: the asserts compile out, the import goes with them, five imports again. Verified in a real browser worker on a real WOFF2 — 132,444 bytes decoded, fvar intact, metrics 100 / 30 / 0.
  • The build's smoke test could not have caught it. It runs in Node, which instantiated the same module happily and reported success on a binary that was dead everywhere it actually runs. It now inspects the import object before instantiating and fails if any value is not callable, which is the browser's own check and does not care where the test runs.
  • Generated CSS, Variants and Add variant were missing from every Google family's editor. googleSection() still referenced a variable 0.36.0 had removed, so render() threw partway and everything after the Google Fonts heading was never appended. Uploaded families never enter that code, which is why it survived testing.
  • The panel restyled its own headings. 0.36.0 stopped the server sending page rules to the builder, and the panel went on injecting the whole stylesheet into the builder document, undoing it on the client. The canvas still gets the stylesheet, because the canvas is a page; the builder document does not, and faces reach the panel through the font set instead.
  • Etch's settings-bar tooltips were buried. Raising the manager to z-index: 950 to clear a third-party overlay at 900 also put it over the bar's tooltips at 102. Back to 60, and that overlay is hidden while the manager is open instead.
  • Apply to was hard to type in. Its warnings were refreshed by re-rendering the whole pane — specimen, variants table and CSS preview included — between keystrokes. It now writes to the three nodes that change, which is what the axis sliders already did.
  • A font read that could not answer waited two minutes, on a timeout written for compression. Reads get twenty seconds, so a failure says so rather than sitting on "Loading".

Changed

  • Override theme styles stays visible and goes disabled when Apply to is empty, rather than disappearing. It only ever acted on that rule, but a control that vanishes reads as a control that broke.
  • A family's typography role is an accent badge on its card rather than plain muted text, so Body text no longer sits at the weight of a caption.

Everyone on 0.36.0 should take this one. Full detail in CHANGELOG.md.

v0.36.0 — The builder gets its own interface back

Choose a tag to compare

@donkanishka donkanishka released this 02 Sep 10:55
d054766

Two features that had been waiting on a binary, and seven fixes. Most of the fixes are consequences of 0.35.0 making the typography tokens real, and every one of them was found by using the panel rather than by reading it.

Fixed

  • The typography tokens were restyling the Etch builder itself. ?etch=magic is a front-end request, so the generated stylesheet loads in the builder shell like anywhere else -- and once the tokens started writing a real rule, body, p, li, a, button reached into Etch's own interface. Measured there: Etch's body, its paragraphs and its buttons all rendered in whichever family held the body-text token. The same was quietly true of any Apply to naming a bare element. build_css() now draws a line between declaring fonts and painting a page. The builder shell and the block editor get the faces and the family variables; the canvas iframe and the real front end still get everything, because both are the page.
  • The Google weights row was a delete control wearing a download label. Installing replaces a family's variant list with whatever that row holds, so unticking a weight and pressing Download selection downloaded nothing and removed the weight. Installed weights are ticked and locked now, the row only adds, and the button counts what it will actually fetch. Weights are removed in the Variants table, which says so.
  • Variable axes were offered for fonts that have none. They were read from Google's catalogue, which describes a family however it was fetched, so a library holding five static Open Sans weight files was given sliders for an axis none of those files carry. Whether a family is variable is now read from the install: a weight stored as a range, or axes actually read out of a file.
  • Apply to now refuses any selector a typography token covers, not only one this family's own token covers. A second family naming h1 used to win, because a plain selector beats a token, and quietly took the heading font from whoever held it. The two features no longer overlap: the tokens own their tags and Apply to owns everything else.
  • The warnings under Apply to were never live. The field updated the save bar and nothing else, so the lines naming refused selectors sat stale while you typed the very thing they warn about.
  • A disabled family counted as holding its token in the panel and as holding nothing in the stylesheet, so the panel dropped an Apply to selector that the stylesheet then wrote.
  • The panel sat below overlays painted by other plugins. At z-index: 60 a fixed layer at 900 drew over the manager. It sits at 950 now: above the builder's decorations, below Etch's menus and toasts at 999 and 1000.

Added

  • The WOFF2 decoder. Only the encoder half of google/woff2 was compiled in, so a variable font that arrived already compressed carried its axes where nothing could read them. Verified by the build's own smoke test, on the binary that ships: Inter 876,576 to 350,352 and back to 878,964 bytes with fvar intact, and a decoder that refuses garbage. It cost 72,201 bytes.
  • Reading a font already on the server. A family whose axes are unknown offers to read its files. An empty result is recorded as "looked, found nothing"; a file that cannot be read stores nothing, so a failed read is never mistaken for a font without axes.
  • Holding the space while a font loads. An optional metric-matched fallback per family, carrying that font's own vertical metrics from its hhea table, so line boxes are the right height before the web font arrives.

Changed

  • The format filter on Font files shows even when the folder holds one format, because a single WOFF2 8 chip says everything is already converted.
  • An already-installed Google weight is ticked, so having one looks different from having asked for one.

Full detail in CHANGELOG.md.

v0.35.0 — Tokens that reach the page

Choose a tag to compare

@donkanishka donkanishka released this 01 Sep 14:53
f37a99c

An eleventh pass, eight items, driven a screenshot at a time. Its spine is that a control should mean what it says: a checkbox should belong to something, a label should describe what is true, and a switch should change something.

The largest finding was not in the list it came from. Asked why the typography tokens did nothing, the answer was that they had never done anything, on any site.

Fixed

  • The typography tokens never reached the page. Ticking a role declared --heading-font-family and stopped, leaving the rule that uses it to Automatic.css, which only writes one once its Typography section is configured, and which is not there at all on a site without ACSS. Measured on a live install: nothing read either token, so Use for headings was a switch that changed no pixels. The block now carries the rule as well, on the same selectors ACSS uses, so a configured ACSS site gets two rules that agree and every other site gets one that works.
  • The Automatic.css bridge never fired. The token block was attached to ACSS's own stylesheet handle at priority 99 and bailed when that handle was not registered yet, leaving an empty <style id="automaticcss-core-inline-css"> on the page. That ordering matters now the block carries a body rule, because ACSS sets body { font-family: system-ui } directly. It uses a handle of its own, enqueued last, so it no longer depends on another plugin's timing.
  • Files in the trash claimed to be in use. "In use" was true of any file a family mapped, including a family that is trashed or disabled and therefore emits no @font-face at all. Three states now: in use, not loaded, unused. What counts as safe to delete is unchanged, because restoring a trashed family still needs its files.
  • A select-all in one file table answered for the others. It compared how many files were selected against how many the table held, so choosing two files anywhere made a two-file table read as fully selected, and unticking any header cleared the selection in all three.
  • Apply to no longer writes a selector its own role already covers. Both would have set font-family on the same element, leaving source order to decide.
  • Tested up to said 6.8 while the plugin runs on 7.0.

Added

  • A search field and format chips on Font files. The screen lists the whole shared fonts folder and has no ceiling, so it gained the toolbar its sibling screens already had.
  • A select-all in each file table's head, scoped to that group, beside the one in the bulk bar that answers for every group at once.
  • A notice when Etch is not active. The panel opens from the Etch Settings Bar and nowhere else, so without Etch the plugin activated, kept serving its fonts, and offered no way in.

Changed

  • The select-all checkbox that floated above the grid is gone. It sat outside the cards it selected, with no label and nothing to head. Select all is a word in the bulk bar now, and it counts what a filter left on screen.
  • Taking a typography token from another family says so, under the toggle rather than in a toast: it is unsaved state, so it behaves like unsaved state and Discard is what puts it back.

Full detail in CHANGELOG.md.

v0.34.1 — The panel opens again

Choose a tag to compare

@donkanishka donkanishka released this 01 Sep 00:23
696deea

A hotfix. 0.34.0 could not be opened at all — please update.

Fixed

  • The Font Manager control never appeared. The settings key list added in 0.34.0 was declared beside the functions that read it, several hundred lines below the state object that fingerprints the settings as it is built. var hoisting supplies the name but not the value, so the fingerprint read undefined, panel.js threw before it could register its Settings Bar control, and the plugin was unreachable from the builder. The list is now declared above the state it feeds.
  • CI could not have caught it. node --check parses a file and stops there, so it passed happily on a script that died on its first line of real work. tools/boot-check.js now boots the panel against a stubbed DOM and fails the build if it throws; run against 0.34.0 it reproduces the exact error.

Everything in 0.34.0 is unchanged and now reachable.