Skip to content

Commit 857dff6

Browse files
committed
feat(core): extract maple-render-core and automate crate publishing
Session-Id: 033876f1-e95b-4b2c-b333-aa69786c03a6
1 parent e28d23e commit 857dff6

22 files changed

Lines changed: 483 additions & 213 deletions

File tree

.github/workflows/ci.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- trunk
7+
- main
8+
tags:
9+
- "core-v*"
10+
workflow_dispatch:
11+
inputs:
12+
publish:
13+
description: "Publish maple-render-core to crates.io"
14+
required: false
15+
default: false
16+
type: boolean
17+
18+
permissions:
19+
contents: read
20+
21+
env:
22+
CARGO_TERM_COLOR: always
23+
CARGO_TARGET_DIR: target
24+
25+
jobs:
26+
rust:
27+
runs-on: ubuntu-latest
28+
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- name: Set up Rust
33+
uses: dtolnay/rust-toolchain@stable
34+
with:
35+
components: rustfmt, clippy
36+
targets: wasm32-unknown-unknown
37+
38+
- name: Cache Rust artifacts
39+
uses: Swatinem/rust-cache@v2
40+
41+
- name: Format check
42+
run: cargo fmt --all -- --check
43+
44+
- name: Clippy (root crate)
45+
run: cargo clippy --locked --all-targets -- -D warnings
46+
47+
- name: Check root crate
48+
run: cargo check --locked
49+
50+
- name: Test root crate
51+
run: cargo test --locked --lib
52+
53+
- name: Check wasm exports
54+
run: cargo check --locked --lib --target wasm32-unknown-unknown
55+
56+
- name: Check maple-render-core standalone crate
57+
run: cargo check --manifest-path crates/maple-render-core/Cargo.toml
58+
59+
- name: Test maple-render-core standalone crate
60+
run: cargo test --manifest-path crates/maple-render-core/Cargo.toml
61+
62+
- name: Verify maple-render-core publish dry-run
63+
run: cargo publish --dry-run --allow-dirty --manifest-path crates/maple-render-core/Cargo.toml
64+
65+
publish-maple-render-core:
66+
name: Publish maple-render-core
67+
runs-on: ubuntu-latest
68+
needs: rust
69+
if: startsWith(github.ref, 'refs/tags/core-v') || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true')
70+
71+
steps:
72+
- uses: actions/checkout@v4
73+
74+
- name: Set up Rust
75+
uses: dtolnay/rust-toolchain@stable
76+
77+
- name: Cache Rust artifacts
78+
uses: Swatinem/rust-cache@v2
79+
80+
- name: Validate tag matches crate version
81+
if: startsWith(github.ref, 'refs/tags/core-v')
82+
run: |
83+
VERSION=$(grep '^version = ' crates/maple-render-core/Cargo.toml | head -n1 | cut -d '"' -f2)
84+
TAG_VERSION="${GITHUB_REF_NAME#core-v}"
85+
if [ "$VERSION" != "$TAG_VERSION" ]; then
86+
echo "Tag version ($TAG_VERSION) does not match crate version ($VERSION)"
87+
exit 1
88+
fi
89+
90+
- name: Publish maple-render-core to crates.io
91+
env:
92+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
93+
run: cargo publish --manifest-path crates/maple-render-core/Cargo.toml --token "$CARGO_REGISTRY_TOKEN"

Cargo.lock

Lines changed: 15 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@ crate-type = ["cdylib", "rlib"]
1010
[dependencies]
1111
clap = { version = "4", features = ["derive"] }
1212
image = "0.25"
13-
gif = "0.13"
14-
zip = { version = "2.1", default-features = false, features = ["deflate"] }
1513
eyre = "0.6"
1614
color-eyre = "0.6"
1715
serde = { version = "1", features = ["derive"] }
1816
serde_json = "1"
19-
delaunator = "1.0"
20-
ab_glyph = "0.2"
21-
imageproc = "0.25"
17+
maple-render-core = { path = "crates/maple-render-core", version = "0.1.0" }
2218

2319
[target.'cfg(target_arch = "wasm32")'.dependencies]
2420
wasm-bindgen = "0.2"
@@ -37,5 +33,3 @@ panic = "abort"
3733
version = "0.1"
3834
default-features = false
3935

40-
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rayon]
41-
version = "1.10"

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
maple allows you to composite images/text with templates into animated GIFs/Videos. `templates/` has some fun ones to try!
66

7+
Core rendering logic now lives in the publishable [`maple-render-core`](crates/maple-render-core) crate. The core crate does not bundle templates or fonts.
8+
79
## Install
810

911
```
@@ -12,6 +14,14 @@ cargo install --path .
1214

1315
Requires Rust 1.80+. Video output requires ffmpeg.
1416

17+
## Core crate (publishable)
18+
19+
The standalone core crate lives at `crates/maple-render-core`.
20+
21+
```bash
22+
cargo publish --manifest-path crates/maple-render-core/Cargo.toml
23+
```
24+
1525
## Usage
1626

1727
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "maple-render-core"
3+
version = "0.1.0"
4+
edition = "2021"
5+
rust-version = "1.80"
6+
description = "Core rendering and animation logic for Maple templates"
7+
license = "MIT"
8+
repository = "https://github.com/davebcn87/maple"
9+
readme = "README.md"
10+
keywords = ["gif", "image", "rendering", "template", "animation"]
11+
categories = ["multimedia::images", "multimedia::video"]
12+
13+
[dependencies]
14+
image = "0.25"
15+
gif = "0.13"
16+
zip = { version = "2.1", default-features = false, features = ["deflate"] }
17+
serde = { version = "1", features = ["derive"] }
18+
serde_json = "1"
19+
delaunator = "1.0"
20+
ab_glyph = "0.2"
21+
imageproc = "0.25"
22+
23+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rayon]
24+
version = "1.10"

crates/maple-render-core/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# maple-render-core
2+
3+
Core rendering engine extracted from Maple.
4+
5+
This crate contains:
6+
7+
- Template repository loading (`Repository`)
8+
- Frame compositing and mapping (`Render`, `Renders`)
9+
- GIF/video helpers (`GifAnim`, `VidAnim`)
10+
- Quantization pipeline
11+
- Input handling for images and text
12+
13+
It intentionally does **not** bundle templates or font assets.
14+
For text rendering, callers must pass font bytes into `Input::from_text`.
15+
16+
## Publish checklist
17+
18+
From the repository root:
19+
20+
```bash
21+
# 1) Validate locally
22+
cargo check --manifest-path crates/maple-render-core/Cargo.toml
23+
cargo test --manifest-path crates/maple-render-core/Cargo.toml
24+
25+
# 2) Inspect package contents
26+
cargo package --manifest-path crates/maple-render-core/Cargo.toml --list
27+
28+
# 3) Full registry validation without upload
29+
cargo publish --manifest-path crates/maple-render-core/Cargo.toml --dry-run
30+
31+
# 4) Publish for real
32+
cargo publish --manifest-path crates/maple-render-core/Cargo.toml
33+
```
34+
35+
## Minimal usage
36+
37+
```rust
38+
use maple_render_core::{GifAnim, Input, Inputs, Renders, Repository, TextOptions};
39+
use maple_render_core::render::RenderQuality;
40+
41+
fn main() -> Result<(), Box<dyn std::error::Error>> {
42+
// Load a template zip generated using Maple's template format.
43+
let repo = Repository::load("templates/toaster.zip")?;
44+
45+
// Add image input.
46+
let mut inputs = Inputs::new();
47+
inputs.push(Input::load("examples/monkey.jpg")?);
48+
49+
// Optional: add text input using your own font bytes.
50+
let font_data = std::fs::read("fonts/DejaVuSans-Bold.ttf")?;
51+
let text = Input::from_text("HELLO", 2, &TextOptions::default(), &font_data)?;
52+
inputs.push(text);
53+
54+
// Render and encode GIF.
55+
let mut renders = Renders::new(repo, inputs, RenderQuality::Sampled);
56+
let mut gif = GifAnim::new(renders);
57+
gif.apply()?;
58+
gif.save("out.gif")?;
59+
60+
Ok(())
61+
}
62+
```
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use std::{fs::File, io::{BufWriter, Cursor}, path::Path};
1+
use std::{
2+
fs::File,
3+
io::{BufWriter, Cursor},
4+
path::Path,
5+
};
26

37
use gif::{Encoder, Frame, Repeat};
48
use image::RgbaImage;
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ use imageproc::drawing::draw_text_mut;
66

77
use crate::error::{Error, Result};
88

9-
const DEFAULT_FONT: &[u8] = include_bytes!("../fonts/DejaVuSans-Bold.ttf");
10-
119
#[derive(Clone)]
1210
pub struct TextOptions {
1311
pub font_size: f32,
@@ -92,8 +90,13 @@ impl Input {
9290
input
9391
}
9492

95-
pub fn from_text(text: &str, layer: u8, options: &TextOptions) -> Result<Self> {
96-
let font = FontRef::try_from_slice(DEFAULT_FONT)
93+
pub fn from_text(
94+
text: &str,
95+
layer: u8,
96+
options: &TextOptions,
97+
font_data: &[u8],
98+
) -> Result<Self> {
99+
let font = FontRef::try_from_slice(font_data)
97100
.map_err(|e| Error::TextRender(format!("Failed to load font: {}", e)))?;
98101

99102
let scale = PxScale::from(options.font_size);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pub mod error;
2+
pub mod gif_anim;
3+
pub mod input;
4+
pub mod mapping;
5+
pub mod pixer;
6+
pub mod quantize;
7+
pub mod render;
8+
pub mod renders;
9+
pub mod repository;
10+
pub mod template;
11+
pub mod vid_anim;
12+
13+
pub use error::{Error, Result};
14+
pub use gif_anim::GifAnim;
15+
pub use input::{Input, Inputs, TextOptions};
16+
pub use mapping::Mapping;
17+
pub use render::Render;
18+
pub use renders::Renders;
19+
pub use repository::Repository;
20+
pub use template::Template;

0 commit comments

Comments
 (0)