Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
62 / 62
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
SegmentToDelta
100.00% covered (success)
100.00%
62 / 62
100.00% covered (success)
100.00%
2 / 2
17
100.00% covered (success)
100.00%
1 / 1
 parseData
100.00% covered (success)
100.00%
55 / 55
100.00% covered (success)
100.00%
1 / 1
12
 shiftToSigned
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Pdf\Build\Font\TrueType\Table\Cmap;
16
17/**
18 * CMAP segment-to-delta table class
19 *
20 * @category   Pop
21 * @package    Pop\Pdf
22 * @author     Nick Sagona, III <nick@popphp.org>
23 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    6.0.0
26 */
27class SegmentToDelta
28{
29
30    /**
31     * Method to parse the Segment to Delta (Format 4) CMAP data
32     *
33     * @param  string $data
34     * @return array
35     */
36    public static function parseData(string $data): array
37    {
38        $ary = unpack(
39            'nsegCountx2/' .
40            'nsearchRange/' .
41            'nentrySelector/' .
42            'nrangeShift', substr($data, 0, 8)
43        );
44
45        $ary['segCount'] = $ary['segCountx2'] / 2;
46        $ary['endCount'] = array();
47
48        $bytePos = 8;
49        for ($i = 0; $i < $ary['segCount']; $i++) {
50            $ar = unpack('nendCount', substr($data, $bytePos, 2));
51            $ary['endCount'][$i] = $ar['endCount'];
52            $bytePos += 2;
53        }
54
55        $ar = unpack('nreservedPad', substr($data, $bytePos, 2));
56        $bytePos += 2;
57
58        $ary['reservedPad'] = $ar['reservedPad'];
59
60        $ary['startCount'] = array();
61
62        for ($i = 0; $i < $ary['segCount']; $i++) {
63            $ar = unpack('nstartCount', substr($data, $bytePos, 2));
64            $ary['startCount'][$i] = $ar['startCount'];
65            $bytePos += 2;
66        }
67
68        $ary['idDelta'] = array();
69
70        for ($i = 0; $i < $ary['segCount']; $i++) {
71            $ar = unpack('nidDelta', substr($data, $bytePos, 2));
72            $ary['idDelta'][$i] = self::shiftToSigned($ar['idDelta']);
73            $bytePos += 2;
74        }
75
76        $ary['idRangeOffset'] = array();
77
78        for ($i = 0; $i < $ary['segCount']; $i++) {
79            $ar = unpack('nidRangeOffset', substr($data, $bytePos, 2));
80            $ary['idRangeOffset'][$i] = $ar['idRangeOffset'] >> 1;
81            $bytePos += 2;
82        }
83
84        $ary['glyphIndexArray'] = array();
85
86        for (; $bytePos < strlen($data); $bytePos += 2) {
87            $ar = unpack('nglyphIndex', substr($data, $bytePos, 2));
88            $ary['glyphIndexArray'][] = $ar['glyphIndex'];
89        }
90
91        $ary['glyphNumbers'] = array();
92
93        for ($segmentNum = 0; $segmentNum < $ary['segCount']; $segmentNum++) {
94            if ($ary['idRangeOffset'][$segmentNum] == 0) {
95                $delta = $ary['idDelta'][$segmentNum];
96
97                for ($code = $ary['startCount'][$segmentNum];
98                     $code <= $ary['endCount'][$segmentNum];
99                     $code++) {
100                    $ary['glyphNumbers'][$code] = ($code + $delta) % 65536;
101                }
102            } else {
103                $code       = $ary['startCount'][$segmentNum];
104                $glyphIndex = $ary['idRangeOffset'][$segmentNum] - ($ary['segCount'] - $segmentNum) - 1;
105
106                while ($code <= $ary['endCount'][$segmentNum]) {
107                    if (isset($ary['glyphIndexArray'][$glyphIndex])) {
108                        $ary['glyphNumbers'][$code] = $ary['glyphIndexArray'][$glyphIndex];
109                    }
110
111                    $code++;
112                    $glyphIndex++;
113                }
114            }
115        }
116
117        $ary['mapData'] = str_repeat("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 8192);
118        // Fill the index
119        foreach ($ary['glyphNumbers'] as $charCode => $glyph) {
120            $ary['mapData'][$charCode * 2] = chr($glyph >> 8);
121            $ary['mapData'][$charCode * 2 + 1] = chr($glyph & 0xFF);
122        }
123
124        return $ary;
125    }
126
127    /**
128     * Method to shift an unpacked signed short from big endian to little endian
129     *
130     * @param  int|array $values
131     * @return int|array
132     */
133    public static function shiftToSigned(int|array $values): int|array
134    {
135        if (is_array($values)) {
136            foreach ($values as $key => $value) {
137                if ($value >= pow(2, 15)) {
138                    $values[$key] -= pow(2, 16);
139                }
140            }
141        } else {
142            if ($values >= pow(2, 15)) {
143                $values -= pow(2, 16);
144            }
145        }
146
147        return $values;
148    }
149
150}