fix: improve share image design and optimize file size#145
fix: improve share image design and optimize file size#145
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughReworks the share-image generator: adds OG image header extraction (load_header + HEADER_STRIP), replaces per-tile Wordle rendering with a centered mini-grid, adds CJK-aware font selection and dynamic text/score layout, and updates image composition to use the pre-cropped header and 64-color palette output. Changes
Sequence Diagram(s)sequenceDiagram
participant Main as "scripts/generate_share_images.py::main"
participant Loader as "load_header()"
participant Generator as "generate_image()"
participant Fonts as "Font selector (CJK/DejaVu)"
participant Composer as "Image composer (header, score, mini-grid, text)"
participant FS as "Filesystem (og-image.png / output.png)"
Main->>Loader: ensure HEADER_STRIP initialized
Loader->>FS: open `og-image.png`
FS-->>Loader: raw image
Loader-->>Main: HEADER_STRIP
Main->>Generator: generate image for score/result/text
Generator->>Fonts: pick fonts by language (CJK_LANGS)
Fonts-->>Generator: font objects
Generator->>Composer: provide HEADER_STRIP, fonts, score, result
Composer->>Composer: draw header, center score, draw_mini_grid, wrap text
Composer->>FS: save final image (palette reduced)
FS-->>Main: output path
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Pixel-perfect header cropped from og-image.png (was hand-drawn approximation) - 1/3 - 2/3 layout for score and mini grid (proper spacing and vertical centering) - Auto-sizing challenge text (44→26px) for longer translations - NotoSansCJK fallback for Korean (fixes missing glyphs) - Palette-mode PNG optimization (13MB → 7MB)
20b785d to
883931c
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/generate_share_images.py (1)
202-202:⚠️ Potential issue | 🟡 MinorString comparison for boolean config is fragile.
The comparison
lang_config.get("right_to_left", "false") == "true"assumes the JSON value is a string. If the config contains a boolean (true/falseinstead of"true"/"false"), RTL will silently fail.Proposed fix for robustness
- "is_rtl": lang_config.get("right_to_left", "false") == "true", + "is_rtl": lang_config.get("right_to_left", False) in (True, "true"),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/generate_share_images.py` at line 202, The current assignment for "is_rtl" uses a fragile string comparison lang_config.get("right_to_left", "false") == "true" which fails if the config contains a boolean; update the logic where "is_rtl" is set (in scripts/generate_share_images.py, referencing lang_config and the "right_to_left" key) to normalize the value to a boolean: retrieve the raw value, if it's a string interpret common truthy strings (e.g., "true","1","yes") case-insensitively, otherwise coerce booleans/numerics to bool, and assign that boolean to "is_rtl" so both true/false and "true"/"false" representations work robustly.
🧹 Nitpick comments (3)
scripts/generate_share_images.py (3)
39-46: Hardcoded Linux font paths limit cross-platform development.The font paths assume a Linux environment with DejaVu and Noto fonts installed at specific system locations. This works for containerized builds but may break local development on macOS/Windows.
Consider documenting the font dependencies or adding fallback logic if cross-platform support is needed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/generate_share_images.py` around lines 39 - 46, The hardcoded font paths (FONT_DEJAVU, FONT_DEJAVU_BOLD, FONT_CJK, FONT_CJK_BOLD, SCORE_FONT) assume Linux and break on macOS/Windows; update generate_share_images.py to implement fallback/discovery: try environment variables (e.g., FONT_PATH_*), then check a short list of common platform locations, then attempt to load bundled fonts in the repo, and finally fall back to PIL’s default font or raise a clear error; keep CJK_LANGS logic unchanged but ensure CJK font resolution uses the same multi-path fallback so Hangul characters load cross-platform.
183-184: Use non-deprecated Pillow constants.
Image.ADAPTIVEis deprecated in Pillow 10+ in favor ofImage.Palette.ADAPTIVE.Proposed fix
# Convert to palette mode for smaller file size (~6 distinct colors) - return img.convert("P", palette=Image.ADAPTIVE, colors=64) + return img.convert("P", palette=Image.Palette.ADAPTIVE, colors=64)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/generate_share_images.py` around lines 183 - 184, Replace the deprecated Image.ADAPTIVE constant in the img.convert call with the non-deprecated Image.Palette.ADAPTIVE: change the return in the image conversion (the img.convert("P", palette=..., colors=64) call) to use Image.Palette.ADAPTIVE instead of Image.ADAPTIVE; ensure any necessary imports are available so Image.Palette is accessible in the scope where the conversion occurs.
64-74: ReplaceImage.LANCZOSwithImage.Resampling.LANCZOSfor Pillow 12 compatibility.While
Image.LANCZOSis still supported in Pillow 12+,Image.Resampling.LANCZOSis the recommended constant going forward.Optionally, add explicit error handling for the missing OG image file to provide a clearer error message before the script fails at usage.
Proposed fix
def load_header(): """Crop and scale the WORDLE tiles + globe from og-image.png.""" global HEADER_STRIP og = Image.open(OG_IMAGE_PATH) # Crop the tiles+globe band (y=185..335 in the 1200x630 original) strip = og.crop((0, 185, 1200, 335)) HEADER_STRIP = strip.resize( (int(strip.width * HEADER_HEIGHT / strip.height), HEADER_HEIGHT), - Image.LANCZOS, + Image.Resampling.LANCZOS, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/generate_share_images.py` around lines 64 - 74, In load_header(), replace the deprecated Image.LANCZOS usage with Image.Resampling.LANCZOS when calling strip.resize to ensure Pillow 12 compatibility, i.e., update the resampling constant referenced in that call (function: load_header, globals: HEADER_STRIP, OG_IMAGE_PATH); additionally, add a small try/except around Image.open(OG_IMAGE_PATH) to catch FileNotFoundError (or IOError) and raise or log a clearer message before proceeding so missing OG image errors are explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/generate_share_images.py`:
- Around line 162-181: The code assumes wrap_text(display_text, font_main, draw,
max_w) always returns at least one line; if display_text is empty it returns []
and later accesses lines[0], causing IndexError. Add a guard after computing
lines (the variable from wrap_text) to handle an empty list: if not lines, skip
the drawing block or set a safe default (e.g., no text or a placeholder) before
the existing if len(lines) >= 2 / else branches. Update references around the
draw.textbbox and draw.text calls (where font_main and font_cta are used) so
they only run when lines contains the expected elements.
---
Outside diff comments:
In `@scripts/generate_share_images.py`:
- Line 202: The current assignment for "is_rtl" uses a fragile string comparison
lang_config.get("right_to_left", "false") == "true" which fails if the config
contains a boolean; update the logic where "is_rtl" is set (in
scripts/generate_share_images.py, referencing lang_config and the
"right_to_left" key) to normalize the value to a boolean: retrieve the raw
value, if it's a string interpret common truthy strings (e.g., "true","1","yes")
case-insensitively, otherwise coerce booleans/numerics to bool, and assign that
boolean to "is_rtl" so both true/false and "true"/"false" representations work
robustly.
---
Nitpick comments:
In `@scripts/generate_share_images.py`:
- Around line 39-46: The hardcoded font paths (FONT_DEJAVU, FONT_DEJAVU_BOLD,
FONT_CJK, FONT_CJK_BOLD, SCORE_FONT) assume Linux and break on macOS/Windows;
update generate_share_images.py to implement fallback/discovery: try environment
variables (e.g., FONT_PATH_*), then check a short list of common platform
locations, then attempt to load bundled fonts in the repo, and finally fall back
to PIL’s default font or raise a clear error; keep CJK_LANGS logic unchanged but
ensure CJK font resolution uses the same multi-path fallback so Hangul
characters load cross-platform.
- Around line 183-184: Replace the deprecated Image.ADAPTIVE constant in the
img.convert call with the non-deprecated Image.Palette.ADAPTIVE: change the
return in the image conversion (the img.convert("P", palette=..., colors=64)
call) to use Image.Palette.ADAPTIVE instead of Image.ADAPTIVE; ensure any
necessary imports are available so Image.Palette is accessible in the scope
where the conversion occurs.
- Around line 64-74: In load_header(), replace the deprecated Image.LANCZOS
usage with Image.Resampling.LANCZOS when calling strip.resize to ensure Pillow
12 compatibility, i.e., update the resampling constant referenced in that call
(function: load_header, globals: HEADER_STRIP, OG_IMAGE_PATH); additionally, add
a small try/except around Image.open(OG_IMAGE_PATH) to catch FileNotFoundError
(or IOError) and raise or log a clearer message before proceeding so missing OG
image errors are explicit.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3d680750-de7b-401d-bac9-78cfcd6dead8
⛔ Files ignored due to path filters (299)
webapp/static/images/share/ar_1.pngis excluded by!**/*.pngwebapp/static/images/share/ar_2.pngis excluded by!**/*.pngwebapp/static/images/share/ar_3.pngis excluded by!**/*.pngwebapp/static/images/share/ar_4.pngis excluded by!**/*.pngwebapp/static/images/share/ar_5.pngis excluded by!**/*.pngwebapp/static/images/share/ar_6.pngis excluded by!**/*.pngwebapp/static/images/share/ar_x.pngis excluded by!**/*.pngwebapp/static/images/share/az_1.pngis excluded by!**/*.pngwebapp/static/images/share/az_2.pngis excluded by!**/*.pngwebapp/static/images/share/az_3.pngis excluded by!**/*.pngwebapp/static/images/share/az_4.pngis excluded by!**/*.pngwebapp/static/images/share/az_5.pngis excluded by!**/*.pngwebapp/static/images/share/az_6.pngis excluded by!**/*.pngwebapp/static/images/share/az_x.pngis excluded by!**/*.pngwebapp/static/images/share/bg_1.pngis excluded by!**/*.pngwebapp/static/images/share/bg_2.pngis excluded by!**/*.pngwebapp/static/images/share/bg_3.pngis excluded by!**/*.pngwebapp/static/images/share/bg_4.pngis excluded by!**/*.pngwebapp/static/images/share/bg_5.pngis excluded by!**/*.pngwebapp/static/images/share/bg_6.pngis excluded by!**/*.pngwebapp/static/images/share/bg_x.pngis excluded by!**/*.pngwebapp/static/images/share/br_1.pngis excluded by!**/*.pngwebapp/static/images/share/br_2.pngis excluded by!**/*.pngwebapp/static/images/share/br_3.pngis excluded by!**/*.pngwebapp/static/images/share/br_4.pngis excluded by!**/*.pngwebapp/static/images/share/br_5.pngis excluded by!**/*.pngwebapp/static/images/share/br_6.pngis excluded by!**/*.pngwebapp/static/images/share/br_x.pngis excluded by!**/*.pngwebapp/static/images/share/ca_1.pngis excluded by!**/*.pngwebapp/static/images/share/ca_2.pngis excluded by!**/*.pngwebapp/static/images/share/ca_3.pngis excluded by!**/*.pngwebapp/static/images/share/ca_4.pngis excluded by!**/*.pngwebapp/static/images/share/ca_5.pngis excluded by!**/*.pngwebapp/static/images/share/ca_6.pngis excluded by!**/*.pngwebapp/static/images/share/ca_x.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_1.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_2.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_3.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_4.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_5.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_6.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_x.pngis excluded by!**/*.pngwebapp/static/images/share/cs_1.pngis excluded by!**/*.pngwebapp/static/images/share/cs_2.pngis excluded by!**/*.pngwebapp/static/images/share/cs_3.pngis excluded by!**/*.pngwebapp/static/images/share/cs_4.pngis excluded by!**/*.pngwebapp/static/images/share/cs_5.pngis excluded by!**/*.pngwebapp/static/images/share/cs_6.pngis excluded by!**/*.pngwebapp/static/images/share/cs_x.pngis excluded by!**/*.pngwebapp/static/images/share/da_1.pngis excluded by!**/*.pngwebapp/static/images/share/da_2.pngis excluded by!**/*.pngwebapp/static/images/share/da_3.pngis excluded by!**/*.pngwebapp/static/images/share/da_4.pngis excluded by!**/*.pngwebapp/static/images/share/da_5.pngis excluded by!**/*.pngwebapp/static/images/share/da_6.pngis excluded by!**/*.pngwebapp/static/images/share/da_x.pngis excluded by!**/*.pngwebapp/static/images/share/de_1.pngis excluded by!**/*.pngwebapp/static/images/share/de_2.pngis excluded by!**/*.pngwebapp/static/images/share/de_3.pngis excluded by!**/*.pngwebapp/static/images/share/de_4.pngis excluded by!**/*.pngwebapp/static/images/share/de_5.pngis excluded by!**/*.pngwebapp/static/images/share/de_6.pngis excluded by!**/*.pngwebapp/static/images/share/de_x.pngis excluded by!**/*.pngwebapp/static/images/share/el_1.pngis excluded by!**/*.pngwebapp/static/images/share/el_2.pngis excluded by!**/*.pngwebapp/static/images/share/el_3.pngis excluded by!**/*.pngwebapp/static/images/share/el_4.pngis excluded by!**/*.pngwebapp/static/images/share/el_5.pngis excluded by!**/*.pngwebapp/static/images/share/el_6.pngis excluded by!**/*.pngwebapp/static/images/share/el_x.pngis excluded by!**/*.pngwebapp/static/images/share/en_1.pngis excluded by!**/*.pngwebapp/static/images/share/en_2.pngis excluded by!**/*.pngwebapp/static/images/share/en_3.pngis excluded by!**/*.pngwebapp/static/images/share/en_4.pngis excluded by!**/*.pngwebapp/static/images/share/en_5.pngis excluded by!**/*.pngwebapp/static/images/share/en_6.pngis excluded by!**/*.pngwebapp/static/images/share/en_x.pngis excluded by!**/*.pngwebapp/static/images/share/eo_1.pngis excluded by!**/*.pngwebapp/static/images/share/eo_2.pngis excluded by!**/*.pngwebapp/static/images/share/eo_3.pngis excluded by!**/*.pngwebapp/static/images/share/eo_4.pngis excluded by!**/*.pngwebapp/static/images/share/eo_5.pngis excluded by!**/*.pngwebapp/static/images/share/eo_6.pngis excluded by!**/*.pngwebapp/static/images/share/eo_x.pngis excluded by!**/*.pngwebapp/static/images/share/es_1.pngis excluded by!**/*.pngwebapp/static/images/share/es_2.pngis excluded by!**/*.pngwebapp/static/images/share/es_3.pngis excluded by!**/*.pngwebapp/static/images/share/es_4.pngis excluded by!**/*.pngwebapp/static/images/share/es_5.pngis excluded by!**/*.pngwebapp/static/images/share/es_6.pngis excluded by!**/*.pngwebapp/static/images/share/es_x.pngis excluded by!**/*.pngwebapp/static/images/share/et_1.pngis excluded by!**/*.pngwebapp/static/images/share/et_2.pngis excluded by!**/*.pngwebapp/static/images/share/et_3.pngis excluded by!**/*.pngwebapp/static/images/share/et_4.pngis excluded by!**/*.pngwebapp/static/images/share/et_5.pngis excluded by!**/*.pngwebapp/static/images/share/et_6.pngis excluded by!**/*.pngwebapp/static/images/share/et_x.pngis excluded by!**/*.pngwebapp/static/images/share/eu_1.pngis excluded by!**/*.pngwebapp/static/images/share/eu_2.pngis excluded by!**/*.pngwebapp/static/images/share/eu_3.pngis excluded by!**/*.pngwebapp/static/images/share/eu_4.pngis excluded by!**/*.pngwebapp/static/images/share/eu_5.pngis excluded by!**/*.pngwebapp/static/images/share/eu_6.pngis excluded by!**/*.pngwebapp/static/images/share/eu_x.pngis excluded by!**/*.pngwebapp/static/images/share/fa_1.pngis excluded by!**/*.pngwebapp/static/images/share/fa_2.pngis excluded by!**/*.pngwebapp/static/images/share/fa_3.pngis excluded by!**/*.pngwebapp/static/images/share/fa_4.pngis excluded by!**/*.pngwebapp/static/images/share/fa_5.pngis excluded by!**/*.pngwebapp/static/images/share/fa_6.pngis excluded by!**/*.pngwebapp/static/images/share/fa_x.pngis excluded by!**/*.pngwebapp/static/images/share/fi_1.pngis excluded by!**/*.pngwebapp/static/images/share/fi_2.pngis excluded by!**/*.pngwebapp/static/images/share/fi_3.pngis excluded by!**/*.pngwebapp/static/images/share/fi_4.pngis excluded by!**/*.pngwebapp/static/images/share/fi_5.pngis excluded by!**/*.pngwebapp/static/images/share/fi_6.pngis excluded by!**/*.pngwebapp/static/images/share/fi_x.pngis excluded by!**/*.pngwebapp/static/images/share/fo_1.pngis excluded by!**/*.pngwebapp/static/images/share/fo_2.pngis excluded by!**/*.pngwebapp/static/images/share/fo_3.pngis excluded by!**/*.pngwebapp/static/images/share/fo_4.pngis excluded by!**/*.pngwebapp/static/images/share/fo_5.pngis excluded by!**/*.pngwebapp/static/images/share/fo_6.pngis excluded by!**/*.pngwebapp/static/images/share/fo_x.pngis excluded by!**/*.pngwebapp/static/images/share/fr_1.pngis excluded by!**/*.pngwebapp/static/images/share/fr_2.pngis excluded by!**/*.pngwebapp/static/images/share/fr_3.pngis excluded by!**/*.pngwebapp/static/images/share/fr_4.pngis excluded by!**/*.pngwebapp/static/images/share/fr_5.pngis excluded by!**/*.pngwebapp/static/images/share/fr_6.pngis excluded by!**/*.pngwebapp/static/images/share/fr_x.pngis excluded by!**/*.pngwebapp/static/images/share/fur_1.pngis excluded by!**/*.pngwebapp/static/images/share/fur_2.pngis excluded by!**/*.pngwebapp/static/images/share/fur_3.pngis excluded by!**/*.pngwebapp/static/images/share/fur_4.pngis excluded by!**/*.pngwebapp/static/images/share/fur_5.pngis excluded by!**/*.pngwebapp/static/images/share/fur_6.pngis excluded by!**/*.pngwebapp/static/images/share/fur_x.pngis excluded by!**/*.pngwebapp/static/images/share/fy_1.pngis excluded by!**/*.pngwebapp/static/images/share/fy_2.pngis excluded by!**/*.pngwebapp/static/images/share/fy_3.pngis excluded by!**/*.pngwebapp/static/images/share/fy_4.pngis excluded by!**/*.pngwebapp/static/images/share/fy_5.pngis excluded by!**/*.pngwebapp/static/images/share/fy_6.pngis excluded by!**/*.pngwebapp/static/images/share/fy_x.pngis excluded by!**/*.pngwebapp/static/images/share/ga_1.pngis excluded by!**/*.pngwebapp/static/images/share/ga_2.pngis excluded by!**/*.pngwebapp/static/images/share/ga_3.pngis excluded by!**/*.pngwebapp/static/images/share/ga_4.pngis excluded by!**/*.pngwebapp/static/images/share/ga_5.pngis excluded by!**/*.pngwebapp/static/images/share/ga_6.pngis excluded by!**/*.pngwebapp/static/images/share/ga_x.pngis excluded by!**/*.pngwebapp/static/images/share/gd_1.pngis excluded by!**/*.pngwebapp/static/images/share/gd_2.pngis excluded by!**/*.pngwebapp/static/images/share/gd_3.pngis excluded by!**/*.pngwebapp/static/images/share/gd_4.pngis excluded by!**/*.pngwebapp/static/images/share/gd_5.pngis excluded by!**/*.pngwebapp/static/images/share/gd_6.pngis excluded by!**/*.pngwebapp/static/images/share/gd_x.pngis excluded by!**/*.pngwebapp/static/images/share/gl_1.pngis excluded by!**/*.pngwebapp/static/images/share/gl_2.pngis excluded by!**/*.pngwebapp/static/images/share/gl_3.pngis excluded by!**/*.pngwebapp/static/images/share/gl_4.pngis excluded by!**/*.pngwebapp/static/images/share/gl_5.pngis excluded by!**/*.pngwebapp/static/images/share/gl_6.pngis excluded by!**/*.pngwebapp/static/images/share/gl_x.pngis excluded by!**/*.pngwebapp/static/images/share/he_1.pngis excluded by!**/*.pngwebapp/static/images/share/he_2.pngis excluded by!**/*.pngwebapp/static/images/share/he_3.pngis excluded by!**/*.pngwebapp/static/images/share/he_4.pngis excluded by!**/*.pngwebapp/static/images/share/he_5.pngis excluded by!**/*.pngwebapp/static/images/share/he_6.pngis excluded by!**/*.pngwebapp/static/images/share/he_x.pngis excluded by!**/*.pngwebapp/static/images/share/hr_1.pngis excluded by!**/*.pngwebapp/static/images/share/hr_2.pngis excluded by!**/*.pngwebapp/static/images/share/hr_3.pngis excluded by!**/*.pngwebapp/static/images/share/hr_4.pngis excluded by!**/*.pngwebapp/static/images/share/hr_5.pngis excluded by!**/*.pngwebapp/static/images/share/hr_6.pngis excluded by!**/*.pngwebapp/static/images/share/hr_x.pngis excluded by!**/*.pngwebapp/static/images/share/hu_1.pngis excluded by!**/*.pngwebapp/static/images/share/hu_2.pngis excluded by!**/*.pngwebapp/static/images/share/hu_3.pngis excluded by!**/*.pngwebapp/static/images/share/hu_4.pngis excluded by!**/*.pngwebapp/static/images/share/hu_5.pngis excluded by!**/*.pngwebapp/static/images/share/hu_6.pngis excluded by!**/*.pngwebapp/static/images/share/hu_x.pngis excluded by!**/*.pngwebapp/static/images/share/hy_1.pngis excluded by!**/*.pngwebapp/static/images/share/hy_2.pngis excluded by!**/*.pngwebapp/static/images/share/hy_3.pngis excluded by!**/*.pngwebapp/static/images/share/hy_4.pngis excluded by!**/*.pngwebapp/static/images/share/hy_5.pngis excluded by!**/*.pngwebapp/static/images/share/hy_6.pngis excluded by!**/*.pngwebapp/static/images/share/hy_x.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_1.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_2.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_3.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_4.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_5.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_6.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_x.pngis excluded by!**/*.pngwebapp/static/images/share/ia_1.pngis excluded by!**/*.pngwebapp/static/images/share/ia_2.pngis excluded by!**/*.pngwebapp/static/images/share/ia_3.pngis excluded by!**/*.pngwebapp/static/images/share/ia_4.pngis excluded by!**/*.pngwebapp/static/images/share/ia_5.pngis excluded by!**/*.pngwebapp/static/images/share/ia_6.pngis excluded by!**/*.pngwebapp/static/images/share/ia_x.pngis excluded by!**/*.pngwebapp/static/images/share/ie_1.pngis excluded by!**/*.pngwebapp/static/images/share/ie_2.pngis excluded by!**/*.pngwebapp/static/images/share/ie_3.pngis excluded by!**/*.pngwebapp/static/images/share/ie_4.pngis excluded by!**/*.pngwebapp/static/images/share/ie_5.pngis excluded by!**/*.pngwebapp/static/images/share/ie_6.pngis excluded by!**/*.pngwebapp/static/images/share/ie_x.pngis excluded by!**/*.pngwebapp/static/images/share/is_1.pngis excluded by!**/*.pngwebapp/static/images/share/is_2.pngis excluded by!**/*.pngwebapp/static/images/share/is_3.pngis excluded by!**/*.pngwebapp/static/images/share/is_4.pngis excluded by!**/*.pngwebapp/static/images/share/is_5.pngis excluded by!**/*.pngwebapp/static/images/share/is_6.pngis excluded by!**/*.pngwebapp/static/images/share/is_x.pngis excluded by!**/*.pngwebapp/static/images/share/it_1.pngis excluded by!**/*.pngwebapp/static/images/share/it_2.pngis excluded by!**/*.pngwebapp/static/images/share/it_3.pngis excluded by!**/*.pngwebapp/static/images/share/it_4.pngis excluded by!**/*.pngwebapp/static/images/share/it_5.pngis excluded by!**/*.pngwebapp/static/images/share/it_6.pngis excluded by!**/*.pngwebapp/static/images/share/it_x.pngis excluded by!**/*.pngwebapp/static/images/share/ka_1.pngis excluded by!**/*.pngwebapp/static/images/share/ka_2.pngis excluded by!**/*.pngwebapp/static/images/share/ka_3.pngis excluded by!**/*.pngwebapp/static/images/share/ka_4.pngis excluded by!**/*.pngwebapp/static/images/share/ka_5.pngis excluded by!**/*.pngwebapp/static/images/share/ka_6.pngis excluded by!**/*.pngwebapp/static/images/share/ka_x.pngis excluded by!**/*.pngwebapp/static/images/share/ko_1.pngis excluded by!**/*.pngwebapp/static/images/share/ko_2.pngis excluded by!**/*.pngwebapp/static/images/share/ko_3.pngis excluded by!**/*.pngwebapp/static/images/share/ko_4.pngis excluded by!**/*.pngwebapp/static/images/share/ko_5.pngis excluded by!**/*.pngwebapp/static/images/share/ko_6.pngis excluded by!**/*.pngwebapp/static/images/share/ko_x.pngis excluded by!**/*.pngwebapp/static/images/share/la_1.pngis excluded by!**/*.pngwebapp/static/images/share/la_2.pngis excluded by!**/*.pngwebapp/static/images/share/la_3.pngis excluded by!**/*.pngwebapp/static/images/share/la_4.pngis excluded by!**/*.pngwebapp/static/images/share/la_5.pngis excluded by!**/*.pngwebapp/static/images/share/la_6.pngis excluded by!**/*.pngwebapp/static/images/share/la_x.pngis excluded by!**/*.pngwebapp/static/images/share/lb_1.pngis excluded by!**/*.pngwebapp/static/images/share/lb_2.pngis excluded by!**/*.pngwebapp/static/images/share/lb_3.pngis excluded by!**/*.pngwebapp/static/images/share/lb_4.pngis excluded by!**/*.pngwebapp/static/images/share/lb_5.pngis excluded by!**/*.pngwebapp/static/images/share/lb_6.pngis excluded by!**/*.pngwebapp/static/images/share/lb_x.pngis excluded by!**/*.pngwebapp/static/images/share/lt_1.pngis excluded by!**/*.pngwebapp/static/images/share/lt_2.pngis excluded by!**/*.pngwebapp/static/images/share/lt_3.pngis excluded by!**/*.pngwebapp/static/images/share/lt_4.pngis excluded by!**/*.pngwebapp/static/images/share/lt_5.pngis excluded by!**/*.pngwebapp/static/images/share/lt_6.pngis excluded by!**/*.pngwebapp/static/images/share/lt_x.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_1.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_2.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_3.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_4.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_5.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_6.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_x.pngis excluded by!**/*.pngwebapp/static/images/share/lv_1.pngis excluded by!**/*.pngwebapp/static/images/share/lv_2.pngis excluded by!**/*.pngwebapp/static/images/share/lv_3.pngis excluded by!**/*.pngwebapp/static/images/share/lv_4.pngis excluded by!**/*.pngwebapp/static/images/share/lv_5.pngis excluded by!**/*.pngwebapp/static/images/share/lv_6.pngis excluded by!**/*.pngwebapp/static/images/share/lv_x.pngis excluded by!**/*.pngwebapp/static/images/share/mi_1.pngis excluded by!**/*.pngwebapp/static/images/share/mi_2.pngis excluded by!**/*.pngwebapp/static/images/share/mi_3.pngis excluded by!**/*.pngwebapp/static/images/share/mi_4.pngis excluded by!**/*.pngwebapp/static/images/share/mi_5.pngis excluded by!**/*.pngwebapp/static/images/share/mi_6.pngis excluded by!**/*.pngwebapp/static/images/share/mi_x.pngis excluded by!**/*.pngwebapp/static/images/share/mk_1.pngis excluded by!**/*.pngwebapp/static/images/share/mk_2.pngis excluded by!**/*.pngwebapp/static/images/share/mk_3.pngis excluded by!**/*.pngwebapp/static/images/share/mk_4.pngis excluded by!**/*.pngwebapp/static/images/share/mk_5.pngis excluded by!**/*.pngwebapp/static/images/share/mk_6.pngis excluded by!**/*.pngwebapp/static/images/share/mk_x.pngis excluded by!**/*.pngwebapp/static/images/share/mn_1.pngis excluded by!**/*.pngwebapp/static/images/share/mn_2.pngis excluded by!**/*.pngwebapp/static/images/share/mn_3.pngis excluded by!**/*.pngwebapp/static/images/share/mn_4.pngis excluded by!**/*.pngwebapp/static/images/share/mn_5.pngis excluded by!**/*.png
📒 Files selected for processing (1)
scripts/generate_share_images.py
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
scripts/generate_share_images.py (1)
162-181:⚠️ Potential issue | 🟡 MinorGuard against empty wrapped text before indexing.
wrap_text()can return[]for empty/whitespace input, andlines[0]then raisesIndexError.Suggested fix
for size in (44, 38, 32, 26): font_main = get_font(font_reg, size) font_cta = get_font(font_bold, size + 4) lines = wrap_text(display_text, font_main, draw, max_w) if len(lines) <= 2: break + if not lines: + lines = [""] + if len(lines) >= 2:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/generate_share_images.py` around lines 162 - 181, The block assumes wrap_text(display_text, font_main, draw, max_w) returns at least one line but wrap_text can return an empty list; add a guard after computing lines to handle an empty list before accessing lines[0] or lines[1:]. In the function where wrap_text is called (variables: lines, display_text, font_main, font_cta, draw, WIDTH), check if not lines: either skip drawing/return early or draw a fallback (e.g., nothing or a default message) so subsequent accesses to lines[0] and lines[1:] and calls to draw.textbbox do not raise IndexError. Ensure both the if len(lines) >= 2 and the else branch only run when lines is non-empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/generate_share_images.py`:
- Around line 159-168: The loop that reduces font size using get_font and
wrap_text may still yield >2 lines at the minimum size (size 26), but the code
then joins all remaining lines into line2 which can overflow; change the
post-loop handling in the block that uses lines to enforce a hard two-line cap:
keep line1 = lines[0], construct line2 from the remaining text but truncate it
with an ellipsis so its rendered width (measured with draw.textlength or
font_cta metrics) does not exceed max_w, ensuring you measure with the chosen
font (font_main/font_cta) and trim words/characters until the width fits; update
the logic around variables lines, line1, line2 and reuse wrap_text/measurement
utilities rather than allowing an unbounded join.
- Around line 170-181: Several draw.text(...) calls (around the block using
bbox1, bbox2, WIDTH, font_main, font_cta and the else branch using bbox) exceed
the 100-char line length; run the Ruff formatter (or manually wrap the long
draw.text argument lists) so each line is <=100 characters, breaking long
argument lists across multiple lines and aligning subsequent args, and reformat
the tuple calculations ((WIDTH - (bbox[2] - bbox[0])) // 2, ...) and the
draw.text(...) calls for both the two-line branch (line1/line2 using
bbox1/bbox2) and the single-line else branch (line using bbox) to satisfy the
linter.
- Around line 64-73: The load_header() function currently opens OG_IMAGE_PATH
without a context manager and doesn't handle missing files; change
Image.open(OG_IMAGE_PATH) to use a with-statement (e.g., with
Image.open(OG_IMAGE_PATH) as og:) so the file handle is closed safely, and wrap
that block in a try/except catching FileNotFoundError/OSError to raise or log a
clear error that mentions OG_IMAGE_PATH before attempting to crop/resize and
assign HEADER_STRIP.
- Around line 39-43: Replace the hard-coded absolute font paths by implementing
resilient font resolution in scripts/generate_share_images.py: for each symbol
(FONT_DEJAVU, FONT_DEJAVU_BOLD, FONT_CJK, FONT_CJK_BOLD, SCORE_FONT) attempt to
load from a list of candidate paths and fall back to a bundled font or PIL’s
default if none exist, and log a warning when falling back; ensure functions
that call ImageFont.truetype (or equivalent) use the resolved path or fallback
object so font loading won’t crash when the specific system fonts are missing.
---
Duplicate comments:
In `@scripts/generate_share_images.py`:
- Around line 162-181: The block assumes wrap_text(display_text, font_main,
draw, max_w) returns at least one line but wrap_text can return an empty list;
add a guard after computing lines to handle an empty list before accessing
lines[0] or lines[1:]. In the function where wrap_text is called (variables:
lines, display_text, font_main, font_cta, draw, WIDTH), check if not lines:
either skip drawing/return early or draw a fallback (e.g., nothing or a default
message) so subsequent accesses to lines[0] and lines[1:] and calls to
draw.textbbox do not raise IndexError. Ensure both the if len(lines) >= 2 and
the else branch only run when lines is non-empty.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3e27e104-12ab-4b29-bee1-6461ff6b2223
⛔ Files ignored due to path filters (289)
webapp/static/images/share/ar_1.pngis excluded by!**/*.pngwebapp/static/images/share/ar_2.pngis excluded by!**/*.pngwebapp/static/images/share/ar_3.pngis excluded by!**/*.pngwebapp/static/images/share/ar_4.pngis excluded by!**/*.pngwebapp/static/images/share/ar_5.pngis excluded by!**/*.pngwebapp/static/images/share/ar_6.pngis excluded by!**/*.pngwebapp/static/images/share/ar_x.pngis excluded by!**/*.pngwebapp/static/images/share/az_1.pngis excluded by!**/*.pngwebapp/static/images/share/az_2.pngis excluded by!**/*.pngwebapp/static/images/share/az_3.pngis excluded by!**/*.pngwebapp/static/images/share/az_4.pngis excluded by!**/*.pngwebapp/static/images/share/az_5.pngis excluded by!**/*.pngwebapp/static/images/share/az_6.pngis excluded by!**/*.pngwebapp/static/images/share/az_x.pngis excluded by!**/*.pngwebapp/static/images/share/bg_1.pngis excluded by!**/*.pngwebapp/static/images/share/bg_2.pngis excluded by!**/*.pngwebapp/static/images/share/bg_3.pngis excluded by!**/*.pngwebapp/static/images/share/bg_4.pngis excluded by!**/*.pngwebapp/static/images/share/bg_5.pngis excluded by!**/*.pngwebapp/static/images/share/bg_6.pngis excluded by!**/*.pngwebapp/static/images/share/bg_x.pngis excluded by!**/*.pngwebapp/static/images/share/br_1.pngis excluded by!**/*.pngwebapp/static/images/share/br_2.pngis excluded by!**/*.pngwebapp/static/images/share/br_3.pngis excluded by!**/*.pngwebapp/static/images/share/br_4.pngis excluded by!**/*.pngwebapp/static/images/share/br_5.pngis excluded by!**/*.pngwebapp/static/images/share/br_6.pngis excluded by!**/*.pngwebapp/static/images/share/br_x.pngis excluded by!**/*.pngwebapp/static/images/share/ca_1.pngis excluded by!**/*.pngwebapp/static/images/share/ca_2.pngis excluded by!**/*.pngwebapp/static/images/share/ca_3.pngis excluded by!**/*.pngwebapp/static/images/share/ca_4.pngis excluded by!**/*.pngwebapp/static/images/share/ca_5.pngis excluded by!**/*.pngwebapp/static/images/share/ca_6.pngis excluded by!**/*.pngwebapp/static/images/share/ca_x.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_1.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_2.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_3.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_4.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_5.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_6.pngis excluded by!**/*.pngwebapp/static/images/share/ckb_x.pngis excluded by!**/*.pngwebapp/static/images/share/cs_1.pngis excluded by!**/*.pngwebapp/static/images/share/cs_2.pngis excluded by!**/*.pngwebapp/static/images/share/cs_3.pngis excluded by!**/*.pngwebapp/static/images/share/cs_4.pngis excluded by!**/*.pngwebapp/static/images/share/cs_5.pngis excluded by!**/*.pngwebapp/static/images/share/cs_6.pngis excluded by!**/*.pngwebapp/static/images/share/cs_x.pngis excluded by!**/*.pngwebapp/static/images/share/da_1.pngis excluded by!**/*.pngwebapp/static/images/share/da_2.pngis excluded by!**/*.pngwebapp/static/images/share/da_3.pngis excluded by!**/*.pngwebapp/static/images/share/da_4.pngis excluded by!**/*.pngwebapp/static/images/share/da_5.pngis excluded by!**/*.pngwebapp/static/images/share/da_6.pngis excluded by!**/*.pngwebapp/static/images/share/da_x.pngis excluded by!**/*.pngwebapp/static/images/share/de_1.pngis excluded by!**/*.pngwebapp/static/images/share/de_2.pngis excluded by!**/*.pngwebapp/static/images/share/de_3.pngis excluded by!**/*.pngwebapp/static/images/share/de_4.pngis excluded by!**/*.pngwebapp/static/images/share/de_5.pngis excluded by!**/*.pngwebapp/static/images/share/de_6.pngis excluded by!**/*.pngwebapp/static/images/share/de_x.pngis excluded by!**/*.pngwebapp/static/images/share/el_1.pngis excluded by!**/*.pngwebapp/static/images/share/el_2.pngis excluded by!**/*.pngwebapp/static/images/share/el_3.pngis excluded by!**/*.pngwebapp/static/images/share/el_4.pngis excluded by!**/*.pngwebapp/static/images/share/el_5.pngis excluded by!**/*.pngwebapp/static/images/share/el_6.pngis excluded by!**/*.pngwebapp/static/images/share/el_x.pngis excluded by!**/*.pngwebapp/static/images/share/en_1.pngis excluded by!**/*.pngwebapp/static/images/share/en_2.pngis excluded by!**/*.pngwebapp/static/images/share/en_3.pngis excluded by!**/*.pngwebapp/static/images/share/en_4.pngis excluded by!**/*.pngwebapp/static/images/share/en_5.pngis excluded by!**/*.pngwebapp/static/images/share/en_6.pngis excluded by!**/*.pngwebapp/static/images/share/en_x.pngis excluded by!**/*.pngwebapp/static/images/share/eo_1.pngis excluded by!**/*.pngwebapp/static/images/share/eo_2.pngis excluded by!**/*.pngwebapp/static/images/share/eo_3.pngis excluded by!**/*.pngwebapp/static/images/share/eo_4.pngis excluded by!**/*.pngwebapp/static/images/share/eo_5.pngis excluded by!**/*.pngwebapp/static/images/share/eo_6.pngis excluded by!**/*.pngwebapp/static/images/share/eo_x.pngis excluded by!**/*.pngwebapp/static/images/share/es_1.pngis excluded by!**/*.pngwebapp/static/images/share/es_2.pngis excluded by!**/*.pngwebapp/static/images/share/es_3.pngis excluded by!**/*.pngwebapp/static/images/share/es_4.pngis excluded by!**/*.pngwebapp/static/images/share/es_5.pngis excluded by!**/*.pngwebapp/static/images/share/es_6.pngis excluded by!**/*.pngwebapp/static/images/share/es_x.pngis excluded by!**/*.pngwebapp/static/images/share/et_1.pngis excluded by!**/*.pngwebapp/static/images/share/et_2.pngis excluded by!**/*.pngwebapp/static/images/share/et_3.pngis excluded by!**/*.pngwebapp/static/images/share/et_4.pngis excluded by!**/*.pngwebapp/static/images/share/et_5.pngis excluded by!**/*.pngwebapp/static/images/share/et_6.pngis excluded by!**/*.pngwebapp/static/images/share/et_x.pngis excluded by!**/*.pngwebapp/static/images/share/eu_1.pngis excluded by!**/*.pngwebapp/static/images/share/eu_2.pngis excluded by!**/*.pngwebapp/static/images/share/eu_3.pngis excluded by!**/*.pngwebapp/static/images/share/eu_4.pngis excluded by!**/*.pngwebapp/static/images/share/eu_5.pngis excluded by!**/*.pngwebapp/static/images/share/eu_6.pngis excluded by!**/*.pngwebapp/static/images/share/eu_x.pngis excluded by!**/*.pngwebapp/static/images/share/fa_1.pngis excluded by!**/*.pngwebapp/static/images/share/fa_2.pngis excluded by!**/*.pngwebapp/static/images/share/fa_3.pngis excluded by!**/*.pngwebapp/static/images/share/fa_4.pngis excluded by!**/*.pngwebapp/static/images/share/fa_5.pngis excluded by!**/*.pngwebapp/static/images/share/fa_6.pngis excluded by!**/*.pngwebapp/static/images/share/fa_x.pngis excluded by!**/*.pngwebapp/static/images/share/fi_1.pngis excluded by!**/*.pngwebapp/static/images/share/fi_2.pngis excluded by!**/*.pngwebapp/static/images/share/fi_3.pngis excluded by!**/*.pngwebapp/static/images/share/fi_4.pngis excluded by!**/*.pngwebapp/static/images/share/fi_5.pngis excluded by!**/*.pngwebapp/static/images/share/fi_6.pngis excluded by!**/*.pngwebapp/static/images/share/fi_x.pngis excluded by!**/*.pngwebapp/static/images/share/fo_1.pngis excluded by!**/*.pngwebapp/static/images/share/fo_2.pngis excluded by!**/*.pngwebapp/static/images/share/fo_3.pngis excluded by!**/*.pngwebapp/static/images/share/fo_4.pngis excluded by!**/*.pngwebapp/static/images/share/fo_5.pngis excluded by!**/*.pngwebapp/static/images/share/fo_6.pngis excluded by!**/*.pngwebapp/static/images/share/fo_x.pngis excluded by!**/*.pngwebapp/static/images/share/fr_1.pngis excluded by!**/*.pngwebapp/static/images/share/fr_2.pngis excluded by!**/*.pngwebapp/static/images/share/fr_3.pngis excluded by!**/*.pngwebapp/static/images/share/fr_4.pngis excluded by!**/*.pngwebapp/static/images/share/fr_5.pngis excluded by!**/*.pngwebapp/static/images/share/fr_6.pngis excluded by!**/*.pngwebapp/static/images/share/fr_x.pngis excluded by!**/*.pngwebapp/static/images/share/fur_1.pngis excluded by!**/*.pngwebapp/static/images/share/fur_2.pngis excluded by!**/*.pngwebapp/static/images/share/fur_3.pngis excluded by!**/*.pngwebapp/static/images/share/fur_4.pngis excluded by!**/*.pngwebapp/static/images/share/fur_5.pngis excluded by!**/*.pngwebapp/static/images/share/fur_6.pngis excluded by!**/*.pngwebapp/static/images/share/fur_x.pngis excluded by!**/*.pngwebapp/static/images/share/fy_1.pngis excluded by!**/*.pngwebapp/static/images/share/fy_2.pngis excluded by!**/*.pngwebapp/static/images/share/fy_3.pngis excluded by!**/*.pngwebapp/static/images/share/fy_4.pngis excluded by!**/*.pngwebapp/static/images/share/fy_5.pngis excluded by!**/*.pngwebapp/static/images/share/fy_6.pngis excluded by!**/*.pngwebapp/static/images/share/fy_x.pngis excluded by!**/*.pngwebapp/static/images/share/ga_1.pngis excluded by!**/*.pngwebapp/static/images/share/ga_2.pngis excluded by!**/*.pngwebapp/static/images/share/ga_3.pngis excluded by!**/*.pngwebapp/static/images/share/ga_4.pngis excluded by!**/*.pngwebapp/static/images/share/ga_5.pngis excluded by!**/*.pngwebapp/static/images/share/ga_6.pngis excluded by!**/*.pngwebapp/static/images/share/ga_x.pngis excluded by!**/*.pngwebapp/static/images/share/gd_1.pngis excluded by!**/*.pngwebapp/static/images/share/gd_2.pngis excluded by!**/*.pngwebapp/static/images/share/gd_3.pngis excluded by!**/*.pngwebapp/static/images/share/gd_4.pngis excluded by!**/*.pngwebapp/static/images/share/gd_5.pngis excluded by!**/*.pngwebapp/static/images/share/gd_6.pngis excluded by!**/*.pngwebapp/static/images/share/gd_x.pngis excluded by!**/*.pngwebapp/static/images/share/gl_1.pngis excluded by!**/*.pngwebapp/static/images/share/gl_2.pngis excluded by!**/*.pngwebapp/static/images/share/gl_3.pngis excluded by!**/*.pngwebapp/static/images/share/gl_4.pngis excluded by!**/*.pngwebapp/static/images/share/gl_5.pngis excluded by!**/*.pngwebapp/static/images/share/gl_6.pngis excluded by!**/*.pngwebapp/static/images/share/gl_x.pngis excluded by!**/*.pngwebapp/static/images/share/he_1.pngis excluded by!**/*.pngwebapp/static/images/share/he_2.pngis excluded by!**/*.pngwebapp/static/images/share/he_3.pngis excluded by!**/*.pngwebapp/static/images/share/he_4.pngis excluded by!**/*.pngwebapp/static/images/share/he_5.pngis excluded by!**/*.pngwebapp/static/images/share/he_6.pngis excluded by!**/*.pngwebapp/static/images/share/he_x.pngis excluded by!**/*.pngwebapp/static/images/share/hr_1.pngis excluded by!**/*.pngwebapp/static/images/share/hr_2.pngis excluded by!**/*.pngwebapp/static/images/share/hr_3.pngis excluded by!**/*.pngwebapp/static/images/share/hr_4.pngis excluded by!**/*.pngwebapp/static/images/share/hr_5.pngis excluded by!**/*.pngwebapp/static/images/share/hr_6.pngis excluded by!**/*.pngwebapp/static/images/share/hr_x.pngis excluded by!**/*.pngwebapp/static/images/share/hu_1.pngis excluded by!**/*.pngwebapp/static/images/share/hu_2.pngis excluded by!**/*.pngwebapp/static/images/share/hu_3.pngis excluded by!**/*.pngwebapp/static/images/share/hu_4.pngis excluded by!**/*.pngwebapp/static/images/share/hu_5.pngis excluded by!**/*.pngwebapp/static/images/share/hu_6.pngis excluded by!**/*.pngwebapp/static/images/share/hu_x.pngis excluded by!**/*.pngwebapp/static/images/share/hy_1.pngis excluded by!**/*.pngwebapp/static/images/share/hy_2.pngis excluded by!**/*.pngwebapp/static/images/share/hy_3.pngis excluded by!**/*.pngwebapp/static/images/share/hy_4.pngis excluded by!**/*.pngwebapp/static/images/share/hy_5.pngis excluded by!**/*.pngwebapp/static/images/share/hy_6.pngis excluded by!**/*.pngwebapp/static/images/share/hy_x.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_1.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_2.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_3.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_4.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_5.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_6.pngis excluded by!**/*.pngwebapp/static/images/share/hyw_x.pngis excluded by!**/*.pngwebapp/static/images/share/ia_1.pngis excluded by!**/*.pngwebapp/static/images/share/ia_2.pngis excluded by!**/*.pngwebapp/static/images/share/ia_3.pngis excluded by!**/*.pngwebapp/static/images/share/ia_4.pngis excluded by!**/*.pngwebapp/static/images/share/ia_5.pngis excluded by!**/*.pngwebapp/static/images/share/ia_6.pngis excluded by!**/*.pngwebapp/static/images/share/ia_x.pngis excluded by!**/*.pngwebapp/static/images/share/ie_1.pngis excluded by!**/*.pngwebapp/static/images/share/ie_2.pngis excluded by!**/*.pngwebapp/static/images/share/ie_3.pngis excluded by!**/*.pngwebapp/static/images/share/ie_4.pngis excluded by!**/*.pngwebapp/static/images/share/ie_5.pngis excluded by!**/*.pngwebapp/static/images/share/ie_6.pngis excluded by!**/*.pngwebapp/static/images/share/ie_x.pngis excluded by!**/*.pngwebapp/static/images/share/is_1.pngis excluded by!**/*.pngwebapp/static/images/share/is_2.pngis excluded by!**/*.pngwebapp/static/images/share/is_3.pngis excluded by!**/*.pngwebapp/static/images/share/is_4.pngis excluded by!**/*.pngwebapp/static/images/share/is_5.pngis excluded by!**/*.pngwebapp/static/images/share/is_6.pngis excluded by!**/*.pngwebapp/static/images/share/is_x.pngis excluded by!**/*.pngwebapp/static/images/share/it_1.pngis excluded by!**/*.pngwebapp/static/images/share/it_2.pngis excluded by!**/*.pngwebapp/static/images/share/it_3.pngis excluded by!**/*.pngwebapp/static/images/share/it_4.pngis excluded by!**/*.pngwebapp/static/images/share/it_5.pngis excluded by!**/*.pngwebapp/static/images/share/it_6.pngis excluded by!**/*.pngwebapp/static/images/share/it_x.pngis excluded by!**/*.pngwebapp/static/images/share/ka_1.pngis excluded by!**/*.pngwebapp/static/images/share/ka_2.pngis excluded by!**/*.pngwebapp/static/images/share/ka_3.pngis excluded by!**/*.pngwebapp/static/images/share/ka_4.pngis excluded by!**/*.pngwebapp/static/images/share/ka_5.pngis excluded by!**/*.pngwebapp/static/images/share/ka_6.pngis excluded by!**/*.pngwebapp/static/images/share/ka_x.pngis excluded by!**/*.pngwebapp/static/images/share/ko_1.pngis excluded by!**/*.pngwebapp/static/images/share/ko_2.pngis excluded by!**/*.pngwebapp/static/images/share/ko_3.pngis excluded by!**/*.pngwebapp/static/images/share/ko_4.pngis excluded by!**/*.pngwebapp/static/images/share/ko_5.pngis excluded by!**/*.pngwebapp/static/images/share/ko_6.pngis excluded by!**/*.pngwebapp/static/images/share/ko_x.pngis excluded by!**/*.pngwebapp/static/images/share/la_1.pngis excluded by!**/*.pngwebapp/static/images/share/la_2.pngis excluded by!**/*.pngwebapp/static/images/share/la_3.pngis excluded by!**/*.pngwebapp/static/images/share/la_4.pngis excluded by!**/*.pngwebapp/static/images/share/la_5.pngis excluded by!**/*.pngwebapp/static/images/share/la_6.pngis excluded by!**/*.pngwebapp/static/images/share/la_x.pngis excluded by!**/*.pngwebapp/static/images/share/lb_1.pngis excluded by!**/*.pngwebapp/static/images/share/lb_2.pngis excluded by!**/*.pngwebapp/static/images/share/lb_3.pngis excluded by!**/*.pngwebapp/static/images/share/lb_4.pngis excluded by!**/*.pngwebapp/static/images/share/lb_5.pngis excluded by!**/*.pngwebapp/static/images/share/lb_6.pngis excluded by!**/*.pngwebapp/static/images/share/lb_x.pngis excluded by!**/*.pngwebapp/static/images/share/lt_1.pngis excluded by!**/*.pngwebapp/static/images/share/lt_2.pngis excluded by!**/*.pngwebapp/static/images/share/lt_3.pngis excluded by!**/*.pngwebapp/static/images/share/lt_4.pngis excluded by!**/*.pngwebapp/static/images/share/lt_5.pngis excluded by!**/*.pngwebapp/static/images/share/lt_6.pngis excluded by!**/*.pngwebapp/static/images/share/lt_x.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_1.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_2.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_3.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_4.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_5.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_6.pngis excluded by!**/*.pngwebapp/static/images/share/ltg_x.pngis excluded by!**/*.pngwebapp/static/images/share/lv_1.pngis excluded by!**/*.pngwebapp/static/images/share/lv_2.pngis excluded by!**/*.pngwebapp/static/images/share/lv_3.pngis excluded by!**/*.pngwebapp/static/images/share/lv_4.pngis excluded by!**/*.pngwebapp/static/images/share/lv_5.pngis excluded by!**/*.pngwebapp/static/images/share/lv_6.pngis excluded by!**/*.pngwebapp/static/images/share/lv_x.pngis excluded by!**/*.pngwebapp/static/images/share/mi_1.pngis excluded by!**/*.pngwebapp/static/images/share/mi_2.pngis excluded by!**/*.pngwebapp/static/images/share/mi_3.pngis excluded by!**/*.pngwebapp/static/images/share/mi_4.pngis excluded by!**/*.pngwebapp/static/images/share/mi_5.pngis excluded by!**/*.pngwebapp/static/images/share/mi_6.pngis excluded by!**/*.pngwebapp/static/images/share/mi_x.pngis excluded by!**/*.pngwebapp/static/images/share/mk_1.pngis excluded by!**/*.pngwebapp/static/images/share/mk_2.pngis excluded by!**/*.png
📒 Files selected for processing (2)
scripts/generate_share_images.pywebapp/app.py
✅ Files skipped from review due to trivial changes (1)
- webapp/app.py
| FONT_DEJAVU = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" | ||
| FONT_DEJAVU_BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" | ||
| FONT_CJK = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc" | ||
| FONT_CJK_BOLD = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc" | ||
| SCORE_FONT = "/usr/share/fonts/truetype/noto/NotoSans-Bold.ttf" |
There was a problem hiding this comment.
Avoid hard-coded system font paths without fallback.
These absolute paths will fail on environments where Noto fonts are not installed, causing generation to crash.
Suggested fix
+def pick_font_path(*candidates):
+ for candidate in candidates:
+ if os.path.exists(candidate):
+ return candidate
+ raise FileNotFoundError(f"No usable font found in: {candidates}")
+
-FONT_DEJAVU = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
-FONT_DEJAVU_BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
-FONT_CJK = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
-FONT_CJK_BOLD = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc"
-SCORE_FONT = "/usr/share/fonts/truetype/noto/NotoSans-Bold.ttf"
+FONT_DEJAVU = pick_font_path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
+FONT_DEJAVU_BOLD = pick_font_path("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf")
+FONT_CJK = pick_font_path(
+ "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
+ FONT_DEJAVU,
+)
+FONT_CJK_BOLD = pick_font_path(
+ "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc",
+ FONT_DEJAVU_BOLD,
+)
+SCORE_FONT = pick_font_path(
+ "/usr/share/fonts/truetype/noto/NotoSans-Bold.ttf",
+ FONT_DEJAVU_BOLD,
+)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/generate_share_images.py` around lines 39 - 43, Replace the
hard-coded absolute font paths by implementing resilient font resolution in
scripts/generate_share_images.py: for each symbol (FONT_DEJAVU,
FONT_DEJAVU_BOLD, FONT_CJK, FONT_CJK_BOLD, SCORE_FONT) attempt to load from a
list of candidate paths and fall back to a bundled font or PIL’s default if none
exist, and log a warning when falling back; ensure functions that call
ImageFont.truetype (or equivalent) use the resolved path or fallback object so
font loading won’t crash when the specific system fonts are missing.
| def load_header(): | ||
| """Crop and scale the WORDLE tiles + globe from og-image.png.""" | ||
| global HEADER_STRIP | ||
| og = Image.open(OG_IMAGE_PATH) | ||
| # Crop the tiles+globe band (y=185..335 in the 1200x630 original) | ||
| strip = og.crop((0, 185, 1200, 335)) | ||
| HEADER_STRIP = strip.resize( | ||
| (int(strip.width * HEADER_HEIGHT / strip.height), HEADER_HEIGHT), | ||
| Image.LANCZOS, | ||
| ) |
There was a problem hiding this comment.
Handle missing og-image.png and close file handles safely.
Image.open() should be wrapped in a context manager, and missing file errors should be explicit for faster diagnosis.
Suggested fix
def load_header():
"""Crop and scale the WORDLE tiles + globe from og-image.png."""
global HEADER_STRIP
- og = Image.open(OG_IMAGE_PATH)
- # Crop the tiles+globe band (y=185..335 in the 1200x630 original)
- strip = og.crop((0, 185, 1200, 335))
+ if not os.path.exists(OG_IMAGE_PATH):
+ raise FileNotFoundError(f"Required image not found: {OG_IMAGE_PATH}")
+ with Image.open(OG_IMAGE_PATH) as og:
+ # Crop the tiles+globe band (y=185..335 in the 1200x630 original)
+ strip = og.crop((0, 185, 1200, 335))
HEADER_STRIP = strip.resize(
(int(strip.width * HEADER_HEIGHT / strip.height), HEADER_HEIGHT),
Image.LANCZOS,
)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/generate_share_images.py` around lines 64 - 73, The load_header()
function currently opens OG_IMAGE_PATH without a context manager and doesn't
handle missing files; change Image.open(OG_IMAGE_PATH) to use a with-statement
(e.g., with Image.open(OG_IMAGE_PATH) as og:) so the file handle is closed
safely, and wrap that block in a try/except catching FileNotFoundError/OSError
to raise or log a clear error that mentions OG_IMAGE_PATH before attempting to
crop/resize and assign HEADER_STRIP.
| for size in (44, 38, 32, 26): | ||
| font_main = get_font(font_reg, size) | ||
| font_cta = get_font(font_bold, size + 4) | ||
| lines = wrap_text(display_text, font_main, draw, max_w) | ||
| if len(lines) <= 2: | ||
| break | ||
|
|
||
| if len(lines) >= 2: | ||
| line1 = lines[0] | ||
| line2 = " ".join(lines[1:]) |
There was a problem hiding this comment.
Two-line cap is not guaranteed at minimum font size.
If text still wraps to >2 lines at size 26, the current branch joins all remaining lines into line2, which can overflow the width and break the layout objective.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/generate_share_images.py` around lines 159 - 168, The loop that
reduces font size using get_font and wrap_text may still yield >2 lines at the
minimum size (size 26), but the code then joins all remaining lines into line2
which can overflow; change the post-loop handling in the block that uses lines
to enforce a hard two-line cap: keep line1 = lines[0], construct line2 from the
remaining text but truncate it with an ellipsis so its rendered width (measured
with draw.textlength or font_cta metrics) does not exceed max_w, ensuring you
measure with the chosen font (font_main/font_cta) and trim words/characters
until the width fits; update the logic around variables lines, line1, line2 and
reuse wrap_text/measurement utilities rather than allowing an unbounded join.
| draw.text(((WIDTH - (bbox1[2] - bbox1[0])) // 2, 430), line1, fill=WHITE, font=font_main) | ||
| bbox2 = draw.textbbox((0, 0), line2, font=font_cta) | ||
| draw.text( | ||
| ((WIDTH - (bbox2[2] - bbox2[0])) // 2, 430 + size + 12), | ||
| line2, | ||
| fill=GREEN, | ||
| font=font_cta, | ||
| ) | ||
| else: | ||
| line = lines[0] | ||
| bbox = draw.textbbox((0, 0), line, font=font_cta) | ||
| draw.text(((WIDTH - (bbox[2] - bbox[0])) // 2, 460), line, fill=GREEN, font=font_cta) |
There was a problem hiding this comment.
Run Ruff formatting to enforce max line length.
A few updated draw.text(...) lines exceed the 100-character limit.
As per coding guidelines, "Use Ruff formatter and linter with 100 character line length for Python code".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/generate_share_images.py` around lines 170 - 181, Several
draw.text(...) calls (around the block using bbox1, bbox2, WIDTH, font_main,
font_cta and the else branch using bbox) exceed the 100-char line length; run
the Ruff formatter (or manually wrap the long draw.text argument lists) so each
line is <=100 characters, breaking long argument lists across multiple lines and
aligning subsequent args, and reformat the tuple calculations ((WIDTH - (bbox[2]
- bbox[0])) // 2, ...) and the draw.text(...) calls for both the two-line branch
(line1/line2 using bbox1/bbox2) and the single-line else branch (line using
bbox) to satisfy the linter.
|
Superseded by #146 which was merged first with the same changes. |
Summary
og-image.pnginstead of drawing an approximation with codeTest plan
Summary by CodeRabbit
New Features
Improvements