diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index df30416..dc9d0be 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,6 +16,10 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install libfontconfig libfontconfig1-dev - name: Build run: cargo build --verbose - name: Run tests @@ -27,5 +31,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install libfontconfig libfontconfig1-dev - name: Clippy run: cargo clippy -- -Dwarnings diff --git a/Cargo.lock b/Cargo.lock index db3faeb..c38f589 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" @@ -171,6 +171,12 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.4" @@ -215,10 +221,11 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cc" -version = "1.2.7" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -280,6 +287,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -292,6 +308,58 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "19.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -367,6 +435,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "expat-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" +dependencies = [ + "cmake", + "pkg-config", +] + [[package]] name = "exr" version = "1.73.0" @@ -411,6 +489,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "flate2" version = "1.0.35" @@ -421,6 +505,45 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "font-loader" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c49d6b4c11dca1a1dd931a34a9f397e2da91abe3de4110505f3530a80e560b52" +dependencies = [ + "core-foundation", + "core-text", + "libc", + "servo-fontconfig", + "winapi", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "freetype-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" +dependencies = [ + "cmake", + "libc", + "pkg-config", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -526,11 +649,13 @@ dependencies = [ [[package]] name = "imgii" -version = "0.7.6" +version = "0.8.0" dependencies = [ "ab_glyph", + "anyhow", "clap", "env_logger", + "font-loader", "image", "imageproc", "log", @@ -538,6 +663,7 @@ dependencies = [ "rascii_art_img", "rayon", "regex", + "thiserror 2.0.18", ] [[package]] @@ -885,7 +1011,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags", + "bitflags 2.9.4", "crc32fast", "fdeflate", "flate2", @@ -1019,9 +1145,9 @@ dependencies = [ [[package]] name = "rascii_art_img" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12329487092c3301421bf8625241af47e375ebd67fc01b98d0b7527e3a398145" +checksum = "606a1fb24a05ce8558ad454e7df938e2f29da101b7f3daaa70ef78d78c533e91" dependencies = [ "ansi_term", "clap", @@ -1059,7 +1185,7 @@ dependencies = [ "rand_chacha", "simd_helpers", "system-deps", - "thiserror", + "thiserror 1.0.69", "v_frame", "wasm-bindgen", ] @@ -1178,6 +1304,27 @@ dependencies = [ "serde", ] +[[package]] +name = "servo-fontconfig" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" +dependencies = [ + "libc", + "servo-fontconfig-sys", +] + +[[package]] +name = "servo-fontconfig-sys" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" +dependencies = [ + "expat-sys", + "freetype-sys", + "pkg-config", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1260,7 +1407,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -1274,6 +1430,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiff" version = "0.10.3" diff --git a/Cargo.toml b/Cargo.toml index 7d3abdc..5f41510 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "imgii" authors = ["Stattek"] -version = "0.7.6" +version = "0.8.0" edition = "2024" license = "MIT" description = "ASCII Image Generator" @@ -12,15 +12,18 @@ readme = "README.md" [dependencies] ab_glyph = "0.2.32" +anyhow = "1.0.102" clap = "4.5.48" env_logger = "0.11.8" +font-loader = "0.11.0" image = { version = "0.25.8", features = ["gif"]} imageproc = "0.25.0" log = "0.4.28" num_cpus = "1.17.0" -rascii_art_img = "0.4.7" +rascii_art_img = "0.4.8" rayon = "1.11.0" regex = "1.12.1" +thiserror = "2.0.18" [profile.release] opt-level=3 diff --git a/README.md b/README.md index da69f66..e35e8a9 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,22 @@ Supports popular image types as input, such as PNG, JPEG, GIF, WEBP, and more. ## Installing +### Requirements + +Building requires the following dependencies: + +For Ubuntu/Debian: + +```sh +sudo apt-get install libfontconfig libfontconfig1-dev +``` + +For Arch Linux: + +```sh +sudo pacman -S fontconfig +``` + Install `imgii` as a binary: ```bash @@ -41,7 +57,7 @@ Usage: imgii [OPTIONS] [FINAL_IMAGE_INDEX] Arguments: - Path to the input image + Path to the input image. Can also specify a format for an input, if is also set to the final input image index. @@ -66,6 +82,9 @@ Options: -H, --height Height (in characters) of the output image, if not specified, it will be calculated to keep the aspect ratio + -n, --font-name + The name of the installed monospace font to use. Must be an installed TrueType font (.ttf) + -f, --font-size The font size of the output image. Larger font sizes incur harsher performance penalties. @@ -98,7 +117,7 @@ Options: ```bash # Makes assumptions about how to convert the image. -cargo run --release -- input_img.png output_ascii_img.png +imgii input_img.png output_ascii_img.png ``` ### Examples Using Arguments @@ -106,23 +125,26 @@ cargo run --release -- input_img.png output_ascii_img.png ```bash # renders an image with a width of 150 characters, using the block charset. # NOTE: Just setting --width will maintain the aspect ratio of the input -cargo run --release -- --charset block --width 100 input_img.png output_ascii_img.png +imgii --charset block --width 100 input_img.png output_ascii_img.png # Sets a background behind the result, can often look better -cargo run --release -- --background input.gif output_ascii.gif +imgii --background input.gif output_ascii.gif # Forces the output to have a width of 10 characters and a height of 100 characters. -cargo run --release -- --width 10 --height 100 input.gif output_ascii.gif +imgii --width 10 --height 100 input.gif output_ascii.gif + +# Uses a specific font +imgii input.png output.png --font-name "Adwaita Mono" ``` ### Example With More Verbose Output ```bash # Running with debug logs enabled -RUST_LOG=debug cargo run --release -- --background input_img.png output_ascii_img.png +RUST_LOG=debug imgii --background input_img.png output_ascii_img.png # Running with info logs enabled -RUST_LOG=info cargo run --release -- --background input_img.png output_ascii_img.png +RUST_LOG=info imgii --background input_img.png output_ascii_img.png ``` ## Supported Output Image Types @@ -137,7 +159,7 @@ Specifying an output type can be done simply by changing the filetype in the out ```bash # Takes an input GIF and imgii will convert to an output GIF -cargo run --release -- input.gif output.gif +imgii input.gif output.gif ``` ## Example Output diff --git a/fonts/UbuntuMono.ttf b/fonts/UbuntuMono.ttf deleted file mode 100644 index 23f0350..0000000 Binary files a/fonts/UbuntuMono.ttf and /dev/null differ diff --git a/src/conversion/converters/generic_converter.rs b/src/conversion/converters/generic_converter.rs index 68d20b3..65ed59d 100644 --- a/src/conversion/converters/generic_converter.rs +++ b/src/conversion/converters/generic_converter.rs @@ -1,21 +1,17 @@ +//! Generic converter implementation for rendering ASCII as individual images. + use std::{collections::HashMap, sync::Arc}; use crate::{ ImgiiOptions, conversion::{image_data::ImageData, render_char_to_png::str_to_png}, - error::{FontError, ImgiiError, ParseIntError, RenderError}, + error::{FontError, ImageError, ImgiiError, ParseError}, }; use super::super::render_char_to_png::{ColoredStr, str_to_transparent_png}; use ab_glyph::FontRef; use regex::Regex; -// TODO: Read this font at runtime instead and allow the user to choose - -// read bytes for the font -const FONT_FILE: &str = "../../../fonts/UbuntuMono.ttf"; -const FONT_BYTES: &[u8] = include_bytes!("../../../fonts/UbuntuMono.ttf"); - /// Simple struct for holding a 2d image with its width and height. #[derive(Clone, Debug)] pub(crate) struct Imgii2dImage { @@ -36,9 +32,11 @@ pub(crate) fn render_ascii_generic( ascii_text: String, ) -> Result { // set up font for rendering - let font = FontRef::try_from_slice(FONT_BYTES) + let font = FontRef::try_from_slice(imgii_options.font().as_slice()) // there's nothing useful in this error, convert it! - .map_err(|_| FontError::new(String::from(FONT_FILE)))?; + .map_err(|_| FontError::FontLoad { + font_name: String::from(imgii_options.font_name()), + })?; // 2d Vec of images for each character let mut image_2d_vec = Vec::new(); @@ -70,14 +68,20 @@ pub(crate) fn render_ascii_generic( // create the image for this character for (_full_str, [r, g, b, the_str]) in re.captures_iter(line).map(|c| c.extract()) { - let red = r.parse::().map_err(|err| { - ParseIntError::new(String::from("red"), String::from(the_str), err) + let red = r.parse::().map_err(|err| ParseError::ParseColor { + value_name: String::from("red"), + the_str: String::from(the_str), + err, })?; - let green = g.parse::().map_err(|err| { - ParseIntError::new(String::from("green"), String::from(the_str), err) + let green = g.parse::().map_err(|err| ParseError::ParseColor { + value_name: String::from("green"), + the_str: String::from(the_str), + err, })?; - let blue = b.parse::().map_err(|err| { - ParseIntError::new(String::from("blue"), String::from(the_str), err) + let blue = b.parse::().map_err(|err| ParseError::ParseColor { + value_name: String::from("blue"), + the_str: String::from(the_str), + err, })?; let generated_png = { @@ -102,13 +106,18 @@ pub(crate) fn render_ascii_generic( // we haven't rendered this image before, so render it let image_data = Arc::from(str_to_png(&colored, &font, imgii_options)); let result = rendered_images.insert(colored, image_data.clone()); - if result.is_some() { - return Err(RenderError::new(String::from( - "this image should not exist already in the hash map", - )) - .into()); + match result { + None => image_data, + Some(colored) => { + // the returned image from insert should be the same as the one + // we put in + return Err(ImageError::Render { + reason: format!( + "the image ({colored:?}) should not exist already in the hash map", + ), + }.into()); + } } - image_data } } } @@ -127,10 +136,12 @@ pub(crate) fn render_ascii_generic( } else { // check that this width is always the same now that we have the width if width != line_width { - return Err(RenderError::new(format!( - "width {} is not equal to the current line width {}", - width, line_width - )) + return Err(ImageError::Render { + reason: format!( + "width {} is not equal to the current line width {}", + width, line_width + ), + } .into()); } } @@ -139,11 +150,13 @@ pub(crate) fn render_ascii_generic( // Check that the length of the final vector is what we expect. If not, something has gone // terribly wrong, and we should not continue. if width * height != image_2d_vec.len() { - return Err(RenderError::new(format!( - "expected length of the 2d vector was {} but got {}", - width * height, - image_2d_vec.len() - )) + return Err(ImageError::Render { + reason: format!( + "expected length of the 2d vector was {} but got {}", + width * height, + image_2d_vec.len() + ), + } .into()); } diff --git a/src/conversion/converters/gif_converter.rs b/src/conversion/converters/gif_converter.rs index ff0356a..764fbb9 100644 --- a/src/conversion/converters/gif_converter.rs +++ b/src/conversion/converters/gif_converter.rs @@ -1,8 +1,10 @@ +//! Handles rendering for GIF. + use std::{fs::File, io::BufReader}; use crate::{ conversion::converters::generic_converter::{Imgii2dImage, render_ascii_generic}, - error::{BoxedDynErr, ImgiiError}, + error::ImgiiError, options::{ImgiiOptions, RasciiOptions}, }; @@ -211,9 +213,9 @@ pub(crate) fn read_deconstructed_gif( Err(err) => { // the input data in the gif was wrong - // convert to boxed err then convert to ImgiiError - let err_box: BoxedDynErr = Box::new(err); // have to specify `dyn Error`. ugh. - return Err(err_box.into()); + // convert to anyhow err then convert to ImgiiError + let err = anyhow::Error::new(err); + return Err(err.into()); } }; @@ -222,8 +224,8 @@ pub(crate) fn read_deconstructed_gif( Ok(frames) => frames, Err(err) => { // the data is malformed in this GIF - let err_box: BoxedDynErr = Box::new(err); - return Err(err_box.into()); + let err = anyhow::Error::new(err); + return Err(err.into()); } }; let ret = frames diff --git a/src/conversion/converters/png_converter.rs b/src/conversion/converters/png_converter.rs index 7144589..e9e788f 100644 --- a/src/conversion/converters/png_converter.rs +++ b/src/conversion/converters/png_converter.rs @@ -1,7 +1,9 @@ +//! Handles implementation for rendering PNG. + use super::generic_converter::render_ascii_generic; use crate::{ conversion::converters::generic_converter::Imgii2dImage, - error::{BoxedDynErr, ImgiiError}, + error::ImgiiError, options::{ImgiiOptions, RasciiOptions}, }; @@ -39,9 +41,10 @@ pub(crate) fn read_png_as_ascii( ) -> Result { // render the ascii text with RASCII let mut ascii_text = String::new(); - let loaded_img = open(input_file_name).map_err(|err| -> BoxedDynErr { Box::new(err) })?; + let loaded_img = + open(input_file_name).map_err(|err| -> ImgiiError { anyhow::Error::new(err).into() })?; render_image_to(&loaded_img, &mut ascii_text, rascii_options) - .map_err(|err| -> BoxedDynErr { Box::new(err) })?; + .map_err(|err| -> ImgiiError { anyhow::Error::new(err).into() })?; Ok(ascii_text) } diff --git a/src/conversion/image_data.rs b/src/conversion/image_data.rs index 50ef605..548e814 100644 --- a/src/conversion/image_data.rs +++ b/src/conversion/image_data.rs @@ -1,3 +1,5 @@ +//! Implementation for generic image data. + use image::ImageBuffer; // easier to read diff --git a/src/conversion/image_writer.rs b/src/conversion/image_writer.rs index 13c5ef5..02bfeea 100644 --- a/src/conversion/image_writer.rs +++ b/src/conversion/image_writer.rs @@ -1,9 +1,11 @@ +//! Implementation for writing ascii as an image. + use crate::{ conversion::{ converters::generic_converter::Imgii2dImage, image_data::{ImageData, InternalImage}, }, - error::{ImgiiError, InvalidParameterError}, + error::ImgiiError, }; use rayon::prelude::*; @@ -37,7 +39,7 @@ impl AsciiImageWriter { pub(crate) fn from_2d_vec(the_image: Imgii2dImage) -> Result { if the_image.image_2d.is_empty() { // no image to build - return Err(InvalidParameterError::new(String::from("parts")).into()); + return Err(ImgiiError::InvalidArgument); } // find out the new canvas size diff --git a/src/conversion/render_char_to_png.rs b/src/conversion/render_char_to_png.rs index 5b13dbf..c570a2d 100644 --- a/src/conversion/render_char_to_png.rs +++ b/src/conversion/render_char_to_png.rs @@ -1,3 +1,5 @@ +//! Implementation for rendering a character as an image. + use crate::{conversion::image_data::ImageData, options::ImgiiOptions}; use ab_glyph::{FontRef, PxScale}; use image::{ImageBuffer, Rgba}; diff --git a/src/error.rs b/src/error.rs index f1f8624..931190d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,397 +1,90 @@ -use std::{error::Error, fmt::Display}; +//! Error implementation for `imgii` errors. -/* -* NOTE: Struct definitions go below. -*/ - -/// This is the type that we want to use for boxed errors to make sure they're thread-safe. -type DynError = dyn Error + Send + Sync; - -/// Boxed error type for converting to [`ImgiiError`]. -/// -/// # Example -/// -/// This is useful for errors from other crates, which we don't know or care about their errors. -/// -/// ``` -/// use image::open; -/// use imgii::error::BoxedDynErr; -/// // open an image, convert it into an ImgiiError -/// let input_file_name = "test.png"; -/// let loaded_img = open(input_file_name).map_err(|err| -> BoxedDynErr { Box::new(err) }); -/// ``` -pub type BoxedDynErr = Box; +use thiserror::Error; /// An error that can be returned by Imgii. Represents errors when converting images. -#[derive(Debug)] +#[derive(Error, Debug)] pub enum ImgiiError { /// Errors related to fonts. - Font(FontError), + #[error("{0}")] + Font(#[from] FontError), /// Error related to parsing ASCII. - Parse(ParseError), - /// Unknown, unspecified internal error. - Internal(InternalError), + #[error("{0}")] + Parse(#[from] ParseError), /// Error related to image. - Image(ImageError), + #[error("{0}")] + Image(#[from] ImageError), /// I/O operation error. - Io(std::io::Error), - Other(OtherError), + #[error("{0}")] + Io(#[from] std::io::Error), + #[error("{0}")] + Other(#[from] anyhow::Error), + /// Invalid argument error. + #[error("invalid argument(s) provided")] + InvalidArgument, + /// Unknown, unspecified internal error. + #[error("an internal error has occurred")] + Internal, } -/// Font error. Use this when something related to the font has gone wrong. -/// -/// Suberror of [`ImgiiError`]. -#[derive(Debug, Clone)] -pub struct FontError { - font_name: String, +#[derive(Debug, Error, Clone)] +pub enum FontError { + #[error("could not load font {font_name}")] + FontLoad { font_name: String }, } /// ASCII text parsing error. Use this when parsing ASCII text and something goes wrong. /// /// Suberror of [`ImgiiError`]. -#[derive(Debug, Clone)] +#[derive(Error, Debug, Clone)] pub enum ParseError { /// Handles regex errors. - Regex(regex::Error), + #[error("imgii regular expression failed ({0})")] + Regex(#[from] regex::Error), /// Handles errors related to parsing values. - ParseValue(ParseIntError), + #[error("could not parse int value from string due to {0}")] + ParseInt(#[from] std::num::ParseIntError), + /// Handles errors related to parsing colors from colored output + #[error("could not parse value {value_name} from string ({the_str}), parse error ({err})")] + ParseColor { + /// The name of the value to parse. + value_name: String, + /// The string that parsing was attempted on but failed. + the_str: String, + /// The `std::num::ParseIntError` that was emitted upon failure to parse. + err: std::num::ParseIntError, + }, } -/// Regular expression compiler error. -/// Doesn't actually implement Error, as it is easier to implement functionality in the super -/// error, [`ParseError`]. -/// -/// Suberror of [`ParseError`]. -#[derive(Debug, Clone)] -pub struct ParseIntError { - /// The name of the value to parse. - value_name: String, - /// The string that parsing was attempted on but failed. - the_str: String, - /// The `std::num::ParseIntError` that was emitted upon failure to parse. - err: std::num::ParseIntError, -} - -/// Some other, unknown internal error. -/// -/// Suberror of [`ImgiiError`]. -#[derive(Debug, Clone)] -pub struct InternalError; - /// Represents an error while creating an image. /// /// Suberror of [`ImgiiError`]. -#[derive(Debug, Clone)] +#[derive(Error, Debug, Clone)] pub enum ImageError { - InvalidParameter(InvalidParameterError), - ParseImage(ParseImageError), - Render(RenderError), -} - -/// Contains other errors. These are errors that can be emitted from other crates for various -/// reasons. -/// -/// Suberror of [`ImgiiError`]. -#[derive(Debug)] -pub struct OtherError { - // we can hold any other Error in here - other_err: BoxedDynErr, -} - -/// Represents an invalid parameter error when creating image. -/// -/// Suberror of [`ImageError`]. -#[derive(Debug, Clone)] -pub struct InvalidParameterError { - parameter_name: String, -} - -/// Represents an error that occurred while parsing an image. -/// -/// Suberror of [`ImageError`]. -#[derive(Debug, Clone)] -pub struct ParseImageError { - /// The row number of the image where this occurred. - image_row_number: usize, -} - -/// Represents an error that occurred while rendering an image. -/// -/// Suberror of [`ImageError`]. -#[derive(Debug, Clone)] -pub struct RenderError { - /// The reason for the render error. Since this error is intended to handle various internals - /// that aren't well represented by errors, this will explain why the error ocurred. - reason: String, + #[error("invalid parameter {parameter_name} found")] + InvalidParameter { parameter_name: String }, + #[error("parsing error found at row {image_row_number} of the image")] + ParseImage { + /// The row number of the image where this occurred. + image_row_number: usize, + }, + #[error("rendering failed because {reason}")] + Render { + /// The reason for the render error. Since this error is intended to handle various internals + /// that aren't well represented by errors, this will explain why the error ocurred. + reason: String, + }, } -/* - * NOTE: Implement `Display` below for errors that are intended to also implement Error. - */ - -impl Display for ImgiiError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ImgiiError::Font(font_error) => { - write!(f, "{font_error}") - } - ImgiiError::Parse(parse_error) => { - write!(f, "{parse_error}") - } - ImgiiError::Internal(internal_error) => { - write!(f, "{internal_error}") - } - ImgiiError::Io(io_error) => { - write!(f, "{io_error}") - } - ImgiiError::Other(other_error) => { - write!(f, "{other_error}") - } - ImgiiError::Image(image_error) => { - write!(f, "{image_error}") - } - } - } -} - -impl Display for FontError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "could not read font {}", self.font_name) - } -} - -impl Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Regex(err) => { - // let's just print their error out with ours - write!(f, "imgii regular expression failed ({})", err) - } - Self::ParseValue(err) => { - write!( - f, - "could not parse value {} from string ({}), parse error ({})", - err.value_name, err.the_str, err.err - ) - } - } - } -} - -impl Display for InternalError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "an internal error has occurred") - } -} - -impl Display for ImageError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ImageError::InvalidParameter(invalid_parameter_error) => { - write!( - f, - "invalid parameter {} found", - invalid_parameter_error.parameter_name - ) - } - ImageError::ParseImage(parse_image_error) => { - write!( - f, - "parsing error found at row {} of the image", - parse_image_error.image_row_number - ) - } - ImageError::Render(render_error) => { - write!( - f, - "error occurred while rendering image ({})", - render_error.reason - ) - } - } - } -} - -impl Display for OtherError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "an error from another crate/boxed error occurred ({})", - self.other_err - ) - } -} - -/* - * NOTE: Implement Error for error types below. - */ - -// we don't need to implement anything since there are default implementations for this trait -impl Error for FontError {} -impl Error for ParseError {} -impl Error for ImgiiError {} -impl Error for OtherError {} - /* * NOTE: Implement any `From` traits here. */ // NOTE: -// ImgiiError should only have to implement From for all of its direct suberrors, but Rust makes me -// do another From impl for the errors that can be converted into a suberror type too. -// -// The suberrors can implement From for anything that can be converted into them specifically, then -// for each of those, a simple From can be implemented for ImgiiError so we can call `.into()` -// to convert a suberror type into the main ImgiiError type. -// -// This makes it easier to maintain, as more errors are added. This pattern should be replicated for -// suberrors which have their own suberrors. -impl From for ImgiiError { - fn from(err: FontError) -> Self { - Self::Font(err) - } -} - -impl From for ImgiiError { - fn from(value: ParseError) -> Self { - Self::Parse(value) - } -} - -impl From for ImgiiError { - fn from(err: InternalError) -> Self { - Self::Internal(err) - } -} - -impl From for ImgiiError { - fn from(value: std::io::Error) -> Self { - Self::Io(value) - } -} - -impl From for ImgiiError { - fn from(value: ImageError) -> Self { - Self::Image(value) - } -} - -impl From for ImgiiError { - fn from(value: InvalidParameterError) -> Self { - Self::Image(ImageError::InvalidParameter(value)) - } -} - -impl From for ImgiiError { - fn from(value: ParseImageError) -> Self { - Self::Image(ImageError::ParseImage(value)) - } -} - -impl From for ImgiiError { - fn from(value: RenderError) -> Self { - Self::Image(ImageError::Render(value)) - } -} - -// for converting from errors boxed at runtime -impl From for ImgiiError { - fn from(value: BoxedDynErr) -> Self { - Self::Other(OtherError::new(value)) - } -} - -// for converting from a regular expression error (not ours) -impl From for ParseError { - fn from(err: regex::Error) -> Self { - Self::Regex(err) - } -} +// ImgiiError should only have to implement From for errors for convenience. This avoids having to +// map_err from one error to another. impl From for ImgiiError { fn from(err: regex::Error) -> Self { Self::Parse(ParseError::Regex(err)) } } - -impl From for ParseError { - fn from(err: ParseIntError) -> Self { - Self::ParseValue(err) - } -} -impl From for ImgiiError { - fn from(err: ParseIntError) -> Self { - Self::Parse(ParseError::ParseValue(err)) - } -} - -/* - * NOTE: Add any custom implementation blocks for errors below. - */ - -impl FontError { - /// Creates a new [`FontError`]. - /// - /// * `font_name`: The font file name which failed to be created. - #[must_use] - pub fn new(font_name: String) -> Self { - Self { font_name } - } -} - -impl ParseIntError { - /// Creates a new [`ParseIntError`]. - /// - /// * `value_name`: The value name to parse. - /// * `the_str`: The string that parsing was attempted on. - /// * `err`: The `std::num::ParseIntError` that was emitted. - #[must_use] - pub fn new(value_name: String, the_str: String, err: std::num::ParseIntError) -> Self { - Self { - value_name, - the_str, - err, - } - } -} - -impl OtherError { - /// Creates a new [`OtherError`] from a boxed error (created at runtime). - /// - /// For use with other kinds of errors that the program can handle. - /// - /// * `other_err`: The other error, boxed. - #[must_use] - pub fn new(other_err: BoxedDynErr) -> Self { - Self { other_err } - } -} - -impl InvalidParameterError { - /// Creates a new [`InvalidParameterError`]. - /// - /// * `parameter_name`: The parameter name(s) that was invalid. - #[must_use] - pub fn new(parameter_name: String) -> Self { - Self { parameter_name } - } -} - -impl ParseImageError { - /// Creates a new [`ParseImageError`]. - /// - /// * `image_row_number`: The image row number. - #[must_use] - pub fn new(image_row_number: usize) -> Self { - Self { image_row_number } - } -} - -impl RenderError { - /// Creates a new [`RenderError`]. - /// - /// * `reason`: The reason for the render error. - #[must_use] - pub fn new(reason: String) -> Self { - Self { reason } - } -} diff --git a/src/fonts.rs b/src/fonts.rs new file mode 100644 index 0000000..c59672c --- /dev/null +++ b/src/fonts.rs @@ -0,0 +1,23 @@ +//! Contains helper functionality for handling fonts. + +use font_loader::system_fonts; + +/// Lists all of the fonts that are installed on the system. +pub fn list_fonts() -> Vec { + let mut property = system_fonts::FontPropertyBuilder::new().monospace().build(); + system_fonts::query_specific(&mut property) +} + +/// Loads the specified monospace font from the system. +/// +/// * `font_name`: The name of the font. +/// +/// # Returns +/// `Option<(Vec, i32)>` containing the bytes of the font, followed by the index of the font. +pub fn load_monospace_font(font_name: &str) -> Option<(Vec, i32)> { + let property = system_fonts::FontPropertyBuilder::new() + .monospace() + .family(font_name) + .build(); + system_fonts::get(&property) +} diff --git a/src/image_types.rs b/src/image_types.rs index 0af3894..61a2887 100644 --- a/src/image_types.rs +++ b/src/image_types.rs @@ -1,3 +1,5 @@ +//! Contains helpers for handling supported output image types. + /// Holds the image types that imgii can output. /// Each value holds an index into the `IMAGE_STR_TYPES` array. #[derive(Debug, Clone, Copy)] diff --git a/src/lib.rs b/src/lib.rs index c577759..ed09e4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub(crate) mod conversion; pub mod error; +pub mod fonts; pub mod image_types; pub mod options; @@ -19,7 +20,7 @@ use crate::{ }, image_writer::AsciiImageWriter, }, - error::{BoxedDynErr, ImgiiError}, + error::ImgiiError, options::ImgiiOptions, }; @@ -53,7 +54,8 @@ use crate::{ /// let imgii_options = ImgiiOptionsBuilder::new() /// .charset(from_enum(Charset::Minimal)) /// .background(false) -/// .build(); +/// .build() +/// .unwrap(); /// /// // perform the conversion /// match convert_to_ascii_png( @@ -85,7 +87,7 @@ pub fn convert_to_ascii_png( .imagebuf .as_buffer() .save(&output_file_name) - .map_err(|err| -> BoxedDynErr { Box::new(err) })?; + .map_err(|err| -> ImgiiError { anyhow::Error::new(err).into() })?; Ok(()) } @@ -119,7 +121,8 @@ pub fn convert_to_ascii_png( /// let imgii_options = ImgiiOptionsBuilder::new() /// .background(true) // set black background behind GIF /// .width(50) // keeps the aspect ratio but is 50 pixels wide -/// .build(); +/// .build() +/// .unwrap(); /// /// // perform the conversion /// match convert_to_ascii_gif( @@ -160,6 +163,7 @@ pub fn convert_to_ascii_gif( .into_par_iter() .filter_map(|(writer, frame_metadata)| match writer { // let's just get rid of errors and try our best with what we've got + // NOTE: this will discard frames with errors. Ok(writer) => Some((writer, frame_metadata)), Err(_) => None, }) @@ -184,8 +188,8 @@ pub fn convert_to_ascii_gif( let err = gif_encoder.set_repeat(image::codecs::gif::Repeat::Infinite); if let Err(err) = err { // repeat couldn't be set properly - let err_box: BoxedDynErr = Box::new(err); - return Err(err_box.into()); + let err = anyhow::Error::new(err); + return Err(err.into()); } // FUTURE: the longest part of the GIF creation process is encoding...is there any way to speed @@ -194,8 +198,8 @@ pub fn convert_to_ascii_gif( // encode the frames match gif_encoder.encode_frames(frames) { Err(err) => { - let err_box: BoxedDynErr = Box::new(err); - Err(err_box.into()) + let err = anyhow::Error::new(err); + Err(err.into()) } _ => Ok(()), } diff --git a/src/main.rs b/src/main.rs index 0f68a15..5a9683f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,12 @@ +//! The main file for the `imgii` CLI tool. + use clap::Parser; use clap::builder as clap_builder; use clap::builder::styling as clap_styling; +use imgii::error::FontError; +use imgii::error::ImgiiError; +use imgii::fonts::list_fonts; +use imgii::fonts::load_monospace_font; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use std::{sync::Arc, time::Instant}; @@ -16,7 +22,7 @@ use imgii::{ #[derive(Debug, Parser)] #[command(author, version, about, styles=set_color_style())] struct Args { - /// Path to the input image + /// Path to the input image. /// /// Can also specify a format for an input, if is also set to the final /// input image index. @@ -40,10 +46,14 @@ struct Args { width: Option, /// Height (in characters) of the output image, if not specified, it will be calculated to keep - /// the aspect ratio + /// the aspect ratio. #[arg(short = 'H', long)] height: Option, + /// The name of the installed monospace font to use. Must be an installed TrueType font (.ttf). + #[arg(short = 'n', long)] + font_name: Option, + /// The font size of the output image. /// Larger font sizes incur harsher performance penalties. /// @@ -144,38 +154,79 @@ fn setup_threads() { } } +/// Loads a font based on the input font name. +/// +/// * `font_name`: The optional font name. Uses the first (alphabetically) monospace font installed +/// on the system. +/// * `builder`: The builder to add the font to. +fn imgii_builder_load_font<'a>( + font_name: Option, + builder: ImgiiOptionsBuilder<'a>, +) -> Result, ImgiiError> { + // get the font name + let font_name = { + match font_name { + Some(font_name) => font_name, + None => { + // if we didn't get a font name, use the first font (alphabetically) that is + // installed on the system + let mut fonts = list_fonts(); + log::debug!("Found fonts installed on system: {:?}", fonts); + assert!( + !fonts.is_empty(), + "there are no monospace truetype (.ttf) fonts installed that could be found" + ); + fonts.swap_remove(0) + } + } + }; + + // read the font that the user wants to use + log::debug!("Attempting to load font {}", font_name); + // NOTE: if the user inputs an invalid font, it seems to fall back to the first monospace + // font it can find + match load_monospace_font(&font_name) { + Some((font, _)) => { + // successfully loaded font + Ok(builder.font(font).font_name(font_name)) + } + None => { + // could not load the font + Err(ImgiiError::Font(FontError::FontLoad { font_name })) + } + } +} + /// Creates an instance of [`ImgiiOptions`] for the CLI for imgii. /// -/// * `font_size`: The font size argument. -/// * `background`: The background flag. +/// * `args`: The CLI arguments. +/// * `rascii_charset`: The rascii fn create_imgii_options<'a>( - font_size: Option, - background: bool, - width: Option, - height: Option, - invert: bool, + args: Args, rascii_charset: Charset, - char_override: Option, -) -> ImgiiOptions<'a> { - let mut builder = ImgiiOptionsBuilder::new().background(background); - +) -> Result, ImgiiError> { + let mut builder: ImgiiOptionsBuilder<'a> = + ImgiiOptionsBuilder::new().background(args.background); // build the complex values first - if let Some(font_size) = font_size { + + // load the font + builder = imgii_builder_load_font(args.font_name, builder)?; + if let Some(font_size) = args.font_size { builder = builder.font_size(font_size); } - if let Some(width) = width { + if let Some(width) = args.width { builder = builder.width(width); } - if let Some(height) = height { + if let Some(height) = args.height { builder = builder.height(height); } - if let Some(char_override) = char_override { + if let Some(char_override) = args.char_override { // converts the string to a string vec if it is Some, otherwise stores as None - builder = builder.char_override(convert_string_to_str_vec(char_override)); + builder = builder.char_override(convert_string_to_str_vec(&char_override)); } builder - .invert(invert) + .invert(args.invert) .charset(from_enum(rascii_charset)) .build() } @@ -216,16 +267,10 @@ fn main() { }; // our options for rendering ASCII in imgii - let imgii_options = create_imgii_options( - args.font_size, - args.background, - args.width, - args.height, - args.invert, - rascii_charset, - args.char_override, - ); - log::debug!("imgii options = {:?}", imgii_options); + let Ok(imgii_options) = create_imgii_options(args, rascii_charset) else { + panic!("could not create imgii options"); + }; + log::debug!("imgii options = {}", imgii_options); // Now, handle the conversion match image_type { diff --git a/src/options.rs b/src/options.rs index 427a270..5d32a1b 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,5 +1,7 @@ //! The options for using imgii. +use std::fmt::Display; + // We need to re-export these, as they might be necessary for users of this library. Imgii's CLI // uses these. pub use rascii_art_img::RenderOptions as RasciiOptions; @@ -8,6 +10,8 @@ pub use rascii_art_img::{ convert_string_to_str_vec, }; +use crate::error::ImgiiError; + const DEFAULT_CHAR_FONT_SIZE: u32 = 16; // NOTE: we don't want to ever make members of ImgiiOptions public so users can't cause imgii to @@ -16,6 +20,12 @@ const DEFAULT_CHAR_FONT_SIZE: u32 = 16; /// Options for creating the output ASCII PNG. #[derive(Debug, Clone)] pub struct ImgiiOptions<'a> { + /// The loaded bytes containing the font. + font: Vec, + + /// The font name. + font_name: String, + /// The font size of the output image. font_size: u32, @@ -31,14 +41,34 @@ pub struct ImgiiOptions<'a> { impl<'a> ImgiiOptions<'a> { /// Creates a new image options object. #[must_use] - fn new(font_size: u32, background: bool, rascii_options: RasciiOptions<'a>) -> Self { + fn new( + font: Vec, + font_name: String, + font_size: u32, + background: bool, + rascii_options: RasciiOptions<'a>, + ) -> Self { Self { + font, + font_name, font_size, background, rascii_options, } } + /// Gets the font data. + #[must_use] + pub fn font(&self) -> &Vec { + &self.font + } + + /// Gets the font name. + #[must_use] + pub fn font_name(&self) -> &str { + &self.font_name + } + /// Gets the font size to use to generate the image. #[must_use] pub fn font_size(&self) -> u32 { @@ -46,19 +76,46 @@ impl<'a> ImgiiOptions<'a> { } /// Gets the background flag. If true, sets a background behind the output image. + #[must_use] pub fn background(&self) -> bool { self.background } /// Gets the RASCII options. + #[must_use] pub fn rascii_options(&self) -> &RasciiOptions<'a> { &self.rascii_options } } +impl<'a> Display for ImgiiOptions<'a> { + /// Writes an `[ImgiiOptions]` instance. This should be preferred over debug printing, as + /// `[ImgiiOptions]` can hold a lot of binary data. + /// + /// * `f`: The formatter. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // write everything that won't spam a bunch of binary data + write!( + f, + "{{font.len()={}; font_name={}; font_size={}, background={}; rascii_options={:?}}}", + self.font.len(), + self.font_name, + self.font_size, + self.background, + self.rascii_options + ) + } +} + /// Builder for [`ImgiiOptions`]. Intended way to create options for Imgii. #[derive(Debug, Clone)] pub struct ImgiiOptionsBuilder<'a> { + /// The name of the installed system font to render + font: Option>, + + /// The font name. + font_name: Option, + /// The font size of the output image. font_size: u32, @@ -72,6 +129,8 @@ pub struct ImgiiOptionsBuilder<'a> { impl<'a> Default for ImgiiOptionsBuilder<'a> { fn default() -> Self { Self { + font: None, + font_name: None, font_size: DEFAULT_CHAR_FONT_SIZE, background: false, rascii_options: RasciiOptions::default() @@ -88,6 +147,22 @@ impl<'a> ImgiiOptionsBuilder<'a> { Self::default() } + /// Sets the font of the output [`ImgiiOptions`]. + /// + /// * `font`: The loaded font bytes. + pub fn font(mut self, font: Vec) -> Self { + self.font = Some(font); + self + } + + /// Sets the font name of the output [`ImgiiOptions`]. + /// + /// * `font_name`: The name of the font. + pub fn font_name(mut self, font_name: String) -> Self { + self.font_name = Some(font_name); + self + } + /// Sets the font size of the output [`ImgiiOptions`]. /// /// * `font_size`: The font size. @@ -105,8 +180,18 @@ impl<'a> ImgiiOptionsBuilder<'a> { } /// Builds a new [`ImgiiOptions`] instance from chosen values in this builder. - pub fn build(&self) -> ImgiiOptions<'a> { - ImgiiOptions::new(self.font_size, self.background, self.rascii_options.clone()) + pub fn build(&self) -> Result, ImgiiError> { + let (Some(font), Some(font_name)) = (self.font.clone(), self.font_name.clone()) else { + return Err(ImgiiError::InvalidArgument); + }; + + Ok(ImgiiOptions::new( + font, + font_name, + self.font_size, + self.background, + self.rascii_options.clone(), + )) } /* diff --git a/ubuntu-font-license-1.0.txt b/ubuntu-font-license-1.0.txt deleted file mode 100644 index aa36342..0000000 --- a/ubuntu-font-license-1.0.txt +++ /dev/null @@ -1,96 +0,0 @@ -------------------------------- -UBUNTU FONT LICENCE Version 1.0 -------------------------------- - -PREAMBLE -This licence allows the licensed fonts to be used, studied, modified and -redistributed freely. The fonts, including any derivative works, can be -bundled, embedded, and redistributed provided the terms of this licence -are met. The fonts and derivatives, however, cannot be released under -any other licence. The requirement for fonts to remain under this -licence does not require any document created using the fonts or their -derivatives to be published under this licence, as long as the primary -purpose of the document is not to be a vehicle for the distribution of -the fonts. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this licence and clearly marked as such. This may -include source files, build scripts and documentation. - -"Original Version" refers to the collection of Font Software components -as received under this licence. - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to -a new environment. - -"Copyright Holder(s)" refers to all individuals and companies who have a -copyright ownership of the Font Software. - -"Substantially Changed" refers to Modified Versions which can be easily -identified as dissimilar to the Font Software by users of the Font -Software comparing the Original Version with the Modified Version. - -To "Propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification and with or without charging -a redistribution fee), making available to the public, and in some -countries other activities as well. - -PERMISSION & CONDITIONS -This licence does not grant any rights under trademark law and all such -rights are reserved. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of the Font Software, to propagate the Font Software, subject to -the below conditions: - -1) Each copy of the Font Software must contain the above copyright -notice and this licence. These can be included either as stand-alone -text files, human-readable headers or in the appropriate machine- -readable metadata fields within text or binary files as long as those -fields can be easily viewed by the user. - -2) The font name complies with the following: -(a) The Original Version must retain its name, unmodified. -(b) Modified Versions which are Substantially Changed must be renamed to -avoid use of the name of the Original Version or similar names entirely. -(c) Modified Versions which are not Substantially Changed must be -renamed to both (i) retain the name of the Original Version and (ii) add -additional naming elements to distinguish the Modified Version from the -Original Version. The name of such Modified Versions must be the name of -the Original Version, with "derivative X" where X represents the name of -the new work, appended to that name. - -3) The name(s) of the Copyright Holder(s) and any contributor to the -Font Software shall not be used to promote, endorse or advertise any -Modified Version, except (i) as required by this licence, (ii) to -acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with -their explicit written permission. - -4) The Font Software, modified or unmodified, in part or in whole, must -be distributed entirely under this licence, and must not be distributed -under any other licence. The requirement for fonts to remain under this -licence does not affect any document created using the Font Software, -except any version of the Font Software extracted from a document -created using the Font Software may only be distributed under this -licence. - -TERMINATION -This licence becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF -COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER -DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file