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),
}
}