Add authenticated media certificate generator

This commit is contained in:
Lambda 2024-09-05 21:51:19 +00:00
parent be14f5bddc
commit d6fe411443
14 changed files with 1930 additions and 11 deletions

View file

@ -0,0 +1,161 @@
use std::collections::HashMap;
use comemo::Prehashed;
use time::OffsetDateTime;
use typst::{
diag::{FileError, FileResult},
eval::Tracer,
foundations::{Bytes, Datetime, IntoValue},
syntax::{FileId, Source, VirtualPath},
text::{Font, FontBook},
visualize::Color,
Library, World,
};
const EMBEDDED_FONTS: &[&[u8]] = &[
include_bytes!("../res/ComicNeue-Bold.ttf"),
include_bytes!("../res/NotoColorEmoji.ttf"),
];
const AUTH_SOURCE: &str = include_str!("../res/auth.typ");
const UNAUTH_SOURCE: &str = include_str!("../res/unauth.typ");
const FILE_SOURCES: &[(&str, &[u8])] = &[
("confetti.svg", include_bytes!("../res/confetti.svg")),
("party.svg", include_bytes!("../res/party.svg")),
];
fn get_fonts() -> (FontBook, Vec<Font>) {
let mut book = FontBook::new();
let mut fonts = vec![];
for font in EMBEDDED_FONTS
.iter()
.flat_map(|data| Font::iter(Bytes::from_static(data)))
{
book.push(font.info().clone());
fonts.push(font);
}
(book, fonts)
}
/// A minimal world that generates Authenticated Media certificates
pub struct AuthMediaWorld {
/// The name of the server issuing the certificate
issuer: String,
/// File ID of the authenticated source
auth_id: FileId,
/// File ID of the unauthenticated source
unauth_id: FileId,
/// True if the authenticated source should be used
is_authenticated: bool,
/// Typst's standard library.
library: Prehashed<Library>,
/// Metadata about discovered fonts.
book: Prehashed<FontBook>,
/// Locations of and storage for lazily loaded fonts.
fonts: Vec<Font>,
/// Maps file ids to source files and buffers.
slots: HashMap<FileId, (Bytes, Option<Source>)>,
/// The current datetime if requested. This is stored here to ensure it is
/// always the same within one compilation. Reset between compilations.
now: Datetime,
}
impl AuthMediaWorld {
#[must_use]
pub fn new(issuer: String) -> Self {
let auth_id = FileId::new_fake(VirtualPath::new("<auth>"));
let unauth_id = FileId::new_fake(VirtualPath::new("<unauth>"));
let mut slots = HashMap::new();
for &(name, data) in FILE_SOURCES {
let id = FileId::new(None, VirtualPath::new(format!("/{name}")));
let bytes = data.into();
let source = std::str::from_utf8(data)
.ok()
.map(|s| Source::new(id, s.to_owned()));
slots.insert(id, (bytes, source));
}
let library = Library::builder().build();
let (book, fonts) = get_fonts();
let now = Datetime::Date(OffsetDateTime::now_utc().date());
Self {
issuer,
auth_id,
unauth_id,
is_authenticated: false,
library: Prehashed::new(library),
book: Prehashed::new(book),
fonts,
slots,
now,
}
}
pub fn generate(&mut self, target: Option<&str>) -> Option<Vec<u8>> {
let mut inputs =
vec![("issuer".into(), self.issuer.as_str().into_value())];
if let Some(target) = target {
inputs.push(("target".into(), target.into_value()));
}
let library = Library::builder()
.with_inputs(inputs.into_iter().collect())
.build();
self.library.update(|old| *old = library);
self.is_authenticated = target.is_some();
let mut tracer = Tracer::new();
let document = typst::compile(self, &mut tracer).unwrap();
if document.pages.len() != 1 {
return None;
}
typst_render::render(&document.pages[0].frame, 2.0, Color::WHITE)
.encode_png()
.ok()
}
}
impl World for AuthMediaWorld {
fn library(&self) -> &Prehashed<Library> {
&self.library
}
fn book(&self) -> &Prehashed<FontBook> {
&self.book
}
fn main(&self) -> Source {
if self.is_authenticated {
Source::new(self.auth_id, AUTH_SOURCE.to_owned())
} else {
Source::new(self.unauth_id, UNAUTH_SOURCE.to_owned())
}
}
fn source(&self, id: FileId) -> FileResult<Source> {
let (_bytes, source) = self.slots.get(&id).ok_or(
FileError::NotFound(id.vpath().as_rooted_path().to_owned()),
)?;
Ok(source.as_ref().ok_or(FileError::NotSource)?.clone())
}
fn file(&self, id: FileId) -> FileResult<Bytes> {
let (bytes, _source) = self.slots.get(&id).ok_or(
FileError::NotFound(id.vpath().as_rooted_path().to_owned()),
)?;
Ok(bytes.clone())
}
fn font(&self, index: usize) -> Option<Font> {
self.fonts.get(index).cloned()
}
fn today(&self, offset: Option<i64>) -> Option<Datetime> {
assert_eq!(offset, None, "today offset not supported");
Some(self.now)
}
}