Τεχνικό Άρθρο

Custom Huffman πίνακες JBIG2 σε καθαρό Pascal decoder

Το PDFlibPas έκδοση 3.539.22 αποκωδικοποιεί εγγενώς custom Huffman πίνακες JBIG2: ο καθαρά Pascal decoder στο PDFlibJBIG2.pas αναλύει το segment Tables (τύπος 53), αναθέτει canonical prefix codes με τη σειρά των γραμμών του πίνακα όπως απαιτεί το ITU-T T.88 Annex B.3, καταναλώνει τις αναφορές custom πινάκων με τη σειρά των selectors για symbol dictionaries και text regions, και οριοθετεί κάθε ανάγνωση από το δηλωμένο μήκος segment και όχι από όσα bytes τυχαίνει να ακολουθούν

Το αρχείο που επέβαλε αυτή τη δουλειά ήταν στην επιφάνειά του αναπάντεχα συνηθισμένο. Ένα σαρωμένο συμβόλαιο, συμπιεσμένο JBIG2 με Huffman symbol coding αντί για τον πολύ πιο συνηθισμένο αριθμητικό κωδικοποιητή, και με τον encoder να στέλνει δικούς του code tables αντί για τους standard πίνακες B.1 έως B.15. Δύο ανεξάρτητοι decoders διαφωνούσαν στα refinement pixels του, και ο τότε decoder του PDFlibPas έβγαζε κείμενο που έμοιαζε να πέρασε από τεμαχιστή: θραύσματα glyph μετατοπισμένα λίγα pixels, μια στήλη από κάθε χαρακτήρα χαμένη. Τίποτα δεν σήκωνε error. Αυτού του σχήματος είναι το bug που επιβιώνει επί χρόνια, γιατί ένας decoder που απορρίπτει αρχείο παίρνει support ticket, ενώ ένας decoder που το αποδίδει λίγο λάθος παίρνει έναν πελάτη που υποθέτει ότι το scan ήταν κακό

Τι περιέχει στην πραγματικότητα ένα segment Tables JBIG2;

Ένα segment Tables είναι μια συμπαγής περιγραφή ενός Huffman πίνακα: ένα byte flags, δύο όρια signed 32-bit, και μετά μια σειρά ζευγών (μήκος prefix, μήκος range) που τεμαχίζουν το διάστημα ανάμεσα στα όρια, όπως προβλέπεται στο T.88 §7.4.13 και στο Annex B.2. Το bit 0 του byte flags είναι το HTOOB και λέει αν ο πίνακας έχει out-of-band code. Τα bits 1 έως 3 συν ένα δίνουν το HTPS, τον αριθμό των bits που γράφει κάθε μήκος prefix· τα bits 4 έως 6 συν ένα δίνουν το HTRS, το πλάτος κάθε πεδίου μήκους range. Το bit 7 είναι δεσμευμένο, και το PDFlibPas απορρίπτει το segment αν είναι set αντί να μαντεύει τι εννοούσε μια μελλοντική αναθεώρηση. Τα HTLOW και HTHIGH ακολουθούν ως signed 32-bit integers, που είναι το πρώτο σημείο όπου μπορεί να χαλάσει ένας decoder: το να τα διαβάσεις ως unsigned κάνει έναν πίνακα του οποίου το κάτω όριο είναι αρνητικό, που είναι απόλυτα φυσιολογικό για delta-coded πλάτη symbol, να φαίνεται ότι ξεκινά από τα τέσσερα δισεκατομμύρια. Κάθε πεδίο περνάει από ένα τοπικό helper ReadField που ελέγχει το αίτημα απέναντι στη bit θέση όπου τελειώνουν τα δεδομένα του segment πριν αγγίξει τον reader, γιατί ένας πίνακας που διαβάζει πέρα από το segment του θα κατανάλωνε την επικεφαλίδα του επόμενου segment ως μήκη prefix

Διάταξη segment Tables πίσω από την αποκωδικοποίηση custom Huffman JBIG2 στο PDFlibPas: ένα byte flags με HTOOB, HTPS και HTRS συν ένα δεσμευμένο bit που απορρίπτεται, signed όρια HTLOW και HTHIGH, μια σειρά ζευγών μήκους prefix και range, και οι escape γραμμές jbig2HuffmanLOW, μια σταθερή πάνω γραμμή 32-bit και το προαιρετικό jbig2HuffmanOOB
Κάθε πεδίο του segment διαβάζεται μέσα από helper με έλεγχο ορίων γιατί ένας πίνακας που διαβάζει πέρα από το δηλωμένο τέλος του θα κατανάλωνε την επικεφαλίδα του επόμενου segment ως μήκη prefix, και οι sentinel escape γραμμές ταιριάζουν με τους ενσωματωμένους standard πίνακες
// TCodeTableSegment.readSegment, PDFlibJBIG2.pas
EndBit := (Int64(decoder.reader.bytePointer) +
  segmentHeader.getSegmentDataLength) * 8;
Flags := ReadField(8);
if (Flags and $80) <> 0 then
  raise EJBIG2DecodeError.Create('reserved custom Huffman table flag');
PrefixBits := ((Flags shr 1) and 7) + 1;   // HTPS
RangeBits  := ((Flags shr 4) and 7) + 1;   // HTRS
LowValue   := Integer(ReadField(32));      // HTLOW με πρόσημο
HighValue  := Integer(ReadField(32));      // HTHIGH με πρόσημο
if LowValue >= HighValue then
  raise EJBIG2DecodeError.Create('invalid custom Huffman range bounds');
CurrentValue := LowValue;
while CurrentValue < HighValue do
begin
  PrefixLength := ReadField(PrefixBits);
  RangeLength  := ReadField(RangeBits);
  if RangeLength > 32 then
    raise EJBIG2DecodeError.Create('invalid custom Huffman range length');
  AddLine(CurrentValue, PrefixLength, RangeLength);
  Inc(CurrentValue, Int64(1) shl RangeLength);
end;
AddLine(LowValue - 1, ReadField(PrefixBits), jbig2HuffmanLOW);
AddLine(HighValue,   ReadField(PrefixBits), 32);
if (Flags and 1) <> 0 then
  AddLine(0, ReadField(PrefixBits), jbig2HuffmanOOB);

Οι δύο γραμμές που προστίθενται μετά τον βρόχο είναι οι escape γραμμές από το Annex B.2: η κάτω γραμμή range ξεκινά από HTLOW μείον ένα και μετρά προς τα κάτω, η πάνω γραμμή range ξεκινά από το HTHIGH με σταθερό range 32-bit, και η προαιρετική γραμμή OOB δεν έχει καθόλου τιμή. Το PDFlibPas τις σημειώνει με τους sentinel μήκεις range jbig2HuffmanLOW ($FFFFFFFD) και jbig2HuffmanOOB ($FFFFFFFE), την ίδια σύμβαση που χρησιμοποιούν οι δεκαπέντε ενσωματωμένοι standard πίνακές του, ώστε ο βρόχος αποκωδικοποίησης να μην νοιάζεται αν ένας πίνακας ήρθε από την προδιαγραφή ή από το αρχείο

Γιατί πρέπει τα prefix codes να ανατίθενται με τη σειρά των γραμμών του πίνακα;

Επειδή ο encoder δεν γράφει ποτέ τους κωδικούς. Ένα segment Tables JBIG2 κουβαλάει μόνο μήκη prefix, και οι δύο πλευρές ανακατασκευάζουν τα πραγματικά bit patterns με την canonical διαδικασία του Annex B.3: μετράς πόσες γραμμές έχουν κάθε μήκος, αναθέτεις πρώτα κωδικούς μήκους ένα, μετά κάνεις shift αριστερά και συνεχίζεις, και μέσα σε ένα μήκος μοιράζεις κωδικούς με τη σειρά που εμφανίζονται οι γραμμές. Κάθε απόκλιση από εκείνη τη σειρά παράγει σιωπηλά έναν διαφορετικό πίνακα. Ο decoder δεν θα το καταλάβει, γιατί κάθε bit pattern που παράγει εξακολουθεί να είναι έγκυρος prefix code, απλώς όχι αυτός που χρησιμοποίησε ο encoder, και η έξοδος είναι ένα πειστικής όψης bitmap συναρμολογημένο από λάθος σύμβολα

Ανάθεση canonical prefix codes στον JBIG2 decoder του PDFlibPas: στο segment φτάνουν μόνο μήκη prefix, ένα σταθερό counting sort πάνω σε Counts, Starts και Positions κρατά τη σειρά δήλωσης μέσα σε κάθε μήκος, οι κωδικοί μήκους ένα μοιράζονται πρώτα και ο κωδικός κάνει shift αριστερά ανά μήκος, με την υπερσυνδρομή να απορρίπτεται από τον έλεγχο Kraft
Ο encoder δεν γράφει ποτέ τα bit patterns, οπότε κάθε απόκλιση από τη σειρά των γραμμών χτίζει σιωπηλά έναν διαφορετικό αλλά έγκυρο prefix code και η έξοδος μοιάζει πειστική· οι γραμμές μηδενικού μήκους πέφτουν ως αχρησιμοποίητες και τα prefix πάνω από 32 bits απορρίπτονται
// THuffmanDecoder.buildTable, PDFlibJBIG2.pas
FillChar(Counts, SizeOf(Counts), 0);
for I := 0 to length - 1 do
begin
  if table[I].prefixLen > 32 then
    raise EJBIG2DecodeError.Create(
      'Huffman prefixes longer than 32 bits are not supported');
  Inc(Counts[table[I].prefixLen]);
end;
Active := 0;
Code := 0;
for Bits := 1 to 32 do
begin
  Starts[Bits]    := Active;
  Positions[Bits] := Active;
  Inc(Active, Counts[Bits]);
  if Code + UInt64(Counts[Bits]) > (UInt64(1) shl Bits) then
    raise EJBIG2DecodeError.Create('oversubscribed Huffman prefix codes');
  Code := (Code + UInt64(Counts[Bits])) shl 1;
end;
SetLength(Result, Active + 1);
for I := 0 to length - 1 do            // σταθερό: η αρχική σειρά κρατείται
  if table[I].prefixLen > 0 then       // μέσα σε κάθε μήκος prefix
  begin
    Result[Positions[table[I].prefixLen]] := table[I];
    Inc(Positions[table[I].prefixLen]);
  end;
Code := 0;
for Bits := 1 to 32 do
begin
  for I := Starts[Bits] to Positions[Bits] - 1 do
  begin
    Result[I].prefix := Cardinal(Code);
    Inc(Code);
  end;
  Code := Code shl 1;
end;
Result[Active].rangeLen := jbig2HuffmanEOT;

Το THuffmanDecoder.buildTable είναι counting sort και όχι comparison sort για έναν λόγο: ένα πέρασμα μέτρησης πάνω σε Counts, Starts και Positions είναι σταθερό εκ κατασκευής, οπότε γραμμές ίσου μήκους prefix καταλήγουν στο αποτέλεσμα με τη σειρά που δηλώθηκαν, που είναι ακριβώς η ταξινόμηση με βάση την οποία το Annex B.3 αναθέτει κωδικούς. Γραμμές με μήκος prefix μηδέν πέφτουν πριν την ανάθεση κωδικών, επειδή το B.3 τις ορίζει ως αχρησιμοποίητες και όχι ως κωδικούς ενός bit. Δύο guards κάθονται στον ίδιο βρόχο. Ο έλεγχος υπερσυνδρομής πιάνει πίνακα του οποίου τα μήκη διεκδικούν περισσότερους κωδικούς απ' όσους χωράει ένας prefix code τέτοιου βάθους, που είναι η ανισότητα Kraft εκφρασμένη ως σύγκριση integers· χωρίς αυτόν, ένας εχθρικός πίνακας παράγει κωδικό που ταιριάζει δύο γραμμές και ο decoder διαλέγει όποια σκανάρει πρώτη. Το ανώτατο όριο 32-bit υπάρχει επειδή το prefix είναι Cardinal και ο matcher στο decodeInt συσσωρεύει bits σε έναν. Το T.88 επιτρέπει μεγαλύτερα prefix στο χαρτί, το PDFlibPas τα απορρίπτει κατ' όνομα, και δεν έχει δει ποτέ κανείς πραγματικό encoder να εκπέμπει ένα. Η αριθμητική των τιμών χρειάζεται την ίδια φροντίδα με την αριθμητική των κωδικών: το THuffmanTable.val είναι Int64, και η κάτω γραμμή range αποκωδικοποιείται ως val - readBits(32), ένα offset unsigned 32-bit που αφαιρείται από HTLOW μείον ένα. Με ενδιάμεσα Integer η αφαίρεση κάνει wrap, και η τυλιγμένη τιμή γίνεται μετά δεκτή ως πλάτος symbol. Η διαδρομή 64-bit υπολογίζει την πραγματική τιμή, την ελέγχει απέναντι στο signed εύρος 32-bit, και πετάει αν δεν χωράει, που μετατρέπει μια σιωπηλή διαφθορά σε ρητή άρνηση

Γιατί δεν ενεργοποιούνταν ποτέ οι custom πίνακες πριν το 3.539.22;

Δύο ελαττώματα κρύβονταν το ένα το άλλο. Το πρώτο ήταν bug setter μίας γραμμής: το TTextRegionHuffmanFlags.setFlags δεχόταν το όρισμά του υπό το ίδιο όνομα με το πεδίο που αποθήκευε, οπότε το Self.flagsAsInt := flagsAsInt ανέθετε το μη αρχικοποιημένο πεδίο στον εαυτό του και κάθε selector διαβαζόταν πίσω ως μηδέν, που έστελνε text regions που ζητούσαν custom πίνακες στους standard πίνακες F, H και K. Το δεύτερο ελάττωμα σήμαινε ότι η διόρθωση μόνο του πρώτου θα παρήγε ακόμα κατεστραμμένα σύμβολα. Όταν ένα Huffman symbol dictionary αποθηκεύει τα σύμβολά του ως μη συμπιεσμένο συλλογικό bitmap, το τελευταίο byte κάθε σειράς είναι μερικό, και ο παλιός βρόχος αντιγραφής μεταχειριζόταν το padding, που κρατά τον αριθμό των έγκυρων bits, ως τη θέση του χαμηλότερου έγκυρου bit· μια σειρά πλάτους 63 pixels αντέγραφε ένα bit από το τελικό της byte αντί για επτά. Ο διορθωμένος βρόχος τρέχει for bitPointer := 7 downto ((8 - padding) and 7), και συνθετικά fixtures στα πλάτη 7-bit και 9-bit καρφώνουν και τις δύο πλευρές του ορίου byte. Με τους selectors να διαβάζονται πλέον σωστά, οι πίνακες μοιράζονται με τη σειρά που τους απαριθμεί η προδιαγραφή, που το T.88 §7.4.3.1.2 καθηλώνει για text regions ως FS, DS, DT, RDW, RDH, RDX, RDY και RSIZE και το §7.4.2.1.1 καθηλώνει για symbol dictionaries ως DH, DW, BMSIZE και AGGINST. Κάθε selector δύο bits σημαίνει standard πίνακα 0 ή 1, reserved για 2 σε πεδία με μόνο δύο standard πίνακες, και custom για 3, και κάθε custom επιλογή καταναλώνει το επόμενο segment Tables ανάμεσα στα referred-to segments με τη σειρά αναφοράς. Το NextCustomHuffmanTable κάνει ακριβώς αυτή τη βόλτα και πετάει missing custom Huffman table reference όταν μια region αναφέρεται σε λιγότερους πίνακες απ' όσους ζητούν οι selectors της. Μία ακόμα γραμμή ανήκει στο ίδιο fix: ένα Huffman symbol dictionary του οποίου τα input και new symbols αθροίζονται σε ένα υπολογίζει μήκος symbol code μηδέν από τον τύπο log2, ενώ η παραλλαγή Huffman της μορφής γράφει κάθε symbol ID με τουλάχιστον ένα bit, οπότε το if sdHuffman and (symbolCodeLength = 0) then symbolCodeLength := 1 στο TSymbolDictionarySegment κρατά τη διαδρομή refinement και aggregate από το να διαβάζει μηδέν bits ανά symbol ID

Τι εγγυάται το όριο του segment;

Το PDFlibPas μεταχειρίζεται το μήκος δεδομένων segment σε κάθε επικεφαλίδα ως σύμβαση που και οι δύο κατευθύνσεις οφείλουν να τιμήσουν: ένα segment δεν μπορεί να διαβάσει πέρα από το δηλωμένο τέλος του, και δεν μπορεί να τελειώσει νωρίτερα αφήνοντας την επόμενη επικεφαλίδα σε απρόβλεπτο offset. Οι κανόνες που βγαίνουν από αυτή τη σύμβαση είναι καθένας μικρός. Ένα μήκος δεδομένων με το bit 31 set είναι ο δείκτης άγνωστου μήκους του T.88 §7.2.7, και το handleSegmentDataLength τον αντιστοιχίζει σε αρνητική τιμή που το readSegments απορρίπτει ευθέως αντί να σαράρει μπροστά για τερματισμό. Κάθε referred-to segment number πρέπει να είναι μικρότερο από τον αριθμό του τρέχοντος segment και να υπάρχει ήδη, οπότε μια μπροστινή ή κρεμασμένη αναφορά αποτυγχάνει πριν κάποια region προσπαθήσει να την επιλύσει. Τα END_OF_PAGE και END_OF_FILE πρέπει να δηλώνουν μηδέν bytes δεδομένων. Ένα segment Profiles (τύπος 52) κουβαλάει έναν μετρητή 32-bit ακολουθούμενο από τόσους identifiers 32-bit και κανένα pixel, οπότε ελέγχεται ως 4 συν 4 επί τον μετρητή απέναντι στο δηλωμένο μήκος, παραλείπεται, και κρατιέται στη λίστα segments μόνο ώστε επόμενα segments να μπορούν ακόμα να αναφέρονται σε αυτό με τον αριθμό του. Ένας άγνωστος profile identifier δεν είναι άγνωστη κωδικοποίηση, και η μεταχείρισή του ως τέτοια θα απέρριπτε αρχεία που αποκωδικοποιούνται μια χαρά

// TJBIG2StreamDecoder.readSegments, PDFlibJBIG2.pas
DataLength := segmentHeader.getSegmentDataLength;
if DataLength < 0 then
  raise EJBIG2DecodeError.Create(Context +
    'unknown or oversized segment length is not supported');
if DataLength > Length(reader.Data) - reader.bytePointer then
  raise EJBIG2DecodeError.Create(Context + 'truncated segment data');
DataEnd := reader.bytePointer + DataLength;
for I := 0 to noOfReferredToSegments - 1 do
  if (referredToSegments[I] >= segmentHeader.getSegmentNumber) or
     (findSegment(referredToSegments[I]) = nil) then
    raise EJBIG2DecodeError.Create(Context + 'invalid segment reference');
// ... δημιουργία του segment object για αυτόν τον τύπο ...
reader.SegmentEnd := DataEnd;
segment.readSegment;
if reader.bytePointer > DataEnd then
  raise EJBIG2DecodeError.Create(Context +
    'decoded data exceeds declared segment length');
if reader.bytePointer < DataEnd then
begin
  reader.bytePointer := DataEnd;   // το MMR μπορεί να αφήσει το EOFB χωρίς ανάγνωση
  reader.bitPointer := 7;
end;

Η ουρά εκείνου του βρόχου είναι όπου πήγε στραβά μια προηγούμενη έκδοση του decoder σε MMR-coded regions. Ένας MMR decoder ξέρει ότι τελείωσε όταν παραχθεί το τελευταίο pixel της τελευταίας σειράς, που μπορεί να συμβεί πριν έχει καταναλώσει τον τερματισμό EOFB που το T.88 §6.2.5.7 τοποθετεί στο τέλος των δεδομένων. Ο παλιός κώδικας υπέθετε ότι ο reader βρισκόταν στην επόμενη επικεφαλίδα, οπότε τα εναπομείναντα bytes του τερματισμού αναλύονταν ως segment number και το stream αποτύγχανε λίγα bytes μετά με παραπλανητικό error. Τώρα το δηλωμένο τέλος κερδίζει: το διάβασμα πέρα από αυτό είναι error, το να σταματήσεις νωρίτερα είναι φυσιολογικό, και ο reader μετακινείται στο DataEnd με τον bit pointer επαναφερόμενο ώστε η επόμενη επικεφαλίδα να διαβάζεται από εκεί που είπε το αρχείο. Η ίδια πειθαρχία εμφανίζεται όπου κι αν το PDFlibPas αναλύει μη έμπιστες δομές PDF: το δηλωμένο μήκος είναι το όριο, και ο decoder δεν ψάχνει για πιο ευγενικό

Πού διαβάζει το Huffman refinement το μέγεθος του bitmap του;

Πριν ξεκινήσει ο αριθμητικός decoder, και από ένα πεδίο που υπάρχει μόνο σε λειτουργία Huffman. Όταν μια instance text region κουβαλάει refinement (RI διάφορο του μηδενός) και το SBHUFF είναι set, το T.88 §6.4.11 έχει τον decoder να διαβάζει RDW, RDH, RDX και RDY με τους επιλεγμένους πίνακές τους, μετά BMSIZE με τον πίνακα RSIZE, μετά να ευθυγραμμιστεί σε όριο byte, και μόνο τότε να τρέξει τη γενική αποκωδικοποίηση refinement πάνω σε ακριβώς BMSIZE bytes. Τα text regions σε αριθμητική λειτουργία δεν έχουν τέτοιο πεδίο, και ένας decoder που μοιράζεται ένα code path για τις δύο λειτουργίες θα το προσπεράσει, θα ξεκινήσει τον αριθμητικό decoder δύο ή περισσότερα bytes νωρίτερα, και θα κάνει refinement κάθε symbol πάνω σε σκουπίδια. Η διαδρομή symbol dictionary με REFAGG και μία instance refinement, περιγραφμένη στο §6.5.8.2.2, έχει το ίδιο πεδίο BMSIZE με τις ίδιες συνέπειες. Στο PDFlibPas το άνω όριο εκείνου του μεγέθους είναι το TStreamReader.SegmentEnd, το τέλος του τρέχοντος segment όπως το θέτει το readSegments, όχι το τέλος όλου του stream, γιατί ένα BMSIZE που μπορεί να καλυφθεί μόνο με δανεικά bytes από το επόμενο segment είναι malformed και η επικύρωσή του απέναντι στο μήκος του stream θα άφηνε τον αριθμητικό decoder να διαβάσει μέσα στην επόμενη επικεφαλίδα. Το κάτω όριο των δύο bytes αντικατοπτρίζει το αρχικό ζεύγος bytes που ο αριθμητικός decoder καταναλώνει πάντα, και μετά το refinement ο reader πηδά στο RefinementEnd ό,τι κι αν έκανε ο αριθμητικός decoder στο διάβασμα μπροστά, αφού η τελική του θέση δεν είναι η θέση του επόμενου Huffman-coded πεδίου

Όρια refinement σε λειτουργία Huffman στον JBIG2 decoder του PDFlibPas: τα RDW, RDH, RDX και RDY αποκωδικοποιούνται από τους πίνακές τους, το BMSIZE αποκωδικοποιείται από τον πίνακα RSIZE και ευθυγραμμίζεται σε byte, μετά ο αριθμητικός decoder κάνει refinement σε ακριβώς BMSIZE bytes που κρατούνται ανάμεσα στο RefinementEnd και το SegmentEnd, απορρίπτοντας μεγέθη κάτω από δύο ή πέρα από το όριο του segment
Το κάτω όριο των δύο bytes αντικατοπτρίζει το αρχικό ζεύγος που ο αριθμητικός decoder καταναλώνει πάντα, το άνω όριο είναι το τρέχον segment και όχι όλο το stream, και μετά το refinement ο reader πηδά στο RefinementEnd ό,τι κι αν διάβασε μπροστά
// Αποκωδικοποίηση text region TJBIG2Bitmap, διαδρομή Huffman refinement
RefinementSize := huffmanDecoder.decodeInt(huffmanRSizeTable).intResult;
huffmanDecoder.consumeRemainingBits;
if (RefinementSize < 2) or
   (RefinementSize > huffmanDecoder.reader.SegmentEnd -
                     huffmanDecoder.reader.bytePointer) then
  raise EJBIG2DecodeError.Create('invalid refinement bitmap size');
RefinementEnd := huffmanDecoder.reader.bytePointer + RefinementSize;
arithmeticDecoder.start;
// ... readGenericRefinementRegion ...
if huffmanDecoder.reader.bytePointer > RefinementEnd then
  raise EJBIG2DecodeError.Create('refinement data exceeds declared size');
huffmanDecoder.reader.bytePointer := RefinementEnd;
huffmanDecoder.reader.bitPointer := 7;

Τι επαληθεύτηκε, και τι εξακολουθεί να απορρίπτεται

Το δείγμα που ξεκίνησε όλα αυτά, μια εικόνα JBIG2 500 επί 473 pixels με custom πίνακες και Huffman refinement, αποκωδικοποιείται πλέον σε bitmap με μηδέν διαφορετικά pixels απέναντι σε έναν ανεξάρτητο decoder, και τα συνθετικά fixtures συλλογικού bitmap 7-bit και 9-bit παράγουν τις αναμενόμενες σειρές και στα δύο. Οι δύο ανεξάρτητοι decoders που διαφωνούσαν στο αρχικό δείγμα εξακολουθούν να διαφωνούν μεταξύ τους· το PDFlibPas ταιριάζει με τον έναν, και η ειλικρινής διατύπωση είναι ότι η εγγενής έξοδος συμφωνεί με μία ανεξάρτητη υλοποίηση και με την προδιαγραφή όπως διαβάστηκε, όχι ότι κάθε decoder στον κόσμο συμφωνεί. Η πλευρά malformed της suite καλύπτει:

  • ένα δεσμευμένο bit flags ή μια δεσμευμένη τιμή selector
  • έναν πίνακα κομμένο στη μέση μιας γραμμής
  • υπερσυνδρομημένα μήκη prefix και prefix πάνω από 32 bits
  • μια region των οποίων οι selectors ζητούν περισσότερους custom πίνακες απ' όσους αναφέρει
  • επιβεβαίωση ότι η παρωχημένη έξοδος καθαρίζεται μετά από αποτυχημένη αποκωδικοποίηση αντί να μείνει στη θέση της για να την πάρει ο caller λανθασμένα για αποτέλεσμα

Τρία όρια μένουν σκόπιμα. Η οργάνωση stream random-access, όπου όλες οι επικεφαλίδες segments προηγούνται όλων των δεδομένων segments, πετάει JBIG2 random-access organisation is not supported μόλις διαβαστούν τα flags της επικεφαλίδας αρχείου, γιατί δεν υπάρχει αντιπροσωπευτικό δείγμα για να επαληθευτεί απέναντί του και μια μισή υλοποίηση είναι χειρότερη από μια επώνυμη άρνηση. Οι custom πίνακες κόβονται στις 65.536 γραμμές και σε prefix 32-bit. Και η δημόσια είσοδος αποκωδικοποίησης, το TPLJBIG2Decoder.LoadFromByteArray, επιστρέφει το bitmap της πρώτης σελίδας με τη σειρά stream μέσω getPageAsJBIG2Bitmap(0), του πρώτου segment page-information που συναντιέται, αντί να ψάχνει association σελίδων μηδέν· τα ενσωματωμένα streams PDF αριθμούν ρουτίνα τη μοναδική τους σελίδα ως 1, και το αίτημα σελίδας 0 με association δεν θα έβρισκε τίποτα. Το κείμενο αποτυχίας καταλήγει στο TPLJBIG2Decoder.LastError, τη διαγνωστική του εσωτερικού decoder που κουβαλάει τον αριθμό, τον τύπο και το byte offset του segment της βλάβης, και δεν είναι το ίδιο πράγμα με το TPDFlib.LastErrorCode επιπέδου βιβλιοθήκης. Κανένα από αυτά δεν αγγίζει την πλευρά της κωδικοποίησης, που καλύπτεται στις σημειώσεις για τα JBIG2 encoder backends και τον τρόπο συνδέσεώς τους· η διαδρομή ανάγνωσης πρέπει να δεχτεί ό,τι αποφάσισε να εκπέμψει ο encoder κάποιου άλλου, και μοιράζεται τους κανόνες της με την υπόλοιπη στοίβα εικόνων, συμπεριλαμβανομένου του ενσωματωμένου TIFF decoder και των επώνυμων αρνήσεών του για BigTIFF και tiled διάταξη: άρνηση κατ' όνομα, ποτέ δανεισμός bytes πάνω από δηλωμένο όριο, και αριθμητική αρκετά φαρδιά ώστε ένα τυλιγμένο ενδιάμεσο να μην μπορεί να περάσει για έγκυρη απάντηση. Αν αξιολογείτε εγγενή διαδρομή ανάγνωσης JBIG2 για Delphi ή C++Builder, ο decoder και η υπόλοιπη διαχείριση εικόνων τεκμηριώνονται στη σελίδα του PDF Library for Delphi