mirror of
https://github.com/wez/wezterm.git
synced 2024-11-27 02:25:28 +03:00
Add shaper post-processing function
This function is intended to deal with certain kinds of ligatures and certain combining sequences that don't have corresponding glyphs. It isn't hooked up to the gui yet, but does have unit tests that are probably mostly correct. refs: https://github.com/wez/wezterm/issues/478
This commit is contained in:
parent
1bbe594354
commit
ee17e4e174
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -4396,11 +4396,13 @@ dependencies = [
|
||||
"fontconfig",
|
||||
"freetype",
|
||||
"harfbuzz",
|
||||
"k9",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"metrics",
|
||||
"mux",
|
||||
"ordered-float",
|
||||
"pretty_env_logger",
|
||||
"rangeset",
|
||||
"termwiz",
|
||||
"thiserror",
|
||||
@ -4431,6 +4433,7 @@ dependencies = [
|
||||
"hdrhistogram",
|
||||
"http_req",
|
||||
"image",
|
||||
"k9",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
@ -4444,6 +4447,7 @@ dependencies = [
|
||||
"ordered-float",
|
||||
"palette",
|
||||
"portable-pty",
|
||||
"pretty_env_logger",
|
||||
"promise",
|
||||
"pulldown-cmark",
|
||||
"rangeset",
|
||||
|
@ -254,6 +254,12 @@ pub fn use_default_configuration() {
|
||||
CONFIG.use_defaults();
|
||||
}
|
||||
|
||||
/// Use a config that doesn't depend on the user's
|
||||
/// environment and is suitable for unit testing
|
||||
pub fn use_test_configuration() {
|
||||
CONFIG.use_test();
|
||||
}
|
||||
|
||||
/// Returns a handle to the current configuration
|
||||
pub fn configuration() -> ConfigHandle {
|
||||
CONFIG.get()
|
||||
@ -389,6 +395,14 @@ impl ConfigInner {
|
||||
self.error.take();
|
||||
self.generation += 1;
|
||||
}
|
||||
|
||||
fn use_test(&mut self) {
|
||||
FontLocatorSelection::ConfigDirsOnly.set_default();
|
||||
let config = Config::default_config();
|
||||
self.config = Arc::new(config);
|
||||
self.error.take();
|
||||
self.generation += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Configuration {
|
||||
@ -417,6 +431,13 @@ impl Configuration {
|
||||
inner.use_defaults();
|
||||
}
|
||||
|
||||
/// Use a config that doesn't depend on the user's
|
||||
/// environment and is suitable for unit testing
|
||||
pub fn use_test(&self) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.use_test();
|
||||
}
|
||||
|
||||
/// Reload the configuration
|
||||
pub fn reload(&self) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
@ -39,3 +39,7 @@ winapi = "0.3"
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9"
|
||||
core-text = "19.0"
|
||||
|
||||
[dev-dependencies]
|
||||
k9 = "0.10.0"
|
||||
pretty_env_logger = "0.4"
|
||||
|
@ -6,6 +6,7 @@ pub mod freetype;
|
||||
|
||||
/// A bitmap representation of a glyph.
|
||||
/// The data is stored as pre-multiplied RGBA 32bpp.
|
||||
#[derive(Debug)]
|
||||
pub struct RasterizedGlyph {
|
||||
pub data: Vec<u8>,
|
||||
pub height: usize,
|
||||
|
@ -420,9 +420,12 @@ 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 is_space = text == " ";
|
||||
|
||||
let info = GlyphInfo {
|
||||
#[cfg(debug_assertions)]
|
||||
text,
|
||||
is_space,
|
||||
cluster: cluster as u32,
|
||||
num_cells: num_cells as u8,
|
||||
font_idx: font_index,
|
||||
|
@ -33,9 +33,11 @@ impl<'a> std::fmt::Debug for Info<'a> {
|
||||
|
||||
fn make_glyphinfo(text: &str, font_idx: usize, info: &Info) -> GlyphInfo {
|
||||
let num_cells = unicode_column_width(text) as u8;
|
||||
let is_space = text == " ";
|
||||
GlyphInfo {
|
||||
#[cfg(debug_assertions)]
|
||||
text: text.into(),
|
||||
is_space,
|
||||
num_cells,
|
||||
font_idx,
|
||||
glyph_pos: info.codepoint,
|
||||
@ -160,7 +162,7 @@ impl HarfbuzzShaper {
|
||||
buf.set_language(harfbuzz::language_from_string("en")?);
|
||||
buf.add_str(s);
|
||||
buf.set_cluster_level(
|
||||
harfbuzz::hb_buffer_cluster_level_t::HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES,
|
||||
harfbuzz::hb_buffer_cluster_level_t::HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS,
|
||||
);
|
||||
|
||||
let cell_width;
|
||||
@ -187,7 +189,7 @@ impl HarfbuzzShaper {
|
||||
}
|
||||
}
|
||||
|
||||
if font_idx + 1 == self.fonts.len() {
|
||||
if font_idx > 0 && font_idx + 1 == self.fonts.len() {
|
||||
// We are the last resort font, so each codepoint is considered
|
||||
// to be worthy of a fallback lookup
|
||||
for c in s.chars() {
|
||||
@ -423,3 +425,171 @@ impl FontShaper for HarfbuzzShaper {
|
||||
self.metrics_for_idx(metrics_idx, size, dpi)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::FontDatabase;
|
||||
use config::FontAttributes;
|
||||
use k9::assert_equal as assert_eq;
|
||||
|
||||
#[test]
|
||||
fn ligatures() {
|
||||
let _ = pretty_env_logger::formatted_builder()
|
||||
.is_test(true)
|
||||
.filter_level(log::LevelFilter::Trace)
|
||||
.try_init();
|
||||
|
||||
let mut db = FontDatabase::with_built_in().unwrap();
|
||||
let handle = db
|
||||
.resolve(&FontAttributes {
|
||||
family: "JetBrains Mono".into(),
|
||||
bold: false,
|
||||
is_fallback: false,
|
||||
italic: false,
|
||||
})
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let mut shaper = HarfbuzzShaper::new(&[handle]).unwrap();
|
||||
{
|
||||
let mut no_glyphs = vec![];
|
||||
let info = shaper.shape("abc", 10., 72, &mut no_glyphs).unwrap();
|
||||
assert!(no_glyphs.is_empty(), "{:?}", no_glyphs);
|
||||
assert_eq!(
|
||||
info,
|
||||
vec![
|
||||
GlyphInfo {
|
||||
cluster: 0,
|
||||
font_idx: 0,
|
||||
glyph_pos: 180,
|
||||
num_cells: 1,
|
||||
text: "a".into(),
|
||||
x_advance: PixelLength::new(6.),
|
||||
x_offset: PixelLength::new(0.),
|
||||
y_advance: PixelLength::new(0.),
|
||||
y_offset: PixelLength::new(0.),
|
||||
},
|
||||
GlyphInfo {
|
||||
cluster: 1,
|
||||
font_idx: 0,
|
||||
glyph_pos: 205,
|
||||
num_cells: 1,
|
||||
text: "b".into(),
|
||||
x_advance: PixelLength::new(6.),
|
||||
x_offset: PixelLength::new(0.),
|
||||
y_advance: PixelLength::new(0.),
|
||||
y_offset: PixelLength::new(0.),
|
||||
},
|
||||
GlyphInfo {
|
||||
cluster: 2,
|
||||
font_idx: 0,
|
||||
glyph_pos: 206,
|
||||
num_cells: 1,
|
||||
text: "c".into(),
|
||||
x_advance: PixelLength::new(6.),
|
||||
x_offset: PixelLength::new(0.),
|
||||
y_advance: PixelLength::new(0.),
|
||||
y_offset: PixelLength::new(0.),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut no_glyphs = vec![];
|
||||
let info = shaper.shape("<", 10., 72, &mut no_glyphs).unwrap();
|
||||
assert!(no_glyphs.is_empty(), "{:?}", no_glyphs);
|
||||
assert_eq!(
|
||||
info,
|
||||
vec![GlyphInfo {
|
||||
cluster: 0,
|
||||
font_idx: 0,
|
||||
glyph_pos: 726,
|
||||
num_cells: 1,
|
||||
text: "<".into(),
|
||||
x_advance: PixelLength::new(6.),
|
||||
x_offset: PixelLength::new(0.),
|
||||
y_advance: PixelLength::new(0.),
|
||||
y_offset: PixelLength::new(0.),
|
||||
},]
|
||||
);
|
||||
}
|
||||
{
|
||||
// This is a ligatured sequence, but you wouldn't know
|
||||
// from this info :-/
|
||||
let mut no_glyphs = vec![];
|
||||
let info = shaper.shape("<-", 10., 72, &mut no_glyphs).unwrap();
|
||||
assert!(no_glyphs.is_empty(), "{:?}", no_glyphs);
|
||||
assert_eq!(
|
||||
info,
|
||||
vec![
|
||||
GlyphInfo {
|
||||
cluster: 0,
|
||||
font_idx: 0,
|
||||
glyph_pos: 1212,
|
||||
num_cells: 1,
|
||||
text: "<".into(),
|
||||
x_advance: PixelLength::new(6.),
|
||||
x_offset: PixelLength::new(0.),
|
||||
y_advance: PixelLength::new(0.),
|
||||
y_offset: PixelLength::new(0.),
|
||||
},
|
||||
GlyphInfo {
|
||||
cluster: 1,
|
||||
font_idx: 0,
|
||||
glyph_pos: 1065,
|
||||
num_cells: 1,
|
||||
text: "-".into(),
|
||||
x_advance: PixelLength::new(6.),
|
||||
x_offset: PixelLength::new(0.),
|
||||
y_advance: PixelLength::new(0.),
|
||||
y_offset: PixelLength::new(0.),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut no_glyphs = vec![];
|
||||
let info = shaper.shape("<--", 10., 72, &mut no_glyphs).unwrap();
|
||||
assert!(no_glyphs.is_empty(), "{:?}", no_glyphs);
|
||||
assert_eq!(
|
||||
info,
|
||||
vec![
|
||||
GlyphInfo {
|
||||
cluster: 0,
|
||||
font_idx: 0,
|
||||
glyph_pos: 726,
|
||||
num_cells: 1,
|
||||
text: "<".into(),
|
||||
x_advance: PixelLength::new(6.),
|
||||
x_offset: PixelLength::new(0.),
|
||||
y_advance: PixelLength::new(0.),
|
||||
y_offset: PixelLength::new(0.),
|
||||
},
|
||||
GlyphInfo {
|
||||
cluster: 1,
|
||||
font_idx: 0,
|
||||
glyph_pos: 1212,
|
||||
num_cells: 1,
|
||||
text: "-".into(),
|
||||
x_advance: PixelLength::new(6.),
|
||||
x_offset: PixelLength::new(0.),
|
||||
y_advance: PixelLength::new(0.),
|
||||
y_offset: PixelLength::new(0.),
|
||||
},
|
||||
GlyphInfo {
|
||||
cluster: 2,
|
||||
font_idx: 0,
|
||||
glyph_pos: 623,
|
||||
num_cells: 1,
|
||||
text: "-".into(),
|
||||
x_advance: PixelLength::new(6.),
|
||||
x_offset: PixelLength::new(0.),
|
||||
y_advance: PixelLength::new(0.),
|
||||
y_offset: PixelLength::new(0.),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,12 @@ pub mod allsorts;
|
||||
pub mod harfbuzz;
|
||||
|
||||
/// Holds information about a shaped glyph
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct GlyphInfo {
|
||||
/// We only retain text in debug mode for diagnostic purposes
|
||||
#[cfg(debug_assertions)]
|
||||
pub text: String,
|
||||
pub is_space: bool,
|
||||
/// Offset within text
|
||||
pub cluster: u32,
|
||||
/// How many cells/columns this glyph occupies horizontally
|
||||
|
@ -87,3 +87,6 @@ default = ["vendor_openssl"]
|
||||
# FIXME: find a way to magically disable vendor_openssl only on linux!
|
||||
vendor_openssl = ["openssl/vendored"]
|
||||
|
||||
[dev-dependencies]
|
||||
k9 = "0.10.0"
|
||||
pretty_env_logger = "0.4"
|
||||
|
@ -1,5 +1,7 @@
|
||||
use super::utilsprites::RenderMetrics;
|
||||
use ::window::bitmaps::atlas::{Atlas, Sprite};
|
||||
#[cfg(test)]
|
||||
use ::window::bitmaps::ImageTexture;
|
||||
use ::window::bitmaps::{Image, Texture2d};
|
||||
use ::window::glium::backend::Context as GliumContext;
|
||||
use ::window::glium::texture::SrgbTexture2d;
|
||||
@ -130,6 +132,27 @@ pub struct GlyphCache<T: Texture2d> {
|
||||
metrics: RenderMetrics,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl GlyphCache<ImageTexture> {
|
||||
pub fn new_in_memory(
|
||||
fonts: &Rc<FontConfiguration>,
|
||||
size: usize,
|
||||
metrics: &RenderMetrics,
|
||||
) -> anyhow::Result<Self> {
|
||||
let surface = Rc::new(ImageTexture::new(size, size));
|
||||
let atlas = Atlas::new(&surface).expect("failed to create new texture atlas");
|
||||
|
||||
Ok(Self {
|
||||
fonts: Rc::clone(fonts),
|
||||
glyph_cache: HashMap::new(),
|
||||
image_cache: HashMap::new(),
|
||||
atlas,
|
||||
metrics: metrics.clone(),
|
||||
line_glyphs: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GlyphCache<SrgbTexture2d> {
|
||||
pub fn new_gl(
|
||||
backend: &Rc<GliumContext>,
|
||||
|
@ -2,6 +2,7 @@
|
||||
use super::quad::*;
|
||||
use super::renderstate::*;
|
||||
use super::utilsprites::RenderMetrics;
|
||||
use crate::gui::glyphcache::{CachedGlyph, GlyphCache};
|
||||
use crate::gui::overlay::{
|
||||
confirm_close_pane, confirm_close_tab, confirm_close_window, confirm_quit_program, launcher,
|
||||
start_overlay, start_overlay_pane, tab_navigator, CopyOverlay, SearchOverlay,
|
||||
@ -47,6 +48,7 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time::{Duration, Instant};
|
||||
use termwiz::cellcluster::CellCluster;
|
||||
use termwiz::color::{ColorAttribute, RgbColor};
|
||||
use termwiz::hyperlink::Hyperlink;
|
||||
use termwiz::image::ImageData;
|
||||
@ -194,6 +196,116 @@ impl PrevCursorPos {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct GlyphPosition {
|
||||
glyph_idx: u32,
|
||||
cluster: u32,
|
||||
num_cells: u8,
|
||||
x_offset: PixelLength,
|
||||
bearing_x: f32,
|
||||
bitmap_pixel_width: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ShapedInfo<T>
|
||||
where
|
||||
T: Texture2d,
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
glyph: Rc<CachedGlyph<T>>,
|
||||
pos: GlyphPosition,
|
||||
}
|
||||
|
||||
impl<T> ShapedInfo<T>
|
||||
where
|
||||
T: Texture2d,
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
/// Process the results from the shaper.
|
||||
/// Ideally this would not be needed, but the shaper doesn't
|
||||
/// merge certain forms of ligatured cluster, and won't merge
|
||||
/// certain combining sequences for which no glyph could be
|
||||
/// found for the resultant grapheme.
|
||||
/// This function's goal is to handle those two cases.
|
||||
pub fn process(
|
||||
cluster: &CellCluster,
|
||||
infos: &[GlyphInfo],
|
||||
glyphs: &[Rc<CachedGlyph<T>>],
|
||||
) -> Vec<ShapedInfo<T>> {
|
||||
let mut pos = vec![];
|
||||
let mut run = None;
|
||||
for (info, glyph) in infos.iter().zip(glyphs.iter()) {
|
||||
if !info.is_space && glyph.texture.is_none() {
|
||||
if run.is_none() {
|
||||
run.replace(ShapedInfo {
|
||||
pos: GlyphPosition {
|
||||
glyph_idx: info.glyph_pos,
|
||||
cluster: info.cluster,
|
||||
num_cells: info.num_cells,
|
||||
x_offset: info.x_advance,
|
||||
bearing_x: 0.,
|
||||
bitmap_pixel_width: 0,
|
||||
},
|
||||
glyph: Rc::clone(glyph),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let run = run.as_mut().unwrap();
|
||||
run.pos.num_cells += info.num_cells;
|
||||
run.pos.x_offset += info.x_advance;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(mut run) = run.take() {
|
||||
run.glyph = Rc::clone(glyph);
|
||||
run.pos.glyph_idx = info.glyph_pos;
|
||||
run.pos.num_cells += info.num_cells;
|
||||
run.pos.bitmap_pixel_width = glyph.texture.as_ref().unwrap().coords.width() as u32;
|
||||
run.pos.bearing_x = (run.pos.x_offset.get() + glyph.bearing_x.get() as f64) as f32;
|
||||
run.pos.x_offset = info.x_advance - PixelLength::new(run.pos.bearing_x as f64);
|
||||
pos.push(run);
|
||||
} else {
|
||||
let cell_idx = cluster.byte_to_cell_idx[info.cluster as usize];
|
||||
if let Some(prior) = pos.last() {
|
||||
let prior_cell_idx = cluster.byte_to_cell_idx[prior.pos.cluster as usize];
|
||||
if cell_idx <= prior_cell_idx {
|
||||
// This is a tricky case: if we have a cluster such as
|
||||
// 1F470 1F3FF 200D 2640 (woman with veil: dark skin tone)
|
||||
// and the font doesn't define a glyph for it, the shaper
|
||||
// may give us a sequence of three output clusters, each
|
||||
// comprising: veil, skin tone and female respectively.
|
||||
// Those all have the same info.cluster which
|
||||
// means that they all resolve to the same cell_idx.
|
||||
// In this case, the cluster is logically a single cell,
|
||||
// and the best presentation is of the veil, so we pick
|
||||
// that one and ignore the rest of the glyphs that map to
|
||||
// this same cell.
|
||||
// Ideally we'd overlay this with a "something is broken"
|
||||
// glyph in the corner.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
pos.push(ShapedInfo {
|
||||
pos: GlyphPosition {
|
||||
glyph_idx: info.glyph_pos,
|
||||
bitmap_pixel_width: glyph
|
||||
.texture
|
||||
.as_ref()
|
||||
.map_or(0, |t| t.coords.width() as u32),
|
||||
cluster: info.cluster,
|
||||
num_cells: info.num_cells,
|
||||
x_offset: info.x_offset,
|
||||
bearing_x: glyph.bearing_x.get() as f32,
|
||||
},
|
||||
glyph: Rc::clone(glyph),
|
||||
});
|
||||
}
|
||||
}
|
||||
pos
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct PaneState {
|
||||
/// If is_some(), the top row of the visible screen.
|
||||
@ -4071,3 +4183,227 @@ fn window_mods_to_termwiz_mods(modifiers: ::window::Modifiers) -> termwiz::input
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use config::TextStyle;
|
||||
use k9::assert_equal as assert_eq;
|
||||
use std::rc::Rc;
|
||||
use wezterm_font::LoadedFont;
|
||||
|
||||
fn cluster_and_shape<T>(
|
||||
glyph_cache: &mut GlyphCache<T>,
|
||||
style: &TextStyle,
|
||||
font: &Rc<LoadedFont>,
|
||||
text: &str,
|
||||
) -> Vec<GlyphPosition>
|
||||
where
|
||||
T: Texture2d,
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
let line = Line::from_text(text, &CellAttributes::default());
|
||||
let cell_clusters = line.cluster();
|
||||
assert_eq!(cell_clusters.len(), 1);
|
||||
let cluster = &cell_clusters[0];
|
||||
let infos = font.shape(&cluster.text).unwrap();
|
||||
let glyphs = infos
|
||||
.iter()
|
||||
.map(|info| {
|
||||
let cell_idx = cluster.byte_to_cell_idx[info.cluster as usize];
|
||||
|
||||
let followed_by_space = match line.cells().get(cell_idx + 1) {
|
||||
Some(cell) => cell.str() == " ",
|
||||
None => false,
|
||||
};
|
||||
|
||||
glyph_cache
|
||||
.cached_glyph(info, &style, followed_by_space)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
eprintln!("infos: {:#?}", infos);
|
||||
eprintln!("glyphs: {:#?}", glyphs);
|
||||
ShapedInfo::process(&cluster, &infos, &glyphs)
|
||||
.into_iter()
|
||||
.map(|p| p.pos)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ligatures() {
|
||||
config::use_test_configuration();
|
||||
let _ = pretty_env_logger::formatted_builder()
|
||||
.is_test(true)
|
||||
.filter_level(log::LevelFilter::Trace)
|
||||
.try_init();
|
||||
|
||||
let fonts = Rc::new(FontConfiguration::new().unwrap());
|
||||
let render_metrics = RenderMetrics::new(&fonts).unwrap();
|
||||
let mut glyph_cache = GlyphCache::new_in_memory(&fonts, 128, &render_metrics).unwrap();
|
||||
|
||||
let style = TextStyle::default();
|
||||
let font = fonts.resolve_font(&style).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
cluster_and_shape(&mut glyph_cache, &style, &font, "ab"),
|
||||
vec![
|
||||
GlyphPosition {
|
||||
glyph_idx: 180,
|
||||
cluster: 0,
|
||||
num_cells: 1,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 0.0,
|
||||
bitmap_pixel_width: 7,
|
||||
},
|
||||
GlyphPosition {
|
||||
glyph_idx: 205,
|
||||
cluster: 1,
|
||||
num_cells: 1,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 1.0,
|
||||
bitmap_pixel_width: 6,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cluster_and_shape(&mut glyph_cache, &style, &font, "a b"),
|
||||
vec![
|
||||
GlyphPosition {
|
||||
glyph_idx: 180,
|
||||
cluster: 0,
|
||||
num_cells: 1,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 0.0,
|
||||
bitmap_pixel_width: 7,
|
||||
},
|
||||
GlyphPosition {
|
||||
glyph_idx: 686,
|
||||
cluster: 1,
|
||||
num_cells: 1,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 0.0,
|
||||
bitmap_pixel_width: 0,
|
||||
},
|
||||
GlyphPosition {
|
||||
glyph_idx: 205,
|
||||
cluster: 2,
|
||||
num_cells: 1,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 1.0,
|
||||
bitmap_pixel_width: 6,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cluster_and_shape(&mut glyph_cache, &style, &font, "a b"),
|
||||
vec![
|
||||
GlyphPosition {
|
||||
glyph_idx: 180,
|
||||
cluster: 0,
|
||||
num_cells: 1,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 0.0,
|
||||
bitmap_pixel_width: 7,
|
||||
},
|
||||
GlyphPosition {
|
||||
glyph_idx: 686,
|
||||
cluster: 1,
|
||||
num_cells: 1,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 0.0,
|
||||
bitmap_pixel_width: 0,
|
||||
},
|
||||
GlyphPosition {
|
||||
glyph_idx: 686,
|
||||
cluster: 2,
|
||||
num_cells: 1,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 0.0,
|
||||
bitmap_pixel_width: 0,
|
||||
},
|
||||
GlyphPosition {
|
||||
glyph_idx: 205,
|
||||
cluster: 3,
|
||||
num_cells: 1,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 1.0,
|
||||
bitmap_pixel_width: 6,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cluster_and_shape(&mut glyph_cache, &style, &font, "<-"),
|
||||
vec![GlyphPosition {
|
||||
glyph_idx: 1065,
|
||||
cluster: 0,
|
||||
num_cells: 2,
|
||||
x_offset: PixelLength::new(7.0),
|
||||
bearing_x: 1.0,
|
||||
bitmap_pixel_width: 14,
|
||||
}]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cluster_and_shape(&mut glyph_cache, &style, &font, "<>"),
|
||||
vec![GlyphPosition {
|
||||
glyph_idx: 1089,
|
||||
cluster: 0,
|
||||
num_cells: 2,
|
||||
x_offset: PixelLength::new(6.0),
|
||||
bearing_x: 2.0,
|
||||
bitmap_pixel_width: 12,
|
||||
}]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cluster_and_shape(&mut glyph_cache, &style, &font, "|=>"),
|
||||
vec![GlyphPosition {
|
||||
glyph_idx: 1040,
|
||||
cluster: 0,
|
||||
num_cells: 3,
|
||||
x_offset: PixelLength::new(6.0),
|
||||
bearing_x: 2.0,
|
||||
bitmap_pixel_width: 21,
|
||||
}]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cluster_and_shape(&mut glyph_cache, &style, &font, "<!--"),
|
||||
vec![GlyphPosition {
|
||||
glyph_idx: 1071,
|
||||
cluster: 0,
|
||||
num_cells: 4,
|
||||
x_offset: PixelLength::new(7.0),
|
||||
bearing_x: 1.0,
|
||||
bitmap_pixel_width: 30,
|
||||
}]
|
||||
);
|
||||
|
||||
let deaf_man_medium_light_skin_tone = "\u{1F9CF}\u{1F3FC}\u{200D}\u{2642}\u{FE0F}";
|
||||
println!(
|
||||
"deaf_man_medium_light_skin_tone: {}",
|
||||
deaf_man_medium_light_skin_tone
|
||||
);
|
||||
assert_eq!(
|
||||
cluster_and_shape(
|
||||
&mut glyph_cache,
|
||||
&style,
|
||||
&font,
|
||||
deaf_man_medium_light_skin_tone
|
||||
),
|
||||
vec![GlyphPosition {
|
||||
glyph_idx: 298,
|
||||
cluster: 0,
|
||||
num_cells: 2,
|
||||
x_offset: PixelLength::new(0.0),
|
||||
bearing_x: 1.0666667,
|
||||
bitmap_pixel_width: 14,
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -337,6 +337,15 @@ pub struct Image {
|
||||
height: usize,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Image {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.debug_struct("Image")
|
||||
.field("width", &self.width)
|
||||
.field("height", &self.height)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for Image {
|
||||
fn into(self) -> Vec<u8> {
|
||||
self.data
|
||||
@ -490,6 +499,7 @@ impl BitmapImage for Image {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImageTexture {
|
||||
pub image: RefCell<Image>,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user