| 110 | |
| 111 | |
| 112 | def build_icons_router(static_root: Path) -> APIRouter: |
| 113 | router = APIRouter(prefix="/api/icons", tags=["icons"]) |
| 114 | |
| 115 | @router.post("/export") |
| 116 | def export_icons(body: IconExportRequest) -> dict[str, str | bool]: |
| 117 | if len(body.icons) > MAX_ICONS_PER_REQUEST: |
| 118 | raise HTTPException( |
| 119 | status_code=400, |
| 120 | detail=f"Too many icons (max {MAX_ICONS_PER_REQUEST}).", |
| 121 | ) |
| 122 | |
| 123 | bg = hex_to_rgb(body.bg_color) |
| 124 | lines: list[str] = [] |
| 125 | |
| 126 | if body.mode == "tft_rgb565": |
| 127 | lines.append("// Auto-generated by DisplayKit (Icon List → Export TFT, server)\n") |
| 128 | lines.append("#pragma once\n\n") |
| 129 | lines.append("#include <Arduino.h>\n") |
| 130 | lines.append("#include <pgmspace.h>\n\n") |
| 131 | lines.append( |
| 132 | "// Note: any transparent pixels are mapped to the current DisplayKit background color.\n\n" |
| 133 | ) |
| 134 | filename = "displaykit_icons_rgb565.h" |
| 135 | else: |
| 136 | lines.append("// Auto-generated by DisplayKit (Icon List → Export U8g2, server)\n") |
| 137 | lines.append("#pragma once\n\n") |
| 138 | lines.append("#include <Arduino.h>\n") |
| 139 | lines.append("#include <pgmspace.h>\n\n") |
| 140 | lines.append("// Use with: u8g2.drawXBMP(x, y, w, h, <name>);\n\n") |
| 141 | filename = "displaykit_icons_xbm.h" |
| 142 | |
| 143 | for ref in body.icons: |
| 144 | rel = ref.file.strip() |
| 145 | try: |
| 146 | path = resolve_icon_file(static_root, rel) |
| 147 | except ValueError as e: |
| 148 | lines.append(f"// {rel}\n") |
| 149 | lines.append(f"// ERROR: {e}\n\n") |
| 150 | continue |
| 151 | if not path.is_file(): |
| 152 | lines.append(f"// {rel}\n") |
| 153 | lines.append("// ERROR: file not found\n\n") |
| 154 | continue |
| 155 | |
| 156 | inferred = infer_size_from_path(rel) |
| 157 | try: |
| 158 | with Image.open(path) as probe: |
| 159 | probe = probe.convert("RGBA") |
| 160 | nw, nh = probe.size |
| 161 | tw, th = inferred if inferred else (nw, nh) |
| 162 | tw, th, rgba = load_rgba_resized(path, tw, th) |
| 163 | except Exception as e: # noqa: BLE001 — surface as C comment |
| 164 | lines.append(f"// {rel}\n") |
| 165 | lines.append(f"// ERROR: {e}\n\n") |
| 166 | continue |
| 167 | |
| 168 | sym = safe_symbol_from_path(rel) |
| 169 | if body.mode == "tft_rgb565": |