gdritter repos delve / master mk-cards / src / markup.rs
master

Tree @master (Download .tar.gz)

markup.rs @master

85aa32e
 
 
 
 
 
3eb313b
85aa32e
3eb313b
 
 
 
85aa32e
 
3eb313b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85aa32e
 
3eb313b
 
 
 
 
85aa32e
 
 
3eb313b
85aa32e
 
 
 
3eb313b
 
85aa32e
 
 
 
 
 
 
3eb313b
85aa32e
 
 
 
3eb313b
85aa32e
 
 
 
3eb313b
 
85aa32e
 
 
3eb313b
 
85aa32e
 
 
3eb313b
85aa32e
 
 
 
3eb313b
85aa32e
 
 
 
 
 
 
 
 
 
 
 
 
3eb313b
85aa32e
 
 
3eb313b
 
85aa32e
 
 
 
 
 
3eb313b
 
85aa32e
3eb313b
85aa32e
 
 
3eb313b
85aa32e
 
 
 
 
 
3eb313b
7a0dfe7
85aa32e
 
 
 
 
3eb313b
 
 
 
 
85aa32e
 
 
 
 
 
 
3eb313b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
use cairo;
use pango;
use pangocairo;
use failure;

use pango::LayoutExt;
pub use pango::Weight;

/// A `Font` is a more convenient, less imperative abstraction over
/// the Pango `FontDescription` type
pub struct Font {
    f: pango::FontDescription,
}

impl Font {
    /// Create a new `Font` from a textual description of the font
    pub fn new(s: &str) -> Font {
        Font { f: pango::FontDescription::from_string(s) }
    }

    /// Set the font size in points
    pub fn size(mut self, s: i32) -> Font {
        self.f.set_size(s * pango::SCALE);
        self
    }

    /// Set the font weight
    pub fn weight(mut self, w: pango::Weight) -> Font {
        self.f.set_weight(w);
        self
    }

    /// Get the underlying pango `FontDescription` value
    pub fn get(self) -> pango::FontDescription {
        self.f
    }
}

/// A `MarkupBuffer` is an abstraction that allows a high-level
/// mostly-correct-by-construction (in theory, anyway) approach to
/// adding Pango-level formatting
pub struct MarkupBuffer {
    buf: String,
}

impl MarkupBuffer {
    /// Create a new empty `MarkupBuffer`
    pub fn new() -> MarkupBuffer {
        MarkupBuffer { buf: String::new() }
    }

    /// Create a view on the `MarkupBuffer` that permits markup of the
    /// added strings
    pub fn markup<'a>(&'a mut self) -> Markup<'a> {
        Markup {
            view: self,
            frags: Vec::new(),
        }
    }

    /// Add a string fragment to the markup buffer
    pub fn push(&mut self, s: &str) {
        self.buf.push_str(s);
    }

    /// Add a single space to the buffer
    pub fn space(&mut self) {
        self.buf.push_str(" ");
    }

    /// Get the underlying string value of the buffer
    fn get(self) -> String {
        self.buf
    }

    /// Draw the buffer to the underlying Cairo surface, with an
    /// optional width for layout purposes
    pub fn show_with_font(
        self,
        ctx: &cairo::Context,
        font: Font,
        width: Option<i32>,
    ) -> Result<(), failure::Error> {
        let layout = pangocairo::functions::create_layout(ctx)
            .ok_or(format_err!("Unable to create Pango layout"))?;
        layout.set_font_description(Some(&font.get()));
        if let Some(w) = width {
            layout.set_width(w * pango::SCALE);
        }
        let content = self.get();
        let (attrs, text, _) = pango::parse_markup(&content, '\0')?;
        layout.set_attributes(&attrs);
        layout.set_text(&text);
        pangocairo::functions::show_layout(ctx, &layout);
        Ok(())
    }
}

enum Frag {
    Weight(pango::Weight),
    Size(u8),
}

/// A `Markup` value is a view into a `MarkupBuffer` that permits
/// extra markup to be applied transparently.
pub struct Markup<'a> {
    view: &'a mut MarkupBuffer,
    frags: Vec<Frag>,
}

impl<'a> Markup<'a> {
    /// Give this view a different weight than usual
    pub fn weight(self, w: pango::Weight) -> Markup<'a> {
        let Markup { view, mut frags } = self;
        frags.push(Frag::Weight(w));
        Markup { view, frags }
    }

    /// Set the font size in points for the view
    pub fn size(self, s: u8) -> Markup<'a> {
        let Markup { view, mut frags } = self;
        frags.push(Frag::Size(s));
        Markup { view, frags }
    }

    /// Add a string with the extra markup supplied
    pub fn push(&mut self, s: &str) {
        self.view.push("<span ");
        for f in self.frags.iter() {
            match f {
                &Frag::Size(s) =>
                    self.view.push(&format!("font='{}' ", s)),
                &Frag::Weight(s) =>
                    self.view.push(&format!(
                        "font_weight='{}' ",
                        weight_to_string(s),
                    )),
            }
        }
        self.view.push(">");
        self.view.push(s);
        self.view.push("</span>");
    }
}

fn weight_to_string(w: Weight) -> &'static str {
    match w {
        Weight::Thin => "thin",
        Weight::Ultralight => "ultralight",
        Weight::Light => "light",
        Weight::Semilight => "semilight",
        Weight::Book => "book",
        Weight::Normal => "normal",
        Weight::Medium => "medium",
        Weight::Semibold => "semibold",
        Weight::Bold => "bold",
        Weight::Ultrabold => "ultrabold",
        Weight::Heavy => "heavy",
        Weight::Ultraheavy => "ultraheavy",
        _ => panic!("Unknown weight: {:?}", w),
    }
}