use ecow::EcoString;
use typst_library::layout::Transform;
use typst_utils::ResolvedPicoStr;
use xmlwriter::XmlWriter;
use crate::DedupId;
pub struct SvgElem<'a> {
xml: &'a mut XmlWriter,
}
impl<'a> SvgElem<'a> {
pub fn new(xml: &'a mut XmlWriter, name: &str) -> Self {
xml.start_element(name);
Self { xml }
}
pub fn elem(&mut self, name: &str) -> SvgElem<'_> {
SvgElem::new(self.xml, name)
}
pub fn lazy_elem<'b>(&'b mut self, name: &'static str) -> LazySvgElem<'a, 'b> {
LazySvgElem::new(self, name)
}
pub fn attr(&mut self, name: &str, value: impl SvgDisplay) -> &mut Self {
self.attr_with(name, |attr| value.fmt(attr));
self
}
pub fn attr_with(
&mut self,
name: &str,
fmt: impl FnOnce(&mut SvgFormatter),
) -> &mut Self {
self.xml
.write_attribute_raw(name, |buf| fmt(&mut SvgFormatter::new(buf)));
self
}
pub fn with(&mut self, f: impl FnOnce(&mut Self)) -> &mut Self {
f(self);
self
}
}
impl Drop for SvgElem<'_> {
fn drop(&mut self) {
self.xml.end_element();
}
}
pub struct LazySvgElem<'a, 'b> {
inner: &'b mut SvgElem<'a>,
initialized: bool,
name: &'static str,
}
impl<'a, 'b> LazySvgElem<'a, 'b> {
pub fn new(parent: &'b mut SvgElem<'a>, name: &'static str) -> Self {
Self { inner: parent, initialized: false, name }
}
pub fn init<'c>(&'c mut self) -> &'c mut SvgElem<'a> {
if !self.initialized {
self.inner.xml.start_element(self.name);
self.initialized = true;
}
self.inner
}
pub fn lazy<'c>(&'c mut self) -> &'c mut SvgElem<'a> {
self.inner
}
}
impl Drop for LazySvgElem<'_, '_> {
fn drop(&mut self) {
if self.initialized {
self.inner.xml.end_element();
}
}
}
pub trait SvgWrite: Sized {
fn push_str(&mut self, value: &str);
fn push_char(&mut self, value: char) {
self.push_str(value.encode_utf8(&mut [0; 4]));
}
fn push_num(&mut self, num: f64) {
const ROUNDING_FACTOR: f64 = 10_u32.pow(9) as f64;
let num = (ROUNDING_FACTOR * num).round() / ROUNDING_FACTOR;
if num == num as i64 as f64 {
self.push_int(num as i64);
return;
}
let mut buf = ryu::Buffer::new();
self.push_str(buf.format(num));
}
fn push_int(&mut self, num: i64) {
let mut buf = itoa::Buffer::new();
self.push_str(buf.format(num));
}
fn push_nums(&mut self, nums: impl IntoIterator<Item = f64>) {
for (i, num) in nums.into_iter().enumerate() {
if i > 0 {
self.push_str(" ");
}
self.push_num(num);
}
}
fn push(&mut self, value: impl SvgDisplay) {
value.fmt(self);
}
}
pub struct SvgFormatter<'a, T = Vec<u8>> {
buf: &'a mut T,
}
impl<'a, T> SvgFormatter<'a, T> {
pub fn new(buf: &'a mut T) -> Self {
Self { buf }
}
}
impl SvgWrite for SvgFormatter<'_, Vec<u8>> {
fn push_str(&mut self, value: &str) {
self.buf.extend_from_slice(value.as_bytes());
}
}
impl SvgWrite for SvgFormatter<'_, EcoString> {
fn push_str(&mut self, value: &str) {
self.buf.push_str(value);
}
}
pub trait SvgDisplay {
fn fmt(&self, f: &mut impl SvgWrite);
}
impl<T: SvgDisplay> SvgDisplay for &T {
fn fmt(&self, f: &mut impl SvgWrite) {
<T as SvgDisplay>::fmt(self, f)
}
}
impl SvgDisplay for char {
fn fmt(&self, f: &mut impl SvgWrite) {
f.push_char(*self);
}
}
impl SvgDisplay for &str {
fn fmt(&self, f: &mut impl SvgWrite) {
f.push_str(self);
}
}
impl SvgDisplay for EcoString {
fn fmt(&self, f: &mut impl SvgWrite) {
f.push_str(self);
}
}
impl SvgDisplay for ResolvedPicoStr {
fn fmt(&self, f: &mut impl SvgWrite) {
f.push_str(self);
}
}
impl SvgDisplay for f64 {
fn fmt(&self, f: &mut impl SvgWrite) {
f.push_num(*self);
}
}
pub struct SvgTransform(pub Transform);
impl SvgDisplay for SvgTransform {
fn fmt(&self, f: &mut impl SvgWrite) {
let sx = self.0.sx.get();
let sy = self.0.sy.get();
let kx = self.0.kx.get();
let ky = self.0.ky.get();
let tx = self.0.tx.to_pt();
let ty = self.0.ty.to_pt();
if self.0.is_only_scale() {
f.push_str("scale(");
if sx == sy {
f.push_num(sx);
} else {
f.push_nums([sx, sy]);
}
f.push_str(")")
} else if self.0.is_only_translate() {
f.push_str("translate(");
if ty == 0.0 {
f.push_num(tx);
} else {
f.push_nums([tx, ty]);
}
f.push_str(")");
} else {
f.push_str("matrix(");
f.push_nums([sx, ky, kx, sy, tx, ty]);
f.push_str(")");
}
}
}
pub struct SvgUrl<T>(pub T);
impl SvgDisplay for SvgUrl<DedupId> {
fn fmt(&self, f: &mut impl SvgWrite) {
f.push_str("url(#");
f.push(self.0);
f.push_str(")");
}
}
pub struct SvgIdRef<T>(pub T);
impl SvgDisplay for SvgIdRef<DedupId> {
fn fmt(&self, f: &mut impl SvgWrite) {
f.push_str("#");
f.push(self.0);
}
}
impl<S: AsRef<str>> SvgDisplay for SvgIdRef<S> {
fn fmt(&self, f: &mut impl SvgWrite) {
f.push_str("#");
f.push_str(self.0.as_ref());
}
}