Image fit modes
@ -14,7 +14,7 @@ use ttf_parser::{name_id, GlyphId, Tag};
|
|||||||
|
|
||||||
use super::subset;
|
use super::subset;
|
||||||
use crate::font::{find_name, FaceId, FontStore};
|
use crate::font::{find_name, FaceId, FontStore};
|
||||||
use crate::frame::{Element, Frame, Geometry};
|
use crate::frame::{Element, Frame, Geometry, Text};
|
||||||
use crate::geom::{self, Color, Em, Length, Paint, Size};
|
use crate::geom::{self, Color, Em, Length, Paint, Size};
|
||||||
use crate::image::{Image, ImageId, ImageStore};
|
use crate::image::{Image, ImageId, ImageStore};
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
@ -36,16 +36,16 @@ struct PdfExporter<'a> {
|
|||||||
frames: &'a [Rc<Frame>],
|
frames: &'a [Rc<Frame>],
|
||||||
fonts: &'a FontStore,
|
fonts: &'a FontStore,
|
||||||
images: &'a ImageStore,
|
images: &'a ImageStore,
|
||||||
glyphs: HashMap<FaceId, HashSet<u16>>,
|
|
||||||
font_map: Remapper<FaceId>,
|
font_map: Remapper<FaceId>,
|
||||||
image_map: Remapper<ImageId>,
|
image_map: Remapper<ImageId>,
|
||||||
|
glyphs: HashMap<FaceId, HashSet<u16>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PdfExporter<'a> {
|
impl<'a> PdfExporter<'a> {
|
||||||
fn new(ctx: &'a Context, frames: &'a [Rc<Frame>]) -> Self {
|
fn new(ctx: &'a Context, frames: &'a [Rc<Frame>]) -> Self {
|
||||||
let mut glyphs = HashMap::<FaceId, HashSet<u16>>::new();
|
|
||||||
let mut font_map = Remapper::new();
|
let mut font_map = Remapper::new();
|
||||||
let mut image_map = Remapper::new();
|
let mut image_map = Remapper::new();
|
||||||
|
let mut glyphs = HashMap::<FaceId, HashSet<u16>>::new();
|
||||||
let mut alpha_masks = 0;
|
let mut alpha_masks = 0;
|
||||||
|
|
||||||
for frame in frames {
|
for frame in frames {
|
||||||
@ -56,7 +56,6 @@ impl<'a> PdfExporter<'a> {
|
|||||||
let set = glyphs.entry(text.face_id).or_default();
|
let set = glyphs.entry(text.face_id).or_default();
|
||||||
set.extend(text.glyphs.iter().map(|g| g.id));
|
set.extend(text.glyphs.iter().map(|g| g.id));
|
||||||
}
|
}
|
||||||
Element::Geometry(_, _) => {}
|
|
||||||
Element::Image(id, _) => {
|
Element::Image(id, _) => {
|
||||||
let img = ctx.images.get(id);
|
let img = ctx.images.get(id);
|
||||||
if img.buf.color().has_alpha() {
|
if img.buf.color().has_alpha() {
|
||||||
@ -64,7 +63,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
image_map.insert(id);
|
image_map.insert(id);
|
||||||
}
|
}
|
||||||
Element::Link(_, _) => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,8 +119,8 @@ impl<'a> PdfExporter<'a> {
|
|||||||
for ((page_id, content_id), page) in
|
for ((page_id, content_id), page) in
|
||||||
self.refs.pages().zip(self.refs.contents()).zip(self.frames)
|
self.refs.pages().zip(self.refs.contents()).zip(self.frames)
|
||||||
{
|
{
|
||||||
let w = page.size.w.to_pt() as f32;
|
let w = page.size.w.to_f32();
|
||||||
let h = page.size.h.to_pt() as f32;
|
let h = page.size.h.to_f32();
|
||||||
|
|
||||||
let mut page_writer = self.writer.page(page_id);
|
let mut page_writer = self.writer.page(page_id);
|
||||||
page_writer
|
page_writer
|
||||||
@ -131,10 +130,10 @@ impl<'a> PdfExporter<'a> {
|
|||||||
let mut annotations = page_writer.annotations();
|
let mut annotations = page_writer.annotations();
|
||||||
for (pos, element) in page.elements() {
|
for (pos, element) in page.elements() {
|
||||||
if let Element::Link(href, size) = element {
|
if let Element::Link(href, size) = element {
|
||||||
let x = pos.x.to_pt() as f32;
|
let x = pos.x.to_f32();
|
||||||
let y = (page.size.h - pos.y).to_pt() as f32;
|
let y = (page.size.h - pos.y).to_f32();
|
||||||
let w = size.w.to_pt() as f32;
|
let w = size.w.to_f32();
|
||||||
let h = size.h.to_pt() as f32;
|
let h = size.h.to_f32();
|
||||||
|
|
||||||
annotations
|
annotations
|
||||||
.push()
|
.push()
|
||||||
@ -158,148 +157,9 @@ 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 writer = PageExporter::new(self);
|
||||||
|
let content = writer.write(page);
|
||||||
// We only write font switching actions when the used face changes. To
|
self.writer.stream(id, &deflate(&content)).filter(Filter::FlateDecode);
|
||||||
// do that, we need to remember the active face.
|
|
||||||
let mut face_id = None;
|
|
||||||
let mut size = Length::zero();
|
|
||||||
let mut fill: Option<Paint> = None;
|
|
||||||
let mut in_text_state = false;
|
|
||||||
|
|
||||||
for (pos, element) in page.elements() {
|
|
||||||
// Make sure the content stream is in the correct state.
|
|
||||||
match element {
|
|
||||||
Element::Text(_) if !in_text_state => {
|
|
||||||
content.begin_text();
|
|
||||||
in_text_state = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Element::Geometry(..) | Element::Image(..) if in_text_state => {
|
|
||||||
content.end_text();
|
|
||||||
in_text_state = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let x = pos.x.to_pt() as f32;
|
|
||||||
let y = (page.size.h - pos.y).to_pt() as f32;
|
|
||||||
|
|
||||||
match *element {
|
|
||||||
Element::Text(ref text) => {
|
|
||||||
if fill != Some(text.fill) {
|
|
||||||
write_fill(&mut content, text.fill);
|
|
||||||
fill = Some(text.fill);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, also check if we need to issue a font switching
|
|
||||||
// action.
|
|
||||||
if face_id != Some(text.face_id) || text.size != size {
|
|
||||||
face_id = Some(text.face_id);
|
|
||||||
size = text.size;
|
|
||||||
|
|
||||||
let name = format_eco!("F{}", self.font_map.map(text.face_id));
|
|
||||||
content.set_font(Name(name.as_bytes()), size.to_pt() as f32);
|
|
||||||
}
|
|
||||||
|
|
||||||
let face = self.fonts.get(text.face_id);
|
|
||||||
|
|
||||||
// Position the text.
|
|
||||||
content.set_text_matrix([1.0, 0.0, 0.0, 1.0, x, y]);
|
|
||||||
|
|
||||||
let mut positioned = content.show_positioned();
|
|
||||||
let mut items = positioned.items();
|
|
||||||
let mut adjustment = Em::zero();
|
|
||||||
let mut encoded = vec![];
|
|
||||||
|
|
||||||
// Write the glyphs with kerning adjustments.
|
|
||||||
for glyph in &text.glyphs {
|
|
||||||
adjustment += glyph.x_offset;
|
|
||||||
|
|
||||||
if !adjustment.is_zero() {
|
|
||||||
if !encoded.is_empty() {
|
|
||||||
items.show(Str(&encoded));
|
|
||||||
encoded.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
items.adjust(-adjustment.to_pdf());
|
|
||||||
adjustment = Em::zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
encoded.push((glyph.id >> 8) as u8);
|
|
||||||
encoded.push((glyph.id & 0xff) as u8);
|
|
||||||
|
|
||||||
if let Some(advance) = face.advance(glyph.id) {
|
|
||||||
adjustment += glyph.x_advance - advance;
|
|
||||||
}
|
|
||||||
|
|
||||||
adjustment -= glyph.x_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !encoded.is_empty() {
|
|
||||||
items.show(Str(&encoded));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Element::Geometry(ref geometry, paint) => {
|
|
||||||
content.save_state();
|
|
||||||
|
|
||||||
match *geometry {
|
|
||||||
Geometry::Rect(Size { w, h }) => {
|
|
||||||
let w = w.to_pt() as f32;
|
|
||||||
let h = h.to_pt() as f32;
|
|
||||||
if w > 0.0 && h > 0.0 {
|
|
||||||
write_fill(&mut content, paint);
|
|
||||||
content.rect(x, y - h, w, h);
|
|
||||||
content.fill_nonzero();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Geometry::Ellipse(size) => {
|
|
||||||
let path = geom::Path::ellipse(size);
|
|
||||||
write_fill(&mut content, paint);
|
|
||||||
write_path(&mut content, x, y, &path);
|
|
||||||
}
|
|
||||||
Geometry::Line(target, thickness) => {
|
|
||||||
write_stroke(&mut content, paint, thickness.to_pt() as f32);
|
|
||||||
content.move_to(x, y);
|
|
||||||
content.line_to(
|
|
||||||
x + target.x.to_pt() as f32,
|
|
||||||
y - target.y.to_pt() as f32,
|
|
||||||
);
|
|
||||||
content.stroke();
|
|
||||||
}
|
|
||||||
Geometry::Path(ref path) => {
|
|
||||||
write_fill(&mut content, paint);
|
|
||||||
write_path(&mut content, x, y, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content.restore_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
Element::Image(id, Size { w, h }) => {
|
|
||||||
let name = format!("Im{}", self.image_map.map(id));
|
|
||||||
let w = w.to_pt() as f32;
|
|
||||||
let h = h.to_pt() as f32;
|
|
||||||
|
|
||||||
content.save_state();
|
|
||||||
content.concat_matrix([w, 0.0, 0.0, h, x, y - h]);
|
|
||||||
content.x_object(Name(name.as_bytes()));
|
|
||||||
content.restore_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
Element::Link(_, _) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if in_text_state {
|
|
||||||
content.end_text();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.writer
|
|
||||||
.stream(id, &deflate(&content.finish()))
|
|
||||||
.filter(Filter::FlateDecode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_fonts(&mut self) {
|
fn write_fonts(&mut self) {
|
||||||
@ -350,7 +210,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
let num_glyphs = ttf.number_of_glyphs();
|
let num_glyphs = ttf.number_of_glyphs();
|
||||||
(0 .. num_glyphs).map(|g| {
|
(0 .. num_glyphs).map(|g| {
|
||||||
let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0);
|
let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0);
|
||||||
face.to_em(x).to_pdf()
|
face.to_em(x).to_font_units()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -363,16 +223,16 @@ impl<'a> PdfExporter<'a> {
|
|||||||
|
|
||||||
let global_bbox = ttf.global_bounding_box();
|
let global_bbox = ttf.global_bounding_box();
|
||||||
let bbox = Rect::new(
|
let bbox = Rect::new(
|
||||||
face.to_em(global_bbox.x_min).to_pdf(),
|
face.to_em(global_bbox.x_min).to_font_units(),
|
||||||
face.to_em(global_bbox.y_min).to_pdf(),
|
face.to_em(global_bbox.y_min).to_font_units(),
|
||||||
face.to_em(global_bbox.x_max).to_pdf(),
|
face.to_em(global_bbox.x_max).to_font_units(),
|
||||||
face.to_em(global_bbox.y_max).to_pdf(),
|
face.to_em(global_bbox.y_max).to_font_units(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let italic_angle = ttf.italic_angle().unwrap_or(0.0);
|
let italic_angle = ttf.italic_angle().unwrap_or(0.0);
|
||||||
let ascender = face.ascender.to_pdf();
|
let ascender = face.ascender.to_font_units();
|
||||||
let descender = face.descender.to_pdf();
|
let descender = face.descender.to_font_units();
|
||||||
let cap_height = face.cap_height.to_pdf();
|
let cap_height = face.cap_height.to_font_units();
|
||||||
let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0);
|
let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0);
|
||||||
|
|
||||||
// Write the font descriptor (contains metrics about the font).
|
// Write the font descriptor (contains metrics about the font).
|
||||||
@ -474,45 +334,244 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a fill change into a content stream.
|
/// A writer for the contents of a single page.
|
||||||
fn write_fill(content: &mut Content, fill: Paint) {
|
struct PageExporter<'a> {
|
||||||
let Paint::Color(Color::Rgba(c)) = fill;
|
fonts: &'a FontStore,
|
||||||
content.set_fill_rgb(c.r as f32 / 255.0, c.g as f32 / 255.0, c.b as f32 / 255.0);
|
font_map: &'a Remapper<FaceId>,
|
||||||
|
image_map: &'a Remapper<ImageId>,
|
||||||
|
content: Content,
|
||||||
|
in_text_state: bool,
|
||||||
|
face_id: Option<FaceId>,
|
||||||
|
font_size: Length,
|
||||||
|
font_fill: Option<Paint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a stroke change into a content stream.
|
impl<'a> PageExporter<'a> {
|
||||||
fn write_stroke(content: &mut Content, stroke: Paint, thickness: f32) {
|
/// Create a new page exporter.
|
||||||
match stroke {
|
fn new(exporter: &'a PdfExporter) -> Self {
|
||||||
Paint::Color(Color::Rgba(c)) => {
|
Self {
|
||||||
content.set_stroke_rgb(
|
fonts: exporter.fonts,
|
||||||
c.r as f32 / 255.0,
|
font_map: &exporter.font_map,
|
||||||
c.g as f32 / 255.0,
|
image_map: &exporter.image_map,
|
||||||
c.b as f32 / 255.0,
|
content: Content::new(),
|
||||||
);
|
in_text_state: false,
|
||||||
|
face_id: None,
|
||||||
|
font_size: Length::zero(),
|
||||||
|
font_fill: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content.set_line_width(thickness);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a path into a content stream.
|
/// Write the page frame into the content stream.
|
||||||
fn write_path(content: &mut Content, x: f32, y: f32, path: &geom::Path) {
|
fn write(mut self, frame: &Frame) -> Vec<u8> {
|
||||||
let f = |length: Length| length.to_pt() as f32;
|
self.write_frame(0.0, frame.size.h.to_f32(), frame);
|
||||||
for elem in &path.0 {
|
self.content.finish()
|
||||||
match elem {
|
}
|
||||||
geom::PathElement::MoveTo(p) => content.move_to(x + f(p.x), y + f(p.y)),
|
|
||||||
geom::PathElement::LineTo(p) => content.line_to(x + f(p.x), y + f(p.y)),
|
/// Write a frame into the content stream.
|
||||||
geom::PathElement::CubicTo(p1, p2, p3) => content.cubic_to(
|
fn write_frame(&mut self, x: f32, y: f32, frame: &Frame) {
|
||||||
x + f(p1.x),
|
if frame.clips {
|
||||||
y + f(p1.y),
|
let w = frame.size.w.to_f32();
|
||||||
x + f(p2.x),
|
let h = frame.size.h.to_f32();
|
||||||
y + f(p2.y),
|
self.content.save_state();
|
||||||
x + f(p3.x),
|
self.content.move_to(x, y);
|
||||||
y + f(p3.y),
|
self.content.line_to(x + w, y);
|
||||||
),
|
self.content.line_to(x + w, y - h);
|
||||||
geom::PathElement::ClosePath => content.close_path(),
|
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 {
|
||||||
|
Element::Text(_) if !self.in_text_state => {
|
||||||
|
self.content.begin_text();
|
||||||
|
self.in_text_state = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Element::Geometry(..) | Element::Image(..) if self.in_text_state => {
|
||||||
|
self.content.end_text();
|
||||||
|
self.in_text_state = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = x + offset.x.to_f32();
|
||||||
|
let y = y - offset.y.to_f32();
|
||||||
|
|
||||||
|
match *element {
|
||||||
|
Element::Text(ref text) => {
|
||||||
|
self.write_text(x, y, text);
|
||||||
|
}
|
||||||
|
Element::Geometry(ref geometry, paint) => {
|
||||||
|
self.write_geometry(x, y, geometry, paint);
|
||||||
|
}
|
||||||
|
Element::Image(id, size) => {
|
||||||
|
self.write_image(x, y, id, size);
|
||||||
|
}
|
||||||
|
Element::Link(_, _) => {}
|
||||||
|
Element::Frame(ref frame) => {
|
||||||
|
self.write_frame(x, y, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.in_text_state {
|
||||||
|
self.content.end_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
if frame.clips {
|
||||||
|
self.content.restore_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a glyph run into the content stream.
|
||||||
|
fn write_text(&mut self, x: f32, y: f32, text: &Text) {
|
||||||
|
if self.font_fill != Some(text.fill) {
|
||||||
|
self.write_fill(text.fill);
|
||||||
|
self.font_fill = Some(text.fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then, also check if we need to issue a font switching
|
||||||
|
// action.
|
||||||
|
if self.face_id != Some(text.face_id) || self.font_size != text.size {
|
||||||
|
self.face_id = Some(text.face_id);
|
||||||
|
self.font_size = text.size;
|
||||||
|
|
||||||
|
let name = format_eco!("F{}", self.font_map.map(text.face_id));
|
||||||
|
self.content.set_font(Name(name.as_bytes()), text.size.to_f32());
|
||||||
|
}
|
||||||
|
|
||||||
|
let face = self.fonts.get(text.face_id);
|
||||||
|
|
||||||
|
// Position the text.
|
||||||
|
self.content.set_text_matrix([1.0, 0.0, 0.0, 1.0, x, y]);
|
||||||
|
|
||||||
|
let mut positioned = self.content.show_positioned();
|
||||||
|
let mut items = positioned.items();
|
||||||
|
let mut adjustment = Em::zero();
|
||||||
|
let mut encoded = vec![];
|
||||||
|
|
||||||
|
// Write the glyphs with kerning adjustments.
|
||||||
|
for glyph in &text.glyphs {
|
||||||
|
adjustment += glyph.x_offset;
|
||||||
|
|
||||||
|
if !adjustment.is_zero() {
|
||||||
|
if !encoded.is_empty() {
|
||||||
|
items.show(Str(&encoded));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
items.adjust(-adjustment.to_font_units());
|
||||||
|
adjustment = Em::zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded.push((glyph.id >> 8) as u8);
|
||||||
|
encoded.push((glyph.id & 0xff) as u8);
|
||||||
|
|
||||||
|
if let Some(advance) = face.advance(glyph.id) {
|
||||||
|
adjustment += glyph.x_advance - advance;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustment -= glyph.x_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !encoded.is_empty() {
|
||||||
|
items.show(Str(&encoded));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write an image into the content stream.
|
||||||
|
fn write_image(&mut self, x: f32, y: f32, id: ImageId, size: Size) {
|
||||||
|
let name = format!("Im{}", self.image_map.map(id));
|
||||||
|
let w = size.w.to_f32();
|
||||||
|
let h = size.h.to_f32();
|
||||||
|
|
||||||
|
self.content.save_state();
|
||||||
|
self.content.concat_matrix([w, 0.0, 0.0, h, x, y - h]);
|
||||||
|
self.content.x_object(Name(name.as_bytes()));
|
||||||
|
self.content.restore_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a geometrical shape into the content stream.
|
||||||
|
fn write_geometry(&mut self, x: f32, y: f32, geometry: &Geometry, paint: Paint) {
|
||||||
|
self.content.save_state();
|
||||||
|
|
||||||
|
match *geometry {
|
||||||
|
Geometry::Rect(Size { w, h }) => {
|
||||||
|
let w = w.to_f32();
|
||||||
|
let h = h.to_f32();
|
||||||
|
if w > 0.0 && h > 0.0 {
|
||||||
|
self.write_fill(paint);
|
||||||
|
self.content.rect(x, y - h, w, h);
|
||||||
|
self.content.fill_nonzero();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Geometry::Ellipse(size) => {
|
||||||
|
let path = geom::Path::ellipse(size);
|
||||||
|
self.write_fill(paint);
|
||||||
|
self.write_filled_path(x, y, &path);
|
||||||
|
}
|
||||||
|
Geometry::Line(target, thickness) => {
|
||||||
|
self.write_stroke(paint, thickness.to_f32());
|
||||||
|
self.content.move_to(x, y);
|
||||||
|
self.content.line_to(x + target.x.to_f32(), y - target.y.to_f32());
|
||||||
|
self.content.stroke();
|
||||||
|
}
|
||||||
|
Geometry::Path(ref path) => {
|
||||||
|
self.write_fill(paint);
|
||||||
|
self.write_filled_path(x, y, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content.restore_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write and fill path into a content stream.
|
||||||
|
fn write_filled_path(&mut self, x: f32, y: f32, path: &geom::Path) {
|
||||||
|
for elem in &path.0 {
|
||||||
|
match elem {
|
||||||
|
geom::PathElement::MoveTo(p) => {
|
||||||
|
self.content.move_to(x + p.x.to_f32(), y + p.y.to_f32())
|
||||||
|
}
|
||||||
|
geom::PathElement::LineTo(p) => {
|
||||||
|
self.content.line_to(x + p.x.to_f32(), y + p.y.to_f32())
|
||||||
|
}
|
||||||
|
geom::PathElement::CubicTo(p1, p2, p3) => self.content.cubic_to(
|
||||||
|
x + p1.x.to_f32(),
|
||||||
|
y + p1.y.to_f32(),
|
||||||
|
x + p2.x.to_f32(),
|
||||||
|
y + p2.y.to_f32(),
|
||||||
|
x + p3.x.to_f32(),
|
||||||
|
y + p3.y.to_f32(),
|
||||||
|
),
|
||||||
|
geom::PathElement::ClosePath => self.content.close_path(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.content.fill_nonzero();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a fill change into a content stream.
|
||||||
|
fn write_fill(&mut self, fill: Paint) {
|
||||||
|
let Paint::Color(Color::Rgba(c)) = fill;
|
||||||
|
self.content.set_fill_rgb(
|
||||||
|
c.r as f32 / 255.0,
|
||||||
|
c.g as f32 / 255.0,
|
||||||
|
c.b as f32 / 255.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a stroke change into a content stream.
|
||||||
|
fn write_stroke(&mut self, stroke: Paint, thickness: f32) {
|
||||||
|
let Paint::Color(Color::Rgba(c)) = stroke;
|
||||||
|
self.content.set_stroke_rgb(
|
||||||
|
c.r as f32 / 255.0,
|
||||||
|
c.g as f32 / 255.0,
|
||||||
|
c.b as f32 / 255.0,
|
||||||
|
);
|
||||||
|
self.content.set_line_width(thickness);
|
||||||
}
|
}
|
||||||
content.fill_nonzero();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The compression level for the deflating.
|
/// The compression level for the deflating.
|
||||||
@ -693,14 +752,26 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Additional methods for [`Length`].
|
||||||
|
trait LengthExt {
|
||||||
|
/// Convert an em length to a number of points.
|
||||||
|
fn to_f32(self) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LengthExt for Length {
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
self.to_pt() as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Additional methods for [`Em`].
|
/// Additional methods for [`Em`].
|
||||||
trait EmExt {
|
trait EmExt {
|
||||||
/// Convert an em length to a number of PDF font units.
|
/// Convert an em length to a number of PDF font units.
|
||||||
fn to_pdf(self) -> f32;
|
fn to_font_units(self) -> f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmExt for Em {
|
impl EmExt for Em {
|
||||||
fn to_pdf(self) -> f32 {
|
fn to_font_units(self) -> f32 {
|
||||||
1000.0 * self.get() as f32
|
1000.0 * self.get() as f32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
81
src/frame.rs
@ -16,8 +16,10 @@ pub struct Frame {
|
|||||||
pub size: Size,
|
pub size: Size,
|
||||||
/// The baseline of the frame measured from the top.
|
/// The baseline of the frame measured from the top.
|
||||||
pub baseline: Length,
|
pub baseline: Length,
|
||||||
|
/// Whether this frame should be a clipping boundary.
|
||||||
|
pub clips: bool,
|
||||||
/// The elements composing this layout.
|
/// The elements composing this layout.
|
||||||
pub children: Vec<(Point, FrameChild)>,
|
pub elements: Vec<(Point, Element)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
@ -25,37 +27,44 @@ impl Frame {
|
|||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(size: Size, baseline: Length) -> Self {
|
pub fn new(size: Size, baseline: Length) -> Self {
|
||||||
assert!(size.is_finite());
|
assert!(size.is_finite());
|
||||||
Self { size, baseline, children: vec![] }
|
Self {
|
||||||
}
|
size,
|
||||||
|
baseline,
|
||||||
/// Add an element at a position in the foreground.
|
elements: vec![],
|
||||||
pub fn push(&mut self, pos: Point, element: Element) {
|
clips: false,
|
||||||
self.children.push((pos, FrameChild::Element(element)));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an element at a position in the background.
|
/// Add an element at a position in the background.
|
||||||
pub fn prepend(&mut self, pos: Point, element: Element) {
|
pub fn prepend(&mut self, pos: Point, element: Element) {
|
||||||
self.children.insert(0, (pos, FrameChild::Element(element)));
|
self.elements.insert(0, (pos, element));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an element at a position in the foreground.
|
||||||
|
pub fn push(&mut self, pos: Point, element: Element) {
|
||||||
|
self.elements.push((pos, element));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a frame element.
|
/// Add a frame element.
|
||||||
pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) {
|
pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) {
|
||||||
self.children.push((pos, FrameChild::Group(subframe)))
|
self.elements.push((pos, Element::Frame(subframe)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add all elements of another frame, placing them relative to the given
|
/// Add all elements of another frame, placing them relative to the given
|
||||||
/// position.
|
/// position.
|
||||||
pub fn merge_frame(&mut self, pos: Point, subframe: Self) {
|
pub fn merge_frame(&mut self, pos: Point, subframe: Self) {
|
||||||
if pos == Point::zero() && self.children.is_empty() {
|
if subframe.clips {
|
||||||
self.children = subframe.children;
|
self.push_frame(pos, Rc::new(subframe));
|
||||||
|
} else if pos == Point::zero() && self.elements.is_empty() {
|
||||||
|
self.elements = subframe.elements;
|
||||||
} else {
|
} else {
|
||||||
for (subpos, child) in subframe.children {
|
for (subpos, child) in subframe.elements {
|
||||||
self.children.push((pos + subpos, child));
|
self.elements.push((pos + subpos, child));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over all elements in the frame and its children.
|
/// An iterator over all non-frame elements in this and nested frames.
|
||||||
pub fn elements(&self) -> Elements {
|
pub fn elements(&self) -> Elements {
|
||||||
Elements { stack: vec![(0, Point::zero(), self)] }
|
Elements { stack: vec![(0, Point::zero(), self)] }
|
||||||
}
|
}
|
||||||
@ -63,7 +72,7 @@ impl Frame {
|
|||||||
|
|
||||||
impl Debug for Frame {
|
impl Debug for Frame {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
struct Children<'a>(&'a [(Point, FrameChild)]);
|
struct Children<'a>(&'a [(Point, Element)]);
|
||||||
|
|
||||||
impl Debug for Children<'_> {
|
impl Debug for Children<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
@ -74,30 +83,12 @@ impl Debug for Frame {
|
|||||||
f.debug_struct("Frame")
|
f.debug_struct("Frame")
|
||||||
.field("size", &self.size)
|
.field("size", &self.size)
|
||||||
.field("baseline", &self.baseline)
|
.field("baseline", &self.baseline)
|
||||||
.field("children", &Children(&self.children))
|
.field("clips", &self.clips)
|
||||||
|
.field("children", &Children(&self.elements))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A frame can contain two different kinds of children: a leaf element or a
|
|
||||||
/// nested frame.
|
|
||||||
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum FrameChild {
|
|
||||||
/// A leaf node in the frame tree.
|
|
||||||
Element(Element),
|
|
||||||
/// An interior group.
|
|
||||||
Group(Rc<Frame>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for FrameChild {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Element(element) => element.fmt(f),
|
|
||||||
Self::Group(frame) => frame.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator over all elements in a frame, alongside with their positions.
|
/// An iterator over all elements in a frame, alongside with their positions.
|
||||||
pub struct Elements<'a> {
|
pub struct Elements<'a> {
|
||||||
stack: Vec<(usize, Point, &'a Frame)>,
|
stack: Vec<(usize, Point, &'a Frame)>,
|
||||||
@ -108,23 +99,21 @@ impl<'a> Iterator for Elements<'a> {
|
|||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let (cursor, offset, frame) = self.stack.last_mut()?;
|
let (cursor, offset, frame) = self.stack.last_mut()?;
|
||||||
match frame.children.get(*cursor) {
|
if let Some((pos, e)) = frame.elements.get(*cursor) {
|
||||||
Some((pos, FrameChild::Group(f))) => {
|
if let Element::Frame(f) = e {
|
||||||
let new_offset = *offset + *pos;
|
let new_offset = *offset + *pos;
|
||||||
self.stack.push((0, new_offset, f.as_ref()));
|
self.stack.push((0, new_offset, f.as_ref()));
|
||||||
self.next()
|
self.next()
|
||||||
}
|
} else {
|
||||||
Some((pos, FrameChild::Element(e))) => {
|
|
||||||
*cursor += 1;
|
*cursor += 1;
|
||||||
Some((*offset + *pos, e))
|
Some((*offset + *pos, e))
|
||||||
}
|
}
|
||||||
None => {
|
} else {
|
||||||
self.stack.pop();
|
self.stack.pop();
|
||||||
if let Some((cursor, _, _)) = self.stack.last_mut() {
|
if let Some((cursor, _, _)) = self.stack.last_mut() {
|
||||||
*cursor += 1;
|
*cursor += 1;
|
||||||
}
|
|
||||||
self.next()
|
|
||||||
}
|
}
|
||||||
|
self.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,6 +130,8 @@ pub enum Element {
|
|||||||
Image(ImageId, Size),
|
Image(ImageId, Size),
|
||||||
/// A link to an external resource.
|
/// A link to an external resource.
|
||||||
Link(String, Size),
|
Link(String, Size),
|
||||||
|
/// A subframe, which can be a clipping boundary.
|
||||||
|
Frame(Rc<Frame>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A run of shaped text.
|
/// A run of shaped text.
|
||||||
|
@ -127,11 +127,15 @@ impl Length {
|
|||||||
self == other || (self - other).to_raw().abs() < 1e-6
|
self == other || (self - other).to_raw().abs() < 1e-6
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a checked division by a number, returning `None` if the result
|
/// Perform a checked division by a number, returning zero if the result
|
||||||
/// is not finite.
|
/// is not finite.
|
||||||
pub fn div_finite(self, number: f64) -> Option<Self> {
|
pub fn safe_div(self, number: f64) -> Self {
|
||||||
let result = self.to_raw() / number;
|
let result = self.to_raw() / number;
|
||||||
result.is_finite().then(|| Self::raw(result))
|
if result.is_finite() {
|
||||||
|
Self::raw(result)
|
||||||
|
} else {
|
||||||
|
Self::zero()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,9 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
|
use crate::geom::{Linear, Spec};
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
use crate::library::DocumentNode;
|
use crate::library::{DocumentNode, SizedNode};
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Layout a document node into a collection of frames.
|
/// Layout a document node into a collection of frames.
|
||||||
@ -93,6 +94,23 @@ pub struct PackedNode {
|
|||||||
hash: u64,
|
hash: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PackedNode {
|
||||||
|
/// Force a size for this node.
|
||||||
|
///
|
||||||
|
/// If at least one of `width` and `height` is `Some`, this wraps the node
|
||||||
|
/// in a [`SizedNode`]. Otherwise, it returns the node unchanged.
|
||||||
|
pub fn sized(self, width: Option<Linear>, height: Option<Linear>) -> PackedNode {
|
||||||
|
if width.is_some() || height.is_some() {
|
||||||
|
Layout::pack(SizedNode {
|
||||||
|
sizing: Spec::new(width, height),
|
||||||
|
child: self,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Layout for PackedNode {
|
impl Layout for PackedNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
@ -102,9 +102,9 @@ pub enum LineKind {
|
|||||||
impl LineDecoration {
|
impl LineDecoration {
|
||||||
/// Apply a line decoration to a all text elements in a frame.
|
/// Apply a line decoration to a all text elements in a frame.
|
||||||
pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) {
|
pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) {
|
||||||
for i in 0 .. frame.children.len() {
|
for i in 0 .. frame.elements.len() {
|
||||||
let (pos, child) = &frame.children[i];
|
let (pos, child) = &frame.elements[i];
|
||||||
if let FrameChild::Element(Element::Text(text)) = child {
|
if let Element::Text(text) = child {
|
||||||
let face = ctx.fonts.get(text.face_id);
|
let face = ctx.fonts.get(text.face_id);
|
||||||
let metrics = match self.kind {
|
let metrics = match self.kind {
|
||||||
LineKind::Underline => face.underline,
|
LineKind::Underline => face.underline,
|
||||||
|
@ -167,7 +167,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
cols.pop();
|
cols.pop();
|
||||||
rows.pop();
|
rows.pop();
|
||||||
|
|
||||||
// We use the regions only for auto row measurement and constraints.
|
// We use the regions for auto row measurement. Since at that moment,
|
||||||
|
// columns are already sized, we can enable horizontal expansion.
|
||||||
let expand = regions.expand;
|
let expand = regions.expand;
|
||||||
regions.expand = Spec::new(true, false);
|
regions.expand = Spec::new(true, false);
|
||||||
|
|
||||||
|
@ -9,7 +9,9 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
|
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
|
let fit = args.named("fit")?.unwrap_or_default();
|
||||||
|
|
||||||
|
// Load the image.
|
||||||
let full = ctx.make_path(&path.v);
|
let full = ctx.make_path(&path.v);
|
||||||
let id = ctx.images.load(&full).map_err(|err| {
|
let id = ctx.images.load(&full).map_err(|err| {
|
||||||
Error::boxed(path.span, match err.kind() {
|
Error::boxed(path.span, match err.kind() {
|
||||||
@ -18,10 +20,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Value::Template(Template::from_inline(move |_| ImageNode {
|
Ok(Value::Template(Template::from_inline(move |_| {
|
||||||
id,
|
ImageNode { id, fit }.pack().sized(width, height)
|
||||||
width,
|
|
||||||
height,
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,10 +30,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
pub struct ImageNode {
|
pub struct ImageNode {
|
||||||
/// The id of the image file.
|
/// The id of the image file.
|
||||||
pub id: ImageId,
|
pub id: ImageId,
|
||||||
/// The fixed width, if any.
|
/// How the image should adjust itself to a given area.
|
||||||
pub width: Option<Linear>,
|
pub fit: ImageFit,
|
||||||
/// The fixed height, if any.
|
|
||||||
pub height: Option<Linear>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ImageNode {
|
impl Layout for ImageNode {
|
||||||
@ -42,36 +40,77 @@ impl Layout for ImageNode {
|
|||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
|
let &Regions { current, expand, .. } = regions;
|
||||||
|
|
||||||
let img = ctx.images.get(self.id);
|
let img = ctx.images.get(self.id);
|
||||||
let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
|
let pixel_w = img.width() as f64;
|
||||||
let pixel_ratio = pixel_size.x / pixel_size.y;
|
let pixel_h = img.height() as f64;
|
||||||
|
|
||||||
let width = self.width.map(|w| w.resolve(regions.base.w));
|
let region_ratio = current.w / current.h;
|
||||||
let height = self.height.map(|w| w.resolve(regions.base.h));
|
let pixel_ratio = pixel_w / pixel_h;
|
||||||
|
let wide = region_ratio < pixel_ratio;
|
||||||
|
|
||||||
let mut cts = Constraints::new(regions.expand);
|
// The space into which the image will be placed according to its fit.
|
||||||
cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
|
let canvas = if expand.x && expand.y {
|
||||||
|
current
|
||||||
let size = match (width, height) {
|
} else if expand.x || (wide && current.w.is_finite()) {
|
||||||
(Some(width), Some(height)) => Size::new(width, height),
|
Size::new(current.w, current.w.safe_div(pixel_ratio))
|
||||||
(Some(width), None) => Size::new(width, width / pixel_ratio),
|
} else if current.h.is_finite() {
|
||||||
(None, Some(height)) => Size::new(height * pixel_ratio, height),
|
Size::new(current.h * pixel_ratio, current.h)
|
||||||
(None, None) => {
|
} else {
|
||||||
cts.exact.x = Some(regions.current.w);
|
Size::new(Length::pt(pixel_w), Length::pt(pixel_h))
|
||||||
if regions.current.w.is_finite() {
|
|
||||||
// Fit to width.
|
|
||||||
Size::new(regions.current.w, regions.current.w / pixel_ratio)
|
|
||||||
} else {
|
|
||||||
// Unbounded width, we have to make up something,
|
|
||||||
// so it is 1pt per pixel.
|
|
||||||
pixel_size.map(Length::pt).to_size()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frame = Frame::new(size, size.h);
|
// The actual size of the fitted image.
|
||||||
frame.push(Point::zero(), Element::Image(self.id, size));
|
let size = match self.fit {
|
||||||
|
ImageFit::Contain | ImageFit::Cover => {
|
||||||
|
if wide == (self.fit == ImageFit::Contain) {
|
||||||
|
Size::new(canvas.w, canvas.w / pixel_ratio)
|
||||||
|
} else {
|
||||||
|
Size::new(canvas.h * pixel_ratio, canvas.h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageFit::Stretch => canvas,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
Point::new((canvas.w - size.w) / 2.0, (canvas.h - size.h) / 2.0),
|
||||||
|
Element::Image(self.id, size),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut cts = Constraints::new(regions.expand);
|
||||||
|
cts.exact = regions.current.to_spec().map(Some);
|
||||||
vec![frame.constrain(cts)]
|
vec![frame.constrain(cts)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How an image should adjust itself to a given area.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum ImageFit {
|
||||||
|
/// The image should be fully contained in the area.
|
||||||
|
Contain,
|
||||||
|
/// The image should completely cover the area.
|
||||||
|
Cover,
|
||||||
|
/// The image should be stretched so that it exactly fills the area.
|
||||||
|
Stretch,
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
ImageFit,
|
||||||
|
Expected: "string",
|
||||||
|
Value::Str(string) => match string.as_str() {
|
||||||
|
"contain" => Self::Contain,
|
||||||
|
"cover" => Self::Cover,
|
||||||
|
"stretch" => Self::Stretch,
|
||||||
|
_ => Err(r#"expected "contain", "cover" or "stretch""#)?,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ImageFit {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Contain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -46,9 +46,7 @@ impl Layout for PadNode {
|
|||||||
frames.iter_mut().zip(regions.iter())
|
frames.iter_mut().zip(regions.iter())
|
||||||
{
|
{
|
||||||
fn solve_axis(length: Length, padding: Linear) -> Length {
|
fn solve_axis(length: Length, padding: Linear) -> Length {
|
||||||
(length + padding.abs)
|
(length + padding.abs).safe_div(1.0 - padding.rel.get())
|
||||||
.div_finite(1.0 - padding.rel.get())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve for the size `padded` that satisfies (approximately):
|
// Solve for the size `padded` that satisfies (approximately):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::f64::consts::SQRT_2;
|
use std::f64::consts::SQRT_2;
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{PadNode, SizedNode};
|
use super::PadNode;
|
||||||
use crate::util::RcExt;
|
use crate::util::RcExt;
|
||||||
|
|
||||||
/// `rect`: A rectangle with optional content.
|
/// `rect`: A rectangle with optional content.
|
||||||
@ -65,20 +65,13 @@ fn shape_impl(
|
|||||||
let fill = fill.unwrap_or(Color::Rgba(RgbaColor::gray(175)));
|
let fill = fill.unwrap_or(Color::Rgba(RgbaColor::gray(175)));
|
||||||
|
|
||||||
Value::Template(Template::from_inline(move |style| {
|
Value::Template(Template::from_inline(move |style| {
|
||||||
let shape = Layout::pack(ShapeNode {
|
ShapeNode {
|
||||||
kind,
|
kind,
|
||||||
fill: Some(Paint::Color(fill)),
|
fill: Some(Paint::Color(fill)),
|
||||||
child: body.as_ref().map(|body| body.pack(style)),
|
child: body.as_ref().map(|body| body.pack(style)),
|
||||||
});
|
|
||||||
|
|
||||||
if width.is_some() || height.is_some() {
|
|
||||||
Layout::pack(SizedNode {
|
|
||||||
sizing: Spec::new(width, height),
|
|
||||||
child: shape,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
shape
|
|
||||||
}
|
}
|
||||||
|
.pack()
|
||||||
|
.sized(width, height)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +105,7 @@ impl Layout for ShapeNode {
|
|||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
// Layout.
|
// Layout, either with or without child.
|
||||||
let mut frame = if let Some(child) = &self.child {
|
let mut frame = if let Some(child) = &self.child {
|
||||||
let mut node: &dyn Layout = child;
|
let mut node: &dyn Layout = child;
|
||||||
|
|
||||||
@ -141,15 +134,18 @@ impl Layout for ShapeNode {
|
|||||||
frames = node.layout(ctx, &pod);
|
frames = node.layout(ctx, &pod);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate and set constraints.
|
// Extract the frame.
|
||||||
assert_eq!(frames.len(), 1);
|
|
||||||
Rc::take(frames.into_iter().next().unwrap().item)
|
Rc::take(frames.into_iter().next().unwrap().item)
|
||||||
} else {
|
} else {
|
||||||
|
// When there's no child, fill the area if expansion is on,
|
||||||
|
// otherwise fall back to a default size.
|
||||||
let default = Length::pt(30.0);
|
let default = Length::pt(30.0);
|
||||||
let size = Size::new(
|
let size = Size::new(
|
||||||
if regions.expand.x {
|
if regions.expand.x {
|
||||||
regions.current.w
|
regions.current.w
|
||||||
} else {
|
} else {
|
||||||
|
// For rectangle and ellipse, the default shape is a bit
|
||||||
|
// wider than high.
|
||||||
match self.kind {
|
match self.kind {
|
||||||
ShapeKind::Square | ShapeKind::Circle => default,
|
ShapeKind::Square | ShapeKind::Circle => default,
|
||||||
ShapeKind::Rect | ShapeKind::Ellipse => 1.5 * default,
|
ShapeKind::Rect | ShapeKind::Ellipse => 1.5 * default,
|
||||||
@ -161,7 +157,7 @@ impl Layout for ShapeNode {
|
|||||||
Frame::new(size, size.h)
|
Frame::new(size, size.h)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add background shape if desired.
|
// Add background fill if desired.
|
||||||
if let Some(fill) = self.fill {
|
if let Some(fill) = self.fill {
|
||||||
let (pos, geometry) = match self.kind {
|
let (pos, geometry) = match self.kind {
|
||||||
ShapeKind::Square | ShapeKind::Rect => {
|
ShapeKind::Square | ShapeKind::Rect => {
|
||||||
@ -175,11 +171,10 @@ impl Layout for ShapeNode {
|
|||||||
frame.prepend(pos, Element::Geometry(geometry, fill));
|
frame.prepend(pos, Element::Geometry(geometry, fill));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate tight constraints for now.
|
// Return tight constraints for now.
|
||||||
let mut cts = Constraints::new(regions.expand);
|
let mut cts = Constraints::new(regions.expand);
|
||||||
cts.exact = regions.current.to_spec().map(Some);
|
cts.exact = regions.current.to_spec().map(Some);
|
||||||
cts.base = regions.base.to_spec().map(Some);
|
cts.base = regions.base.to_spec().map(Some);
|
||||||
|
|
||||||
vec![frame.constrain(cts)]
|
vec![frame.constrain(cts)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,7 @@ pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let body: Template = args.find().unwrap_or_default();
|
let body: Template = args.find().unwrap_or_default();
|
||||||
Ok(Value::Template(Template::from_inline(move |style| {
|
Ok(Value::Template(Template::from_inline(move |style| {
|
||||||
let child = body.pack(style);
|
body.pack(style).sized(width, height)
|
||||||
if width.is_some() || height.is_some() {
|
|
||||||
Layout::pack(SizedNode { sizing: Spec::new(width, height), child })
|
|
||||||
} else {
|
|
||||||
child
|
|
||||||
}
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,12 +16,7 @@ pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let body: Template = args.find().unwrap_or_default();
|
let body: Template = args.find().unwrap_or_default();
|
||||||
Ok(Value::Template(Template::from_block(move |style| {
|
Ok(Value::Template(Template::from_block(move |style| {
|
||||||
let child = body.pack(style);
|
body.pack(style).sized(width, height)
|
||||||
if width.is_some() || height.is_some() {
|
|
||||||
Layout::pack(SizedNode { sizing: Spec::new(width, height), child })
|
|
||||||
} else {
|
|
||||||
child
|
|
||||||
}
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ impl Layout for MoveNode {
|
|||||||
self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(),
|
self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (point, _) in &mut Rc::make_mut(frame).children {
|
for (point, _) in &mut Rc::make_mut(frame).elements {
|
||||||
*point += offset;
|
*point += offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 317 B |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
@ -5,30 +5,42 @@
|
|||||||
|
|
||||||
// Load an RGBA PNG image.
|
// Load an RGBA PNG image.
|
||||||
#image("../../res/rhino.png")
|
#image("../../res/rhino.png")
|
||||||
#pagebreak()
|
|
||||||
|
|
||||||
// Load an RGB JPEG image.
|
// Load an RGB JPEG image.
|
||||||
|
#page(height: 60pt)
|
||||||
#image("../../res/tiger.jpg")
|
#image("../../res/tiger.jpg")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test configuring the size and fitting behaviour of images.
|
// Test configuring the size and fitting behaviour of images.
|
||||||
|
|
||||||
// Set width explicitly.
|
// Set width and height explicitly.
|
||||||
#image("../../res/rhino.png", width: 50pt)
|
#image("../../res/rhino.png", width: 30pt)
|
||||||
|
#image("../../res/rhino.png", height: 30pt)
|
||||||
// Set height explicitly.
|
|
||||||
#image("../../res/rhino.png", height: 50pt)
|
|
||||||
|
|
||||||
// Set width and height explicitly and force stretching.
|
// Set width and height explicitly and force stretching.
|
||||||
#image("../../res/rhino.png", width: 25pt, height: 50pt)
|
#image("../../res/tiger.jpg", width: 100%, height: 20pt, fit: "stretch")
|
||||||
|
|
||||||
// Make sure the bounding-box of the image is correct.
|
// Make sure the bounding-box of the image is correct.
|
||||||
#align(bottom, right)
|
#align(bottom, right)
|
||||||
#image("../../res/tiger.jpg", width: 60pt)
|
#image("../../res/tiger.jpg", width: 40pt)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Does not fit to height of page.
|
// Test all three fit modes.
|
||||||
|
#page(height: 50pt, margins: 0pt)
|
||||||
|
#grid(
|
||||||
|
columns: 3,
|
||||||
|
rows: 100%,
|
||||||
|
gutter: 3pt,
|
||||||
|
image("../../res/tiger.jpg", fit: "contain"),
|
||||||
|
image("../../res/tiger.jpg", fit: "cover"),
|
||||||
|
image("../../res/tiger.jpg", fit: "stretch"),
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Does not fit to remaining height of page.
|
||||||
#page(height: 60pt)
|
#page(height: 60pt)
|
||||||
|
Stuff \
|
||||||
|
Stuff
|
||||||
#image("../../res/rhino.png")
|
#image("../../res/rhino.png")
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
columns: 4 * (1fr,),
|
columns: 4 * (1fr,),
|
||||||
row-gutter: 10pt,
|
row-gutter: 10pt,
|
||||||
column-gutter: (0pt, 10%),
|
column-gutter: (0pt, 10%),
|
||||||
image("../../res/rhino.png"),
|
align(top, image("../../res/rhino.png")),
|
||||||
align(right, rect(width: 100%, fill: eastern)[LoL]),
|
align(right, rect(width: 100%, fill: eastern)[LoL]),
|
||||||
[rofl],
|
[rofl],
|
||||||
[\ A] * 3,
|
[\ A] * 3,
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
#page(width: 50pt, margins: 0pt)
|
#page(width: 50pt, margins: 0pt)
|
||||||
#stack(dir: btt, ..items)
|
#stack(dir: btt, ..items)
|
||||||
#pagebreak()
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test spacing.
|
// Test spacing.
|
||||||
|
149
tests/typeset.rs
@ -13,9 +13,7 @@ use typst::diag::Error;
|
|||||||
use typst::eval::Value;
|
use typst::eval::Value;
|
||||||
use typst::font::Face;
|
use typst::font::Face;
|
||||||
use typst::frame::{Element, Frame, Geometry, Text};
|
use typst::frame::{Element, Frame, Geometry, Text};
|
||||||
use typst::geom::{
|
use typst::geom::{self, Color, Length, Paint, PathElement, RgbaColor, Sides, Size};
|
||||||
self, Color, Length, Paint, PathElement, Point, RgbaColor, Sides, Size,
|
|
||||||
};
|
|
||||||
use typst::image::Image;
|
use typst::image::Image;
|
||||||
use typst::layout::layout;
|
use typst::layout::layout;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
@ -390,66 +388,96 @@ fn draw(ctx: &Context, frames: &[Rc<Frame>], dpp: f32) -> sk::Pixmap {
|
|||||||
let width = 2.0 * pad + frames.iter().map(|l| l.size.w).max().unwrap_or_default();
|
let width = 2.0 * pad + frames.iter().map(|l| l.size.w).max().unwrap_or_default();
|
||||||
let height = pad + frames.iter().map(|l| l.size.h + pad).sum::<Length>();
|
let height = pad + frames.iter().map(|l| l.size.h + pad).sum::<Length>();
|
||||||
|
|
||||||
let pixel_width = (dpp * width.to_pt() as f32) as u32;
|
let pxw = (dpp * width.to_pt() as f32) as u32;
|
||||||
let pixel_height = (dpp * height.to_pt() as f32) as u32;
|
let pxh = (dpp * height.to_pt() as f32) as u32;
|
||||||
if pixel_width > 4000 || pixel_height > 4000 {
|
if pxw > 4000 || pxh > 4000 {
|
||||||
panic!(
|
panic!(
|
||||||
"overlarge image: {} by {} ({:?} x {:?})",
|
"overlarge image: {} by {} ({:?} x {:?})",
|
||||||
pixel_width, pixel_height, width, height,
|
pxw, pxh, width, height,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut canvas = sk::Pixmap::new(pixel_width, pixel_height).unwrap();
|
let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap();
|
||||||
let ts = sk::Transform::from_scale(dpp, dpp);
|
|
||||||
canvas.fill(sk::Color::BLACK);
|
canvas.fill(sk::Color::BLACK);
|
||||||
|
|
||||||
let mut origin = Point::splat(pad);
|
let mut mask = sk::ClipMask::new();
|
||||||
|
let rect = sk::Rect::from_xywh(0.0, 0.0, pxw as f32, pxh as f32).unwrap();
|
||||||
|
let path = sk::PathBuilder::from_rect(rect);
|
||||||
|
mask.set_path(pxw, pxh, &path, sk::FillRule::default(), false);
|
||||||
|
|
||||||
|
let mut ts = sk::Transform::from_scale(dpp, dpp)
|
||||||
|
.pre_translate(pad.to_pt() as f32, pad.to_pt() as f32);
|
||||||
|
|
||||||
for frame in frames {
|
for frame in frames {
|
||||||
let mut paint = sk::Paint::default();
|
let mut background = sk::Paint::default();
|
||||||
paint.set_color(sk::Color::WHITE);
|
background.set_color(sk::Color::WHITE);
|
||||||
canvas.fill_rect(
|
|
||||||
sk::Rect::from_xywh(
|
|
||||||
origin.x.to_pt() as f32,
|
|
||||||
origin.y.to_pt() as f32,
|
|
||||||
frame.size.w.to_pt() as f32,
|
|
||||||
frame.size.h.to_pt() as f32,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
&paint,
|
|
||||||
ts,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (pos, element) in frame.elements() {
|
let w = frame.size.w.to_pt() as f32;
|
||||||
let global = origin + pos;
|
let h = frame.size.h.to_pt() as f32;
|
||||||
let x = global.x.to_pt() as f32;
|
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
|
||||||
let y = global.y.to_pt() as f32;
|
canvas.fill_rect(rect, &background, ts, None);
|
||||||
let ts = ts.pre_translate(x, y);
|
|
||||||
match *element {
|
|
||||||
Element::Text(ref text) => {
|
|
||||||
draw_text(&mut canvas, ts, ctx.fonts.get(text.face_id), text);
|
|
||||||
}
|
|
||||||
Element::Geometry(ref geometry, paint) => {
|
|
||||||
draw_geometry(&mut canvas, ts, geometry, paint);
|
|
||||||
}
|
|
||||||
Element::Image(id, size) => {
|
|
||||||
draw_image(&mut canvas, ts, ctx.images.get(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
origin.y += frame.size.h + pad;
|
draw_frame(&mut canvas, ts, &mask, ctx, frame);
|
||||||
|
ts = ts.pre_translate(0.0, (frame.size.h + pad).to_pt() as f32);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas
|
canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, face: &Face, text: &Text) {
|
fn draw_frame(
|
||||||
|
canvas: &mut sk::Pixmap,
|
||||||
|
ts: sk::Transform,
|
||||||
|
mask: &sk::ClipMask,
|
||||||
|
ctx: &Context,
|
||||||
|
frame: &Frame,
|
||||||
|
) {
|
||||||
|
let mut storage;
|
||||||
|
let mask = if frame.clips {
|
||||||
|
let w = frame.size.w.to_pt() as f32;
|
||||||
|
let h = frame.size.h.to_pt() as f32;
|
||||||
|
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
|
||||||
|
let path = sk::PathBuilder::from_rect(rect).transform(ts).unwrap();
|
||||||
|
storage = mask.clone();
|
||||||
|
storage.intersect_path(&path, sk::FillRule::default(), false);
|
||||||
|
&storage
|
||||||
|
} else {
|
||||||
|
mask
|
||||||
|
};
|
||||||
|
|
||||||
|
for (pos, element) in &frame.elements {
|
||||||
|
let x = pos.x.to_pt() as f32;
|
||||||
|
let y = pos.y.to_pt() as f32;
|
||||||
|
let ts = ts.pre_translate(x, y);
|
||||||
|
|
||||||
|
match *element {
|
||||||
|
Element::Text(ref text) => {
|
||||||
|
draw_text(canvas, ts, mask, ctx.fonts.get(text.face_id), text);
|
||||||
|
}
|
||||||
|
Element::Geometry(ref geometry, paint) => {
|
||||||
|
draw_geometry(canvas, ts, mask, geometry, paint);
|
||||||
|
}
|
||||||
|
Element::Image(id, size) => {
|
||||||
|
draw_image(canvas, ts, mask, ctx.images.get(id), size);
|
||||||
|
}
|
||||||
|
Element::Link(_, s) => {
|
||||||
|
let outline = Geometry::Rect(s);
|
||||||
|
let paint = Paint::Color(Color::Rgba(RgbaColor::new(40, 54, 99, 40)));
|
||||||
|
draw_geometry(canvas, ts, mask, &outline, paint);
|
||||||
|
}
|
||||||
|
Element::Frame(ref frame) => {
|
||||||
|
draw_frame(canvas, ts, mask, ctx, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_text(
|
||||||
|
canvas: &mut sk::Pixmap,
|
||||||
|
ts: sk::Transform,
|
||||||
|
mask: &sk::ClipMask,
|
||||||
|
face: &Face,
|
||||||
|
text: &Text,
|
||||||
|
) {
|
||||||
let ttf = face.ttf();
|
let ttf = face.ttf();
|
||||||
let size = text.size.to_pt() as f32;
|
let size = text.size.to_pt() as f32;
|
||||||
let units_per_em = ttf.units_per_em() as f32;
|
let units_per_em = ttf.units_per_em() as f32;
|
||||||
@ -481,7 +509,7 @@ fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, face: &Face, text: &Tex
|
|||||||
if let Some(fill) = &node.fill {
|
if let Some(fill) = &node.fill {
|
||||||
let path = convert_usvg_path(&node.data);
|
let path = convert_usvg_path(&node.data);
|
||||||
let (paint, fill_rule) = convert_usvg_fill(fill);
|
let (paint, fill_rule) = convert_usvg_fill(fill);
|
||||||
canvas.fill_path(&path, &paint, fill_rule, ts, None);
|
canvas.fill_path(&path, &paint, fill_rule, ts, Some(mask));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -497,7 +525,7 @@ fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, face: &Face, text: &Tex
|
|||||||
let dx = (raster.x as f32) / (img.width() as f32) * size;
|
let dx = (raster.x as f32) / (img.width() as f32) * size;
|
||||||
let dy = (raster.y as f32) / (img.height() as f32) * size;
|
let dy = (raster.y as f32) / (img.height() as f32) * size;
|
||||||
let ts = ts.pre_translate(dx, -size - dy);
|
let ts = ts.pre_translate(dx, -size - dy);
|
||||||
draw_image(canvas, ts, &img, Size::new(w, h));
|
draw_image(canvas, ts, mask, &img, Size::new(w, h));
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, draw normal outline.
|
// Otherwise, draw normal outline.
|
||||||
let mut builder = WrappedPathBuilder(sk::PathBuilder::new());
|
let mut builder = WrappedPathBuilder(sk::PathBuilder::new());
|
||||||
@ -507,7 +535,7 @@ fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, face: &Face, text: &Tex
|
|||||||
let path = builder.0.finish().unwrap();
|
let path = builder.0.finish().unwrap();
|
||||||
let mut paint = convert_typst_paint(text.fill);
|
let mut paint = convert_typst_paint(text.fill);
|
||||||
paint.anti_alias = true;
|
paint.anti_alias = true;
|
||||||
canvas.fill_path(&path, &paint, sk::FillRule::default(), ts, None);
|
canvas.fill_path(&path, &paint, sk::FillRule::default(), ts, Some(mask));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,6 +546,7 @@ fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, face: &Face, text: &Tex
|
|||||||
fn draw_geometry(
|
fn draw_geometry(
|
||||||
canvas: &mut sk::Pixmap,
|
canvas: &mut sk::Pixmap,
|
||||||
ts: sk::Transform,
|
ts: sk::Transform,
|
||||||
|
mask: &sk::ClipMask,
|
||||||
geometry: &Geometry,
|
geometry: &Geometry,
|
||||||
paint: Paint,
|
paint: Paint,
|
||||||
) {
|
) {
|
||||||
@ -529,11 +558,11 @@ fn draw_geometry(
|
|||||||
let w = width.to_pt() as f32;
|
let w = width.to_pt() as f32;
|
||||||
let h = height.to_pt() as f32;
|
let h = height.to_pt() as f32;
|
||||||
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
|
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();
|
||||||
canvas.fill_rect(rect, &paint, ts, None);
|
canvas.fill_rect(rect, &paint, ts, Some(mask));
|
||||||
}
|
}
|
||||||
Geometry::Ellipse(size) => {
|
Geometry::Ellipse(size) => {
|
||||||
let path = convert_typst_path(&geom::Path::ellipse(size));
|
let path = convert_typst_path(&geom::Path::ellipse(size));
|
||||||
canvas.fill_path(&path, &paint, rule, ts, None);
|
canvas.fill_path(&path, &paint, rule, ts, Some(mask));
|
||||||
}
|
}
|
||||||
Geometry::Line(target, thickness) => {
|
Geometry::Line(target, thickness) => {
|
||||||
let path = {
|
let path = {
|
||||||
@ -544,16 +573,22 @@ fn draw_geometry(
|
|||||||
|
|
||||||
let mut stroke = sk::Stroke::default();
|
let mut stroke = sk::Stroke::default();
|
||||||
stroke.width = thickness.to_pt() as f32;
|
stroke.width = thickness.to_pt() as f32;
|
||||||
canvas.stroke_path(&path, &paint, &stroke, ts, None);
|
canvas.stroke_path(&path, &paint, &stroke, ts, Some(mask));
|
||||||
}
|
}
|
||||||
Geometry::Path(ref path) => {
|
Geometry::Path(ref path) => {
|
||||||
let path = convert_typst_path(path);
|
let path = convert_typst_path(path);
|
||||||
canvas.fill_path(&path, &paint, rule, ts, None);
|
canvas.fill_path(&path, &paint, rule, ts, Some(mask));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_image(canvas: &mut sk::Pixmap, ts: sk::Transform, img: &Image, size: Size) {
|
fn draw_image(
|
||||||
|
canvas: &mut sk::Pixmap,
|
||||||
|
ts: sk::Transform,
|
||||||
|
mask: &sk::ClipMask,
|
||||||
|
img: &Image,
|
||||||
|
size: Size,
|
||||||
|
) {
|
||||||
let mut pixmap = sk::Pixmap::new(img.buf.width(), img.buf.height()).unwrap();
|
let mut pixmap = sk::Pixmap::new(img.buf.width(), img.buf.height()).unwrap();
|
||||||
for ((_, _, src), dest) in img.buf.pixels().zip(pixmap.pixels_mut()) {
|
for ((_, _, src), dest) in img.buf.pixels().zip(pixmap.pixels_mut()) {
|
||||||
let Rgba([r, g, b, a]) = src;
|
let Rgba([r, g, b, a]) = src;
|
||||||
@ -575,7 +610,7 @@ fn draw_image(canvas: &mut sk::Pixmap, ts: sk::Transform, img: &Image, size: Siz
|
|||||||
);
|
);
|
||||||
|
|
||||||
let rect = sk::Rect::from_xywh(0.0, 0.0, view_width, view_height).unwrap();
|
let rect = sk::Rect::from_xywh(0.0, 0.0, view_width, view_height).unwrap();
|
||||||
canvas.fill_rect(rect, &paint, ts, None);
|
canvas.fill_rect(rect, &paint, ts, Some(mask));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_typst_paint(paint: Paint) -> sk::Paint<'static> {
|
fn convert_typst_paint(paint: Paint) -> sk::Paint<'static> {
|
||||||
|