diff --git a/src/portal.rs b/src/portal.rs index 5f9905b6..d601475f 100644 --- a/src/portal.rs +++ b/src/portal.rs @@ -2,6 +2,7 @@ mod ptl_display; mod ptl_remote_desktop; mod ptl_render_ctx; mod ptl_screencast; +mod ptl_text; mod ptr_gui; use { diff --git a/src/portal/ptl_text.rs b/src/portal/ptl_text.rs new file mode 100644 index 00000000..81112393 --- /dev/null +++ b/src/portal/ptl_text.rs @@ -0,0 +1,101 @@ +use { + crate::{ + format::ARGB8888, + gfx_api::{GfxContext, GfxTexture}, + pango::{ + consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE}, + CairoContext, CairoImageSurface, PangoCairoContext, PangoFontDescription, PangoLayout, + }, + rect::Rect, + theme::Color, + }, + std::{ops::Neg, rc::Rc, sync::Arc}, +}; + +struct Data { + image: Rc, + cctx: Rc, + _pctx: Rc, + _fd: PangoFontDescription, + layout: PangoLayout, +} + +fn create_data(font: &str, width: i32, height: i32, scale: Option) -> Option { + let image = CairoImageSurface::new_image_surface(CAIRO_FORMAT_ARGB32, width, height).ok()?; + let cctx = image.create_context().ok()?; + let pctx = cctx.create_pango_context().ok()?; + let mut fd = PangoFontDescription::from_string(font); + if let Some(scale) = scale { + fd.set_size((fd.size() as f64 * scale).round() as _); + } + let layout = pctx.create_layout().ok()?; + layout.set_font_description(&fd); + Some(Data { + image, + cctx, + _pctx: pctx, + _fd: fd, + layout, + }) +} + +fn measure(font: &str, text: &str, scale: Option, full: bool) -> Option { + let data = create_data(font, 1, 1, scale)?; + data.layout.set_text(text); + let mut res = TextMeasurement::default(); + res.ink_rect = data.layout.inc_pixel_rect(); + if full { + res.logical_rect = data.layout.logical_pixel_rect(); + res.baseline = data.layout.pixel_baseline(); + } + Some(res) +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct TextMeasurement { + pub ink_rect: Rect, + pub logical_rect: Rect, + pub baseline: i32, +} + +pub fn render( + ctx: &Rc, + height: Option, + font: &Arc, + text: &str, + color: Color, + scale: Option, + include_measurements: bool, +) -> Option<(Rc, TextMeasurement)> { + let measurement = measure(font, text, scale, include_measurements)?; + let y = match height { + Some(_) => None, + _ => Some(measurement.ink_rect.y1().neg()), + }; + let x = measurement.ink_rect.x1().neg(); + let width = measurement.ink_rect.width(); + let height = height.unwrap_or(measurement.ink_rect.height()); + let data = create_data(font, width, height, scale)?; + data.layout.set_text(text); + let font_height = data.layout.pixel_size().1; + data.cctx.set_operator(CAIRO_OPERATOR_SOURCE); + data.cctx + .set_source_rgba(color.r as _, color.g as _, color.b as _, color.a as _); + let y = y.unwrap_or((height - font_height) / 2); + data.cctx.move_to(x as f64, y as f64); + data.layout.show_layout(); + data.image.flush(); + let bytes = data.image.data().ok()?; + ctx.clone() + .shmem_texture( + None, + bytes, + ARGB8888, + width, + height, + data.image.stride(), + None, + ) + .ok() + .map(|t| (t.into_texture(), measurement)) +} diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 23ba3ff6..fd5a1510 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -5,12 +5,16 @@ use { cursor::KnownCursor, fixed::Fixed, format::ARGB8888, - gfx_api::{needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, ReleaseSync}, + gfx_api::{ + needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, + }, ifs::zwlr_layer_shell_v1::OVERLAY, - portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, + portal::{ + ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, + ptl_text::{self, TextMeasurement}, + }, renderer::renderer_base::RendererBase, scale::Scale, - text::{self, TextMeasurement, TextTexture}, theme::Color, utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, @@ -31,10 +35,10 @@ use { }, ahash::AHashSet, std::{ - borrow::Cow, cell::{Cell, RefCell}, ops::Deref, rc::Rc, + sync::Arc, }, }; @@ -117,8 +121,8 @@ pub struct Button { pub bg_color: Cell, pub bg_hover_color: Cell, pub text: RefCell, - pub font: RefCell>, - pub tex: CloneCell>, + pub font: Arc, + pub tex: CloneCell>>, pub owner: CloneCell>>, } @@ -139,7 +143,7 @@ impl Default for Button { bg_color: Cell::new(Color::from_gray(255)), bg_hover_color: Cell::new(Color::from_gray(255)), text: Default::default(), - font: RefCell::new(DEFAULT_FONT.into()), + font: Arc::new(DEFAULT_FONT.to_string()), tex: Default::default(), owner: Default::default(), } @@ -162,21 +166,16 @@ impl GuiElement for Button { _max_width: f32, _max_height: f32, ) -> (f32, f32) { - let old_tex = self.tex.take(); - let font = self.font.borrow_mut(); let text = self.text.borrow_mut(); - let tex = text::render_fitting2( + let tex = ptl_text::render( ctx, - old_tex, None, - &font, + &self.font, &text, Color::from_gray(0), - false, Some(scale as _), true, - ) - .ok(); + ); let (tex, msmt) = match tex { Some((a, b)) => (Some(a), Some(b)), _ => (None, None), @@ -215,7 +214,7 @@ impl GuiElement for Button { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get()); r.render_texture( - &tex.texture, + &tex, None, tx.round() as _, ty.round() as _, @@ -262,16 +261,16 @@ const DEFAULT_FONT: &str = "sans-serif 16"; pub struct Label { pub data: GuiElementData, - pub font: RefCell>, + pub font: Arc, pub text: RefCell, - pub tex: CloneCell>, + pub tex: CloneCell>>, } impl Default for Label { fn default() -> Self { Self { data: Default::default(), - font: RefCell::new(DEFAULT_FONT.into()), + font: Arc::new(DEFAULT_FONT.into()), text: RefCell::new("".to_string()), tex: Default::default(), } @@ -290,24 +289,19 @@ impl GuiElement for Label { _max_width: f32, _max_height: f32, ) -> (f32, f32) { - let old_tex = self.tex.take(); let text = self.text.borrow_mut(); - let font = self.font.borrow_mut(); - let tex = text::render_fitting2( + let tex = ptl_text::render( ctx, - old_tex, None, - &font, + &self.font, &text, Color::from_gray(255), - false, Some(scale as _), false, - ) - .ok(); + ); let (tex, width, height) = match tex { Some((t, _)) => { - let (width, height) = t.texture.size(); + let (width, height) = t.size(); (Some(t.clone()), width, height) } _ => (None, 0, 0), @@ -320,7 +314,7 @@ impl GuiElement for Label { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x, y); r.render_texture( - &tex.texture, + &tex, None, tx.round() as _, ty.round() as _,