开发者

GD2 Fonts locking on Windows/Apache

I have a PHP script that uses GD2 to create an image. It uses a TrueTypeFont file to generate text in the image with imagettftext (and imagettfbbox). This script can be run on both Windows and Linux machines so I decided to copy a TTF file from the Windows/Fonts directory into the source-code, else I wouldn't know where to look for it. I'm not at all happy with this solution but I don't know of a better one.

The real problem tho开发者_如何转开发ugh is that on Windows/Apache the font file gets locked after it's been used once. The only way to unlock it is to restart Apache. The locking is a problem because I can't delete the file when I want to, which is especially annoying if you're using a version system.

So my problem has 3 solutions:

  • Is there a way to avoid locking of font files (in source code/webroot) on Windows/Apache?
  • Or is there a way to avoid copying the font file and use a native available TrueTypeFont? (OS independent if at all possible, likely hosts are Windows and Linux - Mac, not so much)
  • Or is there a way to avoid using a TrueTypeFont and still get pretty (aliased) text with 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 


I also faced such an issue with GD but on a Debian distro. I found two solutions that might lead to wamp solutions as well :

a- install the bundle graphic library from dotdeb.org libgd i/o native php GD library and recompile the sources the with-freetype option,

or

b- install imagick instead of GD

I applied solution "a" successfully on a debian distro.

Here are some links I used at that time :
imagick on wamp
howtoforge
drupal
libgd
boutell


My solution is a combination of suggestions. I checked StackOverflow policy on splitting bounties and it's not possible. The bounty must be awarded to a single comprehensive answer. I would like to split my bounty to all the people who replied / commented here, if there is an alternative way please contact me.

I didn't have much luck with the .gdf fonts, I can't really find any good looking fonts. It did point me in the direction of imagestring which can draw text without the need of any font file (gdf or ttf) - see the php doc. The 'default' font is just some monospace font which isn't very pretty but it serves nicely as a fallback.

To avoid the .ttf locking I will attempt to find the OS font folder and load the font from there. This has been tested on my Windows development machine. If the .ttf file can't be found it will use the native font fallback from imagestring.

I've chosen an Object-Oriented approach to make an abstraction on how the text is written to the image.

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);
    }
}

Use like:

$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);


Why not using the library-defined font path?

From the imagettftext() docs, you can use the library font path:

Depending on which version of the GD library PHP is using, when fontfile does not begin with a leading / then .ttf will be appended to the filename and the library will attempt to search for that filename along a library-defined font path.

See the gd_info() page to find out which ttf library your windows php version is using. Then check the according library documentation what the font-path is.

Using TTFs from the fontpath might have the problem.


I do know a workaround that is a bit laborious. A similar solution as I suggested: imagettftext-and-the-euro-sign.

In my case this could work as I'm really just looking for a way to generate some very simple images with text. It averts my problem but of course doesn't fix the underlying issue.


Using GD fonts (.gdf) will solve the issue with the files getting locked. You can move/rename/delete them as you wish even after using them.

They aren't quite as pretty as .ttf fonts, but with a bit of tinkering with gaussian blur you can get them to look almost identical to their ttf counterparts in terms of anti aliasing. Sample text with arial:

GD2 Fonts locking on Windows/Apache

$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);


Firstly, my apologies if this does not work. For i could only test them on WAMP. Where this bug (according to online research) does not affect.

1) Make sure to imagedestroy($im);

2) To get the font folders for either platforms

<?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();
?>

*Note this list of OS is not fully extensive, you may need to add / modify it for your needs.

3) [NOT RECOMMENDED] Persudo-"Cache" the font files : And leave the cache to clear on its own after the first run on server reset. That way, while the fonts gets 'locked' it is only the cache copy that gets locked. Not the actual working files you are 'playing' with. Hence it does not affect your work cycle. The files does get deleted eventually, when the system is rebooted, and they are 'cleared to delete'.

EDIT: Note that you can always point the folder to a tmp folder in a linux setup, should work somewhat the same.

<?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) UPDATED: Just leave all your fonts in one folder, and only do deletes of what is not needed before the actual / final deployment of your server. =) Just leave the font folder alone.

Example shared server structure.

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

Hence for any website / application nested in the root www folder, to access the shared font folder just use

'../fonts/fontName.ttf'

Hence, whenever you make changes and updates to the app / site, you save the trouble of conflicts in fonts.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜