Skip to content

List custom, core and bundled fonts together in the Font Manager - #2015

Merged
jakejackson1 merged 1 commit into
developmentfrom
feature/font-manager-downloaded-fonts
Nov 9, 2026
Merged

jakejackson1 merged 1 commit into
developmentfrom
feature/font-manager-downloaded-fonts

Conversation

@gravitypdf-bot

@gravitypdf-bot gravitypdf-bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The Font Manager now holds custom, downloaded core and bundled fonts together in one sidebar, with a detail pane for each font, following the 7.0 design for what 6.19 can already do. Until now a downloaded core font could only be removed by hand from the fonts folder, and nothing showed which PDFs used a font.

  • No new endpoints: a scope argument on the existing /gravity-pdf/v1/fonts/ and /fonts/{id} routes does the new work. It defaults to custom, so existing consumers see no change.
  • GET /fonts/?scope=all returns every row: custom and listed bundled fonts by name, then core fonts in the font dropdown's order.
  • GET /fonts/?scope=usage returns why fonts are in use and which PDFs use them. It walks the forms, so rows appear first and Delete enables when it returns.
  • POST /fonts/{id}?scope=core installs a core font's missing files and returns its row. It returns 404 for a bundled, unknown or custom-replaced family, 409 with downloads off and 502 when a download fails.
  • DELETE /fonts/{id}?scope=core removes a core font's files and keeps its licence files. It returns 409 when in use and 404 for a bundled, unknown or absent family.
  • DELETE /gravity-pdf/v1/fonts/{id} now returns 409 for a custom font in use. This is a behaviour change for anything calling the route. GPDFAPI::delete_pdf_font() is unchanged, as the upgrade uses it to remove records a font source now serves.
  • The selected font is listed first, above fixed Custom, Bundled, Core and Available to install groups.
  • Core fonts not on disk are listed as available to install with their download size, and each has an Install button. A partly installed font can fetch its missing styles. They aren't listed with downloads off, or if they have no label and can't be chosen for a PDF.
  • Core fonts show the languages and scripts they support, worked out from the characters in each file, and a sample in their own script where Latin letters wouldn't show what they're for.
  • Clicking a font opens its detail: styles, size, what's using it, links to the settings of its PDFs (in a new tab) and a live preview. Bundled and core fonts are read-only; custom fonts open the update form. Core and custom fonts can be deleted while unused.
  • Previews wait until the font has been on screen for a moment and show one style until another is picked. Fonts over 4 MB wait for a click on Load preview. A spinner shows beside each style while it loads, and a failed style says so. A font that isn't installed previews from its download URL on raw.githubusercontent.com.
  • Sun-ExtA isn't offered a preview, as browsers reject the file (Sun-ExtA.ttf has a malformed vhea table, so browsers reject it mpdf#881).
  • Uploads are rows rather than tiles, deleting asks in a dialog, and the actions sit in a bar pinned to the bottom of the pane.
  • The modal shell, buttons, search box and rows follow the mockup, and the style labels now read "Italic" and "Bold Italic".
  • Custom fonts show their styles and size in the update form.
  • Multisite: only super admins install or delete core fonts, and a subsite admin sees a note saying so. A core font used on another site counts as in use, and only super admins pay for walking the other sites. A custom font belongs to its own site, so other sites don't count.
  • With downloads turned off (constant or filter), the core font's description and delete dialog warn that the font won't come back on its own.
  • The form scan pages by ID rather than OFFSET and compares bytes rather than using the column's collation, which is about 6x faster. The delete check stops at the first thing using the font.

Try it

  1. Choose Free Serif as a PDF's font, then run wp cron event run gfpdf_core_font_sync. Open the Font Manager from the PDF's Font setting: Free Serif is at the top, tagged Active and saying "In use".
  2. Click Arimo, then a core font: each opens a read-only pane with its size, preview and "Set as active". Set one active: it moves to the top, and the groups stay in order.
  3. Open a font used by a PDF: "In use" links to that PDF's settings in a new tab, and Delete is disabled and says why on hover.
  4. Open an unused core font and delete it: a dialog asks first, then the row goes away, a message confirms it and the files leave the fonts folder.
  5. Click a custom font: the update form opens. Replace a file and save. Press browser Back: the add form shows again.
  6. Click "+ Add new font", drag a .ttf onto a row and add the font.
  7. Add define( 'GPDF_AUTO_INSTALL_FONTS', false );, then open an unused core font. Its description and delete dialog warn that it won't come back on its own.
  8. Open a font under Available to install: its languages and preview show. Click Install: it moves to Core. Delete it: it returns to Available to install.
  9. Open Sun Ext under Available to install: it says it can't be previewed, and nothing downloads.
  10. On a multisite network, open a core font as a subsite admin: there is no Delete or Install, and a note says why. Set a core font as another site's default font: it says "Used on 1 other site".

Test plan

  • PHPUnit: full suite on single site (2,525 tests) and font and upgrade classes on multisite (521 tests). Two test_an_unwritable_folder_fails_without_a_request cases fail locally only, and passed in CI on the previous commit
  • Jest: full suite passes (470 tests)
  • Browser: installed and deleted a real font, checked previews from the download URL and failed loads in Firefox and Chromium
  • Playwright: core/managers/font-manager.spec.ts passes (18 tests, core project)
  • ESLint and CSS lint clean, PHPCS clean on the changed PHP files, and project-wide PHPStan clean
  • Manual run through the Try it steps
  • core-with-permalinks in CI
  • A real right-to-left locale and a screen reader
  • CI is green

Skipped

Performance on large sites (tracked in #2018)

  • Usage still walks every form each time the popup opens, and every site for a super admin. Storing it would remove the walk.
  • The usage response and the PDF list per font have no cap, about 185 bytes per PDF. Loading each font's list when it's opened depends on storing usage.

Known limitations

  • A fonts folder moved with gfpdf_font_location previews when its path maps to a URL, and says it's unavailable when it doesn't. A folder that maps to a URL but blocks web access shows "Couldn't load this style".
  • "4 styles · In use" scrambles its word order in a right-to-left sidebar row.

Clean-up

  • Shared CSS mixins for the repeated focus ring and tints.
  • Merging the near-identical Add and Update form fields.
  • A shared helper for the PDF settings URL, which now has four copies.
  • ShowMessage for the deleted-font notice, which needs a success style first.
  • One shape for font rows and font URLs across the model, controller and reducer.

Design differences from the mockup

  • 7.0 features with nothing behind them yet: Refresh and sync line, updates banner, gear and Language settings, the Browse menu, licence line, emoji notice.
  • Real fonts in the sidebar swatches, because that would download every font file just to open the list.
  • The "New font" placeholder row, which would compete with the selected font being listed first.
  • Disabling "Add font" on an empty form, because clicking it is what shows the validation messages.
  • The two-pane mobile switch: narrow screens stack the list above the detail.
  • The preview's number field beside the size slider, the highlighted template-tag code block, per-style heading and paragraph previews, and the "Edit font" and "Save changes" wording.
  • The mockup's lighter input borders, which would fail the 3:1 contrast requirement.
More info
  • Timing: on a 981-form site, the list takes 9ms and usage 241ms. Before the split and the ID paging, one request took 883ms.
  • Usage in its own state: fontUsage sits beside fontList in the store rather than merged into its rows, because changing fontList repopulates an open update panel and would reset what's being typed.
  • Reading other sites: Model_Required_Fonts::get_site_font_keys( $site_id, $unset_default ) reads one site's default font, locale font and PDF fonts by table prefix, never switch_to_blog(), so no other site's plugins or filters run. Keep font state in one place for every site, and keep every site's fonts in the 6.19 cleanup #2004's 6.19 cleanup calls the same method.
  • Same key: a custom font replaces a core or bundled font of the same key, in the list as in the dropdown. Model_Font_Sources::get_listed_families() holds that rule for both.
  • Panels: the route decides which panel shows, rather than a class toggled on the DOM. Browser Back no longer leaves the update form open over the add form's data (covered by an E2E test), and the slide-in panel's height and tab index workarounds are gone.
  • Removed as dead: the move-selected-font-to-top action, adjustFontListHeight, the tab index plumbing, orphaned strings and the old 961–1200px rules that were breaking the actions bar at those widths. FontDeleteButton replaces two copies of the delete logic.
  • Preview outside the form: the save button submits through its form attribute, so Enter in the preview can't save the font. The delete dialog now covers the sidebar for core fonts too.
  • Style labels: "Italic" and "Bold Italic" replace "Italics" and "Bold Italics" everywhere those strings are used.
  • Overlap: Keep font files another site uses when deleting or editing a custom font #2012 also changes Controller_Custom_Fonts::delete_item(), so expect a conflict there.
  • Plain permalinks: the REST base already has a query string, so fontsUrl() adds scope through updateURLParameter().
  • Possible follow-ups: the list could move out of Model_Font_Downloader into its own model, and apiGetCustomFontList / GET_CUSTOM_FONT_LIST now fetch every type, so the names are out of date.
  • E2E setup: the tests copy the bundled Arimo file in under core font names, so they make no downloads. The in-use test sets its default font with a new X-GPDF-E2E-Default-Font header in tools/mu-plugins/gravitypdf.php, which applies to that test's requests only.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Coverage report for commit: 37b7e8c
File: ./tmp/jest-coverage/clover.xml

Cover ┌─────────────────────────┐ Freq.
   0% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  10% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  20% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  30% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  40% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  50% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  60% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  70% │ █░░░░░░░░░░░░░░░░░░░░░░ │  3.1%
  80% │ ██░░░░░░░░░░░░░░░░░░░░░ │  4.7%
  90% │ ██████░░░░░░░░░░░░░░░░░ │ 18.8%
 100% │ ███████████████████████ │ 73.4%
      └─────────────────────────┘
 *Legend:* █ = Current Distribution 
Summary - Lines: 93.99% | Methods: 90.96% | Branches: 85.66%
FilesLinesMethodsBranches
src/assets/js/react/actions
   fontManager.js97.92%91.67%100.00%
   templates.js100.00%100.00%100.00%
src/assets/js/react/components/Alert
   Alert.js100.00%100.00%100.00%
src/assets/js/react/components
   CustomHashRouter.js100.00%100.00%100.00%
   Empty.js100.00%100.00%100.00%
   ShowMessage.js80.65%80.00%72.22%
   Spinner.js100.00%100.00%100.00%
src/assets/js/react/components/FontManager
   AddFont.js100.00%100.00%100.00%
   AddUpdateFontFooter.js100.00%100.00%91.67%
   AdvancedButton.js100.00%100.00%100.00%
   DeleteConfirm.js90.91%75.00%100.00%
   FontDeleteButton.js100.00%80.00%100.00%
   FontDetail.js100.00%100.00%100.00%
   FontInstallButton.js100.00%66.67%100.00%
   FontList.js100.00%50.00%65.22%
   FontListAlertMessage.js100.00%100.00%100.00%
   FontListItems.js96.00%83.33%88.24%
   FontListSkeleton.js100.00%100.00%100.00%
   FontManager.js73.68%50.00%50.00%
   FontManagerBody.js89.12%90.32%84.96%
   FontManagerHeader.js100.00%100.00%25.00%
   FontPreview.js100.00%100.00%100.00%
   FontStatus.js100.00%66.67%100.00%
   FontUsage.js100.00%66.67%100.00%
   FontVariant.js94.12%80.00%90.24%
   FontVariantLabel.js100.00%100.00%100.00%
   Icon.js100.00%100.00%100.00%
   InitialAddUpdateState.js100.00%100.00%100.00%
   SearchBox.js90.00%66.67%69.23%
   TemplateTooltip.js100.00%100.00%100.00%
   UpdateFont.js75.00%50.00%64.29%
src/assets/js/react/components/Modal
   CloseDialog.js93.33%66.67%70.59%
src/assets/js/react/components/Template
   TemplateActivateButton.js100.00%100.00%100.00%
   TemplateButton.js85.71%66.67%100.00%
   TemplateContainer.js71.43%66.67%25.00%
   TemplateDeleteButton.js100.00%100.00%70.00%
   TemplateDocsLink.js100.00%100.00%100.00%
   TemplateFooterActions.js100.00%100.00%100.00%
   TemplateHeaderNavigation.js82.35%85.71%70.00%
   TemplateHeaderTitle.js100.00%100.00%100.00%
   TemplateList.js100.00%100.00%100.00%
   TemplateListItem.js100.00%100.00%92.86%
   TemplateListItemComponents.js100.00%100.00%100.00%
   TemplateManagerClosed.js100.00%100.00%100.00%
   TemplateScreenshot.js100.00%100.00%100.00%
   TemplateScreenshots.js100.00%100.00%50.00%
   TemplateSearch.js93.75%88.89%50.00%
   TemplateSingle.js100.00%100.00%100.00%
   TemplateSingleComponents.js100.00%100.00%75.00%
   TemplateUploader.js100.00%100.00%95.00%
   TemplateUploaderContext.js100.00%100.00%100.00%
   TemplateUploaderTile.js100.00%100.00%100.00%
src/assets/js/react/reducers
   fontManagerReducer.js91.46%100.00%81.08%
   index.js100.00%100.00%100.00%
   templateReducer.js100.00%100.00%100.00%
src/assets/js/react/sagas
   fontManager.js90.16%92.86%88.57%
   index.js100.00%100.00%100.00%
   templates.js91.30%100.00%100.00%
src/assets/js/react/selectors
   getTemplates.js91.11%100.00%83.33%
src/assets/js/react/utilities/FontManager
   associatedFontManagerSelectBox.js94.44%100.00%77.78%
   fontManagerReducer.js100.00%100.00%94.12%
   toggleUpdateFont.js100.00%100.00%100.00%
src/assets/js/react/utilities/Template
   hasPendingUpload.js100.00%100.00%100.00%
src/assets/js/react/utilities
   withRouterHooks.js100.00%100.00%100.00%

🤖 Jest coverage report

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Coverage report for commit: 37b7e8c
File: tmp/coverage/report-xml/merged.xml

Cover ┌─────────────────────────┐ Freq.
   0% │ ██████░░░░░░░░░░░░░░░░░ │  7.6%
  10% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  20% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.4%
  30% │ █░░░░░░░░░░░░░░░░░░░░░░ │  1.3%
  40% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.4%
  50% │ ███████░░░░░░░░░░░░░░░░ │  9.3%
  60% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.9%
  70% │ ██░░░░░░░░░░░░░░░░░░░░░ │  2.7%
  80% │ █████████████░░░░░░░░░░ │ 16.9%
  90% │ ███████████████████████ │ 32.0%
 100% │ █████████████████████░░ │ 28.4%
      └─────────────────────────┘
 *Legend:* █ = Current Distribution 
Summary - Lines: 89.51% | Methods: 95.69%
FilesLinesMethodsBranches
/var/www/html/wp-content/plugins/gravity-pdf
   api.php98.14%100.00%100.00%
   gravity-pdf-updater.php53.97%100.00%100.00%
   pdf.php59.82%81.25%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Controller
   Controller_Actions.php99.10%100.00%100.00%
   Controller_Activation.php95.83%100.00%100.00%
   Controller_Custom_Fonts.php95.47%100.00%100.00%
   Controller_Debug.php100.00%100.00%100.00%
   Controller_Export_Entries.php96.77%100.00%100.00%
   Controller_Font_Downloader.php100.00%100.00%100.00%
   Controller_Font_Sources.php100.00%100.00%100.00%
   Controller_Form_Settings.php87.37%90.00%100.00%
   Controller_Install.php100.00%100.00%100.00%
   Controller_Mergetags.php100.00%100.00%100.00%
   Controller_PDF.php81.46%100.00%100.00%
   Controller_Pdf_Cache.php100.00%100.00%100.00%
   Controller_Pdf_Queue.php85.27%87.50%100.00%
   Controller_Save_Core_Fonts.php91.67%100.00%100.00%
   Controller_Settings.php86.90%100.00%100.00%
   Controller_Shortcodes.php100.00%100.00%100.00%
   Controller_System_Report.php100.00%100.00%100.00%
   Controller_Templates.php100.00%100.00%100.00%
   Controller_Uninstaller.php83.33%77.78%100.00%
   Controller_Upgrade_Routines.php94.39%100.00%100.00%
   Controller_Webhooks.php100.00%100.00%100.00%
   Controller_Zapier.php100.00%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Exceptions
   GravityPdfDatabaseUpdateException.php50.00%100.00%100.00%
   GravityPdfDomainException.php50.00%100.00%100.00%
   GravityPdfException.php50.00%100.00%100.00%
   GravityPdfFontNotFoundException.php50.00%100.00%100.00%
   GravityPdfIdException.php50.00%100.00%100.00%
   GravityPdfModelNotUpdatedException.php50.00%100.00%100.00%
   GravityPdfNotATemplateException.php50.00%100.00%100.00%
   GravityPdfRuntimeException.php50.00%100.00%100.00%
   GravityPdfShortcodeEntryIdException.php50.00%100.00%100.00%
   GravityPdfShortcodePdfConditionalLogicFailedException.php50.00%100.00%100.00%
   GravityPdfShortcodePdfConfigNotFoundException.php50.00%100.00%100.00%
   GravityPdfShortcodePdfInactiveException.php50.00%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper/Fields
   Field_Address.php92.86%100.00%100.00%
   Field_Chainedselect.php66.67%75.00%100.00%
   Field_Checkbox.php94.44%100.00%100.00%
   Field_Consent.php95.00%100.00%100.00%
   Field_Coupon.php--100.00%
   Field_Creditcard.php83.33%100.00%100.00%
   Field_Date.php83.33%100.00%100.00%
   Field_Default.php83.33%100.00%100.00%
   Field_Discount.php44.00%75.00%100.00%
   Field_Email.php83.33%100.00%100.00%
   Field_Fg_Ls_Consent.php92.86%100.00%100.00%
   Field_Fg_Ls_Signature.php73.08%66.67%100.00%
   Field_Fileupload.php95.83%100.00%100.00%
   Field_Form.php90.91%100.00%100.00%
   Field_Hidden.php81.82%100.00%100.00%
   Field_Html.php89.47%100.00%100.00%
   Field_Image_Choice.php86.67%100.00%100.00%
   Field_Likert.php97.22%100.00%100.00%
   Field_List.php92.41%100.00%100.00%
   Field_Multi_Choice.php50.00%100.00%100.00%
   Field_Multiselect.php92.59%100.00%100.00%
   Field_Name.php84.62%100.00%100.00%
   Field_Number.php83.33%100.00%100.00%
   Field_Option.php96.15%100.00%100.00%
   Field_Page.php83.33%100.00%100.00%
   Field_Phone.php94.55%100.00%100.00%
   Field_Poll.php93.75%100.00%100.00%
   Field_Post_Category.php85.00%100.00%100.00%
   Field_Post_Content.php82.35%100.00%100.00%
   Field_Post_Custom_Field.php50.00%100.00%100.00%
   Field_Post_Excerpt.php81.82%100.00%100.00%
   Field_Post_Image.php94.00%100.00%100.00%
   Field_Post_Tags.php90.91%100.00%100.00%
   Field_Post_Title.php81.82%100.00%100.00%
   Field_Product.php92.31%100.00%100.00%
   Field_Products.php84.32%100.00%100.00%
   Field_Quantity.php84.62%100.00%100.00%
   Field_Quiz.php89.74%100.00%100.00%
   Field_Radio.php94.74%100.00%100.00%
   Field_Rank.php95.24%100.00%100.00%
   Field_Rating.php95.24%100.00%100.00%
   Field_Repeater.php97.39%100.00%100.00%
   Field_Section.php90.74%100.00%100.00%
   Field_Select.php94.12%100.00%100.00%
   Field_Shipping.php92.86%100.00%100.00%
   Field_Signature.php71.11%100.00%100.00%
   Field_Slim.php82.35%100.00%100.00%
   Field_Slim_Post.php91.30%100.00%100.00%
   Field_Subtotal.php65.38%75.00%100.00%
   Field_Survey.php95.00%100.00%100.00%
   Field_Tax.php32.00%50.00%100.00%
   Field_Text.php81.82%100.00%100.00%
   Field_Textarea.php91.18%100.00%100.00%
   Field_Time.php81.82%100.00%100.00%
   Field_Tos.php92.86%100.00%100.00%
   Field_Total.php88.00%100.00%100.00%
   Field_Website.php85.71%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper/Fonts
   FlushCache.php92.31%100.00%100.00%
   LocalFile.php88.89%100.00%100.00%
   LocalFilesystem.php66.67%100.00%100.00%
   SupportsOtl.php87.50%100.00%100.00%
   TtfFontValidation.php84.62%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper
   Helper_Abstract_Addon.php94.09%100.00%100.00%
   Helper_Abstract_Config_Settings.php75.00%100.00%100.00%
   Helper_Abstract_Controller.php-100.00%100.00%
   Helper_Abstract_Field_Products.php89.47%66.67%100.00%
   Helper_Abstract_Fields.php96.10%95.83%100.00%
   Helper_Abstract_Fields_Input_Type.php84.00%100.00%100.00%
   Helper_Abstract_Form.php--100.00%
   Helper_Abstract_Model.php100.00%100.00%100.00%
   Helper_Abstract_Options.php77.13%79.66%100.00%
   Helper_Abstract_Pdf_Shortcode.php94.67%95.65%100.00%
   Helper_Abstract_View.php96.77%100.00%100.00%
   Helper_Async_Notifications.php64.29%100.00%100.00%
   Helper_Data.php98.43%100.00%100.00%
   Helper_Field_Container.php92.31%100.00%100.00%
   Helper_Field_Container_Gf25.php92.00%100.00%100.00%
   Helper_Field_Container_Void.php16.67%-100.00%
   Helper_Form.php80.65%64.29%100.00%
   Helper_Interface_Actions.php-100.00%100.00%
   Helper_Interface_Config.php50.00%100.00%100.00%
   Helper_Interface_Config_Settings.php50.00%100.00%100.00%
   Helper_Interface_Deprecated_Features.php50.00%100.00%100.00%
   Helper_Interface_Extension_Settings.php-100.00%100.00%
   Helper_Interface_Extension_Uninstaller.php-100.00%100.00%
   Helper_Interface_Field_Pdf_Config.php50.00%100.00%100.00%
   Helper_Interface_Filters.php-100.00%100.00%
   Helper_Interface_Setup_TearDown.php50.00%100.00%100.00%
   Helper_Interface_Url_Signer.php-100.00%100.00%
   Helper_Logger.php-100.00%100.00%
   Helper_Misc.php89.17%100.00%100.00%
   Helper_Mpdf.php50.00%100.00%100.00%
   Helper_Notices.php92.98%100.00%100.00%
   Helper_Options_Fields.php98.10%100.00%100.00%
   Helper_PDF.php88.61%97.56%100.00%
   Helper_PDF_List_Table.php84.50%92.31%100.00%
   Helper_Pdf_Queue.php84.31%100.00%100.00%
   Helper_QueryPath.php80.00%100.00%100.00%
   Helper_Render_Health.php85.71%100.00%100.00%
   Helper_Sha256_Url_Signer.php90.91%100.00%100.00%
   Helper_Singleton.php100.00%100.00%100.00%
   Helper_Templates.php97.29%100.00%100.00%
   Helper_Trait_Logger.php25.00%-100.00%
   Helper_Trait_Removed_Methods.php100.00%100.00%100.00%
   Helper_Url_Signer.php84.82%100.00%100.00%
/var/www/html/wp-content/plugins/gravity-pdf/src/Helper/Licensing
Table truncated to fit comment

🤖 PHPUnit coverage report

@gravitypdf-bot
gravitypdf-bot force-pushed the feature/font-manager-downloaded-fonts branch 2 times, most recently from f5c9ccc to 771fac4 Compare November 5, 2026 22:51
@jakejackson1
jakejackson1 added this pull request to stack #2017 November 6, 2026 02:57
Base automatically changed from feature/core-font-upgrade-prune to development November 6, 2026 03:06
@jakejackson1
jakejackson1 force-pushed the feature/font-manager-downloaded-fonts branch from 771fac4 to 52fdbd8 Compare November 6, 2026 03:06
* @since 6.19.0
*/
public function can_view_fonts(): bool {
$allowed = GPDFAPI::get_form_class()->has_capability( 'gravityforms_edit_forms' );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should match the pdf editing permissions.

@gravitypdf-bot gravitypdf-bot changed the title Delete downloaded core fonts from the Font Manager List custom, core and bundled fonts together in the Font Manager Nov 6, 2026
@jakejackson1

jakejackson1 commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Should replace with @wordpress/components? Or leave until 7.0?

@gravitypdf-bot
gravitypdf-bot force-pushed the feature/font-manager-downloaded-fonts branch 3 times, most recently from 0d77c74 to 9b74d46 Compare November 8, 2026 23:07
@gravitypdf-bot
gravitypdf-bot force-pushed the feature/font-manager-downloaded-fonts branch 7 times, most recently from 0321f1b to 2e608c0 Compare November 9, 2026 00:57
@gravitypdf-bot
gravitypdf-bot force-pushed the feature/font-manager-downloaded-fonts branch from 2e608c0 to edf2097 Compare November 9, 2026 02:16
On-demand downloading makes deleting a core font safe, as one a PDF needs comes back on its own, but the Font Manager
only listed custom fonts, so a downloaded font could only be removed by hand from the fonts folder.

There is now one list that the search box filters: custom fonts, downloadable fonts with files on disk and listed
bundled fonts. Following the 7.0 Font Manager design for what 6.19 can already do, fonts sit in a sidebar with the
selected font first and Custom, Bundled and Core groups below it. Clicking a font opens its detail: bundled and core
fonts are read-only, custom fonts open the update form. Each shows its styles, size, what's using it and a live preview,
and links to the settings of the PDFs using it. Uploads are rows rather than tiles, deleting asks in a dialog, and the
actions sit in a bar pinned to the bottom of the pane.

A preview is a whole TTF per style, so it waits until it has been on screen for a moment, as moving to another font
doesn't cancel a download, and shows one style until another is picked from its dropdown. Anything over 4 MB, such as
Sun-ExtA at 22 MB, waits for a click on Load preview, using the per-style file_sizes now on each row. Text uses
font-display: swap rather than staying blank while the files load, with a spinner beside each style until its file has, or a note if it couldn't load. Sun-ExtA isn't offered a preview, as
its vhea table claims more metrics than it has glyphs and browsers reject the file.
A fonts folder moved with gfpdf_font_location has its
path mapped to a URL, and the preview says it's unavailable when there isn't one.

The route now decides which panel shows, rather than a class toggled on the DOM, so browser Back no longer leaves the
update form open. That removes the slide-in panel's height and tab index workarounds.

GET /fonts/?scope=all returns every row and GET /fonts/ stays custom-only by default so existing consumers keep working. Working out
what's in use walks every form, and on Multisite every site, so GET /fonts/?scope=usage serves `reasons`, `pdfs_label`
and `pdfs` separately: rows appear at once and Delete enables when the reasons arrive. Saved custom fonts return the same
row as the list.

A font in use can't be deleted, custom fonts included: DELETE /fonts/{id} now returns 409 for one, though not
GPDFAPI::delete_pdf_font(), which the upgrade uses to remove records a font source now serves. DELETE
/fonts/{id}?scope=core returns 409 when in use and 404 for a bundled, unknown or absent family. It removes the family's
manifest files whatever their hash (an explicit action, unlike the upgrade prune), keeps the licence files, clears the
files' failure and back-off entries, flushes the family's metrics and logs what it removed. With downloads turned off
the confirm says the font won't come back.

Core fonts follow the order of the font dropdown. Those with nothing on disk are listed as available to install, unless downloads are off or they have no label, so
can't be chosen for a PDF. POST /fonts/{id}?scope=core downloads a family's missing files whatever their back-off, and
returns its row. On Multisite only super admins can, as with delete. Deleting returns the row too, so a font that can be
installed again goes back to the available group. With nothing on the site to preview, an available font's preview loads
from its manifest URL. Core rows also carry the languages and scripts each font supports, worked out from the characters
in its file, and a sample in its own script for fonts whose Latin letters wouldn't show what they're for.

The in-use reasons come from Model_Required_Fonts::get_font_reasons(), which get_required_font_files() is now built on,
so the list and the background sync share one walk of the forms, which now pages by ID as OFFSET ran its LIKE over every
form it skipped. On Multisite the fonts folder is shared, so only super admins can delete core fonts, and "in use" also
counts every other site's default font, locale font and PDF fonts, though a site's custom fonts are its own. Those come
from Model_Required_Fonts::get_site_font_keys(), which reads a site's tables by prefix rather than switch_to_blog() so no
other site's plugins or filters run. One options query per site also says whether Gravity Forms has run there
(gf_db_version), so no site pays a separate table check. Only super admins walk the other sites for the usage list, as
only they can delete core fonts. The form scan's LIKE compares bytes, as the column's collation made it about 6x slower.
The delete check stops at the first thing using the font, so the form reads are generators.

All of this is on the existing /fonts/ and /fonts/{id} routes, so there are no new endpoints to maintain.

The E2E test copies a core font no other spec uses into the fonts folder, as downloads are off on the E2E site, and puts
one in use with an X-GPDF-E2E-Default-Font header rather than a PDF selecting it, which would count it as required in the
other specs' system reports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gravitypdf-bot
gravitypdf-bot force-pushed the feature/font-manager-downloaded-fonts branch from edf2097 to 37b7e8c Compare November 9, 2026 02:40
@jakejackson1
jakejackson1 merged commit 4cc1934 into development Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants