Files

93 lines
5.2 KiB
PHP

<?php
// Tests de la recherche de photo dans un CV : php tests/test_photo.php
declare(strict_types=1);
const VIVIER_RACINE = __DIR__ . '/..';
require __DIR__ . '/../src/i18n.php';
require __DIR__ . '/../src/Photo.php';
$ok = 0; $ko = 0;
function verifier(string $nom, bool $cond): void {
global $ok, $ko;
if ($cond) { $ok++; } else { $ko++; echo "ECHEC : $nom\n"; }
}
if (!Photo::disponible()) { echo "Extension GD absente : tests ignorés.\n"; exit(0); }
/** Image aux teintes variées, comme une photo. */
function portrait(int $l, int $h, string $format = 'jpeg'): string {
$img = imagecreatetruecolor($l, $h);
mt_srand(7);
for ($y = 0; $y < $h; $y += 4) {
for ($x = 0; $x < $l; $x += 4) {
imagefilledrectangle($img, $x, $y, $x + 3, $y + 3, imagecolorallocate($img, ($x * 255 / $l + mt_rand(0, 60)) % 256, ($y * 255 / $h + mt_rand(0, 60)) % 256, mt_rand(60, 200)));
}
}
ob_start(); $format === 'png' ? imagepng($img) : imagejpeg($img, null, 90); return (string) ob_get_clean();
}
/** Image à deux couleurs, comme un logo. */
function logo(int $l, int $h): string {
$img = imagecreatetruecolor($l, $h);
imagefill($img, 0, 0, imagecolorallocate($img, 255, 255, 255));
imagefilledrectangle($img, (int) ($l / 4), (int) ($h / 4), (int) ($l * 3 / 4), (int) ($h * 3 / 4), imagecolorallocate($img, 206, 183, 59));
ob_start(); imagepng($img); return (string) ob_get_clean();
}
function dimensions(?string $jpeg): array { $t = $jpeg ? getimagesizefromstring($jpeg) : [0, 0, 0]; return [$t[0], $t[1], $t[2]]; }
$tmp = sys_get_temp_dir() . '/vivier-test-' . bin2hex(random_bytes(4));
mkdir($tmp);
// --- Word : la photo parmi un logo et un bandeau
$zip = new ZipArchive(); $zip->open("$tmp/cv.docx", ZipArchive::CREATE);
$zip->addFromString('word/document.xml', '<w:document/>');
$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', '<w:document/>');
$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('<?php echo 1;') === null);
array_map('unlink', glob("$tmp/*")); rmdir($tmp);
echo "$ok réussis, $ko échoués\n";
exit($ko > 0 ? 1 : 0);