From 7751960fd1cdae96054ecef058d43c642af073cf Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Sat, 4 Jul 2026 21:14:52 -0500 Subject: [PATCH 1/4] Filter furigana from Japanese OCR output Ports the furigana-stripping feature from the previous better-cjk work onto dev's OCR pipeline. Small ruby-text characters rendered above the main text are detected by height and vertical position and removed before the CJK words are merged in GetTextFromOcrLine. Adds a deterministic unit test using OCR test doubles rather than the prior branch's flat-word-border approach, which depended on the now-removed PostOcrUtilities implementation. Co-Authored-By: Claude Opus 4.8 --- Tests/OcrTests.cs | 40 +++++++++++++++++++ Text-Grab/Utilities/OcrUtilities.cs | 62 ++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/Tests/OcrTests.cs b/Tests/OcrTests.cs index 996488be..71d631e8 100644 --- a/Tests/OcrTests.cs +++ b/Tests/OcrTests.cs @@ -476,6 +476,33 @@ public async Task GetTesseractGitHubLanguage() File.Delete(tempFilePath); } + [Fact] + public void BuildTextFromOcrLines_FiltersFuriganaForJapanese() + { + // Given a Japanese line where the kanji 黒 is annotated with the small + // furigana くろ rendered directly above it. + FakeOcrLine line = new("くろ黒ごま", new Windows.Foundation.Rect(0, 0, 60, 30)) + { + Words = + [ + // Furigana: short and sitting above the kanji it annotates. + new FakeOcrWord("くろ", new Windows.Foundation.Rect(0, 0, 16, 8)), + // Main text: full-height single characters. + new FakeOcrWord("黒", new Windows.Foundation.Rect(0, 10, 20, 20)), + new FakeOcrWord("ご", new Windows.Foundation.Rect(20, 10, 20, 20)), + new FakeOcrWord("ま", new Windows.Foundation.Rect(40, 10, 20, 20)), + ] + }; + + FakeOcrLinesWords ocrResult = new() { Lines = [line] }; + + // When + string text = OcrUtilities.BuildTextFromOcrLines(new GlobalLang("ja"), ocrResult); + + // Then the furigana is dropped, leaving only the main text. + Assert.Equal("黒ごま", text); + } + private sealed class FakeOcrLinesWords : IOcrLinesWords { public string Text { get; set; } = string.Empty; @@ -499,4 +526,17 @@ public FakeOcrLine(string text, Windows.Foundation.Rect boundingBox) public Windows.Foundation.Rect BoundingBox { get; set; } } + + private sealed class FakeOcrWord : IOcrWord + { + public FakeOcrWord(string text, Windows.Foundation.Rect boundingBox) + { + Text = text; + BoundingBox = boundingBox; + } + + public string Text { get; set; } + + public Windows.Foundation.Rect BoundingBox { get; set; } + } } diff --git a/Text-Grab/Utilities/OcrUtilities.cs b/Text-Grab/Utilities/OcrUtilities.cs index f3b65301..8442c52b 100644 --- a/Text-Grab/Utilities/OcrUtilities.cs +++ b/Text-Grab/Utilities/OcrUtilities.cs @@ -69,10 +69,14 @@ public static void GetTextFromOcrLine(this IOcrLine ocrLine, bool isSpaceJoining } else { + // For CJK languages, filter out likely furigana (small ruby-text + // characters above the main text) before merging the words. + IEnumerable words = FilterFurigana([.. ocrLine.Words]); + bool isFirstWord = true; bool isPrevWordSpaceJoining = false; - foreach (IOcrWord ocrWord in ocrLine.Words) + foreach (IOcrWord ocrWord in words) { string wordString = ocrWord.Text; @@ -95,6 +99,62 @@ public static void GetTextFromOcrLine(this IOcrLine ocrLine, bool isSpaceJoining text.ReplaceGreekOrCyrillicWithLatin(); } + /// + /// Removes words that are likely furigana: small ruby-text characters + /// rendered above the main text in Japanese. A word is treated as furigana + /// when it is noticeably shorter than the line's median word height and sits + /// directly above a larger word that overlaps it horizontally. + /// + internal static List FilterFurigana(List words) + { + if (words.Count == 0) + return words; + + // Furigana is typically around half the height of the main text. + List heights = [.. words.Select(w => w.BoundingBox.Height).OrderBy(h => h)]; + double medianHeight = heights[heights.Count / 2]; + double furiganaThreshold = medianHeight * 0.6; + + List filteredWords = []; + + for (int i = 0; i < words.Count; i++) + { + IOcrWord word = words[i]; + bool isProbablyFurigana = false; + + if (word.BoundingBox.Height < furiganaThreshold) + { + // Only treat it as furigana when a larger word sits below it and + // overlaps horizontally (i.e. the kanji it annotates). + for (int j = 0; j < words.Count; j++) + { + if (i == j) + continue; + + IOcrWord otherWord = words[j]; + + bool isBelow = otherWord.BoundingBox.Top > word.BoundingBox.Bottom; + bool overlapsHorizontally = !(otherWord.BoundingBox.Right < word.BoundingBox.Left + || otherWord.BoundingBox.Left > word.BoundingBox.Right); + bool isLarger = otherWord.BoundingBox.Height > furiganaThreshold; + + if (isBelow && overlapsHorizontally && isLarger) + { + isProbablyFurigana = word.Text.Length <= 2; + break; + } + } + } + + if (!isProbablyFurigana) + filteredWords.Add(word); + } + + // If everything was filtered, fall back to the original words to avoid + // dropping the whole line. + return filteredWords.Count > 0 ? filteredWords : words; + } + public static async Task GetTextFromAbsoluteRectAsync( Rect rect, ILanguage language, From 6f015fa660950dba34cd1fcc80e0057f367d2469 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Sat, 4 Jul 2026 22:14:13 -0500 Subject: [PATCH 2/4] Add thorough tests for CJK furigana filtering Covers the FilterFurigana heuristic directly (empty/single/uniform input, overlap and vertical-position requirements, the "larger word below" requirement, and the word-length cutoff) plus end-to-end language gating through BuildTextFromOcrLines for Japanese, Chinese, and space-joining languages. Co-Authored-By: Claude Opus 4.8 --- Tests/OcrTests.cs | 232 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) diff --git a/Tests/OcrTests.cs b/Tests/OcrTests.cs index 71d631e8..cabc960b 100644 --- a/Tests/OcrTests.cs +++ b/Tests/OcrTests.cs @@ -503,6 +503,238 @@ public void BuildTextFromOcrLines_FiltersFuriganaForJapanese() Assert.Equal("黒ごま", text); } + // ----- FilterFurigana unit tests (the geometry heuristic) ----- + + [Fact] + public void FilterFurigana_EmptyList_ReturnsEmpty() + { + List result = OcrUtilities.FilterFurigana([]); + + Assert.Empty(result); + } + + [Fact] + public void FilterFurigana_SingleWord_IsKept() + { + List words = [Word("黒", 0, 0, 20, 20)]; + + List result = OcrUtilities.FilterFurigana(words); + + Assert.Equal(["黒"], result.Select(w => w.Text)); + } + + [Fact] + public void FilterFurigana_UniformHeights_KeepsAllInOrder() + { + // No word is small relative to the median, so nothing is furigana. + List words = + [ + Word("黒", 0, 0, 20, 20), + Word("ご", 20, 0, 20, 20), + Word("ま", 40, 0, 20, 20), + ]; + + List result = OcrUtilities.FilterFurigana(words); + + Assert.Equal(["黒", "ご", "ま"], result.Select(w => w.Text)); + } + + [Fact] + public void FilterFurigana_RemovesSmallWordAboveOverlappingKanji() + { + List words = + [ + Word("くろ", 0, 0, 16, 8), // furigana: short, sitting above + Word("黒", 0, 10, 20, 20), // kanji: taller, below, overlapping + ]; + + List result = OcrUtilities.FilterFurigana(words); + + Assert.Equal(["黒"], result.Select(w => w.Text)); + } + + [Fact] + public void FilterFurigana_KeepsSmallWordWhenNotHorizontallyOverlapping() + { + // Small, but nowhere near a kanji horizontally, so it is real text. + List words = + [ + Word("くろ", 100, 0, 16, 8), + Word("黒", 0, 10, 20, 20), + ]; + + List result = OcrUtilities.FilterFurigana(words); + + Assert.Equal(["くろ", "黒"], result.Select(w => w.Text)); + } + + [Fact] + public void FilterFurigana_KeepsSmallWordBelowMainText() + { + // Furigana sits above its kanji; a small word BELOW a larger word is + // not furigana and must be kept. + List words = + [ + Word("黒", 0, 0, 20, 20), + Word("くろ", 0, 22, 16, 8), + ]; + + List result = OcrUtilities.FilterFurigana(words); + + Assert.Equal(["黒", "くろ"], result.Select(w => w.Text)); + } + + [Fact] + public void FilterFurigana_KeepsSmallWordWhenWordBelowIsNotLarger() + { + // A small word directly above another small word is not furigana: + // furigana requires a larger word (the kanji) beneath it. The two tall + // words only exist to raise the median height. + List words = + [ + Word("く", 0, 0, 8, 8), + Word("ろ", 0, 10, 8, 8), // below + overlapping, but also small + Word("本", 50, 0, 20, 20), + Word("語", 80, 0, 20, 20), + ]; + + List result = OcrUtilities.FilterFurigana(words); + + Assert.Equal(["く", "ろ", "本", "語"], result.Select(w => w.Text)); + } + + [Theory] + [InlineData("く", true)] // 1-char ruby is removed + [InlineData("くろ", true)] // 2-char ruby is removed + [InlineData("くろが", false)] // 3+ chars is treated as real text and kept + public void FilterFurigana_OnlyRemovesShortWords(string rubyText, bool removed) + { + List words = + [ + Word(rubyText, 0, 0, 16, 8), + Word("黒", 0, 10, 20, 20), + ]; + + List result = OcrUtilities.FilterFurigana(words); + + string[] expected = removed ? ["黒"] : [rubyText, "黒"]; + Assert.Equal(expected, result.Select(w => w.Text)); + } + + [Fact] + public void FilterFurigana_RemovesMultipleFuriganaKeepingMainText() + { + List words = + [ + Word("くろ", 0, 0, 16, 8), + Word("黒", 0, 10, 20, 20), + Word("ごま", 20, 0, 16, 8), + Word("米", 20, 10, 20, 20), + ]; + + List result = OcrUtilities.FilterFurigana(words); + + Assert.Equal(["黒", "米"], result.Select(w => w.Text)); + } + + // ----- BuildTextFromOcrLines integration (language gating) ----- + + [Fact] + public void BuildTextFromOcrLines_JapaneseWithoutFurigana_IsUnchanged() + { + FakeOcrLine line = new("黒ごま", new Windows.Foundation.Rect(0, 0, 60, 20)) + { + Words = + [ + Word("黒", 0, 0, 20, 20), + Word("ご", 20, 0, 20, 20), + Word("ま", 40, 0, 20, 20), + ] + }; + FakeOcrLinesWords ocrResult = new() { Lines = [line] }; + + string text = OcrUtilities.BuildTextFromOcrLines(new GlobalLang("ja"), ocrResult); + + Assert.Equal("黒ごま", text); + } + + [Fact] + public void BuildTextFromOcrLines_ChineseText_JoinsWithoutSpaces() + { + FakeOcrLine line = new("中文", new Windows.Foundation.Rect(0, 0, 40, 20)) + { + Words = + [ + Word("中", 0, 0, 20, 20), + Word("文", 20, 0, 20, 20), + ] + }; + FakeOcrLinesWords ocrResult = new() { Lines = [line] }; + + string text = OcrUtilities.BuildTextFromOcrLines(new GlobalLang("zh-Hans"), ocrResult); + + Assert.Equal("中文", text); + } + + [Fact] + public void BuildTextFromOcrLines_FiltersRubyTextForChinese() + { + // The same small-ruby heuristic also runs for Chinese, another + // non-space-joining language (e.g. bopomofo above a character). + FakeOcrLine line = new("ㄓ中文", new Windows.Foundation.Rect(0, 0, 40, 30)) + { + Words = + [ + Word("ㄓ", 0, 0, 8, 8), + Word("中", 0, 10, 20, 20), + Word("文", 20, 10, 20, 20), + ] + }; + FakeOcrLinesWords ocrResult = new() { Lines = [line] }; + + string text = OcrUtilities.BuildTextFromOcrLines(new GlobalLang("zh-Hans"), ocrResult); + + Assert.Equal("中文", text); + } + + [Fact] + public void BuildTextFromOcrLines_SpaceJoiningLanguage_DoesNotFilterFurigana() + { + // For space-joining languages the whole line text is used verbatim, so + // the furigana heuristic never runs, even with a tiny word present. + var settings = AppUtilities.TextGrabSettings; + bool originalParagraphDetection = settings.ParagraphDetection; + bool originalCorrectErrors = settings.CorrectErrors; + settings.ParagraphDetection = false; + settings.CorrectErrors = false; + + try + { + FakeOcrLine line = new("Hello World", new Windows.Foundation.Rect(0, 0, 100, 30)) + { + Words = + [ + Word("x", 0, 0, 4, 4), // tiny word that would be furigana in CJK + Word("Hello", 0, 10, 50, 20), + Word("World", 55, 10, 50, 20), + ] + }; + FakeOcrLinesWords ocrResult = new() { Lines = [line] }; + + string text = OcrUtilities.BuildTextFromOcrLines(new GlobalLang("en-US"), ocrResult); + + Assert.Equal("Hello World" + System.Environment.NewLine, text); + } + finally + { + settings.ParagraphDetection = originalParagraphDetection; + settings.CorrectErrors = originalCorrectErrors; + } + } + + private static FakeOcrWord Word(string text, double x, double y, double width, double height) + => new(text, new Windows.Foundation.Rect(x, y, width, height)); + private sealed class FakeOcrLinesWords : IOcrLinesWords { public string Text { get; set; } = string.Empty; From d1e0aa9db47a70cd07d420a8da83d5db1a4fb836 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Sun, 5 Jul 2026 17:40:27 -0500 Subject: [PATCH 3/4] Add RemoveFurigana setting and UI for CJK OCR cleanup Added a user setting to enable/disable furigana (ruby text) removal from Japanese and Chinese OCR results. Updated LanguageSettings UI with a toggle, explanation, and info note. Synced toggle state with settings and cache invalidation logic. --- Text-Grab/App.config | 3 ++ Text-Grab/Pages/LanguageSettings.xaml | 41 +++++++++++++++++++++++ Text-Grab/Pages/LanguageSettings.xaml.cs | 12 +++++++ Text-Grab/Properties/Settings.Designer.cs | 24 +++++++++---- Text-Grab/Properties/Settings.settings | 3 ++ 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/Text-Grab/App.config b/Text-Grab/App.config index 98a9942b..c5ff66fd 100644 --- a/Text-Grab/App.config +++ b/Text-Grab/App.config @@ -268,6 +268,9 @@ True + + True + diff --git a/Text-Grab/Pages/LanguageSettings.xaml b/Text-Grab/Pages/LanguageSettings.xaml index 96412571..4eb0c13a 100644 --- a/Text-Grab/Pages/LanguageSettings.xaml +++ b/Text-Grab/Pages/LanguageSettings.xaml @@ -72,6 +72,47 @@ Content="Learn more about Windows AI Foundry" NavigateUri="https://learn.microsoft.com/en-us/windows/ai/apis/" /> + + + Furigana are the small kana readings printed above kanji in Japanese (and similar ruby + annotations in Chinese). The OCR engine reads them as extra text, which clutters the + result. When enabled, Text Grab detects those small ruby lines by their size and position + and removes them, keeping only the main text. + + + + Remove furigana (ruby text) from Japanese and Chinese OCR + + + + + This is a work in progress. Furigana detection is based on a size-and-position + heuristic, so it can occasionally miss a reading or remove text it shouldn't. I'm + actively trying to improve how Text Grab handles Japanese and Chinese. If you have + experience with CJK text, OCR, or furigana and would be willing to help, please email + me at + joe@joefinapps.com. Thank you! + + + True + + True + From eb4bae778c87c75f18d36716baa97a6fd55a138e Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Sun, 5 Jul 2026 17:41:28 -0500 Subject: [PATCH 4/4] Add Japanese OCR tests, furigana removal, and line order Comprehensive Japanese OCR test coverage with a new furigana-annotated image. Added geometry-based line ordering and furigana line filtering to OcrUtilities, with opt-in removal via settings. Updated test project to include the new image and validated all new behaviors with unit tests. --- Tests/Images/Ja-Lang-Image.png | Bin 0 -> 61930 bytes Tests/OcrTests.cs | 242 +++++++++++++++++++++++++++- Tests/Tests.csproj | 3 + Text-Grab/Utilities/OcrUtilities.cs | 127 ++++++++++++++- 4 files changed, 368 insertions(+), 4 deletions(-) create mode 100644 Tests/Images/Ja-Lang-Image.png diff --git a/Tests/Images/Ja-Lang-Image.png b/Tests/Images/Ja-Lang-Image.png new file mode 100644 index 0000000000000000000000000000000000000000..3d1c6ae9653280e777c2b8f5df1316643e0af669 GIT binary patch literal 61930 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 ZbXoMl5L+X>2sq#&C#@`1CSe-ze*n_1{wV+e literal 0 HcmV?d00001 diff --git a/Tests/OcrTests.cs b/Tests/OcrTests.cs index cabc960b..c34afb0e 100644 --- a/Tests/OcrTests.cs +++ b/Tests/OcrTests.cs @@ -7,6 +7,7 @@ using Text_Grab; using Text_Grab.Interfaces; using Text_Grab.Models; +using Text_Grab.Properties; using Text_Grab.Utilities; using Windows.Globalization; @@ -90,6 +91,18 @@ BANK CHARGES 344 670 326 49% TOTAL EXPENDITURES 1,016,684 2,065,620 1,048,936 51% REVENUES OVERY(UNDER) EXPENDITURES $9,749 $0 $9,749 N/A"; + private const string JaTestExpectedResult = @"""くろ からだ しつ +黒ごまは体にいいです。タンバク質やカルシウムが +かみ くろ こうか +たくさんあります。髪を黒くする効果もあります。 +くろ あぶら はだ かみ りようり +黒ごま油は肌や髪に使います。料理にも使います。 +かゆ た からだ +お粥やデサートに入れます。でも、食べすきると体 +た +によくないです。少しすつ食べましよう。 +"""; + [WpfFact] public async Task OcrFontSampleImage() { @@ -268,6 +281,135 @@ public void GroupWrappedParagraphLines_CombinesWrappedLinesIntoParagraphBlocks() Assert.Equal("New paragraph.", groups[1].SingleLineText); } + private const string jaTestPath = @".\Images\Ja-Lang-Image.png"; + + // The reading-order-corrected OCR output for Ja-Lang-Image.png. Furigana ruby + // lines are still present inline (they are kept per the current line-ordering + // fix), but every line now appears in top-to-bottom / left-to-right reading + // order instead of the scrambled order the Windows OCR engine returns. + // + // JaTestExpectedResult (above) is the aspirational, fully-corrected target: + // furigana grouped per row with full-width spaces AND engine misreads fixed + // (からだ vs からた, こうか vs カ, ...). Reaching it needs more than ordering: + // furigana row grouping plus OCR error correction that recovers dakuten and + // small-kana the engine drops. This constant captures what is achievable today. + private const string JaReadingOrderResult = + "くろからたしつ黒ごまは体にいいです。タンバク質やカルシウムがかみ彡ろカたくさんあります。髪を黒くする効果もあります。くろあぶらはだかみりようり黒ごま油は肌や髪に使います。料理にも使います。かゆたからだお粥やデサ ー トに入れます。でも、食べすき、ると体たによくないです。少しすっ食べましよう。"; + + // With furigana removal enabled, the ruby-reading lines are dropped and only + // the main body text remains (still subject to the engine's own misreads and + // one stray mis-detected fragment "み彡" the geometry heuristic cannot catch). + private const string JaFuriganaRemovedResult = + "黒ごまは体にいいです。タンバク質やカルシウムがみ彡たくさんあります。髪を黒くする効果もあります。黒ごま油は肌や髪に使います。料理にも使います。お粥やデサ ー トに入れます。でも、食べすき、ると体によくないです。少しすっ食べましよう。"; + + [WpfFact] + public async Task OcrJapaneseImage_ReadingOrder_KeepsFuriganaWhenDisabled() + { + // Given + GlobalLang japanese = new("ja"); + + // Skip if the Japanese OCR language pack is not installed on this machine. + if (!Windows.Media.Ocr.OcrEngine.IsLanguageSupported(japanese.OriginalLanguage)) + return; + + Settings settings = AppUtilities.TextGrabSettings; + bool originalRemoveFurigana = settings.RemoveFurigana; + settings.RemoveFurigana = false; + + try + { + // When + string ocrTextResult = await OcrUtilities.OcrAbsoluteFilePathAsync( + FileUtilities.GetPathToLocalFile(jaTestPath), japanese); + + // Then furigana are kept, but every line is in natural reading order + // (top-to-bottom, left-to-right). + Assert.Equal(JaReadingOrderResult, ocrTextResult); + } + finally + { + settings.RemoveFurigana = originalRemoveFurigana; + } + } + + [WpfFact] + public async Task OcrJapaneseImage_RemovesFuriganaWhenEnabled() + { + // Given + GlobalLang japanese = new("ja"); + + if (!Windows.Media.Ocr.OcrEngine.IsLanguageSupported(japanese.OriginalLanguage)) + return; + + Settings settings = AppUtilities.TextGrabSettings; + bool originalRemoveFurigana = settings.RemoveFurigana; + settings.RemoveFurigana = true; + + try + { + // When + string ocrTextResult = await OcrUtilities.OcrAbsoluteFilePathAsync( + FileUtilities.GetPathToLocalFile(jaTestPath), japanese); + + // Then the furigana ruby lines are dropped, leaving the main text. + Assert.Equal(JaFuriganaRemovedResult, ocrTextResult); + } + finally + { + settings.RemoveFurigana = originalRemoveFurigana; + } + } + + [WpfFact] + public async Task InspectJapaneseOcrOutput() + { + // Exploration harness: dumps the raw OCR lines/words with their bounding + // boxes so we can see exactly what the Windows OCR engine returns for a + // furigana-heavy Japanese image, and how the current pipeline processes it. + GlobalLang japanese = new("ja"); + + if (!Windows.Media.Ocr.OcrEngine.IsLanguageSupported(japanese.OriginalLanguage)) + return; + + Bitmap testBitmap = new(FileUtilities.GetPathToLocalFile(jaTestPath)); + double scale = await OcrUtilities.GetIdealScaleFactorForOcrAsync(testBitmap, japanese); + Bitmap scaledBitmap = ImageMethods.ScaleBitmapUniform(testBitmap, scale); + IOcrLinesWords ocrResult = await OcrUtilities.GetOcrResultFromImageAsync(scaledBitmap, japanese); + + StringBuilder report = new(); + report.AppendLine($"scale factor: {scale:0.###}"); + report.AppendLine($"line count: {ocrResult.Lines.Length}"); + report.AppendLine(); + + for (int i = 0; i < ocrResult.Lines.Length; i++) + { + IOcrLine line = ocrResult.Lines[i]; + Windows.Foundation.Rect lb = line.BoundingBox; + report.AppendLine( + $"LINE {i,2} Y={lb.Y,7:0.0} H={lb.Height,6:0.0} X={lb.X,7:0.0} W={lb.Width,7:0.0} \"{line.Text}\""); + foreach (IOcrWord w in line.Words) + { + Windows.Foundation.Rect wb = w.BoundingBox; + report.AppendLine( + $" word Y={wb.Y,7:0.0} H={wb.Height,6:0.0} X={wb.X,7:0.0} W={wb.Width,7:0.0} \"{w.Text}\""); + } + } + + report.AppendLine(); + report.AppendLine("=== reading-flow ordered lines ==="); + foreach (IOcrLine line in OcrUtilities.OrderLinesForReadingFlow(ocrResult.Lines)) + report.AppendLine($" Y={line.BoundingBox.Y,7:0.0} X={line.BoundingBox.X,7:0.0} \"{line.Text}\""); + + report.AppendLine(); + report.AppendLine("=== BuildTextFromOcrLines (current pipeline output) ==="); + report.AppendLine(OcrUtilities.BuildTextFromOcrLines(japanese, ocrResult)); + + string outPath = Path.Combine(Path.GetTempPath(), "ja-ocr-report.txt"); + await File.WriteAllTextAsync(outPath, report.ToString(), new UTF8Encoding(true)); + System.Diagnostics.Debug.WriteLine(report.ToString()); + System.Diagnostics.Debug.WriteLine($"Report written to {outPath}"); + } + [WpfFact] public async Task ReadQrCode() { @@ -702,7 +844,7 @@ public void BuildTextFromOcrLines_SpaceJoiningLanguage_DoesNotFilterFurigana() { // For space-joining languages the whole line text is used verbatim, so // the furigana heuristic never runs, even with a tiny word present. - var settings = AppUtilities.TextGrabSettings; + Settings settings = AppUtilities.TextGrabSettings; bool originalParagraphDetection = settings.ParagraphDetection; bool originalCorrectErrors = settings.CorrectErrors; settings.ParagraphDetection = false; @@ -732,6 +874,104 @@ public void BuildTextFromOcrLines_SpaceJoiningLanguage_DoesNotFilterFurigana() } } + [Fact] + public void OrderLinesForReadingFlow_SortsRowsTopToBottomAndLeftToRight() + { + // Mimics the Windows OCR engine returning furigana ruby lines and a + // trailing fragment out of reading order (as seen with Ja-Lang-Image.png). + // Row 1 (y~0): furigana くろ + main-line reading, emitted out of x-order. + // Row 2 (y~30): the main text line. + FakeOcrLine furiganaRight = new("しつ", new Windows.Foundation.Rect(200, 0, 20, 8)); + FakeOcrLine furiganaLeft = new("くろ", new Windows.Foundation.Rect(0, 0, 20, 8)); + FakeOcrLine mainLine = new("黒ごま質", new Windows.Foundation.Rect(0, 30, 240, 20)); + + // Engine order is scrambled: right furigana, main line, then left furigana. + FakeOcrLinesWords ocrResult = new() + { + Lines = [furiganaRight, mainLine, furiganaLeft] + }; + + IReadOnlyList ordered = OcrUtilities.OrderLinesForReadingFlow(ocrResult.Lines); + + Assert.Equal(["くろ", "しつ", "黒ごま質"], ordered.Select(l => l.Text)); + } + + [Fact] + public void OrderLinesForReadingFlow_KeepsSeparateRowsInVerticalOrder() + { + // Two furigana rows and two main-text rows interleaved and shuffled must + // come back strictly top-to-bottom. + FakeOcrLine ruby2 = new("かみ", new Windows.Foundation.Rect(0, 100, 20, 8)); + FakeOcrLine main2 = new("髪", new Windows.Foundation.Rect(0, 130, 40, 20)); + FakeOcrLine ruby1 = new("くろ", new Windows.Foundation.Rect(0, 0, 20, 8)); + FakeOcrLine main1 = new("黒", new Windows.Foundation.Rect(0, 30, 40, 20)); + + FakeOcrLinesWords ocrResult = new() { Lines = [main2, ruby1, main1, ruby2] }; + + IReadOnlyList ordered = OcrUtilities.OrderLinesForReadingFlow(ocrResult.Lines); + + Assert.Equal(["くろ", "黒", "かみ", "髪"], ordered.Select(l => l.Text)); + } + + [Fact] + public void FilterFuriganaLines_RemovesShortLineAboveTallerOverlappingLine() + { + // A short furigana line sitting just above a taller kanji line that it + // overlaps horizontally is dropped. + FakeOcrLine furigana = new("くろ", new Windows.Foundation.Rect(0, 0, 40, 8)); + FakeOcrLine mainLine = new("黒ごま", new Windows.Foundation.Rect(0, 10, 120, 20)); + + FakeOcrLinesWords ocrResult = new() { Lines = [furigana, mainLine] }; + + IReadOnlyList result = OcrUtilities.FilterFuriganaLines(ocrResult.Lines); + + Assert.Equal(["黒ごま"], result.Select(l => l.Text)); + } + + [Fact] + public void FilterFuriganaLines_KeepsTwoBodyLinesOfSimilarHeight() + { + // Two normal body lines stacked vertically: neither is much shorter than + // the other, so nothing is treated as furigana. + FakeOcrLine top = new("黒ごまは体に", new Windows.Foundation.Rect(0, 0, 200, 20)); + FakeOcrLine bottom = new("たくさんあります", new Windows.Foundation.Rect(0, 26, 200, 20)); + + FakeOcrLinesWords ocrResult = new() { Lines = [top, bottom] }; + + IReadOnlyList result = OcrUtilities.FilterFuriganaLines(ocrResult.Lines); + + Assert.Equal(["黒ごまは体に", "たくさんあります"], result.Select(l => l.Text)); + } + + [Fact] + public void FilterFuriganaLines_KeepsShortLineNotHorizontallyOverlappingAnyKanji() + { + // A short line off to the side (no taller line beneath it) is real text. + FakeOcrLine shortSide = new("注", new Windows.Foundation.Rect(300, 0, 20, 8)); + FakeOcrLine mainLine = new("黒ごま", new Windows.Foundation.Rect(0, 10, 120, 20)); + + FakeOcrLinesWords ocrResult = new() { Lines = [shortSide, mainLine] }; + + IReadOnlyList result = OcrUtilities.FilterFuriganaLines(ocrResult.Lines); + + Assert.Equal(["注", "黒ごま"], result.Select(l => l.Text)); + } + + [Fact] + public void FilterFuriganaLines_KeepsShortLineWhenGapIsTooLarge() + { + // Short line far above a taller line is a separate heading/body line, not + // a hugging ruby annotation, so it is kept. + FakeOcrLine shortHeading = new("メモ", new Windows.Foundation.Rect(0, 0, 40, 8)); + FakeOcrLine mainLine = new("黒ごま", new Windows.Foundation.Rect(0, 60, 120, 20)); + + FakeOcrLinesWords ocrResult = new() { Lines = [shortHeading, mainLine] }; + + IReadOnlyList result = OcrUtilities.FilterFuriganaLines(ocrResult.Lines); + + Assert.Equal(["メモ", "黒ごま"], result.Select(l => l.Text)); + } + private static FakeOcrWord Word(string text, double x, double y, double width, double height) => new(text, new Windows.Foundation.Rect(x, y, width, height)); diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 19ae399f..800d7bab 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -58,6 +58,9 @@ PreserveNewest + + PreserveNewest + diff --git a/Text-Grab/Utilities/OcrUtilities.cs b/Text-Grab/Utilities/OcrUtilities.cs index 8442c52b..3976c137 100644 --- a/Text-Grab/Utilities/OcrUtilities.cs +++ b/Text-Grab/Utilities/OcrUtilities.cs @@ -70,8 +70,11 @@ public static void GetTextFromOcrLine(this IOcrLine ocrLine, bool isSpaceJoining else { // For CJK languages, filter out likely furigana (small ruby-text - // characters above the main text) before merging the words. - IEnumerable words = FilterFurigana([.. ocrLine.Words]); + // characters above the main text) before merging the words. This is + // opt-in via the RemoveFurigana setting. + IEnumerable words = DefaultSettings.RemoveFurigana + ? FilterFurigana([.. ocrLine.Words]) + : ocrLine.Words; bool isFirstWord = true; bool isPrevWordSpaceJoining = false; @@ -598,7 +601,23 @@ .. GroupWrappedParagraphLines( } else { - foreach (IOcrLine ocrLine in lines) + // Windows OCR returns CJK lines - especially furigana ruby lines and + // stray fragments - in an order that does not follow the page's reading + // flow, so re-sort by geometry (top-to-bottom, then left-to-right) + // before joining. Space-joining languages keep the engine order because + // paragraph detection above already handles their layout. + IReadOnlyList orderedLines = isSpaceJoiningOCRLang + ? lines + : OrderLinesForReadingFlow(lines); + + // Windows OCR emits furigana (Japanese ruby readings) as their own + // short lines sitting directly above the kanji they annotate, so the + // word-level filter above never catches them. Drop those whole lines + // when furigana removal is enabled. + if (!isSpaceJoiningOCRLang && DefaultSettings.RemoveFurigana) + orderedLines = FilterFuriganaLines(orderedLines); + + foreach (IOcrLine ocrLine in orderedLines) ocrLine.GetTextFromOcrLine(isSpaceJoiningOCRLang, text); } @@ -608,6 +627,108 @@ .. GroupWrappedParagraphLines( return text.ToString(); } + /// + /// Re-orders OCR lines into natural reading flow: groups lines that share a + /// horizontal row (their vertical extents overlap), orders rows top-to-bottom, + /// and orders the lines within each row left-to-right. Windows OCR frequently + /// returns CJK lines out of order (furigana above kanji, trailing fragments), + /// which scrambles the concatenated text without this pass. + /// + internal static IReadOnlyList OrderLinesForReadingFlow(IReadOnlyList lines) + { + if (lines.Count <= 1) + return lines; + + // Stable sort by the top edge so rows are discovered top-to-bottom. + List byTop = [.. lines.OrderBy(line => line.BoundingBox.Top)]; + + List> rows = []; + double currentRowTop = 0; + double currentRowBottom = 0; + + foreach (IOcrLine line in byTop) + { + Windows.Foundation.Rect box = line.BoundingBox; + + if (rows.Count > 0) + { + double overlap = Math.Min(currentRowBottom, box.Bottom) - Math.Max(currentRowTop, box.Top); + double minHeight = Math.Min(currentRowBottom - currentRowTop, box.Height); + + // A line joins the current row when it overlaps the row's vertical + // band by more than half of the shorter of the two heights. + if (minHeight > 0 && overlap > minHeight * 0.5) + { + rows[^1].Add(line); + currentRowTop = Math.Min(currentRowTop, box.Top); + currentRowBottom = Math.Max(currentRowBottom, box.Bottom); + continue; + } + } + + rows.Add([line]); + currentRowTop = box.Top; + currentRowBottom = box.Bottom; + } + + List ordered = []; + foreach (List row in rows) + ordered.AddRange(row.OrderBy(line => line.BoundingBox.Left)); + + return ordered; + } + + /// + /// Removes whole OCR lines that are likely furigana: short ruby-reading lines + /// that sit directly above a substantially taller line overlapping them + /// horizontally (the kanji they annotate). Windows OCR returns furigana as + /// their own lines, so this complements the word-level . + /// The heuristic is intentionally conservative and geometry-only; it can miss + /// mis-detected readings and is offered as an opt-in, experimental setting. + /// + internal static IReadOnlyList FilterFuriganaLines(IReadOnlyList lines) + { + if (lines.Count < 2) + return lines; + + List kept = []; + + for (int i = 0; i < lines.Count; i++) + { + Windows.Foundation.Rect box = lines[i].BoundingBox; + bool isFurigana = false; + + for (int j = 0; j < lines.Count; j++) + { + if (i == j) + continue; + + Windows.Foundation.Rect other = lines[j].BoundingBox; + + bool isBelow = other.Top >= box.Bottom; + bool overlapsHorizontally = !(other.Right < box.Left || other.Left > box.Right); + // The annotated kanji is markedly taller than its reading. + bool isSubstantiallyTaller = other.Height > box.Height * 1.4; + // Ruby text hugs the top of its character; a large vertical gap + // means these are separate lines of body text, not a reading. + bool isCloseAbove = other.Top - box.Bottom < box.Height; + + if (isBelow && overlapsHorizontally && isSubstantiallyTaller && isCloseAbove) + { + isFurigana = true; + break; + } + } + + if (!isFurigana) + kept.Add(lines[i]); + } + + // Never drop everything - fall back to the input if the heuristic would + // erase the whole result. + return kept.Count > 0 ? kept : lines; + } + internal static bool ShouldUseParagraphDetection(bool isSpaceJoiningLanguage, bool isTableMode = false) { return DefaultSettings.ParagraphDetection && isSpaceJoiningLanguage && !isTableMode;