mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 13:24:35 +00:00
M3: right-to-left punctuation doesn't decide a line's direction, and an alphabetic mark gives a taken-back neutral no direction (view.copy-text)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
This commit is contained in:
1 parent
8c1977f051
commit
aab3682629
2 files changed
+43
-6
No files matched your search
@@ -356,11 +356,13 @@ fn keep_taken_back_blanks(glyphs: &[&TextGlyph], placed: &mut [usize]) {
|
||||
let mark = |g: &TextGlyph| !g.text.is_empty() && g.text.chars().all(|c| unicode_bidi::bidi_class(c) == unicode_bidi::BidiClass::NSM);
|
||||
// The direction a glyph gives the text around it: Some(false) for left-to-right letters, Some(true) for
|
||||
// right-to-left letters and for numbers, which read as right-to-left beside neutrals (UAX #9 N1, as in
|
||||
// `reverse_rtl`), None for a glyph without one.
|
||||
// `reverse_rtl`), None for a glyph without one. Nonspacing marks give none, alphabetic ones (U+0345) included: a
|
||||
// mark has the direction of the letter it sits on.
|
||||
let strong = |g: &TextGlyph| {
|
||||
if g.text.chars().any(|c| c.is_alphabetic() && !is_rtl(c)) {
|
||||
let spacing = || g.text.chars().filter(|&c| unicode_bidi::bidi_class(c) != unicode_bidi::BidiClass::NSM);
|
||||
if spacing().any(|c| c.is_alphabetic() && !is_rtl(c)) {
|
||||
Some(false)
|
||||
} else if g.text.chars().any(char::is_alphanumeric) {
|
||||
} else if spacing().any(char::is_alphanumeric) {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
@@ -597,13 +599,14 @@ fn bidi_class(text: &str) -> Bidi {
|
||||
}
|
||||
|
||||
/// Whether a line of the glyphs `line` is right-to-left: it has more right-to-left letters than left-to-right ones.
|
||||
/// Letters are counted one by one, so a ligature glyph ("ffi") counts each of its letters, and nonspacing marks count
|
||||
/// toward neither direction: a mark has the direction of the letter it sits on.
|
||||
/// Letters are counted one by one, so a ligature glyph ("ffi") counts each of its letters. Punctuation of a
|
||||
/// right-to-left script (a maqaf, an Arabic comma) is not a letter and counts toward neither direction, nor do
|
||||
/// nonspacing marks: a mark has the direction of the letter it sits on.
|
||||
fn is_rtl_line(glyphs: &[&TextGlyph], line: impl Iterator<Item = usize>) -> bool {
|
||||
let (mut rtl, mut ltr) = (0usize, 0usize);
|
||||
for g in line.filter_map(|i| glyphs.get(i)) {
|
||||
for c in g.text.chars().filter(|&c| unicode_bidi::bidi_class(c) != unicode_bidi::BidiClass::NSM) {
|
||||
if is_rtl(c) && !c.is_numeric() {
|
||||
if is_rtl(c) && c.is_alphabetic() {
|
||||
rtl = rtl.saturating_add(1);
|
||||
} else if c.is_alphabetic() && !is_rtl(c) {
|
||||
ltr = ltr.saturating_add(1);
|
||||
|
||||
@@ -2482,6 +2482,40 @@ fn a_ligature_counts_each_of_its_letters_toward_a_lines_direction() {
|
||||
assert_eq!(line(&["ffi"]), split);
|
||||
}
|
||||
|
||||
/// Punctuation of a right-to-left script is not a letter and doesn't decide a line's direction: two Latin letters,
|
||||
/// a Hebrew letter and a maqaf and a sof pasuq after it read Latin first.
|
||||
#[test]
|
||||
fn right_to_left_punctuation_does_not_decide_a_lines_direction() {
|
||||
let mut v = vec![em_box("a", 0.0, 0.0, 6.0, 11.0), em_box("b", 6.0, 0.0, 12.0, 11.0)];
|
||||
let mut x = 16.0;
|
||||
for c in ["\u{5C3}", "\u{5BE}", "ש"] {
|
||||
v.push(em_box(c, x, 0.0, x + 6.5, 11.0));
|
||||
x += 6.5;
|
||||
}
|
||||
let text = layout(v).plain_text();
|
||||
assert!(text.starts_with("ab"), "{text:?}");
|
||||
}
|
||||
|
||||
/// An alphabetic nonspacing mark (U+0345) gives the text around a taken-back neutral no direction: a question mark
|
||||
/// taken back over the space that starts a reverse-order show string reads as on the same line without the mark.
|
||||
#[test]
|
||||
fn an_alphabetic_mark_beside_a_taken_back_neutral_gives_it_no_direction() {
|
||||
let line = |mark: bool| {
|
||||
let mut v = vec![em_box(" ", 10.0, 0.0, 13.5, 11.0), em_box("?", 9.9, 0.0, 15.0, 11.0)];
|
||||
let mut x = 15.0;
|
||||
for c in ["ף", "י", "ע", "ס"] {
|
||||
v.push(em_box(c, x, 0.0, x + 6.5, 11.0));
|
||||
x += 6.5;
|
||||
}
|
||||
if mark {
|
||||
v.push(em_box("\u{345}", 14.0, 0.0, 14.0, 11.0));
|
||||
}
|
||||
layout(v).plain_text()
|
||||
};
|
||||
let without = line(false);
|
||||
assert_eq!(line(true).replace('\u{345}', ""), without);
|
||||
}
|
||||
|
||||
/// A mark of a left-to-right script that rides on no letter has no direction of its own: U+0345 (alphabetic, bidi
|
||||
/// class NSM) drawn on the last letter of a Hebrew word, left of it and right after a Latin word, reads with the
|
||||
/// Hebrew word, after that letter, as a Hebrew point drawn there does.
|
||||
|
||||
Reference in new issue
Block a user