clq

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

http://www.ugia.cn/?p=124

 

.fon字体文件

几年前痴迷于nfo2pic的时候,就一直在找.fon文件的格式说明,以便从里面把字体点阵提取出来,但是没有找到,最后我和andot不 得不抓屏,然后分析图片,把点阵数据提取出来做成gd可以使用的字体。其后一直未放弃寻找,直到前几天想起来,找到一点线索,然后顺藤摸瓜,终于搞清楚 了.fon文件的格式,参照python的一个脚本用php提取出了.fon字体中的点阵数据,并制作成gd可用的字体。


.fon其实是标准的windows可执行文件(.exe)格式,分NE(New Executable)PE(Portable Executable)两种类型,字体作为资源存在其中。NE是旧的可执行文件格式,从win95开始的32位可执行程序就都是PE了。字体资源里的字体都是标准的fnt格式,可在此查看.fnt文件的格式说明

下面就是我临时参照Simon Tathamdewinfont写的提取程序,pe格式的因为没有测试文件,所以没有分析,仅供参考:

  1. /**
  2. * 提取.fon文件中的字体点阵数据,并制作gd中imageloadfont函数可用的字体
  3. *
  4. * @author: legend(legendsky@hotmail.com)
  5. * @copyright   UGiA.CN
  6. * @link: http://www.ugia.cn/?p=124
  7. *
  8. * usage:
  9. *
  10. * <?php
  11. * include('class_fon.php');
  12. *
  13. * $fon = new Fon("consol10.fon");
  14. *
  15. * if ($fon == false)
  16. * {
  17. *     echo $fon->errno . ": " . $fon->errstr;
  18. * }
  19. */
  20.  
  21. class Fon
  22. {
  23.     var $file   = '';
  24.     var $stream = null;
  25.     var $savepath = '';
  26.  
  27.     var $errno  = 0;
  28.     var $errstr = '';
  29.  
  30.  
  31.     function fon($file = '', $savepath = '')
  32.     {
  33.         if ($file !== '')
  34.         {
  35.             $this->parse($file, $savepath);
  36.         }
  37.     }
  38.  
  39.     function error($errno, $errstr)
  40.     {
  41.         $this->errno = $errno;
  42.         $this->errstr = $errstr;
  43.     }
  44.  
  45.     function parse($file, $savepath)
  46.     {
  47.         if (!$file)
  48.         {
  49.             $this->error(1001, 'Please assign a file!');
  50.             return false;
  51.         }
  52.        
  53.         $this->savepath = $savepath ? str_replace("\\", "/", $savepath) : './';
  54.         $this->savepath .= substr($this->savepath, -1) != '/' ? '/' : '';
  55.  
  56.         if (is_resource($file))
  57.         {
  58.             $this->stream = $file;
  59.         }
  60.         else
  61.         {
  62.             $this->file = $file;
  63.             $this->stream = file_get_contents($file);
  64.         }
  65.  
  66.         if (!$this->stream)
  67.         {
  68.             $this->error(1002, 'Can not open file!');
  69.             return false;
  70.         }
  71.  
  72.         $fonts = $this->parse_fon();
  73.  
  74.         if (!$fonts)
  75.         {
  76.             return false;
  77.         }
  78.        
  79.         if (!is_dir($this->savepath . $fonts[0]['facename']))
  80.         {
  81.             @mkdir($this->savepath . $fonts[0]['facename'], 0700);
  82.         }
  83.  
  84.         foreach ($fontsas$k => $font)
  85.         {
  86.             $filename  = $font['facename'] . sprintf("_%02d", $k);
  87.             $filename .= "_" . $font['width'] . "x" . $font['height'];
  88.             if ($font['italic'])$filename .= "_italic";
  89.             if ($font['underline'])$filename .= "_underline";
  90.             if ($font['strikeout'])$filename .= "_strikeout";
  91.             $filename .= ".fd";
  92.  
  93.             $this->print_font($font, $this->savepath . $font['facename'] . "/" . $filename);
  94.         }
  95.     }
  96.  
  97.     function parse_fon()
  98.     {
  99.         $s = & new Stream($this->stream);
  100.  
  101.         if (substr($this->stream, 0, 2) != 'MZ')
  102.         {
  103.             $this->error(2001, 'MZ signature not found!');
  104.             return false;
  105.         }
  106.        
  107.         $neoff = $s->dword(0x3c); // 标志位offset
  108.  
  109.         if (substr($this->stream, $neoff, 2) == 'NE')
  110.         {
  111.             return $this->parse_ne($neoff);
  112.         }
  113.         else if(substr($this->stream, $neoff, 4) == "PE\0\0")
  114.         {
  115.             return $this->parse_pe($neoff);
  116.         }
  117.        
  118.         $this->error(2002, 'NE or PE signature not found');
  119.         return false;
  120.     }
  121.  
  122.     function parse_ne($neoff)
  123.     {
  124.         $stream = & $this->stream;
  125.         $s = & new Stream($this->stream);
  126.  
  127.         $ret = array();
  128.  
  129.         // Find the resource table.
  130.         $rtable = $neoff + $s->word($neoff + 0x24);
  131.  
  132.         // 32h: A shift count that is used to align the logical sector. This
  133.         // count is log2 of the segment sector size. It is typically 4,
  134.         // although the default count is 9.
  135.         $shift = $s->word($rtable);
  136.  
  137.         // Now loop over the rest of the resource table.
  138.         $p = $rtable + 2;
  139.         while (1)
  140.         {
  141.             $rtype = $s->word($p);
  142.            
  143.             // end of resource table
  144.             if ($rtype == 0)
  145.             {
  146.                 break;
  147.             }
  148.            
  149.             $count = $s->word($p + 2);           
  150.             // type, count, 4 bytes reserved
  151.             $p += 8;
  152.            
  153.             for ($i = 0; $i < $count; $i ++)
  154.             {
  155.                 $start = $s->word($p) << $shift;
  156.                 $size = $s->word($p + 2) << $shift;
  157.                
  158.                 if ($start < 0 || $size < 0 || $start + $size > strlen($this->stream))
  159.                 {
  160.                     $this->error(2003, 'Resource overruns file boundaries');
  161.  
  162.                     return false;
  163.                 }
  164.                
  165.                 // this is an actual font
  166.                 if ($rtype == 0x8008)
  167.                 {
  168.                     $font = $this->parse_fnt(substr($this->stream, $start, $size));
  169.                     //echo "font start at $start, size: $size\n";
  170.                     $ret[] = $font;
  171.                 }
  172.  
  173.                 // start, size, flags, name/id, 4 bytes reserved
  174.                 $p += 12;
  175.             }
  176.         }
  177.        
  178.         return $ret;
  179.     }
  180.    
  181.     function print_font($font, $filename)
  182.     {
  183.         $fp  = fopen($filename, "w");
  184.         $gdf = fopen(substr($filename, 0, -2) . 'gdf', "w"); // GD Font
  185.  
  186.         fwrite($fp, "# .fd font description generated by dewinfont(php).\n\n");
  187.         fwrite($fp, "facename $font[facename]\n");
  188.         fwrite($fp, "copyright $font[copyright]\n\n");
  189.         fwrite($fp, "height $font[height]\n");
  190.         fwrite($fp, "ascent $font[ascent]\n");
  191.        
  192.         // gd
  193.         fwrite($gdf, "\0\1\0\0");
  194.         fwrite($gdf, "\0\0\0\0");
  195.         fwrite($gdf, chr($font['width']) . "\0\0\0", 4);
  196.         fwrite($gdf, chr($font['height']) . "\0\0\0", 4);
  197.  
  198.         if ($font['height'] == $font['pointsize'])fwrite($fp, "#");
  199.         fwrite($fp, "pointsize $font[pointsize]\n\n");
  200.  
  201.         if (!$font['italic'])fwrite($fp, "#");       
  202.         fwrite($fp, "italic " . ($font['italic'] ? 'yes' : 'no') . "\n");
  203.  
  204.         if (!$font['underline'])fwrite($fp, "#");
  205.         fwrite($fp, "underline " . ($font['underline'] ? 'yes' : 'no') . "\n");
  206.  
  207.         if (!$font['strikeout'])fwrite($fp, "#");       
  208.         fwrite($fp, "strikeout " . ($font['strikeout'] ? 'yes' : 'no') . "\n");
  209.        
  210.         if ($font['weight'] == 400)fwrite($fp, "#");
  211.         fwrite($fp, "weight $font[weight]\n\n");
  212.  
  213.         if ($font['charset'] == 0)fwrite($fp, "#");
  214.         fwrite($fp, "charset $font[charset]\n\n");     
  215.  
  216.         for ($i = 0; $i < 256; $i ++)
  217.         {
  218.             fwrite($fp, "char $i\nwidth " . $font['chars'][$i]['width'] . "\n");
  219.  
  220.             if ($font['chars'][$i]['width'] != 0)
  221.             {
  222.                 for ($j = 0; $j < $font['height']; $j ++)
  223.                 {
  224.                     $v = $font['chars'][$i]['data'][$j];
  225.                     $m = 1 << ($font['chars'][$i]['width'] - 1);
  226.                     for ($k = 0; $k < $font['chars'][$i]['width']; $k ++)
  227.                     {
  228.                         if ($v & $m)
  229.                         {
  230.                             fwrite($fp, "M");
  231.                             fwrite($gdf, in_array($i, array(7, 8, 9, 10, 13, 26)) ? "\0" : "\1");
  232.                         }
  233.                         else
  234.                         {
  235.                             fwrite($fp, ".");
  236.                             fwrite($gdf, "\0");
  237.                         }
  238.  
  239.                         $v = $v << 1;
  240.                     }
  241.  
  242.                     fwrite($fp, "\n");
  243.                 }
  244.  
  245.                 fwrite($fp, "\n");
  246.             }
  247.         }
  248.        
  249.         //fwrite($gdf, "(C)2007 UGiA.CN");
  250.         fclose($gdf);
  251.         fclose($fp);
  252.     }
  253.  
  254.     function parse_fnt($stream)
  255.     {
  256.         $s = & new Stream($stream);
  257.        
  258.         $font = array();
  259.  
  260.         $font['version'] = $s->word(0);
  261.         $font['copyright'] = substr($stream, 6, 60);
  262.         $ftype = $s->word(0x42);
  263.  
  264.         if ($ftype & 1)
  265.         {
  266.             // This font is a vector font
  267.             return;
  268.         }
  269.        
  270.         // face name offset
  271.         $off_facename = $s->dword(0x69);
  272.  
  273.         if ($off_facename < 0 || $off_facename > strlen($stream))
  274.         {
  275.             // Face name not contained within font data
  276.             return;
  277.         }
  278.  
  279.         $font['facename'] = $s->read_str($off_facename);
  280.         $font['pointsize'] = $s->word(0x44);
  281.         $font['ascent'] = $s->word(0x4a);
  282.         $font['width'] = 0; // max width
  283.         $font['height'] = $s->word(0x58);
  284.         $font['italic'] = $s->byte(0x50);
  285.         $font['underline'] = $s->byte(0x51);
  286.         $font['strikeout'] = $s->byte(0x52);
  287.         $font['weight'] = $s->word(0x53);
  288.         $font['charset'] = $s->byte(0x55);
  289.        
  290.         // Read the char table.    
  291.         if ($font['version'] == 0x200)
  292.         {
  293.             $ctstart = 0x76;
  294.             $ctsize = 4;
  295.         }
  296.         else
  297.         {
  298.             $ctstart = 0x94;
  299.             $ctsize = 6;
  300.         }
  301.  
  302.         $maxwidth = 0;
  303.  
  304.         $font['chars'] = array();
  305.  
  306.         for ($i = 0; $i < 256; $i ++)
  307.         {
  308.             $font['chars'][$i]['data'] = array_fill(0, $font['height'], 0);
  309.         }
  310.  
  311.         $firstchar = $s->byte(0x5f);
  312.         $lastchar = $s->byte(0x60);
  313.         #print "$firstchar,$lastchar ";
  314.         for ($i = $firstchar; $i <= $lastchar; $i ++)
  315.         {
  316.             $entry = $ctstart + $ctsize * ($i - $firstchar);
  317.             $w = $s->word($entry);
  318.             $font['chars'][$i]['width'] = $w;
  319.             $font['width'] = $w > $font['width'] ? $w : $font['width'];
  320.            
  321.             if ($ctsize == 4)
  322.             {
  323.                 $off = $s->word($entry + 2);
  324.             }
  325.             else
  326.             {
  327.                 $off = $s->dword($entry + 2);
  328.             }
  329.            
  330.             $widthbytes = floor(($w + 7) / 8);
  331.             //echo $widthbytes . " ";
  332.             for ($j = 0; $j < $font['height']; $j ++)
  333.             {
  334.                 for ($k = 0; $k < $widthbytes; $k ++)
  335.                 {
  336.                     $bytepos = $off + $k * $font['height'] + $j;
  337.                    
  338.                     $font['chars'][$i]['data'][$j] = $font['chars'][$i]['data'][$j] << 8;
  339.                     $font['chars'][$i]['data'][$j] = $font['chars'][$i]['data'][$j] | $s->byte($bytepos);
  340.                 }
  341.  
  342.                 $font['chars'][$i]['data'][$j] = $font['chars'][$i]['data'][$j] >> (8 * $widthbytes - $w);
  343.                 //echo $font['chars'][$i]['data'][$j] . " ";
  344.             }          
  345.         }
  346.  
  347.         //print_r($font);
  348.  
  349.         return $font;
  350.     }
  351. }
  352.  
  353. class Stream
  354. {
  355.     var $stream = '';
  356.  
  357.     function stream($stream)
  358.     {
  359.         $this->stream = $stream;
  360.     }
  361.  
  362.     function byte($offset)
  363.     {
  364.         return ord($this->stream{$offset});
  365.     }
  366.  
  367.     function word($offset)
  368.     {
  369.         return $this->byte($offset + 0) + 256 * $this->byte($offset + 1);
  370.     }
  371.  
  372.     function dword($offset)
  373.     {
  374.         return $this->word($offset + 0) | ($this->word($offset + 2) << 16);
  375.     }
  376.  
  377.     function read_str($offset)
  378.     {
  379.         $pos = strpos($this->stream, "\0", $offset);
  380.         if ($pos !== false)
  381.         {
  382.             return substr($this->stream, $offset, $pos - $offset);
  383.         }
  384.  
  385.         return substr($this->stream, $offset);
  386.     }
  387. }

注意:程序执行后会生成一个用字体名字命名的文件夹,里面包含若干pd和gbf文件,.gbf为可供gd所使用字体。

参考资料:
NE格式:http://www.program-transformation.org/Transform/NeFormat
PE格式:http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspxhttp://msdn.microsoft.com/msdnmag/issues/02/03/pe2/default.aspx
FNT字体文件格式:http://support.microsoft.com/kb/65123
Simon Tatham的主页:http://www.chiark.greenend.org.uk/~sgtatham/fonts/
python版的dewinfont程序:http://www.chiark.greenend.org.uk/~sgtatham/fonts/dewinfont


posted on 2012-02-21 13:04  clq  阅读(2696)  评论(1编辑  收藏  举报