<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
This is a fix/improvement that makes all kinds of alignments work in
`TextEdit` when a custom `LayoutJob` with halign is used.
I used the simplest approach possible to avoid unwanted bugs as I wasn't
sure what's safe to change, but there's potentially better ways to
achieve this. In particular, I'm not sure I fully understand the
rationale behind aligning rows in a `Galley` based on the latter's
leftmost border, considering the size is what's ultimately used in
widgets (in `TextEdit` at least).
Regardless, here's a demo of this PR:
https://github.com/user-attachments/assets/5d9801d7-73af-4576-80c5-47f169700462
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
- fix for https://github.com/rerun-io/reality/pull/1075
The galleys row size was calculated by looking at the last glyphs pos_x,
which got changed to be rounded to integers when we added subpixel
binning. This introduced a subtle bug which caused the width of galleys
to be slightly off.
This PR fixes this by looking at the actual cursor position instead,
which is not rounded.
Also added a test to ensure this is correct. Previously, for the second
and last line, the `x` was too close to the `0`.
<img width="48" height="67" alt="image"
src="https://github.com/user-attachments/assets/a69a4cc3-b3f3-4553-ab92-73cb2e7a358c"
/>
---------
Co-authored-by: lucasmerlin <8009393+lucasmerlin@users.noreply.github.com>
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* Closes N/A
* [x] I have followed the instructions in the PR template
This was mostly from last month, but I never got around to submitting
it.
This PR adds font variation coordinates to the `TextFormat` struct, and
uses them when rendering text. The coordinates are stored in a
`SmallVec`; I've chosen to store up to 2 inline, which makes it take up
24 bytes (the minimum possible for a `SmallVec`). The variation axis
tags are stored as the `font_types::Tag` type, which I've chosen to
re-export from `epaint::text`.
The variation coordinates are resolved to a `skrifa::Location` during
font rendering/scaling, and are cached in the same way as all the other
scaled metrics. I've renamed the `ScaledMetrics` struct to
`StyledMetrics`, since it now also contains the resolved variation
coordinates. I haven't benchmarked the performance of text layout with
variation coordinates, but the existing text layout performance is
unchanged.
I've replaced the API for manually overriding a font's weight
(https://github.com/emilk/egui/pull/7790) with an API for manually
overriding any variation coordinates via `FontTweak`. This should
support the same use case as #7790 while being substantially more
flexible.
I have *not* yet added any higher-level API for mapping style attributes
(weight, width, slant, etc) to variation coordinates or to different
font faces within a single family. That's a pretty huge can of worms,
and it'd involve rethinking the split between `FontId` and `TextFormat`
(and whether `FontId` is so big that we should provide a way to reuse
it). This API is intentionally pretty low-level for now.
Likewise, I've intentionally not used variation coordinates when
computing a font's row height. I can't think of any fonts that change
their vertical metrics depending on variation axes, so this should be
fine for now.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* [x] I have followed the instructions in the PR template
This PR just exposes the pos_from_layout_cursor function as public.
Hi, I'm trying to make a git gui with a merge editor, and for this I
need a much more efficient and flexible text editor than the one
currently in egui. So I'm working on building a more suitable one, which
I intend to contribute back once it's working, but for now I would like
to not need to fork the entirety of egui. By exposing this one function
I (and others) can much more easily reuse Galleys.
I suggest also exposing end_pos, but I'm not currently using that. Let
me know if I should update this PR to do so.
Thanks for the otherwise awesome tool :)
* Closes N/A
* [x] I have followed the instructions in the PR template
This appears to have snuck in as part of
https://github.com/emilk/egui/pull/7790, which claimed to only be a
bugfix but introduced a new `font_weight` method.
I believe there's no way to access the method from *public* code since
it's only defined on `FontsImpl`, not the public-facing `FontsView`.
It's also not used *privately* in epaint, meaning it's completely dead
code.
Even if we *do* want some sort of future API for getting a font's
weight, it requires more consideration. For instance, this API will
return the default weight for variable fonts, which is not documented
anywhere and might not be what we want.
Previously, when loading a variable font (e.g. via
`egui::FontData::from_static`),
the font was rendered using the default (often the lightest) weight,
ignoring any preferred weight configuration.
This change applies the specified weight to skrifa's `Location` for the
`wght` axis,
ensuring that variable fonts are rendered with the intended font weight.
## Summary
Fixes variable font weight not being applied during rendering. The
`FontData::weight()` method now properly configures the font variation
axis.
## Changes
- Add `location: Location` field to `FontFace` to store variation
coordinates
- Pass `location` parameter through to glyph rendering functions
- Apply weight to skrifa's `LocationRef` in `DrawSettings` and
`HintingInstance`
## Weight Priority
1. `preferred_weight` from `FontData::weight()`
2. OS/2 table's `us_weight_class`
3. Variable font's fvar default value
4. `Location::default()`
## Related Issue
- #3218 : Not follow font id, but goal would be same
## Todo
* [x] Apply preferred font weight when loading variable fonts
* [ ] Add small size variable fonts for docs and egui (need discussion)
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* Closes N/A
* [x] I have followed the instructions in the PR template
I'll probably come back to this and clean it up a bit. This PR
reimplements ab_glyph's functionality on top of Skrifa, a somewhat
lower-level font API that's being used in Chrome now.
Skrifa doesn't perform rasterization itself, so I'm using
[vello_cpu](https://github.com/linebender/vello) from the Linebender
project for rasterization. It's still in its early days, but I believe
it's already quite fast. It also supports color and gradient fills, so
color emoji support will be easier.
Skrifa also supports font hinting, which should make text look a bit
nicer / less blurry.
Here's the current ab_glyph rendering:
<img width="1592" height="1068" alt="image"
src="https://github.com/user-attachments/assets/2385b66e-23f8-4c6e-b8c2-ea90e0eea4e4"
/>
Here's Skrifa *without* hinting--it looks almost identical, but there
are some subpixel differences, probably due to rasterizer behavior:
<img width="1592" height="1068" alt="image"
src="https://github.com/user-attachments/assets/a815f3e9-65ac-4940-bc00-571177bef53d"
/>
Here's Skrifa *with* hinting:
<img width="1592" height="1068" alt="image"
src="https://github.com/user-attachments/assets/d6cc0669-3537-4377-bba9-ed5ef09664db"
/>
Hinting does make the horizontal strokes look a bit bolder, which makes
me wonder once again about increasing the font weight from "light" to
"regular".
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Selected text now gets the color of `visuals.selection.stroke.color`.
This means you can have inverted colors for selected text, like in the
new test:
<img width="154" height="46" alt="image"
src="https://github.com/user-attachments/assets/2666361d-d7e2-4d50-8e4d-2fcc128f1a81"
/>
It also means the color of selected text in labels matches that of the
text color of selected buttons.
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
Moves `ends_with_newline` into `PlacedRow` to avoid clones during
layout.
I don't think there was a rationale stronger than "don't change too
much" for not doing this in https://github.com/emilk/egui/pull/5411, so
I should've just done this from the start.
This was a significant part of the profile for text layout (as it cloned
almost every `Row`, even though it only needed to change a single
boolean).
Before:
<img width="757" height="250" alt="image"
src="https://github.com/user-attachments/assets/d1c2afd1-f1ec-4cf5-9d05-f5a5a78052df"
/>
After:
<img width="615" height="249" alt="image"
src="https://github.com/user-attachments/assets/c70966da-c892-4e84-adba-494d0f37f263"
/>
(note that these profiles focus solely on the top-level
`Galley::layout_inline` subtree, also don't compare sample count as the
duration of these tests was completely arbitrary)
egui_demo_lib `*text_layout*` benches:
<img width="791" height="461" alt="image"
src="https://github.com/user-attachments/assets/4f97ce84-2768-4876-9488-d42f8f358ed1"
/>
* [X] I have followed the instructions in the PR template
(As usual, the tests fail for me even on master but the failures on
master and with these changes seem the same :))
Fixes#7378
Includes a regression test that previously failed and now succeeds.
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
* Follow up to #7146
Previously when galleys were splitted, each exept the last had an extra
empty row that had to be removed when they were concated. This changes
it to remove the `\n` from the layout jobs when splitting.
* [x] I have followed the instructions in the PR template
Splitting this out from the Parley work as requested. This removes
`FontImage` and makes the font atlas use a `ColorImage`. It converts
alpha to coverage at glyph-drawing time, not at delta-upload time.
This doesn't do much now, but will allow for color emoji rendering once
we start using Parley.
I've changed things around so that we pass in `text_alpha_to_coverage`
to the `Fonts` the same way we do with `pixels_per_point` and
`max_texture_side`, reusing the existing code to check if the setting
differs and recreating the font atlas if so. I'm not quite sure why this
wasn't done in the first place.
I've left `ImageData` as an enum for now, in case we want to add support
for more texture pixel formats in the future (which I personally think
would be worthwhile). If you'd like, I can just remove that enum
entirely.
Closes#7077.
This fixes the problem shown in #7077 where clearing a `TextEdit`
wouldn't reset its cursor position. I've fixed that by adding back the
`TextCursorState::range` method, which clamps the selection range to
that of the passed `Galley`, and calling it in the same places where it
was called before #5785.
(/cc @juancampa)
* [x] I have followed the instructions in the PR template
Fixes a regression introduced in https://github.com/emilk/egui/pull/5411
(possibly
d74bee536f)
that breaks `leading_space` handling.
I think this is what the condition should be but I haven't touched this
code in a while.
## What
(written by @emilk)
When editing long text (thousands of line), egui would previously
re-layout the entire text on each edit. This could be slow.
With this PR, we instead split the text into paragraphs (split on `\n`)
and then cache each such paragraph. When editing text then, only the
changed paragraph needs to be laid out again.
Still, there is overhead from splitting the text, hashing each
paragraph, and then joining the results, so the runtime complexity is
still O(N).
In our benchmark, editing a 2000 line string goes from ~8ms to ~300 ms,
a speedup of ~25x.
In the future, we could also consider laying out each paragraph in
parallel, to speed up the initial layout of the text.
## Details
This is an ~~almost complete~~ implementation of the approach described
by emilk [in this
comment](<https://github.com/emilk/egui/issues/3086#issuecomment-1724205777>),
excluding CoW semantics for `LayoutJob` (but including them for `Row`).
It supersedes the previous unsuccessful attempt here:
https://github.com/emilk/egui/pull/4000.
Draft because:
- [X] ~~Currently individual rows will have `ends_with_newline` always
set to false.
This breaks selection with Ctrl+A (and probably many other things)~~
- [X] ~~The whole block for doing the splitting and merging should
probably become a function (I'll do that later).~~
- [X] ~~I haven't run the check script, the tests, and haven't made sure
all of the examples build (although I assume they probably don't rely on
Galley internals).~~
- [x] ~~Layout is sometimes incorrect (missing empty lines, wrapping
sometimes makes text overlap).~~
- A lot of text-related code had to be changed so this needs to be
properly tested to ensure no layout issues were introduced, especially
relating to the now row-relative coordinate system of `Row`s. Also this
requires that we're fine making these very breaking changes.
It does significantly improve the performance of rendering large blocks
of text (if they have many newlines), this is the test program I used to
test it (adapted from <https://github.com/emilk/egui/issues/3086>):
<details>
<summary>code</summary>
```rust
use eframe::egui::{self, CentralPanel, TextEdit};
use std::fmt::Write;
fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions {
..Default::default()
};
eframe::run_native(
"editor big file test",
options,
Box::new(|_cc| Ok(Box::<MyApp>::new(MyApp::new()))),
)
}
struct MyApp {
text: String,
}
impl MyApp {
fn new() -> Self {
let mut string = String::new();
for line_bytes in (0..50000).map(|_| (0u8..50)) {
for byte in line_bytes {
write!(string, " {byte:02x}").unwrap();
}
write!(string, "\n").unwrap();
}
println!("total bytes: {}", string.len());
MyApp { text: string }
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
CentralPanel::default().show(ctx, |ui| {
let start = std::time::Instant::now();
egui::ScrollArea::vertical().show(ui, |ui| {
let code_editor = TextEdit::multiline(&mut self.text)
.code_editor()
.desired_width(f32::INFINITY)
.desired_rows(40);
let response = code_editor.show(ui).response;
if response.changed() {
println!("total bytes now: {}", self.text.len());
}
});
let end = std::time::Instant::now();
let time_to_update = end - start;
if time_to_update.as_secs_f32() > 0.5 {
println!("Long update took {:.3}s", time_to_update.as_secs_f32())
}
});
}
}
```
</details>
I think the way to proceed would be to make a new type, something like
`PositionedRow`, that would wrap an `Arc<Row>` but have a separate `pos`
~~and `ends_with_newline`~~ (that would mean `Row` only holds a `size`
instead of a `rect`). This type would of course have getters that would
allow you to easily get a `Rect` from it and probably a `Deref` to the
underlying `Row`.
~~I haven't done this yet because I wanted to get some opinions whether
this would be an acceptable API first.~~ This is now implemented, but of
course I'm still open to discussion about this approach and whether it's
what we want to do.
Breaking changes (currently):
- The `Galley::rows` field has a different type.
- There is now a `PlacedRow` wrapper for `Row`.
- `Row` now uses a coordinate system relative to itself instead of the
`Galley`.
* Closes <https://github.com/emilk/egui/issues/3086>
* [X] I have followed the instructions in the PR template
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Might want to draw from `interaction.interact_radius` style instead of
hard-coding the margin, but I didn't want to create a breaking change.
If desired, I can follow up with a separate PR to address that concern.
* Closes <https://github.com/emilk/egui/issues/5796>
* [x] I have followed the instructions in the PR template
Enabled the `missing_assert_message` lint
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Lucas Meurer <lucasmeurer96@gmail.com>
This removes the `expand(1.0)` on text background colors, since it makes
translucent background colors have bad looking bleeding.
There is probably a smarter solution than disabling the highlighting
entirely, but I don't see a way to do that while keeping the area
consumed consistent between translucent/solid colors, or adding a decent
step up in complexity.
Since this makes it impossible to tell if selected text is highlighted,
this also adds a blanket `0.5` gamma multiply to the text selection
background color. If that is undesirable because it's a bad arbitrary
number choice, or if it's too much of an unexpected change and just the
default values should be changed, please let me know.
These changes cause the tests that use screenshots with highlighted text
to fail, though I am not sure how to update those tests to match the
changes.
<details>
<summary>Comparison Images</summary>
Current:

After changes:

</details>
<details>
<summary>Code used to make comparison images</summary>
```rs
fn color_text_format(ui: &Ui, color: Color32) -> TextFormat {
TextFormat { font_id: FontId::monospace(ui.text_style_height(&egui::TextStyle::Monospace)), background: color, ..Default::default() }
}
fn color_sequence_galley(ui: &Ui, text: &str, colors: [Color32; 3]) -> Arc<Galley> {
let mut layout_job = LayoutJob::default();
for color in colors {
layout_job.append(text, 0.0, color_text_format(ui, color));
}
ui.fonts(|f| f.layout_job(layout_job))
}
fn color_sequence_row(ui: &mut Ui, label_text: &str, text: &str, colors: [Color32; 3]) {
ui.label(label_text);
ui.label(color_sequence_galley(ui, text, colors));
ui.end_row();
}
egui::Grid::new("comparison display").show(ui, |ui| {
ui.ctx().set_pixels_per_point(2.0);
let transparent = Color32::TRANSPARENT;
let solid = Color32::RED;
let solid_2 = Color32::GREEN;
let translucent_1 = Color32::GRAY.gamma_multiply(0.5);
let translucent_2 = Color32::GREEN.gamma_multiply(0.5);
color_sequence_row(ui, "Transparent to Solid:", " ", [transparent, solid, transparent]);
color_sequence_row(ui, "Translucent to Transparent:", " ", [transparent, translucent_1, transparent]);
color_sequence_row(ui, "Solid to Transparent:", " ", [solid, solid_2, solid]);
color_sequence_row(ui, "Solid to Solid:", " ", [solid, transparent, solid]);
color_sequence_row(ui, "Solid to Translucent:", " ", [solid, translucent_1, solid]);
color_sequence_row(ui, "Translucent to Translucent:", " ", [translucent_1, translucent_2, translucent_1]);
color_sequence_row(ui, "Transparent to Solid:", "a", [transparent, solid, transparent]);
color_sequence_row(ui, "Translucent to Transparent:", "a", [transparent, translucent_1, transparent]);
color_sequence_row(ui, "Solid to Transparent:", "a", [solid, solid_2, solid]);
color_sequence_row(ui, "Solid to Solid:", "a", [solid, transparent, solid]);
color_sequence_row(ui, "Solid to Translucent:", "a", [solid, translucent_1, solid]);
color_sequence_row(ui, "Translucent to Translucent:", "a", [translucent_1, translucent_2, translucent_1]);
})
```
</details>
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* [x] I have followed the instructions in the PR template
This PR implements `AsRef<[u8]>` for `FontData`, allowing it to be
passed into `fontdb`'s
[`Source`](https://docs.rs/fontdb/0.16.2/fontdb/enum.Source.html) type.
This would allow `egui` and `cosmic_text` to share font data with
eachother
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* Closes N/A, but this is part of
https://github.com/emilk/egui/issues/3378
* [x] I have followed the instructions in the PR template
Other text layout libraries in Rust--namely, Parley and Cosmic
Text--have one canonical text cursor type (Parley's is a byte index,
Cosmic Text's also stores the line index). To prepare for migrating egui
to one of those libraries, it should also have only one text cursor
type. I also think simplifying the API is a good idea in and of
itself--having three different cursor types that you have to convert
between (and a `Cursor` struct which contains all three at once) is
confusing.
After a bit of experimentation, I found that the best cursor type to
coalesce around is `CCursor`. In the few places where we need a
paragraph index or row/column position, we can calculate them as
necessary.
I've removed `CursorRange` and `PCursorRange` (the latter appears to
have never been used), merging the functionality with `CCursorRange`. To
preserve the cursor position when navigating row-by-row, `CCursorRange`
now stores the previous horizontal position of the cursor.
I've also removed `PCursor`, and renamed `RowCursor` to `LayoutCursor`
(since it includes not only the row but the column). I have not renamed
either `CCursorRange` or `CCursor` as those names are used in a lot of
places, and I don't want to clutter this PR with a bunch of renames.
I'll leave it for a later PR.
Finally, I've removed the deprecated methods from `TextEditState`--it
made the refactoring easier, and it should be pretty easy to migrate to
the equivalent `TextCursorState` methods.
I'm not sure how many breaking changes people will actually encounter. A
lot of these APIs were technically public, but I don't think many were
useful. The `TextBuffer` trait now takes `&CCursorRange` instead of
`&CursorRange` in a couple of methods, and I renamed
`CCursorRange::sorted` to `CCursorRange::sorted_cursors` to match
`CursorRange`.
I did encounter a couple of apparent minor bugs when testing out text
cursor behavior, but I checked them against the current version of egui
and they're all pre-existing.
egui never accesses the `FontDefinitions`' member fields mutably, except
in `fonts_tweak_ui` where it cloned the `FontDefinitions` object anyway.
This patch reduces system memory consumption for shared font
definitions.
And also removes some overhead from copying (e.g. for the per
`pixel_per_points` font atlas)
Also it allows to keep a copy of the font definitions outside of egui.
In my App that uses international fonts:
Before:

New:

Note: If `Arc` is not wanted, then it could ofc be abstracted away.
I know this is quite a breaking change API wise, but would like to hear
your opinion.
When using fonts with an average of 50,000 characters,
'epaint texture atlas overflowed!' may be printed and cause problems.
It is necessary to expand the max value related to texture.
* Closes#5256
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
make it easier to add fonts.
For example if I want to add a custom FontFamily or if the user wants to
add a Chinese fallback
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>