Skip to content

Fetch large font files in the background when a PDF needs them - #2014

Merged
jakejackson1 merged 1 commit into
developmentfrom
feature/fetch-cjk-substitutes
Nov 10, 2026
Merged

jakejackson1 merged 1 commit into
developmentfrom
feature/fetch-cjk-substitutes

Conversation

@gravitypdf-bot

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

Copy link
Copy Markdown
Contributor

Summary

Font files over 5 MB are no longer downloaded in the middle of a PDF render. When a PDF needs one, it renders without that font, the file goes into the background font sync, and that render isn't cached, so the next view after the download draws the missing characters. Until now, an English site rendering CJK entry text never got Sun-ExtA, and Sun-ExtB was never fetched at all. A file counts as large by the manifest's size, so plugin-added sources and later manifests are covered without a hard-coded list. On today's manifest that's Sun-ExtA (23 MB), Sun-ExtB (17.6 MB) and Aegyptus (5.8 MB); UnBatang (4.97 MB) is just under. Answers #2005's render-cache question.

  • Model_Font_Downloader::download_or_queue() queues a missing large file instead of downloading it and marks the render degraded (Helper_Render_Health). The on-demand finder calls it in place of download() and returns the fallback font. Debug Mode falls back too for a queued file; a large file that can't be downloaded (downloads off, or it gave up) still throws there.
  • Model_Font_Downloader has a "wanted" list (gfpdf_font_wanted, a main-network option like the failure record). The sync fetches required + wanted files with the same retry, give-up and failure notice, and the queued 6.19 upgrade cleanup keeps wanted files. An outdated Sun-ExtA or Sun-ExtB is still removed, and the sync fetches the current one.
  • A large file whose name another font holds (say a hand-replaced Sun-ExtA, found by size alone) follows the same rule: the finder queues the manifest's, marks the render degraded and keeps the file at the name in use for that render. A hashed copy of a large file is used without queueing, with or without the plain file.
  • Sun-ExtA is always listed in backupSubsFont. Sun-ExtB stays as mPDF's backupSIPFont and Sun-ExtA's sip-ext, on disk or not.
  • The fork's FontSubstitution already reads each substitute's ranges file without loading the font. It now drives detection: mPDF reaches the finder only when a character needs that font.
  • The gfpdf-dejavu-symbols stand-in for backupSIPFont is removed.
  • The uninstaller removes the wanted list.

Try it

  1. On an English site with an empty fonts folder (or no Sun-ExtA.ttf), view a PDF of an entry with Japanese text, e.g. 日本語.
  2. The PDF renders right away, with the Japanese as missing glyphs. The log shows "Font queued for the background sync as a PDF needs it" and no download during the render.
  3. Run cron (wp cron event run gfpdf_core_font_sync). Sun-ExtA.ttf appears in the fonts folder.
  4. View the PDF again. The Japanese now renders (the first render wasn't cached).
  5. Repeat with 𠀀 (CJK Extension B), which queues Sun-ExtB.ttf.
  6. With add_filter( 'gfpdf_auto_install_fonts', '__return_false' ), nothing is queued and the PDF is cached as before.

Test plan

  • PHPUnit, single site (641 tests) and multisite (612): Test_Font_Rendering, Test_Model_Font_Sources, Test_Model_Font_Downloader, Test_On_Demand_Font_File_Finder, Test_Helper_PDF, Test_Uninstaller, Test_Model_Required_Fonts, Test_Model_Pdf_Render_Cache, Test_System_Report, Test_Controller_Font_Downloader, Test_Controller_Upgrade_Routines, Test_Git_Blob, Test_Downloaded_Fonts.
  • The only failures are environment-only, from the container running as root and from the test shim: test_an_unwritable_folder_fails_without_a_request x2, Test_Git_Blob::test_an_unreadable_file_has_no_hash, test_the_kill_switch_constant_turns_downloads_off.
  • PHPCS clean on the changed files, PHPStan clean on the changed src/ files.
  • CI is green
More info

Threshold. Model_Font_Sources::LARGE_FILE_SIZE = 5 * MB_IN_BYTES, compared with the manifest entry's size in is_large_file(). The manifest is only read on a finder miss, as before, so a render with its fonts on disk still never loads it.

Detection. An earlier draft scanned the HTML before the render against the fork's ranges files. It had to copy mPDF's rules: the Arabic/Indic blocks it never substitutes, Plane 2 going to backupSIPFont, ranges parsing. It also added an HTML pass to every render. Once the finder stopped downloading large files mid-render, there was no reason to keep large substitutes out of the lists. mPDF's FontSubstitution (GravityPDF/mpdf#827, #837) already picks a substitute from the shipped ranges files without loading it, and only asks the finder when a character needs it. So:

  • CJK text on an English site queues Sun-ExtA.
  • A CJK Extension B character queues Sun-ExtB, through backupSIPFont or Sun-ExtA's sip-ext.
  • Extension E (no core font has it), Latin and emoji text queue nothing.
  • A large selected or CSS font (e.g. Sun-ExtA as the PDF's font, or Aegyptus) goes through the same finder branch.

Detection needs no ranges file for a selected font. A large substitute needs one, and all three large core files have one. A plugin-added large file without one is only reached as a selected font.

Cache. The render is marked degraded instead of bumping the cache generation when a download completes. The render that lacked the font is never cached, so there's nothing to invalidate, and other PDFs' cached copies survive a font arriving.

Side effects to know about:

  • While a large substitute is pending, the fallback (the PDF's default font if on disk, else Arimo) stands in under its key. mPDF can log a "ranges file … may have moved text into the wrong font" warning on those renders, because the stand-in's size isn't the ranges file's. Font metrics regenerate when the real file arrives (Backport the stale font metrics fix (7f8d7ec4) to gravitypdf-v6 mpdf#825).
  • A Japanese or Chinese site no longer downloads Sun-ExtA mid-render before the sync has it. The first renders are degraded until the sync, which already fetches it as a required language font.
  • If a small substitute fails to download, e.g. Garuda for Thai, mPDF can move that text on to Sun-ExtA, which has Thai, and queue it. That is a 23 MB download caused by a failed 57 KB one. Garuda's own retry still runs.
  • A PDF whose custom font draws CJK still never reaches Sun-ExtA, because mPDF only substitutes characters the current font lacks.

Follow-ups, not in this PR: Jomolhari (2.2 MB Tibetan) and Quivira (1.4 MB symbols) on the substitution list.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Coverage report for commit: ef4857f
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.58%
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: ef4857f
File: tmp/coverage/report-xml/merged.xml

Cover ┌─────────────────────────┐ Freq.
   0% │ ██████░░░░░░░░░░░░░░░░░ │  7.1%
  10% │ ░░░░░░░░░░░░░░░░░░░░░░░ │  0.0%
  20% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.4%
  30% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.9%
  40% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.4%
  50% │ ███████░░░░░░░░░░░░░░░░ │  9.3%
  60% │ █░░░░░░░░░░░░░░░░░░░░░░ │  1.3%
  70% │ ██░░░░░░░░░░░░░░░░░░░░░ │  2.7%
  80% │ ████████████░░░░░░░░░░░ │ 16.4%
  90% │ ███████████████████████ │ 31.6%
 100% │ ██████████████████████░ │ 29.8%
      └─────────────────────────┘
 *Legend:* █ = Current Distribution 
Summary - Lines: 90.27% | Methods: 95.90%
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.php100.00%100.00%100.00%
   Controller_Activation.php95.83%100.00%100.00%
   Controller_Custom_Fonts.php95.64%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.php95.19%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.33%81.36%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/fetch-cjk-substitutes branch from 5c07450 to 09d16ab Compare November 6, 2026 00:33
@gravitypdf-bot
gravitypdf-bot changed the base branch from feature/core-font-upgrade-prune to feature/font-file-conflict-rename November 6, 2026 00:34
@jakejackson1
jakejackson1 force-pushed the feature/font-file-conflict-rename branch from f7acf3e to 74c3b06 Compare November 6, 2026 03:06
@gravitypdf-bot
gravitypdf-bot force-pushed the feature/font-file-conflict-rename branch from 74c3b06 to 1ad0a3c Compare November 9, 2026 05:30
@gravitypdf-bot
gravitypdf-bot force-pushed the feature/font-file-conflict-rename branch 2 times, most recently from 62cbe50 to 38e02af Compare November 9, 2026 06:09
Base automatically changed from feature/font-file-conflict-rename to development November 9, 2026 06:13
@gravitypdf-bot
gravitypdf-bot force-pushed the feature/fetch-cjk-substitutes branch 2 times, most recently from 1f8e648 to 9aafcec Compare November 10, 2026 05:42
@gravitypdf-bot gravitypdf-bot added update-visual-baselines Refresh Lost Pixel visual baselines from this PR's run (pair with run-tests) and removed update-visual-baselines Refresh Lost Pixel visual baselines from this PR's run (pair with run-tests) labels Nov 10, 2026
A downloadable font file over 5 MB (Model_Font_Sources::LARGE_FILE_SIZE, read from the manifest's size, so it covers
plugin-added sources and later manifests too) is never downloaded mid-render. On today's manifest that's Sun-ExtA
(23 MB), Sun-ExtB (17.6 MB) and Aegyptus (5.8 MB); UnBatang (4.97 MB) is just under.

When mPDF asks the on-demand finder for a missing large file, Model_Font_Downloader::download_or_queue() adds it to a "wanted" list
(gfpdf_font_wanted, a network option like the failure record) and queues the background sync, marks the render
degraded through Helper_Render_Health and returns the fallback font. The sync fetches required + wanted files with the
same retry, give-up and failure notice, and the 6.19 upgrade cleanup keeps wanted files. A file whose downloads are
off, or that gave up, isn't queued and the render is cached as before. Debug Mode falls back for a queued file too,
since it isn't an error; a large file that can't download still throws there.

Because the finder no longer downloads a large file mid-render, Sun-ExtA is listed in backupSubsFont and Sun-ExtB left
as mPDF's backupSIPFont (with Sun-ExtA's sip-ext link) whether or not they're on disk. mPDF's FontSubstitution
(GravityPDF/mpdf#827, #837) already picks a substitute from the ranges files the fork ships, without loading the font,
and asks the finder only when a character needs it. That is the detection: an English site rendering CJK text queues
Sun-ExtA, a CJK Extension B character queues Sun-ExtB, and Extension E (no core font has it), Latin or emoji text
queues nothing. A selected or CSS font that's a large file goes the same way. An earlier draft scanned the HTML before
the render against the ranges files; it duplicated mPDF's scan rules (skipped blocks, Plane 2, ranges parsing) and
added an HTML pass to every render, so it was dropped for this.

Degraded rather than a cache-generation bump when the download completes: the render that lacked the font is never
cached, so nothing needs invalidating, and other PDFs' cached copies aren't thrown away when a font arrives.

A large file whose name another font holds (a hand-replaced Sun-ExtA, found by size alone) follows the same rule: the
finder queues the manifest's, marks the render degraded and keeps the file at the name in use for that render. A hashed
copy of a large file is used without queueing, whether or not the plain file is there. The wanted list is a main-network
option, as the failure record is.

The gfpdf-dejavu-symbols stand-in for backupSIPFont is gone: mPDF's default Sun-ExtB is always the SIP font now.
Jomolhari and Quivira stay off the substitution list (follow-up).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gravitypdf-bot
gravitypdf-bot force-pushed the feature/fetch-cjk-substitutes branch from 6d53090 to ef4857f Compare November 10, 2026 05:54
@jakejackson1
jakejackson1 merged commit c43337f into development Nov 10, 2026
@jakejackson1
jakejackson1 deleted the feature/fetch-cjk-substitutes branch November 10, 2026 06:14
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