Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.45% covered (success)
95.45%
42 / 44
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
RunLengthFilter
95.45% covered (success)
95.45%
42 / 44
50.00% covered (danger)
50.00%
1 / 2
20
0.00% covered (danger)
0.00%
0 / 1
 encode
92.59% covered (success)
92.59%
25 / 27
0.00% covered (danger)
0.00%
0 / 1
15.09
 decode
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Filters;
6
7/**
8 * RunLengthDecode — basic run-length encoding (ISO 32000-2 §7.4.5).
9 *
10 * Rarely used standalone in modern PDFs but required by the spec.
11 * Effective only for data with long runs of identical bytes (e.g.,
12 * large solid-color image regions).
13 */
14final class RunLengthFilter implements FilterInterface
15{
16    public function encode(string $data): string
17    {
18        $output = '';
19        $len = strlen($data);
20        $i = 0;
21
22        while ($i < $len) {
23            // Check for run of repeated bytes
24            $runByte = $data[$i];
25            $runLen = 1;
26            while ($runLen < 128 && ($i + $runLen) < $len && $data[$i + $runLen] === $runByte) {
27                $runLen++;
28            }
29
30            if ($runLen >= 2) {
31                // Repeated run: length byte 257 - runLen, then the byte
32                $output .= chr(257 - $runLen) . $runByte;
33                $i += $runLen;
34            } else {
35                // Literal run: scan forward for non-repeated bytes
36                $litStart = $i;
37                $litLen = 0;
38                while ($litLen < 128 && ($i + $litLen) < $len) {
39                    // Look ahead: if the next two bytes are the same, stop literal run
40                    if ($litLen > 0 && ($i + $litLen + 1) < $len && $data[$i + $litLen] === $data[$i + $litLen + 1]) {
41                        break;
42                    }
43                    // Also check if a run of 3+ identical bytes follows (worth encoding as run)
44                    if (($i + $litLen + 2) < $len
45                        && $data[$i + $litLen] === $data[$i + $litLen + 1]
46                        && $data[$i + $litLen] === $data[$i + $litLen + 2]) {
47                        break;
48                    }
49                    $litLen++;
50                }
51                if ($litLen === 0) {
52                    $litLen = 1;
53                }
54                $output .= chr($litLen - 1) . substr($data, $litStart, $litLen);
55                $i += $litLen;
56            }
57        }
58        // EOD byte
59        $output .= chr(128);
60        return $output;
61    }
62
63    public function decode(string $data): string
64    {
65        $output = '';
66        $len = strlen($data);
67        $i = 0;
68
69        while ($i < $len) {
70            $length = ord($data[$i]);
71            $i++;
72
73            if ($length === 128) {
74                // EOD
75                break;
76            } elseif ($length <= 127) {
77                // Copy next length+1 bytes literally
78                $count = $length + 1;
79                $output .= substr($data, $i, $count);
80                $i += $count;
81            } else {
82                // Repeat next byte 257-length times
83                $count = 257 - $length;
84                if ($i < $len) {
85                    $output .= str_repeat($data[$i], $count);
86                    $i++;
87                }
88            }
89        }
90
91        return $output;
92    }
93}