Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
2b1b5f1
Parlay init
expenses Sep 18, 2025
1896dff
Start on femtovg
expenses Sep 19, 2025
3cdfcd8
Cargo fmt
expenses Sep 19, 2025
92abddf
Decimate fonts.rs
expenses Sep 19, 2025
3ca1cbb
Use fill_glyphs
expenses Sep 19, 2025
8e436b5
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 19, 2025
1c7f237
Use positioned_glyphs instead
expenses Sep 19, 2025
5e032fd
Clean up a little
expenses Sep 19, 2025
e7f3eef
Format
expenses Sep 19, 2025
6ad8388
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 19, 2025
250d677
Few fixes
expenses Sep 19, 2025
853dfae
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 22, 2025
d386748
More small changes
expenses Sep 19, 2025
50af622
Clean up
expenses Sep 19, 2025
8a920cc
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 22, 2025
59b84e8
Display text cursor
expenses Sep 19, 2025
9181073
Handle text_input_cursor_rect_for_byte_offset
expenses Sep 19, 2025
2367531
stoke glyphs
expenses Sep 19, 2025
bb7e23f
Handle text selections
expenses Sep 19, 2025
9de1a06
Stroke selection as well
expenses Sep 19, 2025
6e79006
Fix wierd cargo.toml padding
expenses Sep 19, 2025
d400847
Move selection and stroking to brush settings
expenses Sep 19, 2025
f05b211
Removed commented out code
expenses Sep 19, 2025
094a5b6
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 22, 2025
6a879cb
Cursor sizing
expenses Sep 19, 2025
4de14d4
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 22, 2025
fb40679
Mark unused variables
expenses Sep 19, 2025
572868a
_scale -> scale
expenses Sep 23, 2025
0c0009a
Handle a lot more layout options
expenses Sep 23, 2025
c33437b
Use the parley cursor
expenses Sep 23, 2025
a5a2c31
Removed unused PhysicalPoint
expenses Sep 23, 2025
3b0a5a8
Start combining stuff WIP
expenses Sep 23, 2025
ebb04d2
Move things into i_slint_core::textlayout::sharedparley
expenses Sep 23, 2025
d312751
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 23, 2025
316a4bd
Go back to splitting paragraphs correctly
expenses Sep 23, 2025
de0a1ae
Handle font_metrics via ttf_parser
expenses Sep 23, 2025
16a1b33
Move (lack of) overflow handling to sharedparley
expenses Sep 23, 2025
f2864e8
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 23, 2025
24b0e9b
impl Deref for Layout
expenses Sep 24, 2025
8198345
Be more explit about the width passed to layout being physical
expenses Sep 24, 2025
275293c
Cargo fmt, rename fonts to font_cache
expenses Sep 24, 2025
74645aa
Use a thread local for layout context
expenses Sep 24, 2025
05c4c96
Use parley selection
expenses Sep 24, 2025
bab85e7
fix femtovg wgpu
expenses Sep 24, 2025
98e882c
Switch femtovg branch
expenses Sep 24, 2025
b7ba9d5
max_physical_width -> max_width
expenses Sep 24, 2025
75e351a
Box contexts
expenses Sep 24, 2025
ad073dc
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 24, 2025
c82870f
Fallback to GenericFamily::SansSerif if no font is set
expenses Sep 19, 2025
58536c2
Add paint.set_font_size
expenses Sep 25, 2025
41719fa
Use max_physical_height
expenses Sep 25, 2025
7a1a06e
Fix text_size to return correct logical sizes
tronical Sep 25, 2025
5b2bc7c
Use femtovg from crates.io
expenses Sep 25, 2025
48be35b
Fix C++ build
tronical Sep 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions internal/common/sharedfontique.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub use ttf_parser;
use std::collections::HashMap;
use std::sync::Arc;

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

#[derive(Clone)]
pub struct Collection {
inner: fontique::Collection,
source_cache: fontique::SourceCache,
pub inner: fontique::Collection,
pub source_cache: fontique::SourceCache,
pub default_fonts: Arc<HashMap<std::path::PathBuf, fontique::QueryFont>>,
}

Expand Down
12 changes: 4 additions & 8 deletions internal/compiler/passes/embed_glyphs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,11 @@ pub fn embed_glyphs<'a>(
let font = {
let mut query = collection.query();

if let Some(ref family) = family {
query.set_families(std::iter::once(fontique::QueryFamily::from(
family.as_str(),
)));
query.set_families(std::iter::once(if let Some(ref family) = family {
fontique::QueryFamily::from(family.as_str())
} else {
query.set_families(std::iter::once(fontique::QueryFamily::Generic(
fontique::GenericFamily::SansSerif,
)));
}
fontique::QueryFamily::Generic(fontique::GenericFamily::SansSerif)
}));

let mut font = None;

Expand Down
3 changes: 3 additions & 0 deletions internal/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ unstable-wgpu-26 = ["dep:wgpu-26"]

default = ["std", "unicode"]

shared-parley = ["shared-fontique", "dep:parley"]

[dependencies]
i-slint-common = { workspace = true, features = ["default"] }
i-slint-core-macros = { workspace = true, features = ["default"] }
Expand Down Expand Up @@ -100,6 +102,7 @@ unicode-script = { version = "0.5.7", optional = true }
integer-sqrt = { version = "0.1.5" }
bytemuck = { workspace = true, optional = true, features = ["derive"] }
sys-locale = { version = "0.3.2", optional = true }
parley = { version = "0.5.0", optional = true }

image = { workspace = true, optional = true, default-features = false }
clru = { workspace = true, optional = true }
Expand Down
8 changes: 5 additions & 3 deletions internal/core/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,11 @@ impl FontRequest {
let mut collection = sharedfontique::get_collection();

let mut query = collection.query();
query.set_families(
self.family.as_ref().map(|family| fontique::QueryFamily::from(family.as_str())),
);
query.set_families(std::iter::once(if let Some(family) = self.family.as_ref() {
fontique::QueryFamily::from(family.as_str())
} else {
fontique::QueryFamily::Generic(fontique::GenericFamily::SansSerif)
}));

query.set_attributes(fontique::Attributes {
weight: self
Expand Down
3 changes: 3 additions & 0 deletions internal/core/textlayout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ use linebreak_simple::{BreakOpportunity, LineBreakIterator};
mod fragments;
mod glyphclusters;
mod shaping;
#[cfg(feature = "shared-parley")]
/// cbindgen:ignore
pub mod sharedparley;
use shaping::ShapeBuffer;
pub use shaping::{AbstractFont, FontMetrics, Glyph, TextShaper};

Expand Down
153 changes: 153 additions & 0 deletions internal/core/textlayout/sharedparley.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright © SixtyFPS GmbH <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

pub use parley;

use std::boxed::Box;
use std::cell::RefCell;

use crate::{
graphics::FontRequest,
items::TextStrokeStyle,
lengths::{LogicalLength, ScaleFactor},
textlayout::{TextHorizontalAlignment, TextOverflow, TextVerticalAlignment, TextWrap},
};
use i_slint_common::sharedfontique;

pub const DEFAULT_FONT_SIZE: LogicalLength = LogicalLength::new(12.);

struct Contexts {
layout: parley::LayoutContext<Brush>,
font: parley::FontContext,
}

impl Default for Contexts {
fn default() -> Self {
Self {
font: parley::FontContext {
collection: sharedfontique::COLLECTION.inner.clone(),
source_cache: sharedfontique::COLLECTION.source_cache.clone(),
},
layout: Default::default(),
}
}
}

std::thread_local! {
static CONTEXTS: RefCell<Box<Contexts>> = Default::default();
}

#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub struct Brush {
pub stroke: Option<TextStrokeStyle>,
}

pub struct LayoutOptions {
pub max_width: Option<LogicalLength>,
pub max_height: Option<LogicalLength>,
pub horizontal_align: TextHorizontalAlignment,
pub vertical_align: TextVerticalAlignment,
pub stroke: Option<TextStrokeStyle>,
pub font_request: Option<FontRequest>,
pub text_wrap: TextWrap,
pub text_overflow: TextOverflow,
}

impl Default for LayoutOptions {
fn default() -> Self {
Self {
max_width: None,
max_height: None,
horizontal_align: TextHorizontalAlignment::Left,
vertical_align: TextVerticalAlignment::Top,
stroke: None,
font_request: None,
text_wrap: TextWrap::WordWrap,
text_overflow: TextOverflow::Clip,
}
}
}

pub fn layout(text: &str, scale_factor: ScaleFactor, options: LayoutOptions) -> Layout {
let max_physical_width = options.max_width.map(|max_width| (max_width * scale_factor).get());
let max_physical_height = options.max_height.map(|max_height| max_height * scale_factor);
let pixel_size = options
.font_request
.as_ref()
.and_then(|font_request| font_request.pixel_size)
.unwrap_or(DEFAULT_FONT_SIZE);

let mut layout = CONTEXTS.with_borrow_mut(move |contexts| {
let mut builder =
contexts.layout.ranged_builder(&mut contexts.font, text, scale_factor.get(), true);
if let Some(ref font_request) = options.font_request {
if let Some(family) = &font_request.family {
builder.push_default(parley::StyleProperty::FontStack(
parley::style::FontStack::Single(parley::style::FontFamily::Named(
family.as_str().into(),
)),
));
}
if let Some(weight) = font_request.weight {
builder.push_default(parley::StyleProperty::FontWeight(
parley::style::FontWeight::new(weight as f32),
));
}
if let Some(letter_spacing) = font_request.letter_spacing {
builder.push_default(parley::StyleProperty::LetterSpacing(letter_spacing.get()));
}
builder.push_default(parley::StyleProperty::FontStyle(if font_request.italic {
parley::style::FontStyle::Italic
} else {
parley::style::FontStyle::Normal
}));
}
builder.push_default(parley::StyleProperty::FontSize(pixel_size.get()));
builder.push_default(parley::StyleProperty::WordBreak(match options.text_wrap {
TextWrap::NoWrap => parley::style::WordBreakStrength::KeepAll,
TextWrap::WordWrap => parley::style::WordBreakStrength::Normal,
TextWrap::CharWrap => parley::style::WordBreakStrength::BreakAll,
}));
if options.text_overflow == TextOverflow::Elide {
todo!();
}

builder.push_default(parley::StyleProperty::Brush(Brush { stroke: options.stroke }));

builder.build(text)
});

layout.break_all_lines(max_physical_width);
layout.align(
max_physical_width,
match options.horizontal_align {
TextHorizontalAlignment::Left => parley::Alignment::Left,
TextHorizontalAlignment::Center => parley::Alignment::Middle,
TextHorizontalAlignment::Right => parley::Alignment::Right,
},
parley::AlignmentOptions::default(),
);

let y_offset = match (max_physical_height, options.vertical_align) {
(Some(max_height), TextVerticalAlignment::Center) => {
(max_height.get() - layout.height()) / 2.0
}
(Some(max_height), TextVerticalAlignment::Bottom) => max_height.get() - layout.height(),
(None, _) | (Some(_), TextVerticalAlignment::Top) => 0.0,
};

Layout { inner: layout, y_offset }
}

pub struct Layout {
inner: parley::Layout<Brush>,
pub y_offset: f32,
}

impl std::ops::Deref for Layout {
type Target = parley::Layout<Brush>;

fn deref(&self) -> &Self::Target {
&self.inner
}
}
4 changes: 2 additions & 2 deletions internal/renderers/femtovg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ wgpu-26 = ["dep:wgpu-26", "femtovg/wgpu", "i-slint-core/unstable-wgpu-26"]
unstable-wgpu-26 = ["wgpu-26"]

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

Expand All @@ -34,7 +34,7 @@ derive_more = { workspace = true }
lyon_path = "1.0"
pin-weak = "1"
scoped-tls-hkt = "0.1"
femtovg = { version = "0.17.0" }
femtovg = { version = "0.18.1" }
ttf-parser = { workspace = true }
unicode-script = { version = "0.5.4" } # Use the same version was femtovg's rustybuzz, to avoid duplicate crates
imgref = { version = "1.6.1" }
Expand Down
39 changes: 39 additions & 0 deletions internal/renderers/femtovg/font_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright © SixtyFPS GmbH <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

// cspell:ignore Noto fontconfig

use core::num::NonZeroUsize;
use femtovg::TextContext;
use i_slint_core::textlayout::sharedparley::parley;
use std::cell::RefCell;
use std::collections::HashMap;

pub struct FontCache {
pub(crate) text_context: femtovg::TextContext,
fonts: HashMap<(u64, u32), femtovg::FontId>,
}

impl Default for FontCache {
fn default() -> Self {
let text_context = TextContext::default();
text_context.resize_shaped_words_cache(NonZeroUsize::new(10_000_000).unwrap());
text_context.resize_shaping_run_cache(NonZeroUsize::new(1_000_000).unwrap());

Self { text_context, fonts: Default::default() }
}
}

impl FontCache {
pub fn font(&mut self, font: &parley::Font) -> femtovg::FontId {
let text_context = self.text_context.clone();

*self.fonts.entry((font.data.id(), font.index)).or_insert_with(move || {
text_context.add_shared_font_with_index(font.data.clone(), font.index).unwrap()
})
}
}

thread_local! {
pub static FONT_CACHE: RefCell<FontCache> = RefCell::new(Default::default())
}
Loading
Loading