Spaces:
Runtime error
Runtime error
Download backend/app/slides/build.py from firebird-technologies/NoteStack-Test: direct link, hf CLI and curl.
- Browser
- Download file 7.04 kB
-
https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/main/backend/app/slides/build.py
- Command line
-
hf download hf://spaces/firebird-technologies/NoteStack-Test/backend/app/slides/build.py
-
curl -L -o build.py https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/main/backend/app/slides/build.py
7.04 kB
| """From a written deck to what is stored and shown. `finish` picks each slide's variant, fits its text, and checks | |
| the result in Chrome when one is installed. `restructure` takes a deck back from the editor and fits it again. | |
| `view` reads a stored deck back (clamped again, so older or edited decks still draw) and gives its pages; one slide | |
| that cannot be drawn becomes a plain one instead of breaking the deck.""" | |
| import copy | |
| import logging | |
| import random | |
| from collections import Counter | |
| from app.infographics.image import ImageUnavailable | |
| from app.slides import export | |
| from app.slides.content import clamp_deck, clamp_slide, empty_slide | |
| from app.slides.layout import GEOMETRY, SCALES, VARIANTS, assign_variants, compose, fits_variant, seed_of | |
| from app.slides.render import slide_html | |
| from app.slides.themes import theme_id | |
| log = logging.getLogger(__name__) | |
| def fit(deck: dict, theme: str, fmt: str, seed: int) -> dict: | |
| """Every slide as it will be drawn: text shortened where it had to be, so what is stored is what is shown.""" | |
| out = copy.deepcopy(deck) | |
| out["slides"] = [compose(out, i, theme, fmt, seed).slide for i in range(len(out["slides"]))] | |
| return out | |
| def finish(deck: dict, theme: str, fmt: str, seed: int, previous: list[tuple[str, str]] | None = None) -> dict: | |
| """Variants, then refit. Raises ValueError when a slide cannot be drawn at all.""" | |
| theme = theme_id(theme) | |
| deck = copy.deepcopy(deck) | |
| assign_variants(deck["slides"], theme, seed, previous) | |
| return refit(deck, theme, fmt, seed) | |
| def refit(deck: dict, theme: str, fmt: str, seed: int) -> dict: | |
| """Fitted text and a Chrome check that no text box overflows (slides that do are drawn a step smaller and fitted | |
| again), keeping each slide's variant. Raises ValueError when a slide cannot be drawn at all.""" | |
| theme = theme_id(theme) | |
| deck = fit(deck, theme, fmt, seed) | |
| for _ in range(3): | |
| try: | |
| bad = export.overflowing(deck, theme, fmt, seed) | |
| except ImageUnavailable: | |
| break # no Chrome here: the measured fit alone keeps text inside its boxes | |
| if not bad: | |
| break | |
| log.info("slides %s overflow in Chrome, drawing them smaller", sorted(bad)) | |
| for i in bad: | |
| deck["slides"][i]["shrink"] = min(int(deck["slides"][i].get("shrink") or 0) + 1, len(SCALES) - 1) | |
| deck = fit(deck, theme, fmt, seed) | |
| for i in range(len(deck["slides"])): | |
| slide_html(deck, i, theme, fmt, seed) # raises if a slide cannot be drawn: better now than when shown | |
| return deck | |
| class BadEdit(ValueError): | |
| """A deck sent back from the editor that cannot be kept (no slides, or too many).""" | |
| MAX_SLIDES = 30 | |
| def fill_variants(slides: list[dict], theme: str, seed: int) -> None: | |
| """Give a variant, in place, to each slide that has none or whose variant no longer suits it (a new slide, a changed | |
| layout, a card row now with five points). Every other slide keeps its own, so editing one slide never redraws the | |
| rest. The choice follows assign_variants: suited to the content, not the same as a neighbour, unused ones first.""" | |
| theme = theme_id(theme) | |
| def ok(i: int) -> bool: | |
| s = slides[i] | |
| return s["variant"] in VARIANTS[s["layout"]] and fits_variant(s, theme, s["variant"]) | |
| keep = [ok(i) for i in range(len(slides))] | |
| for i, s in enumerate(slides): | |
| before = (slides[i - 1]["layout"], slides[i - 1]["variant"]) if i else None | |
| if keep[i] and (s["layout"], s["variant"]) != before: | |
| continue | |
| after = (slides[i + 1]["layout"], slides[i + 1]["variant"]) if i + 1 < len(slides) and keep[i + 1] else None | |
| options = [v for v in VARIANTS[s["layout"]] if fits_variant(s, theme, v)] or list(VARIANTS[s["layout"]]) | |
| fresh = [v for v in options if (s["layout"], v) not in (before, after)] or options | |
| used = Counter((x["layout"], x["variant"]) for j, x in enumerate(slides) if j != i) | |
| rnd = random.Random(seed + i) | |
| s["variant"] = min(fresh, key=lambda v: (used[(s["layout"], v)], rnd.random())) | |
| keep[i] = True | |
| def restructure(content: dict, slides: object, final: bool) -> dict: | |
| """The stored deck with the slides the editor sent (text edited, slides added, removed, moved, or changed to another | |
| layout). They are coerced like model output (clamp_deck: plain text, the format's limits, valid layouts), slides | |
| that need one get a variant, and the deck is fitted: quickly for a preview, with the Chrome check when `final`.""" | |
| got = stored(content) | |
| if not got: | |
| raise BadEdit("This deck has no slides to edit.") | |
| deck, theme, fmt, seed = got | |
| if not isinstance(slides, list) or not slides: | |
| raise BadEdit("A deck needs at least one slide.") | |
| if len(slides) > MAX_SLIDES: | |
| raise BadEdit(f"A deck can have at most {MAX_SLIDES} slides.") | |
| new = clamp_deck({"title": deck["title"], "subtitle": deck["subtitle"], "slides": slides}, fmt) | |
| if not new: | |
| raise BadEdit("A deck needs at least one slide with a heading.") | |
| for s in new["slides"]: | |
| s["shrink"] = 0 # text changed: fitted afresh, so shorter text can be drawn larger again | |
| fill_variants(new["slides"], theme, seed) | |
| return refit(new, theme, fmt, seed) if final else fit(new, theme, fmt, seed) | |
| def slots(content: dict) -> list[list[str]]: | |
| """For each slide, the parts its variant has a place for (kicker, lead, body, by, closing...): what the editor | |
| can offer to add.""" | |
| got = stored(content) | |
| if not got: | |
| return [] | |
| deck, theme, _fmt, _seed = got | |
| out = [] | |
| for s in deck["slides"]: | |
| variants = GEOMETRY[theme][s["layout"]] | |
| out.append(sorted(k for k, v in (variants.get(s["variant"]) or next(iter(variants.values()))).items() if v)) | |
| return out | |
| def stored(content: dict) -> tuple[dict, str, str, int] | None: | |
| """A stored deck, clamped, with its theme, format and seed (None when there is none).""" | |
| fmt = content.get("format") if content.get("format") in ("detailed", "presenter") else "detailed" | |
| deck = clamp_deck(content.get("deck") or {}, fmt) | |
| if not deck: | |
| return None | |
| return deck, theme_id(content.get("theme")), fmt, seed_of(content.get("seed")) | |
| def _placeholder(deck: dict, index: int) -> dict: | |
| s = empty_slide("section") | |
| s["heading"] = deck.get("title") or "Slide" | |
| return clamp_slide(s, "detailed") | |
| def view(content: dict) -> list[str]: | |
| """Each slide of a stored deck as a standalone page.""" | |
| got = stored(content) | |
| if not got: | |
| return [] | |
| deck, theme, fmt, seed = got | |
| pages = [] | |
| for i in range(len(deck["slides"])): | |
| try: | |
| pages.append(slide_html(deck, i, theme, fmt, seed)) | |
| except Exception: | |
| log.exception("slide %s could not be drawn", i) | |
| safe = copy.deepcopy(deck) | |
| safe["slides"][i] = _placeholder(deck, i) | |
| pages.append(slide_html(safe, i, theme, fmt, seed)) | |
| return pages | |