open("$tmp/cv.docx", ZipArchive::CREATE);
$zip->addFromString('word/document.xml', '');
$zip->addFromString('word/media/image1.png', logo(400, 400));
$zip->addFromString('word/media/image2.jpeg', portrait(600, 760));
$zip->addFromString('word/media/image3.jpeg', portrait(900, 120));
$zip->addFromString('word/media/image4.png', portrait(40, 50, 'png'));
$zip->close();
[$l, $h, $type] = dimensions(Photo::extraire("$tmp/cv.docx", 'docx'));
verifier('Word : le portrait est retenu, pas le logo ni le bandeau', $type === IMAGETYPE_JPEG && abs($l / max($h, 1) - 600 / 760) < 0.02);
verifier('Word : photo réduite à 480 px au plus', max($l, $h) === 480);
$zip = new ZipArchive(); $zip->open("$tmp/sans.docx", ZipArchive::CREATE);
$zip->addFromString('word/document.xml', '');
$zip->addFromString('word/media/image1.png', logo(400, 400));
$zip->close();
verifier('Word sans photo : rien n\'est retenu', Photo::extraire("$tmp/sans.docx", 'docx') === null);
// --- PDF : image JPEG
$jpeg = portrait(300, 380);
$pdf = "%PDF-1.4\n1 0 obj\n<< /Type /XObject /Subtype /Image /Width 300 /Height 380 /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length " . strlen($jpeg) . " >>\nstream\n" . $jpeg . "\nendstream\nendobj\n%%EOF";
file_put_contents("$tmp/jpeg.pdf", $pdf);
[$l, $h] = dimensions(Photo::extraire("$tmp/jpeg.pdf", 'pdf'));
verifier('PDF : image JPEG retrouvée', $l === 300 && $h === 380);
// --- PDF : image compressée sans perte, avec et sans prédicteur
$img = imagecreatefromstring(portrait(200, 240));
$brut = ''; $filtre = '';
for ($y = 0; $y < 240; $y++) {
$ligne = '';
for ($x = 0; $x < 200; $x++) { $c = imagecolorat($img, $x, $y); $ligne .= chr(($c >> 16) & 255) . chr(($c >> 8) & 255) . chr($c & 255); }
$brut .= $ligne; $filtre .= "\0" . $ligne;
}
$objet = static fn (string $donnees, string $plus = ''): string => "%PDF-1.4\n4 0 obj\n<< /Subtype /Image /Width 200 /Height 240 /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode $plus/Length 9 0 R >>\nstream\r\n" . $donnees . "\r\nendstream\nendobj\n%%EOF";
file_put_contents("$tmp/flate.pdf", $objet(gzcompress($brut)));
[$l, $h] = dimensions(Photo::extraire("$tmp/flate.pdf", 'pdf'));
verifier('PDF : image sans perte retrouvée, longueur indirecte', $l === 200 && $h === 240);
file_put_contents("$tmp/predicteur.pdf", $objet(gzcompress($filtre), '/DecodeParms << /Predictor 15 /Colors 3 /Columns 200 >> '));
[$l, $h] = dimensions(Photo::extraire("$tmp/predicteur.pdf", 'pdf'));
verifier('PDF : image sans perte avec prédicteur retrouvée', $l === 200 && $h === 240);
file_put_contents("$tmp/texte.pdf", "%PDF-1.4\n1 0 obj\n<< /Length 20 >>\nstream\nBT (Bonjour) Tj ET\nendstream\nendobj\n%%EOF");
verifier('PDF sans image : rien n\'est retenu', Photo::extraire("$tmp/texte.pdf", 'pdf') === null);
file_put_contents("$tmp/casse.pdf", "%PDF-1.4 << /Subtype /Image /Width 300 /Height 300 /Filter /DCTDecode >> stream\nceci n'est pas une image");
verifier('PDF endommagé : aucune erreur', Photo::extraire("$tmp/casse.pdf", 'pdf') === null);
verifier('format sans image (txt) : rien', Photo::extraire(__FILE__, 'txt') === null);
// --- Normalisation
[$l, $h, $type] = dimensions(Photo::normaliser(portrait(1200, 800, 'png')));
verifier('chargement manuel : PNG converti en JPEG réduit', $type === IMAGETYPE_JPEG && $l === 480 && $h === 320);
verifier('fichier qui n\'est pas une image : refusé', Photo::normaliser(' 0 ? 1 : 0);