isQpdfAvailable()) { return null; } if (!file_exists($absolutePath)) { return null; } $originalSize = filesize($absolutePath); if (false === $originalSize) { return null; } $tempOutput = tempnam(sys_get_temp_dir(), 'pdf_out_'); $command = sprintf( 'qpdf --linearize --object-streams=generate %s %s 2>&1', escapeshellarg($absolutePath), escapeshellarg($tempOutput) ); exec($command, $cmdOutput, $returnCode); if (0 !== $returnCode || !file_exists($tempOutput)) { @unlink($tempOutput); return null; } $compressedSize = filesize($tempOutput); if (false === $compressedSize || $compressedSize >= $originalSize) { @unlink($tempOutput); return null; } if (!rename($tempOutput, $absolutePath)) { @unlink($tempOutput); return null; } return [ 'size' => $compressedSize, 'originalSize' => $originalSize, 'saved' => $originalSize - $compressedSize, ]; } public function compressBase64Pdf(string $base64Data): ?array { if (!$this->isQpdfAvailable()) { return null; } // Remove data URI prefix if present $originalBase64 = $base64Data; if (str_contains($base64Data, ',')) { $base64Data = explode(',', $base64Data, 2)[1]; } $pdfContent = base64_decode($base64Data, true); if (false === $pdfContent) { return null; } $originalSize = strlen($pdfContent); // Create temp files $tempInput = tempnam(sys_get_temp_dir(), 'pdf_in_'); $tempOutput = tempnam(sys_get_temp_dir(), 'pdf_out_'); file_put_contents($tempInput, $pdfContent); // Compress with qpdf (lossless) $command = sprintf( 'qpdf --linearize --object-streams=generate %s %s 2>&1', escapeshellarg($tempInput), escapeshellarg($tempOutput) ); exec($command, $cmdOutput, $returnCode); if (0 !== $returnCode || !file_exists($tempOutput)) { @unlink($tempInput); @unlink($tempOutput); return null; } $compressedContent = file_get_contents($tempOutput); $compressedSize = strlen($compressedContent); @unlink($tempInput); @unlink($tempOutput); // Only return compressed version if it's smaller if ($compressedSize >= $originalSize) { return null; } // Rebuild with data URI prefix $newBase64 = 'data:application/pdf;base64,'.base64_encode($compressedContent); return [ 'path' => $newBase64, 'size' => $compressedSize, 'originalSize' => $originalSize, 'saved' => $originalSize - $compressedSize, ]; } private function isQpdfAvailable(): bool { if (null === $this->qpdfAvailable) { exec('which qpdf', $qpdfPath, $returnCode); $this->qpdfAvailable = 0 === $returnCode; } return $this->qpdfAvailable; } }