import { useCallback } from 'react'; import { getFontSymbolId, matchesSearch } from '../utils/fontUtils'; // Mode logarithmique pour ajuster la taille selon le nombre de variantes const ENABLE_LOGARITHMIC_SIZING = true; /** * Hook spécialisé pour le rendu des glyphes D3 * Centralise la logique de création et mise à jour des glyphes */ export const useGlyphRenderer = () => { // Fonction pour calculer la taille logarithmique basée sur le nombre de variantes const calculateLogarithmicSize = useCallback((font, currentSize) => { console.log(`🔍 calculateLogarithmicSize called for ${font.name}`); // Debug: log pour toutes les polices importantes (format Title Case) if (font.name === 'Noto' || font.name === 'Playwrite' || font.name === 'Baloo') { console.log(`🔍 ${font.name}:`, { enabled: ENABLE_LOGARITHMIC_SIZING, hasFusionInfo: !!font.fusionInfo, fusionInfo: font.fusionInfo, currentSize }); } if (!ENABLE_LOGARITHMIC_SIZING) { return currentSize; } // Vérifier si c'est une police fusionnée const isMerged = font.fusionInfo && font.fusionInfo.merged; const variantCount = font.fusionInfo ? font.fusionInfo.originalCount : 1; if (!isMerged) { return currentSize; } // Échelle inverse-logarithmique pour un effet plus doux // Plus il y a de variantes, plus la police est grande, mais avec un effet logarithmique const sizeMultiplier = 1.0 + Math.log(variantCount) / 10; const finalSize = currentSize * sizeMultiplier; // Debug: log pour les polices importantes (format Title Case) if (font.name === 'Noto' || font.name === 'Playwrite' || font.name === 'Baloo') { console.log(`📏 ${font.name}: currentSize=${currentSize.toFixed(2)}, finalSize=${finalSize.toFixed(2)}`); } return finalSize; }, []); // Fonction pour créer les glyphes const createGlyphs = useCallback((container, positions, darkMode, characterSize, filter, searchTerm, colorScale, debugMode, onFontSelect, selectedFont, visualStateRef) => { console.log('🚀 createGlyphs called with ENABLE_LOGARITHMIC_SIZING:', ENABLE_LOGARITHMIC_SIZING); const baseSize = 16; const currentSize = baseSize * characterSize; const hitAreaSize = currentSize * 1.5; const offset = currentSize / 2; const glyphs = container.selectAll('.font-glyph-group') .data(positions) .enter() .append('g') .attr('class', 'font-glyph-group') .attr('data-font', d => d.name) .attr('transform', d => `translate(${d.x}, ${d.y})`) .each(function(d) { // Log pour les polices fusionnées (format Title Case) if (d.name === 'Noto' || d.name === 'Playwrite' || d.name === 'Baloo') { console.log(`🎯 Found merged font: ${d.name}`, d); } }) .style('opacity', d => { const familyMatch = filter === 'all' || d.family === filter; const searchMatch = matchesSearch(d, searchTerm); const isActive = selectedFont && selectedFont.name === d.name; return isActive ? 1 : (familyMatch && searchMatch ? 1 : 0.2); }) .style('cursor', d => (selectedFont && selectedFont.name === d.name) ? 'default' : 'pointer') .style('pointer-events', 'all') .style('will-change', 'transform') .on('mouseover', function(event, d) { if (!d || !d.name || visualStateRef.current.isTransitioning) return; // Notifier le composant parent du hover if (window.onFontHover) { window.onFontHover(d); } }) .on('mouseout', function(event, d) { if (!d || !d.name || visualStateRef.current.isTransitioning) return; // Notifier le composant parent de la fin du hover if (window.onFontUnhover) { window.onFontUnhover(); } }) .on('click', function(event, d) { if (!d || !d.name) return; if (window.onFontUnhover) { window.onFontUnhover(); } if (onFontSelect) { onFontSelect(d); } else if (d.google_fonts_url) { window.open(d.google_fonts_url, '_blank'); } else { console.warn('Google Fonts URL not available for:', d.name); } }); // Zone de clic - cercle glyphs.append('circle') .attr('class', 'font-hit-area') .attr('r', d => { const logSize = calculateLogarithmicSize(d, currentSize); return Math.max(hitAreaSize / 2, logSize * 0.8); // Zone de clic adaptée à la taille }) .attr('fill', 'transparent') .attr('data-font', d => d.name); // Glyphe visible glyphs.append('use') .attr('xlink:href', d => `#${getFontSymbolId(d.imageName || d.name)}`) .attr('x', d => { const isActive = selectedFont && selectedFont.name === d.name; const logSize = calculateLogarithmicSize(d, currentSize); d.__logSize = logSize; // Stocker la taille pour useVisualState const activeOffset = isActive ? -(logSize * 2) / 2 : -logSize / 2; return activeOffset; }) .attr('y', d => { const isActive = selectedFont && selectedFont.name === d.name; const logSize = calculateLogarithmicSize(d, currentSize); d.__logSize = logSize; // Stocker la taille pour useVisualState const activeOffset = isActive ? -(logSize * 2) / 2 : -logSize / 2; return activeOffset; }) .attr('width', d => { const isActive = selectedFont && selectedFont.name === d.name; const logSize = calculateLogarithmicSize(d, currentSize); d.__logSize = logSize; // Stocker la taille pour useVisualState return isActive ? logSize * 2 : logSize; }) .attr('height', d => { const isActive = selectedFont && selectedFont.name === d.name; const logSize = calculateLogarithmicSize(d, currentSize); d.__logSize = logSize; // Stocker la taille pour useVisualState return isActive ? logSize * 2 : logSize; }) .attr('fill', darkMode ? '#ffffff' : d => colorScale(d.family)) .style('fill', darkMode ? '#ffffff' : null) .style('color', darkMode ? '#ffffff' : null) .style('vector-effect', 'non-scaling-stroke') .style('shape-rendering', 'geometricPrecision') .style('text-rendering', 'geometricPrecision') .style('image-rendering', 'crisp-edges') .style('paint-order', 'stroke fill') .attr('class', 'font-glyph'); // Masquer les glyphes sans nom glyphs.filter(d => !d.name).style('display', 'none'); // Zones de debug if (debugMode) { glyphs.selectAll('.debug-hit-area').remove(); glyphs.append('circle') .attr('class', 'debug-hit-area') .attr('r', hitAreaSize / 2) .attr('fill', 'none') .attr('stroke', 'none') .attr('stroke-width', 0) .attr('stroke-dasharray', 'none') .style('pointer-events', 'none'); } else { glyphs.selectAll('.debug-hit-area').remove(); } return glyphs; }, [calculateLogarithmicSize]); // Fonction pour mettre à jour les positions des glyphes const updateGlyphPositions = useCallback((container, positions) => { container.selectAll('.font-glyph-group') .data(positions) .transition() .duration(300) .attr('transform', d => `translate(${d.x}, ${d.y})`); }, []); // Fonction pour mettre à jour la taille des glyphes const updateGlyphDimensions = useCallback((container, characterSize, selectedFont) => { const baseSize = 16; const currentSize = baseSize * characterSize; const fontGlyphs = container.selectAll('.font-glyph'); fontGlyphs .transition() .duration(300) .attr('width', d => { const isActive = selectedFont && selectedFont.name === d.name; const logSize = calculateLogarithmicSize(d, currentSize); d.__logSize = logSize; // Stocker la taille pour useVisualState return isActive ? logSize * 2 : logSize; }) .attr('height', d => { const isActive = selectedFont && selectedFont.name === d.name; const logSize = calculateLogarithmicSize(d, currentSize); d.__logSize = logSize; // Stocker la taille pour useVisualState return isActive ? logSize * 2 : logSize; }) .attr('x', d => { const isActive = selectedFont && selectedFont.name === d.name; const logSize = calculateLogarithmicSize(d, currentSize); d.__logSize = logSize; // Stocker la taille pour useVisualState const activeOffset = isActive ? -(logSize * 2) / 2 : -logSize / 2; return activeOffset; }) .attr('y', d => { const isActive = selectedFont && selectedFont.name === d.name; const logSize = calculateLogarithmicSize(d, currentSize); d.__logSize = logSize; // Stocker la taille pour useVisualState const activeOffset = isActive ? -(logSize * 2) / 2 : -logSize / 2; return activeOffset; }); }, [calculateLogarithmicSize]); return { createGlyphs, updateGlyphPositions, updateGlyphDimensions }; };