Checkpoint

This commit is contained in:
Antonio Scandurra
2023-10-21 16:01:47 +02:00
parent f3979a9f28
commit e4fe9538d7
107 changed files with 81 additions and 81 deletions
@@ -0,0 +1,162 @@
use schemars::{
schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
JsonSchema,
};
macro_rules! create_definitions {
($($(#[$meta:meta])* ($name:ident, $idx:expr)),* $(,)?) => {
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FontFeatures {
enabled: u64,
disabled: u64,
}
impl FontFeatures {
$(
pub fn $name(&self) -> Option<bool> {
if (self.enabled & (1 << $idx)) != 0 {
Some(true)
} else if (self.disabled & (1 << $idx)) != 0 {
Some(false)
} else {
None
}
}
)*
}
impl std::fmt::Debug for FontFeatures {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("FontFeatures");
$(
if let Some(value) = self.$name() {
debug.field(stringify!($name), &value);
};
)*
debug.finish()
}
}
impl<'de> serde::Deserialize<'de> for FontFeatures {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{MapAccess, Visitor};
use std::fmt;
struct FontFeaturesVisitor;
impl<'de> Visitor<'de> for FontFeaturesVisitor {
type Value = FontFeatures;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map of font features")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut enabled: u64 = 0;
let mut disabled: u64 = 0;
while let Some((key, value)) = access.next_entry::<String, Option<bool>>()? {
let idx = match key.as_str() {
$(stringify!($name) => $idx,)*
_ => continue,
};
match value {
Some(true) => enabled |= 1 << idx,
Some(false) => disabled |= 1 << idx,
None => {}
};
}
Ok(FontFeatures { enabled, disabled })
}
}
let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
Ok(features)
}
}
impl serde::Serialize for FontFeatures {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(None)?;
$(
let feature = stringify!($name);
if let Some(value) = self.$name() {
map.serialize_entry(feature, &value)?;
}
)*
map.end()
}
}
impl JsonSchema for FontFeatures {
fn schema_name() -> String {
"FontFeatures".into()
}
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> Schema {
let mut schema = SchemaObject::default();
let properties = &mut schema.object().properties;
let feature_schema = Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))),
..Default::default()
});
$(
properties.insert(stringify!($name).to_owned(), feature_schema.clone());
)*
schema.into()
}
}
};
}
create_definitions!(
(calt, 0),
(case, 1),
(cpsp, 2),
(frac, 3),
(liga, 4),
(onum, 5),
(ordn, 6),
(pnum, 7),
(ss01, 8),
(ss02, 9),
(ss03, 10),
(ss04, 11),
(ss05, 12),
(ss06, 13),
(ss07, 14),
(ss08, 15),
(ss09, 16),
(ss10, 17),
(ss11, 18),
(ss12, 19),
(ss13, 20),
(ss14, 21),
(ss15, 22),
(ss16, 23),
(ss17, 24),
(ss18, 25),
(ss19, 26),
(ss20, 27),
(subs, 28),
(sups, 29),
(swsh, 30),
(titl, 31),
(tnum, 32),
(zero, 33)
);
+154
View File
@@ -0,0 +1,154 @@
use crate::{
black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
};
use smallvec::SmallVec;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct DecorationRun {
pub len: u32,
pub color: Hsla,
pub underline: Option<UnderlineStyle>,
}
#[derive(Clone, Default, Debug)]
pub struct Line {
pub(crate) layout: Arc<WrappedLineLayout>,
pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
}
impl Line {
pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
size(
self.layout.width,
line_height * (self.layout.wrap_boundaries.len() + 1),
)
}
pub fn wrap_count(&self) -> usize {
self.layout.wrap_boundaries.len()
}
pub fn paint(
&self,
origin: Point<Pixels>,
line_height: Pixels,
cx: &mut WindowContext,
) -> Result<()> {
let padding_top =
(line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + self.layout.layout.ascent);
let mut style_runs = self.decorations.iter();
let mut wraps = self.layout.wrap_boundaries.iter().peekable();
let mut run_end = 0;
let mut color = black();
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let text_system = cx.text_system().clone();
let mut glyph_origin = origin;
let mut prev_glyph_position = Point::default();
for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
let max_glyph_size = text_system
.bounding_box(run.font_id, self.layout.layout.font_size)?
.size;
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
wraps.next();
if let Some((underline_origin, underline_style)) = current_underline.take() {
cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
}
glyph_origin.x = origin.x;
glyph_origin.y += line_height;
}
prev_glyph_position = glyph.position;
let glyph_origin = glyph_origin + baseline_offset;
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
if glyph.index >= run_end {
if let Some(style_run) = style_runs.next() {
if let Some((_, underline_style)) = &mut current_underline {
if style_run.underline.as_ref() != Some(underline_style) {
finished_underline = current_underline.take();
}
}
if let Some(run_underline) = style_run.underline.as_ref() {
current_underline.get_or_insert((
point(
glyph_origin.x,
origin.y
+ baseline_offset.y
+ (self.layout.layout.descent * 0.618),
),
UnderlineStyle {
color: Some(run_underline.color.unwrap_or(style_run.color)),
thickness: run_underline.thickness,
wavy: run_underline.wavy,
},
));
}
run_end += style_run.len as usize;
color = style_run.color;
} else {
run_end = self.layout.text.len();
finished_underline = current_underline.take();
}
}
if let Some((underline_origin, underline_style)) = finished_underline {
cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
}
let max_glyph_bounds = Bounds {
origin: glyph_origin,
size: max_glyph_size,
};
let content_mask = cx.content_mask();
if max_glyph_bounds.intersects(&content_mask.bounds) {
if glyph.is_emoji {
cx.paint_emoji(
glyph_origin,
run.font_id,
glyph.id,
self.layout.layout.font_size,
)?;
} else {
cx.paint_glyph(
glyph_origin,
run.font_id,
glyph.id,
self.layout.layout.font_size,
color,
)?;
}
}
}
}
if let Some((underline_start, underline_style)) = current_underline.take() {
let line_end_x = origin.x + self.layout.layout.width;
cx.paint_underline(
underline_start,
line_end_x - underline_start.x,
&underline_style,
)?;
}
Ok(())
}
}
+295
View File
@@ -0,0 +1,295 @@
use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString};
use derive_more::{Deref, DerefMut};
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
use std::{
borrow::Borrow,
collections::HashMap,
hash::{Hash, Hasher},
sync::Arc,
};
#[derive(Default, Debug)]
pub struct LineLayout {
pub font_size: Pixels,
pub width: Pixels,
pub ascent: Pixels,
pub descent: Pixels,
pub runs: Vec<ShapedRun>,
}
#[derive(Debug)]
pub struct ShapedRun {
pub font_id: FontId,
pub glyphs: SmallVec<[ShapedGlyph; 8]>,
}
#[derive(Clone, Debug)]
pub struct ShapedGlyph {
pub id: GlyphId,
pub position: Point<Pixels>,
pub index: usize,
pub is_emoji: bool,
}
impl LineLayout {
pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
if x >= self.width {
None
} else {
for run in self.runs.iter().rev() {
for glyph in run.glyphs.iter().rev() {
if glyph.position.x <= x {
return Some(glyph.index);
}
}
}
Some(0)
}
}
pub fn x_for_index(&self, index: usize) -> Pixels {
for run in &self.runs {
for glyph in &run.glyphs {
if glyph.index >= index {
return glyph.position.x;
}
}
}
self.width
}
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
for run in &self.runs {
for glyph in &run.glyphs {
if glyph.index >= index {
return Some(run.font_id);
}
}
}
None
}
fn compute_wrap_boundaries(
&self,
text: &str,
wrap_width: Pixels,
) -> SmallVec<[WrapBoundary; 1]> {
let mut boundaries = SmallVec::new();
let mut first_non_whitespace_ix = None;
let mut last_candidate_ix = None;
let mut last_candidate_x = px(0.);
let mut last_boundary = WrapBoundary {
run_ix: 0,
glyph_ix: 0,
};
let mut last_boundary_x = px(0.);
let mut prev_ch = '\0';
let mut glyphs = self
.runs
.iter()
.enumerate()
.flat_map(move |(run_ix, run)| {
run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
let character = text[glyph.index..].chars().next().unwrap();
(
WrapBoundary { run_ix, glyph_ix },
character,
glyph.position.x,
)
})
})
.peekable();
while let Some((boundary, ch, x)) = glyphs.next() {
if ch == '\n' {
continue;
}
if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
last_candidate_ix = Some(boundary);
last_candidate_x = x;
}
if ch != ' ' && first_non_whitespace_ix.is_none() {
first_non_whitespace_ix = Some(boundary);
}
let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
let width = next_x - last_boundary_x;
if width > wrap_width && boundary > last_boundary {
if let Some(last_candidate_ix) = last_candidate_ix.take() {
last_boundary = last_candidate_ix;
last_boundary_x = last_candidate_x;
} else {
last_boundary = boundary;
last_boundary_x = x;
}
boundaries.push(last_boundary);
}
prev_ch = ch;
}
boundaries
}
}
#[derive(Deref, DerefMut, Default, Debug)]
pub struct WrappedLineLayout {
#[deref]
#[deref_mut]
pub layout: LineLayout,
pub text: SharedString,
pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct WrapBoundary {
pub run_ix: usize,
pub glyph_ix: usize,
}
pub(crate) struct LineLayoutCache {
prev_frame: Mutex<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
curr_frame: RwLock<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
platform_text_system: Arc<dyn PlatformTextSystem>,
}
impl LineLayoutCache {
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
Self {
prev_frame: Mutex::new(HashMap::new()),
curr_frame: RwLock::new(HashMap::new()),
platform_text_system,
}
}
pub fn start_frame(&self) {
let mut prev_frame = self.prev_frame.lock();
let mut curr_frame = self.curr_frame.write();
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
curr_frame.clear();
}
pub fn layout_line(
&self,
text: &SharedString,
font_size: Pixels,
runs: &[FontRun],
wrap_width: Option<Pixels>,
) -> Arc<WrappedLineLayout> {
let key = &CacheKeyRef {
text,
font_size,
runs,
wrap_width,
} as &dyn AsCacheKeyRef;
let curr_frame = self.curr_frame.upgradable_read();
if let Some(layout) = curr_frame.get(key) {
return layout.clone();
}
let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
curr_frame.insert(key, layout.clone());
layout
} else {
let layout = self.platform_text_system.layout_line(text, font_size, runs);
let wrap_boundaries = wrap_width
.map(|wrap_width| layout.compute_wrap_boundaries(text.as_ref(), wrap_width))
.unwrap_or_default();
let wrapped_line = Arc::new(WrappedLineLayout {
layout,
text: text.clone(),
wrap_boundaries,
});
let key = CacheKey {
text: text.clone(),
font_size,
runs: SmallVec::from(runs),
wrap_width,
};
curr_frame.insert(key, wrapped_line.clone());
wrapped_line
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct FontRun {
pub(crate) len: usize,
pub(crate) font_id: FontId,
}
trait AsCacheKeyRef {
fn as_cache_key_ref(&self) -> CacheKeyRef;
}
#[derive(Eq)]
struct CacheKey {
text: SharedString,
font_size: Pixels,
runs: SmallVec<[FontRun; 1]>,
wrap_width: Option<Pixels>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct CacheKeyRef<'a> {
text: &'a str,
font_size: Pixels,
runs: &'a [FontRun],
wrap_width: Option<Pixels>,
}
impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
self.as_cache_key_ref() == other.as_cache_key_ref()
}
}
impl<'a> Eq for (dyn AsCacheKeyRef + 'a) {}
impl<'a> Hash for (dyn AsCacheKeyRef + 'a) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_cache_key_ref().hash(state)
}
}
impl AsCacheKeyRef for CacheKey {
fn as_cache_key_ref(&self) -> CacheKeyRef {
CacheKeyRef {
text: &self.text,
font_size: self.font_size,
runs: self.runs.as_slice(),
wrap_width: self.wrap_width,
}
}
}
impl PartialEq for CacheKey {
fn eq(&self, other: &Self) -> bool {
self.as_cache_key_ref().eq(&other.as_cache_key_ref())
}
}
impl Hash for CacheKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_cache_key_ref().hash(state);
}
}
impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for CacheKey {
fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
self as &dyn AsCacheKeyRef
}
}
impl<'a> AsCacheKeyRef for CacheKeyRef<'a> {
fn as_cache_key_ref(&self) -> CacheKeyRef {
*self
}
}
@@ -0,0 +1,278 @@
use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem};
use collections::HashMap;
use std::{iter, sync::Arc};
pub struct LineWrapper {
platform_text_system: Arc<dyn PlatformTextSystem>,
pub(crate) font_id: FontId,
pub(crate) font_size: Pixels,
cached_ascii_char_widths: [Option<Pixels>; 128],
cached_other_char_widths: HashMap<char, Pixels>,
}
impl LineWrapper {
pub const MAX_INDENT: u32 = 256;
pub fn new(
font_id: FontId,
font_size: Pixels,
text_system: Arc<dyn PlatformTextSystem>,
) -> Self {
Self {
platform_text_system: text_system,
font_id,
font_size,
cached_ascii_char_widths: [None; 128],
cached_other_char_widths: HashMap::default(),
}
}
pub fn wrap_line<'a>(
&'a mut self,
line: &'a str,
wrap_width: Pixels,
) -> impl Iterator<Item = Boundary> + 'a {
let mut width = px(0.);
let mut first_non_whitespace_ix = None;
let mut indent = None;
let mut last_candidate_ix = 0;
let mut last_candidate_width = px(0.);
let mut last_wrap_ix = 0;
let mut prev_c = '\0';
let mut char_indices = line.char_indices();
iter::from_fn(move || {
for (ix, c) in char_indices.by_ref() {
if c == '\n' {
continue;
}
if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
last_candidate_ix = ix;
last_candidate_width = width;
}
if c != ' ' && first_non_whitespace_ix.is_none() {
first_non_whitespace_ix = Some(ix);
}
let char_width = self.width_for_char(c);
width += char_width;
if width > wrap_width && ix > last_wrap_ix {
if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
{
indent = Some(
Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
);
}
if last_candidate_ix > 0 {
last_wrap_ix = last_candidate_ix;
width -= last_candidate_width;
last_candidate_ix = 0;
} else {
last_wrap_ix = ix;
width = char_width;
}
if let Some(indent) = indent {
width += self.width_for_char(' ') * indent as f32;
}
return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
}
prev_c = c;
}
None
})
}
#[inline(always)]
fn width_for_char(&mut self, c: char) -> Pixels {
if (c as u32) < 128 {
if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
cached_width
} else {
let width = self.compute_width_for_char(c);
self.cached_ascii_char_widths[c as usize] = Some(width);
width
}
} else {
if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
*cached_width
} else {
let width = self.compute_width_for_char(c);
self.cached_other_char_widths.insert(c, width);
width
}
}
}
fn compute_width_for_char(&self, c: char) -> Pixels {
let mut buffer = [0; 4];
let buffer = c.encode_utf8(&mut buffer);
self.platform_text_system
.layout_line(
buffer,
self.font_size,
&[FontRun {
len: 1,
font_id: self.font_id,
}],
)
.width
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Boundary {
pub ix: usize,
pub next_indent: u32,
}
impl Boundary {
fn new(ix: usize, next_indent: u32) -> Self {
Self { ix, next_indent }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{font, App};
#[test]
fn test_wrap_line() {
App::test().run(|cx| {
let text_system = cx.text_system().clone();
let mut wrapper = LineWrapper::new(
text_system.font_id(&font("Courier")).unwrap(),
px(16.),
text_system.platform_text_system.clone(),
);
assert_eq!(
wrapper
.wrap_line("aa bbb cccc ddddd eeee", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(12, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper
.wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
.collect::<Vec<_>>(),
&[
Boundary::new(4, 0),
Boundary::new(11, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper
.wrap_line(" aaaaaaa", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 5),
Boundary::new(9, 5),
Boundary::new(11, 5),
]
);
assert_eq!(
wrapper
.wrap_line(" ", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 0),
Boundary::new(21, 0)
]
);
assert_eq!(
wrapper
.wrap_line(" aaaaaaaaaaaaaa", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 3),
Boundary::new(18, 3),
Boundary::new(22, 3),
]
);
});
}
// todo!("move this to a test on TextSystem::layout_text")
// todo! repeat this test
// #[test]
// fn test_wrap_shaped_line() {
// App::test().run(|cx| {
// let text_system = cx.text_system().clone();
// let normal = TextRun {
// len: 0,
// font: font("Helvetica"),
// color: Default::default(),
// underline: Default::default(),
// };
// let bold = TextRun {
// len: 0,
// font: font("Helvetica").bold(),
// color: Default::default(),
// underline: Default::default(),
// };
// impl TextRun {
// fn with_len(&self, len: usize) -> Self {
// let mut this = self.clone();
// this.len = len;
// this
// }
// }
// let text = "aa bbb cccc ddddd eeee".into();
// let lines = text_system
// .layout_text(
// &text,
// px(16.),
// &[
// normal.with_len(4),
// bold.with_len(5),
// normal.with_len(6),
// bold.with_len(1),
// normal.with_len(7),
// ],
// None,
// )
// .unwrap();
// let line = &lines[0];
// let mut wrapper = LineWrapper::new(
// text_system.font_id(&normal.font).unwrap(),
// px(16.),
// text_system.platform_text_system.clone(),
// );
// assert_eq!(
// wrapper
// .wrap_shaped_line(&text, &line, px(72.))
// .collect::<Vec<_>>(),
// &[
// ShapedBoundary {
// run_ix: 1,
// glyph_ix: 3
// },
// ShapedBoundary {
// run_ix: 2,
// glyph_ix: 3
// },
// ShapedBoundary {
// run_ix: 4,
// glyph_ix: 2
// }
// ],
// );
// });
// }
}