From 99cde255ac748ae8ce8a0130cae53b3bbd198443 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Thu, 24 Jul 2025 00:13:58 +0545 Subject: [PATCH 1/2] fix(fastapi): adds a default transparent shadow effect if shadow is None while creating PPTX, fix(nextjs): improvements on presentation_to_pptx_model endpoint, fix(nextjs): improves pdf export --- servers/fastapi/services/database.py | 2 +- .../services/pptx_presentation_creator.py | 91 ++++++---- servers/fastapi/services/temp_file_service.py | 2 +- servers/fastapi/tests/test_pptx_creator.py | 40 +++++ .../outline/components/OutlineItem.tsx | 2 +- servers/nextjs/app/api/export-as-pdf/route.ts | 2 +- .../api/presentation_to_pptx_model/route.ts | 158 +++++++++++------- servers/nextjs/utils/pptx_models_utils.ts | 68 ++------ 8 files changed, 222 insertions(+), 143 deletions(-) create mode 100644 servers/fastapi/tests/test_pptx_creator.py diff --git a/servers/fastapi/services/database.py b/servers/fastapi/services/database.py index f85eb80c..b08605b2 100644 --- a/servers/fastapi/services/database.py +++ b/servers/fastapi/services/database.py @@ -7,7 +7,7 @@ from utils.get_env import get_app_data_directory_env, get_database_url_env database_url = get_database_url_env() or "sqlite:///" + os.path.join( - get_app_data_directory_env(), "fastapi.db" + get_app_data_directory_env() or "/tmp/presenton", "fastapi.db" ) connect_args = {} if "sqlite" in database_url: diff --git a/servers/fastapi/services/pptx_presentation_creator.py b/servers/fastapi/services/pptx_presentation_creator.py index 1bbde2a5..8b2cda43 100644 --- a/servers/fastapi/services/pptx_presentation_creator.py +++ b/servers/fastapi/services/pptx_presentation_creator.py @@ -404,43 +404,76 @@ class PptxPresentationCreator: # # Remove existing shadow effects if present effect_list = sp_pr.find("a:effectLst", namespaces=nsmap) if effect_list: - old_shadow = effect_list.find("a:outerShdw") - if old_shadow: + old_outer_shadow = effect_list.find("a:outerShdw") + if old_outer_shadow: effect_list.remove( - old_shadow, namespaces=nsmap + old_outer_shadow, namespaces=nsmap + ) # Remove the old shadow + old_inner_shadow = effect_list.find("a:innerShdw") + if old_inner_shadow: + effect_list.remove( + old_inner_shadow, namespaces=nsmap + ) # Remove the old shadow + old_prst_shadow = effect_list.find("a:prstShdw") + if old_prst_shadow: + effect_list.remove( + old_prst_shadow, namespaces=nsmap ) # Remove the old shadow - - if not shadow: - return if not effect_list: effect_list = etree.SubElement( sp_pr, f"{{{nsmap['a']}}}effectLst", nsmap=nsmap ) - outer_shadow = etree.SubElement( - effect_list, - f"{{{nsmap['a']}}}outerShdw", - { - "blurRad": f"{Pt(shadow.radius)}", - "dir": f"{shadow.angle * 1000}", - "dist": f"{Pt(shadow.offset)}", - "rotWithShape": "0", - }, - nsmap=nsmap, - ) - color_element = etree.SubElement( - outer_shadow, - f"{{{nsmap['a']}}}srgbClr", - {"val": f"{shadow.color}"}, - nsmap=nsmap, - ) - etree.SubElement( - color_element, - f"{{{nsmap['a']}}}alpha", - {"val": f"{int(shadow.opacity * 100000)}"}, - nsmap=nsmap, - ) + if shadow is None: + # Apply shadow with zero values when shadow is None + outer_shadow = etree.SubElement( + effect_list, + f"{{{nsmap['a']}}}outerShdw", + { + "blurRad": "0", + "dist": "0", + "dir": "0", + }, + nsmap=nsmap, + ) + color_element = etree.SubElement( + outer_shadow, + f"{{{nsmap['a']}}}srgbClr", + {"val": "000000"}, + nsmap=nsmap, + ) + etree.SubElement( + color_element, + f"{{{nsmap['a']}}}alpha", + {"val": "0"}, + nsmap=nsmap, + ) + else: + # Apply the provided shadow + outer_shadow = etree.SubElement( + effect_list, + f"{{{nsmap['a']}}}outerShdw", + { + "blurRad": f"{Pt(shadow.radius)}", + "dir": f"{shadow.angle * 1000}", + "dist": f"{Pt(shadow.offset)}", + "rotWithShape": "0", + }, + nsmap=nsmap, + ) + color_element = etree.SubElement( + outer_shadow, + f"{{{nsmap['a']}}}srgbClr", + {"val": f"{shadow.color}"}, + nsmap=nsmap, + ) + etree.SubElement( + color_element, + f"{{{nsmap['a']}}}alpha", + {"val": f"{int(shadow.opacity * 100000)}"}, + nsmap=nsmap, + ) def set_fill_opacity(self, fill, opacity): if opacity is None or opacity >= 1.0: diff --git a/servers/fastapi/services/temp_file_service.py b/servers/fastapi/services/temp_file_service.py index f4c59cf5..6fdf07a6 100644 --- a/servers/fastapi/services/temp_file_service.py +++ b/servers/fastapi/services/temp_file_service.py @@ -8,7 +8,7 @@ from utils.get_env import get_temp_directory_env class TempFileService: def __init__(self): - self.base_dir = get_temp_directory_env() + self.base_dir = get_temp_directory_env() or "/tmp/presenton" # TODO: Uncomment this when we want to cleanup the base dir on startup # self.cleanup_base_dir() os.makedirs(self.base_dir, exist_ok=True) diff --git a/servers/fastapi/tests/test_pptx_creator.py b/servers/fastapi/tests/test_pptx_creator.py new file mode 100644 index 00000000..9cc92718 --- /dev/null +++ b/servers/fastapi/tests/test_pptx_creator.py @@ -0,0 +1,40 @@ +import asyncio +from models.pptx_models import ( + PptxAutoShapeBoxModel, + PptxFillModel, + PptxPositionModel, + PptxPresentationModel, + PptxSlideModel, +) +from services.pptx_presentation_creator import PptxPresentationCreator +from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE + + +pptx_model = PptxPresentationModel( + slides=[ + PptxSlideModel( + shapes=[ + PptxAutoShapeBoxModel( + type=MSO_AUTO_SHAPE_TYPE.RECTANGLE, + position=PptxPositionModel( + left=20, + right=20, + width=100, + height=100, + ), + fill=PptxFillModel( + color="000000", + opacity=0.5, + ), + ) + ] + ) + ] +) + + +def test_pptx_creator(): + temp_dir = "/tmp/presenton" + pptx_creator = PptxPresentationCreator(pptx_model, temp_dir) + asyncio.run(pptx_creator.create_ppt()) + pptx_creator.save("debug/test.pptx") diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineItem.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineItem.tsx index 4030603b..5972a3e5 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineItem.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineItem.tsx @@ -108,7 +108,7 @@ export function OutlineItem({ {isStreaming ?