Added Word Count

This commit is contained in:
2026-04-08 14:26:50 +00:00
parent 43df9fb760
commit c588867625
9 changed files with 174 additions and 31 deletions
+85 -16
View File
@@ -1,10 +1,54 @@
use crate::world::MemoryWorld;
use serde::Serialize;
use std::collections::HashMap;
use typst::diag::{SourceDiagnostic, Warned};
use typst::layout::{Frame, FrameItem};
use typst_layout::PagedDocument;
use typst_pdf::{pdf, PdfOptions};
use typst_render::render;
#[derive(Serialize, Clone)]
pub struct DocumentStats {
pub pages: usize,
pub words: usize,
pub characters: usize,
pub characters_excluding_spaces: usize,
}
fn extract_stats(doc: &PagedDocument) -> DocumentStats {
let mut text = String::new();
let pages = doc.pages().len();
for page in doc.pages() {
extract_frame_text(&page.frame, &mut text);
}
let words = text.split_whitespace().count();
let characters = text.chars().count();
let characters_excluding_spaces = text.chars().filter(|c| !c.is_whitespace()).count();
DocumentStats {
pages,
words,
characters,
characters_excluding_spaces,
}
}
fn extract_frame_text(frame: &Frame, text: &mut String) {
for (_, item) in frame.items() {
match item {
FrameItem::Text(text_item) => {
text.push_str(&text_item.text);
text.push(' ');
}
FrameItem::Group(group) => {
extract_frame_text(&group.frame, text);
}
_ => {}
}
}
}
pub struct TypstCompiler;
impl TypstCompiler {
@@ -16,30 +60,41 @@ impl TypstCompiler {
&self,
text: String,
files: HashMap<String, Vec<u8>>,
) -> Result<(Vec<String>, String), Vec<(SourceDiagnostic, Option<std::ops::Range<usize>>)>> {
) -> Result<
(Vec<String>, String, DocumentStats),
Vec<(SourceDiagnostic, Option<std::ops::Range<usize>>)>,
> {
let world = MemoryWorld::new(text, files);
match typst::compile::<PagedDocument>(&world) {
Warned {
output: Ok(doc),
warnings: _,
} => {
let stats = extract_stats(&doc);
let svgs = doc.pages().iter().map(typst_svg::svg).collect();
let thumbnail = if let Some(page) = doc.pages().first() {
typst_svg::svg(page)
} else {
String::new()
};
Ok((svgs, thumbnail))
Ok((svgs, thumbnail, stats))
}
Warned {
output: Err(errors),
warnings: _,
} => {
use typst::World;
let diag = errors.into_iter().map(|d| {
let range = d.span.id().and_then(|id| world.source(id).ok()).and_then(|s| s.range(d.span));
(d, range)
}).collect();
let diag = errors
.into_iter()
.map(|d| {
let range = d
.span
.id()
.and_then(|id| world.source(id).ok())
.and_then(|s| s.range(d.span));
(d, range)
})
.collect();
Err(diag)
}
}
@@ -67,11 +122,18 @@ impl TypstCompiler {
warnings: _,
} => {
use typst::World;
Err(errors.into_iter().map(|d| {
let range = d.span.id().and_then(|id| world.source(id).ok()).and_then(|s| s.range(d.span));
(d, range)
}).collect())
},
Err(errors
.into_iter()
.map(|d| {
let range = d
.span
.id()
.and_then(|id| world.source(id).ok())
.and_then(|s| s.range(d.span));
(d, range)
})
.collect())
}
}
}
@@ -99,11 +161,18 @@ impl TypstCompiler {
warnings: _,
} => {
use typst::World;
Err(errors.into_iter().map(|d| {
let range = d.span.id().and_then(|id| world.source(id).ok()).and_then(|s| s.range(d.span));
(d, range)
}).collect())
},
Err(errors
.into_iter()
.map(|d| {
let range = d
.span
.id()
.and_then(|id| world.source(id).ok())
.and_then(|s| s.range(d.span));
(d, range)
})
.collect())
}
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ use sqlx::{Pool, Postgres};
pub async fn init_db() -> Pool<Postgres> {
let db_url = std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://postgres:password@localhost:5432/typstdrive".to_string());
.unwrap_or_else(|_| "postgres://postgres:password@192.168.1.214:5432/typstdrive".to_string());
let pool = PgPoolOptions::new()
.max_connections(5)
+7 -2
View File
@@ -54,10 +54,13 @@ pub struct CompileRequest {
pub document_id: Option<String>,
}
use crate::compiler::DocumentStats;
#[derive(Serialize)]
pub struct CompileResponse {
pub svgs: Option<Vec<String>>,
pub errors: Option<Vec<Diagnostic>>,
pub stats: Option<DocumentStats>,
}
#[derive(Serialize)]
@@ -215,7 +218,7 @@ pub async fn compile_handler(
let compiler = state.compiler.lock().await;
match compiler.compile_svg(payload.text, files_map) {
Ok((svgs, thumbnail)) => {
Ok((svgs, thumbnail, stats)) => {
if let Some(doc_id) = &payload.document_id {
if can_save_thumbnail {
let _ = sqlx::query("UPDATE documents SET thumbnail_svg = $1 WHERE id = $2")
@@ -229,6 +232,7 @@ pub async fn compile_handler(
Json(CompileResponse {
svgs: Some(svgs),
errors: None,
stats: Some(stats),
})
}
Err(diags) => {
@@ -244,6 +248,7 @@ pub async fn compile_handler(
Json(CompileResponse {
svgs: None,
errors: Some(errors),
stats: None,
})
}
}
@@ -318,7 +323,7 @@ pub async fn export_handler(
Err(_) => (StatusCode::BAD_REQUEST, "Compilation failed").into_response(),
},
"svg" => match compiler.compile_svg(payload.text, files_map.clone()) {
Ok((svgs, _)) => {
Ok((svgs, _, _)) => {
let mut combined = String::new();