From 2925b4147353da2c305e2bb1cb5c0be56e15c113 Mon Sep 17 00:00:00 2001 From: nyabla <16510465+nyabla@users.noreply.github.com> Date: Wed, 29 Mar 2023 22:21:34 +0100 Subject: [PATCH] feat: generate preview PNGs --- Cargo.lock | 9 +++++- Cargo.toml | 1 + src/command.rs | 77 ++++++++++++++++++++++++++++++++++++++++++-------- src/main.rs | 3 ++ 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00dc9c8b..2e056f45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "biblatex" version = "0.7.0" @@ -1608,6 +1614,7 @@ name = "typst-lsp" version = "0.3.0" dependencies = [ "append-only-vec", + "base64 0.21.0", "codespan-reporting", "comemo", "dirs", @@ -1750,7 +1757,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a261d60a7215fa339482047cc3dafd4e22e2bf34396aaebef2b707355bbb39c0" dependencies = [ - "base64", + "base64 0.13.1", "data-url", "flate2", "float-cmp", diff --git a/Cargo.toml b/Cargo.toml index 249db89a..56e34e7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ license = "Apache-2.0 OR MIT" [dependencies] append-only-vec = "0.1.2" +base64 = "0.21.0" codespan-reporting = "0.11" comemo = "0.2" dirs = "4" diff --git a/src/command.rs b/src/command.rs index b8d25354..d444f4f1 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,22 +1,27 @@ -use std::fs; +use std::{fs, path::Path}; +use base64::Engine as _; +use base64::engine::general_purpose::STANDARD as BASE64; use serde_json::Value; use tower_lsp::{ jsonrpc::{Error, Result}, - lsp_types::Url, + lsp_types::{MessageType, Url}, }; +use typst::geom::Color; use crate::Backend; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LspCommand { ExportPdf, + GeneratePreview, } impl From for String { fn from(command: LspCommand) -> Self { match command { LspCommand::ExportPdf => "typst-lsp.doPdfExport".to_string(), + LspCommand::GeneratePreview => "typst-lsp.generatePreview".to_string(), } } } @@ -25,6 +30,7 @@ impl LspCommand { pub fn parse(command: &str) -> Option { match command { "typst-lsp.doPdfExport" => Some(Self::ExportPdf), + "typst-lsp.generatePreview" => Some(Self::GeneratePreview), _ => None, } } @@ -34,20 +40,23 @@ impl LspCommand { } } +fn validate_uri_argument(arguments: Vec) -> Result { + if arguments.is_empty() { + return Err(Error::invalid_params("Missing file URI argument")); + } + let Some(file_uri) = arguments.first().and_then(|v| v.as_str()) else { + return Err(Error::invalid_params( + "Missing file URI as first argument", + )); + }; + Url::parse(file_uri).map_err(|_| Error::invalid_params("Parameter is not a valid URI")) +} + /// Here are implemented the handlers for each command. impl Backend { /// Export the current document as a PDF file. The client is reponsible for passing the correct file URI. pub async fn command_export_pdf(&self, arguments: Vec) -> Result<()> { - if arguments.is_empty() { - return Err(Error::invalid_params("Missing file URI argument")); - } - let Some(file_uri) = arguments.first().and_then(|v| v.as_str()) else { - return Err(Error::invalid_params( - "Missing file URI as first argument", - )); - }; - let file_uri = Url::parse(file_uri) - .map_err(|_| Error::invalid_params("Parameter is not a valid URI"))?; + let file_uri = validate_uri_argument(arguments)?; let text = fs::read_to_string( file_uri .to_file_path() @@ -57,4 +66,48 @@ impl Backend { self.compile_diags_export(file_uri, text, true).await; Ok(()) } + + pub async fn command_generate_preview(&self, arguments: Vec) -> Result> { + let file_uri = validate_uri_argument(arguments)?; + let text = fs::read_to_string( + file_uri + .to_file_path() + .map_err(|_| Error::invalid_params("Could not convert file URI to path"))?, + ) + .map_err(|_| Error::internal_error())?; + + let mut world_lock = self.world.write().await; + let world = world_lock.as_mut().unwrap(); + + world.reset(); + + match world.resolve_with(Path::new(&file_uri.to_file_path().unwrap()), &text) { + Ok(id) => { + world.main = id; + } + Err(e) => { + self.client + .log_message(MessageType::ERROR, format!("{:?}", e)) + .await; + } + } + + match typst::compile(world) { + Ok(document) => { + let data_urls: Vec = document + .pages + .iter() + .map(|frame| typst::export::render(frame, 1.5, Color::WHITE)) + .map(|pixmap| pixmap.encode_png().unwrap()) + .map(|buf| BASE64.encode(buf)) + .map(|encoded| Value::String("data:image/png;base64,".to_string() + &encoded)) + .collect(); + + return Ok(Some(serde_json::Value::Array(data_urls))); + } + Err(_errors) => {} + }; + + Ok(None) + } } diff --git a/src/main.rs b/src/main.rs index f3772a72..d50ec94a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -127,6 +127,9 @@ impl LanguageServer for Backend { Some(LspCommand::ExportPdf) => { self.command_export_pdf(arguments).await?; } + Some(LspCommand::GeneratePreview) => { + return Ok(self.command_generate_preview(arguments).await?) + } None => { return Err(Error::method_not_found()); }