diff --git a/Cargo.lock b/Cargo.lock index 8325bbe81..392f5d493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4392,6 +4392,24 @@ dependencies = [ "typst-ts-test-common", ] +[[package]] +name = "typst-ts-incremental-test" +version = "0.4.2-rc1" +dependencies = [ + "anyhow", + "comemo", + "hex", + "sha2", + "tokio", + "typst", + "typst-syntax", + "typst-ts-compiler", + "typst-ts-core", + "typst-ts-dev-server", + "typst-ts-svg-exporter", + "typst-ts-test-common", +] + [[package]] name = "typst-ts-integration-test" version = "0.4.2-rc1" diff --git a/Cargo.toml b/Cargo.toml index 5f6b14324..df154dfe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ "tests/common", "tests/heap-profile", + "tests/incremental", "tests/integration", "tests/std", ] diff --git a/core/src/vector/flat_vm.rs b/core/src/vector/flat_vm.rs index 7dd1478cb..ffdaf46bf 100644 --- a/core/src/vector/flat_vm.rs +++ b/core/src/vector/flat_vm.rs @@ -338,15 +338,15 @@ where ) { let child_ref = &transformed.1; let state = state.pre_apply(&transformed.0); - if matches!(prev_item_, Some(ir::FlatSvgItem::Item(ir::TransformedRef(_item, prev_ref))) - if prev_ref == child_ref) - { - // assert!(item != &transformed.0); - ts.render_diff_item_ref_at(state, self, Point::default(), child_ref, child_ref); - return; + match prev_item_ { + // if both items are transformed, we can reuse the internal item with transforming it a + // bit. + Some(ir::FlatSvgItem::Item(ir::TransformedRef(_item, prev_ref))) => { + ts.render_diff_item_ref_at(state, self, Point::default(), child_ref, prev_ref); + } + _ => ts.render_item_ref(state, self, child_ref), } // failed to reuse - ts.render_item_ref(state, self, child_ref); } /// Render a diff text into the underlying context. diff --git a/tests/incremental/Cargo.toml b/tests/incremental/Cargo.toml new file mode 100644 index 000000000..76813eb77 --- /dev/null +++ b/tests/incremental/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "typst-ts-incremental-test" +authors.workspace = true +version.workspace = true +license.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +typst.workspace = true +typst-syntax.workspace = true +comemo.workspace = true + +sha2.workspace = true +anyhow.workspace = true +tokio.workspace = true + +typst-ts-dev-server.workspace = true +typst-ts-test-common.workspace = true +typst-ts-core.workspace = true +hex.workspace = true +typst-ts-compiler.workspace = true +typst-ts-svg-exporter.workspace = true + +[features] +generate = [] diff --git a/tests/incremental/src/main.rs b/tests/incremental/src/main.rs new file mode 100644 index 000000000..98df6607b --- /dev/null +++ b/tests/incremental/src/main.rs @@ -0,0 +1,105 @@ +use std::path::Path; + +use typst::model::Document; +use typst_ts_compiler::{ + service::{CompileDriver, CompileExporter, Compiler}, + ShadowApi, TypstSystemWorld, +}; +use typst_ts_core::{ + config::CompileOpts, + exporter_builtins::GroupExporter, + vector::{ + incr::{IncrDocClient, IncrDocServer}, + ir::{Abs, Point, Rect}, + stream::BytesModuleStream, + }, +}; +use typst_ts_svg_exporter::IncrSvgDocClient; + +fn get_driver( + workspace_dir: &Path, + entry_file_path: &Path, + exporter: GroupExporter, +) -> CompileExporter { + let project_base = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let font_path = project_base.join("assets/fonts"); + let world = TypstSystemWorld::new(CompileOpts { + root_dir: workspace_dir.to_owned(), + no_system_fonts: true, + font_paths: vec![font_path], + ..CompileOpts::default() + }) + .unwrap(); + + let driver = CompileDriver { + world, + entry_file: entry_file_path.to_owned(), + }; + + CompileExporter::new(driver).with_exporter(exporter) +} + +pub fn test_compiler( + workspace_dir: &Path, + entry_file_path: &Path, + exporter: GroupExporter, +) { + let mut driver = get_driver(workspace_dir, entry_file_path, exporter); + let mut content = { std::fs::read_to_string(entry_file_path).expect("Could not read file") }; + + let mut incr_server = IncrDocServer::default(); + let mut incr_client = IncrDocClient::default(); + let mut incr_svg_client = IncrSvgDocClient::default(); + + let window = Rect { + lo: Point::new(Abs::from(0.), Abs::from(0.)), + hi: Point::new(Abs::from(1e33), Abs::from(1e33)), + }; + + let mut diff = vec![]; + + // checkout the entry file + let main_id = driver.main_id(); + + let doc = driver + .with_shadow_file_by_id(main_id, content.as_bytes().into(), |driver| { + driver.compile(&mut Default::default()) + }) + .unwrap(); + let server_delta = incr_server.pack_delta(doc); + let server_delta = BytesModuleStream::from_slice(&server_delta).checkout_owned(); + incr_client.merge_delta(server_delta); + + for i in 0..200 { + println!("Iteration {}", i); + + content = content.replace("@netwok2020", "@netwok2020 x"); + + let doc = driver + .with_shadow_file_by_id(main_id, content.as_bytes().into(), |driver| { + driver.compile(&mut Default::default()) + }) + .unwrap(); + + let server_delta = incr_server.pack_delta(doc); + let sd = server_delta.len(); + let server_delta = BytesModuleStream::from_slice(&server_delta).checkout_owned(); + incr_client.merge_delta(server_delta); + incr_client.set_layout(incr_client.doc.layouts[0].unwrap_single()); + let cd = incr_svg_client.render_in_window(&mut incr_client, window); + // std::fs::write(format!("{}.svg", i), cd.clone()).unwrap(); + diff.push((sd, cd.len())); + + comemo::evict(10); + } + + println!("diff: {:?}", diff); +} + +pub fn main() { + let workspace_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let entry_file_path = workspace_dir.join("fuzzers/corpora/typst-templates/ieee/main.typ"); + + let noop_exporter = GroupExporter::new(vec![]); + test_compiler(&workspace_dir, &entry_file_path, noop_exporter); +}