Passed
Pull Request — master (#52)
by Stefan
03:58 queued 02:07
created

FontMeta::findAndExtractCandidate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 12
cts 12
cp 1
rs 9.6333
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 2
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 1
    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 1
        if (!is_readable($fileName)) {
71
            throw new \Exception('File ' . $fileName . ' is not readable');
72
        }
73
74 1
        $this->fileName = $fileName;
75 1
        $this->readFontMetadata();
76 1
    }
77
78
    /**
79
     * @param string $name
80
     * @param array  $args
81
     * @return mixed
82
     */
83 1
    public function __call($name, $args)
84
    {
85 1
        $property = lcfirst(substr($name, 3));
86 1
        return isset($this->data[$property]) ? $this->data[$property] : null;
87
    }
88
89
    /**
90
     * Read the font Metadata
91
     *
92
     * @throws \Exception
93
     */
94 1
    public function readFontMetadata()
95
    {
96 1
        $fontHandle = fopen($this->fileName, "r");
97 1
        $offset = $this->getNameTableOffset($fontHandle);
98
99 1
        fseek($fontHandle, $offset, SEEK_SET);
100
101 1
        $nameTableHeader = fread($fontHandle, 6);
102
103 1
        $numberOfRecords = $this->unpack(substr($nameTableHeader, 2, 2));
104 1
        $storageOffset = $this->unpack(substr($nameTableHeader, 4, 2));
105
106 1
        for ($a = 0; $a < $numberOfRecords; $a++) {
107 1
            $this->findAndExtractCandidate($fontHandle, $offset, $storageOffset);
108
        }
109
110 1
        fclose($fontHandle);
111 1
    }
112
113
    /**
114
     * @param resource $fontHandle
115
     *
116
     * @return int
117
     * @throws \Exception
118
     */
119 1
    private function getNameTableOffset($fontHandle)
120
    {
121 1
        $numberOfTables = $this->unpack(substr(fread($fontHandle, 12), 4, 2));
122
123 1
        for ($t = 0; $t < $numberOfTables; $t++) {
124 1
            $tableDirectory = fread($fontHandle, 16);
125 1
            $szTag = substr($tableDirectory, 0, 4);
126 1
            if (strtolower($szTag) == 'name') {
127 1
                return $this->unpack(substr($tableDirectory, 8, 4), 'N');
128
            }
129
        }
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 1
    private function findAndExtractCandidate($fontHandle, $offset, $storageOffset)
141
    {
142 1
        $nameRecord = fread($fontHandle, 12);
143
144 1
        $nameId = $this->unpack(substr($nameRecord, 6, 2));
145
146 1
        $stringLength = $this->unpack(substr($nameRecord, 8, 2));
147 1
        $stringOffset = $this->unpack(substr($nameRecord, 10, 2));
148
149 1
        if ($stringLength > 0) {
150 1
            $position = ftell($fontHandle);
151 1
            fseek($fontHandle, $offset + $stringOffset + $storageOffset, SEEK_SET);
152
153 1
            $testValue = fread($fontHandle, $stringLength);
154 1
            $this->extractCandidate($nameId, $testValue);
155
156 1
            fseek($fontHandle, $position, SEEK_SET);
157
        }
158 1
    }
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 1
    private function extractCandidate($nameId, $testValue)
168
    {
169 1
        $map = array_keys($this->data);
170 1
        if (strlen(trim($testValue)) && array_key_exists($nameId, $map)) {
171 1
            $this->data[$map[$nameId]] = $this->cleanupValue(trim($testValue));
172
        }
173 1
    }
174
175
    /**
176
     * Remove ASCII ctrl characters
177
     *
178
     * @param string $value
179
     * @return string
180
     */
181 1
    private function cleanupValue($value)
182
    {
183 1
        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 1
    private function unpack($value, $format = 'n')
194
    {
195 1
        $unpacked = unpack($format, $value);
196 1
        return (int)array_pop($unpacked);
197
    }
198
}
199