Skip to content

Latest commit

 

History

History
179 lines (154 loc) · 5.47 KB

File metadata and controls

179 lines (154 loc) · 5.47 KB
layout title date updated tags categories
posts
ECharts源码解析之文字包围盒
2020-09-21 09:16:41 -0700
2020-09-21 09:16:41 -0700
源码解读
ECharts源码解析
ECharts
可视化
学习
源码解读
ECharts

版本:V5.3.2

背景

由于SVG Text标签并无background-*属性所以要实现文字背景相对比较困难,ECharts实现Text的方案是基于Text的外层包围盒,来绘制矩形实现文字背景的。 通过本文我们简单探究下echarts是如何实现获取Text的包围盒的。

image-20220526100211246

源码

Path: /src/contain/text.ts

/**
 *
 * Get bounding rect for outer usage. Compatitable with old implementation
 * Which includes text newline.
 */
export function getBoundingRect(
    text: string,
    font: string,
    textAlign?: TextAlign,
    textBaseline?: TextVerticalAlign
) {
    const textLines = ((text || '') + '').split('\n');
    const len = textLines.length;
    //  区分单行和多行文字
    if (len === 1) {
        return innerGetBoundingRect(textLines[0], font, textAlign, textBaseline);
    }
    else {
        const uniondRect = new BoundingRect(0, 0, 0, 0);
        for (let i = 0; i < textLines.length; i++) {
            const rect = innerGetBoundingRect(textLines[i], font, textAlign, textBaseline);
            i === 0 ? uniondRect.copy(rect) : uniondRect.union(rect);
        }
        return uniondRect;
    }
}

/**
 *
 * Get bounding rect for inner usage(TSpan)
 * Which not include text newline.
 */
export function innerGetBoundingRect(
    text: string,
    font: string,
    textAlign?: TextAlign,
    textBaseline?: TextVerticalAlign
): BoundingRect {
    // 获取 宽高信息
    const width = getWidth(text, font);
    const height = getLineHeight(font);

    // 获取xy信息
    const x = adjustTextX(0, width, textAlign);
    const y = adjustTextY(0, height, textBaseline);

    // 写入外层包围盒
    const rect = new BoundingRect(x, y, width, height);

    return rect;
}

export function getWidth(text: string, font: string): number {
    font = font || DEFAULT_FONT;
    let cacheOfFont = textWidthCache[font];
    if (!cacheOfFont) {
        cacheOfFont = textWidthCache[font] = new LRU(500);
    }
    let width = cacheOfFont.get(text);
    if (width == null) {
        //这里在计算宽度
        width = platformApi.measureText(text, font).width;
        cacheOfFont.put(text, width);
    }

    return width;
}

// 这个计算高度的方式挺诡异 但似乎有效
export function getLineHeight(font?: string): number {
    // FIXME A rough approach.
    return getWidth('国', font);
}

export function adjustTextX(x: number, width: number, textAlign: TextAlign): number {
    // TODO Right to left language
    if (textAlign === 'right') {
        x -= width;
    }
    else if (textAlign === 'center') {
        x -= width / 2;
    }
    return x;
}

export function adjustTextY(y: number, height: number, verticalAlign: TextVerticalAlign): number {
    if (verticalAlign === 'middle') {
        y -= height / 2;
    }
    else if (verticalAlign === 'bottom') {
        y -= height;
    }
    return y;
}

Path: /core/platform.ts

  measureText: (function () {
      let _ctx: CanvasRenderingContext2D;
      let _cachedFont: string;
      return (text: string, font?: string) => {
          if (!_ctx) {
              const canvas = platformApi.createCanvas();
              _ctx = canvas && canvas.getContext('2d');
          }
          if (_ctx) {
              if (_cachedFont !== font) {
                  _cachedFont = _ctx.font = font || DEFAULT_FONT;
              }
              // 这里使用了 canvas 的 measureText API来获取text的宽度
              return _ctx.measureText(text);
          }
          else {
              text = text || '';
              font = font || DEFAULT_FONT;
              // Use font size if there is no other method can be used.
              const res = /^([0-9]*?)px$/.exec(font);
              const fontSize = +(res && res[1]) || DEFAULT_FONT_SIZE;
              let width = 0;
              if (font.indexOf('mono') >= 0) {   // is monospace
                  width = fontSize * text.length;
              }
              else {
                  for (let i = 0; i < text.length; i++) {
                      const preCalcWidth = DEFAULT_TEXT_WIDTH_MAP[text[i]];
                      width += preCalcWidth == null ? fontSize : (preCalcWidth * fontSize);
                  }
              }
              return { width };
          }
      };
  })(),

小结

SVGText宽高计算本质上还是使用Canvas的方式来计算的。

Text的宽度计算是基于canvasmeasureTextAPI来实现的。高度计算是计算了一个类矩形文字的宽度来实现的(这里采用了'国'字)。X,Y的计算则依赖了Text本身的位置以及左右和上下对齐的方式来重新计算的。

参考 & 引用

SVG - Text with background color and rounded borders - Stack Overflow

css - Background color of text in SVG - Stack Overflow

Examples - Apache ECharts