Skip to content

Keep every font alias a package registers - #36

Open
jakejackson1 wants to merge 1 commit into
gravitypdffrom
fix/fonttrans-alias-collapse
Open

Keep every font alias a package registers#36
jakejackson1 wants to merge 1 commit into
gravitypdffrom
fix/fonttrans-alias-collapse

Conversation

@jakejackson1

Copy link
Copy Markdown
Member

Summary

initFontRegistry() ends by deduplicating each of the six properties it merged from the font packages. That is right for five of them — backupSubsFont, BMPonly, sans_fonts, serif_fonts and mono_fonts are lists of font keys, where a repeat is noise.

fonttrans is the sixth, and it is not a list. It maps an alias to the font it resolves to, keyed by alias, and array_unique() compares values. So two aliases naming one font collapse to whichever came first — silently, with no warning and nothing in the merged config to hint at it.

A package registering the obvious pair for a metric-compatible substitute:

public function getFontAliases()
{
    return [ 'arial' => 'somefont', 'helvetica' => 'somefont' ];
}

ends up with arial alone, and font-family: helvetica stops resolving for a reason the package author cannot see from their own code. Aliasing several names onto one font is the ordinary case for a substitute family, not an edge one.

Try it

Register a package whose getFontAliases() returns two aliases for the same font, construct Mpdf, and inspect $mpdf->fonttrans. Before this change it holds one entry; after, both.

Test plan

InitFontRegistryTest::testEveryAliasSurvivesEvenWhenTheyNameOneFont covers it, with TestFontRegistrationA gaining the two-aliases-one-font pair. Reverting the fix errors the test with Undefined array key "aliasTwo", so it pins the behaviour rather than merely describing it.

Full suite 1,123 tests / 2,644 assertions, OK; composer cs clean.

The change
$merged = array_merge($values, $this->$property);
$this->$property = $property === 'fonttrans' ? $merged : array_unique($merged);

The precedence the original expressed is unchanged — package values still come before whatever is already on the property, and for fonttrans array_merge()'s own last-wins-per-key behaviour is the correct rule for a map. Only the deduplication is skipped, and only for the one property where "duplicate" was being measured against the wrong thing.

`initFontRegistry()` finishes by deduplicating each property it merged:

    $this->$property = array_unique(array_merge($values, $this->$property));

That is right for five of the six. `backupSubsFont`, `BMPonly`,
`sans_fonts`, `serif_fonts` and `mono_fonts` are lists of font keys, and
a repeated key there is noise worth dropping.

`fonttrans` is not a list. It maps an alias to the font it resolves to,
keyed by alias, and `array_unique()` compares *values* — so two aliases
naming one font collapse to whichever appeared first, silently. A
package registering

    ['arial' => 'somefont', 'helvetica' => 'somefont']

ends up with `arial` alone, and `font-family: helvetica` stops resolving
for no reason the author can see. Aliasing several names onto one font
is the ordinary case for a metric-compatible substitute, so this is not
an edge.

Deduplicate the five lists as before and leave the map alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jakejackson1 added a commit to GravityPDF/gravity-pdf that referenced this pull request Sep 7, 2026
mPDF learned about fonts two ways in 6.x: a hard-coded list of the core
fonts the installer downloaded, and a glob of the uploads fonts directory
run on every render, whose results were invisible to the Font Manager.
Both are gone.

`Registry` builds two package layers from one cached read of the font
tables — the fonts bundled in the plugin, and every row — and hands them
to mPDF as a font registry. `Helper_PDF::begin_pdf()` builds the whole
font config from it: the registry itself, the default font, the document
script and language, the language map, and `autoScriptToLang`, which is
load-bearing rather than a preference — mPDF never substitutes Arabic or
Indic glyphs, so a lang tag on the run is the only route to a font for
those scripts.

Because the same object answers `get_grouped_fonts()`, the settings
dropdown can no longer offer a font that will not render. It used to
list the 6.x core fonts unconditionally, whether or not the installer had
ever been run.

`Loose_Font_Importer` replaces the render-time glob, and ships in the
same change that removes it so no render loses a font it used to find.
Each loose file becomes a row under the key 6.x derived from its
filename, so existing templates keep resolving; a file that will not
parse is skipped and logged rather than fatal, which is what it was
before on the first render that selected it.

The core-font installer's admin nag goes with them: its one-time-action
route, its condition and redirect, its view and template, and the
Tools-tab button. The React bundle that button mounted is now
unreachable, but it is left for the removal sweep in the next phase
rather than widened into this one.

`tools/mu-plugins/mpdf.php` is deleted too. It forced mPDF into
core-fonts mode for the whole test suite, so no test had ever proved a
real font was embedded. `Test_Bundled_Render` replaces it and asserts
what it was hiding: `onlyCoreFonts` false, all four Arimo faces loaded
for bold and italic, the consent ticks coming from the symbol
supplement, an unknown font-family falling back to Arimo, and a real PDF
with Arimo embedded.

Two smaller things fall out. `begin_pdf()` normalises a filtered
`fontDir` to an array, because the package layers append to it and the
public `gfpdf_mpdf_class_config` filter has long been handed a bare
string — harmless while nothing appended, fatal now. And font aliases
are asserted on the package rather than on mPDF's merged config:
`initFontRegistry()` deduplicates that map by value, so `arial` and
`helvetica` both naming Arimo collapse to one. Fixed on the fork in
GravityPDF/mpdf#36; this tightens once the plugin re-pins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant