fontmap / src /components /FontMap /hooks /useGlyphRenderer.js
tfrere's picture
tfrere HF Staff
first commit
eebc40f
raw
history blame
9.24 kB
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
};
};