diff --git a/render-wasm/src/shapes/text_horizontal/overflow.rs b/render-wasm/src/shapes/text_horizontal/overflow.rs index 3f99b7a11a..d06a80f413 100644 --- a/render-wasm/src/shapes/text_horizontal/overflow.rs +++ b/render-wasm/src/shapes/text_horizontal/overflow.rs @@ -19,7 +19,9 @@ impl HorizontalParagraphPlan { if !(policy.is_chinese() || matches!( policy, - TextLayoutPolicy::KoreanWords | TextLayoutPolicy::KoreanSyllable + TextLayoutPolicy::Japanese + | TextLayoutPolicy::KoreanWords + | TextLayoutPolicy::KoreanSyllable )) || !width.is_finite() || width <= 0.0 diff --git a/render-wasm/src/shapes/text_horizontal/tests.rs b/render-wasm/src/shapes/text_horizontal/tests.rs index 763aa4c6f6..9f8f5efff8 100644 --- a/render-wasm/src/shapes/text_horizontal/tests.rs +++ b/render-wasm/src/shapes/text_horizontal/tests.rs @@ -1876,7 +1876,7 @@ fn horizontal_annotations_attach_to_the_ideographic_em_box() { #[test] fn horizontal_indivisible_punctuation_unit_overflows_a_narrow_box() { use crate::shapes::text_policy::KoreanWrap; - for language in ["zh-Hant-TW", "zh-Hans-CN", "zh-Hant-HK", "ko"] { + for language in ["zh-Hant-TW", "zh-Hans-CN", "zh-Hant-HK", "ko", "ja"] { let (_, plan, layout) = cjk_horizontal_layout(&["文", "。"], language, KoreanWrap::KeepWords, 20.0); let starts: Vec<_> = layout @@ -1896,6 +1896,55 @@ fn horizontal_indivisible_punctuation_unit_overflows_a_narrow_box() { } } +#[test] +fn japanese_narrow_box_keeps_ruby_bases_whole_and_respects_kinsoku() { + use crate::shapes::kinsoku::forbidden_at_line_start; + use crate::shapes::text_vertical::test_support::provider; + init_state(); + let mut fonts = skia::textlayout::FontCollection::new(); + fonts.set_asset_font_manager(Some( + provider(include_bytes!("../../fonts/cjk-locl-test.ttf")).into(), + )); + let mut resources = + crate::render::RenderResources::try_new_headless().expect("headless resources"); + let _guard = crate::globals::TestRenderResourcesGuard::install(&mut resources); + let mut base = make_span("文文", 0.0); + base.font_size = 20.0; + base.ruby = "ぶんぶん".to_string(); + let mut paragraph = make_paragraph( + vec![make_span("「", 0.0), base, make_span("」、文文。", 0.0)], + 0.0, + ); + paragraph.set_language("ja".to_string()); + let source: Vec = "「文文」、文文。".chars().collect(); + let (plan, layout) = cjk_layout_paragraph(¶graph, 20.0, &fonts); + let starts: Vec = layout + .get_line_metrics() + .iter() + .map(|line| plan.offsets.builder_to_source(line.start_index)) + .collect(); + for start in &starts { + assert!( + !forbidden_at_line_start(source[*start]), + "line {starts:?} starts with {}", + source[*start] + ); + } + let targets = horizontal_ruby_targets(¶graph, &plan.offsets); + assert_eq!(targets.len(), 1); + let rects = layout.get_rects_for_range( + targets[0].1.clone(), + RectHeightStyle::Tight, + RectWidthStyle::Tight, + ); + assert!( + rects + .iter() + .all(|r| (r.rect.top() - rects[0].rect.top()).abs() < 0.01), + "the ruby base splits over lines {starts:?}" + ); +} + #[test] fn narrow_atomic_overflow_retains_normal_wrap_budget_and_explicit_newlines() { use crate::shapes::text_policy::KoreanWrap; diff --git a/render-wasm/src/shapes/text_policy.rs b/render-wasm/src/shapes/text_policy.rs index d3d718946b..4294d9c512 100644 --- a/render-wasm/src/shapes/text_policy.rs +++ b/render-wasm/src/shapes/text_policy.rs @@ -142,6 +142,9 @@ impl TextLayoutPolicy { && !opening(before) && !closing(after) && !(self.is_korean() && "‐‑–-・·々〻ー".contains(after)) + && !(self == Self::Japanese + && (crate::shapes::kinsoku::forbidden_at_line_start(after) + || crate::shapes::kinsoku::forbidden_at_line_end(before))) && !(before == after && matches!(after, '…' | '‥' | '—' | '―')) && !("$$£€₩¥¥₽₺₫++--−".contains(before) && numeric(after)) && !(numeric(before) && "%%‰℃℉°".contains(after))