// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package ocr import ( "context" "image" "os" "strings" "testing" "time" ) // This is a real installed-engine check, intentionally opt-in. Ordinary tests // and the reader build must work with no tesseract executable or language data. func TestNativeTesseract(t *testing.T) { if os.Getenv("CTHULHU_NATIVE_OCR") != "1" { t.Skip("set CTHULHU_NATIVE_OCR=1 to test installed Tesseract") } engine := NewEngine() catalog, err := engine.Discover(context.Background()) if err != nil { t.Fatal(err) } if language, err := catalog.resolve_language("eng"); err != nil || language != "eng" { t.Fatal("the native painted-text fixture requires installed English data") } file, err := os.Open("testdata/painted-text.png") if err != nil { t.Fatal(err) } defer file.Close() pixels, _, err := image.Decode(file) if err != nil { t.Fatal(err) } for _, layout := range []Layout{AutomaticPage, SingleBlock, SparseText} { start := time.Now() result, err := engine.Recognize(context.Background(), pixels, Options{Language: "eng", Layout: layout}) if err != nil { t.Fatal(err) } // Accept ordinary spacing/case variation between engine/model versions; // this simple fixture is a pipe/coordinate proof, not an accuracy corpus. actual := strings.ToLower(strings.Join(strings.Fields(result.Text), " ")) if actual != "ocr test window open settings value 42" { t.Fatalf("PSM %d did not read the known fixture: %q", layout, result.Text) } if len(result.Lines) != 3 || len(result.Words) != 7 { t.Fatalf("PSM %d lost line/word structure: %+v", layout, result) } for _, word := range result.Words { if !valid_word_bounds(word.Bounds, result.Width, result.Height) || word.Bounds.Min.X < 20 || word.Bounds.Min.Y < 15 || word.Bounds.Max.Y > 185 { t.Fatalf("PSM %d word outside painted area: %+v", layout, word) } } t.Logf("PSM %d: %d words, %d lines, total %s (includes catalog and PNG encoding)", layout, len(result.Words), len(result.Lines), time.Since(start)) } scaled, err := engine.Recognize(context.Background(), pixels, Options{Language: "eng", Layout: SingleBlock, Scale: 2, Grayscale: true}) if err != nil { t.Fatal(err) } if scaled.Width != pixels.Bounds().Dx()*2 || scaled.Height != pixels.Bounds().Dy()*2 || len(scaled.Words) == 0 { t.Fatalf("scaled recognition did not retain image coordinates: %dx%d, %d words", scaled.Width, scaled.Height, len(scaled.Words)) } for _, word := range scaled.Words { if !valid_word_bounds(word.Bounds, scaled.Width, scaled.Height) { t.Fatalf("invalid scaled word: %+v", word) } } }