Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
Choice
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
11 / 11
31
100.00% covered (success)
100.00%
1 / 1
 addOption
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 hasOptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCombo
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setEdit
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setSort
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setMultiSelect
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setDoNotSpellCheck
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setCommitOnSelChange
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isCombo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isMultiSelect
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStream
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
13
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\Document\Page\Field;
16
17use Pop\Color\Color;
18
19/**
20 * Pdf page button field 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.2.0
28 */
29class Choice extends AbstractField
30{
31
32    /**
33     * Field options
34     * @var array
35     */
36    protected array $options = [];
37
38    /**
39     * Add an option
40     *
41     * @param  string  $value
42     * @param  ?string $label
43     * @return Choice
44     */
45    public function addOption(string $value, ?string $label = null): Choice
46    {
47        $this->options[] = (($label !== null) && ($label !== $value)) ? [$value, $label] : $value;
48        return $this;
49    }
50
51    /**
52     * Has options
53     *
54     * @return bool
55     */
56    public function hasOptions(): bool
57    {
58        return (count($this->options) > 0);
59    }
60
61    /**
62     * Set combo
63     *
64     * @return Choice
65     */
66    public function setCombo(): Choice
67    {
68        if (!in_array(18, $this->flagBits)) {
69            $this->flagBits[] = 18;
70        }
71        return $this;
72    }
73
74    /**
75     * Set edit
76     *
77     * @return Choice
78     */
79    public function setEdit(): Choice
80    {
81        if (!in_array(19, $this->flagBits)) {
82            $this->flagBits[] = 19;
83        }
84        return $this;
85    }
86
87    /**
88     * Set sort
89     *
90     * @return Choice
91     */
92    public function setSort(): Choice
93    {
94        if (!in_array(20, $this->flagBits)) {
95            $this->flagBits[] = 20;
96        }
97        return $this;
98    }
99
100    /**
101     * Set multi-select
102     *
103     * @return Choice
104     */
105    public function setMultiSelect(): Choice
106    {
107        if (!in_array(22, $this->flagBits)) {
108            $this->flagBits[] = 22;
109        }
110        return $this;
111    }
112
113    /**
114     * Set do not spell check
115     *
116     * @return Choice
117     */
118    public function setDoNotSpellCheck(): Choice
119    {
120        if (!in_array(23, $this->flagBits)) {
121            $this->flagBits[] = 23;
122        }
123        return $this;
124    }
125
126    /**
127     * Set commit on select change
128     *
129     * @return Choice
130     */
131    public function setCommitOnSelChange(): Choice
132    {
133        if (!in_array(27, $this->flagBits)) {
134            $this->flagBits[] = 27;
135        }
136        return $this;
137    }
138
139    /**
140     * Is combo
141     *
142     * @return bool
143     */
144    public function isCombo(): bool
145    {
146        return in_array(18, $this->flagBits);
147    }
148
149    /**
150     * Is multi-select
151     *
152     * @return bool
153     */
154    public function isMultiSelect(): bool
155    {
156        return in_array(22, $this->flagBits);
157    }
158
159    /**
160     * Get the field stream
161     *
162     * @param  int     $i
163     * @param  int     $pageIndex
164     * @param  ?string $fontReference
165     * @param  int     $x
166     * @param  int     $y
167     * @return string
168     */
169    public function getStream(int $i, int $pageIndex, ?string $fontReference, int $x, int $y): string
170    {
171        $text    = null;
172        $options = null;
173        $color   = '0 g';
174
175        if ($this->fontColor !== null) {
176            if ($this->fontColor instanceof Color\Rgb) {
177                $color = $this->fontColor->render(Color\Rgb::PERCENT) . " rg";
178            } else if ($this->fontColor instanceof Color\Cmyk) {
179                $color = $this->fontColor->render(Color\Cmyk::PERCENT) . " k";
180            } else if ($this->fontColor instanceof Color\Grayscale) {
181                $color = $this->fontColor->render(Color\Grayscale::PERCENT) . " g";
182            }
183        }
184
185        if ($fontReference !== null) {
186            $fontReference = substr($fontReference, 0, strpos($fontReference, ' '));
187            $text          = '    /DA(' . $this->encryptLiteral($fontReference . ' ' . $this->size . ' Tf ' . $color) . ')';
188        }
189
190        $name    = ($this->name !== null) ? '    /T(' . $this->encryptLiteral($this->name) . ')/TU(' . $this->encryptLiteral($this->name) .
191            ')/TM(' . $this->encryptLiteral($this->name) . ')' : '';
192        $flags   = (count($this->flagBits) > 0) ? "\n    /Ff " . $this->getFlags() . "\n" : null;
193        // Per PDF 32000-1 12.7.4.4, a choice field's /V (and /DV) is a text
194        // string, not a bare Name - unlike Button, where /V is a Name that
195        // must agree with /AS. Emitting it as a parenthesized, escaped (and,
196        // if configured, encrypted) literal via encryptLiteral() is both the
197        // spec-correct form and closes a dictionary-injection hole: this
198        // value can come directly from untrusted HTML (<option value>) via
199        // Build\Html\Form\Layout, and a raw, unescaped value could inject
200        // arbitrary dictionary keys or simply produce malformed syntax for
201        // any multi-word value.
202        $value   = ($this->value !== null) ? "\n    /V (" . $this->encryptLiteral($this->value) . ")\n" : null;
203        $default = ($this->defaultValue !== null) ? "\n    /DV (" . $this->encryptLiteral($this->defaultValue) . ")\n" : null;
204
205        if (count($this->options) > 0) {
206            $options = "    /Opt [ ";
207            foreach ($this->options as $option) {
208                if (is_array($option)) {
209                    $options .= '[(' . $this->encryptLiteral($option[0]) . ') (' . $this->encryptLiteral($option[1]) . ')] ';
210                } else {
211                    $options .= '(' . $this->encryptLiteral($option) . ') ';
212                }
213            }
214            $options .= "]\n";
215        }
216
217        // Return the stream
218        return "{$i} 0 obj\n<<\n    /Type /Annot\n    /Subtype /Widget\n    /FT /Ch\n    /Rect [{$x} {$y} " .
219            ($this->width + $x) . " " . ($this->height + $y) . "]{$value}{$default}\n    /P {$pageIndex} 0 R\n" .
220            "    \n{$text}\n{$name}\n{$flags}\n{$options}" .
221            $this->getAppearanceCharacteristics() . $this->getBorderStyle() . ">>\nendobj\n\n";
222    }
223
224}