Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.44% covered (success)
94.44%
34 / 36
90.91% covered (success)
90.91%
10 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Font
94.44% covered (success)
94.44%
34 / 36
90.91% covered (success)
90.91%
10 / 11
19.06
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 standardFonts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFont
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
 getFont
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isStandard
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isEmbedded
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStandardFonts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getParsedFont
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getStringWidth
85.71% covered (success)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
5.07
 parser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Pdf\Document;
15
16use Pop\Pdf\Build\Font\AbstractFont;
17use Pop\Pdf\Build\Font\Parser;
18use InvalidArgumentException;
19
20/**
21 * Pdf font class
22 *
23 * @category   Pop
24 * @package    Pop\Pdf
25 * @author     Nick Sagona, III <dev@nolainteractive.com>
26 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
27 * @license    http://www.popphp.org/license     New BSD License
28 * @version    5.0.0
29 */
30class Font
31{
32
33    /**
34     * Standard font constants
35     */
36    const ARIAL                      = 'Arial';
37    const ARIAL_ITALIC               = 'Arial,Italic';
38    const ARIAL_BOLD                 = 'Arial,Bold';
39    const ARIAL_BOLD_ITALIC          = 'Arial,BoldItalic';
40    const COURIER                    = 'Courier';
41    const COURIER_OBLIQUE            = 'Courier-Oblique';
42    const COURIER_BOLD               = 'Courier-Bold';
43    const COURIER_BOLD_OBLIQUE       = 'Courier-BoldOblique';
44    const COURIER_NEW                = 'CourierNew';
45    const COURIER_NEW_ITALIC         = 'CourierNew,Italic';
46    const COURIER_NEW_BOLD           = 'CourierNew,Bold';
47    const COURIER_NEW_BOLD_ITALIC    = 'CourierNew,BoldItalic';
48    const HELVETICA                  = 'Helvetica';
49    const HELVETICA_OBLIQUE          = 'Helvetica-Oblique';
50    const HELVETICA_BOLD             = 'Helvetica-Bold';
51    const HELVETICA_BOLD_OBLIQUE     = 'Helvetica-BoldOblique';
52    const SYMBOL                     = 'Symbol';
53    const TIMES_ROMAN                = 'Times-Roman';
54    const TIMES_BOLD                 = 'Times-Bold';
55    const TIMES_ITALIC               = 'Times-Italic';
56    const TIMES_BOLD_ITALIC          = 'Times-BoldItalic';
57    const TIMES_NEW_ROMAN            = 'TimesNewRoman';
58    const TIMES_NEW_ROMAN_ITALIC     = 'TimesNewRoman,Italic';
59    const TIMES_NEW_ROMAN_BOLD       = 'TimesNewRoman,Bold';
60    const TIMES_NEW_ROMAN_BOLDITALIC = 'TimesNewRoman,BoldItalic';
61    const ZAPF_DINGBATS              = 'ZapfDingbats';
62
63    /**
64     * Standard PDF fonts
65     * @var array
66     */
67    protected array $standardFonts = [
68        'Arial', 'Arial,Italic', 'Arial,Bold', 'Arial,BoldItalic', 'Courier', 'CourierNew', 'Courier-Oblique',
69        'CourierNew,Italic', 'Courier-Bold', 'CourierNew,Bold', 'Courier-BoldOblique', 'CourierNew,BoldItalic',
70        'Helvetica', 'Helvetica-Oblique', 'Helvetica-Bold', 'Helvetica-BoldOblique', 'Symbol', 'Times-Roman',
71        'Times-Bold', 'Times-Italic', 'Times-BoldItalic', 'TimesNewRoman', 'TimesNewRoman,Italic',
72        'TimesNewRoman,Bold', 'TimesNewRoman,BoldItalic', 'ZapfDingbats'
73    ];
74
75    /**
76     * Font
77     * @var ?string
78     */
79    protected ?string $font = null;
80
81    /**
82     * Font name
83     * @var ?string
84     */
85    protected ?string $name = null;
86
87    /**
88     * Flag for a standard font
89     * @var bool
90     */
91    protected bool $isStandard = false;
92
93    /**
94     * Flag for an embedded font file
95     * @var bool
96     */
97    protected bool$isEmbedded = false;
98
99    /**
100     * Font parser
101     * @var ?Parser
102     */
103    protected ?Parser $parser = null;
104
105    /**
106     * Constructor
107     *
108     * Instantiate a PDF font.
109     *
110     * @param ?string $font
111     */
112    public function __construct(?string $font = null)
113    {
114        if ($font !== null) {
115            $this->setFont($font);
116        }
117    }
118
119    /**
120     * Get standard PDF fonts in an array
121     *
122     * @return array
123     */
124    public static function standardFonts(): array
125    {
126        return (new self())->getStandardFonts();
127    }
128
129    /**
130     * Set font
131     *
132     * @param  string $font
133     * @throws InvalidArgumentException|\Pop\Pdf\Build\Font\Exception
134     * @return Font
135     */
136    public function setFont(string $font): Font
137    {
138        $this->font = $font;
139        if (in_array($font, $this->standardFonts)) {
140            $this->isStandard = true;
141            $this->name       = $font;
142        } else if (file_exists($font)) {
143            $this->isEmbedded = true;
144            $this->parser     = new Parser($this->font);
145            $this->name       = $this->parser->getFontName();
146        } else {
147            throw new InvalidArgumentException(
148                "Error: The font '" . $font . "' is not valid. It must be a standard PDF font or a font file."
149            );
150        }
151
152        return $this;
153    }
154
155    /**
156     * Get font
157     *
158     * @return ?string
159     */
160    public function getFont(): ?string
161    {
162        return $this->font;
163    }
164
165    /**
166     * Get font name
167     *
168     * @return ?string
169     */
170    public function getName(): ?string
171    {
172        return $this->name;
173    }
174
175    /**
176     * Determine if the font is a standard font
177     *
178     * @return bool
179     */
180    public function isStandard(): bool
181    {
182        return $this->isStandard;
183    }
184
185    /**
186     * Determine if the font is an embedded font
187     *
188     * @return bool
189     */
190    public function isEmbedded(): bool
191    {
192        return $this->isEmbedded;
193    }
194
195    /**
196     * Get available standard fonts
197     *
198     * @return array
199     */
200    public function getStandardFonts(): array
201    {
202        return $this->standardFonts;
203    }
204
205    /**
206     * Get the font parser
207     *
208     * @return ?AbstractFont
209     */
210    public function getParsedFont(): ?AbstractFont
211    {
212        return ($this->parser !== null) ? $this->parser->getFont() : null;
213    }
214
215    /**
216     * Attempt to get string width
217     *
218     * @param  string $string
219     * @param  mixed  $size
220     * @throws Exception
221     * @return mixed
222     */
223    public function getStringWidth(string $string, string $size): mixed
224    {
225        if ($this->parser !== null) {
226            return $this->parser->getFont()->getStringWidth($string, $size);
227        } else {
228            $fontClass = '\Pop\Pdf\Build\Font\Standard\\' . str_replace([',', '-'], ['', ''], $this->name);
229            if (!class_exists($fontClass)) {
230                throw new Exception('Error: That standard font class was not found.');
231            }
232            $font   = new $fontClass();
233            $widths = [];
234
235            $drawingString = iconv('UTF-8', 'UTF-16BE//IGNORE', $string);
236            $characters    = [];
237
238            for ($i = 0; $i < strlen($drawingString); $i++) {
239                $characters[] = (ord($drawingString[$i++]) << 8 ) | ord($drawingString[$i]);
240            }
241
242            foreach ($characters as $character) {
243                $widths[] = $font->getGlyphWidth($character);
244            }
245
246            return (array_sum($widths) / $font->getUnitsPerEm()) * $size;
247        }
248    }
249
250    /**
251     * Get the font parser
252     *
253     * @return ?Parser
254     */
255    public function parser(): ?Parser
256    {
257        return $this->parser;
258    }
259
260}