use crate::core;
use crate::core::alignment;
use crate::core::text::{Alignment, Ellipsis, Hit, LineHeight, Shaping, Span, Text, Wrapping};
use crate::core::{Font, Pixels, Point, Rectangle, Size};
use crate::text;
use std::fmt;
use std::sync::{self, Arc};
#[derive(Clone, PartialEq)]
pub struct Paragraph(Arc<Internal>);
#[derive(Clone)]
struct Internal {
buffer: cosmic_text::Buffer,
font: Font,
shaping: Shaping,
wrapping: Wrapping,
ellipsis: Ellipsis,
align_x: Alignment,
align_y: alignment::Vertical,
bounds: Size,
min_bounds: Size,
version: text::Version,
hint: bool,
hint_factor: f32,
}
impl Paragraph {
pub fn new() -> Self {
Self::default()
}
pub fn buffer(&self) -> &cosmic_text::Buffer {
&self.internal().buffer
}
pub fn downgrade(&self) -> Weak {
let paragraph = self.internal();
Weak {
raw: Arc::downgrade(paragraph),
min_bounds: paragraph.min_bounds,
align_x: paragraph.align_x,
align_y: paragraph.align_y,
}
}
fn internal(&self) -> &Arc<Internal> {
&self.0
}
}
impl core::text::Paragraph for Paragraph {
type Font = Font;
fn with_text(text: Text<&str>) -> Self {
log::trace!("Allocating plain paragraph: {}", text.content);
let mut font_system = text::font_system().write().expect("Write font system");
let (hint, hint_factor) = match text::hint_factor(text.size, text.hint_factor) {
Some(hint_factor) => (true, hint_factor),
_ => (false, 1.0),
};
let mut buffer = cosmic_text::Buffer::new(
font_system.raw(),
cosmic_text::Metrics::new(
f32::from(text.size) * hint_factor,
f32::from(text.line_height.to_absolute(text.size)) * hint_factor,
),
);
if hint {
buffer.set_hinting(font_system.raw(), cosmic_text::Hinting::Enabled);
}
buffer.set_size(
font_system.raw(),
Some(text.bounds.width * hint_factor),
Some(text.bounds.height * hint_factor),
);
buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping));
buffer.set_ellipsize(
font_system.raw(),
text::to_ellipsize(text.ellipsis, text.bounds.height * hint_factor),
);
buffer.set_text(
font_system.raw(),
text.content,
&text::to_attributes(text.font),
text::to_shaping(text.shaping, text.content),
None,
);
let min_bounds = text::align(&mut buffer, font_system.raw(), text.align_x) / hint_factor;
Self(Arc::new(Internal {
buffer,
hint,
hint_factor,
font: text.font,
align_x: text.align_x,
align_y: text.align_y,
shaping: text.shaping,
wrapping: text.wrapping,
ellipsis: text.ellipsis,
bounds: text.bounds,
min_bounds,
version: font_system.version(),
}))
}
fn with_spans<Link>(text: Text<&[Span<'_, Link>]>) -> Self {
log::trace!("Allocating rich paragraph: {} spans", text.content.len());
let mut font_system = text::font_system().write().expect("Write font system");
let (hint, hint_factor) = match text::hint_factor(text.size, text.hint_factor) {
Some(hint_factor) => (true, hint_factor),
_ => (false, 1.0),
};
let mut buffer = cosmic_text::Buffer::new(
font_system.raw(),
cosmic_text::Metrics::new(
f32::from(text.size) * hint_factor,
f32::from(text.line_height.to_absolute(text.size)) * hint_factor,
),
);
if hint {
buffer.set_hinting(font_system.raw(), cosmic_text::Hinting::Enabled);
}
buffer.set_size(
font_system.raw(),
Some(text.bounds.width * hint_factor),
Some(text.bounds.height * hint_factor),
);
buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping));
buffer.set_rich_text(
font_system.raw(),
text.content.iter().enumerate().map(|(i, span)| {
let attrs = text::to_attributes(span.font.unwrap_or(text.font));
let attrs = match (span.size, span.line_height) {
(None, None) => attrs,
_ => {
let size = span.size.unwrap_or(text.size);
attrs.metrics(cosmic_text::Metrics::new(
f32::from(size) * hint_factor,
f32::from(
span.line_height
.unwrap_or(text.line_height)
.to_absolute(size),
) * hint_factor,
))
}
};
let attrs = if let Some(color) = span.color {
attrs.color(text::to_color(color))
} else {
attrs
};
(span.text.as_ref(), attrs.metadata(i))
}),
&text::to_attributes(text.font),
cosmic_text::Shaping::Advanced,
None,
);
let min_bounds = text::align(&mut buffer, font_system.raw(), text.align_x) / hint_factor;
Self(Arc::new(Internal {
buffer,
hint,
hint_factor,
font: text.font,
align_x: text.align_x,
align_y: text.align_y,
shaping: text.shaping,
wrapping: text.wrapping,
ellipsis: text.ellipsis,
bounds: text.bounds,
min_bounds,
version: font_system.version(),
}))
}
fn resize(&mut self, new_bounds: Size) {
let paragraph = Arc::make_mut(&mut self.0);
let mut font_system = text::font_system().write().expect("Write font system");
paragraph.buffer.set_size(
font_system.raw(),
Some(new_bounds.width * paragraph.hint_factor),
Some(new_bounds.height * paragraph.hint_factor),
);
let min_bounds = text::align(&mut paragraph.buffer, font_system.raw(), paragraph.align_x)
/ paragraph.hint_factor;
paragraph.bounds = new_bounds;
paragraph.min_bounds = min_bounds;
}
fn compare(&self, text: Text<()>) -> core::text::Difference {
let font_system = text::font_system().read().expect("Read font system");
let paragraph = self.internal();
let metrics = paragraph.buffer.metrics();
if paragraph.version != font_system.version
|| metrics.font_size != text.size.0 * paragraph.hint_factor
|| metrics.line_height
!= text.line_height.to_absolute(text.size).0 * paragraph.hint_factor
|| paragraph.font != text.font
|| paragraph.shaping != text.shaping
|| paragraph.wrapping != text.wrapping
|| paragraph.ellipsis != text.ellipsis
|| paragraph.align_x != text.align_x
|| paragraph.align_y != text.align_y
|| paragraph.hint.then_some(paragraph.hint_factor)
!= text::hint_factor(text.size, text.hint_factor)
{
core::text::Difference::Shape
} else if paragraph.bounds != text.bounds {
core::text::Difference::Bounds
} else {
core::text::Difference::None
}
}
fn hint_factor(&self) -> Option<f32> {
self.0.hint.then_some(self.0.hint_factor)
}
fn size(&self) -> Pixels {
Pixels(self.0.buffer.metrics().font_size / self.0.hint_factor)
}
fn font(&self) -> Font {
self.0.font
}
fn line_height(&self) -> LineHeight {
LineHeight::Absolute(Pixels(
self.0.buffer.metrics().line_height / self.0.hint_factor,
))
}
fn align_x(&self) -> Alignment {
self.internal().align_x
}
fn align_y(&self) -> alignment::Vertical {
self.internal().align_y
}
fn wrapping(&self) -> Wrapping {
self.0.wrapping
}
fn ellipsis(&self) -> Ellipsis {
self.0.ellipsis
}
fn shaping(&self) -> Shaping {
self.0.shaping
}
fn bounds(&self) -> Size {
self.0.bounds
}
fn min_bounds(&self) -> Size {
self.internal().min_bounds
}
fn hit_test(&self, point: Point) -> Option<Hit> {
let cursor = self
.internal()
.buffer
.hit(point.x * self.0.hint_factor, point.y * self.0.hint_factor)?;
Some(Hit::CharOffset(cursor.index))
}
fn hit_span(&self, point: Point) -> Option<usize> {
let internal = self.internal();
let cursor = internal
.buffer
.hit(point.x * self.0.hint_factor, point.y * self.0.hint_factor)?;
let line = internal.buffer.lines.get(cursor.line)?;
if cursor.index >= line.text().len() {
return None;
}
let index = match cursor.affinity {
cosmic_text::Affinity::Before => cursor.index.saturating_sub(1),
cosmic_text::Affinity::After => cursor.index,
};
let mut hit = None;
let glyphs = line
.layout_opt()
.as_ref()?
.iter()
.flat_map(|line| line.glyphs.iter());
for glyph in glyphs {
if glyph.start <= index && index < glyph.end {
hit = Some(glyph);
break;
}
}
Some(hit?.metadata)
}
fn span_bounds(&self, index: usize) -> Vec<Rectangle> {
let internal = self.internal();
let mut bounds = Vec::new();
let mut current_bounds = None;
let glyphs = internal
.buffer
.layout_runs()
.flat_map(|run| {
let line_top = run.line_top;
let line_height = run.line_height;
run.glyphs
.iter()
.map(move |glyph| (line_top, line_height, glyph))
})
.skip_while(|(_, _, glyph)| glyph.metadata != index)
.take_while(|(_, _, glyph)| glyph.metadata == index);
for (line_top, line_height, glyph) in glyphs {
let y = line_top + glyph.y;
let new_bounds = || {
Rectangle::new(
Point::new(glyph.x, y),
Size::new(glyph.w, glyph.line_height_opt.unwrap_or(line_height)),
) * (1.0 / self.0.hint_factor)
};
match current_bounds.as_mut() {
None => {
current_bounds = Some(new_bounds());
}
Some(current_bounds) if y != current_bounds.y => {
bounds.push(*current_bounds);
*current_bounds = new_bounds();
}
Some(current_bounds) => {
current_bounds.width += glyph.w / self.0.hint_factor;
}
}
}
bounds.extend(current_bounds);
bounds
}
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
use unicode_segmentation::UnicodeSegmentation;
let run = self.internal().buffer.layout_runs().nth(line)?;
let mut last_start = None;
let mut last_grapheme_count = 0;
let mut graphemes_seen = 0;
let glyph = run
.glyphs
.iter()
.find(|glyph| {
if Some(glyph.start) != last_start {
last_grapheme_count = run.text[glyph.start..glyph.end].graphemes(false).count();
last_start = Some(glyph.start);
graphemes_seen += last_grapheme_count;
}
graphemes_seen >= index
})
.or_else(|| run.glyphs.last())?;
let advance = if index == 0 {
0.0
} else {
glyph.w
* (1.0
- graphemes_seen.saturating_sub(index) as f32
/ last_grapheme_count.max(1) as f32)
};
Some(Point::new(
(glyph.x + glyph.x_offset * glyph.font_size + advance) / self.0.hint_factor,
(glyph.y - glyph.y_offset * glyph.font_size) / self.0.hint_factor,
))
}
}
impl Default for Paragraph {
fn default() -> Self {
Self(Arc::new(Internal::default()))
}
}
impl fmt::Debug for Paragraph {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let paragraph = self.internal();
f.debug_struct("Paragraph")
.field("font", ¶graph.font)
.field("shaping", ¶graph.shaping)
.field("horizontal_alignment", ¶graph.align_x)
.field("vertical_alignment", ¶graph.align_y)
.field("bounds", ¶graph.bounds)
.field("min_bounds", ¶graph.min_bounds)
.finish()
}
}
impl PartialEq for Internal {
fn eq(&self, other: &Self) -> bool {
self.font == other.font
&& self.shaping == other.shaping
&& self.align_x == other.align_x
&& self.align_y == other.align_y
&& self.bounds == other.bounds
&& self.min_bounds == other.min_bounds
&& self.buffer.metrics() == other.buffer.metrics()
}
}
impl Default for Internal {
fn default() -> Self {
Self {
buffer: cosmic_text::Buffer::new_empty(cosmic_text::Metrics {
font_size: 1.0,
line_height: 1.0,
}),
font: Font::default(),
shaping: Shaping::default(),
wrapping: Wrapping::default(),
ellipsis: Ellipsis::default(),
align_x: Alignment::Default,
align_y: alignment::Vertical::Top,
bounds: Size::ZERO,
min_bounds: Size::ZERO,
version: text::Version::default(),
hint: false,
hint_factor: 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct Weak {
raw: sync::Weak<Internal>,
pub min_bounds: Size,
pub align_x: Alignment,
pub align_y: alignment::Vertical,
}
impl Weak {
pub fn upgrade(&self) -> Option<Paragraph> {
self.raw.upgrade().map(Paragraph)
}
}
impl PartialEq for Weak {
fn eq(&self, other: &Self) -> bool {
match (self.raw.upgrade(), other.raw.upgrade()) {
(Some(p1), Some(p2)) => p1 == p2,
_ => false,
}
}
}