Background and attribution
Red Cards are small, wallet-sized cards that clearly state a person's constitutional rights, most commonly the right to remain silent and the right to refuse consent to searches.
They are often used during encounters with law enforcement to:
- communicate rights without escalating a situation
- overcome language barriers
- provide clarity under stress
This project is inspired by the Know Your Rights red cards published by the Immigrant Legal Resource Center (ILRC) and similar advocacy organizations. The goal is to make these materials easier to generate, translate, and distribute at scale, while preserving accuracy and readability. The original Red Cards and translated materials are available at ilrc.org/redcards. This project is not affiliated with the ILRC and does not constitute legal advice.
Most tools that attempt to generate multilingual PDFs fail once you leave basic Latin text. RedCard API focuses on the hard, real-world constraints:
- multilingual rendering reliability across 22 scripts
- right-to-left text shaping for Arabic and Hebrew
- script-aware wrapping for no-space languages
- strict font availability and coverage validation
- print-ready output that works in real cutting/printing workflows
The result is an API that advocacy groups, civic platforms, or community tools can realistically rely on.
What I built
I built a backend-first platform for generating and serving print-ready red cards, with a companion React frontend that handles language selection, preview, deep links, and print workflows.
- Supports 56 languages across 22 Unicode scripts, from Latin and Cyrillic to CJK, Arabic, Hebrew, Thai, Khmer, Myanmar, Ethiopic, and more.
- Two render modes:
legacy(two-page front/back grids) andfold(single-page fold sheet with front and back side-by-side per row). - Two print paths: backend PDF download and browser-native fold-sheet printing.
- Every card carries a translated front side and a fixed English back side explaining 4th and 5th Amendment rights.
API design
The backend exposes a focused set of endpoints that separate content retrieval from PDF generation:
GET /api/health-- service status and loaded language count.GET /api/config-- returns valid layout options, render modes, and page size so the frontend does not hardcode backend constraints.GET /api/languages-- returns all 56 languages with metadata includingfontSupported,rtl,official, and source attribution per entry.GET /api/card/{code}-- returns structured card content (header, bullets, source metadata) for a single language, used by the frontend for live preview.GET /api/render/{code}-- generates a printable PDF and returns it as a byte stream. Acceptsmode(legacyorfold) andcards_per_pagequery parameters.
Configuration is managed through pydantic-settings with CARD_-prefixed environment variables covering page size, margins, default layouts, CORS origins, and translation file paths.
Rendering pipeline
Each PDF request follows a five-stage pipeline:
- Validation -- mode, language code, and layout count are validated against known enums. Invalid mode returns
400; unknown language returns404; uninitialized store returns503. - Layout calculation -- geometry is computed per mode.
legacybuilds a grid across two pages (4, 6, 8, or 12 cards).foldbuilds side-by-side front/back rows on a single page (4, 5, or 6 rows). If a fold request receives a legacy-range layout value, it normalizes to the fold default. - Content preparation --
TranslationsStoreloads all language data from JSON at startup, normalizes entries, and serves them from memory. Front content is language-specific; back content is fixed English. - PDF rendering -- ReportLab draws cards into the computed layout positions with script-aware text wrapping, RTL shaping, and adaptive font scaling applied per card.
- Response -- the PDF is built entirely in memory and returned as a downloadable byte stream with no disk I/O.
Script and font system
Script-aware text wrapping
The 22 supported scripts fall into two wrapping strategies. CJK, Thai, Lao, Khmer, and Myanmar use character-level wrapping because these scripts do not reliably use spaces as word boundaries. Latin, Cyrillic, Arabic, Hebrew, Greek, and other space-delimited scripts use word-boundary wrapping. A safety pass re-wraps any line that still exceeds the card width after the primary strategy runs.
RTL support
Arabic and Hebrew text passes through arabic-reshaper for glyph joining, then python-bidi for logical-to-visual reordering. This prevents mirrored punctuation and broken sentence flow in the rendered PDF output.
Font resolution
The font system uses Noto Sans families for broad Unicode coverage. FontManager initializes lazily on the first request and registers font files from assets/fonts/. A script detection module maps each language code to a Unicode script enum, then to the appropriate Noto Sans family and file pair. The fontSupported flag is computed per language and surfaced in /api/languages so the frontend can warn users or disable PDF download when fonts are unavailable.
Adaptive font scaling
A two-level scaling system ensures text fits within card boundaries regardless of language verbosity. The first level computes a layout-based scale factor from the ratio of card area to a base reference size. The second level runs a bidirectional content-fit search (find_best_fit_scale) that scales up when content is short and scales down when content is dense, bounded by min/max factors and absolute font-size caps. Both the translated front and English back use this adaptive fit.
Frontend application
Routes
The frontend uses a route split that separates onboarding from operational workflow:
/-- hero landing page with product overview and calls to action./redcards-- operational workspace for language selection, card preview, and print controls./redcards/:code-- deep-linked language workspace. The URL language code takes priority over thelocalStorageselection, making shared links and bookmarks reliable./builder-- translation payload builder for authoring new card content.
Print workflows
Two print paths are available depending on user needs:
- Backend PDF download -- calls
/api/render/{code}with the selected mode and layout, then triggers a browser download. - Browser-native fold sheet -- renders the fold layout directly in the browser and triggers
window.print(), with guidance for disabling headers/footers, setting scale to 100%, and configuring duplex settings.
Translation builder
The builder is a frontend-only authoring tool that replaces manual JSON editing with a guided form. It includes language name, code, RTL toggle, header, and the full set of Red Card statements. A live preview updates as the user types. Export actions include Print From Browser, Copy JSON, Download JSON, and Reset Form. The generated payload produces both front.named_points and front.bullets, with a merge rule that combines card handoff and inside-home guidance into one final bullet.
Error handling
The frontend maps backend failure states to user-facing messages: 400 with font_not_available triggers a font warning, 404 shows a language-not-found banner with recovery actions, 503 indicates the service is starting up, and generic 5xx errors display a backend failure message.
Repositories
- Backend API: github.com/pierre-projects/redcardapi
- Frontend App: github.com/pierre-projects/redcardapi-frontend
Tech stack
- Backend: FastAPI, Uvicorn, pydantic-settings, ReportLab
- Text/script handling: arabic-reshaper, python-bidi, script-aware wrapping utilities
- Fonts: Noto Sans families across 22 Unicode scripts
- Data: TranslationsStore (JSON-backed, loaded at startup, normalized in memory)
- Frontend: React, Vite, React Router
- Print: ReportLab PDF generation (backend), browser-native print (frontend)


