Welcome to Tigerland

This commit is contained in:
Laurenz 2021-11-22 15:26:56 +01:00
parent ed50661378
commit 0a974d86ba
6 changed files with 96 additions and 57 deletions

View File

@ -14,7 +14,7 @@ use ttf_parser::{name_id, GlyphId, Tag};
use super::subset;
use crate::font::{find_name, FaceId, FontStore};
use crate::frame::{Element, Frame, Geometry, Shape, Stroke, Text};
use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
use crate::geom::{self, Color, Em, Length, Paint, Size};
use crate::image::{Image, ImageId, ImageStore};
use crate::Context;
@ -369,18 +369,6 @@ impl<'a> PageExporter<'a> {
/// Write a frame into the content stream.
fn write_frame(&mut self, x: f32, y: f32, frame: &Frame) {
if frame.clips {
let w = frame.size.w.to_f32();
let h = frame.size.h.to_f32();
self.content.save_state();
self.content.move_to(x, y);
self.content.line_to(x + w, y);
self.content.line_to(x + w, y - h);
self.content.line_to(x, y - h);
self.content.clip_nonzero();
self.content.end_path();
}
for (offset, element) in &frame.elements {
// Make sure the content stream is in the correct state.
match element {
@ -401,10 +389,10 @@ impl<'a> PageExporter<'a> {
let y = y - offset.y.to_f32();
match *element {
Element::Group(ref group) => self.write_group(x, y, group),
Element::Text(ref text) => self.write_text(x, y, text),
Element::Shape(ref shape) => self.write_shape(x, y, shape),
Element::Image(id, size) => self.write_image(x, y, id, size),
Element::Frame(ref frame) => self.write_frame(x, y, frame),
Element::Link(_, _) => {}
}
}
@ -412,8 +400,24 @@ impl<'a> PageExporter<'a> {
if self.in_text_state {
self.content.end_text();
}
}
if frame.clips {
fn write_group(&mut self, x: f32, y: f32, group: &Group) {
if group.clips {
let w = group.frame.size.w.to_f32();
let h = group.frame.size.h.to_f32();
self.content.save_state();
self.content.move_to(x, y);
self.content.line_to(x + w, y);
self.content.line_to(x + w, y - h);
self.content.line_to(x, y - h);
self.content.clip_nonzero();
self.content.end_path();
}
self.write_frame(x, y, &group.frame);
if group.clips {
self.content.restore_state();
}
}

View File

@ -16,8 +16,6 @@ pub struct Frame {
pub size: Size,
/// The baseline of the frame measured from the top.
pub baseline: Length,
/// Whether this frame should be a clipping boundary.
pub clips: bool,
/// The elements composing this layout.
pub elements: Vec<(Point, Element)>,
}
@ -27,12 +25,7 @@ impl Frame {
#[track_caller]
pub fn new(size: Size, baseline: Length) -> Self {
assert!(size.is_finite());
Self {
size,
baseline,
elements: vec![],
clips: false,
}
Self { size, baseline, elements: vec![] }
}
/// Add an element at a position in the background.
@ -45,17 +38,16 @@ impl Frame {
self.elements.push((pos, element));
}
/// Add a frame element.
pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) {
self.elements.push((pos, Element::Frame(subframe)))
/// Add a group element.
pub fn push_frame(&mut self, pos: Point, frame: Rc<Self>) {
self.elements
.push((pos, Element::Group(Group { frame, clips: false })))
}
/// Add all elements of another frame, placing them relative to the given
/// position.
pub fn merge_frame(&mut self, pos: Point, subframe: Self) {
if subframe.clips {
self.push_frame(pos, Rc::new(subframe));
} else if pos == Point::zero() && self.elements.is_empty() {
if pos == Point::zero() && self.elements.is_empty() {
self.elements = subframe.elements;
} else {
for (subpos, child) in subframe.elements {
@ -90,7 +82,6 @@ impl Debug for Frame {
f.debug_struct("Frame")
.field("size", &self.size)
.field("baseline", &self.baseline)
.field("clips", &self.clips)
.field("children", &Children(&self.elements))
.finish()
}
@ -107,9 +98,9 @@ impl<'a> Iterator for Elements<'a> {
fn next(&mut self) -> Option<Self::Item> {
let (cursor, offset, frame) = self.stack.last_mut()?;
if let Some((pos, e)) = frame.elements.get(*cursor) {
if let Element::Frame(f) = e {
if let Element::Group(g) = e {
let new_offset = *offset + *pos;
self.stack.push((0, new_offset, f.as_ref()));
self.stack.push((0, new_offset, g.frame.as_ref()));
self.next()
} else {
*cursor += 1;
@ -128,6 +119,8 @@ impl<'a> Iterator for Elements<'a> {
/// The building block frames are composed of.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Element {
/// A group of elements.
Group(Group),
/// A run of shaped text.
Text(Text),
/// A geometric shape with optional fill and stroke.
@ -136,8 +129,15 @@ pub enum Element {
Image(ImageId, Size),
/// A link to an external resource and its trigger region.
Link(String, Size),
/// A subframe, which can be a clipping boundary.
Frame(Rc<Frame>),
}
/// A group of elements with optional clipping.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Group {
/// The group's frame.
pub frame: Rc<Frame>,
/// Whether the frame should be a clipping boundary.
pub clips: bool,
}
/// A run of shaped text.

View File

@ -75,12 +75,22 @@ impl Layout for ImageNode {
// The position of the image so that it is centered in the canvas.
let mut frame = Frame::new(canvas, canvas.h);
frame.clips = self.fit == ImageFit::Cover;
frame.push(
(canvas - size).to_point() / 2.0,
Element::Image(self.id, size),
);
// Create a clipping group if the image mode is `cover`.
if self.fit == ImageFit::Cover {
let group = Group {
frame: Rc::new(frame),
clips: self.fit == ImageFit::Cover,
};
frame = Frame::new(canvas, canvas.h);
frame.push(Point::zero(), Element::Group(group));
}
let mut cts = Constraints::new(regions.expand);
cts.exact = regions.current.to_spec().map(Some);
vec![frame.constrain(cts)]

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@ -0,0 +1,18 @@
// Test placing a background image on a page.
---
#page(paper: "a10", flip: true)
#font(fill: white)
#place(
dx: -10pt,
dy: -10pt,
image(
"../../res/tiger.jpg",
fit: "cover",
width: 100% + 20pt,
height: 100% + 20pt,
)
)
#align(bottom, right)[
_Welcome to_ #underline[*Tigerland*]
]

View File

@ -12,7 +12,7 @@ use walkdir::WalkDir;
use typst::diag::Error;
use typst::eval::{Smart, Value};
use typst::font::Face;
use typst::frame::{Element, Frame, Geometry, Shape, Stroke, Text};
use typst::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
use typst::geom::{self, Color, Length, Paint, PathElement, RgbaColor, Sides, Size};
use typst::image::Image;
use typst::layout::layout;
@ -431,30 +431,15 @@ fn draw_frame(
ctx: &Context,
frame: &Frame,
) {
let mut storage;
let mask = if frame.clips {
let w = frame.size.w.to_f32();
let h = frame.size.h.to_f32();
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
let path = sk::PathBuilder::from_rect(rect).transform(ts).unwrap();
let rule = sk::FillRule::default();
storage = mask.clone();
if storage.intersect_path(&path, rule, false).is_none() {
// Fails if clipping rect is empty. In that case we just clip everything
// by returning.
return;
}
&storage
} else {
mask
};
for (pos, element) in &frame.elements {
let x = pos.x.to_f32();
let y = pos.y.to_f32();
let ts = ts.pre_translate(x, y);
match *element {
Element::Group(ref group) => {
draw_group(canvas, ts, mask, ctx, group);
}
Element::Text(ref text) => {
draw_text(canvas, ts, mask, ctx.fonts.get(text.face_id), text);
}
@ -469,13 +454,35 @@ fn draw_frame(
let shape = Shape::filled(Geometry::Rect(s), fill);
draw_shape(canvas, ts, mask, &shape);
}
Element::Frame(ref frame) => {
draw_frame(canvas, ts, mask, ctx, frame);
}
}
}
}
fn draw_group(
canvas: &mut sk::Pixmap,
ts: sk::Transform,
mask: &sk::ClipMask,
ctx: &Context,
group: &Group,
) {
if group.clips {
let w = group.frame.size.w.to_f32();
let h = group.frame.size.h.to_f32();
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
let path = sk::PathBuilder::from_rect(rect).transform(ts).unwrap();
let rule = sk::FillRule::default();
let mut mask = mask.clone();
if mask.intersect_path(&path, rule, false).is_none() {
// Fails if clipping rect is empty. In that case we just clip everything
// by returning.
return;
}
draw_frame(canvas, ts, &mask, ctx, &group.frame);
} else {
draw_frame(canvas, ts, mask, ctx, &group.frame);
};
}
fn draw_text(
canvas: &mut sk::Pixmap,
ts: sk::Transform,