1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

Move layout-related code into text_layout.rs

This commit is contained in:
Emil Ernerfeldt
2026-03-28 16:26:50 +01:00
parent c340d2f139
commit 23ae8fdacb
2 changed files with 96 additions and 81 deletions

View File

@@ -534,49 +534,16 @@ impl FontFace {
}
}
/// Shape a text run and return the raw [`harfrust::GlyphBuffer`].
///
/// The caller should iterate `glyph_infos()` / `glyph_positions()` (both
/// `Copy` slices) and convert font units to pixels using `metrics.px_scale_factor`.
/// After iteration, recycle the buffer via `glyph_buffer.clear()`.
pub fn shape_text(
&self,
text: &str,
coords: &VariationCoords,
mut buffer: harfrust::UnicodeBuffer,
flags: harfrust::BufferFlags,
) -> harfrust::GlyphBuffer {
let font_ref = &self.font.borrow_dependent().skrifa;
pub(crate) fn skrifa_font_ref(&self) -> &skrifa::FontRef<'_> {
&self.font.borrow_dependent().skrifa
}
// Build shaper with variable font instance if variation coordinates are set.
let variations: Vec<harfrust::Variation> = self
.tweak
.coords
.as_ref()
.iter()
.chain(coords.as_ref().iter())
.map(|&(tag, value)| harfrust::Variation { tag, value })
.collect();
pub(crate) fn tweak(&self) -> &FontTweak {
&self.tweak
}
let instance = if variations.is_empty() {
None
} else {
Some(harfrust::ShaperInstance::from_variations(
font_ref, variations,
))
};
let shaper = self
.shaper_data
.shaper(font_ref)
.instance(instance.as_ref())
.build();
buffer.set_flags(flags);
buffer.push_str(text);
buffer.guess_segment_properties();
shaper.shape(buffer, &[])
pub(crate) fn shaper_data(&self) -> &harfrust::ShaperData {
&self.shaper_data
}
pub fn allocate_glyph(
@@ -708,43 +675,6 @@ impl Font<'_> {
s.chars().all(|c| self.has_glyph(c))
}
/// Segment text into runs where each run uses a single font face.
///
/// Grapheme clusters are never split across runs: if a combining mark
/// falls back to a different font than its base character, it stays
/// with the base character's font (the shaper will handle it).
///
/// NOTE: Segmentation is by font face, not by Unicode script. A run may
/// mix scripts (e.g. Latin + Cyrillic) when they share the same font.
/// This is acceptable for scripts with similar shaping rules, but would
/// need script-aware splitting once RTL/bidi support is added.
///
/// Results are appended to `out` (which is cleared first) to allow
/// the caller to reuse the allocation across calls.
pub(crate) fn segment_into_runs(&mut self, text: &str, out: &mut Vec<TextRun>) {
use unicode_segmentation::UnicodeSegmentation as _;
out.clear();
for (byte_offset, grapheme_str) in text.grapheme_indices(true) {
let byte_end = byte_offset + grapheme_str.len();
let base_char = grapheme_str.chars().next().unwrap_or(' ');
let (font_key, _) = self.glyph_info(base_char);
if let Some(last_run) = out.last_mut()
&& last_run.font_key == font_key
{
last_run.byte_range.end = byte_end;
continue;
}
out.push(TextRun {
font_key,
byte_range: byte_offset..byte_end,
});
}
}
/// `\n` will (intentionally) show up as the replacement character.
pub(crate) fn glyph_info(&mut self, c: char) -> (FontFaceKey, GlyphInfo) {
if let Some(font_index_glyph_info) = self.cached_family.glyph_info_cache.get(&c) {

View File

@@ -12,7 +12,8 @@ use crate::{
use super::{
FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, PlacedRow, Row, RowVisuals,
font::{Font, TextRun},
VariationCoords,
font::{Font, FontFace, TextRun},
};
// ----------------------------------------------------------------------------
@@ -359,7 +360,7 @@ fn layout_section(
continue;
}
font.segment_into_runs(segment, &mut runs);
segment_into_runs(&mut font, segment, &mut runs);
let num_runs = runs.len();
for (run_idx, run) in runs.iter().enumerate() {
@@ -380,7 +381,7 @@ fn layout_section(
flags |= harfrust::BufferFlags::END_OF_TEXT;
}
let glyph_buffer = font_face.shape_text(run_text, &format.coords, shape_buffer, flags);
let glyph_buffer = shape_text(font_face, run_text, &format.coords, shape_buffer, flags);
layout_shaped_run(
&mut font,
@@ -1228,6 +1229,90 @@ impl RowBreakCandidates {
// ----------------------------------------------------------------------------
/// Segment text into runs where each run uses a single font face.
///
/// Grapheme clusters are never split across runs: if a combining mark
/// falls back to a different font than its base character, it stays
/// with the base character's font (the shaper will handle it).
///
/// NOTE: Segmentation is by font face, not by Unicode script. A run may
/// mix scripts (e.g. Latin + Cyrillic) when they share the same font.
/// This is acceptable for scripts with similar shaping rules, but would
/// need script-aware splitting once RTL/bidi support is added.
///
/// Results are appended to `out` (which is cleared first) to allow
/// the caller to reuse the allocation across calls.
fn segment_into_runs(font: &mut Font<'_>, text: &str, out: &mut Vec<TextRun>) {
use unicode_segmentation::UnicodeSegmentation as _;
out.clear();
for (byte_offset, grapheme_str) in text.grapheme_indices(true) {
let byte_end = byte_offset + grapheme_str.len();
let base_char = grapheme_str.chars().next().unwrap_or(' ');
let (font_key, _) = font.glyph_info(base_char);
if let Some(last_run) = out.last_mut()
&& last_run.font_key == font_key
{
last_run.byte_range.end = byte_end;
continue;
}
out.push(TextRun {
font_key,
byte_range: byte_offset..byte_end,
});
}
}
/// Shape a text run and return the raw [`harfrust::GlyphBuffer`].
///
/// The caller should iterate `glyph_infos()` / `glyph_positions()` (both
/// `Copy` slices) and convert font units to pixels using `metrics.px_scale_factor`.
/// After iteration, recycle the buffer via `glyph_buffer.clear()`.
fn shape_text(
font_face: &FontFace,
text: &str,
coords: &VariationCoords,
mut buffer: harfrust::UnicodeBuffer,
flags: harfrust::BufferFlags,
) -> harfrust::GlyphBuffer {
let font_ref = font_face.skrifa_font_ref();
let tweak = font_face.tweak();
// Build shaper with variable font instance if variation coordinates are set.
let variations: Vec<harfrust::Variation> = tweak
.coords
.as_ref()
.iter()
.chain(coords.as_ref().iter())
.map(|&(tag, value)| harfrust::Variation { tag, value })
.collect();
let instance = if variations.is_empty() {
None
} else {
Some(harfrust::ShaperInstance::from_variations(
font_ref, variations,
))
};
let shaper = font_face
.shaper_data()
.shaper(font_ref)
.instance(instance.as_ref())
.build();
buffer.set_flags(flags);
buffer.push_str(text);
buffer.guess_segment_properties();
shaper.shape(buffer, &[])
}
// ----------------------------------------------------------------------------
#[cfg(test)]
mod tests {