mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
commit
bfc2f5aefc
@ -13,9 +13,10 @@ use pdf_writer::{
|
|||||||
};
|
};
|
||||||
use ttf_parser::{name_id, GlyphId};
|
use ttf_parser::{name_id, GlyphId};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
use crate::env::{Env, ImageResource, ResourceId};
|
use crate::env::{Env, ImageResource, ResourceId};
|
||||||
use crate::geom::Length;
|
use crate::geom::Length;
|
||||||
use crate::layout::{Element, Frame};
|
use crate::layout::{Element, Fill, Frame, Shape};
|
||||||
|
|
||||||
/// Export a collection of frames into a _PDF_ document.
|
/// Export a collection of frames into a _PDF_ document.
|
||||||
///
|
///
|
||||||
@ -57,6 +58,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
images.insert(image.res);
|
images.insert(image.res);
|
||||||
}
|
}
|
||||||
|
Element::Geometry(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,6 +129,56 @@ impl<'a> PdfExporter<'a> {
|
|||||||
fn write_page(&mut self, id: Ref, page: &'a Frame) {
|
fn write_page(&mut self, id: Ref, page: &'a Frame) {
|
||||||
let mut content = Content::new();
|
let mut content = Content::new();
|
||||||
|
|
||||||
|
for (pos, element) in &page.elements {
|
||||||
|
match element {
|
||||||
|
Element::Image(image) => {
|
||||||
|
let name = format!("Im{}", self.images.map(image.res));
|
||||||
|
let size = image.size;
|
||||||
|
let x = pos.x.to_pt() as f32;
|
||||||
|
let y = (page.size.height - pos.y - size.height).to_pt() as f32;
|
||||||
|
let w = size.width.to_pt() as f32;
|
||||||
|
let h = size.height.to_pt() as f32;
|
||||||
|
|
||||||
|
content.save_state();
|
||||||
|
content.matrix(w, 0.0, 0.0, h, x, y);
|
||||||
|
content.x_object(Name(name.as_bytes()));
|
||||||
|
content.restore_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
Element::Geometry(geometry) => {
|
||||||
|
content.save_state();
|
||||||
|
|
||||||
|
match geometry.fill {
|
||||||
|
Fill::Color(Color::Rgba(c)) => {
|
||||||
|
content.fill_rgb(
|
||||||
|
c.r as f32 / 255.,
|
||||||
|
c.g as f32 / 255.,
|
||||||
|
c.b as f32 / 255.,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Fill::Image(_) => todo!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = pos.x.to_pt() as f32;
|
||||||
|
|
||||||
|
match &geometry.shape {
|
||||||
|
Shape::Rect(r) => {
|
||||||
|
let w = r.width.to_pt() as f32;
|
||||||
|
let h = r.height.to_pt() as f32;
|
||||||
|
let y = (page.size.height - pos.y - r.height).to_pt() as f32;
|
||||||
|
if w > 0.0 && h > 0.0 {
|
||||||
|
content.rect(x, y, w, h, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content.restore_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We only write font switching actions when the used face changes. To
|
// We only write font switching actions when the used face changes. To
|
||||||
// do that, we need to remember the active face.
|
// do that, we need to remember the active face.
|
||||||
let mut face = FaceId::MAX;
|
let mut face = FaceId::MAX;
|
||||||
@ -153,22 +205,6 @@ impl<'a> PdfExporter<'a> {
|
|||||||
|
|
||||||
drop(text);
|
drop(text);
|
||||||
|
|
||||||
for (pos, element) in &page.elements {
|
|
||||||
if let Element::Image(image) = element {
|
|
||||||
let name = format!("Im{}", self.images.map(image.res));
|
|
||||||
let size = image.size;
|
|
||||||
let x = pos.x.to_pt() as f32;
|
|
||||||
let y = (page.size.height - pos.y - size.height).to_pt() as f32;
|
|
||||||
let w = size.width.to_pt() as f32;
|
|
||||||
let h = size.height.to_pt() as f32;
|
|
||||||
|
|
||||||
content.save_state();
|
|
||||||
content.matrix(w, 0.0, 0.0, h, x, y);
|
|
||||||
content.x_object(Name(name.as_bytes()));
|
|
||||||
content.restore_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.writer.stream(id, &content.finish());
|
self.writer.stream(id, &content.finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
37
src/layout/background.rs
Normal file
37
src/layout/background.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// A node that represents a rectangular box.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct NodeBackground {
|
||||||
|
/// The background fill.
|
||||||
|
pub fill: Fill,
|
||||||
|
/// The child node to be filled in.
|
||||||
|
pub child: Node,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout for NodeBackground {
|
||||||
|
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||||
|
let mut layouted = self.child.layout(ctx, areas);
|
||||||
|
|
||||||
|
if let Some(first) = layouted.frames_mut().first_mut() {
|
||||||
|
first.elements.insert(
|
||||||
|
0,
|
||||||
|
(
|
||||||
|
Point::ZERO,
|
||||||
|
Element::Geometry(Geometry {
|
||||||
|
shape: Shape::Rect(first.size),
|
||||||
|
fill: self.fill.clone(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
layouted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NodeBackground> for NodeAny {
|
||||||
|
fn from(background: NodeBackground) -> Self {
|
||||||
|
Self::new(background)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
//! Layouting.
|
//! Layouting.
|
||||||
|
|
||||||
|
mod background;
|
||||||
mod fixed;
|
mod fixed;
|
||||||
mod node;
|
mod node;
|
||||||
mod pad;
|
mod pad;
|
||||||
@ -8,10 +9,12 @@ mod spacing;
|
|||||||
mod stack;
|
mod stack;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
use crate::env::{Env, ResourceId};
|
use crate::env::{Env, ResourceId};
|
||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
use crate::shaping::Shaped;
|
use crate::shaping::Shaped;
|
||||||
|
|
||||||
|
pub use background::*;
|
||||||
pub use fixed::*;
|
pub use fixed::*;
|
||||||
pub use node::*;
|
pub use node::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
@ -54,7 +57,7 @@ impl NodePages {
|
|||||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Frame> {
|
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Frame> {
|
||||||
let areas = Areas::repeat(self.size);
|
let areas = Areas::repeat(self.size);
|
||||||
let layouted = self.child.layout(ctx, &areas);
|
let layouted = self.child.layout(ctx, &areas);
|
||||||
layouted.frames()
|
layouted.into_frames()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,9 +160,27 @@ pub enum Layouted {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Layouted {
|
impl Layouted {
|
||||||
/// Return all frames contained in this variant (zero, one or arbitrarily
|
/// Return a reference to all frames contained in this variant (zero, one or
|
||||||
/// many).
|
/// arbitrarily many).
|
||||||
pub fn frames(self) -> Vec<Frame> {
|
pub fn frames(&self) -> &[Frame] {
|
||||||
|
match self {
|
||||||
|
Self::Spacing(_) => &[],
|
||||||
|
Self::Frame(frame, _) => std::slice::from_ref(frame),
|
||||||
|
Self::Frames(frames, _) => frames,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a mutable reference to all frames contained in this variant.
|
||||||
|
pub fn frames_mut(&mut self) -> &mut [Frame] {
|
||||||
|
match self {
|
||||||
|
Self::Spacing(_) => &mut [],
|
||||||
|
Self::Frame(frame, _) => std::slice::from_mut(frame),
|
||||||
|
Self::Frames(frames, _) => frames,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all frames contained in this varian.
|
||||||
|
pub fn into_frames(self) -> Vec<Frame> {
|
||||||
match self {
|
match self {
|
||||||
Self::Spacing(_) => vec![],
|
Self::Spacing(_) => vec![],
|
||||||
Self::Frame(frame, _) => vec![frame],
|
Self::Frame(frame, _) => vec![frame],
|
||||||
@ -204,6 +225,37 @@ pub enum Element {
|
|||||||
Text(Shaped),
|
Text(Shaped),
|
||||||
/// An image.
|
/// An image.
|
||||||
Image(Image),
|
Image(Image),
|
||||||
|
/// Some shape that could hold another frame.
|
||||||
|
Geometry(Geometry),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of graphic fill to be applied to a [`Shape`].
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Fill {
|
||||||
|
/// The fill is a color.
|
||||||
|
Color(Color),
|
||||||
|
/// The fill is an image.
|
||||||
|
Image(Image),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shape with some kind of fill.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Geometry {
|
||||||
|
/// The shape to draw.
|
||||||
|
pub shape: Shape,
|
||||||
|
/// How the shape looks on the inside.
|
||||||
|
//
|
||||||
|
// TODO: This could be made into a Vec<Fill> or something such that
|
||||||
|
// the user can compose multiple fills with alpha values less
|
||||||
|
// than one to achieve cool effects.
|
||||||
|
pub fill: Fill,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some shape.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Shape {
|
||||||
|
/// A rectangle.
|
||||||
|
Rect(Size),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An image element.
|
/// An image element.
|
||||||
|
@ -15,14 +15,8 @@ impl Layout for NodePad {
|
|||||||
let areas = shrink(areas, self.padding);
|
let areas = shrink(areas, self.padding);
|
||||||
|
|
||||||
let mut layouted = self.child.layout(ctx, &areas);
|
let mut layouted = self.child.layout(ctx, &areas);
|
||||||
match &mut layouted {
|
for frame in layouted.frames_mut() {
|
||||||
Layouted::Spacing(_) => {}
|
pad(frame, self.padding);
|
||||||
Layouted::Frame(frame, _) => pad(frame, self.padding),
|
|
||||||
Layouted::Frames(frames, _) => {
|
|
||||||
for frame in frames {
|
|
||||||
pad(frame, self.padding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layouted
|
layouted
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
use crate::eval::Softness;
|
use crate::layout::{Expansion, Fill, NodeFixed, NodeSpacing, NodeStack};
|
||||||
use crate::layout::{Expansion, NodeFixed, NodeSpacing, NodeStack};
|
|
||||||
use crate::paper::{Paper, PaperClass};
|
use crate::paper::{Paper, PaperClass};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::{eval::Softness, layout::NodeBackground};
|
||||||
|
|
||||||
/// `align`: Align content along the layouting axes.
|
/// `align`: Align content along the layouting axes.
|
||||||
///
|
///
|
||||||
@ -175,6 +175,7 @@ impl Display for Alignment {
|
|||||||
/// # Named arguments
|
/// # Named arguments
|
||||||
/// - Width of the box: `width`, of type `linear` relative to parent width.
|
/// - Width of the box: `width`, of type `linear` relative to parent width.
|
||||||
/// - Height of the box: `height`, of type `linear` relative to parent height.
|
/// - Height of the box: `height`, of type `linear` relative to parent height.
|
||||||
|
/// - Background color of the box: `color`, of type `color`.
|
||||||
pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||||
let snapshot = ctx.state.clone();
|
let snapshot = ctx.state.clone();
|
||||||
|
|
||||||
@ -182,6 +183,7 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
let height = args.get(ctx, "height");
|
let height = args.get(ctx, "height");
|
||||||
let main = args.get(ctx, "main-dir");
|
let main = args.get(ctx, "main-dir");
|
||||||
let cross = args.get(ctx, "cross-dir");
|
let cross = args.get(ctx, "cross-dir");
|
||||||
|
let color = args.get(ctx, "color");
|
||||||
|
|
||||||
ctx.set_dirs(Gen::new(main, cross));
|
ctx.set_dirs(Gen::new(main, cross));
|
||||||
|
|
||||||
@ -199,11 +201,21 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
|
let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
|
||||||
let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
|
let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
|
||||||
|
|
||||||
ctx.push(NodeFixed {
|
let fixed_node = NodeFixed {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
child: NodeStack { dirs, align, expand, children }.into(),
|
child: NodeStack { dirs, align, expand, children }.into(),
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if let Some(color) = color {
|
||||||
|
ctx.push(NodeBackground {
|
||||||
|
fill: Fill::Color(color),
|
||||||
|
child: Node::Any(fixed_node.into()),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ctx.push(fixed_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ctx.state = snapshot;
|
ctx.state = snapshot;
|
||||||
Value::None
|
Value::None
|
||||||
|
BIN
tests/library/ref/box.png
Normal file
BIN
tests/library/ref/box.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
23
tests/library/typ/box.typ
Normal file
23
tests/library/typ/box.typ
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#[page "a5", flip: true]
|
||||||
|
|
||||||
|
// Rectangle with width, should have paragraph height
|
||||||
|
#[box width: 2cm, color: #9650D6][aa]
|
||||||
|
|
||||||
|
Sometimes there is no box
|
||||||
|
|
||||||
|
// Rectangle with height, should span line
|
||||||
|
#[box height: 2cm, width: 100%, color: #734CED][bb]
|
||||||
|
|
||||||
|
// Empty rectangle with width and height
|
||||||
|
#[box width: 6cm, height: 12pt, color: #CB4CED]
|
||||||
|
|
||||||
|
// This empty rectangle should not be displayed
|
||||||
|
#[box width: 2in, color: #ff0000]
|
||||||
|
|
||||||
|
// This one should be
|
||||||
|
#[box height: 15mm, width: 100%, color: #494DE3]
|
||||||
|
|
||||||
|
// These are in a row!
|
||||||
|
#[box width: 2in, height: 10pt, color: #D6CD67]
|
||||||
|
#[box width: 2in, height: 10pt, color: #EDD466]
|
||||||
|
#[box width: 2in, height: 10pt, color: #E3BE62]
|
@ -20,7 +20,7 @@ use typst::eval::{Args, EvalContext, Scope, State, Value, ValueFunc};
|
|||||||
use typst::export::pdf;
|
use typst::export::pdf;
|
||||||
use typst::font::FsIndexExt;
|
use typst::font::FsIndexExt;
|
||||||
use typst::geom::{Length, Point, Sides, Size, Spec};
|
use typst::geom::{Length, Point, Sides, Size, Spec};
|
||||||
use typst::layout::{Element, Expansion, Frame, Image};
|
use typst::layout::{Element, Expansion, Fill, Frame, Geometry, Image, Shape};
|
||||||
use typst::library;
|
use typst::library;
|
||||||
use typst::parse::{LineMap, Scanner};
|
use typst::parse::{LineMap, Scanner};
|
||||||
use typst::pretty::{Pretty, Printer};
|
use typst::pretty::{Pretty, Printer};
|
||||||
@ -409,6 +409,9 @@ fn draw(frames: &[Frame], env: &Env, pixel_per_pt: f32) -> Canvas {
|
|||||||
Element::Image(image) => {
|
Element::Image(image) => {
|
||||||
draw_image(&mut canvas, pos, env, image);
|
draw_image(&mut canvas, pos, env, image);
|
||||||
}
|
}
|
||||||
|
Element::Geometry(geom) => {
|
||||||
|
draw_geometry(&mut canvas, pos, env, geom);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,6 +447,27 @@ fn draw_text(canvas: &mut Canvas, pos: Point, env: &Env, shaped: &Shaped) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_geometry(canvas: &mut Canvas, pos: Point, _: &Env, element: &Geometry) {
|
||||||
|
let x = pos.x.to_pt() as f32;
|
||||||
|
let y = pos.y.to_pt() as f32;
|
||||||
|
|
||||||
|
let mut paint = Paint::default();
|
||||||
|
|
||||||
|
match &element.fill {
|
||||||
|
Fill::Color(c) => match c {
|
||||||
|
typst::color::Color::Rgba(c) => paint.set_color_rgba8(c.r, c.g, c.b, c.a),
|
||||||
|
},
|
||||||
|
Fill::Image(_) => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match &element.shape {
|
||||||
|
Shape::Rect(s) => {
|
||||||
|
let (w, h) = (s.width.to_pt() as f32, s.height.to_pt() as f32);
|
||||||
|
canvas.fill_rect(Rect::from_xywh(x, y, w, h).unwrap(), &paint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, element: &Image) {
|
fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, element: &Image) {
|
||||||
let img = &env.resources.loaded::<ImageResource>(element.res);
|
let img = &env.resources.loaded::<ImageResource>(element.res);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user