يشغّل HotPDF Tesseract داخل عملية Delphi لديك عبر HPDFCreateTesseractDLLOCREngine، مصنع أُضيف في v2.772.0 يحمّل ديناميكياً DLL متوافقاً مع Tesseract 5، ويقود الـ C API عنده(TessBaseAPIInit2 و TessBaseAPIRecognize ومُكرِّر النتيجة)، ويعيد IHPDFOCREngine. وتستخدم THotPDF.ApplyLoadedOCRTextLayer ذلك المحرك لإضافة طبقة نص Unicode غير مرئية قابلة للبحث إلى صفحات PDF الممسوحة
وكان المُعَرِّف نفسه مبلغاً إليه عبر مُكيِّف tesseract.exe الخارجي الذي يكتب BMP ويحلل TSV. ذلك المسار يعمل، لكن كل صفحة تدفع ثمن إطلاق عملية وملف bitmap مؤقت وصيغة نص بلا خطوط أساس وبلا تحكم في تقسيم الصفحة. ونداء الـ DLL يزيل الثلاثة. كما يزيل جدار العملية، أي أن ربطاً بلغة Pascal يجلس فوق بنى C وقيم bool في C وسلاسل مخصصة من C مباشرةً. وأغلب ما يستحق المعرفة عن هذا المُكيِّف هو المواضع التي قد يخطئ فيها ذلك الربط بهدوء
كيف تشغّل Tesseract داخل العملية من Delphi عبر HotPDF؟
تشغيل Tesseract داخل العملية عبر HotPDF يأخذ نداء مصنع واحداً في وحدة HPDFTesseractRecognition ونداء ApplyLoadedOCRTextLayer نفسه الذي تستخدمه كل محركات OCR لدى HotPDF. ويتحقق المصنع بحماس. يجب أن يوجد ملف الـ DLL ومجلد tessdata، وأن يحوي معرّف اللغة حروفاً ASCII وأرقاماً و _ و + فقط، وأن يملك كل نموذج في تركيبة مثل chi_sim+eng ملف .traineddata مطابقاً، وأن تفاضل كل التصديرات المطلوبة الـ 21 قبل إعادة المحرك. أخطاء الضبط ترفع EArgumentException؛ و DLL يفشل تحميله يرفع EOSError مع كود خطأ Windows وتلميح بفحص البنية والاعتماديات
uses
SysUtils, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// تطبيق Win64 يحتاج DLL ذا 64-بت؛ وتقبع DLLs الاعتماديات بجواره
Engine := HPDFCreateTesseractDLLOCREngine('C:\OCR\Win64\libtesseract-5.dll',
'C:\OCR\tessdata', 'chi_sim+eng'); // THPDFTesseractOptions.Default
Doc := THotPDF.Create(nil);
try
Doc.AutoLaunch := False;
if Doc.LoadFromFile(SourceFile) < 1 then
raise Exception.Create('Cannot load ' + SourceFile);
Options := THPDFOCRTextLayerOptions.Default; // 300 DPI، MinimumConfidence 0.5
// قائمة صفحات فارغة تعني كل الصفحات؛ والصفحات ذات نص مسبق تُتخطى
if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
raise Exception.Create(string(Info.Diagnostic));
Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
' words accepted, ', Info.DroppedWordCount, ' dropped');
Doc.SaveLoadedDocument(TargetFile);
finally
Doc.Free;
end;
end;
يضبط THPDFTesseractOptions.Default قيمة PageSegMode إلى tpsAuto، و EngineMode إلى temDefault، و TimeoutMilliseconds إلى 60,000 و MaxPixels إلى 16,777,216. وميزانية البكسل أهم مما تبدو. صفحة US Letter عند 300 DPI الافتراضية تعرض إلى 2,550 × 3,300 بكسل، نحو 8.4 مليون، فتتسع. والصفحة نفسها عند 600 DPI هي 5,100 × 6,600، نحو 33.7 مليون، فيرفضها المُكيِّف قبل أن يرى Tesseract بكسلاً. ارفع MaxPixels (السقف 67,108,864) أو أبقِ الـ DPI كما هو؛ وكل جانب أيضاً مسقوف بـ 32,767 بكسل
ويُحمَّل الـ DLL بـ LoadLibraryEx بأعلام البحث الخاصة بمجلد الـ DLL نفسه زائد الدلائل الآمنة الافتراضية، فتستطيع مكتبات الصور التي يعتمد عليها Tesseract أن تسكن بجواره دون لمس PATH أو الدليل الحالي. ولا تحزم HotPDF ولا تنزّل أي بيئة تشغيل OCR أو نموذج؛ فأنت تزوّد الاثنين
ما الذي يتغير مقارنةً بمُكيِّف tesseract.exe؟
يقايض مُكيِّف الـ DLL عزلَ العملية بمخرج أغنى وعبءٍ لكل صفحة أدنى. كلا المُكيِّفَين يندمجان في مسار طبقة النص نفسه، فالتعيين الإحداثي وتصفية الثقة والتثبيت الكل أو لا شيء متطابقة؛ وما يختلف هو كيف تدخل البكسلات وتخرج الكلمات
| الجانب | مُكيِّف tesseract.exe | مُكيِّف Tesseract DLL |
|---|---|---|
| المصنع | HPDFCreateTesseractOCREngine | HPDFCreateTesseractDLLOCREngine |
| البكسلات داخل | ملف BMP في دليل مؤقت خاص | مخزن رمادي 8-بت في الذاكرة |
| الكلمات خارج | TSV على مستوى الكلمة، مسقوف بـ 64 MiB | مُكرِّر نتيجة، UTF-8 لكل كلمة |
| خطوط الأساس | غير متاحة | تُمرر من TessPageIteratorBaseline |
| تقسيم الصفحة ووضع المحرك | التقسيم التلقائي فقط | THPDFTesseractPageSegMode، THPDFTesseractEngineMode |
| المهلة | قاسية: تُنهى العملية الابنة | تعاونية: على Tesseract أن يلاحظ |
| عزل الانهيار والذاكرة | عملية منفصلة | لا شيء، يتشارك فضاء عناوينك |
وتكلفة واحدة لا تختفي. كل نداء Recognize ينشئ نسخة API خاصة به ويستدعي TessBaseAPIInit2، فتُهيَّأ نماذج اللغة لكل صفحة لا مرة لكل محرك. مخزن الملفات في نظام التشغيل يلين إعادة التحميل، لكن على مجموعات نماذج كبيرة متعددة اللغات يظل التكلفة الثابتة المهيمنة لكل صفحة، ويُعَدّ مقابل مهلة التعرف. يأخذ محرك DLL الـ RapidOCR داخل العملية التصميم المعاكس ويبقي نماذج ONNX مقيمة طوال عمر المحرك؛ ومشكلات الحدود (C ABI ومخازن مستعارة وعمل أصلي لا يُقاطَع) من العائلة نفسها
لماذا لا تستطيع Delphi نسخ بنية مراقب Tesseract؟
لا تستطيع Delphi عكس مراقب تقدم Tesseract بأمان لأن ETEXT_DESC يحوي حقولاً داخلية متغيرة بنسخة الإصدار، فسجل منسوخ يدوياً يضع callback الإلغاء والمهلة عند إزاحات خاطئة على بعض البناءات. ولا يفشل شيء بصوت عالٍ حين يحدث ذلك. يقرأ Tesseract ببساطة مؤشر callback الخاص بك من حقل صار يحمل شيئاً آخر، أو لا يرى المهلة إطلاقاً
لذلك تعامل HotPDF المراقب مؤشراً معتماً ولا تلمسه إلا عبر التوابع المصدَّرة: TessMonitorCreate و TessMonitorSetCancelThis و TessMonitorSetCancelFunc و TessMonitorSetDeadlineMSecs و TessMonitorDelete. وإن ربطت الـ C API بنفسك لغرض آخر فالنمط نفسه يسري. الهيكل أدناه كود ربطك أنت لا API من HotPDF، وهو يعكس التصريحات التي تستخدمها HotPDF داخلياً
type
// C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
TTessMonitorCreate = function: Pointer; cdecl; // ETEXT_DESC*، لا تُفاضَل قط
TTessMonitorDelete = procedure(Monitor: Pointer); cdecl;
TTessMonitorSetCancelFunc = procedure(Monitor: Pointer; Func: TTessCancelFunc); cdecl;
TTessMonitorSetCancelThis = procedure(Monitor, CancelThis: Pointer); cdecl;
TTessMonitorSetDeadlineMSecs = procedure(Monitor: Pointer; MSecs: Integer); cdecl;
TTessBaseAPIRecognize = function(Handle, Monitor: Pointer): Integer; cdecl;
TOCRJob = record
CancelRequested: Boolean;
DeadlineTick: UInt64;
end;
POCRJob = ^TOCRJob;
function ShouldCancel(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
begin
// يجري على مكدّس Tesseract: اقرأ الأعلام والساعة ولا ترفع أبداً
Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
(GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;
// الاستخدام، مع مؤشرات التوابع المفاضَلة عبر GetProcAddress:
// Monitor := MonitorCreate();
// try
// MonitorSetCancelThis(Monitor, @Job);
// MonitorSetCancelFunc(Monitor, ShouldCancel);
// MonitorSetDeadlineMSecs(Monitor, RemainingMs);
// RC := BaseAPIRecognize(API, Monitor);
// finally
// MonitorDelete(Monitor);
// end;
تفصيلان في ذلك الهيكل مقصودان. يعيد الـ callback قيمة Boolean، وهي بايت واحد في Delphi و Free Pascal كليهما، مطابقةً لقيمة bool في C داخل TessCancelFunc. وقيمة BOOL ذية الأربع بايتات في Windows أو LongBool في Delphi تبدو قابلة للتبادل وليست كذلك: حين يكتب طرف بايتاً واحداً ويقرأ الآخر أربعة، تكون البايتات العليا من مسجل الإعادة كل ما تبقى فيه، وقد تصل قيمة false بوصفها true. ويعقّد الترويسةُ نفسها الأمور أكثر، لأن توابع مثل TessPageIteratorBoundingBox تعيد int، مصرّحاً عنها لدى HotPDF بوصفها Integer. اقرأ نوع C لكل قيمة عائدة بدل افتراض اصطلاح واحد للـ API كله
والتفصيل الثاني أن الـ callback لا يرفع أبداً. استثناء Delphi يفك تراصه عبر أُطَر ++C الخاصة بـ Tesseract سلوك غير معرّف، لذا يقرأ callback الخاص بـ HotPDF رمزَ الإلغاء وقيمة GetTickCount64 رتيبةَ العدّ فقط. ويحوّل المُكيِّف النتيجة إلى تشخيص إلغاء أو انتهاء مهلة بعد عودة TessBaseAPIRecognize، ويجري ذلك الفحص بغض النظر عن كود الإعادة الأصلي
أي مؤشرات أصلية يملكها جانب Delphi؟
يملك مُكيِّف Tesseract DLL لدى HotPDF ثلاثة كائنات أصلية لكل طلب، نسخة الـ API والمراقب ومُكرِّر النتيجة، ويستعير كل ما عداهما. كل نداء Recognize ينشئ مجموعته الخاصة ويحررها في كتلة finally: TessResultIteratorDelete ثم TessMonitorDelete ثم TessBaseAPIDelete. وإفلات واجهة المحرك يفرّغ المكتبة
- يعيد
TessResultIteratorGetPageIteratorمنظرَاً مستعاراً داخل مُكرِّر النتيجة، لا كائناً جديداً. تستخدمه HotPDF لـTessPageIteratorBoundingBoxوTessPageIteratorBaselineولا تحرره أبداً؛ وحذفه منفرداً كان سيحرر الذاكرة نفسها مرتين - يعيد
TessResultIteratorGetUTF8Textسلسلة خصصتها بيئة تشغيل الـ DLL الخاصة. تنسخها HotPDF وتعيدها عبرTessDeleteTextفي كتلةfinally؛ فـFreeMemفي Pascal كان سيحررها على الكومة الخاطئة - يُفك نص الكلمة بتحقق UTF-8 صارم ويُفحص طوله قبل التحويل. الكلمات ذات محارف التحكم أو UTF-8 المشوه أو الصناديق خارج الصورة أو المستطيلات المقلوبة أو الثقة خارج 0–100 تُسقط الطلبَ بدل ترقيعها بصمت
- النص الكلي لكل طلب مسقوف بـ 1,048,576 وحدة كود UTF-16، ويجب أن يتسع عدد الكلمات في ميزانية الطلب المسلَّمة من
ApplyLoadedOCRTextLayer
وتصل الثقة على مساحة 0–100 وتُكيَّف إلى 0–1، فتعني THPDFOCRTextLayerOptions.MinimumConfidence الشيءَ نفسه لكل محرك. وحين يبلّغ Tesseract خطَّ أساس تمرَّر طرفاه معاً؛ وإلا لجأ مسار طبقة النص إلى تقديره الهندسي، تماماً كما يفعل لدخل TSV
لماذا يتحقق من enum قبل أن يبلغ الـ DLL؟
تنسخ HotPDF الترتيبي الخام لقيم PageSegMode و EngineMode إلى Integer قبل فحص المدى، لأن المترجم قد يفترض أن متغير enum يحمل دائماً قيمة مصرّحاً عنها ويطوي Ord(X) > Ord(High(T)) إلى false ثابتة. والترتيبيات ليست زينة: يتبع THPDFTesseractPageSegMode ترقيم تقسيم الصفحات في Tesseract من 0 إلى 13، و THPDFTesseractEngineMode يتبع ترقيم وضع المحرك من 0 إلى 3، وكلاهما يذهب إلى الـ DLL أعداداً صحيحة مجردة. وسجل خيارات بُني بـ FillChar أو امتلأ من تيار أو مُرِّر من C++Builder بعدد صحيح مسبوك يستطيع أن يحمل بايتاً مثل 200. والتحقق من الترتيبي المنسوخ يحوّل ذلك إلى EArgumentException عند وقت المصنع بدل وضع غير معرّف داخل كود أصلي. ويرفض المصنع أيضاً tpsOSDOnly و tpsAutoOnly لأنهما لا ينتجان كلمات، ويتطلب osd.traineddata لقيمتَي tpsAutoOSD و tpsSparseTextOSD
ماذا تضمن مهلة التعرف فعلاً؟
مهلة Tesseract DLL تعاونية: تستطيع HotPDF أن توقف عملها الخاص وأن تطلب من Tesseract أن يتوقف، لكنها لا تستطيع إجبار الكود الأصلي على العودة. يبدأ الساعة حين يبدأ Recognize، فتحويل الـ bitmap وتهيئة النموذج يستهلكان الميزانية نفسها التي يستهلكها التعرف. تفحص HotPDF الوقت المنقضي ورمز الإلغاء أثناء تحويل الرمادي وبين الكلمات أثناء تكرار النتائج، وتمرر الميلي ثواني المتبقية إلى TessMonitorSetDeadlineMSecs قبل استدعاء TessBaseAPIRecognize
والفجوة داخل النداء الأصلي. يُستشار مراقب Tesseract أثناء التعرف على الكلمات، لا أثناء TessBaseAPIInit2 أو تحليل تخطيط الصفحة، فقد يجري تحميل نموذج بطيء أو تخطيط مرضي بعد المهلة قبل أن تُبلَّغ. وميزانيتا البكسل والمخرج لا تسقفان أيضاً استهلاك الذاكرة الخاص بالمكتبة الأصلية نفسها. إن احتجت عاملاً تستطيع قتله فاستخدم مُكيِّف العملية؛ تلك هي المقايضة الصادقة لا ميزة ناقصة
وتقسيم الصفحة هو حيث يكسب مُكيِّف الـ DLL مقامه على الدخل الصعب. النماذج والتسميات والجداول الممسوحة بحقول متفرقة كثيراً ما تُعرَف أحسن بـ tpsSparseText من التقسيم التلقائي الذي يحاول تجميع أعمدة وفقرات غير موجودة
procedure OCRFormPages(Doc: THotPDF; const Pages: array of Integer);
var
Engine: IHPDFOCREngine;
TessOptions: THPDFTesseractOptions;
LayerOptions: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
TessOptions := THPDFTesseractOptions.Default;
TessOptions.PageSegMode := tpsSparseText; // حقول متفرقة بلا تجميع أعمدة
TessOptions.EngineMode := temLSTMOnly; // يحتاج نماذج LSTM في tessdata
TessOptions.TimeoutMilliseconds := 20000; // يشمل تهيئة النموذج
Engine := HPDFCreateTesseractDLLOCREngine('C:\OCR\Win64\libtesseract-5.dll',
'C:\OCR\tessdata', 'eng+deu', TessOptions);
LayerOptions := THPDFOCRTextLayerOptions.Default;
LayerOptions.MinimumConfidence := 0.6;
if not Doc.ApplyLoadedOCRTextLayer(Pages, Engine, LayerOptions, Info) then
case Info.Status of
otlsCancelled:
Writeln('OCR cancelled, document unchanged');
otlsEngineError:
Writeln('Tesseract failed or timed out: ', string(Info.Diagnostic));
else
Writeln(string(Info.Diagnostic));
end;
end;
المهلة المنتهية تظهر otlsEngineError مع التشخيص Tesseract DLL OCR timed out، بينما يظهر الرمز الملغى otlsCancelled. وفي الحالتين تكون ApplyLoadedOCRTextLayer قد عرفت كل صفحة مختارة قبل أن تبدأ معاملة التثبيت، فيترك فشلُ الصفحة 40 من 50 المستندَ المحمّل كما كان تماماً. ولاحظ أن tpsSingleLine و tpsSingleBlock و tpsSparseText تغيّر التقسيم فقط؛ ولا شيء منها يستقيم مسحاً مائلاً
Free Pascal و Lazarus: بكسلات متقادمة وصينية مفقودة
كلا مصنعَي Tesseract يعملان في Free Pascal و Lazarus على Windows ببناءَي Win32 و Win64 منذ v2.772.1، بعد إصلاحين خاصين بـ FPC. أعد بناء حزمة Lazarus للبنية الهدف أولاً؛ والانتقال العام مغطى في HotPDF على Free Pascal و Lazarus Win64
الإصلاح الأول يخص البكسلات. إن TBitmap في LCL مكتوب عبر scanlines يستطيع تحديث صورته الخام دون تحديث مقبض bitmap الخاص بـ Windows، فـ GetDIBits على ذلك المقبض يعيد البكسلات القديمة. وكان العَرَض حيراً: نص مرسوم مباشرة على bitmap عُرف، بينما صفحة معروضة بمحرك PDF لدى HotPDF أنتجت قائمة كلمات فارغة. على FPC يقرأ المُكيِّف الآن لقطة واعية بالصيغة عبر CreateIntfImage، وهي تحترم صيغة بكسل الصورة الخام وترتيب صفوفها. ويبقي بناء Delphi مسار GetDIBits على نسخة خاصة 24-بت. ولا يعدّل أيٌّ من البناءين bitmap المستدعي
والإصلاح الثاني يخص مُكيِّف tesseract.exe. يخزّن TStringList في FPC سلاسل ANSI، فإسناد نص TSV المفكوك من UTF-8 إلى Lines.Text كان يُسقط بصمت كل محرف صيني أو من المستوى التكميلي لم تستطع صفحة كود ANSI للنظام تمثيله. يحتفظ مسار FPC الآن بـ TSV بوصفه بايتات UTF-8، ويسلخ الـ BOM على مستوى البايت ويفك كل كلمة إلى UnicodeString منفردة. لم يكن لمُكيِّف الـ DLL هذه المشكلة قط لأنه يفك كل كلمة مباشرة من المُكرِّر
مرجع سريع
- المصنع:
HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options])فيHPDFTesseractRecognition، أضيف في v2.772.0، ودعم FPC في v2.772.1 - الافتراضات:
tpsAuto، temDefault، 60,000 ms، 16,777,216 بكسل؛ مدى المهلة 1–3,600,000 ms وسقف البكسل 67,108,864 - طابق عرضة الـ DLL مع التطبيق وضعَ DLLs الاعتماديات بجوار DLL الـ Tesseract
- عامل المراقب معتماً؛ لا تنسخ
ETEXT_DESCإلى سجل Pascal أبداً - صرّح عن callback الإلغاء بـ
cdeclونتيجةBooleanمن بايت واحد، ولا تدع استثناءً يهرب منه أبداً - حرّر نص المُكرِّر بـ
TessDeleteText؛ ولا تحرر مُكرِّر الصفحة المأخوذ من مُكرِّر النتيجة أبداً - توقع أن تكون المهلة تعاونية: تستطيع تهيئة النموذج وتحليل التخطيط تجاوزها
- استخدم مُكيِّف tesseract.exe حين تحتاج إنهاءً قاسياً أو عزل انهيارات
مُكيِّف Tesseract DLL ومُكيِّفات العمليات ومحرك OCR المدمج كلها تُشحن مع مكوّن HotPDF Delphi PDF component لـ Delphi و C++Builder و Free Pascal؛ وانظر صفحة منتج HotPDF للإصدارات والتنزيلات