diff --git a/internal/core/Cargo.toml b/internal/core/Cargo.toml index 7b043e5698..1cfe8721cb 100644 --- a/internal/core/Cargo.toml +++ b/internal/core/Cargo.toml @@ -48,7 +48,7 @@ unsafe-single-threaded = [] unicode = ["unicode-script", "unicode-linebreak"] -software-renderer-systemfonts = ["shared-fontique", "rustybuzz", "fontdue", "software-renderer"] +software-renderer-systemfonts = ["shared-fontique", "rustybuzz", "fontdue", "software-renderer", "shared-parley"] software-renderer = ["bytemuck"] image-decoders = ["dep:image", "dep:clru"] diff --git a/internal/core/software_renderer.rs b/internal/core/software_renderer.rs index 314dd50e2a..c8ff25c27c 100644 --- a/internal/core/software_renderer.rs +++ b/internal/core/software_renderer.rs @@ -717,7 +717,35 @@ impl RendererSealed for SoftwareRenderer { scale_factor: ScaleFactor, text_wrap: TextWrap, ) -> LogicalSize { - fonts::text_size(font_request, text, max_width, scale_factor, text_wrap) + let font = fonts::match_font(&font_request, scale_factor); + + match (font, parley_disabled()) { + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(_), false) => { + sharedparley::text_size(font_request, text, max_width, scale_factor, text_wrap) + } + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(vf), true) => { + let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor); + let (longest_line_width, height) = layout.text_size( + text, + max_width.map(|max_width| (max_width.cast() * scale_factor).cast()), + text_wrap, + ); + (PhysicalSize::from_lengths(longest_line_width, height).cast() / scale_factor) + .cast() + } + (fonts::Font::PixelFont(pf), _) => { + let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor); + let (longest_line_width, height) = layout.text_size( + text, + max_width.map(|max_width| (max_width.cast() * scale_factor).cast()), + text_wrap, + ); + (PhysicalSize::from_lengths(longest_line_width, height).cast() / scale_factor) + .cast() + } + } } fn font_metrics( @@ -725,7 +753,39 @@ impl RendererSealed for SoftwareRenderer { font_request: crate::graphics::FontRequest, scale_factor: ScaleFactor, ) -> crate::items::FontMetrics { - fonts::font_metrics(font_request, scale_factor) + let font = fonts::match_font(&font_request, scale_factor); + + match (font, parley_disabled()) { + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(_), false) => sharedparley::font_metrics(font_request), + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(font), true) => { + let ascent: LogicalLength = (font.ascent().cast() / scale_factor).cast(); + let descent: LogicalLength = (font.descent().cast() / scale_factor).cast(); + let x_height: LogicalLength = (font.x_height().cast() / scale_factor).cast(); + let cap_height: LogicalLength = (font.cap_height().cast() / scale_factor).cast(); + + crate::items::FontMetrics { + ascent: ascent.get() as _, + descent: descent.get() as _, + x_height: x_height.get() as _, + cap_height: cap_height.get() as _, + } + } + (fonts::Font::PixelFont(font), _) => { + let ascent: LogicalLength = (font.ascent().cast() / scale_factor).cast(); + let descent: LogicalLength = (font.descent().cast() / scale_factor).cast(); + let x_height: LogicalLength = (font.x_height().cast() / scale_factor).cast(); + let cap_height: LogicalLength = (font.cap_height().cast() / scale_factor).cast(); + + crate::items::FontMetrics { + ascent: ascent.get() as _, + descent: descent.get() as _, + x_height: x_height.get() as _, + cap_height: cap_height.get() as _, + } + } + } } fn text_input_byte_offset_for_position( @@ -735,20 +795,30 @@ impl RendererSealed for SoftwareRenderer { font_request: crate::graphics::FontRequest, scale_factor: ScaleFactor, ) -> usize { - let visual_representation = text_input.visual_representation(None); - let font = fonts::match_font(&font_request, scale_factor); - let width = (text_input.width().cast() * scale_factor).cast(); - let height = (text_input.height().cast() * scale_factor).cast(); + match (font, parley_disabled()) { + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(_), false) => { + sharedparley::text_input_byte_offset_for_position( + text_input, + pos, + font_request, + scale_factor, + ) + } + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(vf), true) => { + let visual_representation = text_input.visual_representation(None); - let pos = (pos.cast() * scale_factor) - .clamp(euclid::point2(0., 0.), euclid::point2(i16::MAX, i16::MAX).cast()) - .cast(); + let width = (text_input.width().cast() * scale_factor).cast(); + let height = (text_input.height().cast() * scale_factor).cast(); - match font { - fonts::Font::PixelFont(pf) => { - let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor); + let pos = (pos.cast() * scale_factor) + .clamp(euclid::point2(0., 0.), euclid::point2(i16::MAX, i16::MAX).cast()) + .cast(); + + let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor); let paragraph = TextParagraphLayout { string: &visual_representation.text, @@ -766,9 +836,17 @@ impl RendererSealed for SoftwareRenderer { paragraph.byte_offset_for_position((pos.x_length(), pos.y_length())), ) } - #[cfg(feature = "software-renderer-systemfonts")] - fonts::Font::VectorFont(vf) => { - let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor); + (fonts::Font::PixelFont(pf), _) => { + let visual_representation = text_input.visual_representation(None); + + let width = (text_input.width().cast() * scale_factor).cast(); + let height = (text_input.height().cast() * scale_factor).cast(); + + let pos = (pos.cast() * scale_factor) + .clamp(euclid::point2(0., 0.), euclid::point2(i16::MAX, i16::MAX).cast()) + .cast(); + + let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor); let paragraph = TextParagraphLayout { string: &visual_representation.text, @@ -796,33 +874,25 @@ impl RendererSealed for SoftwareRenderer { font_request: crate::graphics::FontRequest, scale_factor: ScaleFactor, ) -> LogicalRect { - let visual_representation = text_input.visual_representation(None); - let font = fonts::match_font(&font_request, scale_factor); - let width = (text_input.width().cast() * scale_factor).cast(); - let height = (text_input.height().cast() * scale_factor).cast(); - - let (cursor_position, cursor_height) = match font { - fonts::Font::PixelFont(pf) => { - let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor); - - let paragraph = TextParagraphLayout { - string: &visual_representation.text, - layout, - max_width: width, - max_height: height, - horizontal_alignment: text_input.horizontal_alignment(), - vertical_alignment: text_input.vertical_alignment(), - wrap: text_input.wrap(), - overflow: TextOverflow::Clip, - single_line: false, - }; - - (paragraph.cursor_pos_for_byte_offset(byte_offset), pf.height()) + match (font, parley_disabled()) { + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(_), false) => { + sharedparley::text_input_cursor_rect_for_byte_offset( + text_input, + byte_offset, + font_request, + scale_factor, + ) } #[cfg(feature = "software-renderer-systemfonts")] - fonts::Font::VectorFont(vf) => { + (fonts::Font::VectorFont(vf), true) => { + let visual_representation = text_input.visual_representation(None); + + let width = (text_input.width().cast() * scale_factor).cast(); + let height = (text_input.height().cast() * scale_factor).cast(); + let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor); let paragraph = TextParagraphLayout { @@ -837,20 +907,55 @@ impl RendererSealed for SoftwareRenderer { single_line: false, }; - (paragraph.cursor_pos_for_byte_offset(byte_offset), vf.height()) - } - }; + let cursor_position = paragraph.cursor_pos_for_byte_offset(byte_offset); + let cursor_height = vf.height(); - (PhysicalRect::new( - PhysicalPoint::from_lengths(cursor_position.0, cursor_position.1), - PhysicalSize::from_lengths( - (text_input.text_cursor_width().cast() * scale_factor).cast(), - cursor_height, - ), - ) - .cast() - / scale_factor) - .cast() + (PhysicalRect::new( + PhysicalPoint::from_lengths(cursor_position.0, cursor_position.1), + PhysicalSize::from_lengths( + (text_input.text_cursor_width().cast() * scale_factor).cast(), + cursor_height, + ), + ) + .cast() + / scale_factor) + .cast() + } + (fonts::Font::PixelFont(pf), _) => { + let visual_representation = text_input.visual_representation(None); + + let width = (text_input.width().cast() * scale_factor).cast(); + let height = (text_input.height().cast() * scale_factor).cast(); + + let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor); + + let paragraph = TextParagraphLayout { + string: &visual_representation.text, + layout, + max_width: width, + max_height: height, + horizontal_alignment: text_input.horizontal_alignment(), + vertical_alignment: text_input.vertical_alignment(), + wrap: text_input.wrap(), + overflow: TextOverflow::Clip, + single_line: false, + }; + + let cursor_position = paragraph.cursor_pos_for_byte_offset(byte_offset); + let cursor_height = pf.height(); + + (PhysicalRect::new( + PhysicalPoint::from_lengths(cursor_position.0, cursor_position.1), + PhysicalSize::from_lengths( + (text_input.text_cursor_width().cast() * scale_factor).cast(), + cursor_height, + ), + ) + .cast() + / scale_factor) + .cast() + } + } } fn free_graphics_resources( @@ -937,6 +1042,15 @@ impl RendererSealed for SoftwareRenderer { } } +fn parley_disabled() -> bool { + #[cfg(feature = "software-renderer-systemfonts")] + { + std::env::var("SLINT_SOFTWARE_RENDERER_PARLEY_DISABLED").is_ok() + } + #[cfg(not(feature = "software-renderer-systemfonts"))] + false +} + fn render_window_frame_by_line( window: &WindowInner, background: Brush, @@ -2272,36 +2386,41 @@ impl crate::item_rendering::ItemRenderer for SceneBuilder<'_, T size: LogicalSize, _cache: &CachedRenderingData, ) { - let string = text.text(); - if string.trim().is_empty() { - return; - } - let geom = LogicalRect::from(size); - if !self.should_draw(&geom) { - return; - } - let font_request = text.font_request(self_rc); - let color = self.alpha_color(text.color().color()); - let max_size = (geom.size.cast() * self.scale_factor).cast(); - - // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside - // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below). - // FIXME: we should allow drawing outside of the Text element's boundaries. - let physical_clip = if let Some(logical_clip) = self.current_state.clip.intersection(&geom) - { - logical_clip.cast() * self.scale_factor - } else { - return; // This should have been caught earlier already - }; - let offset = self.current_state.offset.to_vector().cast() * self.scale_factor; - let font = fonts::match_font(&font_request, self.scale_factor); - match font { - fonts::Font::PixelFont(pf) => { - let layout = fonts::text_layout_for_font(&pf, &font_request, self.scale_factor); + match (font, parley_disabled()) { + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(_), false) => { + sharedparley::draw_text(self, text, Some(text.font_request(self_rc)), size); + } + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(vf), true) => { + let string = text.text(); + if string.trim().is_empty() { + return; + } + let geom = LogicalRect::from(size); + if !self.should_draw(&geom) { + return; + } + + let color = self.alpha_color(text.color().color()); + let max_size = (geom.size.cast() * self.scale_factor).cast(); + + // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside + // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below). + // FIXME: we should allow drawing outside of the Text element's boundaries. + let physical_clip = + if let Some(logical_clip) = self.current_state.clip.intersection(&geom) { + logical_clip.cast() * self.scale_factor + } else { + return; // This should have been caught earlier already + }; + let offset = self.current_state.offset.to_vector().cast() * self.scale_factor; + + let layout = fonts::text_layout_for_font(&vf, &font_request, self.scale_factor); let (horizontal_alignment, vertical_alignment) = text.alignment(); let paragraph = TextParagraphLayout { @@ -2318,9 +2437,31 @@ impl crate::item_rendering::ItemRenderer for SceneBuilder<'_, T self.draw_text_paragraph(¶graph, physical_clip, offset, color, None); } - #[cfg(feature = "software-renderer-systemfonts")] - fonts::Font::VectorFont(vf) => { - let layout = fonts::text_layout_for_font(&vf, &font_request, self.scale_factor); + (fonts::Font::PixelFont(pf), _) => { + let string = text.text(); + if string.trim().is_empty() { + return; + } + let geom = LogicalRect::from(size); + if !self.should_draw(&geom) { + return; + } + + let color = self.alpha_color(text.color().color()); + let max_size = (geom.size.cast() * self.scale_factor).cast(); + + // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside + // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below). + // FIXME: we should allow drawing outside of the Text element's boundaries. + let physical_clip = + if let Some(logical_clip) = self.current_state.clip.intersection(&geom) { + logical_clip.cast() * self.scale_factor + } else { + return; // This should have been caught earlier already + }; + let offset = self.current_state.offset.to_vector().cast() * self.scale_factor; + + let layout = fonts::text_layout_for_font(&pf, &font_request, self.scale_factor); let (horizontal_alignment, vertical_alignment) = text.alignment(); let paragraph = TextParagraphLayout { @@ -2346,59 +2487,52 @@ impl crate::item_rendering::ItemRenderer for SceneBuilder<'_, T self_rc: &ItemRc, size: LogicalSize, ) { - let geom = LogicalRect::from(size); - if !self.should_draw(&geom) { - return; - } - let font_request = text_input.font_request(self_rc); - let max_size = (geom.size.cast() * self.scale_factor).cast(); - - // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside - // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below). - // FIXME: we should allow drawing outside of the Text element's boundaries. - let physical_clip = if let Some(logical_clip) = self.current_state.clip.intersection(&geom) - { - logical_clip.cast() * self.scale_factor - } else { - return; // This should have been caught earlier already - }; - let offset = self.current_state.offset.to_vector().cast() * self.scale_factor; - let font = fonts::match_font(&font_request, self.scale_factor); - let text_visual_representation = text_input.visual_representation(None); - let color = self.alpha_color(text_visual_representation.text_color.color()); - - let selection = - (!text_visual_representation.selection_range.is_empty()).then_some(SelectionInfo { - selection_background: self.alpha_color(text_input.selection_background_color()), - selection_color: self.alpha_color(text_input.selection_foreground_color()), - selection: text_visual_representation.selection_range.clone(), - }); - - let cursor_pos_and_height = match font { - fonts::Font::PixelFont(pf) => { - let paragraph = TextParagraphLayout { - string: &text_visual_representation.text, - layout: fonts::text_layout_for_font(&pf, &font_request, self.scale_factor), - max_width: max_size.width_length(), - max_height: max_size.height_length(), - horizontal_alignment: text_input.horizontal_alignment(), - vertical_alignment: text_input.vertical_alignment(), - wrap: text_input.wrap(), - overflow: TextOverflow::Clip, - single_line: text_input.single_line(), - }; - - self.draw_text_paragraph(¶graph, physical_clip, offset, color, selection); - - text_visual_representation.cursor_position.map(|cursor_offset| { - (paragraph.cursor_pos_for_byte_offset(cursor_offset), pf.height()) - }) + match (font, parley_disabled()) { + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(_), false) => { + sharedparley::draw_text_input( + self, + text_input, + Some(text_input.font_request(self_rc)), + size, + None, + ); } #[cfg(feature = "software-renderer-systemfonts")] - fonts::Font::VectorFont(vf) => { + (fonts::Font::VectorFont(vf), true) => { + let geom = LogicalRect::from(size); + if !self.should_draw(&geom) { + return; + } + + let max_size = (geom.size.cast() * self.scale_factor).cast(); + + // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside + // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below). + // FIXME: we should allow drawing outside of the Text element's boundaries. + let physical_clip = + if let Some(logical_clip) = self.current_state.clip.intersection(&geom) { + logical_clip.cast() * self.scale_factor + } else { + return; // This should have been caught earlier already + }; + let offset = self.current_state.offset.to_vector().cast() * self.scale_factor; + + let text_visual_representation = text_input.visual_representation(None); + let color = self.alpha_color(text_visual_representation.text_color.color()); + + let selection = (!text_visual_representation.selection_range.is_empty()).then_some( + SelectionInfo { + selection_background: self + .alpha_color(text_input.selection_background_color()), + selection_color: self.alpha_color(text_input.selection_foreground_color()), + selection: text_visual_representation.selection_range.clone(), + }, + ); + let paragraph = TextParagraphLayout { string: &text_visual_representation.text, layout: fonts::text_layout_for_font(&vf, &font_request, self.scale_factor), @@ -2413,42 +2547,128 @@ impl crate::item_rendering::ItemRenderer for SceneBuilder<'_, T self.draw_text_paragraph(¶graph, physical_clip, offset, color, selection); - text_visual_representation.cursor_position.map(|cursor_offset| { - (paragraph.cursor_pos_for_byte_offset(cursor_offset), vf.height()) - }) - } - }; + let cursor_pos_and_height = + text_visual_representation.cursor_position.map(|cursor_offset| { + (paragraph.cursor_pos_for_byte_offset(cursor_offset), vf.height()) + }); - if let Some(((cursor_x, cursor_y), cursor_height)) = cursor_pos_and_height { - let cursor_rect = PhysicalRect::new( - PhysicalPoint::from_lengths(cursor_x, cursor_y), - PhysicalSize::from_lengths( - (text_input.text_cursor_width().cast() * self.scale_factor).cast(), - cursor_height, - ), - ); + if let Some(((cursor_x, cursor_y), cursor_height)) = cursor_pos_and_height { + let cursor_rect = PhysicalRect::new( + PhysicalPoint::from_lengths(cursor_x, cursor_y), + PhysicalSize::from_lengths( + (text_input.text_cursor_width().cast() * self.scale_factor).cast(), + cursor_height, + ), + ); - if let Some(clipped_src) = cursor_rect.intersection(&physical_clip.cast()) { - let geometry = clipped_src.translate(offset.cast()).transformed(self.rotation); - #[allow(unused_mut)] - let mut cursor_color = text_visual_representation.cursor_color; - #[cfg(all(feature = "std", target_os = "macos"))] - { - // On macOs, the cursor color is different than other platform. Use a hack to pass the screenshot test. - static IS_SCREENSHOT_TEST: std::sync::OnceLock = - std::sync::OnceLock::new(); - if *IS_SCREENSHOT_TEST.get_or_init(|| { - std::env::var_os("CARGO_PKG_NAME").unwrap_or_default() - == "test-driver-screenshots" - }) { - cursor_color = color; + if let Some(clipped_src) = cursor_rect.intersection(&physical_clip.cast()) { + let geometry = + clipped_src.translate(offset.cast()).transformed(self.rotation); + #[allow(unused_mut)] + let mut cursor_color = text_visual_representation.cursor_color; + #[cfg(all(feature = "std", target_os = "macos"))] + { + // On macOs, the cursor color is different than other platform. Use a hack to pass the screenshot test. + static IS_SCREENSHOT_TEST: std::sync::OnceLock = + std::sync::OnceLock::new(); + if *IS_SCREENSHOT_TEST.get_or_init(|| { + std::env::var_os("CARGO_PKG_NAME").unwrap_or_default() + == "test-driver-screenshots" + }) { + cursor_color = color; + } + } + let args = target_pixel_buffer::DrawRectangleArgs::from_rect( + geometry.cast(), + self.alpha_color(cursor_color).into(), + ); + self.processor.process_rectangle(&args, geometry); } } - let args = target_pixel_buffer::DrawRectangleArgs::from_rect( - geometry.cast(), - self.alpha_color(cursor_color).into(), + } + (fonts::Font::PixelFont(pf), _) => { + let geom = LogicalRect::from(size); + if !self.should_draw(&geom) { + return; + } + + let max_size = (geom.size.cast() * self.scale_factor).cast(); + + // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside + // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below). + // FIXME: we should allow drawing outside of the Text element's boundaries. + let physical_clip = + if let Some(logical_clip) = self.current_state.clip.intersection(&geom) { + logical_clip.cast() * self.scale_factor + } else { + return; // This should have been caught earlier already + }; + let offset = self.current_state.offset.to_vector().cast() * self.scale_factor; + + let text_visual_representation = text_input.visual_representation(None); + let color = self.alpha_color(text_visual_representation.text_color.color()); + + let selection = (!text_visual_representation.selection_range.is_empty()).then_some( + SelectionInfo { + selection_background: self + .alpha_color(text_input.selection_background_color()), + selection_color: self.alpha_color(text_input.selection_foreground_color()), + selection: text_visual_representation.selection_range.clone(), + }, ); - self.processor.process_rectangle(&args, geometry); + + let paragraph = TextParagraphLayout { + string: &text_visual_representation.text, + layout: fonts::text_layout_for_font(&pf, &font_request, self.scale_factor), + max_width: max_size.width_length(), + max_height: max_size.height_length(), + horizontal_alignment: text_input.horizontal_alignment(), + vertical_alignment: text_input.vertical_alignment(), + wrap: text_input.wrap(), + overflow: TextOverflow::Clip, + single_line: text_input.single_line(), + }; + + self.draw_text_paragraph(¶graph, physical_clip, offset, color, selection); + + let cursor_pos_and_height = + text_visual_representation.cursor_position.map(|cursor_offset| { + (paragraph.cursor_pos_for_byte_offset(cursor_offset), pf.height()) + }); + + if let Some(((cursor_x, cursor_y), cursor_height)) = cursor_pos_and_height { + let cursor_rect = PhysicalRect::new( + PhysicalPoint::from_lengths(cursor_x, cursor_y), + PhysicalSize::from_lengths( + (text_input.text_cursor_width().cast() * self.scale_factor).cast(), + cursor_height, + ), + ); + + if let Some(clipped_src) = cursor_rect.intersection(&physical_clip.cast()) { + let geometry = + clipped_src.translate(offset.cast()).transformed(self.rotation); + #[allow(unused_mut)] + let mut cursor_color = text_visual_representation.cursor_color; + #[cfg(all(feature = "std", target_os = "macos"))] + { + // On macOs, the cursor color is different than other platform. Use a hack to pass the screenshot test. + static IS_SCREENSHOT_TEST: std::sync::OnceLock = + std::sync::OnceLock::new(); + if *IS_SCREENSHOT_TEST.get_or_init(|| { + std::env::var_os("CARGO_PKG_NAME").unwrap_or_default() + == "test-driver-screenshots" + }) { + cursor_color = color; + } + } + let args = target_pixel_buffer::DrawRectangleArgs::from_rect( + geometry.cast(), + self.alpha_color(cursor_color).into(), + ); + self.processor.process_rectangle(&args, geometry); + } + } } } } @@ -2566,9 +2786,19 @@ impl crate::item_rendering::ItemRenderer for SceneBuilder<'_, T let font = fonts::match_font(&font_request, self.scale_factor); let clip = self.current_state.clip.cast() * self.scale_factor; - match font { - fonts::Font::PixelFont(pf) => { - let layout = fonts::text_layout_for_font(&pf, &font_request, self.scale_factor); + match (font, parley_disabled()) { + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(_), false) => { + sharedparley::draw_text( + self, + std::pin::pin!((crate::SharedString::from(string), Brush::from(color))), + None, + LogicalSize::new(1., 1.), // Non-zero size to avoid an early return + ); + } + #[cfg(feature = "software-renderer-systemfonts")] + (fonts::Font::VectorFont(vf), true) => { + let layout = fonts::text_layout_for_font(&vf, &font_request, self.scale_factor); let paragraph = TextParagraphLayout { string, @@ -2584,9 +2814,8 @@ impl crate::item_rendering::ItemRenderer for SceneBuilder<'_, T self.draw_text_paragraph(¶graph, clip, Default::default(), color, None); } - #[cfg(feature = "software-renderer-systemfonts")] - fonts::Font::VectorFont(vf) => { - let layout = fonts::text_layout_for_font(&vf, &font_request, self.scale_factor); + (fonts::Font::PixelFont(pf), _) => { + let layout = fonts::text_layout_for_font(&pf, &font_request, self.scale_factor); let paragraph = TextParagraphLayout { string, @@ -2621,3 +2850,137 @@ impl crate::item_rendering::ItemRenderer for SceneBuilder<'_, T impl crate::item_rendering::ItemRendererFeatures for SceneBuilder<'_, T> { const SUPPORTS_TRANSFORMATIONS: bool = false; } + +#[cfg(feature = "software-renderer-systemfonts")] +use crate::textlayout::sharedparley; + +#[cfg(feature = "software-renderer-systemfonts")] +impl sharedparley::GlyphRenderer for SceneBuilder<'_, T> { + type PlatformBrush = Color; + + fn platform_brush_for_color(&mut self, color: &Color) -> Option { + Some(*color) + } + + fn platform_text_fill_brush( + &mut self, + brush: Brush, + _size: LogicalSize, + ) -> Option { + match brush { + Brush::SolidColor(color) => Some(color), + _ => None, + } + } + + fn platform_text_stroke_brush( + &mut self, + brush: Brush, + _physical_stroke_width: f32, + _size: LogicalSize, + ) -> Option { + match brush { + Brush::SolidColor(color) => Some(color), + _ => None, + } + } + + fn fill_rectangle(&mut self, mut physical_rect: sharedparley::PhysicalRect, color: Color) { + if color.alpha() == 0 { + return; + } + + let global_offset = + (self.current_state.offset.to_vector().cast() * self.scale_factor).cast(); + + physical_rect.origin += global_offset; + let physical_rect = physical_rect.cast().transformed(self.rotation); + + let args = target_pixel_buffer::DrawRectangleArgs::from_rect( + physical_rect.cast(), + Brush::SolidColor(color), + ); + self.processor.process_rectangle(&args, physical_rect); + } + + fn draw_glyph_run( + &mut self, + font: &sharedparley::parley::FontData, + font_size: sharedparley::PhysicalLength, + color: Self::PlatformBrush, + y_offset: sharedparley::PhysicalLength, + glyphs_it: &mut dyn Iterator, + ) { + let fontdue_font = fonts::systemfonts::get_or_create_fontdue_font_from_blob_and_index( + &font.data, font.index, + ); + let font = fonts::vectorfont::VectorFont::new_from_blob_and_index( + font.data.clone(), + font.index, + fontdue_font, + font_size.cast(), + ); + + let global_offset = + (self.current_state.offset.to_vector().cast() * self.scale_factor).cast(); + + for positioned_glyph in glyphs_it { + let Some(glyph) = std::num::NonZero::new(positioned_glyph.id as u16) + .and_then(|id| font.render_vector_glyph(id)) + else { + continue; + }; + + let glyph_offset: euclid::Vector2D = euclid::Vector2D::from_lengths( + euclid::Length::new(positioned_glyph.x), + euclid::Length::new(positioned_glyph.y) + y_offset, + ) + .cast(); + + let gl_y = PhysicalLength::new(glyph.y.truncate() as i16); + let target_rect: PhysicalRect = euclid::Rect::::new( + (PhysicalPoint::from_lengths(PhysicalLength::new(0), -gl_y - glyph.height) + + global_offset + + glyph_offset) + .cast() + + euclid::vec2(glyph.bounds.xmin, 0.0), + glyph.size().cast(), + ) + .cast() + .transformed(self.rotation); + + let data = { + let source_rect = euclid::rect(0, 0, glyph.width.0, glyph.height.0); + target_pixel_buffer::TextureDataContainer::Shared { + buffer: SharedBufferData::AlphaMap { + data: glyph.alpha_map, + width: glyph.pixel_stride, + }, + source_rect, + } + }; + + let color = self.alpha_color(color); + let physical_clip = + (self.current_state.clip.translate(self.current_state.offset.to_vector()).cast() + * self.scale_factor) + .round() + .transformed(self.rotation); + + let t = target_pixel_buffer::DrawTextureArgs { + data, + colorize: Some(color), + // color already is mixed with global alpha + alpha: color.alpha(), + dst_x: target_rect.origin.x as _, + dst_y: target_rect.origin.y as _, + dst_width: target_rect.size.width as _, + dst_height: target_rect.size.height as _, + rotation: self.rotation.orientation, + tiling: None, + }; + + self.processor.process_target_texture(&t, physical_clip.cast()); + } + } +} diff --git a/internal/core/software_renderer/fonts.rs b/internal/core/software_renderer/fonts.rs index 738a9c55c3..4e143ddb21 100644 --- a/internal/core/software_renderer/fonts.rs +++ b/internal/core/software_renderer/fonts.rs @@ -7,9 +7,8 @@ use core::cell::RefCell; use super::{Fixed, PhysicalLength, PhysicalSize}; use crate::graphics::{BitmapFont, FontRequest}; -use crate::items::TextWrap; -use crate::lengths::{LogicalLength, LogicalSize, ScaleFactor}; -use crate::textlayout::{FontMetrics, TextLayout}; +use crate::lengths::{LogicalLength, ScaleFactor}; +use crate::textlayout::TextLayout; use crate::Coord; crate::thread_local! { @@ -39,6 +38,26 @@ impl RenderableGlyph { } } +// Subset of `RenderableGlyph`, specfically for VectorFonts. +#[cfg(feature = "software-renderer-systemfonts")] +#[derive(Clone)] +pub struct RenderableVectorGlyph { + pub x: Fixed, + pub y: Fixed, + pub width: PhysicalLength, + pub height: PhysicalLength, + pub alpha_map: Rc<[u8]>, + pub pixel_stride: u16, + pub bounds: fontdue::OutlineBounds, +} + +#[cfg(feature = "software-renderer-systemfonts")] +impl RenderableVectorGlyph { + pub fn size(&self) -> PhysicalSize { + PhysicalSize::from_lengths(self.width, self.height) + } +} + pub trait GlyphRenderer { fn render_glyph(&self, glyph_id: core::num::NonZeroU16) -> Option; /// The amount of pixel in the original image that correspond to one pixel in the rendered image @@ -182,53 +201,3 @@ where pub fn register_bitmap_font(font_data: &'static BitmapFont) { BITMAP_FONTS.with(|fonts| fonts.borrow_mut().push(font_data)) } - -pub fn text_size( - font_request: FontRequest, - text: &str, - max_width: Option, - scale_factor: ScaleFactor, - text_wrap: TextWrap, -) -> LogicalSize { - let font = match_font(&font_request, scale_factor); - let (longest_line_width, height) = match font { - Font::PixelFont(pf) => { - let layout = text_layout_for_font(&pf, &font_request, scale_factor); - layout.text_size( - text, - max_width.map(|max_width| (max_width.cast() * scale_factor).cast()), - text_wrap, - ) - } - #[cfg(feature = "software-renderer-systemfonts")] - Font::VectorFont(vf) => { - let layout = text_layout_for_font(&vf, &font_request, scale_factor); - layout.text_size( - text, - max_width.map(|max_width| (max_width.cast() * scale_factor).cast()), - text_wrap, - ) - } - }; - - (PhysicalSize::from_lengths(longest_line_width, height).cast() / scale_factor).cast() -} - -pub fn font_metrics( - font_request: FontRequest, - scale_factor: ScaleFactor, -) -> crate::items::FontMetrics { - let font = match_font(&font_request, scale_factor); - - let ascent: LogicalLength = (font.ascent().cast() / scale_factor).cast(); - let descent: LogicalLength = (font.descent().cast() / scale_factor).cast(); - let x_height: LogicalLength = (font.x_height().cast() / scale_factor).cast(); - let cap_height: LogicalLength = (font.cap_height().cast() / scale_factor).cast(); - - crate::items::FontMetrics { - ascent: ascent.get() as _, - descent: descent.get() as _, - x_height: x_height.get() as _, - cap_height: cap_height.get() as _, - } -} diff --git a/internal/core/software_renderer/fonts/systemfonts.rs b/internal/core/software_renderer/fonts/systemfonts.rs index 24aa0029e8..15dbbf4b03 100644 --- a/internal/core/software_renderer/fonts/systemfonts.rs +++ b/internal/core/software_renderer/fonts/systemfonts.rs @@ -14,19 +14,22 @@ use super::super::PhysicalLength; use super::vectorfont::VectorFont; crate::thread_local! { - static FONTDUE_FONTS: RefCell>> = Default::default(); + static FONTDUE_FONTS: RefCell>> = Default::default(); } -fn get_or_create_fontdue_font(font: &fontique::QueryFont) -> Rc { +pub fn get_or_create_fontdue_font_from_blob_and_index( + blob: &fontique::Blob, + index: u32, +) -> Rc { FONTDUE_FONTS.with(|font_cache| { font_cache .borrow_mut() - .entry(font.family.0) + .entry((blob.id(), index)) .or_insert_with(move || { fontdue::Font::from_bytes( - font.blob.data(), + blob.data(), fontdue::FontSettings { - collection_index: font.index, + collection_index: index, scale: 40., ..Default::default() }, @@ -38,6 +41,10 @@ fn get_or_create_fontdue_font(font: &fontique::QueryFont) -> Rc { }) } +fn get_or_create_fontdue_font(font: &fontique::QueryFont) -> Rc { + get_or_create_fontdue_font_from_blob_and_index(&font.blob, font.index) +} + pub fn match_font( request: &super::FontRequest, scale_factor: super::ScaleFactor, diff --git a/internal/core/software_renderer/fonts/vectorfont.rs b/internal/core/software_renderer/fonts/vectorfont.rs index 45707cf308..60d4d87eb8 100644 --- a/internal/core/software_renderer/fonts/vectorfont.rs +++ b/internal/core/software_renderer/fonts/vectorfont.rs @@ -9,29 +9,26 @@ use crate::software_renderer::PhysicalLength; use crate::textlayout::{Glyph, TextShaper}; use i_slint_common::sharedfontique::fontique; -use super::RenderableGlyph; +use super::RenderableVectorGlyph; // A length in font design space. struct FontUnit; type FontLength = euclid::Length; type FontScaleFactor = euclid::Scale; -type GlyphCacheKey = (fontique::FamilyId, PhysicalLength, core::num::NonZeroU16); +type GlyphCacheKey = (u64, u32, PhysicalLength, core::num::NonZeroU16); struct RenderableGlyphWeightScale; -impl clru::WeightScale for RenderableGlyphWeightScale { - fn weight(&self, _: &GlyphCacheKey, value: &RenderableGlyph) -> usize { - match &value.alpha_map { - super::GlyphAlphaMap::Static(_) => 0, - super::GlyphAlphaMap::Shared(data) => data.len(), - } +impl clru::WeightScale for RenderableGlyphWeightScale { + fn weight(&self, _: &GlyphCacheKey, value: &RenderableVectorGlyph) -> usize { + value.alpha_map.len() } } type GlyphCache = clru::CLruCache< GlyphCacheKey, - RenderableGlyph, + RenderableVectorGlyph, std::collections::hash_map::RandomState, RenderableGlyphWeightScale, >; @@ -46,7 +43,8 @@ crate::thread_local!(static GLYPH_CACHE: core::cell::RefCell = ); pub struct VectorFont { - font: fontique::QueryFont, + font_index: u32, + font_blob: fontique::Blob, fontdue_font: Rc, ascender: PhysicalLength, descender: PhysicalLength, @@ -63,7 +61,16 @@ impl VectorFont { fontdue_font: Rc, pixel_size: PhysicalLength, ) -> Self { - let face = rustybuzz::ttf_parser::Face::parse(font.blob.data(), font.index).unwrap(); + Self::new_from_blob_and_index(font.blob, font.index, fontdue_font, pixel_size) + } + + pub fn new_from_blob_and_index( + font_blob: fontique::Blob, + font_index: u32, + fontdue_font: Rc, + pixel_size: PhysicalLength, + ) -> Self { + let face = rustybuzz::ttf_parser::Face::parse(font_blob.data(), font_index).unwrap(); let ascender = FontLength::new(face.ascender() as _); let descender = FontLength::new(face.descender() as _); @@ -73,7 +80,8 @@ impl VectorFont { let units_per_em = face.units_per_em(); let scale = FontScaleFactor::new(pixel_size.get() as f32 / units_per_em as f32); Self { - font, + font_index, + font_blob, fontdue_font, ascender: (ascender.cast() * scale).cast(), descender: (descender.cast() * scale).cast(), @@ -84,6 +92,39 @@ impl VectorFont { cap_height: (cap_height.cast() * scale).cast(), } } + + pub fn render_vector_glyph( + &self, + glyph_id: core::num::NonZeroU16, + ) -> Option { + GLYPH_CACHE.with(|cache| { + let mut cache = cache.borrow_mut(); + + let cache_key = (self.font_blob.id(), self.font_index, self.pixel_size, glyph_id); + + if let Some(entry) = cache.get(&cache_key) { + Some(entry.clone()) + } else { + let (metrics, alpha_map) = + self.fontdue_font.rasterize_indexed(glyph_id.get(), self.pixel_size.get() as _); + + let alpha_map: Rc<[u8]> = alpha_map.into(); + + let glyph = super::RenderableVectorGlyph { + x: Fixed::from_integer(metrics.xmin.try_into().unwrap()), + y: Fixed::from_integer(metrics.ymin.try_into().unwrap()), + width: PhysicalLength::new(metrics.width.try_into().unwrap()), + height: PhysicalLength::new(metrics.height.try_into().unwrap()), + alpha_map, + pixel_stride: metrics.width.try_into().unwrap(), + bounds: metrics.bounds, + }; + + cache.put_with_weight(cache_key, glyph.clone()).ok(); + Some(glyph) + } + }) + } } impl TextShaper for VectorFont { @@ -98,7 +139,7 @@ impl TextShaper for VectorFont { buffer.push_str(text); let face = - rustybuzz::ttf_parser::Face::parse(self.font.blob.data(), self.font.index).unwrap(); + rustybuzz::ttf_parser::Face::parse(self.font_blob.data(), self.font_index).unwrap(); let rb_face = rustybuzz::Face::from_face(face); let glyph_buffer = rustybuzz::shape(&rb_face, &[], buffer); @@ -129,7 +170,7 @@ impl TextShaper for VectorFont { fn glyph_for_char(&self, ch: char) -> Option> { let face = - rustybuzz::ttf_parser::Face::parse(self.font.blob.data(), self.font.index).unwrap(); + rustybuzz::ttf_parser::Face::parse(self.font_blob.data(), self.font_index).unwrap(); face.glyph_index(ch).map(|glyph_index| { let mut out_glyph = Glyph::default(); @@ -174,32 +215,14 @@ impl crate::textlayout::FontMetrics for VectorFont { impl super::GlyphRenderer for VectorFont { fn render_glyph(&self, glyph_id: core::num::NonZeroU16) -> Option { - GLYPH_CACHE.with(|cache| { - let mut cache = cache.borrow_mut(); - - let cache_key = (self.font.family.0, self.pixel_size, glyph_id); - - if let Some(entry) = cache.get(&cache_key) { - Some(entry.clone()) - } else { - let (metrics, alpha_map) = - self.fontdue_font.rasterize_indexed(glyph_id.get(), self.pixel_size.get() as _); - - let alpha_map: Rc<[u8]> = alpha_map.into(); - - let glyph = super::RenderableGlyph { - x: Fixed::from_integer(metrics.xmin.try_into().unwrap()), - y: Fixed::from_integer(metrics.ymin.try_into().unwrap()), - width: PhysicalLength::new(metrics.width.try_into().unwrap()), - height: PhysicalLength::new(metrics.height.try_into().unwrap()), - alpha_map: alpha_map.into(), - sdf: false, - pixel_stride: metrics.width.try_into().unwrap(), - }; - - cache.put_with_weight(cache_key, glyph.clone()).ok(); - Some(glyph) - } + self.render_vector_glyph(glyph_id).map(|glyph| super::RenderableGlyph { + x: glyph.x, + y: glyph.y, + width: glyph.width, + height: glyph.height, + alpha_map: glyph.alpha_map.into(), + pixel_stride: glyph.pixel_stride, + sdf: false, }) }