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
101
102wxString wmfFontDirectory()
103{
104 wxFileName fontDir;
105 fontDir.AssignDir( PATHS::GetStockDataPath() );
106 fontDir.AppendDir( wxS( "libwmf" ) );
107 fontDir.AppendDir( wxS( "fonts" ) );
108
109 if( fontDir.DirExists() )
110 return fontDir.GetPath();
111
112 wxFileName buildDir;
113 buildDir.AssignDir( PATHS::GetExecutablePath() );
114
115 for( int depth = 0; depth < 4; ++depth )
116 {
117 wxFileName candidate = buildDir;
118 candidate.AppendDir( wxS( "libwmf" ) );
119 candidate.AppendDir( wxS( "fonts" ) );
120
121 if( candidate.DirExists() )
122 return candidate.GetPath();
123
124 buildDir.RemoveLastDir();
125 }
126
127 wxLogTrace( traceSchPlugin, wxS( "libwmf fonts missing (looked for %s); WMF rendering will fail" ),
128 fontDir.GetPath() );
129
130 return fontDir.GetPath();
131}
132
133
134OLE_IMAGE_PAYLOAD classifyContents( std::vector<uint8_t> aData, std::string aName )
135{
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 ) )
139 return { OLE_IMAGE_TYPE::BMP, std::move( aData ), std::move( aName ) };
140
141 if( aData.size() >= 40 )
142 {
143 // Validate the DIB fields before selecting CONTENTS and hiding a valid presentation.
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 );
149
150 bool depthIsValid = bitCount == 1 || bitCount == 4 || bitCount == 8 || bitCount == 16
151 || bitCount == 24 || bitCount == 32;
152
153 if( headerSize >= 40 && headerSize <= 200 && planes == 1 && depthIsValid && width != 0
154 && height != 0 )
155 {
156 return { OLE_IMAGE_TYPE::DIB, std::move( aData ), std::move( aName ) };
157 }
158 }
159
160 if( isWmf( aData.data(), aData.size() ) )
161 return { OLE_IMAGE_TYPE::WMF, std::move( aData ), std::move( aName ) };
162
163 return {};
164}
165
166
167OLE_IMAGE_PAYLOAD classifyPresentation( std::vector<uint8_t> aData )
168{
169 if( aData.size() < 40 )
170 return {};
171
172 uint32_t clipboardFormat = readU32( aData.data() + 4 );
174
175 if( clipboardFormat == 3 || clipboardFormat == 14 )
176 type = OLE_IMAGE_TYPE::WMF;
177 else if( clipboardFormat == 8 )
178 type = OLE_IMAGE_TYPE::DIB;
179
180 if( type == OLE_IMAGE_TYPE::NONE )
181 return {};
182
183 return { type, { aData.begin() + 40, aData.end() }, "\\x02OlePres000" };
184}
185
186
187OLE_IMAGE_PAYLOAD classifyNative( const std::vector<uint8_t>& aData )
188{
189 // Ole10Native starts with a length. A 0x0002 flag selects the Packager header.
190 if( aData.size() < 6 )
191 return {};
192
193 size_t stated = readU32( aData.data() );
194
195 if( stated > aData.size() - 4 )
196 return {};
197
198 size_t offset = 4;
199 size_t length = stated;
200
201 if( readU16( aData.data() + 4 ) == 0x0002 )
202 {
203 offset += 2;
204
205 // The label and the originating path are NUL terminated; the temporary path that
206 // follows them is counted instead.
207 for( int i = 0; i < 2; ++i )
208 {
209 while( offset < aData.size() && aData[offset] != 0 )
210 ++offset;
211
212 if( offset >= aData.size() )
213 return {};
214
215 ++offset;
216 }
217
218 if( aData.size() - offset < 8 )
219 return {};
220
221 offset += 4;
222
223 size_t pathLength = readU32( aData.data() + offset );
224 offset += 4;
225
226 if( pathLength > aData.size() - offset )
227 return {};
228
229 offset += pathLength;
230
231 if( aData.size() - offset < 4 )
232 return {};
233
234 length = readU32( aData.data() + offset );
235 offset += 4;
236
237 if( length > aData.size() - offset )
238 return {};
239 }
240
241 std::vector<uint8_t> body( aData.begin() + offset, aData.begin() + offset + length );
242
243 return classifyContents( std::move( body ), "\\x01Ole10Native" );
244}
245
246
247} // namespace
248
249
250std::optional<std::pair<size_t, size_t>> OleEmbeddedCompoundFile( const std::vector<uint8_t>& aPayload )
251{
252 constexpr size_t PROLOGUE = 26;
253
254 if( aPayload.size() < PROLOGUE + CFB_MAGIC.size() )
255 return std::nullopt;
256
257 uint64_t stated = readU32( aPayload.data() );
258 uint64_t length = readU32( aPayload.data() + 22 );
259
260 // The two length fields must agree. Capture truncates the unused tail of the final sector,
261 // so the length is not always a multiple of 512; never round it up.
262 if( stated != length + 22 || length < CFB_MAGIC.size() )
263 return std::nullopt;
264
265 if( !std::equal( CFB_MAGIC.begin(), CFB_MAGIC.end(), aPayload.begin() + PROLOGUE ) )
266 return std::nullopt;
267
268 // A container cut short of its stated length still reads; the compound file reader pads the
269 // final sector. Clamping keeps that working without letting the length address absent bytes.
270 size_t extent = std::min<size_t>( length, aPayload.size() - PROLOGUE );
271
272 return std::make_pair( PROLOGUE, extent );
273}
274
275
276OLE_IMAGE_PAYLOAD ExtractOleImage( const uint8_t* aCfb, size_t aSize )
277{
278 if( !aCfb || aSize < 512 || aSize > MAX_CFB_BYTES )
279 return {};
280
281 try
282 {
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;
287
288 reader.EnumFiles( reader.GetRootEntry(), -1,
289 [&]( const CFB::COMPOUND_FILE_ENTRY* aEntry, const CFB::utf16string&, int )
290 {
291 if( !reader.IsStream( aEntry ) )
292 return 0;
293
294 if( entryNameIs( aEntry, u"CONTENTS" ) )
295 contents = aEntry;
296 else if( entryNameIs( aEntry, u"\x02OlePres000" ) )
297 presentation = aEntry;
298 else if( entryNameIs( aEntry, u"\x01Ole10Native" ) )
299 native = aEntry;
300
301 return 0;
302 } );
303
304 if( contents )
305 {
306 OLE_IMAGE_PAYLOAD result = classifyContents( readStream( reader, contents ), "CONTENTS" );
307
308 if( result.type != OLE_IMAGE_TYPE::NONE )
309 return result;
310 }
311
312 if( presentation )
313 {
314 OLE_IMAGE_PAYLOAD result = classifyPresentation( readStream( reader, presentation ) );
315
316 if( result.type != OLE_IMAGE_TYPE::NONE )
317 return result;
318 }
319
320 if( native )
321 return classifyNative( readStream( reader, native ) );
322 }
323 catch( const std::exception& )
324 {
325 }
326
327 return {};
328}
329
330
331OLE_IMAGE_PAYLOAD ExtractOleImageFromPayload( const std::vector<uint8_t>& aPayload )
332{
333 std::optional<std::pair<size_t, size_t>> located = OleEmbeddedCompoundFile( aPayload );
334
335 if( !located )
336 return {};
337
338 // The reader wants whole sectors, and a container cut short of its stated length is still
339 // readable once the final one is padded.
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 ) );
343
344 return ExtractOleImage( compound.data(), compound.size() );
345}
346
347
348bool OleMakeBmpFromDib( const std::vector<uint8_t>& aDib, wxMemoryBuffer& aOut )
349{
350 if( aDib.size() < 40 || aDib.size() > std::numeric_limits<uint32_t>::max() - 14 )
351 return false;
352
353 uint32_t biSize = readU32( aDib.data() );
354
355 if( biSize < 40 || biSize > 200 || biSize > aDib.size() )
356 return false;
357
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() );
364
365 if( pixelOffset > fileSize )
366 return false;
367
368 std::array<uint8_t, 14> header{};
369 header[0] = 'B';
370 header[1] = 'M';
371
372 for( int shift = 0; shift < 32; shift += 8 )
373 {
374 header[2 + shift / 8] = static_cast<uint8_t>( fileSize >> shift );
375 header[10 + shift / 8] = static_cast<uint8_t>( pixelOffset >> shift );
376 }
377
378 aOut.AppendData( header.data(), header.size() );
379 aOut.AppendData( aDib.data(), aDib.size() );
380 return true;
381}
382
383
384std::vector<uint8_t> OleExtractEmbeddedEmf( const std::vector<uint8_t>& aWmf )
385{
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;
390
391 size_t headerOffset = 0;
392
393 if( aWmf.size() >= 4 && readU32( aWmf.data() ) == 0x9AC6CDD7 )
394 headerOffset = 22;
395
396 if( aWmf.size() < headerOffset + 18 || readU16( aWmf.data() + headerOffset + 2 ) != 9 )
397 return {};
398
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;
404
405 while( offset + 6 <= aWmf.size() )
406 {
407 uint32_t sizeWords = readU32( aWmf.data() + offset );
408
409 if( sizeWords < 3 || sizeWords > ( aWmf.size() - offset ) / 2 )
410 return {};
411
412 size_t recordSize = static_cast<size_t>( sizeWords ) * 2;
413
414 uint16_t function = readU16( aWmf.data() + offset + 4 );
415
416 if( function == c_META_ESCAPE && recordSize >= 10 + c_COMMENT_HEADER_SIZE
417 && readU16( aWmf.data() + offset + 6 ) == c_ENHANCED_METAFILE )
418 {
419 uint16_t byteCount = readU16( aWmf.data() + offset + 8 );
420
421 if( byteCount < c_COMMENT_HEADER_SIZE || static_cast<size_t>( byteCount ) + 10 > recordSize )
422 return {};
423
424 const uint8_t* header = aWmf.data() + offset + 10;
425
426 if( readU32( header ) != c_WMFC_IDENTIFIER || readU32( header + 4 ) != 1 )
427 return {};
428
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 );
433
434 if( chunkSize > byteCount - c_COMMENT_HEADER_SIZE || emfSize < remaining )
435 return {};
436
437 if( chunkCount == 0 )
438 {
439 expectedRecordCount = recordCount;
440 expectedEmfSize = emfSize;
441
442 if( emfSize > aWmf.size() )
443 return {};
444
445 emf.reserve( emfSize );
446 }
447 else if( recordCount != expectedRecordCount || emfSize != expectedEmfSize )
448 {
449 return {};
450 }
451
452 if( chunkSize > expectedEmfSize - emf.size()
453 || remaining != expectedEmfSize - emf.size() - chunkSize )
454 return {};
455
456 emf.insert( emf.end(), header + c_COMMENT_HEADER_SIZE, header + c_COMMENT_HEADER_SIZE + chunkSize );
457 ++chunkCount;
458 }
459
460 offset += recordSize;
461
462 if( function == 0 )
463 break;
464 }
465
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() )
469 {
470 return {};
471 }
472
473 return emf;
474}
475
476
477std::vector<uint8_t> OleExtractCiImage( const std::vector<uint8_t>& aPayload )
478{
479 constexpr std::string_view ciMarker = "~~CI_IMAGE~~";
480 constexpr size_t DIB_HEADER = 40;
481
482 // The CI marker follows the preview DIB, including its palette and padded rows.
483 if( aPayload.size() < DIB_HEADER )
484 return {};
485
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 );
492
493 if( headerSize != DIB_HEADER || planes != 1 || width <= 0 || height == 0 )
494 return {};
495
496 if( depth != 1 && depth != 4 && depth != 8 && depth != 16 && depth != 24 && depth != 32 )
497 return {};
498
499 if( !paletteEntries && depth <= 8 )
500 paletteEntries = uint32_t( 1 ) << depth;
501
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;
505
506 if( headerBytes > aPayload.size() || rows > ( aPayload.size() - headerBytes ) / stride )
507 return {};
508
509 size_t previewSize = static_cast<size_t>( headerBytes + stride * rows );
510
511 if( aPayload.size() - previewSize < ciMarker.size() )
512 return {};
513
514 auto marker = aPayload.begin() + previewSize;
515
516 if( !std::equal( ciMarker.begin(), ciMarker.end(), marker ) )
517 return {};
518
519 if( OleEmbeddedCompoundFile( aPayload ) )
520 return {};
521
522 // After the marker: NUL, digit count, decimal byte length, then the raster bytes.
523 auto header = marker + ciMarker.size();
524
525 if( aPayload.end() - header < 2 || *header != 0 )
526 return {};
527
528 size_t digits = header[1];
529
530 if( digits < 1 || digits > 10 || static_cast<size_t>( aPayload.end() - header ) < 2 + digits )
531 return {};
532
533 size_t length = 0;
534
535 for( size_t i = 0; i < digits; ++i )
536 {
537 uint8_t byte = header[2 + i];
538
539 if( byte < '0' || byte > '9' )
540 return {};
541
542 length = length * 10 + static_cast<size_t>( byte - '0' );
543 }
544
545 auto image = header + 2 + digits;
546
547 if( static_cast<size_t>( aPayload.end() - image ) < length )
548 return {};
549
550 return std::vector<uint8_t>( image, image + length );
551}
552
553
554VECTOR2I OleWmfRenderSize( int aNaturalWidth, int aNaturalHeight, int aMaxWidth, int aMaxHeight,
555 double aTargetAspect )
556{
557 if( aNaturalWidth <= 0 || aNaturalHeight <= 0 || aMaxWidth <= 0 || aMaxHeight <= 0 )
558 return VECTOR2I( 0, 0 );
559
560 if( !std::isfinite( aTargetAspect ) )
561 return VECTOR2I( 0, 0 );
562
563 if( aTargetAspect > 0.0 )
564 {
565 double width = aMaxWidth;
566 double height = width / aTargetAspect;
567
568 if( height > aMaxHeight )
569 {
570 height = aMaxHeight;
571 width = height * aTargetAspect;
572 }
573
574 return VECTOR2I( std::max( 1, KiROUND( width ) ), std::max( 1, KiROUND( height ) ) );
575 }
576
577 double scale = std::min( static_cast<double>( aMaxWidth ) / aNaturalWidth,
578 static_cast<double>( aMaxHeight ) / aNaturalHeight );
579 scale = std::min( scale, 1.0 );
580 return VECTOR2I( std::max( 1, KiROUND( aNaturalWidth * scale ) ),
581 std::max( 1, KiROUND( aNaturalHeight * scale ) ) );
582}
583
584bool OleRenderWmf( const std::vector<uint8_t>& aWmf, int aMaxWidth, int aMaxHeight, wxImage& aImage,
585 double aTargetAspect )
586{
587 if( aWmf.empty() || aWmf.size() > MAX_STREAM_BYTES
588 || aWmf.size() > static_cast<size_t>( std::numeric_limits<long>::max() ) )
589 {
590 return false;
591 }
592
593 // Copy the buffer for libwmf. Recompute the standard size when a placeable header is present.
594 std::vector<uint8_t> normalized = aWmf;
595
596 if( normalized.size() >= 40 && readU32( normalized.data() ) == 0x9AC6CDD7 )
597 {
598 uint32_t standardWords = static_cast<uint32_t>( ( normalized.size() - 22 ) / 2 );
599
600 for( int shift = 0; shift < 32; shift += 8 )
601 normalized[28 + shift / 8] = static_cast<uint8_t>( standardWords >> shift );
602 }
603
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;
610
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;
613
614 if( wmf_api_create( &api, flags, &options ) != wmf_E_None )
615 return false;
616
617 std::unique_ptr<wmfAPI, decltype( &wmf_api_destroy )> apiOwner( api, wmf_api_destroy );
618
619 wmf_gd_t* gd = WMF_GD_GetData( api );
620 gd->type = wmf_gd_image;
621
622 if( wmf_mem_open( api, normalized.data(), static_cast<long>( normalized.size() ) ) != wmf_E_None )
623 {
624 return false;
625 }
626
627 wmfD_Rect bbox;
628
629 if( wmf_scan( api, 0, &bbox ) != wmf_E_None )
630 {
631 return false;
632 }
633
634 unsigned int naturalWidth = 0;
635 unsigned int naturalHeight = 0;
636
637 if( wmf_display_size( api, &naturalWidth, &naturalHeight, 144.0, 144.0 ) != wmf_E_None || naturalWidth == 0
638 || naturalHeight == 0 )
639 {
640 return false;
641 }
642
643 VECTOR2I renderSize = OleWmfRenderSize( naturalWidth, naturalHeight, std::max( 1, aMaxWidth ),
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 );
647
648 gd->bbox = bbox;
649 gd->width = width;
650 gd->height = height;
651
652 if( wmf_play( api, 0, &bbox ) != wmf_E_None )
653 {
654 return false;
655 }
656
657 int* pixels = wmf_gd_get_image_pixels( api );
658
659 if( !pixels || !aImage.Create( width, height, false ) )
660 {
661 return false;
662 }
663
664 unsigned char* rgb = aImage.GetData();
665
666 for( size_t i = 0; i < static_cast<size_t>( width ) * height; ++i )
667 {
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 );
671 }
672
673 return true;
674}
675
676
677bool OleRenderMetafilePreview( const std::vector<uint8_t>& aWmf, int aMaxWidth, int aMaxHeight,
678 wxImage& aImage, double aTargetAspect, bool* aUsedEmbeddedEmf )
679{
680 if( aUsedEmbeddedEmf )
681 *aUsedEmbeddedEmf = false;
682
683 std::vector<uint8_t> emf = OleExtractEmbeddedEmf( aWmf );
684
685 if( !emf.empty() && OleRenderEmf( emf, aMaxWidth, aMaxHeight, aImage, aTargetAspect ) )
686 {
687 if( aUsedEmbeddedEmf )
688 *aUsedEmbeddedEmf = true;
689
690 return true;
691 }
692
693 return OleRenderWmf( aWmf, aMaxWidth, aMaxHeight, aImage, aTargetAspect );
694}
695
696
697wxString OleDescribeImagePayload( const std::vector<uint8_t>& aPayload )
698{
699 if( aPayload.empty() )
700 return wxS( "empty" );
701
702 auto starts = [&]( std::initializer_list<uint8_t> aSig, size_t aOffset = 0 )
703 {
704 if( aPayload.size() < aOffset + aSig.size() )
705 return false;
706
707 return std::equal( aSig.begin(), aSig.end(), aPayload.begin() + aOffset );
708 };
709
710 if( starts( { 0x89, 'P', 'N', 'G' } ) )
711 return wxS( "PNG" );
712 if( starts( { 0xFF, 0xD8, 0xFF } ) )
713 return wxS( "JPEG" );
714 if( starts( { 'G', 'I', 'F', '8' } ) )
715 return wxS( "GIF" );
716 if( starts( { 'B', 'M' } ) )
717 return wxS( "BMP" );
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 } ) )
725 return wxS( "WMF" );
726 if( starts( { 'E', 'M', 'F', 0x20 }, 40 ) )
727 return wxS( "EMF" );
728 if( starts( { 0xD0, 0xCF, 0x11, 0xE0 } ) )
729 return wxS( "OLE compound document" );
730
731 wxString head;
732
733 for( size_t i = 0; i < std::min<size_t>( 8, aPayload.size() ); ++i )
734 head += wxString::Format( wxS( "%02X" ), aPayload[i] );
735
736 return wxString::Format( wxS( "unrecognized, %zu bytes starting %s" ), aPayload.size(), head );
737}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
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:122
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:683