@@ -90,7 +90,7 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> implements IPro
9090 _dy : number = 0 ;
9191
9292 contextAndRatio ?: { context : CanvasRenderingContext2D ; ratio : number ; canvas : HTMLCanvasElement } ;
93- _board : number [ ] ;
93+ _board : Uint32Array ;
9494 /** 已经绘制文字的最小包围盒 */
9595 _bounds : Bounds ;
9696
@@ -189,20 +189,20 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> implements IPro
189189 const distSize0 = Math . max ( d . width , d . height ) ;
190190 if ( distSize0 <= maxSize0 ) {
191191 // 扩大尺寸满足最小字体要求 =》 按照要求扩大board
192- this . expandBoard ( this . _board , this . _bounds , distSize0 / this . _size [ 0 ] ) ;
192+ this . _board = this . expandBoard ( this . _board , this . _bounds , distSize0 / this . _size [ 0 ] ) ;
193193 } else if ( this . options . clip ) {
194194 // 扩大尺寸不满足最小字体要求,但支持裁剪 =》 按最大尺寸扩大,裁剪词语
195- this . expandBoard ( this . _board , this . _bounds , maxSize0 / this . _size [ 0 ] ) ;
195+ this . _board = this . expandBoard ( this . _board , this . _bounds , maxSize0 / this . _size [ 0 ] ) ;
196196 } else {
197197 // 扩大尺寸不满足最小字体要求,且不支持裁剪 =》 丢弃词语
198198 return true ;
199199 }
200200 } else if ( this . _placeStatus === 3 ) {
201201 // 扩大画布
202- this . expandBoard ( this . _board , this . _bounds ) ;
202+ this . _board = this . expandBoard ( this . _board , this . _bounds ) ;
203203 } else {
204204 // 扩大画布
205- this . expandBoard ( this . _board , this . _bounds ) ;
205+ this . _board = this . expandBoard ( this . _board , this . _bounds ) ;
206206 }
207207 // 更新一次状态,下次大尺寸词语进入裁剪
208208 this . updateBoardExpandStatus ( d . fontSize ) ;
@@ -223,7 +223,7 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> implements IPro
223223 this . _originSize = [ ...this . _size ] ;
224224 const contextAndRatio = this . getContext ( vglobal . createCanvas ( { width : 1 , height : 1 } ) ) ;
225225 this . contextAndRatio = contextAndRatio ;
226- this . _board = new Array ( ( this . _size [ 0 ] >> 5 ) * this . _size [ 1 ] ) . fill ( 0 ) ;
226+ this . _board = new Uint32Array ( ( this . _size [ 0 ] >> 5 ) * this . _size [ 1 ] ) . fill ( 0 ) ;
227227 // 已经绘制文字的最小包围盒
228228 this . _bounds = null ;
229229
@@ -340,34 +340,68 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> implements IPro
340340 this . _size = this . _size . map ( v => v * ( 1 - minRatio ) ) as any ;
341341 }
342342
343- // 扩充 bitmap
344- private expandBoard ( board : number [ ] , bounds : Bounds , factor ?: any ) {
345- const expandedLeftWidth = ( this . _size [ 0 ] * ( factor || 1.1 ) - this . _size [ 0 ] ) >> 5 ;
343+ /**
344+ * [已优化] 通过重建法高效扩展画板,添加边框。
345+ *
346+ * @returns {number[] } 返回一个全新的、尺寸更大的画板数组。
347+ * @notice 这是一个重大变更:此函数不再原地修改 board,而是返回一个新数组。
348+ * 调用方需要相应地更新其引用,例如:this.board = this.expandBoard(...);
349+ */
350+ private expandBoard ( board : Uint32Array , bounds : Bounds , factor ?: any ) : Uint32Array {
351+ // --- 1. 计算所有尺寸和偏移量 ---
352+ const oldW = this . _size [ 0 ] ;
353+ const oldH = this . _size [ 1 ] ;
354+ const oldRowStride = oldW >> 5 ; // 每行的“块”数
355+
356+ // 计算水平和垂直方向需要增加的“块”数
357+ const expandedLeftWidth = ( oldW * ( factor || 1.1 ) - oldW ) >> 5 ;
346358 let diffWidth = expandedLeftWidth * 2 > 2 ? expandedLeftWidth : 2 ;
347359 if ( diffWidth % 2 !== 0 ) {
348- diffWidth ++ ;
360+ diffWidth ++ ; // 确保为偶数,以便左右对称
349361 }
350- let diffHeight = Math . ceil ( ( this . _size [ 1 ] * ( diffWidth << 5 ) ) / this . _size [ 0 ] ) ;
362+
363+ let diffHeight = Math . ceil ( ( oldH * ( diffWidth << 5 ) ) / oldW ) ;
351364 if ( diffHeight % 2 !== 0 ) {
352- diffHeight ++ ;
365+ diffHeight ++ ; // 确保为偶数,以便上下对称
353366 }
354- const w = this . _size [ 0 ] ;
355- const h = this . _size [ 1 ] ;
356- const widthArr = new Array ( diffWidth ) . fill ( 0 ) ;
357-
358- const heightArr = new Array ( ( diffHeight / 2 ) * ( diffWidth + ( w >> 5 ) ) ) . fill ( 0 ) ;
359- this . insertZerosToArray ( board , h * ( w >> 5 ) , heightArr . length + diffWidth / 2 ) ;
360- for ( let i = h - 1 ; i > 0 ; i -- ) {
361- this . insertZerosToArray ( board , i * ( w >> 5 ) , widthArr . length ) ;
367+
368+ const newW = oldW + ( diffWidth << 5 ) ;
369+ const newH = oldH + diffHeight ;
370+ const newRowStride = newW >> 5 ;
371+
372+ const paddingLeft = diffWidth / 2 ;
373+ const paddingTop = diffHeight / 2 ;
374+
375+ // --- 2. 创建并填充新画板 ---
376+ const newBoard = new Uint32Array ( newH * newRowStride ) . fill ( 0 ) ;
377+
378+ // --- 3. 一次性将旧数据复制到新画板中心 ---
379+ for ( let y = 0 ; y < oldH ; y ++ ) {
380+ // 计算旧画板中当前行的读取位置
381+ const sourceStartIndex = y * oldRowStride ;
382+ const sourceEndIndex = sourceStartIndex + oldRowStride ;
383+
384+ // 计算新画板中当前行的写入位置(考虑顶部和左侧边框)
385+ const destStartIndex = ( y + paddingTop ) * newRowStride + paddingLeft ;
386+
387+ // 使用 slice 提取行数据(高效),用 set 写入新位置(最高效)
388+ const rowData = board . slice ( sourceStartIndex , sourceEndIndex ) ;
389+ newBoard . set ( rowData , destStartIndex ) ;
362390 }
363- this . insertZerosToArray ( board , 0 , heightArr . length + diffWidth / 2 ) ;
364- this . _size = [ w + ( diffWidth << 5 ) , h + diffHeight ] ;
391+
392+ // --- 4. 更新尺寸和边界信息 ---
393+ this . _size = [ newW , newH ] ;
365394 if ( bounds ) {
366- bounds [ 0 ] . x += ( diffWidth << 5 ) / 2 ;
367- bounds [ 0 ] . y += diffHeight / 2 ;
368- bounds [ 1 ] . x += ( diffWidth << 5 ) / 2 ;
369- bounds [ 1 ] . y += diffHeight / 2 ;
395+ const offsetX = ( diffWidth << 5 ) / 2 ;
396+ const offsetY = diffHeight / 2 ;
397+ bounds [ 0 ] . x += offsetX ;
398+ bounds [ 0 ] . y += offsetY ;
399+ bounds [ 1 ] . x += offsetX ;
400+ bounds [ 1 ] . y += offsetY ;
370401 }
402+
403+ // --- 5. 返回新创建的画板 ---
404+ return newBoard ;
371405 }
372406
373407 // 分组扩充填充数组, 一次填充超过大概126000+会报stack overflow,worker环境下大概6w,这边取个比较小的
@@ -402,7 +436,7 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> implements IPro
402436 return { context : context , ratio : ratio , canvas } ;
403437 }
404438
405- private place ( board : number [ ] , tag : TagItem , bounds : Bounds , maxRadius : number ) {
439+ private place ( board : Uint32Array , tag : TagItem , bounds : Bounds , maxRadius : number ) {
406440 let isCollide = false ;
407441 // 情况1,超长词语
408442 if ( this . shouldShrinkContinue ( ) && ( tag . width > this . _size [ 0 ] || tag . height > this . _size [ 1 ] ) ) {
@@ -725,7 +759,7 @@ function cloudSprite(contextAndRatio: any, d: TagItem, data: TagItem[], di: numb
725759}
726760
727761// Use mask-based collision detection.
728- function cloudCollide ( tag : TagItem , board : number [ ] , size : [ number , number ] ) {
762+ function cloudCollide ( tag : TagItem , board : Uint32Array , size : [ number , number ] ) {
729763 const sw = size [ 0 ] >> 5 ;
730764 const sprite = tag . sprite ;
731765 const w = tag . width >> 5 ;
0 commit comments