Skip to content
Open

4.16 #689

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
148 commits
Select commit Hold shift + click to select a range
6c5b6fd
Add Whisper.net and NAudio dependencies for audio transcription
TheJoeFin Jul 10, 2026
f1d51ae
Add AudioTranscriptionModel setting
TheJoeFin Jul 10, 2026
220f72a
Add local Whisper audio transcription utilities
TheJoeFin Jul 10, 2026
f2e6105
Wire audio transcription into EditTextWindow
TheJoeFin Jul 10, 2026
8e25160
Add Whisper.net and NAudio dependencies for audio transcription
TheJoeFin Jul 10, 2026
56c89b5
Add AudioTranscriptionModel setting
TheJoeFin Jul 10, 2026
7832089
Add local Whisper audio transcription utilities
TheJoeFin Jul 10, 2026
d8001df
Wire audio transcription into EditTextWindow
TheJoeFin Jul 10, 2026
bc3cbe0
rebase onto main
TheJoeFin Aug 20, 2026
afa2d98
update styles and fix old dropdown
TheJoeFin Aug 20, 2026
7b8b408
update packages and fix API change
TheJoeFin Aug 20, 2026
b1f8772
Add user setting and UI for audio transcription toggle
TheJoeFin Aug 20, 2026
8d90ac9
Remove duplicate Windows App SDK package references
TheJoeFin Aug 20, 2026
95d3b77
Migrate test project to Microsoft.Testing.Platform
TheJoeFin Aug 20, 2026
4bfef08
Use dotnet test --project in CI workflows
TheJoeFin Aug 20, 2026
dfb5ed5
Add simultaneous mic+system audio capture for live transcription
TheJoeFin Aug 22, 2026
900720e
Refactor translation to use WinAiTranslator utility
TheJoeFin Aug 22, 2026
78961b5
Add secure LAF token injection for builds and docs
TheJoeFin Aug 22, 2026
1274b35
Refactor: centralize Windows AI model usage & errors
TheJoeFin Aug 22, 2026
d56bebe
Add AI-powered meeting notes summarization feature
TheJoeFin Aug 22, 2026
a834325
Add robust recovery for dropped Windows AI connections
TheJoeFin Aug 22, 2026
10dcb61
Add Ctrl+N hotkey to open selection in a new Edit Text window
TheJoeFin Aug 23, 2026
2754eca
Open Summarize and Meeting Notes results in a new window
TheJoeFin Aug 23, 2026
9a8e12d
Merge pull request #679 from TheJoeFin/LAF-Translate
TheJoeFin Aug 23, 2026
3fb3cdb
Add hot-words and file-info helpers to AudioTranscriptionUtilities
TheJoeFin Aug 23, 2026
058afa7
Add picker mode to QuickSimpleLookup
TheJoeFin Aug 23, 2026
b6f1c13
Add "Open Audio / Video..." window with hot words support
TheJoeFin Aug 23, 2026
141da31
Fix QSL CSV parsing dropping commas in long values
TheJoeFin Aug 23, 2026
120c6b5
Add toast notification when audio transcription completes
TheJoeFin Aug 23, 2026
f85954f
Add option to include timecodes in transcription output
TheJoeFin Aug 23, 2026
610c26d
Cache Grab Templates in memory to avoid disk I/O on every menu open
TheJoeFin Aug 23, 2026
bf89591
Load Grab Template menu items only when that submenu opens
TheJoeFin Aug 23, 2026
aa83e44
Reuse cached language list for capture menus instead of re-probing
TheJoeFin Aug 23, 2026
53c3595
Add Select-Black.ico and Select-White.ico to project
TheJoeFin Aug 23, 2026
94f1c52
Add user setting TrayIconStyle with default "Color"
TheJoeFin Aug 23, 2026
06ce22e
Support tray icon style refresh and new style enum
TheJoeFin Aug 23, 2026
8042e36
Add user setting for tray icon style (color/monochrome)
TheJoeFin Aug 23, 2026
135f770
Add "Just Icon" option to transcribe button context menu
TheJoeFin Aug 23, 2026
6b995a4
Add determinate progress bars for audio transcription
TheJoeFin Aug 24, 2026
ec5fb89
Update WindowsAppSDK and PdfPig NuGet packages
TheJoeFin Aug 24, 2026
05ddd50
Move the audio debug log to LocalAppData and cap its size
TheJoeFin Aug 24, 2026
2317a6a
Keep the Whisper factory alive while transcriptions still use it
TheJoeFin Aug 24, 2026
d8bd88a
Fix transcription editor sync, CTS leak, and late error dialog
TheJoeFin Aug 24, 2026
05e911b
Report summarize failures instead of opening them as a summary
TheJoeFin Aug 24, 2026
f435260
Release the shared language model under its lock
TheJoeFin Aug 24, 2026
a3bf1e6
Only show the post-grab translation dialog on real failures
TheJoeFin Aug 24, 2026
02ac34d
Stop GrabFrame translation from running re-entrantly
TheJoeFin Aug 24, 2026
1f3dd5c
Return copies from the grab template cache, and guard it
TheJoeFin Aug 24, 2026
58749ba
Show media durations longer than an hour correctly
TheJoeFin Aug 24, 2026
415bf22
Merge pull request #677 from TheJoeFin/audio
TheJoeFin Aug 24, 2026
88852c6
Add a Clean Up Text action for messy pasted text
TheJoeFin Aug 24, 2026
85dd682
Add CF_HTML table clipboard support and tests
TheJoeFin Aug 24, 2026
7c91712
Fix Find & Replace navigation selecting wrong text in markdown mode
TheJoeFin Aug 24, 2026
3d71294
code style and using markdown options from VS Markdown extension
TheJoeFin Aug 24, 2026
32e0c3e
Refactor audio capture, improve test cancellation
TheJoeFin Aug 24, 2026
fd31a5a
Add transcription model picker with details to Open Audio/Video window
TheJoeFin Aug 24, 2026
1e6f444
Fix runaway transcription garbage on long audio files
TheJoeFin Aug 24, 2026
cb6f344
Clicking a transcription-complete toast reactivates its window
TheJoeFin Aug 24, 2026
4315fa4
Fix transcription toast activation missing the app-already-running path
TheJoeFin Aug 24, 2026
d6dbd51
Add Local AI completion notification and new-window result option
TheJoeFin Aug 25, 2026
e47e1f0
Switch to spreadsheet mode after a successful Convert to Table
TheJoeFin Aug 25, 2026
0cc6e3d
Add camera capture option to EditTextWindow and GrabFrame
TheJoeFin Aug 25, 2026
288f6d1
Scaffold Text-Grab.Core, Text-Grab.Core.Windows, and Tests.Core projects
TheJoeFin Aug 27, 2026
c48db75
Move StringMethods.cs into Text-Grab.Core
TheJoeFin Aug 27, 2026
0fb1265
Move CharacterUtilities.cs and TextSearchUtilities.cs into Text-Grab.…
TheJoeFin Aug 27, 2026
700cab2
Move RecognizerExecutor.cs and BuiltInRecognizer.cs into Text-Grab.Core
TheJoeFin Aug 27, 2026
e677b54
Move StoredRegex.cs and split PatternItem.cs into Text-Grab.Core
TheJoeFin Aug 27, 2026
c110cd1
Move ILanguage.cs and OcrLinesWords.cs into Text-Grab.Core.Windows
TheJoeFin Aug 27, 2026
edefeaa
Move the OCR engine implementations into Text-Grab.Core.Windows
TheJoeFin Aug 27, 2026
30af90f
Establish RectangleF as the Core tier's geometry currency
TheJoeFin Aug 27, 2026
8398af2
Give Core a settings seam via ITextGrabSettings
TheJoeFin Aug 27, 2026
d7edfca
Scaffold Tests.Core.Windows, guard the tier boundaries, wire CI
TheJoeFin Aug 27, 2026
cd24749
Rebuild the Core split plan from five reconnaissance passes
TheJoeFin Aug 27, 2026
d251324
Move Singleton, StreamWrapper, NullAsyncResult and Json into Text-Gra…
TheJoeFin Aug 27, 2026
bf171f7
Extract packaging identity and move StorageFileExtensions into Text-G…
TheJoeFin Aug 27, 2026
9c4d2e5
Read the app version from the entry assembly, not the executing one
TheJoeFin Aug 27, 2026
ce24f0b
Merge Enums.cs into Text-Grab.Core and move GetOpenContentKindForPath
TheJoeFin Aug 27, 2026
35f9b14
Move ten Models files into Text-Grab.Core and finish the FileOpenUtil…
TheJoeFin Aug 27, 2026
f6a1cd0
Move seven Utilities/Extensions/Interfaces files into Core, split two…
TheJoeFin Aug 27, 2026
f05be11
Convert WordBorderInfo/TemplateRegion to RectangleF, move both plus G…
TheJoeFin Aug 27, 2026
147ece4
Move CalculationService into Text-Grab.Core
TheJoeFin Aug 27, 2026
0000bc6
Split MarkdownDocumentUtilities: pure AST/regex helpers into Core
TheJoeFin Aug 27, 2026
7e539c0
Move eight zero-blocker files into Text-Grab.Core.Windows
TheJoeFin Aug 27, 2026
966a497
Move SettingsStorageExtensions and LimitedAccessFeatureUtilities into…
TheJoeFin Aug 27, 2026
4711c5e
Move the Windows AI chain into Text-Grab.Core.Windows
TheJoeFin Aug 27, 2026
dff9aa4
Split Extensions/LanguageExtensions.cs into Core.Windows and an app-s…
TheJoeFin Aug 27, 2026
90af6b8
Record the RTL blocker batch 3d created for 4c
TheJoeFin Aug 27, 2026
93c1729
Move the automation settings provider and profile into Text-Grab.Core
TheJoeFin Aug 27, 2026
0d3b485
Move OcrOutput and BarcodeUtilities into Text-Grab.Core.Windows
TheJoeFin Aug 30, 2026
cc9c581
Move TesseractHelper into Text-Grab.Core.Windows, split out the downl…
TheJoeFin Aug 30, 2026
bd84507
Delete dead ResultTable code, move the clustering algorithm to Core
TheJoeFin Aug 30, 2026
3b0bf02
Split OcrUtilities, move the OCR text assembly into Core.Windows
TheJoeFin Aug 30, 2026
9ef3171
Move HDR tone-mapping and imaging leaves into Core/Core.Windows
TheJoeFin Aug 30, 2026
bbf601b
Split ImageMethods.cs, move the headless bitmap helpers to Core.Windows
TheJoeFin Aug 30, 2026
60c7b21
Split ClipboardUtilities.cs, move the CF_HTML table parser to plain Core
TheJoeFin Aug 30, 2026
bb5484c
Split FreeformCaptureUtilities.cs, move CreateMaskedBitmap to Core.Wi…
TheJoeFin Aug 30, 2026
5ad4968
Drop the dead ToSourceAsync and the WinUI package it pulled in
TheJoeFin Aug 30, 2026
f156b63
Move HdrScreenCapture into Core.Windows behind a UI-thread hook
TheJoeFin Aug 30, 2026
f103294
Move the Windows AI and language chain into Core.Windows
TheJoeFin Aug 30, 2026
08d6a8b
Move the text-to-speech chain into Text-Grab.Core / Core.Windows
TheJoeFin Aug 30, 2026
cd9a7c1
Move AudioTranscriptionUtilities into Text-Grab.Core.Windows wholesale
TheJoeFin Aug 30, 2026
baa98f0
Split WebSearchUrlModel into a pure record and an app-side catalog
TheJoeFin Aug 30, 2026
3952d92
Deferred-ledger sweep: move FileUtilities/ContextMenuUtilities, prune…
TheJoeFin Aug 30, 2026
a8591aa
Move HistoryInfo to Core.Windows, retiring its System.Windows.Rect
TheJoeFin Aug 30, 2026
212234a
6e: split the history persistence pipeline out of HistoryService
TheJoeFin Aug 30, 2026
9f78694
Plan: record 6e and the HistoryInfo move; reopen two section 7 rows
TheJoeFin Aug 30, 2026
a3643e6
7a: move seven pure model/utility test suites to Tests.Core
TheJoeFin Aug 30, 2026
873ea58
7a: move five headless-Windows test suites to Tests.Core.Windows
TheJoeFin Aug 30, 2026
5585199
7a: split five test suites across the tier boundary
TheJoeFin Aug 30, 2026
fe4fd59
7a: delete ScaffoldingSmokeTests, its job is done
TheJoeFin Aug 30, 2026
695671f
7a follow-up: restore CRLF and attribute spacing
TheJoeFin Aug 30, 2026
6497dd3
7b: remove the dead Table-Complex.png content item
TheJoeFin Aug 30, 2026
3f4b222
7b: move GrabFrameFileUtilities and FileAssociationUtilities to Core.…
TheJoeFin Aug 30, 2026
688f100
Plan: re-derive the deferred ledger and never-move list against B2
TheJoeFin Aug 30, 2026
0e43f83
Plan: record 7a's test-placement decisions and add the final layer map
TheJoeFin Aug 30, 2026
1f0d921
7a fix: map Tests.Core.Windows to its real solution platforms
TheJoeFin Aug 30, 2026
49df54d
Merge pull request #681 from TheJoeFin/proj-structure
TheJoeFin Sep 1, 2026
3d25518
Add a brand context page for doing better marketing
TheJoeFin Sep 2, 2026
3e12c7a
Improve resource disposal, error handling, and add LLM restart UI
TheJoeFin Sep 4, 2026
588cbc4
Improve PDF paragraph detection and add test PDF asset
TheJoeFin Sep 4, 2026
110be98
Auto-select Table mode when a new grab targets a Spreadsheet-mode ETW
TheJoeFin Sep 5, 2026
89c4b42
Route OCR grab results in Spreadsheet mode through the structured tab…
TheJoeFin Sep 5, 2026
de65583
Fix table OCR gluing same-row columns together with no separator
TheJoeFin Sep 5, 2026
104504b
Enable table mode for text-based PDFs (fixes #684)
TheJoeFin Sep 5, 2026
ee8623f
Add draggable corner handles to reposition the table boundary
TheJoeFin Sep 5, 2026
f718a75
Cap ETW recent-item menus to 10 entries each
TheJoeFin Sep 5, 2026
94f2c61
Confirm before an automatic refresh discards unsaved Grab Frame edits
TheJoeFin Sep 5, 2026
337ffea
Bubble word text edits up to the Grab Frame as unsaved changes
TheJoeFin Sep 5, 2026
d81e839
Updated packages
TheJoeFin Sep 5, 2026
8f8af09
Tidy GrabFrame formatting
TheJoeFin Sep 14, 2026
9c2f6ed
Keep the dragged table boundary across refreshes and PDF pages
TheJoeFin Sep 14, 2026
018a415
Add "Grab multiple pages" to the Grab Frame Grab button
TheJoeFin Sep 14, 2026
02c9937
Ignore repeated headers and footers when grabbing multiple pages
TheJoeFin Sep 14, 2026
e0ebdf5
Prevent Local AI actions when output is unchanged
TheJoeFin Sep 14, 2026
f2e905c
Coordinate Windows AI model lifetime
TheJoeFin Sep 19, 2026
84801ae
Preserve final live transcription
TheJoeFin Sep 19, 2026
faebd54
Deliver aggregate Local AI results once
TheJoeFin Sep 19, 2026
f57dc52
Restore mixed PDF table OCR
TheJoeFin Sep 19, 2026
b4d437f
Update packages
TheJoeFin Sep 19, 2026
0475413
bump version to 4.16.0
TheJoeFin Sep 19, 2026
8b7529e
Pass CancellationToken to WaitAsync in tests
TheJoeFin Sep 19, 2026
228ae6b
Improve unpackaged release build workflow
TheJoeFin Sep 19, 2026
849f76e
don't make transcribe on by default
TheJoeFin Sep 22, 2026
e22339f
Make EditTextWindow commands spreadsheet-aware
TheJoeFin Sep 22, 2026
227aa8e
Add Models settings page for managing local Whisper models
TheJoeFin Sep 22, 2026
f045deb
Add medium/large Whisper models and separate live vs file model selec…
TheJoeFin Sep 22, 2026
be22b83
Simplify null-conditional event/property cleanup in two controls
TheJoeFin Sep 22, 2026
2ba66e7
Release the shared Whisper model when live/file transcription ends
TheJoeFin Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .claude/brand-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Brand context for Text Grab

---

## Product

What it is, in one sentence a stranger would understand: Text Grab is a Windows app to copy any text you can see or hear, then: edit, clean, save, recalled, and automate all of your text heavy tasks with clever functions and local AI!

What it actually does (the mechanism, not the promise): Uses 100% local models to do Optical Character Recognition (OCR) and transcription. Many build in functions to automate tedious tasks, as well as using local LLMs for other text actions like translation, summarization, and more!

What it does NOT do (this prevents overclaiming): Text Grab does not ship OCR models they are all built into Windows, or 3rd party.

## Audience

Who buys it: Power users, information workers, developers, designers, engineers, technical folks

What they believe before they arrive: working with text is tedious and annoying, usually avoided as much as possible, manually transcribing text from an image or audio is annoying and there must be a better way.

What they worry about at 2am: They are wasting their mind doing tedious data manipulation or looking up snippets, minor formatting, etc. and changing their workflows or relying on others more than they need to.

What they'd use instead if you didn't exist: Doing the work manually, not being as confident, using tools like Excel, or Google Sheets and VS Code, maybe AI agents with some light scripting.

## Positioning

The one thing true about us that a competitor could not also say: Laser focused on making you more productive with text, from getting the text in to transformations. Native first; local first, always offline: built with WPF and only using local models.

Category we compete in:

Named competitors: Windows Snipping Tool, PowerToys Text Extractor, LightPDF, OCRSpace, FreeOCR, OnlineOCR, Simple OCR, Adobe Acrobat Pro DC, PDFelement, Easy Screen OCR, Boxoft Free OCR, ABBYY FineReader, Nanonets, Free OCR to Word, Nanonets, Filestack

## Proof

Numbers we can cite (with source and date): as of September 1st: over 160k downloads on GitHub, and nearly 5k stars, over 12k downloads from the Microsoft Store

Named customers we're allowed to name: Windows Snipping Tool, PowerToys Text Extractor, ABBYY FineReader, Adobe Acrobat Pro DC

Claims that need legal sign-off: none

## Voice

How we sound: Excited to save users time, to save them from tedium, to make them more productive. skeptical of cloud services that you never know what is going on with your data or when the app will change. Serious about the end to end text value pipeline.

How we never sound: Like we are the best or first doing OCR.

Words we always use: Productive, speed, faster, integrated, end-to-end

Words we never use: Proprietary, —

## Constraints

Regulatory or legal limits: The OCR is not perfect and always needs review

Anything off-limits: Guarantee support
24 changes: 23 additions & 1 deletion .github/workflows/Release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@ env:
PROJECT: 'Text-Grab'
PROJECT_PATH: 'Text-Grab/Text-Grab.csproj'
TEST_PATH: 'Tests/Tests.csproj'
TEST_CORE_PATH: 'Tests.Core/Tests.Core.csproj'
TEST_CORE_WINDOWS_PATH: 'Tests.Core.Windows/Tests.Core.Windows.csproj'
BUILD_X64: 'bld/x64'
BUILD_X64_SC: 'bld/x64/Text-Grab-Self-Contained'
BUILD_ARM64: 'bld/arm64'
BUILD_ARM64_SC: 'bld/arm64/Text-Grab-Self-Contained'
ARTIFACT_SIGNING_ENDPOINT: 'https://eus.codesigning.azure.net/'
ARTIFACT_SIGNING_ACCOUNT_NAME: 'JoeFinAppsSigningCerts'
ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME: 'JoeFinApps'
# Unlock token for the Windows AI language model (a Limited Access Feature). Absent secrets
# leave the properties empty, which just builds without on-device text AI rather than failing.
LAF_TOKEN: ${{ secrets.LAF_TOKEN }}
LAF_PUBLISHER_ID: ${{ secrets.LAF_PUBLISHER_ID }}

jobs:
build:
Expand All @@ -46,8 +52,16 @@ jobs:
- name: Install dependencies
run: dotnet restore ${{ env.PROJECT_PATH }}

# The pure tier runs first: it needs no display and finishes in about a second, so a
# logic regression fails the job before the slow WPF/STA suite starts.
- name: Run Core tests
run: dotnet test --project ${{ env.TEST_CORE_PATH }}

- name: Run Core.Windows tests
run: dotnet test --project ${{ env.TEST_CORE_WINDOWS_PATH }} -r win-x64

- name: Run tests
run: dotnet test ${{ env.TEST_PATH }} -r win-x64
run: dotnet test --project ${{ env.TEST_PATH }} -r win-x64

- name: Compute build version, archive paths, and release metadata
id: compute
Expand Down Expand Up @@ -89,6 +103,8 @@ jobs:
-p:PublishReadyToRun=false
-p:PublishSingleFile=true
-p:CopyOutputSymbolsToPublishDirectory=false
-p:LafToken=$env:LAF_TOKEN
-p:LafPublisherId=$env:LAF_PUBLISHER_ID
--nologo

- name: Build x64 self-contained
Expand All @@ -103,6 +119,8 @@ jobs:
-p:PublishReadyToRun=true
-p:PublishSingleFile=true
-p:CopyOutputSymbolsToPublishDirectory=false
-p:LafToken=$env:LAF_TOKEN
-p:LafPublisherId=$env:LAF_PUBLISHER_ID
--nologo

- name: Build ARM64 framework-dependent
Expand All @@ -116,6 +134,8 @@ jobs:
-p:PublishSingleFile=true
-p:EnableMsixTooling=true
-p:CopyOutputSymbolsToPublishDirectory=false
-p:LafToken=$env:LAF_TOKEN
-p:LafPublisherId=$env:LAF_PUBLISHER_ID
--nologo

- name: Build ARM64 self-contained
Expand All @@ -129,6 +149,8 @@ jobs:
-p:PublishSingleFile=true
-p:EnableMsixTooling=true
-p:CopyOutputSymbolsToPublishDirectory=false
-p:LafToken=$env:LAF_TOKEN
-p:LafPublisherId=$env:LAF_PUBLISHER_ID
--nologo

- name: Rename ARM64 executables
Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/buildDev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ concurrency:
env:
PROJECT_PATH: "Text-Grab/Text-Grab.csproj"
TEST_PATH: "Tests/Tests.csproj"
TEST_CORE_PATH: "Tests.Core/Tests.Core.csproj"
TEST_CORE_WINDOWS_PATH: "Tests.Core.Windows/Tests.Core.Windows.csproj"
# Unlock token for the Windows AI language model (a Limited Access Feature). Absent secrets
# leave the properties empty, which just builds without on-device text AI rather than failing.
LAF_TOKEN: ${{ secrets.LAF_TOKEN }}
LAF_PUBLISHER_ID: ${{ secrets.LAF_PUBLISHER_ID }}

jobs:
build:
Expand All @@ -26,11 +32,17 @@ jobs:
run: dotnet restore ${{ env.PROJECT_PATH }}
- name: Build
run: dotnet build ${{ env.PROJECT_PATH }} -p:EnableMsixTooling=true
# The pure tier runs first: it needs no display and finishes in about a second, so a
# logic regression fails the job before the slow WPF/STA suite starts.
- name: Test Core
run: dotnet test --project ${{ env.TEST_CORE_PATH }}
- name: Test Core.Windows
run: dotnet test --project ${{ env.TEST_CORE_WINDOWS_PATH }} -r win-x64
- name: Test
run: dotnet test ${{ env.TEST_PATH }} -r win-x64
run: dotnet test --project ${{ env.TEST_PATH }} -r win-x64

- name: Build for release and Publish
run: dotnet publish ${{ env.PROJECT_PATH }} -c Release --self-contained -r win-x64 -p:PublishSingleFile=true -p:EnableMsixTooling=true -o publish
run: dotnet publish ${{ env.PROJECT_PATH }} -c Release --self-contained -r win-x64 -p:PublishSingleFile=true -p:EnableMsixTooling=true -p:LafToken=$env:LAF_TOKEN -p:LafPublisherId=$env:LAF_PUBLISHER_ID -o publish

- name: Upload artifact
uses: actions/upload-artifact@v7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
using Text_Grab;
using Text_Grab.Models;
using Text_Grab.Utilities;
using UnitsNet;
using Windows.Storage.Streams;
using static System.Net.Mime.MediaTypeNames;

namespace Tests;
namespace Text_Grab.Tests.Core.Windows;

// Headless half of the original Tests/BarcodeUtilitiesTests.cs (batch 7a). ReadTestSingleQRCode
// stayed behind as Tests/BarcodeUtilitiesImageTests.cs: it is [WpfFact]-tagged, and Xunit.StaFact
// cannot be referenced here (it pulls in WindowsBase, which TierBoundaryTests bans). This half has
// 3 methods against that one's 1, so it kept the original name.
public class BarcodeUtilitiesTests
{
[Fact]
Expand Down Expand Up @@ -47,20 +49,6 @@ public void TryToReadBarcodes_WithTwoQrCodes_ReturnsTwoResults()
Assert.Contains(results, r => r.RawOutput == "https://example.org");
}

[WpfFact]
public void ReadTestSingleQRCode()
{
string expectedOutput = "This is a test of the QR Code system";
string testFilePath = FileUtilities.GetPathToLocalFile(@".\Images\QrCodeTestImage.png");

Bitmap testBmp = new(testFilePath);

List<OcrOutput> result = BarcodeUtilities.TryToReadBarcodes(testBmp);

Assert.Single(result);
Assert.Equal(expectedOutput, result[0].RawOutput);
}

[Fact]
public async Task GetBitmapFromIRandomAccessStream_ReturnsBitmapIndependentOfSourceStream()
{
Expand All @@ -73,7 +61,7 @@ public async Task GetBitmapFromIRandomAccessStream_ReturnsBitmapIndependentOfSou
using InMemoryRandomAccessStream randomAccessStream = new();
_ = await randomAccessStream.WriteAsync(memoryStream.ToArray().AsBuffer());

Bitmap clonedBitmap = ImageMethods.GetBitmapFromIRandomAccessStream(randomAccessStream);
Bitmap clonedBitmap = BitmapUtilities.GetBitmapFromIRandomAccessStream(randomAccessStream);

Assert.Equal(8, clonedBitmap.Width);
Assert.Equal(8, clonedBitmap.Height);
Expand Down
50 changes: 50 additions & 0 deletions Tests.Core.Windows/FakeTextGrabSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Runtime.CompilerServices;
using Text_Grab.Interfaces;
using Text_Grab.Services;

namespace Text_Grab.Tests.Core.Windows;

/// <summary>
/// Registers a <see cref="SettingsAccess"/> resolver for this test host. Tests.Core.Windows has
/// no app assembly to supply one via a <c>[ModuleInitializer]</c> the way Tests does (see
/// SettingsAccess.Current's remarks), so any moved test whose production code path reads settings
/// - here, a handful of OcrTests methods that call into OcrUtilities.BuildTextFromOcrLines, which
/// reads ParagraphDetection/RemoveFurigana/CorrectErrors/CorrectToLatin internally - would throw
/// InvalidOperationException without one installed.
///
/// Every default below is copied from Text-Grab/Properties/Settings.settings's "(Default)"
/// profile, so a moved test that depends on a default value (e.g. RemoveFurigana=true dropping
/// furigana words) sees exactly what it saw running inside the app-hosted Tests project.
/// </summary>
internal static class TestSettingsInitializer
{
[ModuleInitializer]
internal static void Register() => SettingsAccess.SetResolver(() => new FakeTextGrabSettings());
}

/// <summary>Minimal ITextGrabSettings double seeded with Settings.settings's shipped defaults.</summary>
internal sealed class FakeTextGrabSettings : ITextGrabSettings
{
public bool CorrectErrors { get; set; } = true;
public bool CorrectToLatin { get; set; } = true;
public bool OverrideAiArchCheck { get; set; }
public bool ParagraphDetection { get; set; } = true;
public bool RemoveFurigana { get; set; } = true;
public bool TryToReadBarcodes { get; set; } = true;
public bool HdrCaptureCorrection { get; set; }
public bool HdrBorderlessGranted { get; set; }
public bool UiAutomationEnabled { get; set; }
public bool WindowsAiDescriptionEnabled { get; set; }
public bool UiAutomationFallbackToOcr { get; set; } = true;
public bool UseTesseract { get; set; }
public string TesseractPath { get; set; } = string.Empty;
public string LastUsedLang { get; set; } = string.Empty;
public int TtsSpeakWordLimit { get; set; } = 100;
public string TtsVoiceName { get; set; } = string.Empty;
public double TtsSpeakingRate { get; set; } = 1;
public string AudioTranscriptionModel { get; set; } = "BaseMultilingual";
public string LiveTranscriptionModel { get; set; } = "BaseMultilingual";
public bool EnableFileBackedManagedSettings { get; set; }

public void Save() { }
}
36 changes: 36 additions & 0 deletions Tests.Core.Windows/FileUtilitiesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Text_Grab.Utilities;

namespace Text_Grab.Tests.Core.Windows;

// Pure half of the original Tests/FilesIoTests.cs (batch 7a): FileUtilities.GetVisualDocumentFilter
// is Core.Windows-only and needs no app type. The rest of that file needed WPF or app-side members
// and kept the FilesIoTests name in Tests; the IoUtilities-only tests moved separately to
// Tests.Core/IoUtilitiesTests.cs.
public class FileUtilitiesTests
{
[Fact]
public void GetVisualDocumentFilter_IncludesPdfSupport()
{
string filter = FileUtilities.GetVisualDocumentFilter();

Assert.Contains("Image and PDF files|", filter);
Assert.Contains("PDF files|*.pdf", filter);
Assert.Contains("Image files|", filter);
}

// Joined FileUtilities in 7b once GrabFrameFileUtilities followed HistoryInfo to
// Core.Windows and GetOpenDocumentFilter() no longer needed the app-side
// OpenDocumentFilterUtilities split.
[Fact]
public void GetOpenDocumentFilter_IncludesVisualAndTextOptions()
{
string filter = FileUtilities.GetOpenDocumentFilter();

Assert.Contains("Supported documents|", filter);
Assert.Contains("Image and PDF files|", filter);
Assert.Contains("Spreadsheet documents|*.csv;*.tsv;*.tab", filter);
Assert.Contains("Markdown documents|*.md;*.markdown", filter);
Assert.Contains("Text documents (*.txt)|*.txt", filter);
Assert.Contains("All files (*.*)|*.*", filter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
using System.IO;
using System.IO.Compression;
using System.Text.Json;
using System.Windows;
using Text_Grab;
using Text_Grab.Models;
using Text_Grab.Utilities;

namespace Tests;
namespace Text_Grab.Tests.Core.Windows;

// Moved wholesale in 7b: GrabFrameFileUtilities followed HistoryInfo to Core.Windows once the
// HistoryInfo blocker cleared (a8591aa), and every assertion here is against that headless pair
// (GrabFrameFileUtilities, HistoryInfo/WordBorderInfo) with no WPF type in sight - the file had
// an unused `using System.Windows;` from before that move, since dropped.
public class GrabFrameFileTests
{
[Fact]
Expand All @@ -21,13 +24,13 @@ public async Task SaveAndLoad_RoundTripsMetadataWordBordersAndImage()
new()
{
Word = "Hello",
BorderRect = new Rect(1, 2, 30, 12),
BorderRect = new RectangleF(1, 2, 30, 12),
LineNumber = 0,
},
new()
{
Word = "World",
BorderRect = new Rect(35, 2, 32, 12),
BorderRect = new RectangleF(35, 2, 32, 12),
LineNumber = 0,
},
];
Expand All @@ -40,7 +43,7 @@ public async Task SaveAndLoad_RoundTripsMetadataWordBordersAndImage()
IsTable = true,
LanguageTag = "en-US",
LanguageKind = LanguageKind.Global,
PositionRect = new Rect(100, 120, 400, 300),
PositionRect = new RectangleF(100, 120, 400, 300),
WordBorderInfoJson = JsonSerializer.Serialize(wordBorders),
ImageContent = new Bitmap(64, 48),
};
Expand All @@ -60,7 +63,7 @@ public async Task SaveAndLoad_RoundTripsMetadataWordBordersAndImage()
Assert.True(loaded.IsTable);
Assert.Equal("en-US", loaded.LanguageTag);
Assert.Equal(LanguageKind.Global, loaded.LanguageKind);
Assert.Equal(new Rect(100, 120, 400, 300), loaded.PositionRect);
Assert.Equal(new RectangleF(100, 120, 400, 300), loaded.PositionRect);

Assert.NotNull(loaded.ImageContent);
Assert.Equal(64, loaded.ImageContent!.Width);
Expand Down Expand Up @@ -91,7 +94,7 @@ public async Task SaveGrabFrameFileAsync_DoesNotMutateSuppliedInfo()

string originalWordBordersJson = JsonSerializer.Serialize(new List<WordBorderInfo>
{
new() { Word = "Hello", BorderRect = new Rect(1, 2, 30, 12), LineNumber = 0 },
new() { Word = "Hello", BorderRect = new RectangleF(1, 2, 30, 12), LineNumber = 0 },
});
Bitmap originalImage = new(64, 48);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Drawing;
using Text_Grab.Utilities.Hdr;

namespace Tests;
namespace Text_Grab.Tests.Core.Windows;

public class HdrScreenCaptureTests
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Drawing;
using Text_Grab.Utilities;

namespace Tests;
namespace Text_Grab.Tests.Core.Windows;

public class ImageChangeDetectorTests
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Text_Grab;
using Text_Grab.Models;

namespace Tests;
namespace Text_Grab.Tests.Core.Windows;

public class LanguageTests
{
Expand Down
Loading
Loading