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;
102wxString wmfFontDirectory()
106 fontDir.AppendDir( wxS(
"libwmf" ) );
107 fontDir.AppendDir( wxS(
"fonts" ) );
109 if( fontDir.DirExists() )
110 return fontDir.GetPath();
115 for(
int depth = 0; depth < 4; ++depth )
117 wxFileName candidate = buildDir;
118 candidate.AppendDir( wxS(
"libwmf" ) );
119 candidate.AppendDir( wxS(
"fonts" ) );
121 if( candidate.DirExists() )
122 return candidate.GetPath();
124 buildDir.RemoveLastDir();
127 wxLogTrace(
traceSchPlugin, wxS(
"libwmf fonts missing (looked for %s); WMF rendering will fail" ),
130 return fontDir.GetPath();
134OLE_IMAGE_PAYLOAD classifyContents( std::vector<uint8_t> aData, std::string aName )
136 if( ( aData.size() >= 2 && aData[0] ==
'B' && aData[1] ==
'M' )
137 || ( aData.size() >= 4 && aData[0] == 0x89 && aData[1] ==
'P' && aData[2] ==
'N' && aData[3] ==
'G' )
138 || ( aData.size() >= 3 && aData[0] == 0xFF && aData[1] == 0xD8 && aData[2] == 0xFF ) )
141 if( aData.size() >= 40 )
144 uint32_t headerSize = readU32( aData.data() );
145 int32_t width =
static_cast<int32_t
>( readU32( aData.data() + 4 ) );
146 int32_t height =
static_cast<int32_t
>( readU32( aData.data() + 8 ) );
147 uint16_t planes = readU16( aData.data() + 12 );
148 uint16_t bitCount = readU16( aData.data() + 14 );
150 bool depthIsValid = bitCount == 1 || bitCount == 4 || bitCount == 8 || bitCount == 16
151 || bitCount == 24 || bitCount == 32;
153 if( headerSize >= 40 && headerSize <= 200 && planes == 1 && depthIsValid && width != 0
160 if( isWmf( aData.data(), aData.size() ) )
169 if( aData.size() < 40 )
172 uint32_t clipboardFormat = readU32( aData.data() + 4 );
175 if( clipboardFormat == 3 || clipboardFormat == 14 )
177 else if( clipboardFormat == 8 )
183 return { type, { aData.begin() + 40, aData.end() },
"\\x02OlePres000" };
190 if( aData.size() < 6 )
193 size_t stated = readU32( aData.data() );
195 if( stated > aData.size() - 4 )
199 size_t length = stated;
201 if( readU16( aData.data() + 4 ) == 0x0002 )
207 for(
int i = 0; i < 2; ++i )
209 while( offset < aData.size() && aData[offset] != 0 )
212 if( offset >= aData.size() )
218 if( aData.size() - offset < 8 )
223 size_t pathLength = readU32( aData.data() + offset );
226 if( pathLength > aData.size() - offset )
229 offset += pathLength;
231 if( aData.size() - offset < 4 )
234 length = readU32( aData.data() + offset );
237 if( length > aData.size() - offset )
241 std::vector<uint8_t> body( aData.begin() + offset, aData.begin() + offset + length );
243 return classifyContents( std::move( body ),
"\\x01Ole10Native" );
252 constexpr size_t PROLOGUE = 26;
254 if( aPayload.size() < PROLOGUE + CFB_MAGIC.size() )
257 uint64_t stated = readU32( aPayload.data() );
258 uint64_t length = readU32( aPayload.data() + 22 );
262 if( stated != length + 22 || length < CFB_MAGIC.size() )
265 if( !std::equal( CFB_MAGIC.begin(), CFB_MAGIC.end(), aPayload.begin() + PROLOGUE ) )
270 size_t extent = std::min<size_t>( length, aPayload.size() - PROLOGUE );
272 return std::make_pair( PROLOGUE, extent );
278 if( !aCfb || aSize < 512 || aSize > MAX_CFB_BYTES )
283 CFB::CompoundFileReader reader( aCfb, aSize );
284 const CFB::COMPOUND_FILE_ENTRY* contents =
nullptr;
285 const CFB::COMPOUND_FILE_ENTRY* presentation =
nullptr;
286 const CFB::COMPOUND_FILE_ENTRY* native =
nullptr;
288 reader.EnumFiles( reader.GetRootEntry(), -1,
289 [&](
const CFB::COMPOUND_FILE_ENTRY* aEntry,
const CFB::utf16string&,
int )
291 if( !reader.IsStream( aEntry ) )
294 if( entryNameIs( aEntry, u
"CONTENTS" ) )
296 else if( entryNameIs( aEntry, u
"\x02OlePres000" ) )
297 presentation = aEntry;
298 else if( entryNameIs( aEntry, u
"\x01Ole10Native" ) )
321 return classifyNative( readStream( reader, native ) );
323 catch(
const std::exception& )
340 std::vector<uint8_t> compound( aPayload.begin() + located->first,
341 aPayload.begin() + located->first + located->second );
342 compound.resize( ( compound.size() + 511 ) & ~
size_t( 511 ) );
350 if( aDib.size() < 40 || aDib.size() > std::numeric_limits<uint32_t>::max() - 14 )
353 uint32_t biSize = readU32( aDib.data() );
355 if( biSize < 40 || biSize > 200 || biSize > aDib.size() )
358 uint32_t bitCount = readU16( aDib.data() + 14 );
359 uint32_t compression = readU32( aDib.data() + 16 );
360 uint32_t clrUsed = readU32( aDib.data() + 32 );
361 uint32_t paletteEntries = clrUsed ? clrUsed : ( bitCount <= 8 ? ( 1u << bitCount ) : 0 );
362 uint64_t pixelOffset = 14ULL + biSize + uint64_t( paletteEntries ) * 4 + ( compression == 3 ? 12 : 0 );
363 uint32_t fileSize = 14 +
static_cast<uint32_t
>( aDib.size() );
365 if( pixelOffset > fileSize )
368 std::array<uint8_t, 14> header{};
372 for(
int shift = 0; shift < 32; shift += 8 )
374 header[2 + shift / 8] =
static_cast<uint8_t
>( fileSize >> shift );
375 header[10 + shift / 8] =
static_cast<uint8_t
>( pixelOffset >> shift );
378 aOut.AppendData( header.data(), header.size() );
379 aOut.AppendData( aDib.data(), aDib.size() );
386 constexpr uint32_t c_WMFC_IDENTIFIER = 0x43464D57;
387 constexpr uint16_t c_META_ESCAPE = 0x0626;
388 constexpr uint16_t c_ENHANCED_METAFILE = 0x000F;
389 constexpr size_t c_COMMENT_HEADER_SIZE = 34;
391 size_t headerOffset = 0;
393 if( aWmf.size() >= 4 && readU32( aWmf.data() ) == 0x9AC6CDD7 )
396 if( aWmf.size() < headerOffset + 18 || readU16( aWmf.data() + headerOffset + 2 ) != 9 )
399 size_t offset = headerOffset + 18;
400 uint32_t expectedRecordCount = 0;
401 uint32_t expectedEmfSize = 0;
402 uint32_t chunkCount = 0;
403 std::vector<uint8_t> emf;
405 while( offset + 6 <= aWmf.size() )
407 uint32_t sizeWords = readU32( aWmf.data() + offset );
409 if( sizeWords < 3 || sizeWords > ( aWmf.size() - offset ) / 2 )
412 size_t recordSize =
static_cast<size_t>( sizeWords ) * 2;
414 uint16_t function = readU16( aWmf.data() + offset + 4 );
416 if( function == c_META_ESCAPE && recordSize >= 10 + c_COMMENT_HEADER_SIZE
417 && readU16( aWmf.data() + offset + 6 ) == c_ENHANCED_METAFILE )
419 uint16_t byteCount = readU16( aWmf.data() + offset + 8 );
421 if( byteCount < c_COMMENT_HEADER_SIZE ||
static_cast<size_t>( byteCount ) + 10 > recordSize )
424 const uint8_t* header = aWmf.data() + offset + 10;
426 if( readU32( header ) != c_WMFC_IDENTIFIER || readU32( header + 4 ) != 1 )
429 uint32_t recordCount = readU32( header + 18 );
430 uint32_t chunkSize = readU32( header + 22 );
431 uint32_t remaining = readU32( header + 26 );
432 uint32_t emfSize = readU32( header + 30 );
434 if( chunkSize > byteCount - c_COMMENT_HEADER_SIZE || emfSize < remaining )
437 if( chunkCount == 0 )
439 expectedRecordCount = recordCount;
440 expectedEmfSize = emfSize;
442 if( emfSize > aWmf.size() )
445 emf.reserve( emfSize );
447 else if( recordCount != expectedRecordCount || emfSize != expectedEmfSize )
452 if( chunkSize > expectedEmfSize - emf.size()
453 || remaining != expectedEmfSize - emf.size() - chunkSize )
456 emf.insert( emf.end(), header + c_COMMENT_HEADER_SIZE, header + c_COMMENT_HEADER_SIZE + chunkSize );
460 offset += recordSize;
466 if( chunkCount == 0 || chunkCount != expectedRecordCount || emf.size() != expectedEmfSize || emf.size() < 52
467 || readU32( emf.data() ) != 1 || readU32( emf.data() + 40 ) != 0x464D4520
468 || readU32( emf.data() + 48 ) != emf.size() )
479 constexpr std::string_view ciMarker =
"~~CI_IMAGE~~";
480 constexpr size_t DIB_HEADER = 40;
483 if( aPayload.size() < DIB_HEADER )
486 uint32_t headerSize = readU32( aPayload.data() );
487 int32_t width =
static_cast<int32_t
>( readU32( aPayload.data() + 4 ) );
488 int32_t height =
static_cast<int32_t
>( readU32( aPayload.data() + 8 ) );
489 uint16_t planes = readU16( aPayload.data() + 12 );
490 uint16_t depth = readU16( aPayload.data() + 14 );
491 uint32_t paletteEntries = readU32( aPayload.data() + 32 );
493 if( headerSize != DIB_HEADER || planes != 1 || width <= 0 || height == 0 )
496 if( depth != 1 && depth != 4 && depth != 8 && depth != 16 && depth != 24 && depth != 32 )
499 if( !paletteEntries && depth <= 8 )
500 paletteEntries = uint32_t( 1 ) << depth;
502 uint64_t rows = height < 0 ? -
static_cast<int64_t
>( height ) : height;
503 uint64_t stride = ( (
static_cast<uint64_t
>( width ) * depth + 31 ) / 32 ) * 4;
504 uint64_t headerBytes = DIB_HEADER +
static_cast<uint64_t
>( paletteEntries ) * 4;
506 if( headerBytes > aPayload.size() || rows > ( aPayload.size() - headerBytes ) / stride )
509 size_t previewSize =
static_cast<size_t>( headerBytes + stride * rows );
511 if( aPayload.size() - previewSize < ciMarker.size() )
514 auto marker = aPayload.begin() + previewSize;
516 if( !std::equal( ciMarker.begin(), ciMarker.end(), marker ) )
523 auto header = marker + ciMarker.size();
525 if( aPayload.end() - header < 2 || *header != 0 )
528 size_t digits = header[1];
530 if( digits < 1 || digits > 10 ||
static_cast<size_t>( aPayload.end() - header ) < 2 + digits )
535 for(
size_t i = 0; i < digits; ++i )
537 uint8_t
byte = header[2 + i];
539 if(
byte <
'0' ||
byte >
'9' )
542 length = length * 10 +
static_cast<size_t>(
byte -
'0' );
545 auto image = header + 2 + digits;
547 if(
static_cast<size_t>( aPayload.end() -
image ) < length )
550 return std::vector<uint8_t>(
image,
image + length );
555 double aTargetAspect )
557 if( aNaturalWidth <= 0 || aNaturalHeight <= 0 || aMaxWidth <= 0 || aMaxHeight <= 0 )
560 if( !std::isfinite( aTargetAspect ) )
563 if( aTargetAspect > 0.0 )
565 double width = aMaxWidth;
566 double height = width / aTargetAspect;
568 if( height > aMaxHeight )
571 width = height * aTargetAspect;
577 double scale = std::min(
static_cast<double>( aMaxWidth ) / aNaturalWidth,
578 static_cast<double>( aMaxHeight ) / aNaturalHeight );
584bool OleRenderWmf(
const std::vector<uint8_t>& aWmf,
int aMaxWidth,
int aMaxHeight, wxImage& aImage,
585 double aTargetAspect )
587 if( aWmf.empty() || aWmf.size() > MAX_STREAM_BYTES
588 || aWmf.size() >
static_cast<size_t>( std::numeric_limits<long>::max() ) )
594 std::vector<uint8_t> normalized = aWmf;
596 if( normalized.size() >= 40 && readU32( normalized.data() ) == 0x9AC6CDD7 )
598 uint32_t standardWords =
static_cast<uint32_t
>( ( normalized.size() - 22 ) / 2 );
600 for(
int shift = 0; shift < 32; shift += 8 )
601 normalized[28 + shift / 8] =
static_cast<uint8_t
>( standardWords >> shift );
604 wmfAPI* api =
nullptr;
605 wmfAPI_Options options{};
606 wxCharBuffer fontDir = wmfFontDirectory().utf8_str();
607 char* fontDirs[] = { fontDir.data(),
nullptr };
608 options.function = wmf_gd_function;
609 options.fontdirs = fontDirs;
611 constexpr unsigned long flags = WMF_OPT_FUNCTION | WMF_OPT_FONTDIRS | WMF_OPT_SYS_FONTS
612 | WMF_OPT_IGNORE_NONFATAL | WMF_OPT_NO_DEBUG | WMF_OPT_NO_ERROR;
614 if( wmf_api_create( &api, flags, &options ) != wmf_E_None )
617 std::unique_ptr<wmfAPI,
decltype( &wmf_api_destroy )> apiOwner( api, wmf_api_destroy );
619 wmf_gd_t* gd = WMF_GD_GetData( api );
620 gd->type = wmf_gd_image;
622 if( wmf_mem_open( api, normalized.data(),
static_cast<long>( normalized.size() ) ) != wmf_E_None )
629 if( wmf_scan( api, 0, &bbox ) != wmf_E_None )
634 unsigned int naturalWidth = 0;
635 unsigned int naturalHeight = 0;
637 if( wmf_display_size( api, &naturalWidth, &naturalHeight, 144.0, 144.0 ) != wmf_E_None || naturalWidth == 0
638 || naturalHeight == 0 )
644 std::max( 1, aMaxHeight ), aTargetAspect );
645 unsigned int width =
static_cast<unsigned int>( renderSize.
x );
646 unsigned int height =
static_cast<unsigned int>( renderSize.
y );
652 if( wmf_play( api, 0, &bbox ) != wmf_E_None )
657 int* pixels = wmf_gd_get_image_pixels( api );
659 if( !pixels || !aImage.Create( width, height,
false ) )
664 unsigned char* rgb = aImage.GetData();
666 for(
size_t i = 0; i < static_cast<size_t>( width ) * height; ++i )
668 rgb[3 * i] =
static_cast<unsigned char>( ( pixels[i] >> 16 ) & 0xFF );
669 rgb[3 * i + 1] =
static_cast<unsigned char>( ( pixels[i] >> 8 ) & 0xFF );
670 rgb[3 * i + 2] =
static_cast<unsigned char>( pixels[i] & 0xFF );
678 wxImage& aImage,
double aTargetAspect,
bool* aUsedEmbeddedEmf )
680 if( aUsedEmbeddedEmf )
681 *aUsedEmbeddedEmf =
false;
685 if( !emf.empty() &&
OleRenderEmf( emf, aMaxWidth, aMaxHeight, aImage, aTargetAspect ) )
687 if( aUsedEmbeddedEmf )
688 *aUsedEmbeddedEmf =
true;
693 return OleRenderWmf( aWmf, aMaxWidth, aMaxHeight, aImage, aTargetAspect );
699 if( aPayload.empty() )
700 return wxS(
"empty" );
702 auto starts = [&]( std::initializer_list<uint8_t> aSig,
size_t aOffset = 0 )
704 if( aPayload.size() < aOffset + aSig.size() )
707 return std::equal( aSig.begin(), aSig.end(), aPayload.begin() + aOffset );
710 if( starts( { 0x89,
'P',
'N',
'G' } ) )
712 if( starts( { 0xFF, 0xD8, 0xFF } ) )
713 return wxS(
"JPEG" );
714 if( starts( {
'G',
'I',
'F',
'8' } ) )
716 if( starts( {
'B',
'M' } ) )
718 if( starts( {
'I',
'I', 0x2A, 0x00 } ) )
719 return wxS(
"TIFF" );
720 if( starts( {
'M',
'M', 0x00, 0x2A } ) )
721 return wxS(
"TIFF" );
722 if( starts( { 0xD7, 0xCD, 0xC6, 0x9A } ) )
723 return wxS(
"placeable WMF" );
724 if( starts( { 0x01, 0x00, 0x09, 0x00 } ) )
726 if( starts( {
'E',
'M',
'F', 0x20 }, 40 ) )
728 if( starts( { 0xD0, 0xCF, 0x11, 0xE0 } ) )
729 return wxS(
"OLE compound document" );
733 for(
size_t i = 0; i < std::min<size_t>( 8, aPayload.size() ); ++i )
734 head += wxString::Format( wxS(
"%02X" ), aPayload[i] );
736 return wxString::Format( wxS(
"unrecognized, %zu bytes starting %s" ), aPayload.size(), head );
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)
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