import React, { useState, useCallback, useEffect, useRef } from 'react'; // Helper function to create a loading spinner SVG const Spinner = () => ( ); // Main App Component export default function App() { const [imageSrc, setImageSrc] = useState(null); const [base64ImageData, setBase64ImageData] = useState(null); const [uploadedFileMimeType, setUploadedFileMimeType] = useState('image/png'); const [extractedText, setExtractedText] = useState(''); const [textStyleDescription, setTextStyleDescription] = useState(''); const [isOriginalStyleBold, setIsOriginalStyleBold] = useState(false); // New state for boldness const [fontSuggestions, setFontSuggestions] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); const loadedFontFamiliesRef = useRef(new Set()); const apiKey = ""; // Should be left empty const handleImageUpload = (event) => { const file = event.target.files[0]; if (file) { if (file.size > 4 * 1024 * 1024) { setError('Image size should be less than 4MB.'); return; } setError(''); setUploadedFileMimeType(file.type); const reader = new FileReader(); reader.onloadend = () => { setImageSrc(reader.result); setBase64ImageData(reader.result.split(',')[1]); setExtractedText(''); setTextStyleDescription(''); setIsOriginalStyleBold(false); // Reset boldness setFontSuggestions([]); }; reader.readAsDataURL(file); } }; useEffect(() => { fontSuggestions.forEach(font => { // Ensure we request bold (700) weight if available for Google Fonts if (font.name && font.url && font.url.includes('fonts.google.com') && !loadedFontFamiliesRef.current.has(font.name)) { loadedFontFamiliesRef.current.add(font.name); const link = document.createElement('link'); const fontNameForUrl = font.name.replace(/\s+/g, '+'); // Requesting 400 and 700 weights link.href = `https://fonts.googleapis.com/css2?family=${fontNameForUrl}:wght@400;700&display=swap`; link.rel = 'stylesheet'; document.head.appendChild(link); } }); }, [fontSuggestions]); const analyzeImageAndSuggestFont = useCallback(async () => { if (!base64ImageData) { setError('Please upload an image first.'); return; } setIsLoading(true); setError(''); setExtractedText(''); setTextStyleDescription(''); setIsOriginalStyleBold(false); setFontSuggestions([]); try { const imageAnalysisPrompt = `From the provided image, analyze the text and provide the following details in a JSON object with keys "extractedText" and "textStyleDescription": 1. "extractedText": Extract all visible text. If no text is found, explicitly state "No text found". 2. "textStyleDescription": Provide a detailed description of the visual style of the most prominent text. Include aspects like: - Font category (e.g., Serif, Sans-serif, Script, Display, Monospace, Handwritten). - Weight (e.g., Light, Regular, Bold, Black). Crucially, if the text appears bold, include the word "Bold" in this description. - Width (e.g., Condensed, Normal, Expanded). - Contrast (e.g., Low, Medium, High - referring to thick/thin stroke variation). - Specific features (e.g., rounded corners, geometric shapes, high x-height, specific serif style like slab or bracketed, decorative elements, calligraphic details). - Overall impression (e.g., modern, classic, playful, formal, technical, elegant, retro, futuristic). If multiple distinct text styles are present, focus on the most dominant or visually interesting one. If the style is ambiguous or no text is found, state "Text style is unclear or not discernible".`; const imageAnalysisPayload = { contents: [{ role: "user", parts: [{ text: imageAnalysisPrompt }, { inlineData: { mimeType: uploadedFileMimeType, data: base64ImageData }}]}], generationConfig: { responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { "extractedText": { "type": "STRING" }, "textStyleDescription": { "type": "STRING" }}, required: ["extractedText", "textStyleDescription"] } } }; const imageAnalysisApiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; const imageAnalysisResponse = await fetch(imageAnalysisApiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(imageAnalysisPayload) }); if (!imageAnalysisResponse.ok) { const errorData = await imageAnalysisResponse.json(); throw new Error(`Image analysis failed: ${errorData?.error?.message || imageAnalysisResponse.statusText}`); } const imageAnalysisResult = await imageAnalysisResponse.json(); let analysisData; if (imageAnalysisResult.candidates?.[0]?.content?.parts?.[0]?.text) { try { analysisData = JSON.parse(imageAnalysisResult.candidates[0].content.parts[0].text); } catch (e) { throw new Error("Received malformed style description from AI."); } } else { throw new Error("Could not get style description from image analysis."); } const currentExtractedText = analysisData.extractedText || "No text extracted."; const currentTextStyleDescription = analysisData.textStyleDescription || "Style description not available."; setExtractedText(currentExtractedText); setTextStyleDescription(currentTextStyleDescription); // Check if the described style is bold if (currentTextStyleDescription.toLowerCase().includes('bold')) { setIsOriginalStyleBold(true); } if (currentTextStyleDescription && !currentTextStyleDescription.toLowerCase().includes("unclear") && !currentTextStyleDescription.toLowerCase().includes("not discernible") && !currentTextStyleDescription.toLowerCase().includes("no text found")) { const fontSuggestionPrompt = ` The visual style of a font has been described as: "${currentTextStyleDescription}". Your task is to suggest up to five common web fonts that MOST CLOSELY RESEMBLE this described style. Aim for the best possible visual similarity based on the description. These fonts do not need to be restricted to Google Fonts. If the description includes "Bold", try to suggest fonts that have a readily available bold weight or are inherently bold. For each suggested font, provide: 1. "name": The exact font name for use in CSS (e.g., "Arial", "Helvetica Neue", "Georgia", "Times New Roman", "Roboto", "Open Sans"). 2. "url": A direct URL to a page where the font can be viewed or obtained (e.g., a Google Fonts page, a foundry page, a type specimen page, or a general font information site). If a direct link for viewing/obtaining is not readily available, provide a URL to a page with more information about the font. If no suitable URL can be found, this field can be an empty string. If, despite your best efforts, you cannot find close matches or the description is too vague, suggest "Inter" (URL: https://fonts.google.com/specimen/Inter) and "Lato" (URL: https://fonts.google.com/specimen/Lato) as versatile defaults, and fill the remaining suggestions with other popular, distinct web fonts. Prioritize suggestions that genuinely attempt to match the style description. Ensure URLs are valid if provided. Format the response as a JSON object with a single key "suggestions", which is an array of objects, each having "name" and "url".`; const fontSuggestionPayload = { contents: [{ role: "user", parts: [{ text: fontSuggestionPrompt }] }], generationConfig: { responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { "suggestions": { type: "ARRAY", items: { type: "OBJECT", properties: { "name": { "type": "STRING" }, "url": { "type": "STRING" }}, required: ["name", "url"] } } }, required: ["suggestions"] } } }; const fontApiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; const fontResponse = await fetch(fontApiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(fontSuggestionPayload) }); if (!fontResponse.ok) { const errorData = await fontResponse.json(); throw new Error(`Font suggestion failed: ${errorData?.error?.message || fontResponse.statusText}`); } const fontResult = await fontResponse.json(); if (fontResult.candidates?.[0]?.content?.parts?.[0]?.text) { try { const fontData = JSON.parse(fontResult.candidates[0].content.parts[0].text); setFontSuggestions(fontData.suggestions?.slice(0, 5) || []); } catch (e) { setFontSuggestions([{ name: "Inter", url: "https://fonts.google.com/specimen/Inter" }]); } } else { setFontSuggestions([{ name: "Inter", url: "https://fonts.google.com/specimen/Inter" }]); } } else { setFontSuggestions([ { name: "Inter", url: "https://fonts.google.com/specimen/Inter" }, { name: "Lato", url: "https://fonts.google.com/specimen/Lato" }, { name: "Arial", url: "" }, { name: "Times New Roman", url: "" }, { name: "Verdana", url: "" } ].slice(0,5)); } } catch (err) { setError(err.message || 'An error occurred during analysis.'); setFontSuggestions([ { name: "Inter", url: "https://fonts.google.com/specimen/Inter" }, { name: "Lato", url: "https://fonts.google.com/specimen/Lato" }, { name: "Georgia", url: "" }, { name: "Courier New", url: "" }, { name: "Comic Sans MS", url: "" } ].slice(0,5)); } finally { setIsLoading(false); } }, [base64ImageData, apiKey, uploadedFileMimeType]); const handleClear = () => { setImageSrc(null); setBase64ImageData(null); setUploadedFileMimeType('image/png'); setExtractedText(''); setTextStyleDescription(''); setIsOriginalStyleBold(false); setFontSuggestions([]); setError(''); setIsLoading(false); const fileInput = document.getElementById('imageUpload'); if (fileInput) fileInput.value = ''; loadedFontFamiliesRef.current.clear(); }; return (
{imageSrc && (
Uploaded preview
)}
{error && (
Error: {error}
)} {(extractedText || textStyleDescription || fontSuggestions.length > 0) && !isLoading && (
{extractedText && (

Extracted Text:

{extractedText}

)} {textStyleDescription && (

AI - Described Text Style:

{textStyleDescription}

{isOriginalStyleBold &&

(Style detected as bold)

}
)} {fontSuggestions.length > 0 && (

Font Suggestions:

    {fontSuggestions.map((font, index) => { const sampleText = extractedText && extractedText.toLowerCase() !== 'no text found' && extractedText.trim().length > 0 ? extractedText.substring(0, 10) : "Sample"; const isGoogleFont = font.url && font.url.includes('fonts.google.com'); return (
  • {sampleText}
  • ); })}

Note: These are AI-generated suggestions based on perceived style, not exact font identification. Font samples are rendered by your browser (Google Fonts are preloaded if possible).

)}
)}
); }