diff --git a/src/font/fcwrap.rs b/src/font/fcwrap.rs index 70194c85c..2150f3ece 100644 --- a/src/font/fcwrap.rs +++ b/src/font/fcwrap.rs @@ -5,6 +5,7 @@ pub use fontconfig::*; use std::ffi::{CStr, CString}; use std::fmt; use std::mem; +use std::os::raw::c_int; use std::ptr; static FC_MONO: i32 = 100; @@ -77,13 +78,12 @@ impl FcResultWrap { pub fn as_err(&self) -> Error { // the compiler thinks we defined these globals, when all // we did was import them from elsewhere - #[allow(non_upper_case_globals)] match self.0 { - FcResultMatch => err_msg("FcResultMatch"), - FcResultNoMatch => err_msg("FcResultNoMatch"), - FcResultTypeMismatch => err_msg("FcResultTypeMismatch"), - FcResultNoId => err_msg("FcResultNoId"), - FcResultOutOfMemory => err_msg("FcResultOutOfMemory"), + fontconfig::FcResultMatch => err_msg("FcResultMatch"), + fontconfig::FcResultNoMatch => err_msg("FcResultNoMatch"), + fontconfig::FcResultTypeMismatch => err_msg("FcResultTypeMismatch"), + fontconfig::FcResultNoId => err_msg("FcResultNoId"), + fontconfig::FcResultOutOfMemory => err_msg("FcResultOutOfMemory"), _ => format_err!("FcResult holds invalid value {}", self.0), } } @@ -235,6 +235,24 @@ impl Pattern { } } + pub fn get_integer(&self, key: &str) -> Result { + unsafe { + let key = CString::new(key)?; + let mut ival: c_int = 0; + let res = FcResultWrap(FcPatternGetInteger( + self.pat, + key.as_ptr(), + 0, + &mut ival as *mut _, + )); + if !res.succeeded() { + Err(res.as_err()) + } else { + Ok(ival) + } + } + } + pub fn get_string(&self, key: &str) -> Result { unsafe { let key = CString::new(key)?; @@ -266,9 +284,10 @@ impl Drop for Pattern { impl fmt::Debug for Pattern { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + // unsafe{FcPatternPrint(self.pat);} fmt.write_str( &self - .format("Pattern(%{+family,style,weight,slant,spacing{%{=unparse}}})") + .format("Pattern(%{+family,style,weight,slant,spacing,file,index,fontformat{%{=unparse}}})") .unwrap(), ) } diff --git a/src/font/locator/font_config.rs b/src/font/locator/font_config.rs index ae0bd7c79..b51a52b2c 100644 --- a/src/font/locator/font_config.rs +++ b/src/font/locator/font_config.rs @@ -3,6 +3,7 @@ use crate::font::fcwrap; use crate::font::locator::{FontDataHandle, FontLocator}; use failure::Fallible; use fcwrap::Pattern as FontPattern; +use std::convert::TryInto; /// A FontLocator implemented using the system font loading /// functions provided by font-config @@ -16,12 +17,8 @@ impl FontLocator for FontConfigFontLocator { for attr in fonts_selection { let mut pattern = FontPattern::new()?; pattern.family(&attr.family)?; - if attr.bold { - pattern.add_integer("weight", 200)?; - } - if attr.italic { - pattern.add_integer("slant", 100)?; - } + pattern.add_integer("weight", if attr.bold { 200 } else { 80 })?; + pattern.add_integer("slant", if attr.italic { 100 } else { 0 })?; /* pattern.add_double("size", config.font_size * font_scale)?; pattern.add_double("dpi", config.dpi)?; @@ -39,7 +36,7 @@ impl FontLocator for FontConfigFontLocator { let handle = FontDataHandle::OnDisk { path: file.into(), - index: 0, // FIXME: extract this from pat! + index: pat.get_integer("index")?.try_into()?, }; // When it comes to handling fallback, we prefer our diff --git a/src/font/parser.rs b/src/font/parser.rs index 66dcbfb7b..6484464fa 100644 --- a/src/font/parser.rs +++ b/src/font/parser.rs @@ -9,26 +9,27 @@ use crate::font::shaper::{FallbackIdx, FontMetrics, GlyphInfo}; use crate::font::units::*; use allsorts::binary::read::{ReadScope, ReadScopeOwned}; use allsorts::font_data_impl::read_cmap_subtable; -use allsorts::fontfile::FontFile; use allsorts::gpos::{gpos_apply, Info, Placement}; use allsorts::gsub::{gsub_apply_default, GlyphOrigin, RawGlyph}; use allsorts::layout::{new_layout_cache, GDEFTable, LayoutCache, LayoutTable, GPOS, GSUB}; use allsorts::post::PostTable; use allsorts::tables::cmap::{Cmap, CmapSubtable}; use allsorts::tables::{ - FontTableProvider, HeadTable, HheaTable, HmtxTable, MaxpTable, NameTable, OffsetTable, - OpenTypeFile, OpenTypeFont, TTCHeader, + HeadTable, HheaTable, HmtxTable, MaxpTable, OffsetTable, OpenTypeFile, OpenTypeFont, }; use allsorts::tag; -use failure::{bail, format_err, Fallible}; +use failure::{format_err, Fallible}; use std::convert::TryInto; use std::path::{Path, PathBuf}; use termwiz::cell::unicode_column_width; #[derive(Debug)] -enum MaybeShaped { +pub enum MaybeShaped { Resolved(GlyphInfo), - Unresolved(RawGlyph<()>), + Unresolved { + raw: RawGlyph<()>, + slice_start: usize, + }, } /// Represents a parsed font @@ -38,7 +39,7 @@ pub struct ParsedFont { cmap_subtable: CmapSubtable<'static>, gpos_cache: Option>, - gsub_cache: LayoutCache, + gsub_cache: Option>, gdef_table: Option, hmtx: HmtxTable<'static>, post: PostTable<'static>, @@ -174,11 +175,6 @@ impl ParsedFont { usize::from(hhea.num_h_metrics), ))?; - let gsub_table = otf - .find_table_record(tag::GSUB) - .ok_or_else(|| format_err!("GSUB table record not found"))? - .read_table(&file.scope)? - .read::>()?; let gdef_table: Option = otf .find_table_record(tag::GDEF) .map(|gdef_record| -> Fallible { @@ -193,9 +189,16 @@ impl ParsedFont { .read::>()?) }) .transpose()?; - let gsub_cache = new_layout_cache(gsub_table); let gpos_cache = opt_gpos_table.map(new_layout_cache); + let gsub_cache = otf + .find_table_record(tag::GSUB) + .map(|gsub| -> Fallible> { + Ok(gsub.read_table(&file.scope)?.read::>()?) + }) + .transpose()? + .map(new_layout_cache); + Ok(Self { otf, names, @@ -230,25 +233,54 @@ impl ParsedFont { let underline_position = PixelLength::new(self.post.header.underline_position as f64 * pixel_scale); let descender = PixelLength::new(self.hhea.descender as f64 * pixel_scale); - let cell_height = PixelLength::new(self.hhea.line_gap as f64 * pixel_scale); + let cell_height = PixelLength::new( + (self.hhea.ascender - self.hhea.descender + self.hhea.line_gap) as f64 * pixel_scale, + ); + log::error!( + "hhea: ascender={} descender={} line_gap={} \ + advance_width_max={} min_lsb={} min_rsb={} \ + x_max_extent={}", + self.hhea.ascender, + self.hhea.descender, + self.hhea.line_gap, + self.hhea.advance_width_max, + self.hhea.min_left_side_bearing, + self.hhea.min_right_side_bearing, + self.hhea.x_max_extent + ); - // FIXME: for freetype/harfbuzz, we look at a number of glyphs and compute this for - // ourselves - let cell_width = PixelLength::new(self.hhea.advance_width_max as f64 * pixel_scale); + let mut cell_width = 0; + // Compute the max width based on ascii chars + for i in 0x20..0x7fu8 { + if let Ok(Some(glyph_index)) = self.glyph_index_for_char(i as char) { + if let Ok(h) = self + .hmtx + .horizontal_advance(glyph_index, self.hhea.num_h_metrics) + { + cell_width = cell_width.max(h); + } + } + } + let cell_width = PixelLength::new(cell_width as f64) * pixel_scale; - FontMetrics { + let metrics = FontMetrics { cell_width, cell_height, descender, underline_thickness, underline_position, - } + }; + + log::error!("metrics: {:?}", metrics); + + metrics } pub fn shape_text>( &self, text: T, - font_index: usize, + slice_index: usize, + font_index: FallbackIdx, script: u32, lang: u32, point_size: f64, @@ -272,16 +304,18 @@ impl ParsedFont { let vertical = false; - gsub_apply_default( - &|| vec![], //map_char('\u{25cc}')], - &self.gsub_cache, - self.gdef_table.as_ref(), - script, - lang, - vertical, - self.num_glyphs, - &mut glyphs, - )?; + if let Some(gsub_cache) = self.gsub_cache.as_ref() { + gsub_apply_default( + &|| vec![], //map_char('\u{25cc}')], + gsub_cache, + self.gdef_table.as_ref(), + script, + lang, + vertical, + self.num_glyphs, + &mut glyphs, + )?; + } // Note: init_from_glyphs silently elides entries that // have no glyph in the current font! we need to deal @@ -304,8 +338,13 @@ impl ParsedFont { let mut pos = Vec::new(); let mut glyph_iter = glyphs.into_iter(); + let mut cluster = slice_index; - for (cluster, glyph_info) in infos.into_iter().enumerate() { + fn reverse_engineer_glyph_text(glyph: &RawGlyph<()>) -> String { + glyph.unicodes.iter().collect() + } + + for glyph_info in infos.into_iter() { let mut input_glyph = glyph_iter .next() .ok_or_else(|| format_err!("more output infos than input glyphs!"))?; @@ -313,7 +352,13 @@ impl ParsedFont { while input_glyph.unicodes != glyph_info.glyph.unicodes { // Info::init_from_glyphs skipped the input glyph, so let's be // sure to emit a placeholder for it - pos.push(MaybeShaped::Unresolved(input_glyph)); + let text = reverse_engineer_glyph_text(&input_glyph); + pos.push(MaybeShaped::Unresolved { + raw: input_glyph, + slice_start: cluster, + }); + + cluster += text.len(); input_glyph = glyph_iter.next().ok_or_else(|| { format_err!("more output infos than input glyphs! (loop bottom)") @@ -348,10 +393,22 @@ impl ParsedFont { let x_advance = PixelLength::new(x_advance as f64 * pixel_scale); let y_advance = PixelLength::new(y_advance as f64 * pixel_scale); - let text: String = glyph_info.glyph.unicodes.iter().collect(); - let num_cells = unicode_column_width(&text); + let text = reverse_engineer_glyph_text(&glyph_info.glyph); + let text_len = text.len(); - pos.push(MaybeShaped::Resolved(GlyphInfo { + // let num_cells = glyph_info.glyph.unicodes.len(); + let num_cells = unicode_column_width(&text); + if num_cells != 1 { + log::error!( + "x_advance={} num_cells={} font_idx={} kern={}", + x_advance, + num_cells, + font_index, + glyph_info.kerning + ); + } + + let info = GlyphInfo { #[cfg(debug_assertions)] text, cluster: cluster as u32, @@ -362,7 +419,15 @@ impl ParsedFont { y_advance, x_offset: PixelLength::new(0.), y_offset: PixelLength::new(0.), - })); + }; + + cluster += text_len; + + if num_cells != 1 { + log::error!("{:?}", info); + } + + pos.push(MaybeShaped::Resolved(info)); } Ok(pos) diff --git a/src/font/shaper/allsorts.rs b/src/font/shaper/allsorts.rs new file mode 100644 index 000000000..e3a41d716 --- /dev/null +++ b/src/font/shaper/allsorts.rs @@ -0,0 +1,162 @@ +use crate::font::locator::FontDataHandle; +use crate::font::parser::*; +use crate::font::shaper::{FallbackIdx, FontMetrics, FontShaper, GlyphInfo}; +use failure::{bail, format_err, Fallible}; + +pub struct AllsortsShaper { + fonts: Vec>, +} + +impl AllsortsShaper { + pub fn new(handles: &[FontDataHandle]) -> Fallible { + let mut fonts = vec![]; + let mut success = false; + for handle in handles { + match ParsedFont::from_locator(handle) { + Ok(font) => { + fonts.push(Some(font)); + success = true; + } + Err(err) => { + log::warn!("Failed to parse {:?}: {}", handle, err); + fonts.push(None); + } + } + } + if !success { + bail!("failed to load any fonts in this fallback set!?"); + } + Ok(Self { fonts }) + } + + fn shape_into( + &self, + font_index: FallbackIdx, + s: &str, + slice_index: usize, + script: u32, + lang: u32, + font_size: f64, + dpi: u32, + results: &mut Vec, + ) -> Fallible<()> { + let font = match self.fonts.get(font_index) { + Some(Some(font)) => font, + Some(None) => { + return self.shape_into( + font_index + 1, + s, + slice_index, + script, + lang, + font_size, + dpi, + results, + ); + } + None => { + // We ran out of fallback fonts, so use a replacement + // character that is likely to be in one of those fonts + let mut alt_text = String::new(); + let num_chars = s.chars().count(); + for _ in 0..num_chars { + alt_text.push('?'); + } + if alt_text == s { + // We already tried to fallback to this and failed + return Err(format_err!("could not fallback to ? character")); + } + return self.shape_into( + 0, + &alt_text, + slice_index, + script, + lang, + font_size, + dpi, + results, + ); + } + }; + let first_pass = + font.shape_text(s, slice_index, font_index, script, lang, font_size, dpi)?; + + let mut item_iter = first_pass.into_iter(); + let mut fallback_run = String::new(); + let mut fallback_start_pos = 0; + while let Some(item) = item_iter.next() { + match item { + MaybeShaped::Resolved(info) => { + results.push(info); + } + MaybeShaped::Unresolved { raw, slice_start } => { + // There was no glyph in that font, so we'll need to shape + // using a fallback. Let's collect together any potential + // run of unresolved entries first + fallback_start_pos = slice_start + slice_index; + for &c in &raw.unicodes { + fallback_run.push(c); + } + + while let Some(item) = item_iter.next() { + match item { + MaybeShaped::Unresolved { raw, .. } => { + for &c in &raw.unicodes { + fallback_run.push(c); + } + } + MaybeShaped::Resolved(info) => { + self.shape_into( + font_index + 1, + &fallback_run, + fallback_start_pos, + script, + lang, + font_size, + dpi, + results, + )?; + fallback_run.clear(); + results.push(info); + } + } + } + } + } + } + + if !fallback_run.is_empty() { + self.shape_into( + font_index + 1, + &fallback_run, + fallback_start_pos, + script, + lang, + font_size, + dpi, + results, + )?; + } + + Ok(()) + } +} + +impl FontShaper for AllsortsShaper { + fn shape(&self, text: &str, size: f64, dpi: u32) -> Fallible> { + let mut results = vec![]; + let script = allsorts::tag::LATN; + let lang = allsorts::tag::DFLT; + self.shape_into(0, text, 0, script, lang, size, dpi, &mut results)?; + Ok(results) + } + + fn metrics(&self, size: f64, dpi: u32) -> Fallible { + for font in &self.fonts { + if let Some(font) = font { + return Ok(font.get_metrics(size, dpi)); + } + } + bail!("no fonts available for collecting metrics!?"); + } +} diff --git a/src/font/shaper/mod.rs b/src/font/shaper/mod.rs index 28607f136..96e6b0b23 100644 --- a/src/font/shaper/mod.rs +++ b/src/font/shaper/mod.rs @@ -4,6 +4,7 @@ use failure::{format_err, Error, Fallible}; use serde_derive::*; use std::sync::Mutex; +pub mod allsorts; pub mod harfbuzz; /// Holds information about a shaped glyph @@ -65,6 +66,7 @@ pub trait FontShaper { #[derive(Debug, Deserialize, Clone, Copy)] pub enum FontShaperSelection { + Allsorts, Harfbuzz, } @@ -90,12 +92,13 @@ impl FontShaperSelection { } pub fn variants() -> Vec<&'static str> { - vec!["Harfbuzz"] + vec!["Harfbuzz", "AllSorts"] } pub fn new_shaper(self, handles: &[FontDataHandle]) -> Fallible> { match self { Self::Harfbuzz => Ok(Box::new(harfbuzz::HarfbuzzShaper::new(handles)?)), + Self::Allsorts => Ok(Box::new(allsorts::AllsortsShaper::new(handles)?)), } } } @@ -105,6 +108,7 @@ impl std::str::FromStr for FontShaperSelection { fn from_str(s: &str) -> Result { match s.to_lowercase().as_ref() { "harfbuzz" => Ok(Self::Harfbuzz), + "allsorts" => Ok(Self::Allsorts), _ => Err(format_err!( "{} is not a valid FontShaperSelection variant, possible values are {:?}", s, diff --git a/src/frontend/gui/glyphcache.rs b/src/frontend/gui/glyphcache.rs index bb0348407..50c8b8300 100644 --- a/src/frontend/gui/glyphcache.rs +++ b/src/frontend/gui/glyphcache.rs @@ -32,6 +32,20 @@ pub struct CachedGlyph { pub scale: f64, } +impl std::fmt::Debug for CachedGlyph { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { + fmt.debug_struct("CachedGlyph") + .field("has_color", &self.has_color) + .field("x_offset", &self.x_offset) + .field("y_offset", &self.y_offset) + .field("bearing_x", &self.bearing_x) + .field("bearing_y", &self.bearing_y) + .field("scale", &self.scale) + .field("texture", &self.texture) + .finish() + } +} + pub struct GlyphCache { glyph_cache: HashMap>>, pub atlas: Atlas, diff --git a/window/src/bitmaps/atlas.rs b/window/src/bitmaps/atlas.rs index f32b57cf9..6d7dbe524 100644 --- a/window/src/bitmaps/atlas.rs +++ b/window/src/bitmaps/atlas.rs @@ -123,6 +123,16 @@ where pub coords: Rect, } +impl std::fmt::Debug for Sprite { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { + fmt.debug_struct("Sprite") + .field("coords", &self.coords) + .field("texture_width", &self.texture.width()) + .field("texture_height", &self.texture.height()) + .finish() + } +} + impl Clone for Sprite where T: Texture2d, @@ -149,6 +159,7 @@ where /// These are used to handle multi-cell wide glyphs. /// Each cell is nominally `cell_width` wide but font metrics /// may result in the glyphs being wider than this. +#[derive(Debug)] pub struct SpriteSlice { /// This is glyph X out of num_cells pub cell_idx: usize,