Merge pull request #175 from Imberflur/stable-wrap
Fix #134 and include a test for it.
This commit is contained in:
commit
001d2baac2
5 changed files with 260 additions and 48 deletions
|
|
@ -58,3 +58,6 @@ members = [
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.5.1", default-features = false, features = ["cargo_bench_support"] }
|
criterion = { version = "0.5.1", default-features = false, features = ["cargo_bench_support"] }
|
||||||
|
|
||||||
|
[profile.test]
|
||||||
|
opt-level = 1
|
||||||
|
|
|
||||||
93
fonts/FiraMono-LICENSE
Normal file
93
fonts/FiraMono-LICENSE
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
BIN
fonts/FiraMono-Medium.ttf
Normal file
BIN
fonts/FiraMono-Medium.ttf
Normal file
Binary file not shown.
129
src/shape.rs
129
src/shape.rs
|
|
@ -569,11 +569,14 @@ impl ShapeSpan {
|
||||||
let mut start_word = 0;
|
let mut start_word = 0;
|
||||||
for (end_lb, _) in unicode_linebreak::linebreaks(span) {
|
for (end_lb, _) in unicode_linebreak::linebreaks(span) {
|
||||||
let mut start_lb = end_lb;
|
let mut start_lb = end_lb;
|
||||||
for (i, c) in span[start_word..end_lb].char_indices() {
|
for (i, c) in span[start_word..end_lb].char_indices().rev() {
|
||||||
if start_word + i == end_lb {
|
// TODO: Not all whitespace characters are linebreakable, e.g. 00A0 (No-break
|
||||||
break;
|
// space)
|
||||||
} else if c.is_whitespace() {
|
// https://www.unicode.org/reports/tr14/#GL
|
||||||
|
// https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt
|
||||||
|
if c.is_whitespace() {
|
||||||
start_lb = start_word + i;
|
start_lb = start_word + i;
|
||||||
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -946,9 +949,9 @@ impl ShapeLine {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut fit_x = line_width;
|
|
||||||
for (span_index, span) in self.spans.iter().enumerate() {
|
for (span_index, span) in self.spans.iter().enumerate() {
|
||||||
let mut word_range_width = 0.;
|
let mut word_range_width = 0.;
|
||||||
|
let mut width_before_last_blank = 0.;
|
||||||
let mut number_of_blanks: u32 = 0;
|
let mut number_of_blanks: u32 = 0;
|
||||||
|
|
||||||
// Create the word ranges that fits in a visual line
|
// Create the word ranges that fits in a visual line
|
||||||
|
|
@ -957,19 +960,30 @@ impl ShapeLine {
|
||||||
let mut fitting_start = (span.words.len(), 0);
|
let mut fitting_start = (span.words.len(), 0);
|
||||||
for (i, word) in span.words.iter().enumerate().rev() {
|
for (i, word) in span.words.iter().enumerate().rev() {
|
||||||
let word_width = font_size * word.x_advance;
|
let word_width = font_size * word.x_advance;
|
||||||
if fit_x - word_width >= 0. {
|
|
||||||
|
// Addition in the same order used to compute the final width, so that
|
||||||
|
// relayouts with that width as the `line_width` will produce the same
|
||||||
|
// wrapping results.
|
||||||
|
if current_visual_line.w + (word_range_width + word_width)
|
||||||
|
<= line_width
|
||||||
|
// Include one blank word over the width limit since it won't be
|
||||||
|
// counted in the final width
|
||||||
|
|| (word.blank
|
||||||
|
&& (current_visual_line.w + word_range_width) <= line_width)
|
||||||
|
{
|
||||||
// fits
|
// fits
|
||||||
fit_x -= word_width;
|
|
||||||
word_range_width += word_width;
|
|
||||||
if word.blank {
|
if word.blank {
|
||||||
number_of_blanks += 1;
|
number_of_blanks += 1;
|
||||||
|
width_before_last_blank = word_range_width;
|
||||||
}
|
}
|
||||||
|
word_range_width += word_width;
|
||||||
continue;
|
continue;
|
||||||
} else if wrap == Wrap::Glyph {
|
} else if wrap == Wrap::Glyph {
|
||||||
for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
|
for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
|
||||||
let glyph_width = font_size * glyph.x_advance;
|
let glyph_width = font_size * glyph.x_advance;
|
||||||
if fit_x - glyph_width >= 0. {
|
if current_visual_line.w + (word_range_width + glyph_width)
|
||||||
fit_x -= glyph_width;
|
<= line_width
|
||||||
|
{
|
||||||
word_range_width += glyph_width;
|
word_range_width += glyph_width;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -985,30 +999,33 @@ impl ShapeLine {
|
||||||
current_visual_line = VisualLine::default();
|
current_visual_line = VisualLine::default();
|
||||||
|
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
fit_x = line_width - glyph_width;
|
|
||||||
word_range_width = glyph_width;
|
word_range_width = glyph_width;
|
||||||
fitting_start = (i, glyph_i + 1);
|
fitting_start = (i, glyph_i + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Wrap::Word
|
// Wrap::Word
|
||||||
let mut trailing_space_width = None;
|
|
||||||
if let Some(previous_word) = span.words.get(i + 1) {
|
// TODO: What if the previous span ended with whitespace and the next
|
||||||
// Current word causing a wrap is not whitespace, so we ignore the
|
// span wraps a new line? Is that possible?
|
||||||
// previous word if it's a whitespace
|
//
|
||||||
if previous_word.blank {
|
// TODO: This comment it outdated, the current word can be a
|
||||||
trailing_space_width =
|
// whitespace.
|
||||||
Some(previous_word.x_advance * font_size);
|
//
|
||||||
number_of_blanks = number_of_blanks.saturating_sub(1);
|
// Current word causing a wrap is not whitespace, so we ignore the
|
||||||
}
|
// previous word if it's a whitespace
|
||||||
}
|
let trailing_blank = span
|
||||||
if let Some(width) = trailing_space_width {
|
.words
|
||||||
|
.get(i + 1)
|
||||||
|
.map_or(false, |previous_word| previous_word.blank);
|
||||||
|
if trailing_blank {
|
||||||
|
number_of_blanks = number_of_blanks.saturating_sub(1);
|
||||||
add_to_visual_line(
|
add_to_visual_line(
|
||||||
&mut current_visual_line,
|
&mut current_visual_line,
|
||||||
span_index,
|
span_index,
|
||||||
(i + 2, 0),
|
(i + 2, 0),
|
||||||
fitting_start,
|
fitting_start,
|
||||||
word_range_width - width,
|
width_before_last_blank,
|
||||||
number_of_blanks,
|
number_of_blanks,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1026,11 +1043,9 @@ impl ShapeLine {
|
||||||
|
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
if word.blank {
|
if word.blank {
|
||||||
fit_x = line_width;
|
|
||||||
word_range_width = 0.;
|
word_range_width = 0.;
|
||||||
fitting_start = (i, 0);
|
fitting_start = (i, 0);
|
||||||
} else {
|
} else {
|
||||||
fit_x = line_width - word_width;
|
|
||||||
word_range_width = word_width;
|
word_range_width = word_width;
|
||||||
fitting_start = (i + 1, 0);
|
fitting_start = (i + 1, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -1049,19 +1064,26 @@ impl ShapeLine {
|
||||||
let mut fitting_start = (0, 0);
|
let mut fitting_start = (0, 0);
|
||||||
for (i, word) in span.words.iter().enumerate() {
|
for (i, word) in span.words.iter().enumerate() {
|
||||||
let word_width = font_size * word.x_advance;
|
let word_width = font_size * word.x_advance;
|
||||||
if fit_x - word_width >= 0. {
|
if current_visual_line.w + (word_range_width + word_width)
|
||||||
|
<= line_width
|
||||||
|
// Include one blank word over the width limit since it won't be
|
||||||
|
// counted in the final width.
|
||||||
|
|| (word.blank
|
||||||
|
&& (current_visual_line.w + word_range_width) <= line_width)
|
||||||
|
{
|
||||||
// fits
|
// fits
|
||||||
fit_x -= word_width;
|
|
||||||
word_range_width += word_width;
|
|
||||||
if word.blank {
|
if word.blank {
|
||||||
number_of_blanks += 1;
|
number_of_blanks += 1;
|
||||||
|
width_before_last_blank = word_range_width;
|
||||||
}
|
}
|
||||||
|
word_range_width += word_width;
|
||||||
continue;
|
continue;
|
||||||
} else if wrap == Wrap::Glyph {
|
} else if wrap == Wrap::Glyph {
|
||||||
for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
|
for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
|
||||||
let glyph_width = font_size * glyph.x_advance;
|
let glyph_width = font_size * glyph.x_advance;
|
||||||
if fit_x - glyph_width >= 0. {
|
if current_visual_line.w + (word_range_width + glyph_width)
|
||||||
fit_x -= glyph_width;
|
<= line_width
|
||||||
|
{
|
||||||
word_range_width += glyph_width;
|
word_range_width += glyph_width;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1077,32 +1099,24 @@ impl ShapeLine {
|
||||||
current_visual_line = VisualLine::default();
|
current_visual_line = VisualLine::default();
|
||||||
|
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
fit_x = line_width - glyph_width;
|
|
||||||
word_range_width = glyph_width;
|
word_range_width = glyph_width;
|
||||||
fitting_start = (i, glyph_i);
|
fitting_start = (i, glyph_i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Wrap::Word
|
// Wrap::Word
|
||||||
let mut trailing_space_width = None;
|
|
||||||
if i > 0 {
|
// Current word causing a wrap is not whitespace, so we ignore the
|
||||||
if let Some(previous_word) = span.words.get(i - 1) {
|
// previous word if it's a whitespace
|
||||||
// Current word causing a wrap is not whitespace, so we ignore the
|
let trailing_blank = i > 0 && span.words[i - 1].blank;
|
||||||
// previous word if it's a whitespace
|
if trailing_blank {
|
||||||
if previous_word.blank {
|
number_of_blanks = number_of_blanks.saturating_sub(1);
|
||||||
trailing_space_width =
|
|
||||||
Some(previous_word.x_advance * font_size);
|
|
||||||
number_of_blanks = number_of_blanks.saturating_sub(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(width) = trailing_space_width {
|
|
||||||
add_to_visual_line(
|
add_to_visual_line(
|
||||||
&mut current_visual_line,
|
&mut current_visual_line,
|
||||||
span_index,
|
span_index,
|
||||||
fitting_start,
|
fitting_start,
|
||||||
(i - 1, 0),
|
(i - 1, 0),
|
||||||
word_range_width - width,
|
width_before_last_blank,
|
||||||
number_of_blanks,
|
number_of_blanks,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1120,11 +1134,9 @@ impl ShapeLine {
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
|
|
||||||
if word.blank {
|
if word.blank {
|
||||||
fit_x = line_width;
|
|
||||||
word_range_width = 0.;
|
word_range_width = 0.;
|
||||||
fitting_start = (i + 1, 0);
|
fitting_start = (i + 1, 0);
|
||||||
} else {
|
} else {
|
||||||
fit_x = line_width - word_width;
|
|
||||||
word_range_width = word_width;
|
word_range_width = word_width;
|
||||||
fitting_start = (i, 0);
|
fitting_start = (i, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -1166,6 +1178,19 @@ impl ShapeLine {
|
||||||
(Align::Center, _) => (line_width - visual_line.w) / 2.0,
|
(Align::Center, _) => (line_width - visual_line.w) / 2.0,
|
||||||
(Align::End, _) => line_width - visual_line.w,
|
(Align::End, _) => line_width - visual_line.w,
|
||||||
(Align::Justified, _) => {
|
(Align::Justified, _) => {
|
||||||
|
// TODO: Only certain `is_whitespace` chars are typically expanded.
|
||||||
|
//
|
||||||
|
// https://www.unicode.org/reports/tr14/#Introduction
|
||||||
|
// > When expanding or compressing interword space according to common
|
||||||
|
// > typographical practice, only the spaces marked by U+0020 SPACE and U+00A0
|
||||||
|
// > NO-BREAK SPACE are subject to compression, and only spaces marked by U+0020
|
||||||
|
// > SPACE, U+00A0 NO-BREAK SPACE, and occasionally spaces marked by U+2009 THIN
|
||||||
|
// > SPACE are subject to expansion. All other space characters normally have
|
||||||
|
// > fixed width.
|
||||||
|
//
|
||||||
|
// (also some spaces aren't followed by potential linebreaks but they could
|
||||||
|
// still be expanded)
|
||||||
|
|
||||||
// Don't justify the last line in a paragraph.
|
// Don't justify the last line in a paragraph.
|
||||||
if visual_line.spaces > 0 && index != number_of_visual_lines - 1 {
|
if visual_line.spaces > 0 && index != number_of_visual_lines - 1 {
|
||||||
(line_width - visual_line.w) / visual_line.spaces as f32
|
(line_width - visual_line.w) / visual_line.spaces as f32
|
||||||
|
|
@ -1335,7 +1360,15 @@ impl ShapeLine {
|
||||||
let mut glyphs_swap = Vec::new();
|
let mut glyphs_swap = Vec::new();
|
||||||
mem::swap(&mut glyphs, &mut glyphs_swap);
|
mem::swap(&mut glyphs, &mut glyphs_swap);
|
||||||
layout_lines.push(LayoutLine {
|
layout_lines.push(LayoutLine {
|
||||||
w: if self.rtl { start_x - x } else { x },
|
w: if align != Align::Justified {
|
||||||
|
visual_line.w
|
||||||
|
} else {
|
||||||
|
if self.rtl {
|
||||||
|
start_x - x
|
||||||
|
} else {
|
||||||
|
x
|
||||||
|
}
|
||||||
|
},
|
||||||
max_ascent: max_ascent * font_size,
|
max_ascent: max_ascent * font_size,
|
||||||
max_descent: max_descent * font_size,
|
max_descent: max_descent * font_size,
|
||||||
glyphs: glyphs_swap,
|
glyphs: glyphs_swap,
|
||||||
|
|
|
||||||
83
tests/wrap_stability.rs
Normal file
83
tests/wrap_stability.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
use cosmic_text::{
|
||||||
|
fontdb, Align, Attrs, AttrsList, BidiParagraphs, Family, FontSystem, LayoutLine, ShapeLine,
|
||||||
|
Shaping, Weight, Wrap,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test for https://github.com/pop-os/cosmic-text/issues/134
|
||||||
|
//
|
||||||
|
// Being able to get the same wrapping when feeding the measured width back into ShapeLine::layout
|
||||||
|
// as the new width limit is very useful for certain UI layout use cases.
|
||||||
|
#[test]
|
||||||
|
fn stable_wrap() {
|
||||||
|
let font_size = 18.0;
|
||||||
|
let attrs = AttrsList::new(
|
||||||
|
Attrs::new()
|
||||||
|
.family(Family::Name("FiraMono"))
|
||||||
|
.weight(Weight::MEDIUM),
|
||||||
|
);
|
||||||
|
let mut font_system =
|
||||||
|
FontSystem::new_with_locale_and_db("en-US".into(), fontdb::Database::new());
|
||||||
|
let font = std::fs::read("fonts/FiraMono-Medium.ttf").unwrap();
|
||||||
|
font_system.db_mut().load_font_data(font);
|
||||||
|
|
||||||
|
let mut check_wrap = |text: &_, wrap, start_width| {
|
||||||
|
let line = ShapeLine::new(&mut font_system, text, &attrs, Shaping::Advanced);
|
||||||
|
|
||||||
|
let layout_unbounded = line.layout(font_size, start_width, wrap, Some(Align::Left));
|
||||||
|
let max_width = layout_unbounded.iter().map(|l| l.w).fold(0.0, f32::max);
|
||||||
|
let new_limit = f32::min(start_width, max_width);
|
||||||
|
|
||||||
|
let layout_bounded = line.layout(font_size, new_limit, wrap, Some(Align::Left));
|
||||||
|
let bounded_max_width = layout_bounded.iter().map(|l| l.w).fold(0.0, f32::max);
|
||||||
|
|
||||||
|
// For debugging:
|
||||||
|
// dbg_layout_lines(text, &layout_unbounded);
|
||||||
|
// dbg_layout_lines(text, &layout_bounded);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
(max_width, layout_unbounded.len()),
|
||||||
|
(bounded_max_width, layout_bounded.len()),
|
||||||
|
"Wrap \"{wrap:?}\" with text: \"{text}\"",
|
||||||
|
);
|
||||||
|
for (u, b) in layout_unbounded[1..].iter().zip(layout_bounded[1..].iter()) {
|
||||||
|
assert_eq!(u.w, b.w, "Wrap {wrap:?} with text: \"{text}\"",);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let hello_sample = std::fs::read_to_string("sample/hello.txt").unwrap();
|
||||||
|
let cases = [
|
||||||
|
"(6) SomewhatBoringDisplayTransform",
|
||||||
|
"",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
// This has several cases where the line would wrap when the computed width was used as the
|
||||||
|
// width limit.
|
||||||
|
.chain(BidiParagraphs::new(&hello_sample));
|
||||||
|
|
||||||
|
for text in cases {
|
||||||
|
for wrap in [Wrap::Word, Wrap::Glyph] {
|
||||||
|
for start_width in [f32::MAX, 80.0, 198.2132, 20.0, 4.0, 300.0] {
|
||||||
|
check_wrap(text, wrap, start_width);
|
||||||
|
let with_spaces = format!("{text} ");
|
||||||
|
check_wrap(&with_spaces, wrap, start_width);
|
||||||
|
let with_spaces_2 = format!("{text} ");
|
||||||
|
check_wrap(&with_spaces_2, wrap, start_width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn dbg_layout_lines(text: &str, lines: &[LayoutLine]) {
|
||||||
|
for line in lines {
|
||||||
|
let mut s = String::new();
|
||||||
|
for glyph in line.glyphs.iter() {
|
||||||
|
s.push_str(&text[glyph.start..glyph.end]);
|
||||||
|
}
|
||||||
|
println!("\"{s}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue