Skip to content

Commit 13623bc

Browse files
expensesautofix-ci[bot]tronical
authored
feature/fontique: switch femtovg to parley (#9466)
* Parlay init * Start on femtovg * Cargo fmt * Decimate fonts.rs * Use fill_glyphs * [autofix.ci] apply automated fixes * Use positioned_glyphs instead * Clean up a little * Format * [autofix.ci] apply automated fixes * Few fixes * [autofix.ci] apply automated fixes * More small changes * Clean up * [autofix.ci] apply automated fixes * Display text cursor * Handle text_input_cursor_rect_for_byte_offset * stoke glyphs * Handle text selections * Stroke selection as well * Fix wierd cargo.toml padding * Move selection and stroking to brush settings * Removed commented out code * [autofix.ci] apply automated fixes * Cursor sizing * [autofix.ci] apply automated fixes * Mark unused variables * _scale -> scale * Handle a lot more layout options * Use the parley cursor * Removed unused PhysicalPoint * Start combining stuff WIP * Move things into i_slint_core::textlayout::sharedparley * [autofix.ci] apply automated fixes * Go back to splitting paragraphs correctly * Handle font_metrics via ttf_parser * Move (lack of) overflow handling to sharedparley * [autofix.ci] apply automated fixes * impl Deref for Layout * Be more explit about the width passed to layout being physical * Cargo fmt, rename fonts to font_cache * Use a thread local for layout context * Use parley selection * fix femtovg wgpu * Switch femtovg branch * max_physical_width -> max_width * Box contexts * [autofix.ci] apply automated fixes * Fallback to GenericFamily::SansSerif if no font is set * Add paint.set_font_size * Use max_physical_height * Fix text_size to return correct logical sizes * Use femtovg from crates.io * Fix C++ build The `sharedparley` module declares a public `Layout` struct, which clashes with the `Layout` struct we use in the C++ API. The former however is entirely internal to Rust, so we can instruct cbindgen to ignore the module. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Simon Hausmann <[email protected]>
1 parent e4bf75b commit 13623bc

File tree

13 files changed

+406
-937
lines changed

13 files changed

+406
-937
lines changed

internal/common/sharedfontique.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub use ttf_parser;
77
use std::collections::HashMap;
88
use std::sync::Arc;
99

10-
static COLLECTION: std::sync::LazyLock<Collection> = std::sync::LazyLock::new(|| {
10+
pub static COLLECTION: std::sync::LazyLock<Collection> = std::sync::LazyLock::new(|| {
1111
let mut collection = fontique::Collection::new(fontique::CollectionOptions {
1212
shared: true,
1313
..Default::default()
@@ -55,8 +55,8 @@ pub fn get_collection() -> Collection {
5555

5656
#[derive(Clone)]
5757
pub struct Collection {
58-
inner: fontique::Collection,
59-
source_cache: fontique::SourceCache,
58+
pub inner: fontique::Collection,
59+
pub source_cache: fontique::SourceCache,
6060
pub default_fonts: Arc<HashMap<std::path::PathBuf, fontique::QueryFont>>,
6161
}
6262

internal/compiler/passes/embed_glyphs.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,11 @@ pub fn embed_glyphs<'a>(
137137
let font = {
138138
let mut query = collection.query();
139139

140-
if let Some(ref family) = family {
141-
query.set_families(std::iter::once(fontique::QueryFamily::from(
142-
family.as_str(),
143-
)));
140+
query.set_families(std::iter::once(if let Some(ref family) = family {
141+
fontique::QueryFamily::from(family.as_str())
144142
} else {
145-
query.set_families(std::iter::once(fontique::QueryFamily::Generic(
146-
fontique::GenericFamily::SansSerif,
147-
)));
148-
}
143+
fontique::QueryFamily::Generic(fontique::GenericFamily::SansSerif)
144+
}));
149145

150146
let mut font = None;
151147

internal/core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ unstable-wgpu-26 = ["dep:wgpu-26"]
6767

6868
default = ["std", "unicode"]
6969

70+
shared-parley = ["shared-fontique", "dep:parley"]
71+
7072
[dependencies]
7173
i-slint-common = { workspace = true, features = ["default"] }
7274
i-slint-core-macros = { workspace = true, features = ["default"] }
@@ -100,6 +102,7 @@ unicode-script = { version = "0.5.7", optional = true }
100102
integer-sqrt = { version = "0.1.5" }
101103
bytemuck = { workspace = true, optional = true, features = ["derive"] }
102104
sys-locale = { version = "0.3.2", optional = true }
105+
parley = { version = "0.5.0", optional = true }
103106

104107
image = { workspace = true, optional = true, default-features = false }
105108
clru = { workspace = true, optional = true }

internal/core/graphics.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,11 @@ impl FontRequest {
164164
let mut collection = sharedfontique::get_collection();
165165

166166
let mut query = collection.query();
167-
query.set_families(
168-
self.family.as_ref().map(|family| fontique::QueryFamily::from(family.as_str())),
169-
);
167+
query.set_families(std::iter::once(if let Some(family) = self.family.as_ref() {
168+
fontique::QueryFamily::from(family.as_str())
169+
} else {
170+
fontique::QueryFamily::Generic(fontique::GenericFamily::SansSerif)
171+
}));
170172

171173
query.set_attributes(fontique::Attributes {
172174
weight: self

internal/core/textlayout.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ use linebreak_simple::{BreakOpportunity, LineBreakIterator};
4040
mod fragments;
4141
mod glyphclusters;
4242
mod shaping;
43+
#[cfg(feature = "shared-parley")]
44+
/// cbindgen:ignore
45+
pub mod sharedparley;
4346
use shaping::ShapeBuffer;
4447
pub use shaping::{AbstractFont, FontMetrics, Glyph, TextShaper};
4548

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright © SixtyFPS GmbH <[email protected]>
2+
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3+
4+
pub use parley;
5+
6+
use std::boxed::Box;
7+
use std::cell::RefCell;
8+
9+
use crate::{
10+
graphics::FontRequest,
11+
items::TextStrokeStyle,
12+
lengths::{LogicalLength, ScaleFactor},
13+
textlayout::{TextHorizontalAlignment, TextOverflow, TextVerticalAlignment, TextWrap},
14+
};
15+
use i_slint_common::sharedfontique;
16+
17+
pub const DEFAULT_FONT_SIZE: LogicalLength = LogicalLength::new(12.);
18+
19+
struct Contexts {
20+
layout: parley::LayoutContext<Brush>,
21+
font: parley::FontContext,
22+
}
23+
24+
impl Default for Contexts {
25+
fn default() -> Self {
26+
Self {
27+
font: parley::FontContext {
28+
collection: sharedfontique::COLLECTION.inner.clone(),
29+
source_cache: sharedfontique::COLLECTION.source_cache.clone(),
30+
},
31+
layout: Default::default(),
32+
}
33+
}
34+
}
35+
36+
std::thread_local! {
37+
static CONTEXTS: RefCell<Box<Contexts>> = Default::default();
38+
}
39+
40+
#[derive(Debug, Default, PartialEq, Clone, Copy)]
41+
pub struct Brush {
42+
pub stroke: Option<TextStrokeStyle>,
43+
}
44+
45+
pub struct LayoutOptions {
46+
pub max_width: Option<LogicalLength>,
47+
pub max_height: Option<LogicalLength>,
48+
pub horizontal_align: TextHorizontalAlignment,
49+
pub vertical_align: TextVerticalAlignment,
50+
pub stroke: Option<TextStrokeStyle>,
51+
pub font_request: Option<FontRequest>,
52+
pub text_wrap: TextWrap,
53+
pub text_overflow: TextOverflow,
54+
}
55+
56+
impl Default for LayoutOptions {
57+
fn default() -> Self {
58+
Self {
59+
max_width: None,
60+
max_height: None,
61+
horizontal_align: TextHorizontalAlignment::Left,
62+
vertical_align: TextVerticalAlignment::Top,
63+
stroke: None,
64+
font_request: None,
65+
text_wrap: TextWrap::WordWrap,
66+
text_overflow: TextOverflow::Clip,
67+
}
68+
}
69+
}
70+
71+
pub fn layout(text: &str, scale_factor: ScaleFactor, options: LayoutOptions) -> Layout {
72+
let max_physical_width = options.max_width.map(|max_width| (max_width * scale_factor).get());
73+
let max_physical_height = options.max_height.map(|max_height| max_height * scale_factor);
74+
let pixel_size = options
75+
.font_request
76+
.as_ref()
77+
.and_then(|font_request| font_request.pixel_size)
78+
.unwrap_or(DEFAULT_FONT_SIZE);
79+
80+
let mut layout = CONTEXTS.with_borrow_mut(move |contexts| {
81+
let mut builder =
82+
contexts.layout.ranged_builder(&mut contexts.font, text, scale_factor.get(), true);
83+
if let Some(ref font_request) = options.font_request {
84+
if let Some(family) = &font_request.family {
85+
builder.push_default(parley::StyleProperty::FontStack(
86+
parley::style::FontStack::Single(parley::style::FontFamily::Named(
87+
family.as_str().into(),
88+
)),
89+
));
90+
}
91+
if let Some(weight) = font_request.weight {
92+
builder.push_default(parley::StyleProperty::FontWeight(
93+
parley::style::FontWeight::new(weight as f32),
94+
));
95+
}
96+
if let Some(letter_spacing) = font_request.letter_spacing {
97+
builder.push_default(parley::StyleProperty::LetterSpacing(letter_spacing.get()));
98+
}
99+
builder.push_default(parley::StyleProperty::FontStyle(if font_request.italic {
100+
parley::style::FontStyle::Italic
101+
} else {
102+
parley::style::FontStyle::Normal
103+
}));
104+
}
105+
builder.push_default(parley::StyleProperty::FontSize(pixel_size.get()));
106+
builder.push_default(parley::StyleProperty::WordBreak(match options.text_wrap {
107+
TextWrap::NoWrap => parley::style::WordBreakStrength::KeepAll,
108+
TextWrap::WordWrap => parley::style::WordBreakStrength::Normal,
109+
TextWrap::CharWrap => parley::style::WordBreakStrength::BreakAll,
110+
}));
111+
if options.text_overflow == TextOverflow::Elide {
112+
todo!();
113+
}
114+
115+
builder.push_default(parley::StyleProperty::Brush(Brush { stroke: options.stroke }));
116+
117+
builder.build(text)
118+
});
119+
120+
layout.break_all_lines(max_physical_width);
121+
layout.align(
122+
max_physical_width,
123+
match options.horizontal_align {
124+
TextHorizontalAlignment::Left => parley::Alignment::Left,
125+
TextHorizontalAlignment::Center => parley::Alignment::Middle,
126+
TextHorizontalAlignment::Right => parley::Alignment::Right,
127+
},
128+
parley::AlignmentOptions::default(),
129+
);
130+
131+
let y_offset = match (max_physical_height, options.vertical_align) {
132+
(Some(max_height), TextVerticalAlignment::Center) => {
133+
(max_height.get() - layout.height()) / 2.0
134+
}
135+
(Some(max_height), TextVerticalAlignment::Bottom) => max_height.get() - layout.height(),
136+
(None, _) | (Some(_), TextVerticalAlignment::Top) => 0.0,
137+
};
138+
139+
Layout { inner: layout, y_offset }
140+
}
141+
142+
pub struct Layout {
143+
inner: parley::Layout<Brush>,
144+
pub y_offset: f32,
145+
}
146+
147+
impl std::ops::Deref for Layout {
148+
type Target = parley::Layout<Brush>;
149+
150+
fn deref(&self) -> &Self::Target {
151+
&self.inner
152+
}
153+
}

internal/renderers/femtovg/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ wgpu-26 = ["dep:wgpu-26", "femtovg/wgpu", "i-slint-core/unstable-wgpu-26"]
2323
unstable-wgpu-26 = ["wgpu-26"]
2424

2525
[dependencies]
26-
i-slint-core = { workspace = true, features = ["default", "box-shadow-cache", "shared-fontique"] }
26+
i-slint-core = { workspace = true, features = ["default", "box-shadow-cache", "shared-fontique", "shared-parley"] }
2727
i-slint-core-macros = { workspace = true, features = ["default"] }
2828
i-slint-common = { workspace = true, features = ["default", "shared-fontique"] }
2929

@@ -34,7 +34,7 @@ derive_more = { workspace = true }
3434
lyon_path = "1.0"
3535
pin-weak = "1"
3636
scoped-tls-hkt = "0.1"
37-
femtovg = { version = "0.17.0" }
37+
femtovg = { version = "0.18.1" }
3838
ttf-parser = { workspace = true }
3939
unicode-script = { version = "0.5.4" } # Use the same version was femtovg's rustybuzz, to avoid duplicate crates
4040
imgref = { version = "1.6.1" }
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright © SixtyFPS GmbH <[email protected]>
2+
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3+
4+
// cspell:ignore Noto fontconfig
5+
6+
use core::num::NonZeroUsize;
7+
use femtovg::TextContext;
8+
use i_slint_core::textlayout::sharedparley::parley;
9+
use std::cell::RefCell;
10+
use std::collections::HashMap;
11+
12+
pub struct FontCache {
13+
pub(crate) text_context: femtovg::TextContext,
14+
fonts: HashMap<(u64, u32), femtovg::FontId>,
15+
}
16+
17+
impl Default for FontCache {
18+
fn default() -> Self {
19+
let text_context = TextContext::default();
20+
text_context.resize_shaped_words_cache(NonZeroUsize::new(10_000_000).unwrap());
21+
text_context.resize_shaping_run_cache(NonZeroUsize::new(1_000_000).unwrap());
22+
23+
Self { text_context, fonts: Default::default() }
24+
}
25+
}
26+
27+
impl FontCache {
28+
pub fn font(&mut self, font: &parley::Font) -> femtovg::FontId {
29+
let text_context = self.text_context.clone();
30+
31+
*self.fonts.entry((font.data.id(), font.index)).or_insert_with(move || {
32+
text_context.add_shared_font_with_index(font.data.clone(), font.index).unwrap()
33+
})
34+
}
35+
}
36+
37+
thread_local! {
38+
pub static FONT_CACHE: RefCell<FontCache> = RefCell::new(Default::default())
39+
}

0 commit comments

Comments
 (0)