11import Anchor from './anchor' ;
22import { getAnchors , getCenterAnchor } from './get_anchors' ;
33import { shapeText , shapeIcon , WritingMode , fitIconToText , isPositionedIcon , getPositionedIconSize , isFullyStretchableX , isFullyStretchableY } from './shaping' ;
4- import { getGlyphQuads , getIconQuads } from './quads' ;
4+ import { getGlyphQuads , getIconQuads , getIconQuadsNumber , type SymbolQuad } from './quads' ;
55import { warnOnce , degToRad , clamp } from '../util/util' ;
66import {
77 allowsVerticalWritingMode ,
@@ -857,11 +857,23 @@ function addFeature(bucket: SymbolBucket,
857857 }
858858 const layout = bucket . layers [ 0 ] . layout ;
859859
860+ const glyphSize = ONE_EM ;
861+ const fontScale = layoutTextSize * sizes . textScaleFactor / glyphSize ;
862+
860863 const defaultShaping = getDefaultHorizontalShaping ( shapedTextOrientations . horizontal ) || shapedTextOrientations . vertical ;
864+
865+ // Store text shaping data for icon-text-fit appearance updates
866+ if ( iconTextFit !== 'none' && bucket . appearanceFeatureData && feature . index < bucket . appearanceFeatureData . length ) {
867+ const featureData = bucket . appearanceFeatureData [ feature . index ] ;
868+ if ( featureData ) {
869+ featureData . textShaping = defaultShaping ;
870+ featureData . iconTextFitPadding = layout . get ( 'icon-text-fit-padding' ) . evaluate ( feature , { } , canonical ) ;
871+ featureData . fontScale = fontScale ;
872+ }
873+ }
861874 const isGlobe = projection . name === 'globe' ;
862875
863- const glyphSize = ONE_EM ,
864- textMaxBoxScale = bucket . tilePixelRatio * textMaxSize / glyphSize ,
876+ const textMaxBoxScale = bucket . tilePixelRatio * textMaxSize / glyphSize ,
865877 iconBoxScale = bucket . tilePixelRatio * layoutIconSize ,
866878 symbolMinDistance = tilePixelRatioForSymbolSpacing ( bucket . overscaling , bucket . zoom ) * layout . get ( 'symbol-spacing' ) ,
867879 textPadding = layout . get ( 'text-padding' ) * bucket . tilePixelRatio ,
@@ -1036,7 +1048,8 @@ function addTextVertices(bucket: SymbolBucket,
10361048 canonical ,
10371049 brightness ,
10381050 false ,
1039- symbolInstanceIndex ) ;
1051+ symbolInstanceIndex ,
1052+ glyphQuads . length ) ;
10401053
10411054 // The placedSymbolArray is used at render time in drawTileSymbols
10421055 // These indices allow access to the array at collision detection time
@@ -1229,7 +1242,12 @@ function addSymbol(bucket: SymbolBucket,
12291242 const iconQuads = getIconQuads ( shapedIcon , iconRotate , isSDFIcon , hasIconTextFit , sizes . iconScaleFactor ) ;
12301243 const verticalIconQuads = verticallyShapedIcon ? getIconQuads ( verticallyShapedIcon , iconRotate , isSDFIcon , hasIconTextFit , sizes . iconScaleFactor ) : undefined ;
12311244 iconBoxIndex = evaluateBoxCollisionFeature ( collisionBoxArray , collisionFeatureAnchor , anchor , featureIndex , sourceLayerIndex , bucketIndex , shapedIcon , iconPadding , iconRotate , null , iconCollisionBounds ) ;
1232- numIconVertices = iconQuads . length * 4 ;
1245+ // Calculate maximum quads needed across layout icon and all appearance variants
1246+ // to prevent vertex buffer overflow during appearance updates
1247+ const maxQuadCount = calculateMaxIconQuadCount ( bucket , iconQuads , verticalIconQuads ,
1248+ layer . layout , feature , canonical , bucket . iconAtlasPositions ,
1249+ hasIconTextFit ) ;
1250+ numIconVertices = maxQuadCount * 4 ;
12331251
12341252 let iconSizeData = null ;
12351253
@@ -1271,12 +1289,13 @@ function addSymbol(bucket: SymbolBucket,
12711289 canonical ,
12721290 brightness ,
12731291 hasAnySecondaryIcon ,
1274- bucket . symbolInstances . length ) ;
1292+ bucket . symbolInstances . length ,
1293+ maxQuadCount ) ;
12751294
12761295 placedIconSymbolIndex = bucket . icon . placedSymbolArray . length - 1 ;
12771296
12781297 if ( verticalIconQuads ) {
1279- numVerticalIconVertices = verticalIconQuads . length * 4 ;
1298+ numVerticalIconVertices = maxQuadCount * 4 ;
12801299
12811300 bucket . addSymbols (
12821301 bucket . icon ,
@@ -1297,7 +1316,8 @@ function addSymbol(bucket: SymbolBucket,
12971316 canonical ,
12981317 brightness ,
12991318 hasAnySecondaryIcon ,
1300- bucket . symbolInstances . length ) ;
1319+ bucket . symbolInstances . length ,
1320+ maxQuadCount ) ;
13011321
13021322 verticalPlacedIconSymbolIndex = bucket . icon . placedSymbolArray . length - 1 ;
13031323 }
@@ -1428,3 +1448,58 @@ function anchorIsTooClose(bucket: SymbolBucket, text: string, repeatDistance: nu
14281448 compareText [ text ] . push ( anchor ) ;
14291449 return false ;
14301450}
1451+
1452+ function calculateMaxIconQuadCount (
1453+ bucket : SymbolBucket ,
1454+ iconQuads : Array < SymbolQuad > ,
1455+ verticalIconQuads : Array < SymbolQuad > | undefined ,
1456+ layout : PossiblyEvaluated < LayoutProps > ,
1457+ feature : SymbolFeature ,
1458+ canonical : CanonicalTileID ,
1459+ imagePositions : ImagePositionMap ,
1460+ hasIconTextFit : boolean ,
1461+ ) : number {
1462+ const symbolLayer = bucket . layers [ 0 ] ;
1463+ const appearances = symbolLayer . appearances ;
1464+
1465+ // Start with the layout icon quad count
1466+ let maxQuadCount = iconQuads . length ;
1467+
1468+ // Check vertical icon quads if they exist
1469+ if ( verticalIconQuads ) {
1470+ maxQuadCount = Math . max ( maxQuadCount , verticalIconQuads . length ) ;
1471+ }
1472+
1473+ if ( appearances . length === 0 ) {
1474+ return maxQuadCount ;
1475+ }
1476+
1477+ const [ iconSizeScaleRangeMin , iconSizeScaleRangeMax ] = layout . get ( 'icon-size-scale-range' ) ;
1478+ const iconScaleFactor = clamp ( 1 , iconSizeScaleRangeMin , iconSizeScaleRangeMax ) ;
1479+
1480+ // Check each appearance that has an icon to find maximum quad count needed
1481+ for ( const appearance of appearances ) {
1482+ const unevaluatedProperties = appearance . getUnevaluatedProperties ( ) ;
1483+ const iconImageProperty = unevaluatedProperties . _values [ 'icon-image' ] . value !== undefined ;
1484+
1485+ if ( iconImageProperty ) {
1486+ const appearanceIconImage = symbolLayer . getAppearanceValueAndResolveTokens ( appearance , 'icon-image' , feature , canonical , [ ] ) ;
1487+ if ( appearanceIconImage ) {
1488+ const icon = bucket . getResolvedImageFromTokens ( appearanceIconImage as string ) ;
1489+ if ( icon ) {
1490+ // Ideally we shouldn't need to compute the scaled image variant because all
1491+ // different sized versions of the same icon have the same number of stretchable
1492+ // areas, which is what we need but unfortunately, since imagePositions stores the
1493+ // position by the stringified sized icon we need to compute it
1494+ const unevaluatedIconSize = unevaluatedProperties . _values [ 'icon-size' ] ;
1495+ const iconSizeData = getSizeData ( bucket . zoom , unevaluatedIconSize , bucket . worldview ) ;
1496+ const imageVariant = getScaledImageVariant ( icon , iconSizeData , unevaluatedIconSize , canonical , bucket . zoom , feature , bucket . pixelRatio , iconScaleFactor , bucket . worldview ) ;
1497+ const imagePosition = imagePositions . get ( imageVariant . iconPrimary . toString ( ) ) ;
1498+ maxQuadCount = Math . max ( maxQuadCount , getIconQuadsNumber ( imagePosition , hasIconTextFit ) ) ;
1499+ }
1500+ }
1501+ }
1502+ }
1503+
1504+ return maxQuadCount ;
1505+ }
0 commit comments