mirror of
https://github.com/slint-ui/slint.git
synced 2025-12-23 09:19:32 +00:00
Software renderer: use parley (#9656)
* Store font index and blob instead of full fontique::QueryFont * fontdue changes * VectorFont takes blob and index * Start implementing sharedparley::GlyphRenderer for SceneBuilder * Change brush to be a color as the sw renderer can't do fancy gradients anyway * Implement trait fully * Use trait instead if correct features are set * Remove gl_x * Slight fixes for the parley update * [autofix.ci] apply automated fixes * Add a RenderableVectorGlyph type in order to compile type enforce vector alpha map and sdf stuff * Add shared-parley to software-renderer-systemfonts feature * Check font type before branching into legacy or parley codepaths * Fix fill_rectangle offset * Set alpha colors, physical clip * Adjust font_size in swrenderer * Apply rotation to selection rectangle Switch order [autofix.ci] apply automated fixes Add missing mut * Use sharedparley for text_size and font_metrics * Put RenderableVectorGlyph behind feature flag * [autofix.ci] apply automated fixes * Remove unused font functions * Allow rendering without parley * Add missing cfg flags * Use env variable instead * Use font.index in glyph cache key * Handle font matching and fallbacks differently * Update references * Put the parley disabled check behind a cfg flag * Update references again * Fix fill_rectangle rounding * [autofix.ci] apply automated fixes * Add bounds.xmin to target_rect * Update references * Update tests after bounds changes * Round target_rect to fix rotate180 screenshot tests * Update references yet again * Get rid of rounding on target rect * Use casts to properly truncate numbers. Hopefully tests now pass. * Update references for hopefully final time * Again * Update references yet again * Revert match_font, screenshot references --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
13c7db5330
commit
b31aba96ce
5 changed files with 626 additions and 264 deletions
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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<T: ProcessScene> 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<T: ProcessScene> 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<T: ProcessScene> 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<T: ProcessScene> 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<bool> =
|
||||
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<bool> =
|
||||
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<bool> =
|
||||
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<T: ProcessScene> 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<T: ProcessScene> 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<T: ProcessScene> crate::item_rendering::ItemRenderer for SceneBuilder<'_, T
|
|||
impl<T: ProcessScene> 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<T: ProcessScene> sharedparley::GlyphRenderer for SceneBuilder<'_, T> {
|
||||
type PlatformBrush = Color;
|
||||
|
||||
fn platform_brush_for_color(&mut self, color: &Color) -> Option<Self::PlatformBrush> {
|
||||
Some(*color)
|
||||
}
|
||||
|
||||
fn platform_text_fill_brush(
|
||||
&mut self,
|
||||
brush: Brush,
|
||||
_size: LogicalSize,
|
||||
) -> Option<Self::PlatformBrush> {
|
||||
match brush {
|
||||
Brush::SolidColor(color) => Some(color),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn platform_text_stroke_brush(
|
||||
&mut self,
|
||||
brush: Brush,
|
||||
_physical_stroke_width: f32,
|
||||
_size: LogicalSize,
|
||||
) -> Option<Self::PlatformBrush> {
|
||||
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<Item = sharedparley::parley::layout::Glyph>,
|
||||
) {
|
||||
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<i16, PhysicalPx> = 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::<f32, PhysicalPx>::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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<i32, 8>,
|
||||
pub y: Fixed<i32, 8>,
|
||||
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<RenderableGlyph>;
|
||||
/// 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<LogicalLength>,
|
||||
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 _,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,19 +14,22 @@ use super::super::PhysicalLength;
|
|||
use super::vectorfont::VectorFont;
|
||||
|
||||
crate::thread_local! {
|
||||
static FONTDUE_FONTS: RefCell<HashMap<fontique::FamilyId, Rc<fontdue::Font>>> = Default::default();
|
||||
static FONTDUE_FONTS: RefCell<HashMap<(u64, u32), Rc<fontdue::Font>>> = Default::default();
|
||||
}
|
||||
|
||||
fn get_or_create_fontdue_font(font: &fontique::QueryFont) -> Rc<fontdue::Font> {
|
||||
pub fn get_or_create_fontdue_font_from_blob_and_index(
|
||||
blob: &fontique::Blob<u8>,
|
||||
index: u32,
|
||||
) -> Rc<fontdue::Font> {
|
||||
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<fontdue::Font> {
|
|||
})
|
||||
}
|
||||
|
||||
fn get_or_create_fontdue_font(font: &fontique::QueryFont) -> Rc<fontdue::Font> {
|
||||
get_or_create_fontdue_font_from_blob_and_index(&font.blob, font.index)
|
||||
}
|
||||
|
||||
pub fn match_font(
|
||||
request: &super::FontRequest,
|
||||
scale_factor: super::ScaleFactor,
|
||||
|
|
|
|||
|
|
@ -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<i32, FontUnit>;
|
||||
type FontScaleFactor = euclid::Scale<f32, FontUnit, PhysicalPx>;
|
||||
|
||||
type GlyphCacheKey = (fontique::FamilyId, PhysicalLength, core::num::NonZeroU16);
|
||||
type GlyphCacheKey = (u64, u32, PhysicalLength, core::num::NonZeroU16);
|
||||
|
||||
struct RenderableGlyphWeightScale;
|
||||
|
||||
impl clru::WeightScale<GlyphCacheKey, RenderableGlyph> 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<GlyphCacheKey, RenderableVectorGlyph> 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<GlyphCache> =
|
|||
);
|
||||
|
||||
pub struct VectorFont {
|
||||
font: fontique::QueryFont,
|
||||
font_index: u32,
|
||||
font_blob: fontique::Blob<u8>,
|
||||
fontdue_font: Rc<fontdue::Font>,
|
||||
ascender: PhysicalLength,
|
||||
descender: PhysicalLength,
|
||||
|
|
@ -63,7 +61,16 @@ impl VectorFont {
|
|||
fontdue_font: Rc<fontdue::Font>,
|
||||
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<u8>,
|
||||
font_index: u32,
|
||||
fontdue_font: Rc<fontdue::Font>,
|
||||
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<RenderableVectorGlyph> {
|
||||
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<Glyph<PhysicalLength>> {
|
||||
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<PhysicalLength> for VectorFont {
|
|||
|
||||
impl super::GlyphRenderer for VectorFont {
|
||||
fn render_glyph(&self, glyph_id: core::num::NonZeroU16) -> Option<super::RenderableGlyph> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue