mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Switch to tiny-skia 🎨
This commit is contained in:
parent
11e44516fa
commit
e30d896c7b
@ -18,12 +18,14 @@ unicode-xid = "0.2"
|
|||||||
|
|
||||||
# feature = "serde"
|
# feature = "serde"
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
# for the CLI
|
||||||
anyhow = { version = "1", optional = true }
|
anyhow = { version = "1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
memmap = "0.7"
|
memmap = "0.7"
|
||||||
raqote = { version = "0.8", default-features = false }
|
tiny-skia = "0.2"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 2
|
opt-level = 2
|
||||||
|
@ -12,6 +12,8 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// # Positional arguments
|
/// # Positional arguments
|
||||||
/// - The path to the image (string)
|
/// - The path to the image (string)
|
||||||
|
///
|
||||||
|
/// Supports PNG and JPEG files.
|
||||||
pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
|
pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
let path = args.need::<_, Spanned<String>>(ctx, 0, "path");
|
let path = args.need::<_, Spanned<String>>(ctx, 0, "path");
|
||||||
let width = args.get::<_, Linear>(ctx, "width");
|
let width = args.get::<_, Linear>(ctx, "width");
|
||||||
@ -92,7 +94,11 @@ impl Layout for Image {
|
|||||||
|
|
||||||
impl Debug for Image {
|
impl Debug for Image {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.pad("Image")
|
f.debug_struct("Image")
|
||||||
|
.field("width", &self.width)
|
||||||
|
.field("height", &self.height)
|
||||||
|
.field("align", &self.align)
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 77 KiB |
Binary file not shown.
Before Width: | Height: | Size: 935 KiB After Width: | Height: | Size: 930 KiB |
145
tests/typeset.rs
145
tests/typeset.rs
@ -7,7 +7,10 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use fontdock::fs::{FsIndex, FsSource};
|
use fontdock::fs::{FsIndex, FsSource};
|
||||||
use memmap::Mmap;
|
use memmap::Mmap;
|
||||||
use raqote::{DrawTarget, PathBuilder, SolidSource, Source, Transform, Vector};
|
use tiny_skia::{
|
||||||
|
Canvas, Color, ColorU8, FillRule, FilterQuality, Paint, PathBuilder, Pattern, Pixmap,
|
||||||
|
Rect, SpreadMode, Transform,
|
||||||
|
};
|
||||||
use ttf_parser::OutlineBuilder;
|
use ttf_parser::OutlineBuilder;
|
||||||
|
|
||||||
use typst::diag::{Feedback, Pass};
|
use typst::diag::{Feedback, Pass};
|
||||||
@ -26,9 +29,6 @@ const PDF_DIR: &str = "pdf";
|
|||||||
const PNG_DIR: &str = "png";
|
const PNG_DIR: &str = "png";
|
||||||
const REF_DIR: &str = "ref";
|
const REF_DIR: &str = "ref";
|
||||||
|
|
||||||
const BLACK: SolidSource = SolidSource { r: 0, g: 0, b: 0, a: 255 };
|
|
||||||
const WHITE: SolidSource = SolidSource { r: 255, g: 255, b: 255, a: 255 };
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env::set_current_dir(env::current_dir().unwrap().join("tests")).unwrap();
|
env::set_current_dir(env::current_dir().unwrap().join("tests")).unwrap();
|
||||||
|
|
||||||
@ -133,8 +133,8 @@ fn test(src_path: &Path, pdf_path: &Path, png_path: &Path, loader: &SharedFontLo
|
|||||||
|
|
||||||
let loader = loader.borrow();
|
let loader = loader.borrow();
|
||||||
|
|
||||||
let surface = render(&layouts, &loader, 2.0);
|
let canvas = draw(&layouts, &loader, 2.0);
|
||||||
surface.write_png(png_path).unwrap();
|
canvas.pixmap.save_png(png_path).unwrap();
|
||||||
|
|
||||||
let pdf_data = pdf::export(&layouts, &loader);
|
let pdf_data = pdf::export(&layouts, &loader);
|
||||||
fs::write(pdf_path, pdf_data).unwrap();
|
fs::write(pdf_path, pdf_data).unwrap();
|
||||||
@ -170,106 +170,107 @@ impl TestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(layouts: &[BoxLayout], loader: &FontLoader, scale: f64) -> DrawTarget {
|
fn draw(layouts: &[BoxLayout], loader: &FontLoader, pixel_per_pt: f32) -> Canvas {
|
||||||
let pad = Length::pt(scale * 10.0);
|
let pad = Length::pt(5.0);
|
||||||
|
|
||||||
|
let height = pad + layouts.iter().map(|l| l.size.height + pad).sum::<Length>();
|
||||||
let width = 2.0 * pad
|
let width = 2.0 * pad
|
||||||
+ layouts
|
+ layouts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|l| scale * l.size.width)
|
.map(|l| l.size.width)
|
||||||
.max_by(|a, b| a.partial_cmp(&b).unwrap())
|
.max_by(|a, b| a.partial_cmp(&b).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let height =
|
let pixel_width = (pixel_per_pt * width.to_pt() as f32) as u32;
|
||||||
pad + layouts.iter().map(|l| scale * l.size.height + pad).sum::<Length>();
|
let pixel_height = (pixel_per_pt * height.to_pt() as f32) as u32;
|
||||||
|
let mut canvas = Canvas::new(pixel_width, pixel_height).unwrap();
|
||||||
|
canvas.scale(pixel_per_pt, pixel_per_pt);
|
||||||
|
canvas.pixmap.fill(Color::BLACK);
|
||||||
|
|
||||||
let int_width = width.to_pt().round() as i32;
|
let mut origin = Point::new(pad, pad);
|
||||||
let int_height = height.to_pt().round() as i32;
|
|
||||||
let mut surface = DrawTarget::new(int_width, int_height);
|
|
||||||
surface.clear(BLACK);
|
|
||||||
|
|
||||||
let mut offset = Point::new(pad, pad);
|
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
surface.fill_rect(
|
let mut paint = Paint::default();
|
||||||
offset.x.to_pt() as f32,
|
paint.set_color(Color::WHITE);
|
||||||
offset.y.to_pt() as f32,
|
|
||||||
(scale * layout.size.width).to_pt() as f32,
|
canvas.fill_rect(
|
||||||
(scale * layout.size.height).to_pt() as f32,
|
Rect::from_xywh(
|
||||||
&Source::Solid(WHITE),
|
origin.x.to_pt() as f32,
|
||||||
&Default::default(),
|
origin.y.to_pt() as f32,
|
||||||
|
layout.size.width.to_pt() as f32,
|
||||||
|
layout.size.height.to_pt() as f32,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
&paint,
|
||||||
);
|
);
|
||||||
|
|
||||||
for &(pos, ref element) in &layout.elements {
|
for &(pos, ref element) in &layout.elements {
|
||||||
let pos = scale * pos + offset;
|
let pos = origin + pos;
|
||||||
|
|
||||||
match element {
|
match element {
|
||||||
LayoutElement::Text(shaped) => {
|
LayoutElement::Text(shaped) => {
|
||||||
render_shaped(&mut surface, loader, shaped, pos, scale)
|
draw_text(&mut canvas, loader, shaped, pos)
|
||||||
}
|
|
||||||
LayoutElement::Image(image) => {
|
|
||||||
render_image(&mut surface, image, pos, scale)
|
|
||||||
}
|
}
|
||||||
|
LayoutElement::Image(image) => draw_image(&mut canvas, image, pos),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
offset.y += scale * layout.size.height + pad;
|
origin.y += layout.size.height + pad;
|
||||||
}
|
}
|
||||||
|
|
||||||
surface
|
canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_shaped(
|
fn draw_text(canvas: &mut Canvas, loader: &FontLoader, shaped: &Shaped, pos: Point) {
|
||||||
surface: &mut DrawTarget,
|
|
||||||
loader: &FontLoader,
|
|
||||||
shaped: &Shaped,
|
|
||||||
pos: Point,
|
|
||||||
scale: f64,
|
|
||||||
) {
|
|
||||||
let face = loader.get_loaded(shaped.face).get();
|
let face = loader.get_loaded(shaped.face).get();
|
||||||
|
|
||||||
for (&glyph, &offset) in shaped.glyphs.iter().zip(&shaped.offsets) {
|
for (&glyph, &offset) in shaped.glyphs.iter().zip(&shaped.offsets) {
|
||||||
|
let units_per_em = face.units_per_em().unwrap_or(1000);
|
||||||
|
|
||||||
|
let x = (pos.x + offset).to_pt() as f32;
|
||||||
|
let y = (pos.y + shaped.font_size).to_pt() as f32;
|
||||||
|
let scale = (shaped.font_size / units_per_em as f64).to_pt() as f32;
|
||||||
|
|
||||||
let mut builder = WrappedPathBuilder(PathBuilder::new());
|
let mut builder = WrappedPathBuilder(PathBuilder::new());
|
||||||
face.outline_glyph(glyph, &mut builder);
|
face.outline_glyph(glyph, &mut builder);
|
||||||
let path = builder.0.finish();
|
|
||||||
|
|
||||||
let units_per_em = face.units_per_em().unwrap_or(1000);
|
let path = builder.0.finish().unwrap();
|
||||||
let s = scale * (shaped.font_size / units_per_em as f64);
|
let placed = path
|
||||||
let x = pos.x + scale * offset;
|
.transform(&Transform::from_row(scale, 0.0, 0.0, -scale, x, y).unwrap())
|
||||||
let y = pos.y + scale * shaped.font_size;
|
.unwrap();
|
||||||
|
|
||||||
let t = Transform::create_scale(s.to_pt() as f32, -s.to_pt() as f32)
|
let mut paint = Paint::default();
|
||||||
.post_translate(Vector::new(x.to_pt() as f32, y.to_pt() as f32));
|
paint.anti_alias = true;
|
||||||
|
|
||||||
surface.fill(
|
canvas.fill_path(&placed, &paint, FillRule::default());
|
||||||
&path.transform(&t),
|
|
||||||
&Source::Solid(SolidSource { r: 0, g: 0, b: 0, a: 255 }),
|
|
||||||
&Default::default(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_image(surface: &mut DrawTarget, image: &ImageElement, pos: Point, scale: f64) {
|
fn draw_image(canvas: &mut Canvas, image: &ImageElement, pos: Point) {
|
||||||
let mut data = vec![];
|
let mut pixmap = Pixmap::new(image.buf.width(), image.buf.height()).unwrap();
|
||||||
for pixel in image.buf.pixels() {
|
for (src, dest) in image.buf.pixels().zip(pixmap.pixels_mut()) {
|
||||||
let [r, g, b, a] = pixel.0;
|
let [r, g, b, a] = src.0;
|
||||||
data.push(
|
*dest = ColorU8::from_rgba(r, g, b, a).premultiply();
|
||||||
((a as u32) << 24)
|
|
||||||
| ((r as u32) << 16)
|
|
||||||
| ((g as u32) << 8)
|
|
||||||
| ((b as u32) << 0),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
surface.draw_image_with_size_at(
|
let view_width = image.size.width.to_pt() as f32;
|
||||||
(scale * image.size.width.to_pt()) as f32,
|
let view_height = image.size.height.to_pt() as f32;
|
||||||
(scale * image.size.height.to_pt()) as f32,
|
|
||||||
pos.x.to_pt() as f32,
|
let x = pos.x.to_pt() as f32;
|
||||||
pos.y.to_pt() as f32,
|
let y = pos.y.to_pt() as f32;
|
||||||
&raqote::Image {
|
let scale_x = view_width as f32 / pixmap.width() as f32;
|
||||||
width: image.buf.dimensions().0 as i32,
|
let scale_y = view_height as f32 / pixmap.height() as f32;
|
||||||
height: image.buf.dimensions().1 as i32,
|
|
||||||
data: &data,
|
let mut paint = Paint::default();
|
||||||
},
|
paint.shader = Pattern::new(
|
||||||
&Default::default(),
|
&pixmap,
|
||||||
|
SpreadMode::Pad,
|
||||||
|
FilterQuality::Bilinear,
|
||||||
|
1.0,
|
||||||
|
Transform::from_row(scale_x, 0.0, 0.0, scale_y, x, y).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.fill_rect(
|
||||||
|
Rect::from_xywh(x, y, view_width, view_height).unwrap(),
|
||||||
|
&paint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user