Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Shared;
   4  
   5  use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
   6  use PhpOffice\PhpSpreadsheet\RichText\RichText;
   7  use PhpOffice\PhpSpreadsheet\Style\Alignment;
   8  use PhpOffice\PhpSpreadsheet\Style\Font as FontStyle;
   9  
  10  class Font
  11  {
  12      // Methods for resolving autosize value
  13      const AUTOSIZE_METHOD_APPROX = 'approx';
  14      const AUTOSIZE_METHOD_EXACT = 'exact';
  15  
  16      private const AUTOSIZE_METHODS = [
  17          self::AUTOSIZE_METHOD_APPROX,
  18          self::AUTOSIZE_METHOD_EXACT,
  19      ];
  20  
  21      /** Character set codes used by BIFF5-8 in Font records */
  22      const CHARSET_ANSI_LATIN = 0x00;
  23      const CHARSET_SYSTEM_DEFAULT = 0x01;
  24      const CHARSET_SYMBOL = 0x02;
  25      const CHARSET_APPLE_ROMAN = 0x4D;
  26      const CHARSET_ANSI_JAPANESE_SHIFTJIS = 0x80;
  27      const CHARSET_ANSI_KOREAN_HANGUL = 0x81;
  28      const CHARSET_ANSI_KOREAN_JOHAB = 0x82;
  29      const CHARSET_ANSI_CHINESE_SIMIPLIFIED = 0x86; //    gb2312
  30      const CHARSET_ANSI_CHINESE_TRADITIONAL = 0x88; //    big5
  31      const CHARSET_ANSI_GREEK = 0xA1;
  32      const CHARSET_ANSI_TURKISH = 0xA2;
  33      const CHARSET_ANSI_VIETNAMESE = 0xA3;
  34      const CHARSET_ANSI_HEBREW = 0xB1;
  35      const CHARSET_ANSI_ARABIC = 0xB2;
  36      const CHARSET_ANSI_BALTIC = 0xBA;
  37      const CHARSET_ANSI_CYRILLIC = 0xCC;
  38      const CHARSET_ANSI_THAI = 0xDD;
  39      const CHARSET_ANSI_LATIN_II = 0xEE;
  40      const CHARSET_OEM_LATIN_I = 0xFF;
  41  
  42      //  XXX: Constants created!
  43      /** Font filenames */
  44      const ARIAL = 'arial.ttf';
  45      const ARIAL_BOLD = 'arialbd.ttf';
  46      const ARIAL_ITALIC = 'ariali.ttf';
  47      const ARIAL_BOLD_ITALIC = 'arialbi.ttf';
  48  
  49      const CALIBRI = 'calibri.ttf';
  50      const CALIBRI_BOLD = 'calibrib.ttf';
  51      const CALIBRI_ITALIC = 'calibrii.ttf';
  52      const CALIBRI_BOLD_ITALIC = 'calibriz.ttf';
  53  
  54      const COMIC_SANS_MS = 'comic.ttf';
  55      const COMIC_SANS_MS_BOLD = 'comicbd.ttf';
  56  
  57      const COURIER_NEW = 'cour.ttf';
  58      const COURIER_NEW_BOLD = 'courbd.ttf';
  59      const COURIER_NEW_ITALIC = 'couri.ttf';
  60      const COURIER_NEW_BOLD_ITALIC = 'courbi.ttf';
  61  
  62      const GEORGIA = 'georgia.ttf';
  63      const GEORGIA_BOLD = 'georgiab.ttf';
  64      const GEORGIA_ITALIC = 'georgiai.ttf';
  65      const GEORGIA_BOLD_ITALIC = 'georgiaz.ttf';
  66  
  67      const IMPACT = 'impact.ttf';
  68  
  69      const LIBERATION_SANS = 'LiberationSans-Regular.ttf';
  70      const LIBERATION_SANS_BOLD = 'LiberationSans-Bold.ttf';
  71      const LIBERATION_SANS_ITALIC = 'LiberationSans-Italic.ttf';
  72      const LIBERATION_SANS_BOLD_ITALIC = 'LiberationSans-BoldItalic.ttf';
  73  
  74      const LUCIDA_CONSOLE = 'lucon.ttf';
  75      const LUCIDA_SANS_UNICODE = 'l_10646.ttf';
  76  
  77      const MICROSOFT_SANS_SERIF = 'micross.ttf';
  78  
  79      const PALATINO_LINOTYPE = 'pala.ttf';
  80      const PALATINO_LINOTYPE_BOLD = 'palab.ttf';
  81      const PALATINO_LINOTYPE_ITALIC = 'palai.ttf';
  82      const PALATINO_LINOTYPE_BOLD_ITALIC = 'palabi.ttf';
  83  
  84      const SYMBOL = 'symbol.ttf';
  85  
  86      const TAHOMA = 'tahoma.ttf';
  87      const TAHOMA_BOLD = 'tahomabd.ttf';
  88  
  89      const TIMES_NEW_ROMAN = 'times.ttf';
  90      const TIMES_NEW_ROMAN_BOLD = 'timesbd.ttf';
  91      const TIMES_NEW_ROMAN_ITALIC = 'timesi.ttf';
  92      const TIMES_NEW_ROMAN_BOLD_ITALIC = 'timesbi.ttf';
  93  
  94      const TREBUCHET_MS = 'trebuc.ttf';
  95      const TREBUCHET_MS_BOLD = 'trebucbd.ttf';
  96      const TREBUCHET_MS_ITALIC = 'trebucit.ttf';
  97      const TREBUCHET_MS_BOLD_ITALIC = 'trebucbi.ttf';
  98  
  99      const VERDANA = 'verdana.ttf';
 100      const VERDANA_BOLD = 'verdanab.ttf';
 101      const VERDANA_ITALIC = 'verdanai.ttf';
 102      const VERDANA_BOLD_ITALIC = 'verdanaz.ttf';
 103  
 104      const FONT_FILE_NAMES = [
 105          'Arial' => [
 106              'x' => self::ARIAL,
 107              'xb' => self::ARIAL_BOLD,
 108              'xi' => self::ARIAL_ITALIC,
 109              'xbi' => self::ARIAL_BOLD_ITALIC,
 110          ],
 111          'Calibri' => [
 112              'x' => self::CALIBRI,
 113              'xb' => self::CALIBRI_BOLD,
 114              'xi' => self::CALIBRI_ITALIC,
 115              'xbi' => self::CALIBRI_BOLD_ITALIC,
 116          ],
 117          'Comic Sans MS' => [
 118              'x' => self::COMIC_SANS_MS,
 119              'xb' => self::COMIC_SANS_MS_BOLD,
 120              'xi' => self::COMIC_SANS_MS,
 121              'xbi' => self::COMIC_SANS_MS_BOLD,
 122          ],
 123          'Courier New' => [
 124              'x' => self::COURIER_NEW,
 125              'xb' => self::COURIER_NEW_BOLD,
 126              'xi' => self::COURIER_NEW_ITALIC,
 127              'xbi' => self::COURIER_NEW_BOLD_ITALIC,
 128          ],
 129          'Georgia' => [
 130              'x' => self::GEORGIA,
 131              'xb' => self::GEORGIA_BOLD,
 132              'xi' => self::GEORGIA_ITALIC,
 133              'xbi' => self::GEORGIA_BOLD_ITALIC,
 134          ],
 135          'Impact' => [
 136              'x' => self::IMPACT,
 137              'xb' => self::IMPACT,
 138              'xi' => self::IMPACT,
 139              'xbi' => self::IMPACT,
 140          ],
 141          'Liberation Sans' => [
 142              'x' => self::LIBERATION_SANS,
 143              'xb' => self::LIBERATION_SANS_BOLD,
 144              'xi' => self::LIBERATION_SANS_ITALIC,
 145              'xbi' => self::LIBERATION_SANS_BOLD_ITALIC,
 146          ],
 147          'Lucida Console' => [
 148              'x' => self::LUCIDA_CONSOLE,
 149              'xb' => self::LUCIDA_CONSOLE,
 150              'xi' => self::LUCIDA_CONSOLE,
 151              'xbi' => self::LUCIDA_CONSOLE,
 152          ],
 153          'Lucida Sans Unicode' => [
 154              'x' => self::LUCIDA_SANS_UNICODE,
 155              'xb' => self::LUCIDA_SANS_UNICODE,
 156              'xi' => self::LUCIDA_SANS_UNICODE,
 157              'xbi' => self::LUCIDA_SANS_UNICODE,
 158          ],
 159          'Microsoft Sans Serif' => [
 160              'x' => self::MICROSOFT_SANS_SERIF,
 161              'xb' => self::MICROSOFT_SANS_SERIF,
 162              'xi' => self::MICROSOFT_SANS_SERIF,
 163              'xbi' => self::MICROSOFT_SANS_SERIF,
 164          ],
 165          'Palatino Linotype' => [
 166              'x' => self::PALATINO_LINOTYPE,
 167              'xb' => self::PALATINO_LINOTYPE_BOLD,
 168              'xi' => self::PALATINO_LINOTYPE_ITALIC,
 169              'xbi' => self::PALATINO_LINOTYPE_BOLD_ITALIC,
 170          ],
 171          'Symbol' => [
 172              'x' => self::SYMBOL,
 173              'xb' => self::SYMBOL,
 174              'xi' => self::SYMBOL,
 175              'xbi' => self::SYMBOL,
 176          ],
 177          'Tahoma' => [
 178              'x' => self::TAHOMA,
 179              'xb' => self::TAHOMA_BOLD,
 180              'xi' => self::TAHOMA,
 181              'xbi' => self::TAHOMA_BOLD,
 182          ],
 183          'Times New Roman' => [
 184              'x' => self::TIMES_NEW_ROMAN,
 185              'xb' => self::TIMES_NEW_ROMAN_BOLD,
 186              'xi' => self::TIMES_NEW_ROMAN_ITALIC,
 187              'xbi' => self::TIMES_NEW_ROMAN_BOLD_ITALIC,
 188          ],
 189          'Trebuchet MS' => [
 190              'x' => self::TREBUCHET_MS,
 191              'xb' => self::TREBUCHET_MS_BOLD,
 192              'xi' => self::TREBUCHET_MS_ITALIC,
 193              'xbi' => self::TREBUCHET_MS_BOLD_ITALIC,
 194          ],
 195          'Verdana' => [
 196              'x' => self::VERDANA,
 197              'xb' => self::VERDANA_BOLD,
 198              'xi' => self::VERDANA_ITALIC,
 199              'xbi' => self::VERDANA_BOLD_ITALIC,
 200          ],
 201      ];
 202  
 203      /**
 204       * AutoSize method.
 205       *
 206       * @var string
 207       */
 208      private static $autoSizeMethod = self::AUTOSIZE_METHOD_APPROX;
 209  
 210      /**
 211       * Path to folder containing TrueType font .ttf files.
 212       *
 213       * @var string
 214       */
 215      private static $trueTypeFontPath = '';
 216  
 217      /**
 218       * How wide is a default column for a given default font and size?
 219       * Empirical data found by inspecting real Excel files and reading off the pixel width
 220       * in Microsoft Office Excel 2007.
 221       * Added height in points.
 222       */
 223      public const DEFAULT_COLUMN_WIDTHS = [
 224          'Arial' => [
 225              1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
 226              2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
 227              3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
 228  
 229              4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
 230              5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
 231              6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
 232              7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
 233              8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
 234              9 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.0],
 235              10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
 236          ],
 237          'Calibri' => [
 238              1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
 239              2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
 240              3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.00],
 241              4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
 242              5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
 243              6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
 244              7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
 245              8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
 246              9 => ['px' => 56, 'width' => 9.33203125, 'height' => 12.0],
 247              10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
 248              11 => ['px' => 64, 'width' => 9.14062500, 'height' => 15.0],
 249          ],
 250          'Verdana' => [
 251              1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
 252              2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
 253              3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
 254              4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
 255              5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
 256              6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
 257              7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
 258              8 => ['px' => 64, 'width' => 9.14062500, 'height' => 10.5],
 259              9 => ['px' => 72, 'width' => 9.00000000, 'height' => 11.25],
 260              10 => ['px' => 72, 'width' => 9.00000000, 'height' => 12.75],
 261          ],
 262      ];
 263  
 264      /**
 265       * List of column widths. Replaced by constant;
 266       * previously it was public and updateable, allowing
 267       * user to make inappropriate alterations.
 268       *
 269       * @deprecated 1.25.0 Use DEFAULT_COLUMN_WIDTHS constant instead.
 270       *
 271       * @var array
 272       */
 273      public static $defaultColumnWidths = self::DEFAULT_COLUMN_WIDTHS;
 274  
 275      /**
 276       * Set autoSize method.
 277       *
 278       * @param string $method see self::AUTOSIZE_METHOD_*
 279       *
 280       * @return bool Success or failure
 281       */
 282      public static function setAutoSizeMethod($method)
 283      {
 284          if (!in_array($method, self::AUTOSIZE_METHODS)) {
 285              return false;
 286          }
 287          self::$autoSizeMethod = $method;
 288  
 289          return true;
 290      }
 291  
 292      /**
 293       * Get autoSize method.
 294       *
 295       * @return string
 296       */
 297      public static function getAutoSizeMethod()
 298      {
 299          return self::$autoSizeMethod;
 300      }
 301  
 302      /**
 303       * Set the path to the folder containing .ttf files. There should be a trailing slash.
 304       * Typical locations on variout some platforms:
 305       *    <ul>
 306       *        <li>C:/Windows/Fonts/</li>
 307       *        <li>/usr/share/fonts/truetype/</li>
 308       *        <li>~/.fonts/</li>
 309       * </ul>.
 310       *
 311       * @param string $folderPath
 312       */
 313      public static function setTrueTypeFontPath($folderPath): void
 314      {
 315          self::$trueTypeFontPath = $folderPath;
 316      }
 317  
 318      /**
 319       * Get the path to the folder containing .ttf files.
 320       *
 321       * @return string
 322       */
 323      public static function getTrueTypeFontPath()
 324      {
 325          return self::$trueTypeFontPath;
 326      }
 327  
 328      /**
 329       * Calculate an (approximate) OpenXML column width, based on font size and text contained.
 330       *
 331       * @param FontStyle $font Font object
 332       * @param null|RichText|string $cellText Text to calculate width
 333       * @param int $rotation Rotation angle
 334       * @param null|FontStyle $defaultFont Font object
 335       * @param bool $filterAdjustment Add space for Autofilter or Table dropdown
 336       */
 337      public static function calculateColumnWidth(
 338          FontStyle $font,
 339          $cellText = '',
 340          $rotation = 0,
 341          ?FontStyle $defaultFont = null,
 342          bool $filterAdjustment = false,
 343          int $indentAdjustment = 0
 344      ): int {
 345          // If it is rich text, use plain text
 346          if ($cellText instanceof RichText) {
 347              $cellText = $cellText->getPlainText();
 348          }
 349  
 350          // Special case if there are one or more newline characters ("\n")
 351          $cellText = $cellText ?? '';
 352          if (strpos(/** @scrutinizer ignore-type */ $cellText, "\n") !== false) {
 353              $lineTexts = explode("\n", $cellText);
 354              $lineWidths = [];
 355              foreach ($lineTexts as $lineText) {
 356                  $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment);
 357              }
 358  
 359              return max($lineWidths); // width of longest line in cell
 360          }
 361  
 362          // Try to get the exact text width in pixels
 363          $approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
 364          $columnWidth = 0;
 365          if (!$approximate) {
 366              $columnWidthAdjust = ceil(
 367                  self::getTextWidthPixelsExact(
 368                      str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
 369                      $font,
 370                      0
 371                  ) * 1.07
 372              );
 373  
 374              try {
 375                  // Width of text in pixels excl. padding
 376                  // and addition because Excel adds some padding, just use approx width of 'n' glyph
 377                  $columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + $columnWidthAdjust;
 378              } catch (PhpSpreadsheetException $e) {
 379                  $approximate = true;
 380              }
 381          }
 382  
 383          if ($approximate) {
 384              $columnWidthAdjust = self::getTextWidthPixelsApprox(
 385                  str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
 386                  $font,
 387                  0
 388              );
 389              // Width of text in pixels excl. padding, approximation
 390              // and addition because Excel adds some padding, just use approx width of 'n' glyph
 391              $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
 392          }
 393  
 394          // Convert from pixel width to column width
 395          $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle());
 396  
 397          // Return
 398          return (int) round($columnWidth, 6);
 399      }
 400  
 401      /**
 402       * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
 403       */
 404      public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): int
 405      {
 406          if (!function_exists('imagettfbbox')) {
 407              throw new PhpSpreadsheetException('GD library needs to be enabled');
 408          }
 409  
 410          // font size should really be supplied in pixels in GD2,
 411          // but since GD2 seems to assume 72dpi, pixels and points are the same
 412          $fontFile = self::getTrueTypeFontFileFromFont($font);
 413          $textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text);
 414          if ($textBox === false) {
 415              // @codeCoverageIgnoreStart
 416              throw new PhpSpreadsheetException('imagettfbbox failed');
 417              // @codeCoverageIgnoreEnd
 418          }
 419  
 420          // Get corners positions
 421          $lowerLeftCornerX = $textBox[0];
 422          $lowerRightCornerX = $textBox[2];
 423          $upperRightCornerX = $textBox[4];
 424          $upperLeftCornerX = $textBox[6];
 425  
 426          // Consider the rotation when calculating the width
 427          return max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX);
 428      }
 429  
 430      /**
 431       * Get approximate width in pixels for a string of text in a certain font at a certain rotation angle.
 432       *
 433       * @param string $columnText
 434       * @param int $rotation
 435       *
 436       * @return int Text width in pixels (no padding added)
 437       */
 438      public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0)
 439      {
 440          $fontName = $font->getName();
 441          $fontSize = $font->getSize();
 442  
 443          // Calculate column width in pixels. We assume fixed glyph width. Result varies with font name and size.
 444          switch ($fontName) {
 445              case 'Calibri':
 446                  // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
 447                  $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
 448                  $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
 449  
 450                  break;
 451              case 'Arial':
 452                  // value 8 was set because of experience in different exports at Arial 10 font.
 453                  $columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
 454                  $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
 455  
 456                  break;
 457              case 'Verdana':
 458                  // value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
 459                  $columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
 460                  $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
 461  
 462                  break;
 463              default:
 464                  // just assume Calibri
 465                  $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
 466                  $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
 467  
 468                  break;
 469          }
 470  
 471          // Calculate approximate rotated column width
 472          if ($rotation !== 0) {
 473              if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
 474                  // stacked text
 475                  $columnWidth = 4; // approximation
 476              } else {
 477                  // rotated text
 478                  $columnWidth = $columnWidth * cos(deg2rad($rotation))
 479                                  + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation
 480              }
 481          }
 482  
 483          // pixel width is an integer
 484          return (int) $columnWidth;
 485      }
 486  
 487      /**
 488       * Calculate an (approximate) pixel size, based on a font points size.
 489       *
 490       * @param int $fontSizeInPoints Font size (in points)
 491       *
 492       * @return int Font size (in pixels)
 493       */
 494      public static function fontSizeToPixels($fontSizeInPoints)
 495      {
 496          return (int) ((4 / 3) * $fontSizeInPoints);
 497      }
 498  
 499      /**
 500       * Calculate an (approximate) pixel size, based on inch size.
 501       *
 502       * @param int $sizeInInch Font size (in inch)
 503       *
 504       * @return int Size (in pixels)
 505       */
 506      public static function inchSizeToPixels($sizeInInch)
 507      {
 508          return $sizeInInch * 96;
 509      }
 510  
 511      /**
 512       * Calculate an (approximate) pixel size, based on centimeter size.
 513       *
 514       * @param int $sizeInCm Font size (in centimeters)
 515       *
 516       * @return float Size (in pixels)
 517       */
 518      public static function centimeterSizeToPixels($sizeInCm)
 519      {
 520          return $sizeInCm * 37.795275591;
 521      }
 522  
 523      /**
 524       * Returns the font path given the font.
 525       *
 526       * @return string Path to TrueType font file
 527       */
 528      public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true)
 529      {
 530          if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) {
 531              throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
 532          }
 533  
 534          $name = $font->getName();
 535          if (!isset(self::FONT_FILE_NAMES[$name])) {
 536              throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
 537          }
 538          $bold = $font->getBold();
 539          $italic = $font->getItalic();
 540          $index = 'x';
 541          if ($bold) {
 542              $index .= 'b';
 543          }
 544          if ($italic) {
 545              $index .= 'i';
 546          }
 547          $fontFile = self::FONT_FILE_NAMES[$name][$index];
 548  
 549          $separator = '';
 550          if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
 551              $separator = DIRECTORY_SEPARATOR;
 552          }
 553          $fontFile = self::$trueTypeFontPath . $separator . $fontFile;
 554  
 555          // Check if file actually exists
 556          if ($checkPath && !file_exists($fontFile)) {
 557              throw new PhpSpreadsheetException('TrueType Font file not found');
 558          }
 559  
 560          return $fontFile;
 561      }
 562  
 563      public const CHARSET_FROM_FONT_NAME = [
 564          'EucrosiaUPC' => self::CHARSET_ANSI_THAI,
 565          'Wingdings' => self::CHARSET_SYMBOL,
 566          'Wingdings 2' => self::CHARSET_SYMBOL,
 567          'Wingdings 3' => self::CHARSET_SYMBOL,
 568      ];
 569  
 570      /**
 571       * Returns the associated charset for the font name.
 572       *
 573       * @param string $fontName Font name
 574       *
 575       * @return int Character set code
 576       */
 577      public static function getCharsetFromFontName($fontName)
 578      {
 579          return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN;
 580      }
 581  
 582      /**
 583       * Get the effective column width for columns without a column dimension or column with width -1
 584       * For example, for Calibri 11 this is 9.140625 (64 px).
 585       *
 586       * @param FontStyle $font The workbooks default font
 587       * @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units
 588       *
 589       * @return mixed Column width
 590       */
 591      public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false)
 592      {
 593          if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) {
 594              // Exact width can be determined
 595              $columnWidth = $returnAsPixels ?
 596                  self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px']
 597                      : self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width'];
 598          } else {
 599              // We don't have data for this particular font and size, use approximation by
 600              // extrapolating from Calibri 11
 601              $columnWidth = $returnAsPixels ?
 602                  self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px']
 603                      : self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width'];
 604              $columnWidth = $columnWidth * $font->getSize() / 11;
 605  
 606              // Round pixels to closest integer
 607              if ($returnAsPixels) {
 608                  $columnWidth = (int) round($columnWidth);
 609              }
 610          }
 611  
 612          return $columnWidth;
 613      }
 614  
 615      /**
 616       * Get the effective row height for rows without a row dimension or rows with height -1
 617       * For example, for Calibri 11 this is 15 points.
 618       *
 619       * @param FontStyle $font The workbooks default font
 620       *
 621       * @return float Row height in points
 622       */
 623      public static function getDefaultRowHeightByFont(FontStyle $font)
 624      {
 625          $name = $font->getName();
 626          $size = $font->getSize();
 627          if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) {
 628              $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height'];
 629          } elseif ($name === 'Arial' || $name === 'Verdana') {
 630              $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0;
 631          } else {
 632              $rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0;
 633          }
 634  
 635          return $rowHeight;
 636      }
 637  }