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