wingtip 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wingtip/__init__.py +0 -0
- wingtip/fonts/fira_sans.ttf +0 -0
- wingtip/fonts/poppins.ttf +0 -0
- wingtip/generate_card.py +125 -0
- wingtip/latex_extension.py +19 -0
- wingtip/main.py +1983 -0
- wingtip/serve.py +175 -0
- wingtip/static/css/admonitions.css +151 -0
- wingtip/static/css/custom.css +348 -0
- wingtip/static/js/search.js +228 -0
- wingtip/static/vendor/auto-render.min.js +1 -0
- wingtip/static/vendor/clipboard.min.js +7 -0
- wingtip/static/vendor/dark.css +886 -0
- wingtip/static/vendor/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Main-Bold.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Main-Italic.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Main-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Math-Italic.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Script-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- wingtip/static/vendor/fonts/material-icons.ttf +0 -0
- wingtip/static/vendor/iconify.min.js +13 -0
- wingtip/static/vendor/katex.min.css +1 -0
- wingtip/static/vendor/katex.min.js +1 -0
- wingtip/static/vendor/light.css +886 -0
- wingtip/static/vendor/material-icons.css +20 -0
- wingtip/template.html +1281 -0
- wingtip-0.4.1.dist-info/METADATA +342 -0
- wingtip-0.4.1.dist-info/RECORD +44 -0
- wingtip-0.4.1.dist-info/WHEEL +4 -0
- wingtip-0.4.1.dist-info/entry_points.txt +2 -0
wingtip/__init__.py
ADDED
|
File without changes
|
|
Binary file
|
|
Binary file
|
wingtip/generate_card.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# wingtip/generate_card.py
|
|
4
|
+
# Generate social card for GitHub Pages
|
|
5
|
+
|
|
6
|
+
import pathlib
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import urllib.request
|
|
10
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
11
|
+
|
|
12
|
+
def get_google_font(font_name):
|
|
13
|
+
"""Download and cache a Google Font"""
|
|
14
|
+
# Convert font name to Google Fonts API format
|
|
15
|
+
api_name = font_name.replace(' ', '+')
|
|
16
|
+
|
|
17
|
+
# Create fonts cache directory
|
|
18
|
+
cache_dir = pathlib.Path(__file__).parent / "fonts"
|
|
19
|
+
cache_dir.mkdir(exist_ok=True)
|
|
20
|
+
|
|
21
|
+
# Check if font is already cached
|
|
22
|
+
font_file = cache_dir / f"{font_name.lower().replace(' ', '_')}.ttf"
|
|
23
|
+
if font_file.exists():
|
|
24
|
+
return str(font_file)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# Get font CSS URL from Google Fonts API
|
|
28
|
+
css_url = f"https://fonts.googleapis.com/css2?family={api_name}&display=swap"
|
|
29
|
+
req = urllib.request.Request(css_url, headers={'User-Agent': 'Mozilla/5.0'})
|
|
30
|
+
css = urllib.request.urlopen(req).read().decode('utf-8')
|
|
31
|
+
|
|
32
|
+
# Extract TTF URL from CSS
|
|
33
|
+
ttf_url = re.search(r'src: url\((.+?\.ttf)\)', css)
|
|
34
|
+
if not ttf_url:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
# Download TTF file
|
|
38
|
+
ttf_url = ttf_url.group(1)
|
|
39
|
+
urllib.request.urlretrieve(ttf_url, font_file)
|
|
40
|
+
return str(font_file)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"Failed to download Google Font {font_name}: {e}")
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def generate_social_card(title, tagline, theme="light", font="Poppins", logo=None):
|
|
46
|
+
size = (1200, 630)
|
|
47
|
+
bg_color = "#f6ede3" # Warm background color
|
|
48
|
+
fg_color = "#000000" # Black text
|
|
49
|
+
card = Image.new("RGB", size, bg_color)
|
|
50
|
+
draw = ImageDraw.Draw(card)
|
|
51
|
+
|
|
52
|
+
# Try to find or download the font
|
|
53
|
+
try:
|
|
54
|
+
# First try the specified font as a path
|
|
55
|
+
font_path = font
|
|
56
|
+
if not os.path.exists(font_path):
|
|
57
|
+
# Then try bundled fonts directory
|
|
58
|
+
font_path = os.path.join(os.path.dirname(__file__), "fonts", font)
|
|
59
|
+
|
|
60
|
+
# If not found, try downloading from Google Fonts
|
|
61
|
+
if not os.path.exists(font_path):
|
|
62
|
+
google_font = get_google_font(font)
|
|
63
|
+
if google_font:
|
|
64
|
+
font_path = google_font
|
|
65
|
+
else:
|
|
66
|
+
# Fallback to Arial
|
|
67
|
+
font_path = "Arial"
|
|
68
|
+
|
|
69
|
+
title_font = ImageFont.truetype(font_path, 72)
|
|
70
|
+
tagline_font = ImageFont.truetype(font_path, 40)
|
|
71
|
+
except OSError:
|
|
72
|
+
# If all else fails, use default bitmap font
|
|
73
|
+
title_font = ImageFont.load_default()
|
|
74
|
+
tagline_font = ImageFont.load_default()
|
|
75
|
+
|
|
76
|
+
# Load and resize custom logo
|
|
77
|
+
logo_width = 0 # Default if logo fails to load
|
|
78
|
+
|
|
79
|
+
# Logo is opt-in and project-relative. Hardcoding "wingtip-logo.png" meant
|
|
80
|
+
# every user's build printed a failure for an asset only this repo has.
|
|
81
|
+
logo_path = logo
|
|
82
|
+
if logo_path and not os.path.exists(logo_path):
|
|
83
|
+
logo_path = None
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
if not logo_path:
|
|
87
|
+
raise FileNotFoundError("no logo configured")
|
|
88
|
+
logo_img = Image.open(logo_path)
|
|
89
|
+
|
|
90
|
+
# Convert to RGBA if needed
|
|
91
|
+
if logo_img.mode != 'RGBA':
|
|
92
|
+
logo_img = logo_img.convert('RGBA')
|
|
93
|
+
|
|
94
|
+
# Make logo full height of card
|
|
95
|
+
logo_height = size[1] # Full height
|
|
96
|
+
ratio = logo_img.width / logo_img.height
|
|
97
|
+
logo_width = int(logo_height * ratio)
|
|
98
|
+
logo_img = logo_img.resize((logo_width, logo_height), Image.Resampling.LANCZOS)
|
|
99
|
+
|
|
100
|
+
# Position logo flush with left edge
|
|
101
|
+
logo_y = 0 # Top edge
|
|
102
|
+
logo_x = 0 # Left edge
|
|
103
|
+
|
|
104
|
+
# Paste logo with alpha channel
|
|
105
|
+
card.paste(logo_img, (logo_x, logo_y), logo_img)
|
|
106
|
+
except FileNotFoundError:
|
|
107
|
+
pass # No logo: text starts from the margin. Not an error.
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"Warning: could not render logo '{logo_path}': {e}")
|
|
110
|
+
|
|
111
|
+
# Position text to the right of logo with spacing
|
|
112
|
+
text_x = logo_width + 0 # 0px gap from logo
|
|
113
|
+
text_y = size[1] // 3 # Start text 1/3 down from top
|
|
114
|
+
|
|
115
|
+
# Draw title
|
|
116
|
+
title_bbox = draw.textbbox((0, 0), title, font=title_font)
|
|
117
|
+
title_height = title_bbox[3] - title_bbox[1]
|
|
118
|
+
draw.text((text_x, text_y), title, font=title_font, fill=fg_color)
|
|
119
|
+
|
|
120
|
+
# Draw tagline below title with 40px gap
|
|
121
|
+
draw.text((text_x, text_y + title_height + 40), tagline, font=tagline_font, fill=fg_color)
|
|
122
|
+
|
|
123
|
+
output = pathlib.Path("docs/site/social-card.png")
|
|
124
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
card.save(output)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import markdown
|
|
3
|
+
from markdown.preprocessors import Preprocessor
|
|
4
|
+
|
|
5
|
+
class LaTeXPreservationExtension(markdown.Extension):
|
|
6
|
+
def extendMarkdown(self, md):
|
|
7
|
+
md.preprocessors.register(LaTeXPreservationPreprocessor(md), 'latex_preservation', 25)
|
|
8
|
+
|
|
9
|
+
class LaTeXPreservationPreprocessor(Preprocessor):
|
|
10
|
+
def run(self, lines):
|
|
11
|
+
text = '\n'.join(lines)
|
|
12
|
+
# Replace LaTeX delimiters with unique placeholders
|
|
13
|
+
text = re.sub(r'\\\[(.*?)\\\]', lambda m: f'DISPLAYMATH_START{m.group(1)}DISPLAYMATH_END', text)
|
|
14
|
+
text = re.sub(r'\\\((.*?)\\\)', lambda m: f'INLINEMATH_START{m.group(1)}INLINEMATH_END', text)
|
|
15
|
+
# Split back into lines
|
|
16
|
+
return text.split('\n')
|
|
17
|
+
|
|
18
|
+
def makeExtension(*args, **kwargs):
|
|
19
|
+
return LaTeXPreservationExtension(*args, **kwargs)
|