Windows/Apache下的GD2字体锁定问题

4
我有一个使用GD2创建图像的PHP脚本。它使用TrueTypeFont文件通过imagettftext(和imagettfbbox)在图像中生成文本。 这个脚本可以在Windows和Linux机器上运行,因此我决定将TTF文件从Windows / Fonts目录复制到源代码中,否则我就不知道该去哪里查找。 我对这个解决方案并不满意,但我不知道有更好的解决方案。
然而,真正的问题是,在Windows / Apache上,字体文件在使用一次后会被锁定。解锁的唯一方法是重新启动Apache。锁定是一个问题,因为我无法在需要时删除该文件,尤其是如果您正在使用版本系统时更加烦人。
所以我的问题有3个解决方案:
  • 有没有办法避免在Windows / Apache上锁定字体文件(在源代码/ Webroot中)?
  • 或者有没有办法避免复制字体文件并使用本地可用的TrueTypeFont?(如果可能的话与操作系统无关,可能的主机是Windows和Linux-Mac不太可能)
  • 或者有没有办法避免使用TrueTypeFont并仍然获得漂亮(平滑)的PHP GD2文本?
GD Support  enabled
GD Version  bundled (2.0.34 compatible)
FreeType Support    enabled
FreeType Linkage    with freetype
FreeType Version    2.1.9
T1Lib Support   enabled
GIF Read Support    enabled
GIF Create Support  enabled
JPG Support     enabled
PNG Support     enabled
WBMP Support    enabled
XBM Support     enabled 

1
我几年前遇到了同样的问题,但不记得如何解决。我今晚会查看我的存档硬盘,看看能否找到解决方案的代码,但我不记得它是仅仅绕过锁定还是一个真正的解决方案来防止它。 - Niklas
2
@Frits van Campen,恐怕我要让你失望了。在我的上一个项目中没有解决方案,相反,我最终创建了一个副本放在操作系统的tmp文件夹中,并将字体文件的内容复制到其中。当时我已经做了很多研究,除非有什么改变,否则问题就是FreeType库打开了一个字体处理程序,但不会再关闭它。这里有一个错误链接http://bugs.php.net/bug.php?id=24450。如果您不想使用我选择的临时文件解决方案,我建议您尝试其他两个选项。 - Niklas
谢谢您的评论。至少它让我知道有其他人花时间在这上面,也找不到解决方案(“没有解决方案”也是我的问题的答案之一)。我将研究临时文件解决方法。如果有人对另外两个选项有任何想法,请随意分享!我很乐意授予此赏金。 - Halcyon
只是好奇,你是否使用有维护(重启)计划的服务器?也许可以找到类似的解决方法。 - PicoCreator
1
嗯,是的,计划从未重新启动 :D - Halcyon
显示剩余4条评论
6个回答

1

为什么不使用库定义的字体路径?

imagettftext()文档中可以看到,您可以使用库定义的字体路径:

根据PHP使用的GD库的版本,当fontfile没有以斜杠开头时,.ttf将被附加到文件名,并且库将尝试沿着库定义的字体路径搜索该文件名。

请参见{{link2:gd_info()页面}},查找您的Windows PHP版本使用的ttf库。然后检查相应的库文档以获取字体路径。

从字体路径中使用TTF可能会出现问题。


谢谢您的回复。这只是一种非常模糊的包含字体文件的方式。是否有特定于操作系统的默认设置?是否有一种检查存在哪些字体文件的方法?我希望这个脚本能够始终正常工作,即使在我无法控制环境的主机上也是如此。当然,目前它已经可以正常工作了。 - Halcyon
好的,我并不想听起来无礼。请查阅你使用的 GD2 图像处理库的手册来了解更多关于字体路径的信息。 - hakre
这是我从手册中得到的内容,但一点也没有帮助我。我不想使用花哨的方式来包含我的字体文件,我想要防止字体文件被锁定。如果您知道一种方法,请提供一个代码示例。我愿意为此付出高额奖金。对我来说,这是一个微不足道的功能,所以我没有时间在任何环境的排列组合上检查解决方案,但它对我来说足够重要,以奖励相当可观的赏金。我不是在请求某人解决问题,而是想要有这个问题并解决过它的人的经验。 - Halcyon
你是指TTF文件吗?我想我正在使用从TCPDF“借来”的TTF文件,即arial.ttf。要么我从Windows/Fonts中取出了该文件。请随意责备我。我完全不知道。 - Halcyon
不,我的意思是您正在使用的GD2 PHP模块所使用的TTF软件库。请参见我的答案中的“gd_info()”链接。 - hakre
显示剩余9条评论

1

我也曾在Debian发行版上遇到过GD的问题。我找到了两个解决方案,可能也适用于wamp:

a- 从dotdeb.org安装捆绑图形库libgd i/o本地php GD库,并使用with-freetype选项重新编译源代码。

或者

b- 安装imagick代替GD。

我成功地应用了“a”解决方案在Debian发行版上。

这是我当时使用的一些链接:
在WAMP上使用Imagick
HowtoForge
Drupal
Libgd
Boutell


感谢您的回答。很遗憾,重新编译PHP对我来说不是一个选择,而且在我的环境中已经启用了_with-freetype_,所以我怀疑它是否能解决我的特定问题。ImageMagick 对我来说也不是一个选择,因为大多数主机没有启用它。我正在寻找一个非常通用的解决方案,来解决我认为是一个小错误的问题。 - Halcyon
你确认字体锁定问题也出现在 Debian 机器上了吗? - Halcyon
1
@Frits van Campen:我之前遇到过这个问题。我不记得是因为GD库本身还是字体锁定的原因导致使用这些GD函数出现了错误。但我记得,除了重新编译捆绑的GD库之外,我还不得不导入额外的TTF字体。祝你好运,因为我也记得这个问题给我带来了很大的头痛。 - hornetbzz

1

我的解决方案是建立在多个建议的基础上。我查看了StackOverflow有关分割赏金的政策,发现这是不可能的。赏金必须授予一个全面的答案。如果有其他方法可以将我的赏金分给所有在此回复/评论的人,请与我联系。

我没有太多运气找到好看的.gdf字体。它指引我使用imagestring绘制文本而无需任何字体文件(gdf或ttf)-请参阅php文档。'默认'字体只是一些等宽字体,不太漂亮,但作为备选项很好用。

为避免.ttf锁定,我将尝试查找操作系统字体文件夹并从那里加载字体。这已在我的Windows开发机上进行了测试。如果找不到.ttf文件,则会使用imagestring的本地字体备选项。

我选择了面向对象的方法来抽象化文本如何写入图像。

abstract class TextWriter {

    public static function getInstance() {
        $osName = php_uname( 's' );

        if (strtoupper(substr($osName, 0, 3)) === 'WIN') {
            $path = 'C:/Windows/Fonts/';
        } else if (strtoupper(substr($osName, 0, 5)) === 'LINUX') {
            $path = '/usr/share/fonts/truetype/';
        } else if (strtoupper(substr($osName, 0, 7)) === 'FREEBSD') {
            $path = '/usr/local/lib/X11/fonts/TrueType';
        }
        if (is_dir($path) && is_file($path . "/arial.ttf")) {
            return new TTFTextWriter($path . "/arial.ttf");
        }
        return new NativeTextWriter();
    }

    abstract public function get_dimenions($string);
    abstract public function write_text($img_resource, $x, $y, $text, $color);

}

class TTFTextWriter extends TextWriter {

    private $ttf_file;
    private $fontsize = 10;

    public function __construct($ttf_file) {
        $this->ttf_file = $ttf_file;
    }

    public function get_dimenions($text) {
        $dimenions = imagettfbbox($this->fontsize, 0, $this->ttf_file, $text);
        return array($dimenions[2], abs($dimenions[5] - $dimenions[3]));
    }

    public function write_text($img_resource, $x, $y, $text, $color) {
        imagettftext($img_resource, $this->fontsize, 0, $x, $y, $color, $this->ttf_file, $text);
    }

}

class NativeTextWriter extends TextWriter {

    private $fontsize = 3;  // number between 1-5 see manual for imagestring
    private $text_width = 7;  // corresponds to $fontsize 3
    private $text_height = 15;  // corresponds to $fontsize 3

    public function get_dimenions($text) {
        return array(strlen($text) * $this->text_width, $this->text_height);
    }

    public function write_text($img_resource, $x, $y, $text, $color) {
        imagestring($img_resource, $this->fontsize, $x, $y - $this->text_height, $text, $color);
    }
}

使用方法:

$writer = TextWriter::getInstance();

$dimenions = $writer->get_dimenions($text);
$width = $dimenions[0];
$height = $dimenions[1];
$im = imagecreatetruecolor($width, $height);
$black = imagecolorallocate($im, 1, 1, 1);

$writer->write_text($im, 0, 0, $text, $black);
header('Content-Type: image/gif');

imagegif($im);
imagedestroy($im);

没关系:只要赏金不浪费,被分成一半并给予最高票数,输了250比零... :p 安慰是超越所有答案哈哈。也许可以建议接受多个答案并分割赏金。毕竟,在编程中,可能有多种方法来完成指定的工作,而这两种方法并没有优劣之分 :p - PicoCreator

0

我知道一个有点费力的解决方法。与我建议的类似的解决方案:imagettftext-and-the-euro-sign

在我的情况下,这可能有效,因为我只是想找到一种生成带有文本的非常简单的图像的方法。它避免了我的问题,但当然没有解决根本问题。


0

使用GD字体(.gdf)可以解决文件被锁定的问题。即使在使用它们之后,您也可以随意移动/重命名/删除它们。

它们不像.ttf字体那样漂亮,但是通过对高斯模糊进行一些调整,您可以使它们在抗锯齿方面几乎与ttf字体相同。使用Arial的示例文本:

enter image description here

$im = imagecreatetruecolor(400, 200);

$bg = imagecolorallocate($im, 255, 255, 255);

imagefill ( $im , 0 , 0 ,$bg );
$textcolor = imagecolorallocate($im, 0, 0, 0);
imageantialias ( $im ,true );

$font = imageloadfont('arial-reg-20.gdf');

imagestring($im, $font, 10, 10, 'Hello world!', $textcolor);

imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR,10);
imagestring($im, $font, 10, 10, 'Hello world!', $textcolor);
imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR,1);
// Output the image
header('Content-type: image/png');

imagepng($im);
imagedestroy($im);

我该如何查找或创建一个.gdf文件?这并不像我希望的那样简单 :( - Halcyon
已经有很多字体可供使用了,例如搜索.gdf字体或arial.gdf将会给你带来很多结果。同样地,也有很多不同的工具可以将ttf文件转换为gdf。 - Niklas
是的,但对于.gdf文件可能存在大端和小端编码问题。我不知道这将在哪种机器上运行。 - Halcyon
1
@Frits van Campen,您在问题中提到只需要Windows/Linux(不需要Mac),因此认为这不是问题,因为它们都是小端。 - Niklas
我明白了,那会让它变得容易很多 :) 我会在周一尝试这个。 - Halcyon

0

首先,如果这个方法不起作用,我很抱歉。因为我只能在WAMP上测试它们。根据在线研究,这个错误不会影响WAMP。

1) 确保使用imagedestroy($im);

2) 获取任一平台的字体文件夹

<?php
function fontFolder() {
    $osName = php_uname( 's' );

    if (strtoupper(substr($osName, 0, 3)) === 'WIN') {
        return '/Windows/Fonts/';
    }

    if (strtoupper(substr($osName, 0, 5)) === 'LINUX') {
        return '/usr/share/fonts/truetype/';
    }

    if (strtoupper(substr($osName, 0, 7)) === 'FREEBSD') {
        //This is not tested
        return '/usr/share/fonts/truetype/';
    }
}

echo fontFolder();
?>

*注意,此操作系统列表并不完整,您可能需要根据自己的需求添加/修改。

3)[不建议使用] 模拟“缓存”字体文件:在服务器重置后,让缓存自行清除。这样,虽然字体被“锁定”,但只有缓存副本被锁定,而不是您正在“玩耍”的实际工作文件。因此,它不会影响您的工作周期。当系统重新启动时,文件最终会被删除,并且它们已经“准备好删除”。

编辑:请注意,在Linux设置中,您始终可以将文件夹指向tmp文件夹,应该可以起到类似的作用。

<?php
/**
Recursive delete, with a 'file/folder igonore option'
[$ignoreArra] : An array or a single string, to ignore delete (folder or file)
[$ignoreRootFolder] : Ignores the starting root folder
**/
function recursiveDelete($str, $ignoreArray = null, $ignoreRootFolder = false){
    if($str == '' || $str == '/')   {   //Prevent accidental 'worse case scenerios'
        return false;   
    }

    //Ensures it working as an array
    if($ignoreArray == null) {
        $ignoreArray = array(); //new Array
    }
    if(!is_array( $ignoreArray ) ) {
        $ignoreArray = array( $ignoreArray );
    }

    if(is_file($str)){
        if(in_array( $str, $ignoreArray ) ) {
            return false;
        } //else
        return @unlink($str);
    }
    elseif(is_dir($str)){
        $scan = glob(rtrim($str,'/').'/*');
        $chk = true;
        foreach($scan as $index=>$path) {
            $buf = recursiveDelete($path, $ignoreArray);
            if( $buf == false ) {
                $chk = false;
            }
        }

        if( in_array( $str, $ignoreArray ) || $chk == false || $ignoreRootFolder ) {
            return false;
        } else {
            return @rmdir($str);
        }
    }   else    {
        return false;
    }
}

define('fontCacheFolder', './font_cache/');
function fontCache($fontFolder, $fontFile) {
    $cachedFile = fontCacheFolder.$fontFile;
    recursiveDelete( fontCacheFolder , $cachedFile , true);
    if( is_file( $cachedFile ) ) {
        return $cachedFile;
    }
    copy( $fontFolder.$fontFile, $cachedFile);
    return $cachedFile;
}

echo fontCache('./', 'arial.ttf');
?>

4)已更新:将所有字体放在一个文件夹中,只在服务器实际/最终部署之前删除不需要的字体。=)只需让字体文件夹保持原样。

示例共享服务器结构。

www/root
   +----- fonts
   +----- app A
   +----- site B

因此,对于嵌套在根www文件夹中的任何网站/应用程序,要访问共享字体文件夹只需使用
'../fonts/fontName.ttf'

因此,每当您对应用程序/网站进行更改和更新时,都可以避免字体冲突的麻烦。

我已经检查过了,但这不应该有影响,因为PHP在关闭时会清理所有资源。 - Halcyon
@Frits van Campen,3)我指的是伪缓存。它在代码中=P至于4)-.-"那你为什么还要删除/等等...你可以把它留着不动。 - PicoCreator
@Frits van Campen,我投票支持选项2),除非您使用自定义字体。= X - PicoCreator
如果是这种情况,只需在不需要清理的驱动器上创建一个专用字体文件夹即可。因为这似乎是仅限于APACHE的问题。根据您的解释,我猜第3点基本上是无用的。您始终可以在服务器上拥有一个中央字体文件夹,供所有应用程序/等使用。如果您的服务器共享于不同的应用程序/站点之间,您可以在其父文件夹中拥有一个共享字体文件夹 =) - PicoCreator
1
@Frits van Campen:建立一个集中的字体文件夹的目的是为了永远不需要清理它。有关更多详细信息,请参阅第4点的编辑... - PicoCreator
显示剩余7条评论

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