{"id":50520969,"url":"https://github.com/4o4e/tavolo","last_synced_at":"2026-06-03T04:01:34.670Z","repository":{"id":356673845,"uuid":"1233147266","full_name":"4o4E/Tavolo","owner":"4o4E","description":"Headless Kotlin image processing and offline rendering toolkit powered by Skiko","archived":false,"fork":false,"pushed_at":"2026-05-31T12:04:57.000Z","size":141089,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T14:07:02.813Z","etag":null,"topics":["bdf-font","compose-dsl","gif","gif-processing","graphics","headless","http-server","image-processing","kotlin","offline-rendering","skiko","svg"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/4o4E.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-08T16:34:54.000Z","updated_at":"2026-05-31T12:05:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/4o4E/Tavolo","commit_stats":null,"previous_names":["4o4e/tavolo"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/4o4E/Tavolo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4o4E%2FTavolo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4o4E%2FTavolo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4o4E%2FTavolo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4o4E%2FTavolo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/4o4E","download_url":"https://codeload.github.com/4o4E/Tavolo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4o4E%2FTavolo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33847265,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bdf-font","compose-dsl","gif","gif-processing","graphics","headless","http-server","image-processing","kotlin","offline-rendering","skiko","svg"],"created_at":"2026-06-03T04:01:33.940Z","updated_at":"2026-06-03T04:01:34.656Z","avatar_url":"https://github.com/4o4E.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tavolo\n\n[English](README_EN.md) | 简体中文\n\n`Tavolo` 读作 `TAH-vo-lo`，来自 Esperanto，含义为“图层”。\n\n## 项目目标\n\nTavolo 的目标是在没有显示输出的服务器环境中完成图片处理和离线渲染。\n它不依赖桌面窗口或交互式画布，而是把输入数据、字体、图片资源和绘图 DSL 渲染成图片。\n核心渲染能力基于 [Skiko](https://github.com/JetBrains/skiko)。\n\n适合的场景包括：\n\n- 在 headless server 中生成静态图片、卡片、图表和 SVG 混排内容。\n- 对图片或 GIF 做离线逐帧处理，生成表情、滤镜和动态图效果。\n- 通过 HTTP 服务暴露图片处理指令，供机器人或业务系统调用。\n- 解析 BDF 点阵字体，服务于点阵文字和像素风图片生成。\n\n## 模块概览\n\n| 模块 | 作用 |\n| --- | --- |\n| [`graphics`](graphics/src/main/kotlin) | Compose 风格绘图 DSL，支持布局、文本、图片、SVG、图表、Modifier 效果和 3D 渲染。 |\n| [`gif-codec`](gif-codec/src/main/kotlin) | GIF 编解码和逐帧处理框架，部分实现参考 [`cssxsh/mirai-skia-plugin`](https://github.com/cssxsh/mirai-skia-plugin)。 |\n| [`core`](core/src/main/kotlin) | 图片处理指令、表情生成器和输入驱动的图片生成能力。 |\n| [`bdf-parser`](bdf-parser/src/main/kotlin) | BDF 点阵字体解析。 |\n| [`http-server`](http-server) | HTTP 指令服务，封装命令查询和图片执行接口。 |\n| [`http-client`](http-client) | HTTP 指令服务客户端。 |\n\n## 渲染预览\n\n示例图片由 `graphics` 模块的人工测试生成。README 只保留一个最小可读示例；复杂示例直接链接到对应人工测试源码，避免图片和简化代码不一致。\n\n### Hello World\n\n![Hello World Compose 示例](docs/assets/readme/compose-hello-world.png)\n\n对应人工测试：[`ComposeHelloWorldManualTest.kt`](graphics/src/manualTest/kotlin/ComposeHelloWorldManualTest.kt)\n\n\u003cdetails\u003e\n\u003csummary\u003e查看对应 Compose 代码\u003c/summary\u003e\n\n```kotlin\nval uiFont = ManualTestSupport.uiFont\n\nManualTestSupport.saveCompose(\"README-01-Hello-World\") {\n    box(\n        modifier = Modifier\n            .size(860f, 360f)\n            .background(Color.makeRGB(26, 34, 48))\n            .padding(42f)\n    ) {\n        column(\n            modifier = Modifier\n                .background(Color.makeRGB(255, 255, 255))\n                .padding(36f)\n        ) {\n            text(\n                \"Hello, World!\",\n                fontSize = 56f,\n                textColor = Color.makeRGB(38, 58, 92),\n                fontFamily = uiFont\n            )\n            text(\n                \"Tavolo Compose DSL\",\n                modifier = Modifier.padding(top = 18f),\n                fontSize = 28f,\n                textColor = Color.makeRGB(87, 103, 128),\n                fontFamily = uiFont\n            )\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n### 更多示例\n\n#### 标准 SVG 组件\n\n![SVG Compose 组件](docs/assets/readme/compose-svg.png)\n\n源码：[`ComposeSvgManualTest.kt`](graphics/src/manualTest/kotlin/ComposeSvgManualTest.kt)\n\n#### 瀑布流布局\n\n![Compose 瀑布流布局](docs/assets/readme/compose-waterfall.png)\n\n源码：[`ComposeWaterfallManualTest.kt`](graphics/src/manualTest/kotlin/ComposeWaterfallManualTest.kt)\n\n#### Modifier 视觉效果\n\n![Compose Modifier 效果](docs/assets/readme/compose-effects.png)\n\n源码：[`ComposeEffectManualTest.kt`](graphics/src/manualTest/kotlin/ComposeEffectManualTest.kt)\n\n#### 图表组件\n\n![Compose 图表组件](docs/assets/readme/compose-charts.png)\n\n源码：[`ComposeThemeManualTest.kt`](graphics/src/manualTest/kotlin/ComposeThemeManualTest.kt)\n\n## 引入依赖\n\n版本请在 [Release](https://github.com/4o4E/Tavolo/releases) 中查看。正式版本发布到 Maven Central，`-SNAPSHOT` 预览版本发布到 Sonatype snapshot 仓库。\n\n然后在项目中引入依赖：\n\n```kotlin\nval version = \"2.6.1\"\n\nrepositories {\n    mavenCentral()\n    // 仅在使用 -SNAPSHOT 版本时需要。\n    maven(\"https://central.sonatype.com/repository/maven-snapshots/\")\n}\n\ndependencies {\n    implementation(\"top.e404.tavolo:tavolo-core:${version}\")\n    implementation(\"top.e404.tavolo:tavolo-graphics:${version}\")\n    implementation(\"top.e404.tavolo:tavolo-gif-codec:${version}\")\n    implementation(\"top.e404.tavolo:tavolo-common:${version}\")\n}\n```\n\n## 字体引入\n\n文本渲染前需要先注册字体，`fontFamily` 接收的是 `FontManager` 中的字体名，不是字体文件路径。`graphics` 模块已经通过 `api` 暴露 `common` 模块，使用 `tavolo-graphics` 时可以直接引入 `FontManager`。\n\n### 方案一：使用系统字体\n\n适合桌面程序、Windows 服务，或已经明确安装目标字体的机器。系统字体名需要以运行机器实际可见的 family 名称为准。\n\n```kotlin\nimport top.e404.tavolo.util.FontManager\n\nval uiFont = FontManager.registerSystem(\"ui\", \"Microsoft YaHei\")\nFontManager.registerSystem(\"emoji\", \"Segoe UI Emoji\")\nFontManager.defaultFamily = uiFont\n```\n\n### 方案二：Docker 容器直接映射系统字体\n\nDocker 容器不会自动继承宿主机字体；如果要复用宿主机字体，需要把宿主机字体目录只读挂载到容器内，再按容器内路径注册字体。\n\n```powershell\ndocker run --rm `\n  -v C:\\Windows\\Fonts:/host-fonts:ro `\n  your-image\n```\n\n```kotlin\nimport top.e404.tavolo.util.FontManager\nimport java.io.File\n\nval uiFont = FontManager.registerFile(\"ui\", File(\"/host-fonts/msyh.ttc\"))\nFontManager.registerFile(\"emoji\", File(\"/host-fonts/seguiemj.ttf\"))\nFontManager.defaultFamily = uiFont\n```\n\nLinux 宿主机可以把 `/usr/share/fonts` 或业务字体目录挂载到容器内，容器里的注册代码保持同样模式。\n\n### 方案三：使用 data 目录\n\n适合随应用包分发一组固定字体，并使用 common 模块里的预置字体名。把字体文件放到 `TavoloFonts.fontDir` 指向的目录，默认目录为 `data/font`。\n\n例如使用 `TavoloFonts.LW` 时，需要准备 `data/font/LXGWWenKai-Regular.ttf`：\n\n```kotlin\nimport top.e404.tavolo.TavoloFonts\nimport top.e404.tavolo.util.FontManager\n\nTavoloFonts.fontDir = \"data/font\"\nval uiFont = TavoloFonts.register(TavoloFonts.LW)\nFontManager.defaultFamily = uiFont\n```\n\n### 方案四：手动注册字体\n\n适合业务自带品牌字体、租户级字体，或运行时从文件、字节流加载字体。手动注册后，业务代码仍然只通过返回的字体名引用。\n\n```kotlin\nimport top.e404.tavolo.util.FontManager\nimport java.io.File\n\nval titleFont = FontManager.registerFile(\"brand-title\", File(\"font/BrandTitle.ttf\"))\nval fontBytes = File(\"font/TenantBody.ttf\").readBytes()\nval bodyFont = FontManager.registerBytes(\"tenant-body\", fontBytes)\n\ntext(\n    \"Tavolo\",\n    fontSize = 36f,\n    fontFamily = titleFont\n)\ntext(\n    \"业务字体\",\n    fontSize = 20f,\n    fontFamily = bodyFont\n)\n```\n\n## 本地验证\n\n请先确保本机已安装并配置 Java 17 或更高版本。Tavolo 发布产物仍保持 Java 11 运行兼容。\n\n常规测试：\n\n```shell\n./gradlew test\n```\n\n只生成 README 的 Hello World 示例图：\n\n```shell\n./gradlew :graphics:manualTest --tests \"*ComposeHelloWorldManualTest\"\n```\n\n人工测试输出位于 `run/out`。\n\n## 文档入口\n\n- [快速开始](docs/快速开始.md)\n- [人工测试说明](docs/人工测试说明.md)\n- [Compose 绘图 DSL 与渲染抽象设计](docs/Compose绘图DSL与渲染抽象设计.md)\n- [3D 渲染实现设计](docs/3D渲染实现设计.md)\n- [指令资源与能力注册设计](docs/指令资源与能力注册设计.md)\n- [HTTP 指令服务设计](docs/HTTP指令服务设计.md)\n- [TODO](docs/TODO.md)\n- [下游升级到 2.5.0：emoji 字体回退](docs/下游升级到2.5.0-emoji回退.md)\n\n## 许可证\n\nTavolo 使用 [Apache License 2.0](LICENSE) 开源，出处声明见 [NOTICE](NOTICE)。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F4o4e%2Ftavolo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F4o4e%2Ftavolo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F4o4e%2Ftavolo/lists"}