mirror of
https://github.com/konvajs/konva.git
synced 2025-06-28 05:01:41 +08:00
feat: implement multi-styled text shape
This commit is contained in:
parent
d8da0c742e
commit
65c06c6fbc
@ -17,6 +17,7 @@ import { Ring } from './shapes/Ring';
|
||||
import { Sprite } from './shapes/Sprite';
|
||||
import { Star } from './shapes/Star';
|
||||
import { Text } from './shapes/Text';
|
||||
import { MultiStyledText } from './shapes/MultiStyledText';
|
||||
import { TextPath } from './shapes/TextPath';
|
||||
import { Transformer } from './shapes/Transformer';
|
||||
import { Wedge } from './shapes/Wedge';
|
||||
@ -59,6 +60,7 @@ export const Konva = Core.Util._assign(Core, {
|
||||
Star,
|
||||
Text,
|
||||
TextPath,
|
||||
MultiStyledText,
|
||||
Transformer,
|
||||
Wedge,
|
||||
/**
|
||||
|
671
src/shapes/MultiStyledText.ts
Normal file
671
src/shapes/MultiStyledText.ts
Normal file
@ -0,0 +1,671 @@
|
||||
import { Context } from '../Context'
|
||||
import { Factory } from '../Factory'
|
||||
import { _registerNode } from '../Global'
|
||||
import { Shape, ShapeConfig } from '../Shape'
|
||||
import { GetSet } from '../types'
|
||||
import { Util } from '../Util'
|
||||
import { getNumberOrAutoValidator, getNumberValidator, getBooleanValidator, getStringValidator } from '../Validators'
|
||||
|
||||
let dummyContext: Context & CanvasRenderingContext2D
|
||||
function getDummyContext() {
|
||||
if (dummyContext) {
|
||||
return dummyContext
|
||||
}
|
||||
dummyContext = Util.createCanvasElement().getContext('2d') as any
|
||||
return dummyContext
|
||||
}
|
||||
|
||||
function normalizeFontFamily(fontFamily: string) {
|
||||
return fontFamily
|
||||
.split(',')
|
||||
.map((family) => {
|
||||
family = family.trim();
|
||||
const hasSpace = family.indexOf(' ') >= 0;
|
||||
const hasQuotes = family.indexOf('"') >= 0 || family.indexOf("'") >= 0;
|
||||
if (hasSpace && !hasQuotes) {
|
||||
family = `"${family}"`;
|
||||
}
|
||||
return family;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
export interface TextStyle {
|
||||
start: number // start position of the style
|
||||
end?: number // end position of the style, if undefined it means until the end
|
||||
fontFamily: string
|
||||
fontSize: number
|
||||
fontStyle: 'normal' | 'italic' | 'bold' | 'italic bold' | 'bold italic'
|
||||
fontVariant: 'normal' | 'small-caps'
|
||||
textDecoration: '' | 'underline' | 'line-through' | 'underline line-through'
|
||||
fill: string
|
||||
stroke: string
|
||||
}
|
||||
|
||||
type TextPart = {
|
||||
text: string
|
||||
width: number
|
||||
style: Omit<TextStyle, 'start' | 'end'>
|
||||
}
|
||||
|
||||
export interface MultiStyledTextConfig extends ShapeConfig {
|
||||
text?: string;
|
||||
textStyles?: TextStyle[]
|
||||
align?: string;
|
||||
verticalAlign?: string;
|
||||
padding?: number;
|
||||
lineHeight?: number;
|
||||
letterSpacing?: number;
|
||||
wrap?: string;
|
||||
ellipsis?: boolean;
|
||||
}
|
||||
|
||||
export class MultiStyledText extends Shape<MultiStyledTextConfig> {
|
||||
public className = 'MultiStyledText'
|
||||
|
||||
public align!: GetSet<'left' | 'center' | 'right' | 'justify', this>
|
||||
public letterSpacing!: GetSet<number, this>
|
||||
public verticalAlign!: GetSet<'top' | 'middle' | 'bottom', this>
|
||||
public padding!: GetSet<number, this>
|
||||
public lineHeight!: GetSet<number, this>
|
||||
public text!: GetSet<string, this>
|
||||
public textStyles!: GetSet<TextStyle[], this>
|
||||
public wrap!: GetSet<'word' | 'char' | 'none', this>
|
||||
public ellipsis!: GetSet<boolean, this>
|
||||
|
||||
private textLines: {
|
||||
width: number
|
||||
totalHeight: number
|
||||
parts: TextPart[]
|
||||
}[] = []
|
||||
private linesWidth!: number
|
||||
private linesHeight!: number
|
||||
|
||||
// used when drawing
|
||||
private drawState!: {
|
||||
x: number
|
||||
y: number
|
||||
text: string
|
||||
}
|
||||
|
||||
constructor(config?: MultiStyledTextConfig) {
|
||||
super(config)
|
||||
// update text data for certain attr changes
|
||||
for (const attr of [
|
||||
'padding', 'wrap', 'lineHeight', 'letterSpacing', 'textStyles', 'width', 'height', 'text'
|
||||
]) {
|
||||
this.on(`${attr}Change.konva`, this.computeTextParts)
|
||||
}
|
||||
this.computeTextParts()
|
||||
}
|
||||
|
||||
private formatFont (part: Pick<TextPart, 'style'>) {
|
||||
return `${part.style.fontStyle} ${part.style.fontVariant} ${part.style.fontSize}px ${normalizeFontFamily(part.style.fontFamily)}`
|
||||
}
|
||||
|
||||
private measurePart (part: Omit<TextPart, 'width'>) {
|
||||
const context = getDummyContext()
|
||||
context.save()
|
||||
context.font = this.formatFont(part)
|
||||
const width = context.measureText(part.text).width
|
||||
context.restore()
|
||||
return width
|
||||
}
|
||||
|
||||
private computeTextParts () {
|
||||
this.textLines = []
|
||||
const lines = this.text().split('\n')
|
||||
const maxWidth = this.attrs.width
|
||||
const maxHeight = this.attrs.height
|
||||
const hasFixedWidth = maxWidth !== 'auto' && maxWidth !== undefined
|
||||
const hasFixedHeight = maxHeight !== 'auto' && maxHeight !== undefined
|
||||
|
||||
const shouldWrap = this.wrap() !== 'none'
|
||||
const wrapAtWord = this.wrap() !== 'char' && shouldWrap
|
||||
const shouldAddEllipsis = this.ellipsis()
|
||||
const styles = this.textStyles()
|
||||
const ellipsis = '…'
|
||||
const additionalWidth = shouldAddEllipsis ? this.measurePart({ text: ellipsis, style: styles[styles.length - 1] }) : 0;
|
||||
|
||||
const stylesByChar = Array.from(this.text()).map((char, index) => {
|
||||
return {
|
||||
char,
|
||||
style: styles.find((style) => index >= style.start && (typeof style.end === 'undefined' || style.end >= index))!
|
||||
}
|
||||
})
|
||||
const findParts = (start: number, end: number) => {
|
||||
// find matching characters
|
||||
const chars = stylesByChar.slice(start, end)
|
||||
// group them by style
|
||||
const parts: TextPart[] = []
|
||||
for (const char of chars) {
|
||||
const similarGroupIndex = parts.findIndex((part) => part.style === char.style)
|
||||
if (similarGroupIndex === -1) {
|
||||
parts.push({ text: char.char, width: 0, style: char.style })
|
||||
continue
|
||||
}
|
||||
parts[similarGroupIndex].text += char.char
|
||||
}
|
||||
return parts
|
||||
}
|
||||
const measureSubstring = (start: number, end: number) => {
|
||||
return measureParts(findParts(start, end))
|
||||
}
|
||||
const measureParts = (parts: TextPart[]) => {
|
||||
return parts.reduce((size, part) => {
|
||||
part.width = this.measurePart(part)
|
||||
return size + part.width
|
||||
}, 0)
|
||||
}
|
||||
const measureHeightParts = (parts: TextPart[]) => {
|
||||
return Math.max(...parts.map((part) => {
|
||||
return part.style.fontSize * this.lineHeight()
|
||||
}))
|
||||
}
|
||||
const addLine = (width: number, height: number, parts: TextPart[]) => {
|
||||
// if element height is fixed, abort if adding one more line would overflow
|
||||
// so we don't add this line, the loop will be broken anyway
|
||||
if (hasFixedHeight && (currentHeight + height) > maxHeight) {
|
||||
return
|
||||
}
|
||||
this.textLines.push({
|
||||
width,
|
||||
parts: parts.map((part) => {
|
||||
// compute size if not already computed during part creation
|
||||
part.width = part.width === 0 ? this.measurePart(part) : part.width
|
||||
return part
|
||||
}),
|
||||
totalHeight: height
|
||||
})
|
||||
}
|
||||
|
||||
let currentHeight = 0
|
||||
let charCount = 0
|
||||
for (let line of lines) {
|
||||
let lineWidth = measureSubstring(charCount, charCount + line.length)
|
||||
let lineHeight: number
|
||||
|
||||
if (hasFixedWidth && lineWidth > maxWidth) {
|
||||
/*
|
||||
* if width is fixed and line does not fit entirely
|
||||
* break the line into multiple fitting lines
|
||||
*/
|
||||
let cursor = 0
|
||||
while (line.length > 0) {
|
||||
/*
|
||||
* use binary search to find the longest substring that
|
||||
* that would fit in the specified width
|
||||
*/
|
||||
var low = 0,
|
||||
high = line.length,
|
||||
match = '',
|
||||
matchWidth = 0
|
||||
while (low < high) {
|
||||
var mid = (low + high) >>> 1,
|
||||
substr = line.slice(0, mid + 1),
|
||||
substrWidth = measureSubstring(charCount + cursor, charCount + cursor + mid + 1) + additionalWidth
|
||||
if (substrWidth <= maxWidth) {
|
||||
low = mid + 1
|
||||
match = substr
|
||||
matchWidth = substrWidth
|
||||
} else {
|
||||
high = mid
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 'low' is now the index of the substring end
|
||||
* 'match' is the substring
|
||||
* 'matchWidth' is the substring width in px
|
||||
*/
|
||||
if (match) {
|
||||
// a fitting substring was found
|
||||
if (wrapAtWord) {
|
||||
// try to find a space or dash where wrapping could be done
|
||||
let wrapIndex: number
|
||||
var nextChar = line[match.length]
|
||||
var nextIsSpaceOrDash = nextChar === ' ' || nextChar === '-'
|
||||
if (nextIsSpaceOrDash && matchWidth <= maxWidth) {
|
||||
wrapIndex = match.length
|
||||
} else {
|
||||
wrapIndex = Math.max(match.lastIndexOf(' '), match.lastIndexOf('-')) + 1
|
||||
}
|
||||
if (wrapIndex > 0) {
|
||||
// re-cut the substring found at the space/dash position
|
||||
low = wrapIndex
|
||||
match = match.slice(0, low)
|
||||
matchWidth = measureSubstring(charCount + cursor, charCount + cursor + low)
|
||||
}
|
||||
}
|
||||
// match = match.trimRight()
|
||||
const parts = findParts(charCount + cursor, charCount + cursor + low)
|
||||
lineHeight = measureHeightParts(parts)
|
||||
addLine(measureParts(parts), lineHeight, parts)
|
||||
currentHeight += lineHeight
|
||||
if (
|
||||
!shouldWrap ||
|
||||
(hasFixedHeight && currentHeight + lineHeight > maxHeight)
|
||||
) {
|
||||
const lastLine = this.textLines[this.textLines.length - 1]
|
||||
if (lastLine) {
|
||||
if (shouldAddEllipsis) {
|
||||
const lastPart = lastLine.parts[lastLine.parts.length - 1]
|
||||
const lastPartWidthWithEllipsis = this.measurePart({ ...lastPart, text: `${lastPart.text}${ellipsis}` })
|
||||
const haveSpace = lastPartWidthWithEllipsis < maxWidth
|
||||
if (!haveSpace) {
|
||||
lastPart.text = lastPart.text.slice(0, lastPart.text.length - 3)
|
||||
}
|
||||
lastLine.parts.splice(lastLine.parts.length - 1, 1)
|
||||
lastLine.parts.push({
|
||||
...lastPart,
|
||||
width: lastPartWidthWithEllipsis,
|
||||
text: `${lastPart.text}${ellipsis}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* stop wrapping if wrapping is disabled or if adding
|
||||
* one more line would overflow the fixed height
|
||||
*/
|
||||
break
|
||||
}
|
||||
line = line.slice(low)
|
||||
cursor += low
|
||||
// line = line.trimLeft()
|
||||
if (line.length > 0) {
|
||||
// Check if the remaining text would fit on one line
|
||||
const parts = findParts(charCount + cursor, charCount + cursor + line.length)
|
||||
lineWidth = measureParts(parts)
|
||||
if (lineWidth <= maxWidth) {
|
||||
// if it does, add the line and break out of the loop
|
||||
const height = measureHeightParts(parts)
|
||||
addLine(lineWidth, height, parts)
|
||||
currentHeight += height
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// not even one character could fit in the element, abort
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const parts = findParts(charCount, charCount + line.length)
|
||||
lineHeight = measureHeightParts(parts)
|
||||
addLine(lineWidth, lineHeight, parts)
|
||||
}
|
||||
|
||||
// if element height is fixed, abort if adding one more line would overflow
|
||||
// so we stop here to avoid processing useless lines
|
||||
if (hasFixedHeight && (currentHeight + lineHeight!) > maxHeight) {
|
||||
break
|
||||
}
|
||||
|
||||
charCount += line.length
|
||||
currentHeight += lineHeight!
|
||||
}
|
||||
|
||||
this.linesHeight = this.textLines.reduce((size, line) => size + line.totalHeight, 0)
|
||||
this.linesWidth = Math.max(...this.textLines.map((line) => line.width, 0))
|
||||
}
|
||||
|
||||
public getHeight (): number {
|
||||
const isAuto = this.attrs.height === 'auto' || this.attrs.height === undefined
|
||||
if (!isAuto) {
|
||||
return this.attrs.height
|
||||
}
|
||||
return this.linesHeight + this.padding() * 2
|
||||
}
|
||||
|
||||
public getWidth (): number {
|
||||
const isAuto = this.attrs.width === 'auto' || this.attrs.width === undefined
|
||||
if (!isAuto) {
|
||||
return this.attrs.width
|
||||
}
|
||||
return this.linesWidth + this.padding() * 2
|
||||
}
|
||||
|
||||
/**
|
||||
* @description This method is called when the shape should render
|
||||
* on canvas
|
||||
*/
|
||||
protected _sceneFunc(context: Context & CanvasRenderingContext2D) {
|
||||
if (this.text().length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const totalWidth = this.getWidth()
|
||||
const totalHeight = this.getHeight()
|
||||
|
||||
context.setAttr('textBaseline', 'middle')
|
||||
context.setAttr('textAlign', 'left')
|
||||
|
||||
// handle vertical alignment
|
||||
const padding = this.padding()
|
||||
let alignY = 0
|
||||
if (this.verticalAlign() === 'middle') {
|
||||
alignY = (totalHeight - this.linesHeight - padding * 2) / 2;
|
||||
} else if (this.verticalAlign() === 'bottom') {
|
||||
alignY = totalHeight - this.linesHeight - padding * 2;
|
||||
}
|
||||
context.translate(padding, alignY + padding)
|
||||
|
||||
let y = this.textLines[0].totalHeight / 2
|
||||
let lineIndex = 0
|
||||
for (const line of this.textLines) {
|
||||
const isLastLine = lineIndex === this.textLines.length - 1
|
||||
let lineX = 0
|
||||
let lineY = 0
|
||||
context.save()
|
||||
|
||||
// horizontal alignment
|
||||
if (this.align() === 'right') {
|
||||
lineX += totalWidth - line.width - padding * 2
|
||||
} else if (this.align() === 'center') {
|
||||
lineY += (totalWidth - line.width - padding * 2) / 2
|
||||
}
|
||||
|
||||
for (const part of line.parts) {
|
||||
|
||||
// style
|
||||
if (part.style.textDecoration.includes('underline')) {
|
||||
context.save();
|
||||
context.beginPath()
|
||||
|
||||
context.moveTo(
|
||||
lineX,
|
||||
y + lineY + Math.round(part.style.fontSize / 2)
|
||||
)
|
||||
const spacesNumber = part.text.split(' ').length - 1
|
||||
const oneWord = spacesNumber === 0
|
||||
const lineWidth =
|
||||
this.align() === 'justify' && isLastLine && !oneWord
|
||||
? totalWidth - padding * 2
|
||||
: part.width
|
||||
context.lineTo(
|
||||
lineX + Math.round(lineWidth),
|
||||
y + lineY + Math.round(part.style.fontSize / 2)
|
||||
)
|
||||
|
||||
// I have no idea what is real ratio
|
||||
// just /15 looks good enough
|
||||
context.lineWidth = part.style.fontSize / 15
|
||||
context.strokeStyle = part.style.fill
|
||||
context.stroke()
|
||||
context.restore()
|
||||
}
|
||||
if (part.style.textDecoration.includes('line-through')) {
|
||||
context.save()
|
||||
context.beginPath()
|
||||
context.moveTo(lineX, y + lineY)
|
||||
const spacesNumber = part.text.split(' ').length - 1
|
||||
const oneWord = spacesNumber === 0
|
||||
const lineWidth =
|
||||
this.align() === 'justify' && isLastLine && !oneWord
|
||||
? totalWidth - padding * 2
|
||||
: part.width
|
||||
context.lineTo(
|
||||
lineX + Math.round(lineWidth),
|
||||
y + lineY
|
||||
)
|
||||
context.lineWidth = part.style.fontSize / 15
|
||||
context.strokeStyle = part.style.fill
|
||||
context.stroke()
|
||||
context.restore()
|
||||
}
|
||||
|
||||
this.fill(part.style.fill)
|
||||
this.stroke(part.style.stroke)
|
||||
context.setAttr('font', this.formatFont(part))
|
||||
|
||||
// text
|
||||
if (this.letterSpacing() !== 0 || this.align() === 'justify') {
|
||||
const spacesNumber = part.text.split(' ').length - 1
|
||||
var array = Array.from(part.text)
|
||||
for (let li = 0; li < array.length; li++) {
|
||||
const letter = array[li]
|
||||
// skip justify for the last line
|
||||
if (letter === ' ' && lineIndex !== this.textLines.length - 1 && this.align() === 'justify') {
|
||||
lineX += (totalWidth - padding * 2 - line.width) / spacesNumber;
|
||||
}
|
||||
this.drawState = {
|
||||
x: lineX,
|
||||
y: y + lineY,
|
||||
text: letter
|
||||
}
|
||||
context.fillStrokeShape(this)
|
||||
lineX += this.measurePart({ ...part, text: letter }) + this.letterSpacing()
|
||||
}
|
||||
} else {
|
||||
this.drawState = {
|
||||
x: lineX,
|
||||
y: y + lineY,
|
||||
text: part.text
|
||||
}
|
||||
context.fillStrokeShape(this)
|
||||
lineX += part.width + this.letterSpacing()
|
||||
}
|
||||
}
|
||||
|
||||
context.restore()
|
||||
if (typeof this.textLines[lineIndex + 1] !== 'undefined') {
|
||||
y += this.textLines[lineIndex + 1].totalHeight
|
||||
}
|
||||
++lineIndex
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description This method is called by context.fillStrokeShape(this)
|
||||
* to fill the shape
|
||||
*/
|
||||
public _fillFunc = (context: Context) => {
|
||||
context.fillText(this.drawState.text, this.drawState.x, this.drawState.y)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description This method is called by context.fillStrokeShape(this)
|
||||
* to stroke the shape
|
||||
*/
|
||||
public _strokeFunc = (context: Context) => {
|
||||
context.strokeText(this.drawState.text, this.drawState.x, this.drawState.y, undefined)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description This method should render on canvas a rect with
|
||||
* the width and the height of the text shape
|
||||
*/
|
||||
protected _hitFunc(context: Context & CanvasRenderingContext2D) {
|
||||
context.beginPath()
|
||||
context.rect(0, 0, this.getWidth(), this.getHeight())
|
||||
context.closePath()
|
||||
context.fillStrokeShape(this)
|
||||
}
|
||||
|
||||
// for text we can't disable stroke scaling
|
||||
// if we do, the result will be unexpected
|
||||
public getStrokeScaleEnabled() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
_registerNode(MultiStyledText)
|
||||
|
||||
/**
|
||||
* get/set width of text area, which includes padding.
|
||||
* @name Konva.Text#width
|
||||
* @method
|
||||
* @param {Number} width
|
||||
* @returns {Number}
|
||||
* @example
|
||||
* // get width
|
||||
* var width = text.width();
|
||||
*
|
||||
* // set width
|
||||
* text.width(20);
|
||||
*
|
||||
* // set to auto
|
||||
* text.width('auto');
|
||||
* text.width() // will return calculated width, and not "auto"
|
||||
*/
|
||||
Factory.overWriteSetter(MultiStyledText, 'width', getNumberOrAutoValidator())
|
||||
|
||||
/**
|
||||
* get/set the height of the text area, which takes into account multi-line text, line heights, and padding.
|
||||
* @name Konva.Text#height
|
||||
* @method
|
||||
* @param {Number} height
|
||||
* @returns {Number}
|
||||
* @example
|
||||
* // get height
|
||||
* var height = text.height();
|
||||
*
|
||||
* // set height
|
||||
* text.height(20);
|
||||
*
|
||||
* // set to auto
|
||||
* text.height('auto');
|
||||
* text.height() // will return calculated height, and not "auto"
|
||||
*/
|
||||
Factory.overWriteSetter(MultiStyledText, 'height', getNumberOrAutoValidator())
|
||||
|
||||
/**
|
||||
* get/set padding
|
||||
* @name Konva.Text#padding
|
||||
* @method
|
||||
* @param {Number} padding
|
||||
* @returns {Number}
|
||||
* @example
|
||||
* // get padding
|
||||
* var padding = text.padding();
|
||||
*
|
||||
* // set padding to 10 pixels
|
||||
* text.padding(10);
|
||||
*/
|
||||
Factory.addGetterSetter(MultiStyledText, 'padding', 0, getNumberValidator())
|
||||
|
||||
/**
|
||||
* get/set horizontal align of text. Can be 'left', 'center', 'right' or 'justify'
|
||||
* @name Konva.Text#align
|
||||
* @method
|
||||
* @param {String} align
|
||||
* @returns {String}
|
||||
* @example
|
||||
* // get text align
|
||||
* var align = text.align();
|
||||
*
|
||||
* // center text
|
||||
* text.align('center');
|
||||
*
|
||||
* // align text to right
|
||||
* text.align('right');
|
||||
*/
|
||||
Factory.addGetterSetter(MultiStyledText, 'align', 'left')
|
||||
|
||||
/**
|
||||
* get/set vertical align of text. Can be 'top', 'middle', 'bottom'.
|
||||
* @name Konva.Text#verticalAlign
|
||||
* @method
|
||||
* @param {String} verticalAlign
|
||||
* @returns {String}
|
||||
* @example
|
||||
* // get text vertical align
|
||||
* var verticalAlign = text.verticalAlign();
|
||||
*
|
||||
* // center text
|
||||
* text.verticalAlign('middle');
|
||||
*/
|
||||
Factory.addGetterSetter(MultiStyledText, 'verticalAlign', 'top')
|
||||
|
||||
/**
|
||||
* get/set line height. The default is 1.
|
||||
* @name Konva.Text#lineHeight
|
||||
* @method
|
||||
* @param {Number} lineHeight
|
||||
* @returns {Number}
|
||||
* @example
|
||||
* // get line height
|
||||
* var lineHeight = text.lineHeight();
|
||||
*
|
||||
* // set the line height
|
||||
* text.lineHeight(2);
|
||||
*/
|
||||
Factory.addGetterSetter(MultiStyledText, 'lineHeight', 1, getNumberValidator())
|
||||
|
||||
/**
|
||||
* get/set wrap. Can be "word", "char", or "none". Default is "word".
|
||||
* In "word" wrapping any word still can be wrapped if it can't be placed in the required width
|
||||
* without breaks.
|
||||
* @name Konva.Text#wrap
|
||||
* @method
|
||||
* @param {String} wrap
|
||||
* @returns {String}
|
||||
* @example
|
||||
* // get wrap
|
||||
* var wrap = text.wrap();
|
||||
*
|
||||
* // set wrap
|
||||
* text.wrap('word');
|
||||
*/
|
||||
Factory.addGetterSetter(MultiStyledText, 'wrap', 'word')
|
||||
|
||||
/**
|
||||
* get/set ellipsis. Can be true or false. Default is false. If ellipses is true,
|
||||
* Konva will add "..." at the end of the text if it doesn't have enough space to write characters.
|
||||
* That is possible only when you limit both width and height of the text
|
||||
* @name Konva.Text#ellipsis
|
||||
* @method
|
||||
* @param {Boolean} ellipsis
|
||||
* @returns {Boolean}
|
||||
* @example
|
||||
* // get ellipsis param, returns true or false
|
||||
* var ellipsis = text.ellipsis();
|
||||
*
|
||||
* // set ellipsis
|
||||
* text.ellipsis(true);
|
||||
*/
|
||||
Factory.addGetterSetter(MultiStyledText, 'ellipsis', false, getBooleanValidator())
|
||||
|
||||
/**
|
||||
* set letter spacing property. Default value is 0.
|
||||
* @name Konva.Text#letterSpacing
|
||||
* @method
|
||||
* @param {Number} letterSpacing
|
||||
*/
|
||||
Factory.addGetterSetter(MultiStyledText, 'letterSpacing', 0, getNumberValidator())
|
||||
|
||||
/**
|
||||
* get/set text
|
||||
* @name Konva.Text#text
|
||||
* @method
|
||||
* @param {String} text
|
||||
* @returns {String}
|
||||
* @example
|
||||
* // get text
|
||||
* var text = text.text();
|
||||
*
|
||||
* // set text
|
||||
* text.text('Hello world!');
|
||||
*/
|
||||
Factory.addGetterSetter(MultiStyledText, 'text', '', getStringValidator())
|
||||
|
||||
/**
|
||||
* get/set textStyles
|
||||
* @name Konva.Text#textStyles
|
||||
* @method
|
||||
* @param {TextStyle[]} textStyles
|
||||
* @returns {String}
|
||||
* @example
|
||||
* // set styles
|
||||
* text.textStyles([{ start: 0, fontFamily: 'Roboto' }]);
|
||||
*/
|
||||
const defaultStyle: TextStyle = {
|
||||
start: 0,
|
||||
fill: 'black',
|
||||
stroke: 'black',
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 12,
|
||||
fontStyle: 'normal',
|
||||
fontVariant: 'normal',
|
||||
textDecoration: ''
|
||||
}
|
||||
Factory.addGetterSetter(MultiStyledText, 'textStyles', [defaultStyle])
|
Loading…
Reference in New Issue
Block a user