UNPKG

devexpress-diagram

Version:

DevExpress Diagram Control

241 lines (208 loc) 9.43 kB
import { UnitConverter } from "@devexpress/utils/lib/class/unit-converter"; import { svgNS } from "../RenderHelper"; import { SvgPrimitive } from "./Primitive"; import { TextStyle } from "../../Model/Style"; import { RenderUtils } from "../Utils"; import { ITextMeasurer, TextOwner } from "../Measurer/ITextMeasurer"; import { wordsByLines, textToParagraphs, LINE_HEIGHT, textToWords } from "../../Utils/TextUtils"; export enum TextAngle { Angle0deg = 0, Angle90deg = 90, Angle180deg = 180, Angle270deg = 270 } export class TextPrimitive extends SvgPrimitive<SVGTextElement> { filterId: string; private textSegmens: string[]; private renderHelper: TextPrimitiveRenderHelper; static readonly baselineCorrection = 0.35; constructor( public x: number, public y: number, public text: string, public owner: TextOwner, public textWidth?: number, public textHeight?: number, public textSpacing?: number, style?: TextStyle, public reverseTextAhchor?: boolean, clipPathId?: string, filterId?: string, public angle?: TextAngle, onApplyProperties?: (SVGElement) => void) { super(style, "", clipPathId, onApplyProperties); this.filterId = filterId; this.textSegmens = textToParagraphs(this.text); this.renderHelper = this.createRenderHelper(); if(this.textWidth !== undefined && this.textWidth !== undefined) { this.x = this.renderHelper.getTextX(this.x); this.y = this.renderHelper.getTextY(this.y); } } protected createMainElement(): SVGTextElement { return document.createElementNS(svgNS, "text"); } applyElementProperties(element: SVGTextElement, measurer: ITextMeasurer) { this.setUnitAttribute(element, "x", this.x); this.setUnitAttribute(element, "y", this.y); if(this.filterId) element.setAttribute("filter", RenderUtils.getUrlPathById(this.filterId)); super.applyElementProperties(element, measurer); if(element.getAttribute("appliedText") !== this.text || element.getAttribute("appliedSize") !== (this.fitToSize && this.fitToSize.toString())) { this.createTSpanElements(element, measurer); element.setAttribute("appliedText", this.text); element.setAttribute("appliedSize", (this.fitToSize && this.fitToSize.toString())); } else this.prepareTSpanElements(element); this.renderHelper.prepareMainElement(element, this.x, this.y); } private createTSpanElements(element: SVGTextElement, measurer: ITextMeasurer) { RenderUtils.removeContent(element); this.textSegmens.forEach((txt, index) => { if(!txt && this.textSegmens.length > 1) { const span = this.createTSpanElement(element); span.textContent = " "; } else if(this.fitToSize) { const words = textToWords(txt); const lines = wordsByLines(UnitConverter.twipsToPixels(this.fitToSize), words, () => measurer.measureWords(words, this.style, this.owner)); lines.forEach(line => { const span = this.createTSpanElement(element); span.textContent = line; }); if(!lines.length) { const span = this.createTSpanElement(element); span.textContent = " "; } } else { const tSpan = this.createTSpanElement(element); tSpan.textContent = txt; } }); const firstTSpan = <SVGTSpanElement>element.firstChild; if(firstTSpan) this.prepareFirstTSpanElement(firstTSpan, element.childNodes.length); } protected createTSpanElement(parent: SVGTextElement): SVGTSpanElement { const tSpan = document.createElementNS(svgNS, "tspan"); parent.appendChild(tSpan); this.prepareTSpanElement(tSpan); return tSpan; } protected prepareTSpanElements(element: SVGTextElement) { for(let i = 0; i < element.childNodes.length; i++) { const tSpan = <SVGTSpanElement>element.childNodes[i]; this.prepareTSpanElement(tSpan); } const firstTSpan = <SVGTSpanElement>element.firstChild; if(firstTSpan) this.prepareFirstTSpanElement(firstTSpan, element.childNodes.length); } private prepareTSpanElement(tSpan: SVGTSpanElement) { this.renderHelper.prepareTSpanElement(tSpan, this.x, this.y); } protected prepareFirstTSpanElement(tSpan: SVGTSpanElement, lineCount: number) { this.renderHelper.prepareFirstTSpanElement(tSpan, lineCount); } protected applyElementStyleProperties(element: SVGTextElement) { this.applyElementStylePropertiesCore(element, this.reverseTextAhchor); } private get fitToSize() { return this.renderHelper.fitToSize; } private createRenderHelper() { switch(this.angle) { case TextAngle.Angle90deg: return new TextPrimitive90degRenderHelper(this); case TextAngle.Angle180deg: return new TextPrimitive180degRenderHelper(this); case TextAngle.Angle270deg: return new TextPrimitive270degRenderHelper(this); default: return new TextPrimitiveRenderHelper(this); } } } export class TextPrimitiveRenderHelper { constructor(public primitive: TextPrimitive) { } get textWidth(): number { return this.primitive.textWidth; } get textHeight(): number { return this.primitive.textHeight; } get fitToSize(): number { return this.textWidth; } get textAnchor(): string { return this.primitive.style["text-anchor"]; } get textSpacing(): number { return this.primitive.textSpacing; } get angle(): number { return undefined; } get needRotation(): boolean { return false; } prepareMainElement(element: SVGTextElement, x: number, y: number) { if(this.needRotation) element.setAttribute("transform", "rotate(" + this.angle + ", " + UnitConverter.twipsToPixels(x) + ", " + UnitConverter.twipsToPixels(y) + ")"); } prepareTSpanElement(tSpan: SVGTSpanElement, x: number, y: number) { this.primitive.setUnitAttribute(tSpan, "x", x); tSpan.setAttribute("dy", LINE_HEIGHT + "em"); } prepareFirstTSpanElement(tSpan: SVGTSpanElement, lineCount: number) { const dy = -((lineCount - 1) / 2) + TextPrimitive.baselineCorrection; tSpan.setAttribute("dy", dy.toFixed(2) + "em"); } getTextX(x: number): number { if(!this.textAnchor || this.textAnchor === "middle") return x + this.textWidth / 2; else if(this.textAnchor === "end") return x + this.textWidth - this.textSpacing; else if(this.textAnchor === "start") return x + this.textSpacing; return x; } getTextY(y: number): number { return y + this.textHeight / 2; } protected setUnitAttribute(element: SVGElement, key: string, value: number | string) { this.primitive.setUnitAttribute(element, key, value); } } export class TextPrimitive90degRenderHelper extends TextPrimitiveRenderHelper { constructor(primitive: TextPrimitive) { super(primitive); } get fitToSize(): number { return this.textHeight; } get angle(): number { return 90; } get needRotation(): boolean { return true; } getTextX(x: number): number { return x + this.textWidth / 2; } getTextY(y: number): number { if(!this.textAnchor || this.textAnchor === "middle") return y + this.textHeight / 2; else if(this.textAnchor === "end") return y + this.textHeight - this.textSpacing; else if(this.textAnchor === "start") return y + this.textSpacing; return y; } } export class TextPrimitive180degRenderHelper extends TextPrimitiveRenderHelper { constructor(primitive: TextPrimitive) { super(primitive); } get angle(): number { return 180; } get needRotation(): boolean { return true; } getTextX(x: number): number { if(!this.textAnchor || this.textAnchor === "middle") return x + this.textWidth / 2; else if(this.textAnchor === "start") return x + this.textWidth - this.textSpacing; else if(this.textAnchor === "end") return x + this.textSpacing; return x; } } export class TextPrimitive270degRenderHelper extends TextPrimitive90degRenderHelper { constructor(primitive: TextPrimitive) { super(primitive); } get angle(): number { return 270; } getTextY(y: number): number { if(!this.textAnchor || this.textAnchor === "middle") return y + this.textHeight / 2; else if(this.textAnchor === "start") return y + this.textHeight - this.textSpacing; else if(this.textAnchor === "end") return y + this.textSpacing; return y; } }