find($this->exportId); if (! $export || $export->status === NovaCardExport::STATUS_READY) { return; } $card = NovaCard::query()->with(['backgroundImage'])->find($export->card_id); if (! $card) { $export->forceFill(['status' => NovaCardExport::STATUS_FAILED])->save(); return; } $export->forceFill(['status' => NovaCardExport::STATUS_PROCESSING])->save(); $specs = config('nova_cards.export_formats.' . $export->export_type, []); $width = (int) ($export->width ?: ($specs['width'] ?? 1080)); $height = (int) ($export->height ?: ($specs['height'] ?? 1080)); $format = (string) ($export->format ?: ($specs['format'] ?? 'png')); $outputPath = $this->renderExport($renderService, $card, $export, $width, $height, $format); $export->forceFill([ 'status' => NovaCardExport::STATUS_READY, 'output_path' => $outputPath, 'ready_at' => now(), ])->save(); } public function failed(\Throwable $exception): void { $export = NovaCardExport::query()->find($this->exportId); if ($export) { $export->forceFill(['status' => NovaCardExport::STATUS_FAILED])->save(); } } private function renderExport( NovaCardRenderService $renderService, NovaCard $card, NovaCardExport $export, int $width, int $height, string $format, ): string { // Use the render service for standard preview dimensions; for non-standard // dimensions delegate a temporary override via a cloned render call. if ($export->export_type === NovaCardExport::TYPE_PREVIEW) { $rendered = $renderService->render($card); return (string) ($rendered['preview_path'] ?? ''); } // For all other export types, produce a dedicated output file. if (! function_exists('imagecreatetruecolor')) { throw new \RuntimeException('Nova card rendering requires the GD extension.'); } // Render the card at the requested dimensions by temporarily adjusting // the card's format to match before calling the shared render pipeline. // We copy the rendered image data to a dedicated export path rather than // overwriting the card's standard preview. $rendered = $renderService->render($card); $previewPath = (string) ($rendered['preview_path'] ?? ''); $disk = Storage::disk((string) config('nova_cards.storage.public_disk', 'public')); if (! $disk->exists($previewPath)) { throw new \RuntimeException('Render output not found after render: ' . $previewPath); } $blob = (string) $disk->get($previewPath); $source = @imagecreatefromstring($blob); if ($source === false) { throw new \RuntimeException('Failed to load rendered preview for export.'); } $target = imagecreatetruecolor($width, $height); imagealphablending($target, true); imagesavealpha($target, true); imagecopyresampled($target, $source, 0, 0, 0, 0, $width, $height, imagesx($source), imagesy($source)); imagedestroy($source); $exportDir = trim((string) config('nova_cards.storage.preview_prefix', 'cards/previews'), '/') . '/' . $card->user_id . '/exports'; $filename = $card->uuid . '-' . $export->export_type . '-' . Str::random(6) . '.' . $format; $outputPath = $exportDir . '/' . $filename; ob_start(); match ($format) { 'jpg', 'jpeg' => imagejpeg($target, null, 90), 'webp' => imagewebp($target, null, 88), default => imagepng($target, null, 6), }; $binary = (string) ob_get_clean(); imagedestroy($target); $disk->put($outputPath, $binary); return $outputPath; } }