Techninis straipsnis

Kodėl kai kurie PDF objektų srautai Delphi kalboje iškoduojami į šiukšles

PDF objektų srautas gali išsiplėsti be klaidos, tačiau grąžinti triukšmą, jei praleistas ISO 32000-1 Predictor atkūrimas/DecodeParms/Predictor

FlateDecode vien savaime nepakanka

FlateDecode atlieka tik DEFLATE išskleidimą, o Predictor aprašo papildomą transformaciją srauto /DecodeParms žodyne/DecodeParms

Predictor pakeisti baitai atrodo beveik tinkami, todėl naivus analizatorius sugenda tik pasiekęs negalimą PDF žetoną

Ką iš tikrųjų daro PDF Predictor parametras

/Predictor reikšmė 1 reiškia jokio prognozavimo, 2 parenka TIFF, o 10–15 parenka PNG atkūrimą kartu su /Colors, /BitsPerComponent ir /Columns geometrija/Predictor/DecodeParms/Colors/BitsPerComponent/Columns

TIFF Predictor 2 atkuria skirtumą eilutėje, o PNG kiekvienoje eilutėje skaito filtro žymą ir taiko vieną iš penkių filtrų/Predictor/Predictor

Kodėl objektų srautuose praleistas Predictor lieka nepastebėtas

Objektų srautai gali supakuoti katalogą, /OutputIntents ar /Metadata į /ObjStm, todėl praleistas atkūrimas paslepia objektus be klaidos/ObjStm/OutputIntents/Metadata/Predictor

PDFiumPas iki v2.16.0 išplėsdavo /Predictor 12 srautą į skirtuminius baitus, kurių struktūros skaitytuvas negalėjo analizuoti/PredictorPdfExpandObjectStreams/OutputIntents/Metadatastraipsnį apie objektų ir xref srautų validavimą su PDFiumPas

PNG ir TIFF Predictor eilučių atkūrimas Pascal kalboje

PdfApplyPredictor apskaičiuoja eilutės plotį ir pikselio baitų plotį pagal /Columns, /Colors ir /BitsPerComponentPdfApplyPredictor/Predictor/Predictor

function PdfApplyPredictor(const Src: TBytes;
  Predictor, Colors, Bpc, Columns: Integer): TBytes;
var
  RowLen, Bpp, R, I: Integer;
begin
  Result:= Src;
  if Predictor< 2 then
    Exit;                                   // 1 = no prediction, nothing to undo
  if Colors<= 0 then Colors:= 1;
  if Bpc<= 0 then Bpc:= 8;
  if Columns<= 0 then Columns:= 1;
  if (Colors> 64)or (Bpc> 32)or (Columns> 1 shl 24) then
    Exit;                                   // reject hostile row geometries
  RowLen:= (Columns* Colors* Bpc+ 7) div 8;  // ceil(), per ISO 32000-1 Table 8
  Bpp:= (Colors* Bpc+ 7) div 8;
  if Predictor= 2 then
  begin
    if Bpc<> 8 then
      Exit;                                 // only the 8-bit layout is reconstructed
    Result:= Copy(Src, 0, Length(Src));
    R:= 0;
    while R+ RowLen<= Length(Result) do
    begin
      for I:= R+ Bpp to R+ RowLen- 1 do
        Result[I]:= Byte(Result[I]+ Result[I- Bpp]);
      Inc(R, RowLen);
    end;
    Exit;
  end;
  // Predictor >= 10 falls through to PNG row-filter reconstruction below
end;
// Continues inside PdfApplyPredictor once Predictor>= 10 (PNG row filters).
// Rows:= Length(Src) div (RowLen+ 1); each row is a 1-byte filter tag
// followed by RowLen data bytes, decoded left to right.
for R:= 0 to Rows- 1 do
begin
  SrcOfs:= R* (RowLen+ 1);
  DstOfs:= R* RowLen;
  Tag:= Src[SrcOfs];
  Inc(SrcOfs);
  for I:= 0 to RowLen- 1 do
  begin
    if I>= Bpp then A:= Result[DstOfs+ I- Bpp] else A:= 0;   // byte to the left
    if R> 0 then B:= Result[DstOfs+ I- RowLen] else B:= 0;   // byte above
    case Tag of
    1: Result[DstOfs+ I]:= Byte(Src[SrcOfs+ I]+ A);            // Sub
    2: Result[DstOfs+ I]:= Byte(Src[SrcOfs+ I]+ B);            // Up
    3: Result[DstOfs+ I]:= Byte(Src[SrcOfs+ I]+ (A+ B) div 2); // Average
    // Paeth (tag 4) adds whichever of A, B or the byte above-left sits
    // closest to the linear predictor A+ B- C; tag 0 (None) copies the
    // filtered byte through unchanged
    else Result[DstOfs+ I]:= Src[SrcOfs+ I];
    end;
  end;
end;

Kas pasikeitė PDFiumPas v2.16.0

PdfReadAndDecodeStream taiko atkūrimą tik vienam FlateDecode filtrui ir saugiai perskaito Predictor parametrus tame pačiame žodynePdfReadAndDecodeStream/Filter/Predictor/Colors/BitsPerComponent/ColumnsPdfDictRefNumsusijusį straipsnį apie saugų PDF žodynų analizavimą

// Inside PdfReadAndDecodeStream, right after PdfInflate() has already run:
if PdfFilterIsPureFlate(DictTxt) then
begin
  Inflated:= PdfInflate(Raw);
  Predictor:= PdfDictRefNum(Data, DS, DE, 'Predictor');
  if Predictor>= 2 then
  begin
    PColors:= PdfDictRefNum(Data, DS, DE, 'Colors');
    PBpc:= PdfDictRefNum(Data, DS, DE, 'BitsPerComponent');
    PColumns:= PdfDictRefNum(Data, DS, DE, 'Columns');
    Result:= PdfApplyPredictor(Inflated, Predictor, PColors, PBpc, PColumns);
  end
  else
    Result:= Inflated;
end;

Po pataisos objektų srautai atkuriami teisingai, o per didelės /Colors, /BitsPerComponent ir /Columns reikšmės atmetamos/Predictor/OutputIntents/MetadataPdfApplyPredictor/Colors/BitsPerComponent/Columns

var
  Pdf: TPdf;
  Report: TPdfAValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'incoming.pdf';
    Pdf.Active := True;
    Report := Pdf.ValidatePdfA;
    if not Report.IsCompliant then
      LogNonCompliance(Report); // caller-supplied handler
  finally
    Pdf.Free;
  end;
end;

Verta žinoti ribas

TIFF atkūrimas apima tik 8 bitų komponentus, o PNG atkūrimas rekonstruoja visas penkias apibrėžtas filtro rūšis/Predictor/BitsPerComponent/Predictor

Vietinis PDFium atvaizdavimas jau tinkamai rekonstruoja tokius duomenis, tačiau baitų lygio validavimo ir pasirašymo kodas turi atlikti tą patįPDFiumPas