Add support for a font-metrics property to Text/TextInput. (#6452)

The struct held provides access to the design metrics of the font scaled
to the font pixel size used by the element.

ChangeLog: Slint Language: Added font-metrics property to `Text` and `TextInput`.

Closes #6047
This commit is contained in:
Simon Hausmann 2024-10-05 17:00:46 +02:00 committed by GitHub
parent b41b389e55
commit 0b028bfb6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 742 additions and 55 deletions

View file

@ -784,6 +784,7 @@ When not part of a layout, its width or height defaults to 100% of the parent el
- **`font-size`** (_in_ _length_): The font size of the text.
- **`font-weight`** (_in_ _int_): The weight of the font. The values range from 100 (lightest) to 900 (thickest). 400 is the normal weight.
- **`font-italic`** (_in_ _bool_): Whether or not the font face should be drawn italicized or not. (default value: false)
- **`font-metrics`** (_out_ _struct [`FontMetrics`](structs.md#fontmetrics)_): The design metrics of the font scaled to the font pixel size used by the element.
- **`has-focus`** (_out_ _bool_): `TextInput` sets this to `true` when it's focused. Only then it receives [`KeyEvent`](structs.md#keyevent)s.
- **`horizontal-alignment`** (_in_ _enum [`TextHorizontalAlignment`](enums.md#texthorizontalalignment)_): The horizontal alignment of the text.
- **`input-type`** (_in_ _enum [`InputType`](enums.md#inputtype)_): Use this to configure `TextInput` for editing special input, such as password fields. (default value: `text`)
@ -847,6 +848,7 @@ and the text itself.
- **`font-size`** (_in_ _length_): The font size of the text.
- **`font-weight`** (_in_ _int_): The weight of the font. The values range from 100 (lightest) to 900 (thickest). 400 is the normal weight.
- **`font-italic`** (_in_ _bool_): Whether or not the font face should be drawn italicized or not. (default value: false)
- **`font-metrics`** (_out_ _struct [`FontMetrics`](structs.md#fontmetrics)_): The design metrics of the font scaled to the font pixel size used by the element.
- **`horizontal-alignment`** (_in_ _enum [`TextHorizontalAlignment`](enums.md#texthorizontalalignment)_): The horizontal alignment of the text.
- **`letter-spacing`** (_in_ _length_): The letter spacing allows changing the spacing between the glyphs. A positive value increases the spacing and a negative value decreases the distance. (default value: 0)
- **`overflow`** (_in_ _enum [`TextOverflow`](enums.md#textoverflow)_): What happens when the text overflows (default value: clip).

View file

@ -2163,13 +2163,27 @@ impl i_slint_core::renderer::RendererSealed for QtWindow {
_scale_factor: ScaleFactor,
text_wrap: TextWrap,
) -> LogicalSize {
get_font(font_request).text_size(
get_font(font_request).font_metrics().text_size(
text,
max_width.map(|logical_width| logical_width.get()),
text_wrap,
)
}
fn font_metrics(
&self,
font_request: i_slint_core::graphics::FontRequest,
_scale_factor: ScaleFactor,
) -> i_slint_core::items::FontMetrics {
let qt_font_metrics = get_font(font_request).font_metrics();
i_slint_core::items::FontMetrics {
ascent: qt_font_metrics.ascent(),
descent: -qt_font_metrics.descent(),
x_height: qt_font_metrics.x_height(),
cap_height: qt_font_metrics.cap_height(),
}
}
fn text_input_byte_offset_for_position(
&self,
text_input: Pin<&i_slint_core::items::TextInput>,
@ -2407,9 +2421,9 @@ fn get_font(request: FontRequest) -> QFont {
})
}
cpp_class! {pub unsafe struct QFont as "QFont"}
cpp_class! {pub unsafe struct QFontMetricsF as "QFontMetricsF"}
impl QFont {
impl QFontMetricsF {
fn text_size(&self, text: &str, max_width: Option<f32>, text_wrap: TextWrap) -> LogicalSize {
let string = qttypes::QString::from(text);
let char_wrap = text_wrap == TextWrap::CharWrap;
@ -2418,12 +2432,50 @@ impl QFont {
r.height = f32::MAX as _;
r.width = max as _;
}
let size = cpp! { unsafe [self as "const QFont*", string as "QString", r as "QRectF", char_wrap as "bool"]
let size = cpp! { unsafe [self as "const QFontMetricsF*", string as "QString", r as "QRectF", char_wrap as "bool"]
-> qttypes::QSizeF as "QSizeF" {
return QFontMetricsF(*self).boundingRect(r, r.isEmpty() ? 0 : ((char_wrap) ? Qt::TextWrapAnywhere : Qt::TextWordWrap) , string).size();
return self->boundingRect(r, r.isEmpty() ? 0 : ((char_wrap) ? Qt::TextWrapAnywhere : Qt::TextWordWrap) , string).size();
}};
LogicalSize::new(size.width as _, size.height as _)
}
fn ascent(&self) -> f32 {
cpp! { unsafe [self as "const QFontMetricsF*"]
-> f32 as "float" {
return self->ascent();
}}
}
fn descent(&self) -> f32 {
cpp! { unsafe [self as "const QFontMetricsF*"]
-> f32 as "float" {
return self->descent();
}}
}
fn cap_height(&self) -> f32 {
cpp! { unsafe [self as "const QFontMetricsF*"]
-> f32 as "float" {
return self->capHeight();
}}
}
fn x_height(&self) -> f32 {
cpp! { unsafe [self as "const QFontMetricsF*"]
-> f32 as "float" {
return self->xHeight();
}}
}
}
cpp_class! {pub unsafe struct QFont as "QFont"}
impl QFont {
fn font_metrics(&self) -> QFontMetricsF {
cpp! { unsafe [self as "const QFont *"] -> QFontMetricsF as "QFontMetricsF" {
return QFontMetricsF(*self);
}}
}
}
thread_local! {

View file

@ -173,6 +173,14 @@ impl RendererSealed for TestingWindow {
LogicalSize::new(text.len() as f32 * 10., 10.)
}
fn font_metrics(
&self,
_font_request: i_slint_core::graphics::FontRequest,
_scale_factor: ScaleFactor,
) -> i_slint_core::items::FontMetrics {
i_slint_core::items::FontMetrics { ascent: 7., descent: 3., x_height: 3., cap_height: 7. }
}
// this works only for single line text
fn text_input_byte_offset_for_position(
&self,

View file

@ -18,10 +18,11 @@ path = "lib.rs"
[features]
default = []
shared-fontdb = ["dep:fontdb", "dep:libloading", "derive_more", "cfg-if"]
shared-fontdb = ["dep:fontdb", "dep:libloading", "derive_more", "cfg-if", "dep:ttf-parser"]
[dependencies]
fontdb = { workspace = true, optional = true }
ttf-parser = { workspace = true, optional = true }
derive_more = { workspace = true, optional = true }
cfg-if = { version = "1", optional = true }

View file

@ -162,6 +162,26 @@ macro_rules! for_each_builtin_structs {
change_time: crate::animations::Instant,
}
}
/// A structure to hold metrics of a font for a specified pixel size.
struct FontMetrics {
@name = "slint::private_api::FontMetrics"
export {
/// The distance between the baseline and the top of the tallest glyph in the font.
ascent: Coord,
/// The distance between the baseline and the bottom of the tallest glyph in the font.
/// This is usually negative.
descent: Coord,
/// The distance between the baseline and the horizontal midpoint of the tallest glyph in the font,
/// or zero if not specified by the font.
x_height: Coord,
/// The distance between the baseline and the top of a regular upper-case glyph in the font,
/// or zero if not specified by the font.
cap_height: Coord,
}
private {
}
}
];
};
}

View file

@ -5,6 +5,7 @@ use std::cell::RefCell;
use std::sync::Arc;
pub use fontdb;
pub use ttf_parser;
#[derive(derive_more::Deref)]
pub struct FontDatabase {
@ -220,3 +221,26 @@ pub fn register_font_from_path(_path: &std::path::Path) -> Result<(), Box<dyn st
)
.into());
}
/// Font metrics in design space. Scale with desired pixel size and divided by units_per_em
/// to obtain pixel metrics.
#[derive(Clone)]
pub struct DesignFontMetrics {
pub ascent: f32,
pub descent: f32,
pub x_height: f32,
pub cap_height: f32,
pub units_per_em: f32,
}
impl DesignFontMetrics {
pub fn new(face: ttf_parser::Face<'_>) -> Self {
Self {
ascent: face.ascender() as f32,
descent: face.descender() as f32,
x_height: face.x_height().unwrap_or_default() as f32,
cap_height: face.capital_height().unwrap_or_default() as f32,
units_per_em: face.units_per_em() as f32,
}
}
}

View file

@ -78,6 +78,8 @@ pub struct BitmapFont {
pub units_per_em: f32,
pub ascent: f32,
pub descent: f32,
pub x_height: f32,
pub cap_height: f32,
pub glyphs: Vec<BitmapGlyphs>,
pub weight: u16,
pub italic: bool,

View file

@ -45,6 +45,7 @@ pub enum BuiltinFunction {
SetSelectionOffsets,
/// A function that belongs to an item (such as TextInput's select-all function).
ItemMemberFunction(String),
ItemFontMetrics,
/// the "42".to_float()
StringToFloat,
/// the "42".is_float()
@ -167,6 +168,10 @@ impl BuiltinFunction {
return_type: Box::new(Type::Void),
args: vec![Type::ElementReference],
},
BuiltinFunction::ItemFontMetrics => Type::Function {
return_type: Box::new(crate::typeregister::font_metrics_type()),
args: vec![Type::ElementReference],
},
BuiltinFunction::StringToFloat => {
Type::Function { return_type: Box::new(Type::Float32), args: vec![Type::String] }
}
@ -353,6 +358,7 @@ impl BuiltinFunction {
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
BuiltinFunction::SetSelectionOffsets => false,
BuiltinFunction::ItemMemberFunction(..) => false,
BuiltinFunction::ItemFontMetrics => false, // depends also on Window's font properties
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
BuiltinFunction::ColorRgbaStruct
| BuiltinFunction::ColorHsvaStruct
@ -419,6 +425,7 @@ impl BuiltinFunction {
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
BuiltinFunction::SetSelectionOffsets => false,
BuiltinFunction::ItemMemberFunction(..) => false,
BuiltinFunction::ItemFontMetrics => true,
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
BuiltinFunction::ColorRgbaStruct
| BuiltinFunction::ColorHsvaStruct

View file

@ -899,6 +899,8 @@ fn embed_resource(
units_per_em,
ascent,
descent,
x_height,
cap_height,
glyphs,
weight,
italic,
@ -998,6 +1000,8 @@ fn embed_resource(
.units_per_em = {units_per_em},
.ascent = {ascent},
.descent = {descent},
.x_height = {x_height},
.cap_height = {cap_height},
.glyphs = slint::cbindgen_private::Slice<slint::cbindgen_private::BitmapGlyphs>{{ {glyphsets_var}, {glyphsets_size} }},
.weight = {weight},
.italic = {italic},
@ -3542,6 +3546,15 @@ fn compile_builtin_function_call(
panic!("internal error: invalid args to ItemMemberFunction {:?}", arguments)
}
}
BuiltinFunction::ItemFontMetrics => {
if let [llr::Expression::PropertyReference(pr)] = arguments {
let item_rc = access_item_rc(pr, ctx);
let window = access_window_field(ctx);
format!("slint_cpp_text_item_fontmetrics(&{window}.handle(), &{item_rc})")
} else {
panic!("internal error: invalid args to ItemFontMetrics {:?}", arguments)
}
}
BuiltinFunction::ItemAbsolutePosition => {
if let [llr::Expression::PropertyReference(pr)] = arguments {
let item_rc = access_item_rc(pr, ctx);

View file

@ -2644,6 +2644,19 @@ fn compile_builtin_function_call(
panic!("internal error: invalid args to ItemMemberFunction {:?}", arguments)
}
}
BuiltinFunction::ItemFontMetrics => {
if let [Expression::PropertyReference(pr)] = arguments {
let item = access_member(pr, ctx);
let window_adapter_tokens = access_window_adapter_field(ctx);
item.then(|item| {
quote!(
#item.font_metrics(#window_adapter_tokens)
)
})
} else {
panic!("internal error: invalid args to ItemMemberFunction {:?}", arguments)
}
}
BuiltinFunction::ImplicitLayoutInfo(orient) => {
if let [Expression::PropertyReference(pr)] = arguments {
let item = access_member(pr, ctx);
@ -2985,7 +2998,7 @@ fn generate_resources(doc: &Document) -> Vec<TokenStream> {
)
},
#[cfg(feature = "software-renderer")]
crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(crate::embedded_resources::BitmapFont { family_name, character_map, units_per_em, ascent, descent, glyphs, weight, italic }) => {
crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(crate::embedded_resources::BitmapFont { family_name, character_map, units_per_em, ascent, descent, x_height, cap_height, glyphs, weight, italic }) => {
let character_map_size = character_map.len();
@ -3037,6 +3050,8 @@ fn generate_resources(doc: &Document) -> Vec<TokenStream> {
units_per_em: #units_per_em,
ascent: #ascent,
descent: #descent,
x_height: #x_height,
cap_height: #cap_height,
glyphs: sp::Slice::from_slice({
#link_section
static GLYPHS : [sp::BitmapGlyphs; #glyphs_size] = [#(#glyphs),*];

View file

@ -347,19 +347,41 @@ impl Type {
}
}
#[derive(Debug, Clone)]
pub enum BuiltinPropertyDefault {
None,
Expr(Expression),
Fn(fn(&crate::object_tree::ElementRc) -> Expression),
}
impl BuiltinPropertyDefault {
pub fn expr(&self, elem: &crate::object_tree::ElementRc) -> Option<Expression> {
match self {
BuiltinPropertyDefault::None => None,
BuiltinPropertyDefault::Expr(expression) => Some(expression.clone()),
BuiltinPropertyDefault::Fn(init_expr) => Some(init_expr(elem)),
}
}
}
/// Information about properties in NativeClass
#[derive(Debug, Clone)]
pub struct BuiltinPropertyInfo {
/// The property type
pub ty: Type,
/// When set, this is the initial value that we will have to set if no other binding were specified
pub default_value: Option<Expression>,
/// When != None, this is the initial value that we will have to set if no other binding were specified
pub default_value: BuiltinPropertyDefault,
pub property_visibility: PropertyVisibility,
}
impl BuiltinPropertyInfo {
pub fn new(ty: Type) -> Self {
Self { ty, default_value: None, property_visibility: PropertyVisibility::InOut }
Self {
ty,
default_value: BuiltinPropertyDefault::None,
property_visibility: PropertyVisibility::InOut,
}
}
pub fn is_native_output(&self) -> bool {
@ -405,7 +427,11 @@ impl ElementType {
} else {
Cow::Borrowed(name)
};
match b.properties.get(resolved_name.as_ref()) {
match b
.properties
.get(resolved_name.as_ref())
.or_else(|| b.reserved_properties.get(resolved_name.as_ref()))
{
None => {
if b.is_non_item_type {
PropertyLookupResult {
@ -472,9 +498,12 @@ impl ElementType {
);
r
}
Self::Builtin(b) => {
b.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
}
Self::Builtin(b) => b
.properties
.iter()
.chain(b.reserved_properties.iter())
.map(|(k, t)| (k.clone(), t.ty.clone()))
.collect(),
Self::Native(n) => {
n.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
}
@ -719,6 +748,7 @@ pub struct BuiltinElement {
pub name: String,
pub native_class: Rc<NativeClass>,
pub properties: BTreeMap<String, BuiltinPropertyInfo>,
pub reserved_properties: BTreeMap<String, BuiltinPropertyInfo>,
pub additional_accepted_child_types: HashMap<String, ElementType>,
pub disallow_global_types_as_child_elements: bool,
/// Non-item type do not have reserved properties (x/width/rowspan/...) added to them (eg: PropertyAnimation)

View file

@ -99,6 +99,7 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => isize::MAX,
BuiltinFunction::SetSelectionOffsets => isize::MAX,
BuiltinFunction::ItemMemberFunction(..) => isize::MAX,
BuiltinFunction::ItemFontMetrics => PROPERTY_ACCESS_COST,
BuiltinFunction::StringToFloat => 50,
BuiltinFunction::StringIsFloat => 50,
BuiltinFunction::ColorRgbaStruct => 50,

View file

@ -11,7 +11,8 @@ use std::rc::Rc;
use crate::expression_tree::{BuiltinFunction, Expression};
use crate::langtype::{
BuiltinElement, BuiltinPropertyInfo, DefaultSizeBinding, ElementType, NativeClass, Type,
BuiltinElement, BuiltinPropertyDefault, BuiltinPropertyInfo, DefaultSizeBinding, ElementType,
NativeClass, Type,
};
use crate::object_tree::{self, *};
use crate::parser::{identifier_text, syntax_nodes, SyntaxKind, SyntaxNode};
@ -93,7 +94,7 @@ pub(crate) fn load_builtins(register: &mut TypeRegister) {
if let Some(e) = p.BindingExpression() {
let ty = info.ty.clone();
info.default_value = Some(compiled(e, register, ty));
info.default_value = BuiltinPropertyDefault::Expr(compiled(e, register, ty));
}
(prop_name, info)

View file

@ -9,7 +9,7 @@
use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
use crate::expression_tree::{self, BindingExpression, Expression, Unit};
use crate::langtype::{BuiltinElement, Enumeration, NativeClass, Type};
use crate::langtype::{BuiltinElement, BuiltinPropertyDefault, Enumeration, NativeClass, Type};
use crate::langtype::{ElementType, PropertyLookupResult};
use crate::layout::{LayoutConstraints, Orientation};
use crate::namedreference::NamedReference;
@ -1851,7 +1851,7 @@ fn apply_default_type_properties(element: &mut Element) {
// Apply default property values on top:
if let ElementType::Builtin(builtin_base) = &element.base_type {
for (prop, info) in &builtin_base.properties {
if let Some(expr) = &info.default_value {
if let BuiltinPropertyDefault::Expr(expr) = &info.default_value {
element.bindings.entry(prop.clone()).or_insert_with(|| {
let mut binding = BindingExpression::from(expr.clone());
binding.priority = i32::MAX;

View file

@ -20,6 +20,7 @@ struct Font {
id: fontdb::ID,
#[deref]
fontdue_font: Rc<fontdue::Font>,
metrics: i_slint_common::sharedfontdb::DesignFontMetrics,
}
#[cfg(target_arch = "wasm32")]
@ -212,6 +213,20 @@ fn embed_glyphs_with_fontdb<'a>(
}
};
let Some(Ok(metrics)) = i_slint_common::sharedfontdb::FONT_DB.with(|fontdb| {
fontdb.borrow().with_face_data(face_id, |font_data, face_index| {
i_slint_common::sharedfontdb::ttf_parser::Face::parse(font_data, face_index)
.map(|face| i_slint_common::sharedfontdb::DesignFontMetrics::new(face))
})
}) else {
diag.push_error(
format!("error parsing font for embedding {}", path.display()),
&generic_diag_location,
);
return;
};
let Some(family_name) = fontdb
.face(face_id)
.expect("must succeed as we are within face_data with same face_id")
@ -232,7 +247,7 @@ fn embed_glyphs_with_fontdb<'a>(
let embedded_bitmap_font = embed_font(
&fontdb,
family_name,
Font { id: face_id, fontdue_font },
Font { id: face_id, fontdue_font, metrics },
&pixel_sizes,
characters_seen.iter().cloned(),
&fallback_fonts,
@ -373,19 +388,16 @@ fn embed_font(
})
.collect();
// Get the basic metrics in design coordinates
let metrics = font
.horizontal_line_metrics(font.units_per_em())
.expect("encountered font without hmtx table");
let face_info = fontdb.face(font.id).unwrap();
BitmapFont {
family_name,
character_map,
units_per_em: font.units_per_em(),
ascent: metrics.ascent,
descent: metrics.descent,
units_per_em: font.metrics.units_per_em,
ascent: font.metrics.ascent,
descent: font.metrics.descent,
x_height: font.metrics.x_height,
cap_height: font.metrics.cap_height,
glyphs,
weight: face_info.weight.0,
italic: face_info.style != fontdb::Style::Normal,

View file

@ -93,7 +93,12 @@ fn should_materialize(
}
let has_declared_property = match base_type {
ElementType::Component(c) => has_declared_property(&c.root_element.borrow(), prop),
ElementType::Builtin(b) => b.properties.contains_key(prop),
ElementType::Builtin(b) => {
if let Some(info) = b.reserved_properties.get(prop) {
return Some(info.ty.clone());
}
b.properties.contains_key(prop)
}
ElementType::Native(n) => {
n.lookup_property(prop).map_or(false, |prop_type| prop_type.is_property_type())
}
@ -128,6 +133,14 @@ fn has_declared_property(elem: &Element, prop: &str) -> bool {
/// Initialize a sensible default binding for the now materialized property
pub fn initialize(elem: &ElementRc, name: &str) -> Option<Expression> {
if let ElementType::Builtin(b) = &elem.borrow().base_type {
if let Some(expr) =
b.reserved_properties.get(name).and_then(|prop| prop.default_value.expr(elem))
{
return Some(expr);
}
}
let expr = match name {
"min-height" => layout_constraint_prop(elem, "min", Orientation::Vertical),
"min-width" => layout_constraint_prop(elem, "min", Orientation::Horizontal),

View file

@ -0,0 +1,13 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
export component Foo inherits Rectangle {
Text {
font-metrics: 42;
// ^error{Cannot assign to output property 'font-metrics'}
// ^^error{Cannot convert float to slint::private_api::FontMetrics}
}
property <length> font-metrics: 100px;
}

View file

@ -9,7 +9,8 @@ use std::rc::Rc;
use crate::expression_tree::BuiltinFunction;
use crate::langtype::{
BuiltinElement, BuiltinPropertyInfo, ElementType, Enumeration, PropertyLookupResult, Type,
BuiltinElement, BuiltinPropertyDefault, BuiltinPropertyInfo, ElementType, Enumeration,
PropertyLookupResult, Type,
};
use crate::object_tree::{Component, PropertyVisibility};
use crate::typeloader;
@ -370,6 +371,25 @@ impl TypeRegister {
_ => unreachable!(),
};
let font_metrics_prop = crate::langtype::BuiltinPropertyInfo {
ty: font_metrics_type(),
property_visibility: PropertyVisibility::Output,
default_value: BuiltinPropertyDefault::Fn(|elem| {
crate::expression_tree::Expression::FunctionCall {
function: Box::new(
crate::expression_tree::Expression::BuiltinFunctionReference(
BuiltinFunction::ItemFontMetrics,
None,
),
),
arguments: vec![crate::expression_tree::Expression::ElementReference(
Rc::downgrade(elem),
)],
source_location: None,
}
}),
};
match &mut register.elements.get_mut("TextInput").unwrap() {
ElementType::Builtin(ref mut b) => {
let text_input = Rc::get_mut(b).unwrap();
@ -380,6 +400,18 @@ impl TypeRegister {
text_input
.member_functions
.insert("set-selection-offsets".into(), BuiltinFunction::SetSelectionOffsets);
text_input
.reserved_properties
.insert("font-metrics".into(), font_metrics_prop.clone());
}
_ => unreachable!(),
};
match &mut register.elements.get_mut("Text").unwrap() {
ElementType::Builtin(ref mut b) => {
let text = Rc::get_mut(b).unwrap();
text.reserved_properties.insert("font-metrics".into(), font_metrics_prop);
}
_ => unreachable!(),
@ -548,3 +580,18 @@ pub fn logical_point_type() -> Type {
rust_attributes: None,
}
}
pub fn font_metrics_type() -> Type {
Type::Struct {
fields: IntoIterator::into_iter([
("ascent".to_string(), Type::LogicalLength),
("descent".to_string(), Type::LogicalLength),
("x-height".to_string(), Type::LogicalLength),
("cap-height".to_string(), Type::LogicalLength),
])
.collect(),
name: Some("slint::private_api::FontMetrics".into()),
node: None,
rust_attributes: None,
}
}

View file

@ -56,6 +56,10 @@ pub struct BitmapFont {
pub ascent: f32,
/// The font descent in design metrics (typically negative)
pub descent: f32,
/// The font's x-height.
pub x_height: f32,
/// The font's cap-height.
pub cap_height: f32,
/// A vector of pre-rendered glyph sets. Each glyph set must have the same number of glyphs,
/// which must be at least as big as the largest glyph index in the character map.
pub glyphs: Slice<'static, BitmapGlyphs>,

View file

@ -8,9 +8,9 @@ When adding an item or a property, it needs to be kept in sync with different pl
Lookup the [`crate::items`] module documentation.
*/
use super::{
InputType, Item, ItemConsts, ItemRc, KeyEventResult, KeyEventType, PointArg,
PointerEventButton, RenderingResult, TextHorizontalAlignment, TextOverflow, TextStrokeStyle,
TextVerticalAlignment, TextWrap, VoidArg,
FontMetrics, InputType, Item, ItemConsts, ItemRc, ItemRef, KeyEventResult, KeyEventType,
PointArg, PointerEventButton, RenderingResult, TextHorizontalAlignment, TextOverflow,
TextStrokeStyle, TextVerticalAlignment, TextWrap, VoidArg,
};
use crate::graphics::{Brush, Color, FontRequest};
use crate::input::{
@ -200,6 +200,15 @@ impl RenderText for ComplexText {
}
}
impl ComplexText {
pub fn font_metrics(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>) -> FontMetrics {
let window_inner = WindowInner::from_pub(window_adapter.window());
let scale_factor = ScaleFactor::new(window_inner.scale_factor());
let font_request = self.font_request(window_inner);
window_adapter.renderer().font_metrics(font_request, scale_factor)
}
}
/// The implementation of the `Text` element
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
@ -349,6 +358,15 @@ impl RenderText for SimpleText {
}
}
impl SimpleText {
pub fn font_metrics(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>) -> FontMetrics {
let window_inner = WindowInner::from_pub(window_adapter.window());
let scale_factor = ScaleFactor::new(window_inner.scale_factor());
let font_request = self.font_request(window_inner);
window_adapter.renderer().font_metrics(font_request, scale_factor)
}
}
fn text_layout_info(
text: Pin<&dyn RenderText>,
window_adapter: &Rc<dyn WindowAdapter>,
@ -1822,6 +1840,13 @@ impl TextInput {
undo_items.push(last);
self.undo_items.set(undo_items);
}
pub fn font_metrics(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>) -> FontMetrics {
let window_inner = WindowInner::from_pub(window_adapter.window());
let scale_factor = ScaleFactor::new(window_inner.scale_factor());
let font_request = self.font_request(window_adapter);
window_adapter.renderer().font_metrics(font_request, scale_factor)
}
}
fn next_paragraph_boundary(text: &str, last_cursor_pos: usize) -> usize {
@ -1944,3 +1969,31 @@ pub unsafe extern "C" fn slint_textinput_paste(
let self_rc = ItemRc::new(self_component.clone(), self_index);
text_input.paste(window_adapter, &self_rc);
}
pub fn slint_text_item_fontmetrics(
window_adapter: &Rc<dyn WindowAdapter>,
item_ref: Pin<ItemRef<'_>>,
) -> FontMetrics {
if let Some(simple_text) = ItemRef::downcast_pin::<SimpleText>(item_ref) {
simple_text.font_metrics(&window_adapter)
} else if let Some(complex_text) = ItemRef::downcast_pin::<ComplexText>(item_ref) {
complex_text.font_metrics(&window_adapter)
} else if let Some(text_input) = ItemRef::downcast_pin::<TextInput>(item_ref) {
text_input.font_metrics(&window_adapter)
} else {
Default::default()
}
}
#[cfg(feature = "ffi")]
#[no_mangle]
pub unsafe extern "C" fn slint_cpp_text_item_fontmetrics(
window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
self_index: u32,
) -> FontMetrics {
let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
let self_rc = ItemRc::new(self_component.clone(), self_index);
let self_ref = self_rc.borrow();
slint_text_item_fontmetrics(window_adapter, self_ref)
}

View file

@ -38,6 +38,13 @@ pub trait RendererSealed {
text_wrap: TextWrap,
) -> LogicalSize;
/// Returns the metrics of the given font.
fn font_metrics(
&self,
font_request: crate::graphics::FontRequest,
scale_factor: ScaleFactor,
) -> crate::items::FontMetrics;
/// Returns the (UTF-8) byte offset in the text property that refers to the character that contributed to
/// the glyph cluster that's visually nearest to the given coordinate. This is used for hit-testing,
/// for example when receiving a mouse click into a text field. Then this function returns the "cursor"

View file

@ -47,6 +47,7 @@ macro_rules! declare_ValueType_2 {
crate::lengths::LogicalLength,
crate::component_factory::ComponentFactory,
crate::api::LogicalPosition,
crate::items::FontMetrics,
$(crate::items::$Name,)*
];
};

View file

@ -672,6 +672,14 @@ impl RendererSealed for SoftwareRenderer {
fonts::text_size(font_request, text, max_width, scale_factor, text_wrap)
}
fn font_metrics(
&self,
font_request: crate::graphics::FontRequest,
scale_factor: ScaleFactor,
) -> crate::items::FontMetrics {
fonts::font_metrics(font_request, scale_factor)
}
fn text_input_byte_offset_for_position(
&self,
text_input: Pin<&crate::items::TextInput>,

View file

@ -13,7 +13,7 @@ use super::{PhysicalLength, PhysicalSize};
use crate::graphics::{BitmapFont, FontRequest};
use crate::items::TextWrap;
use crate::lengths::{LogicalLength, LogicalSize, ScaleFactor};
use crate::textlayout::TextLayout;
use crate::textlayout::{FontMetrics, TextLayout};
use crate::Coord;
thread_local! {
@ -61,6 +61,48 @@ pub enum Font {
VectorFont(vectorfont::VectorFont),
}
impl crate::textlayout::FontMetrics<PhysicalLength> for Font {
fn ascent(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.ascent(),
#[cfg(all(feature = "software-renderer-systemfonts", not(target_arch = "wasm32")))]
Font::VectorFont(vector_font) => vector_font.ascent(),
}
}
fn height(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.height(),
#[cfg(all(feature = "software-renderer-systemfonts", not(target_arch = "wasm32")))]
Font::VectorFont(vector_font) => vector_font.height(),
}
}
fn descent(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.descent(),
#[cfg(all(feature = "software-renderer-systemfonts", not(target_arch = "wasm32")))]
Font::VectorFont(vector_font) => vector_font.descent(),
}
}
fn x_height(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.x_height(),
#[cfg(all(feature = "software-renderer-systemfonts", not(target_arch = "wasm32")))]
Font::VectorFont(vector_font) => vector_font.x_height(),
}
}
fn cap_height(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.cap_height(),
#[cfg(all(feature = "software-renderer-systemfonts", not(target_arch = "wasm32")))]
Font::VectorFont(vector_font) => vector_font.cap_height(),
}
}
}
pub fn match_font(request: &FontRequest, scale_factor: ScaleFactor) -> Font {
let requested_weight = request
.weight
@ -173,3 +215,22 @@ pub fn text_size(
(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 _,
}
}

View file

@ -26,6 +26,14 @@ impl BitmapGlyphs {
pub fn pixel_size(&self) -> PhysicalLength {
PhysicalLength::new(self.pixel_size)
}
/// Returns the x-height of the font scaled to the font's pixel size.
pub fn x_height(&self, font: &BitmapFont) -> PhysicalLength {
(PhysicalLength::new(self.pixel_size).cast() * font.x_height / font.units_per_em).cast()
}
/// Returns the cap-height of the font scaled to the font's pixel size.
pub fn cap_height(&self, font: &BitmapFont) -> PhysicalLength {
(PhysicalLength::new(self.pixel_size).cast() * font.cap_height / font.units_per_em).cast()
}
}
// A font that is resolved to a specific pixel size.
@ -127,4 +135,12 @@ impl crate::textlayout::FontMetrics<PhysicalLength> for PixelFont {
fn height(&self) -> PhysicalLength {
self.glyphs.height(self.bitmap_font)
}
fn x_height(&self) -> PhysicalLength {
self.glyphs.x_height(&self.bitmap_font)
}
fn cap_height(&self) -> PhysicalLength {
self.glyphs.cap_height(&self.bitmap_font)
}
}

View file

@ -52,6 +52,8 @@ pub struct VectorFont {
height: PhysicalLength,
scale: FontScaleFactor,
pixel_size: PhysicalLength,
x_height: PhysicalLength,
cap_height: PhysicalLength,
}
impl VectorFont {
@ -68,6 +70,9 @@ impl VectorFont {
let ascender = FontLength::new(face.ascender() as _);
let descender = FontLength::new(face.descender() as _);
let height = FontLength::new(face.height() as _);
let x_height = FontLength::new(face.x_height().unwrap_or_default() as _);
let cap_height =
FontLength::new(face.capital_height().unwrap_or_default() as _);
let units_per_em = face.units_per_em();
let scale = FontScaleFactor::new(pixel_size.get() as f32 / units_per_em as f32);
Self {
@ -78,6 +83,8 @@ impl VectorFont {
height: (height.cast() * scale).cast(),
scale,
pixel_size,
x_height: (x_height.cast() * scale).cast(),
cap_height: (cap_height.cast() * scale).cast(),
}
})
.unwrap()
@ -173,6 +180,14 @@ impl crate::textlayout::FontMetrics<PhysicalLength> for VectorFont {
fn descent(&self) -> PhysicalLength {
self.descender
}
fn x_height(&self) -> PhysicalLength {
self.x_height
}
fn cap_height(&self) -> PhysicalLength {
self.cap_height
}
}
impl super::GlyphRenderer for VectorFont {

View file

@ -413,6 +413,14 @@ impl FontMetrics<f32> for FixedTestFont {
fn descent(&self) -> f32 {
-5.
}
fn x_height(&self) -> f32 {
3.
}
fn cap_height(&self) -> f32 {
4.
}
}
#[test]

View file

@ -74,6 +74,8 @@ pub trait FontMetrics<Length: Copy + core::ops::Sub<Output = Length>> {
}
fn ascent(&self) -> Length;
fn descent(&self) -> Length;
fn x_height(&self) -> Length;
fn cap_height(&self) -> Length;
}
pub trait AbstractFont: TextShaper + FontMetrics<<Self as TextShaper>::Length> {}
@ -298,6 +300,14 @@ impl<'a> FontMetrics<f32> for &rustybuzz::Face<'a> {
fn descent(&self) -> f32 {
self.descender() as _
}
fn x_height(&self) -> f32 {
rustybuzz::ttf_parser::Face::x_height(self).unwrap_or_default() as _
}
fn cap_height(&self) -> f32 {
rustybuzz::ttf_parser::Face::capital_height(self).unwrap_or_default() as _
}
}
#[cfg(test)]

View file

@ -745,6 +745,37 @@ fn call_builtin_function(
panic!("internal error: argument to set-selection-offsetsAll must be an element")
}
}
BuiltinFunction::ItemFontMetrics => {
if arguments.len() != 1 {
panic!(
"internal error: incorrect argument count to item font metrics function call"
)
}
let component = match local_context.component_instance {
ComponentInstance::InstanceRef(c) => c,
ComponentInstance::GlobalComponent(_) => {
panic!(
"Cannot invoke item font metrics function on item from a global component"
)
}
};
if let Expression::ElementReference(element) = &arguments[0] {
generativity::make_guard!(guard);
let elem = element.upgrade().unwrap();
let enclosing_component = enclosing_component_for_element(&elem, component, guard);
let description = enclosing_component.description;
let item_info = &description.items[elem.borrow().id.as_str()];
let item_ref =
unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
let window_adapter = component.window_adapter();
let metrics =
i_slint_core::items::slint_text_item_fontmetrics(&window_adapter, item_ref);
metrics.into()
} else {
panic!("internal error: argument to set-selection-offsetsAll must be an element")
}
}
BuiltinFunction::StringIsFloat => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to StringIsFloat")

View file

@ -107,10 +107,32 @@ pub(crate) fn text_size(
/ scale_factor
}
#[derive(Copy, Clone)]
pub(crate) fn font_metrics(
font_request: i_slint_core::graphics::FontRequest,
) -> i_slint_core::items::FontMetrics {
let primary_font = FONT_CACHE.with(|cache| {
let query = font_request.to_fontdb_query();
cache.borrow_mut().load_single_font(font_request.family.as_ref(), query)
});
let logical_pixel_size = (font_request.pixel_size.unwrap_or(DEFAULT_FONT_SIZE)).get();
let units_per_em = primary_font.design_font_metrics.units_per_em;
i_slint_core::items::FontMetrics {
ascent: primary_font.design_font_metrics.ascent * logical_pixel_size / units_per_em,
descent: primary_font.design_font_metrics.descent * logical_pixel_size / units_per_em,
x_height: primary_font.design_font_metrics.x_height * logical_pixel_size / units_per_em,
cap_height: primary_font.design_font_metrics.cap_height * logical_pixel_size / units_per_em,
}
}
#[derive(Clone)]
struct LoadedFont {
femtovg_font_id: femtovg::FontId,
fontdb_face_id: fontdb::ID,
design_font_metrics: i_slint_common::sharedfontdb::DesignFontMetrics,
}
struct SharedFontData(std::sync::Arc<dyn AsRef<[u8]>>);
@ -189,7 +211,7 @@ impl FontCache {
};
if let Some(loaded_font) = self.loaded_fonts.get(&cache_key) {
return *loaded_font;
return loaded_font.clone();
}
//let now = std::time::Instant::now();
@ -233,13 +255,18 @@ impl FontCache {
.expect("invalid fontdb face id")
});
let design_font_metrics = {
let face = ttf_parser::Face::parse(shared_data.as_ref().as_ref(), face_index).unwrap();
i_slint_common::sharedfontdb::DesignFontMetrics::new(face)
};
let femtovg_font_id = text_context
.add_shared_font_with_index(SharedFontData(shared_data), face_index)
.unwrap();
//println!("Loaded {:#?} in {}ms.", request, now.elapsed().as_millis());
let new_font = LoadedFont { femtovg_font_id, fontdb_face_id };
self.loaded_fonts.insert(cache_key, new_font);
let new_font = LoadedFont { femtovg_font_id, fontdb_face_id, design_font_metrics };
self.loaded_fonts.insert(cache_key, new_font.clone());
new_font
}

View file

@ -357,6 +357,14 @@ impl RendererSealed for FemtoVGRenderer {
crate::fonts::text_size(&font_request, scale_factor, text, max_width)
}
fn font_metrics(
&self,
font_request: i_slint_core::graphics::FontRequest,
_scale_factor: ScaleFactor,
) -> i_slint_core::items::FontMetrics {
crate::fonts::font_metrics(font_request)
}
fn text_input_byte_offset_for_position(
&self,
text_input: Pin<&i_slint_core::items::TextInput>,

View file

@ -426,6 +426,14 @@ impl i_slint_core::renderer::RendererSealed for SkiaRenderer {
/ scale_factor
}
fn font_metrics(
&self,
font_request: i_slint_core::graphics::FontRequest,
scale_factor: ScaleFactor,
) -> i_slint_core::items::FontMetrics {
textlayout::font_metrics(font_request, scale_factor)
}
fn text_input_byte_offset_for_position(
&self,
text_input: std::pin::Pin<&i_slint_core::items::TextInput>,

View file

@ -22,7 +22,7 @@ enum CustomFontSource {
}
struct FontCache {
font_collection: skia_safe::textlayout::FontCollection,
font_collection: RefCell<skia_safe::textlayout::FontCollection>,
font_mgr: skia_safe::FontMgr,
type_face_font_provider: RefCell<skia_safe::textlayout::TypefaceFontProvider>,
custom_fonts: RefCell<HashMap<String, CustomFontSource>>,
@ -39,7 +39,7 @@ thread_local! {
// to pick up the custom font.
font_collection.set_asset_font_manager(Some(type_face_font_provider.clone().into()));
font_collection.set_dynamic_font_manager(font_mgr.clone());
FontCache { font_collection, font_mgr, type_face_font_provider: RefCell::new(type_face_font_provider), custom_fonts: Default::default() }
FontCache { font_collection: RefCell::new(font_collection), font_mgr, type_face_font_provider: RefCell::new(type_face_font_provider), custom_fonts: Default::default() }
}
}
@ -58,6 +58,18 @@ pub struct Selection {
pub underline: bool,
}
fn font_style_for_request(font_request: &FontRequest) -> skia_safe::FontStyle {
skia_safe::FontStyle::new(
font_request.weight.map_or(skia_safe::font_style::Weight::NORMAL, |w| w.into()),
skia_safe::font_style::Width::NORMAL,
if font_request.italic {
skia_safe::font_style::Slant::Italic
} else {
skia_safe::font_style::Slant::Upright
},
)
}
pub fn create_layout(
font_request: FontRequest,
scale_factor: ScaleFactor,
@ -73,7 +85,7 @@ pub fn create_layout(
) -> (skia_safe::textlayout::Paragraph, PhysicalPoint) {
let mut text_style = text_style.unwrap_or_default();
if let Some(family_name) = font_request.family {
if let Some(family_name) = font_request.family.as_ref() {
text_style.set_font_families(&[family_name.as_str()]);
}
@ -83,15 +95,7 @@ pub fn create_layout(
text_style.set_letter_spacing((letter_spacing * scale_factor).get());
}
text_style.set_font_size(pixel_size.get());
text_style.set_font_style(skia_safe::FontStyle::new(
font_request.weight.map_or(skia_safe::font_style::Weight::NORMAL, |w| w.into()),
skia_safe::font_style::Width::NORMAL,
if font_request.italic {
skia_safe::font_style::Slant::Italic
} else {
skia_safe::font_style::Slant::Upright
},
));
text_style.set_font_style(font_style_for_request(&font_request));
let mut style = skia_safe::textlayout::ParagraphStyle::new();
@ -113,7 +117,10 @@ pub fn create_layout(
style.set_text_style(&text_style);
let mut builder = FONT_CACHE.with(|font_cache| {
skia_safe::textlayout::ParagraphBuilder::new(&style, font_cache.font_collection.clone())
skia_safe::textlayout::ParagraphBuilder::new(
&style,
font_cache.font_collection.borrow().clone(),
)
});
if let Some(selection) = selection {
@ -166,6 +173,40 @@ pub fn create_layout(
(paragraph, PhysicalPoint::from_lengths(Default::default(), layout_top_y))
}
pub fn font_metrics(
font_request: i_slint_core::graphics::FontRequest,
scale_factor: ScaleFactor,
) -> i_slint_core::items::FontMetrics {
let (layout, _) = create_layout(
font_request,
scale_factor,
" ",
None,
None,
PhysicalLength::new(f32::MAX),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
None,
);
let fonts = layout.get_fonts();
let Some(font_info) = fonts.first() else {
return Default::default();
};
let metrics = font_info.font.metrics().1;
i_slint_core::items::FontMetrics {
ascent: -metrics.ascent / scale_factor.get(),
descent: -metrics.descent / scale_factor.get(),
x_height: metrics.x_height / scale_factor.get(),
cap_height: metrics.cap_height / scale_factor.get(),
}
}
fn register_font(source: CustomFontSource) -> Result<(), Box<dyn std::error::Error>> {
FONT_CACHE.with(|font_cache| {
if font_cache

View file

@ -0,0 +1,35 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
export component TestCase inherits Window {
width: 100phx;
height: 100phx;
simple-text := Text { }
complex-text := Text {
stroke-width: 2px;
}
text-input := TextInput { }
out property <bool> test: simple-text.font-metrics.ascent == complex-text.font-metrics.ascent && complex-text.font-metrics.ascent == text-input.font-metrics.ascent && text-input.font-metrics.ascent == 7px;
}
/*
```rust
let instance = TestCase::new().unwrap();
assert!(instance.get_test());
```
```cpp
auto handle = TestCase::create();
const TestCase &instance = *handle;
assert(instance.get_test());
```
```js
let instance = new slint.TestCase({});
assert(instance.test);
```
*/

View file

@ -0,0 +1,91 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
import { Slider, Palette } from "std-widgets.slint";
component MetricsLabel {
in property <string> name;
in property <length> value;
in property <length> baseline;
in property <color> color: t.color;
out property <length> text-width: t.preferred-width;
Rectangle {
y: root.baseline - root.value;
height: 1px;
border-color: root.color;
border-width: 1px;
t := Text {
x: 0; //parent.width - self.width;
y: - self.height;
text: {
if root.name == "baseline" {
return root.name;
}
"\{root.name} (\{Math.round(root.value / 1px)}px)";
}
}
}
}
import "../../../examples/printerdemo/ui/fonts/NotoSans-Regular.ttf";
export component AppWindow inherits Window {
width: l.x + l.width;
height: l.y + 2 * l.font-size;
l := Text {
y: self.font-size / 2;
x: max(baseline.text-width, ascent.text-width, descent.text-width, x-height.text-width, cap-height.text-width);
text: "Sphinx";
font-family: "Noto Sans";
font-size: 96px;
}
baseline := MetricsLabel {
x: 0;
y: l.y;
width: 100%;
name: "baseline";
value: 0;
baseline: l.font-metrics.ascent;
color: red;
}
ascent := MetricsLabel {
x: 0;
y: l.y;
width: 100%;
name: "ascent";
value: l.font-metrics.ascent;
baseline: l.font-metrics.ascent;
}
descent := MetricsLabel {
x: 0;
y: l.y;
width: 100%;
name: "descent";
value: l.font-metrics.descent;
baseline: l.font-metrics.ascent;
}
x-height := MetricsLabel {
x: 0;
y: l.y;
width: 100%;
name: "x-height";
value: l.font-metrics.x-height;
baseline: l.font-metrics.ascent;
}
cap-height := MetricsLabel {
x: 0;
y: l.y;
width: 100%;
name: "cap-height";
value: l.font-metrics.cap-height;
baseline: l.font-metrics.ascent;
}
}

View file

@ -321,9 +321,11 @@ fn insert_property_definitions(
}
match &element.borrow().base_type {
ElementType::Component(c) => binding_value(&c.root_element, prop, &mut 0),
ElementType::Builtin(b) => {
b.properties.get(prop).and_then(|p| p.default_value.clone()).unwrap_or_default()
}
ElementType::Builtin(b) => b
.properties
.get(prop)
.and_then(|p| p.default_value.expr(element))
.unwrap_or_default(),
_ => Expression::Invalid,
}
}
@ -383,7 +385,7 @@ pub(super) fn get_properties(
ty: t.ty.clone(),
declared_at: None,
defined_at: None,
default_value: t.default_value.clone(),
default_value: t.default_value.expr(&current_element),
group: b.name.clone(),
group_priority: depth,
})