Fix bugs across app + server, optimize UI/UX, add Gitea CI
Bug fixes (Flutter):
- Wrap multi-statement DB writes (insert/update/delete note, deleteDocument,
deletePageData, OCR FTS merge, migrations) in transactions to prevent data
loss on interruption and a read-modify-write FTS race.
- Fix PdfDocument leaks on exception (try/finally dispose) and preserve image
aspect ratio when stamping images onto PDF pages.
- Guard file-picker against empty selection (was .single -> crash).
- Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF
pages; capture page synchronously on save to stop wrong-page data loss.
- Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race,
and search N+1; transform stored annotations on PDF page rotation.
- Normalize pen pressure for devices without a pressure range.
- PPT: single source of truth for slide strokes so ink displays and exports.
UI/UX:
- Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors
and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/
save/find), toolbar overflow handling, friendlier empty states, semantic OCR
status badges, relative timestamps, 1-based page indicators, large-deck PPT
navigation, and a scratchpad-scope label in split view.
Server (optional backend):
- Persist JWT secret (was per-process random), block path traversal in storage,
fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync
guard, constant-time login, and split out heavy OCR deps so the API/tests run
without them.
CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a
Windows release build; pristine `flutter analyze`, all Flutter and server tests
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
|
|
|
#include "ocr_channel.h"
|
|
|
|
|
|
|
|
|
|
#include <flutter/encodable_value.h>
|
2026-06-21 05:49:26 +08:00
|
|
|
#include <flutter/flutter_engine.h>
|
Fix bugs across app + server, optimize UI/UX, add Gitea CI
Bug fixes (Flutter):
- Wrap multi-statement DB writes (insert/update/delete note, deleteDocument,
deletePageData, OCR FTS merge, migrations) in transactions to prevent data
loss on interruption and a read-modify-write FTS race.
- Fix PdfDocument leaks on exception (try/finally dispose) and preserve image
aspect ratio when stamping images onto PDF pages.
- Guard file-picker against empty selection (was .single -> crash).
- Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF
pages; capture page synchronously on save to stop wrong-page data loss.
- Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race,
and search N+1; transform stored annotations on PDF page rotation.
- Normalize pen pressure for devices without a pressure range.
- PPT: single source of truth for slide strokes so ink displays and exports.
UI/UX:
- Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors
and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/
save/find), toolbar overflow handling, friendlier empty states, semantic OCR
status badges, relative timestamps, 1-based page indicators, large-deck PPT
navigation, and a scratchpad-scope label in split view.
Server (optional backend):
- Persist JWT secret (was per-process random), block path traversal in storage,
fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync
guard, constant-time login, and split out heavy OCR deps so the API/tests run
without them.
CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a
Windows release build; pristine `flutter analyze`, all Flutter and server tests
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
|
|
|
#include <flutter/method_channel.h>
|
|
|
|
|
#include <flutter/standard_method_codec.h>
|
|
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <string>
|
2026-06-21 16:19:51 +08:00
|
|
|
#include <thread>
|
Fix bugs across app + server, optimize UI/UX, add Gitea CI
Bug fixes (Flutter):
- Wrap multi-statement DB writes (insert/update/delete note, deleteDocument,
deletePageData, OCR FTS merge, migrations) in transactions to prevent data
loss on interruption and a read-modify-write FTS race.
- Fix PdfDocument leaks on exception (try/finally dispose) and preserve image
aspect ratio when stamping images onto PDF pages.
- Guard file-picker against empty selection (was .single -> crash).
- Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF
pages; capture page synchronously on save to stop wrong-page data loss.
- Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race,
and search N+1; transform stored annotations on PDF page rotation.
- Normalize pen pressure for devices without a pressure range.
- PPT: single source of truth for slide strokes so ink displays and exports.
UI/UX:
- Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors
and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/
save/find), toolbar overflow handling, friendlier empty states, semantic OCR
status badges, relative timestamps, 1-based page indicators, large-deck PPT
navigation, and a scratchpad-scope label in split view.
Server (optional backend):
- Persist JWT secret (was per-process random), block path traversal in storage,
fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync
guard, constant-time login, and split out heavy OCR deps so the API/tests run
without them.
CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a
Windows release build; pristine `flutter analyze`, all Flutter and server tests
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
#include <winrt/Windows.Foundation.h>
|
|
|
|
|
#include <winrt/Windows.Graphics.Imaging.h>
|
|
|
|
|
#include <winrt/Windows.Media.Ocr.h>
|
|
|
|
|
#include <winrt/Windows.Storage.Streams.h>
|
|
|
|
|
#include <winrt/base.h>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> ExtractBytes(const flutter::EncodableValue& value) {
|
|
|
|
|
if (const auto* bytes = std::get_if<std::vector<uint8_t>>(&value)) {
|
|
|
|
|
return *bytes;
|
|
|
|
|
}
|
|
|
|
|
if (const auto* list = std::get_if<flutter::EncodableList>(&value)) {
|
|
|
|
|
std::vector<uint8_t> out;
|
|
|
|
|
out.reserve(list->size());
|
|
|
|
|
for (const auto& item : *list) {
|
|
|
|
|
if (const auto* i = std::get_if<int32_t>(&item)) {
|
|
|
|
|
out.push_back(static_cast<uint8_t>(*i));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string RecognizePng(const std::vector<uint8_t>& bytes) {
|
|
|
|
|
if (bytes.empty()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using namespace winrt;
|
|
|
|
|
using namespace Windows::Storage::Streams;
|
|
|
|
|
using namespace Windows::Graphics::Imaging;
|
|
|
|
|
using namespace Windows::Media::Ocr;
|
|
|
|
|
|
|
|
|
|
InMemoryRandomAccessStream stream;
|
|
|
|
|
{
|
|
|
|
|
DataWriter writer{stream};
|
|
|
|
|
writer.WriteBytes(bytes);
|
|
|
|
|
writer.StoreAsync().get();
|
|
|
|
|
writer.DetachStream();
|
|
|
|
|
}
|
|
|
|
|
stream.Seek(0);
|
|
|
|
|
|
|
|
|
|
BitmapDecoder decoder = BitmapDecoder::CreateAsync(stream).get();
|
|
|
|
|
SoftwareBitmap bitmap = decoder.GetSoftwareBitmapAsync().get();
|
|
|
|
|
|
|
|
|
|
OcrEngine engine = OcrEngine::TryCreateFromUserProfileLanguages();
|
|
|
|
|
if (!engine) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OcrResult result = engine.RecognizeAsync(bitmap).get();
|
|
|
|
|
return winrt::to_string(result.Text());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> g_ocr_channel;
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void RegisterOcrChannel(flutter::FlutterEngine* engine) {
|
2026-06-21 16:19:51 +08:00
|
|
|
// NOTE: do NOT call winrt::init_apartment() here. This runs on the Flutter
|
|
|
|
|
// platform thread, which main.cpp has already initialized as an STA via
|
|
|
|
|
// CoInitializeEx(COINIT_APARTMENTTHREADED). Calling init_apartment() (which
|
|
|
|
|
// defaults to multi-threaded) would throw RPC_E_CHANGED_MODE and crash the
|
|
|
|
|
// app before the window appears. The WinRT OCR work runs on its own
|
|
|
|
|
// dedicated MTA worker thread per call instead (see below).
|
Fix bugs across app + server, optimize UI/UX, add Gitea CI
Bug fixes (Flutter):
- Wrap multi-statement DB writes (insert/update/delete note, deleteDocument,
deletePageData, OCR FTS merge, migrations) in transactions to prevent data
loss on interruption and a read-modify-write FTS race.
- Fix PdfDocument leaks on exception (try/finally dispose) and preserve image
aspect ratio when stamping images onto PDF pages.
- Guard file-picker against empty selection (was .single -> crash).
- Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF
pages; capture page synchronously on save to stop wrong-page data loss.
- Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race,
and search N+1; transform stored annotations on PDF page rotation.
- Normalize pen pressure for devices without a pressure range.
- PPT: single source of truth for slide strokes so ink displays and exports.
UI/UX:
- Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors
and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/
save/find), toolbar overflow handling, friendlier empty states, semantic OCR
status badges, relative timestamps, 1-based page indicators, large-deck PPT
navigation, and a scratchpad-scope label in split view.
Server (optional backend):
- Persist JWT secret (was per-process random), block path traversal in storage,
fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync
guard, constant-time login, and split out heavy OCR deps so the API/tests run
without them.
CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a
Windows release build; pristine `flutter analyze`, all Flutter and server tests
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
|
|
|
g_ocr_channel =
|
|
|
|
|
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
|
|
|
|
|
engine->messenger(), "badnote/ocr",
|
|
|
|
|
&flutter::StandardMethodCodec::GetInstance());
|
|
|
|
|
|
|
|
|
|
g_ocr_channel->SetMethodCallHandler(
|
|
|
|
|
[](const flutter::MethodCall<flutter::EncodableValue>& call,
|
|
|
|
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>
|
|
|
|
|
result) {
|
|
|
|
|
if (call.method_name() != "recognize") {
|
|
|
|
|
result->NotImplemented();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!call.arguments()) {
|
|
|
|
|
result->Error("invalid_args", "Expected PNG byte data");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 16:19:51 +08:00
|
|
|
const auto png_bytes = ExtractBytes(*call.arguments());
|
|
|
|
|
|
|
|
|
|
// Run the WinRT OCR on a dedicated multi-threaded-apartment worker so
|
|
|
|
|
// its blocking .get() calls behave correctly and the platform thread's
|
|
|
|
|
// STA apartment is left untouched. We join before returning so the
|
|
|
|
|
// MethodResult is completed on the platform thread.
|
|
|
|
|
std::string text;
|
|
|
|
|
std::string error;
|
|
|
|
|
std::thread worker([&]() {
|
|
|
|
|
try {
|
|
|
|
|
winrt::init_apartment(winrt::apartment_type::multi_threaded);
|
|
|
|
|
text = RecognizePng(png_bytes);
|
|
|
|
|
winrt::uninit_apartment();
|
|
|
|
|
} catch (const winrt::hresult_error& e) {
|
|
|
|
|
error = winrt::to_string(e.message());
|
|
|
|
|
} catch (...) {
|
|
|
|
|
error = "Unknown OCR error";
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
worker.join();
|
|
|
|
|
|
|
|
|
|
if (!error.empty()) {
|
|
|
|
|
result->Error("ocr_failed", error);
|
|
|
|
|
} else {
|
Fix bugs across app + server, optimize UI/UX, add Gitea CI
Bug fixes (Flutter):
- Wrap multi-statement DB writes (insert/update/delete note, deleteDocument,
deletePageData, OCR FTS merge, migrations) in transactions to prevent data
loss on interruption and a read-modify-write FTS race.
- Fix PdfDocument leaks on exception (try/finally dispose) and preserve image
aspect ratio when stamping images onto PDF pages.
- Guard file-picker against empty selection (was .single -> crash).
- Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF
pages; capture page synchronously on save to stop wrong-page data loss.
- Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race,
and search N+1; transform stored annotations on PDF page rotation.
- Normalize pen pressure for devices without a pressure range.
- PPT: single source of truth for slide strokes so ink displays and exports.
UI/UX:
- Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors
and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/
save/find), toolbar overflow handling, friendlier empty states, semantic OCR
status badges, relative timestamps, 1-based page indicators, large-deck PPT
navigation, and a scratchpad-scope label in split view.
Server (optional backend):
- Persist JWT secret (was per-process random), block path traversal in storage,
fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync
guard, constant-time login, and split out heavy OCR deps so the API/tests run
without them.
CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a
Windows release build; pristine `flutter analyze`, all Flutter and server tests
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
|
|
|
result->Success(flutter::EncodableValue(text));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|