KiCad PCB EDA Suite
Loading...
Searching...
No Matches
ole_emf.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
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <sch_io/ole_image.h>
21
22#include <algorithm>
23#include <cmath>
24#include <limits>
25#include <memory>
26#include <string_view>
27
28#include <boost/endian/conversion.hpp>
29#include <emf2svg.h>
30#include <fontconfig/fontconfig.h>
31#include <nanosvg.h>
32#include <nanosvgrast.h>
33#include <wx/filename.h>
34#include <wx/image.h>
35#include <wx/log.h>
36
37namespace
38{
39
40constexpr size_t MAX_EMF_BYTES = 64 * 1024 * 1024;
41constexpr size_t MAX_RENDER_PIXELS = 16 * 1024 * 1024;
42
43using FONT_CONFIG = std::unique_ptr<FcConfig, decltype( &FcConfigDestroy )>;
44
45FONT_CONFIG emfFontConfig()
46{
47 FONT_CONFIG config( FcConfigCreate(), FcConfigDestroy );
48
49 if( !config )
50 return config;
51
53
54 const char* aliases = R"(<fontconfig>
55<cachedir prefix="xdg">fontconfig</cachedir>
56<alias><family>Calibri</family><prefer><family>Carlito</family></prefer></alias>
57<alias><family>Arial</family><prefer><family>Nimbus Sans</family></prefer></alias>
58<alias><family>sans-serif</family><prefer><family>Source Sans 3</family></prefer></alias>
59</fontconfig>)";
60
61 if( !FcConfigParseAndLoadFromMemory( config.get(), reinterpret_cast<const FcChar8*>( aliases ), FcTrue ) )
62 return FONT_CONFIG( nullptr, FcConfigDestroy );
63
64 // Glyph indices belong to these font files, not other installed revisions of the same family.
65 const char* files[] = { "SourceSansPro-Regular.ttf", "SourceSansPro-Semibold.ttf",
66 "SourceSans3-Regular.otf", "SourceSans3-Semibold.otf",
67 "Carlito-Regular.ttf", "Carlito-Bold.ttf",
68 "NimbusSans-Regular.t1", "NimbusSans-Bold.t1",
69 "NimbusSans-Italic.t1", "NimbusSans-BoldItalic.t1" };
70
71 bool added = false;
72
73 for( const char* file : files )
74 {
75 wxCharBuffer path = ( directory + wxS( "/" ) + wxString::FromUTF8( file ) ).utf8_str();
76
77 if( FcConfigAppFontAddFile( config.get(), reinterpret_cast<const FcChar8*>( path.data() ) ) )
78 added = true;
79 else
80 wxLogTrace( wxS( "OLE" ), wxS( "Cannot load EMF font %s" ), wxString::FromUTF8( path.data() ) );
81 }
82
83 if( !added )
84 config.reset();
85
86 return config;
87}
88
89
90bool containsRaster( std::string_view aSvg )
91{
92 size_t offset = 0;
93
94 while( ( offset = aSvg.find( '<', offset ) ) != std::string_view::npos )
95 {
96 if( aSvg.substr( offset, 9 ) == "<![CDATA[" )
97 {
98 offset = aSvg.find( "]]>", offset + 9 );
99
100 if( offset == std::string_view::npos )
101 return false;
102
103 offset += 3;
104 }
105 else if( aSvg.substr( offset, 7 ) == "<image " )
106 {
107 return true;
108 }
109 else
110 {
111 ++offset;
112 }
113 }
114
115 return false;
116}
117
118} // namespace
119
120
121bool OleRenderEmf( const std::vector<uint8_t>& aEmf, int aMaxWidth, int aMaxHeight, wxImage& aImage,
122 double aTargetAspect )
123{
124 using boost::endian::load_little_u32;
125 using boost::endian::load_little_s32;
126
127 if( aEmf.size() < 108 || aEmf.size() > MAX_EMF_BYTES || load_little_u32( aEmf.data() ) != 1
128 || load_little_u32( aEmf.data() + 4 ) < 88
129 || load_little_u32( aEmf.data() + 40 ) != 0x464D4520
130 || load_little_u32( aEmf.data() + 48 ) != aEmf.size() || !std::isfinite( aTargetAspect ) )
131 {
132 return false;
133 }
134
135 int64_t width = int64_t( load_little_s32( aEmf.data() + 16 ) ) - load_little_s32( aEmf.data() + 8 );
136 int64_t height = int64_t( load_little_s32( aEmf.data() + 20 ) ) - load_little_s32( aEmf.data() + 12 );
137
138 if( width <= 0 || height <= 0 || width > std::numeric_limits<int>::max()
139 || height > std::numeric_limits<int>::max() )
140 {
141 return false;
142 }
143
144 // Reject truncated records before the library reads their headers.
145 size_t offset = 0;
146
147 while( offset < aEmf.size() )
148 {
149 if( aEmf.size() - offset < 8 )
150 return false;
151
152 uint32_t length = load_little_u32( aEmf.data() + offset + 4 );
153
154 if( length < 8 || length % 4 != 0 || length > aEmf.size() - offset )
155 return false;
156
157 offset += length;
158 }
159
160 VECTOR2I size = OleWmfRenderSize( static_cast<int>( width ), static_cast<int>( height ),
161 aMaxWidth, aMaxHeight, aTargetAspect );
162
163 if( size.x <= 0 || size.y <= 0 || size_t( size.x ) * size.y > MAX_RENDER_PIXELS )
164 return false;
165
166 const FONT_CONFIG fonts = emfFontConfig();
167
168 if( !fonts )
169 return false;
170
171 generatorOptions options{};
172 options.svgDelimiter = true;
173 options.imgWidth = size.x;
174 options.imgHeight = size.y;
175 options.fontConfig = fonts.get();
176
177 std::vector<char> input( aEmf.begin(), aEmf.end() );
178 char* svg = nullptr;
179 size_t svgLength = 0;
180 int converted = emf2svg( input.data(), input.size(), &svg, &svgLength, &options );
181 std::unique_ptr<char, decltype( &free )> svgOwner( svg, free );
182
183 if( !converted || !svg || svgLength == 0 || svgLength > MAX_EMF_BYTES )
184 return false;
185
186 // NanoSVG cannot draw embedded rasters; let the caller try its WMF preview.
187 if( containsRaster( std::string_view( svg, svgLength ) ) )
188 return false;
189
190 std::unique_ptr<NSVGimage, decltype( &nsvgDelete )> drawing(
191 nsvgParseWithFontConfig( svg, "px", 96, fonts.get() ), nsvgDelete );
192
193 if( !drawing || !drawing->shapes || !std::isfinite( drawing->width ) || !std::isfinite( drawing->height )
194 || drawing->width <= 0 || drawing->height <= 0 || drawing->width > aMaxWidth + 1.0
195 || drawing->height > aMaxHeight + 1.0 )
196 {
197 return false;
198 }
199
200 int renderWidth = std::max( 1, KiROUND( drawing->width ) );
201 int renderHeight = std::max( 1, KiROUND( drawing->height ) );
202
203 if( size_t( renderWidth ) * renderHeight > MAX_RENDER_PIXELS )
204 return false;
205
206 std::unique_ptr<NSVGrasterizer, decltype( &nsvgDeleteRasterizer )> rasterizer(
207 nsvgCreateRasterizer(), nsvgDeleteRasterizer );
208
209 if( !rasterizer )
210 return false;
211
212 std::vector<unsigned char> pixels( size_t( renderWidth ) * renderHeight * 4 );
213 nsvgRasterize( rasterizer.get(), drawing.get(), 0, 0, 1, pixels.data(), renderWidth, renderHeight,
214 renderWidth * 4 );
215 wxImage image( renderWidth, renderHeight );
216
217 if( !image.IsOk() )
218 return false;
219
220 unsigned char* rgb = image.GetData();
221
222 for( size_t i = 0; i < pixels.size() / 4; ++i )
223 {
224 unsigned int alpha = pixels[i * 4 + 3];
225
226 for( size_t channel = 0; channel < 3; ++channel )
227 rgb[i * 3 + channel] = ( pixels[i * 4 + channel] * alpha + 255 * ( 255 - alpha ) + 127 ) / 255;
228 }
229
230 if( renderWidth != size.x || renderHeight != size.y )
231 image.Rescale( size.x, size.y, wxIMAGE_QUALITY_HIGH );
232
233 aImage = std::move( image );
234 return aImage.IsOk();
235}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
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...
VECTOR2I OleWmfRenderSize(int aNaturalWidth, int aNaturalHeight, int aMaxWidth, int aMaxHeight, double aTargetAspect)
std::string path
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708