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 Tatham的dewinfont写的提取程序,pe格式的因为没有测试文件,所以没有分析,仅供参考:
- /**
- * 提取.fon文件中的字体点阵数据,并制作gd中imageloadfont函数可用的字体
- *
- * @author: legend(legendsky@hotmail.com)
- * @copyright UGiA.CN
- * @link: http://www.ugia.cn/?p=124
- *
- * usage:
- *
- * <?php
- * include(\'class_fon.php\');
- *
- * $fon = new Fon("consol10.fon");
- *
- * if ($fon == false)
- * {
- * echo $fon->errno . ": " . $fon->errstr;
- * }
- */
- class Fon
- {
- var $file = \'\';
- var $stream = null;
- var $savepath = \'\';
- var $errno = 0;
- var $errstr = \'\';
- function fon($file = \'\', $savepath = \'\')
- {
- if ($file !== \'\')
- {
- $this->parse($file, $savepath);
- }
- }
- function error($errno, $errstr)
- {
- $this->errno = $errno;
- $this->errstr = $errstr;
- }
- function parse($file, $savepath)
- {
- if (!$file)
- {
- $this->error(1001, \'Please assign a file!\');
- return false;
- }
- $this->savepath = $savepath ? str_replace("\\", "/", $savepath) : \'./\';
- $this->savepath .= substr($this->savepath, -1) != \'/\' ? \'/\' : \'\';
- if (is_resource($file))
- {
- $this->stream = $file;
- }
- else
- {
- $this->file = $file;
- $this->stream = file_get_contents($file);
- }
- if (!$this->stream)
- {
- $this->error(1002, \'Can not open file!\');
- return false;
- }
- $fonts = $this->parse_fon();
- if (!$fonts)
- {
- return false;
- }
- if (!is_dir($this->savepath . $fonts[0][\'facename\']))
- {
- @mkdir($this->savepath . $fonts[0][\'facename\'], 0700);
- }
- foreach ($fontsas$k => $font)
- {
- $filename = $font[\'facename\'] . sprintf("_%02d", $k);
- $filename .= "_" . $font[\'width\'] . "x" . $font[\'height\'];
- if ($font[\'italic\'])$filename .= "_italic";
- if ($font[\'underline\'])$filename .= "_underline";
- if ($font[\'strikeout\'])$filename .= "_strikeout";
- $filename .= ".fd";
- $this->print_font($font, $this->savepath . $font[\'facename\'] . "/" . $filename);
- }
- }
- function parse_fon()
- {
- $s = & new Stream($this->stream);
- if (substr($this->stream, 0, 2) != \'MZ\')
- {
- $this->error(2001, \'MZ signature not found!\');
- return false;
- }
- $neoff = $s->dword(0x3c); // 标志位offset
- if (substr($this->stream, $neoff, 2) == \'NE\')
- {
- return $this->parse_ne($neoff);
- }
- else if(substr($this->stream, $neoff, 4) == "PE\0\0")
- {
- return $this->parse_pe($neoff);
- }
- $this->error(2002, \'NE or PE signature not found\');
- return false;
- }
- function parse_ne($neoff)
- {
- $stream = & $this->stream;
- $s = & new Stream($this->stream);
- $ret = array();
- // Find the resource table.
- $rtable = $neoff + $s->word($neoff + 0x24);
- // 32h: A shift count that is used to align the logical sector. This
- // count is log2 of the segment sector size. It is typically 4,
- // although the default count is 9.
- $shift = $s->word($rtable);
- // Now loop over the rest of the resource table.
- $p = $rtable + 2;
- while (1)
- {
- $rtype = $s->word($p);
- // end of resource table
- if ($rtype == 0)
- {
- break;
- }
- $count = $s->word($p + 2);
- // type, count, 4 bytes reserved
- $p += 8;
- for ($i = 0; $i < $count; $i ++)
- {
- $start = $s->word($p) << $shift;
- $size = $s->word($p + 2) << $shift;
- if ($start < 0 || $size < 0 || $start + $size > strlen($this->stream))
- {
- $this->error(2003, \'Resource overruns file boundaries\');
- return false;
- }
- // this is an actual font
- if ($rtype == 0x8008)
- {
- $font = $this->parse_fnt(substr($this->stream, $start, $size));
- //echo "font start at $start, size: $size\n";
- $ret[] = $font;
- }
- // start, size, flags, name/id, 4 bytes reserved
- $p += 12;
- }
- }
- return $ret;
- }
- function print_font($font, $filename)
- {
- $fp = fopen($filename, "w");
- $gdf = fopen(substr($filename, 0, -2) . \'gdf\', "w"); // GD Font
- fwrite($fp, "# .fd font description generated by dewinfont(php).\n\n");
- fwrite($fp, "facename $font[facename]\n");
- fwrite($fp, "copyright $font[copyright]\n\n");
- fwrite($fp, "height $font[height]\n");
- fwrite($fp, "ascent $font[ascent]\n");
- // gd
- fwrite($gdf, "\0\1\0\0");
- fwrite($gdf, "\0\0\0\0");
- fwrite($gdf, chr($font[\'width\']) . "\0\0\0", 4);
- fwrite($gdf, chr($font[\'height\']) . "\0\0\0", 4);
- if ($font[\'height\'] == $font[\'pointsize\'])fwrite($fp, "#");
- fwrite($fp, "pointsize $font[pointsize]\n\n");
- if (!$font[\'italic\'])fwrite($fp, "#");
- fwrite($fp, "italic " . ($font[\'italic\'] ? \'yes\' : \'no\') . "\n");
- if (!$font[\'underline\'])fwrite($fp, "#");
- fwrite($fp, "underline " . ($font[\'underline\'] ? \'yes\' : \'no\') . "\n");
- if (!$font[\'strikeout\'])fwrite($fp, "#");
- fwrite($fp, "strikeout " . ($font[\'strikeout\'] ? \'yes\' : \'no\') . "\n");
- if ($font[\'weight\'] == 400)fwrite($fp, "#");
- fwrite($fp, "weight $font[weight]\n\n");
- if ($font[\'charset\'] == 0)fwrite($fp, "#");
- fwrite($fp, "charset $font[charset]\n\n");
- for ($i = 0; $i < 256; $i ++)
- {
- fwrite($fp, "char $i\nwidth " . $font[\'chars\'][$i][\'width\'] . "\n");
- if ($font[\'chars\'][$i][\'width\'] != 0)
- {
- for ($j = 0; $j < $font[\'height\']; $j ++)
- {
- $v = $font[\'chars\'][$i][\'data\'][$j];
- $m = 1 << ($font[\'chars\'][$i][\'width\'] - 1);
- for ($k = 0; $k < $font[\'chars\'][$i][\'width\']; $k ++)
- {
- if ($v & $m)
- {
- fwrite($fp, "M");
- fwrite($gdf, in_array($i, array(7, 8, 9, 10, 13, 26)) ? "\0" : "\1");
- }
- else
- {
- fwrite($fp, ".");
- fwrite($gdf, "\0");
- }
- $v = $v << 1;
- }
- fwrite($fp, "\n");
- }
- fwrite($fp, "\n");
- }
- }
- //fwrite($gdf, "(C)2007 UGiA.CN");
- fclose($gdf);
- fclose($fp);
- }
- function parse_fnt($stream)
- {
- $s = & new Stream($stream);
- $font = array();
- $font[\'version\'] = $s->word(0);
- $font[\'copyright\'] = substr($stream, 6, 60);
- $ftype = $s->word(0x42);
- if ($ftype & 1)
- {
- // This font is a vector font
- return;
- }
- // face name offset
- $off_facename = $s->dword(0x69);
- if ($off_facename < 0 || $off_facename > strlen($stream))
- {
- // Face name not contained within font data
- return;
- }
- $font[\'facename\'] = $s->read_str($off_facename);
- $font[\'pointsize\'] = $s->word(0x44);
- $font[\'ascent\'] = $s->word(0x4a);
- $font[\'width\'] = 0; // max width
- $font[\'height\'] = $s->word(0x58);
- $font[\'italic\'] = $s->byte(0x50);
- $font[\'underline\'] = $s->byte(0x51);
- $font[\'strikeout\'] = $s->byte(0x52);
- $font[\'weight\'] = $s->word(0x53);
- $font[\'charset\'] = $s->byte(0x55);
- // Read the char table.
- if ($font[\'version\'] == 0x200)
- {
- $ctstart = 0x76;
- $ctsize = 4;
- }
- else
- {
- $ctstart = 0x94;
- $ctsize = 6;
- }
- $maxwidth = 0;
- $font[\'chars\'] = array();
- for ($i = 0; $i < 256; $i ++)
- {
- $font[\'chars\'][$i][\'data\'] = array_fill(0, $font[\'height\'], 0);
- }
- $firstchar = $s->byte(0x5f);
- $lastchar = $s->byte(0x60);
- #print "$firstchar,$lastchar ";
- for ($i = $firstchar; $i <= $lastchar; $i ++)
- {
- $entry = $ctstart + $ctsize * ($i - $firstchar);
- $w = $s->word($entry);
- $font[\'chars\'][$i][\'width\'] = $w;
- $font[\'width\'] = $w > $font[\'width\'] ? $w : $font[\'width\'];
- if ($ctsize == 4)
- {
- $off = $s->word($entry + 2);
- }
- else
- {
- $off = $s->dword($entry + 2);
- }
- $widthbytes = floor(($w + 7) / 8);
- //echo $widthbytes . " ";
- for ($j = 0; $j < $font[\'height\']; $j ++)
- {
- for ($k = 0; $k < $widthbytes; $k ++)
- {
- $bytepos = $off + $k * $font[\'height\'] + $j;
- $font[\'chars\'][$i][\'data\'][$j] = $font[\'chars\'][$i][\'data\'][$j] << 8;
- $font[\'chars\'][$i][\'data\'][$j] = $font[\'chars\'][$i][\'data\'][$j] | $s->byte($bytepos);
- }
- $font[\'chars\'][$i][\'data\'][$j] = $font[\'chars\'][$i][\'data\'][$j] >> (8 * $widthbytes - $w);
- //echo $font[\'chars\'][$i][\'data\'][$j] . " ";
- }
- }
- //print_r($font);
- return $font;
- }
- }
- class Stream
- {
- var $stream = \'\';
- function stream($stream)
- {
- $this->stream = $stream;
- }
- function byte($offset)
- {
- return ord($this->stream{$offset});
- }
- function word($offset)
- {
- return $this->byte($offset + 0) + 256 * $this->byte($offset + 1);
- }
- function dword($offset)
- {
- return $this->word($offset + 0) | ($this->word($offset + 2) << 16);
- }
- function read_str($offset)
- {
- $pos = strpos($this->stream, "\0", $offset);
- if ($pos !== false)
- {
- return substr($this->stream, $offset, $pos - $offset);
- }
- return substr($this->stream, $offset);
- }
- }
注意:程序执行后会生成一个用字体名字命名的文件夹,里面包含若干pd和gbf文件,.gbf为可供gd所使用字体。
参考资料:
NE格式:http://www.program-transformation.org/Transform/NeFormat
PE格式:http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx 及 http://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