Completed
Push — rendered-cell-sizes ( 8ba9d2...8dc155 )
by Stefan
05:59 queued 03:02
created

FontMeta::extractCandidate()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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