diff --git a/Rules/Braille/Russian/Russian_Rules.yaml b/Rules/Braille/Russian/Russian_Rules.yaml
index ee9004aed..1bf1603c9 100644
--- a/Rules/Braille/Russian/Russian_Rules.yaml
+++ b/Rules/Braille/Russian/Russian_Rules.yaml
@@ -1555,6 +1555,32 @@
replace:
- x: "DefinitionValue(., 'Braille', 'RussianFunctionBraille')"
+-
+ name: contextual-square-bracket-recognition-point
+ tag: mo
+ match:
+ - "parent::*[IsBracketed(., '[', ']')] and"
+ - "((.='[' and not(preceding-sibling::*) and following-sibling::*[1][self::m:mi or self::m:mtext]) or"
+ - " (.=']' and not(following-sibling::*) and preceding-sibling::*[1][self::m:mi or self::m:mtext]))"
+ replace:
+ - T: "⠠"
+ - x: "text()"
+
+-
+ name: contextual-angle-bracket-recognition-point
+ tag: mo
+ match:
+ - "parent::*[IsBracketed(., '⟨', '⟩')] and"
+ - "((.='⟨' and not(preceding-sibling::*)) or (.='⟩' and not(following-sibling::*))) and"
+ - "(parent::*/*[2][self::m:mi or self::m:mtext] or"
+ - " parent::*/*[last()-1][self::m:mi or self::m:mtext])"
+ replace:
+ - T: "⠈"
+ - test:
+ - if: ".='⟨'"
+ then: [T: "⠪"]
+ else: [T: "⠕"]
+
-
name: div-operator-function
tag: mo
diff --git a/Rules/Braille/Russian/unicode.yaml b/Rules/Braille/Russian/unicode.yaml
index 6b08895b9..4fc43ea9d 100644
--- a/Rules/Braille/Russian/unicode.yaml
+++ b/Rules/Braille/Russian/unicode.yaml
@@ -214,6 +214,8 @@
- "≢": [t: "⠀⠰⠾"]
- "≤": [t: "⠀⠪⠶"]
- "≥": [t: "⠀⠕⠶"]
+- "≶": [T: "⠀⠪⠕⠀"]
+- "≷": [T: "⠀⠕⠪⠀"]
- "≪": [t: "⠀⠪⠪⠀"]
- "≫": [t: "⠀⠕⠕⠀"]
- "≺": [t: "⠀⠒⠪⠀"]
@@ -238,6 +240,10 @@
- "⋰": [t: "⠠⠲⠲⠲"]
- "⋱": [t: "⠠⠲⠲⠲"]
- "△": [t: "⠸⠙"]
+- "⟨": [T: "⠪"]
+- "⟩": [T: "⠕"]
+- "↑": [T: "⠰⠌"]
+- "↓": [T: "⠘⠡"]
- "→": [t: "⠀⠒⠕"]
- "⟶": [t: "⠀⠒⠕"]
- "←": [t: "⠀⠦⠶"]
diff --git a/Rules/Languages/ru/ClearSpeak_Rules.yaml b/Rules/Languages/ru/ClearSpeak_Rules.yaml
index c0955204c..0815c96c5 100644
--- a/Rules/Languages/ru/ClearSpeak_Rules.yaml
+++ b/Rules/Languages/ru/ClearSpeak_Rules.yaml
@@ -760,6 +760,24 @@
- pause: short
- x: "*[3]"
+# Handle log and ln when they remain individual mi elements rather than the
+# canonical three-child function-application mrow handled by the next rule.
+- name: ClearSpeak-log
+ tag: mi
+ match: ".='log' or .='ln'"
+ replace:
+ - bookmark: "@id"
+ - test:
+ if: "$Verbosity!='Terse' and following-sibling::*[2] and
+ not(IsNode(following-sibling::*[2],'simple'))"
+ then: [T: "от"]
+ - test:
+ - if: ".='log'"
+ then: [T: "логарифм"]
+ - else_if: "$ClearSpeak_Log = 'LnAsNaturalLog'"
+ then: [T: "натуральный логарифм"]
+ else: [T: "эл эн"]
+
- name: ClearSpeak-log
tag: mrow
variables: [{log_is_simple: "IsNode(*[3],'simple')"}]
diff --git a/Rules/Languages/ru/navigate.yaml b/Rules/Languages/ru/navigate.yaml
index 527dcce60..01bc51a76 100644
--- a/Rules/Languages/ru/navigate.yaml
+++ b/Rules/Languages/ru/navigate.yaml
@@ -91,6 +91,15 @@
# Rules for speaking what happens when moving into or out of a notation
+# Prefix/silent intents with no NavigationParts entry (for example, unary
+# minus, a vector, or a modified variable) should not announce "in"/"out of".
+- name: into-or-out-of-prefix-or-silent-without-parts
+ tag: "*"
+ match: "$Move2D != '' and
+ (contains(@data-intent-property, ':prefix:') or contains(@data-intent-property, ':silent:')) and
+ GetNavigationPartName(name(.), count($Child2D/preceding-sibling::*)) = ''"
+ replace: []
+
- name: into-or-out-of-mfrac
tag: "*"
match: "$Move2D != '' and (@data-from-mathml='mfrac' or self::m:mfrac)"
diff --git a/tests/Languages/ru.rs b/tests/Languages/ru.rs
index 397e29a08..97edff4ee 100644
--- a/tests/Languages/ru.rs
+++ b/tests/Languages/ru.rs
@@ -31,4 +31,5 @@ mod units;
mod chemistry;
mod alphabets;
mod intent;
-mod mtable;
\ No newline at end of file
+mod mtable;
+mod navigate;
diff --git a/tests/Languages/ru/ClearSpeak/functions.rs b/tests/Languages/ru/ClearSpeak/functions.rs
index 941e694d8..5171150f6 100644
--- a/tests/Languages/ru/ClearSpeak/functions.rs
+++ b/tests/Languages/ru/ClearSpeak/functions.rs
@@ -144,6 +144,22 @@ fn natural_log() -> Result<()> {
return Ok(());
}
+#[test]
+fn individual_log_mi() -> Result<()> {
+ let expr = "";
+ test("ru", "ClearSpeak", expr, "логарифм")?;
+ return Ok(());
+}
+
+#[test]
+fn individual_ln_mi() -> Result<()> {
+ let expr = "";
+ test("ru", "ClearSpeak", expr, "эл эн")?;
+ test_ClearSpeak("ru", "ClearSpeak_Log", "LnAsNaturalLog", expr,
+ "натуральный логарифм")?;
+ return Ok(());
+}
+
#[test]
fn explicit_function_call_with_parens() -> Result<()> {
let expr = "";
diff --git a/tests/Languages/ru/navigate.rs b/tests/Languages/ru/navigate.rs
new file mode 100644
index 000000000..690df9eff
--- /dev/null
+++ b/tests/Languages/ru/navigate.rs
@@ -0,0 +1,56 @@
+//! Russian navigation speech for prefix/silent intents without NavigationParts.
+
+use crate::common::*;
+use anyhow::Result;
+use std::panic::{catch_unwind, AssertUnwindSafe};
+
+fn init_nav(mathml: &str) -> Result<()> {
+ set_rules_dir(abs_rules_dir_path())?;
+ set_preference("Language", "ru")?;
+ set_preference("SpeechStyle", "SimpleSpeak")?;
+ set_preference("Verbosity", "Medium")?;
+ set_preference("NavMode", "Enhanced")?;
+ set_preference("NavVerbosity", "Verbose")?;
+ set_preference("AutoZoomOut", "False")?;
+ set_preference("Overview", "False")?;
+ set_mathml(mathml)?;
+ Ok(())
+}
+
+fn assert_zoom_in(mathml: &str, expected: &str) -> Result<()> {
+ init_panic_handler();
+ let result = catch_unwind(AssertUnwindSafe(|| {
+ init_nav(mathml)?;
+ let speech = do_navigate_command("ZoomIn")?;
+ let trimmed_speech = speech.trim_end_matches([' ', ',', ';']).to_string();
+ assert_eq!(expected, trimmed_speech);
+ Ok(())
+ }));
+ report_any_panic(result)
+}
+
+#[test]
+fn no_parts_prefix_vector_suppresses_base_announcement() -> Result<()> {
+ let expr = r#"
+
+ "#;
+ assert_zoom_in(expr, "переход внутрь; вэ")
+}
+
+#[test]
+fn no_parts_silent_modified_variable_suppresses_base_announcement() -> Result<()> {
+ let expr = r#"
+
+ "#;
+ assert_zoom_in(expr, "переход внутрь; икс")
+}
diff --git a/tests/braille/Russian/russian.rs b/tests/braille/Russian/russian.rs
index 5138fb374..1b40f13b7 100644
--- a/tests/braille/Russian/russian.rs
+++ b/tests/braille/Russian/russian.rs
@@ -1387,6 +1387,35 @@ fn source_less_common_math_symbols_11() -> Result<()> {
return test_russian_braille(r#""#, "⠠⠁⠀⠼⠀⠃");
}
+#[test]
+fn source_gost_order_relations_greater_or_less() -> Result<()> {
+ return test_russian_braille(r#""#, "⠠⠭⠀⠕⠪⠀⠽");
+}
+
+#[test]
+fn source_gost_order_relations_less_or_greater() -> Result<()> {
+ return test_russian_braille(r#""#, "⠠⠭⠀⠪⠕⠀⠽");
+}
+
+#[test]
+fn source_gost_large_vertical_arrows() -> Result<()> {
+ test_russian_braille(r#""#, "⠰⠌")?;
+ return test_russian_braille(r#""#, "⠘⠡");
+}
+
+#[test]
+fn source_gost_contextual_square_bracket_recognition_points() -> Result<()> {
+ test_russian_braille(r#""#, "⠠⠷⠠⠭⠠⠾")?;
+ return test_russian_braille(r#""#, "⠷⠼⠁⠾");
+}
+
+#[test]
+fn source_gost_contextual_angle_bracket_recognition_points() -> Result<()> {
+ test_russian_braille(r#""#, "⠈⠪⠠⠭⠈⠕")?;
+ test_russian_braille(r#""#, "⠪⠼⠁⠕")?;
+ return test_russian_braille(r#""#, "⠠⠭⠀⠪⠀⠽");
+}
+
#[test]
fn source_typeform_and_mathvariant_indicators() -> Result<()> {
let expr = r#""#;