@@ -41,11 +41,10 @@ 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 ;
47- const fontSize = computeTextFontSizeInPixels ( style ) ;
48- return computeTextLinesHeight ( fontSize , numberOfLines ) + 2 * PADDING_AUTORESIZE_VERTICAL ;
46+ const lines = splitTextToWidth ( ctx , content , style , maxWidth ) ;
47+ return computeMultilineTextSize ( ctx , lines , style ) . height + 2 * PADDING_AUTORESIZE_VERTICAL ;
4948}
5049
5150export function getDefaultContextFont (
@@ -58,29 +57,48 @@ export function getDefaultContextFont(
5857 return `${ italicStr } ${ weight } ${ fontSize } px ${ DEFAULT_FONT } ` ;
5958}
6059
61- const textWidthCache : Record < string , Record < string , number > > = { } ;
60+ export function computeMultilineTextSize (
61+ context : Canvas2DContext ,
62+ textLines : string [ ] ,
63+ style : Style = { } ,
64+ fontUnit : "px" | "pt" = "pt"
65+ ) {
66+ if ( ! textLines . length ) return { width : 0 , height : 0 } ;
67+ const font = computeTextFont ( style , fontUnit ) ;
68+ const sizes = textLines . map ( ( line ) => computeCachedTextDimension ( context , line , font ) ) ;
69+ const height = computeTextLinesHeight ( sizes [ 0 ] . height , textLines . length ) ;
70+ const width = Math . max ( ...sizes . map ( ( size ) => size . width ) ) ;
71+ if ( ! style . rotation ) {
72+ return { height, width } ;
73+ }
74+ const cos = Math . abs ( Math . cos ( style . rotation ) ) ;
75+ const sin = Math . abs ( Math . sin ( style . rotation ) ) ;
76+ return { width : width * cos + height * sin , height : sin * width + cos * height } ;
77+ }
6278
6379export function computeTextWidth (
6480 context : Canvas2DContext ,
6581 text : string ,
66- style : Style ,
82+ style : Style = { } ,
6783 fontUnit : "px" | "pt" = "pt"
6884) {
6985 const font = computeTextFont ( style , fontUnit ) ;
70- return computeCachedTextWidth ( context , text , font ) ;
86+ return computeCachedTextWidth ( context , text , font , style . rotation ) ;
7187}
7288
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 ;
89+ function computeCachedTextWidth (
90+ context : Canvas2DContext ,
91+ text : string ,
92+ font : string ,
93+ rotation ?: number
94+ ) {
95+ const size = computeCachedTextDimension ( context , text , font ) ;
96+ if ( ! rotation ) {
97+ return size . width ;
8298 }
83- return textWidthCache [ font ] [ text ] ;
99+ const cos = Math . abs ( Math . cos ( rotation ) ) ;
100+ const sin = Math . abs ( Math . sin ( rotation ) ) ;
101+ return size . width * cos + size . height * sin ;
84102}
85103
86104const textDimensionsCache : Record < string , Record < string , { width : number ; height : number } > > = { } ;
@@ -92,23 +110,31 @@ export function computeTextDimension(
92110 fontUnit : "px" | "pt" = "pt"
93111) : { width : number ; height : number } {
94112 const font = computeTextFont ( style , fontUnit ) ;
95- context . save ( ) ;
96- context . font = font ;
97- const dimensions = computeCachedTextDimension ( context , text ) ;
98- context . restore ( ) ;
99- return dimensions ;
113+ const size = computeCachedTextDimension ( context , text , font ) ;
114+ if ( ! style . rotation ) {
115+ return size ;
116+ }
117+ const cos = Math . abs ( Math . cos ( style . rotation ) ) ;
118+ const sin = Math . abs ( Math . sin ( style . rotation ) ) ;
119+ return {
120+ width : size . width * cos + size . height * sin ,
121+ height : size . height * cos + size . width * sin ,
122+ } ;
100123}
101124
102125function computeCachedTextDimension (
103126 context : Canvas2DContext ,
104- text : string
127+ text : string ,
128+ font : string
105129) : { width : number ; height : number } {
106- const font = context . font ;
107130 if ( ! textDimensionsCache [ font ] ) {
108131 textDimensionsCache [ font ] = { } ;
109132 }
110133 if ( textDimensionsCache [ font ] [ text ] === undefined ) {
134+ context . save ( ) ;
135+ context . font = font ;
111136 const measure = context . measureText ( text ) ;
137+ context . restore ( ) ;
112138 const width = measure . width ;
113139 const height = measure . fontBoundingBoxAscent + measure . fontBoundingBoxDescent ;
114140 textDimensionsCache [ font ] [ text ] = { width, height } ;
@@ -396,3 +422,61 @@ export function sliceTextToFitWidth(
396422 const slicedText = text . slice ( 0 , Math . max ( 0 , lowerBoundLen - 1 ) ) ;
397423 return slicedText ? slicedText + ellipsis : "" ;
398424}
425+
426+ /**
427+ * Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
428+ * with to original's text vertical and horizontal alignment.
429+ */
430+ export function computeRotationPosition (
431+ rect : { x : number ; y : number ; textWidth : number ; textHeight : number } ,
432+ style : Style
433+ ) : PixelPosition {
434+ if ( ! style . rotation || style . rotation % ( Math . PI * 2 ) === 0 ) {
435+ return rect ;
436+ }
437+ let { x, y } = rect ; // top-left when align=left and top-right when align=right, top-center when align=center
438+ const cos = Math . cos ( - style . rotation ) ;
439+ const sin = Math . sin ( - style . rotation ) ;
440+ const width = rect . textWidth - MIN_CELL_TEXT_MARGIN ;
441+ const height = rect . textHeight ;
442+
443+ const center = style . align === "center" ;
444+ const rotateTowardCellCenter = ( style . align === "left" ) === sin < 0 ;
445+
446+ const sh = sin * height ;
447+ const sw = Math . abs ( sin * width ) ;
448+ const ch = cos * height ;
449+
450+ // Adapt the anchor position based on the alignment and rotation
451+ if ( style . verticalAlign === "top" ) {
452+ if ( center ) {
453+ y += sw / 2 ;
454+ x -= sh / 2 ;
455+ } else if ( rotateTowardCellCenter ) {
456+ x -= sh ;
457+ } else {
458+ y += sw ;
459+ }
460+ } else if ( ! style . verticalAlign || style . verticalAlign === "bottom" ) {
461+ y += height - ch ;
462+ if ( center ) {
463+ y -= sw / 2 ;
464+ x -= sh / 2 ;
465+ } else if ( rotateTowardCellCenter ) {
466+ x -= sh ;
467+ y -= sw ;
468+ }
469+ } else {
470+ if ( center ) {
471+ x -= sh / 2 ;
472+ } else if ( rotateTowardCellCenter ) {
473+ x -= sh ;
474+ y -= sw / 2 ;
475+ } else {
476+ y += sw / 2 + ch / 4 ;
477+ }
478+ }
479+
480+ // Return the coordinate in the rotate 2d plane
481+ return { x : cos * x - sin * y , y : cos * y + sin * x } ;
482+ }
0 commit comments