-
Notifications
You must be signed in to change notification settings - Fork 2
fix(seo): declare site icons for crawlers and ship favicon.ico #11838
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| ### Fixed | ||
|
|
||
| - **Google showed no icon for anyplot.ai.** Crawlers are routed to the bot HTML | ||
| from `api/routers/seo.py`, whose `<head>` declared no icon, and Google's | ||
| `/favicon.ico` fallback returned 404. The bot template now carries the same | ||
| icon links as `app/index.html`, and `app/public/` ships a real `favicon.ico` | ||
| (16, 32, 48 px) and an `apple-touch-icon.png`. `Organization.logo` points at | ||
| the new square `icon-512.png` instead of the 1200×630 banner. (#11838) | ||
|
|
||
| ### Changed | ||
|
|
||
| - **The favicon is drawn from the real MonoLisa outlines.** An SVG favicon | ||
| loads no webfonts, so the `<text>` mark always rendered in the viewer's | ||
| system monospace. `scripts/generate_favicon.py` outlines the `ap` monogram | ||
| from MonoLisa Bold into plain paths on an opaque paper ground (a transparent | ||
| icon with dark ink vanished on dark result pages) and rasterizes the ICO and | ||
| PNG siblings from that one SVG. (#11838) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| #!/usr/bin/env python3 | ||
| """Generate the site icons in `app/public/` from the real MonoLisa outlines. | ||
|
|
||
| The icon is the `ap` monogram over the brand-green square on the paper ground. | ||
| It is written as plain `<path>` outlines: an SVG used as a favicon is rendered | ||
| in an isolated context that loads no webfonts, so `<text>` in MonoLisa always | ||
| fell back to whatever monospace face the viewer's system had. The raster | ||
| siblings exist because crawlers and older clients never read the SVG — | ||
| Google's fallback is `/favicon.ico`, iOS wants `/apple-touch-icon.png`, and | ||
| the schema.org `Organization.logo` needs a square image (`/icon-512.png`). | ||
|
|
||
| Usage: | ||
| uv run --with resvg-py python scripts/generate_favicon.py | ||
|
|
||
| The outputs are committed; rerun only when the mark or the palette changes. | ||
| MonoLisa is fetched through `core.images` (GCS, cached in `/tmp/anyplot-fonts`) | ||
| and only its outlines for the two letters end up in the repository. | ||
|
MarkusNeusinger marked this conversation as resolved.
|
||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from io import BytesIO | ||
| from pathlib import Path | ||
|
|
||
| import resvg_py | ||
| from fontTools.pens.boundsPen import BoundsPen | ||
| from fontTools.pens.svgPathPen import SVGPathPen | ||
| from fontTools.pens.transformPen import TransformPen | ||
| from fontTools.ttLib import TTFont | ||
| from PIL import Image | ||
|
|
||
| from core.images import _get_monolisa_font_path | ||
|
|
||
|
|
||
| PUBLIC_DIR = Path(__file__).resolve().parent.parent / "app" / "public" | ||
|
|
||
| PAPER = "#F5F3EC" # --bg-page | ||
| INK = "#1A1A17" # --ink | ||
| GREEN = "#009E73" # --imprint-green | ||
|
|
||
| VIEWBOX = 32 | ||
| TEXT = "ap" | ||
| WEIGHT = 700 | ||
| FONT_SIZE = 19.0 | ||
| BASELINE = 16.5 | ||
| LETTER_SPACING = -0.4 | ||
| SQUARE_TOP = 20.0 | ||
| SQUARE_SIZE = 5.0 | ||
| CORNER_RADIUS = 6 | ||
|
|
||
| ICO_SIZES = [(16, 16), (32, 32), (48, 48)] | ||
| PNG_OUTPUTS = {"apple-touch-icon.png": 180, "icon-512.png": 512} | ||
|
|
||
|
|
||
| def _format(value: float) -> str: | ||
| return f"{value:.2f}".rstrip("0").rstrip(".") | ||
|
|
||
|
|
||
| def build_svg(font_path: Path, corner_radius: int = CORNER_RADIUS) -> str: | ||
| """Outline the monogram at the icon's geometry and return the SVG document.""" | ||
| font = TTFont(font_path) | ||
| glyphs = font.getGlyphSet(location={"wght": WEIGHT}) | ||
| cmap = font.getBestCmap() | ||
| scale = FONT_SIZE / font["head"].unitsPerEm | ||
|
|
||
| names = [cmap[ord(char)] for char in TEXT] | ||
| width = sum(glyphs[name].width * scale for name in names) + LETTER_SPACING * (len(names) - 1) | ||
| x = VIEWBOX / 2 - width / 2 | ||
|
|
||
| commands = [] | ||
| ink_left = None | ||
| for name in names: | ||
| transform = (scale, 0, 0, -scale, x, BASELINE) | ||
| pen = SVGPathPen(glyphs, ntos=_format) | ||
| glyphs[name].draw(TransformPen(pen, transform)) | ||
| commands.append(pen.getCommands()) | ||
| if ink_left is None: | ||
| bounds = BoundsPen(glyphs) | ||
| glyphs[name].draw(TransformPen(bounds, transform)) | ||
| ink_left = bounds.bounds[0] | ||
| x += glyphs[name].width * scale + LETTER_SPACING | ||
|
|
||
| return ( | ||
| f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {VIEWBOX} {VIEWBOX}">\n' | ||
| f' <rect width="{VIEWBOX}" height="{VIEWBOX}" rx="{corner_radius}" fill="{PAPER}"/>\n' | ||
| f' <path fill="{INK}" d="{" ".join(commands)}"/>\n' | ||
| f' <rect x="{_format(ink_left)}" y="{_format(SQUARE_TOP)}" width="{_format(SQUARE_SIZE)}" ' | ||
| f'height="{_format(SQUARE_SIZE)}" fill="{GREEN}"/>\n' | ||
| "</svg>\n" | ||
| ) | ||
|
|
||
|
|
||
| def render_png(svg: str, size: int) -> bytes: | ||
| return bytes(resvg_py.svg_to_bytes(svg_string=svg, width=size, height=size)) | ||
|
|
||
|
|
||
| def main() -> None: | ||
| font_path = _get_monolisa_font_path() | ||
| if font_path is None: | ||
| raise SystemExit("MonoLisa is unavailable (GCS access needed); refusing to outline a fallback face.") | ||
|
|
||
| svg = build_svg(font_path) | ||
| (PUBLIC_DIR / "favicon.svg").write_text(svg, encoding="utf-8") | ||
|
|
||
| # The large PNGs are full-bleed squares: iOS and Google apply their own mask, | ||
| # and transparent corners would come out black on a home screen. | ||
| square_svg = build_svg(font_path, corner_radius=0) | ||
| for filename, size in PNG_OUTPUTS.items(): | ||
| (PUBLIC_DIR / filename).write_bytes(render_png(square_svg, size)) | ||
|
|
||
| # Each ICO entry is rendered at its own size rather than downscaled from one | ||
| # large bitmap, so the 16 px entry keeps the hinting resvg gives it. | ||
| frames = [Image.open(BytesIO(render_png(svg, width))) for width, _ in ICO_SIZES] | ||
| frames[-1].save(PUBLIC_DIR / "favicon.ico", format="ICO", sizes=ICO_SIZES, append_images=frames[:-1]) | ||
|
|
||
| for filename in ("favicon.svg", "favicon.ico", *PNG_OUTPUTS): | ||
| print(f"{filename}: {(PUBLIC_DIR / filename).stat().st_size} B") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.