use std::fmt::{self, Debug, Formatter};
use std::num::NonZeroUsize;
use std::sync::Arc;
use typst_syntax::Span;
use typst_utils::{LazyHash, Numeric};
use crate::foundations::{Dict, Label, Value, cast, dict};
use crate::introspection::{Location, Tag};
use crate::layout::{Abs, Axes, FixedAlignment, Length, Point, Size, Transform};
use crate::model::Destination;
use crate::text::TextItem;
use crate::visualize::{Color, Curve, FixedStroke, Geometry, Image, Paint, Shape};
#[derive(Default, Clone, Hash)]
pub struct Frame {
size: Size,
baseline: Option<Abs>,
items: Arc<LazyHash<Vec<(Point, FrameItem)>>>,
kind: FrameKind,
}
impl Frame {
#[track_caller]
pub fn new(size: Size, kind: FrameKind) -> Self {
assert!(size.is_finite());
Self {
size,
baseline: None,
items: Arc::new(LazyHash::new(vec![])),
kind,
}
}
#[track_caller]
pub fn soft(size: Size) -> Self {
Self::new(size, FrameKind::Soft)
}
#[track_caller]
pub fn hard(size: Size) -> Self {
Self::new(size, FrameKind::Hard)
}
pub fn set_kind(&mut self, kind: FrameKind) {
self.kind = kind;
}
pub fn with_kind(mut self, kind: FrameKind) -> Self {
self.kind = kind;
self
}
pub fn kind(&self) -> FrameKind {
self.kind
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn size(&self) -> Size {
self.size
}
pub fn size_mut(&mut self) -> &mut Size {
&mut self.size
}
pub fn set_size(&mut self, size: Size) {
self.size = size;
}
pub fn width(&self) -> Abs {
self.size.x
}
pub fn height(&self) -> Abs {
self.size.y
}
pub fn baseline(&self) -> Abs {
self.baseline.unwrap_or(self.size.y)
}
pub fn has_baseline(&self) -> bool {
self.baseline.is_some()
}
pub fn set_baseline(&mut self, baseline: Abs) {
self.baseline = Some(baseline);
}
pub fn ascent(&self) -> Abs {
self.baseline()
}
pub fn descent(&self) -> Abs {
self.size.y - self.baseline()
}
pub fn items(&self) -> std::slice::Iter<'_, (Point, FrameItem)> {
self.items.iter()
}
}
impl Frame {
pub fn layer(&self) -> usize {
self.items.len()
}
pub fn push(&mut self, pos: Point, item: FrameItem) {
Arc::make_mut(&mut self.items).push((pos, item));
}
pub fn push_multiple<I>(&mut self, items: I)
where
I: IntoIterator<Item = (Point, FrameItem)>,
{
Arc::make_mut(&mut self.items).extend(items);
}
pub fn push_frame(&mut self, pos: Point, frame: Frame) {
if self.should_inline(&frame) {
self.inline(self.layer(), pos, frame);
} else {
self.push(pos, FrameItem::Group(GroupItem::new(frame)));
}
}
#[track_caller]
pub fn insert(&mut self, layer: usize, pos: Point, item: FrameItem) {
Arc::make_mut(&mut self.items).insert(layer, (pos, item));
}
pub fn prepend(&mut self, pos: Point, item: FrameItem) {
self.insert(0, pos, item);
}
pub fn prepend_multiple<I>(&mut self, items: I)
where
I: IntoIterator<Item = (Point, FrameItem)>,
{
Arc::make_mut(&mut self.items).splice(0..0, items);
}
pub fn prepend_frame(&mut self, pos: Point, frame: Frame) {
if self.should_inline(&frame) {
self.inline(0, pos, frame);
} else {
self.prepend(pos, FrameItem::Group(GroupItem::new(frame)));
}
}
fn should_inline(&self, frame: &Frame) -> bool {
frame.kind().is_soft() && (self.items.is_empty() || frame.items.len() <= 5)
}
fn inline(&mut self, layer: usize, pos: Point, frame: Frame) {
if frame.items.is_empty() {
return;
}
if pos.is_zero() && self.items.is_empty() {
self.items = frame.items;
return;
}
let range = layer..layer;
if pos.is_zero() {
let sink = Arc::make_mut(&mut self.items);
match Arc::try_unwrap(frame.items) {
Ok(items) => {
sink.splice(range, items.into_inner());
}
Err(arc) => {
sink.splice(range, arc.iter().cloned());
}
}
return;
}
let sink = Arc::make_mut(&mut self.items);
match Arc::try_unwrap(frame.items) {
Ok(items) => {
sink.splice(
range,
items.into_inner().into_iter().map(|(p, e)| (p + pos, e)),
);
}
Err(arc) => {
sink.splice(range, arc.iter().cloned().map(|(p, e)| (p + pos, e)));
}
}
}
}
impl Frame {
pub fn clear(&mut self) {
if Arc::strong_count(&self.items) == 1 {
Arc::make_mut(&mut self.items).clear();
} else {
self.items = Arc::new(LazyHash::new(vec![]));
}
}
pub fn resize(&mut self, target: Size, align: Axes<FixedAlignment>) -> Point {
if self.size == target {
return Point::zero();
}
let offset =
align.zip_map(target - self.size, FixedAlignment::position).to_point();
self.size = target;
self.translate(offset);
offset
}
pub fn translate(&mut self, offset: Point) {
if !offset.is_zero() {
if let Some(baseline) = &mut self.baseline {
*baseline += offset.y;
}
for (point, _) in Arc::make_mut(&mut self.items).iter_mut() {
*point += offset;
}
}
}
pub fn hide(&mut self) {
Arc::make_mut(&mut self.items).retain_mut(|(_, item)| match item {
FrameItem::Group(group) => {
group.frame.hide();
!group.frame.is_empty()
}
FrameItem::Tag(_) => true,
_ => false,
});
}
pub fn fill(&mut self, fill: impl Into<Paint>) {
self.prepend(
Point::zero(),
FrameItem::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()),
);
}
pub fn transform(&mut self, transform: Transform) {
if !self.is_empty() {
self.group(|g| g.transform = transform);
}
}
pub fn clip(&mut self, clip_curve: Curve) {
if !self.is_empty() {
self.group(|g| g.clip = Some(clip_curve));
}
}
pub fn label(&mut self, label: Label) {
self.group(|g| g.label = Some(label));
}
pub fn set_parent(&mut self, parent: FrameParent) {
if !self.is_empty() {
self.group(|g| g.parent = Some(parent));
}
}
fn group<F>(&mut self, f: F)
where
F: FnOnce(&mut GroupItem),
{
let mut wrapper = Frame::soft(self.size);
wrapper.baseline = self.baseline;
let mut group = GroupItem::new(std::mem::take(self));
f(&mut group);
wrapper.push(Point::zero(), FrameItem::Group(group));
*self = wrapper;
}
}
impl Frame {
pub fn mark_box(mut self) -> Self {
self.mark_box_in_place();
self
}
pub fn mark_box_in_place(&mut self) {
self.insert(
0,
Point::zero(),
FrameItem::Shape(
Geometry::Rect(self.size).filled(Color::TEAL.with_alpha(0.5)),
Span::detached(),
),
);
self.insert(
1,
Point::with_y(self.baseline()),
FrameItem::Shape(
Geometry::Line(Point::with_x(self.size.x))
.stroked(FixedStroke::from_pair(Color::RED, Abs::pt(1.0))),
Span::detached(),
),
);
}
pub fn mark_point(&mut self, pos: Point) {
let radius = Abs::pt(2.0);
self.push(
pos - Point::splat(radius),
FrameItem::Shape(
Geometry::Curve(Curve::ellipse(Size::splat(2.0 * radius)))
.filled(Color::GREEN),
Span::detached(),
),
);
}
pub fn mark_line(&mut self, y: Abs) {
self.push(
Point::with_y(y),
FrameItem::Shape(
Geometry::Line(Point::with_x(self.size.x))
.stroked(FixedStroke::from_pair(Color::GREEN, Abs::pt(1.0))),
Span::detached(),
),
);
}
}
impl Debug for Frame {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("Frame ")?;
f.debug_list()
.entries(self.items.iter().map(|(_, item)| item))
.finish()
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub enum FrameKind {
#[default]
Soft,
Hard,
}
impl FrameKind {
pub fn is_soft(self) -> bool {
matches!(self, Self::Soft)
}
pub fn is_hard(self) -> bool {
matches!(self, Self::Hard)
}
}
#[derive(Clone, Hash)]
pub enum FrameItem {
Group(GroupItem),
Text(TextItem),
Shape(Shape, Span),
Image(Image, Size, Span),
Link(Destination, Size),
Tag(Tag),
}
impl Debug for FrameItem {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Group(group) => group.fmt(f),
Self::Text(text) => write!(f, "{text:?}"),
Self::Shape(shape, _) => write!(f, "{shape:?}"),
Self::Image(image, _, _) => write!(f, "{image:?}"),
Self::Link(dest, _) => write!(f, "Link({dest:?})"),
Self::Tag(tag) => write!(f, "{tag:?}"),
}
}
}
#[derive(Clone, Hash)]
pub struct GroupItem {
pub frame: Frame,
pub transform: Transform,
pub clip: Option<Curve>,
pub label: Option<Label>,
pub parent: Option<FrameParent>,
}
impl GroupItem {
pub fn new(frame: Frame) -> Self {
Self {
frame,
transform: Transform::identity(),
clip: None,
label: None,
parent: None,
}
}
}
impl Debug for GroupItem {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("Group ")?;
self.frame.fmt(f)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FrameParent {
pub location: Location,
pub inherit: Inherit,
}
impl FrameParent {
pub const fn new(location: Location, inherit: Inherit) -> Self {
Self { location, inherit }
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Inherit {
Yes,
No,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Position {
pub page: NonZeroUsize,
pub point: Point,
}
cast! {
Position,
self => Value::Dict(self.into()),
mut dict: Dict => {
let page = dict.take("page")?.cast()?;
let x: Length = dict.take("x")?.cast()?;
let y: Length = dict.take("y")?.cast()?;
dict.finish(&["page", "x", "y"])?;
Self { page, point: Point::new(x.abs, y.abs) }
},
}
impl From<Position> for Dict {
fn from(pos: Position) -> Self {
dict! {
"page" => pos.page,
"x" => pos.point.x,
"y" => pos.point.y,
}
}
}