diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e02af63..3a4d2d7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -825,7 +825,7 @@ jobs: - name: Verify the introductory Python examples run: | python -m venv output/reference-env - output/reference-env/bin/python -m pip install --only-binary=:all: fullbleed==2.5.22 + output/reference-env/bin/python -m pip install --only-binary=:all: fullbleed==2.5.34 output/reference-env/bin/python -I tools/verify_quickstarts.py - name: Verify the downloadable Node invoice and guide run: python tools/verify_node_examples.py diff --git a/README.md b/README.md index 23298fb..d6af888 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,14 @@ python tools/sync_reference.py --source /path/to/fullbleed-official --ref v2.5.2 `docs/reference-source.json` records the exact source commit. Use a built, installed release wheel to rerun examples and review the final PDFs and previews before replacing downloadable artifacts. `docs/assets/examples/verification.json` records the checks performed on published examples. -Run `python -I tools/verify_quickstarts.py` in an environment with that reference -release installed. It executes the Python snippets directly from five introductory -pages, checks their PDF text and page counts, and exercises the documented project -commands. CI retains the PDFs, previews, and structured report. Older showcase, -playground, and tutorial downloads keep their own verified release pins. +Run `python -I tools/verify_quickstarts.py` in an environment with +`fullbleed==2.5.34` installed. The first-run examples are qualified against this +stable package independently of the frozen API reference. The verifier executes +the Python snippets directly from five introductory pages, checks their PDF +text, embedded fonts and page counts, and exercises the documented project +commands. It runs both Python quickstart tabs and requires their PDFs and previews +to match byte for byte. CI retains the outputs and structured report. Older +showcase, playground, and tutorial downloads keep their own verified release pins. The tagged C# notice lives in `examples/dotnet-accessibility`. When updating it, restore its lockfile from public NuGet, render both `ua1` and `ua2`, and review diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 896259d..20d981c 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,6 +1,6 @@ --- -title: Generate PDFs in Python from HTML and CSS -description: Copy a small Python script to create your first PDF, then try a complete Fullbleed project with bundled fonts and previews. +title: Generate PDFs in Python with components or HTML and CSS +description: Create your first PDF with reusable Python components or HTML/CSS. Copy either complete example, use bundled fonts, and render the same invoice. --- # Create your first PDF in Python @@ -8,32 +8,72 @@ Prefer to try it before setting up Python? [Open the browser playground](../play to edit HTML/CSS and download a PDF or runnable project. Use the [invoice notebook](notebook.md) to work with Python data in Colab. -Install Fullbleed into your Python environment: +Choose **Python components** to compose a document with functions, or +**HTML/CSS** to bring a template. Both examples below use the stable API, embed +the bundled Inter font, and write the same one-page invoice. + +Install Fullbleed in your Python environment. If you need an isolated environment, +follow the [virtual environment setup](installation.md#use-a-virtual-environment) +first. ```bash python -m pip install fullbleed ``` -Save this as `hello.py`: - -```python -from importlib.resources import files -from pathlib import Path -import fullbleed - -font = files("fullbleed_assets").joinpath("fonts/Inter-Variable.ttf") -engine = fullbleed.PdfEngine(font_files=[str(font)]) -html = "

Invoice INV-1042

Consulting: USD 1,200.00

" -css = """ -@page { size: A4; margin: 20mm; } -body { font-family: Inter; color: #203a36; } -h1 { color: #175c52; } -""" -pdf = engine.render_pdf(html, css) -Path("invoice.pdf").write_bytes(pdf) -``` - -The example embeds the Inter font included with Fullbleed. +Choose one tab and save its complete example as `hello.py`. These examples are +checked with Fullbleed **2.5.34**. + +=== "Python components" + + ```python + from importlib.resources import files + from pathlib import Path + import fullbleed + from fullbleed.ui import Document, Heading, Paragraph + + @Document( + title="Invoice INV-1042", + styles={ + "@page": {"size": "A4", "margin": "20mm"}, + "body": {"font_family": "Inter", "color": "#203a36"}, + ".invoice-title": {"color": "#175c52"}, + }, + ) + def Invoice(number, description, amount): + return ( + Heading("Invoice ", number, level=1, class_name="invoice-title"), + Paragraph(description, ": ", amount), + ) + + font = files("fullbleed_assets").joinpath("fonts/Inter-Variable.ttf") + engine = fullbleed.PdfEngine(font_files=[str(font)]) + invoice = Invoice("INV-1042", "Consulting", "USD 1,200.00") + Path("invoice.pdf").write_bytes(invoice.render_pdf(engine)) + ``` + +=== "HTML/CSS" + + ```python + from importlib.resources import files + from pathlib import Path + import fullbleed + + font = files("fullbleed_assets").joinpath("fonts/Inter-Variable.ttf") + engine = fullbleed.PdfEngine(font_files=[str(font)]) + html = '

Invoice INV-1042

Consulting: USD 1,200.00

' + css = """ + @page { size: A4; margin: 20mm; } + body { font-family: Inter; color: #203a36; } + .invoice-title { color: #175c52; } + """ + pdf = engine.render_pdf(html, css) + Path("invoice.pdf").write_bytes(pdf) + ``` + +`Invoice(...)` receives ordinary Python arguments. `@Document(styles=...)` +attaches the stylesheet to its result. The heading's +`class_name="invoice-title"` matches the `.invoice-title` rule; the HTML/CSS +example uses the same class and values. Run it and open the resulting file: @@ -44,6 +84,13 @@ python -m fullbleed inspect pdf invoice.pdf --json For a PNG preview of the finalized PDF, continue to the [font and preview walkthrough](first-pdf.md). +Continue with [reusable components, lists, and styles](../guides/python-components.md) +or the [designed HTML/CSS invoice](../guides/invoices.md). The separate +[component alpha](../guides/component-preview-11.md) adds portable component +styles, themes, named parts, and styled/unstyled HTML export. It has its own +explicit installation instructions; the examples above use the stable +`fullbleed.ui` API. + ## Start with a complete project For a component-based report with vendored assets and verification output, run these commands in a new directory: diff --git a/tools/verify_quickstarts.py b/tools/verify_quickstarts.py index 3666b38..d77be25 100644 --- a/tools/verify_quickstarts.py +++ b/tools/verify_quickstarts.py @@ -14,6 +14,7 @@ import fullbleed ROOT = Path(__file__).resolve().parents[1] +QUICKSTART_RELEASE = "2.5.34" FIXTURES = [ ("index.md", "invoice.pdf", [("Invoice INV-1042", "USD 1,200.00")], True), ("getting-started/quickstart.md", "invoice.pdf", [("Invoice INV-1042", "USD 1,200.00")], True), @@ -34,7 +35,9 @@ def main(): out = args.out.resolve() out.mkdir(parents=True, exist_ok=True) reference = json.loads((ROOT / "docs/reference-source.json").read_text(encoding="utf-8")) - expected = reference["release"].removeprefix("v") + # First-run examples track a qualified stable package independently of the + # frozen API reference and the older downloadable showcase projects. + expected = QUICKSTART_RELEASE installed = version("fullbleed") if installed != expected: raise SystemExit(f"Install fullbleed=={expected} in this environment; found {installed}.") @@ -83,8 +86,37 @@ def inspect(path, page_texts=None, embedded_font=False): blocks = re.findall(r"```python\n(.*?)```", source, flags=re.S) # The first home-page example uses the newer component API. Its own # Windows/Linux gate runs it with the component starter's pinned release. - expected_blocks = 2 if page in ("index.md", "engine/assets.md") else 1 + expected_blocks = 2 if page in ( + "index.md", "engine/assets.md", "getting-started/quickstart.md" + ) else 1 assert len(blocks) == expected_blocks, (page, len(blocks)) + if page == "getting-started/quickstart.md": + assert f"Fullbleed **{QUICKSTART_RELEASE}**" in source + paired = [] + for authoring, block in zip(("components", "html-css"), blocks): + folder = workspace / ("getting-started_quickstart_" + authoring) + folder.mkdir() + code = dedent(block) + (folder / "hello.py").write_text(code, encoding="utf-8", newline="\n") + run("hello", ["-I", "hello.py"], folder) + result = run("inspect-cli", [ + "-I", "-m", "fullbleed", "inspect", "pdf", "invoice.pdf", "--json" + ], folder) + assert json.loads(result.stdout)["ok"] + evidence = { + "page": page, "authoring": authoring, + "source_sha256": hashlib.sha256(code.encode()).hexdigest(), + **inspect(folder / output, required, embedded_font), + } + snippets.append(evidence) + paired.append(evidence) + assert paired[0]["sha256"] == paired[1]["sha256"], ( + "Quickstart authoring paths must produce the same PDF", paired + ) + assert [p["sha256"] for p in paired[0]["previews"]] == [ + p["sha256"] for p in paired[1]["previews"] + ], "Quickstart previews differ" + continue folder = workspace / page.removesuffix(".md").replace("/", "_") folder.mkdir() code = dedent(blocks[1] if page == "index.md" else blocks[0]) @@ -108,9 +140,6 @@ def inspect(path, page_texts=None, embedded_font=False): "feature": "bundled_standard_font_previews", "available": available}) if page == "getting-started/first-pdf.md": assert len(list((folder / "output/preview").glob("*.png"))) == 1 - if page == "getting-started/quickstart.md": - result = run("inspect-cli", ["-I", "-m", "fullbleed", "inspect", "pdf", "invoice.pdf", "--json"], folder) - assert json.loads(result.stdout)["ok"] project = workspace / "my-report" project.mkdir() @@ -123,6 +152,8 @@ def inspect(path, page_texts=None, embedded_font=False): assert (folder / "report.py").is_file(), folder result = {"ok": True, "engine": installed, "reference_commit": reference["commit"], + "quickstart_authoring_paths": ["components", "html-css"], + "quickstart_pdf_and_previews_identical": True, "platform": sys.platform, "python": sys.version, "snippets": snippets, "capability_snippets": capability_snippets, "scaffold": scaffold, "commands": checks,