msg_tool/
types.rs

1//! Basic types
2use clap::ValueEnum;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
7#[serde(untagged, rename_all = "camelCase")]
8/// Text Encoding
9pub enum Encoding {
10    /// Automatically detect encoding
11    Auto,
12    /// UTF-8 encoding
13    Utf8,
14    /// Shift-JIS encoding
15    Cp932,
16    /// GB2312 encoding
17    Gb2312,
18    /// Code page encoding (Windows only)
19    #[cfg(windows)]
20    CodePage(u32),
21}
22
23impl Default for Encoding {
24    fn default() -> Self {
25        Encoding::Utf8
26    }
27}
28
29impl Encoding {
30    /// Returns true if the encoding is Shift-JIS (CP932).
31    pub fn is_jis(&self) -> bool {
32        match self {
33            Self::Cp932 => true,
34            #[cfg(windows)]
35            Self::CodePage(code_page) => *code_page == 932,
36            _ => false,
37        }
38    }
39
40    /// Returns true if the encoding is UTF8.
41    pub fn is_utf8(&self) -> bool {
42        match self {
43            Self::Utf8 => true,
44            #[cfg(windows)]
45            Self::CodePage(code_page) => *code_page == 65001,
46            _ => false,
47        }
48    }
49}
50
51#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
52/// Text Encoding (for CLI)
53pub enum TextEncoding {
54    /// Use script's default encoding
55    Default,
56    /// Automatically detect encoding
57    Auto,
58    /// UTF-8 encoding
59    Utf8,
60    #[value(alias("jis"))]
61    /// Shift-JIS encoding
62    Cp932,
63    #[value(alias("gbk"))]
64    /// GB2312 encoding
65    Gb2312,
66}
67
68#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
69/// Script type
70pub enum OutputScriptType {
71    /// Text script
72    M3t,
73    /// JSON which can be used for GalTransl
74    Json,
75    /// YAML (same as JSON, but with YAML syntax)
76    Yaml,
77    /// Custom output
78    Custom,
79}
80
81impl OutputScriptType {
82    /// Returns true if the script type is custom.
83    pub fn is_custom(&self) -> bool {
84        matches!(self, OutputScriptType::Custom)
85    }
86}
87
88impl AsRef<str> for OutputScriptType {
89    /// Returns the extension for the script type.
90    fn as_ref(&self) -> &str {
91        match self {
92            OutputScriptType::M3t => "m3t",
93            OutputScriptType::Json => "json",
94            OutputScriptType::Yaml => "yaml",
95            OutputScriptType::Custom => "",
96        }
97    }
98}
99
100#[cfg(feature = "circus")]
101#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
102/// Circus MES game
103pub enum CircusMesType {
104    /// fortissimo//Akkord:Bsusvier
105    Ffexa,
106    /// fortissimo EXS//Akkord:nächsten Phase
107    Ffexs,
108    /// Eternal Fantasy
109    Ef,
110    /// D.C.〜ダ・カーポ〜 温泉編
111    Dcos,
112    /// ことり Love Ex P
113    Ktlep,
114    /// D.C.WhiteSeason
115    Dcws,
116    /// D.C. Summer Vacation
117    Dcsv,
118    /// D.C.P.C.(Vista)
119    Dcpc,
120    /// D.C.〜ダ・カーポ〜 MEMORIES DISC
121    Dcmems,
122    /// D.C. Dream X’mas
123    Dcdx,
124    /// D.C.A.S. 〜ダ・カーポ〜アフターシーズンズ
125    Dcas,
126    /// D.C.II 春風のアルティメットバトル!
127    Dcbs,
128    /// D.C.II Fall in Love
129    Dc2fl,
130    /// D.C.II 春風のアルティメットバトル!
131    Dc2bs,
132    /// D.C.II Dearest Marriage
133    Dc2dm,
134    /// D.C.II 〜featuring Yun2〜
135    Dc2fy,
136    /// D.C.II C.C. 月島小恋のらぶらぶバスルーム
137    Dc2cckko,
138    /// D.C.II C.C. 音姫先生のどきどき特別授業
139    Dc2ccotm,
140    /// D.C.II Spring Celebration
141    Dc2sc,
142    /// D.C.II To You
143    Dc2ty,
144    /// D.C.II P.C.
145    Dc2pc,
146    /// D.C.III RX-rated
147    Dc3rx,
148    /// D.C.III P.P.~ダ・カーポIII プラチナパートナー~
149    Dc3pp,
150    /// D.C.III WithYou
151    Dc3wy,
152    /// D.C.III DreamDays
153    Dc3dd,
154    /// D.C.4 ~ダ・カーポ4~
155    Dc4,
156    /// D.C.4 Plus Harmony 〜ダ・カーポ4〜 プラスハーモニー
157    Dc4ph,
158    /// D.S. -Dal Segno-
159    Ds,
160    /// D.S.i.F. -Dal Segno- in Future
161    Dsif,
162    /// てんぷれ!
163    Tmpl,
164    /// 百花百狼/Hyakka Hyakurou
165    Nightshade,
166}
167
168#[cfg(feature = "circus")]
169impl AsRef<str> for CircusMesType {
170    /// Returns the name.
171    fn as_ref(&self) -> &str {
172        match self {
173            CircusMesType::Ffexa => "ffexa",
174            CircusMesType::Ffexs => "ffexs",
175            CircusMesType::Ef => "ef",
176            CircusMesType::Dcos => "dcos",
177            CircusMesType::Ktlep => "ktlep",
178            CircusMesType::Dcws => "dcws",
179            CircusMesType::Dcsv => "dcsv",
180            CircusMesType::Dcpc => "dcpc",
181            CircusMesType::Dcmems => "dcmems",
182            CircusMesType::Dcdx => "dcdx",
183            CircusMesType::Dcas => "dcas",
184            CircusMesType::Dcbs => "dcbs",
185            CircusMesType::Dc2fl => "dc2fl",
186            CircusMesType::Dc2bs => "dc2bs",
187            CircusMesType::Dc2dm => "dc2dm",
188            CircusMesType::Dc2fy => "dc2fy",
189            CircusMesType::Dc2cckko => "dc2cckko",
190            CircusMesType::Dc2ccotm => "dc2ccotm",
191            CircusMesType::Dc2sc => "dc2sc",
192            CircusMesType::Dc2ty => "dc2ty",
193            CircusMesType::Dc2pc => "dc2pc",
194            CircusMesType::Dc3rx => "dc3rx",
195            CircusMesType::Dc3pp => "dc3pp",
196            CircusMesType::Dc3wy => "dc3wy",
197            CircusMesType::Dc3dd => "dc3dd",
198            CircusMesType::Dc4 => "dc4",
199            CircusMesType::Dc4ph => "dc4ph",
200            CircusMesType::Ds => "ds",
201            CircusMesType::Dsif => "dsif",
202            CircusMesType::Tmpl => "tmpl",
203            CircusMesType::Nightshade => "nightshade",
204        }
205    }
206}
207
208/// Extra configuration options for the script.
209#[derive(Debug, Clone, Default)]
210pub struct ExtraConfig {
211    #[cfg(feature = "circus")]
212    /// Circus Game for circus MES script.
213    pub circus_mes_type: Option<CircusMesType>,
214    #[cfg(feature = "escude-arc")]
215    /// Whether to use fake compression for Escude archive
216    pub escude_fake_compress: bool,
217    #[cfg(feature = "escude")]
218    /// The path to the Escude enum script file (enum_scr.bin)
219    pub escude_enum_scr: Option<String>,
220    #[cfg(feature = "bgi")]
221    /// Duplicate same strings when importing into BGI scripts.
222    /// Enable this will cause BGI scripts to become very large.
223    pub bgi_import_duplicate: bool,
224    #[cfg(feature = "bgi")]
225    /// Disable appending new strings to the end of BGI scripts.
226    /// Disable may cause BGI scripts broken.
227    pub bgi_disable_append: bool,
228    #[cfg(feature = "image")]
229    /// Output image type
230    pub image_type: Option<ImageOutputType>,
231    #[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
232    /// Detect all files in BGI archive as SysGrp Images. By default, only files which name is `sysgrp.arc` will enabled this.
233    pub bgi_is_sysgrp_arc: Option<bool>,
234    #[cfg(feature = "bgi-img")]
235    /// Whether to create scrambled SysGrp images. When in import mode, the default value depends on the original image.
236    /// When in creation mode, it is not enabled by default.
237    pub bgi_img_scramble: Option<bool>,
238    #[cfg(feature = "cat-system-arc")]
239    /// CatSystem2 engine int archive password
240    pub cat_system_int_encrypt_password: Option<String>,
241    #[cfg(feature = "cat-system-img")]
242    /// Draw CatSystem2 image on canvas (if canvas width and height are specified in file)
243    pub cat_system_image_canvas: bool,
244    #[cfg(feature = "kirikiri")]
245    /// Kirikiri language index in script. If not specified, the first language will be used.
246    pub kirikiri_language_index: Option<usize>,
247    #[cfg(feature = "kirikiri")]
248    /// Export COMU message to extra json file. (for Kirikiri SCN script.)
249    /// Only CIRCUS's game have COMU message.
250    pub kirikiri_export_comumode: bool,
251    #[cfg(feature = "kirikiri")]
252    /// Kirikiri COMU message translation. key is original text, value is translated text.
253    pub kirikiri_comumode_json: Option<std::sync::Arc<HashMap<String, String>>>,
254    #[cfg(feature = "kirikiri")]
255    /// Remove empty lines in Kirikiri KS script.
256    pub kirikiri_remove_empty_lines: bool,
257    #[cfg(feature = "kirikiri")]
258    /// Kirikiri name commands, used to extract names from ks script.
259    pub kirikiri_name_commands: std::sync::Arc<std::collections::HashSet<String>>,
260    #[cfg(feature = "kirikiri")]
261    /// Kirikiri message commands, used to extract more message from ks script.
262    pub kirikiri_message_commands: std::sync::Arc<std::collections::HashSet<String>>,
263    #[cfg(feature = "bgi-arc")]
264    /// Whether to compress files in BGI archive when packing BGI archive.
265    pub bgi_compress_file: bool,
266    #[cfg(feature = "bgi-arc")]
267    /// Minimum length of match size for DSC compression. Possible values are 2-256.
268    pub bgi_compress_min_len: usize,
269    #[cfg(feature = "emote-img")]
270    /// Whether to overlay PIMG images. (By default, true if all layers are not group layers.)
271    pub emote_pimg_overlay: Option<bool>,
272    #[cfg(feature = "artemis-arc")]
273    /// Disable Artemis archive (.pfs) XOR encryption when packing.
274    pub artemis_arc_disable_xor: bool,
275    #[cfg(feature = "artemis")]
276    /// Artemis script indent size, used to format Artemis script.
277    /// Default is 4 spaces.
278    pub artemis_indent: Option<usize>,
279    #[cfg(feature = "artemis")]
280    /// Disable Artemis script indent, used to format Artemis script.
281    pub artemis_no_indent: bool,
282    #[cfg(feature = "artemis")]
283    /// Max line width in Artemis script, used to format Artemis script.
284    pub artemis_max_line_width: usize,
285    #[cfg(feature = "artemis")]
286    /// Specify the language of Artemis AST script.
287    /// If not specified, the first language will be used.
288    pub artemis_ast_lang: Option<String>,
289    #[cfg(feature = "cat-system")]
290    /// CatSystem2 CSTL script language, used to extract messages from CSTL script.
291    /// If not specified, the first language will be used.
292    pub cat_system_cstl_lang: Option<String>,
293    #[cfg(feature = "flate2")]
294    /// Zlib compression level. 0 means no compression, 9 means best compression.
295    pub zlib_compression_level: u32,
296    #[cfg(feature = "image")]
297    /// PNG compression level.
298    pub png_compression_level: PngCompressionLevel,
299    #[cfg(feature = "circus-img")]
300    /// Keep original BPP when importing Circus CRX images.
301    pub circus_crx_keep_original_bpp: bool,
302    #[cfg(feature = "circus-img")]
303    /// Use zstd compression for Circus CRX images. (CIRCUS Engine don't support this. Hook is required.)
304    pub circus_crx_zstd: bool,
305    #[cfg(feature = "zstd")]
306    /// Zstd compression level. 0 means default compression level (3), 22 means best compression.
307    pub zstd_compression_level: i32,
308    #[cfg(feature = "circus-img")]
309    /// Circus CRX image row type mode
310    pub circus_crx_mode: crate::scripts::circus::image::crx::CircusCrxMode,
311    #[cfg(feature = "ex-hibit")]
312    /// ExHibit xor key for rld script.
313    /// Use [ReExHIBIT](https://github.com/ZQF-ReVN/RxExHIBIT) to find the key.
314    pub ex_hibit_rld_xor_key: Option<u32>,
315    #[cfg(feature = "ex-hibit")]
316    /// ExHibit def.rld xor key.
317    pub ex_hibit_rld_def_xor_key: Option<u32>,
318    #[cfg(feature = "ex-hibit")]
319    /// ExHibit rld xor keys.
320    pub ex_hibit_rld_keys: Option<Box<[u32; 0x100]>>,
321    #[cfg(feature = "ex-hibit")]
322    /// ExHibit def.rld xor keys.
323    pub ex_hibit_rld_def_keys: Option<Box<[u32; 0x100]>>,
324    #[cfg(feature = "mozjpeg")]
325    /// JPEG quality for output images, 0-100. 100 means best quality.
326    pub jpeg_quality: u8,
327    #[cfg(feature = "webp")]
328    /// Use WebP lossless compression for output images.
329    pub webp_lossless: bool,
330    #[cfg(feature = "webp")]
331    /// WebP quality for output images, 0-100. 100 means best quality.
332    pub webp_quality: u8,
333    #[cfg(feature = "circus-img")]
334    /// Draw Circus CRX images on canvas (if canvas width and height are specified in file)
335    pub circus_crx_canvas: bool,
336    /// Try use YAML format instead of JSON when custom exporting.
337    pub custom_yaml: bool,
338    #[cfg(feature = "entis-gls")]
339    /// Entis GLS srcxml script language, used to extract messages from srcxml script.
340    /// If not specified, the first language will be used.
341    pub entis_gls_srcxml_lang: Option<String>,
342    #[cfg(feature = "will-plus")]
343    /// Disable disassembly for WillPlus ws2 script.
344    /// Use another parser to parse the script.
345    /// Should only be used when the default parser not works well.
346    pub will_plus_ws2_no_disasm: bool,
347    #[cfg(feature = "artemis")]
348    /// Artemis Engine blacklist tag names for TXT script.
349    /// This is used to ignore these tags when finding names in Artemis TXT script.
350    pub artemis_txt_blacklist_names: std::sync::Arc<std::collections::HashSet<String>>,
351    #[cfg(feature = "artemis")]
352    /// Specify the language of Artemis TXT script.
353    /// If not specified, the first language will be used.
354    pub artemis_txt_lang: Option<String>,
355}
356
357#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
358/// Script type
359pub enum ScriptType {
360    #[cfg(feature = "artemis")]
361    /// Artemis Engine AST script
362    Artemis,
363    #[cfg(feature = "artemis")]
364    /// Artemis Engine ASB script
365    ArtemisAsb,
366    #[cfg(feature = "artemis")]
367    /// Artemis Engine TXT script
368    ArtemisTxt,
369    #[cfg(feature = "artemis-arc")]
370    #[value(alias("pfs"))]
371    /// Artemis archive (pfs)
372    ArtemisArc,
373    #[cfg(feature = "bgi")]
374    #[value(alias("ethornell"))]
375    /// Buriko General Interpreter/Ethornell Script
376    BGI,
377    #[cfg(feature = "bgi")]
378    #[value(alias("ethornell-bsi"))]
379    /// Buriko General Interpreter/Ethornell bsi script (._bsi)
380    BGIBsi,
381    #[cfg(feature = "bgi")]
382    #[value(alias("ethornell-bp"))]
383    /// Buriko General Interpreter/Ethornell bp script (._bp)
384    BGIBp,
385    #[cfg(feature = "bgi-arc")]
386    #[value(alias = "ethornell-arc-v1")]
387    /// Buriko General Interpreter/Ethornell archive v1
388    BGIArcV1,
389    #[cfg(feature = "bgi-arc")]
390    #[value(alias = "ethornell-arc-v2", alias = "bgi-arc", alias = "ethornell-arc")]
391    /// Buriko General Interpreter/Ethornell archive v2
392    BGIArcV2,
393    #[cfg(feature = "bgi-arc")]
394    #[value(alias("ethornell-dsc"))]
395    /// Buriko General Interpreter/Ethornell compressed file (DSC)
396    BGIDsc,
397    #[cfg(feature = "bgi-audio")]
398    #[value(alias("ethornell-audio"))]
399    /// Buriko General Interpreter/Ethornell audio file (Ogg/Vorbis)
400    BGIAudio,
401    #[cfg(feature = "bgi-img")]
402    #[value(alias("ethornell-img"))]
403    /// Buriko General Interpreter/Ethornell image (Image files in sysgrp.arc)
404    BGIImg,
405    #[cfg(feature = "bgi-img")]
406    #[value(alias("ethornell-cbg"))]
407    /// Buriko General Interpreter/Ethornell Compressed Background image (CBG)
408    BGICbg,
409    #[cfg(feature = "cat-system")]
410    /// CatSystem2 engine scene script
411    CatSystem,
412    #[cfg(feature = "cat-system")]
413    /// CatSystem2 engine CSTL script
414    CatSystemCstl,
415    #[cfg(feature = "cat-system-arc")]
416    /// CatSystem2 engine archive
417    CatSystemInt,
418    #[cfg(feature = "cat-system-img")]
419    /// CatSystem2 engine image
420    CatSystemHg3,
421    #[cfg(feature = "circus")]
422    /// Circus MES script
423    Circus,
424    #[cfg(feature = "circus-arc")]
425    /// Circus Image archive
426    CircusCrm,
427    #[cfg(feature = "circus-arc")]
428    /// Circus DAT archive
429    CircusDat,
430    #[cfg(feature = "circus-arc")]
431    /// Circus PCK archive
432    CircusPck,
433    #[cfg(feature = "circus-audio")]
434    /// Circus PCM audio
435    CircusPcm,
436    #[cfg(feature = "circus-img")]
437    /// Circus CRX Image
438    CircusCrx,
439    #[cfg(feature = "circus-img")]
440    /// Circus Differential Image
441    CircusCrxd,
442    #[cfg(feature = "emote-img")]
443    #[value(alias("pimg"))]
444    /// Emote PIMG image
445    EmotePimg,
446    #[cfg(feature = "emote-img")]
447    #[value(alias("dref"))]
448    /// Emote DREF(DPAK-referenced) image
449    EmoteDref,
450    #[cfg(feature = "entis-gls")]
451    /// Entis GLS srcxml Script
452    EntisGls,
453    #[cfg(feature = "escude-arc")]
454    /// Escude bin archive
455    EscudeArc,
456    #[cfg(feature = "escude")]
457    /// Escude bin script
458    Escude,
459    #[cfg(feature = "escude")]
460    /// Escude list script
461    EscudeList,
462    #[cfg(feature = "ex-hibit")]
463    /// ExHibit rld script
464    ExHibit,
465    #[cfg(feature = "hexen-haus")]
466    /// HexenHaus bin script
467    HexenHaus,
468    #[cfg(feature = "kirikiri")]
469    #[value(alias("kr-scn"))]
470    /// Kirikiri SCN script
471    KirikiriScn,
472    #[cfg(feature = "kirikiri")]
473    #[value(alias("kr-simple-crypt"))]
474    /// Kirikiri SimpleCrypt's text file
475    KirikiriSimpleCrypt,
476    #[cfg(feature = "kirikiri")]
477    #[value(alias = "kr", alias = "kr-ks", alias = "kirikiri-ks")]
478    /// Kirikiri script
479    Kirikiri,
480    #[cfg(feature = "kirikiri-img")]
481    #[value(alias("kr-tlg"))]
482    /// Kirikiri TLG image
483    KirikiriTlg,
484    #[cfg(feature = "kirikiri")]
485    #[value(alias("kr-mdf"))]
486    /// Kirikiri MDF (zlib compressed) file
487    KirikiriMdf,
488    #[cfg(feature = "softpal")]
489    /// Softpal src script
490    Softpal,
491    #[cfg(feature = "will-plus")]
492    #[value(alias("adv-hd-ws2"))]
493    /// WillPlus ws2 script
494    WillPlusWs2,
495    #[cfg(feature = "yaneurao-itufuru")]
496    #[value(alias("itufuru"))]
497    /// Yaneurao Itufuru script
498    YaneuraoItufuru,
499    #[cfg(feature = "yaneurao-itufuru")]
500    #[value(alias("itufuru-arc"))]
501    /// Yaneurao Itufuru script archive
502    YaneuraoItufuruArc,
503}
504
505#[derive(Clone, Debug, Serialize, Deserialize)]
506/// Message structure for scripts
507pub struct Message {
508    #[serde(skip_serializing_if = "Option::is_none")]
509    /// Optional name for the message, used in some scripts.
510    pub name: Option<String>,
511    /// The actual message content.
512    pub message: String,
513}
514
515impl Message {
516    /// Creates a new `Message` instance.
517    pub fn new(message: String, name: Option<String>) -> Self {
518        Message { message, name }
519    }
520}
521
522/// Result of script operation.
523pub enum ScriptResult {
524    /// Operation completed successfully.
525    Ok,
526    /// Operation completed without any changes.
527    /// For example, no messages found in the script.
528    Ignored,
529}
530
531#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
532/// Format type (for CLI)
533pub enum FormatType {
534    /// Wrap line with fixed length
535    Fixed,
536    /// Do not wrap line
537    None,
538}
539
540/// Format options
541pub enum FormatOptions {
542    /// Wrap line with fixed length
543    Fixed {
544        /// Fixed length
545        length: usize,
546        /// Whether to keep original line breaks
547        keep_original: bool,
548    },
549    /// Do not wrap line
550    None,
551}
552
553#[derive(Debug, Serialize, Deserialize)]
554/// Name table cell
555pub struct NameTableCell {
556    #[serde(rename = "JP_Name")]
557    /// Original name
558    pub jp_name: String,
559    #[serde(rename = "CN_Name")]
560    /// Translated name
561    pub cn_name: String,
562    #[serde(rename = "Count")]
563    /// Number of times this name appears in the script
564    pub count: usize,
565}
566
567#[derive(Debug, Serialize, Deserialize)]
568/// Replacement table for string replacements
569pub struct ReplacementTable {
570    #[serde(flatten)]
571    /// Map of original strings to their replacements
572    pub map: HashMap<String, String>,
573}
574
575#[cfg(feature = "image")]
576#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
577/// Image color type
578pub enum ImageColorType {
579    /// Grayscale image
580    Grayscale,
581    /// RGB image
582    Rgb,
583    /// RGBA image
584    Rgba,
585    /// BGR image
586    Bgr,
587    /// BGRA image
588    Bgra,
589}
590
591#[cfg(feature = "image")]
592impl ImageColorType {
593    /// Returns the number of bytes per pixel for the color type and depth.
594    pub fn bpp(&self, depth: u8) -> u16 {
595        match self {
596            ImageColorType::Grayscale => depth as u16,
597            ImageColorType::Rgb => depth as u16 * 3,
598            ImageColorType::Rgba => depth as u16 * 4,
599            ImageColorType::Bgr => depth as u16 * 3,
600            ImageColorType::Bgra => depth as u16 * 4,
601        }
602    }
603}
604
605#[cfg(feature = "image")]
606#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
607/// Image output type
608pub enum ImageOutputType {
609    /// PNG image
610    Png,
611    #[cfg(feature = "image-jpg")]
612    /// JPEG image
613    Jpg,
614    #[cfg(feature = "image-webp")]
615    /// WebP image
616    Webp,
617}
618
619#[cfg(feature = "image")]
620impl TryFrom<&str> for ImageOutputType {
621    type Error = anyhow::Error;
622
623    /// Try to convert a extension string to an `ImageOutputType`.
624    /// Extensions are case-insensitive.
625    fn try_from(value: &str) -> Result<Self, Self::Error> {
626        match value.to_ascii_lowercase().as_str() {
627            "png" => Ok(ImageOutputType::Png),
628            #[cfg(feature = "image-jpg")]
629            "jpg" => Ok(ImageOutputType::Jpg),
630            #[cfg(feature = "image-jpg")]
631            "jpeg" => Ok(ImageOutputType::Jpg),
632            #[cfg(feature = "image-webp")]
633            "webp" => Ok(ImageOutputType::Webp),
634            _ => Err(anyhow::anyhow!("Unsupported image output type: {}", value)),
635        }
636    }
637}
638
639#[cfg(feature = "image")]
640impl TryFrom<&std::path::Path> for ImageOutputType {
641    type Error = anyhow::Error;
642
643    fn try_from(value: &std::path::Path) -> Result<Self, Self::Error> {
644        if let Some(ext) = value.extension() {
645            Self::try_from(ext.to_string_lossy().as_ref())
646        } else {
647            Err(anyhow::anyhow!("No extension found in path"))
648        }
649    }
650}
651
652#[cfg(feature = "image")]
653impl AsRef<str> for ImageOutputType {
654    /// Returns the extension for the image output type.
655    fn as_ref(&self) -> &str {
656        match self {
657            ImageOutputType::Png => "png",
658            #[cfg(feature = "image-jpg")]
659            ImageOutputType::Jpg => "jpg",
660            #[cfg(feature = "image-webp")]
661            ImageOutputType::Webp => "webp",
662        }
663    }
664}
665
666#[cfg(feature = "image")]
667#[derive(Clone, Debug)]
668/// Image data
669pub struct ImageData {
670    /// Image width in pixels
671    pub width: u32,
672    /// Image height in pixels
673    pub height: u32,
674    /// Image color type
675    pub color_type: ImageColorType,
676    /// Image depth in bits per channel
677    pub depth: u8,
678    /// Image data
679    pub data: Vec<u8>,
680}
681
682#[cfg(feature = "image")]
683#[derive(Clone, Debug)]
684/// Image data with name
685pub struct ImageDataWithName {
686    /// Image name
687    pub name: String,
688    /// Image data
689    pub data: ImageData,
690}
691
692#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
693/// BOM type
694pub enum BomType {
695    /// No BOM
696    None,
697    /// UTF-8 BOM
698    Utf8,
699    /// UTF-16 Little Endian BOM
700    Utf16LE,
701    /// UTF-16 Big Endian BOM
702    Utf16BE,
703}
704
705impl BomType {
706    /// Returns the byte sequence for the BOM type.
707    pub fn as_bytes(&self) -> &'static [u8] {
708        match self {
709            BomType::None => &[],
710            BomType::Utf8 => b"\xEF\xBB\xBF",
711            BomType::Utf16LE => b"\xFF\xFE",
712            BomType::Utf16BE => b"\xFE\xFF",
713        }
714    }
715}
716
717#[cfg(feature = "image")]
718#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
719/// PNG compression level
720pub enum PngCompressionLevel {
721    #[value(alias = "d")]
722    /// Default level
723    Default,
724    #[value(alias = "f")]
725    /// Fast minimal compression
726    Fast,
727    #[value(alias = "b")]
728    /// Higher compression level
729    ///
730    /// Best in this context isn't actually the highest possible level
731    /// the encoder can do, but is meant to emulate the `Best` setting in the `Flate2`
732    /// library.
733    Best,
734}
735
736#[cfg(feature = "image")]
737impl Default for PngCompressionLevel {
738    fn default() -> Self {
739        PngCompressionLevel::Default
740    }
741}
742
743#[cfg(feature = "image")]
744impl PngCompressionLevel {
745    /// Converts the [PngCompressionLevel] to a [png::Compression] enum.
746    pub fn to_compression(&self) -> png::Compression {
747        match self {
748            PngCompressionLevel::Default => png::Compression::Default,
749            PngCompressionLevel::Fast => png::Compression::Fast,
750            PngCompressionLevel::Best => png::Compression::Best,
751        }
752    }
753}