~julienxx/castor

Only read settings at launch v1 PROPOSED

Mike Burns: 14
 Read in start_url once
 Read background_color once
 Refactor: pass the Settings struct to draw funcs
 Move get_gemini_text_font_family into method
 Move max_width into method
 Move Pango markup span rendering into Settings
 Pango markup span rendering for LineType::Quote
 Pango markup span rendering for LineType::ListItem
 Pango markup for LineType::H3
 Pango markup for LineType::H2
 Pango markup for LineType::H1
 Pango markup for LineType::Preformatted
 Pango markup for LineType::Gopher and Finger
 Cleanup

 29 files changed, 662 insertions(+), 708 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~julienxx/castor/patches/19437/mbox | git am -3
Learn more about email & git

[PATCH 01/14] Read in start_url once Export this patch

Read in the settings when the program launches, then use that setting to
pull out the `start_url`. This lead to a few changes:

- Move `start_url()` into a method instead of function. We will be
passing the `Setting` struct around and this makes the syntax a little
easier to work with.
- Make the `Setting` struct public so we can pass it around.
- Pass around a `&str` instead of `String`. This cuts down on the amount
of data we have to copy and allows us to use existing `&str`-expecting
functions, and everything else explained in [the Rust Design Patterns
chapter on this].
- The `route_url` function now takes a `&str`, so propagate that
through using `as_ref()` and `as_deref()` as appropriate.

[the Rust Design Patterns chapter on this]: https://rust-unofficial.github.io/patterns/idioms/coercion-arguments.html

This is step one of a longer refactoring with the goal of reducing the
number of times we read the config file.
---
 src/main.rs     | 23 +++++++++++++----------
 src/settings.rs | 21 ++++++++++++---------
 2 files changed, 25 insertions(+), 19 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index bcbc7c3..adc1842 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -32,6 +32,9 @@ mod status;
use status::Status;

fn main() {
    // Read in settings
    let config = settings::read();

    // Start up the GTK3 subsystem.
    gtk::init().expect("Unable to start GTK3. Error");

@@ -102,8 +105,8 @@ fn main() {
        let gui_clone = gui.clone();
        let url_bar = gui.url_bar();
        url_bar.connect_activate(move |b| {
            let url = b.get_text().expect("get_text failed").to_string();
            route_url(&gui_clone, url)
            let url = b.get_text().expect("get_text failed");
            route_url(&gui_clone, url.as_str())
        });
    }

@@ -124,26 +127,26 @@ fn main() {
    match args.len() {
        // no argument passed, check settings
        1 => {
            if let Some(url) = settings::start_url() {
            if let Some(url) = config.start_url() {
                route_url(&gui, url)
            }
        }
        // Use argument as initial URL
        _ => route_url(&gui, args[1].to_string()),
        _ => route_url(&gui, &args[1]),
    }

    gui.start();
    gtk::main();
}

fn route_url(gui: &Arc<Gui>, url: String) {
fn route_url(gui: &Arc<Gui>, url: &str) {
    if url == "" {
    } else if url.starts_with("gemini://") {
        visit_url(&gui, Gemini { source: url })
        visit_url(&gui, Gemini { source: url.to_string() })
    } else if url.starts_with("gopher://") {
        visit_url(&gui, Gopher { source: url })
        visit_url(&gui, Gopher { source: url.to_string() })
    } else if url.starts_with("finger://") {
        visit_url(&gui, Finger { source: url })
        visit_url(&gui, Finger { source: url.to_string() })
    } else {
        visit_url(
            &gui,
@@ -192,8 +195,8 @@ fn visit(gui: &Arc<Gui>, url: &Url) {

fn refresh(gui: &Arc<Gui>) {
    let url_bar = gui.url_bar();
    let url = url_bar.get_text().expect("get_text failed").to_string();
    route_url(&gui, url)
    let url = url_bar.get_text().expect("get_text failed");
    route_url(&gui, url.as_str())
}

fn update_url_field(gui: &Arc<Gui>, url: &str) {
diff --git a/src/settings.rs b/src/settings.rs
index ee226e3..c80aa55 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -8,13 +8,22 @@ use std::io::Read;
use serde_derive::Deserialize;

#[derive(Deserialize)]
struct Settings {
pub struct Settings {
    general: Option<General>,
    colors: Option<Color>,
    characters: Option<Character>,
    fonts: Option<Font>,
}

impl Settings {
    pub fn start_url(&self) -> Option<&str> {
        match &self.general {
            Some(general) => general.start_url.as_deref(),
            None => None,
        }
    }
}

#[derive(Deserialize)]
struct General {
    start_url: Option<String>,
@@ -70,13 +79,6 @@ struct QuoteColor {
    background: Option<String>,
}

pub fn start_url() -> Option<String> {
    match read().general {
        Some(general) => general.start_url,
        None => None,
    }
}

pub fn max_width() -> Option<usize> {
    match read().general {
        Some(general) => general.max_width,
@@ -449,9 +451,10 @@ pub fn get_list_character() -> String {
    }
}

fn read() -> Settings {
pub fn read() -> Settings {
    let mut file = settings_file();
    let mut content = String::new();
    println!("reading from config");
    file.read_to_string(&mut content)
        .expect("Unable to read file");

-- 
2.20.1

[PATCH 02/14] Read background_color once Export this patch

Since we are already reading the settings on app launch for
`start_url()`, reuse that existing `Settings` struct for the
`background_color()`. Like for the `start_url()`, change the value to be
a `&str`.

This means we now read the config file once at program launch.
---
 src/main.rs     |  2 +-
 src/settings.rs | 11 +++++++----
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index adc1842..9a6bc07 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -42,7 +42,7 @@ fn main() {
    let gui = Arc::new(Gui::new());

    // Set background color
    if let Some(color) = settings::background_color() {
    if let Some(color) = config.background_color() {
        let provider = gtk::CssProvider::new();
        provider
            .load_from_data(format!("textview text {{ background-color: {}; }}", color).as_bytes())
diff --git a/src/settings.rs b/src/settings.rs
index c80aa55..0caec8c 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -22,6 +22,13 @@ impl Settings {
            None => None,
        }
    }

    pub fn background_color(&self) -> Option<&str> {
        match &self.colors {
            Some(colors) => colors.background.as_deref(),
            None => None,
        }
    }
}

#[derive(Deserialize)]
@@ -403,10 +410,6 @@ pub fn get_text_color() -> String {
    }
}

pub fn background_color() -> Option<String> {
    read().colors?.background
}

fn h1_character() -> Option<String> {
    read().characters?.h1
}
-- 
2.20.1

[PATCH 03/14] Refactor: pass the Settings struct to draw funcs Export this patch

This is a step with the goal of reducing the number of times we read the
config file.
---
 src/dialog.rs |  4 +--
 src/draw.rs   | 43 +++++++++++++++++--------------
 src/main.rs   | 70 +++++++++++++++++++++++++++++----------------------
 3 files changed, 66 insertions(+), 51 deletions(-)

diff --git a/src/dialog.rs b/src/dialog.rs
index 485a5ec..3587229 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -41,7 +41,7 @@ pub fn error(gui: &Arc<Gui>, message: &str) {
    dialog.show_all();
}

pub fn input(gui: &Arc<Gui>, url: Url, message: &str) {
pub fn input(gui: &Arc<Gui>, config: &Arc<crate::settings::Settings>, url: Url, message: &str) {
    let dialog = gtk::Dialog::new_with_buttons(
        Some(message),
        Some(gui.window()),
@@ -63,7 +63,7 @@ pub fn input(gui: &Arc<Gui>, url: Url, message: &str) {
        let cleaned: &str = &url[..Position::AfterPath];
        let full_url = format!("{}?{}", cleaned.to_string(), response);

        crate::visit_url(&gui, Gemini { source: full_url });
        crate::visit_url(&gui, &config, Gemini { source: full_url });
    }

    dialog.destroy();
diff --git a/src/draw.rs b/src/draw.rs
index 451adef..a05c05c 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -15,10 +15,12 @@ use crate::gemini::link::Link as GeminiLink;
use crate::gopher::link::Link as GopherLink;
use crate::gui::Gui;
use crate::protocols::{Finger, Gemini, Gopher};
use crate::settings;


pub fn gemini_content(
    gui: &Arc<Gui>,
    config: &Arc<settings::Settings>,
    content: Vec<Result<crate::gemini::parser::TextElement, crate::gemini::parser::ParseError>>,
) -> TextBuffer {
    let content_view = gui.content_view();
@@ -149,7 +151,7 @@ pub fn gemini_content(
                    let mut end_iter = buffer.get_end_iter();
                    buffer.insert_markup(&mut end_iter, &mono_span(escape_text(&link_item)));
                } else {
                    gemini_link(&gui, link_item);
                    gemini_link(&gui, &config, link_item);
                }
            }
            Err(_) => println!("Something failed."),
@@ -158,7 +160,7 @@ pub fn gemini_content(
    buffer
}

pub fn gemini_text_content(gui: &Arc<Gui>, content: std::str::Lines) -> TextBuffer {
pub fn gemini_text_content(gui: &Arc<Gui>, config: &Arc<settings::Settings>, content: std::str::Lines) -> TextBuffer {
    let content_view = gui.content_view();
    let buffer = content_view.get_buffer().unwrap();

@@ -178,6 +180,7 @@ pub fn gemini_text_content(gui: &Arc<Gui>, content: std::str::Lines) -> TextBuff

pub fn gopher_content(
    gui: &Arc<Gui>,
    config: &Arc<settings::Settings>,
    content: Vec<Result<crate::gopher::parser::TextElement, crate::gopher::parser::ParseError>>,
) -> TextBuffer {
    let content_view = gui.content_view();
@@ -201,16 +204,16 @@ pub fn gopher_content(
                );
            }
            Ok(crate::gopher::parser::TextElement::LinkItem(link_item)) => {
                gopher_link(&gui, colors::cleanup(&link_item));
                gopher_link(&gui, &config, colors::cleanup(&link_item));
            }
            Ok(crate::gopher::parser::TextElement::ExternalLinkItem(link_item)) => {
                gopher_link(&gui, colors::cleanup(&link_item));
                gopher_link(&gui, &config, colors::cleanup(&link_item));
            }
            Ok(crate::gopher::parser::TextElement::Image(link_item)) => {
                gopher_link(&gui, link_item);
                gopher_link(&gui, &config, link_item);
            }
            Ok(crate::gopher::parser::TextElement::Binary(link_item)) => {
                gopher_link(&gui, link_item);
                gopher_link(&gui, &config, link_item);
            }
            Err(_) => println!("Something failed."),
        }
@@ -220,6 +223,7 @@ pub fn gopher_content(

pub fn finger_content(
    gui: &Arc<Gui>,
    config: &Arc<settings::Settings>,
    content: Vec<Result<crate::finger::parser::TextElement, crate::finger::parser::ParseError>>,
) -> TextBuffer {
    let content_view = gui.content_view();
@@ -247,7 +251,7 @@ pub fn finger_content(
    buffer
}

pub fn gemini_link(gui: &Arc<Gui>, link_item: String) {
pub fn gemini_link(gui: &Arc<Gui>, config: &Arc<settings::Settings>, link_item: String) {
    match GeminiLink::from_str(&link_item) {
        Ok(GeminiLink::Finger(url, label)) => {
            let button_label = if label.is_empty() {
@@ -256,10 +260,10 @@ pub fn gemini_link(gui: &Arc<Gui>, link_item: String) {
                label
            };
            let finger_label = format!("{} [Finger]", button_label);
            insert_button(&gui, url, finger_label);
            insert_button(&gui, &config, url, finger_label);
        }
        Ok(GeminiLink::Gemini(url, label)) => {
            insert_button(&gui, url, label);
            insert_button(&gui, &config, url, label);
        }
        Ok(GeminiLink::Gopher(url, label)) => {
            let button_label = if label.is_empty() {
@@ -268,7 +272,7 @@ pub fn gemini_link(gui: &Arc<Gui>, link_item: String) {
                label
            };
            let gopher_label = format!("{} [Gopher]", button_label);
            insert_button(&gui, url, gopher_label);
            insert_button(&gui, &config, url, gopher_label);
        }
        Ok(GeminiLink::Http(url, label)) => {
            let button_label = if label.is_empty() {
@@ -312,14 +316,14 @@ pub fn gemini_link(gui: &Arc<Gui>, link_item: String) {
        }
        Ok(GeminiLink::Relative(url, label)) => {
            let new_url = Gemini { source: url }.to_absolute_url().unwrap();
            insert_button(&gui, new_url, label);
            insert_button(&gui, &config, new_url, label);
        }
        Ok(GeminiLink::Unknown(_, _)) => (),
        Err(_) => (),
    }
}

pub fn gopher_link(gui: &Arc<Gui>, link_item: String) {
pub fn gopher_link(gui: &Arc<Gui>, config: &Arc<settings::Settings>, link_item: String) {
    match GopherLink::from_str(&link_item) {
        Ok(GopherLink::Http(url, label)) => {
            let button_label = if label.is_empty() {
@@ -338,7 +342,7 @@ pub fn gopher_link(gui: &Arc<Gui>, link_item: String) {
                label
            };
            let gopher_label = format!("{} [Gopher]", button_label);
            insert_button(&gui, url, gopher_label);
            insert_button(&gui, &config, url, gopher_label);
        }
        Ok(GopherLink::Image(url, label)) => {
            let button_label = if label.is_empty() {
@@ -359,11 +363,11 @@ pub fn gopher_link(gui: &Arc<Gui>, link_item: String) {
            insert_gopher_file_button(&gui, url, file_label);
        }
        Ok(GopherLink::Gemini(url, label)) => {
            insert_button(&gui, url, label);
            insert_button(&gui, &config, url, label);
        }
        Ok(GopherLink::Relative(url, label)) => {
            let new_url = Gopher { source: url }.to_absolute_url().unwrap();
            insert_button(&gui, new_url, label);
            insert_button(&gui, &config, new_url, label);
        }
        Ok(GopherLink::Ftp(url, label)) => {
            let button_label = if label.is_empty() {
@@ -390,7 +394,7 @@ pub fn gopher_link(gui: &Arc<Gui>, link_item: String) {
    }
}

pub fn insert_button(gui: &Arc<Gui>, url: Url, label: String) {
fn insert_button(gui: &Arc<Gui>, config: &Arc<settings::Settings>, url: Url, label: String) {
    let content_view = gui.content_view();
    let buffer = content_view.get_buffer().unwrap();

@@ -403,11 +407,12 @@ pub fn insert_button(gui: &Arc<Gui>, url: Url, label: String) {
    let button = gtk::Button::new_with_label(&button_label);
    button.set_tooltip_text(Some(&url.to_string()));

    let config = config.clone();
    button.connect_clicked(clone!(@weak gui => move |_| {
        match url.scheme() {
            "finger" => crate::visit_url(&gui, Finger { source: url.to_string() }),
            "gemini" => crate::visit_url(&gui, Gemini { source: url.to_string() }),
            "gopher" => crate::visit_url(&gui, Gopher { source: url.to_string() }),
            "finger" => crate::visit_url(&gui, &config, Finger { source: url.to_string() }),
            "gemini" => crate::visit_url(&gui, &config, Gemini { source: url.to_string() }),
            "gopher" => crate::visit_url(&gui, &config, Gopher { source: url.to_string() }),
            _ => ()
        }
    }));
diff --git a/src/main.rs b/src/main.rs
index 9a6bc07..1a22ad9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -33,7 +33,7 @@ use status::Status;

fn main() {
    // Read in settings
    let config = settings::read();
    let config = Arc::new(settings::read());

    // Start up the GTK3 subsystem.
    gtk::init().expect("Unable to start GTK3. Error");
@@ -59,8 +59,9 @@ fn main() {
    {
        let button = gui.back_button();
        let gui = gui.clone();
        let config = config.clone();
        button.connect_clicked(move |_| {
            go_back(&gui);
            go_back(&gui, &config);
        });
    }

@@ -68,17 +69,19 @@ fn main() {
    {
        let button = gui.forward_button();
        let gui = gui.clone();
        let config = config.clone();
        button.connect_clicked(move |_| {
            go_forward(&gui);
            go_forward(&gui, &config);
        });
    }

    // Bind refresh button
    {
        let button = gui.refresh_button();
        let config = config.clone();
        let gui = gui.clone();
        button.connect_clicked(move |_| {
            refresh(&gui);
            refresh(&gui, &config);
        });
    }

@@ -95,28 +98,31 @@ fn main() {
    {
        let button = gui.show_bookmarks_button();
        let gui = gui.clone();
        let config = config.clone();
        button.connect_clicked(move |_| {
            show_bookmarks(&gui);
            show_bookmarks(&gui, &config);
        });
    }

    // Bind URL bar
    {
        let gui_clone = gui.clone();
        let config = config.clone();
        let url_bar = gui.url_bar();
        url_bar.connect_activate(move |b| {
            let url = b.get_text().expect("get_text failed");
            route_url(&gui_clone, url.as_str())
            route_url(&gui_clone, &config, url.as_str())
        });
    }

    // Bind Mouse-Back
    {
        let gui_clone = gui.clone();
        let config = config.clone();
        let content_view = gui.content_view();
        content_view.connect_button_press_event(move |_, event| {
            if event.get_button() == 8 {
                go_back(&gui_clone);
                go_back(&gui_clone, &config);
            }
            Inhibit(false)
        });
@@ -128,28 +134,29 @@ fn main() {
        // no argument passed, check settings
        1 => {
            if let Some(url) = config.start_url() {
                route_url(&gui, url)
                route_url(&gui, &config, url)
            }
        }
        // Use argument as initial URL
        _ => route_url(&gui, &args[1]),
        _ => route_url(&gui, &config, &args[1]),
    }

    gui.start();
    gtk::main();
}

fn route_url(gui: &Arc<Gui>, url: &str) {
fn route_url(gui: &Arc<Gui>, config: &Arc<settings::Settings>, url: &str) {
    if url == "" {
    } else if url.starts_with("gemini://") {
        visit_url(&gui, Gemini { source: url.to_string() })
        visit_url(&gui, &config, Gemini { source: url.to_string() })
    } else if url.starts_with("gopher://") {
        visit_url(&gui, Gopher { source: url.to_string() })
        visit_url(&gui, &config, Gopher { source: url.to_string() })
    } else if url.starts_with("finger://") {
        visit_url(&gui, Finger { source: url.to_string() })
        visit_url(&gui, &config, Finger { source: url.to_string() })
    } else {
        visit_url(
            &gui,
            &config,
            Gemini {
                source: format!("gemini://{}", url),
            },
@@ -157,34 +164,37 @@ fn route_url(gui: &Arc<Gui>, url: &str) {
    };
}

fn go_back(gui: &Arc<Gui>) {
fn go_back(gui: &Arc<Gui>, config: &Arc<settings::Settings>) {
    if let Some(prev) = history::get_previous_url() {
        visit(gui, &prev);
        visit(gui, &config, &prev);
    }
}

fn go_forward(gui: &Arc<Gui>) {
fn go_forward(gui: &Arc<Gui>, config: &Arc<settings::Settings>) {
    if let Some(next) = history::get_next_url() {
        visit(gui, &next);
        visit(gui, &config, &next);
    }
}

fn visit(gui: &Arc<Gui>, url: &Url) {
fn visit(gui: &Arc<Gui>, config: &Arc<settings::Settings>, url: &Url) {
    match url.scheme() {
        "finger" => visit_url(
            gui,
            &config,
            Finger {
                source: url.to_string(),
            },
        ),
        "gemini" => visit_url(
            gui,
            &config,
            Gemini {
                source: url.to_string(),
            },
        ),
        "gopher" => visit_url(
            gui,
            &config,
            Gopher {
                source: url.to_string(),
            },
@@ -193,10 +203,10 @@ fn visit(gui: &Arc<Gui>, url: &Url) {
    }
}

fn refresh(gui: &Arc<Gui>) {
fn refresh(gui: &Arc<Gui>, config: &Arc<settings::Settings>) {
    let url_bar = gui.url_bar();
    let url = url_bar.get_text().expect("get_text failed");
    route_url(&gui, url.as_str())
    route_url(&gui, &config, url.as_str())
}

fn update_url_field(gui: &Arc<Gui>, url: &str) {
@@ -218,23 +228,23 @@ fn add_bookmark(gui: &Arc<Gui>) {
    }
}

fn show_bookmarks(gui: &Arc<Gui>) {
fn show_bookmarks(gui: &Arc<Gui>, config: &Arc<settings::Settings>) {
    let content_view = gui.content_view();

    let bookmarks_list = format!("# Bookmarks\n\n{}", bookmarks::content());
    let parsed_content = gemini::parser::parse(bookmarks_list);

    clear_buffer(&content_view);
    draw::gemini_content(&gui, parsed_content);
    draw::gemini_content(&gui, &config, parsed_content);

    update_url_field(&gui, "::bookmarks");

    content_view.show_all();
}

pub fn visit_url<T: AbsoluteUrl + Protocol>(gui: &Arc<Gui>, url: T) {
pub fn visit_url<T: AbsoluteUrl + Protocol>(gui: &Arc<Gui>, config: &Arc<settings::Settings>, url: T) {
    if url.get_source_str() == "gemini://::bookmarks" {
        show_bookmarks(&gui);
        show_bookmarks(&gui, &config);
        return;
    }

@@ -264,10 +274,10 @@ pub fn visit_url<T: AbsoluteUrl + Protocol>(gui: &Arc<Gui>, url: T) {
                                        clear_buffer(&content_view);
                                        if meta.starts_with("text/gemini") {
                                            let parsed_content = gemini::parser::parse(content_str);
                                            draw::gemini_content(&gui, parsed_content);
                                            draw::gemini_content(&gui, &config, parsed_content);
                                        } else {
                                            // just a text file
                                            draw::gemini_text_content(&gui, content_str.lines());
                                            draw::gemini_text_content(&gui, &config, content_str.lines());
                                        }

                                        content_view.show_all();
@@ -282,7 +292,7 @@ pub fn visit_url<T: AbsoluteUrl + Protocol>(gui: &Arc<Gui>, url: T) {
                                Status::RedirectTemporary(new_url)
                                | Status::RedirectPermanent(new_url) => {
                                    history::append(absolute_url.as_str());
                                    visit_url(&gui, Gemini { source: new_url });
                                    visit_url(&gui, &config, Gemini { source: new_url });
                                }
                                Status::TransientCertificateRequired(_meta)
                                | Status::AuthorisedCertificatedRequired(_meta) => {
@@ -292,7 +302,7 @@ pub fn visit_url<T: AbsoluteUrl + Protocol>(gui: &Arc<Gui>, url: T) {
                                    );
                                }
                                Status::Input(message) => {
                                    dialog::input(&gui, absolute_url, &message);
                                    dialog::input(&gui, &config, absolute_url, &message);
                                }
                                _ => (),
                            }
@@ -318,7 +328,7 @@ pub fn visit_url<T: AbsoluteUrl + Protocol>(gui: &Arc<Gui>, url: T) {

                        let parsed_content = gopher::parser::parse(content_str);
                        clear_buffer(&content_view);
                        draw::gopher_content(&gui, parsed_content);
                        draw::gopher_content(&gui, &config, parsed_content);

                        content_view.show_all();
                    }
@@ -342,7 +352,7 @@ pub fn visit_url<T: AbsoluteUrl + Protocol>(gui: &Arc<Gui>, url: T) {

                        let parsed_content = finger::parser::parse(content_str);
                        clear_buffer(&content_view);
                        draw::finger_content(&gui, parsed_content);
                        draw::finger_content(&gui, &config, parsed_content);

                        content_view.show_all();
                    }
-- 
2.20.1

[PATCH 04/14] Move get_gemini_text_font_family into method Export this patch

Move the `get_gemini_text_font_family()` function into a method on the
`Settings` struct. This allows us to use the existing `Settings` struct
that we read at the start of `main`.

This reduces the number of times we read the config file while rendering
a document.
---
 src/draw.rs     |  3 +--
 src/settings.rs | 19 ++++++++-----------
 2 files changed, 9 insertions(+), 13 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index a05c05c..8f85c95 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -27,7 +27,6 @@ pub fn gemini_content(
    let buffer = content_view.get_buffer().unwrap();

    let mut mono_toggle = false;
    let font_family = crate::settings::get_gemini_text_font_family();

    for el in content {
        match el {
@@ -139,7 +138,7 @@ pub fn gemini_content(
                        &format!(
                            "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
                            crate::settings::get_text_color(),
                            font_family,
                            config.get_gemini_text_font_family(),
                            crate::settings::get_gemini_text_font_size(),
                            wrap_text(&text, &gui)
                        ),
diff --git a/src/settings.rs b/src/settings.rs
index 0caec8c..5e9f568 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -29,6 +29,14 @@ impl Settings {
            None => None,
        }
    }

    pub fn get_gemini_text_font_family(&self) -> &str {
        self.gemini_text_font_family().unwrap_or(DEFAULT_FONT)
    }

    fn gemini_text_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.text.as_ref()?.family.as_deref()
    }
}

#[derive(Deserialize)]
@@ -122,17 +130,6 @@ pub fn get_finger_font_size() -> i32 {
    }
}

fn gemini_text_font_family() -> Option<String> {
    read().fonts?.gemini?.text?.family
}

pub fn get_gemini_text_font_family() -> String {
    match gemini_text_font_family() {
        Some(family) => family,
        None => String::from(DEFAULT_FONT),
    }
}

fn gemini_text_font_size() -> Option<i32> {
    read().fonts?.gemini?.text?.size
}
-- 
2.20.1

[PATCH 05/14] Move max_width into method Export this patch

Moving the `max_width` function into a method on the `Settings` struct
means we don't have the read the config file every time we calculate the
maximum width of some text. This was happening on every line rendered;
doing this reduces the number of reads by that much.
---
 src/draw.rs     | 14 +++++++-------
 src/settings.rs | 14 +++++++-------
 2 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index 8f85c95..dc814f0 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -104,7 +104,7 @@ pub fn gemini_content(
                          crate::settings::get_gemini_list_font_family(),
                          crate::settings::get_gemini_list_font_style(),
                          crate::settings::get_list_character(),
                          wrap_text(&item, &gui)
                          wrap_text(&item, &gui, &config)
                      ),
                  );
                }
@@ -123,7 +123,7 @@ pub fn gemini_content(
                            crate::settings::get_gemini_quote_font_family(),
                            crate::settings::get_gemini_quote_font_size(),
                            crate::settings::get_gemini_quote_font_style(),
                            wrap_text(&text, &gui)
                            wrap_text(&text, &gui, &config)
                        ),
                    );
                }
@@ -140,7 +140,7 @@ pub fn gemini_content(
                            crate::settings::get_text_color(),
                            config.get_gemini_text_font_family(),
                            crate::settings::get_gemini_text_font_size(),
                            wrap_text(&text, &gui)
                            wrap_text(&text, &gui, &config)
                        ),
                    );
                }
@@ -469,8 +469,8 @@ pub fn insert_external_button(gui: &Arc<Gui>, url: Url, label: &str) {
    buffer.insert(&mut end_iter, "\n");
}

fn wrap_text(str: &str, gui: &Arc<Gui>) -> String {
    fill(&escape_text(str), width(&gui))
fn wrap_text(str: &str, gui: &Arc<Gui>, config: &Arc<settings::Settings>) -> String {
    fill(&escape_text(str), width(&gui, &config))
}

fn escape_text(str: &str) -> String {
@@ -486,8 +486,8 @@ fn mono_span(text: String) -> String {
    )
}

fn width(gui: &Arc<Gui>) -> usize {
fn width(gui: &Arc<Gui>, config: &Arc<settings::Settings>) -> usize {
    let (win_width, _) = gtk::ApplicationWindow::get_size(gui.window());
    let calculated_width = (win_width / 10).try_into().unwrap();
    std::cmp::min(calculated_width, crate::settings::max_width().unwrap_or(std::usize::MAX))
    std::cmp::min(calculated_width, config.max_width().unwrap_or(std::usize::MAX))
}
diff --git a/src/settings.rs b/src/settings.rs
index 5e9f568..3a52ea7 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -34,6 +34,13 @@ impl Settings {
        self.gemini_text_font_family().unwrap_or(DEFAULT_FONT)
    }

    pub fn max_width(&self) -> Option<usize> {
        match &self.general {
            Some(general) => general.max_width,
            None => None,
        }
    }

    fn gemini_text_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.text.as_ref()?.family.as_deref()
    }
@@ -94,13 +101,6 @@ struct QuoteColor {
    background: Option<String>,
}

pub fn max_width() -> Option<usize> {
    match read().general {
        Some(general) => general.max_width,
        None => None,
    }
}

const DEFAULT_FONT: &str = "serif";
const DEFAULT_FONT_STYLE: &str = "normal";
const DEFAULT_FONT_SIZE: i32 = 11 * pango_sys::PANGO_SCALE;
-- 
2.20.1

[PATCH 06/14] Move Pango markup span rendering into Settings Export this patch

Put the `Settings` struct in charge of the Pango markup for a given line
type. It knows all about the configuration data and how it maps to a
line type.

This introduces a new enum, `LineType`, for describing what kind of line
we're operating on. It only knows about `Text` so far.

This reduces the number of reads of the config file while rendering
normal text.

This also sets us up for a potential future refactoring where a helper
method renders the `span` with knowledge about the line type's settings
and defaults.
---
 src/draw.rs     | 12 ++----------
 src/settings.rs | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 39 insertions(+), 10 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index dc814f0..8128ca4 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -133,16 +133,8 @@ pub fn gemini_content(
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(colors::colorize(&text)));
                } else {
                    buffer.insert_markup(
                        &mut end_iter,
                        &format!(
                            "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
                            crate::settings::get_text_color(),
                            config.get_gemini_text_font_family(),
                            crate::settings::get_gemini_text_font_size(),
                            wrap_text(&text, &gui, &config)
                        ),
                    );
                    let markup = config.format_markup(settings::LineType::Text, &wrap_text(&text, &gui, &config));
                    buffer.insert_markup(&mut end_iter, &markup);
                }
            }
            Ok(crate::gemini::parser::TextElement::LinkItem(link_item)) => {
diff --git a/src/settings.rs b/src/settings.rs
index 3a52ea7..fd4199a 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -7,6 +7,10 @@ use std::io::Read;

use serde_derive::Deserialize;

pub enum LineType {
    Text,
}

#[derive(Deserialize)]
pub struct Settings {
    general: Option<General>,
@@ -30,6 +34,20 @@ impl Settings {
        }
    }

    pub fn format_markup(&self, line_type: LineType, text: &str) -> String {
        match line_type {
            LineType::Text => {
                format!(
                    "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
                    self.get_text_color(),
                    self.get_gemini_text_font_family(),
                    self.get_gemini_text_font_size(),
                    text,
                )
            }
        }
    }

    pub fn get_gemini_text_font_family(&self) -> &str {
        self.gemini_text_font_family().unwrap_or(DEFAULT_FONT)
    }
@@ -44,6 +62,25 @@ impl Settings {
    fn gemini_text_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.text.as_ref()?.family.as_deref()
    }

    fn get_text_color(&self) -> &str {
        self.text_color().unwrap_or("black")
    }

    fn text_color(&self) -> Option<&str> {
        self.colors.as_ref()?.text.as_deref()
    }

    pub fn get_gemini_text_font_size(&self) -> i32 {
        match &self.gemini_text_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
    }

    fn gemini_text_font_size(&self) -> Option<i32> {
        self.fonts.as_ref()?.gemini.as_ref()?.text.as_ref()?.size
    }
}

#[derive(Deserialize)]
-- 
2.20.1

[PATCH 07/14] Pango markup span rendering for LineType::Quote Export this patch

This currently looks like it is shuffling clutter around, and that is
what is happening. We need to expand before we can do the next
refactoring.
---
 src/draw.rs     |  14 +-----
 src/settings.rs | 123 +++++++++++++++++++++++-------------------------
 2 files changed, 62 insertions(+), 75 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index 8128ca4..608ba22 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -114,18 +114,8 @@ pub fn gemini_content(
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(text));
                } else {
                    buffer.insert_markup(
                        &mut end_iter,
                        &format!(
                            "<span foreground=\"{}\" background=\"{}\" font_family=\"{}\" size=\"{}\" style=\"{}\">{}</span>\n",
                            crate::settings::get_gemini_quote_foreground_color(),
                            crate::settings::get_gemini_quote_background_color(),
                            crate::settings::get_gemini_quote_font_family(),
                            crate::settings::get_gemini_quote_font_size(),
                            crate::settings::get_gemini_quote_font_style(),
                            wrap_text(&text, &gui, &config)
                        ),
                    );
                    let markup = config.format_markup(settings::LineType::Quote, &wrap_text(&text, &gui, &config));
                    buffer.insert_markup(&mut end_iter, &markup);
                }
            }
            Ok(crate::gemini::parser::TextElement::Text(text)) => {
diff --git a/src/settings.rs b/src/settings.rs
index fd4199a..b1993df 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -9,6 +9,7 @@ use serde_derive::Deserialize;

pub enum LineType {
    Text,
    Quote,
}

#[derive(Deserialize)]
@@ -44,14 +45,21 @@ impl Settings {
                    self.get_gemini_text_font_size(),
                    text,
                )
            }
            },
            LineType::Quote => {
                format!(
                    "<span foreground=\"{}\" background=\"{}\" font_family=\"{}\" size=\"{}\" style=\"{}\">{}</span>\n",
                    self.get_gemini_quote_foreground_color(),
                    self.get_gemini_quote_background_color(),
                    self.get_gemini_quote_font_family(),
                    self.get_gemini_quote_font_size(),
                    self.get_gemini_quote_font_style(),
                    text,
                )
            },
        }
    }

    pub fn get_gemini_text_font_family(&self) -> &str {
        self.gemini_text_font_family().unwrap_or(DEFAULT_FONT)
    }

    pub fn max_width(&self) -> Option<usize> {
        match &self.general {
            Some(general) => general.max_width,
@@ -59,10 +67,22 @@ impl Settings {
        }
    }

    fn get_gemini_text_font_family(&self) -> &str {
        self.gemini_text_font_family().unwrap_or(DEFAULT_FONT)
    }

    fn gemini_text_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.text.as_ref()?.family.as_deref()
    }

    fn get_gemini_quote_font_family(&self) -> &str {
        self.gemini_quote_font_family().unwrap_or(DEFAULT_FONT)
    }

    fn gemini_quote_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.quote.as_ref()?.family.as_deref()
    }

    fn get_text_color(&self) -> &str {
        self.text_color().unwrap_or("black")
    }
@@ -71,7 +91,7 @@ impl Settings {
        self.colors.as_ref()?.text.as_deref()
    }

    pub fn get_gemini_text_font_size(&self) -> i32 {
    fn get_gemini_text_font_size(&self) -> i32 {
        match &self.gemini_text_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
@@ -81,6 +101,40 @@ impl Settings {
    fn gemini_text_font_size(&self) -> Option<i32> {
        self.fonts.as_ref()?.gemini.as_ref()?.text.as_ref()?.size
    }

    fn get_gemini_quote_font_size(&self) -> i32 {
        match &self.gemini_quote_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
    }

    fn gemini_quote_font_size(&self) -> Option<i32> {
        self.fonts.as_ref()?.gemini.as_ref()?.quote.as_ref()?.size
    }

    fn get_gemini_quote_foreground_color(&self) -> &str {
        self.quote_color().and_then(|color| color.foreground.as_deref()).unwrap_or("#e4e4e4")
    }

    fn get_gemini_quote_background_color(&self) -> &str {
        self.quote_color().and_then(|color| color.background.as_deref()).unwrap_or("gray")
    }

    fn quote_color(&self) -> Option<&QuoteColor> {
        self.colors.as_ref()?.quote.as_ref()
    }

    fn get_gemini_quote_font_style(&self) -> &str {
        match self.gemini_quote_font_style() {
            Some(style) => style,
            None => "italic",
        }
    }

    fn gemini_quote_font_style(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.quote.as_ref()?.style.as_deref()
    }
}

#[derive(Deserialize)]
@@ -289,18 +343,6 @@ fn gemini_list_font_style() -> Option<String> {
    read().fonts?.gemini?.list?.style
}

fn gemini_quote_font_family() -> Option<String> {
    read().fonts?.gemini?.quote?.family
}

fn gemini_quote_font_size() -> Option<i32> {
    read().fonts?.gemini?.quote?.size
}

fn gemini_quote_font_style() -> Option<String> {
    read().fonts?.gemini?.quote?.style
}

pub fn get_gemini_list_font_size() -> i32 {
    match gemini_list_font_size() {
        Some(size) => size * pango_sys::PANGO_SCALE,
@@ -322,27 +364,6 @@ pub fn get_gemini_list_font_style() -> String {
    }
}

pub fn get_gemini_quote_font_size() -> i32 {
    match gemini_quote_font_size() {
        Some(size) => size * pango_sys::PANGO_SCALE,
        None => DEFAULT_FONT_SIZE,
    }
}

pub fn get_gemini_quote_font_family() -> String {
    match gemini_quote_font_family() {
        Some(family) => family,
        None => String::from(DEFAULT_FONT),
    }
}

pub fn get_gemini_quote_font_style() -> String {
    match gemini_quote_font_style() {
        Some(style) => style,
        None => String::from("italic"),
    }
}

fn gopher_font_family() -> Option<String> {
    read().fonts?.gopher?.family
}
@@ -409,30 +430,6 @@ pub fn get_list_color() -> String {
    }
}

fn quote_color() -> Option<QuoteColor> {
    read().colors?.quote
}

pub fn get_gemini_quote_foreground_color() -> String {
    match quote_color() {
        Some(color) => match color.foreground {
            Some(color) => color,
            None => String::from("#e4e4e4"),
        },
        None => String::from("#e4e4e4"),
    }
}

pub fn get_gemini_quote_background_color() -> String {
    match quote_color() {
        Some(color) => match color.background {
            Some(color) => color,
            None => String::from("grey"),
        },
        None => String::from("grey"),
    }
}

fn text_color() -> Option<String> {
    read().colors?.text
}
-- 
2.20.1

[PATCH 08/14] Pango markup span rendering for LineType::ListItem Export this patch

---
 src/draw.rs     |  14 +-----
 src/settings.rs | 110 ++++++++++++++++++++++++------------------------
 2 files changed, 57 insertions(+), 67 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index 608ba22..9646d00 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -95,18 +95,8 @@ pub fn gemini_content(
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(item));
                } else {
                    buffer.insert_markup(
                      &mut end_iter,
                      &format!(
                          "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
                          crate::settings::get_list_color(),
                          crate::settings::get_gemini_list_font_size(),
                          crate::settings::get_gemini_list_font_family(),
                          crate::settings::get_gemini_list_font_style(),
                          crate::settings::get_list_character(),
                          wrap_text(&item, &gui, &config)
                      ),
                  );
                    let markup = config.format_markup(settings::LineType::ListItem, &wrap_text(&item, &gui, &config));
                    buffer.insert_markup(&mut end_iter, &markup);
                }
            }
            Ok(crate::gemini::parser::TextElement::Quote(text)) => {
diff --git a/src/settings.rs b/src/settings.rs
index b1993df..a210dd1 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -10,6 +10,7 @@ use serde_derive::Deserialize;
pub enum LineType {
    Text,
    Quote,
    ListItem,
}

#[derive(Deserialize)]
@@ -57,6 +58,17 @@ impl Settings {
                    text,
                )
            },
            LineType::ListItem => {
                format!(
                    "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
                    self.get_list_color(),
                    self.get_gemini_list_font_size(),
                    self.get_gemini_list_font_family(),
                    self.get_gemini_list_font_style(),
                    self.get_list_character(),
                    text,
                )
            }
        }
    }

@@ -135,6 +147,49 @@ impl Settings {
    fn gemini_quote_font_style(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.quote.as_ref()?.style.as_deref()
    }

    fn get_list_color(&self) -> &str {
        self.list_color().unwrap_or("green")
    }

    fn list_color(&self) -> Option<&str> {
        self.colors.as_ref()?.list.as_deref()
    }

    fn get_gemini_list_font_size(&self) -> i32 {
        match self.gemini_list_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
    }

    fn get_gemini_list_font_family(&self) -> &str {
        self.gemini_list_font_family().unwrap_or(DEFAULT_FONT)
    }

    fn get_gemini_list_font_style(&self) -> &str {
        self.gemini_list_font_style().unwrap_or(DEFAULT_FONT_STYLE)
    }

    fn gemini_list_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.list.as_ref()?.family.as_deref()
    }

    fn gemini_list_font_size(&self) -> Option<i32> {
        self.fonts.as_ref()?.gemini.as_ref()?.list.as_ref()?.size
    }

    fn gemini_list_font_style(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.list.as_ref()?.style.as_deref()
    }

    fn get_list_character(&self) -> &str {
        self.list_character().unwrap_or("â– ")
    }

    fn list_character(&self) -> Option<&str> {
        self.characters.as_ref()?.list.as_deref()
    }
}

#[derive(Deserialize)]
@@ -331,39 +386,6 @@ pub fn get_gemini_h3_font_style() -> String {
    }
}

fn gemini_list_font_family() -> Option<String> {
    read().fonts?.gemini?.list?.family
}

fn gemini_list_font_size() -> Option<i32> {
    read().fonts?.gemini?.list?.size
}

fn gemini_list_font_style() -> Option<String> {
    read().fonts?.gemini?.list?.style
}

pub fn get_gemini_list_font_size() -> i32 {
    match gemini_list_font_size() {
        Some(size) => size * pango_sys::PANGO_SCALE,
        None => DEFAULT_FONT_SIZE,
    }
}

pub fn get_gemini_list_font_family() -> String {
    match gemini_list_font_family() {
        Some(family) => family,
        None => String::from(DEFAULT_FONT),
    }
}

pub fn get_gemini_list_font_style() -> String {
    match gemini_list_font_style() {
        Some(style) => style,
        None => String::from(DEFAULT_FONT_STYLE),
    }
}

fn gopher_font_family() -> Option<String> {
    read().fonts?.gopher?.family
}
@@ -419,17 +441,6 @@ pub fn get_h3_color() -> String {
    }
}

fn list_color() -> Option<String> {
    read().colors?.list
}

pub fn get_list_color() -> String {
    match list_color() {
        Some(color) => color,
        None => String::from("green"),
    }
}

fn text_color() -> Option<String> {
    read().colors?.text
}
@@ -474,17 +485,6 @@ pub fn get_h3_character() -> String {
    }
}

fn list_character() -> Option<String> {
    read().characters?.list
}

pub fn get_list_character() -> String {
    match list_character() {
        Some(char) => char,
        None => String::from("â– "),
    }
}

pub fn read() -> Settings {
    let mut file = settings_file();
    let mut content = String::new();
-- 
2.20.1

[PATCH 09/14] Pango markup for LineType::H3 Export this patch

---
 src/draw.rs     |  14 +-----
 src/settings.rs | 110 ++++++++++++++++++++++++------------------------
 2 files changed, 57 insertions(+), 67 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index 9646d00..277f8e2 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -76,18 +76,8 @@ pub fn gemini_content(
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(escape_text(&header)));
                } else {
                    buffer.insert_markup(
                        &mut end_iter,
                        &format!(
                            "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
                            crate::settings::get_h3_color(),
                            crate::settings::get_gemini_h3_font_size(),
                            crate::settings::get_gemini_h3_font_family(),
                            crate::settings::get_gemini_h3_font_style(),
                            crate::settings::get_h3_character(),
                            escape_text(&header)
                        ),
                    );
                    let markup = config.format_markup(settings::LineType::H3, &escape_text(&header));
                    buffer.insert_markup(&mut end_iter, &markup);
                }
            }
            Ok(crate::gemini::parser::TextElement::ListItem(item)) => {
diff --git a/src/settings.rs b/src/settings.rs
index a210dd1..df5880a 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -11,6 +11,7 @@ pub enum LineType {
    Text,
    Quote,
    ListItem,
    H3,
}

#[derive(Deserialize)]
@@ -68,6 +69,17 @@ impl Settings {
                    self.get_list_character(),
                    text,
                )
            },
            LineType::H3 => {
                format!(
                    "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
                    self.get_h3_color(),
                    self.get_gemini_h3_font_size(),
                    self.get_gemini_h3_font_family(),
                    self.get_gemini_h3_font_style(),
                    self.get_h3_character(),
                    text
                )
            }
        }
    }
@@ -190,6 +202,49 @@ impl Settings {
    fn list_character(&self) -> Option<&str> {
        self.characters.as_ref()?.list.as_deref()
    }

    fn get_h3_color(&self) -> &str {
        self.h3_color().unwrap_or("#87CEFA")
    }

    fn h3_color(&self) -> Option<&str> {
        self.colors.as_ref()?.h3.as_deref()
    }

    fn get_gemini_h3_font_size(&self) -> i32 {
        match self.gemini_h3_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_H3_FONT_SIZE,
        }
    }

    fn get_gemini_h3_font_family(&self) -> &str {
        self.gemini_h3_font_family().unwrap_or(DEFAULT_FONT)
    }

    fn get_gemini_h3_font_style(&self) -> &str {
        self.gemini_h3_font_style().unwrap_or(DEFAULT_FONT_STYLE)
    }

    fn gemini_h3_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.h3.as_ref()?.family.as_deref()
    }

    fn gemini_h3_font_size(&self) -> Option<i32> {
        self.fonts.as_ref()?.gemini.as_ref()?.h3.as_ref()?.size
    }

    fn gemini_h3_font_style(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.h3.as_ref()?.style.as_deref()
    }

    fn get_h3_character(&self) -> &str {
        self.h3_character().unwrap_or("")
    }

    fn h3_character(&self) -> Option<&str> {
        self.characters.as_ref()?.h3.as_deref()
    }
}

#[derive(Deserialize)]
@@ -353,39 +408,6 @@ pub fn get_gemini_h2_font_style() -> String {
    }
}

fn gemini_h3_font_family() -> Option<String> {
    read().fonts?.gemini?.h3?.family
}

fn gemini_h3_font_size() -> Option<i32> {
    read().fonts?.gemini?.h3?.size
}

fn gemini_h3_font_style() -> Option<String> {
    read().fonts?.gemini?.h3?.style
}

pub fn get_gemini_h3_font_size() -> i32 {
    match gemini_h3_font_size() {
        Some(size) => size * pango_sys::PANGO_SCALE,
        None => DEFAULT_H3_FONT_SIZE,
    }
}

pub fn get_gemini_h3_font_family() -> String {
    match gemini_h3_font_family() {
        Some(family) => family,
        None => String::from(DEFAULT_FONT),
    }
}

pub fn get_gemini_h3_font_style() -> String {
    match gemini_h3_font_style() {
        Some(style) => style,
        None => String::from(DEFAULT_FONT_STYLE),
    }
}

fn gopher_font_family() -> Option<String> {
    read().fonts?.gopher?.family
}
@@ -430,17 +452,6 @@ pub fn get_h2_color() -> String {
    }
}

fn h3_color() -> Option<String> {
    read().colors?.h3
}

pub fn get_h3_color() -> String {
    match h3_color() {
        Some(color) => color,
        None => String::from("#87CEFA"),
    }
}

fn text_color() -> Option<String> {
    read().colors?.text
}
@@ -474,17 +485,6 @@ pub fn get_h2_character() -> String {
    }
}

fn h3_character() -> Option<String> {
    read().characters?.h3
}

pub fn get_h3_character() -> String {
    match h3_character() {
        Some(char) => char,
        None => String::new(),
    }
}

pub fn read() -> Settings {
    let mut file = settings_file();
    let mut content = String::new();
-- 
2.20.1

[PATCH 10/14] Pango markup for LineType::H2 Export this patch

---
 src/draw.rs     |  14 +-----
 src/settings.rs | 112 ++++++++++++++++++++++++------------------------
 2 files changed, 58 insertions(+), 68 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index 277f8e2..0a14ba2 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -57,18 +57,8 @@ pub fn gemini_content(
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(escape_text(&header)));
               } else {
                  buffer.insert_markup(
                      &mut end_iter,
                      &format!(
                          "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
                          crate::settings::get_h2_color(),
                          crate::settings::get_gemini_h2_font_size(),
                          crate::settings::get_gemini_h2_font_family(),
                          crate::settings::get_gemini_h2_font_style(),
                          crate::settings::get_h2_character(),
                          escape_text(&header)
                      ),
                  );
                   let markup = config.format_markup(settings::LineType::H2, &escape_text(&header));
                   buffer.insert_markup(&mut end_iter, &markup);
                }
            }
            Ok(crate::gemini::parser::TextElement::H3(header)) => {
diff --git a/src/settings.rs b/src/settings.rs
index df5880a..d11d0a0 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -12,6 +12,7 @@ pub enum LineType {
    Quote,
    ListItem,
    H3,
    H2,
}

#[derive(Deserialize)]
@@ -80,7 +81,18 @@ impl Settings {
                    self.get_h3_character(),
                    text
                )
            }
            },
            LineType::H2 => {
                format!(
                    "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
                    self.get_h2_color(),
                    self.get_gemini_h2_font_size(),
                    self.get_gemini_h2_font_family(),
                    self.get_gemini_h2_font_style(),
                    self.get_h2_character(),
                    text,
                )
            },
        }
    }

@@ -245,6 +257,49 @@ impl Settings {
    fn h3_character(&self) -> Option<&str> {
        self.characters.as_ref()?.h3.as_deref()
    }

    fn get_h2_color(&self) -> &str {
        self.h2_color().unwrap_or("#FF1493")
    }

    fn h2_color(&self) -> Option<&str> {
        self.colors.as_ref()?.h2.as_deref()
    }

    fn get_gemini_h2_font_size(&self) -> i32 {
        match self.gemini_h2_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_H2_FONT_SIZE,
        }
    }

    fn get_gemini_h2_font_family(&self) -> &str {
        self.gemini_h2_font_family().unwrap_or(DEFAULT_FONT)
    }

    fn get_gemini_h2_font_style(&self) -> &str {
        self.gemini_h2_font_style().unwrap_or(DEFAULT_FONT_STYLE)
    }

    fn gemini_h2_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.h2.as_ref()?.family.as_deref()
    }

    fn gemini_h2_font_size(&self) -> Option<i32> {
        self.fonts.as_ref()?.gemini.as_ref()?.h2.as_ref()?.size
    }

    fn gemini_h2_font_style(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.h2.as_ref()?.style.as_deref()
    }

    fn get_h2_character(&self) -> &str {
        self.h2_character().unwrap_or("")
    }

    fn h2_character(&self) -> Option<&str> {
        self.characters.as_ref()?.h2.as_deref()
    }
}

#[derive(Deserialize)]
@@ -375,39 +430,6 @@ pub fn get_gemini_h1_font_style() -> String {
    }
}

fn gemini_h2_font_family() -> Option<String> {
    read().fonts?.gemini?.h2?.family
}

fn gemini_h2_font_size() -> Option<i32> {
    read().fonts?.gemini?.h2?.size
}

fn gemini_h2_font_style() -> Option<String> {
    read().fonts?.gemini?.h2?.style
}

pub fn get_gemini_h2_font_size() -> i32 {
    match gemini_h2_font_size() {
        Some(size) => size * pango_sys::PANGO_SCALE,
        None => DEFAULT_H2_FONT_SIZE,
    }
}

pub fn get_gemini_h2_font_family() -> String {
    match gemini_h2_font_family() {
        Some(family) => family,
        None => String::from(DEFAULT_FONT),
    }
}

pub fn get_gemini_h2_font_style() -> String {
    match gemini_h2_font_style() {
        Some(style) => style,
        None => String::from(DEFAULT_FONT_STYLE),
    }
}

fn gopher_font_family() -> Option<String> {
    read().fonts?.gopher?.family
}
@@ -441,17 +463,6 @@ pub fn get_h1_color() -> String {
    }
}

fn h2_color() -> Option<String> {
    read().colors?.h2
}

pub fn get_h2_color() -> String {
    match h2_color() {
        Some(color) => color,
        None => String::from("#FF1493"),
    }
}

fn text_color() -> Option<String> {
    read().colors?.text
}
@@ -474,17 +485,6 @@ pub fn get_h1_character() -> String {
    }
}

fn h2_character() -> Option<String> {
    read().characters?.h2
}

pub fn get_h2_character() -> String {
    match h2_character() {
        Some(char) => char,
        None => String::new(),
    }
}

pub fn read() -> Settings {
    let mut file = settings_file();
    let mut content = String::new();
-- 
2.20.1

[PATCH 11/14] Pango markup for LineType::H1 Export this patch

---
 src/draw.rs     |  14 +-----
 src/settings.rs | 110 ++++++++++++++++++++++++------------------------
 2 files changed, 57 insertions(+), 67 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index 0a14ba2..4c800db 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -38,18 +38,8 @@ pub fn gemini_content(
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(escape_text(&header)));
                } else {
                  buffer.insert_markup(
                      &mut end_iter,
                      &format!(
                          "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
                          crate::settings::get_h1_color(),
                          crate::settings::get_gemini_h1_font_size(),
                          crate::settings::get_gemini_h1_font_family(),
                          crate::settings::get_gemini_h1_font_style(),
                          crate::settings::get_h1_character(),
                          escape_text(&header)
                      ),
                  );
                    let markup = config.format_markup(settings::LineType::H1, &escape_text(&header));
                    buffer.insert_markup(&mut end_iter, &markup);
                }
            }
            Ok(crate::gemini::parser::TextElement::H2(header)) => {
diff --git a/src/settings.rs b/src/settings.rs
index d11d0a0..e97e667 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -13,6 +13,7 @@ pub enum LineType {
    ListItem,
    H3,
    H2,
    H1,
}

#[derive(Deserialize)]
@@ -93,6 +94,17 @@ impl Settings {
                    text,
                )
            },
            LineType::H1 => {
                format!(
                    "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
                    self.get_h1_color(),
                    self.get_gemini_h1_font_size(),
                    self.get_gemini_h1_font_family(),
                    self.get_gemini_h1_font_style(),
                    self.get_h1_character(),
                    text,
                )
            }
        }
    }

@@ -300,6 +312,49 @@ impl Settings {
    fn h2_character(&self) -> Option<&str> {
        self.characters.as_ref()?.h2.as_deref()
    }

    fn get_h1_color(&self) -> &str {
        self.h1_color().unwrap_or("#9932CC")
    }

    fn h1_color(&self) -> Option<&str> {
        self.colors.as_ref()?.h1.as_deref()
    }

    fn get_gemini_h1_font_size(&self) -> i32 {
        match self.gemini_h1_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_H1_FONT_SIZE,
        }
    }

    fn get_gemini_h1_font_family(&self) -> &str {
        self.gemini_h1_font_family().unwrap_or(DEFAULT_FONT)
    }

    fn get_gemini_h1_font_style(&self) -> &str {
        self.gemini_h1_font_style().unwrap_or(DEFAULT_FONT_STYLE)
    }

    fn gemini_h1_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.h1.as_ref()?.family.as_deref()
    }

    fn gemini_h1_font_size(&self) -> Option<i32> {
        self.fonts.as_ref()?.gemini.as_ref()?.h1.as_ref()?.size
    }

    fn gemini_h1_font_style(&self) -> Option<&str> {
        self.fonts.as_ref()?.gemini.as_ref()?.h1.as_ref()?.style.as_deref()
    }

    fn get_h1_character(&self) -> &str {
        self.h1_character().unwrap_or("")
    }

    fn h1_character(&self) -> Option<&str> {
        self.characters.as_ref()?.h1.as_deref()
    }
}

#[derive(Deserialize)]
@@ -397,39 +452,6 @@ pub fn get_gemini_text_font_size() -> i32 {
    }
}

fn gemini_h1_font_family() -> Option<String> {
    read().fonts?.gemini?.h1?.family
}

fn gemini_h1_font_size() -> Option<i32> {
    read().fonts?.gemini?.h1?.size
}

fn gemini_h1_font_style() -> Option<String> {
    read().fonts?.gemini?.h1?.style
}

pub fn get_gemini_h1_font_size() -> i32 {
    match gemini_h1_font_size() {
        Some(size) => size * pango_sys::PANGO_SCALE,
        None => DEFAULT_H1_FONT_SIZE,
    }
}

pub fn get_gemini_h1_font_family() -> String {
    match gemini_h1_font_family() {
        Some(family) => family,
        None => String::from(DEFAULT_FONT),
    }
}

pub fn get_gemini_h1_font_style() -> String {
    match gemini_h1_font_style() {
        Some(style) => style,
        None => String::from(DEFAULT_FONT_STYLE),
    }
}

fn gopher_font_family() -> Option<String> {
    read().fonts?.gopher?.family
}
@@ -452,17 +474,6 @@ pub fn get_gopher_font_size() -> i32 {
    }
}

fn h1_color() -> Option<String> {
    read().colors?.h1
}

pub fn get_h1_color() -> String {
    match h1_color() {
        Some(color) => color,
        None => String::from("#9932CC"),
    }
}

fn text_color() -> Option<String> {
    read().colors?.text
}
@@ -474,17 +485,6 @@ pub fn get_text_color() -> String {
    }
}

fn h1_character() -> Option<String> {
    read().characters?.h1
}

pub fn get_h1_character() -> String {
    match h1_character() {
        Some(char) => char,
        None => String::new(),
    }
}

pub fn read() -> Settings {
    let mut file = settings_file();
    let mut content = String::new();
-- 
2.20.1

[PATCH 12/14] Pango markup for LineType::Preformatted Export this patch

This fills in the "monospace" spans. One of them specified the size and
the other used the default. Collapse them to both specify the size so
the user can configure all monospace text at once.
---
 src/draw.rs     | 46 +++++++++++++++++++---------------------------
 src/settings.rs | 22 ++++++++++------------
 2 files changed, 29 insertions(+), 39 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index 4c800db..0b8292a 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -36,7 +36,8 @@ pub fn gemini_content(
            Ok(crate::gemini::parser::TextElement::H1(header)) => {
                let mut end_iter = buffer.get_end_iter();
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(escape_text(&header)));
                    let markup = config.format_markup(settings::LineType::Preformatted, &escape_text(&header));
                    buffer.insert_markup(&mut end_iter, &markup);
                } else {
                    let markup = config.format_markup(settings::LineType::H1, &escape_text(&header));
                    buffer.insert_markup(&mut end_iter, &markup);
@@ -45,16 +46,18 @@ pub fn gemini_content(
            Ok(crate::gemini::parser::TextElement::H2(header)) => {
                let mut end_iter = buffer.get_end_iter();
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(escape_text(&header)));
               } else {
                   let markup = config.format_markup(settings::LineType::H2, &escape_text(&header));
                   buffer.insert_markup(&mut end_iter, &markup);
                    let markup = config.format_markup(settings::LineType::Preformatted, &escape_text(&header));
                    buffer.insert_markup(&mut end_iter, &markup);
                } else {
                    let markup = config.format_markup(settings::LineType::H2, &escape_text(&header));
                    buffer.insert_markup(&mut end_iter, &markup);
                }
            }
            Ok(crate::gemini::parser::TextElement::H3(header)) => {
                let mut end_iter = buffer.get_end_iter();
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(escape_text(&header)));
                    let markup = config.format_markup(settings::LineType::Preformatted, &escape_text(&header));
                    buffer.insert_markup(&mut end_iter, &markup);
                } else {
                    let markup = config.format_markup(settings::LineType::H3, &escape_text(&header));
                    buffer.insert_markup(&mut end_iter, &markup);
@@ -63,7 +66,8 @@ pub fn gemini_content(
            Ok(crate::gemini::parser::TextElement::ListItem(item)) => {
                let mut end_iter = buffer.get_end_iter();
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(item));
                    let markup = config.format_markup(settings::LineType::Preformatted, &item);
                    buffer.insert_markup(&mut end_iter, &markup);
                } else {
                    let markup = config.format_markup(settings::LineType::ListItem, &wrap_text(&item, &gui, &config));
                    buffer.insert_markup(&mut end_iter, &markup);
@@ -72,7 +76,8 @@ pub fn gemini_content(
            Ok(crate::gemini::parser::TextElement::Quote(text)) => {
                let mut end_iter = buffer.get_end_iter();
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(text));
                    let markup = config.format_markup(settings::LineType::Preformatted, &text);
                    buffer.insert_markup(&mut end_iter, &markup);
                } else {
                    let markup = config.format_markup(settings::LineType::Quote, &wrap_text(&text, &gui, &config));
                    buffer.insert_markup(&mut end_iter, &markup);
@@ -81,7 +86,8 @@ pub fn gemini_content(
            Ok(crate::gemini::parser::TextElement::Text(text)) => {
                let mut end_iter = buffer.get_end_iter();
                if mono_toggle {
                    buffer.insert_markup(&mut end_iter, &mono_span(colors::colorize(&text)));
                    let markup = config.format_markup(settings::LineType::Preformatted, &colors::colorize(&text));
                    buffer.insert_markup(&mut end_iter, &markup);
                } else {
                    let markup = config.format_markup(settings::LineType::Text, &wrap_text(&text, &gui, &config));
                    buffer.insert_markup(&mut end_iter, &markup);
@@ -90,7 +96,8 @@ pub fn gemini_content(
            Ok(crate::gemini::parser::TextElement::LinkItem(link_item)) => {
                if mono_toggle {
                    let mut end_iter = buffer.get_end_iter();
                    buffer.insert_markup(&mut end_iter, &mono_span(escape_text(&link_item)));
                    let markup = config.format_markup(settings::LineType::Preformatted, &escape_text(&link_item));
                    buffer.insert_markup(&mut end_iter, &markup);
                } else {
                    gemini_link(&gui, &config, link_item);
                }
@@ -107,14 +114,8 @@ pub fn gemini_text_content(gui: &Arc<Gui>, config: &Arc<settings::Settings>, con

    for line in content {
        let mut end_iter = buffer.get_end_iter();
        buffer.insert_markup(
            &mut end_iter,
            &format!(
                "<span foreground=\"{}\" font_family=\"monospace\">{}</span>\n",
                crate::settings::get_text_color(),
                colors::colorize(&line)
            ),
        );
        let markup = config.format_markup(settings::LineType::Preformatted, &colors::colorize(&line));
        buffer.insert_markup(&mut end_iter, &markup);
    }
    buffer
}
@@ -419,15 +420,6 @@ fn escape_text(str: &str) -> String {
    String::from(glib::markup_escape_text(&str).as_str())
}

fn mono_span(text: String) -> String {
    format!(
        "<span foreground=\"{}\" font_family=\"monospace\" size=\"{}\">{}</span>\n",
        crate::settings::get_text_color(),
        crate::settings::get_gemini_text_font_size(),
        text
    )
}

fn width(gui: &Arc<Gui>, config: &Arc<settings::Settings>) -> usize {
    let (win_width, _) = gtk::ApplicationWindow::get_size(gui.window());
    let calculated_width = (win_width / 10).try_into().unwrap();
diff --git a/src/settings.rs b/src/settings.rs
index e97e667..18c99d7 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -14,6 +14,7 @@ pub enum LineType {
    H3,
    H2,
    H1,
    Preformatted,
}

#[derive(Deserialize)]
@@ -104,7 +105,15 @@ impl Settings {
                    self.get_h1_character(),
                    text,
                )
            }
            },
            LineType::Preformatted => {
                format!(
                    "<span foreground=\"{}\" font_family=\"monospace\" size=\"{}\">{}</span>\n",
                    self.get_text_color(),
                    self.get_gemini_text_font_size(),
                    text,
                )
            },
        }
    }

@@ -441,17 +450,6 @@ pub fn get_finger_font_size() -> i32 {
    }
}

fn gemini_text_font_size() -> Option<i32> {
    read().fonts?.gemini?.text?.size
}

pub fn get_gemini_text_font_size() -> i32 {
    match gemini_text_font_size() {
        Some(size) => size * pango_sys::PANGO_SCALE,
        None => DEFAULT_FONT_SIZE,
    }
}

fn gopher_font_family() -> Option<String> {
    read().fonts?.gopher?.family
}
-- 
2.20.1

[PATCH 13/14] Pango markup for LineType::Gopher and Finger Export this patch

---
 src/draw.rs     |  24 ++--------
 src/settings.rs | 113 +++++++++++++++++++++++++-----------------------
 2 files changed, 62 insertions(+), 75 deletions(-)

diff --git a/src/draw.rs b/src/draw.rs
index 0b8292a..c543fb6 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -133,17 +133,9 @@ pub fn gopher_content(
            Ok(crate::gopher::parser::TextElement::Text(text)) => {
                let mut end_iter = buffer.get_end_iter();
                let text = colors::colorize(&text);
                let markup = config.format_markup(settings::LineType::Gopher, &text);

                buffer.insert_markup(
                    &mut end_iter,
                    &format!(
                        "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
                        crate::settings::get_text_color(),
                        crate::settings::get_gopher_font_family(),
                        crate::settings::get_gopher_font_size(),
                        text
                    ),
                );
                buffer.insert_markup(&mut end_iter, &markup);
            }
            Ok(crate::gopher::parser::TextElement::LinkItem(link_item)) => {
                gopher_link(&gui, &config, colors::cleanup(&link_item));
@@ -175,17 +167,9 @@ pub fn finger_content(
        match el {
            Ok(crate::finger::parser::TextElement::Text(text)) => {
                let mut end_iter = buffer.get_end_iter();
                let markup = config.format_markup(settings::LineType::Finger, &escape_text(&text));

                buffer.insert_markup(
                    &mut end_iter,
                    &format!(
                        "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
                        crate::settings::get_text_color(),
                        crate::settings::get_finger_font_family(),
                        crate::settings::get_finger_font_size(),
                        escape_text(&text)
                    ),
                );
                buffer.insert_markup(&mut end_iter, &markup);
            }
            Err(_) => println!("Something failed."),
        }
diff --git a/src/settings.rs b/src/settings.rs
index 18c99d7..d785324 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -15,6 +15,8 @@ pub enum LineType {
    H2,
    H1,
    Preformatted,
    Gopher,
    Finger,
}

#[derive(Deserialize)]
@@ -114,6 +116,24 @@ impl Settings {
                    text,
                )
            },
            LineType::Gopher => {
                format!(
                    "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
                    self.get_text_color(),
                    self.get_gopher_font_family(),
                    self.get_gopher_font_size(),
                    text
                )
            },
            LineType::Finger => {
                format!(
                    "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
                    self.get_text_color(),
                    self.get_finger_font_family(),
                    self.get_finger_font_size(),
                    text,
                )
            },
        }
    }

@@ -364,6 +384,44 @@ impl Settings {
    fn h1_character(&self) -> Option<&str> {
        self.characters.as_ref()?.h1.as_deref()
    }

    fn get_gopher_font_family(&self) -> &str {
        self.gopher_font_family().unwrap_or(DEFAULT_FONT)
    }

    fn get_gopher_font_size(&self) -> i32 {
        match self.gopher_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
    }

    fn gopher_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.gopher.as_ref()?.family.as_deref()
    }

    fn gopher_font_size(&self) -> Option<i32> {
        self.fonts.as_ref()?.gopher.as_ref()?.size
    }

    fn get_finger_font_family(&self) -> &str {
        self.finger_font_family().unwrap_or(DEFAULT_FONT)
    }

    fn get_finger_font_size(&self) -> i32 {
        match self.finger_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
    }

    fn finger_font_family(&self) -> Option<&str> {
        self.fonts.as_ref()?.finger.as_ref()?.family.as_deref()
    }

    fn finger_font_size(&self) -> Option<i32> {
        self.fonts.as_ref()?.finger.as_ref()?.size
    }
}

#[derive(Deserialize)]
@@ -428,61 +486,6 @@ const DEFAULT_H1_FONT_SIZE: i32 = 16 * pango_sys::PANGO_SCALE;
const DEFAULT_H2_FONT_SIZE: i32 = 13 * pango_sys::PANGO_SCALE;
const DEFAULT_H3_FONT_SIZE: i32 = 12 * pango_sys::PANGO_SCALE;

fn finger_font_family() -> Option<String> {
    read().fonts?.finger?.family
}

fn finger_font_size() -> Option<i32> {
    read().fonts?.finger?.size.or(Some(DEFAULT_FONT_SIZE))
}

pub fn get_finger_font_family() -> String {
    match finger_font_family() {
        Some(family) => family,
        None => String::from(DEFAULT_FONT),
    }
}

pub fn get_finger_font_size() -> i32 {
    match finger_font_size() {
        Some(size) => size * pango_sys::PANGO_SCALE,
        None => DEFAULT_FONT_SIZE,
    }
}

fn gopher_font_family() -> Option<String> {
    read().fonts?.gopher?.family
}

fn gopher_font_size() -> Option<i32> {
    read().fonts?.gopher?.size
}

pub fn get_gopher_font_family() -> String {
    match gopher_font_family() {
        Some(family) => family,
        None => String::from(DEFAULT_FONT),
    }
}

pub fn get_gopher_font_size() -> i32 {
    match gopher_font_size() {
        Some(size) => size * pango_sys::PANGO_SCALE,
        None => DEFAULT_FONT_SIZE,
    }
}

fn text_color() -> Option<String> {
    read().colors?.text
}

pub fn get_text_color() -> String {
    match text_color() {
        Some(color) => color,
        None => String::from("black"),
    }
}

pub fn read() -> Settings {
    let mut file = settings_file();
    let mut content = String::new();
-- 
2.20.1

[PATCH 14/14] Cleanup Export this patch

The boring part is that I moved some lines up (or down, depending on
your perspective).

More interestingly, use `Option` methods to simplify the match
statements.

Most interestingly, make `settings::read()` return a
`std::io::Result<Settings>`, use `?` in it and in `settings_file`, and
have `main` return a `std::io::Result<()>`.
---
 src/main.rs     |   6 +-
 src/settings.rs | 251 ++++++++++++++++++++++--------------------------
 2 files changed, 119 insertions(+), 138 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 1a22ad9..c2dd7b2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -31,9 +31,9 @@ mod settings;
mod status;
use status::Status;

fn main() {
fn main() -> std::io::Result<()> {
    // Read in settings
    let config = Arc::new(settings::read());
    let config = Arc::new(settings::read()?);

    // Start up the GTK3 subsystem.
    gtk::init().expect("Unable to start GTK3. Error");
@@ -143,6 +143,8 @@ fn main() {

    gui.start();
    gtk::main();

    Ok(())
}

fn route_url(gui: &Arc<Gui>, config: &Arc<settings::Settings>, url: &str) {
diff --git a/src/settings.rs b/src/settings.rs
index d785324..ca06308 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -27,19 +27,100 @@ pub struct Settings {
    fonts: Option<Font>,
}

#[derive(Deserialize)]
struct General {
    start_url: Option<String>,
    max_width: Option<usize>,
}

#[derive(Deserialize)]
struct Color {
    h1: Option<String>,
    h2: Option<String>,
    h3: Option<String>,
    list: Option<String>,
    text: Option<String>,
    background: Option<String>,
    quote: Option<QuoteColor>,
}

#[derive(Deserialize)]
struct Character {
    h1: Option<String>,
    h2: Option<String>,
    h3: Option<String>,
    list: Option<String>,
}

#[derive(Debug, Deserialize)]
struct Font {
    finger: Option<FontAttr>,
    gemini: Option<GeminiFontAttr>,
    gopher: Option<FontAttr>,
}

#[derive(Debug, Deserialize)]
struct FontAttr {
    family: Option<String>,
    style: Option<String>,
    size: Option<i32>,
}

#[derive(Debug, Deserialize)]
struct GeminiFontAttr {
    text: Option<FontAttr>,
    h1: Option<FontAttr>,
    h2: Option<FontAttr>,
    h3: Option<FontAttr>,
    list: Option<FontAttr>,
    quote: Option<FontAttr>,
}

#[derive(Debug, Deserialize)]
struct QuoteColor {
    foreground: Option<String>,
    background: Option<String>,
}

const DEFAULT_FONT: &str = "serif";
const DEFAULT_FONT_STYLE: &str = "normal";
const DEFAULT_FONT_SIZE: i32 = 11 * pango_sys::PANGO_SCALE;
const DEFAULT_H1_FONT_SIZE: i32 = 16 * pango_sys::PANGO_SCALE;
const DEFAULT_H2_FONT_SIZE: i32 = 13 * pango_sys::PANGO_SCALE;
const DEFAULT_H3_FONT_SIZE: i32 = 12 * pango_sys::PANGO_SCALE;

pub fn read() -> std::io::Result<Settings> {
    let mut file = settings_file()?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;

    let settings: Settings = toml::from_str(&content)?;
    Ok(settings)
}

fn settings_file() -> std::io::Result<File> {
    let mut settings = dirs::config_dir()
        .ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "no XDG config dir"))?;
    settings.push("castor");
    fs::create_dir_all(&settings)?;
    settings.push("settings.toml");
    let file_path = settings.into_os_string();
    let file = OpenOptions::new()
        .create(true)
        .append(true)
        .read(true)
        .open(file_path)?;

    Ok(file)
}

impl Settings {
    pub fn start_url(&self) -> Option<&str> {
        match &self.general {
            Some(general) => general.start_url.as_deref(),
            None => None,
        }
        self.general.as_ref().and_then(|general| general.start_url.as_deref())
    }

    pub fn background_color(&self) -> Option<&str> {
        match &self.colors {
            Some(colors) => colors.background.as_deref(),
            None => None,
        }
        self.colors.as_ref().and_then(|colors| colors.background.as_deref())
    }

    pub fn format_markup(&self, line_type: LineType, text: &str) -> String {
@@ -138,10 +219,7 @@ impl Settings {
    }

    pub fn max_width(&self) -> Option<usize> {
        match &self.general {
            Some(general) => general.max_width,
            None => None,
        }
        self.general.as_ref().and_then(|general| general.max_width)
    }

    fn get_gemini_text_font_family(&self) -> &str {
@@ -169,10 +247,9 @@ impl Settings {
    }

    fn get_gemini_text_font_size(&self) -> i32 {
        match &self.gemini_text_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
        self.gemini_text_font_size().
            map(|size| size * pango_sys::PANGO_SCALE).
            unwrap_or(DEFAULT_FONT_SIZE)
    }

    fn gemini_text_font_size(&self) -> Option<i32> {
@@ -180,10 +257,9 @@ impl Settings {
    }

    fn get_gemini_quote_font_size(&self) -> i32 {
        match &self.gemini_quote_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
        self.gemini_quote_font_size().
            map(|size| size * pango_sys::PANGO_SCALE).
            unwrap_or(DEFAULT_FONT_SIZE)
    }

    fn gemini_quote_font_size(&self) -> Option<i32> {
@@ -203,10 +279,7 @@ impl Settings {
    }

    fn get_gemini_quote_font_style(&self) -> &str {
        match self.gemini_quote_font_style() {
            Some(style) => style,
            None => "italic",
        }
        self.gemini_quote_font_style().unwrap_or("italic")
    }

    fn gemini_quote_font_style(&self) -> Option<&str> {
@@ -222,10 +295,9 @@ impl Settings {
    }

    fn get_gemini_list_font_size(&self) -> i32 {
        match self.gemini_list_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
        self.gemini_list_font_size().
            map(|size| size * pango_sys::PANGO_SCALE).
            unwrap_or(DEFAULT_FONT_SIZE)
    }

    fn get_gemini_list_font_family(&self) -> &str {
@@ -265,10 +337,9 @@ impl Settings {
    }

    fn get_gemini_h3_font_size(&self) -> i32 {
        match self.gemini_h3_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_H3_FONT_SIZE,
        }
        self.gemini_h3_font_size().
            map(|size| size * pango_sys::PANGO_SCALE).
            unwrap_or(DEFAULT_H3_FONT_SIZE)
    }

    fn get_gemini_h3_font_family(&self) -> &str {
@@ -308,10 +379,9 @@ impl Settings {
    }

    fn get_gemini_h2_font_size(&self) -> i32 {
        match self.gemini_h2_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_H2_FONT_SIZE,
        }
        self.gemini_h2_font_size().
            map(|size| size * pango_sys::PANGO_SCALE).
            unwrap_or(DEFAULT_H2_FONT_SIZE)
    }

    fn get_gemini_h2_font_family(&self) -> &str {
@@ -351,10 +421,9 @@ impl Settings {
    }

    fn get_gemini_h1_font_size(&self) -> i32 {
        match self.gemini_h1_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_H1_FONT_SIZE,
        }
        self.gemini_h1_font_size().
            map(|size| size * pango_sys::PANGO_SCALE).
            unwrap_or(DEFAULT_H1_FONT_SIZE)
    }

    fn get_gemini_h1_font_family(&self) -> &str {
@@ -390,10 +459,9 @@ impl Settings {
    }

    fn get_gopher_font_size(&self) -> i32 {
        match self.gopher_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
        self.gopher_font_size().
            map(|size| size * pango_sys::PANGO_SCALE).
            unwrap_or(DEFAULT_FONT_SIZE)
    }

    fn gopher_font_family(&self) -> Option<&str> {
@@ -409,10 +477,9 @@ impl Settings {
    }

    fn get_finger_font_size(&self) -> i32 {
        match self.finger_font_size() {
            Some(size) => size * pango_sys::PANGO_SCALE,
            None => DEFAULT_FONT_SIZE,
        }
        self.finger_font_size().
            map(|size| size * pango_sys::PANGO_SCALE).
            unwrap_or(DEFAULT_FONT_SIZE)
    }

    fn finger_font_family(&self) -> Option<&str> {
@@ -423,91 +490,3 @@ impl Settings {
        self.fonts.as_ref()?.finger.as_ref()?.size
    }
}

#[derive(Deserialize)]
struct General {
    start_url: Option<String>,
    max_width: Option<usize>,
}

#[derive(Deserialize)]
struct Color {
    h1: Option<String>,
    h2: Option<String>,
    h3: Option<String>,
    list: Option<String>,
    text: Option<String>,
    background: Option<String>,
    quote: Option<QuoteColor>,
}

#[derive(Deserialize)]
struct Character {
    h1: Option<String>,
    h2: Option<String>,
    h3: Option<String>,
    list: Option<String>,
}

#[derive(Debug, Deserialize)]
struct Font {
    finger: Option<FontAttr>,
    gemini: Option<GeminiFontAttr>,
    gopher: Option<FontAttr>,
}

#[derive(Debug, Deserialize)]
struct FontAttr {
    family: Option<String>,
    style: Option<String>,
    size: Option<i32>,
}

#[derive(Debug, Deserialize)]
struct GeminiFontAttr {
    text: Option<FontAttr>,
    h1: Option<FontAttr>,
    h2: Option<FontAttr>,
    h3: Option<FontAttr>,
    list: Option<FontAttr>,
    quote: Option<FontAttr>,
}

#[derive(Debug, Deserialize)]
struct QuoteColor {
    foreground: Option<String>,
    background: Option<String>,
}

const DEFAULT_FONT: &str = "serif";
const DEFAULT_FONT_STYLE: &str = "normal";
const DEFAULT_FONT_SIZE: i32 = 11 * pango_sys::PANGO_SCALE;
const DEFAULT_H1_FONT_SIZE: i32 = 16 * pango_sys::PANGO_SCALE;
const DEFAULT_H2_FONT_SIZE: i32 = 13 * pango_sys::PANGO_SCALE;
const DEFAULT_H3_FONT_SIZE: i32 = 12 * pango_sys::PANGO_SCALE;

pub fn read() -> Settings {
    let mut file = settings_file();
    let mut content = String::new();
    println!("reading from config");
    file.read_to_string(&mut content)
        .expect("Unable to read file");

    let settings: Settings = toml::from_str(&content).unwrap();
    settings
}

fn settings_file() -> File {
    let mut settings = dirs::config_dir().unwrap();
    settings.push("castor");
    fs::create_dir_all(&settings).unwrap();
    settings.push("settings.toml");
    let file_path = settings.into_os_string();

    OpenOptions::new()
        .create(true)
        .append(true)
        .read(true)
        .open(file_path)
        .expect("file not found")
}
-- 
2.20.1