@@ -894,7 +894,6 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
894894
895895 const ta = ( cs . getPropertyValue ( 'text-align' ) || parent ?. ta || 'start' ) . trim ( ) . toLowerCase ( ) ;
896896
897- // paddings
898897 let padT = parseFloat ( cs . getPropertyValue ( 'padding-top' ) ) || 0 ;
899898 let padR = parseFloat ( cs . getPropertyValue ( 'padding-right' ) ) || 0 ;
900899 let padB = parseFloat ( cs . getPropertyValue ( 'padding-bottom' ) ) || 0 ;
@@ -905,7 +904,6 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
905904 padT = t ; padR = r ; padB = b ; padL = l ;
906905 }
907906
908- // borders / box-sizing
909907 const bL = parseFloat ( cs . getPropertyValue ( 'border-left-width' ) ) || 0 ;
910908 const bR = parseFloat ( cs . getPropertyValue ( 'border-right-width' ) ) || 0 ;
911909 const box = cs . getPropertyValue ( 'box-sizing' ) || 'content-box' ;
@@ -958,7 +956,6 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
958956 const lcaEl = lcaPath [ lcaPath . length - 1 ] || htmlRoot ;
959957 const lcaStyle = readTextStyle ( lcaEl ) ;
960958
961- // accumulated padding
962959 let padLsum = 0 , padRsum = 0 , padTsum = 0 ;
963960 for ( const el of lcaPath ) {
964961 const st = readTextStyle ( el ) ;
@@ -968,21 +965,15 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
968965 }
969966
970967 const widths = [ Math . max ( 1 , wFO - padLsum - padRsum ) ] ;
971-
972- // For every ancestor in the LCA path, try to get the content width
973968 for ( const el of lcaPath ) {
974969 try {
975970 const cs = CACHE_CSS ( el ) ;
976-
977- // Preferred: clientWidth (padding included, border excluded)
978971 if ( el . clientWidth && el . clientWidth > 0 ) {
979972 const pL = parseFloat ( cs . paddingLeft ) || 0 ;
980973 const pR = parseFloat ( cs . paddingRight ) || 0 ;
981974 const cw = Math . max ( 1 , el . clientWidth - pL - pR ) ;
982975 widths . push ( cw ) ;
983976 }
984-
985- // Fallback: computed width
986977 const wStr = cs . width ;
987978 const wNum = parseFloat ( wStr ) ;
988979 if ( Number . isFinite ( wNum ) && wNum > 0 ) {
@@ -1018,7 +1009,7 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
10181009
10191010 const textEl = document . createElementNS ( SVG_NS , 'text' ) ;
10201011 textEl . setAttribute ( 'x' , String ( baseX ) ) ;
1021- textEl . setAttribute ( 'y' , String ( yFO + padTsum ) ) ; // keep first line at the top of the content box
1012+ textEl . setAttribute ( 'y' , String ( yFO + padTsum ) ) ;
10221013 textEl . setAttribute ( 'text-anchor' , anchor ) ;
10231014 textEl . setAttribute ( 'dominant-baseline' , 'text-before-edge' ) ;
10241015 textEl . setAttribute ( 'xml:space' , 'preserve' ) ;
@@ -1156,6 +1147,39 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
11561147 }
11571148 }
11581149
1150+ const measureHtmlNaturalSize = ( htmlEl ) => {
1151+ try {
1152+ const wrapper = document . createElement ( 'div' ) ;
1153+ wrapper . style . position = 'absolute' ;
1154+ wrapper . style . left = '-99999px' ;
1155+ wrapper . style . top = '-99999px' ;
1156+ wrapper . style . visibility = 'hidden' ;
1157+ wrapper . style . pointerEvents = 'none' ;
1158+ wrapper . style . width = 'auto' ;
1159+ wrapper . style . height = 'auto' ;
1160+
1161+ const clone = htmlEl . cloneNode ( true ) ;
1162+
1163+ clone . style . width = 'auto' ;
1164+ clone . style . height = 'auto' ;
1165+ clone . style . display = 'inline-block' ;
1166+ clone . style . maxWidth = 'none' ;
1167+ clone . style . maxHeight = 'none' ;
1168+ clone . style . boxSizing = 'content-box' ;
1169+
1170+ document . body . appendChild ( wrapper ) ;
1171+ wrapper . appendChild ( clone ) ;
1172+
1173+ const rect = clone . getBoundingClientRect ( ) ;
1174+ const w = Math . ceil ( rect . width || clone . scrollWidth || 0 ) ;
1175+ const h = Math . ceil ( rect . height || clone . scrollHeight || 0 ) ;
1176+
1177+ wrapper . remove ( ) ;
1178+ return { w : Math . max ( 1 , w ) , h : Math . max ( 1 , h ) } ;
1179+ } catch {
1180+ return { w : 0 , h : 0 } ;
1181+ }
1182+ } ;
11591183
11601184 // Text to svg
11611185 for ( const fo of list ) {
@@ -1193,10 +1217,21 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
11931217
11941218 await new Promise ( r => requestAnimationFrame ( r ) ) ;
11951219 const bb = fo . getBBox ( ) ;
1196- const w = Math . max ( 1 , Math . ceil ( bb . width ) ) ;
1197- const h = Math . max ( 1 , Math . ceil ( bb . height ) ) ;
1220+ let w = Math . max ( 1 , Math . ceil ( bb . width ) ) ;
1221+ let h = Math . max ( 1 , Math . ceil ( bb . height ) ) ;
1222+
1223+ // If the html subtree is bigger than FO use the bigger natural size
1224+ const contentRoot = fo . firstElementChild ;
1225+ if ( contentRoot ) {
1226+ const natural = measureHtmlNaturalSize ( contentRoot ) ;
1227+ const attrW = parseFloat ( fo . getAttribute ( 'width' ) || '0' ) || 0 ;
1228+ const attrH = parseFloat ( fo . getAttribute ( 'height' ) || '0' ) || 0 ;
1229+
1230+ w = Math . max ( w , attrW , natural . w ) ;
1231+ h = Math . max ( h , attrH , natural . h ) ;
1232+ }
11981233
1199- if ( ! w || ! h ) {
1234+ if ( ! ( w > 0 && h > 0 ) ) {
12001235 skipped += 1 ;
12011236 continue ;
12021237 }
@@ -1209,8 +1244,10 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
12091244 tempSvg . setAttribute ( 'viewBox' , `0 0 ${ w } ${ h } ` ) ;
12101245
12111246 const foClone = fo . cloneNode ( true ) ;
1212- foClone . setAttribute ( 'x' , '0' ) ; foClone . setAttribute ( 'y' , '0' ) ;
1213- foClone . setAttribute ( 'width' , String ( w ) ) ; foClone . setAttribute ( 'height' , String ( h ) ) ;
1247+ foClone . setAttribute ( 'x' , '0' ) ;
1248+ foClone . setAttribute ( 'y' , '0' ) ;
1249+ foClone . setAttribute ( 'width' , String ( w ) ) ;
1250+ foClone . setAttribute ( 'height' , String ( h ) ) ;
12141251 tempSvg . appendChild ( foClone ) ;
12151252
12161253 const xml = new XMLSerializer ( ) . serializeToString ( tempSvg ) ;
@@ -1239,8 +1276,8 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
12391276 image . setAttribute ( 'href' , dataUrl ) ;
12401277 image . setAttribute ( 'x' , fo . getAttribute ( 'x' ) || String ( bb . x ) ) ;
12411278 image . setAttribute ( 'y' , fo . getAttribute ( 'y' ) || String ( bb . y ) ) ;
1242- image . setAttribute ( 'width' , fo . getAttribute ( 'width' ) || String ( bb . width ) ) ;
1243- image . setAttribute ( 'height' , fo . getAttribute ( 'height' ) || String ( bb . height ) ) ;
1279+ image . setAttribute ( 'width' , String ( w ) ) ;
1280+ image . setAttribute ( 'height' , String ( h ) ) ;
12441281
12451282 fo . parentNode . replaceChild ( image , fo ) ;
12461283 rasterized += 1 ;
@@ -1252,3 +1289,4 @@ async function processForeignObjects(svgRoot, { mode = 'raster' } = {}) {
12521289
12531290 return { converted, rasterized, skipped, errors } ;
12541291}
1292+
0 commit comments