Checkpoint

This commit is contained in:
Antonio Scandurra 2023-10-03 16:30:41 +02:00
parent 08464ee26e
commit e49b411205
6 changed files with 148 additions and 127 deletions

View file

@ -6,8 +6,8 @@ mod mac;
mod test; mod test;
use crate::{ use crate::{
AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, MonochromeSprite, AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, Pixels, Point,
Pixels, Point, RasterizedGlyphId, Result, Scene, ShapedLine, SharedString, Size, RasterizedGlyphId, Result, Scene, ShapedLine, SharedString, Size,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use async_task::Runnable; use async_task::Runnable;
@ -165,13 +165,8 @@ pub trait PlatformTextSystem: Send + Sync {
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>; fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
fn rasterize_glyph( fn rasterize_glyph(
&self, &self,
font_id: FontId, glyph_id: &RasterizedGlyphId,
font_size: f32, ) -> Result<(Bounds<DevicePixels>, Vec<u8>)>;
glyph_id: GlyphId,
subpixel_shift: Point<Pixels>,
scale_factor: f32,
options: RasterizationOptions,
) -> Option<(Bounds<u32>, Vec<u8>)>;
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine; fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine;
fn wrap_line( fn wrap_line(
&self, &self,
@ -185,9 +180,9 @@ pub trait PlatformTextSystem: Send + Sync {
pub trait PlatformAtlas<Key>: Send + Sync { pub trait PlatformAtlas<Key>: Send + Sync {
fn get_or_insert_with( fn get_or_insert_with(
&self, &self,
key: Key, key: &Key,
build: &dyn Fn() -> (Size<DevicePixels>, Vec<u8>), build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Vec<u8>)>,
) -> AtlasTile; ) -> Result<AtlasTile>;
fn clear(&self); fn clear(&self);
} }

View file

@ -1,4 +1,5 @@
use crate::{AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size}; use crate::{AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size};
use anyhow::{anyhow, Result};
use collections::HashMap; use collections::HashMap;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use etagere::BucketedAtlasAllocator; use etagere::BucketedAtlasAllocator;
@ -38,28 +39,30 @@ struct MetalAtlasState<Key> {
impl<Key> PlatformAtlas<Key> for MetalAtlas<Key> impl<Key> PlatformAtlas<Key> for MetalAtlas<Key>
where where
Key: Eq + Hash + Send, Key: Clone + Eq + Hash + Send,
{ {
fn get_or_insert_with( fn get_or_insert_with(
&self, &self,
key: Key, key: &Key,
build: &dyn Fn() -> (Size<DevicePixels>, Vec<u8>), build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Vec<u8>)>,
) -> AtlasTile { ) -> Result<AtlasTile> {
let mut lock = self.0.lock(); let mut lock = self.0.lock();
if let Some(tile) = lock.tiles_by_key.get(&key) { if let Some(tile) = lock.tiles_by_key.get(key) {
return tile.clone(); return Ok(tile.clone());
} else { } else {
let (size, bytes) = build(); let (size, bytes) = build()?;
lock.textures let tile = lock
.textures
.iter_mut() .iter_mut()
.rev() .rev()
.find_map(|texture| texture.allocate(size, &bytes)) .find_map(|texture| texture.allocate(size, &bytes))
.unwrap_or_else(|| { .or_else(|| {
let texture = lock.push_texture(size); let texture = lock.push_texture(size);
texture texture.allocate(size, &bytes)
.allocate(size, &bytes)
.expect("could not allocate a tile in new texture")
}) })
.ok_or_else(|| anyhow!("could not allocate in new texture"))?;
lock.tiles_by_key.insert(key.clone(), tile.clone());
Ok(tile)
} }
} }

View file

@ -1,8 +1,9 @@
use crate::{ use crate::{
point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
GlyphId, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, ShapedGlyph, FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RasterizedGlyphId, Result, ShapedGlyph,
ShapedLine, ShapedRun, SharedString, Size, ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
}; };
use anyhow::anyhow;
use cocoa::appkit::{CGFloat, CGPoint}; use cocoa::appkit::{CGFloat, CGPoint};
use collections::HashMap; use collections::HashMap;
use core_foundation::{ use core_foundation::{
@ -11,11 +12,7 @@ use core_foundation::{
base::{CFRange, TCFType}, base::{CFRange, TCFType},
string::CFString, string::CFString,
}; };
use core_graphics::{ use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext};
base::{kCGImageAlphaPremultipliedLast, CGGlyph},
color_space::CGColorSpace,
context::CGContext,
};
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName}; use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
use font_kit::{ use font_kit::{
font::Font as FontKitFont, font::Font as FontKitFont,
@ -139,21 +136,9 @@ impl PlatformTextSystem for MacTextSystem {
fn rasterize_glyph( fn rasterize_glyph(
&self, &self,
font_id: FontId, glyph_id: &RasterizedGlyphId,
font_size: f32, ) -> Result<(Bounds<DevicePixels>, Vec<u8>)> {
glyph_id: GlyphId, self.0.read().rasterize_glyph(glyph_id)
subpixel_shift: Point<Pixels>,
scale_factor: f32,
options: RasterizationOptions,
) -> Option<(Bounds<u32>, Vec<u8>)> {
self.0.read().rasterize_glyph(
font_id,
font_size,
glyph_id,
subpixel_shift,
scale_factor,
options,
)
} }
fn layout_line( fn layout_line(
@ -247,41 +232,33 @@ impl MacTextSystemState {
fn rasterize_glyph( fn rasterize_glyph(
&self, &self,
font_id: FontId, glyph_id: &RasterizedGlyphId,
font_size: f32, ) -> Result<(Bounds<DevicePixels>, Vec<u8>)> {
glyph_id: GlyphId, let font = &self.fonts[glyph_id.font_id.0];
subpixel_shift: Point<Pixels>, let scale = Transform2F::from_scale(glyph_id.scale_factor);
scale_factor: f32, let glyph_bounds = font.raster_bounds(
options: RasterizationOptions, glyph_id.glyph_id.into(),
) -> Option<(Bounds<u32>, Vec<u8>)> { glyph_id.font_size.into(),
let font = &self.fonts[font_id.0];
let scale = Transform2F::from_scale(scale_factor);
let glyph_bounds = font
.raster_bounds(
glyph_id.into(),
font_size,
scale, scale,
HintingOptions::None, HintingOptions::None,
font_kit::canvas::RasterizationOptions::GrayscaleAa, font_kit::canvas::RasterizationOptions::GrayscaleAa,
) )?;
.ok()?;
if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 { if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
None Err(anyhow!("glyph bounds are empty"))
} else { } else {
// Make room for subpixel variants. // Make room for subpixel variants.
let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32); let subpixel_padding = Vector2I::new(
glyph_id.subpixel_variant.x.min(1) as i32,
glyph_id.subpixel_variant.y.min(1) as i32,
);
let cx_bounds = RectI::new( let cx_bounds = RectI::new(
glyph_bounds.origin(), glyph_bounds.origin(),
glyph_bounds.size() + Vector2I::from(subpixel_padding), glyph_bounds.size() + subpixel_padding,
); );
let mut bytes; let mut bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
let cx; let cx = CGContext::create_bitmap_context(
match options {
RasterizationOptions::Alpha => {
bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
cx = CGContext::create_bitmap_context(
Some(bytes.as_mut_ptr() as *mut _), Some(bytes.as_mut_ptr() as *mut _),
cx_bounds.width() as usize, cx_bounds.width() as usize,
cx_bounds.height() as usize, cx_bounds.height() as usize,
@ -290,20 +267,6 @@ impl MacTextSystemState {
&CGColorSpace::create_device_gray(), &CGColorSpace::create_device_gray(),
kCGImageAlphaOnly, kCGImageAlphaOnly,
); );
}
RasterizationOptions::Bgra => {
bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
cx = CGContext::create_bitmap_context(
Some(bytes.as_mut_ptr() as *mut _),
cx_bounds.width() as usize,
cx_bounds.height() as usize,
8,
cx_bounds.width() as usize * 4,
&CGColorSpace::create_device_rgb(),
kCGImageAlphaPremultipliedLast,
);
}
}
// Move the origin to bottom left and account for scaling, this // Move the origin to bottom left and account for scaling, this
// makes drawing text consistent with the font-kit's raster_bounds. // makes drawing text consistent with the font-kit's raster_bounds.
@ -311,35 +274,31 @@ impl MacTextSystemState {
-glyph_bounds.origin_x() as CGFloat, -glyph_bounds.origin_x() as CGFloat,
(glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat, (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
); );
cx.scale(scale_factor as CGFloat, scale_factor as CGFloat); cx.scale(
glyph_id.scale_factor as CGFloat,
glyph_id.scale_factor as CGFloat,
);
let subpixel_shift = glyph_id
.subpixel_variant
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32 / glyph_id.scale_factor);
cx.set_allows_font_subpixel_positioning(true); cx.set_allows_font_subpixel_positioning(true);
cx.set_should_subpixel_position_fonts(true); cx.set_should_subpixel_position_fonts(true);
cx.set_allows_font_subpixel_quantization(false); cx.set_allows_font_subpixel_quantization(false);
cx.set_should_subpixel_quantize_fonts(false); cx.set_should_subpixel_quantize_fonts(false);
font.native_font() font.native_font()
.clone_with_font_size(font_size as CGFloat) .clone_with_font_size(f32::from(glyph_id.font_size) as CGFloat)
.draw_glyphs( .draw_glyphs(
&[u32::from(glyph_id) as CGGlyph], &[u32::from(glyph_id.glyph_id) as CGGlyph],
&[CGPoint::new( &[CGPoint::new(
(f32::from(subpixel_shift.x) / scale_factor) as CGFloat, subpixel_shift.x as CGFloat,
(f32::from(subpixel_shift.y) / scale_factor) as CGFloat, subpixel_shift.y as CGFloat,
)], )],
cx, cx,
); );
if let RasterizationOptions::Bgra = options { Ok((cx_bounds.into(), bytes))
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
for pixel in bytes.chunks_exact_mut(4) {
pixel.swap(0, 2);
let a = pixel[3] as f32 / 255.;
pixel[0] = (pixel[0] as f32 / a) as u8;
pixel[1] = (pixel[1] as f32 / a) as u8;
pixel[2] = (pixel[2] as f32 / a) as u8;
}
}
Some((cx_bounds.into(), bytes))
} }
} }
@ -549,11 +508,17 @@ impl From<RectF> for Bounds<f32> {
} }
} }
impl From<RectI> for Bounds<u32> { impl From<RectI> for Bounds<DevicePixels> {
fn from(rect: RectI) -> Self { fn from(rect: RectI) -> Self {
Bounds { Bounds {
origin: point(rect.origin_x() as u32, rect.origin_y() as u32), origin: point(
size: size(rect.width() as u32, rect.height() as u32), DevicePixels(rect.origin_x() as u32),
DevicePixels(rect.origin_y() as u32),
),
size: size(
DevicePixels(rect.width() as u32),
DevicePixels(rect.height() as u32),
),
} }
} }
} }

View file

@ -11,7 +11,8 @@ use line_wrapper::*;
pub use text_layout_cache::*; pub use text_layout_cache::*;
use crate::{ use crate::{
px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, UnderlineStyle, px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, RasterizationOptions,
Result, SharedString, Size, UnderlineStyle,
}; };
use collections::HashMap; use collections::HashMap;
use core::fmt; use core::fmt;
@ -30,6 +31,8 @@ pub struct FontId(pub usize);
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)] #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
pub struct FontFamilyId(pub usize); pub struct FontFamilyId(pub usize);
pub const SUBPIXEL_VARIANTS: u8 = 4;
pub struct TextSystem { pub struct TextSystem {
text_layout_cache: Arc<TextLayoutCache>, text_layout_cache: Arc<TextLayoutCache>,
platform_text_system: Arc<dyn PlatformTextSystem>, platform_text_system: Arc<dyn PlatformTextSystem>,
@ -212,6 +215,13 @@ impl TextSystem {
text_system: self.clone(), text_system: self.clone(),
}) })
} }
pub fn rasterize_glyph(
&self,
glyph_id: &RasterizedGlyphId,
) -> Result<(Bounds<DevicePixels>, Vec<u8>)> {
self.platform_text_system.rasterize_glyph(glyph_id)
}
} }
#[derive(Hash, Eq, PartialEq)] #[derive(Hash, Eq, PartialEq)]
@ -370,11 +380,25 @@ pub struct ShapedGlyph {
pub is_emoji: bool, pub is_emoji: bool,
} }
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, PartialEq)]
pub struct RasterizedGlyphId { pub struct RasterizedGlyphId {
font_id: FontId, pub(crate) font_id: FontId,
glyph_id: GlyphId, pub(crate) glyph_id: GlyphId,
font_size: Pixels, pub(crate) font_size: Pixels,
pub(crate) subpixel_variant: Point<u8>,
pub(crate) scale_factor: f32,
}
impl Eq for RasterizedGlyphId {}
impl Hash for RasterizedGlyphId {
fn hash<H: Hasher>(&self, state: &mut H) {
self.font_id.0.hash(state);
self.glyph_id.0.hash(state);
self.font_size.0.to_bits().hash(state);
self.subpixel_variant.hash(state);
self.scale_factor.to_bits().hash(state);
}
} }
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary, black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RasterizedGlyphId, RunStyle,
ShapedLine, ShapedRun, UnderlineStyle, WindowContext, ShapedBoundary, ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
}; };
use anyhow::Result; use anyhow::Result;
use smallvec::SmallVec; use smallvec::SmallVec;

View file

@ -1,8 +1,9 @@
use crate::{ use crate::{
px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, FontId, px, AnyView, AppContext, AtlasTile, AvailableSpace, Bounds, Context, DevicePixels, Effect,
GlyphId, Handle, LayoutId, MainThread, MainThreadOnly, Pixels, PlatformAtlas, PlatformWindow, Element, EntityId, FontId, GlyphId, Handle, LayoutId, MainThread, MainThreadOnly,
Point, RasterizedGlyphId, Reference, Scene, Size, StackContext, StackingOrder, Style, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, RasterizedGlyphId, Reference,
TaffyLayoutEngine, WeakHandle, WindowOptions, Scene, Size, StackContext, StackingOrder, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
SUBPIXEL_VARIANTS,
}; };
use anyhow::Result; use anyhow::Result;
use futures::Future; use futures::Future;
@ -161,6 +162,39 @@ impl<'a, 'w> WindowContext<'a, 'w> {
}) })
} }
pub fn rasterize_glyph(
&self,
font_id: FontId,
glyph_id: GlyphId,
font_size: Pixels,
scale_factor: f32,
target_position: Point<Pixels>,
) -> Result<(AtlasTile, Point<DevicePixels>)> {
let target_position = target_position * scale_factor;
let subpixel_variant = Point {
x: (target_position.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
y: (target_position.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
};
let rasterized_glyph_id = RasterizedGlyphId {
font_id,
glyph_id,
font_size,
subpixel_variant,
scale_factor,
};
let mut offset = Default::default();
let tile = self
.window
.glyph_atlas
.get_or_insert_with(&rasterized_glyph_id, &mut || {
let (bounds, pixels) = self.text_system().rasterize_glyph(&rasterized_glyph_id)?;
offset = bounds.origin;
Ok((bounds.size, pixels))
})?;
Ok((tile, offset))
}
pub(crate) fn draw(&mut self) -> Result<()> { pub(crate) fn draw(&mut self) -> Result<()> {
let unit_entity = self.unit_entity.clone(); let unit_entity = self.unit_entity.clone();
self.update_entity(&unit_entity, |_, cx| { self.update_entity(&unit_entity, |_, cx| {