Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: refactor to reduce cognitive complexity #71

Merged
merged 2 commits into from
Sep 19, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 89 additions & 76 deletions app/SvgUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,35 @@

# Process img tags, replacing base64 SVG images with PNGs
def process_svg(html):
pattern = re.compile(r'<img(?P<intermediate>[^>]+?src="data:)(?P<type>[^;>]*?);base64, (?P<base64>[^">]*?)"')
pattern = re.compile(r'<img(?P<intermediate>[^>]+?src="data:)(?P<type>[^;>]*?);base64,\s?(?P<base64>[^">]*?)"')
return re.sub(pattern, replace_img_base64, html)


# Decode and validate if the provided content is SVG.
def get_svg_content(content_type, content_base64):
# We do not require to have 'image/svg+xml' content type coz not all systems will properly set it

if content_type in NON_SVG_CONTENT_TYPES:
return False # Skip processing if content type set explicitly as not svg

decoded_content = base64.b64decode(content_base64)
if b'\0' in decoded_content:
return False # Skip processing if decoded content is binary (not text)
try:
decoded_content = base64.b64decode(content_base64)
if b'\0' in decoded_content:
return False # Skip processing if decoded content is binary (not text)

svg_content = decoded_content.decode('utf-8')
svg_content = decoded_content.decode('utf-8')

# Fast check that this is a svg
if '</svg>' not in svg_content:
return False
# Fast check that this is a svg
if '</svg>' not in svg_content:
return False

return svg_content
return svg_content
except Exception as e:
logging.error(f"Failed to decode base64 content: {e}")
return False


# Replace base64 SVG images with PNG equivalents in the HTML img tag.
def replace_img_base64(match):
entry = match.group(0)
content_type = match.group('type')
Expand All @@ -42,89 +48,107 @@ def replace_img_base64(match):
svg_content = get_svg_content(content_type, content_base64)
if svg_content is False:
return entry
else:
replaced_content_base64 = replace_svg_with_png(svg_content)
if replaced_content_base64 == content_base64:
return entry # For some reason content wasn't replaced
else:
return f'<img{match.group("intermediate")}image/svg+xml;base64, {replaced_content_base64}"'

replaced_content_base64 = replace_svg_with_png(svg_content)
if replaced_content_base64 == content_base64:
return entry # For some reason content wasn't replaced

return f'<img{match.group("intermediate")}image/svg+xml;base64,{replaced_content_base64}"'


# Checks that base64 encoded content is a svg image and replaces it with the png screenshot made by chromium
def replace_svg_with_png(svg_content):
chromium_executable = os.environ.get('CHROMIUM_EXECUTABLE_PATH')
if not chromium_executable:
logging.error('CHROMIUM_EXECUTABLE_PATH not set')
width, height = extract_svg_dimensions(svg_content)
if not width or not height:
return svg_content

# Fetch width & height from root svg tag
match = re.search(r'<svg[^>]+?width="(?P<width>[\d.]+)', svg_content)
if match:
width = match.group('width')
else:
logging.error('Cannot find svg width in ' + svg_content)
svg_filepath, png_filepath = prepare_temp_files(svg_content)
if not svg_filepath or not png_filepath:
return svg_content

match = re.search(r'<svg[^>]+?height="(?P<height>[\d.]+)', svg_content)
if match:
height = match.group('height')
else:
logging.error('Cannot find svg height in ' + svg_content)
if not convert_svg_to_png(width, height, png_filepath, svg_filepath):
return svg_content

# Log large svg content size
svg_content_length = len(svg_content)
if svg_content_length > 100_000:
logging.warning(f"SVG content length: {svg_content_length}")
png_base64 = read_and_cleanup_png(png_filepath)
return png_base64 if png_base64 else svg_content

# Will be used as a name for tmp files
uuid = str(uuid4())

temp_folder = tempfile.gettempdir()
# Extract the width and height from the SVG tag
def extract_svg_dimensions(svg_content):
width_match = re.search(r'<svg[^>]+?width="(?P<width>[\d.]+)', svg_content)
height_match = re.search(r'<svg[^>]+?height="(?P<height>[\d.]+)', svg_content)

# Put svg into tmp file
svg_filepath = os.path.join(temp_folder, uuid + '.svg')
f = open(svg_filepath, 'w', encoding='utf-8')
f.write(svg_content)
f.close()
width = width_match.group('width') if width_match else None
height = height_match.group('height') if height_match else None

# Feed svg file to chromium
png_filepath = os.path.join(temp_folder, uuid + '.png')
if not width or not height:
logging.error(f"Cannot find SVG dimensions. Width: {width}, Height: {height}")
return width, height

chromium_command = create_chromium_command(
chromium_executable,
height,
width,
png_filepath,
svg_filepath,
)

result = subprocess.run(chromium_command)
# Save the SVG content to a temporary file and return the file paths for the SVG and PNG.
def prepare_temp_files(svg_content):
try:
temp_folder = tempfile.gettempdir()
uuid = str(uuid4())

# Remove tmp svg file
os.remove(svg_filepath)
svg_filepath = os.path.join(temp_folder, f'{uuid}.svg')
png_filepath = os.path.join(temp_folder, f'{uuid}.png')

with open(svg_filepath, 'w', encoding='utf-8') as f:
f.write(svg_content)

return svg_filepath, png_filepath
except Exception as e:
logging.error(f"Failed to save SVG to temp file: {e}")
return None, None


# Convert the SVG file to PNG using Chromium and return success status
def convert_svg_to_png(width, height, png_filepath, svg_filepath):
command = create_chromium_command(width, height, png_filepath, svg_filepath)
result = subprocess.run(command)

if result.returncode != 0:
logging.error(f'Error converting to png, returncode = {result.returncode}')
return svg_content
logging.error(f"Error converting SVG to PNG, return code = {result.returncode}")
return False

# Get resulting screenshot content
with open(png_filepath, 'rb') as img_file:
img_data = img_file.read()
png_base64 = base64.b64encode(img_data).decode('utf-8')
return True


# Read the PNG file, encode it in base64, and clean up the temporary file.
def read_and_cleanup_png(png_filepath):
try:
with open(png_filepath, 'rb') as img_file:
img_data = img_file.read()

# Remove tmp png file
os.remove(png_filepath)
png_base64 = base64.b64encode(img_data).decode('utf-8')
os.remove(png_filepath)
return png_base64
except Exception as e:
logging.error(f"Failed to read or clean up PNG file: {e}")
return None

return png_base64

# Create the Chromium command for converting SVG to PNG
def create_chromium_command(width, height, png_filepath, svg_filepath):
chromium_executable = os.environ.get('CHROMIUM_EXECUTABLE_PATH')
if not chromium_executable:
logging.error('CHROMIUM_EXECUTABLE_PATH is not set.')
return None

def create_chromium_command(chromium_executable, height, width, png_filepath, svg_filepath):
# Check if the ENABLE_HARDWARE_ACCELERATION environment variable is set to true
enable_hardware_acceleration = os.getenv('ENABLE_HARDWARE_ACCELERATION', 'false').lower() == 'true'

command = [
f'{chromium_executable}',
chromium_executable,
'--headless=old',
'--no-sandbox',
'--default-background-color=00000000',
'--hide-scrollbars',
'--enable-features=ConversionMeasurement,AttributionReportingCrossAppWeb',
f'--screenshot={png_filepath}',
f'--window-size={width},{height}',
svg_filepath,
]

if not enable_hardware_acceleration:
Expand All @@ -134,15 +158,4 @@ def create_chromium_command(chromium_executable, height, width, png_filepath, sv
'--disable-dev-shm-usage',
])

command.extend([
'--headless=old', # because of issue in new with SVG conversion
'--no-sandbox',
'--default-background-color=00000000',
'--hide-scrollbars',
'--enable-features=ConversionMeasurement,AttributionReportingCrossAppWeb',
f'--screenshot={png_filepath}',
f'--window-size={width},{height}',
f'{svg_filepath}',
])

return command
Loading