UNPKG

zrender

Version:

A lightweight graphic library providing 2d draw for Apache ECharts

241 lines (218 loc) 7.09 kB
import BoundingRect, { RectLike } from '../core/BoundingRect'; import { Dictionary, TextAlign, TextVerticalAlign, BuiltinTextPosition } from '../core/types'; import LRU from '../core/LRU'; import { DEFAULT_FONT, platformApi } from '../core/platform'; let textWidthCache: Dictionary<LRU<number>> = {}; 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; } /** * * 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); const x = adjustTextX(0, width, textAlign); const y = adjustTextY(0, height, textBaseline); const rect = new BoundingRect(x, y, width, height); return rect; } /** * * 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; } } 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; } export function getLineHeight(font?: string): number { // FIXME A rough approach. return getWidth('国', font); } export function measureText(text: string, font?: string): { width: number } { return platformApi.measureText(text, font); } export function parsePercent(value: number | string, maxValue: number): number { if (typeof value === 'string') { if (value.lastIndexOf('%') >= 0) { return parseFloat(value) / 100 * maxValue; } return parseFloat(value); } return value; } export interface TextPositionCalculationResult { x: number y: number align: TextAlign verticalAlign: TextVerticalAlign } /** * Follow same interface to `Displayable.prototype.calculateTextPosition`. * @public * @param out Prepared out object. If not input, auto created in the method. * @param style where `textPosition` and `textDistance` are visited. * @param rect {x, y, width, height} Rect of the host elment, according to which the text positioned. * @return The input `out`. Set: {x, y, textAlign, textVerticalAlign} */ export function calculateTextPosition( out: TextPositionCalculationResult, opts: { position?: BuiltinTextPosition | (number | string)[] distance?: number // Default 5 global?: boolean }, rect: RectLike ): TextPositionCalculationResult { const textPosition = opts.position || 'inside'; const distance = opts.distance != null ? opts.distance : 5; const height = rect.height; const width = rect.width; const halfHeight = height / 2; let x = rect.x; let y = rect.y; let textAlign: TextAlign = 'left'; let textVerticalAlign: TextVerticalAlign = 'top'; if (textPosition instanceof Array) { x += parsePercent(textPosition[0], rect.width); y += parsePercent(textPosition[1], rect.height); // Not use textAlign / textVerticalAlign textAlign = null; textVerticalAlign = null; } else { switch (textPosition) { case 'left': x -= distance; y += halfHeight; textAlign = 'right'; textVerticalAlign = 'middle'; break; case 'right': x += distance + width; y += halfHeight; textVerticalAlign = 'middle'; break; case 'top': x += width / 2; y -= distance; textAlign = 'center'; textVerticalAlign = 'bottom'; break; case 'bottom': x += width / 2; y += height + distance; textAlign = 'center'; break; case 'inside': x += width / 2; y += halfHeight; textAlign = 'center'; textVerticalAlign = 'middle'; break; case 'insideLeft': x += distance; y += halfHeight; textVerticalAlign = 'middle'; break; case 'insideRight': x += width - distance; y += halfHeight; textAlign = 'right'; textVerticalAlign = 'middle'; break; case 'insideTop': x += width / 2; y += distance; textAlign = 'center'; break; case 'insideBottom': x += width / 2; y += height - distance; textAlign = 'center'; textVerticalAlign = 'bottom'; break; case 'insideTopLeft': x += distance; y += distance; break; case 'insideTopRight': x += width - distance; y += distance; textAlign = 'right'; break; case 'insideBottomLeft': x += distance; y += height - distance; textVerticalAlign = 'bottom'; break; case 'insideBottomRight': x += width - distance; y += height - distance; textAlign = 'right'; textVerticalAlign = 'bottom'; break; } } out = out || {} as TextPositionCalculationResult; out.x = x; out.y = y; out.align = textAlign; out.verticalAlign = textVerticalAlign; return out; }