Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
130 / 130
100.00% covered (success)
100.00%
1 / 1
CRAP
100.00% covered (success)
100.00%
1 / 1
MathConstantsParser
100.00% covered (success)
100.00%
130 / 130
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
1 / 1
 parse
100.00% covered (success)
100.00%
130 / 130
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\FontParser;
6
7/**
8 * Parser for the MathConstants sub-table of an OpenType MATH table.
9 *
10 * Layout per https://learn.microsoft.com/en-us/typography/opentype/spec/math#mathconstants-table :
11 *
12 *   - 4 plain Int16 fields up front (ScriptPercentScaleDown,
13 *     ScriptScriptPercentScaleDown, DelimitedSubFormulaMinHeight,
14 *     DisplayOperatorMinHeight).
15 *   - 51 MathValueRecord fields (each = Int16 FWord + uint16 Device
16 *     table offset). The painter only needs the FWord half.
17 *   - 1 trailing Int16 percent (RadicalDegreeBottomRaisePercent).
18 *
19 * v1 reads the FWord half of each MathValueRecord and discards the
20 * Device table offset. Device tables encode per-PPEM tweaks for
21 * hinting; the painter doesn't ship a hinter so the corrections
22 * would be noise.
23 *
24 * Total fixed-size payload:
25 *   4 * 2 + 51 * (2 + 2) + 2 = 214 bytes.
26 *
27 * Throws on short input - a font's MathConstants record is fixed-
28 * length per the spec and any short read is a malformed font.
29 */
30final class MathConstantsParser
31{
32    public function parse(string $bytes): MathConstants
33    {
34        if (strlen($bytes) < 214) {
35            throw new \RuntimeException(sprintf(
36                'MathConstants sub-table too short: got %d bytes, expected at least 214',
37                strlen($bytes),
38            ));
39        }
40
41        // Cursor walks the byte string; helpers advance it.
42        $cursor = 0;
43        $readInt16 = function () use ($bytes, &$cursor): int {
44            $value = unpack('n', substr($bytes, $cursor, 2))[1] ?? 0;
45            $cursor += 2;
46            // Convert unsigned 16-bit to signed.
47            return $value >= 0x8000 ? $value - 0x10000 : $value;
48        };
49        $readMathValue = function () use ($readInt16, &$cursor): int {
50            $value = $readInt16();
51            $cursor += 2; // Skip the Device table offset (uint16).
52            return $value;
53        };
54
55        // 4 plain Int16 fields.
56        $scriptPercentScaleDown = $readInt16();
57        $scriptScriptPercentScaleDown = $readInt16();
58        $delimitedSubFormulaMinHeight = $readInt16();
59        $displayOperatorMinHeight = $readInt16();
60
61        // 51 MathValueRecord fields in the spec-defined order.
62        $mathLeading = $readMathValue();
63        $axisHeight = $readMathValue();
64        $accentBaseHeight = $readMathValue();
65        $flattenedAccentBaseHeight = $readMathValue();
66        $subscriptShiftDown = $readMathValue();
67        $subscriptTopMax = $readMathValue();
68        $subscriptBaselineDropMin = $readMathValue();
69        $superscriptShiftUp = $readMathValue();
70        $superscriptShiftUpCramped = $readMathValue();
71        $superscriptBottomMin = $readMathValue();
72        $superscriptBaselineDropMax = $readMathValue();
73        $subSuperscriptGapMin = $readMathValue();
74        $superscriptBottomMaxWithSubscript = $readMathValue();
75        $spaceAfterScript = $readMathValue();
76        $upperLimitGapMin = $readMathValue();
77        $upperLimitBaselineRiseMin = $readMathValue();
78        $lowerLimitGapMin = $readMathValue();
79        $lowerLimitBaselineDropMin = $readMathValue();
80        $stackTopShiftUp = $readMathValue();
81        $stackTopDisplayStyleShiftUp = $readMathValue();
82        $stackBottomShiftDown = $readMathValue();
83        $stackBottomDisplayStyleShiftDown = $readMathValue();
84        $stackGapMin = $readMathValue();
85        $stackDisplayStyleGapMin = $readMathValue();
86        $stretchStackTopShiftUp = $readMathValue();
87        $stretchStackBottomShiftDown = $readMathValue();
88        $stretchStackGapAboveMin = $readMathValue();
89        $stretchStackGapBelowMin = $readMathValue();
90        $fractionNumeratorShiftUp = $readMathValue();
91        $fractionNumeratorDisplayStyleShiftUp = $readMathValue();
92        $fractionDenominatorShiftDown = $readMathValue();
93        $fractionDenominatorDisplayStyleShiftDown = $readMathValue();
94        $fractionNumeratorGapMin = $readMathValue();
95        $fractionNumDisplayStyleGapMin = $readMathValue();
96        $fractionRuleThickness = $readMathValue();
97        $fractionDenominatorGapMin = $readMathValue();
98        $fractionDenomDisplayStyleGapMin = $readMathValue();
99        $skewedFractionHorizontalGap = $readMathValue();
100        $skewedFractionVerticalGap = $readMathValue();
101        $overbarVerticalGap = $readMathValue();
102        $overbarRuleThickness = $readMathValue();
103        $overbarExtraAscender = $readMathValue();
104        $underbarVerticalGap = $readMathValue();
105        $underbarRuleThickness = $readMathValue();
106        $underbarExtraDescender = $readMathValue();
107        $radicalVerticalGap = $readMathValue();
108        $radicalDisplayStyleVerticalGap = $readMathValue();
109        $radicalRuleThickness = $readMathValue();
110        $radicalExtraAscender = $readMathValue();
111        $radicalKernBeforeDegree = $readMathValue();
112        $radicalKernAfterDegree = $readMathValue();
113
114        // Trailing plain Int16.
115        $radicalDegreeBottomRaisePercent = $readInt16();
116
117        return new MathConstants(
118            scriptPercentScaleDown: $scriptPercentScaleDown,
119            scriptScriptPercentScaleDown: $scriptScriptPercentScaleDown,
120            delimitedSubFormulaMinHeight: $delimitedSubFormulaMinHeight,
121            displayOperatorMinHeight: $displayOperatorMinHeight,
122            mathLeading: $mathLeading,
123            axisHeight: $axisHeight,
124            accentBaseHeight: $accentBaseHeight,
125            flattenedAccentBaseHeight: $flattenedAccentBaseHeight,
126            subscriptShiftDown: $subscriptShiftDown,
127            subscriptTopMax: $subscriptTopMax,
128            subscriptBaselineDropMin: $subscriptBaselineDropMin,
129            superscriptShiftUp: $superscriptShiftUp,
130            superscriptShiftUpCramped: $superscriptShiftUpCramped,
131            superscriptBottomMin: $superscriptBottomMin,
132            superscriptBaselineDropMax: $superscriptBaselineDropMax,
133            subSuperscriptGapMin: $subSuperscriptGapMin,
134            superscriptBottomMaxWithSubscript: $superscriptBottomMaxWithSubscript,
135            spaceAfterScript: $spaceAfterScript,
136            upperLimitGapMin: $upperLimitGapMin,
137            upperLimitBaselineRiseMin: $upperLimitBaselineRiseMin,
138            lowerLimitGapMin: $lowerLimitGapMin,
139            lowerLimitBaselineDropMin: $lowerLimitBaselineDropMin,
140            stackTopShiftUp: $stackTopShiftUp,
141            stackTopDisplayStyleShiftUp: $stackTopDisplayStyleShiftUp,
142            stackBottomShiftDown: $stackBottomShiftDown,
143            stackBottomDisplayStyleShiftDown: $stackBottomDisplayStyleShiftDown,
144            stackGapMin: $stackGapMin,
145            stackDisplayStyleGapMin: $stackDisplayStyleGapMin,
146            stretchStackTopShiftUp: $stretchStackTopShiftUp,
147            stretchStackBottomShiftDown: $stretchStackBottomShiftDown,
148            stretchStackGapAboveMin: $stretchStackGapAboveMin,
149            stretchStackGapBelowMin: $stretchStackGapBelowMin,
150            fractionNumeratorShiftUp: $fractionNumeratorShiftUp,
151            fractionNumeratorDisplayStyleShiftUp: $fractionNumeratorDisplayStyleShiftUp,
152            fractionDenominatorShiftDown: $fractionDenominatorShiftDown,
153            fractionDenominatorDisplayStyleShiftDown: $fractionDenominatorDisplayStyleShiftDown,
154            fractionNumeratorGapMin: $fractionNumeratorGapMin,
155            fractionNumDisplayStyleGapMin: $fractionNumDisplayStyleGapMin,
156            fractionRuleThickness: $fractionRuleThickness,
157            fractionDenominatorGapMin: $fractionDenominatorGapMin,
158            fractionDenomDisplayStyleGapMin: $fractionDenomDisplayStyleGapMin,
159            skewedFractionHorizontalGap: $skewedFractionHorizontalGap,
160            skewedFractionVerticalGap: $skewedFractionVerticalGap,
161            overbarVerticalGap: $overbarVerticalGap,
162            overbarRuleThickness: $overbarRuleThickness,
163            overbarExtraAscender: $overbarExtraAscender,
164            underbarVerticalGap: $underbarVerticalGap,
165            underbarRuleThickness: $underbarRuleThickness,
166            underbarExtraDescender: $underbarExtraDescender,
167            radicalVerticalGap: $radicalVerticalGap,
168            radicalDisplayStyleVerticalGap: $radicalDisplayStyleVerticalGap,
169            radicalRuleThickness: $radicalRuleThickness,
170            radicalExtraAscender: $radicalExtraAscender,
171            radicalKernBeforeDegree: $radicalKernBeforeDegree,
172            radicalKernAfterDegree: $radicalKernAfterDegree,
173            radicalDegreeBottomRaisePercent: $radicalDegreeBottomRaisePercent,
174        );
175    }
176}