KiCad PCB EDA Suite
Loading...
Searching...
No Matches
ole_image.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your option)
9 * any later version.
10 */
11
12#include <sch_io/ole_image.h>
13
14#include <algorithm>
15#include <boost/endian/conversion.hpp>
16#include <memory>
17#include <utility>
18#include <optional>
19#include <array>
20#include <cmath>
21#include <limits>
22#include <string_view>
23
24#include <wx/buffer.h>
25#include <wx/filename.h>
26#include <wx/image.h>
27
28#include <math/vector2d.h>
29#include <wx/log.h>
30
31#include <compoundfilereader.h>
32#include <paths.h>
33#include <trace_helpers.h>
34
35#include <libwmf/api.h>
36#include <libwmf/gd.h>
37
38
39namespace
40{
41
42constexpr std::array<uint8_t, 8> CFB_MAGIC = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
43
44constexpr size_t MAX_CFB_BYTES = 256 * 1024 * 1024;
45constexpr size_t MAX_STREAM_BYTES = 64 * 1024 * 1024;
46
47
48uint16_t readU16( const uint8_t* aData )
49{
50 return boost::endian::load_little_u16( aData );
51}
52
53
54uint32_t readU32( const uint8_t* aData )
55{
56 return boost::endian::load_little_u32( aData );
57}
58
59
60bool entryNameIs( const CFB::COMPOUND_FILE_ENTRY* aEntry, std::u16string_view aName )
61{
62 // nameLen counts bytes including the terminator and comes from the file, so require the exact
63 // encoded length rather than letting an odd value divide down onto a real name
64 if( aEntry->nameLen != 2 * ( aName.size() + 1 ) || aEntry->name[aName.size()] != 0 )
65 return false;
66
67 for( size_t i = 0; i < aName.size(); ++i )
68 {
69 if( aEntry->name[i] != static_cast<uint16_t>( aName[i] ) )
70 return false;
71 }
72
73 return true;
74}
75
76
77std::vector<uint8_t> readStream( const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
78{
79 uint64_t size = aReader.GetStreamSize( aEntry );
80
81 if( size > MAX_STREAM_BYTES || size > aReader.GetBufferLen() || size > std::numeric_limits<size_t>::max() )
82 return {};
83
84 std::vector<uint8_t> data( static_cast<size_t>( size ) );
85
86 if( !data.empty() )
87 aReader.ReadFile( aEntry, 0, reinterpret_cast<char*>( data.data() ), data.size() );
88
89 return data;
90}
91
92
93bool isWmf( const uint8_t* aData, size_t aSize )
94{
95 if( aSize >= 4 && readU32( aData ) == 0x9AC6CDD7 )
96 return true;
97
98 return aSize >= 4 && ( readU16( aData ) == 1 || readU16( aData ) == 2 ) && readU16( aData + 2 ) == 9;
99}
100
101OLE_IMAGE_PAYLOAD classifyContents( std::vector<uint8_t> aData, std::string aName )
102{
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 ) )
106 return { OLE_IMAGE_TYPE::BMP, std::move( aData ), std::move( aName ) };
107
108 if( aData.size() >= 40 )
109 {
110 // Validate the DIB fields before selecting CONTENTS and hiding a valid presentation.
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 );
116
117 bool depthIsValid = bitCount == 1 || bitCount == 4 || bitCount == 8 || bitCount == 16
118 || bitCount == 24 || bitCount == 32;
119
120 if( headerSize >= 40 && headerSize <= 200 && planes == 1 && depthIsValid && width != 0
121 && height != 0 )
122 {
123 return { OLE_IMAGE_TYPE::DIB, std::move( aData ), std::move( aName ) };
124 }
125 }
126
127 if( isWmf( aData.data(), aData.size() ) )
128 return { OLE_IMAGE_TYPE::WMF, std::move( aData ), std::move( aName ) };
129
130 return {};
131}
132
133
134OLE_IMAGE_PAYLOAD classifyPresentation( std::vector<uint8_t> aData )
135{
136 if( aData.size() < 40 )
137 return {};
138
139 uint32_t clipboardFormat = readU32( aData.data() + 4 );
141
142 if( clipboardFormat == 3 || clipboardFormat == 14 )
143 type = OLE_IMAGE_TYPE::WMF;
144 else if( clipboardFormat == 8 )
145 type = OLE_IMAGE_TYPE::DIB;
146
147 if( type == OLE_IMAGE_TYPE::NONE )
148 return {};
149
150 return { type, { aData.begin() + 40, aData.end() }, "\\x02OlePres000" };
151}
152
153
154OLE_IMAGE_PAYLOAD classifyNative( const std::vector<uint8_t>& aData )
155{
156 // Ole10Native starts with a length. A 0x0002 flag selects the Packager header.
157 if( aData.size() < 6 )
158 return {};
159
160 size_t stated = readU32( aData.data() );
161
162 if( stated > aData.size() - 4 )
163 return {};
164
165 size_t offset = 4;
166 size_t length = stated;
167
168 if( readU16( aData.data() + 4 ) == 0x0002 )
169 {
170 offset += 2;
171
172 // The label and the originating path are NUL terminated; the temporary path that
173 // follows them is counted instead.
174 for( int i = 0; i < 2; ++i )
175 {
176 while( offset < aData.size() && aData[offset] != 0 )
177 ++offset;
178
179 if( offset >= aData.size() )
180 return {};
181
182 ++offset;
183 }
184
185 if( aData.size() - offset < 8 )
186 return {};
187
188 offset += 4;
189
190 size_t pathLength = readU32( aData.data() + offset );
191 offset += 4;
192
193 if( pathLength > aData.size() - offset )
194 return {};
195
196 offset += pathLength;
197
198 if( aData.size() - offset < 4 )
199 return {};
200
201 length = readU32( aData.data() + offset );
202 offset += 4;
203
204 if( length > aData.size() - offset )
205 return {};
206 }
207
208 std::vector<uint8_t> body( aData.begin() + offset, aData.begin() + offset + length );
209
210 return classifyContents( std::move( body ), "\\x01Ole10Native" );
211}
212
213
214} // namespace
215
217{
218 wxFileName fontDir;
219 fontDir.AssignDir( PATHS::GetStockDataPath() );
220 fontDir.AppendDir( wxS( "libwmf" ) );
221 fontDir.AppendDir( wxS( "fonts" ) );
222
223 if( fontDir.DirExists() )
224 return fontDir.GetPath();
225
226 wxFileName buildDir;
227 buildDir.AssignDir( PATHS::GetExecutablePath() );
228
229 for( int depth = 0; depth < 4; ++depth )
230 {
231 wxFileName candidate = buildDir;
232 candidate.AppendDir( wxS( "libwmf" ) );
233 candidate.AppendDir( wxS( "fonts" ) );
234
235 if( candidate.DirExists() )
236 return candidate.GetPath();
237
238 buildDir.RemoveLastDir();
239 }
240
241 wxLogTrace( traceSchPlugin, wxS( "libwmf fonts missing (looked for %s); WMF rendering will fail" ),
242 fontDir.GetPath() );
243
244 return fontDir.GetPath();
245}
246
247
248std::optional<std::pair<size_t, size_t>> OleEmbeddedCompoundFile( const std::vector<uint8_t>& aPayload )
249{
250 constexpr size_t PROLOGUE = 26;
251
252 if( aPayload.size() < PROLOGUE + CFB_MAGIC.size() )
253 return std::nullopt;
254
255 uint64_t stated = readU32( aPayload.data() );
256 uint64_t length = readU32( aPayload.data() + 22 );
257
258 // The two length fields must agree. Capture truncates the unused tail of the final sector,
259 // so the length is not always a multiple of 512; never round it up.
260 if( stated != length + 22 || length < CFB_MAGIC.size() )
261 return std::nullopt;
262
263 if( !std::equal( CFB_MAGIC.begin(), CFB_MAGIC.end(), aPayload.begin() + PROLOGUE ) )
264 return std::nullopt;
265
266 // A container cut short of its stated length still reads; the compound file reader pads the
267 // final sector. Clamping keeps that working without letting the length address absent bytes.
268 size_t extent = std::min<size_t>( length, aPayload.size() - PROLOGUE );
269
270 return std::make_pair( PROLOGUE, extent );
271}
272
273
274OLE_IMAGE_PAYLOAD ExtractOleImage( const uint8_t* aCfb, size_t aSize )
275{
276 if( !aCfb || aSize < 512 || aSize > MAX_CFB_BYTES )
277 return {};
278
279 try
280 {
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;
285
286 reader.EnumFiles( reader.GetRootEntry(), -1,
287 [&]( const CFB::COMPOUND_FILE_ENTRY* aEntry, const CFB::utf16string&, int )
288 {
289 if( !reader.IsStream( aEntry ) )
290 return 0;
291
292 if( entryNameIs( aEntry, u"CONTENTS" ) )
293 contents = aEntry;
294 else if( entryNameIs( aEntry, u"\x02OlePres000" ) )
295 presentation = aEntry;
296 else if( entryNameIs( aEntry, u"\x01Ole10Native" ) )
297 native = aEntry;
298
299 return 0;
300 } );
301
302 if( contents )
303 {
304 OLE_IMAGE_PAYLOAD result = classifyContents( readStream( reader, contents ), "CONTENTS" );
305
306 if( result.type != OLE_IMAGE_TYPE::NONE )
307 return result;
308 }
309
310 if( presentation )
311 {
312 OLE_IMAGE_PAYLOAD result = classifyPresentation( readStream( reader, presentation ) );
313
314 if( result.type != OLE_IMAGE_TYPE::NONE )
315 return result;
316 }
317
318 if( native )
319 return classifyNative( readStream( reader, native ) );
320 }
321 catch( const std::exception& )
322 {
323 }
324
325 return {};
326}
327
328
329OLE_IMAGE_PAYLOAD ExtractOleImageFromPayload( const std::vector<uint8_t>& aPayload )
330{
331 std::optional<std::pair<size_t, size_t>> located = OleEmbeddedCompoundFile( aPayload );
332
333 if( !located )
334 return {};
335
336 // The reader wants whole sectors, and a container cut short of its stated length is still
337 // readable once the final one is padded.
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 ) );
341
342 return ExtractOleImage( compound.data(), compound.size() );
343}
344
345
346bool OleMakeBmpFromDib( const std::vector<uint8_t>& aDib, wxMemoryBuffer& aOut )
347{
348 if( aDib.size() < 40 || aDib.size() > std::numeric_limits<uint32_t>::max() - 14 )
349 return false;
350
351 uint32_t biSize = readU32( aDib.data() );
352
353 if( biSize < 40 || biSize > 200 || biSize > aDib.size() )
354 return false;
355
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() );
362
363 if( pixelOffset > fileSize )
364 return false;
365
366 std::array<uint8_t, 14> header{};
367 header[0] = 'B';
368 header[1] = 'M';
369
370 for( int shift = 0; shift < 32; shift += 8 )
371 {
372 header[2 + shift / 8] = static_cast<uint8_t>( fileSize >> shift );
373 header[10 + shift / 8] = static_cast<uint8_t>( pixelOffset >> shift );
374 }
375
376 aOut.AppendData( header.data(), header.size() );
377 aOut.AppendData( aDib.data(), aDib.size() );
378 return true;
379}
380
381
382std::vector<uint8_t> OleExtractEmbeddedEmf( const std::vector<uint8_t>& aWmf )
383{
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;
388
389 size_t headerOffset = 0;
390
391 if( aWmf.size() >= 4 && readU32( aWmf.data() ) == 0x9AC6CDD7 )
392 headerOffset = 22;
393
394 if( aWmf.size() < headerOffset + 18 || readU16( aWmf.data() + headerOffset + 2 ) != 9 )
395 return {};
396
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;
402
403 while( offset + 6 <= aWmf.size() )
404 {
405 uint32_t sizeWords = readU32( aWmf.data() + offset );
406
407 if( sizeWords < 3 || sizeWords > ( aWmf.size() - offset ) / 2 )
408 return {};
409
410 size_t recordSize = static_cast<size_t>( sizeWords ) * 2;
411
412 uint16_t function = readU16( aWmf.data() + offset + 4 );
413
414 if( function == c_META_ESCAPE && recordSize >= 10 + c_COMMENT_HEADER_SIZE
415 && readU16( aWmf.data() + offset + 6 ) == c_ENHANCED_METAFILE )
416 {
417 uint16_t byteCount = readU16( aWmf.data() + offset + 8 );
418
419 if( byteCount < c_COMMENT_HEADER_SIZE || static_cast<size_t>( byteCount ) + 10 > recordSize )
420 return {};
421
422 const uint8_t* header = aWmf.data() + offset + 10;
423
424 if( readU32( header ) != c_WMFC_IDENTIFIER || readU32( header + 4 ) != 1 )
425 return {};
426
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 );
431
432 if( chunkSize > byteCount - c_COMMENT_HEADER_SIZE || emfSize < remaining )
433 return {};
434
435 if( chunkCount == 0 )
436 {
437 expectedRecordCount = recordCount;
438 expectedEmfSize = emfSize;
439
440 if( emfSize > aWmf.size() )
441 return {};
442
443 emf.reserve( emfSize );
444 }
445 else if( recordCount != expectedRecordCount || emfSize != expectedEmfSize )
446 {
447 return {};
448 }
449
450 if( chunkSize > expectedEmfSize - emf.size()
451 || remaining != expectedEmfSize - emf.size() - chunkSize )
452 return {};
453
454 emf.insert( emf.end(), header + c_COMMENT_HEADER_SIZE, header + c_COMMENT_HEADER_SIZE + chunkSize );
455 ++chunkCount;
456 }
457
458 offset += recordSize;
459
460 if( function == 0 )
461 break;
462 }
463
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() )
467 {
468 return {};
469 }
470
471 return emf;
472}
473
474
475std::vector<uint8_t> OleExtractCiImage( const std::vector<uint8_t>& aPayload )
476{
477 constexpr std::string_view ciMarker = "~~CI_IMAGE~~";
478 constexpr size_t DIB_HEADER = 40;
479
480 // The CI marker follows the preview DIB, including its palette and padded rows.
481 if( aPayload.size() < DIB_HEADER )
482 return {};
483
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 );
490
491 if( headerSize != DIB_HEADER || planes != 1 || width <= 0 || height == 0 )
492 return {};
493
494 if( depth != 1 && depth != 4 && depth != 8 && depth != 16 && depth != 24 && depth != 32 )
495 return {};
496
497 if( !paletteEntries && depth <= 8 )
498 paletteEntries = uint32_t( 1 ) << depth;
499
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;
503
504 if( headerBytes > aPayload.size() || rows > ( aPayload.size() - headerBytes ) / stride )
505 return {};
506
507 size_t previewSize = static_cast<size_t>( headerBytes + stride * rows );
508
509 if( aPayload.size() - previewSize < ciMarker.size() )
510 return {};
511
512 auto marker = aPayload.begin() + previewSize;
513
514 if( !std::equal( ciMarker.begin(), ciMarker.end(), marker ) )
515 return {};
516
517 if( OleEmbeddedCompoundFile( aPayload ) )
518 return {};
519
520 // After the marker: NUL, digit count, decimal byte length, then the raster bytes.
521 auto header = marker + ciMarker.size();
522
523 if( aPayload.end() - header < 2 || *header != 0 )
524 return {};
525
526 size_t digits = header[1];
527
528 if( digits < 1 || digits > 10 || static_cast<size_t>( aPayload.end() - header ) < 2 + digits )
529 return {};
530
531 size_t length = 0;
532
533 for( size_t i = 0; i < digits; ++i )
534 {
535 uint8_t byte = header[2 + i];
536
537 if( byte < '0' || byte > '9' )
538 return {};
539
540 length = length * 10 + static_cast<size_t>( byte - '0' );
541 }
542
543 auto image = header + 2 + digits;
544
545 if( static_cast<size_t>( aPayload.end() - image ) < length )
546 return {};
547
548 return std::vector<uint8_t>( image, image + length );
549}
550
551
552VECTOR2I OleWmfRenderSize( int aNaturalWidth, int aNaturalHeight, int aMaxWidth, int aMaxHeight,
553 double aTargetAspect )
554{
555 if( aNaturalWidth <= 0 || aNaturalHeight <= 0 || aMaxWidth <= 0 || aMaxHeight <= 0 )
556 return VECTOR2I( 0, 0 );
557
558 if( !std::isfinite( aTargetAspect ) )
559 return VECTOR2I( 0, 0 );
560
561 if( aTargetAspect > 0.0 )
562 {
563 double width = aMaxWidth;
564 double height = width / aTargetAspect;
565
566 if( height > aMaxHeight )
567 {
568 height = aMaxHeight;
569 width = height * aTargetAspect;
570 }
571
572 return VECTOR2I( std::max( 1, KiROUND( width ) ), std::max( 1, KiROUND( height ) ) );
573 }
574
575 double scale = std::min( static_cast<double>( aMaxWidth ) / aNaturalWidth,
576 static_cast<double>( aMaxHeight ) / aNaturalHeight );
577 scale = std::min( scale, 1.0 );
578 return VECTOR2I( std::max( 1, KiROUND( aNaturalWidth * scale ) ),
579 std::max( 1, KiROUND( aNaturalHeight * scale ) ) );
580}
581
582bool OleRenderWmf( const std::vector<uint8_t>& aWmf, int aMaxWidth, int aMaxHeight, wxImage& aImage,
583 double aTargetAspect )
584{
585 if( aWmf.empty() || aWmf.size() > MAX_STREAM_BYTES
586 || aWmf.size() > static_cast<size_t>( std::numeric_limits<long>::max() ) )
587 {
588 return false;
589 }
590
591 // Copy the buffer for libwmf. Recompute the standard size when a placeable header is present.
592 std::vector<uint8_t> normalized = aWmf;
593
594 if( normalized.size() >= 40 && readU32( normalized.data() ) == 0x9AC6CDD7 )
595 {
596 uint32_t standardWords = static_cast<uint32_t>( ( normalized.size() - 22 ) / 2 );
597
598 for( int shift = 0; shift < 32; shift += 8 )
599 normalized[28 + shift / 8] = static_cast<uint8_t>( standardWords >> shift );
600 }
601
602 wmfAPI* api = nullptr;
603 wmfAPI_Options options{};
604 wxCharBuffer fontDir = OleLibWmfFontDirectory().utf8_str();
605 char* fontDirs[] = { fontDir.data(), nullptr };
606 options.function = wmf_gd_function;
607 options.fontdirs = fontDirs;
608
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;
611
612 if( wmf_api_create( &api, flags, &options ) != wmf_E_None )
613 return false;
614
615 std::unique_ptr<wmfAPI, decltype( &wmf_api_destroy )> apiOwner( api, wmf_api_destroy );
616
617 wmf_gd_t* gd = WMF_GD_GetData( api );
618 gd->type = wmf_gd_image;
619
620 if( wmf_mem_open( api, normalized.data(), static_cast<long>( normalized.size() ) ) != wmf_E_None )
621 {
622 return false;
623 }
624
625 wmfD_Rect bbox;
626
627 if( wmf_scan( api, 0, &bbox ) != wmf_E_None )
628 {
629 return false;
630 }
631
632 unsigned int naturalWidth = 0;
633 unsigned int naturalHeight = 0;
634
635 if( wmf_display_size( api, &naturalWidth, &naturalHeight, 144.0, 144.0 ) != wmf_E_None || naturalWidth == 0
636 || naturalHeight == 0 )
637 {
638 return false;
639 }
640
641 VECTOR2I renderSize = OleWmfRenderSize( naturalWidth, naturalHeight, std::max( 1, aMaxWidth ),
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 );
645
646 gd->bbox = bbox;
647 gd->width = width;
648 gd->height = height;
649
650 if( wmf_play( api, 0, &bbox ) != wmf_E_None )
651 {
652 return false;
653 }
654
655 int* pixels = wmf_gd_get_image_pixels( api );
656
657 if( !pixels || !aImage.Create( width, height, false ) )
658 {
659 return false;
660 }
661
662 unsigned char* rgb = aImage.GetData();
663
664 for( size_t i = 0; i < static_cast<size_t>( width ) * height; ++i )
665 {
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 );
669 }
670
671 return true;
672}
673
674
675bool OleRenderMetafilePreview( const std::vector<uint8_t>& aWmf, int aMaxWidth, int aMaxHeight,
676 wxImage& aImage, double aTargetAspect, bool* aUsedEmbeddedEmf )
677{
678 if( aUsedEmbeddedEmf )
679 *aUsedEmbeddedEmf = false;
680
681 std::vector<uint8_t> emf = OleExtractEmbeddedEmf( aWmf );
682
683 if( !emf.empty() && OleRenderEmf( emf, aMaxWidth, aMaxHeight, aImage, aTargetAspect ) )
684 {
685 if( aUsedEmbeddedEmf )
686 *aUsedEmbeddedEmf = true;
687
688 return true;
689 }
690
691 return OleRenderWmf( aWmf, aMaxWidth, aMaxHeight, aImage, aTargetAspect );
692}
693
694
695wxString OleDescribeImagePayload( const std::vector<uint8_t>& aPayload )
696{
697 if( aPayload.empty() )
698 return wxS( "empty" );
699
700 auto starts = [&]( std::initializer_list<uint8_t> aSig, size_t aOffset = 0 )
701 {
702 if( aPayload.size() < aOffset + aSig.size() )
703 return false;
704
705 return std::equal( aSig.begin(), aSig.end(), aPayload.begin() + aOffset );
706 };
707
708 if( starts( { 0x89, 'P', 'N', 'G' } ) )
709 return wxS( "PNG" );
710 if( starts( { 0xFF, 0xD8, 0xFF } ) )
711 return wxS( "JPEG" );
712 if( starts( { 'G', 'I', 'F', '8' } ) )
713 return wxS( "GIF" );
714 if( starts( { 'B', 'M' } ) )
715 return wxS( "BMP" );
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 } ) )
723 return wxS( "WMF" );
724 if( starts( { 'E', 'M', 'F', 0x20 }, 40 ) )
725 return wxS( "EMF" );
726 if( starts( { 0xD0, 0xCF, 0x11, 0xE0 } ) )
727 return wxS( "OLE compound document" );
728
729 wxString head;
730
731 for( size_t i = 0; i < std::min<size_t>( 8, aPayload.size() ); ++i )
732 head += wxString::Format( wxS( "%02X" ), aPayload[i] );
733
734 return wxString::Format( wxS( "unrecognized, %zu bytes starting %s" ), aPayload.size(), head );
735}
static std::optional< VECTOR2D > normalized(const VECTOR2D &aVec)
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
Definition paths.cpp:233
static const wxString & GetExecutablePath()
Definition paths.cpp:661
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)
Definition ole_emf.cpp:121
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.
OLE_IMAGE_TYPE
Definition ole_image.h:37
const int scale
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708