A Length::dp font size keeps its logical size on a scaled output instead of growing with the scale
CI / build + test (push) Canceled after 0s
CI / cargo audit (push) Canceled after 0s

On a 14.1" 2560x1440 laptop, where forge picks a 2.25 output scale and ltk draws at buffer scale 3, the title of every settings screen but the first came out about three times its design size, while on a 1920x1080 laptop at scale 1 the same title was smaller than the 28 px heading of the home screen. That title is a `list_item` with `.font_size( Length::widget( 20.0 ) )`, which in `WidgetScaling::Physical` is `Length::dp( 20.0 )`.
`Canvas::resolve_font` resolved a `Dp` length with the canvas' density, which the runtime sets to the output scale, and returned it as a font size. Font sizes are logical: the raster path multiplies them by `dpi_scale`. A `Dp` font size was therefore scaled twice, 20 × 3 = 60 logical px and 180 physical, against 20 at scale 1. Geometry did not have the problem, because `resolve_geom` returns physical pixels that nothing scales again, and neither did a stock widget without an override, because `font_px` already divided by `dpi_scale` in Physical mode. The same `dp` value thus gave one size through `font_px` and another through an explicit override.
`resolve_font` now resolves `Dp` with `density / dpi_scale`, so that `× dpi_scale` at raster yields `n × density` physical pixels, the same as `resolve_geom` gives the same length. Pixel, viewport and fluid lengths are untouched, since they do not depend on the density. The Physical branch of `font_px` now delegates to `resolve_font( Length::dp( design_px ) )`, which is the formula it carried on its own. Every caller of `resolve_font` is affected: the `font_size` overrides of `button`, `text_edit` and `list_item`, and the `size` of `text` and `rich_text` when given a `Dp` or, in Physical mode, a `Length::widget`. At scale 1 nothing changes.
The new test `dp_font_matches_dp_geometry_on_hidpi` checks at scale 3 that a 20 dp font resolves to 20 logical px, equals `font_px( 20 )` for `Length::widget`, and times the scale equals the same length resolved as geometry.
This commit is contained in:
2026-10-02 07:55:23 +02:00
parent 88f69ebffe
commit 0fd23acd2b
+23 -5
View File
@@ -336,10 +336,12 @@ impl Canvas
/// [`Self::viewport_logical`], with this canvas' [`Self::density`],
/// times the global accessibility [`crate::text_scale`]. Counterpart
/// of [`Self::resolve_geom`] for font sizes, which are handed to the
/// raster path pre-`dpi_scale`.
/// raster path pre-`dpi_scale`, so `Dp` uses `density / dpi_scale`.
pub fn resolve_font( &self, l: Length ) -> f32
{
l.resolve_with_density( self.viewport_logical(), Length::EM_BASE_DEFAULT, self.density() )
let scale = self.dpi_scale();
let scale = if scale > 0.0 { scale } else { 1.0 };
l.resolve_with_density( self.viewport_logical(), Length::EM_BASE_DEFAULT, self.density() / scale )
* crate::types::text_scale()
}
@@ -375,9 +377,7 @@ impl Canvas
}
WidgetScaling::Physical =>
{
let scale = self.dpi_scale();
let scale = if scale > 0.0 { scale } else { 1.0 };
design_px * self.density() / scale * crate::types::text_scale()
self.resolve_font( Length::dp( design_px ) )
}
}
}
@@ -1148,6 +1148,24 @@ mod viewport_tests
set_density( 1.0 );
set_widget_scaling( WidgetScaling::Fluid );
}
#[ test ]
fn dp_font_matches_dp_geometry_on_hidpi()
{
use crate::types::{ set_widget_scaling, set_density, WidgetScaling };
let _g = crate::TEST_GLOBALS_LOCK.lock().unwrap_or_else( |e| e.into_inner() );
set_widget_scaling( WidgetScaling::Physical );
set_density( 3.0 );
let mut c = Canvas::new( 2560, 1440 );
c.set_dpi_scale( 3.0 );
assert_eq!( c.resolve_font( Length::dp( 20.0 ) ), 20.0 );
assert_eq!( c.resolve_font( Length::widget( 20.0 ) ), c.font_px( 20.0 ) );
assert_eq!( c.resolve_font( Length::dp( 20.0 ) ) * 3.0, c.resolve_geom( Length::dp( 20.0 ) ) );
set_density( 1.0 );
set_widget_scaling( WidgetScaling::Fluid );
}
}
#[ cfg( test ) ]