Completed
Push — master ( e11ba4...edbaef )
by Stefan
02:48
created

FontMeta::extractCandidate()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 2
crap 3
1
<?php
2
3
namespace OneSheet\Size;
4
5
/**
6
 * Class FontMeta to determine available ttf fonts for cell autosizing.
7
 *
8
 * @method string getCopyright()
9
 * @method string getFontFamily()
10
 * @method string getFontSubFamily()
11
 * @method string getFontIdentifier()
12
 * @method string getFontName()
13
 * @method string getFontVersion()
14
 * @method string getPostscriptName()
15
 * @method string getTrademark()
16
 * @method string getManufacturerName()
17
 * @method string getDesigner()
18
 * @method string getDescription()
19
 * @method string getVendorUrl()
20
 * @method string getDesignerUrl()
21
 * @method string getLicenseDescription()
22
 * @method string getLicenseUrl()
23
 * @method string getReservedField()
24
 * @method string getPreferredFamily()
25
 * @method string getPreferredSubFamily()
26
 * @method string getCompatibleFullName()
27
 * @method string getPostscriptCid()
28
 */
29
class FontMeta
30
{
31
    /**
32
     * @var string
33
     */
34
    private $fileName;
35
36
    /**
37
     * @var array
38
     */
39
    private $data = array(
40
        'copyright' => null,
41
        'fontFamily' => null,
42
        'fontSubFamily' => null,
43
        'fontIdentifier' => null,
44
        'fontName' => null,
45
        'fontVersion' => null,
46
        'postscriptName' => null,
47
        'trademark' => null,
48
        'manufacturerName' => null,
49
        'designer' => null,
50
        'description' => null,
51
        'vendorUrl' => null,
52
        'designerUrl' => null,
53
        'licenseDescription' => null,
54
        'licenseUrl' => null,
55
        'reservedField' => null,
56
        'preferredFamily' => null,
57
        'preferredSubFamily' => null,
58
        'compatibleFullName' => null,
59
        'postscriptCid' => null,
60
    );
61
62
    /**
63
     * FontMeta constructor.
64
     *
65
     * @param string $fileName
66
     * @throws \Exception
67
     */
68 17
    function __construct($fileName)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
69
    {
70 17
        if (!$fileName || !is_readable($fileName)) {
71
            throw new \Exception('File ' . $fileName . ' is not readable');
72
        }
73
74 17
        $this->fileName = $fileName;
75 17
        $this->readFontMetadata();
76 17
    }
77
78
    /**
79
     * @param string $name
80
     * @param array  $args
81
     * @return mixed
82
     */
83 17
    public function __call($name, $args)
84
    {
85 17
        $property = lcfirst(substr($name, 3));
86 17
        return isset($this->data[$property]) ? $this->data[$property] : null;
87
    }
88
89
    /**
90
     * Read the font Metadata
91
     *
92
     * @throws \Exception
93
     */
94 17
    public function readFontMetadata()
95
    {
96 17
        $fontHandle = fopen($this->fileName, "r");
97 17
        $offset = $this->getNameTableOffset($fontHandle);
98
99 17
        fseek($fontHandle, $offset, SEEK_SET);
100
101 17
        $nameTableHeader = fread($fontHandle, 6);
102
103 17
        $numberOfRecords = $this->unpack(substr($nameTableHeader, 2, 2));
104 17
        $storageOffset = $this->unpack(substr($nameTableHeader, 4, 2));
105
106 17
        for ($a = 0; $a < $numberOfRecords; $a++) {
107 17
            $this->findAndExtractCandidate($fontHandle, $offset, $storageOffset);
108 17
        }
109
110 17
        fclose($fontHandle);
111 17
    }
112
113
    /**
114
     * @param resource $fontHandle
115
     *
116
     * @return int
117
     * @throws \Exception
118
     */
119 17
    private function getNameTableOffset($fontHandle)
120
    {
121 17
        $numberOfTables = $this->unpack(substr(fread($fontHandle, 12), 4, 2));
122
123 17
        for ($t = 0; $t < $numberOfTables; $t++) {
124 17
            $tableDirectory = fread($fontHandle, 16);
125 17
            $szTag = substr($tableDirectory, 0, 4);
126 17
            if (strtolower($szTag) == 'name') {
127 17
                return $this->unpack(substr($tableDirectory, 8, 4), 'N');
128
            }
129 17
        }
130
131
        fclose($fontHandle);
132
        throw new \Exception('Can\'t find name table in ' . $this->fileName);
133
    }
134
135
    /**
136
     * @param resource $fontHandle
137
     * @param int      $offset
138
     * @param int      $storageOffset
139
     */
140 17
    private function findAndExtractCandidate($fontHandle, $offset, $storageOffset)
141
    {
142 17
        $nameRecord = fread($fontHandle, 12);
143
144 17
        $nameId = $this->unpack(substr($nameRecord, 6, 2));
145
146 17
        $stringLength = $this->unpack(substr($nameRecord, 8, 2));
147 17
        $stringOffset = $this->unpack(substr($nameRecord, 10, 2));
148
149 17
        if ($stringLength > 0) {
150 17
            $position = ftell($fontHandle);
151 17
            fseek($fontHandle, $offset + $stringOffset + $storageOffset, SEEK_SET);
152
153 17
            $testValue = fread($fontHandle, $stringLength);
154 17
            $this->extractCandidate($nameId, $testValue);
155
156 17
            fseek($fontHandle, $position, SEEK_SET);
157 17
        }
158 17
    }
159
160
    /**
161
     * Extract possible property/attribute. $data keys are
162
     * intentionally sorted in order of the font file table.
163
     *
164
     * @param int    $nameId
165
     * @param string $testValue
166
     */
167 17
    private function extractCandidate($nameId, $testValue)
168
    {
169 17
        $map = array_keys($this->data);
170 17
        if (strlen(trim($testValue)) && array_key_exists($nameId, $map)) {
171 17
            $this->data[$map[$nameId]] = $this->cleanupValue(trim($testValue));
172 17
        }
173 17
    }
174
175
    /**
176
     * Remove ASCII ctrl characters
177
     *
178
     * @param string $value
179
     * @return string
180
     */
181 17
    private function cleanupValue($value)
182
    {
183 17
        return preg_replace('/[[:cntrl:]]/', '', trim($value));
184
    }
185
186
    /**
187
     *  Convert big endian unsigned short[n] or long[N] value to an integer.
188
     *
189
     * @param string $value
190
     * @param string $format
191
     * @return int
192
     */
193 17
    private function unpack($value, $format = 'n')
194
    {
195 17
        $unpacked = unpack($format, $value);
196 17
        return (int)array_pop($unpacked);
197
    }
198
}
199