summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval/template.rs74
-rw-r--r--src/eval/walk.rs1
-rw-r--r--src/export/pdf.rs43
-rw-r--r--src/layout/frame.rs25
-rw-r--r--src/layout/par.rs89
-rw-r--r--src/library/mod.rs1
-rw-r--r--src/library/text.rs17
-rw-r--r--tests/ref/text/links.pngbin0 -> 6947 bytes
-rw-r--r--tests/typ/text/links.typ12
-rw-r--r--tests/typeset.rs7
10 files changed, 196 insertions, 73 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs
index addbb466..0ab49d04 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -20,7 +20,7 @@ pub struct Template(Rc<Vec<TemplateNode>>);
#[derive(Clone)]
enum TemplateNode {
/// A word space.
- Space,
+ Space(Vec<Decoration>),
/// A line break.
Linebreak,
/// A paragraph break.
@@ -28,11 +28,11 @@ enum TemplateNode {
/// A page break.
Pagebreak(bool),
/// Plain text.
- Text(EcoString),
+ Text(EcoString, Vec<Decoration>),
/// Spacing.
Spacing(GenAxis, Linear),
/// An inline node builder.
- Inline(Rc<dyn Fn(&State) -> LayoutNode>),
+ Inline(Rc<dyn Fn(&State) -> LayoutNode>, Vec<Decoration>),
/// An block node builder.
Block(Rc<dyn Fn(&State) -> LayoutNode>),
/// Save the current state.
@@ -43,6 +43,13 @@ enum TemplateNode {
Modify(Rc<dyn Fn(&mut State)>),
}
+/// A template node decoration.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub enum Decoration {
+ /// A link.
+ Link(EcoString),
+}
+
impl Template {
/// Create a new, empty template.
pub fn new() -> Self {
@@ -55,7 +62,7 @@ impl Template {
F: Fn(&State) -> T + 'static,
T: Into<LayoutNode>,
{
- let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()));
+ let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()), vec![]);
Self(Rc::new(vec![node]))
}
@@ -71,7 +78,7 @@ impl Template {
/// Add a word space to the template.
pub fn space(&mut self) {
- self.make_mut().push(TemplateNode::Space);
+ self.make_mut().push(TemplateNode::Space(vec![]));
}
/// Add a line break to the template.
@@ -91,7 +98,7 @@ impl Template {
/// Add text to the template.
pub fn text(&mut self, text: impl Into<EcoString>) {
- self.make_mut().push(TemplateNode::Text(text.into()));
+ self.make_mut().push(TemplateNode::Text(text.into(), vec![]));
}
/// Add text, but in monospace.
@@ -107,6 +114,19 @@ impl Template {
self.make_mut().push(TemplateNode::Spacing(axis, spacing));
}
+ /// Add a decoration to the last template node.
+ pub fn decorate(&mut self, deco: Decoration) {
+ for node in self.make_mut() {
+ let decos = match node {
+ TemplateNode::Space(decos) => decos,
+ TemplateNode::Text(_, decos) => decos,
+ TemplateNode::Inline(_, decos) => decos,
+ _ => continue,
+ };
+ decos.push(deco.clone());
+ }
+ }
+
/// Register a restorable snapshot.
pub fn save(&mut self) {
self.make_mut().push(TemplateNode::Save);
@@ -201,7 +221,7 @@ impl Add<Str> for Template {
type Output = Self;
fn add(mut self, rhs: Str) -> Self::Output {
- Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into()));
+ Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into(), vec![]));
self
}
}
@@ -210,7 +230,7 @@ impl Add<Template> for Str {
type Output = Template;
fn add(self, mut rhs: Template) -> Self::Output {
- Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into()));
+ Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into(), vec![]));
rhs
}
}
@@ -261,26 +281,26 @@ impl Builder {
self.pagebreak(true, false);
}
}
- TemplateNode::Space => self.space(),
+ TemplateNode::Space(decos) => self.space(decos),
TemplateNode::Linebreak => self.linebreak(),
TemplateNode::Parbreak => self.parbreak(),
TemplateNode::Pagebreak(keep) => self.pagebreak(*keep, true),
- TemplateNode::Text(text) => self.text(text),
+ TemplateNode::Text(text, decos) => self.text(text, decos),
TemplateNode::Spacing(axis, amount) => self.spacing(*axis, *amount),
- TemplateNode::Inline(f) => self.inline(f(&self.state)),
+ TemplateNode::Inline(f, decos) => self.inline(f(&self.state), decos),
TemplateNode::Block(f) => self.block(f(&self.state)),
TemplateNode::Modify(f) => f(&mut self.state),
}
}
/// Push a word space into the active paragraph.
- fn space(&mut self) {
- self.stack.par.push_soft(self.make_text_node(' '));
+ fn space(&mut self, decos: &[Decoration]) {
+ self.stack.par.push_soft(self.make_text_node(' ', decos.to_vec()));
}
/// Apply a forced line break.
fn linebreak(&mut self) {
- self.stack.par.push_hard(self.make_text_node('\n'));
+ self.stack.par.push_hard(self.make_text_node('\n', vec![]));
}
/// Apply a forced paragraph break.
@@ -300,16 +320,14 @@ impl Builder {
}
/// Push text into the active paragraph.
- ///
- /// The text is split into lines at newlines.
- fn text(&mut self, text: impl Into<EcoString>) {
- self.stack.par.push(self.make_text_node(text));
+ fn text(&mut self, text: impl Into<EcoString>, decos: &[Decoration]) {
+ self.stack.par.push(self.make_text_node(text, decos.to_vec()));
}
/// Push an inline node into the active paragraph.
- fn inline(&mut self, node: impl Into<LayoutNode>) {
+ fn inline(&mut self, node: impl Into<LayoutNode>, decos: &[Decoration]) {
let align = self.state.aligns.inline;
- self.stack.par.push(ParChild::Any(node.into(), align));
+ self.stack.par.push(ParChild::Any(node.into(), align, decos.to_vec()));
}
/// Push a block node into the active stack, finishing the active paragraph.
@@ -348,11 +366,16 @@ impl Builder {
/// Construct a text node with the given text and settings from the active
/// state.
- fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild {
+ fn make_text_node(
+ &self,
+ text: impl Into<EcoString>,
+ decos: Vec<Decoration>,
+ ) -> ParChild {
ParChild::Text(
text.into(),
self.state.aligns.inline,
Rc::clone(&self.state.font),
+ decos,
)
}
}
@@ -465,11 +488,14 @@ impl ParBuilder {
}
fn push_inner(&mut self, child: ParChild) {
- if let ParChild::Text(curr_text, curr_align, curr_props) = &child {
- if let Some(ParChild::Text(prev_text, prev_align, prev_props)) =
+ if let ParChild::Text(curr_text, curr_align, curr_props, curr_decos) = &child {
+ if let Some(ParChild::Text(prev_text, prev_align, prev_props, prev_decos)) =
self.children.last_mut()
{
- if prev_align == curr_align && Rc::ptr_eq(prev_props, curr_props) {
+ if prev_align == curr_align
+ && Rc::ptr_eq(prev_props, curr_props)
+ && curr_decos == prev_decos
+ {
prev_text.push_str(&curr_text);
return;
}
diff --git a/src/eval/walk.rs b/src/eval/walk.rs
index d99db3ed..db9fbbde 100644
--- a/src/eval/walk.rs
+++ b/src/eval/walk.rs
@@ -115,6 +115,7 @@ fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) {
label.clone(),
state.aligns.inline,
Rc::clone(&state.font),
+ vec![],
)],
};
StackNode {
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 7ff600ff..d613efc3 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -8,8 +8,8 @@ use std::rc::Rc;
use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba};
use miniz_oxide::deflate;
use pdf_writer::{
- CidFontType, ColorSpace, Content, Filter, FontFlags, Name, PdfWriter, Rect, Ref, Str,
- SystemInfo, UnicodeCmap,
+ ActionType, AnnotationType, CidFontType, ColorSpace, Content, Filter, FontFlags,
+ Name, PdfWriter, Rect, Ref, Str, SystemInfo, UnicodeCmap,
};
use ttf_parser::{name_id, GlyphId};
@@ -59,6 +59,7 @@ impl<'a> PdfExporter<'a> {
}
image_map.insert(id);
}
+ Element::Link(_, _) => {}
}
}
}
@@ -116,16 +117,34 @@ impl<'a> PdfExporter<'a> {
for ((page_id, content_id), page) in
self.refs.pages().zip(self.refs.contents()).zip(self.frames)
{
- self.writer
- .page(page_id)
+ let w = page.size.w.to_pt() as f32;
+ let h = page.size.h.to_pt() as f32;
+
+ let mut page_writer = self.writer.page(page_id);
+ page_writer
.parent(self.refs.page_tree)
- .media_box(Rect::new(
- 0.0,
- 0.0,
- page.size.w.to_pt() as f32,
- page.size.h.to_pt() as f32,
- ))
- .contents(content_id);
+ .media_box(Rect::new(0.0, 0.0, w, h));
+
+ let mut annotations = page_writer.annotations();
+ for (pos, element) in page.elements() {
+ if let Element::Link(href, size) = element {
+ let x = pos.x.to_pt() as f32;
+ let y = (page.size.h - pos.y).to_pt() as f32;
+ let w = size.w.to_pt() as f32;
+ let h = size.h.to_pt() as f32;
+
+ annotations
+ .push()
+ .subtype(AnnotationType::Link)
+ .rect(Rect::new(x, y - h, x + w, y))
+ .action()
+ .action_type(ActionType::Uri)
+ .uri(Str(href.as_bytes()));
+ }
+ }
+
+ drop(annotations);
+ page_writer.contents(content_id);
}
}
@@ -248,6 +267,8 @@ impl<'a> PdfExporter<'a> {
content.x_object(Name(name.as_bytes()));
content.restore_state();
}
+
+ Element::Link(_, _) => {}
}
}
diff --git a/src/layout/frame.rs b/src/layout/frame.rs
index e52e2751..15ef541b 100644
--- a/src/layout/frame.rs
+++ b/src/layout/frame.rs
@@ -16,17 +16,17 @@ pub struct Frame {
/// The baseline of the frame measured from the top.
pub baseline: Length,
/// The elements composing this layout.
- children: Vec<(Point, Child)>,
+ pub children: Vec<(Point, FrameChild)>,
}
/// A frame can contain two different kinds of children: a leaf element or a
/// nested frame.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
-enum Child {
+pub enum FrameChild {
/// A leaf node in the frame tree.
Element(Element),
- /// An interior node.
- Frame(Rc<Frame>),
+ /// An interior node with an optional index.
+ Frame(Option<usize>, Rc<Frame>),
}
impl Frame {
@@ -38,17 +38,22 @@ impl Frame {
/// Add an element at a position in the foreground.
pub fn push(&mut self, pos: Point, element: Element) {
- self.children.push((pos, Child::Element(element)));
+ self.children.push((pos, FrameChild::Element(element)));
}
/// Add an element at a position in the background.
pub fn prepend(&mut self, pos: Point, element: Element) {
- self.children.insert(0, (pos, Child::Element(element)))
+ self.children.insert(0, (pos, FrameChild::Element(element)));
}
/// Add a frame element.
pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) {
- self.children.push((pos, Child::Frame(subframe)))
+ self.children.push((pos, FrameChild::Frame(None, subframe)))
+ }
+
+ /// Add a frame element with an index of arbitrary use.
+ pub fn push_indexed_frame(&mut self, pos: Point, index: usize, subframe: Rc<Self>) {
+ self.children.push((pos, FrameChild::Frame(Some(index), subframe)));
}
/// Add all elements of another frame, placing them relative to the given
@@ -85,12 +90,12 @@ impl<'a> Iterator for Elements<'a> {
fn next(&mut self) -> Option<Self::Item> {
let (cursor, offset, frame) = self.stack.last_mut()?;
match frame.children.get(*cursor) {
- Some((pos, Child::Frame(f))) => {
+ Some((pos, FrameChild::Frame(_, f))) => {
let new_offset = *offset + *pos;
self.stack.push((0, new_offset, f.as_ref()));
self.next()
}
- Some((pos, Child::Element(e))) => {
+ Some((pos, FrameChild::Element(e))) => {
*cursor += 1;
Some((*offset + *pos, e))
}
@@ -115,6 +120,8 @@ pub enum Element {
Geometry(Geometry, Paint),
/// A raster image.
Image(ImageId, Size),
+ /// A link to an external resource.
+ Link(String, Size),
}
/// A run of shaped text.
diff --git a/src/layout/par.rs b/src/layout/par.rs
index 3df742a7..e92e5a18 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -4,7 +4,7 @@ use unicode_bidi::{BidiInfo, Level};
use xi_unicode::LineBreakIterator;
use super::*;
-use crate::eval::FontState;
+use crate::eval::{Decoration, FontState};
use crate::util::{EcoString, RangeExt, SliceExt};
type Range = std::ops::Range<usize>;
@@ -26,9 +26,9 @@ pub enum ParChild {
/// Spacing between other nodes.
Spacing(Linear),
/// A run of text and how to align it in its line.
- Text(EcoString, Align, Rc<FontState>),
+ Text(EcoString, Align, Rc<FontState>, Vec<Decoration>),
/// Any child node and how to align it in its line.
- Any(LayoutNode, Align),
+ Any(LayoutNode, Align, Vec<Decoration>),
}
impl Layout for ParNode {
@@ -48,7 +48,7 @@ impl Layout for ParNode {
let layouter = ParLayouter::new(self, ctx, regions, bidi);
// Find suitable linebreaks.
- layouter.layout(ctx, regions.clone())
+ layouter.layout(ctx, &self.children, regions.clone())
}
}
@@ -79,8 +79,8 @@ impl ParNode {
fn strings(&self) -> impl Iterator<Item = &str> {
self.children.iter().map(|child| match child {
ParChild::Spacing(_) => " ",
- ParChild::Text(ref piece, _, _) => piece,
- ParChild::Any(_, _) => "\u{FFFC}",
+ ParChild::Text(ref piece, ..) => piece,
+ ParChild::Any(..) => "\u{FFFC}",
})
}
}
@@ -119,25 +119,25 @@ impl<'a> ParLayouter<'a> {
let mut ranges = vec![];
// Layout the children and collect them into items.
- for (range, child) in par.ranges().zip(&par.children) {
+ for (i, (range, child)) in par.ranges().zip(&par.children).enumerate() {
match *child {
ParChild::Spacing(amount) => {
let resolved = amount.resolve(regions.current.w);
items.push(ParItem::Spacing(resolved));
ranges.push(range);
}
- ParChild::Text(_, align, ref state) => {
+ ParChild::Text(_, align, ref state, _) => {
// TODO: Also split by language and script.
for (subrange, dir) in split_runs(&bidi, range) {
let text = &bidi.text[subrange.clone()];
let shaped = shape(ctx, text, dir, state);
- items.push(ParItem::Text(shaped, align));
+ items.push(ParItem::Text(shaped, align, i));
ranges.push(subrange);
}
}
- ParChild::Any(ref node, align) => {
+ ParChild::Any(ref node, align, _) => {
let frame = node.layout(ctx, regions).remove(0);
- items.push(ParItem::Frame(frame.item, align));
+ items.push(ParItem::Frame(frame.item, align, i));
ranges.push(range);
}
}
@@ -156,9 +156,10 @@ impl<'a> ParLayouter<'a> {
fn layout(
self,
ctx: &mut LayoutContext,
+ children: &[ParChild],
regions: Regions,
) -> Vec<Constrained<Rc<Frame>>> {
- let mut stack = LineStack::new(self.line_spacing, regions);
+ let mut stack = LineStack::new(self.line_spacing, children, regions);
// The current line attempt.
// Invariant: Always fits into `stack.regions.current`.
@@ -273,9 +274,9 @@ enum ParItem<'a> {
/// Spacing between other items.
Spacing(Length),
/// A shaped text run with consistent direction.
- Text(ShapedText<'a>, Align),
+ Text(ShapedText<'a>, Align, usize),
/// A layouted child node.
- Frame(Rc<Frame>, Align),
+ Frame(Rc<Frame>, Align, usize),
}
impl ParItem<'_> {
@@ -283,8 +284,8 @@ impl ParItem<'_> {
pub fn size(&self) -> Size {
match self {
Self::Spacing(amount) => Size::new(*amount, Length::zero()),
- Self::Text(shaped, _) => shaped.size,
- Self::Frame(frame, _) => frame.size,
+ Self::Text(shaped, ..) => shaped.size,
+ Self::Frame(frame, ..) => frame.size,
}
}
@@ -292,8 +293,17 @@ impl ParItem<'_> {
pub fn baseline(&self) -> Length {
match self {
Self::Spacing(_) => Length::zero(),
- Self::Text(shaped, _) => shaped.baseline,
- Self::Frame(frame, _) => frame.baseline,
+ Self::Text(shaped, ..) => shaped.baseline,
+ Self::Frame(frame, ..) => frame.baseline,
+ }
+ }
+
+ /// The index of the `ParChild` that this item belongs to.
+ pub fn index(&self) -> Option<usize> {
+ match *self {
+ Self::Spacing(_) => None,
+ Self::Text(.., index) => Some(index),
+ Self::Frame(.., index) => Some(index),
}
}
}
@@ -301,6 +311,7 @@ impl ParItem<'_> {
/// Stacks lines on top of each other.
struct LineStack<'a> {
line_spacing: Length,
+ children: &'a [ParChild],
full: Size,
regions: Regions,
size: Size,
@@ -312,11 +323,12 @@ struct LineStack<'a> {
impl<'a> LineStack<'a> {
/// Create an empty line stack.
- fn new(line_spacing: Length, regions: Regions) -> Self {
+ fn new(line_spacing: Length, children: &'a [ParChild], regions: Regions) -> Self {
Self {
line_spacing,
- constraints: Constraints::new(regions.expand),
+ children,
full: regions.current,
+ constraints: Constraints::new(regions.expand),
regions,
size: Size::zero(),
lines: vec![],
@@ -368,6 +380,25 @@ impl<'a> LineStack<'a> {
output.merge_frame(pos, frame);
}
+ // For each frame, we look if any decorations apply.
+ for i in 0 .. output.children.len() {
+ let &(point, ref child) = &output.children[i];
+ if let &FrameChild::Frame(Some(frame_idx), ref frame) = child {
+ let size = frame.size;
+ for deco in match &self.children[frame_idx] {
+ ParChild::Spacing(_) => continue,
+ ParChild::Text(.., decos) => decos,
+ ParChild::Any(.., decos) => decos,
+ } {
+ match deco {
+ Decoration::Link(href) => {
+ output.push(point, Element::Link(href.to_string(), size));
+ }
+ }
+ }
+ }
+ }
+
self.finished.push(output.constrain(self.constraints));
self.regions.next();
self.full = self.regions.current;
@@ -426,7 +457,7 @@ impl<'a> LineLayout<'a> {
// Reshape the last item if it's split in half.
let mut last = None;
- if let Some((ParItem::Text(shaped, align), rest)) = items.split_last() {
+ if let Some((ParItem::Text(shaped, align, i), rest)) = items.split_last() {
// Compute the range we want to shape, trimming whitespace at the
// end of the line.
let base = par.ranges[last_idx].start;
@@ -442,7 +473,7 @@ impl<'a> LineLayout<'a> {
if !range.is_empty() || rest.is_empty() {
// Reshape that part.
let reshaped = shaped.reshape(ctx, range);
- last = Some(ParItem::Text(reshaped, *align));
+ last = Some(ParItem::Text(reshaped, *align, *i));
}
items = rest;
@@ -452,7 +483,7 @@ impl<'a> LineLayout<'a> {
// Reshape the start item if it's split in half.
let mut first = None;
- if let Some((ParItem::Text(shaped, align), rest)) = items.split_first() {
+ if let Some((ParItem::Text(shaped, align, i), rest)) = items.split_first() {
// Compute the range we want to shape.
let Range { start: base, end: first_end } = par.ranges[first_idx];
let start = line.start;
@@ -463,7 +494,7 @@ impl<'a> LineLayout<'a> {
if range.len() < shaped.text.len() {
if !range.is_empty() {
let reshaped = shaped.reshape(ctx, range);
- first = Some(ParItem::Text(reshaped, *align));
+ first = Some(ParItem::Text(reshaped, *align, *i));
}
items = rest;
@@ -511,11 +542,11 @@ impl<'a> LineLayout<'a> {
offset += amount;
return;
}
- ParItem::Text(ref shaped, align) => {
+ ParItem::Text(ref shaped, align, _) => {
ruler = ruler.max(align);
Rc::new(shaped.build(ctx))
}
- ParItem::Frame(ref frame, align) => {
+ ParItem::Frame(ref frame, align, _) => {
ruler = ruler.max(align);
frame.clone()
}
@@ -528,7 +559,11 @@ impl<'a> LineLayout<'a> {
);
offset += frame.size.w;
- output.push_frame(pos, frame);
+
+ match item.index() {
+ Some(idx) => output.push_indexed_frame(pos, idx, frame),
+ None => output.push_frame(pos, frame),
+ }
});
output
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 44f1f01f..9d25a008 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -35,6 +35,7 @@ pub fn new() -> Scope {
std.def_func("strike", strike);
std.def_func("underline", underline);
std.def_func("overline", overline);
+ std.def_func("link", link);
// Layout.
std.def_func("page", page);
diff --git a/src/library/text.rs b/src/library/text.rs
index bde2a9aa..cfd2de99 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -1,4 +1,4 @@
-use crate::eval::{FontState, LineState};
+use crate::eval::{Decoration, FontState, LineState};
use crate::layout::Paint;
use super::*;
@@ -197,3 +197,18 @@ fn line_impl(
Ok(Value::Template(template))
}
+
+/// `link`: Set a link.
+pub fn link(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
+ let url = args.expect::<Str>("url")?;
+
+ let mut body = args.eat().unwrap_or_else(|| {
+ let mut template = Template::new();
+ template.text(&url);
+ template
+ });
+
+ body.decorate(Decoration::Link(url.into()));
+
+ Ok(Value::Template(body))
+}
diff --git a/tests/ref/text/links.png b/tests/ref/text/links.png
new file mode 100644
index 00000000..5ac7ee00
--- /dev/null
+++ b/tests/ref/text/links.png
Binary files differ
diff --git a/tests/typ/text/links.typ b/tests/typ/text/links.typ
new file mode 100644
index 00000000..eabb316a
--- /dev/null
+++ b/tests/typ/text/links.typ
@@ -0,0 +1,12 @@
+// Link without body.
+#link("https://example.com/")
+
+// Link with body.
+#link("https://typst.app/")[Some text text text]
+
+// With line break.
+This link appears #link("https://google.com/")[in the middle of] a paragraph.
+
+// Styled with underline and color.
+#let link(url, body) = link(url, [#font(fill: rgb("283663")) #underline(body)])
+You could also make the #link("https://html5zombo.com/")[link look way more typical.]
diff --git a/tests/typeset.rs b/tests/typeset.rs
index 80eb9da4..b788f74d 100644
--- a/tests/typeset.rs
+++ b/tests/typeset.rs
@@ -9,7 +9,7 @@ use tiny_skia as sk;
use ttf_parser::{GlyphId, OutlineBuilder};
use walkdir::WalkDir;
-use typst::color::Color;
+use typst::color::{Color, RgbaColor};
use typst::diag::Error;
use typst::eval::{State, Value};
use typst::geom::{self, Length, PathElement, Point, Sides, Size};
@@ -428,6 +428,11 @@ fn draw(ctx: &Context, frames: &[Rc<Frame>], dpi: f32) -> sk::Pixmap {
Element::Image(id, size) => {
draw_image(&mut canvas, ts, ctx, id, size);
}
+ Element::Link(_, s) => {
+ let outline = Geometry::Rect(s);
+ let paint = Paint::Color(Color::Rgba(RgbaColor::new(40, 54, 99, 40)));
+ draw_geometry(&mut canvas, ts, &outline, paint);
+ }
}
}