@@ -88,7 +88,7 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> {
8888 _dy : number = 0 ;
8989
9090 contextAndRatio ?: { context : CanvasRenderingContext2D ; ratio : number ; canvas : HTMLCanvasElement } ;
91- _board : number [ ] ;
91+ _board : Uint32Array ;
9292 /** 已经绘制文字的最小包围盒 */
9393 _bounds : Bounds ;
9494
@@ -187,20 +187,20 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> {
187187 const distSize0 = Math . max ( d . width , d . height ) ;
188188 if ( distSize0 <= maxSize0 ) {
189189 // 扩大尺寸满足最小字体要求 =》 按照要求扩大board
190- this . expandBoard ( this . _board , this . _bounds , distSize0 / this . _size [ 0 ] ) ;
190+ this . _board = this . expandBoard ( this . _board , this . _bounds , distSize0 / this . _size [ 0 ] ) ;
191191 } else if ( this . options . clip ) {
192192 // 扩大尺寸不满足最小字体要求,但支持裁剪 =》 按最大尺寸扩大,裁剪词语
193- this . expandBoard ( this . _board , this . _bounds , maxSize0 / this . _size [ 0 ] ) ;
193+ this . _board = this . expandBoard ( this . _board , this . _bounds , maxSize0 / this . _size [ 0 ] ) ;
194194 } else {
195195 // 扩大尺寸不满足最小字体要求,且不支持裁剪 =》 丢弃词语
196196 return true ;
197197 }
198198 } else if ( this . _placeStatus === 3 ) {
199199 // 扩大画布
200- this . expandBoard ( this . _board , this . _bounds ) ;
200+ this . _board = this . expandBoard ( this . _board , this . _bounds ) ;
201201 } else {
202202 // 扩大画布
203- this . expandBoard ( this . _board , this . _bounds ) ;
203+ this . _board = this . expandBoard ( this . _board , this . _bounds ) ;
204204 }
205205 // 更新一次状态,下次大尺寸词语进入裁剪
206206 this . updateBoardExpandStatus ( d . fontSize ) ;
@@ -221,7 +221,7 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> {
221221 this . _originSize = [ ...this . _size ] ;
222222 const contextAndRatio = this . getContext ( this . options . createCanvas ( { width : 1 , height : 1 } ) ) ;
223223 this . contextAndRatio = contextAndRatio ;
224- this . _board = new Array ( ( this . _size [ 0 ] >> 5 ) * this . _size [ 1 ] ) . fill ( 0 ) ;
224+ this . _board = new Uint32Array ( ( this . _size [ 0 ] >> 5 ) * this . _size [ 1 ] ) . fill ( 0 ) ;
225225 // 已经绘制文字的最小包围盒
226226 this . _bounds = null ;
227227
@@ -338,34 +338,83 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> {
338338 this . _size = this . _size . map ( v => v * ( 1 - minRatio ) ) as any ;
339339 }
340340
341- // 扩充 bitmap
342- private expandBoard ( board : number [ ] , bounds : Bounds , factor ?: any ) {
343- const expandedLeftWidth = ( this . _size [ 0 ] * ( factor || 1.1 ) - this . _size [ 0 ] ) >> 5 ;
341+ // /**
342+ // * [已优化] 插入指定数量的零到数组中。
343+ // * 针对 length 较小的场景,这是最高效的实现。
344+ // * 在新的 expandBoard 实现中,此函数不再被需要,但为保持完整性而提供。
345+ // */
346+ // private insertZerosToArray(array: any[], index: number, length: number): void {
347+ // if (length <= 0) {
348+ // return;
349+ // }
350+ // // 对于 length 较小的场景,创建临时数组的开销极小,
351+ // // 而单次 splice() 调用可以利用 V8 的 C++ 底层优化,性能最好。
352+ // const zerosToInsert = new Array(length).fill(0);
353+ // array.splice(index, 0, ...zerosToInsert);
354+ // }
355+
356+ /**
357+ * [已优化] 通过重建法高效扩展画板,添加边框。
358+ *
359+ * @returns {number[] } 返回一个全新的、尺寸更大的画板数组。
360+ * @notice 这是一个重大变更:此函数不再原地修改 board,而是返回一个新数组。
361+ * 调用方需要相应地更新其引用,例如:this.board = this.expandBoard(...);
362+ */
363+ private expandBoard ( board : Uint32Array , bounds : Bounds , factor ?: any ) : Uint32Array {
364+ // --- 1. 计算所有尺寸和偏移量 ---
365+ const oldW = this . _size [ 0 ] ;
366+ const oldH = this . _size [ 1 ] ;
367+ const oldRowStride = oldW >> 5 ; // 每行的“块”数
368+
369+ // 计算水平和垂直方向需要增加的“块”数
370+ const expandedLeftWidth = ( oldW * ( factor || 1.1 ) - oldW ) >> 5 ;
344371 let diffWidth = expandedLeftWidth * 2 > 2 ? expandedLeftWidth : 2 ;
345372 if ( diffWidth % 2 !== 0 ) {
346- diffWidth ++ ;
373+ diffWidth ++ ; // 确保为偶数,以便左右对称
347374 }
348- let diffHeight = Math . ceil ( ( this . _size [ 1 ] * ( diffWidth << 5 ) ) / this . _size [ 0 ] ) ;
375+
376+ let diffHeight = Math . ceil ( ( oldH * ( diffWidth << 5 ) ) / oldW ) ;
349377 if ( diffHeight % 2 !== 0 ) {
350- diffHeight ++ ;
378+ diffHeight ++ ; // 确保为偶数,以便上下对称
351379 }
352- const w = this . _size [ 0 ] ;
353- const h = this . _size [ 1 ] ;
354- const widthArr = new Array ( diffWidth ) . fill ( 0 ) ;
355-
356- const heightArr = new Array ( ( diffHeight / 2 ) * ( diffWidth + ( w >> 5 ) ) ) . fill ( 0 ) ;
357- this . insertZerosToArray ( board , h * ( w >> 5 ) , heightArr . length + diffWidth / 2 ) ;
358- for ( let i = h - 1 ; i > 0 ; i -- ) {
359- this . insertZerosToArray ( board , i * ( w >> 5 ) , widthArr . length ) ;
380+
381+ const newW = oldW + ( diffWidth << 5 ) ;
382+ const newH = oldH + diffHeight ;
383+ const newRowStride = newW >> 5 ;
384+
385+ const paddingLeft = diffWidth / 2 ;
386+ const paddingTop = diffHeight / 2 ;
387+
388+ // --- 2. 创建并填充新画板 ---
389+ const newBoard = new Uint32Array ( newH * newRowStride ) . fill ( 0 ) ;
390+
391+ // --- 3. 一次性将旧数据复制到新画板中心 ---
392+ for ( let y = 0 ; y < oldH ; y ++ ) {
393+ // 计算旧画板中当前行的读取位置
394+ const sourceStartIndex = y * oldRowStride ;
395+ const sourceEndIndex = sourceStartIndex + oldRowStride ;
396+
397+ // 计算新画板中当前行的写入位置(考虑顶部和左侧边框)
398+ const destStartIndex = ( y + paddingTop ) * newRowStride + paddingLeft ;
399+
400+ // 使用 slice 提取行数据(高效),用 set 写入新位置(最高效)
401+ const rowData = board . slice ( sourceStartIndex , sourceEndIndex ) ;
402+ newBoard . set ( rowData , destStartIndex ) ;
360403 }
361- this . insertZerosToArray ( board , 0 , heightArr . length + diffWidth / 2 ) ;
362- this . _size = [ w + ( diffWidth << 5 ) , h + diffHeight ] ;
404+
405+ // --- 4. 更新尺寸和边界信息 ---
406+ this . _size = [ newW , newH ] ;
363407 if ( bounds ) {
364- bounds [ 0 ] . x += ( diffWidth << 5 ) / 2 ;
365- bounds [ 0 ] . y += diffHeight / 2 ;
366- bounds [ 1 ] . x += ( diffWidth << 5 ) / 2 ;
367- bounds [ 1 ] . y += diffHeight / 2 ;
408+ const offsetX = ( diffWidth << 5 ) / 2 ;
409+ const offsetY = diffHeight / 2 ;
410+ bounds [ 0 ] . x += offsetX ;
411+ bounds [ 0 ] . y += offsetY ;
412+ bounds [ 1 ] . x += offsetX ;
413+ bounds [ 1 ] . y += offsetY ;
368414 }
415+
416+ // --- 5. 返回新创建的画板 ---
417+ return newBoard ;
369418 }
370419
371420 // 分组扩充填充数组, 一次填充超过大概126000+会报stack overflow,worker环境下大概6w,这边取个比较小的
@@ -400,7 +449,7 @@ export class CloudLayout extends BaseLayout<ICloudLayoutOptions> {
400449 return { context : context , ratio : ratio , canvas } ;
401450 }
402451
403- private place ( board : number [ ] , tag : TagItem , bounds : Bounds , maxRadius : number ) {
452+ private place ( board : Uint32Array , tag : TagItem , bounds : Bounds , maxRadius : number ) {
404453 let isCollide = false ;
405454 // 情况1,超长词语
406455 if ( this . shouldShrinkContinue ( ) && ( tag . width > this . _size [ 0 ] || tag . height > this . _size [ 1 ] ) ) {
@@ -723,7 +772,7 @@ function cloudSprite(contextAndRatio: any, d: TagItem, data: TagItem[], di: numb
723772}
724773
725774// Use mask-based collision detection.
726- function cloudCollide ( tag : TagItem , board : number [ ] , size : [ number , number ] ) {
775+ function cloudCollide ( tag : TagItem , board : Uint32Array , size : [ number , number ] ) {
727776 const sw = size [ 0 ] >> 5 ;
728777 const sprite = tag . sprite ;
729778 const w = tag . width >> 5 ;
0 commit comments