Split layout module

Also moves hit testing to a new Cursor struct to prep for more sophisticated navigation.
This commit is contained in:
Chad Brokaw 2021-09-15 05:08:25 -04:00
parent 3356fd4cf0
commit d28576ce29
8 changed files with 810 additions and 714 deletions

View File

@ -1,709 +0,0 @@
//! Layout types.
use super::data::*;
use super::font::Font;
use super::style::Brush;
use core::ops::Range;
use swash::text::cluster::{Boundary, ClusterInfo};
use swash::{GlyphId, NormalizedCoord, Synthesis};
pub use super::line::BreakLines;
/// Alignment of a layout.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Alignment {
Start,
Middle,
End,
}
impl Default for Alignment {
fn default() -> Self {
Self::Start
}
}
/// Text layout.
#[derive(Clone)]
pub struct Layout<B: Brush> {
pub(crate) data: LayoutData<B>,
}
impl<B: Brush> Layout<B> {
/// Creates an empty layout.
pub fn new() -> Self {
Self::default()
}
/// Returns the style collection for the layout.
pub fn styles(&self) -> &[Style<B>] {
&self.data.styles
}
/// Returns the number of lines in the layout.
pub fn len(&self) -> usize {
self.data.lines.len()
}
/// Returns true if the layout is empty.
pub fn is_empty(&self) -> bool {
self.data.lines.is_empty()
}
/// Returns the line at the specified index.
pub fn get(&self, index: usize) -> Option<Line<B>> {
Some(Line {
layout: &self.data,
data: self.data.lines.get(index)?,
})
}
/// Returns an iterator over the lines in the layout.
pub fn lines(&self) -> impl Iterator<Item = Line<B>> + '_ + Clone {
self.data.lines.iter().map(move |data| Line {
layout: &self.data,
data,
})
}
/// Returns line breaker to compute lines for the layout.
pub fn break_lines(&mut self) -> BreakLines<B> {
BreakLines::new(&mut self.data)
}
/// Breaks all lines with the specified maximum advance and alignment.
pub fn break_all_lines(&mut self, max_advance: Option<f32>, alignment: Alignment) {
self.break_lines()
.break_remaining(max_advance.unwrap_or(f32::MAX), alignment)
}
/// Returns the information about the layout at the specified point.
pub fn hit_test_point(&self, mut x: f32, y: f32) -> HitTestResult {
let mut result = HitTestResult::default();
result.is_inside = x >= 0. && y >= 0.;
let last_line = self.data.lines.len().saturating_sub(1);
for (line_index, line) in self.lines().enumerate() {
let line_metrics = line.metrics();
if y <= line_metrics.baseline || line_index == last_line {
if y > line_metrics.baseline + line_metrics.leading * 0.5 {
result.is_inside = false;
x = f32::MAX;
} else if y < 0. {
x = 0.;
}
result.baseline = line_metrics.baseline;
result.line_index = line_index;
let mut last_edge = line_metrics.offset;
for (run_index, run) in line.runs().enumerate() {
result.run_index = run_index;
let cluster_range = run.data().cluster_range.clone();
for (cluster_index, cluster) in run.visual_clusters().enumerate() {
result.text_range = cluster.text_range();
if run.is_rtl() {
result.cluster_index = cluster_range.end - cluster_index - 1;
} else {
result.cluster_index = cluster_index;
}
let advance = cluster.advance();
if x >= last_edge {
let far_edge = last_edge + advance;
if x < far_edge {
result.is_leading = false;
let middle = (last_edge + far_edge) * 0.5;
if x <= middle {
result.is_leading = true;
result.offset = last_edge;
} else {
result.is_leading = false;
result.offset = far_edge;
}
return result;
}
last_edge = far_edge;
} else {
result.is_inside = false;
result.is_leading = true;
result.offset = line_metrics.offset;
return result;
}
}
}
break;
}
}
result
}
/// Returns information about the layout at the specified text position.
pub fn hit_test_position(&self, mut position: usize) -> HitTestResult {
let mut result = HitTestResult::default();
result.is_leading = true;
result.is_inside = true;
if position >= self.data.text_len {
result.is_inside = false;
result.is_leading = false;
position = self.data.text_len.saturating_sub(1);
}
let last_line = self.data.lines.len().saturating_sub(1);
for (line_index, line) in self.lines().enumerate() {
let line_metrics = line.metrics();
result.baseline = line_metrics.baseline;
result.line_index = line_index;
if !line.text_range().contains(&position) && line_index != last_line {
continue;
}
let mut last_edge = line_metrics.offset;
result.offset = last_edge;
for (run_index, run) in line.runs().enumerate() {
result.run_index = run_index;
if !run.text_range().contains(&position) {
continue;
}
let cluster_range = run.data().cluster_range.clone();
for (cluster_index, cluster) in run.visual_clusters().enumerate() {
result.text_range = cluster.text_range();
result.offset = last_edge;
if run.is_rtl() {
result.cluster_index = cluster_range.end - cluster_index - 1;
} else {
result.cluster_index = cluster_index;
}
let advance = cluster.advance();
if result.text_range.contains(&position) {
if !result.is_inside {
result.offset += advance;
}
return result;
}
last_edge += advance;
}
}
result.offset = last_edge;
break;
}
result.is_leading = false;
result.is_inside = false;
result
}
/// Returns an iterator over the runs in the layout.
pub fn runs(&self) -> impl Iterator<Item = Run<B>> + '_ + Clone {
self.data.runs.iter().map(move |data| Run {
layout: &self.data,
data,
line_data: None,
})
}
}
impl<B: Brush> Default for Layout<B> {
fn default() -> Self {
Self {
data: Default::default(),
}
}
}
/// Sequence of clusters with a single font and style.
#[derive(Copy, Clone)]
pub struct Run<'a, B: Brush> {
layout: &'a LayoutData<B>,
data: &'a RunData,
line_data: Option<&'a LineRunData>,
}
impl<'a, B: Brush> Run<'a, B> {
pub(crate) fn new(
layout: &'a LayoutData<B>,
data: &'a RunData,
line_data: Option<&'a LineRunData>,
) -> Self {
Self {
layout,
data,
line_data,
}
}
/// Returns the font for the run.
pub fn font(&self) -> &Font {
self.layout.fonts.get(self.data.font_index).unwrap()
}
/// Returns the font size for the run.
pub fn font_size(&self) -> f32 {
self.data.font_size
}
/// Returns the synthesis suggestions for the font associated with the run.
pub fn synthesis(&self) -> Synthesis {
self.data.synthesis
}
/// Returns the normalized variation coordinates for the font associated
/// with the run.
pub fn normalized_coords(&self) -> &[NormalizedCoord] {
self.layout
.coords
.get(self.data.coords_range.clone())
.unwrap_or(&[])
}
/// Returns metrics for the run.
pub fn metrics(&self) -> &RunMetrics {
&self.data.metrics
}
/// Returns the original text range for the run.
pub fn text_range(&self) -> Range<usize> {
self.line_data
.map(|d| &d.text_range)
.unwrap_or(&self.data.text_range)
.clone()
}
/// Returns true if the run has right-to-left directionality.
pub fn is_rtl(&self) -> bool {
self.data.bidi_level & 1 != 0
}
/// Returns the number of clusters in the run.
pub fn len(&self) -> usize {
self.line_data
.map(|d| &d.cluster_range)
.unwrap_or(&self.data.cluster_range)
.len()
}
/// Returns true if the run is empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the cluster at the specified index.
pub fn get(&'a self, index: usize) -> Option<Cluster<'a, B>> {
let range = self
.line_data
.map(|d| &d.cluster_range)
.unwrap_or(&self.data.cluster_range);
let index = range.start + index;
Some(Cluster {
run: self,
data: self.layout.clusters.get(index)?,
})
}
/// Returns an iterator over the clusters in logical order.
pub fn clusters(&'a self) -> impl Iterator<Item = Cluster<'a, B>> + 'a + Clone {
let range = self
.line_data
.map(|d| &d.cluster_range)
.unwrap_or(&self.data.cluster_range)
.clone();
Clusters {
run: self,
range,
rev: false,
}
}
/// Returns an iterator over the clusters in visual order.
pub fn visual_clusters(&'a self) -> impl Iterator<Item = Cluster<'a, B>> + 'a + Clone {
let range = self
.line_data
.map(|d| &d.cluster_range)
.unwrap_or(&self.data.cluster_range)
.clone();
Clusters {
run: self,
range,
rev: self.is_rtl(),
}
}
pub(crate) fn data(&self) -> &'a RunData {
self.data
}
}
struct Clusters<'a, B: Brush> {
run: &'a Run<'a, B>,
range: Range<usize>,
rev: bool,
}
impl<'a, B: Brush> Clone for Clusters<'a, B> {
fn clone(&self) -> Self {
Self {
run: self.run,
range: self.range.clone(),
rev: self.rev,
}
}
}
impl<'a, B: Brush> Iterator for Clusters<'a, B> {
type Item = Cluster<'a, B>;
fn next(&mut self) -> Option<Self::Item> {
let index = if self.rev {
self.range.next_back()?
} else {
self.range.next()?
};
Some(Cluster {
run: self.run,
data: self.run.layout.clusters.get(index)?,
})
}
}
/// Metrics information for a run.
#[derive(Copy, Clone, Default, Debug)]
pub struct RunMetrics {
/// Typographic ascent.
pub ascent: f32,
/// Typographic descent.
pub descent: f32,
/// Typographic leading.
pub leading: f32,
/// Offset of the top of underline decoration from the baseline.
pub underline_offset: f32,
/// Thickness of the underline decoration.
pub underline_size: f32,
/// Offset of the top of underline decoration from the baseline.
pub strikethrough_offset: f32,
/// Thickness of the underline decoration.
pub strikethrough_size: f32,
}
/// Atomic unit of text.
#[derive(Copy, Clone)]
pub struct Cluster<'a, B: Brush> {
run: &'a Run<'a, B>,
data: &'a ClusterData,
}
impl<'a, B: Brush> Cluster<'a, B> {
/// Returns the range of text that defines the cluster.
pub fn text_range(&self) -> Range<usize> {
self.data.text_range(self.run.data)
}
/// Returns the advance of the cluster.
pub fn advance(&self) -> f32 {
self.data.advance
}
/// Returns true if the cluster is the beginning of a ligature.
pub fn is_ligature_start(&self) -> bool {
self.data.is_ligature_start()
}
/// Returns true if the cluster is a ligature continuation.
pub fn is_ligature_continuation(&self) -> bool {
self.data.is_ligature_component()
}
/// Returns true if the cluster is a word boundary.
pub fn is_word_boundary(&self) -> bool {
self.data.info.is_boundary()
}
/// Returns true if the cluster is a soft line break.
pub fn is_soft_line_break(&self) -> bool {
self.data.info.boundary() == Boundary::Line
}
/// Returns true if the cluster is a hard line break.
pub fn is_hard_line_break(&self) -> bool {
self.data.info.boundary() == Boundary::Mandatory
}
/// Returns true if the cluster is a space or no-break space.
pub fn is_space_or_nbsp(&self) -> bool {
self.data.info.whitespace().is_space_or_nbsp()
}
/// Returns an iterator over the glyphs in the cluster.
pub fn glyphs(&self) -> impl Iterator<Item = Glyph> + 'a + Clone {
if self.data.glyph_len == 0xFF {
GlyphIter::Single(Some(Glyph {
id: self.data.glyph_offset,
style_index: self.data.style_index,
x: 0.,
y: 0.,
advance: self.data.advance,
}))
} else {
let start = self.run.data.glyph_start + self.data.glyph_offset as usize;
GlyphIter::Slice(
self.run.layout.glyphs[start..start + self.data.glyph_len as usize].iter(),
)
}
}
pub(crate) fn info(&self) -> ClusterInfo {
self.data.info
}
}
/// Glyph with an offset and advance.
#[derive(Copy, Clone, Default, Debug)]
pub struct Glyph {
pub id: GlyphId,
pub style_index: u16,
pub x: f32,
pub y: f32,
pub advance: f32,
}
impl Glyph {
/// Returns the index into the layout style collection.
pub fn style_index(&self) -> usize {
self.style_index as usize
}
}
/// Line in a text layout.
#[derive(Copy, Clone)]
pub struct Line<'a, B: Brush> {
layout: &'a LayoutData<B>,
data: &'a LineData,
}
impl<'a, B: Brush> Line<'a, B> {
/// Returns the metrics for the line.
pub fn metrics(&self) -> &LineMetrics {
&self.data.metrics
}
/// Returns the range of text for the line.
pub fn text_range(&self) -> Range<usize> {
self.data.text_range.clone()
}
/// Returns the number of runs in the line.
pub fn len(&self) -> usize {
self.data.run_range.len()
}
/// Returns true if the line is empty.
pub fn is_empty(&self) -> bool {
self.data.run_range.is_empty()
}
/// Returns the run at the specified index.
pub fn get(&self, index: usize) -> Option<Run<'a, B>> {
let index = self.data.run_range.start + index;
let line_data = self.layout.line_runs.get(index)?;
Some(Run {
layout: self.layout,
data: self.layout.runs.get(line_data.run_index)?,
line_data: Some(line_data),
})
}
/// Returns an iterator over the runs for the line.
pub fn runs(&self) -> impl Iterator<Item = Run<'a, B>> + 'a + Clone {
let copy = self.clone();
let line_runs = &copy.layout.line_runs[self.data.run_range.clone()];
line_runs.iter().map(move |line_data| Run {
layout: copy.layout,
data: &copy.layout.runs[line_data.run_index],
line_data: Some(line_data),
})
}
/// Returns an iterator over the glyph runs for the line.
pub fn glyph_runs(&self) -> impl Iterator<Item = GlyphRun<'a, B>> + 'a + Clone {
GlyphRunIter {
line: self.clone(),
run_index: 0,
glyph_start: 0,
offset: 0.,
}
}
}
/// Metrics information for a line.
#[derive(Copy, Clone, Default, Debug)]
pub struct LineMetrics {
/// Typographic ascent.
pub ascent: f32,
/// Typographic descent.
pub descent: f32,
/// Typographic leading.
pub leading: f32,
/// Offset to the baseline.
pub baseline: f32,
/// Offset for alignment.
pub offset: f32,
/// Full advance of the line.
pub advance: f32,
/// Advance of trailing whitespace.
pub trailing_whitespace: f32,
}
impl LineMetrics {
/// Returns the size of the line (ascent + descent + leading).
pub fn size(&self) -> f32 {
self.ascent + self.descent + self.leading
}
}
/// Style properties.
#[derive(Clone, Debug)]
pub struct Style<B: Brush> {
/// Brush for drawing glyphs.
pub brush: B,
/// Underline decoration.
pub underline: Option<Decoration<B>>,
/// Strikethrough decoration.
pub strikethrough: Option<Decoration<B>>,
}
/// Underline or strikethrough decoration.
#[derive(Clone, Debug)]
pub struct Decoration<B: Brush> {
/// Brush used to draw the decoration.
pub brush: B,
/// Offset of the decoration from the baseline. If `None`, use the metrics
/// of the containing run.
pub offset: Option<f32>,
/// Thickness of the decoration. If `None`, use the metrics of the
/// containing run.
pub size: Option<f32>,
}
/// Result of testing a point or text position in a layout.
#[derive(Clone, Default, Debug)]
pub struct HitTestResult {
/// Baseline metric of the caret position.
pub baseline: f32,
/// Offset along the baseline of the caret position.
pub offset: f32,
/// Index of the containing line.
pub line_index: usize,
/// Index of the run within the containing line.
pub run_index: usize,
/// Index of the cluster within the containing run.
pub cluster_index: usize,
/// Source text range of the cluster.
pub text_range: Range<usize>,
/// True if the hit was on the leading edge of a cluster.
pub is_leading: bool,
/// True if the hit was inside the layout bounds.
pub is_inside: bool,
}
/// Sequence of fully positioned glyphs with the same style.
#[derive(Clone)]
pub struct GlyphRun<'a, B: Brush> {
run: Run<'a, B>,
style: &'a Style<B>,
glyph_start: usize,
glyph_count: usize,
offset: f32,
baseline: f32,
}
impl<'a, B: Brush> GlyphRun<'a, B> {
/// Returns the underlying run.
pub fn run(&self) -> &Run<'a, B> {
&self.run
}
/// Returns the associated style.
pub fn style(&self) -> &Style<B> {
self.style
}
/// Returns an iterator over fully positioned glyphs in the run.
pub fn glyphs(&'a self) -> impl Iterator<Item = Glyph> + 'a + Clone {
let mut offset = self.offset;
let baseline = self.baseline;
let glyphs = self
.run
.visual_clusters()
.map(|cluster| cluster.glyphs())
.flatten();
glyphs
.skip(self.glyph_start)
.take(self.glyph_count)
.map(move |mut g| {
g.x += offset;
g.y += baseline;
offset += g.advance;
g
})
}
}
#[derive(Clone)]
struct GlyphRunIter<'a, B: Brush> {
line: Line<'a, B>,
run_index: usize,
glyph_start: usize,
offset: f32,
}
impl<'a, B: Brush> Iterator for GlyphRunIter<'a, B> {
type Item = GlyphRun<'a, B>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let run = self.line.get(self.run_index)?;
let mut iter = run
.visual_clusters()
.map(|c| c.glyphs())
.flatten()
.skip(self.glyph_start);
if let Some(first) = iter.next() {
let mut advance = first.advance;
let style_index = first.style_index();
let mut glyph_count = 1;
for glyph in iter.take_while(|g| g.style_index() == style_index) {
glyph_count += 1;
advance += glyph.advance;
}
let style = run.layout.styles.get(style_index)?;
let glyph_start = self.glyph_start;
self.glyph_start += glyph_count;
let offset = self.offset;
self.offset += advance;
return Some(GlyphRun {
run,
style,
glyph_start,
glyph_count,
offset: offset + self.line.data.metrics.offset,
baseline: self.line.data.metrics.baseline,
});
}
self.run_index += 1;
self.glyph_start = 0;
}
}
}
#[derive(Clone)]
enum GlyphIter<'a> {
Single(Option<Glyph>),
Slice(core::slice::Iter<'a, Glyph>),
}
impl<'a> Iterator for GlyphIter<'a> {
type Item = Glyph;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Single(glyph) => glyph.take(),
Self::Slice(iter) => {
let glyph = *iter.next()?;
Some(glyph)
}
}
}
}

85
src/layout/cluster.rs Normal file
View File

@ -0,0 +1,85 @@
use super::*;
impl<'a, B: Brush> Cluster<'a, B> {
/// Returns the range of text that defines the cluster.
pub fn text_range(&self) -> Range<usize> {
self.data.text_range(self.run.data)
}
/// Returns the advance of the cluster.
pub fn advance(&self) -> f32 {
self.data.advance
}
/// Returns true if the cluster is the beginning of a ligature.
pub fn is_ligature_start(&self) -> bool {
self.data.is_ligature_start()
}
/// Returns true if the cluster is a ligature continuation.
pub fn is_ligature_continuation(&self) -> bool {
self.data.is_ligature_component()
}
/// Returns true if the cluster is a word boundary.
pub fn is_word_boundary(&self) -> bool {
self.data.info.is_boundary()
}
/// Returns true if the cluster is a soft line break.
pub fn is_soft_line_break(&self) -> bool {
self.data.info.boundary() == Boundary::Line
}
/// Returns true if the cluster is a hard line break.
pub fn is_hard_line_break(&self) -> bool {
self.data.info.boundary() == Boundary::Mandatory
}
/// Returns true if the cluster is a space or no-break space.
pub fn is_space_or_nbsp(&self) -> bool {
self.data.info.whitespace().is_space_or_nbsp()
}
/// Returns an iterator over the glyphs in the cluster.
pub fn glyphs(&self) -> impl Iterator<Item = Glyph> + 'a + Clone {
if self.data.glyph_len == 0xFF {
GlyphIter::Single(Some(Glyph {
id: self.data.glyph_offset,
style_index: self.data.style_index,
x: 0.,
y: 0.,
advance: self.data.advance,
}))
} else {
let start = self.run.data.glyph_start + self.data.glyph_offset as usize;
GlyphIter::Slice(
self.run.layout.glyphs[start..start + self.data.glyph_len as usize].iter(),
)
}
}
pub(crate) fn info(&self) -> ClusterInfo {
self.data.info
}
}
#[derive(Clone)]
enum GlyphIter<'a> {
Single(Option<Glyph>),
Slice(core::slice::Iter<'a, Glyph>),
}
impl<'a> Iterator for GlyphIter<'a> {
type Item = Glyph;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Single(glyph) => glyph.take(),
Self::Slice(iter) => {
let glyph = *iter.next()?;
Some(glyph)
}
}
}
}

204
src/layout/cursor.rs Normal file
View File

@ -0,0 +1,204 @@
//! Hit testing.
use super::*;
/// Represents a position within a layout.
#[derive(Copy, Clone, Default, Debug)]
pub struct Cursor {
path: CursorPath,
baseline: f32,
offset: f32,
advance: f32,
text_start: usize,
text_end: usize,
is_rtl: bool,
is_leading: bool,
is_inside: bool,
}
impl Cursor {
/// Creates a new cursor from the specified layout and point.
pub fn from_point<B: Brush>(layout: &Layout<B>, mut x: f32, y: f32) -> Self {
let mut result = Self::default();
result.is_inside = x >= 0. && y >= 0.;
let last_line = layout.data.lines.len().saturating_sub(1);
for (line_index, line) in layout.lines().enumerate() {
let line_metrics = line.metrics();
if y <= line_metrics.baseline || line_index == last_line {
if y > line_metrics.baseline + line_metrics.leading * 0.5 {
result.is_inside = false;
x = f32::MAX;
} else if y < 0. {
x = 0.;
}
result.baseline = line_metrics.baseline;
result.path.line_index = line_index;
let mut last_edge = line_metrics.offset;
for (run_index, run) in line.runs().enumerate() {
result.path.run_index = run_index;
let cluster_range = run.data().cluster_range.clone();
for (cluster_index, cluster) in run.visual_clusters().enumerate() {
let range = cluster.text_range();
result.text_start = range.start;
result.text_end = range.end;
if run.is_rtl() {
result.path.cluster_index = cluster_range.end - cluster_index - 1;
} else {
result.path.cluster_index = cluster_index;
}
let advance = cluster.advance();
if x >= last_edge {
let far_edge = last_edge + advance;
if x < far_edge {
result.is_leading = false;
let middle = (last_edge + far_edge) * 0.5;
result.advance = advance;
if x <= middle {
result.is_leading = true;
result.offset = last_edge;
} else {
result.is_leading = false;
result.offset = far_edge;
}
return result;
}
last_edge = far_edge;
} else {
result.is_inside = false;
result.is_leading = true;
result.offset = line_metrics.offset;
return result;
}
}
}
break;
}
}
result
}
/// Creates a new cursor for the specified layout and text position.
pub fn from_position<B: Brush>(layout: &Layout<B>, mut position: usize) -> Self {
let mut result = Self::default();
result.is_leading = true;
result.is_inside = true;
if position >= layout.data.text_len {
result.is_inside = false;
result.is_leading = false;
position = layout.data.text_len.saturating_sub(1);
}
let last_line = layout.data.lines.len().saturating_sub(1);
for (line_index, line) in layout.lines().enumerate() {
let line_metrics = line.metrics();
result.baseline = line_metrics.baseline;
result.path.line_index = line_index;
if !line.text_range().contains(&position) && line_index != last_line {
continue;
}
let mut last_edge = line_metrics.offset;
result.offset = last_edge;
for (run_index, run) in line.runs().enumerate() {
result.path.run_index = run_index;
if !run.text_range().contains(&position) {
continue;
}
let cluster_range = run.data().cluster_range.clone();
for (cluster_index, cluster) in run.visual_clusters().enumerate() {
let range = cluster.text_range();
result.text_start = range.start;
result.text_end = range.end;
result.offset = last_edge;
if run.is_rtl() {
result.path.cluster_index = cluster_range.end - cluster_index - 1;
} else {
result.path.cluster_index = cluster_index;
}
let advance = cluster.advance();
if range.contains(&position) {
if !result.is_inside {
result.offset += advance;
}
result.advance = advance;
return result;
}
last_edge += advance;
}
}
result.offset = last_edge;
break;
}
result.is_leading = false;
result.is_inside = false;
result
}
/// Returns the path to the target cluster.
pub fn path(&self) -> &CursorPath {
&self.path
}
/// Returns the offset to the baseline.
pub fn baseline(&self) -> f32 {
self.baseline
}
/// Returns the offset to the target cluster along the baseline.
pub fn offset(&self) -> f32 {
self.offset
}
/// Returns the advance of the target cluster.
pub fn advance(&self) -> f32 {
self.advance
}
/// Returns the range of source text for the target cluster.
pub fn text_range(&self) -> Range<usize> {
self.text_start..self.text_end
}
/// Returns true if the cursor is on the leading edge of the target
/// cluster.
pub fn is_leading(&self) -> bool {
self.is_leading
}
/// Returns true if the target cluster is part of a right-to-left run.
pub fn is_rtl(&self) -> bool {
self.is_rtl
}
/// Returns true if the cursor was created from a point or position
/// that is inside the layout.
pub fn is_inside(&self) -> bool {
self.is_inside
}
}
/// Index based path to a cluster.
#[derive(Copy, Clone, Default, Debug)]
pub struct CursorPath {
/// Index of the containing line.
pub line_index: usize,
/// Index of the run within the containing line.
pub run_index: usize,
/// Index of the cluster within the containing run.
pub cluster_index: usize,
}
impl CursorPath {
/// Returns the line for this path and the specified layout.
pub fn line<'a, B: Brush>(&self, layout: &'a Layout<B>) -> Option<Line<'a, B>> {
layout.get(self.line_index)
}
/// Returns the run for this path and the specified layout.
pub fn run<'a, B: Brush>(&self, layout: &'a Layout<B>) -> Option<Run<'a, B>> {
self.line(layout)?.get(self.run_index)
}
/// Returns the cluster for this path and the specified layout.
pub fn cluster<'a, B: Brush>(&self, layout: &'a Layout<B>) -> Option<Cluster<'a, B>> {
self.run(layout)?.get(self.cluster_index)
}
}

View File

@ -1,8 +1,8 @@
//! Greedy line breaking.
use super::data::*;
use super::layout::*;
use super::style::Brush;
use crate::data::*;
use crate::layout::*;
use crate::style::Brush;
use core::ops::Range;
@ -44,7 +44,6 @@ impl<'a, B: Brush> BreakLines<'a, B> {
/// Computes the next line in the paragraph. Returns the advance and size
/// (width and height for horizontal layouts) of the line.
pub fn break_next(&mut self, max_advance: f32, alignment: Alignment) -> Option<(f32, f32)> {
use swash::text::cluster::Boundary;
self.prev_state = Some(self.state.clone());
let run_count = self.layout.runs.len();
while self.state.i < run_count {

183
src/layout/line/mod.rs Normal file
View File

@ -0,0 +1,183 @@
use super::*;
pub mod greedy;
impl<'a, B: Brush> Line<'a, B> {
/// Returns the metrics for the line.
pub fn metrics(&self) -> &LineMetrics {
&self.data.metrics
}
/// Returns the range of text for the line.
pub fn text_range(&self) -> Range<usize> {
self.data.text_range.clone()
}
/// Returns the number of runs in the line.
pub fn len(&self) -> usize {
self.data.run_range.len()
}
/// Returns true if the line is empty.
pub fn is_empty(&self) -> bool {
self.data.run_range.is_empty()
}
/// Returns the run at the specified index.
pub fn get(&self, index: usize) -> Option<Run<'a, B>> {
let index = self.data.run_range.start + index;
let line_data = self.layout.line_runs.get(index)?;
Some(Run {
layout: self.layout,
data: self.layout.runs.get(line_data.run_index)?,
line_data: Some(line_data),
})
}
/// Returns an iterator over the runs for the line.
pub fn runs(&self) -> impl Iterator<Item = Run<'a, B>> + 'a + Clone {
let copy = self.clone();
let line_runs = &copy.layout.line_runs[self.data.run_range.clone()];
line_runs.iter().map(move |line_data| Run {
layout: copy.layout,
data: &copy.layout.runs[line_data.run_index],
line_data: Some(line_data),
})
}
/// Returns an iterator over the glyph runs for the line.
pub fn glyph_runs(&self) -> impl Iterator<Item = GlyphRun<'a, B>> + 'a + Clone {
GlyphRunIter {
line: self.clone(),
run_index: 0,
glyph_start: 0,
offset: 0.,
}
}
}
/// Metrics information for a line.
#[derive(Copy, Clone, Default, Debug)]
pub struct LineMetrics {
/// Typographic ascent.
pub ascent: f32,
/// Typographic descent.
pub descent: f32,
/// Typographic leading.
pub leading: f32,
/// Offset to the baseline.
pub baseline: f32,
/// Offset for alignment.
pub offset: f32,
/// Full advance of the line.
pub advance: f32,
/// Advance of trailing whitespace.
pub trailing_whitespace: f32,
}
impl LineMetrics {
/// Returns the size of the line (ascent + descent + leading).
pub fn size(&self) -> f32 {
self.ascent + self.descent + self.leading
}
}
/// Sequence of fully positioned glyphs with the same style.
#[derive(Clone)]
pub struct GlyphRun<'a, B: Brush> {
run: Run<'a, B>,
style: &'a Style<B>,
glyph_start: usize,
glyph_count: usize,
offset: f32,
baseline: f32,
}
impl<'a, B: Brush> GlyphRun<'a, B> {
/// Returns the underlying run.
pub fn run(&self) -> &Run<'a, B> {
&self.run
}
/// Returns the associated style.
pub fn style(&self) -> &Style<B> {
self.style
}
/// Returns the offset to the baseline.
pub fn baseline(&self) -> f32 {
self.baseline
}
/// Returns the offset to the first glyph along the baseline.
pub fn offset(&self) -> f32 {
self.offset
}
/// Returns an iterator over fully positioned glyphs in the run.
pub fn glyphs(&'a self) -> impl Iterator<Item = Glyph> + 'a + Clone {
let mut offset = self.offset;
let baseline = self.baseline;
let glyphs = self
.run
.visual_clusters()
.map(|cluster| cluster.glyphs())
.flatten();
glyphs
.skip(self.glyph_start)
.take(self.glyph_count)
.map(move |mut g| {
g.x += offset;
g.y += baseline;
offset += g.advance;
g
})
}
}
#[derive(Clone)]
struct GlyphRunIter<'a, B: Brush> {
line: Line<'a, B>,
run_index: usize,
glyph_start: usize,
offset: f32,
}
impl<'a, B: Brush> Iterator for GlyphRunIter<'a, B> {
type Item = GlyphRun<'a, B>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let run = self.line.get(self.run_index)?;
let mut iter = run
.visual_clusters()
.map(|c| c.glyphs())
.flatten()
.skip(self.glyph_start);
if let Some(first) = iter.next() {
let mut advance = first.advance;
let style_index = first.style_index();
let mut glyph_count = 1;
for glyph in iter.take_while(|g| g.style_index() == style_index) {
glyph_count += 1;
advance += glyph.advance;
}
let style = run.layout.styles.get(style_index)?;
let glyph_start = self.glyph_start;
self.glyph_start += glyph_count;
let offset = self.offset;
self.offset += advance;
return Some(GlyphRun {
run,
style,
glyph_start,
glyph_count,
offset: offset + self.line.data.metrics.offset,
baseline: self.line.data.metrics.baseline,
});
}
self.run_index += 1;
self.glyph_start = 0;
}
}
}

169
src/layout/mod.rs Normal file
View File

@ -0,0 +1,169 @@
//! Layout types.
mod cluster;
mod line;
mod run;
pub mod cursor;
use super::data::*;
use super::font::Font;
use super::style::Brush;
use core::ops::Range;
use swash::text::cluster::{Boundary, ClusterInfo};
use swash::{GlyphId, NormalizedCoord, Synthesis};
pub use cursor::Cursor;
pub use line::greedy::BreakLines;
pub use line::{GlyphRun, LineMetrics};
pub use run::RunMetrics;
/// Alignment of a layout.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Alignment {
Start,
Middle,
End,
}
impl Default for Alignment {
fn default() -> Self {
Self::Start
}
}
/// Text layout.
#[derive(Clone)]
pub struct Layout<B: Brush> {
pub(crate) data: LayoutData<B>,
}
impl<B: Brush> Layout<B> {
/// Creates an empty layout.
pub fn new() -> Self {
Self::default()
}
/// Returns the style collection for the layout.
pub fn styles(&self) -> &[Style<B>] {
&self.data.styles
}
/// Returns the number of lines in the layout.
pub fn len(&self) -> usize {
self.data.lines.len()
}
/// Returns true if the layout is empty.
pub fn is_empty(&self) -> bool {
self.data.lines.is_empty()
}
/// Returns the line at the specified index.
pub fn get(&self, index: usize) -> Option<Line<B>> {
Some(Line {
layout: &self.data,
data: self.data.lines.get(index)?,
})
}
/// Returns an iterator over the lines in the layout.
pub fn lines(&self) -> impl Iterator<Item = Line<B>> + '_ + Clone {
self.data.lines.iter().map(move |data| Line {
layout: &self.data,
data,
})
}
/// Returns line breaker to compute lines for the layout.
pub fn break_lines(&mut self) -> BreakLines<B> {
BreakLines::new(&mut self.data)
}
/// Breaks all lines with the specified maximum advance and alignment.
pub fn break_all_lines(&mut self, max_advance: Option<f32>, alignment: Alignment) {
self.break_lines()
.break_remaining(max_advance.unwrap_or(f32::MAX), alignment)
}
/// Returns an iterator over the runs in the layout.
pub fn runs(&self) -> impl Iterator<Item = Run<B>> + '_ + Clone {
self.data.runs.iter().map(move |data| Run {
layout: &self.data,
data,
line_data: None,
})
}
}
impl<B: Brush> Default for Layout<B> {
fn default() -> Self {
Self {
data: Default::default(),
}
}
}
/// Sequence of clusters with a single font and style.
#[derive(Copy, Clone)]
pub struct Run<'a, B: Brush> {
layout: &'a LayoutData<B>,
data: &'a RunData,
line_data: Option<&'a LineRunData>,
}
/// Atomic unit of text.
#[derive(Copy, Clone)]
pub struct Cluster<'a, B: Brush> {
run: Run<'a, B>,
data: &'a ClusterData,
}
/// Glyph with an offset and advance.
#[derive(Copy, Clone, Default, Debug)]
pub struct Glyph {
pub id: GlyphId,
pub style_index: u16,
pub x: f32,
pub y: f32,
pub advance: f32,
}
impl Glyph {
/// Returns the index into the layout style collection.
pub fn style_index(&self) -> usize {
self.style_index as usize
}
}
/// Line in a text layout.
#[derive(Copy, Clone)]
pub struct Line<'a, B: Brush> {
layout: &'a LayoutData<B>,
data: &'a LineData,
}
/// Style properties.
#[derive(Clone, Debug)]
pub struct Style<B: Brush> {
/// Brush for drawing glyphs.
pub brush: B,
/// Underline decoration.
pub underline: Option<Decoration<B>>,
/// Strikethrough decoration.
pub strikethrough: Option<Decoration<B>>,
}
/// Underline or strikethrough decoration.
#[derive(Clone, Debug)]
pub struct Decoration<B: Brush> {
/// Brush used to draw the decoration.
pub brush: B,
/// Offset of the decoration from the baseline. If `None`, use the metrics
/// of the containing run.
pub offset: Option<f32>,
/// Thickness of the decoration. If `None`, use the metrics of the
/// containing run.
pub size: Option<f32>,
}

166
src/layout/run.rs Normal file
View File

@ -0,0 +1,166 @@
use super::*;
impl<'a, B: Brush> Run<'a, B> {
pub(crate) fn new(
layout: &'a LayoutData<B>,
data: &'a RunData,
line_data: Option<&'a LineRunData>,
) -> Self {
Self {
layout,
data,
line_data,
}
}
/// Returns the font for the run.
pub fn font(&self) -> &Font {
self.layout.fonts.get(self.data.font_index).unwrap()
}
/// Returns the font size for the run.
pub fn font_size(&self) -> f32 {
self.data.font_size
}
/// Returns the synthesis suggestions for the font associated with the run.
pub fn synthesis(&self) -> Synthesis {
self.data.synthesis
}
/// Returns the normalized variation coordinates for the font associated
/// with the run.
pub fn normalized_coords(&self) -> &[NormalizedCoord] {
self.layout
.coords
.get(self.data.coords_range.clone())
.unwrap_or(&[])
}
/// Returns metrics for the run.
pub fn metrics(&self) -> &RunMetrics {
&self.data.metrics
}
/// Returns the original text range for the run.
pub fn text_range(&self) -> Range<usize> {
self.line_data
.map(|d| &d.text_range)
.unwrap_or(&self.data.text_range)
.clone()
}
/// Returns true if the run has right-to-left directionality.
pub fn is_rtl(&self) -> bool {
self.data.bidi_level & 1 != 0
}
/// Returns the number of clusters in the run.
pub fn len(&self) -> usize {
self.line_data
.map(|d| &d.cluster_range)
.unwrap_or(&self.data.cluster_range)
.len()
}
/// Returns true if the run is empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the cluster at the specified index.
pub fn get(&self, index: usize) -> Option<Cluster<'a, B>> {
let range = self
.line_data
.map(|d| &d.cluster_range)
.unwrap_or(&self.data.cluster_range);
let index = range.start + index;
Some(Cluster {
run: self.clone(),
data: self.layout.clusters.get(index)?,
})
}
/// Returns an iterator over the clusters in logical order.
pub fn clusters(&'a self) -> impl Iterator<Item = Cluster<'a, B>> + 'a + Clone {
let range = self
.line_data
.map(|d| &d.cluster_range)
.unwrap_or(&self.data.cluster_range)
.clone();
Clusters {
run: self,
range,
rev: false,
}
}
/// Returns an iterator over the clusters in visual order.
pub fn visual_clusters(&'a self) -> impl Iterator<Item = Cluster<'a, B>> + 'a + Clone {
let range = self
.line_data
.map(|d| &d.cluster_range)
.unwrap_or(&self.data.cluster_range)
.clone();
Clusters {
run: self,
range,
rev: self.is_rtl(),
}
}
pub(crate) fn data(&self) -> &'a RunData {
self.data
}
}
struct Clusters<'a, B: Brush> {
run: &'a Run<'a, B>,
range: Range<usize>,
rev: bool,
}
impl<'a, B: Brush> Clone for Clusters<'a, B> {
fn clone(&self) -> Self {
Self {
run: self.run,
range: self.range.clone(),
rev: self.rev,
}
}
}
impl<'a, B: Brush> Iterator for Clusters<'a, B> {
type Item = Cluster<'a, B>;
fn next(&mut self) -> Option<Self::Item> {
let index = if self.rev {
self.range.next_back()?
} else {
self.range.next()?
};
Some(Cluster {
run: self.run.clone(),
data: self.run.layout.clusters.get(index)?,
})
}
}
/// Metrics information for a run.
#[derive(Copy, Clone, Default, Debug)]
pub struct RunMetrics {
/// Typographic ascent.
pub ascent: f32,
/// Typographic descent.
pub descent: f32,
/// Typographic leading.
pub leading: f32,
/// Offset of the top of underline decoration from the baseline.
pub underline_offset: f32,
/// Thickness of the underline decoration.
pub underline_size: f32,
/// Offset of the top of underline decoration from the baseline.
pub strikethrough_offset: f32,
/// Thickness of the underline decoration.
pub strikethrough_size: f32,
}

View File

@ -2,7 +2,6 @@ pub use swash;
mod bidi;
mod data;
mod line;
mod resolve;
mod shape;
mod util;