Mapboxgl Chrome75版本下发现问题:中文标签无法加载,由Canvas的measureText()方法导致

发布时间 2023-04-17 13:58:29作者: 宇宙野牛

很刁钻的问题,排查了好久。

我自己开发测试用的浏览器(版本为112)运行正常,在老版本(75)谷歌浏览器报错如下:

mapbox-gl.js:32 Uncaught TypeError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': Value is not of type 'long'.
    at Mp.TinySDF.draw (mapbox-gl.js:32)
    at Mp._tinySDF (mapbox-gl.js:32)
    at mapbox-gl.js:32
    at mapbox-gl.js:31
    at Array.forEach (<anonymous>)
    at M (mapbox-gl.js:31)
    at Mp.getGlyphs (mapbox-gl.js:32)
    at Jt.getGlyphs (mapbox-gl.js:36)
    at t.Actor.processTask (mapbox-gl.js:32)
    at t.Actor.receive (mapbox-gl.js:32)

起初以为是Mapboxgl的问题(虽然最终也确实是有一点关系),后来经过艰难排查发现是Canvas的measureText()方法在老版本谷歌浏览器中无法正常返回中文字符的宽度。
在Mapboxgl源码中出问题的部分在这里:

    draw(char) {
        const {
            width: glyphAdvance,
            actualBoundingBoxAscent,
            actualBoundingBoxDescent,
            actualBoundingBoxLeft,
            actualBoundingBoxRight
        } = this.ctx.measureText(char);  
	
        // The integer/pixel part of the top alignment is encoded in metrics.glyphTop
        // The remainder is implicitly encoded in the rasterization
        const glyphTop = Math.ceil(actualBoundingBoxAscent);
        const glyphLeft = 0;

        // If the glyph overflows the canvas size, it will be clipped at the bottom/right
        const glyphWidth = Math.max(0, Math.min(this.size - this.buffer, Math.ceil(actualBoundingBoxRight - actualBoundingBoxLeft)));
        const glyphHeight = Math.min(this.size - this.buffer, glyphTop + Math.ceil(actualBoundingBoxDescent));
        
        const width = glyphWidth + 2 * this.buffer;
        const height = glyphHeight + 2 * this.buffer;

        const len = Math.max(width * height, 0);
        const data = new Uint8ClampedArray(len);

        const glyph = {data, width, height, glyphWidth, glyphHeight, glyphTop, glyphLeft, glyphAdvance};
        if (glyphWidth === 0 || glyphHeight === 0) return glyph;

        const {ctx, buffer, gridInner, gridOuter} = this;
        ctx.clearRect(buffer, buffer, glyphWidth, glyphHeight);
        ctx.fillText(char, buffer, buffer + glyphTop);
        
        const imgData = ctx.getImageData(buffer, buffer, glyphWidth, glyphHeight);

        ...
    }

打印发现glyphWidth, glyphHeight是undefined。这几个值由最上面的actualBoundingBoxAscent, actualBoundingBoxDescent, actualBoundingBoxLeft, actualBoundingBoxRight计算得来,而这四个值在对象是中文字符时没有正常返回,从而导致整个绘制流程出错,结果表现就是带有中文字符标签(text-field)的symbol图层无法加载。

解决办法:

  1. 规避。升级浏览器,或避免标签出现中文。
  2. 修改该部分源码,在测量方法不能正常返回值时给glyphWidth和glyphHeight默认值,这样也可以绘制出中文标签