123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- 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;
- }
|