@@ -13,6 +13,11 @@ type PreparedSvgResult = {
1313 payload : string ;
1414} ;
1515
16+ type SlideSize = {
17+ width : number ;
18+ height : number ;
19+ } ;
20+
1621/**
1722 * Compiles Typst code to SVG and prepares it for insertion.
1823 */
@@ -98,12 +103,21 @@ export async function insertOrUpdateFormula() {
98103 const selection = context . presentation . getSelectedShapes ( ) ;
99104 const selectedSlides = context . presentation . getSelectedSlides ( ) ;
100105 const allSlides = context . presentation . slides ;
106+ const pageSetup = context . presentation . pageSetup ;
101107
102108 selection . load ( "items" ) ;
103109 selectedSlides . load ( "items" ) ;
104110 allSlides . load ( "items" ) ;
111+ pageSetup . load ( [ "slideWidth" , "slideHeight" ] ) ;
105112 await context . sync ( ) ;
106113
114+ const slideSize : SlideSize = {
115+ width : pageSetup . slideWidth ,
116+ height : pageSetup . slideHeight ,
117+ } ;
118+
119+ const fittedSize = fitSizeWithinSlide ( prepared . size , slideSize ) ;
120+
107121 const targetSlide : PowerPoint . Slide | undefined = selectedSlides . items [ 0 ] || allSlides . items [ 0 ] ;
108122 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
109123 if ( ! targetSlide || targetSlide . isNullObject ) {
@@ -119,13 +133,14 @@ export async function insertOrUpdateFormula() {
119133
120134 const typstShape = await findTypstShape ( selection . items , allSlides . items , context ) ;
121135 if ( typstShape ) {
122- position = calculateCenteredPosition ( typstShape , prepared . size ) ;
136+ position = calculateCenteredPosition ( typstShape , fittedSize ) ;
137+ position = clampPositionWithinSlide ( position , fittedSize , slideSize ) ;
123138 rotation = typstShape . rotation ;
124139 typstShape . delete ( ) ;
125140 isReplacing = true ;
126141 await context . sync ( ) ;
127142 } else {
128- position = await calcShapeTopLeftToBeCentered ( prepared . size , context ) ;
143+ position = calcShapeTopLeftToBeCentered ( fittedSize , slideSize ) ;
129144 }
130145
131146 const existingShapeIds = new Set ( targetSlide . shapes . items . map ( shape => shape . id ) ) ;
@@ -135,7 +150,7 @@ export async function insertOrUpdateFormula() {
135150 fillColor : fillColor || null ,
136151 mathMode,
137152 position,
138- size : prepared . size ,
153+ size : fittedSize ,
139154 rotation,
140155 } , targetSlide . id , existingShapeIds ) ;
141156
@@ -236,6 +251,14 @@ export async function bulkUpdateFontSize() {
236251 return ;
237252 }
238253
254+ const pageSetup = context . presentation . pageSetup ;
255+ pageSetup . load ( [ "slideWidth" , "slideHeight" ] ) ;
256+ await context . sync ( ) ;
257+ const slideSize : SlideSize = {
258+ width : pageSetup . slideWidth ,
259+ height : pageSetup . slideHeight ,
260+ } ;
261+
239262 let successCount = 0 ;
240263
241264 for ( const shape of typstShapes ) {
@@ -254,7 +277,13 @@ export async function bulkUpdateFontSize() {
254277 continue ;
255278 }
256279
257- const position = calculateCenteredPosition ( shape , prepared . size ) ;
280+ const fittedSize = fitSizeWithinSlide ( prepared . size , slideSize ) ;
281+ let position = calculateCenteredPosition ( shape , fittedSize ) ;
282+ position = clampPositionWithinSlide (
283+ position ,
284+ fittedSize ,
285+ slideSize ,
286+ ) ;
258287 const rotation = shape . rotation ;
259288
260289 // Capture slide and existing shapes before deletion
@@ -274,7 +303,7 @@ export async function bulkUpdateFontSize() {
274303 fillColor,
275304 mathMode,
276305 position,
277- size : prepared . size ,
306+ size : fittedSize ,
278307 rotation,
279308 } , slideId , existingShapeIds ) ;
280309
@@ -309,19 +338,60 @@ function calculateCenteredPosition(
309338 } ;
310339}
311340
341+ /**
342+ * Scales a shape to fit the slide while preserving aspect ratio.
343+ *
344+ * The scale factor is computed as:
345+ * s = min(slideWidth / shapeWidth, slideHeight / shapeHeight, 1)
346+ */
347+ function fitSizeWithinSlide (
348+ shapeSize : { width : number ; height : number } ,
349+ slideSize : SlideSize ,
350+ ) : { width : number ; height : number } {
351+ if ( shapeSize . width <= 0 || shapeSize . height <= 0 ) {
352+ return shapeSize ;
353+ }
354+
355+ const widthScale = slideSize . width / shapeSize . width ;
356+ const heightScale = slideSize . height / shapeSize . height ;
357+ const scale = Math . min ( widthScale , heightScale , 1 ) ;
358+
359+ return {
360+ width : shapeSize . width * scale ,
361+ height : shapeSize . height * scale ,
362+ } ;
363+ }
364+
365+ /**
366+ * Clamps a position so the full shape remains inside the slide.
367+ *
368+ * The placement is clamped to:
369+ * - left: [0, slideWidth - shapeWidth]
370+ * - top: [0, slideHeight - shapeHeight]
371+ */
372+ function clampPositionWithinSlide (
373+ position : { left : number ; top : number } ,
374+ shapeSize : { width : number ; height : number } ,
375+ slideSize : SlideSize ,
376+ ) : { left : number ; top : number } {
377+ const maxLeft = Math . max ( 0 , slideSize . width - shapeSize . width ) ;
378+ const maxTop = Math . max ( 0 , slideSize . height - shapeSize . height ) ;
379+
380+ return {
381+ left : Math . min ( Math . max ( 0 , position . left ) , maxLeft ) ,
382+ top : Math . min ( Math . max ( 0 , position . top ) , maxTop ) ,
383+ } ;
384+ }
385+
312386/**
313387 * Calculates the top-left position for a shape to be centered on the slide.
314388 */
315- async function calcShapeTopLeftToBeCentered (
389+ function calcShapeTopLeftToBeCentered (
316390 shapeSize : { width : number ; height : number } ,
317- context : PowerPoint . RequestContext ,
391+ slideSize : SlideSize ,
318392) {
319- const pageSetup = context . presentation . pageSetup ;
320- pageSetup . load ( [ "slideWidth" , "slideHeight" ] ) ;
321- await context . sync ( ) ;
322-
323- const centerX = ( pageSetup . slideWidth - shapeSize . width ) / 2 ;
324- const centerY = ( pageSetup . slideHeight - shapeSize . height ) / 2 ;
393+ const centerX = ( slideSize . width - shapeSize . width ) / 2 ;
394+ const centerY = ( slideSize . height - shapeSize . height ) / 2 ;
325395
326- return { left : centerX , top : centerY } ;
396+ return clampPositionWithinSlide ( { left : centerX , top : centerY } , shapeSize , slideSize ) ;
327397}
0 commit comments