Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.44% covered (success)
94.44%
34 / 36
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Ascii85
94.44% covered (success)
94.44%
34 / 36
0.00% covered (danger)
0.00%
0 / 2
15.04
0.00% covered (danger)
0.00%
0 / 1
 decode
96.30% covered (success)
96.30%
26 / 27
0.00% covered (danger)
0.00%
0 / 1
10
 decodeGroup
88.89% covered (success)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
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\Extract\Filter;
16
17use Pop\Pdf\Extract\Exception;
18
19/**
20 * Pdf extract ascii 85 filter class
21 *
22 * @category   Pop
23 * @package    Pop\Pdf
24 * @author     Nick Sagona, III <nick@popphp.org>
25 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    6.0.0
28 */
29class Ascii85 implements FilterInterface
30{
31
32    /**
33     * Maximum decoded stream size in bytes
34     */
35    protected const MAX_DECODED_LENGTH = 67108864;
36
37    /**
38     * Decode an ASCII85Decode stream
39     *
40     * @param  string $data
41     * @param  array  $params
42     * @throws Exception
43     * @return string
44     */
45    public function decode(string $data, array $params = []): string
46    {
47        $data = trim($data);
48
49        if (str_starts_with($data, '<~')) {
50            $data = substr($data, 2);
51        }
52        if (str_ends_with($data, '~>')) {
53            $data = substr($data, 0, -2);
54        }
55
56        $data = preg_replace('/\s+/', '', $data);
57
58        $out   = '';
59        $group = '';
60        $len   = strlen($data);
61
62        for ($i = 0; $i < $len; $i++) {
63            $c = $data[$i];
64
65            if (($c === 'z') && ($group === '')) {
66                // The 'z' shortcut expands a single input byte to 4 output
67                // bytes - unlike every other group here, it costs nothing
68                // from $len to produce output, so it needs the same
69                // decoded-size cap the other amplifying filters already have.
70                $out .= "\0\0\0\0";
71
72                if (strlen($out) > self::MAX_DECODED_LENGTH) {
73                    throw new Exception('Error: Decoded ASCII85Decode stream exceeds the maximum allowed size.');
74                }
75
76                continue;
77            }
78
79            $group .= $c;
80
81            if (strlen($group) === 5) {
82                $out  .= $this->decodeGroup($group);
83                $group = '';
84
85                if (strlen($out) > self::MAX_DECODED_LENGTH) {
86                    throw new Exception('Error: Decoded ASCII85Decode stream exceeds the maximum allowed size.');
87                }
88            }
89        }
90
91        if ($group !== '') {
92            $n     = strlen($group);
93            $padded = str_pad($group, 5, 'u');
94            $out   .= substr($this->decodeGroup($padded), 0, $n - 1);
95        }
96
97        return $out;
98    }
99
100    /**
101     * Decode a 5-character ASCII85 group into 4 raw bytes
102     *
103     * @param  string $group
104     * @throws Exception
105     * @return string
106     */
107    protected function decodeGroup(string $group): string
108    {
109        $value = 0;
110        for ($i = 0; $i < 5; $i++) {
111            $digit = ord($group[$i]) - 33;
112            if (($digit < 0) || ($digit > 84)) {
113                throw new Exception('Error: Invalid ASCII85 data.');
114            }
115            $value = ($value * 85) + $digit;
116        }
117
118        if ($value > 4294967295) {
119            throw new Exception('Error: Invalid ASCII85 data.');
120        }
121
122        return pack('N', $value);
123    }
124
125}