Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
100.00% |
1 / 1 |
|
100.00% |
7 / 7 |
CRAP | |
100.00% |
40 / 40 |
| Detector | |
100.00% |
1 / 1 |
|
100.00% |
7 / 7 |
26 | |
100.00% |
40 / 40 |
| detect | |
100.00% |
1 / 1 |
4 | |
100.00% |
7 / 7 |
|||
| detectFile | |
100.00% |
1 / 1 |
4 | |
100.00% |
7 / 7 |
|||
| isWMF | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
| isPlaceableWMF | |
100.00% |
1 / 1 |
3 | |
100.00% |
4 / 4 |
|||
| isStandardWMF | |
100.00% |
1 / 1 |
4 | |
100.00% |
6 / 6 |
|||
| isEMF | |
100.00% |
1 / 1 |
4 | |
100.00% |
5 / 5 |
|||
| isEMFPlus | |
100.00% |
1 / 1 |
5 | |
100.00% |
10 / 10 |
|||
| <?php | |
| declare(strict_types=1); | |
| namespace PhpOffice\WMF\Reader; | |
| use PhpOffice\WMF\Reader\EMF\GD as EMFReader; | |
| /** | |
| * Detects the type of an image (WMF, EMF, EMF+) from its content | |
| * | |
| * @see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/ | |
| * @see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-emf/ | |
| * @see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-emfplus/ | |
| */ | |
| class Detector | |
| { | |
| public const TYPE_WMF = 'wmf'; | |
| public const TYPE_EMF = 'emf'; | |
| public const TYPE_EMFPLUS = 'emf+'; | |
| public const TYPE_UNKNOWN = 'unknown'; | |
| /** | |
| * Key of the placeable WMF header | |
| */ | |
| protected const WMF_PLACEABLE_KEY = 0x9AC6CDD7; | |
| /** | |
| * Signature of the EMF header (" EMF") | |
| */ | |
| protected const EMF_SIGNATURE = 0x464D4520; | |
| /** | |
| * Identifier of the EMR_COMMENT_EMFPLUS records ("EMF+") | |
| */ | |
| protected const EMFPLUS_IDENTIFIER = 0x2B464D45; | |
| protected const EMFPLUS_HEADER = 0x4001; | |
| /** | |
| * Returns the type of an image : TYPE_WMF, TYPE_EMF, TYPE_EMFPLUS or TYPE_UNKNOWN | |
| * | |
| * EMF+ files are EMF files containing EMF+ records : EMF+ "dual" files can be read as EMF files | |
| */ | |
| public static function detect(string $content): string | |
| { | |
| if (self::isWMF($content)) { | |
| return self::TYPE_WMF; | |
| } | |
| if (self::isEMFPlus($content)) { | |
| return self::TYPE_EMFPLUS; | |
| } | |
| if (self::isEMF($content)) { | |
| return self::TYPE_EMF; | |
| } | |
| return self::TYPE_UNKNOWN; | |
| } | |
| /** | |
| * Returns the type of an image file : TYPE_WMF, TYPE_EMF, TYPE_EMFPLUS or TYPE_UNKNOWN | |
| * | |
| * Only the headers of the file are read | |
| */ | |
| public static function detectFile(string $filename): string | |
| { | |
| if (!is_file($filename) || !is_readable($filename)) { | |
| return self::TYPE_UNKNOWN; | |
| } | |
| $content = (string) file_get_contents($filename, false, null, 0, 88); | |
| if (self::isEMF($content)) { | |
| // The EMF+ header is in the record following the EMF header | |
| list(, $headerSize) = unpack('V', (string) substr($content, 4, 4)); | |
| $content = (string) file_get_contents($filename, false, null, 0, min((int) $headerSize, 65536) + 28); | |
| } | |
| return self::detect($content); | |
| } | |
| /** | |
| * Returns if the content is a WMF file (placeable or not) | |
| */ | |
| public static function isWMF(string $content): bool | |
| { | |
| return self::isPlaceableWMF($content) || self::isStandardWMF($content); | |
| } | |
| /** | |
| * Returns if the content is a placeable WMF file (with a META_PLACEABLE header) | |
| */ | |
| public static function isPlaceableWMF(string $content): bool | |
| { | |
| if (strlen($content) < 22 + 18) { | |
| return false; | |
| } | |
| list(, $key) = unpack('V', (string) substr($content, 0, 4)); | |
| return $key == self::WMF_PLACEABLE_KEY && self::isStandardWMF((string) substr($content, 22)); | |
| } | |
| /** | |
| * Returns if the content starts with a META_HEADER record | |
| */ | |
| protected static function isStandardWMF(string $content): bool | |
| { | |
| if (strlen($content) < 18) { | |
| return false; | |
| } | |
| $header = unpack('vtype/vheaderSize/vversion', (string) substr($content, 0, 6)); | |
| // Type : MEMORYMETAFILE or DISKMETAFILE | |
| // Header size : 9 words | |
| // Version : METAVERSION100 or METAVERSION300 | |
| return in_array($header['type'], [1, 2]) | |
| && $header['headerSize'] == 9 | |
| && in_array($header['version'], [0x0100, 0x0300]); | |
| } | |
| /** | |
| * Returns if the content is a EMF file (including EMF+ files) | |
| */ | |
| public static function isEMF(string $content): bool | |
| { | |
| if (strlen($content) < 88) { | |
| return false; | |
| } | |
| list(, $type, $size) = unpack('V2', (string) substr($content, 0, 8)); | |
| list(, $signature) = unpack('V', (string) substr($content, 40, 4)); | |
| return $type == EMFReader::EMR_HEADER && $size >= 88 && $signature == self::EMF_SIGNATURE; | |
| } | |
| /** | |
| * Returns if the content is a EMF+ file : a EMF file whose first record after the header is a EMF+ header | |
| */ | |
| public static function isEMFPlus(string $content): bool | |
| { | |
| if (!self::isEMF($content)) { | |
| return false; | |
| } | |
| list(, $headerSize) = unpack('V', (string) substr($content, 4, 4)); | |
| $record = (string) substr($content, $headerSize, 28); | |
| if (strlen($record) < 28) { | |
| return false; | |
| } | |
| $comment = unpack('Vtype/Vsize/VdataSize/Videntifier/vplusType', $record); | |
| return $comment['type'] == EMFReader::EMR_GDICOMMENT | |
| && $comment['identifier'] == self::EMFPLUS_IDENTIFIER | |
| && $comment['plusType'] == self::EMFPLUS_HEADER; | |
| } | |
| } |