PHP:如何从.ttf文件中读取字体的“标题”?

24

我需要能够从.ttf True Type字体文件中提取元数据。

我正在构建一个所有设计师都使用的所有字体的中央数据库(他们经常通过电子邮件交换字体以接管设计元素等)。 我想获取所有字体,有些字体名称很愚蠢像00001.ttf,因此文件名无法提供帮助,但我知道这些字体具有元数据,我需要一种在PHP中提取它的方法。

然后我可以创建一个循环来浏览我指定的目录,并获取这个数据(和同时获得的任何其他数据),并将其添加到数据库中。

我只是真正需要帮助读取这个元数据部分。


有趣的问题,尽管上一个提问者没有得到答案 :( https://dev59.com/sVPTa4cB1Zd3GeqPfQOp - John Carter
我很确定可以使用ImageMagick获取字体名称,但是我似乎在ImageMagick网站上找不到任何参考。 :( - tamarintech
可能是重复的问题: PHP:读取字体文件的TrueType / OpenType元数据 - e-sushi
也许6年前发布时它是一个重复的候选问题,但是另一个问题除了指向这里的链接之外没有真正的答案。因为另一个问题而关闭这个问题是愚蠢的。 - i-CONICA
3个回答

21
我发现了这个链接:http://www.phpclasses.org/package/3876-PHP-Extract-information-from-TrueType-font-files.html。它可以实现你想要的功能(我已经测试过并发布了结果)。只需将TTF文件的路径传递给该类,即可解析出数据。然后使用$fontinfo[1].' '.$fontinfo[2]获取字体名称。
如果您不想注册,可以在此处找到该类。 输出的数据
Array
(
    [1] => Almonte Snow
    [2] => Regular
    [3] => RayLarabie: Almonte Snow: 2000
    [4] => Almonte Snow
    [5] => Version 2.000 2004
    [6] => AlmonteSnow
    [8] => Ray Larabie
    [9] => Ray Larabie
    [10] => Larabie Fonts is able to offer unique free fonts through the generous support of visitors to the site. Making fonts is my full-time job and every donation, in any amount, enables me to continue running the site and creating new fonts. If you would like to support Larabie Fonts visit www.larabiefonts.com for details.
    [11] => http://www.larabiefonts.com
    [12] => http://www.typodermic.com
)

使用方法

<?php 
    include 'ttfInfo.class.php'; 
    $fontinfo = getFontInfo('c:\windows\fonts\_LDS_almosnow.ttf'); 
    echo '<pre>'; 
    print_r($fontinfo); 
    echo '</pre>'; 
?>

ttfInfo.class.php

<?php 
/** 
 * ttfInfo class 
 * Retrieve data stored in a TTF files 'name' table 
 * 
 * @original author Unknown 
 * found at http://www.phpclasses.org/browse/package/2144.html 
 * 
 * @ported for used on http://www.nufont.com 
 * @author Jason Arencibia 
 * @version 0.2 
 * @copyright (c) 2006 GrayTap Media 
 * @website http://www.graytap.com 
 * @license GPL 2.0 
 * @access public 
 * 
 * @todo: Make it Retrieve additional information from other tables 
 *  
 */ 
class ttfInfo { 
    /** 
    * variable $_dirRestriction 
    * Restrict the resource pointer to this directory and above. 
    * Change to 1 for to allow the class to look outside of it current directory 
    * @protected 
    * @var int 
    */ 
    protected $_dirRestriction = 1; 
    /** 
    * variable $_dirRestriction 
    * Restrict the resource pointer to this directory and above. 
    * Change to 1 for nested directories 
    * @protected 
    * @var int 
    */ 
    protected $_recursive = 0; 

    /** 
    * variable $fontsdir 
    * This is to declare this variable as protected 
    * don't edit this!!! 
    * @protected 
    */ 
    protected $fontsdir; 
    /** 
    * variable $filename 
    * This is to declare this varable as protected 
    * don't edit this!!! 
    * @protected 
    */ 
    protected $filename; 

    /** 
    * function setFontFile() 
    * set the filename 
    * @public 
    * @param string $data the new value 
    * @return object reference to this 
    */ 
    public function setFontFile($data) 
    { 
        if ($this->_dirRestriction && preg_match('[\.\/|\.\.\/]', $data)) 
        { 
            $this->exitClass('Error: Directory restriction is enforced!'); 
        } 

        $this->filename = $data; 
        return $this; 
    } // public function setFontFile 

    /** 
    * function setFontsDir() 
    * set the Font Directory 
    * @public 
    * @param string $data the new value 
    * @return object referrence to this 
    */ 
    public function setFontsDir($data) 
    { 
        if ($this->_dirRestriction && preg_match('[\.\/|\.\.\/]', $data)) 
        { 
            $this->exitClass('Error: Directory restriction is enforced!'); 
        } 

        $this->fontsdir = $data; 
        return $this; 
    } // public function setFontsDir 

    /** 
    * function readFontsDir()  
    * @public 
    * @return information contained in the TTF 'name' table of all fonts in a directory. 
    */ 
    public function readFontsDir() 
    { 
        if (empty($this->fontsdir)) { $this->exitClass('Error: Fonts Directory has not been set with setFontsDir().'); } 
        if (empty($this->backupDir)){ $this->backupDir = $this->fontsdir; } 

        $this->array = array(); 
        $d = dir($this->fontsdir); 

        while (false !== ($e = $d->read())) 
        { 
            if($e != '.' && $e != '..') 
            { 
                $e = $this->fontsdir . $e; 
                if($this->_recursive && is_dir($e)) 
                { 
                    $this->setFontsDir($e); 
                    $this->array = array_merge($this->array, readFontsDir()); 
                } 
                else if ($this->is_ttf($e) === true) 
                { 
                    $this->setFontFile($e); 
                    $this->array[$e] = $this->getFontInfo(); 
                } 
            } 
        } 

        if (!empty($this->backupDir)){ $this->fontsdir = $this->backupDir; } 

        $d->close(); 
        return $this; 
    } // public function readFontsDir 

    /** 
    * function setProtectedVar() 
    * @public 
    * @param string $var the new variable 
    * @param string $data the new value 
    * @return object reference to this 

    * DISABLED, NO REAL USE YET 

    public function setProtectedVar($var, $data) 
    { 
        if ($var == 'filename') 
        { 
            $this->setFontFile($data); 
        } else { 
            //if (isset($var) && !empty($data)) 
            $this->$var = $data; 
        } 
        return $this; 
    } 
    */ 
    /** 
    * function getFontInfo()  
    * @public 
    * @return information contained in the TTF 'name' table. 
    */ 
    public function getFontInfo() 
    { 
        $fd = fopen ($this->filename, "r"); 
        $this->text = fread ($fd, filesize($this->filename)); 
        fclose ($fd); 

        $number_of_tables = hexdec($this->dec2ord($this->text[4]).$this->dec2ord($this->text[5])); 

        for ($i=0;$i<$number_of_tables;$i++) 
        { 
            $tag = $this->text[12+$i*16].$this->text[12+$i*16+1].$this->text[12+$i*16+2].$this->text[12+$i*16+3];

            if ($tag == 'name') 
            { 
                $this->ntOffset = hexdec( 
                    $this->dec2ord($this->text[12+$i*16+8]).$this->dec2ord($this->text[12+$i*16+8+1]). 
                    $this->dec2ord($this->text[12+$i*16+8+2]).$this->dec2ord($this->text[12+$i*16+8+3])); 

                $offset_storage_dec = hexdec($this->dec2ord($this->text[$this->ntOffset+4]).$this->dec2ord($this->text[$this->ntOffset+5])); 
                $number_name_records_dec = hexdec($this->dec2ord($this->text[$this->ntOffset+2]).$this->dec2ord($this->text[$this->ntOffset+3])); 
            } 
        } 

        $storage_dec = $offset_storage_dec + $this->ntOffset; 
        $storage_hex = strtoupper(dechex($storage_dec)); 

        for ($j=0;$j<$number_name_records_dec;$j++) 
        { 
            $platform_id_dec    = hexdec($this->dec2ord($this->text[$this->ntOffset+6+$j*12+0]).$this->dec2ord($this->text[$this->ntOffset+6+$j*12+1])); 
            $name_id_dec        = hexdec($this->dec2ord($this->text[$this->ntOffset+6+$j*12+6]).$this->dec2ord($this->text[$this->ntOffset+6+$j*12+7])); 
            $string_length_dec    = hexdec($this->dec2ord($this->text[$this->ntOffset+6+$j*12+8]).$this->dec2ord($this->text[$this->ntOffset+6+$j*12+9])); 
            $string_offset_dec    = hexdec($this->dec2ord($this->text[$this->ntOffset+6+$j*12+10]).$this->dec2ord($this->text[$this->ntOffset+6+$j*12+11])); 

            if (!empty($name_id_dec) and empty($font_tags[$name_id_dec])) 
            { 
                for($l=0;$l<$string_length_dec;$l++) 
                { 
                    if (ord($this->text[$storage_dec+$string_offset_dec+$l]) == '0') { continue; } 
                    else { $font_tags[$name_id_dec] .= ($this->text[$storage_dec+$string_offset_dec+$l]); } 
                } 
            } 
        } 
        return $font_tags; 
    } // public function getFontInfo 

    /** 
    * function getCopyright()  
    * @public 
    * @return 'Copyright notice' contained in the TTF 'name' table at index 0 
    */ 
    public function getCopyright() 
    { 
        $this->info = $this->getFontInfo(); 
        return $this->info[0]; 
    } // public function getCopyright 

    /** 
    * function getFontFamily()  
    * @public 
    * @return 'Font Family name' contained in the TTF 'name' table at index 1 
    */ 
    public function getFontFamily() 
    { 
        $this->info = $this->getFontInfo(); 
        return $this->info[1]; 
    } // public function getFontFamily 

    /** 
    * function getFontSubFamily()  
    * @public 
    * @return 'Font Subfamily name' contained in the TTF 'name' table at index 2 
    */ 
    public function getFontSubFamily() 
    { 
        $this->info = $this->getFontInfo(); 
        return $this->info[2]; 
    } // public function getFontSubFamily 

    /** 
    * function getFontId()  
    * @public 
    * @return 'Unique font identifier' contained in the TTF 'name' table at index 3 
    */ 
    public function getFontId() 
    { 
        $this->info = $this->getFontInfo(); 
        return $this->info[3]; 
    } // public function getFontId 

    /** 
    * function getFullFontName()  
    * @public 
    * @return 'Full font name' contained in the TTF 'name' table at index 4 
    */ 
    public function getFullFontName() 
    { 
        $this->info = $this->getFontInfo(); 
        return $this->info[4]; 
    } // public function getFullFontName 

    /** 
    * function dec2ord() 
    * Used to lessen redundant calls to multiple functions. 
    * @protected 
    * @return object 
    */ 
    protected function dec2ord($dec) 
    { 
        return $this->dec2hex(ord($dec)); 
    } // protected function dec2ord 

    /** 
    * function dec2hex() 
    * private function to perform Hexadecimal to decimal with proper padding. 
    * @protected 
    * @return object 
    */ 
    protected function dec2hex($dec) 
    { 
        return str_repeat('0', 2-strlen(($hex=strtoupper(dechex($dec))))) . $hex; 
    } // protected function dec2hex 

    /** 
    * function dec2hex() 
    * private function to perform Hexadecimal to decimal with proper padding. 
    * @protected 
    * @return object 
    */ 
    protected function exitClass($message) 
    { 
        echo $message; 
        exit; 
    } // protected function dec2hex 

    /** 
    * function dec2hex() 
    * private helper function to test in the file in question is a ttf. 
    * @protected 
    * @return object 
    */ 
    protected function is_ttf($file) 
    { 
        $ext = explode('.', $file); 
        $ext = $ext[count($ext)-1]; 
        return preg_match("/ttf$/i",$ext) ? true : false; 
    } // protected function is_ttf 
} // class ttfInfo 

function getFontInfo($resource) 
{ 
    $ttfInfo = new ttfInfo; 
    $ttfInfo->setFontFile($resource); 
    return $ttfInfo->getFontInfo(); 
} 
?>

2021更新

这是一个经过修复的类的更新版本。 https://github.com/HusamAamer/TTFInfo.git


2
我找到了一个链接,其中描述了TTF格式的标题结构:http://www.codeproject.com/KB/GDI/fontnamefromfile.aspx - horatio
你是不是想在原帖下面发表这个评论? - Dutchie432
2
不是真的。我的意思是发布提供有关你答案的额外信息的内容。正如发布的类显然具有文件结构的隐式信息一样,我链接的信息明确定义了标头中的字段并讨论了其中的一些内容。只是放进类很好,但对问题的更深入理解也可能会有所帮助。 - horatio
3
在该代码中,“$font_tags”未定义。这个变量应该在哪里定义?它没有在任何地方声明。这对其他人是如何工作的? - Goose
如何获取字体的PostScript名称 - Yahya Uddin
显示剩余2条评论

4

与之前发布的答案非常相似...我现在已经使用这个类很长时间了。

class fontAttributes extends baseClass
{
    // --- ATTRIBUTES ---

    /**
     *  @access private
     *  @var string
     */
    private $_fileName          = NULL ;                    //  Name of the truetype font file


    /**
     *  @access private
     *  @var string
     */
    private $_copyright         = NULL ;                    //  Copyright


    /**
     *  @access private
     *  @var string
     */
    private $_fontFamily        = NULL ;                    //  Font Family


    /**
     *  @access private
     *  @var string
     */
    private $_fontSubFamily     = NULL ;                    //  Font SubFamily


    /**
     *  @access private
     *  @var string
     */
    private $_fontIdentifier    = NULL ;                    //  Font Unique Identifier


    /**
     *  @access private
     *  @var string
     */
    private $_fontName          = NULL ;                    //  Font Name


    /**
     *  @access private
     *  @var string
     */
    private $_fontVersion       = NULL ;                    //  Font Version


    /**
     *  @access private
     *  @var string
     */
    private $_postscriptName    = NULL ;                    //  Postscript Name


    /**
     *  @access private
     *  @var string
     */
    private $_trademark         = NULL ;                    //  Trademark



    // --- OPERATIONS ---

    private function _returnValue($inString)
    {
        if (ord($inString) == 0) {
            if (function_exists('mb_convert_encoding')) {
                return mb_convert_encoding($inString,"UTF-8","UTF-16");
            } else {
                return str_replace(chr(00),'',$inString);
            }
        } else {
            return $inString;
        }
    }   //  function _returnValue()

    /**
     *  @access public
     *  @return integer
     */
    public function getCopyright()
    {
        return $this->_returnValue($this->_copyright);
    }   //  function getCopyright()


    /**
     *  @access public
     *  @return integer
     */
    public function getFontFamily()
    {
        return $this->_returnValue($this->_fontFamily);
    }   //  function getFontFamily()


    /**
     *  @access public
     *  @return integer
     */
    public function getFontSubFamily()
    {
        return $this->_returnValue($this->_fontSubFamily);
    }   //  function getFontSubFamily()


    /**
     *  @access public
     *  @return integer
     */
    public function getFontIdentifier()
    {
        return $this->_returnValue($this->_fontIdentifier);
    }   //  function getFontIdentifier()


    /**
     *  @access public
     *  @return integer
     */
    public function getFontName()
    {
        return $this->_returnValue($this->_fontName);
    }   //  function getFontName()


    /**
     *  @access public
     *  @return integer
     */
    public function getFontVersion()
    {
        return $this->_returnValue($this->_fontVersion);
    }   //  function getFontVersion()


    /**
     *  @access public
     *  @return integer
     */
    public function getPostscriptName()
    {
        return $this->_returnValue($this->_postscriptName);
    }   //  function getPostscriptName()


    /**
     *  @access public
     *  @return integer
     */
    public function getTrademark()
    {
        return $this->_returnValue($this->_trademark);
    }   //  function getTrademark()


    /**
     *  Convert a big-endian word or longword value to an integer
     *
     *  @access private
     *  @return integer
     */
    private function _UConvert($bytesValue,$byteCount)
    {
        $retVal = 0;
        $bytesLength = strlen($bytesValue);
        for ($i=0; $i < $bytesLength; $i++) {
            $tmpVal = ord($bytesValue{$i});
            $t = pow(256,($byteCount-$i-1));
            $retVal += $tmpVal*$t;
        }

        return $retVal;
    }   //  function UConvert()


    /**
     *  Convert a big-endian word value to an integer
     *
     *  @access private
     *  @return integer
     */
    private function _USHORT($stringValue) {
        return $this->_UConvert($stringValue,2);
    }


    /**
     *  Convert a big-endian word value to an integer
     *
     *  @access private
     *  @return integer
     */
    private function _ULONG($stringValue) {
        return $this->_UConvert($stringValue,4);
    }


    /**
     *  Read the Font Attributes
     *
     *  @access private
     *  @return integer
     */
    private function readFontAttributes() {
        $fontHandle = fopen($this->_fileName, "rb");

        //  Read the file header
        $TT_OFFSET_TABLE = fread($fontHandle, 12);

        $uMajorVersion  = $this->_USHORT(substr($TT_OFFSET_TABLE,0,2));
        $uMinorVersion  = $this->_USHORT(substr($TT_OFFSET_TABLE,2,2));
        $uNumOfTables   = $this->_USHORT(substr($TT_OFFSET_TABLE,4,2));
//      $uSearchRange   = $this->_USHORT(substr($TT_OFFSET_TABLE,6,2));
//      $uEntrySelector = $this->_USHORT(substr($TT_OFFSET_TABLE,8,2));
//      $uRangeShift    = $this->_USHORT(substr($TT_OFFSET_TABLE,10,2));

        //  Check is this is a true type font and the version is 1.0
        if ($uMajorVersion != 1 || $uMinorVersion != 0) {
            fclose($fontHandle);
            throw new Exception($this->_fileName.' is not a Truetype font file') ;
        }

        //  Look for details of the name table
        $nameTableFound = false;
        for ($t=0; $t < $uNumOfTables; $t++) {
            $TT_TABLE_DIRECTORY = fread($fontHandle, 16);
            $szTag = substr($TT_TABLE_DIRECTORY,0,4);
            if (strtolower($szTag) == 'name') {
//              $uCheckSum  = $this->_ULONG(substr($TT_TABLE_DIRECTORY,4,4));
                $uOffset    = $this->_ULONG(substr($TT_TABLE_DIRECTORY,8,4));
//              $uLength    = $this->_ULONG(substr($TT_TABLE_DIRECTORY,12,4));
                $nameTableFound = true;
                break;
            }
        }

        if (!$nameTableFound) {
            fclose($fontHandle);
            throw new Exception('Can\'t find name table in '.$this->_fileName) ;
        }

        //  Set offset to the start of the name table
        fseek($fontHandle,$uOffset,SEEK_SET);

        $TT_NAME_TABLE_HEADER = fread($fontHandle, 6);

//      $uFSelector     = $this->_USHORT(substr($TT_NAME_TABLE_HEADER,0,2));
        $uNRCount       = $this->_USHORT(substr($TT_NAME_TABLE_HEADER,2,2));
        $uStorageOffset = $this->_USHORT(substr($TT_NAME_TABLE_HEADER,4,2));

        $attributeCount = 0;
        for ($a=0; $a < $uNRCount; $a++) {
            $TT_NAME_RECORD = fread($fontHandle, 12);

            $uNameID = $this->_USHORT(substr($TT_NAME_RECORD,6,2));
            if ($uNameID <= 7) {
//              $uPlatformID    = $this->_USHORT(substr($TT_NAME_RECORD,0,2));
                $uEncodingID    = $this->_USHORT(substr($TT_NAME_RECORD,2,2));
//              $uLanguageID    = $this->_USHORT(substr($TT_NAME_RECORD,4,2));
                $uStringLength  = $this->_USHORT(substr($TT_NAME_RECORD,8,2));
                $uStringOffset  = $this->_USHORT(substr($TT_NAME_RECORD,10,2));

                if ($uStringLength > 0) {
                    $nPos = ftell($fontHandle);
                    fseek($fontHandle,$uOffset + $uStringOffset + $uStorageOffset,SEEK_SET);
                    $testValue = fread($fontHandle, $uStringLength);

                    if (trim($testValue) > '') {
                        switch ($uNameID) {
                            case 0  : if ($this->_copyright == NULL) {
                                        $this->_copyright = $testValue;
                                        $attributeCount++;
                                      }
                                      break;
                            case 1  : if ($this->_fontFamily == NULL) {
                                        $this->_fontFamily = $testValue;
                                        $attributeCount++;
                                      }
                                      break;
                            case 2  : if ($this->_fontSubFamily == NULL) {
                                        $this->_fontSubFamily = $testValue;
                                        $attributeCount++;
                                      }
                                      break;
                            case 3  : if ($this->_fontIdentifier == NULL) {
                                        $this->_fontIdentifier = $testValue;
                                        $attributeCount++;
                                      }
                                      break;
                            case 4  : if ($this->_fontName == NULL) {
                                        $this->_fontName = $testValue;
                                        $attributeCount++;
                                      }
                                      break;
                            case 5  : if ($this->_fontVersion == NULL) {
                                        $this->_fontVersion = $testValue;
                                        $attributeCount++;
                                      }
                                      break;
                            case 6  : if ($this->_postscriptName == NULL) {
                                        $this->_postscriptName = $testValue;
                                        $attributeCount++;
                                      }
                                      break;
                            case 7  : if ($this->_trademark == NULL) {
                                        $this->_trademark = $testValue;
                                        $attributeCount++;
                                      }
                                      break;
                        }
                    }
                    fseek($fontHandle,$nPos,SEEK_SET);
                }
            }
            if ($attributeCount > 7) {
                break;
            }
        }

        fclose($fontHandle);
        return true;
    }




    /**
     *  @access constructor
     *  @return void
     */
    function __construct($fileName='') {

        if ($fileName == '') {
            throw new Exception('Font File has not been specified') ;
        }

        $this->_fileName = $fileName;

        if (!file_exists($this->_fileName)) {
            throw new Exception($this->_fileName.' does not exist') ;
        } elseif (!is_readable($this->_fileName)) {
            throw new Exception($this->_fileName.' is not a readable file') ;
        }

        return $this->readFontAttributes();
    }   //  function constructor()


}   /* end of class fontAttributes */

这个怎么用?在你加载类之后,你调用哪个函数并传递什么参数? - Goose
我一开始不知道如何使用它,但在更改了一些代码后,我让它正常工作了。如果提供一个加载后如何使用该类的示例,将会对未来的访问者更有帮助。 - Goose
缺少baseClass?什么是baseClass? - Ajit Kumar

3

当我提出这个问题时,那还不存在。虽然现在可能有更好的方法,但我使用的答案一直为我服务得很好。 - i-CONICA
嗨,Dors!你能否给我一个使用这个库的例子?你提供的示例页面已经不存在了。顺便说一下,我投了你一票,因为这个库支持我需要的WOFF字体。 - DrupalFever
@DrupalFever,Github页面上有一个使用示例,就在项目页面https://github.com/PhenX/php-font-lib上。 - dors

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接