PDFlibPas dekoodaa TIFFin itse kirjoitetulla Object Pascal -jäsentimellä libtiff-sidoksen sijaan, ja versio 3.534.1 kiristi täsmälleen ne kohdat, joissa jäsentin hylkää syötteen. BigTIFF-magia 43 hylätään nyt nimeltä, TileOffsets ja TileByteCounts torjutaan jo tagianalyysin aikana, ja jokainen puskuri mitoitetaan Int64-laskennalla 256 MiB:n dekoodauskaton alla
Korjattava vika ei näy koskaan laboratoriossa. Se ilmenee skannausyhdyskäytävänä, joka on toiminut hiljaa kolme vuotta, kunnes asiakas ohjaa sen läpi paikkatietoarkiston tai kokonaisen kudosnäytteen kuvan. Tiedostossa on kelvollinen TIFF-otsikko. Se jäsenee. Lopputuloksena on sivu raidallista kohinaa tai monigigatavuinen muistivaraus, joka kaataa palvelun, eikä matkan varrella mikään koskaan julistanut syötettä virheelliseksi. Tämä on se vian muoto, jota vastaan kannattaa suunnitella: ei kaatumista, vaan itsevarmasti toimitettu väärä vastaus
Miksi II tai MM ei todista, että sinulla on klassinen TIFF?
Koska tavujärjestysmerkintä on yhteinen kummallekin variantille. Sekä klassinen TIFF että BigTIFF alkavat II- tai MM-merkinnällä, ja kenttä, joka todella erottaa ne, on heti sen jälkeen seuraava 16-bittinen magia: 42 klassiselle TIFFille TIFF 6.0 -määrittelyn mukaisesti, 43 BigTIFFille sen 64-bittisillä siirtymillä. FValidTIFF := PopWord = 42 -muotoon kirjoitettu lataaja ei ole väärässä klassisen TIFFin suhteen, mutta se kutistaa kaksi hyvin erilaista hylkäystä yhdeksi mykäksi totuusarvoksi, jolloin BigTIFF ei eroa mitenkään joku uudelleennimetystä katkaistusta JPEGistä. PDFlibPas erottaa nyt tapaukset toisistaan ja kirjaa jokaisen TPDFTIFF.LastError-kenttään: alle neljä tavua pitkä otsikko, virheellinen tavujärjestysmerkintä, magia 43 ja mikä tahansa muu magia-arvo tuottavat kukin oman tekstin. Kirjasto ei edelleenkään dekoodaa BigTIFFiä, ja sen sanominen suoraan on juuri olennaista. Kutsuja saa selville eron "tämä ei ole TIFF" ja "tämä on TIFF, jonka 64-bittistä siirtymärakennetta sisäänrakennettu dekooderi ei toteuta" välillä, mikä on ero tukipyynnön, johon vastaa yhdellä viestillä, ja sellaisen, joka venyy viikon arvailuksi, välillä
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;
Tiilet ovat eri geometria, eivät toinen offsets-taulukko
PDFlibPas hylkää tiiletetyn TIFFin jo tagianalyysin aikana, ennen kuin mihinkään pikselidataan kosketaan. Vikaa kutsuva oikotie on helppo nähdä: tagi 324 (TileOffsets) ja tagi 325 (TileByteCounts) ovat tiedostosiirtymien ja tavumäärien taulukoita, rakenteeltaan identtiset strip-taulukoiden kanssa, joten olemassa olevien strip-kenttien osoittaminen niihin maksaa kaksi riviä ja kääntyy puhtaasti. Se on silti väärin. Tiilet muodostavat kaksiulotteisen ruudukon täytetyillä reunablokeilla, omalla rivisiirtymällään tiilen sisällä ja ei lainkaan RowsPerStrip-semantiikkaa, niin kuin TIFF 6.0:n tiled-image-luku sanoo suoraan. Tiilidataa syöttäminen strip-dekooderille ei siis epäonnista äänekkäästi. SimpleExtract ja CompDecode kulkevat datan väärällä rivisiirtymällä ja tuottavat kuvan, jonka mitat ovat oikeat mutta pikselit väärät. Vanhempi koodi pahensi tätä säilyttämällä StripsAreTiles-, ColumnsPerTile- ja RowsPerTile-kentät TTIFFPage-tietueessa: tiiligeometria tallennettuna dekooderille, jolla ei ole taustalla mitään tiilikokoonpanijaa. Versiossa 3.534.1 tagien 324 ja 325 käsittelijät nostavat tiilivirheen ja hylkäävät IFD:n välittömästi, joten hylkäys kantaa sanan "tiled" eikä puhkea viikkoja myöhemmin renderöintivalituksena
Yksi mitan rajaus ei ole muistibudjetti
Leveyden ja korkeuden rajaaminen kumpikin 65 535:een on tarpeellista mutta ei lainkaan riittävää, koska varauksia ohjaava suure on tulo. RowsPerStrip * Width * SamplesPerPixel voi ylivuottaa 32-bittisessä laskennassa hyvissä ajoin ennen kuin kumpikaan tekijä saavuttaa oman rajansa, ja ilman ylivuotoakin se voi nimetä muistivarauksen, jota mikään palvelu ei yrittäisi. PDFlibPas laskee rivitavut Int64:llä ja valvoo kolmea kattoa yhdessä: 65 535 per mitta, 32 värikomponenttia ja 256 MiB dekoodattuja tavuja
const
PDFLIB_TIFF_MAX_IMAGE_DIM = 65535;
PDFLIB_TIFF_MAX_COLOR_COMPONENTS = 32;
PDFLIB_TIFF_MAX_DECODED_IMAGE_BYTES = 256 * 1024 * 1024;
// TPDFTIFF.ValidatePageForDecode-rutiinin sisällä
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);
Kolme yksityiskohtaa siellä merkitsevät enemmän kuin vakiot itsessään. Korkeustesti on kirjoitettu jakolaskuna kertolaskun sijaan, joten liian suurta tuloa ei koskaan muodosteta lainkaan. Alle 1:n tai kuvan korkeutta suurempi RowsPerStrip normalisoidaan ensin korkeuteen, mikä on se yksittäisen stripin tulkinta, jota TIFF 6.0 jo edellyttää, ja se estää vihamielistä tagia paisuttamasta strip-puskuria. Ja rutiini on jaettu: ValidatePageForDecode suoritetaan tagianalyysin lopussa ja uudelleen sekä SimpleExtract- että CompDecode-metodien alussa, joten dekooderille suoraan päätyvä koodi ei voi kiertää budjettia. Tämä on sama sääntö, jota PDFlibPas noudattaa jäsentäessään epäluotettavia PDF-objektigraafeja, koska yhdestä kolmesta ovesta valvottu raja ei ole raja
Mitä kutsujan on tarkistettava ennen PageInfo:n lukemista?
Tarkista ensin ValidTIFF, sitten PageCount ja vasta sitten indeksoi PageInfo. Hylätty tiedosto voi jättää PageCountin nollaan, ja GetPageInfo vastaa lukualueen ulkopuoliseen indeksiin alustamattomalla TTIFFPage-tietueella, joten virhepolku, joka lukee resoluution tai näytemäärät matkallaan ilmoittaessaan viasta, lukeekin lopulta kohinaa. Versio 3.534.1 korjasi molemmat kutsujat kirjaston sisällä: kuvantuontipolku lukee XResin ja YResin vain kelvollisen haaran sisällä, ja TPDFlib.GetImagePageCount edellyttää ValidTIFFiä sen sijaan, että luottaisi yksin nollasta poikkeavaan sivumäärään. Alavirrassa AddImageFromFile-metodin Options-argumentti on yhdestä alkava sivunumero monisivuiselle TIFFille, joten GetImagePageCountin on oltava luotettava ennen silmukan alkua, eikä sen jälkeen. Nolla sivua on nyt todellinen vastaus, joka tarkoittaa "täällä ei ole dekoodattavaa", eikä varhaisen paluun sattumaa, mikä merkitsee eniten silloin, kun lajittelet ja lomitat duplex-skannaus-eriä ja yksi hiljaisesti väärin dekoodattu arkki päätyisi väärään kohtaan
var
Pdf: TPDFlib;
Pages, I, ImageID: Integer;
begin
Pdf := TPDFlib.Create;
try
Pages := Pdf.GetImagePageCount('inbox\scan-0417.tif');
if Pages < 1 then
Exit; // virheellinen otsikko, BigTIFF, tiiliasettelu tai budjetin ylitys
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;
Rakenna dekooderi vai linkitä libtiff?
PDFlibPas pitäytyy sisäänrakennetussa dekooderissa, ja ratkaiseva tekijä on alustakattavuus, ei tekijyys. Suunnilleen 1 873 Object Pascal -riviä kääntyy siellä missä kääntäjäkin kulkee: Win32, Win64, macOS, iOS, Android ja FPC Linuxilla. libtiff 4.7.1 on noin 30 000 C-riviä jaettuna 34 tif_*.c-käännösyksikköön, ja nykyään saatavilla olevat esikäännetyt objektitiedostot kattavat vain Windowsin. Sen omaksuminen vaihtaisi täydellisen TIFF-kattavuuden tuettujen alustojen listaan, joka kutistuu siihen koneeseen, jossa C-työkaluketju pyörii, sekä linkkerivaiheeseen, jonka kukaan ei ole vielä käynyt läpi
Sen hinta kannattaa sanoa ilman maalailemista. Sisäänrakennettu dekooderi käsittelee sen, mitä skannattujen dokumenttien työ todella tuottaa: CCITT Group 3 yksi- ja kaksiulotteisena, Group 4:n, LZW:n, Deflaten, PackBitsin ja JPEG-in-TIFF:n WhiteIsZero-, BlackIsZero-, RGB-, paletti- ja CMYK-fotometrioissa Predictor 1:n ja 2:n kanssa. Nämä payloadit osuvat yhteen ISO 32000-1 §7.4.4:n ja §7.4.6:n PDF-suodattimien kanssa, minkä vuoksi TIFF-etuosa kantaa niin suurta painoa skannausputkessa. Se, mitä se ei käsittele, on BigTIFF, tiilet, liukulukuinen Predictor 3, PixarLog ja SGILog, vanhan tyylin JPEG-pakkaus 6 sekä sub-IFD-pyramidit. Versiosta 3.534.1 lähtien jokainen niistä on nimetty hylkäys eikä väärä kuva, ja kirjasto pitää kirjallista laukaisinlistaa libtiff-päätöksen avaamiseksi uudelleen:
- asiakas raportoi BigTIFF-tiedoston ja tarvitsee natiivin tuen muunnosvaiheen sijaan
- asiakas raportoi tiiletetyn TIFFin lääketieteellisestä, GIS- tai teollisesta lähteestä ja tarvitsee sen dekoodattavan paikallisesti
- asiakas raportoi liukulukuisen Predictor 3 -TIFFin
- julkaistu haavoittuvuus osuu sisäänrakennettuihin CCITT- tai LZW-dekoodauspolkuihin
- alustojen välinen perustelu lakkaa pätemästä, joko siksi että macOS-, iOS- ja Android-tuki lopetetaan, tai siksi että uudelleenkäytettävä libtiff-integraatio kattaa jo macOS:n ja Linuxin
Itse siirtymä on rajattu eikä hypoteettinen: USE_LIBTIFF-ehdollinen käännös pitäisi TPDFTIFFin julkisen pinnan ennallaan, ohjaisi LoadFromStreamin TIFFClientOpenin kautta virttakutsuilla ja jättäisi Pascal-jäsentimen varalle alustoille muille kuin Windowsille. Kunnes jokin näistä laukaisimista oikeasti laukeaa, kahden dekooderin ylläpito ja kaksinkertainen testimatriisi ei osta mitään, minkä asiakas voisi aistia. Kulun lykkääminen silloin, kun pakoreitti on jo kirjattuna, on eri asia kuin sen sivuuttaminen
Mihin tämä jättää skannattujen dokumenttien putken
Käsittele TPDFTIFFiä porttina eikä muuntimena. Lataa tiedosto, lue ValidTIFF ja kirjaa LastError sanatarkasti aina kun se on epätosi, koska se merkkijono on nykyään lyhin reitti kenttäraportista diagnoosiin. Portin kariutuneet tiedostot pysyvät korjattavissa muuntamalla ne ylävirrassa, mikä on käytännön vastaus BigTIFF- ja tiililähteille tänä päivänä. Kokonaan TIFFin ulkopuoliseen syötteeseen PDFlibPas vie erillisen reitin AVIF-, HEIF- ja JPEG XL -kuvasyötteen kautta, joten kysymys siitä, mikä dekooderi omistaa minkäkin muodon, pysyy eksplisiittisenä sen sijaan, että se syntyisi vasta matkalla
Kaikki tämä sijaitsee tavallisen kuva-API:n takana, joten dokumenttiputki saa tiukemman rajan muuttamatta riviakaan kutsuvaa koodia sen jälkeen, kun se tarkistaa sivumäärän, jota sen olisi jo pitänyt tarkistaa. Jos punnitat natiivia TIFF-to-PDF-reittiä Delphille tai C++Builderille, koko komponentti ja sen kuvankäsittely on dokumentoitu PDF Library for Delphi -sivulla