Files
iris/core/src/primitive/text.rs
T

1136 lines
36 KiB
Rust

use crate::{
Align, GlyphAtlas, GlyphKey, Len, PaintId, PlacedGlyph, RegionAlign, Textures, WidgetId,
util::{Resources, RscHandle, StrongRscId, Vec2, WeakRscId},
};
use parley::{
Alignment, AlignmentOptions, FontContext, FontFamily, FontStyle, FontWeight, GenericFamily,
Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
fontique::{Blob, Collection, CollectionOptions, FamilyId, FontInfoOverride, FontWidth},
};
use std::{
cell::{Ref, RefCell, RefMut},
collections::HashMap,
fmt,
ops::{Deref, DerefMut, Range},
rc::Rc,
sync::Arc,
};
use swash::{
FontRef,
scale::{Render, ScaleContext, Source, StrikeWith},
zeno::{Format, Vector},
};
#[derive(Clone, Debug)]
pub struct FontDiagnostics {
pub families_found: usize,
pub default_family: Option<String>,
pub default_mono_family: Option<String>,
pub regular_resolved: Option<String>,
pub bold_resolved: Option<String>,
pub italic_resolved: Option<String>,
pub mono_resolved: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FontRegistrationError {
AlreadyRegistered(String),
NotRegistered(String),
InvalidFont(String),
}
impl fmt::Display for FontRegistrationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AlreadyRegistered(family) => {
write!(f, "font data is already registered for {family:?}")
}
Self::NotRegistered(family) => {
write!(f, "no font data is registered for {family:?}")
}
Self::InvalidFont(family) => {
write!(
f,
"font data registered for {family:?} contains no usable fonts"
)
}
}
}
}
impl std::error::Error for FontRegistrationError {}
/// The bucket used by [`crate::Ui::register_font`] when none is specified.
pub const DEFAULT_GLYPH_BUCKET: &str = "default";
struct RegisteredFace {
family: FamilyId,
width: FontWidth,
style: FontStyle,
weight: FontWeight,
}
struct RegisteredFont {
private_name: String,
bucket: u64,
font_id: u64,
faces: Vec<RegisteredFace>,
}
pub struct TextData {
pub font_cx: FontContext,
pub layout_cx: LayoutContext<PaintId>,
scale_cx: ScaleContext,
pub atlas: GlyphAtlas,
/// Physical pixels per dp -- a second copy of
/// `UiRenderState::density`, kept here too because cursor movement and
/// hit-testing shape text from event callbacks that have no `Painter`,
/// so they have nowhere else to read the display's density from. Both copies are
/// set together, from the one place either backend learns the real
/// value (`android::view::new_peer`); this is the same accepted
/// duplication as `AndroidRenderer::content_scale`; a single source of
/// truth would mean carrying a `Painter` (or output size) into every
/// input handler for the sake of one field.
pub density: f32,
registered_families: HashMap<String, RegisteredFont>,
glyph_buckets: HashMap<String, u64>,
font_buckets: HashMap<u64, u64>,
next_registered_family: u64,
next_glyph_bucket: u64,
}
impl Default for TextData {
fn default() -> Self {
let mut font_cx = FontContext::new();
patch_android_monospace(&mut font_cx);
Self {
font_cx,
layout_cx: LayoutContext::new(),
scale_cx: ScaleContext::new(),
atlas: GlyphAtlas::default(),
density: 1.0,
registered_families: HashMap::new(),
glyph_buckets: HashMap::new(),
font_buckets: HashMap::new(),
next_registered_family: 0,
next_glyph_bucket: 1,
}
}
}
/// So this reads `fonts.xml` itself (already on-device, already the
/// authority Compose's own `Typeface.MONOSPACE` resolves through) for the
/// filename that declaration names, then finds which of fontique's
/// *actually* scanned families (from `/system/fonts`, which do carry real
/// font data, just under whatever name the font's own metadata gives it --
/// "Droid Sans Mono" here, but that name is never hardcoded) owns a font
/// file with that name, and registers that family as the `Monospace`
/// generic the way the backend itself would have if its parser had reified
/// the declaration. A no-op if the family is somehow already resolved
/// (future fontique) or nothing matches (no `fonts.xml`, e.g. a headless
/// test, or a device that names it some other way).
#[cfg(target_os = "android")]
fn patch_android_monospace(font_cx: &mut FontContext) {
use parley::fontique::SourceKind;
let already_resolved = font_cx
.collection
.generic_families(GenericFamily::Monospace)
.next()
.is_some();
if already_resolved {
return;
}
let Some(target_file) = android_monospace_font_filename() else {
return;
};
let names: Vec<String> = font_cx
.collection
.family_names()
.map(str::to_string)
.collect();
for name in names {
let Some(id) = font_cx.collection.family_id(&name) else {
continue;
};
let Some(info) = font_cx.collection.family(id) else {
continue;
};
let Some(font) = info.default_font() else {
continue;
};
let SourceKind::Path(path) = font.source().kind() else {
continue;
};
if path.file_name().and_then(|f| f.to_str()) == Some(target_file.as_str()) {
font_cx
.collection
.append_generic_families(GenericFamily::Monospace, std::iter::once(id));
return;
}
}
}
#[cfg(target_os = "android")]
fn android_monospace_font_filename() -> Option<String> {
let android_root = std::env::var("ANDROID_ROOT").unwrap_or_else(|_| "/system".to_string());
let xml =
std::fs::read_to_string(std::path::Path::new(&android_root).join("etc/fonts.xml")).ok()?;
let family_start = xml.find("<family name=\"monospace\">")?;
let block = &xml[family_start..];
let block = &block[..block.find("</family>")?];
let font_tag = block.find("<font")?;
let after_tag = &block[font_tag..];
let content_start = after_tag.find('>')? + 1;
let content = &after_tag[content_start..];
let filename = content[..content.find('<')?].trim();
(!filename.is_empty()).then(|| filename.to_string())
}
#[cfg(not(target_os = "android"))]
fn patch_android_monospace(_font_cx: &mut FontContext) {}
impl TextData {
pub(crate) fn register_font(
&mut self,
family: impl AsRef<str>,
data: impl AsRef<[u8]> + Send + Sync + 'static,
) -> Result<(), FontRegistrationError> {
self.register_font_in(family, DEFAULT_GLYPH_BUCKET, data)
}
pub(crate) fn register_font_in(
&mut self,
family: impl AsRef<str>,
bucket: impl AsRef<str>,
data: impl AsRef<[u8]> + Send + Sync + 'static,
) -> Result<(), FontRegistrationError> {
let family = family.as_ref().to_owned();
if self.registered_families.contains_key(&family) {
return Err(FontRegistrationError::AlreadyRegistered(family));
}
let private_name = format!("__iris_registered_font_{}__", self.next_registered_family);
let blob = Blob::new(Arc::new(data));
Self::validate_font(&family, blob.clone())?;
let bucket = self.glyph_bucket(bucket.as_ref());
let registered = self.add_font(private_name, bucket, blob);
self.next_registered_family += 1;
self.font_buckets.insert(registered.font_id, bucket);
self.registered_families.insert(family, registered);
Ok(())
}
pub(crate) fn replace_font(
&mut self,
family: impl AsRef<str>,
data: impl AsRef<[u8]> + Send + Sync + 'static,
) -> Result<(), FontRegistrationError> {
let family = family.as_ref();
let Some(bucket) = self.registered_families.get(family).map(|font| font.bucket) else {
return Err(FontRegistrationError::NotRegistered(family.to_owned()));
};
self.replace_font_with(family, bucket, data)
}
pub(crate) fn replace_font_in(
&mut self,
family: impl AsRef<str>,
bucket: impl AsRef<str>,
data: impl AsRef<[u8]> + Send + Sync + 'static,
) -> Result<(), FontRegistrationError> {
let family = family.as_ref();
if !self.registered_families.contains_key(family) {
return Err(FontRegistrationError::NotRegistered(family.to_owned()));
}
let bucket = self.glyph_bucket(bucket.as_ref());
self.replace_font_with(family, bucket, data)
}
fn replace_font_with(
&mut self,
family: &str,
bucket: u64,
data: impl AsRef<[u8]> + Send + Sync + 'static,
) -> Result<(), FontRegistrationError> {
let blob = Blob::new(Arc::new(data));
Self::validate_font(family, blob.clone())?;
let old = self.registered_families.remove(family).unwrap();
for face in old.faces {
self.font_cx.collection.unregister_font(
face.family,
face.width,
face.style,
face.weight,
);
}
self.font_buckets.remove(&old.font_id);
self.atlas.clear_bucket(old.bucket);
let registered = self.add_font(old.private_name, bucket, blob);
self.font_buckets.insert(registered.font_id, bucket);
self.registered_families
.insert(family.to_owned(), registered);
Ok(())
}
fn validate_font(family: &str, blob: Blob<u8>) -> Result<(), FontRegistrationError> {
let mut collection = Collection::new(CollectionOptions {
shared: false,
system_fonts: false,
});
let fonts = collection.register_fonts(blob, None);
if fonts.is_empty() {
Err(FontRegistrationError::InvalidFont(family.to_owned()))
} else {
Ok(())
}
}
fn add_font(&mut self, private_name: String, bucket: u64, blob: Blob<u8>) -> RegisteredFont {
let font_id = blob.id();
let fonts = self.font_cx.collection.register_fonts(
blob,
Some(FontInfoOverride {
family_name: Some(&private_name),
..Default::default()
}),
);
debug_assert!(!fonts.is_empty(), "validated font failed its second scan");
let faces = fonts
.into_iter()
.flat_map(|(family, fonts)| {
fonts.into_iter().map(move |font| RegisteredFace {
family,
width: font.width(),
style: font.style(),
weight: font.weight(),
})
})
.collect();
RegisteredFont {
private_name,
bucket,
font_id,
faces,
}
}
fn glyph_bucket(&mut self, name: &str) -> u64 {
if name == DEFAULT_GLYPH_BUCKET {
return crate::render::DEFAULT_GLYPH_BUCKET_ID;
}
if let Some(bucket) = self.glyph_buckets.get(name) {
return *bucket;
}
let bucket = self.next_glyph_bucket;
self.next_glyph_bucket += 1;
self.glyph_buckets.insert(name.to_owned(), bucket);
bucket
}
fn bucket_for_font(&self, font: u64) -> u64 {
self.font_buckets
.get(&font)
.copied()
.unwrap_or(crate::render::DEFAULT_GLYPH_BUCKET_ID)
}
pub(crate) fn is_font_registered(&self, family: impl AsRef<str>) -> bool {
self.registered_families.contains_key(family.as_ref())
}
/// Cloned rather than borrowed because the caller needs it while the
/// layout builder holds `&mut self` -- a `String` per shaped registered
/// run, paid only when the layout is rebuilt.
pub fn resolve_family(&self, family: &str) -> String {
if let Some(font) = self.registered_families.get(family) {
return font.private_name.clone();
}
family.to_owned()
}
pub fn font_diagnostics(&mut self) -> FontDiagnostics {
use parley::fontique::{Attributes, FontWidth, QueryStatus};
let families_found = self.font_cx.collection.family_names().count();
let default_family_id = self
.font_cx
.collection
.generic_families(GenericFamily::SansSerif)
.next();
let default_family = default_family_id
.and_then(|id| self.font_cx.collection.family_name(id).map(str::to_string));
let default_mono_family_id = self
.font_cx
.collection
.generic_families(GenericFamily::Monospace)
.next();
let default_mono_family = default_mono_family_id
.and_then(|id| self.font_cx.collection.family_name(id).map(str::to_string));
let mut resolve_family =
|generic: GenericFamily, weight: FontWeight, style: FontStyle| -> Option<String> {
let mut family_id = None;
{
let mut query = self
.font_cx
.collection
.query(&mut self.font_cx.source_cache);
query.set_families([generic]);
query.set_attributes(Attributes {
width: FontWidth::NORMAL,
style,
weight,
});
query.matches_with(|font| {
family_id = Some(font.family.0);
QueryStatus::Stop
});
}
family_id.and_then(|id| self.font_cx.collection.family_name(id).map(str::to_string))
};
let regular_resolved = resolve_family(
GenericFamily::SansSerif,
FontWeight::NORMAL,
FontStyle::Normal,
);
let bold_resolved = resolve_family(
GenericFamily::SansSerif,
FontWeight::BOLD,
FontStyle::Normal,
);
let italic_resolved = resolve_family(
GenericFamily::SansSerif,
FontWeight::NORMAL,
FontStyle::Italic,
);
let mono_resolved = resolve_family(
GenericFamily::Monospace,
FontWeight::NORMAL,
FontStyle::Normal,
);
FontDiagnostics {
families_found,
default_family,
default_mono_family,
regular_resolved,
bold_resolved,
italic_resolved,
mono_resolved,
}
}
pub fn place(
&mut self,
buffer: &TextBuffer,
textures: &mut Textures,
) -> (Vec<PlacedGlyph>, Vec<PaintId>) {
let mut placed = Vec::new();
let mut paints = Vec::new();
for line in buffer.layout.lines() {
for item in line.items() {
let PositionedLayoutItem::GlyphRun(run) = item else {
continue;
};
let font = run.run().font();
let font_size = run.run().font_size();
let coords = run.run().normalized_coords();
let run_color = run.style().brush.clone();
if !paints.contains(&run_color) {
paints.push(run_color.clone());
}
let Some(font_ref) = FontRef::from_index(font.data.as_ref(), font.index as usize)
else {
continue;
};
let coords_hash = hash_coords(coords);
let font_id = font.data.id();
let bucket = self.bucket_for_font(font_id);
for glyph in run.positioned_glyphs() {
let (x, subpixel) = horizontal_phase(glyph.x);
let key = GlyphKey {
font: font_id,
glyph: glyph.id,
size: (font_size * 16.0).round() as u32,
subpixel,
coords: coords_hash,
};
let entry = match self.atlas.get(bucket, &key) {
Some(entry) => entry,
None => {
let mut scaler = self
.scale_cx
.builder(font_ref)
.size(font_size)
.hint(true)
.normalized_coords(coords)
.build();
let image = Render::new(&[
Source::ColorOutline(0),
Source::ColorBitmap(StrikeWith::BestFit),
Source::Outline,
])
.format(Format::Alpha)
.offset(Vector::new(subpixel as f32 / 4.0, 0.0))
.render(&mut scaler, glyph.id as u16);
match image {
Some(image) => self.atlas.insert(bucket, key, &image, textures),
None => {
self.atlas.insert_empty(bucket, key);
None
}
}
}
};
let Some(entry) = entry else { continue };
placed.push(PlacedGlyph {
entry,
offset: Vec2::new(
x + entry.left as f32,
glyph.y.floor() - entry.top as f32,
),
paint: run_color.slot(),
});
}
}
}
(placed, paints)
}
pub fn render(
&mut self,
buffer: &mut TextBuffer,
attrs: &TextAttrs,
width: Option<f32>,
textures: &mut Textures,
density: f32,
) -> RenderedText {
buffer.shape(self, attrs, width, density);
let (glyphs, paints) = self.place(buffer, textures);
RenderedText {
glyphs: std::sync::Arc::new(glyphs),
paints: std::sync::Arc::new(paints),
size: buffer.size(),
color: attrs.color.clone(),
generation: self.atlas.generation(),
}
}
}
fn horizontal_phase(x: f32) -> (f32, u8) {
let quarters = (x * 4.0).round() as i32;
(quarters.div_euclid(4) as f32, quarters.rem_euclid(4) as u8)
}
pub const SANS_SERIF: &str = "sans-serif";
pub const SERIF: &str = "serif";
pub const MONOSPACE: &str = "monospace";
#[derive(Clone, PartialEq)]
pub struct SpanStyle {
pub range: Range<usize>,
pub color: Option<PaintId>,
pub family: Option<String>,
pub font_size: Option<f32>,
pub bold: bool,
pub italic: bool,
pub underline: bool,
}
impl SpanStyle {
pub fn new(range: Range<usize>) -> Self {
Self {
range,
color: None,
family: None,
font_size: None,
bold: false,
italic: false,
underline: false,
}
}
pub fn color(mut self, color: PaintId) -> Self {
self.color = Some(color);
self
}
pub fn family(mut self, family: impl AsRef<str>) -> Self {
self.family = Some(family.as_ref().to_owned());
self
}
pub fn font_size(mut self, size: f32) -> Self {
self.font_size = Some(size);
self
}
pub fn bold(mut self) -> Self {
self.bold = true;
self
}
pub fn italic(mut self) -> Self {
self.italic = true;
self
}
pub fn underline(mut self) -> Self {
self.underline = true;
self
}
}
#[derive(Clone, PartialEq)]
pub struct TextAttrs {
pub color: PaintId,
pub font_size: f32,
pub line_height: f32,
pub family: String,
pub overflow: TextOverflow,
pub overflow_position: Len,
pub align: RegionAlign,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum TextOverflow {
#[default]
Visible,
Wrap,
Hidden,
Ellipsis,
}
#[derive(Clone, PartialEq)]
struct TextShapeAttrs {
color: PaintId,
font_size: f32,
line_height: f32,
family: String,
}
impl From<&TextAttrs> for TextShapeAttrs {
fn from(attrs: &TextAttrs) -> Self {
Self {
color: attrs.color.clone(),
font_size: attrs.font_size,
line_height: attrs.line_height,
family: attrs.family.clone(),
}
}
}
impl TextShapeAttrs {
fn matches(&self, attrs: &TextAttrs) -> bool {
self.color == attrs.color
&& self.font_size == attrs.font_size
&& self.line_height == attrs.line_height
&& self.family == attrs.family
}
}
pub const LINE_HEIGHT_MULT: f32 = 1.1;
impl Default for TextAttrs {
fn default() -> Self {
let size = 16.0;
Self {
color: PaintId::WHITE,
font_size: size,
line_height: size * LINE_HEIGHT_MULT,
family: SANS_SERIF.to_owned(),
overflow: TextOverflow::Visible,
overflow_position: Len::ZERO,
align: Align::CENTER_LEFT,
}
}
}
/// The text and the layout live in one place because parley's `Layout` borrows
/// nothing but is only meaningful against the string it was built from: keeping
/// them apart is how they get out of step.
pub struct TextBuffer {
text: String,
layout: Layout<PaintId>,
spans: Vec<SpanStyle>,
shaped: Option<(TextShapeAttrs, Option<f32>, f32)>,
}
impl TextBuffer {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
layout: Layout::new(),
spans: Vec::new(),
shaped: None,
}
}
pub fn set_spans(&mut self, spans: Vec<SpanStyle>) {
self.spans = spans;
self.shaped = None;
}
pub fn new_empty() -> Self {
Self::new("")
}
pub fn text(&self) -> &str {
&self.text
}
pub fn layout(&self) -> &Layout<PaintId> {
&self.layout
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
pub fn set_text(&mut self, text: impl Into<String>) {
let text = text.into();
if text != self.text {
self.text = text;
self.shaped = None;
}
}
/// Edit the string in place; invalidates the layout unconditionally, since
/// the caller is assumed to have changed something.
pub fn edit(&mut self) -> &mut String {
self.shaped = None;
&mut self.text
}
pub fn size(&self) -> Vec2 {
Vec2::new(self.layout.width(), self.layout.height())
}
pub fn shape(
&mut self,
data: &mut TextData,
attrs: &TextAttrs,
width: Option<f32>,
density: f32,
) {
if self
.shaped
.as_ref()
.is_some_and(|(old, old_width, old_density)| {
old.matches(attrs) && *old_width == width && *old_density == density
})
{
return;
}
let shape_attrs = TextShapeAttrs::from(attrs);
let base_family = data.resolve_family(&attrs.family);
let span_families: Vec<Option<String>> = self
.spans
.iter()
.map(|span| span.family.as_ref().map(|f| data.resolve_family(f)))
.collect();
let mut builder = data
.layout_cx
.ranged_builder(&mut data.font_cx, &self.text, 1.0, true);
builder.push_default(StyleProperty::FontFamily(FontFamily::from(
base_family.as_str(),
)));
builder.push_default(StyleProperty::FontSize(attrs.font_size * density));
builder.push_default(StyleProperty::LineHeight(LineHeight::Absolute(
attrs.line_height * density,
)));
builder.push_default(StyleProperty::Brush(attrs.color.clone()));
for (span, family) in self.spans.iter().zip(&span_families) {
let range = span.range.clone();
if let Some(color) = &span.color {
builder.push(StyleProperty::Brush(color.clone()), range.clone());
}
if let Some(family) = family {
builder.push(
StyleProperty::FontFamily(FontFamily::from(family.as_str())),
range.clone(),
);
}
if let Some(size) = span.font_size {
builder.push(StyleProperty::FontSize(size * density), range.clone());
}
if span.bold {
builder.push(StyleProperty::FontWeight(FontWeight::BOLD), range.clone());
}
if span.italic {
builder.push(StyleProperty::FontStyle(FontStyle::Italic), range.clone());
}
if span.underline {
builder.push(StyleProperty::Underline(true), range.clone());
}
}
builder.build_into(&mut self.layout, &self.text);
self.layout.break_all_lines(width);
self.layout
.align(Alignment::Start, AlignmentOptions::default());
self.shaped = Some((shape_attrs, width, density));
}
fn invalidate(&mut self) {
// A layout owns the font blobs it was shaped with. Drop it now so
// replacing a registered font does not retain the old bytes until an
// off-screen text resource happens to be shaped again.
self.layout = Layout::new();
self.shaped = None;
}
}
fn hash_coords(coords: &[i16]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for c in coords {
h ^= *c as u16 as u64;
h = h.wrapping_mul(0x1000_0000_01b3);
}
h
}
/// Cheap to clone and to keep, which is the point -- a widget holds one across
/// frames and re-emits its quads without going near the rasteriser. `color`
/// is the buffer's *base* colour (`TextAttrs::color`) for a caller that wants
/// it as a whole (e.g. tinting a cursor to match); the colour each glyph is
/// actually drawn in is `PlacedGlyph::color`, which a `SpanStyle` can
/// override per range.
#[derive(Clone)]
pub struct RenderedText {
pub glyphs: std::sync::Arc<Vec<PlacedGlyph>>,
/// The unique handles whose compact slots the glyphs above carry.
pub paints: std::sync::Arc<Vec<PaintId>>,
pub size: Vec2,
pub color: PaintId,
/// The [`GlyphAtlas::generation`] the glyphs above were placed against.
/// A holder must re-render rather than re-emit these quads once the
/// atlas has moved on (`GlyphAtlas::clear`'s doc says what happens
/// otherwise); `Painter::glyphs` debug-asserts it.
pub generation: u64,
}
pub struct TextRsc {
buffer: TextBuffer,
ellipsis_buffer: TextBuffer,
attrs: TextAttrs,
rendered: Option<RenderedText>,
ellipsis_rendered: Option<(TextShapeAttrs, f32, RenderedText)>,
width: Option<f32>,
density: f32,
owner: Option<WidgetId>,
}
impl TextRsc {
fn new(buffer: TextBuffer, attrs: TextAttrs) -> Self {
Self {
buffer,
ellipsis_buffer: TextBuffer::new("…"),
attrs,
rendered: None,
ellipsis_rendered: None,
width: None,
density: 0.0,
owner: None,
}
}
fn invalidate(&mut self) {
self.buffer.invalidate();
self.rendered = None;
self.ellipsis_buffer.invalidate();
self.ellipsis_rendered = None;
}
}
/// All text storage and shaping state for one [`crate::Ui`]. Widgets retain a
/// compact [`TextHandle`] into this arena rather than owning Parley layouts.
pub struct TextResources {
data: TextData,
entries: Rc<RefCell<Resources<TextRsc>>>,
}
impl TextResources {
pub fn new() -> Self {
Self {
data: TextData::default(),
entries: Rc::new(RefCell::new(Resources::new())),
}
}
pub fn add(resources: Rc<RefCell<Self>>, buffer: TextBuffer, attrs: TextAttrs) -> TextHandle {
let entries = {
let mut resources = resources.borrow_mut();
resources.free_released();
resources.entries.clone()
};
TextHandle {
rsc: RscHandle::add(entries, TextRsc::new(buffer, attrs)),
resources,
}
}
pub fn handle(resources: Rc<RefCell<Self>>, id: StrongRscId<TextRsc>) -> TextHandle {
let entries = resources.borrow().entries.clone();
TextHandle {
rsc: RscHandle::new(id, entries),
resources,
}
}
pub fn upgrade(resources: Rc<RefCell<Self>>, id: WeakRscId<TextRsc>) -> Option<TextHandle> {
let entries = {
let mut resources = resources.borrow_mut();
resources.free_released();
resources.entries.clone()
};
let id = entries.borrow_mut().upgrade(id)?;
Some(Self::handle(resources, id))
}
pub fn free_released(&mut self) {
self.entries.borrow_mut().apply(|_, _| {});
}
pub fn invalidate_all(&mut self) -> Vec<WidgetId> {
let mut owners = Vec::new();
for resource in self.entries.borrow_mut().values_mut() {
resource.invalidate();
if let Some(owner) = resource.owner
&& !owners.contains(&owner)
{
owners.push(owner);
}
}
owners
}
fn shape(&mut self, resource: &mut TextRsc) {
let density = self.data.density;
resource
.buffer
.shape(&mut self.data, &resource.attrs, resource.width, density);
}
fn render(
&mut self,
resource: &mut TextRsc,
width: Option<f32>,
owner: WidgetId,
textures: &mut Textures,
density: f32,
) -> (RenderedText, bool) {
let atlas_generation = self.data.atlas.generation();
resource.owner = Some(owner);
if resource.width == width
&& resource.density == density
&& let Some(rendered) = &resource.rendered
&& rendered.generation == atlas_generation
{
return (rendered.clone(), false);
}
resource.width = width;
resource.density = density;
let rendered = self.data.render(
&mut resource.buffer,
&resource.attrs,
width,
textures,
density,
);
resource.rendered = Some(rendered.clone());
(rendered, true)
}
fn render_ellipsis(
&mut self,
resource: &mut TextRsc,
owner: WidgetId,
textures: &mut Textures,
density: f32,
) -> (RenderedText, bool) {
let shape_attrs = TextShapeAttrs::from(&resource.attrs);
let atlas_generation = self.data.atlas.generation();
resource.owner = Some(owner);
if let Some((cached_attrs, cached_density, rendered)) = &resource.ellipsis_rendered
&& cached_attrs == &shape_attrs
&& *cached_density == density
&& rendered.generation == atlas_generation
{
return (rendered.clone(), false);
}
let rendered = self.data.render(
&mut resource.ellipsis_buffer,
&resource.attrs,
None,
textures,
density,
);
resource.ellipsis_rendered = Some((shape_attrs, density, rendered.clone()));
(rendered, true)
}
}
impl Default for TextResources {
fn default() -> Self {
Self::new()
}
}
impl Deref for TextResources {
type Target = TextData;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl DerefMut for TextResources {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
/// A widget-owned reference to one entry in [`TextResources`]. The arena is
/// shared once per UI; constructing a handle does not allocate a resource of
/// its own.
pub struct TextHandle {
rsc: RscHandle<TextRsc>,
resources: Rc<RefCell<TextResources>>,
}
impl TextHandle {
pub fn strong(&self) -> StrongRscId<TextRsc> {
self.rsc.strong()
}
pub fn weak(&self) -> WeakRscId<TextRsc> {
self.rsc.weak()
}
pub fn text(&self) -> Ref<'_, str> {
Ref::map(self.rsc.get(), |resource| resource.buffer.text())
}
pub fn attrs(&self) -> Ref<'_, TextAttrs> {
Ref::map(self.rsc.get(), |resource| &resource.attrs)
}
pub fn set_text(&mut self, text: impl Into<String>) -> bool {
let text = text.into();
let mut resource = self.rsc.get_mut();
if resource.buffer.text() == text {
return false;
}
resource.buffer.set_text(text);
resource.rendered = None;
true
}
pub fn edit_text<R>(&mut self, edit: impl FnOnce(&mut String) -> R) -> R {
let mut resource = self.rsc.get_mut();
resource.rendered = None;
edit(resource.buffer.edit())
}
pub fn set_spans(&mut self, spans: Vec<SpanStyle>) {
let mut resource = self.rsc.get_mut();
resource.buffer.set_spans(spans);
resource.rendered = None;
}
pub fn attrs_mut(&mut self) -> RefMut<'_, TextAttrs> {
let mut resource = self.rsc.get_mut();
resource.invalidate();
RefMut::map(resource, |resource| &mut resource.attrs)
}
pub fn set_overflow_position(&mut self, position: Len) {
self.rsc.get_mut().attrs.overflow_position = position;
}
pub fn set_overflow(&mut self, overflow: TextOverflow) {
self.rsc.get_mut().attrs.overflow = overflow;
}
pub fn rendered(&self) -> Option<RenderedText> {
self.rsc.get().rendered.clone()
}
pub fn width(&self) -> Option<f32> {
self.rsc.get().width
}
pub fn with_layout<R>(&self, f: impl FnOnce(&Layout<PaintId>, &str) -> R) -> R {
let mut resources = self.resources.borrow_mut();
let mut resource = self.rsc.get_mut_shared();
resources.shape(&mut resource);
f(resource.buffer.layout(), resource.buffer.text())
}
pub fn layout(&self) -> Ref<'_, Layout<PaintId>> {
{
let mut resources = self.resources.borrow_mut();
let mut resource = self.rsc.get_mut_shared();
resources.shape(&mut resource);
}
Ref::map(self.rsc.get(), |resource| resource.buffer.layout())
}
pub fn render(
&self,
width: Option<f32>,
owner: WidgetId,
textures: &mut Textures,
density: f32,
) -> (RenderedText, bool) {
let mut resources = self.resources.borrow_mut();
let mut resource = self.rsc.get_mut_shared();
resources.render(&mut resource, width, owner, textures, density)
}
pub fn render_ellipsis(
&self,
owner: WidgetId,
textures: &mut Textures,
density: f32,
) -> (RenderedText, bool) {
let mut resources = self.resources.borrow_mut();
let mut resource = self.rsc.get_mut_shared();
resources.render_ellipsis(&mut resource, owner, textures, density)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_horizontal_phase_carries_across_pixel_boundaries() {
assert_eq!(horizontal_phase(10.20), (10.0, 1));
assert_eq!(horizontal_phase(10.90), (11.0, 0));
assert_eq!(horizontal_phase(-0.20), (-1.0, 3));
}
#[test]
fn invalid_font_data_is_reported() {
let mut data = TextData::default();
assert_eq!(
data.register_font("icons", b"not a font" as &'static [u8]),
Err(FontRegistrationError::InvalidFont("icons".to_owned()))
);
}
#[test]
fn dropped_handles_release_their_arena_entries() {
let resources = Rc::new(RefCell::new(TextResources::new()));
let text = TextResources::add(
resources.clone(),
TextBuffer::new("temporary"),
TextAttrs::default(),
);
assert_eq!(resources.borrow().entries.borrow().len(), 1);
drop(text);
resources.borrow_mut().free_released();
assert!(resources.borrow().entries.borrow().is_empty());
}
}