1use serde::{Deserialize, Serialize};
6#[cfg(feature = "json-schema")]
7use serde_json::Value;
8
9#[derive(Debug, Clone)]
11pub struct StreamingConfig {
12 pub buffer_size: usize,
14 pub max_depth: usize,
16 pub lazy_loading: bool,
18 pub large_text_threshold: Option<usize>,
20 pub memory_limit: Option<usize>,
22}
23
24impl Default for StreamingConfig {
25 fn default() -> Self {
26 Self {
27 buffer_size: 64 * 1024, max_depth: 256,
29 lazy_loading: false,
30 large_text_threshold: None,
31 memory_limit: Some(100 * 1024 * 1024), }
33 }
34}
35
36#[derive(Debug, Clone, Default, Serialize, Deserialize)]
38#[serde(default)]
39pub struct Config {
40 pub multipass: bool,
42
43 pub pretty: bool,
45
46 pub indent: String,
48
49 pub plugins: Vec<PluginConfig>,
51
52 pub js2svg: Js2SvgOptions,
54
55 pub datauri: Option<DataUriFormat>,
57
58 #[serde(skip)]
60 pub parallel: Option<usize>,
61
62 #[serde(skip)]
64 pub path: Option<String>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(untagged)]
70pub enum PluginConfig {
71 Name(String),
73
74 WithParams {
76 name: String,
77 #[serde(default)]
78 params: serde_json::Value,
79 },
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(default)]
85pub struct Js2SvgOptions {
86 pub pretty: bool,
88
89 pub indent: String,
91
92 pub use_short_tags: bool,
94
95 pub final_newline: bool,
97
98 pub eol: LineEnding,
100}
101
102impl Default for Js2SvgOptions {
103 fn default() -> Self {
104 Self {
105 pretty: false,
106 indent: " ".to_string(),
107 use_short_tags: true,
108 final_newline: true,
109 eol: LineEnding::Lf,
110 }
111 }
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub enum LineEnding {
117 #[serde(alias = "lf")]
119 Lf,
120 #[serde(alias = "crlf")]
122 Crlf,
123 #[serde(alias = "cr")]
125 Cr,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub enum DataUriFormat {
131 #[serde(alias = "base64")]
133 Base64,
134 #[serde(alias = "enc")]
136 Enc,
137 #[serde(alias = "unenc")]
139 Unenc,
140}
141
142#[cfg(feature = "json-schema")]
143use jsonschema::JSONSchema;
144
145use crate::error::VexyError;
146
147impl StreamingConfig {
148 pub fn create_memory_budget(
150 &self,
151 ) -> Option<std::sync::Arc<crate::optimizer::memory::MemoryBudget>> {
152 self.memory_limit
153 .map(|limit| std::sync::Arc::new(crate::optimizer::memory::MemoryBudget::new(limit)))
154 }
155}
156
157impl Config {
158 pub fn new() -> Self {
160 Self::default()
161 }
162
163 pub fn with_default_preset() -> Self {
165 Self {
166 plugins: vec![
167 PluginConfig::Name("removeDoctype".to_string()),
168 PluginConfig::Name("removeXMLProcInst".to_string()),
169 PluginConfig::Name("removeComments".to_string()),
170 PluginConfig::Name("removeMetadata".to_string()),
171 PluginConfig::Name("removeEditorsNSData".to_string()),
172 PluginConfig::Name("mergeStyles".to_string()),
174 PluginConfig::Name("inlineStyles".to_string()),
175 PluginConfig::Name("minifyStyles".to_string()),
176 PluginConfig::Name("cleanupIds".to_string()),
177 PluginConfig::Name("removeUselessDefs".to_string()),
178 PluginConfig::Name("convertColors".to_string()),
180 PluginConfig::Name("removeUnknownsAndDefaults".to_string()),
181 PluginConfig::Name("removeNonInheritableGroupAttrs".to_string()),
182 PluginConfig::Name("removeUselessStrokeAndFill".to_string()),
183 PluginConfig::Name("removeHiddenElems".to_string()),
185 PluginConfig::Name("removeEmptyText".to_string()),
186 PluginConfig::Name("convertShapeToPath".to_string()),
187 PluginConfig::Name("convertEllipseToCircle".to_string()),
188 PluginConfig::Name("moveElemsAttrsToGroup".to_string()),
189 PluginConfig::Name("moveGroupAttrsToElems".to_string()),
190 PluginConfig::Name("collapseGroups".to_string()),
191 PluginConfig::Name("convertPathData".to_string()),
192 PluginConfig::Name("convertTransform".to_string()),
193 PluginConfig::Name("removeEmptyAttrs".to_string()),
194 PluginConfig::Name("removeEmptyContainers".to_string()),
195 PluginConfig::Name("removeUnusedNS".to_string()),
196 PluginConfig::Name("mergePaths".to_string()),
197 PluginConfig::Name("sortAttrs".to_string()),
198 PluginConfig::Name("sortDefsChildren".to_string()),
199 PluginConfig::Name("removeDesc".to_string()),
200 ],
201 ..Default::default()
202 }
203 }
204
205 #[cfg(feature = "json-schema")]
207 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, VexyError> {
208 let content = std::fs::read_to_string(path).map_err(VexyError::Io)?;
209 let config: Value =
210 serde_json::from_str(&content).map_err(|e| VexyError::Config(e.to_string()))?;
211
212 let schema_content = include_str!("../../svgo.schema.json");
213 let schema: Value =
214 serde_json::from_str(schema_content).map_err(|e| VexyError::Config(e.to_string()))?;
215 let compiled_schema =
216 JSONSchema::compile(&schema).map_err(|e| VexyError::Config(e.to_string()))?;
217
218 let result = compiled_schema.validate(&config);
219 if let Err(errors) = result {
220 for error in errors {
221 eprintln!("Configuration error: {}", error);
222 }
223 return Err(VexyError::Config("Invalid configuration".to_string()));
224 }
225
226 serde_json::from_value(config.clone()).map_err(|e| VexyError::Config(e.to_string()))
227 }
228
229 #[cfg(not(feature = "json-schema"))]
231 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, VexyError> {
232 let content = std::fs::read_to_string(path).map_err(VexyError::Io)?;
233 serde_json::from_str(&content).map_err(|e| VexyError::Config(e.to_string()))
234 }
235
236 pub fn get_plugin(&self, name: &str) -> Option<&PluginConfig> {
238 self.plugins.iter().find(|p| p.name() == name)
239 }
240
241 pub fn add_plugin(&mut self, plugin: PluginConfig) {
243 self.plugins.push(plugin);
244 }
245
246 pub fn set_plugin_enabled(&mut self, name: &str, enabled: bool) {
248 for plugin in &mut self.plugins {
249 if plugin.name() == name {
250 match plugin {
251 PluginConfig::Name(plugin_name) => {
252 if !enabled {
253 *plugin = PluginConfig::WithParams {
255 name: plugin_name.clone(),
256 params: serde_json::json!({"enabled": false}),
257 };
258 }
259 }
260 PluginConfig::WithParams { params, .. } => {
261 if let Some(obj) = params.as_object_mut() {
262 obj.insert("enabled".to_string(), serde_json::json!(enabled));
263 } else {
264 *params = serde_json::json!({"enabled": enabled});
265 }
266 }
267 }
268 return;
269 }
270 }
271 }
272
273 pub fn configure_plugin(&mut self, name: &str, params: serde_json::Value) {
275 for plugin in &mut self.plugins {
276 if plugin.name() == name {
277 match plugin {
278 PluginConfig::Name(plugin_name) => {
279 *plugin = PluginConfig::WithParams {
280 name: plugin_name.clone(),
281 params,
282 };
283 }
284 PluginConfig::WithParams {
285 params: existing_params,
286 ..
287 } => {
288 *existing_params = params;
289 }
290 }
291 return;
292 }
293 }
294 self.plugins.push(PluginConfig::WithParams {
296 name: name.to_string(),
297 params,
298 });
299 }
300}
301
302impl PluginConfig {
303 pub fn new(name: String) -> Self {
305 Self::Name(name)
306 }
307
308 pub fn name(&self) -> &str {
310 match self {
311 Self::Name(name) => name,
312 Self::WithParams { name, .. } => name,
313 }
314 }
315
316 pub fn params(&self) -> Option<&serde_json::Value> {
318 match self {
319 Self::Name(_) => None,
320 Self::WithParams { params, .. } => Some(params),
321 }
322 }
323
324 pub fn params_mut(&mut self) -> &mut serde_json::Value {
326 match self {
327 Self::Name(name) => {
328 let name = name.clone();
329 *self = Self::WithParams {
330 name,
331 params: serde_json::json!({}),
332 };
333 match self {
334 Self::WithParams { params, .. } => params,
335 _ => unreachable!(),
336 }
337 }
338 Self::WithParams { params, .. } => params,
339 }
340 }
341}
342
343pub fn load_config_from_directory<P: AsRef<std::path::Path>>(dir: P) -> Result<Config, VexyError> {
345 let dir = dir.as_ref();
346
347 let config_files = [
349 ".vexy-vsvgrc",
350 ".vexy-vsvgrc.json",
351 "vexy-vsvg.config.json",
352 ".svgorc",
353 ".svgorc.json",
354 "svgo.config.json",
355 ];
356
357 for filename in &config_files {
358 let config_path = dir.join(filename);
359 if config_path.exists() {
360 return Config::from_file(config_path).map_err(|e| VexyError::Config(e.to_string()));
361 }
362 }
363
364 Ok(Config::with_default_preset())
366}