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