Checkpoint

This commit is contained in:
Nathan Sobo 2023-10-03 13:52:10 -06:00
parent a8c1958c75
commit 3b27d41c72
9 changed files with 234 additions and 93 deletions

View file

@ -618,6 +618,12 @@ impl From<ScaledPixels> for DevicePixels {
}
}
impl From<DevicePixels> for ScaledPixels {
fn from(device: DevicePixels) -> Self {
ScaledPixels(device.0 as f32)
}
}
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
pub struct Rems(f32);
@ -802,3 +808,76 @@ impl From<()> for Length {
Self::Definite(DefiniteLength::default())
}
}
pub trait IsZero {
fn is_zero(&self) -> bool;
}
impl IsZero for DevicePixels {
fn is_zero(&self) -> bool {
self.0 == 0
}
}
impl IsZero for ScaledPixels {
fn is_zero(&self) -> bool {
self.0 == 0.
}
}
impl IsZero for Pixels {
fn is_zero(&self) -> bool {
self.0 == 0.
}
}
impl IsZero for Rems {
fn is_zero(&self) -> bool {
self.0 == 0.
}
}
impl IsZero for AbsoluteLength {
fn is_zero(&self) -> bool {
match self {
AbsoluteLength::Pixels(pixels) => pixels.is_zero(),
AbsoluteLength::Rems(rems) => rems.is_zero(),
}
}
}
impl IsZero for DefiniteLength {
fn is_zero(&self) -> bool {
match self {
DefiniteLength::Absolute(length) => length.is_zero(),
DefiniteLength::Fraction(fraction) => *fraction == 0.,
}
}
}
impl IsZero for Length {
fn is_zero(&self) -> bool {
match self {
Length::Definite(length) => length.is_zero(),
Length::Auto => false,
}
}
}
impl<T: IsZero + Debug + Clone> IsZero for Point<T> {
fn is_zero(&self) -> bool {
self.x.is_zero() && self.y.is_zero()
}
}
impl<T: IsZero + Debug + Clone> IsZero for Size<T> {
fn is_zero(&self) -> bool {
self.width.is_zero() && self.height.is_zero()
}
}
impl<T: IsZero + Debug + Clone> IsZero for Bounds<T> {
fn is_zero(&self) -> bool {
self.origin.is_zero() && self.size.is_zero()
}
}

View file

@ -6,8 +6,8 @@ mod mac;
mod test;
use crate::{
AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, Pixels, Point,
RasterizeGlyphParams, Result, Scene, ShapedLine, SharedString, Size,
AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId,
GlyphRasterizationParams, Pixels, Point, Result, Scene, ShapedLine, SharedString, Size,
};
use anyhow::anyhow;
use async_task::Runnable;
@ -147,7 +147,7 @@ pub trait PlatformWindow {
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
fn draw(&self, scene: Scene);
fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<RasterizeGlyphParams>>;
fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<GlyphRasterizationParams>>;
}
pub trait PlatformDispatcher: Send + Sync {
@ -163,9 +163,13 @@ pub trait PlatformTextSystem: Send + Sync {
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
fn glyph_raster_bounds(
&self,
params: &GlyphRasterizationParams,
) -> Result<Bounds<DevicePixels>>;
fn rasterize_glyph(
&self,
glyph_id: &RasterizeGlyphParams,
params: &GlyphRasterizationParams,
) -> Result<(Size<DevicePixels>, Vec<u8>)>;
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine;
fn wrap_line(

View file

@ -1,6 +1,6 @@
use crate::{
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, Quad,
RasterizeGlyphParams, Scene, Size,
point, size, AtlasTextureId, DevicePixels, GlyphRasterizationParams, MetalAtlas,
MonochromeSprite, Quad, Scene, Size,
};
use cocoa::{
base::{NO, YES},
@ -22,7 +22,7 @@ pub struct MetalRenderer {
sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
instances: metal::Buffer,
glyph_atlas: Arc<MetalAtlas<RasterizeGlyphParams>>,
glyph_atlas: Arc<MetalAtlas<GlyphRasterizationParams>>,
}
impl MetalRenderer {
@ -126,7 +126,7 @@ impl MetalRenderer {
&*self.layer
}
pub fn glyph_atlas(&self) -> &Arc<MetalAtlas<RasterizeGlyphParams>> {
pub fn glyph_atlas(&self) -> &Arc<MetalAtlas<GlyphRasterizationParams>> {
&self.glyph_atlas
}

View file

@ -1,6 +1,6 @@
use crate::{
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RasterizeGlyphParams, Result,
FontWeight, GlyphId, GlyphRasterizationParams, Pixels, PlatformTextSystem, Point, Result,
ShapedGlyph, ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
};
use anyhow::anyhow;
@ -134,9 +134,16 @@ impl PlatformTextSystem for MacTextSystem {
self.0.read().glyph_for_char(font_id, ch)
}
fn glyph_raster_bounds(
&self,
params: &GlyphRasterizationParams,
) -> Result<Bounds<DevicePixels>> {
self.0.read().raster_bounds(params)
}
fn rasterize_glyph(
&self,
glyph_id: &RasterizeGlyphParams,
glyph_id: &GlyphRasterizationParams,
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
self.0.read().rasterize_glyph(glyph_id)
}
@ -230,37 +237,54 @@ impl MacTextSystemState {
})
}
fn raster_bounds(&self, params: &GlyphRasterizationParams) -> Result<Bounds<DevicePixels>> {
let font = &self.fonts[params.font_id.0];
let scale = Transform2F::from_scale(params.scale_factor);
Ok(font
.raster_bounds(
params.glyph_id.into(),
params.font_size.into(),
scale,
HintingOptions::None,
font_kit::canvas::RasterizationOptions::GrayscaleAa,
)?
.into())
}
fn rasterize_glyph(
&self,
glyph_id: &RasterizeGlyphParams,
params: &GlyphRasterizationParams,
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
let font = &self.fonts[glyph_id.font_id.0];
let scale = Transform2F::from_scale(glyph_id.scale_factor);
let glyph_bounds = font.raster_bounds(
glyph_id.glyph_id.into(),
glyph_id.font_size.into(),
scale,
HintingOptions::None,
font_kit::canvas::RasterizationOptions::GrayscaleAa,
)?;
let glyph_bounds = self.raster_bounds(params)?;
if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
// let scale = Transform2F::from_scale(params.scale_factor);
// let glyph_bounds = font.raster_bounds(
// params.glyph_id.into(),
// params.font_size.into(),
// scale,
// HintingOptions::None,
// font_kit::canvas::RasterizationOptions::GrayscaleAa,
// )?;
if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
Err(anyhow!("glyph bounds are empty"))
} else {
// Make room for subpixel variants.
let subpixel_padding = Vector2I::new(
glyph_id.subpixel_variant.x.min(1) as i32,
glyph_id.subpixel_variant.y.min(1) as i32,
);
let bitmap_size = glyph_bounds.size() + subpixel_padding;
// Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing.
let mut bitmap_size = glyph_bounds.size;
if params.subpixel_variant.x > 0 {
bitmap_size.width += DevicePixels(1);
}
if params.subpixel_variant.y > 0 {
bitmap_size.height += DevicePixels(1);
}
let mut bytes = vec![0; bitmap_size.x() as usize * bitmap_size.y() as usize];
let mut bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
let cx = CGContext::create_bitmap_context(
Some(bytes.as_mut_ptr() as *mut _),
bitmap_size.x() as usize,
bitmap_size.y() as usize,
bitmap_size.width.0 as usize,
bitmap_size.height.0 as usize,
8,
bitmap_size.x() as usize,
bitmap_size.width.0 as usize,
&CGColorSpace::create_device_gray(),
kCGImageAlphaOnly,
);
@ -268,26 +292,27 @@ impl MacTextSystemState {
// Move the origin to bottom left and account for scaling, this
// makes drawing text consistent with the font-kit's raster_bounds.
cx.translate(
-glyph_bounds.origin_x() as CGFloat,
(glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
-glyph_bounds.origin.x.0 as CGFloat,
(glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat,
);
cx.scale(
glyph_id.scale_factor as CGFloat,
glyph_id.scale_factor as CGFloat,
params.scale_factor as CGFloat,
params.scale_factor as CGFloat,
);
let subpixel_shift = glyph_id
let subpixel_shift = params
.subpixel_variant
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32 / glyph_id.scale_factor);
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32 / params.scale_factor);
cx.set_allows_font_subpixel_positioning(true);
cx.set_should_subpixel_position_fonts(true);
cx.set_allows_font_subpixel_quantization(false);
cx.set_should_subpixel_quantize_fonts(false);
font.native_font()
.clone_with_font_size(f32::from(glyph_id.font_size) as CGFloat)
self.fonts[params.font_id.0]
.native_font()
.clone_with_font_size(f32::from(params.font_size) as CGFloat)
.draw_glyphs(
&[u32::from(glyph_id.glyph_id) as CGGlyph],
&[u32::from(params.glyph_id) as CGGlyph],
&[CGPoint::new(
subpixel_shift.x as CGFloat,
subpixel_shift.y as CGFloat,

View file

@ -1,10 +1,10 @@
use super::{ns_string, MetalRenderer, NSRange};
use crate::{
point, px, size, AnyWindowHandle, Bounds, Event, KeyDownEvent, Keystroke, MacScreen, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, NSRectExt,
Pixels, Platform, PlatformAtlas, PlatformDispatcher, PlatformInputHandler, PlatformScreen,
PlatformWindow, Point, RasterizeGlyphParams, Scene, Size, Timer, WindowAppearance,
WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
point, px, size, AnyWindowHandle, Bounds, Event, GlyphRasterizationParams, KeyDownEvent,
Keystroke, MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
MouseMovedEvent, MouseUpEvent, NSRectExt, Pixels, Platform, PlatformAtlas, PlatformDispatcher,
PlatformInputHandler, PlatformScreen, PlatformWindow, Point, Scene, Size, Timer,
WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
};
use block::ConcreteBlock;
use cocoa::{
@ -886,7 +886,7 @@ impl PlatformWindow for MacWindow {
}
}
fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<RasterizeGlyphParams>> {
fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<GlyphRasterizationParams>> {
self.0.lock().renderer.glyph_atlas().clone()
}
}

View file

@ -1,6 +1,6 @@
use std::{iter::Peekable, mem};
use super::{Bounds, Hsla, Pixels, Point};
use super::{Bounds, Hsla, Point};
use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledPixels};
use collections::BTreeMap;
use smallvec::SmallVec;
@ -35,7 +35,7 @@ impl Scene {
let primitive = primitive.into();
match primitive {
Primitive::Quad(mut quad) => {
Primitive::Quad(quad) => {
layer.quads.push(quad);
}
Primitive::Sprite(sprite) => {
@ -233,9 +233,9 @@ impl From<Quad> for Primitive {
#[repr(C)]
pub struct MonochromeSprite {
pub order: u32,
pub bounds: Bounds<Pixels>,
pub clip_bounds: Bounds<Pixels>,
pub clip_corner_radii: Corners<Pixels>,
pub bounds: Bounds<ScaledPixels>,
pub clip_bounds: Bounds<ScaledPixels>,
pub clip_corner_radii: Corners<ScaledPixels>,
pub color: Hsla,
pub tile: AtlasTile,
}

View file

@ -215,9 +215,13 @@ impl TextSystem {
})
}
pub fn raster_bounds(&self, params: &GlyphRasterizationParams) -> Result<Bounds<DevicePixels>> {
self.platform_text_system.glyph_raster_bounds(params)
}
pub fn rasterize_glyph(
&self,
glyph_id: &RasterizeGlyphParams,
glyph_id: &GlyphRasterizationParams,
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
self.platform_text_system.rasterize_glyph(glyph_id)
}
@ -380,7 +384,7 @@ pub struct ShapedGlyph {
}
#[derive(Clone, Debug, PartialEq)]
pub struct RasterizeGlyphParams {
pub struct GlyphRasterizationParams {
pub(crate) font_id: FontId,
pub(crate) glyph_id: GlyphId,
pub(crate) font_size: Pixels,
@ -388,9 +392,9 @@ pub struct RasterizeGlyphParams {
pub(crate) scale_factor: f32,
}
impl Eq for RasterizeGlyphParams {}
impl Eq for GlyphRasterizationParams {}
impl Hash for RasterizeGlyphParams {
impl Hash for GlyphRasterizationParams {
fn hash<H: Hasher>(&self, state: &mut H) {
self.font_id.0.hash(state);
self.glyph_id.0.hash(state);

View file

@ -1,11 +1,10 @@
use crate::{
black, point, px, Bounds, Corners, FontId, Hsla, Layout, MonochromeSprite, Pixels, Point,
RunStyle, ShapedBoundary, ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
};
use anyhow::Result;
use smallvec::SmallVec;
use std::sync::Arc;
use util::ResultExt;
#[derive(Default, Debug, Clone)]
pub struct Line {
@ -162,36 +161,14 @@ impl Line {
if glyph.is_emoji {
todo!()
} else {
if let Some(tile) = cx
.rasterize_glyph(
run.font_id,
glyph.id,
self.layout.font_size,
glyph_origin,
cx.scale_factor(),
)
.log_err()
{
let layer_id = cx.current_layer_id();
let bounds = Bounds {
origin: glyph_origin + todo!(),
size: todo!(),
};
// cx.text_system().raster_bounds()
cx.scene().insert(
layer_id,
MonochromeSprite {
order: layout.order,
bounds,
clip_bounds: bounds,
clip_corner_radii: Corners::default(),
color,
tile,
},
);
}
cx.paint_glyph(
glyph_origin,
layout.order,
run.font_id,
glyph.id,
self.layout.font_size,
color,
)?;
}
}

View file

@ -1,8 +1,9 @@
use crate::{
px, AnyView, AppContext, AtlasTile, AvailableSpace, Bounds, Context, Effect, Element, EntityId,
FontId, GlyphId, Handle, LayoutId, MainThread, MainThreadOnly, Pixels, PlatformAtlas,
PlatformWindow, Point, RasterizeGlyphParams, Reference, Scene, Size, StackContext,
StackingOrder, Style, TaffyLayoutEngine, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
FontId, GlyphId, GlyphRasterizationParams, Handle, Hsla, IsZero, LayoutId, MainThread,
MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, Reference,
Scene, Size, StackContext, StackingOrder, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
SUBPIXEL_VARIANTS,
};
use anyhow::Result;
use futures::Future;
@ -15,7 +16,7 @@ pub struct AnyWindow {}
pub struct Window {
handle: AnyWindowHandle,
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
glyph_atlas: Arc<dyn PlatformAtlas<RasterizeGlyphParams>>,
glyph_atlas: Arc<dyn PlatformAtlas<GlyphRasterizationParams>>,
rem_size: Pixels,
content_size: Size<Pixels>,
layout_engine: TaffyLayoutEngine,
@ -165,6 +166,57 @@ impl<'a, 'w> WindowContext<'a, 'w> {
})
}
pub fn paint_glyph(
&mut self,
origin: Point<Pixels>,
order: u32,
font_id: FontId,
glyph_id: GlyphId,
font_size: Pixels,
color: Hsla,
) -> Result<()> {
let scale_factor = self.scale_factor();
let origin = origin.scale(scale_factor);
let subpixel_variant = Point {
x: (origin.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
y: (origin.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
};
let params = GlyphRasterizationParams {
font_id,
glyph_id,
font_size,
subpixel_variant,
scale_factor,
};
let raster_bounds = self.text_system().raster_bounds(&params)?;
if !raster_bounds.is_zero() {
let layer_id = self.current_layer_id();
let bounds = Bounds {
origin: origin + raster_bounds.origin.map(Into::into),
size: raster_bounds.size.map(Into::into),
};
let tile = self
.window
.glyph_atlas
.get_or_insert_with(&params, &mut || self.text_system().rasterize_glyph(&params))?;
self.window.scene.insert(
layer_id,
MonochromeSprite {
order,
bounds,
clip_bounds: bounds,
clip_corner_radii: Default::default(),
color,
tile,
},
);
}
Ok(())
}
pub fn rasterize_glyph(
&self,
font_id: FontId,
@ -178,7 +230,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
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 = RasterizeGlyphParams {
let rasterized_glyph_id = GlyphRasterizationParams {
font_id,
glyph_id,
font_size,