مقال تقني

فك ترميز رموز QR المدوارة في صفحات PDF بـ HotPDF

يفك HotPDF ترميز رموز QR المدوارة في صفحة PDF محملة بتطبيع مصفوفة الوحدات المعيَّنة عبر اتجاهات D4 الثمانية كلها داخل المدقق نفسه. وإعادة محاولة التدوير الخارجية التي تنجح مع الرموز الخطية لا يمكن أن تنجح مع QR، وفهم السبب يوفر عليك يومًا من مطاردة مدقق يبدو معطوبًا وهو ليس كذلك

والسيناريو عادي بما يكفي. تصل إيصالات تسليم ممسوحة ضوئيًا كملفات PDF، وتحمل كل صفحة ملصق QR، وقد أدخل مشغل الماسح كومة أوراق في أي اتجاه قبلته الصاج. بعض الملصقات معتدلة، وبعضها مائل ربع دورة، وقليل منها مقلوب رأسًا على عقب. تستدعي مدقق الباركود، فتنحل نصف الصفحات، ويعود النصف الآخر فارغًا بلا أي خطأ

لماذا لا يصلح تدوير قناع المسح QR مدوارة أبدًا

لأن تخطيط نمط الباحث في QR لاتماثل عمدًا، وتدوير الصورة كاملة يحافظ على ذلك اللاتماثل بدل إزالته. فـ QR Code يضع ثلاثة مربعات باحث في زوايا أعلى اليسار وأعلى اليمين وأسفل اليسار، ويترك الزاوية أسفل اليمين فارغة (ISO/IEC 18004:2015 §6.3.3). تلك الزاوية المفقودة هي إشارة الاتجاه. دوّر bitmap الصفحة تسعين درجة وتنتقل الفجوة ببساطة إلى زاوية أخرى. ولا يوجد تدوير غير بديهي للمستوى يعيد تخطيط الثلاث زوايا إلى ذاته، فالمدقق الذي لا يقبل سوى الترتيب القياسي سيرفض كل محاولة بدورها

وهذا مهم لأن الحل البديهي هو الحل الخاطئ. فالحدس الطبيعي أن تعلّق إعادة المحاولة من الخارج: صيّر الصفحة، وسلّم القناع إلى المدقق، وإن فشل ذلك فدوّر القناع وحاول مجددًا بـ 90 و180 و270 درجة. فمع Code 39 تلك السياسة صحيحة تمامًا، لأن الرمزية الخطية لها نمط بداية ونهاية يجده الماسح ما إن تجري الأشرطة أفقيًا. أما مع QR فهي أربع إخفاقات مضمونة يليها تقرير بعدم العثور على شيء

زمرة D4 مطبقة على مصفوفة الوحدات

والموضع الصائب للتطبيع بعد التعيينة، على شبكة الوحدات المنطقية لا على قناع البكسلات. ما إن يحل المدقق الرمز إلى مصفوفة n في n من وحدات داكنة وفاتحة، حتى يستطيع عدّ زمرة المربع المزدوجة الوجهين: أربع تدويرات في انعكاسين، ثمانية اتجاهات مرشحة في المجموع. ويفحص لكل مرشحة مثلث الباحثين، والمرشحة الأولى التي تهبط باحثوها الثلاثة في مواضع أعلى اليسار وأعلى اليمين وأسفل اليسار هي الاتجاه الحقيقي. ومن هناك يجري خط المعالجة القائم دون تغيير، لأن بتات معلومات الصيغة ووضع البيانات المتعرج وتصحيح Reed-Solomon كلها تفترض مصفوفة قياسية وتحصل على واحدة الآن

أربع مصيّرات لمصفوفة وحدات QR نفسها في HotPDF تحت تدويرات زمرة D4 عند 0 و90 و180 و270 درجة، تبين أنماط الباحث الثلاثة تهجر الزوايا بينما تنتقل الزاوية الفارغة معها، فلا يقدم الاتجاه القياسي وحده باحثين عند أعلى اليسار وأعلى اليمين وأسفل اليسار إلى المدقق
تدوير قناع البكسلات لا يستطيع إزالة لاتماثل باحث QR، لذا يعدّ HotPDF اتجاهات D4 على مصفوفة الوحدات المعينة ويبقي أول مرشحة يهبط باحثوها أعلى اليسار وأعلى اليمين وأسفل اليسار

وخاصيتان تجعلان هذا رخيصًا. فالمصفوفة صغيرة مقارنة بـ bitmap المصيَّر، فثماني تبديلات مواقع تكلف أقل بكثير من ثماني مصيّرات صفحات. والمصفوفة مصفوفة منطقية نظيفة بناها المعيِّن، فلا تحويل في الطريق يستطيع إدخال قيم لم تُعيَّن قط

كشف الإصدار بحث قابلية قسمة لا قسمة

لا يمكن اشتقاق عدد الوحدات بقسمة العرض المعيَّن على حجم وحدة مفترض، والخطأ هنا مصدر خفي لإخفاقات فك الترميز على المصيّرات عالية الدقة. فرمز QR من إصدار v عرضه 4v + 17 وحدة، فالإصدار 1 هو 21 وحدة والإصدار 40 هو 177. وقناع يبلغ عرضه 126 بكسل يتسق بالقدر نفسه مع الإصدار 1 عند ستة بكسلات لكل وحدة ومع عدة إصدارات أعلى بأحجام وحدات أصغر. والقسمة الخطية تختار أحدها وتكون مخطئة غالبًا

والذي يعمل بحث قابلية قسمة على الإصدارات المرشحة. امش من الإصدار 40 نازلًا إلى الإصدار 1، وأبق المرشحات التي يقسم عدد وحداتها العرض المعيَّن قسمة تامة ويترك ثلاثة بكسلات على الأقل لكل وحدة، وخذ أصغر إصدار ناج. وأرضية البكسلات الثلاث هي ما يمنع البحث من قبول قراءة كثيفة سخيفة لرمز خشن، وقاعدة أصغر إصدار تحل الغموض المتبقي لصالح القراءة التي سينتجها ماسح فعلًا

مشي كشف الإصدار في HotPDF لرمز QR على قناع معيَّن بعرض 126 بكسل، يفحص كل عدد وحدات مرشح 4v زائد 17 من الإصدار 40 نازلًا إلى الإصدار 1 بحثًا عن قابلية قسمة تامة وأرضية وحدة بثلاثة بكسلات قبل أن يفوز أصغر إصدار ناج
عدد وحدات QR يأتي من بحث قابلية قسمة على الإصدارات المرشحة، لا من قسمة عرض القناع على حجم وحدة مفترض، وأصغر إصدار ناج يحل الغموض
var
  Pdf: THotPDF;
  Options: THPDFBarcodeDecodeOptions;
  Codes: THPDFDecodedBarcodes;
  Info: THPDFBarcodeDecodeInfo;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('delivery-notes.pdf');
    Options := THPDFBarcodeDecodeOptions.Default;
    Options.DPI := 300;
    Options.RotationPolicy := bdrpFallback;
    Options.MinimumConfidence := 0.5;
    Options.MaxResults := 16;
    if Pdf.DecodeLoadedPageBarcodes(0, Options, Codes, Info) then
      for I := 0 to High(Codes) do
        if Codes[I].Symbology = bsyQRCode then
          Writeln(Codes[I].Text, '  at ',
            Format('%.0f', [Codes[I].OrientationDegrees]), ' degrees');
  finally
    Pdf.Free;
  end;
end;

تسلّم THPDFBarcodeDecodeOptions.Default سجلًا معبأً لا مسفَّرًا، وهذا مهم لأن DPI صفر أو سقف نتائج صفر طريقة تبدو مشروعة للحصول على لا شيء. وRotationPolicy تتحكم في إعادة المحاولة الخارجية فقط: bdrpNone تصيّر مرة واحدة، وbdrpFallback تعيد محاولة الاتجاهات الأخرى بعد تمريرة أولى فاشلة، وbdrpAll تصيّر كل اتجاه دون شرط. ولأن تطبيع QR يحدث داخل المدقق، تنحل صفحات QR من المحاولة الأولى تحت أي من السياسات الثلاث. والسياسة هناك للرموز الخطية التي تحتاجها فعلًا

كيف تثبت أن تحويل bitmap لا يخترع بكسلات

عدّ الحبر على الجانبين واطلب أن يتطابق المجموعان. فالتدوير تبديل مواقع للبكسلات لا أكثر، فعدد الخلايا غير الصفرية في المخرج يجب أن يساوي عددها في المدخل. وحين بلّغ تدوير قناع في مسار إعادة المحاولة الخارجية عن 4800 خلية مضبوطة داخلة و7439 خارجة، كانت تلك المقارنة المنفردة كافية لإدانة التحويل دون قراءة سطر من هندسته

والسبب دنيوي ويستحق أن يُحمل قاعدة. فالمصفوفة الديناميكية المضبوطة الحجم بـ SetLength غير مضمون وصولها مصفرة إذا كانت ناتج دالة يسلك مسارًا لا يمسحه وقت التشغيل، فالخلايا التي لا يكتبها التدوير تحمل حينها أي بايتات كانت هناك قبلًا. وبعض تلك البايتات البالية غير صفري، وغير الصفري معناه حبر. والإصلاح سطر واحد، FillChar(Result[0], N, 0) قبل أن تجري حلقة التبديل، والانضباط الذي يوحي به أوسع: كل دالة تعيد قناعًا أو ذاكرة bitmap ينبغي أن تمسح مخرجها صراحة بدل الاعتماد على دلالات التخصيص

وما جعل العيب ينجو من ثلاثة إصدارات أكثر إثارة من العيب نفسه. ما إن نقل QR معالجة اتجاهه إلى داخل المدقق، حتى توقف QR عن تمرين تدوير القناع الخارجي كليًا، ولم يبق مستهلك واحد لذلك المسار من الكود سوى Code 39. والبنية المشتركة تخفي أخطاء كهذه طوال الوقت: تغطية ميزة تجعل مسارًا يبدو مختبرًا بينما الميزة التي تعتمد عليه فعلًا لا تملك شيئًا منها. وكل مسار تتوقف ميزة جديدة عن استخدامه يحتاج اختبارًا ما زال يستخدمه

قراءة النتائج بإحداثيات الصفحة

كل قيمة هندسية ينتجها المدقق معبرًا عنها في إطار إحداثيات bitmap المحاولة، والمستدعي يحتاجها في فضاء المستخدم في PDF. وتجري ذلك التحويل على مرحلتين: ألغِ ربع الدورة الذي طبقته إعادة المحاولة، ثم ألغِ تحويل التصيير الذي حوّل فضاء المستخدم على bitmap. وما يصل إلى THPDFDecodedBarcode صندوق إحاطة موازٍ للمحاور في فضاء المستخدم، بـ Left وBottom وRight وTop وفق اصطلاح PDF بأن Y يتصاعد نحو الأعلى، مع OrientationDegrees عكس عقارب الساعة

خط معالجة الباركود في HotPDF من bitmap الصفحة المصيَّر عبر التعيينة إلى مصفوفة وحدات منطقية، وتطبيع D4، وكشف الإصدار بالقسمة، وفك ترميز Reed-Solomon، ثم تحويل الإحداثيات ذو المرحلتين الذي يلغي ربع دورة إعادة المحاولة وتحويل التصيير قبل أن تنشر THPDFDecodedBarcode قيم Left وBottom وRight وTop وOrientationDegrees في فضاء المستخدم
تطبيع QR داخل المدقق يجعل الصفحات تنحل من المحاولة الأولى، بينما يحوّل تحويل الإحداثيات ذو المرحلتين نتائج bitmap المحاولة إلى صناديق موازية للمحاور في فضاء المستخدم

خطئ في اتجاه ذلك التحويل الثاني والعرض سيئ: يفك النص ترميزه بإتقان، لكن الصندوق الذي ترسمه لطبقة مراجعة يهبط على الصورة المرآوية للموضع الصحيح. فكل من يبني واجهة مراجعة فوق المدقق عليه أن يتحقق مقابل عيّنة معلومة، برمز موضوع عمدًا قرب إحدى زوايا الصفحة حتى يكون محور Y المقلوب مرئيًا من نظرة. وينطبق الاستدلال نفسه على أي إحداثية تعبر حدود التصيير، ولهذا يستحق تصيير صفحة PDF إلى bitmap في Delphi أن تفهمه قبل أن تبني فوق المدقق

ما يفعله المدقق المدمج وما لا يفعله

المدقق المدمج تنفيذ محدود بلا اعتماديات، وهو صادق في حدوده بدل التدهور بصمت. يتعرف على Code 39 وQR، ويتحقق من بتات الصيغة المحمية بـ BCH ومن نمط القناع قبل أن ينشر أي بيانات، ولا يحاول استعادة الأخطاء على الرموز التالفة. فإن كان مدخلك صورة فوتوغرافية لملصق منحنٍ تحت ضوء غير متساو، فهذا صنف مشكلات مختلف ويطلب محركًا متخصصًا

// استبدل بمحركك: نفّذ IHPDFBarcodeDecoder ومرّرها إلى
// النسخة الواعية بالمدقق. لا يزال HotPDF يملك تصيير الصفحة
// والميزانيات وتحويل الإحداثيات وإزالة الازدواج
if not Pdf.DecodeLoadedPageBarcodes(PageIndex, MyDecoder, Options,
     Codes, Info) then
  case Info.Status of
    bdsBudgetExceeded:
      Log('raise MaxPixels or lower DPI: ' + string(Info.Diagnostic));
    bdsRenderError:
      Log('page did not render: ' + string(Info.Diagnostic));
    bdsDecoderError:
      Log(string(Info.DecoderName) + ' failed: ' + string(Info.Diagnostic));
  end;

THPDFBarcodeDecodeInfo هي موضع ما يكسب به خط معالجة إنتاجي قيمته. فـ RotationAttemptCount وDecoderCallCount تخبرانك هل جرت إعادة المحاولة الخارجية أصلًا، وReceivedResultCount مقابل AcceptedResultCount تفصل مدققًا لم يجد شيئًا عن عتبة ثقة رفضت كل ما وجده، وRenderedPixels مع PeakWorkingBytes ما ترسمه منحنىً حين يبدأ عمل دفعي يخبط. ومجموعة نتائج فارغة مع bdsSucceeded معناها أن الصفحة فعلًا لا تحمل رمزًا مقروءًا، وهي حقيقة تشغيلية مختلفة عن bdsBudgetExceeded

وحقول الميزانية تستحق قرارًا مدروسًا لا افتراضيًا. فـ MaxPixels وMaxWorkingBytes موجودان لأن DPI يتضاعف تربيعيًا: الانتقال من 300 إلى 600 DPI على صفحة A4 يضاعف كلفة التصيير والتخصيص الذروة أربعة أضعاف، ومدخل غير موثوق يعلن صندوق صفحة هائلًا يستطيع تحويل عمل مسح إلى حادثة نفاد ذاكرة. اضبط السقوف على ما تحتاجه أسوأ وثيقة مشروعة عندك، ثم اترك bdsBudgetExceeded يحوّل الشواذ إلى مسار أبطأ معزول

وإن كانت وثائقك تخلط ملصقات قابلة للقراءة آليًا بنص مطبوع تنوي فهرسته، فمدقق الباركود يتزاوج طبيعيًا مع محرك التعرف المغطى في OCR بمطابقة القوالب داخل HotPDF، وجانب التوليد من القصة نفسها في رسم باركودات في PDF مع HotPDF. وكلاهما يجري على البنية نفسها للتصيير والميزانيات، فخط معالجة وضع حدودًا عاقلة لأحدهما يحصل على الآخر شبه مجانًا

والتسامح مع التدوير إحدى تلك الميزات التي تكون غير مرئية حين تعمل ومستفزّة حين لا تعمل، والعبرة الهندسية تتعمم أبعد من QR: طبّع أقرب ما يمكن إلى التمثيل الدلالي، لا عند طبقة البكسلات التي ما زالت البيانات فيها تحمل كل مصادفة لكيفية التقاطها. ويشحن HotPDF هذا ضمن مكوّن HotPDF Delphi PDF، إلى جانب قطع التصيير وOCR وتحليل الصفحات التي تحتاجها خطوط المعالجة الداخلة نفسها عادة