Files
ElyOffice/crates/ely-lok/src/client/doc.rs
T

276 lines
10 KiB
Rust

//! A document's requests: size, pages, tiles, saving.
use std::io;
use super::{Doc, Rect, Tile, Worker};
use crate::ipc::{Pack, Unpack, tag};
/// How a PDF goes out: pages, comments, tracked changes.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Pdf {
/// First and last page, from 1; None is every page.
pub pages: Option<(u32, u32)>,
/// Comments print in the margin.
pub comments: bool,
/// Tracked changes show as marked.
pub changes: bool,
/// Slides per handout page; 0 prints the slides.
pub handouts: u8,
}
/// One sheet: its name and whether it shows.
#[derive(Clone, Debug, PartialEq)]
pub struct Sheet {
pub name: String,
pub visible: bool,
}
/// The sheets of a spreadsheet and the shown one.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Sheets {
pub current: usize,
pub list: Vec<Sheet>,
}
impl Doc {
/// The sheets, in order, and the one shown.
pub fn sheets(&self, worker: &mut Worker) -> io::Result<Sheets> {
let payload = worker.request(tag::SHEETS, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
let mut unpack = Unpack::new(&payload);
let current = usize::try_from(unpack.u64()?).map_err(io::Error::other)?;
let count = unpack.u64()?;
let list = (0..count)
.map(|_| {
let name = unpack.str()?;
let info: serde_json::Value = serde_json::from_str(&unpack.str()?)
.map_err(|error| io::Error::other(format!("bad part info: {error}")))?;
let visible = match info["visible"].as_u64() {
Some(1) => true,
Some(0) => false,
_ => return Err(io::Error::other(format!("no visibility: {info}"))),
};
Ok(Sheet { name, visible })
})
.collect::<io::Result<_>>()?;
Ok(Sheets { current, list })
}
/// Tells the kernel the shown area and the tile scale.
pub fn visible_area(&self, worker: &mut Worker, (x, y, w, h): Rect, (tile, tile_twips): (u32, i32)) -> io::Result<()> {
let request = Pack::new()
.u64(worker.doc_id(self)?)
.i64(x.into())
.i64(y.into())
.i64(w.into())
.i64(h.into())
.i64(tile.into())
.i64(tile_twips.into())
.bytes();
worker.request(tag::VISIBLE_AREA, &request)?;
Ok(())
}
/// The selection as plain text; Calc joins cells by tabs.
pub fn selection_text(&self, worker: &mut Worker) -> io::Result<String> {
let payload = worker.request(tag::SELECTION_TEXT, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
Unpack::new(&payload).str()
}
/// Completes the function name being typed (TABLE-03).
pub fn complete_function(&self, worker: &mut Worker, name: &str) -> io::Result<()> {
log::info!("lok: completes a function");
let request = Pack::new().u64(worker.doc_id(self)?).str(name).bytes();
worker.request(tag::COMPLETE_FUNCTION, &request)?;
Ok(())
}
/// Shows sheet `part`, from 0.
pub fn show_sheet(&self, worker: &mut Worker, part: usize) -> io::Result<()> {
let request = Pack::new().u64(worker.doc_id(self)?).u64(part as u64).bytes();
worker.request(tag::SHOW_SHEET, &request)?;
Ok(())
}
/// Page count or area in LOK twips.
pub fn size(&self, worker: &mut Worker) -> io::Result<(i64, i64)> {
let payload = worker.request(tag::DOC_SIZE, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
let mut unpack = Unpack::new(&payload);
Ok((unpack.i64()?, unpack.i64()?))
}
/// The size and the client zoom it was measured at.
pub fn size_at(&self, worker: &mut Worker) -> io::Result<((i64, i64), (i32, i32))> {
let payload = worker.request(tag::DOC_SIZE, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
let mut unpack = Unpack::new(&payload);
let size = (unpack.i64()?, unpack.i64()?);
let number = |value: i64| i32::try_from(value).map_err(io::Error::other);
Ok((size, (number(unpack.i64()?)?, number(unpack.i64()?)?)))
}
/// Page rectangles in twips; empty without pages.
pub fn pages(&self, worker: &mut Worker) -> io::Result<Vec<Rect>> {
let payload = worker.request(tag::PAGES, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
let text = Unpack::new(&payload).str()?;
text.split(';')
.filter(|part| !part.trim().is_empty())
.map(|part| {
let numbers: Vec<i32> = part
.split(',')
.map(|n| {
n.trim()
.parse()
.map_err(|_| io::Error::other(format!("a bad page rect: {part}")))
})
.collect::<io::Result<_>>()?;
match numbers.as_slice() {
[x, y, w, h] => Ok((*x, *y, *w, *h)),
_ => Err(io::Error::other(format!("a bad page rect: {part}"))),
}
})
.collect()
}
/// Paints one tile: twips rect into a pixel size.
pub fn paint_tile(
&self,
worker: &mut Worker,
twips: (i32, i32, i32, i32),
pixels: (u32, u32),
) -> io::Result<Tile> {
let (x, y, w, h) = twips;
let (pw, ph) = pixels;
let payload = worker.request(
tag::PAINT_TILE,
&Pack::new()
.u64(worker.doc_id(self)?)
.i64(x as i64)
.i64(y as i64)
.i64(w as i64)
.i64(h as i64)
.i64(pw as i64)
.i64(ph as i64)
.bytes(),
)?;
let mut unpack = Unpack::new(&payload);
let generation = unpack.u64()?;
Ok(Tile {
generation,
pixels: unpack.rest().to_vec(),
})
}
/// Slide or master `part` whole, as `pixels` of BGRA.
pub fn paint_part(
&self,
worker: &mut Worker,
(part, master): (usize, bool),
pixels: (u32, u32),
) -> io::Result<Vec<u8>> {
let request = Pack::new()
.u64(worker.doc_id(self)?)
.u64(part as u64)
.u64(u64::from(master))
.u64(u64::from(pixels.0))
.u64(u64::from(pixels.1))
.bytes();
worker.request(tag::PAINT_PART, &request)
}
/// Slide `part`'s slideshow layers at about `pixels`.
pub fn slide_layers(
&self,
worker: &mut Worker,
part: usize,
pixels: (u32, u32),
) -> io::Result<crate::show::Layers> {
let request = Pack::new()
.u64(worker.doc_id(self)?)
.u64(part as u64)
.u64(u64::from(pixels.0))
.u64(u64::from(pixels.1))
.bytes();
let head = worker.request(tag::SLIDE_LAYERS, &request)?;
let mut unpack = Unpack::new(&head);
let (width, height, count) = (unpack.u64()?, unpack.u64()?, unpack.u64()?);
// Layers arrive in pieces that keep each frame small.
let mut whole = Pack::new().u64(width).u64(height).u64(count);
for index in 0..count {
let (json, painted) = (unpack.str()?, unpack.u64()?);
whole = whole.str(&json).u64(painted);
if painted != 0 {
whole = whole.blob(&self.slide_layer(worker, index)?);
}
}
crate::show::layers(&whole.bytes())
}
/// Layer `index` of the last layers read, piece by piece.
fn slide_layer(&self, worker: &mut Worker, index: u64) -> io::Result<Vec<u8>> {
let id = worker.doc_id(self)?;
let mut pixels = Vec::new();
loop {
let ask = Pack::new().u64(id).u64(index).u64(pixels.len() as u64).bytes();
let reply = worker.request(tag::SLIDE_LAYER, &ask)?;
let mut unpack = Unpack::new(&reply);
let (total, piece) = (unpack.u64()?, unpack.blob()?);
pixels.extend_from_slice(&piece);
if pixels.len() as u64 >= total {
return Ok(pixels);
}
if piece.is_empty() {
return Err(io::Error::other(format!("layer {index} stopped at byte {}", pixels.len())));
}
}
}
/// Saves to `url` as `format`; failure surfaces (LOK-09).
pub fn save_as(&self, worker: &mut Worker, url: &str, format: &str) -> io::Result<()> {
self.store(worker, url, format, "")
}
/// Writes the shown sheet as text split by `separator`.
pub fn export_text(&self, worker: &mut Worker, url: &str, separator: char) -> io::Result<()> {
let request = Pack::new()
.u64(worker.doc_id(self)?)
.str(url)
.u64(u64::from(separator))
.bytes();
worker.request(tag::EXPORT_TEXT, &request)?;
Ok(())
}
/// Writes a PDF of `pages` (from 1, inclusive) to `url`.
pub fn export_pdf(&self, worker: &mut Worker, url: &str, pdf: &Pdf) -> io::Result<()> {
let (from, to) = pdf.pages.unwrap_or((0, 0));
let request = Pack::new()
.u64(worker.doc_id(self)?)
.str(url)
.u64(u64::from(from))
.u64(u64::from(to))
.u64(u64::from(pdf.comments))
.u64(u64::from(pdf.changes))
.u64(u64::from(pdf.handouts))
.bytes();
worker.request(tag::EXPORT_PDF, &request)?;
Ok(())
}
/// Saves and moves the document to `url`; modified clears.
pub fn save_owned(&self, worker: &mut Worker, url: &str, format: &str) -> io::Result<()> {
self.store(worker, url, format, "TakeOwnership")
}
fn store(&self, worker: &mut Worker, url: &str, format: &str, options: &str) -> io::Result<()> {
worker.request(
tag::SAVE_AS,
&Pack::new()
.u64(worker.doc_id(self)?)
.str(url)
.str(format)
.str(options)
.bytes(),
)?;
Ok(())
}
}