|
|
import { useCallback } from 'react'; |
|
|
import { getFontSymbolId, matchesSearch } from '../utils/fontUtils'; |
|
|
|
|
|
|
|
|
const ENABLE_LOGARITHMIC_SIZING = true; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const useGlyphRenderer = () => { |
|
|
|
|
|
|
|
|
const calculateLogarithmicSize = useCallback((font, currentSize) => { |
|
|
console.log(`🔍 calculateLogarithmicSize called for ${font.name}`); |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
const isMerged = font.fusionInfo && font.fusionInfo.merged; |
|
|
const variantCount = font.fusionInfo ? font.fusionInfo.originalCount : 1; |
|
|
|
|
|
if (!isMerged) { |
|
|
return currentSize; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const sizeMultiplier = 1.0 + Math.log(variantCount) / 10; |
|
|
const finalSize = currentSize * sizeMultiplier; |
|
|
|
|
|
|
|
|
if (font.name === 'Noto' || font.name === 'Playwrite' || font.name === 'Baloo') { |
|
|
console.log(`📏 ${font.name}: currentSize=${currentSize.toFixed(2)}, finalSize=${finalSize.toFixed(2)}`); |
|
|
} |
|
|
|
|
|
return finalSize; |
|
|
}, []); |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
if (window.onFontHover) { |
|
|
window.onFontHover(d); |
|
|
} |
|
|
}) |
|
|
.on('mouseout', function(event, d) { |
|
|
if (!d || !d.name || visualStateRef.current.isTransitioning) return; |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
glyphs.append('circle') |
|
|
.attr('class', 'font-hit-area') |
|
|
.attr('r', d => { |
|
|
const logSize = calculateLogarithmicSize(d, currentSize); |
|
|
return Math.max(hitAreaSize / 2, logSize * 0.8); |
|
|
}) |
|
|
.attr('fill', 'transparent') |
|
|
.attr('data-font', d => d.name); |
|
|
|
|
|
|
|
|
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; |
|
|
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; |
|
|
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; |
|
|
return isActive ? logSize * 2 : logSize; |
|
|
}) |
|
|
.attr('height', d => { |
|
|
const isActive = selectedFont && selectedFont.name === d.name; |
|
|
const logSize = calculateLogarithmicSize(d, currentSize); |
|
|
d.__logSize = logSize; |
|
|
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'); |
|
|
|
|
|
|
|
|
glyphs.filter(d => !d.name).style('display', 'none'); |
|
|
|
|
|
|
|
|
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]); |
|
|
|
|
|
|
|
|
const updateGlyphPositions = useCallback((container, positions) => { |
|
|
container.selectAll('.font-glyph-group') |
|
|
.data(positions) |
|
|
.transition() |
|
|
.duration(300) |
|
|
.attr('transform', d => `translate(${d.x}, ${d.y})`); |
|
|
}, []); |
|
|
|
|
|
|
|
|
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; |
|
|
return isActive ? logSize * 2 : logSize; |
|
|
}) |
|
|
.attr('height', d => { |
|
|
const isActive = selectedFont && selectedFont.name === d.name; |
|
|
const logSize = calculateLogarithmicSize(d, currentSize); |
|
|
d.__logSize = logSize; |
|
|
return isActive ? logSize * 2 : logSize; |
|
|
}) |
|
|
.attr('x', d => { |
|
|
const isActive = selectedFont && selectedFont.name === d.name; |
|
|
const logSize = calculateLogarithmicSize(d, currentSize); |
|
|
d.__logSize = logSize; |
|
|
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; |
|
|
const activeOffset = isActive ? -(logSize * 2) / 2 : -logSize / 2; |
|
|
return activeOffset; |
|
|
}); |
|
|
}, [calculateLogarithmicSize]); |
|
|
|
|
|
return { |
|
|
createGlyphs, |
|
|
updateGlyphPositions, |
|
|
updateGlyphDimensions |
|
|
}; |
|
|
}; |
|
|
|