PDFlibPas avkodar TIFF med en handskriven Object Pascal-parser i stället för en libtiff-bindning, och version 3.534.1 åtskarpade exakt där denna parser vägrar indata. BigTIFF-magivärdet 43 avvisas nu vid namn, TileOffsets och TileByteCounts vägras under taggtolkningen, och varje buffert dimensioneras med Int64-arithmetik under ett avkodningstak på 256 MiB
Felet detta stänger syns aldrig i ett labb. Det syns som en skanningsgateway som kört tyst i tre år, tills en kund skickar ett geospatialt arkiv eller en whole-slide-medicinsk bild genom den. Filen har en korrekt TIFF-header. Den kan tolkas. Resultatet är en sida med randigt brus, eller en allokering på flera gigabyte som tar ner tjänsten, och inget längs vägen förklarade någonsin indata som ogiltigt. Det är den felform man bygger emot: inte en krasch, utan ett felaktigt svar som levereras självsäkert
Varför bevisar II eller MM inte att du har en klassisk TIFF?
Eftersom byteordningsmarkören delas av båda dialekterna. Klassisk TIFF och BigTIFF öppnar båda med II eller MM, och fältet som faktiskt skiljer dem åt är den 16-bitarsmagi som kommer omedelbart efter: 42 för klassisk TIFF enligt TIFF 6.0-specifikationen, 43 för BigTIFF med sina 64-bitarsoffsets. En laddare skriven som FValidTIFF := PopWord = 42 har inte fel om klassisk TIFF, men den kollapsar två mycket olika avvisanden till en tyst boolesk flagga, så att en BigTIFF blir omöjlig att skilja från en avhuggen JPEG som någon döpt om. PDFlibPas skiljer nu på fallen och registrerar vart och ett i TPDFTIFF.LastError: en header kortare än fyra byte, en ogiltig byteordningsmarkör, magivärde 43 och alla andra magivärden ger var och en distinkt text. Biblioteket avkodar fortfarande inte BigTIFF, och att säga det rakt ut är själva poängen. Anroparen får skillnaden mellan "detta är ingen TIFF" och "detta är en TIFF vars 64-bitarsoffsetlayout den inbyggda avkodaren inte implementerar", vilket är skillnaden mellan ett supportärende du svarar på i ett meddelande och ett som blir en veckas gissande
var
Tiff: TPDFTIFF;
Page: Integer;
begin
Tiff := TPDFTIFF.Create;
try
Tiff.LoadFromFile('inbox\scan-0417.tif');
if not Tiff.ValidTIFF then
raise Exception.Create('TIFF rejected: ' + Tiff.LastError);
if Tiff.PageCount < 1 then
raise Exception.Create('TIFF carries no decodable page');
for Page := 1 to Tiff.PageCount do
Writeln(Format('page %d: %dx%d, %d spp',
[Page,
Tiff.PageInfo[Page].Width,
Tiff.PageInfo[Page].Height,
Tiff.PageInfo[Page].SamplesPerPixel]));
finally
Tiff.Free;
end;
end;
Tiles är en annan geometri, inte ytterligare en offsets-array
PDFlibPas vägrar tiled TIFF medan taggar tolkas, innan någon pixeldata röras. Den genväg som bjuder in felet är lätt att se: tag 324 (TileOffsets) och tag 325 (TileByteCounts) är arrayer av filoffsets och byteantal, strukturellt identiska med strip-arrayerna, så att peka de befintliga stripfälten på dem kostar två rader och kompilerar rent. Det är också fel. Tiles bildar ett tvådimensionellt rutnät med utfyllda kantblock, sitt eget radsteg inuti varje tile, och ingen RowsPerStrip-semantik alls, som tiled-bildsektionen i TIFF 6.0 redogör för. Att mata tile-payloads till en strip-avkodare misslyckas därför inte högt. SimpleExtract och CompDecode går igenom data med fel radsteg och producerar en bild med rätt dimensioner och fel pixlar. Den äldre koden förvärrade detta genom att behålla StripsAreTiles, ColumnsPerTile och RowsPerTile i TTIFFPage: tile-geometri registrerad av en avkodare utan någon tile-monterare bakom. I 3.534.1 höjer hanterarna för tag 324 och 325 tile-felet och överger IFD omedelbart, så att avvisandet bär ordet "tiled" i stället för att dyka upp veckor senare som ett renderingsklagomål
En dimensionsgräns är ingen minnesbudget
Att begränsa bredd och höjd till 65 535 vardera är nödvändigt och långt ifrån tillräckligt, eftersom den storhet som driver allokeringen är en produkt. RowsPerStrip * Width * SamplesPerPixel kan spilla över 32-bitarsarithmetik långt innan någon av sidorna når sin egen gräns, och även utan spill kan den namnge en allokering som ingen tjänst bör försöka. PDFlibPas beräknar radbyte i Int64 och driver tre tak tillsammans: 65 535 per dimension, 32 färgkomponenter och 256 MiB avkodade byte
const
PDFLIB_TIFF_MAX_IMAGE_DIM = 65535;
PDFLIB_TIFF_MAX_COLOR_COMPONENTS = 32;
PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES = 256 * 1024 * 1024;
// inuti TPDFTIFF.ValidatePageForDecode
BitsPerPixel := Int64(P.BitsPerSample) * P.SamplesPerPixel;
RowBytes := (Int64(P.Width) * BitsPerPixel + 7) div 8;
if (RowBytes < 1) or
(RowBytes > PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES) or
(Int64(P.Height) > PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES div RowBytes) then
Exit(False);
DecodedBytes := RowBytes * P.RowsPerStrip;
if (DecodedBytes < 1) or
(DecodedBytes > PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES) or
(DecodedBytes > MaxInt) then
Exit(False);
Tre detaljer där betyder mer än konstanterna själva. Höjdtestet är skrivet som en division i stället för en multiplikation, så att den överdimensionerade produkten aldrig bildas alls. En RowsPerStrip under 1 eller över bildhöjden normaliseras först till höjden, vilket är den single-strip-läsning TIFF 6.0 redan antyder och som stoppar en fientlig tagg från att blåsa upp stripbufferten. Och rutinen är delad: ValidatePageForDecode körs i slutet av taggtolkningen och igen vid ingången av både SimpleExtract och CompDecode, så att kod som når en avkodare direkt inte kan gå runt budgeten. Det är samma regel PDFlibPas följer när otillförlitliga PDF-objektgrafer tolkas, eftersom en gräns som drivs vid en av tre dörrar inte är en gräns
Vad måste en anropare kontrollera innan PageInfo läses?
Kontrollera ValidTIFF först, sedan PageCount, och först därefter indexera PageInfo. En avvisad fil kan lämna PageCount på noll, och GetPageInfo svarar på ett index utanför intervallet med en oinitierad TTIFFPage-post, så att en felsökväg som läser upplösning eller sampelantal på vägen till att rapportera felet slutar med att läsa brus. Version 3.534.1 rättade båda anroparna inuti biblioteket: bildimportvägen läser XRes och YRes endast inuti den giltiga grenen, och TPDFlib.GetImagePageCount kräver ValidTIFF i stället för att lita på ett icke-noll sidantal på egen hand. Nedströms är Options-argumentet till AddImageFromFile sidnumret, ettbaserat, för en flersidig TIFF, så att GetImagePageCount måste vara pålitligt innan loopen startar snarare än efteråt. Noll sidor är nu ett riktigt svar som betyder "här finns inget som går att avkoda", inte en olycka från en tidig retur, vilket spelar störst roll när du sammanställer och flätar ihop duplex-skannbatcher och ett tyst feltolkat blad skulle hamna på fel position
var
Pdf: TPDFlib;
Pages, I, ImageID: Integer;
begin
Pdf := TPDFlib.Create;
try
Pages := Pdf.GetImagePageCount('inbox\scan-0417.tif');
if Pages < 1 then
Exit; // fel header, BigTIFF, tiled-layout eller över budget
Pdf.NewDocument;
for I := 1 to Pages do
begin
Pdf.NewPage;
ImageID := Pdf.AddImageFromFile('inbox\scan-0417.tif', I);
if ImageID > 0 then
begin
Pdf.SelectImage(ImageID);
Pdf.DrawImage(0, 0, 595, 842);
end;
end;
Pdf.SaveToFile('scan-0417.pdf');
finally
Pdf.Free;
end;
end;
Bygga avkodaren eller länka libtiff?
PDFlibPas behåller den inbyggda avkodaren, och den avgörande faktorn är plattformsräckvidd snarare än författarskap. Ungefär 1 873 rader Object Pascal kompilerar överallt där kompilatorn tar vägen: Win32, Win64, macOS, iOS, Android och FPC på Linux. libtiff 4.7.1 är runt 30 000 rader C utspridda över 34 tif_*.c-översättningsenheter, och de förbyggda objektfiler som finns i dag täcker endast Windows. Att anta det skulle byta fullständig TIFF-täckning mot en lista över plattformar med stöd som krymper till de maskiner som kan köra C-verktygskedjan, plus ett länkningspass ingen ännu har gått igenom
Vad det kostar är värt att säga utan försköning. Den inbyggda avkodaren hanterar det som arbete med inskannade dokument faktiskt producerar: CCITT Group 3 endimensionellt och tvådimensionellt, Group 4, LZW, Deflate, PackBits och JPEG-i-TIFF, över WhiteIsZero, BlackIsZero, RGB, palett- och CMYK-fotometri med Predictor 1 och 2. Dessa payloads stämmer överens med PDF-filtrena i ISO 32000-1 §7.4.4 och §7.4.6, vilket är därför TIFF-front-enden väger så tungt i en skanningspipeline. Det den inte hanterar är BigTIFF, tiles, flyttals-Predictor 3, PixarLog och SGILog, gammaldags JPEG-komprimering 6 och sub-IFD-pyramider. Sedan 3.534.1 är vart och ett av dessa ett namngivet avvisande i stället för en fel bild, och biblioteket förvarar en skriftlig trigglista för att öppna libtiff-beslutet på nytt:
- en kund rapporterar en BigTIFF-fil och behöver inbyggt stöd i stället för ett konverteringssteg
- en kund rapporterar tiled TIFF från medicinska, GIS- eller industriella källor och behöver den avkodad på plats
- en kund rapporterar flyttals-Predictor 3 TIFF
- ett publicerat säkerhetshål landar på de inbyggda CCITT- eller LZW-avkodningsvägarna
- plattformsoberoende-argumentet slutar gälla, antingen för att macOS-, iOS- och Android-stödet släpps, eller för att en återanvändbar libtiff-integration redan täcker macOS och Linux
Själva migreringen är avgränsad snarare än hypotetisk: en USE_LIBTIFF-villkorlig skulle hålla den publika ytan hos TPDFTIFF intakt, dirigera LoadFromStream genom TIFFClientOpen med ström-callbacks, och lämna Pascal-parsern som icke-Windows-fallback. Tills en av dessa triggers faktiskt utlöses, kostar det inget en kund kan känna att underhålla två avkodare och en fördubblad testmatris. Att skjuta upp en kostnad med flyktvägen redan nedskriven är en annan sak än att ignorera den
Där detta lämnar en pipeline för inskannade dokument
Behandla TPDFTIFF som en grind snarare än en konverterare. Ladda filen, läs ValidTIFF, och logga LastError ordagrant när den är falsk, eftersom den strängen nu är den kortaste vägen från en fältrapport till en diagnos. Filer som faller vid grinden förblir återhämtningsbara genom att konverteras uppströms, vilket är det praktiska svaret för BigTIFF- och tiled-källor i dag. För indata helt utanför TIFF tar PDFlibPas en separat väg genom sin AVIF-, HEIF- och JPEG XL-bildindata-väg, så att frågan om vilken avkodare som äger vilket format förblir explicit i stället för framväxande
Allt detta ligger bakom det vanliga bild-API:t, så att en dokumentpipeline får den skarpare gränsen utan att ändra en rad anropskod utöver att kontrollera sidantalet som den redan borde ha kontrollerat. Om du väger en inbyggd TIFF-till-PDF-väg för Delphi eller C++Builder, är hela komponenten och dess bildhantering dokumenterad på sidan PDF Library for Delphi