Logo Tools (#158)

* Logo Tools

* Moved to /tools/logo; Checks dependencies first and stops if they are not there; Added information about 3_remove_branding_from_projectpage.sh in README ; reordered the commands of 1_convert.sh to avoid the logo-horizontal.png problem
This commit is contained in:
David Rotermund
2026-03-28 22:50:01 +01:00
committed by yu-i-i
parent 206c9cbda0
commit 6427c3aafd
12 changed files with 1016 additions and 0 deletions

15
tools/logo/.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
android-chrome-192x192.png
android-chrome-512x512.png
apple-touch-icon.png
favicon-16x16.png
favicon-32x32.png
favicon-compiled.svg
favicon-compiling.svg
favicon-error.svg
favicon.ico
favicon.svg
img
logo-horizontal.png
logo_sw.svg
mask-favicon.svg
overleaf_og_logo.png

78
tools/logo/1_convert.sh Executable file
View File

@@ -0,0 +1,78 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status
set -e
python3 check_dependency.py
mkdir -p img/ol-brand
echo "============================================================"
echo "Logo Conversion Script"
echo "============================================================"
echo ""
# 1. Validation Checks
if [ ! -f "logo.svg" ] || [ ! -f "logo_full.svg" ]; then
echo "ERROR: Required SVG files (logo.svg or logo_full.svg) not found!"
exit 1
fi
echo "✓ Found source SVGs"
# 2. Generate Favicons
cp logo.svg favicon.svg
python3 generate_favicons.py favicon.svg
# 3. Create Black/White/Grey versions
echo "Creating black/white versions..."
python3 create_sw_versions.py logo.svg logo_sw.svg "#000000"
python3 create_sw_versions.py logo.svg mask-favicon.svg "#000000"
python3 create_sw_versions.py logo_full.svg img/ol-brand/overleaf-black.svg "#000000"
python3 create_sw_versions.py logo.svg img/ol-brand/overleaf-o-white.svg "#FFFFFF"
python3 create_sw_versions.py logo_full.svg img/ol-brand/overleaf-white.svg "#FFFFFF"
python3 create_sw_versions.py logo.svg img/ol-brand/overleaf-o-grey.svg "#808080"
# 4. Generate standard icons (creates overleaf_og_logo.png)
echo "Generating standard icons from logo.svg..."
python3 generate_icons.py logo.svg
# 5. Generate additional logos (FIX: Moved up so logo-horizontal.png exists before copying)
echo "Generating logo-horizontal.png from logo_full.svg..."
python3 generate_additional_logos.py logo_full.svg logo-horizontal.png 410 180 --export-area-drawing
# 6. Final File Operations (Copying generated assets to destination)
echo "Finalizing assets..."
cp -f overleaf_og_logo.png img/ol-brand/
cp -f logo-horizontal.png img/ol-brand/
cp -f logo.svg img/ol-brand/overleaf-o.svg
cp -f logo_full.svg img/ol-brand/overleaf.svg
cp -f logo_full.svg img/ol-brand/overleaf-a-ds-solution-mallard.svg
cp -f logo_full.svg img/ol-brand/overleaf-green.svg
cp -f logo.svg img/ol-brand/overleaf-o-dark.svg
# 7. Inline Python for Dark Mallard Variant
python3 - <<'PY'
from pathlib import Path
p = Path('img/ol-brand/overleaf-a-ds-solution-mallard.svg')
out = Path('img/ol-brand/overleaf-a-ds-solution-mallard-dark.svg')
if p.exists():
s = p.read_text(encoding='utf-8')
s = s.replace('#0000ff', '#FFFFFF').replace('#0000FF', '#FFFFFF')
s = s.replace('#00aa00', '#13C965').replace('#00AA00', '#13C965')
s = s.replace('fill:#0000ff', 'fill:#FFFFFF').replace('fill:#0000FF', 'fill:#FFFFFF')
s = s.replace('stroke:#0000ff', 'stroke:#FFFFFF').replace('stroke:#0000FF', 'stroke:#FFFFFF')
s = s.replace('fill:#00aa00', 'fill:#13C965').replace('fill:#00AA00', 'fill:#13C965')
s = s.replace('stroke:#00aa00', 'stroke:#13C965').replace('stroke:#00AA00', 'stroke:#13C965')
out.write_text(s, encoding='utf-8')
print(f"✓ Created {out}")
else:
import sys
print(f"✗ Reference mallard file not found: {p}")
sys.exit(1)
PY
echo ""
echo "============================================================"
echo "✓ All logos generated successfully!"
echo "============================================================"

16
tools/logo/2_install.sh Executable file
View File

@@ -0,0 +1,16 @@
cp -f *.png ../../services/web/public/
cp -f *.svg ../../services/web/public/
cp -f *.ico ../../services/web/public/
cp -f img/ol-brand/*.png ../../services/web/public/img/ol-brand/
cp -f img/ol-brand/*.svg ../../services/web/public/img/ol-brand/
# Explicitly include the dark Mallard variant
cp -f img/ol-brand/overleaf-a-ds-solution-mallard-dark.svg ../../services/web/public/img/ol-brand/
cp -f img/ol-brand/overleaf.svg ../../services/web/frontend/js/shared/svgs
cp -f img/ol-brand/overleaf-*.svg ../../services/web/frontend/js/shared/svgs
# Explicitly copy the dark Mallard variant into shared svgs as well
cp -f img/ol-brand/overleaf-a-ds-solution-mallard-dark.svg ../../services/web/frontend/js/shared/svgs

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
# Removes the ds-nav branding block from the project page sidebar component.
# Usage: bash 3_remove_branding_from_projectpage.sh
TARGET="../../services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx"
BACKUP="$TARGET.bak"
if [ ! -f "$TARGET" ]; then
echo "ERROR: target file not found: $TARGET"
exit 1
fi
echo "Creating backup: $BACKUP"
cp -a "$TARGET" "$BACKUP"
# Use Perl to remove the <div ... className="ds-nav-ds-name" ...>...</div> block across multiple lines.
# This will match regardless of attribute order or whitespace and removes the entire div including trailing newline.
perl -0777 -pe "s#<div[^>]*className\\s*=\\s*[\'\"]ds-nav-ds-name[\'\"][^>]*>.*?</div>\\n?##gis" "$BACKUP" > "$TARGET"
if cmp -s "$TARGET" "$BACKUP"; then
echo "No ds-nav-ds-name block found; no changes made."
exit 0
else
echo "✓ Removed ds-nav-ds-name block from $TARGET"
exit 0
fi

26
tools/logo/README.md Normal file
View File

@@ -0,0 +1,26 @@
# Overview
We assume that two files exist:
* logo.svg (short version of the logo)
* logo_full.svg (wide version of the logo with e.g. University of Somewhere)
# 1_convert.sh
It does a lot of converting logos from one format to another and scales & changes colors of the logo for producing the required set of files.
In the moment it mainly uses inkscape and imagemagick as tools.
# 2_install.sh
This script copies the files to the correct positions before compilation. I seperated the scripts, since you may want to check the logos beforehand. Or do changes by hand.
e.g. I don't like overleaf-a-ds-solution-mallard.svg as a long logo.
# 3_remove_branding_from_projectpage.sh
This script is a maintenance utility designed to modify the project list sidebar by removing specific branding elements from the frontend source code.
Target File: services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx
Action: Removes the <div ... className="ds-nav-ds-name" ...>...</div> block

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env python3
import subprocess
import sys
import os
from pathlib import Path
# Detected ImageMagick command (either 'magick' for IM v7 or 'convert' for IM v6)
IMAGEMAGICK_CMD = None
def check_dependencies():
"""Check if required tools are installed."""
print("Checking dependencies...")
# Core dependency we always require
dependencies = {
"inkscape": "Inkscape (for PNG generation)"
}
missing = []
# Check inkscape
for cmd, description in dependencies.items():
try:
subprocess.run(
[cmd, "--version"],
check=True,
capture_output=True,
text=True
)
print(f"{description} - found")
except (subprocess.CalledProcessError, FileNotFoundError):
print(f"{description} - NOT FOUND")
missing.append(cmd)
# Detect ImageMagick: prefer 'magick' (ImageMagick 7), fall back to 'convert' (ImageMagick 6)
global IMAGEMAGICK_CMD
IMAGEMAGICK_CMD = None
for cmd in ("magick", "convert"):
try:
subprocess.run([cmd, "--version"], check=True, capture_output=True, text=True)
IMAGEMAGICK_CMD = cmd
print(f" ✓ ImageMagick ({cmd}) - found")
break
except (subprocess.CalledProcessError, FileNotFoundError):
continue
if IMAGEMAGICK_CMD is None:
print(f" ✗ ImageMagick - NOT FOUND (tried 'magick' and 'convert')")
missing.append("ImageMagick ('magick' or 'convert')")
if missing:
print(f"\n{'='*60}")
print("ERROR: Missing required dependencies!")
print(f"{'='*60}")
print("\nThe following tools are required but not found:")
for tool in missing:
print(f" - {tool}")
print("\nInstallation instructions:")
print("\n Ubuntu/Debian:")
print(" sudo apt install inkscape imagemagick")
print("\n macOS:")
print(" brew install inkscape imagemagick")
print("\n Windows:")
print(" Download from:")
print(" - https://inkscape.org/")
print(" - https://imagemagick.org/")
print(f"{'='*60}\n")
sys.exit(1)
print("✓ All dependencies found\n")
return True
if __name__ == "__main__":
check_dependencies()

View File

@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""
Create black/white versions of SVG logos.
This script converts all non-transparent and non-white colors to a specified color
(default: black), creating modified versions of the logos.
Usage:
python create_sw_versions.py <input.svg> <output.svg> [color]
python create_sw_versions.py logo.svg logo_sw.svg
python create_sw_versions.py logo.svg logo_sw.svg "#FF0000"
"""
import re
import sys
import os
def style_to_color(style_content, target_color):
"""Convert colors in style attributes to target color."""
# Replace fill colors
style_content = re.sub(
r'fill:\s*#[0-9a-fA-F]{6}',
f'fill:{target_color}',
style_content
)
style_content = re.sub(
r'fill:\s*rgb\([^)]+\)',
f'fill:{target_color}',
style_content
)
# Replace stroke colors
style_content = re.sub(
r'stroke:\s*#[0-9a-fA-F]{6}',
f'stroke:{target_color}',
style_content
)
style_content = re.sub(
r'stroke:\s*rgb\([^)]+\)',
f'stroke:{target_color}',
style_content
)
return style_content
def convert_to_target_color(svg_content, target_color="#000000"):
"""
Convert SVG colors to target color, keeping transparent and white as-is.
Args:
svg_content: String containing SVG XML content
target_color: Target color (default: #000000 for black)
Returns:
Modified SVG content with colors converted to target color
"""
# Handle fill and stroke attributes
svg_content = re.sub(
r'(fill|stroke)="(#(?!ffffff|FFFFFF|fff|FFF)[0-9a-fA-F]{6}|#(?!fff|FFF)[0-9a-fA-F]{3}|rgb\([^)]+\))"',
rf'\1="{target_color}"',
svg_content
)
# Handle style attributes
def replace_style(match):
style_content = match.group(1)
style_content = style_to_color(style_content, target_color)
return f'style="{style_content}"'
svg_content = re.sub(
r'style="([^"]+)"',
replace_style,
svg_content
)
# Handle color attributes (less common)
svg_content = re.sub(
r'color="(#(?!ffffff|FFFFFF|fff|FFF)[0-9a-fA-F]{6}|rgb\([^)]+\))"',
f'color="{target_color}"',
svg_content
)
return svg_content
def process_svg_file(input_file, output_file, target_color="#000000"):
"""
Read SVG file, convert colors to target color, and save.
Args:
input_file: Path to input SVG file
output_file: Path to output SVG file
target_color: Target color (default: #000000)
"""
try:
with open(input_file, 'r', encoding='utf-8') as f:
svg_content = f.read()
# Convert colors
modified_content = convert_to_target_color(svg_content, target_color)
# Write output
with open(output_file, 'w', encoding='utf-8') as f:
f.write(modified_content)
print(f"✓ Created {output_file} with color {target_color}")
return True
except FileNotFoundError:
print(f"✗ Error: {input_file} not found")
return False
except Exception as e:
print(f"✗ Error processing {input_file}: {e}")
return False
def main():
"""Main entry point."""
if len(sys.argv) < 3:
print("=" * 60)
print("ERROR: Invalid usage!")
print("=" * 60)
print()
print("Usage:")
print(" python create_sw_versions.py <input.svg> <output.svg> [color]")
print()
print("Examples:")
print(" python create_sw_versions.py logo.svg logo_sw.svg")
print(" python create_sw_versions.py logo.svg logo_red.svg \"#FF0000\"")
print(" python create_sw_versions.py logo_full.svg logo_full_sw.svg \"#000000\"")
print()
print("Default color: #000000 (black)")
print("=" * 60)
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
target_color = sys.argv[3] if len(sys.argv) > 3 else "#000000"
# Check if input file exists
if not os.path.isfile(input_file):
print("=" * 60)
print("ERROR: Input file not found!")
print("=" * 60)
print()
print(f"The file '{input_file}' does not exist.")
print()
print(f"Current directory: {os.getcwd()}")
print("=" * 60)
sys.exit(1)
# Validate color format
if not re.match(r'^#[0-9a-fA-F]{6}$', target_color):
print("=" * 60)
print("WARNING: Invalid color format!")
print("=" * 60)
print()
print(f"Color '{target_color}' may not be valid.")
print("Expected format: #RRGGBB (e.g., #000000, #FF0000)")
print()
print("Proceeding anyway...")
print("=" * 60)
print()
print(f"Converting: {input_file} -> {output_file}")
print(f"Target color: {target_color}")
print()
if process_svg_file(input_file, output_file, target_color):
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""
Generate logo PNG from SVG using Inkscape.
This script generates a PNG file from an SVG with specified dimensions and options.
Usage:
python generate_additional_logos.py <input.svg> <output.png> <width> <height> [options]
Examples:
python generate_additional_logos.py logo_full.svg logo-horizontal.png 410 180 --export-area-drawing
"""
import subprocess
import sys
import os
def main():
"""Main entry point."""
if len(sys.argv) < 5:
print("=" * 60)
print("ERROR: Invalid usage!")
print("=" * 60)
print()
print("Usage:")
print(" python generate_additional_logos.py <input.svg> <output.png> <width> <height> [options]")
print()
print("Example:")
print(" python generate_additional_logos.py logo_full.svg logo-horizontal.png 410 180 --export-area-drawing")
print()
print("Common options:")
print(" --export-area-drawing Export the bounding box of all objects")
print(" --export-area-page Export the page area (default)")
print("=" * 60)
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
# Parse dimensions
try:
width = int(sys.argv[3])
height = int(sys.argv[4])
except ValueError:
print(f"ERROR: Invalid dimensions - width='{sys.argv[3]}', height='{sys.argv[4]}'")
print("Width and height must be integers.")
sys.exit(1)
# Check if input file exists
if not os.path.isfile(input_file):
print(f"ERROR: Input file '{input_file}' not found")
print(f"Current directory: {os.getcwd()}")
sys.exit(1)
# Build Inkscape command
command = [
"inkscape",
f"--export-filename={output_file}",
f"--export-width={width}",
f"--export-height={height}",
"--export-background-opacity=0"
]
# Add extra options (e.g., --export-area-drawing)
if len(sys.argv) > 5:
command.extend(sys.argv[5:])
# Add input file
command.append(input_file)
# Run Inkscape
print(f"Generating {output_file} ({width}×{height}) from {input_file}")
try:
subprocess.run(command, check=True, capture_output=True, text=True)
print(f"{output_file} created successfully")
sys.exit(0)
except subprocess.CalledProcessError as e:
print(f"✗ Error: {e.stderr}")
sys.exit(1)
except FileNotFoundError:
print("✗ Error: Inkscape not found")
print("Install: sudo apt install inkscape (or brew install inkscape)")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
Generate favicon variants with status indicators (compiled, compiling, error)
Usage: python generate_favicons.py favicon.svg
"""
import sys
from pathlib import Path
def create_compiled_icon(size):
"""Create a green checkmark icon"""
circle = f'<circle cx="{size/2}" cy="{size/2}" r="{size/2}" fill="#22c55e"/>'
check_path = f'<path d="M {size*0.25} {size*0.5} L {size*0.4} {size*0.65} L {size*0.75} {size*0.3}" stroke="white" stroke-width="{size*0.15}" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'
return f'{circle}{check_path}'
def create_compiling_icon(size):
"""Create rotating arrows icon (circular progress)"""
circle = f'<circle cx="{size/2}" cy="{size/2}" r="{size/2}" fill="#e5e7eb"/>'
r = size * 0.3
cx, cy = size/2, size/2
stroke_w = size * 0.06
arrow_len = stroke_w * 2
# Two SHORT arcs with arrow heads, forming an open circle
# Arc 1: short arc at top-right (about 90 degrees)
arc1 = f'<path d="M {cx} {cy-r} A {r} {r} 0 0 1 {cx+r} {cy}" stroke="black" stroke-width="{stroke_w}" fill="none" stroke-linecap="round"/>'
# Arrow head for arc1 (pointing downward/clockwise at right side)
arrow1 = f'<path d="M {cx+r-arrow_len} {cy-arrow_len} L {cx+r} {cy} L {cx+r+arrow_len} {cy-arrow_len}" stroke="black" stroke-width="{stroke_w}" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'
# Arc 2: short arc at bottom-left (about 90 degrees)
arc2 = f'<path d="M {cx} {cy+r} A {r} {r} 0 0 1 {cx-r} {cy}" stroke="black" stroke-width="{stroke_w}" fill="none" stroke-linecap="round"/>'
# Arrow head for arc2 (pointing upward/clockwise at left side)
arrow2 = f'<path d="M {cx-r-arrow_len} {cy+arrow_len} L {cx-r} {cy} L {cx-r+arrow_len} {cy+arrow_len}" stroke="black" stroke-width="{stroke_w}" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'
return f'{circle}{arc1}{arrow1}{arc2}{arrow2}'
def create_error_icon(size):
"""Create a red X icon"""
circle = f'<circle cx="{size/2}" cy="{size/2}" r="{size/2}" fill="#ef4444"/>'
x1 = f'<line x1="{size*0.3}" y1="{size*0.3}" x2="{size*0.7}" y2="{size*0.7}" stroke="white" stroke-width="{size*0.15}" stroke-linecap="round"/>'
x2 = f'<line x1="{size*0.7}" y1="{size*0.3}" x2="{size*0.3}" y2="{size*0.7}" stroke="white" stroke-width="{size*0.15}" stroke-linecap="round"/>'
return f'{circle}{x1}{x2}'
def extract_dimensions(svg_content):
"""Extract width and height from SVG content"""
# Try to find viewBox
if 'viewBox=' in svg_content:
start = svg_content.find('viewBox=')
quote = svg_content[start + 8]
start = start + 9
end = svg_content.find(quote, start)
viewbox = svg_content[start:end]
parts = viewbox.split()
return float(parts[2]), float(parts[3])
# Try to find width and height attributes
width = 100
height = 100
if 'width=' in svg_content:
start = svg_content.find('width=')
quote = svg_content[start + 6]
start = start + 7
end = svg_content.find(quote, start)
width = float(svg_content[start:end].replace('px', ''))
if 'height=' in svg_content:
start = svg_content.find('height=')
quote = svg_content[start + 7]
start = start + 8
end = svg_content.find(quote, start)
height = float(svg_content[start:end].replace('px', ''))
return width, height
def add_overlay_to_svg(input_file, output_file, overlay_creator):
"""Add an overlay icon to the SVG in the lower right corner"""
with open(input_file, 'r') as f:
svg_content = f.read()
# Extract dimensions
width, height = extract_dimensions(svg_content)
# Calculate overlay size and position (1/3 of the icon size)
overlay_size = min(width, height) / 3
overlay_x = width - overlay_size - (overlay_size * 0.1)
overlay_y = height - overlay_size - (overlay_size * 0.1)
# Create overlay group
overlay_svg = f'<g transform="translate({overlay_x}, {overlay_y})">\n {overlay_creator(overlay_size)}\n</g>'
# Find the closing </svg> tag and insert overlay before it
closing_tag = '</svg>'
insert_pos = svg_content.rfind(closing_tag)
if insert_pos == -1:
print(f"Error: Could not find closing </svg> tag in {input_file}")
return
# Insert the overlay
new_content = svg_content[:insert_pos] + overlay_svg + '\n' + svg_content[insert_pos:]
# Write output
with open(output_file, 'w') as f:
f.write(new_content)
print(f"Created: {output_file}")
def main():
if len(sys.argv) != 2:
print("Usage: python generate_favicons.py favicon.svg")
sys.exit(1)
input_file = Path(sys.argv[1])
if not input_file.exists():
print(f"Error: {input_file} not found")
sys.exit(1)
base_name = input_file.stem
output_dir = input_file.parent
# Generate the three variants
add_overlay_to_svg(
input_file,
output_dir / f"{base_name}-compiled.svg",
create_compiled_icon
)
add_overlay_to_svg(
input_file,
output_dir / f"{base_name}-compiling.svg",
create_compiling_icon
)
add_overlay_to_svg(
input_file,
output_dir / f"{base_name}-error.svg",
create_error_icon
)
print("\nAll favicon variants created successfully!")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""
Icon Generator Tool
This script generates various icon sizes from an SVG file using Inkscape,
and creates a favicon.ico file using ImageMagick.
Usage:
python generate_icons.py <input_svg_file>
python generate_icons.py logo.svg
"""
import subprocess
import sys
import os
from pathlib import Path
# Detected ImageMagick command (either 'magick' for IM v7 or 'convert' for IM v6)
IMAGEMAGICK_CMD = None
def run_command(command, description):
"""
Execute a shell command and handle errors.
Args:
command: List of command arguments
description: Human-readable description of the command
"""
print(f"Running: {description}")
try:
result = subprocess.run(
command,
check=True,
capture_output=True,
text=True
)
print(f"{description} completed successfully")
return True
except subprocess.CalledProcessError as e:
print(f"✗ Error during {description}:")
print(f" {e.stderr}")
return False
except FileNotFoundError:
print(f"✗ Error: Required tool not found for {description}")
print(f" Make sure the command '{command[0]}' is installed and in your PATH")
return False
def check_file_exists(filepath):
"""Check if a file exists."""
if not os.path.isfile(filepath):
print(f"\n{'='*60}")
print("ERROR: Input file not found!")
print(f"{'='*60}")
print(f"\nThe file '{filepath}' does not exist.\n")
print("Requirements:")
print(f" 1. Create or place '{filepath}' in the current directory")
print(f" 2. Ensure the file is a valid SVG file")
print(f"\nCurrent directory: {os.getcwd()}")
print(f"{'='*60}\n")
return False
return True
def check_dependencies():
"""Check if required tools are installed."""
print("Checking dependencies...")
# Core dependency we always require
dependencies = {
"inkscape": "Inkscape (for PNG generation)"
}
missing = []
# Check inkscape
for cmd, description in dependencies.items():
try:
subprocess.run(
[cmd, "--version"],
check=True,
capture_output=True,
text=True
)
print(f"{description} - found")
except (subprocess.CalledProcessError, FileNotFoundError):
print(f"{description} - NOT FOUND")
missing.append(cmd)
# Detect ImageMagick: prefer 'magick' (ImageMagick 7), fall back to 'convert' (ImageMagick 6)
global IMAGEMAGICK_CMD
IMAGEMAGICK_CMD = None
for cmd in ("magick", "convert"):
try:
subprocess.run([cmd, "--version"], check=True, capture_output=True, text=True)
IMAGEMAGICK_CMD = cmd
print(f" ✓ ImageMagick ({cmd}) - found")
break
except (subprocess.CalledProcessError, FileNotFoundError):
continue
if IMAGEMAGICK_CMD is None:
print(f" ✗ ImageMagick - NOT FOUND (tried 'magick' and 'convert')")
missing.append("ImageMagick ('magick' or 'convert')")
if missing:
print(f"\n{'='*60}")
print("ERROR: Missing required dependencies!")
print(f"{'='*60}")
print("\nThe following tools are required but not found:")
for tool in missing:
print(f" - {tool}")
print("\nInstallation instructions:")
print("\n Ubuntu/Debian:")
print(" sudo apt install inkscape imagemagick")
print("\n macOS:")
print(" brew install inkscape imagemagick")
print("\n Windows:")
print(" Download from:")
print(" - https://inkscape.org/")
print(" - https://imagemagick.org/")
print(f"{'='*60}\n")
return False
print("✓ All dependencies found\n")
return True
def generate_icons(svg_file):
"""
Generate all icon sizes from an SVG file.
Args:
svg_file: Path to the input SVG file
"""
print(f"Generating icons from: {svg_file}\n")
# Define icon configurations
# Format: (filename, width, height, background)
icons = [
("android-chrome-512x512.png", 512, 512, "--export-background-opacity=0"),
("android-chrome-192x192.png", 192, 192, "--export-background-opacity=0"),
("apple-touch-icon.png", 180, 180, "--export-background=#FFFFFF"),
("favicon-16x16.png", 16, 16, "--export-background-opacity=0"),
("favicon-32x32.png", 32, 32, "--export-background-opacity=0"),
("overleaf_og_logo.png", 256, 256, "--export-background-opacity=0"),
]
success_count = 0
# Generate PNG files using Inkscape
for filename, width, height, background in icons:
command = [
"inkscape",
f"--export-filename={filename}",
f"--export-width={width}",
f"--export-height={height}",
background,
svg_file
]
if run_command(command, f"Generating {filename}"):
success_count += 1
else:
print(f"Warning: Failed to generate {filename}")
print(f"\n{success_count}/{len(icons)} PNG icons generated successfully\n")
# Generate favicon.ico using ImageMagick
if check_file_exists("favicon-32x32.png"):
if IMAGEMAGICK_CMD is None:
print("\n✗ Cannot generate favicon.ico: no ImageMagick command detected")
return False
command = [
IMAGEMAGICK_CMD,
"favicon-32x32.png",
"favicon.ico"
]
if run_command(command, "Generating favicon.ico"):
print("\n✓ All icons generated successfully!")
return True
else:
print("\nWarning: PNG icons generated, but favicon.ico creation failed")
return False
else:
print("\n✗ Cannot generate favicon.ico: favicon-32x32.png not found")
return False
def main():
"""Main entry point."""
if len(sys.argv) != 2:
print(f"\n{'='*60}")
print("ERROR: Invalid usage!")
print(f"{'='*60}")
print("\nUsage: python generate_icons.py <input_svg_file>")
print("\nExample:")
print(" python generate_icons.py logo.svg")
print(f"{'='*60}\n")
sys.exit(1)
svg_file = sys.argv[1]
print("=" * 60)
print("Icon Generator Tool")
print("=" * 60)
print()
# Check if input file exists FIRST
if not check_file_exists(svg_file):
sys.exit(1)
# Check if required tools are installed
if not check_dependencies():
sys.exit(1)
# Proceed with icon generation
success = generate_icons(svg_file)
print("=" * 60)
if success:
print("\nGenerated files:")
print(" - android-chrome-512x512.png (512×512, transparent)")
print(" - android-chrome-192x192.png (192×192, transparent)")
print(" - apple-touch-icon.png (180×180, white background)")
print(" - favicon-16x16.png (16×16, transparent)")
print(" - favicon-32x32.png (32×32, transparent)")
print(" - overleaf_og_logo.png (256×256, transparent)")
print(" - favicon.ico")
sys.exit(0)
else:
print("\n✗ Icon generation completed with errors")
sys.exit(1)
if __name__ == "__main__":
main()

61
tools/logo/logo.svg Normal file
View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="59.52"
height="54.080002"
viewBox="0 0 59.52 54.080002"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<rect
style="fill:#0000ff;stroke:#0000ff"
id="rect1"
width="10.99932"
height="1.8332207"
x="17.017767"
y="9.9218159" />
<rect
style="fill:#0000ff;stroke:#0000ff"
id="rect2"
width="2.2915268"
height="31.426634"
x="25.720428"
y="12.745403" />
<rect
style="fill:#0000ff;stroke:#0000ff"
id="rect3"
width="2.2915268"
height="31.426634"
x="31.764208"
y="12.714312" />
<rect
style="fill:#0000ff;stroke:#0000ff"
id="rect4"
width="10.99932"
height="1.8332207"
x="31.768179"
y="9.8910656" />
<path
d="m 7.6602438,29.399278 q 0,-1.327712 -0.8153199,-2.141471 -0.79134,-0.835175 -2.3260598,-0.835175 v -2.248544 q 1.5347198,0 2.3260598,-0.813759 0.8153199,-0.835174 0.8153199,-2.141471 0,-1.049321 -0.19184,-2.055812 -0.1678599,-1.027906 -0.40766,-2.034398 -0.2158199,-1.006491 -0.4076599,-2.034398 -0.16786,-1.027906 -0.16786,-2.098642 0,-1.434785 0.4796,-2.634009 0.4795999,-1.1992239 1.4387999,-2.0558124 0.9831798,-0.8780032 2.3979992,-1.3491269 1.4388,-0.4925383 3.3572,-0.4925383 h 1.24696 V 8.134469 q 0,0.4282941 -0.33572,0.6210266 -0.33572,0.1713177 -0.5995,0.1713177 h -0.45562 q -1.7985,0 -2.829639,1.0493208 -1.00716,1.0279059 -1.00716,2.8053279 0,1.156394 0.16786,2.227129 0.167859,1.070736 0.3597,2.098642 0.19184,1.027907 0.359699,2.034398 0.167861,0.985077 0.167861,2.012983 0,0.77093 -0.26378,1.4562 -0.26378,0.663856 -0.719401,1.220638 -0.4556194,0.535368 -1.1030793,0.920833 -0.64746,0.36405 -1.3908399,0.556783 0.7433799,0.192732 1.3908399,0.578197 0.6474599,0.36405 1.1030793,0.920833 0.455621,0.535367 0.719401,1.199224 0.26378,0.663855 0.26378,1.434785 0,1.027906 -0.167861,2.034397 -0.167859,1.006492 -0.359699,2.034398 -0.191841,1.027906 -0.3597,2.098643 -0.16786,1.070735 -0.16786,2.227129 0,1.756006 1.00716,2.783912 1.031139,1.049321 2.829639,1.049321 h 0.45562 q 0.26378,0 0.5995,0.171318 0.33572,0.192733 0.33572,0.621027 v 1.670347 h -1.24696 q -1.9184,0 -3.3572,-0.492538 -1.4148194,-0.471124 -2.3979992,-1.349127 -0.9592,-0.856589 -1.4387999,-2.055812 -0.4796,-1.199224 -0.4796,-2.63401 0,-1.070735 0.16786,-2.098642 0.19184,-1.027906 0.4076599,-2.034397 0.2398001,-1.006492 0.40766,-2.012983 0.19184,-1.027906 0.19184,-2.055812 z"
id="text6"
style="font-size:46.4099px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';fill:#0000ff;stroke:#0000ff;stroke-width:0.101133"
aria-label="{" />
<path
d="m 52.078797,21.290035 c 0,0.885141 0.271773,1.598965 0.81532,2.14147 0.52756,0.556783 1.302913,0.835175 2.32606,0.835175 v 2.248544 c -1.023147,0 -1.7985,0.271253 -2.32606,0.81376 -0.543547,0.556782 -0.81532,1.270605 -0.81532,2.14147 0,0.699547 0.06395,1.384818 0.19184,2.055813 0.111907,0.685271 0.247793,1.363403 0.40766,2.034397 0.14388,0.670995 0.279767,1.349127 0.40766,2.034398 0.111907,0.685271 0.16786,1.384818 0.16786,2.098642 0,0.956524 -0.159867,1.834527 -0.4796,2.634009 -0.319733,0.799483 -0.581985,2.085727 -1.4388,2.055813 -3.145619,-0.109822 -2.751636,-1.509052 -2.78168,-1.670348 0.671441,-0.685271 1.007161,-1.62038 1.007161,-2.805327 0,-0.770929 -0.05595,-1.513306 -0.167861,-2.22713 -0.111906,-0.713824 -0.231806,-1.413371 -0.3597,-2.098642 -0.127893,-0.685271 -0.247793,-1.363403 -0.359699,-2.034398 -0.111907,-0.656718 -0.167861,-1.327712 -0.167861,-2.012983 0,-0.513953 0.08793,-0.999352 0.26378,-1.456199 0.175853,-0.442571 0.415654,-0.849451 0.719401,-1.220639 0.303747,-0.356912 0.67144,-0.663856 1.103079,-0.920833 0.43164,-0.2427 0.895253,-0.428294 1.39084,-0.556782 -0.495587,-0.128489 -0.9592,-0.321221 -1.39084,-0.578197 -0.431639,-0.242701 -0.799332,-0.549645 -1.103079,-0.920833 -0.303747,-0.356912 -0.543548,-0.756653 -0.719401,-1.199224 -0.175853,-0.442571 -0.26378,-0.920833 -0.26378,-1.434786 0,-0.685271 0.05595,-1.363403 0.167861,-2.034397 0.111906,-0.670995 0.231806,-1.349127 0.359699,-2.034398 0.127894,-0.685271 0.247794,-1.384818 0.3597,-2.098642 0.111907,-0.713824 0.167861,-1.456201 0.167861,-2.22713 0,-1.170671 -0.33572,-2.098641 -1.007161,-2.783912 C 47.866311,9.3691787 46.923098,9.019405 45.724098,9.019405 h -0.45562 c -0.175853,0 -0.375687,-0.057106 -0.5995,-0.171318 C 44.445165,8.719599 44.333258,8.51259 44.333258,8.22706 V 6.5567131 h 1.24696 c 1.278933,0 2.398,0.1641793 3.3572,0.4925379 0.943213,0.3140827 1.742546,0.7637917 2.397999,1.349127 0.639467,0.5710593 1.119067,1.25633 1.4388,2.055812 0.319733,0.799483 0.4796,1.677486 0.4796,2.63401 0,0.713824 -0.05595,1.413371 -0.16786,2.098642 -0.127893,0.685271 -0.26378,1.363403 -0.40766,2.034398 -0.159867,0.670994 -0.295753,1.341988 -0.40766,2.012982 -0.127893,0.685271 -0.19184,1.370542 -0.19184,2.055813 z"
id="path7"
style="font-size:46.4099px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';fill:#0000ff;stroke:#0000ff;stroke-width:0.101133"
aria-label="{" />
<path
d="m 43.15455,39.746636 9.084315,9.539497 -2.316961,2.002922 -8.117171,-10.368577 -2.978375,3.75192 -0.278753,-8.872599 8.719417,1.520361 z"
id="text7"
style="font-size:22.9019px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';fill:#00aa00;stroke:#00aa00;stroke-width:0.0570312"
aria-label="➚" />
<path
id="rect10"
style="fill:#00aa00;stroke:#00aa00"
d="M 9.6996531,45.462482 H 14.514417 L 12.288364,47.712599 9.6996531,50.46243 Z" />
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

66
tools/logo/logo_full.svg Normal file
View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="144.80148"
height="45.887009"
viewBox="0 0 144.80148 45.887009"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<rect
style="fill:#0000ff;stroke:#0000ff"
id="rect1"
width="10.99932"
height="1.8332207"
x="12.549469"
y="4.0858331" />
<rect
style="fill:#0000ff;stroke:#0000ff"
id="rect2"
width="2.2915268"
height="31.426634"
x="21.252131"
y="6.9094205" />
<rect
style="fill:#0000ff;stroke:#0000ff"
id="rect3"
width="2.2915268"
height="31.426634"
x="27.29591"
y="6.8783288" />
<rect
style="fill:#0000ff;stroke:#0000ff"
id="rect4"
width="10.99932"
height="1.8332207"
x="27.299881"
y="4.0550828" />
<path
d="m 3.1919462,23.563295 q 0,-1.327712 -0.8153199,-2.141471 -0.79134,-0.835175 -2.32605978,-0.835175 v -2.248544 q 1.53471978,0 2.32605978,-0.813759 0.8153199,-0.835174 0.8153199,-2.141471 0,-1.049321 -0.19184,-2.055812 -0.1678599,-1.027906 -0.40766,-2.034398 -0.2158199,-1.006491 -0.4076599,-2.0343978 -0.16786,-1.027906 -0.16786,-2.098642 0,-1.434785 0.4796,-2.634009 Q 2.9761262,3.3263923 3.9353262,2.4698038 4.918506,1.5918006 6.3333254,1.1206769 q 1.4388,-0.49253826 3.3572,-0.49253826 H 10.937485 V 2.2984862 q 0,0.4282941 -0.33572,0.6210266 -0.33572,0.1713177 -0.5995,0.1713177 H 9.5466454 q -1.7985,0 -2.829639,1.0493208 -1.00716,1.0279059 -1.00716,2.8053279 0,1.156394 0.16786,2.227129 0.167859,1.0707358 0.3597,2.0986418 0.19184,1.027907 0.359699,2.034398 0.167861,0.985077 0.167861,2.012983 0,0.77093 -0.26378,1.4562 -0.26378,0.663856 -0.719401,1.220638 -0.4556194,0.535368 -1.1030793,0.920833 -0.64746,0.36405 -1.3908399,0.556783 0.7433799,0.192732 1.3908399,0.578197 0.6474599,0.36405 1.1030793,0.920833 0.455621,0.535367 0.719401,1.199224 0.26378,0.663855 0.26378,1.434785 0,1.027906 -0.167861,2.034397 -0.167859,1.006492 -0.359699,2.034398 -0.191841,1.027906 -0.3597,2.098643 -0.16786,1.070735 -0.16786,2.227129 0,1.756006 1.00716,2.783912 1.031139,1.049321 2.829639,1.049321 h 0.4556196 q 0.26378,0 0.5995,0.171318 0.33572,0.192733 0.33572,0.621027 v 1.670347 H 9.6905254 q -1.9184,0 -3.3572,-0.492538 -1.4148194,-0.471124 -2.3979992,-1.349127 -0.9592,-0.856589 -1.4387999,-2.055812 -0.4796,-1.199224 -0.4796,-2.63401 0,-1.070735 0.16786,-2.098642 0.19184,-1.027906 0.4076599,-2.034397 0.2398001,-1.006492 0.40766,-2.012983 0.19184,-1.027906 0.19184,-2.055812 z"
id="text6"
style="font-size:46.4099px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';fill:#0000ff;stroke:#0000ff;stroke-width:0.101133"
aria-label="{" />
<path
d="m 47.610499,15.454052 c 0,0.885141 0.271773,1.598965 0.81532,2.14147 0.52756,0.556783 1.302913,0.835175 2.32606,0.835175 v 2.248544 c -1.023147,0 -1.7985,0.271253 -2.32606,0.81376 -0.543547,0.556782 -0.81532,1.270605 -0.81532,2.14147 0,0.699547 0.06395,1.384818 0.19184,2.055813 0.111907,0.685271 0.247793,1.363403 0.40766,2.034397 0.14388,0.670995 0.279767,1.349127 0.40766,2.034398 0.111907,0.685271 0.16786,1.384818 0.16786,2.098642 0,0.956524 -0.159867,1.834527 -0.4796,2.634009 -0.319733,0.799483 -0.581985,2.085727 -1.4388,2.055813 -3.145619,-0.109822 -2.751636,-1.509052 -2.78168,-1.670348 0.671441,-0.685271 1.007161,-1.62038 1.007161,-2.805327 0,-0.770929 -0.05595,-1.513306 -0.167861,-2.22713 -0.111906,-0.713824 -0.231806,-1.413371 -0.3597,-2.098642 -0.127893,-0.685271 -0.247793,-1.363403 -0.359699,-2.034398 -0.111907,-0.656718 -0.167861,-1.327712 -0.167861,-2.012983 0,-0.513953 0.08793,-0.999352 0.26378,-1.456199 0.175853,-0.442571 0.415654,-0.849451 0.719401,-1.220639 0.303747,-0.356912 0.67144,-0.663856 1.103079,-0.920833 0.43164,-0.2427 0.895253,-0.428294 1.39084,-0.556782 -0.495587,-0.128489 -0.9592,-0.321221 -1.39084,-0.578197 C 45.6921,18.723364 45.324407,18.41642 45.02066,18.045232 44.716913,17.68832 44.477112,17.288579 44.301259,16.846008 c -0.175853,-0.442571 -0.26378,-0.920833 -0.26378,-1.434786 0,-0.685271 0.05595,-1.363403 0.167861,-2.034397 0.111906,-0.670995 0.231806,-1.349127 0.359699,-2.034398 0.127894,-0.685271 0.247794,-1.3848178 0.3597,-2.0986418 0.111907,-0.713824 0.167861,-1.456201 0.167861,-2.22713 0,-1.170671 -0.33572,-2.098641 -1.007161,-2.783912 C 43.398013,3.5331959 42.4548,3.1834222 41.2558,3.1834222 h -0.45562 c -0.175853,0 -0.375687,-0.057106 -0.5995,-0.171318 -0.223813,-0.128488 -0.33572,-0.335497 -0.33572,-0.621027 V 0.72073034 h 1.24696 c 1.278933,0 2.398,0.1641793 3.3572,0.49253786 0.943213,0.3140827 1.742546,0.7637917 2.397999,1.349127 0.639467,0.5710593 1.119067,1.25633 1.4388,2.055812 0.319733,0.799483 0.4796,1.677486 0.4796,2.63401 0,0.713824 -0.05595,1.413371 -0.16786,2.098642 -0.127893,0.6852708 -0.26378,1.3634028 -0.40766,2.0343978 -0.159867,0.670994 -0.295753,1.341988 -0.40766,2.012982 -0.127893,0.685271 -0.19184,1.370542 -0.19184,2.055813 z"
id="path7"
style="font-size:46.4099px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';fill:#0000ff;stroke:#0000ff;stroke-width:0.101133"
aria-label="{" />
<path
d="m 38.686252,33.910653 9.084315,9.539497 -2.316961,2.002922 -8.117171,-10.368577 -2.978375,3.75192 -0.278753,-8.872599 8.719417,1.520361 z"
id="text7"
style="font-size:22.9019px;font-family:Carlito;-inkscape-font-specification:'Carlito, Normal';fill:#00aa00;stroke:#00aa00;stroke-width:0.0570312"
aria-label="➚" />
<path
id="rect10"
style="fill:#00aa00;stroke:#00aa00"
d="M 5.2313555,39.626499 H 10.046119 L 7.8200664,41.876616 5.2313555,44.626447 Z" />
<path
d="m 84.710592,4.5651461 v 5.0329993 q -2.338345,-2.2447652 -4.99764,-3.3553329 -2.63637,-1.1105687 -5.616613,-1.1105687 -5.868788,0 -8.986582,3.70977 -3.117793,3.6861402 -3.117793,10.6803552 0,6.970585 3.117793,10.680355 3.117794,3.686141 8.986582,3.686141 2.980243,0 5.616613,-1.110569 2.659295,-1.110568 4.99764,-3.355333 v 4.985742 q -2.430046,1.701295 -5.158114,2.551943 -2.705145,0.850647 -5.731239,0.850647 -7.771559,0 -12.241925,-4.891224 -4.470365,-4.914853 -4.470365,-13.397702 0,-8.506477 4.470365,-13.3977022 4.470366,-4.9148539 12.241925,-4.9148539 3.071943,0 5.777089,0.8506478 2.728069,0.8270186 5.112264,2.5046854 z m 7.152587,-2.7173474 h 21.641151 v 4.016948 H 96.494015 V 16.30881 h 16.299645 v 4.016949 H 96.494015 v 12.783345 h 17.422965 v 4.016948 H 91.863179 Z m 34.295731,3.922431 V 19.026158 h 5.82293 q 3.23243,0 4.99765,-1.724925 1.76522,-1.724925 1.76522,-4.914853 0,-3.1663004 -1.76522,-4.8912253 -1.76522,-1.724925 -4.99765,-1.724925 z m -4.63084,-3.922431 h 10.45377 q 5.75417,0 8.68857,2.6937176 2.95731,2.6700895 2.95731,7.8448637 0,5.222032 -2.95731,7.89212 -2.9344,2.670089 -8.68857,2.670089 h -5.82293 v 14.177463 h -4.63084 z"
id="text1"
style="font-size:47.6659px;line-height:125%;font-family:'Bitstream Vera Sans';letter-spacing:0px;word-spacing:0px;fill:#0000ff;stroke:#0000ff;stroke-width:2.3833px"
aria-label="CEP" />
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB