@@ -67,31 +67,38 @@ function parseCoordinate(value: string | null): number {
6767 return Number . isNaN ( parsed ) ? 0 : parsed ;
6868}
6969
70- function measureSpanContentHeight ( span : HTMLElement ) : number {
70+ interface MeasuredSpanContentDimensions {
71+ height : number ;
72+ width : number ;
73+ }
74+
75+ function measureSpanContentDimensions (
76+ span : HTMLElement ,
77+ measureWidth : boolean ,
78+ ) : MeasuredSpanContentDimensions {
7179 const prevHeight = span . style . height ;
80+ const prevWidth = span . style . width ;
7281 const prevOverflow = span . style . overflow ;
7382 try {
7483 span . style . height = 'max-content' ;
7584 span . style . overflow = 'hidden' ;
7685 void span . offsetHeight ; // force reflow
7786 const scrollHeight = span . scrollHeight ;
7887 const rectHeight = span . getBoundingClientRect ( ) . height ;
79- return Math . max ( scrollHeight , rectHeight ) ;
80- } finally {
81- span . style . height = prevHeight ;
82- span . style . overflow = prevOverflow ;
83- }
84- }
88+ let width = span . scrollWidth ;
8589
86- function measureSpanContentWidth ( span : HTMLElement ) : number {
87- const prevWidth = span . style . width ;
88- const prevOverflow = span . style . overflow ;
89- try {
90- span . style . width = 'max-content' ;
91- span . style . overflow = 'hidden' ;
92- void span . offsetWidth ; // force reflow
93- return span . scrollWidth ;
90+ if ( measureWidth ) {
91+ span . style . width = 'max-content' ;
92+ void span . offsetWidth ; // force reflow
93+ width = span . scrollWidth ;
94+ }
95+
96+ return {
97+ height : Math . max ( scrollHeight , rectHeight ) ,
98+ width,
99+ } ;
94100 } finally {
101+ span . style . height = prevHeight ;
95102 span . style . width = prevWidth ;
96103 span . style . overflow = prevOverflow ;
97104 }
@@ -137,8 +144,16 @@ function createCoordConverter(
137144// and horizontally aligned content can overflow as well.
138145function getFOContentBoundsInSVG (
139146 fo : SVGForeignObjectElement ,
140- content : HTMLElement ,
141147 toSVGCoord : ( x : number , y : number ) => SVGPoint ,
148+ {
149+ contentHeight,
150+ contentWidth,
151+ keepForeignObjectWidth,
152+ } : {
153+ contentHeight : number ;
154+ contentWidth : number ;
155+ keepForeignObjectWidth : boolean ;
156+ } ,
142157) : [ number , number , number , number ] {
143158 const foRect = fo . getBoundingClientRect ( ) ;
144159 const foTopLeft = toSVGCoord ( foRect . left , foRect . top ) ;
@@ -156,19 +171,17 @@ function getFOContentBoundsInSVG(
156171 foRect . height > 0 ? foHeightSVG / foRect . height : 1 ;
157172 const svgUnitsPerClientPxX = foRect . width > 0 ? foWidthSVG / foRect . width : 1 ;
158173
159- // Measure actual content dimensions
160- const realScrollHeight = measureSpanContentHeight ( content ) ;
161174 const contentHeightSVG =
162- realScrollHeight > 0
163- ? realScrollHeight * svgUnitsPerClientPxY
175+ contentHeight > 0
176+ ? contentHeight * svgUnitsPerClientPxY
164177 : foHeightSVG ;
165178
166- const computedStyle = window . getComputedStyle ( content ) ;
179+ const computedStyle = window . getComputedStyle ( fo . firstElementChild as Element ) ;
167180 const alignItems = computedStyle . alignItems ;
168181 const justifyContent = computedStyle . justifyContent ;
169- const contentWidthSVG = shouldKeepForeignObjectWidth ( computedStyle )
182+ const contentWidthSVG = keepForeignObjectWidth
170183 ? foWidthSVG
171- : Math . max ( foWidthSVG , measureSpanContentWidth ( content ) * svgUnitsPerClientPxX ) ;
184+ : Math . max ( foWidthSVG , contentWidth * svgUnitsPerClientPxX ) ;
172185
173186 // Calculate vertical bounds
174187 let top : number , bottom : number ;
@@ -214,18 +227,32 @@ function collectForeignObjectExportAdjustments(svg: SVGSVGElement) {
214227 ) . map ( ( fo ) => {
215228 const content = fo . firstElementChild as HTMLElement | null ;
216229 if ( ! content ) return null ;
230+ const computedStyle = window . getComputedStyle ( content ) ;
231+ const keepForeignObjectWidth = shouldKeepForeignObjectWidth ( computedStyle ) ;
232+ const measuredContent = measureSpanContentDimensions (
233+ content ,
234+ ! keepForeignObjectWidth ,
235+ ) ;
217236
218237 const parent =
219238 fo . parentElement instanceof SVGGraphicsElement ? fo . parentElement : svg ;
220239 const toParentCoord = createCoordConverter ( svg , parent ) ;
221240 const toLocalCoord = createCoordConverter ( svg , fo ) ;
222241 if ( ! toParentCoord ) return null ;
223242
224- const parentBounds = getFOContentBoundsInSVG ( fo , content , toParentCoord ) ;
243+ const parentBounds = getFOContentBoundsInSVG ( fo , toParentCoord , {
244+ contentHeight : measuredContent . height ,
245+ contentWidth : measuredContent . width ,
246+ keepForeignObjectWidth,
247+ } ) ;
225248 const originalX = parseCoordinate ( fo . getAttribute ( 'x' ) ) ;
226249 const originalY = parseCoordinate ( fo . getAttribute ( 'y' ) ) ;
227250 const localBounds = toLocalCoord
228- ? getFOContentBoundsInSVG ( fo , content , toLocalCoord )
251+ ? getFOContentBoundsInSVG ( fo , toLocalCoord , {
252+ contentHeight : measuredContent . height ,
253+ contentWidth : measuredContent . width ,
254+ keepForeignObjectWidth,
255+ } )
229256 : null ;
230257 const hasTransform = fo . hasAttribute ( 'transform' ) ;
231258 if ( hasTransform && ! localBounds ) return null ;
@@ -245,7 +272,11 @@ function collectForeignObjectExportAdjustments(svg: SVGSVGElement) {
245272 } ;
246273
247274 return {
248- rootBounds : getFOContentBoundsInSVG ( fo , content , toSVGCoord ) ,
275+ rootBounds : getFOContentBoundsInSVG ( fo , toSVGCoord , {
276+ contentHeight : measuredContent . height ,
277+ contentWidth : measuredContent . width ,
278+ keepForeignObjectWidth,
279+ } ) ,
249280 exportBounds,
250281 } satisfies ForeignObjectExportAdjustment ;
251282 } ) ;
0 commit comments