@@ -41,11 +41,14 @@ export function getCellContentHeight(
4141 content : string ,
4242 style : Style | undefined ,
4343 colSize : number
44- ) {
44+ ) : number {
4545 const maxWidth = style ?. wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined ;
46- const numberOfLines = splitTextToWidth ( ctx , content , style , maxWidth ) . length ;
46+ const lines = splitTextToWidth ( ctx , content , style , maxWidth ) ;
4747 const fontSize = computeTextFontSizeInPixels ( style ) ;
48- return computeTextLinesHeight ( fontSize , numberOfLines ) + 2 * PADDING_AUTORESIZE_VERTICAL ;
48+ if ( ! style ?. rotation ) {
49+ return computeTextLinesHeight ( fontSize , lines . length ) + 2 * PADDING_AUTORESIZE_VERTICAL ;
50+ }
51+ return computeMultilineTextSize ( ctx , lines , style ) . height + 2 * PADDING_AUTORESIZE_VERTICAL ;
4952}
5053
5154export function getDefaultContextFont (
@@ -58,7 +61,38 @@ export function getDefaultContextFont(
5861 return `${ italicStr } ${ weight } ${ fontSize } px ${ DEFAULT_FONT } ` ;
5962}
6063
61- const textWidthCache : Record < string , Record < string , number > > = { } ;
64+ export function computeMultilineTextWidth (
65+ context : Canvas2DContext ,
66+ text : string [ ] ,
67+ style : Style ,
68+ fontUnit : "px" | "pt" = "pt"
69+ ) {
70+ const font = computeTextFont ( style , fontUnit ) ;
71+ const sizes = text . map ( ( line ) => computeCachedTextDimension ( context , line , font ) ) ;
72+ const width = Math . max ( ...sizes . map ( ( size ) => size . width ) ) ;
73+ if ( ! style . rotation ) return width ;
74+ const height = computeTextLinesHeight ( sizes [ 0 ] . height , text . length ) ;
75+ const cos = Math . abs ( Math . cos ( style . rotation ) ) ;
76+ const sin = Math . abs ( Math . sin ( style . rotation ) ) ;
77+ return width * cos + height * sin ;
78+ }
79+
80+ export function computeMultilineTextSize (
81+ context : Canvas2DContext ,
82+ text : string [ ] ,
83+ style : Style ,
84+ fontUnit : "px" | "pt" = "pt"
85+ ) {
86+ if ( ! text . length ) return { width : 0 , height : 0 } ;
87+ const font = computeTextFont ( style , fontUnit ) ;
88+ const sizes = text . map ( ( line ) => computeCachedTextDimension ( context , line , font ) ) ;
89+ const height = computeTextLinesHeight ( sizes [ 0 ] . height , text . length ) ;
90+ const width = Math . max ( ...sizes . map ( ( size ) => size . width ) ) ;
91+ if ( ! style . rotation ) return { height, width } ;
92+ const cos = Math . abs ( Math . cos ( style . rotation ) ) ;
93+ const sin = Math . abs ( Math . sin ( style . rotation ) ) ;
94+ return { width : width * cos + height * sin , height : sin * width + cos * height } ;
95+ }
6296
6397export function computeTextWidth (
6498 context : Canvas2DContext ,
@@ -67,20 +101,20 @@ export function computeTextWidth(
67101 fontUnit : "px" | "pt" = "pt"
68102) {
69103 const font = computeTextFont ( style , fontUnit ) ;
70- return computeCachedTextWidth ( context , text , font ) ;
104+ return computeCachedTextWidth ( context , text , font , style . rotation ) ;
71105}
72106
73- export function computeCachedTextWidth ( context : Canvas2DContext , text : string , font : string ) {
74- if ( ! textWidthCache [ font ] ) {
75- textWidthCache [ font ] = { } ;
76- }
77- if ( textWidthCache [ font ] [ text ] === undefined ) {
78- const oldFont = context . font ;
79- context . font = font ;
80- textWidthCache [ font ] [ text ] = context . measureText ( text ) . width ;
81- context . font = oldFont ;
82- }
83- return textWidthCache [ font ] [ text ] ;
107+ export function computeCachedTextWidth (
108+ context : Canvas2DContext ,
109+ text : string ,
110+ font : string ,
111+ rotation ?: number
112+ ) {
113+ const size = computeCachedTextDimension ( context , text , font ) ;
114+ if ( ! rotation ) return size . width ;
115+ const cos = Math . abs ( Math . cos ( rotation ) ) ;
116+ const sin = Math . abs ( Math . sin ( rotation ) ) ;
117+ return size . width * cos + size . height * sin ;
84118}
85119
86120const textDimensionsCache : Record < string , Record < string , { width : number ; height : number } > > = { } ;
@@ -92,23 +126,29 @@ export function computeTextDimension(
92126 fontUnit : "px" | "pt" = "pt"
93127) : { width : number ; height : number } {
94128 const font = computeTextFont ( style , fontUnit ) ;
95- context . save ( ) ;
96- context . font = font ;
97- const dimensions = computeCachedTextDimension ( context , text ) ;
98- context . restore ( ) ;
99- return dimensions ;
129+ const size = computeCachedTextDimension ( context , text , font ) ;
130+ if ( ! style . rotation ) return size ;
131+ const cos = Math . abs ( Math . cos ( style . rotation ) ) ;
132+ const sin = Math . abs ( Math . sin ( style . rotation ) ) ;
133+ return {
134+ width : size . width * cos + size . height * sin ,
135+ height : size . height * cos + size . width * sin ,
136+ } ;
100137}
101138
102139function computeCachedTextDimension (
103140 context : Canvas2DContext ,
104- text : string
141+ text : string ,
142+ font : string
105143) : { width : number ; height : number } {
106- const font = context . font ;
107144 if ( ! textDimensionsCache [ font ] ) {
108145 textDimensionsCache [ font ] = { } ;
109146 }
110147 if ( textDimensionsCache [ font ] [ text ] === undefined ) {
148+ context . save ( ) ;
149+ context . font = font ;
111150 const measure = context . measureText ( text ) ;
151+ context . restore ( ) ;
112152 const width = measure . width ;
113153 const height = measure . fontBoundingBoxAscent + measure . fontBoundingBoxDescent ;
114154 textDimensionsCache [ font ] [ text ] = { width, height } ;
@@ -396,3 +436,59 @@ export function sliceTextToFitWidth(
396436 const slicedText = text . slice ( 0 , Math . max ( 0 , lowerBoundLen - 1 ) ) ;
397437 return slicedText ? slicedText + ellipsis : "" ;
398438}
439+
440+ /**
441+ * Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
442+ * with to original's text vertical and horizontal alignment.
443+ */
444+ export function computeRotationPosition (
445+ rect : { x : number ; y : number ; textWidth : number ; textHeight : number } ,
446+ style : Style
447+ ) : PixelPosition {
448+ if ( ! style . rotation || ! ( style . rotation % ( Math . PI * 2 ) ) ) return rect ;
449+ let { x, y } = rect ;
450+ const cos = Math . cos ( - style . rotation ) ;
451+ const sin = Math . sin ( - style . rotation ) ;
452+ const width = rect . textWidth - MIN_CELL_TEXT_MARGIN ;
453+ const height = rect . textHeight ;
454+
455+ const center = style . align === "center" ;
456+ const rotateTowardCellCenter = ( style . align === "left" ) !== sin > 0 ;
457+
458+ const sh = sin * height ;
459+ const sw = Math . abs ( sin * width ) ;
460+ const ch = cos * height ;
461+
462+ // Adapt the anchor position based on the alignment and rotation
463+ if ( style . verticalAlign === "top" ) {
464+ if ( center ) {
465+ y += sw / 2 ;
466+ x -= sh / 2 ;
467+ } else if ( rotateTowardCellCenter ) {
468+ x -= sh ;
469+ } else {
470+ y += sw ;
471+ }
472+ } else if ( ! style . verticalAlign || style . verticalAlign === "bottom" ) {
473+ y += height - ch ;
474+ if ( center ) {
475+ y -= sw / 2 ;
476+ x -= sh / 2 ;
477+ } else if ( rotateTowardCellCenter ) {
478+ x -= sh ;
479+ y -= sw ;
480+ }
481+ } else {
482+ if ( center ) {
483+ x -= sh / 2 ;
484+ } else if ( rotateTowardCellCenter ) {
485+ x -= sh ;
486+ y -= sw / 2 ;
487+ } else {
488+ y += sw / 2 + ch / 4 ;
489+ }
490+ }
491+
492+ // Return the coordinate in the rotate 2d plane
493+ return { x : cos * x - sin * y , y : cos * y + sin * x } ;
494+ }
0 commit comments