@@ -71,6 +71,9 @@ export interface TextConfig extends ShapeConfig {
7171 fontVariant ?: string ;
7272 textDecoration ?: string ;
7373 underlineOffset ?: number ;
74+ textDecorationColor ?: string ;
75+ textDecorationWidth ?: number ;
76+ textDecorationStyle ?: string ;
7477 align ?: string ;
7578 verticalAlign ?: string ;
7679 padding ?: number ;
@@ -242,6 +245,9 @@ export class Text extends Shape<TextConfig> {
242245 fill = this . fill ( ) ,
243246 textDecoration = this . textDecoration ( ) ,
244247 underlineOffset = this . underlineOffset ( ) ,
248+ textDecorationColor = this . textDecorationColor ( ) ,
249+ textDecorationWidth = this . textDecorationWidth ( ) ,
250+ textDecorationStyle = this . textDecorationStyle ( ) ,
245251 shouldUnderline = textDecoration . indexOf ( 'underline' ) !== - 1 ,
246252 shouldLineThrough = textDecoration . indexOf ( 'line-through' ) !== - 1 ,
247253 n ;
@@ -309,19 +315,56 @@ export class Text extends Shape<TextConfig> {
309315 : Math . round ( fontSize / 2 ) ) ;
310316 const x = lineTranslateX ;
311317 const y = translateY + lineTranslateY + yOffset ;
312- context . moveTo ( x , y ) ;
313- const lineWidth =
318+ const decoLineWidth =
314319 align === JUSTIFY && ! lastLine ? totalWidth - padding * 2 : width ;
315- context . lineTo ( x + Math . round ( lineWidth ) , y ) ;
320+ const lw = textDecorationWidth || fontSize / 15 ;
321+ context . lineWidth = lw ;
322+
323+ if ( textDecorationColor ) {
324+ context . strokeStyle = textDecorationColor ;
325+ } else {
326+ const gradient = this . _getLinearGradient ( ) ;
327+ context . strokeStyle = gradient || fill ;
328+ }
316329
317- // I have no idea what is real ratio
318- // just /15 looks good enough
319- context . lineWidth = fontSize / 15 ;
330+ if ( textDecorationStyle === 'dashed' ) {
331+ context . setLineDash ( [ lw * 3 , lw * 2 ] ) ;
332+ } else if ( textDecorationStyle === 'dotted' ) {
333+ context . setLineDash ( [ lw , lw * 1.5 ] ) ;
334+ context . lineCap = 'round' ;
335+ } else if ( textDecorationStyle === 'wavy' ) {
336+ // Draw a sine wave instead of a straight line
337+ const step = Math . max ( lw * 2 , 4 ) ;
338+ const amp = Math . max ( lw , 1.5 ) ;
339+ context . moveTo ( x , y ) ;
340+ for ( let wx = x ; wx <= x + Math . round ( decoLineWidth ) ; wx += step ) {
341+ const mid = wx + step / 2 ;
342+ const end = Math . min ( wx + step , x + Math . round ( decoLineWidth ) ) ;
343+ context . quadraticCurveTo ( mid , y - amp , end , y ) ;
344+ wx += step ;
345+ if ( wx > x + Math . round ( decoLineWidth ) ) break ;
346+ const mid2 = wx + step / 2 ;
347+ const end2 = Math . min ( wx + step , x + Math . round ( decoLineWidth ) ) ;
348+ context . quadraticCurveTo ( mid2 , y + amp , end2 , y ) ;
349+ }
350+ context . stroke ( ) ;
351+ context . restore ( ) ;
352+ } else if ( textDecorationStyle === 'double' ) {
353+ const gap = lw * 2 ;
354+ context . moveTo ( x , y ) ;
355+ context . lineTo ( x + Math . round ( decoLineWidth ) , y ) ;
356+ context . moveTo ( x , y + gap ) ;
357+ context . lineTo ( x + Math . round ( decoLineWidth ) , y + gap ) ;
358+ context . stroke ( ) ;
359+ context . restore ( ) ;
360+ }
320361
321- const gradient = this . _getLinearGradient ( ) ;
322- context . strokeStyle = gradient || fill ;
323- context . stroke ( ) ;
324- context . restore ( ) ;
362+ if ( textDecorationStyle !== 'wavy' && textDecorationStyle !== 'double' ) {
363+ context . moveTo ( x , y ) ;
364+ context . lineTo ( x + Math . round ( decoLineWidth ) , y ) ;
365+ context . stroke ( ) ;
366+ context . restore ( ) ;
367+ }
325368 }
326369
327370 // store the starting x position for line-through which is drawn after text
@@ -396,17 +439,35 @@ export class Text extends Shape<TextConfig> {
396439 ? - Math . round ( fontSize / 4 )
397440 : 0 ;
398441 const x = lineThroughStartX ;
399- context . moveTo ( x , translateY + lineTranslateY + yOffset ) ;
400- const lineWidth =
442+ const ltY = translateY + lineTranslateY + yOffset ;
443+ const ltLineWidth =
401444 align === JUSTIFY && ! lastLine ? totalWidth - padding * 2 : width ;
445+ const ltLw = textDecorationWidth || fontSize / 15 ;
446+ context . lineWidth = ltLw ;
447+
448+ if ( textDecorationColor ) {
449+ context . strokeStyle = textDecorationColor ;
450+ } else {
451+ const gradient = this . _getLinearGradient ( ) ;
452+ context . strokeStyle = gradient || fill ;
453+ }
454+
455+ if ( textDecorationStyle === 'dashed' ) {
456+ context . setLineDash ( [ ltLw * 3 , ltLw * 2 ] ) ;
457+ } else if ( textDecorationStyle === 'dotted' ) {
458+ context . setLineDash ( [ ltLw , ltLw * 1.5 ] ) ;
459+ context . lineCap = 'round' ;
460+ }
461+
462+ context . moveTo ( x , ltY ) ;
463+ context . lineTo ( x + Math . round ( ltLineWidth ) , ltY ) ;
464+
465+ if ( textDecorationStyle === 'double' ) {
466+ const gap = ltLw * 2 ;
467+ context . moveTo ( x , ltY + gap ) ;
468+ context . lineTo ( x + Math . round ( ltLineWidth ) , ltY + gap ) ;
469+ }
402470
403- context . lineTo (
404- x + Math . round ( lineWidth ) ,
405- translateY + lineTranslateY + yOffset
406- ) ;
407- context . lineWidth = fontSize / 15 ;
408- const gradient = this . _getLinearGradient ( ) ;
409- context . strokeStyle = gradient || fill ;
410471 context . stroke ( ) ;
411472 context . restore ( ) ;
412473 }
@@ -771,6 +832,9 @@ export class Text extends Shape<TextConfig> {
771832 lineHeight : GetSet < number , this> ;
772833 textDecoration : GetSet < string , this> ;
773834 underlineOffset : GetSet < number , this> ;
835+ textDecorationColor : GetSet < string , this> ;
836+ textDecorationWidth : GetSet < number , this> ;
837+ textDecorationStyle : GetSet < string , this> ;
774838 text : GetSet < string , this> ;
775839 wrap : GetSet < string , this> ;
776840 ellipsis : GetSet < boolean , this> ;
@@ -1078,6 +1142,59 @@ Factory.addGetterSetter(
10781142 getNumberValidator ( )
10791143) ;
10801144
1145+ /**
1146+ * get/set text decoration color. When set, underline and line-through decorations
1147+ * use this color instead of the text fill color.
1148+ * @name Konva.Text#textDecorationColor
1149+ * @method
1150+ * @param {String } textDecorationColor
1151+ * @returns {String }
1152+ * @example
1153+ * // get text decoration color
1154+ * var color = text.textDecorationColor();
1155+ *
1156+ * // set text decoration color
1157+ * text.textDecorationColor('#18A0FB');
1158+ */
1159+ Factory . addGetterSetter ( Text , 'textDecorationColor' , '' ) ;
1160+
1161+ /**
1162+ * get/set text decoration line width. When set, underline and line-through use
1163+ * this width instead of the default (fontSize / 15).
1164+ * @name Konva.Text#textDecorationWidth
1165+ * @method
1166+ * @param {Number } textDecorationWidth
1167+ * @returns {Number }
1168+ * @example
1169+ * // get text decoration width
1170+ * var width = text.textDecorationWidth();
1171+ *
1172+ * // set text decoration width
1173+ * text.textDecorationWidth(2);
1174+ */
1175+ Factory . addGetterSetter (
1176+ Text ,
1177+ 'textDecorationWidth' ,
1178+ undefined ,
1179+ getNumberValidator ( )
1180+ ) ;
1181+
1182+ /**
1183+ * get/set text decoration style. Can be 'solid' (default), 'dashed', 'dotted',
1184+ * 'double', or 'wavy'. Applies to both underline and line-through decorations.
1185+ * @name Konva.Text#textDecorationStyle
1186+ * @method
1187+ * @param {String } textDecorationStyle
1188+ * @returns {String }
1189+ * @example
1190+ * // get text decoration style
1191+ * var style = text.textDecorationStyle();
1192+ *
1193+ * // set wavy underline (e.g. for spell-check)
1194+ * text.textDecorationStyle('wavy');
1195+ */
1196+ Factory . addGetterSetter ( Text , 'textDecorationStyle' , '' ) ;
1197+
10811198/**
10821199 * get/set per-character render hook. The callback is invoked for each grapheme before drawing.
10831200 * It can mutate the provided context (e.g. translate, rotate, change styles) and should return void.
0 commit comments