From 6c74c51ceff3ec1af0b3973e373aba6e315beffa Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Wed, 28 Jun 2017 20:36:01 -0700 Subject: [PATCH] Extend and improve FcPattern bindings The fontconfig `FcPattern` type is wrapped as `fc::Pattern` and `fc::Pattern` ref. All methods for accessing data on the pattern now return an `Iterator`. This API turns out to be much more ergonomic than providing an integer index. We also override the default `nth` implementation of `Iterator` on these accessors to allow random (incremental only) access. For instance, accessing `family` attributes from a pattern: let families = pattern.family(); let second = pattern.nth(1); Or printing available styles for style in pattern.style() { println!("style={}", style); } --- font/src/ft/fc/mod.rs | 195 +++++++++++++--- font/src/ft/fc/pattern.rs | 451 +++++++++++++++++++++++++++++++++----- font/src/ft/mod.rs | 8 +- font/src/lib.rs | 2 +- 4 files changed, 561 insertions(+), 95 deletions(-) diff --git a/font/src/ft/fc/mod.rs b/font/src/ft/fc/mod.rs index d0a55fa..c4cb40e 100644 --- a/font/src/ft/fc/mod.rs +++ b/font/src/ft/fc/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. // use std::ptr; +use std::fmt; use foreign_types::{ForeignType, ForeignTypeRef}; @@ -27,19 +28,19 @@ use self::ffi::{FC_WEIGHT_THIN, FC_WEIGHT_EXTRALIGHT, FC_WEIGHT_LIGHT}; use self::ffi::{FC_WEIGHT_BOOK, FC_WEIGHT_REGULAR, FC_WEIGHT_MEDIUM, FC_WEIGHT_SEMIBOLD}; use self::ffi::{FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABOLD, FC_WEIGHT_BLACK, FC_WEIGHT_EXTRABLACK}; -mod config; +pub mod config; pub use self::config::{Config, ConfigRef}; -mod font_set; +pub mod font_set; pub use self::font_set::{FontSet, FontSetRef}; -mod object_set; +pub mod object_set; pub use self::object_set::{ObjectSet, ObjectSetRef}; -mod char_set; +pub mod char_set; pub use self::char_set::{CharSet, CharSetRef}; -mod pattern; +pub mod pattern; pub use self::pattern::{Pattern, PatternRef}; /// Find the font closest matching the provided pattern. @@ -162,6 +163,143 @@ pub enum Weight { Extrablack = FC_WEIGHT_EXTRABLACK as isize, } +#[derive(Debug, Copy, Clone)] +pub enum Width { + Ultracondensed, + Extracondensed, + Condensed, + Semicondensed, + Normal, + Semiexpanded, + Expanded, + Extraexpanded, + Ultraexpanded, + Other(i32) +} + +impl Width { + fn to_isize(&self) -> isize { + use self::Width::*; + match *self { + Ultracondensed => 50, + Extracondensed => 63, + Condensed => 75, + Semicondensed => 87, + Normal => 100, + Semiexpanded => 113, + Expanded => 125, + Extraexpanded => 150, + Ultraexpanded => 200, + Other(value) => value as isize + } + } +} + +impl From for Width { + fn from(value: isize) -> Self { + match value { + 50 => Width::Ultracondensed, + 63 => Width::Extracondensed, + 75 => Width::Condensed, + 87 => Width::Semicondensed, + 100 => Width::Normal, + 113 => Width::Semiexpanded, + 125 => Width::Expanded, + 150 => Width::Extraexpanded, + 200 => Width::Ultraexpanded, + _ => Width::Other(value as _) + } + } +} + +/// Subpixel geometry +pub enum Rgba { + Unknown, + Rgb, + Bgr, + Vrgb, + Vbgr, + None +} + +impl Rgba { + fn to_isize(&self) -> isize { + match *self { + Rgba::Unknown => 0, + Rgba::Rgb => 1, + Rgba::Bgr => 2, + Rgba::Vrgb => 3, + Rgba::Vbgr => 4, + Rgba::None => 5 + } + } +} + +impl fmt::Display for Rgba { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Rgba::*; + f.write_str(match *self { + Unknown => "unknown", + Rgb => "rgb", + Bgr => "bgr", + Vrgb => "vrgb", + Vbgr => "vbgr", + None => "none", + }) + } +} + +impl From for Rgba { + fn from(val: isize) -> Rgba { + match val { + 1 => Rgba::Rgb, + 2 => Rgba::Bgr, + 3 => Rgba::Vrgb, + 4 => Rgba::Vbgr, + 5 => Rgba::None, + _ => Rgba::Unknown, + } + } +} + +/// Hinting Style +#[derive(Debug, Copy, Clone)] +pub enum HintStyle { + None, + Slight, + Medium, + Full +} + +impl fmt::Display for HintStyle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + HintStyle::None => "none", + HintStyle::Slight => "slight", + HintStyle::Medium => "medium", + HintStyle::Full => "full", + }) + } +} + +/// Lcd filter, used to reduce color fringing with subpixel rendering +pub enum LcdFilter { + None, + Default, + Light, + Legacy +} + +impl fmt::Display for LcdFilter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + LcdFilter::None => "none", + LcdFilter::Default => "default", + LcdFilter::Light => "light", + LcdFilter::Legacy => "legacy", + }) + } +} #[cfg(test)] mod tests { @@ -176,15 +314,17 @@ mod tests { let config = Config::get_current(); let font = super::font_match(config, &mut pattern).expect("match font monospace"); - print!("family={:?}", font.family(0)); - for i in 0.. { - if let Some(style) = font.style(i) { - print!(", style={:?}, ", style); - } else { - break; - } - } - info!(""); + print!("index={:?}; ", font.index()); + print!("family={:?}; ", font.family()); + print!("style={:?}; ", font.style()); + print!("antialias={:?}; ", font.antialias()); + print!("autohint={:?}; ", font.autohint()); + print!("hinting={:?}; ", font.hinting()); + print!("rgba={:?}; ", font.rgba()); + print!("embeddedbitmap={:?}; ", font.embeddedbitmap()); + print!("lcdfilter={:?}; ", font.lcdfilter()); + print!("hintstyle={:?}", font.hintstyle()); + println!(""); } #[test] @@ -198,14 +338,12 @@ mod tests { .expect("sort font monospace"); for font in fonts.into_iter().take(10) { - print!("family={:?}", font.family(0)); - for i in 0.. { - if let Some(style) = font.style(i) { - print!(", style={:?}", style); - } else { - break; - } - } + let font = font.render_prepare(&config, &pattern); + print!("index={:?}; ", font.index()); + print!("family={:?}; ", font.family()); + print!("style={:?}; ", font.style()); + print!("rgba={:?}", font.rgba()); + print!("rgba={:?}", font.rgba()); println!(""); } } @@ -222,14 +360,11 @@ mod tests { let fonts = super::font_sort(config, &mut pattern).expect("font_sort"); for font in fonts.into_iter().take(10) { - print!("family={:?}", font.family(0)); - for i in 0.. { - if let Some(style) = font.style(i) { - print!(", style={:?}", style); - } else { - break; - } - } + let font = font.render_prepare(&config, &pattern); + print!("index={:?}; ", font.index()); + print!("family={:?}; ", font.family()); + print!("style={:?}; ", font.style()); + print!("rgba={:?}", font.rgba()); println!(""); } } diff --git a/font/src/ft/fc/pattern.rs b/font/src/ft/fc/pattern.rs index a7cd9ec..040fae8 100644 --- a/font/src/ft/fc/pattern.rs +++ b/font/src/ft/fc/pattern.rs @@ -12,20 +12,308 @@ // See the License for the specific language governing permissions and // limitations under the License. use std::ptr; +use std::fmt; use std::ffi::{CStr, CString}; use std::path::PathBuf; use std::str; +use std::mem; use libc::{c_char, c_int}; -use foreign_types::{ForeignTypeRef}; +use foreign_types::{ForeignType, ForeignTypeRef}; use super::ffi::FcResultMatch; use super::ffi::{FcPatternDestroy, FcPatternAddCharSet}; use super::ffi::{FcPatternGetString, FcPatternCreate, FcPatternAddString}; use super::ffi::{FcPatternGetInteger, FcPatternAddInteger}; use super::ffi::{FcChar8, FcPattern, FcDefaultSubstitute, FcConfigSubstitute}; +use super::ffi::{FcFontRenderPrepare, FcPatternGetBool, FcBool}; -use super::{MatchKind, ConfigRef, CharSetRef, Weight, Slant}; +use super::{MatchKind, ConfigRef, CharSetRef, Weight, Slant, Width, Rgba, HintStyle, LcdFilter}; + +pub struct StringPropertyIter<'a> { + pattern: &'a PatternRef, + object: &'a [u8], + index: usize +} + +impl<'a> StringPropertyIter<'a> { + fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> StringPropertyIter<'b> { + StringPropertyIter { + pattern: pattern, + object: object, + index: 0 + } + } + + fn get_value(&self, index: usize) -> Option<&'a str> { + let mut value: *mut FcChar8 = ptr::null_mut(); + + let result = unsafe { + FcPatternGetString( + self.pattern.as_ptr(), + self.object.as_ptr() as *mut c_char, + index as c_int, + &mut value + ) + }; + + if result == FcResultMatch { + // Transmute here is to extend lifetime of the str to that of the iterator + // + // Potential unsafety? What happens if the pattern is modified while this ptr is + // borrowed out? + Some(unsafe { + mem::transmute(CStr::from_ptr(value as *const c_char).to_str().unwrap()) + }) + } else { + None + } + } +} + +/// Iterator over interger properties +pub struct BooleanPropertyIter<'a> { + pattern: &'a PatternRef, + object: &'a [u8], + index: usize +} + + +impl<'a> BooleanPropertyIter<'a> { + fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> BooleanPropertyIter<'b> { + BooleanPropertyIter { + pattern: pattern, + object: object, + index: 0 + } + } + + fn get_value(&self, index: usize) -> Option { + let mut value: FcBool = 0; + + let result = unsafe { + FcPatternGetBool( + self.pattern.as_ptr(), + self.object.as_ptr() as *mut c_char, + index as c_int, + &mut value + ) + }; + + if result == FcResultMatch { + Some(!(value == 0)) + } else { + None + } + } +} + +/// Iterator over interger properties +pub struct IntPropertyIter<'a> { + pattern: &'a PatternRef, + object: &'a [u8], + index: usize +} + + +impl<'a> IntPropertyIter<'a> { + fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> IntPropertyIter<'b> { + IntPropertyIter { + pattern: pattern, + object: object, + index: 0 + } + } + + fn get_value(&self, index: usize) -> Option { + let mut value = 0 as c_int; + + let result = unsafe { + FcPatternGetInteger( + self.pattern.as_ptr(), + self.object.as_ptr() as *mut c_char, + index as c_int, + &mut value + ) + }; + + if result == FcResultMatch { + Some(value as isize) + } else { + None + } + } +} + +pub struct RgbaPropertyIter<'a> { + inner: IntPropertyIter<'a>, +} + +impl<'a> RgbaPropertyIter<'a> { + fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> RgbaPropertyIter<'b> { + RgbaPropertyIter { + inner: IntPropertyIter::new(pattern, object) + } + } + + #[inline] + fn inner<'b>(&'b mut self) -> &'b mut IntPropertyIter<'a> { + &mut self.inner + } + + fn get_value(&self, index: usize) -> Option { + self.inner.get_value(index) + .map(Rgba::from) + } +} + +pub struct HintStylePropertyIter<'a> { + inner: IntPropertyIter<'a>, +} + +impl<'a> HintStylePropertyIter<'a> { + fn new<'b>(pattern: &'b PatternRef) -> HintStylePropertyIter<'b> { + HintStylePropertyIter { + inner: IntPropertyIter::new(pattern, b"hintstyle\0") + } + } + + #[inline] + fn inner<'b>(&'b mut self) -> &'b mut IntPropertyIter<'a> { + &mut self.inner + } + + fn get_value(&self, index: usize) -> Option { + self.inner.get_value(index) + .and_then(|hint_style| { + Some(match hint_style { + 0 => HintStyle::None, + 1 => HintStyle::Slight, + 2 => HintStyle::Medium, + 3 => HintStyle::Full, + _ => return None + }) + }) + } +} + +pub struct LcdFilterPropertyIter<'a> { + inner: IntPropertyIter<'a>, +} + +impl<'a> LcdFilterPropertyIter<'a> { + fn new<'b>(pattern: &'b PatternRef) -> LcdFilterPropertyIter<'b> { + LcdFilterPropertyIter { + inner: IntPropertyIter::new(pattern, b"lcdfilter\0") + } + } + + #[inline] + fn inner<'b>(&'b mut self) -> &'b mut IntPropertyIter<'a> { + &mut self.inner + } + + fn get_value(&self, index: usize) -> Option { + self.inner.get_value(index) + .and_then(|hint_style| { + Some(match hint_style { + 0 => LcdFilter::None, + 1 => LcdFilter::Default, + 2 => LcdFilter::Light, + 3 => LcdFilter::Legacy, + _ => return None + }) + }) + } +} + +/// Implement debug for a property iterator +macro_rules! impl_property_iter_debug { + ($iter:ty => $item:ty) => { + impl<'a> fmt::Debug for $iter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[")?; + for i in 0.. { + match self.get_value(i) { + Some(val) => { + if i > 0 { + write!(f, ", {}", val)?; + } else { + write!(f, "{}", val)?; + } + }, + _ => break + } + } + write!(f, "]") + } + } + } +} + +/// Implement Iterator and Debug for a property iterator +macro_rules! impl_property_iter { + ($($iter:ty => $item:ty),*) => { + $( + impl<'a> Iterator for $iter { + type Item = $item; + + fn next(&mut self) -> Option { + let res = self.get_value(self.index); + self.index += 1; + res + } + + #[inline] + fn nth(&mut self, n: usize) -> Option { + self.index += n; + self.next() + } + } + impl_property_iter_debug!($iter => $item); + )* + } +} + +/// Implement Iterator and Debug for a property iterator which internally relies +/// on another property iterator. +macro_rules! impl_derived_property_iter { + ($($iter:ty => $item:ty),*) => { + $( + impl<'a> Iterator for $iter { + type Item = $item; + + fn next(&mut self) -> Option { + let index = { self.inner().index }; + let res = self.get_value(index); + self.inner().index += 1; + res + } + + #[inline] + fn nth(&mut self, n: usize) -> Option { + self.inner().index += n; + self.next() + } + } + impl_property_iter_debug!($iter => $item); + )* + } +} + +// Basic Iterators +impl_property_iter! { + StringPropertyIter<'a> => &'a str, + IntPropertyIter<'a> => isize, + BooleanPropertyIter<'a> => bool +} + +// Derived Iterators +impl_derived_property_iter! { + RgbaPropertyIter<'a> => Rgba, + HintStylePropertyIter<'a> => HintStyle, + LcdFilterPropertyIter<'a> => LcdFilter +} foreign_type! { type CType = FcPattern; @@ -34,13 +322,20 @@ foreign_type! { pub struct PatternRef; } -macro_rules! pattern_add_string { - ($($name:ident => $object:expr),*) => { +macro_rules! pattern_string_accessors { + ($([$getter:ident, $setter:ident] => $object_name:expr),*) => { $( #[inline] - pub fn $name(&mut self, value: &str) -> bool { + pub fn $setter(&mut self, value: &str) -> bool { unsafe { - self.add_string($object, value) + self.add_string($object_name, value) + } + } + + #[inline] + pub fn $getter(&self) -> StringPropertyIter { + unsafe { + self.get_string($object_name) } } )* @@ -66,18 +361,6 @@ impl Pattern { } } -macro_rules! pattern_get_string { - ($($method:ident() => $property:expr),+) => { - $( - pub fn $method(&self, id: isize) -> Option { - unsafe { - self.get_string($property, id) - } - } - )+ - }; -} - macro_rules! pattern_add_integer { ($($method:ident() => $property:expr),+) => { $( @@ -98,29 +381,25 @@ macro_rules! pattern_add_integer { macro_rules! pattern_get_integer { ($($method:ident() => $property:expr),+) => { $( - pub fn $method(&self, id: isize) -> Option { - let mut index = 0 as c_int; + pub fn $method(&self) -> IntPropertyIter { unsafe { - let result = FcPatternGetInteger( - self.as_ptr(), - $property.as_ptr() as *mut c_char, - id as c_int, - &mut index - ); - - if result == FcResultMatch { - Some(index as isize) - } else { - None - } + self.get_integer($property) } } )+ }; } -unsafe fn char8_to_string(fc_str: *mut FcChar8) -> String { - str::from_utf8(CStr::from_ptr(fc_str as *const c_char).to_bytes()).unwrap().to_owned() +macro_rules! boolean_getter { + ($($method:ident() => $property:expr),*) => { + $( + pub fn $method(&self) -> BooleanPropertyIter { + unsafe { + self.get_boolean($property) + } + } + )* + } } impl PatternRef { @@ -151,26 +430,54 @@ impl PatternRef { ) == 1 } - unsafe fn get_string(&self, object: &[u8], index: isize) -> Option { - let mut format: *mut FcChar8 = ptr::null_mut(); - - let result = FcPatternGetString( - self.as_ptr(), - object.as_ptr() as *mut c_char, - index as c_int, - &mut format - ); - - if result == FcResultMatch { - Some(char8_to_string(format)) - } else { - None - } + unsafe fn get_string<'a>(&'a self, object: &'a [u8]) -> StringPropertyIter<'a> { + StringPropertyIter::new(self, object) } - pattern_add_string! { - add_family => b"family\0", - add_style => b"style\0" + unsafe fn get_integer<'a>(&'a self, object: &'a [u8]) -> IntPropertyIter<'a> { + IntPropertyIter::new(self, object) + } + + unsafe fn get_boolean<'a>(&'a self, object: &'a [u8]) -> BooleanPropertyIter<'a> { + BooleanPropertyIter::new(self, object) + } + + pub fn hintstyle<'a>(&'a self) -> HintStylePropertyIter<'a> { + HintStylePropertyIter::new(self) + } + + pub fn lcdfilter<'a>(&'a self) -> LcdFilterPropertyIter<'a> { + LcdFilterPropertyIter::new(self) + } + + boolean_getter! { + antialias() => b"antialias\0", + hinting() => b"hinting\0", + verticallayout() => b"verticallayout\0", + autohint() => b"autohint\0", + globaladvance() => b"globaladvance\0", + scalable() => b"scalable\0", + symbol() => b"symbol\0", + color() => b"color\0", + minspace() => b"minspace\0", + embolden() => b"embolden\0", + embeddedbitmap() => b"embeddedbitmap\0", + decorative() => b"decorative\0" + } + + pattern_string_accessors! { + [family, add_family] => b"family\0", + [familylang, add_familylang] => b"familylang\0", + [style, add_style] => b"style\0", + [stylelang, add_stylelang] => b"stylelang\0", + [fullname, add_fullname] => b"fullname\0", + [fullnamelang, add_fullnamelang] => b"fullnamelang\0", + [foundry, add_foundry] => b"foundry\0", + [capability, add_capability] => b"capability\0", + [fontformat, add_fontformat] => b"fontformat\0", + [fontfeatures, add_fontfeatures] => b"fontfeatures\0", + [namelang, add_namelang] => b"namelang\0", + [postscriptname, add_postscriptname] => b"postscriptname\0" } pub fn set_slant(&mut self, slant: Slant) -> bool { @@ -185,6 +492,36 @@ impl PatternRef { } } + pub fn set_width(&mut self, width: Width) -> bool { + unsafe { + self.add_integer(b"width\0", width.to_isize()) + } + } + + pub fn get_width(&self) -> Option { + unsafe { + self.get_integer(b"width\0") + .nth(0) + .map(Width::from) + } + } + + pub fn rgba(&self) -> RgbaPropertyIter { + RgbaPropertyIter::new(self, b"rgba\0") + } + + pub fn set_rgba(&self, rgba: Rgba) -> bool { + unsafe { + self.add_integer(b"rgba\0", rgba.to_isize()) + } + } + + pub fn render_prepare(&self, config: &ConfigRef, request: &PatternRef) -> Pattern { + unsafe { + let ptr = FcFontRenderPrepare(config.as_ptr(), request.as_ptr(), self.as_ptr()); + Pattern::from_ptr(ptr) + } + } /// Add charset to the pattern /// @@ -202,18 +539,12 @@ impl PatternRef { } } - pub fn file(&self, index: isize) -> Option { + pub fn file(&self, index: usize) -> Option { unsafe { - self.get_string(b"file\0", index) + self.get_string(b"file\0").nth(index) }.map(From::from) } - pattern_get_string! { - fontformat() => b"fontformat\0", - family() => b"family\0", - style() => b"style\0" - } - pattern_get_integer! { index() => b"index\0" } diff --git a/font/src/ft/mod.rs b/font/src/ft/mod.rs index 89ca604..7a75d11 100644 --- a/font/src/ft/mod.rs +++ b/font/src/ft/mod.rs @@ -19,7 +19,7 @@ use std::cmp::min; use freetype::{self, Library, Face}; -mod fc; +pub mod fc; use super::{FontDesc, RasterizedGlyph, Metrics, Size, FontKey, GlyphKey, Weight, Slant, Style}; @@ -142,7 +142,7 @@ impl FreeTypeRasterizer { let font = fc::font_match(fc::Config::get_current(), &mut pattern) .ok_or_else(|| Error::MissingFont(desc.to_owned()))?; - if let (Some(path), Some(index)) = (font.file(0), font.index(0)) { + if let (Some(path), Some(index)) = (font.file(0), font.index().nth(0)) { return Ok(self.library.new_face(path, index)?); } @@ -160,7 +160,7 @@ impl FreeTypeRasterizer { let font = fc::font_match(fc::Config::get_current(), &mut pattern) .ok_or_else(|| Error::MissingFont(desc.to_owned()))?; - if let (Some(path), Some(index)) = (font.file(0), font.index(0)) { + if let (Some(path), Some(index)) = (font.file(0), font.index().nth(0)) { println!("got font path={:?}", path); Ok(self.library.new_face(path, index)?) } @@ -293,7 +293,7 @@ impl FreeTypeRasterizer { let config = fc::Config::get_current(); match fc::font_match(config, &mut pattern) { Some(font) => { - if let (Some(path), Some(index)) = (font.file(0), font.index(0)) { + if let (Some(path), Some(index)) = (font.file(0), font.index().nth(0)) { match self.keys.get(&path) { // We've previously loaded this font, so don't // load it again. diff --git a/font/src/lib.rs b/font/src/lib.rs index 401a29c..f02df43 100644 --- a/font/src/lib.rs +++ b/font/src/lib.rs @@ -48,7 +48,7 @@ use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; // If target isn't macos, reexport everything from ft #[cfg(not(target_os = "macos"))] -mod ft; +pub mod ft; #[cfg(not(target_os = "macos"))] pub use ft::{FreeTypeRasterizer as Rasterizer, Error};