Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
130 / 130 |
|
100.00% |
1 / 1 |
CRAP | |
100.00% |
1 / 1 |
| MathConstantsParser | |
100.00% |
130 / 130 |
|
100.00% |
1 / 1 |
3 | |
100.00% |
1 / 1 |
| parse | |
100.00% |
130 / 130 |
|
100.00% |
1 / 1 |
3 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace 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 | */ |
| 30 | final 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 | } |