Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/Helper/Mpdf/On_Demand_Font_File_Finder.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
}

/**
* Downloads a missing font, else falls back to a font on disk (outside Debug Mode)
* Downloads a missing font, or queues a large one for the background sync, else falls back to a font on disk (outside
* Debug Mode)
*
* @since 6.19.0
*/
Expand Down Expand Up @@ -88,11 +89,12 @@ public function findFontFile( $name ) {
}

/* The downloader's fonts folder, as mPDF's AddFontDirectory() replaces the search directories */
if ( $this->downloader->download( $name ) ) {
if ( $this->downloader->download_or_queue( $name ) ) {
return $this->downloader->get_font_path( $name );
}

$fallback = $this->fallback ? $this->get_fallback_file() : '';
/* Debug Mode falls back too for a queued file, as the font is on its way */
$fallback = $this->fallback || $this->downloader->is_queued( $name ) ? $this->get_fallback_file() : '';
if ( $fallback === '' ) {
throw $e;
}
Expand All @@ -101,7 +103,7 @@ public function findFontFile( $name ) {
}

/* Only a core font in the fonts folder can be another font's file, and a Font Manager record's stays the record's */
if ( $this->downloader->is_in_fonts_folder( $found ) && $this->downloader->is_core_font_file( $name ) && ! $this->downloader->is_record_file( $name ) && $this->downloader->download( $name ) ) {
if ( $this->downloader->is_in_fonts_folder( $found ) && $this->downloader->is_core_font_file( $name ) && ! $this->downloader->is_record_file( $name ) && $this->downloader->download_or_queue( $name ) ) {
return $this->downloader->get_font_path( $name );
}

Expand Down
106 changes: 103 additions & 3 deletions src/Model/Model_Font_Downloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use GFPDF\Helper\Helper_Abstract_Options;
use GFPDF\Helper\Helper_Data;
use GFPDF\Helper\Helper_Misc;
use GFPDF\Helper\Helper_Render_Health;
use GFPDF\Helper\Mpdf\On_Demand_Font_File_Finder;
use GFPDF\Statics\Git_Blob;
use GFPDF_Vendor\Psr\Log\LoggerInterface;
Expand Down Expand Up @@ -121,6 +122,14 @@ class Model_Font_Downloader extends Helper_Abstract_Model {
*/
const OUTDATED = [ 'Garuda.ttf', 'Garuda-Bold.ttf', 'Garuda-Oblique.ttf', 'Garuda-BoldOblique.ttf', 'Sun-ExtA.ttf', 'Sun-ExtB.ttf' ];

/**
* Large files a PDF needed beyond the required set (e.g. Sun-ExtA on an English site), which the sync fetches and the
* cleanup keeps
*
* @since 6.19.0
*/
const WANTED = 'gfpdf_font_wanted';

/**
* @var Model_Font_Sources
* @since 6.19.0
Expand Down Expand Up @@ -489,7 +498,7 @@ public function prune( ?array $keep = null, int $since = 0 ): array {
return [];
}

$needed = $keep ?? $this->required_fonts->get_required_font_files();
$needed = $keep ?? $this->with_wanted( $this->required_fonts->get_required_font_files() );
$keep = array_flip( array_diff( $needed, self::OUTDATED ) );
$removed = [];
$bytes = 0;
Expand Down Expand Up @@ -1103,6 +1112,97 @@ public static function back_off_key( string $file ): string {
return self::BACK_OFF_KEY . md5( $file );
}

/**
* The files given and the wanted ones
*
* @param string[] $files
*
* @return string[]
*
* @since 6.19.0
*/
protected function with_wanted( array $files ): array {
return array_unique( array_merge( $files, $this->get_wanted_files() ) );
}

/**
* Files a PDF needed beyond the required set
*
* @return string[]
*
* @since 6.19.0
*/
public function get_wanted_files(): array {
$wanted = get_network_option( get_main_network_id(), self::WANTED, [] );

return is_array( $wanted ) ? array_values( array_filter( $wanted, 'is_string' ) ) : [];
}

/**
* For a render: download() the file, unless it's a large one that isn't on disk, which is left to the background sync
* so the render never waits on it and isn't cached without it
*
* @return bool Whether the font's own copy is on disk now
*
* @since 6.19.0
*/
public function download_or_queue( string $file ): bool {
if ( ! $this->font_sources->is_large_file( $file ) || $this->font_sources->is_installed( $file ) ) {
return $this->download( $file );
}

if ( $this->queue( $file ) ) {
Helper_Render_Health::mark();
}

return false;
}

/**
* Whether a render without the file has it on the way: a large one, unless downloads are off for it or it gave up
*
* @since 6.19.0
*/
public function is_queued( string $file ): bool {
return $this->font_sources->is_large_file( $file ) && $this->can_queue( $file );
}

/**
* Add a file to the wanted list and queue the sync for it, rather than download it now, unless it's awaiting its retry
*
* @return bool Whether it's still to come
*
* @since 6.19.0
*/
public function queue( string $file ): bool {
if ( ! $this->can_queue( $file ) ) {
return false;
}

$wanted = $this->get_wanted_files();
$is_new = ! in_array( $file, $wanted, true );

if ( $is_new ) {
$this->log->notice( 'Font queued for the background sync as a PDF needs it', [ 'name' => $file ] );
$wanted[] = $file;
update_network_option( get_main_network_id(), self::WANTED, $wanted );
}

/* A failure already scheduled its retry */
if ( ! isset( $this->get_failures()[ $file ] ) && ( $is_new || wp_next_scheduled( self::SYNC_EVENT ) === false ) ) {
$this->schedule_sync();
}

return true;
}

/**
* @since 6.19.0
*/
protected function can_queue( string $file ): bool {
return ! isset( $this->get_failed_files()[ $file ] ) && $this->is_auto_install_enabled( $file );
}

/**
* Queue a single background sync
*
Expand Down Expand Up @@ -1215,7 +1315,7 @@ function ( $site_id ) use ( &$keys ) {
return;
}

$this->prune( $this->required_fonts->get_install_font_files( $keys ), $state['started'] );
$this->prune( $this->with_wanted( $this->required_fonts->get_install_font_files( $keys ) ), $state['started'] );
update_network_option( get_main_network_id(), self::CLEANUP, true );
}

Expand Down Expand Up @@ -1274,7 +1374,7 @@ protected function within_budget( array $items, callable $each ): array {
*/
protected function get_sync_files(): array {
$remaining = get_transient( self::SYNC_REMAINING );
$files = is_array( $remaining ) ? array_filter( $remaining, 'is_string' ) : $this->required_fonts->get_required_font_files();
$files = is_array( $remaining ) ? array_filter( $remaining, 'is_string' ) : $this->with_wanted( $this->required_fonts->get_required_font_files() );
$gave_up = $this->get_failed_files();

$files = array_filter(
Expand Down
64 changes: 19 additions & 45 deletions src/Model/Model_Font_Sources.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ class Model_Font_Sources extends Helper_Abstract_Model {
*/
public const LANGUAGE_FONT_KEYS = 'gfpdf_language_font_keys';

/**
* A downloadable file above this size is never fetched mid-render, only by the background sync
*
* @since 6.19.0
*/
public const LARGE_FILE_SIZE = 5 * MB_IN_BYTES;

/**
* @var Helper_Data
* @since 6.19.0
Expand Down Expand Up @@ -233,34 +240,10 @@ public function set_default_font( $config, $form = [], $entry = [], $settings =

$config['default_font'] = $this->get_default_font( [ $pdf_font ], $fontdata );
$config['backupSubsFont'] = $this->get_substitution_fonts( $fontdata, $config['default_font'] );
$config['backupSIPFont'] = $this->get_sip_font( $fontdata );

/* Sun-ExtA names Sun-ExtB as its own fallback for those characters */
foreach ( $fontdata as $key => $family ) {
$sip_ext = is_array( $family ) ? ( $family['sip-ext'] ?? '' ) : '';
if ( is_string( $sip_ext ) && $sip_ext !== '' && ! $this->is_on_disk( $sip_ext, $fontdata ) ) {
unset( $config['fontdata'][ $key ]['sip-ext'] );
}
}

return $config;
}

/**
* mPDF's backupSIPFont: Sun-ExtB once it's on disk, as mPDF would download 18 MB for one rare CJK Extension B
* character, else the bundled symbols. With none, mPDF stops substituting the rest of a run at that character, e.g.
* 字 after 𠀀; the first render after a font cache flush still does (GravityPDF/mpdf#828).
*
* @since 6.19.0
*/
protected function get_sip_font( array $fontdata ): string {
if ( $this->is_on_disk( 'sun-extb', $fontdata ) ) {
return 'sun-extb';
}

return $this->is_registered( self::SYMBOLS_FONT, $fontdata ) ? self::SYMBOLS_FONT : '';
}

/**
* The first registered font given, else the default font, else Arimo
*
Expand Down Expand Up @@ -440,8 +423,8 @@ public function map_language_font_locales( array $locales ): array {
}

/**
* mPDF's backupSubsFont, Arimo first. mPDF reads which characters a font has from its ranges file and downloads only a
* font that has them, but Sun-ExtA is listed only once it's on disk, as it would download 23 MB mid-render.
* mPDF's backupSubsFont, Arimo first. mPDF reads which characters a font has from its ranges file and loads only a font
* that has them; the finder queues a large one (Sun-ExtA) for the background sync rather than download it mid-render.
*
* @param array|null $fontdata mPDF font data, or null for the registered fonts
* @param string $font The PDF's font, left out as it can't stand in for its own missing glyphs
Expand All @@ -457,7 +440,7 @@ public function get_substitution_fonts( ?array $fontdata = null, string $font =
[ self::ARIMO, self::SYMBOLS_FONT ],
self::SUBSTITUTION_CORE_FONTS,
$this->get_language_font_keys(),
$this->is_on_disk( 'sun-exta', $fontdata ) ? [ 'sun-exta' ] : []
[ 'sun-exta' ]
);

$fonts = array_filter(
Expand All @@ -479,15 +462,6 @@ protected function is_registered( string $font, array $fontdata ): bool {
return is_string( $fontdata[ $font ]['R'] ?? null );
}

/**
* Whether mPDF's font data has a Regular file for the key, and it's in the fonts folder
*
* @since 6.19.0
*/
protected function is_on_disk( string $font, array $fontdata ): bool {
return $this->is_registered( $font, $fontdata ) && $this->is_downloaded( $fontdata[ $font ]['R'] );
}

/**
* The bundled Arimo Regular path
*
Expand Down Expand Up @@ -687,6 +661,15 @@ public function is_core_font_file( string $file ): bool {
return isset( $this->get_manifest()[ $file ] ) && in_array( strtolower( pathinfo( $file, PATHINFO_EXTENSION ) ), [ 'ttf', 'otf' ], true );
}

/**
* Whether a downloadable file is above LARGE_FILE_SIZE
*
* @since 6.19.0
*/
public function is_large_file( string $file ): bool {
return ( $this->get_manifest()[ $file ]['size'] ?? 0 ) > self::LARGE_FILE_SIZE;
}

/**
* Where a manifest file goes in the fonts folder, under its own name
*
Expand Down Expand Up @@ -792,15 +775,6 @@ public function has_hashed_copy( string $file ): bool {
return is_file( $path ) && (int) filesize( $path ) === (int) $this->get_manifest()[ $file ]['size'];
}

/**
* Whether a file is in the fonts folder at its name, which a render checks without reading the manifest
*
* @since 6.19.0
*/
public function is_downloaded( string $file ): bool {
return is_file( $this->get_plain_path( $file ) );
}

/**
* Whether the file's own copy is in the fonts folder: its hashed copy, or the file at its name unless that is
* another font's by size (see is_size_conflict())
Expand Down
5 changes: 3 additions & 2 deletions src/Model/Model_Uninstall.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,15 @@ public function remove_plugin_transients() {
}

/**
* Remove the font state every site shares: the failed downloads and their back-offs, the language fonts and the
* 6.19 cleanup's progress
* Remove the font state every site shares: the failed and wanted downloads and their back-offs, the language
* fonts and the 6.19 cleanup's progress
*
* @since 6.19.0
*/
public function remove_font_state(): void {
delete_network_option( get_main_network_id(), Model_Font_Downloader::FAILURES );
delete_network_option( get_main_network_id(), Model_Font_Downloader::CLEANUP );
delete_network_option( get_main_network_id(), Model_Font_Downloader::WANTED );
delete_site_transient( Model_Font_Sources::LANGUAGE_FONT_KEYS );

$font_sources = \GPDFAPI::get_mvc_class( 'Model_Font_Sources' );
Expand Down
3 changes: 2 additions & 1 deletion tests/phpunit/Concerns/RecordsFontFailures.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,11 @@ protected function font_cleanup_state( array $state = [] ): array {
}

/**
* Delete the failure record, and have the shared downloader read it afresh
* Delete the failure record and the wanted files, and have the shared downloader read it afresh
*/
protected function delete_font_failures(): void {
delete_network_option( get_main_network_id(), Model_Font_Downloader::FAILURES );
delete_network_option( get_main_network_id(), Model_Font_Downloader::WANTED );
GPDFAPI::get_mvc_class( 'Model_Font_Downloader' )->flush();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function test_system_report_lists_the_required_fonts_installed_and_the_su

$this->assertSame( 'core: 0 of 4 required files installed', $directories['core_fonts_installed']['value'] );
$this->assertSame( 'Core Fonts installed', $directories['core_fonts_installed']['label_export'] );
$this->assertSame( 'arimo, gfpdf-dejavu-symbols, dejavusanscondensed, freesans, garuda, khmeros, padaukbook, kaputaunicode, abyssinicasil', $directories['substitution_fonts']['value'] );
$this->assertSame( 'arimo, gfpdf-dejavu-symbols, dejavusanscondensed, freesans, garuda, khmeros, padaukbook, kaputaunicode, abyssinicasil, sun-exta', $directories['substitution_fonts']['value'] );
}

public function test_system_report_flags_a_missing_core_font_manifest() {
Expand Down
Loading