mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 21:32:13 +03:00
fonts: add an Allsorts shaper
Adds the ability to specify `--font-shaper Allsorts` and use that for metrics and shaping. It is sufficient to expand things like ligatures, but there's something slightly off about how the metrics are computed and they differ slightly from the freetype renderer, which leads to some artifacts when rendering with opengl. One of my tests is to `grep message src/main.rs` to pull out the line that has a selection of emoji. The heart emoji is missing from that line currently. Refs: https://github.com/wez/wezterm/issues/66
This commit is contained in:
parent
72f4ec58e4
commit
a9b0197075
@ -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<c_int, Error> {
|
||||
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<String, Error> {
|
||||
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(),
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<LayoutCache<GPOS>>,
|
||||
gsub_cache: LayoutCache<GSUB>,
|
||||
gsub_cache: Option<LayoutCache<GSUB>>,
|
||||
gdef_table: Option<GDEFTable>,
|
||||
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::<LayoutTable<GSUB>>()?;
|
||||
let gdef_table: Option<GDEFTable> = otf
|
||||
.find_table_record(tag::GDEF)
|
||||
.map(|gdef_record| -> Fallible<GDEFTable> {
|
||||
@ -193,9 +189,16 @@ impl ParsedFont {
|
||||
.read::<LayoutTable<GPOS>>()?)
|
||||
})
|
||||
.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<LayoutTable<GSUB>> {
|
||||
Ok(gsub.read_table(&file.scope)?.read::<LayoutTable<GSUB>>()?)
|
||||
})
|
||||
.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<T: AsRef<str>>(
|
||||
&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)
|
||||
|
162
src/font/shaper/allsorts.rs
Normal file
162
src/font/shaper/allsorts.rs
Normal file
@ -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<Option<ParsedFont>>,
|
||||
}
|
||||
|
||||
impl AllsortsShaper {
|
||||
pub fn new(handles: &[FontDataHandle]) -> Fallible<Self> {
|
||||
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<GlyphInfo>,
|
||||
) -> 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<Vec<GlyphInfo>> {
|
||||
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<FontMetrics> {
|
||||
for font in &self.fonts {
|
||||
if let Some(font) = font {
|
||||
return Ok(font.get_metrics(size, dpi));
|
||||
}
|
||||
}
|
||||
bail!("no fonts available for collecting metrics!?");
|
||||
}
|
||||
}
|
@ -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<Box<dyn FontShaper>> {
|
||||
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<Self, Self::Err> {
|
||||
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,
|
||||
|
@ -32,6 +32,20 @@ pub struct CachedGlyph<T: Texture2d> {
|
||||
pub scale: f64,
|
||||
}
|
||||
|
||||
impl<T: Texture2d> std::fmt::Debug for CachedGlyph<T> {
|
||||
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<T: Texture2d> {
|
||||
glyph_cache: HashMap<GlyphKey, Rc<CachedGlyph<T>>>,
|
||||
pub atlas: Atlas<T>,
|
||||
|
@ -123,6 +123,16 @@ where
|
||||
pub coords: Rect,
|
||||
}
|
||||
|
||||
impl<T: Texture2d> std::fmt::Debug for Sprite<T> {
|
||||
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<T> Clone for Sprite<T>
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user