Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
82 / 82 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
| ImageStyle | |
100.00% |
82 / 82 |
|
100.00% |
4 / 4 |
29 | |
100.00% |
1 / 1 |
| readVml | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
9 | |||
| readDrawing | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
11 | |||
| position | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
| cssToPoint | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * This file is part of PHPWord - A pure PHP library for reading and writing |
| 5 | * word processing documents. |
| 6 | * |
| 7 | * PHPWord is free software distributed under the terms of the GNU Lesser |
| 8 | * General Public License version 3 as published by the Free Software Foundation. |
| 9 | * |
| 10 | * For the full copyright and license information, please read the LICENSE |
| 11 | * file that was distributed with this source code. For the full list of |
| 12 | * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. |
| 13 | * |
| 14 | * @see https://github.com/PHPOffice/PHPWord |
| 15 | * |
| 16 | * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 |
| 17 | */ |
| 18 | |
| 19 | namespace PhpOffice\PhpWord\Reader\Word2007; |
| 20 | |
| 21 | use DOMElement; |
| 22 | use PhpOffice\PhpWord\Shared\Converter; |
| 23 | use PhpOffice\PhpWord\Shared\XMLReader; |
| 24 | use PhpOffice\PhpWord\Style\Frame; |
| 25 | |
| 26 | /** |
| 27 | * Size, position and wrapping of an image, read from VML or DrawingML, as an array for Style\Image. |
| 28 | */ |
| 29 | class ImageStyle |
| 30 | { |
| 31 | private const EMU_PER_POINT = 12700; |
| 32 | |
| 33 | private const H_POS = [Frame::POS_ABSOLUTE, Frame::POS_LEFT, Frame::POS_CENTER, Frame::POS_RIGHT, Frame::POS_INSIDE, Frame::POS_OUTSIDE]; |
| 34 | private const V_POS = [Frame::POS_ABSOLUTE, Frame::POS_TOP, Frame::POS_CENTER, Frame::POS_BOTTOM, Frame::POS_INSIDE, Frame::POS_OUTSIDE]; |
| 35 | |
| 36 | /** mso-position-horizontal-relative, and relativeFrom of wp:positionH */ |
| 37 | private const H_POS_REL_TO = [ |
| 38 | 'margin' => Frame::POS_RELTO_MARGIN, |
| 39 | 'page' => Frame::POS_RELTO_PAGE, |
| 40 | 'text' => Frame::POS_RELTO_COLUMN, |
| 41 | 'column' => Frame::POS_RELTO_COLUMN, |
| 42 | 'char' => Frame::POS_RELTO_CHAR, |
| 43 | 'character' => Frame::POS_RELTO_CHAR, |
| 44 | 'left-margin-area' => Frame::POS_RELTO_LMARGIN, |
| 45 | 'leftMargin' => Frame::POS_RELTO_LMARGIN, |
| 46 | 'right-margin-area' => Frame::POS_RELTO_RMARGIN, |
| 47 | 'rightMargin' => Frame::POS_RELTO_RMARGIN, |
| 48 | 'inner-margin-area' => Frame::POS_RELTO_IMARGIN, |
| 49 | 'insideMargin' => Frame::POS_RELTO_IMARGIN, |
| 50 | 'outer-margin-area' => Frame::POS_RELTO_OMARGIN, |
| 51 | 'outsideMargin' => Frame::POS_RELTO_OMARGIN, |
| 52 | ]; |
| 53 | |
| 54 | /** mso-position-vertical-relative, and relativeFrom of wp:positionV */ |
| 55 | private const V_POS_REL_TO = [ |
| 56 | 'margin' => Frame::POS_RELTO_MARGIN, |
| 57 | 'page' => Frame::POS_RELTO_PAGE, |
| 58 | 'text' => Frame::POS_RELTO_TEXT, |
| 59 | 'paragraph' => Frame::POS_RELTO_TEXT, |
| 60 | 'line' => Frame::POS_RELTO_LINE, |
| 61 | 'top-margin-area' => Frame::POS_RELTO_TMARGIN, |
| 62 | 'topMargin' => Frame::POS_RELTO_TMARGIN, |
| 63 | 'bottom-margin-area' => Frame::POS_RELTO_BMARGIN, |
| 64 | 'bottomMargin' => Frame::POS_RELTO_BMARGIN, |
| 65 | 'inner-margin-area' => Frame::POS_RELTO_IMARGIN, |
| 66 | 'insideMargin' => Frame::POS_RELTO_IMARGIN, |
| 67 | 'outer-margin-area' => Frame::POS_RELTO_OMARGIN, |
| 68 | 'outsideMargin' => Frame::POS_RELTO_OMARGIN, |
| 69 | ]; |
| 70 | |
| 71 | private const WRAP = [ |
| 72 | 'square' => Frame::WRAP_SQUARE, |
| 73 | 'tight' => Frame::WRAP_TIGHT, |
| 74 | 'through' => Frame::WRAP_THROUGH, |
| 75 | 'topAndBottom' => Frame::WRAP_TOPBOTTOM, |
| 76 | ]; |
| 77 | |
| 78 | /** |
| 79 | * Read the style of a VML v:shape, as Word and the Word2007 writer write it. |
| 80 | * |
| 81 | * @return array<string, float|string> |
| 82 | */ |
| 83 | public static function readVml(XMLReader $xmlReader, DOMElement $shape): array |
| 84 | { |
| 85 | $css = []; |
| 86 | foreach (explode(';', $shape->getAttribute('style')) as $declaration) { |
| 87 | $pair = explode(':', $declaration, 2); |
| 88 | if (count($pair) === 2) { |
| 89 | $css[strtolower(trim($pair[0]))] = trim($pair[1]); |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | $style = []; |
| 94 | $lengths = [ |
| 95 | 'width' => 'width', |
| 96 | 'height' => 'height', |
| 97 | 'margin-left' => 'left', |
| 98 | 'margin-top' => 'top', |
| 99 | 'mso-wrap-distance-top' => 'wrapDistanceTop', |
| 100 | 'mso-wrap-distance-bottom' => 'wrapDistanceBottom', |
| 101 | 'mso-wrap-distance-left' => 'wrapDistanceLeft', |
| 102 | 'mso-wrap-distance-right' => 'wrapDistanceRight', |
| 103 | ]; |
| 104 | foreach ($lengths as $property => $key) { |
| 105 | $points = self::cssToPoint($css[$property] ?? ''); |
| 106 | if ($points !== null) { |
| 107 | $style[$key] = $points; |
| 108 | } |
| 109 | } |
| 110 | $style = array_merge($style, self::position( |
| 111 | $css['mso-position-horizontal'] ?? '', |
| 112 | $css['mso-position-horizontal-relative'] ?? '', |
| 113 | $css['mso-position-vertical'] ?? '', |
| 114 | $css['mso-position-vertical-relative'] ?? '' |
| 115 | )); |
| 116 | |
| 117 | $position = $css['position'] ?? ''; |
| 118 | if ($position === Frame::POS_ABSOLUTE || $position === Frame::POS_RELATIVE) { |
| 119 | $style['pos'] = $position; |
| 120 | // A floating shape wraps as w10:wrap says, or is behind or in front of the text without it |
| 121 | $wrap = self::WRAP[(string) $xmlReader->getAttribute('type', $shape, 'w10:wrap')] ?? null; |
| 122 | if ($wrap === null) { |
| 123 | $wrap = (int) ($css['z-index'] ?? 0) < 0 ? Frame::WRAP_BEHIND : Frame::WRAP_INFRONT; |
| 124 | } |
| 125 | $style['wrap'] = $wrap; |
| 126 | } |
| 127 | |
| 128 | return $style; |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * Read the style of a DrawingML wp:inline or wp:anchor. |
| 133 | * |
| 134 | * @return array<string, float|string> |
| 135 | */ |
| 136 | public static function readDrawing(XMLReader $xmlReader, DOMElement $frame): array |
| 137 | { |
| 138 | $style = []; |
| 139 | $lengths = [ |
| 140 | 'wp:extent/@cx' => 'width', |
| 141 | 'wp:extent/@cy' => 'height', |
| 142 | '@distT' => 'wrapDistanceTop', |
| 143 | '@distB' => 'wrapDistanceBottom', |
| 144 | '@distL' => 'wrapDistanceLeft', |
| 145 | '@distR' => 'wrapDistanceRight', |
| 146 | ]; |
| 147 | foreach ($lengths as $path => $key) { |
| 148 | $emu = $xmlReader->getValue($path, $frame); |
| 149 | if (is_numeric($emu)) { |
| 150 | $style[$key] = $emu / self::EMU_PER_POINT; |
| 151 | } |
| 152 | } |
| 153 | if ($frame->localName !== 'anchor') { |
| 154 | return $style; |
| 155 | } |
| 156 | |
| 157 | $style['pos'] = Frame::POS_ABSOLUTE; |
| 158 | $style = array_merge($style, self::position( |
| 159 | (string) $xmlReader->getValue('wp:positionH/wp:align', $frame), |
| 160 | (string) $xmlReader->getAttribute('relativeFrom', $frame, 'wp:positionH'), |
| 161 | (string) $xmlReader->getValue('wp:positionV/wp:align', $frame), |
| 162 | (string) $xmlReader->getAttribute('relativeFrom', $frame, 'wp:positionV') |
| 163 | )); |
| 164 | foreach (['H' => 'left', 'V' => 'top'] as $axis => $key) { |
| 165 | $emu = $xmlReader->getValue("wp:position{$axis}/wp:posOffset", $frame); |
| 166 | if (is_numeric($emu)) { |
| 167 | $style[$axis === 'H' ? 'hPos' : 'vPos'] = Frame::POS_ABSOLUTE; |
| 168 | $style[$key] = $emu / self::EMU_PER_POINT; |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | foreach (self::WRAP as $name => $wrap) { |
| 173 | if ($xmlReader->elementExists('wp:wrap' . ucfirst($name), $frame)) { |
| 174 | $style['wrap'] = $wrap; |
| 175 | } |
| 176 | } |
| 177 | if (!isset($style['wrap'])) { |
| 178 | $behind = in_array($frame->getAttribute('behindDoc'), ['1', 'true', 'on'], true); |
| 179 | $style['wrap'] = $behind ? Frame::WRAP_BEHIND : Frame::WRAP_INFRONT; |
| 180 | } |
| 181 | |
| 182 | return $style; |
| 183 | } |
| 184 | |
| 185 | /** |
| 186 | * Alignment and reference of the position, left out when Style\Frame does not know the value. |
| 187 | * |
| 188 | * @return array<string, string> |
| 189 | */ |
| 190 | private static function position(string $hPos, string $hPosRelTo, string $vPos, string $vPosRelTo): array |
| 191 | { |
| 192 | $style = []; |
| 193 | if (in_array($hPos, self::H_POS, true)) { |
| 194 | $style['hPos'] = $hPos; |
| 195 | } |
| 196 | if (isset(self::H_POS_REL_TO[$hPosRelTo])) { |
| 197 | $style['hPosRelTo'] = self::H_POS_REL_TO[$hPosRelTo]; |
| 198 | } |
| 199 | if (in_array($vPos, self::V_POS, true)) { |
| 200 | $style['vPos'] = $vPos; |
| 201 | } |
| 202 | if (isset(self::V_POS_REL_TO[$vPosRelTo])) { |
| 203 | $style['vPosRelTo'] = self::V_POS_REL_TO[$vPosRelTo]; |
| 204 | } |
| 205 | |
| 206 | return $style; |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * A VML length in points; a number without a unit is in pixels. |
| 211 | */ |
| 212 | private static function cssToPoint(string $value): ?float |
| 213 | { |
| 214 | if (!preg_match('/^([+-]?)(\d*\.?\d+)(pt|px|in|cm|mm|pc)?$/', $value, $matches)) { |
| 215 | return null; |
| 216 | } |
| 217 | $points = (float) Converter::cssToPoint($matches[2] . (empty($matches[3]) ? 'px' : $matches[3])); |
| 218 | |
| 219 | return $matches[1] === '-' ? -$points : $points; |
| 220 | } |
| 221 | } |