15#include <boost/endian/conversion.hpp>
25#include <wx/filename.h>
31#include <compoundfilereader.h>
35#include <libwmf/api.h>
42constexpr std::array<uint8_t, 8> CFB_MAGIC = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
44constexpr size_t MAX_CFB_BYTES = 256 * 1024 * 1024;
45constexpr size_t MAX_STREAM_BYTES = 64 * 1024 * 1024;
48uint16_t readU16(
const uint8_t* aData )
50 return boost::endian::load_little_u16( aData );
54uint32_t readU32(
const uint8_t* aData )
56 return boost::endian::load_little_u32( aData );
60bool entryNameIs(
const CFB::COMPOUND_FILE_ENTRY* aEntry, std::u16string_view aName )
64 if( aEntry->nameLen != 2 * ( aName.size() + 1 ) || aEntry->name[aName.size()] != 0 )
67 for(
size_t i = 0; i < aName.size(); ++i )
69 if( aEntry->name[i] !=
static_cast<uint16_t
>( aName[i] ) )
77std::vector<uint8_t> readStream(
const CFB::CompoundFileReader& aReader,
const CFB::COMPOUND_FILE_ENTRY* aEntry )
79 uint64_t size = aReader.GetStreamSize( aEntry );
81 if( size > MAX_STREAM_BYTES || size > aReader.GetBufferLen() || size > std::numeric_limits<size_t>::max() )
84 std::vector<uint8_t> data(
static_cast<size_t>( size ) );
87 aReader.ReadFile( aEntry, 0,
reinterpret_cast<char*
>( data.data() ), data.size() );
93bool isWmf(
const uint8_t* aData,
size_t aSize )
95 if( aSize >= 4 && readU32( aData ) == 0x9AC6CDD7 )
98 return aSize >= 4 && ( readU16( aData ) == 1 || readU16( aData ) == 2 ) && readU16( aData + 2 ) == 9;
101OLE_IMAGE_PAYLOAD classifyContents( std::vector<uint8_t> aData, std::string aName )
103 if( ( aData.size() >= 2 && aData[0] ==
'B' && aData[1] ==
'M' )
104 || ( aData.size() >= 4 && aData[0] == 0x89 && aData[1] ==
'P' && aData[2] ==
'N' && aData[3] ==
'G' )
105 || ( aData.size() >= 3 && aData[0] == 0xFF && aData[1] == 0xD8 && aData[2] == 0xFF ) )
108 if( aData.size() >= 40 )
111 uint32_t headerSize = readU32( aData.data() );
112 int32_t width =
static_cast<int32_t
>( readU32( aData.data() + 4 ) );
113 int32_t height =
static_cast<int32_t
>( readU32( aData.data() + 8 ) );
114 uint16_t planes = readU16( aData.data() + 12 );
115 uint16_t bitCount = readU16( aData.data() + 14 );
117 bool depthIsValid = bitCount == 1 || bitCount == 4 || bitCount == 8 || bitCount == 16
118 || bitCount == 24 || bitCount == 32;
120 if( headerSize >= 40 && headerSize <= 200 && planes == 1 && depthIsValid && width != 0
127 if( isWmf( aData.data(), aData.size() ) )
136 if( aData.size() < 40 )
139 uint32_t clipboardFormat = readU32( aData.data() + 4 );
142 if( clipboardFormat == 3 || clipboardFormat == 14 )
144 else if( clipboardFormat == 8 )
150 return { type, { aData.begin() + 40, aData.end() },
"\\x02OlePres000" };
157 if( aData.size() < 6 )
160 size_t stated = readU32( aData.data() );
162 if( stated > aData.size() - 4 )
166 size_t length = stated;
168 if( readU16( aData.data() + 4 ) == 0x0002 )
174 for(
int i = 0; i < 2; ++i )
176 while( offset < aData.size() && aData[offset] != 0 )
179 if( offset >= aData.size() )
185 if( aData.size() - offset < 8 )
190 size_t pathLength = readU32( aData.data() + offset );
193 if( pathLength > aData.size() - offset )
196 offset += pathLength;
198 if( aData.size() - offset < 4 )
201 length = readU32( aData.data() + offset );
204 if( length > aData.size() - offset )
208 std::vector<uint8_t> body( aData.begin() + offset, aData.begin() + offset + length );
210 return classifyContents( std::move( body ),
"\\x01Ole10Native" );
220 fontDir.AppendDir( wxS(
"libwmf" ) );
221 fontDir.AppendDir( wxS(
"fonts" ) );
223 if( fontDir.DirExists() )
224 return fontDir.GetPath();
229 for(
int depth = 0; depth < 4; ++depth )
231 wxFileName candidate = buildDir;
232 candidate.AppendDir( wxS(
"libwmf" ) );
233 candidate.AppendDir( wxS(
"fonts" ) );
235 if( candidate.DirExists() )
236 return candidate.GetPath();
238 buildDir.RemoveLastDir();
241 wxLogTrace(
traceSchPlugin, wxS(
"libwmf fonts missing (looked for %s); WMF rendering will fail" ),
244 return fontDir.GetPath();
250 constexpr size_t PROLOGUE = 26;
252 if( aPayload.size() < PROLOGUE + CFB_MAGIC.size() )
255 uint64_t stated = readU32( aPayload.data() );
256 uint64_t length = readU32( aPayload.data() + 22 );
260 if( stated != length + 22 || length < CFB_MAGIC.size() )
263 if( !std::equal( CFB_MAGIC.begin(), CFB_MAGIC.end(), aPayload.begin() + PROLOGUE ) )
268 size_t extent = std::min<size_t>( length, aPayload.size() - PROLOGUE );
270 return std::make_pair( PROLOGUE, extent );
276 if( !aCfb || aSize < 512 || aSize > MAX_CFB_BYTES )
281 CFB::CompoundFileReader reader( aCfb, aSize );
282 const CFB::COMPOUND_FILE_ENTRY* contents =
nullptr;
283 const CFB::COMPOUND_FILE_ENTRY* presentation =
nullptr;
284 const CFB::COMPOUND_FILE_ENTRY* native =
nullptr;
286 reader.EnumFiles( reader.GetRootEntry(), -1,
287 [&](
const CFB::COMPOUND_FILE_ENTRY* aEntry,
const CFB::utf16string&,
int )
289 if( !reader.IsStream( aEntry ) )
292 if( entryNameIs( aEntry, u
"CONTENTS" ) )
294 else if( entryNameIs( aEntry, u
"\x02OlePres000" ) )
295 presentation = aEntry;
296 else if( entryNameIs( aEntry, u
"\x01Ole10Native" ) )
319 return classifyNative( readStream( reader, native ) );
321 catch(
const std::exception& )
338 std::vector<uint8_t> compound( aPayload.begin() + located->first,
339 aPayload.begin() + located->first + located->second );
340 compound.resize( ( compound.size() + 511 ) & ~
size_t( 511 ) );
348 if( aDib.size() < 40 || aDib.size() > std::numeric_limits<uint32_t>::max() - 14 )
351 uint32_t biSize = readU32( aDib.data() );
353 if( biSize < 40 || biSize > 200 || biSize > aDib.size() )
356 uint32_t bitCount = readU16( aDib.data() + 14 );
357 uint32_t compression = readU32( aDib.data() + 16 );
358 uint32_t clrUsed = readU32( aDib.data() + 32 );
359 uint32_t paletteEntries = clrUsed ? clrUsed : ( bitCount <= 8 ? ( 1u << bitCount ) : 0 );
360 uint64_t pixelOffset = 14ULL + biSize + uint64_t( paletteEntries ) * 4 + ( compression == 3 ? 12 : 0 );
361 uint32_t fileSize = 14 +
static_cast<uint32_t
>( aDib.size() );
363 if( pixelOffset > fileSize )
366 std::array<uint8_t, 14> header{};
370 for(
int shift = 0; shift < 32; shift += 8 )
372 header[2 + shift / 8] =
static_cast<uint8_t
>( fileSize >> shift );
373 header[10 + shift / 8] =
static_cast<uint8_t
>( pixelOffset >> shift );
376 aOut.AppendData( header.data(), header.size() );
377 aOut.AppendData( aDib.data(), aDib.size() );
384 constexpr uint32_t c_WMFC_IDENTIFIER = 0x43464D57;
385 constexpr uint16_t c_META_ESCAPE = 0x0626;
386 constexpr uint16_t c_ENHANCED_METAFILE = 0x000F;
387 constexpr size_t c_COMMENT_HEADER_SIZE = 34;
389 size_t headerOffset = 0;
391 if( aWmf.size() >= 4 && readU32( aWmf.data() ) == 0x9AC6CDD7 )
394 if( aWmf.size() < headerOffset + 18 || readU16( aWmf.data() + headerOffset + 2 ) != 9 )
397 size_t offset = headerOffset + 18;
398 uint32_t expectedRecordCount = 0;
399 uint32_t expectedEmfSize = 0;
400 uint32_t chunkCount = 0;
401 std::vector<uint8_t> emf;
403 while( offset + 6 <= aWmf.size() )
405 uint32_t sizeWords = readU32( aWmf.data() + offset );
407 if( sizeWords < 3 || sizeWords > ( aWmf.size() - offset ) / 2 )
410 size_t recordSize =
static_cast<size_t>( sizeWords ) * 2;
412 uint16_t function = readU16( aWmf.data() + offset + 4 );
414 if( function == c_META_ESCAPE && recordSize >= 10 + c_COMMENT_HEADER_SIZE
415 && readU16( aWmf.data() + offset + 6 ) == c_ENHANCED_METAFILE )
417 uint16_t byteCount = readU16( aWmf.data() + offset + 8 );
419 if( byteCount < c_COMMENT_HEADER_SIZE ||
static_cast<size_t>( byteCount ) + 10 > recordSize )
422 const uint8_t* header = aWmf.data() + offset + 10;
424 if( readU32( header ) != c_WMFC_IDENTIFIER || readU32( header + 4 ) != 1 )
427 uint32_t recordCount = readU32( header + 18 );
428 uint32_t chunkSize = readU32( header + 22 );
429 uint32_t remaining = readU32( header + 26 );
430 uint32_t emfSize = readU32( header + 30 );
432 if( chunkSize > byteCount - c_COMMENT_HEADER_SIZE || emfSize < remaining )
435 if( chunkCount == 0 )
437 expectedRecordCount = recordCount;
438 expectedEmfSize = emfSize;
440 if( emfSize > aWmf.size() )
443 emf.reserve( emfSize );
445 else if( recordCount != expectedRecordCount || emfSize != expectedEmfSize )
450 if( chunkSize > expectedEmfSize - emf.size()
451 || remaining != expectedEmfSize - emf.size() - chunkSize )
454 emf.insert( emf.end(), header + c_COMMENT_HEADER_SIZE, header + c_COMMENT_HEADER_SIZE + chunkSize );
458 offset += recordSize;
464 if( chunkCount == 0 || chunkCount != expectedRecordCount || emf.size() != expectedEmfSize || emf.size() < 52
465 || readU32( emf.data() ) != 1 || readU32( emf.data() + 40 ) != 0x464D4520
466 || readU32( emf.data() + 48 ) != emf.size() )
477 constexpr std::string_view ciMarker =
"~~CI_IMAGE~~";
478 constexpr size_t DIB_HEADER = 40;
481 if( aPayload.size() < DIB_HEADER )
484 uint32_t headerSize = readU32( aPayload.data() );
485 int32_t width =
static_cast<int32_t
>( readU32( aPayload.data() + 4 ) );
486 int32_t height =
static_cast<int32_t
>( readU32( aPayload.data() + 8 ) );
487 uint16_t planes = readU16( aPayload.data() + 12 );
488 uint16_t depth = readU16( aPayload.data() + 14 );
489 uint32_t paletteEntries = readU32( aPayload.data() + 32 );
491 if( headerSize != DIB_HEADER || planes != 1 || width <= 0 || height == 0 )
494 if( depth != 1 && depth != 4 && depth != 8 && depth != 16 && depth != 24 && depth != 32 )
497 if( !paletteEntries && depth <= 8 )
498 paletteEntries = uint32_t( 1 ) << depth;
500 uint64_t rows = height < 0 ? -
static_cast<int64_t
>( height ) : height;
501 uint64_t stride = ( (
static_cast<uint64_t
>( width ) * depth + 31 ) / 32 ) * 4;
502 uint64_t headerBytes = DIB_HEADER +
static_cast<uint64_t
>( paletteEntries ) * 4;
504 if( headerBytes > aPayload.size() || rows > ( aPayload.size() - headerBytes ) / stride )
507 size_t previewSize =
static_cast<size_t>( headerBytes + stride * rows );
509 if( aPayload.size() - previewSize < ciMarker.size() )
512 auto marker = aPayload.begin() + previewSize;
514 if( !std::equal( ciMarker.begin(), ciMarker.end(), marker ) )
521 auto header = marker + ciMarker.size();
523 if( aPayload.end() - header < 2 || *header != 0 )
526 size_t digits = header[1];
528 if( digits < 1 || digits > 10 ||
static_cast<size_t>( aPayload.end() - header ) < 2 + digits )
533 for(
size_t i = 0; i < digits; ++i )
535 uint8_t
byte = header[2 + i];
537 if(
byte <
'0' ||
byte >
'9' )
540 length = length * 10 +
static_cast<size_t>(
byte -
'0' );
543 auto image = header + 2 + digits;
545 if(
static_cast<size_t>( aPayload.end() -
image ) < length )
548 return std::vector<uint8_t>(
image,
image + length );
553 double aTargetAspect )
555 if( aNaturalWidth <= 0 || aNaturalHeight <= 0 || aMaxWidth <= 0 || aMaxHeight <= 0 )
558 if( !std::isfinite( aTargetAspect ) )
561 if( aTargetAspect > 0.0 )
563 double width = aMaxWidth;
564 double height = width / aTargetAspect;
566 if( height > aMaxHeight )
569 width = height * aTargetAspect;
575 double scale = std::min(
static_cast<double>( aMaxWidth ) / aNaturalWidth,
576 static_cast<double>( aMaxHeight ) / aNaturalHeight );
582bool OleRenderWmf(
const std::vector<uint8_t>& aWmf,
int aMaxWidth,
int aMaxHeight, wxImage& aImage,
583 double aTargetAspect )
585 if( aWmf.empty() || aWmf.size() > MAX_STREAM_BYTES
586 || aWmf.size() >
static_cast<size_t>( std::numeric_limits<long>::max() ) )
596 uint32_t standardWords =
static_cast<uint32_t
>( (
normalized.size() - 22 ) / 2 );
598 for(
int shift = 0; shift < 32; shift += 8 )
599 normalized[28 + shift / 8] =
static_cast<uint8_t
>( standardWords >> shift );
602 wmfAPI* api =
nullptr;
603 wmfAPI_Options options{};
605 char* fontDirs[] = { fontDir.data(),
nullptr };
606 options.function = wmf_gd_function;
607 options.fontdirs = fontDirs;
609 constexpr unsigned long flags = WMF_OPT_FUNCTION | WMF_OPT_FONTDIRS | WMF_OPT_SYS_FONTS
610 | WMF_OPT_IGNORE_NONFATAL | WMF_OPT_NO_DEBUG | WMF_OPT_NO_ERROR;
612 if( wmf_api_create( &api, flags, &options ) != wmf_E_None )
615 std::unique_ptr<wmfAPI,
decltype( &wmf_api_destroy )> apiOwner( api, wmf_api_destroy );
617 wmf_gd_t* gd = WMF_GD_GetData( api );
618 gd->type = wmf_gd_image;
620 if( wmf_mem_open( api,
normalized.data(),
static_cast<long>(
normalized.size() ) ) != wmf_E_None )
627 if( wmf_scan( api, 0, &bbox ) != wmf_E_None )
632 unsigned int naturalWidth = 0;
633 unsigned int naturalHeight = 0;
635 if( wmf_display_size( api, &naturalWidth, &naturalHeight, 144.0, 144.0 ) != wmf_E_None || naturalWidth == 0
636 || naturalHeight == 0 )
642 std::max( 1, aMaxHeight ), aTargetAspect );
643 unsigned int width =
static_cast<unsigned int>( renderSize.
x );
644 unsigned int height =
static_cast<unsigned int>( renderSize.
y );
650 if( wmf_play( api, 0, &bbox ) != wmf_E_None )
655 int* pixels = wmf_gd_get_image_pixels( api );
657 if( !pixels || !aImage.Create( width, height,
false ) )
662 unsigned char* rgb = aImage.GetData();
664 for(
size_t i = 0; i < static_cast<size_t>( width ) * height; ++i )
666 rgb[3 * i] =
static_cast<unsigned char>( ( pixels[i] >> 16 ) & 0xFF );
667 rgb[3 * i + 1] =
static_cast<unsigned char>( ( pixels[i] >> 8 ) & 0xFF );
668 rgb[3 * i + 2] =
static_cast<unsigned char>( pixels[i] & 0xFF );
676 wxImage& aImage,
double aTargetAspect,
bool* aUsedEmbeddedEmf )
678 if( aUsedEmbeddedEmf )
679 *aUsedEmbeddedEmf =
false;
683 if( !emf.empty() &&
OleRenderEmf( emf, aMaxWidth, aMaxHeight, aImage, aTargetAspect ) )
685 if( aUsedEmbeddedEmf )
686 *aUsedEmbeddedEmf =
true;
691 return OleRenderWmf( aWmf, aMaxWidth, aMaxHeight, aImage, aTargetAspect );
697 if( aPayload.empty() )
698 return wxS(
"empty" );
700 auto starts = [&]( std::initializer_list<uint8_t> aSig,
size_t aOffset = 0 )
702 if( aPayload.size() < aOffset + aSig.size() )
705 return std::equal( aSig.begin(), aSig.end(), aPayload.begin() + aOffset );
708 if( starts( { 0x89,
'P',
'N',
'G' } ) )
710 if( starts( { 0xFF, 0xD8, 0xFF } ) )
711 return wxS(
"JPEG" );
712 if( starts( {
'G',
'I',
'F',
'8' } ) )
714 if( starts( {
'B',
'M' } ) )
716 if( starts( {
'I',
'I', 0x2A, 0x00 } ) )
717 return wxS(
"TIFF" );
718 if( starts( {
'M',
'M', 0x00, 0x2A } ) )
719 return wxS(
"TIFF" );
720 if( starts( { 0xD7, 0xCD, 0xC6, 0x9A } ) )
721 return wxS(
"placeable WMF" );
722 if( starts( { 0x01, 0x00, 0x09, 0x00 } ) )
724 if( starts( {
'E',
'M',
'F', 0x20 }, 40 ) )
726 if( starts( { 0xD0, 0xCF, 0x11, 0xE0 } ) )
727 return wxS(
"OLE compound document" );
731 for(
size_t i = 0; i < std::min<size_t>( 8, aPayload.size() ); ++i )
732 head += wxString::Format( wxS(
"%02X" ), aPayload[i] );
734 return wxString::Format( wxS(
"unrecognized, %zu bytes starting %s" ), aPayload.size(), head );
static std::optional< VECTOR2D > normalized(const VECTOR2D &aVec)
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
static const wxString & GetExecutablePath()
const wxChar *const traceSchPlugin
Flag to enable legacy schematic plugin debug output.
bool OleRenderEmf(const std::vector< uint8_t > &aEmf, int aMaxWidth, int aMaxHeight, wxImage &aImage, double aTargetAspect)
wxString OleLibWmfFontDirectory()
Locate the bundled libwmf fonts directory, falling back to a search above the executable so running f...
std::vector< uint8_t > OleExtractCiImage(const std::vector< uint8_t > &aPayload)
The CI marker follows the preview DIB; the raster has a counted decimal length.
VECTOR2I OleWmfRenderSize(int aNaturalWidth, int aNaturalHeight, int aMaxWidth, int aMaxHeight, double aTargetAspect)
bool OleRenderWmf(const std::vector< uint8_t > &aWmf, int aMaxWidth, int aMaxHeight, wxImage &aImage, double aTargetAspect)
OLE_IMAGE_PAYLOAD ExtractOleImageFromPayload(const std::vector< uint8_t > &aPayload)
Read the picture out of an OLE object payload that carries the 26-byte prologue.
bool OleRenderMetafilePreview(const std::vector< uint8_t > &aWmf, int aMaxWidth, int aMaxHeight, wxImage &aImage, double aTargetAspect, bool *aUsedEmbeddedEmf)
Render a metafile preview, preferring an EMF the WMF carries over the WMF itself.
std::vector< uint8_t > OleExtractEmbeddedEmf(const std::vector< uint8_t > &aWmf)
Reassemble an EMF carried by WMF META_ESCAPE_ENHANCED_METAFILE records.
bool OleMakeBmpFromDib(const std::vector< uint8_t > &aDib, wxMemoryBuffer &aOut)
wxString OleDescribeImagePayload(const std::vector< uint8_t > &aPayload)
Include leading bytes when the payload format is unknown.
OLE_IMAGE_PAYLOAD ExtractOleImage(const uint8_t *aCfb, size_t aSize)
Prefer CONTENTS, then OlePres000, then the native stream.
std::optional< std::pair< size_t, size_t > > OleEmbeddedCompoundFile(const std::vector< uint8_t > &aPayload)
The 26-byte prologue stores length at offset 22 and length plus 22 at offset 0.
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.
VECTOR2< int32_t > VECTOR2I