Mayo commited on
fix: render on textless page
Browse files
Cargo.lock
CHANGED
|
@@ -4468,6 +4468,7 @@ dependencies = [
|
|
| 4468 |
"tempfile",
|
| 4469 |
"tokio",
|
| 4470 |
"uuid",
|
|
|
|
| 4471 |
]
|
| 4472 |
|
| 4473 |
[[package]]
|
|
|
|
| 4468 |
"tempfile",
|
| 4469 |
"tokio",
|
| 4470 |
"uuid",
|
| 4471 |
+
"zip 8.6.0",
|
| 4472 |
]
|
| 4473 |
|
| 4474 |
[[package]]
|
koharu-app/src/pipeline/engines/renderer.rs
CHANGED
|
@@ -69,10 +69,6 @@ impl Engine for Model {
|
|
| 69 |
})
|
| 70 |
.collect();
|
| 71 |
|
| 72 |
-
if inputs.is_empty() {
|
| 73 |
-
return Ok(Vec::new());
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
let page_opts = PageRenderOptions {
|
| 77 |
shader_effect: Default::default(),
|
| 78 |
shader_stroke: None,
|
|
|
|
| 69 |
})
|
| 70 |
.collect();
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
let page_opts = PageRenderOptions {
|
| 73 |
shader_effect: Default::default(),
|
| 74 |
shader_stroke: None,
|
tests/integration-tests/Cargo.toml
CHANGED
|
@@ -21,6 +21,7 @@ serde_json = { workspace = true }
|
|
| 21 |
tempfile = { workspace = true }
|
| 22 |
tokio = { workspace = true }
|
| 23 |
uuid = { workspace = true }
|
|
|
|
| 24 |
|
| 25 |
[dev-dependencies]
|
| 26 |
tokio = { workspace = true }
|
|
|
|
| 21 |
tempfile = { workspace = true }
|
| 22 |
tokio = { workspace = true }
|
| 23 |
uuid = { workspace = true }
|
| 24 |
+
zip = { workspace = true }
|
| 25 |
|
| 26 |
[dev-dependencies]
|
| 27 |
tokio = { workspace = true }
|
tests/integration-tests/tests/pipelines.rs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
//! Pipelines + operations + downloads. We don't run real engines (would require
|
| 2 |
//! loading multi-gigabyte models); we verify the routes surface.
|
| 3 |
|
|
|
|
|
|
|
| 4 |
use koharu_client::apis::default_api as api;
|
| 5 |
use koharu_client::models;
|
| 6 |
use koharu_core::{ImageRole, JobStatus, NodeKind, PageId};
|
|
@@ -123,9 +125,10 @@ async fn cancel_operation_via_download_id_is_204() -> anyhow::Result<()> {
|
|
| 123 |
}
|
| 124 |
|
| 125 |
#[tokio::test]
|
| 126 |
-
async fn
|
|
|
|
| 127 |
let app = TestApp::spawn().await?;
|
| 128 |
-
app.open_fresh_project("render-
|
| 129 |
|
| 130 |
let png = TestApp::tiny_png(24, 24, [255, 255, 255, 255]);
|
| 131 |
let page_ids = import_pages(&app, vec![("a.png", png.clone()), ("b.png", png)]).await?;
|
|
@@ -151,18 +154,54 @@ async fn renderer_pipeline_noops_on_pages_without_text_blocks() -> anyhow::Resul
|
|
| 151 |
assert_eq!(job.status, JobStatus::Completed);
|
| 152 |
assert_eq!(job.error, None);
|
| 153 |
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
Ok(())
|
| 168 |
}
|
|
|
|
| 1 |
//! Pipelines + operations + downloads. We don't run real engines (would require
|
| 2 |
//! loading multi-gigabyte models); we verify the routes surface.
|
| 3 |
|
| 4 |
+
use std::io::Cursor;
|
| 5 |
+
|
| 6 |
use koharu_client::apis::default_api as api;
|
| 7 |
use koharu_client::models;
|
| 8 |
use koharu_core::{ImageRole, JobStatus, NodeKind, PageId};
|
|
|
|
| 125 |
}
|
| 126 |
|
| 127 |
#[tokio::test]
|
| 128 |
+
async fn renderer_pipeline_creates_final_render_for_pages_without_text_blocks() -> anyhow::Result<()>
|
| 129 |
+
{
|
| 130 |
let app = TestApp::spawn().await?;
|
| 131 |
+
app.open_fresh_project("render-textless").await?;
|
| 132 |
|
| 133 |
let png = TestApp::tiny_png(24, 24, [255, 255, 255, 255]);
|
| 134 |
let page_ids = import_pages(&app, vec![("a.png", png.clone()), ("b.png", png)]).await?;
|
|
|
|
| 154 |
assert_eq!(job.status, JobStatus::Completed);
|
| 155 |
assert_eq!(job.error, None);
|
| 156 |
|
| 157 |
+
{
|
| 158 |
+
let session = app.app.current_session().expect("session");
|
| 159 |
+
let scene = session.scene.read();
|
| 160 |
+
for page_id in &page_ids {
|
| 161 |
+
let page_id = page_id.parse::<uuid::Uuid>().map(PageId)?;
|
| 162 |
+
let page = scene.page(page_id).expect("page exists");
|
| 163 |
+
let rendered = page
|
| 164 |
+
.nodes
|
| 165 |
+
.values()
|
| 166 |
+
.filter(|node| {
|
| 167 |
+
matches!(&node.kind, NodeKind::Image(img) if img.role == ImageRole::Rendered)
|
| 168 |
+
})
|
| 169 |
+
.count();
|
| 170 |
+
assert_eq!(
|
| 171 |
+
rendered, 1,
|
| 172 |
+
"renderer should create a final render for textless page"
|
| 173 |
+
);
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
let res = app
|
| 178 |
+
.client_config
|
| 179 |
+
.client
|
| 180 |
+
.post(format!("{}/projects/current/export", app.base_url))
|
| 181 |
+
.json(&models::ExportProjectRequest {
|
| 182 |
+
format: models::ExportFormat::Rendered,
|
| 183 |
+
pages: None,
|
| 184 |
+
})
|
| 185 |
+
.send()
|
| 186 |
+
.await?
|
| 187 |
+
.error_for_status()?;
|
| 188 |
+
let body = res.bytes().await?;
|
| 189 |
+
let mut archive = zip::ZipArchive::new(Cursor::new(body.to_vec()))?;
|
| 190 |
+
assert_eq!(
|
| 191 |
+
archive.len(),
|
| 192 |
+
2,
|
| 193 |
+
"rendered export should include every page"
|
| 194 |
+
);
|
| 195 |
+
|
| 196 |
+
let mut names = Vec::new();
|
| 197 |
+
for i in 0..archive.len() {
|
| 198 |
+
names.push(archive.by_index(i)?.name().to_string());
|
| 199 |
}
|
| 200 |
+
names.sort();
|
| 201 |
+
assert!(
|
| 202 |
+
names.iter().all(|name| name.ends_with(".png")),
|
| 203 |
+
"rendered export entries should be PNGs: {names:?}",
|
| 204 |
+
);
|
| 205 |
|
| 206 |
Ok(())
|
| 207 |
}
|