Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Metafile
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
7 / 7
26
100.00% covered (success)
100.00%
1 / 1
 isSupported
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getMimeType
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
6
 isMimeType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtension
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
 getImageSize
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 convertToPng
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 load
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
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
19namespace PhpOffice\PhpWord\Shared;
20
21use Exception;
22use PhpOffice\PhpWord\Settings;
23use PhpOffice\WMF\Reader\Detector;
24use PhpOffice\WMF\Reader\EMF\GD as EMFReader;
25use PhpOffice\WMF\Reader\WMF\GD as WMFReader;
26
27/**
28 * Windows metafiles (WMF, EMF & EMF+) helper, based on phpoffice/wmf.
29 *
30 * The support is optional : it requires the phpoffice/wmf library and the GD extension.
31 *
32 * EMF+ files are EMF files containing EMF+ records : they are stored as EMF files.
33 * The GD readers are used : they are pure PHP and don't depend on external delegates (like Imagick).
34 */
35class Metafile
36{
37    const MIME_WMF = 'image/x-wmf';
38    const MIME_EMF = 'image/x-emf';
39
40    /**
41     * Returns if the metafiles are supported : phpoffice/wmf is installed and the GD extension is loaded.
42     */
43    public static function isSupported(): bool
44    {
45        return class_exists(Detector::class) && extension_loaded('gd');
46    }
47
48    /**
49     * Returns the mime type of a metafile (`image/x-wmf` or `image/x-emf`).
50     *
51     * Returns null if the content is not a metafile or if the metafiles are not supported.
52     */
53    public static function getMimeType(string $content): ?string
54    {
55        if (!self::isSupported()) {
56            return null; // @codeCoverageIgnore
57        }
58
59        switch (Detector::detect($content)) {
60            case Detector::TYPE_WMF:
61                return self::MIME_WMF;
62            case Detector::TYPE_EMF:
63            case Detector::TYPE_EMFPLUS:
64                return self::MIME_EMF;
65            default:
66                return null;
67        }
68    }
69
70    /**
71     * Returns if the mime type is the one of a metafile.
72     */
73    public static function isMimeType(?string $mimeType): bool
74    {
75        return in_array($mimeType, [self::MIME_WMF, self::MIME_EMF], true);
76    }
77
78    /**
79     * Returns the extension of a metafile mime type (`wmf` or `emf`).
80     */
81    public static function getExtension(string $mimeType): ?string
82    {
83        switch ($mimeType) {
84            case self::MIME_WMF:
85                return 'wmf';
86            case self::MIME_EMF:
87                return 'emf';
88            default:
89                return null;
90        }
91    }
92
93    /**
94     * Returns the size of a metafile and its mime type, like getimagesize() : [width, height, mimeType].
95     *
96     * Returns null if the content is not a metafile, if it can't be read or if the metafiles are not supported.
97     *
98     * @return null|array{0: int, 1: int, 2: string}
99     */
100    public static function getImageSize(string $content): ?array
101    {
102        $mimeType = self::getMimeType($content);
103        if ($mimeType === null) {
104            return null;
105        }
106        $reader = self::load($content, $mimeType);
107        if ($reader === null) {
108            return null;
109        }
110
111        $resource = $reader->getResource();
112
113        // GdImage since PHP 8.0, resource before
114        // @phpstan-ignore-next-line
115        return [imagesx($resource), imagesy($resource), $mimeType];
116    }
117
118    /**
119     * Converts a metafile to PNG.
120     *
121     * Returns null if the content is not a metafile, if it can't be read or if the metafiles are not supported.
122     */
123    public static function convertToPng(string $content): ?string
124    {
125        $mimeType = self::getMimeType($content);
126        if ($mimeType === null) {
127            return null;
128        }
129        $reader = self::load($content, $mimeType);
130        if ($reader === null) {
131            return null;
132        }
133
134        $tempFilename = tempnam(Settings::getTempDir(), 'PHPWordMetafile');
135        if ($tempFilename === false) {
136            return null; // @codeCoverageIgnore
137        }
138
139        $png = $reader->save($tempFilename, 'png') ? file_get_contents($tempFilename) : false;
140        @unlink($tempFilename);
141
142        return $png === false ? null : $png;
143    }
144
145    /**
146     * @return null|EMFReader|WMFReader
147     */
148    private static function load(string $content, string $mimeType)
149    {
150        $reader = $mimeType === self::MIME_WMF ? new WMFReader() : new EMFReader();
151
152        try {
153            if (!$reader->loadFromString($content)) {
154                return null;
155            }
156        } catch (Exception $e) {
157            return null;
158        }
159
160        return $reader;
161    }
162}