mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Welcome to Tigerland
This commit is contained in:
parent
ed50661378
commit
0a974d86ba
@ -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();
|
||||
}
|
||||
}
|
||||
|
38
src/frame.rs
38
src/frame.rs
@ -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.
|
||||
|
@ -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)]
|
||||
|
BIN
tests/ref/layout/background.png
Normal file
BIN
tests/ref/layout/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
18
tests/typ/layout/background.typ
Normal file
18
tests/typ/layout/background.typ
Normal 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*]
|
||||
]
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user