Skip to content

Commit 77e40a1

Browse files
authored
Merge pull request #135 from kas-gui/push-qynszxvpupvu
Add run-breaking tests and fixes
2 parents 460d3ae + a66b3f6 commit 77e40a1

File tree

1 file changed

+140
-11
lines changed

1 file changed

+140
-11
lines changed

src/display/text_runs.rs

Lines changed: 140 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ impl TextDisplay {
3434
/// [Requires status][Self#status-of-preparation]: level runs have been
3535
/// prepared and are valid in all ways except size (`dpem`).
3636
///
37+
/// Parameters must match those passed to [`Self::prepare_runs`] for the
38+
/// result to be valid.
39+
///
3740
/// This updates the result of [`TextDisplay::prepare_runs`] due to change
3841
/// in font size.
3942
pub fn resize_runs(&mut self, text: &str, mut font_tokens: impl Iterator<Item = FontToken>) {
@@ -145,7 +148,12 @@ impl TextDisplay {
145148
///
146149
/// [Requires status][Self#status-of-preparation]: none.
147150
///
148-
/// Must be called again if any of `text`, `direction` or `font` change.
151+
/// The `font_tokens` iterator must not be empty and the first token yielded
152+
/// must have [`FontToken::start`] == 0. (Failure to do so will result in an
153+
/// error on debug builds and usage of default values on release builds.)
154+
///
155+
/// Must be called again if any of `text`, `direction` or `font_tokens`
156+
/// change.
149157
/// If only `dpem` changes, [`Self::resize_runs`] may be called instead.
150158
///
151159
/// The text is broken into a set of contiguous "level runs". These runs are
@@ -285,13 +293,8 @@ impl TextDisplay {
285293
require_break = true;
286294
}
287295

288-
let mut new_script = None;
289296
if is_real(script) {
290-
if first_real.is_none() {
291-
first_real = Some(c);
292-
}
293297
if script != input.script {
294-
new_script = Some(script);
295298
require_break |= is_real(input.script);
296299
}
297300
}
@@ -324,8 +327,22 @@ impl TextDisplay {
324327
}
325328
input.script = script;
326329
breaks = Default::default();
327-
} else if is_break && !is_control {
328-
breaks.push(shaper::GlyphBreak::new(to_u32(index)));
330+
} else {
331+
if is_break && !is_control && index > start {
332+
breaks.push(shaper::GlyphBreak::new(to_u32(index)));
333+
}
334+
335+
if input.script == Script::Unknown
336+
|| matches!(input.script, Script::Common | Script::Inherited) && is_real(script)
337+
{
338+
input.script = script;
339+
}
340+
}
341+
342+
if is_real(script) {
343+
if first_real.is_none() {
344+
first_real = Some(c);
345+
}
329346
}
330347

331348
if let Some(token) = next_token.as_ref()
@@ -345,9 +362,6 @@ impl TextDisplay {
345362
last_is_control = is_control;
346363
last_is_htab = is_htab;
347364
emoji_start = new_emoji_start;
348-
if let Some(script) = new_script {
349-
input.script = script;
350-
}
351365
}
352366

353367
let hard_break = ends_with_hard_break(text);
@@ -540,3 +554,118 @@ impl EmojiState {
540554
b
541555
}
542556
}
557+
558+
/// Warning: test results may depend on system fonts
559+
#[cfg(test)]
560+
mod test {
561+
use super::*;
562+
use std::iter;
563+
use std::ops::Range;
564+
use unicode_bidi::Level;
565+
566+
type Expected<'a> = &'a [(Range<usize>, RunSpecial, Level, Script, &'a [u32])];
567+
568+
fn test_breaking(text: &str, dir: Direction, expected: Expected) {
569+
let fonts = iter::once(FontToken {
570+
start: 0,
571+
dpem: 16.0,
572+
font: Default::default(),
573+
});
574+
575+
let mut display = TextDisplay::default();
576+
assert!(display.prepare_runs(text, dir, fonts).is_ok());
577+
578+
for (i, (run, expected)) in display.runs.iter().zip(expected.iter()).enumerate() {
579+
assert_eq!(
580+
run.range.to_std(),
581+
expected.0,
582+
"text range for text \"{text}\", run {i}"
583+
);
584+
assert_eq!(
585+
run.special, expected.1,
586+
"RunSpecial for text \"{text}\", run {i}"
587+
);
588+
assert_eq!(run.level, expected.2, "for text \"{text}\", run {i}");
589+
assert_eq!(run.script, expected.3, "for text \"{text}\", run {i}");
590+
assert_eq!(
591+
run.breaks.iter().map(|b| b.index).collect::<Vec<_>>(),
592+
expected.4,
593+
"wrap-points for text \"{text}\", run {i}"
594+
);
595+
}
596+
assert_eq!(display.runs.len(), expected.len(), "number of runs");
597+
}
598+
599+
#[test]
600+
fn test_breaking_simple() {
601+
let sample = "Layout demo 123";
602+
test_breaking(
603+
sample,
604+
Direction::Auto,
605+
&[(
606+
0..sample.len(),
607+
RunSpecial::None,
608+
Level::ltr(),
609+
Script::Latin,
610+
&[7, 12],
611+
)],
612+
);
613+
}
614+
615+
#[test]
616+
fn test_breaking_bidi() {
617+
let sample = "abc אבג def";
618+
test_breaking(
619+
sample,
620+
Direction::Auto,
621+
&[
622+
(0..4, RunSpecial::None, Level::ltr(), Script::Latin, &[]),
623+
(
624+
4..10,
625+
RunSpecial::NoBreak,
626+
Level::rtl(),
627+
Script::Hebrew,
628+
&[],
629+
),
630+
(10..14, RunSpecial::None, Level::ltr(), Script::Latin, &[11]),
631+
],
632+
);
633+
}
634+
635+
#[test]
636+
fn test_breaking_weak_bidi() {
637+
let sample = "123 (1-2)";
638+
639+
let expected_ltr: Expected =
640+
&[(0..9, RunSpecial::None, Level::ltr(), Script::Common, &[4])];
641+
test_breaking(sample, Direction::Auto, &expected_ltr[..]);
642+
test_breaking(sample, Direction::Ltr, &expected_ltr[..]);
643+
644+
let expected_rtl: Expected = &[
645+
(
646+
0..3,
647+
RunSpecial::NoBreak,
648+
Level::new(2).unwrap(),
649+
Script::Common,
650+
&[],
651+
),
652+
(
653+
3..5,
654+
RunSpecial::NoBreak,
655+
Level::rtl(),
656+
Script::Common,
657+
&[4],
658+
),
659+
(
660+
5..8,
661+
RunSpecial::NoBreak,
662+
Level::new(2).unwrap(),
663+
Script::Common,
664+
&[],
665+
),
666+
(8..9, RunSpecial::None, Level::rtl(), Script::Common, &[]),
667+
];
668+
test_breaking(sample, Direction::AutoRtl, &expected_rtl[..]);
669+
test_breaking(sample, Direction::Rtl, &expected_rtl[..]);
670+
}
671+
}

0 commit comments

Comments
 (0)