.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 ($fonts as $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

原创文章,转载请注明出处。

23 Comments »

  1. Mc@Spring said,

    May 3, 2007 @ 6:20 pm

    佩服一个,高手。。。

  2. macoo said,

    May 4, 2007 @ 9:22 pm

    确实很厉害呀!这个是不是可以做字体用了?像徐静蕾那个字体能用这个程序做么?好奇哈!

  3. macoo said,

    May 4, 2007 @ 9:25 pm

    提取.fon文件中的字体点阵数据,并制作gd中imageloadfont函数可用的字体

  4. legend said,

    May 4, 2007 @ 10:55 pm

    这个是提取旧的windows字体文件格式.fon中的点阵,不能提取ttf的。

  5. zm said,

    May 15, 2007 @ 10:40 pm

    legend:我有一个有关BMP图片的问提,或者说项目要请教.你有时间联系我吗,13802766022.谢谢

  6. legend said,

    May 15, 2007 @ 10:58 pm

    用qq或者msn联系吧。

    msn见网站侧栏,qq为34145068

  7. 中文研究者 said,

    June 4, 2007 @ 5:07 pm

    這是我常去看格式的兩個站點,很多格式那邊都有現成的研究,不用搞反向工程之類的。提供給你參考。

    http://filext.com/detaillist.php?extdetail=OLV

    http://www.wotsit.org/

  8. legend said,

    June 4, 2007 @ 9:45 pm

    谢谢!

  9. zhaiduo said,

    July 12, 2007 @ 10:48 pm

    不错,我也弄来玩玩

  10. 电脑精品教程网 said,

    July 26, 2007 @ 8:34 pm

    虽然这个不是我的专业,不过也支持一个…

  11. evokemora said,

    December 24, 2007 @ 6:35 am

    yoooo!!! new [url=http://onlinefreeporn.net]free porn[/url] coming!
    So here we start:
    >>>>Some lesbo girls

  12. sergiomann said,

    January 5, 2008 @ 12:45 pm

    [url=http://phentermine1.kostenlose-foren.org]buy phentermine[/url]
    [url=http://tramadol1.kostenlose-foren.org]buy tramadol[/url]
    [url=http://viagra1.kostenlose-foren.org]buy viagra[/url]

  13. liaoliao said,

    April 24, 2009 @ 1:22 pm

    支持!

  14. Drieheak said,

    March 12, 2013 @ 9:37 pm

    best for you NQetuXKL [URL=http://www.chanel--replica.tumblr.com/]fake chanel[/URL] to take huge discount FEWgYdeB [URL=http://www.chanel--replica.tumblr.com/ ] http://www.chanel–replica.tumblr.com/ [/URL]

  15. misrrtcmn said,

    April 10, 2013 @ 12:05 pm

    The Last analysis Luxuriousness: Louis Vuitton Outlet?Produce of Hermes Bag on Women Fit an Updated Hermes Suitcase at a Reduced Consequence How to acquire an Authentic [url=http://hermesoutlet-store.webs.com/]Hermes Outlet Online[/url] over and beyond a Hermes Leather Concern Suitcase of Miscellaneous Types for Professionals Provoke It at Leather Dreams The various types of artist handbags and bags Individual Types of [url=http://prada--outlets.webs.com/]Prada Outlet[/url] Manipulate an Idea of the Different Types of a Conniver Chane Bags Designer Handbags 30-60% cancelled Handbags & Accessories Beat Calibre, Authentic, Chanel OutletLuxuriousness, wallets.

    http://www.romanka.com/bbs/uchome/space.php?uid=61882&do=blog&id=4560

    http://www.venturepad.com/

    http://www.sarasotainjurylawyersblog.com/2013/03/rear-end-collsions-in-sarasota.html#comments

  16. pinterest said,

    September 15, 2014 @ 5:56 pm

    It ranks up there with the likes of getting to meet Johnny Depp, the best
    dessert you ever ate, and your husband doing all laundry and cooking for an entire month.
    This tool (once called Pin – Clout) is very similar to Facebook Insights.
    It does mean figuring out where your target audience is engaging (social networking sites) and developing a strategy for how you’re going to reach them.

  17. cartier anelli uomo said,

    July 9, 2015 @ 11:28 am

    Accade che urla dopo che sua anelli di cartier moglie, ma non lasciarlo non invadere con la sua violen cartier anelli prezzi za. Quando molto arrabbiato dopo la signorina, preferisce uscire e languire la nostra rabbia dissipa. Amiti: Ambrogio presenta come un amico fedele, ma anelli di cartier da cartier anelli uomo oro l tipo mfiante.Tessitura tramite il nostro g

  18. bulgari b01 said,

    July 10, 2015 @ 1:56 pm

    C’era troppo sanguinamento, troppo dolore. La t Bulgari anelli esta male. Era stordito. Qualc anello Bulgari ceramica he tempo alla fine del 2001, la Bulgariiti di un gatto ghignante chiamato M. Chat, altro sorriso di corpo, ha cominciato a comparire per le strade di Parigi, e la diffusion anello b zero Bulgari e rapidamente notorietà, ha cominciato ad essere ut

  19. anelli diamanti cartier said,

    August 5, 2015 @ 1:35 pm

    Questa bellezza e di più senza pretese, anello cartier prezzo il rumore simpatia capitali e professionalità rumore sono sotto uno dei giornali cartier prezzi anelli sti organizzatori molto di moda. I esercizio solo la mia mtier grazia passione ds diciotto, vero anello. C ^ ot femminile, Anne Sophie Lap cartier anello di fidanzamento oro rosa ix non dovrebbe mio itriser ^ sull’argomen

  20. Fake Rolex rolex Watches said,

    December 19, 2015 @ 5:37 pm

    http://www.thrillofthechaise.com/ Replica Watches Online Store,Sale Swiss Replica Watches Cheap,More Low Price For You.
    Fake Rolex rolex Watches

  21. bridesmaid bobbleheads said,

    January 10, 2016 @ 11:07 am

    http://www.prakashpatelmd.com/ Personalized Bobbleheads Doll Online Store
    bridesmaid bobbleheads

  22. iPhone クリアケース said,

    March 21, 2017 @ 12:58 am

    http://www.cresa.cat/blogs/svbs/9l8h0noempo.htmlマリメッコ iphoneケース

  23. MargaretteX said,

    June 23, 2017 @ 11:05 am

    I must say you have high quality content here. Your content should go viral.

    You need initial traffic only. How to get massive traffic?
    Search for; Murgrabia’s tools go viral

RSS feed for comments on this post

Leave a Comment