Skip to content

Commit

Permalink
Separate alignment from line breaking
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoburns committed Jul 20, 2024
1 parent 1ef546b commit ac59681
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 114 deletions.
132 changes: 132 additions & 0 deletions parley/src/layout/alignment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2024 the Parley Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use super::{Alignment, BreakReason, LayoutData};
use crate::style::Brush;

pub(crate) fn align<B: Brush>(
layout: &mut LayoutData<B>,
alignment_width: Option<f32>,
alignment: Alignment,
) {
let alignment_width = alignment_width.unwrap_or_else(|| {
let max_line_length = layout
.lines
.iter()
.map(|line| line.metrics.advance)
.max_by(f32::total_cmp)
.unwrap_or(0.0);
max_line_length.min(max_line_length)
});

// Apply alignment to line items
for line in &mut layout.lines {
// TODO: remove this field
line.alignment = alignment;

// Compute free space.
let free_space = alignment_width - line.metrics.advance + line.metrics.trailing_whitespace;

// Alignment only applies if free_space > 0
if free_space <= 0. {
continue;
}

match alignment {
Alignment::Start => {
// Do nothing
}
Alignment::End => {
line.metrics.offset = free_space;
}
Alignment::Middle => {
line.metrics.offset = free_space * 0.5;
}
Alignment::Justified => {
if line.break_reason == BreakReason::None || line.num_spaces == 0 {
continue;
}

let adjustment = free_space / line.num_spaces as f32;
let mut applied = 0;
for line_item in layout.line_items[line.item_range.clone()]
.iter()
.filter(|item| item.is_text_run())
{
// Iterate over clusters in the run
// - Iterate forwards for even bidi levels (which represent RTL runs)
// - Iterate backwards for odd bidi levels (which represent RTL runs)
let clusters = &mut layout.clusters[line_item.cluster_range.clone()];
let bidi_level_is_odd = line_item.bidi_level & 1 != 0;
if bidi_level_is_odd {
for cluster in clusters.iter_mut().rev() {
if applied == line.num_spaces {
break;
}
if cluster.info.whitespace().is_space_or_nbsp() {
cluster.advance += adjustment;
applied += 1;
}
}
} else {
for cluster in clusters.iter_mut() {
if applied == line.num_spaces {
break;
}
if cluster.info.whitespace().is_space_or_nbsp() {
cluster.advance += adjustment;
applied += 1;
}
}
}
}
}
}
}
}

/// Removes previous justification applied to clusters.
/// This is part of resetting state in preparation for re-linebreaking the same layout.
pub(crate) fn unjustify<B: Brush>(layout: &mut LayoutData<B>) {
for line in &layout.lines {
if line.alignment == Alignment::Justified
&& line.max_advance.is_finite()
&& line.max_advance < f32::MAX
{
let extra = line.max_advance - line.metrics.advance + line.metrics.trailing_whitespace;
if line.break_reason != BreakReason::None && line.num_spaces != 0 {
let adjustment = extra / line.num_spaces as f32;
let mut applied = 0;
for line_run in layout.line_items[line.item_range.clone()]
.iter()
.filter(|item| item.is_text_run())
{
if line_run.bidi_level & 1 != 0 {
for cluster in layout.clusters[line_run.cluster_range.clone()]
.iter_mut()
.rev()
{
if applied == line.num_spaces {
break;
}
if cluster.info.whitespace().is_space_or_nbsp() {
cluster.advance -= adjustment;
applied += 1;
}
}
} else {
for cluster in layout.clusters[line_run.cluster_range.clone()].iter_mut() {
if applied == line.num_spaces {
break;
}
if cluster.info.whitespace().is_space_or_nbsp() {
cluster.advance -= adjustment;
applied += 1;
}
}
}
}
}
}
}
}
117 changes: 6 additions & 111 deletions parley/src/layout/line/greedy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

use crate::layout::alignment::unjustify;
use crate::layout::*;
use crate::style::Brush;

Expand Down Expand Up @@ -133,7 +134,7 @@ 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)> {
pub fn break_next(&mut self, max_advance: f32) -> Option<(f32, f32)> {
// Maintain iterator state
if self.done {
return None;
Expand All @@ -150,7 +151,7 @@ impl<'a, B: Brush> BreakLines<'a, B> {
&mut self.lines,
&mut self.state.line,
max_advance,
alignment,
Alignment::Start,
$break_reason,
)
};
Expand Down Expand Up @@ -316,7 +317,7 @@ impl<'a, B: Brush> BreakLines<'a, B> {
// item/run/cluster iteration state back to how it was when the line-breaking opportunity was encountered
else if let Some(prev) = self.state.prev_boundary.take() {
// println!("REVERT");
debug_assert!(prev.state.x != 0.0);
// debug_assert!(prev.state.x != 0.0);

// Q: Why do we revert the line state here, but only revert the indexes if the commit suceeds?
self.state.line = prev.state;
Expand Down Expand Up @@ -393,7 +394,7 @@ impl<'a, B: Brush> BreakLines<'a, B> {

/// Breaks all remaining lines with the specified maximum advance. This
/// consumes the line breaker.
pub fn break_remaining(mut self, max_advance: f32, alignment: Alignment) {
pub fn break_remaining(mut self, max_advance: f32) {
// println!("\nDEBUG ITEMS");
// for item in &self.layout.items {
// match item.kind {
Expand All @@ -407,7 +408,7 @@ impl<'a, B: Brush> BreakLines<'a, B> {

// println!("\nBREAK ALL");

while self.break_next(max_advance, alignment).is_some() {}
while self.break_next(max_advance).is_some() {}
self.finish();
}

Expand Down Expand Up @@ -542,66 +543,6 @@ impl<'a, B: Brush> BreakLines<'a, B> {
}
}

// Apply alignment to line items
let has_finite_width = line.max_advance.is_finite() && line.max_advance < f32::MAX;
if has_finite_width {
// Compute free space. Alignment only applies if free_space > 0
let free_space =
line.max_advance - line.metrics.advance + line.metrics.trailing_whitespace;

if free_space > 0. {
match line.alignment {
Alignment::Start => {
// Do nothing
}
Alignment::End => {
line.metrics.offset = free_space;
}
Alignment::Middle => {
line.metrics.offset = free_space * 0.5;
}
Alignment::Justified => {
if line.break_reason != BreakReason::None && line.num_spaces != 0 {
let adjustment = free_space / line.num_spaces as f32;
let mut applied = 0;
for line_item in self.lines.line_items[line.item_range.clone()]
.iter()
.filter(|item| item.is_text_run())
{
// Iterate over clusters in the run
// - Iterate forwards for even bidi levels (which represent RTL runs)
// - Iterate backwards for odd bidi levels (which represent RTL runs)
let clusters =
&mut self.layout.clusters[line_item.cluster_range.clone()];
let bidi_level_is_odd = line_item.bidi_level & 1 != 0;
if bidi_level_is_odd {
for cluster in clusters.iter_mut().rev() {
if applied == line.num_spaces {
break;
}
if cluster.info.whitespace().is_space_or_nbsp() {
cluster.advance += adjustment;
applied += 1;
}
}
} else {
for cluster in clusters.iter_mut() {
if applied == line.num_spaces {
break;
}
if cluster.info.whitespace().is_space_or_nbsp() {
cluster.advance += adjustment;
applied += 1;
}
}
}
}
}
}
}
}
}

if !have_metrics {
// Line consisting entirely of whitespace?
if !line.item_range.is_empty() {
Expand Down Expand Up @@ -660,52 +601,6 @@ impl<'a, B: Brush> Drop for BreakLines<'a, B> {
}
}

/// Removes previous justification applied to clusters.
/// This is part of resetting state in preparation for re-linebreaking the same layout.
fn unjustify<B: Brush>(layout: &mut LayoutData<B>) {
for line in &layout.lines {
if line.alignment == Alignment::Justified
&& line.max_advance.is_finite()
&& line.max_advance < f32::MAX
{
let extra = line.max_advance - line.metrics.advance + line.metrics.trailing_whitespace;
if line.break_reason != BreakReason::None && line.num_spaces != 0 {
let adjustment = extra / line.num_spaces as f32;
let mut applied = 0;
for line_run in layout.line_items[line.item_range.clone()]
.iter()
.filter(|item| item.is_text_run())
{
if line_run.bidi_level & 1 != 0 {
for cluster in layout.clusters[line_run.cluster_range.clone()]
.iter_mut()
.rev()
{
if applied == line.num_spaces {
break;
}
if cluster.info.whitespace().is_space_or_nbsp() {
cluster.advance -= adjustment;
applied += 1;
}
}
} else {
for cluster in layout.clusters[line_run.cluster_range.clone()].iter_mut() {
if applied == line.num_spaces {
break;
}
if cluster.info.whitespace().is_space_or_nbsp() {
cluster.advance -= adjustment;
applied += 1;
}
}
}
}
}
}
}
}

// fn cluster_range_is_valid(
// mut cluster_range: Range<usize>,
// state_cluster_range: Range<usize>,
Expand Down
15 changes: 12 additions & 3 deletions parley/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

//! Layout types.

mod alignment;
mod cluster;
mod line;
mod run;
Expand All @@ -11,6 +12,8 @@ pub(crate) mod data;

pub mod cursor;

use self::alignment::align;

use super::style::Brush;
use crate::{Font, InlineBox};
use core::ops::Range;
Expand Down Expand Up @@ -116,10 +119,16 @@ impl<B: Brush> Layout<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) {
/// Breaks all lines with the specified maximum advance
pub fn break_all_lines(&mut self, max_advance: Option<f32>) {
self.break_lines()
.break_remaining(max_advance.unwrap_or(f32::MAX), alignment);
.break_remaining(max_advance.unwrap_or(f32::MAX));
}

// Apply to alignment to layout relative to the specified container width. If container_width is not
// specified then the max line length is used.
pub fn align(&mut self, container_width: Option<f32>, alignment: Alignment) {
align(&mut self.data, container_width, alignment);
}

/// Returns an iterator over the runs in the layout.
Expand Down

0 comments on commit ac59681

Please sign in to comment.