mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Export alpha channel into PDF 🧊
This commit is contained in:
parent
982e7671a6
commit
21857064db
@ -5,7 +5,7 @@ use std::collections::HashMap;
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use fontdock::FaceId;
|
use fontdock::FaceId;
|
||||||
use image::{DynamicImage, GenericImageView};
|
use image::{DynamicImage, GenericImageView, Rgba};
|
||||||
use pdf_writer::{
|
use pdf_writer::{
|
||||||
CidFontType, ColorSpace, Content, FontFlags, Name, PdfWriter, Rect, Ref, Str,
|
CidFontType, ColorSpace, Content, FontFlags, Name, PdfWriter, Rect, Ref, Str,
|
||||||
SystemInfo, UnicodeCmap,
|
SystemInfo, UnicodeCmap,
|
||||||
@ -43,17 +43,24 @@ impl<'a> PdfExporter<'a> {
|
|||||||
|
|
||||||
let mut fonts = Remapper::new();
|
let mut fonts = Remapper::new();
|
||||||
let mut images = Remapper::new();
|
let mut images = Remapper::new();
|
||||||
|
let mut alpha_masks = 0;
|
||||||
|
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
for (_, element) in &layout.elements {
|
for (_, element) in &layout.elements {
|
||||||
match element {
|
match element {
|
||||||
LayoutElement::Text(shaped) => fonts.insert(shaped.face),
|
LayoutElement::Text(shaped) => fonts.insert(shaped.face),
|
||||||
LayoutElement::Image(image) => images.insert(image.resource),
|
LayoutElement::Image(image) => {
|
||||||
|
let buf = env.resources.get_loaded::<DynamicImage>(image.res);
|
||||||
|
if buf.color().has_alpha() {
|
||||||
|
alpha_masks += 1;
|
||||||
|
}
|
||||||
|
images.insert(image.res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let refs = Refs::new(layouts.len(), fonts.len(), images.len());
|
let refs = Refs::new(layouts.len(), fonts.len(), images.len(), alpha_masks);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
writer,
|
writer,
|
||||||
@ -154,7 +161,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
|
|
||||||
for (pos, element) in &page.elements {
|
for (pos, element) in &page.elements {
|
||||||
if let LayoutElement::Image(image) = element {
|
if let LayoutElement::Image(image) = element {
|
||||||
let name = format!("Im{}", self.images.map(image.resource));
|
let name = format!("Im{}", self.images.map(image.res));
|
||||||
let size = image.size;
|
let size = image.size;
|
||||||
let x = pos.x.to_pt() as f32;
|
let x = pos.x.to_pt() as f32;
|
||||||
let y = (page.size.height - pos.y - size.height).to_pt() as f32;
|
let y = (page.size.height - pos.y - size.height).to_pt() as f32;
|
||||||
@ -281,15 +288,40 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_images(&mut self) {
|
fn write_images(&mut self) {
|
||||||
|
let mut mask = 0;
|
||||||
|
|
||||||
for (id, resource) in self.refs.images().zip(self.images.layout_indices()) {
|
for (id, resource) in self.refs.images().zip(self.images.layout_indices()) {
|
||||||
let image = self.env.resources.get_loaded::<DynamicImage>(resource);
|
let buf = self.env.resources.get_loaded::<DynamicImage>(resource);
|
||||||
let data = image.to_rgb8().into_raw();
|
let data = buf.to_rgb8().into_raw();
|
||||||
|
|
||||||
|
let mut image = self.writer.image_stream(id, &data);
|
||||||
|
image.width(buf.width() as i32);
|
||||||
|
image.height(buf.height() as i32);
|
||||||
|
image.color_space(ColorSpace::DeviceRGB);
|
||||||
|
image.bits_per_component(8);
|
||||||
|
|
||||||
|
// Add a second gray-scale image containing the alpha values if this
|
||||||
|
// is image has an alpha channel.
|
||||||
|
if buf.color().has_alpha() {
|
||||||
|
let mask_id = self.refs.alpha_mask(mask);
|
||||||
|
|
||||||
|
image.s_mask(mask_id);
|
||||||
|
drop(image);
|
||||||
|
|
||||||
|
let mut samples = vec![];
|
||||||
|
for (_, _, Rgba([_, _, _, a])) in buf.pixels() {
|
||||||
|
samples.push(a);
|
||||||
|
}
|
||||||
|
|
||||||
self.writer
|
self.writer
|
||||||
.image_stream(id, &data)
|
.image_stream(mask_id, &samples)
|
||||||
.width(image.width() as i32)
|
.width(buf.width() as i32)
|
||||||
.height(image.height() as i32)
|
.height(buf.height() as i32)
|
||||||
.color_space(ColorSpace::DeviceRGB)
|
.color_space(ColorSpace::DeviceGray)
|
||||||
.bits_per_component(8);
|
.bits_per_component(8);
|
||||||
|
|
||||||
|
mask += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,6 +336,7 @@ struct Refs {
|
|||||||
contents_start: i32,
|
contents_start: i32,
|
||||||
fonts_start: i32,
|
fonts_start: i32,
|
||||||
images_start: i32,
|
images_start: i32,
|
||||||
|
alpha_masks_start: i32,
|
||||||
end: i32,
|
end: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,14 +351,15 @@ struct FontRefs {
|
|||||||
impl Refs {
|
impl Refs {
|
||||||
const OBJECTS_PER_FONT: usize = 5;
|
const OBJECTS_PER_FONT: usize = 5;
|
||||||
|
|
||||||
fn new(layouts: usize, fonts: usize, images: usize) -> Self {
|
fn new(layouts: usize, fonts: usize, images: usize, alpha_masks: usize) -> Self {
|
||||||
let catalog = 1;
|
let catalog = 1;
|
||||||
let page_tree = catalog + 1;
|
let page_tree = catalog + 1;
|
||||||
let pages_start = page_tree + 1;
|
let pages_start = page_tree + 1;
|
||||||
let contents_start = pages_start + layouts as i32;
|
let contents_start = pages_start + layouts as i32;
|
||||||
let fonts_start = contents_start + layouts as i32;
|
let fonts_start = contents_start + layouts as i32;
|
||||||
let images_start = fonts_start + (Self::OBJECTS_PER_FONT * fonts) as i32;
|
let images_start = fonts_start + (Self::OBJECTS_PER_FONT * fonts) as i32;
|
||||||
let end = images_start + images as i32;
|
let alpha_masks_start = images_start + images as i32;
|
||||||
|
let end = alpha_masks_start + alpha_masks as i32;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
catalog: Ref::new(catalog),
|
catalog: Ref::new(catalog),
|
||||||
@ -334,6 +368,7 @@ impl Refs {
|
|||||||
contents_start,
|
contents_start,
|
||||||
fonts_start,
|
fonts_start,
|
||||||
images_start,
|
images_start,
|
||||||
|
alpha_masks_start,
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -361,6 +396,10 @@ impl Refs {
|
|||||||
fn images(&self) -> impl Iterator<Item = Ref> {
|
fn images(&self) -> impl Iterator<Item = Ref> {
|
||||||
(self.images_start .. self.end).map(Ref::new)
|
(self.images_start .. self.end).map(Ref::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn alpha_mask(&self, i: usize) -> Ref {
|
||||||
|
Ref::new(self.alpha_masks_start + i as i32)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to assign new, consecutive PDF-internal indices to things.
|
/// Used to assign new, consecutive PDF-internal indices to things.
|
||||||
|
@ -183,7 +183,7 @@ pub enum LayoutElement {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ImageElement {
|
pub struct ImageElement {
|
||||||
/// The image.
|
/// The image.
|
||||||
pub resource: ResourceId,
|
pub res: ResourceId,
|
||||||
/// The document size of the image.
|
/// The document size of the image.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,11 @@ pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|||||||
.and_then(|reader| reader.decode().ok())
|
.and_then(|reader| reader.decode().ok())
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((resource, buf)) = loaded {
|
if let Some((res, buf)) = loaded {
|
||||||
let dimensions = buf.dimensions();
|
let dimensions = buf.dimensions();
|
||||||
drop(env);
|
drop(env);
|
||||||
ctx.push(Image {
|
ctx.push(Image {
|
||||||
resource,
|
res,
|
||||||
dimensions,
|
dimensions,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -50,7 +50,7 @@ pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
struct Image {
|
struct Image {
|
||||||
/// The resource id of the image file.
|
/// The resource id of the image file.
|
||||||
resource: ResourceId,
|
res: ResourceId,
|
||||||
/// The pixel dimensions of the image.
|
/// The pixel dimensions of the image.
|
||||||
dimensions: (u32, u32),
|
dimensions: (u32, u32),
|
||||||
/// The fixed width, if any.
|
/// The fixed width, if any.
|
||||||
@ -87,7 +87,7 @@ impl Layout for Image {
|
|||||||
let mut boxed = BoxLayout::new(size);
|
let mut boxed = BoxLayout::new(size);
|
||||||
boxed.push(
|
boxed.push(
|
||||||
Point::ZERO,
|
Point::ZERO,
|
||||||
LayoutElement::Image(ImageElement { resource: self.resource, size }),
|
LayoutElement::Image(ImageElement { res: self.res, size }),
|
||||||
);
|
);
|
||||||
|
|
||||||
Layouted::Layout(boxed, self.align)
|
Layouted::Layout(boxed, self.align)
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 930 KiB After Width: | Height: | Size: 292 KiB |
BIN
tests/res/tiger-alpha.png
Normal file
BIN
tests/res/tiger-alpha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 119 KiB |
@ -1,14 +1,15 @@
|
|||||||
[page: width=10cm, height=10cm, margins=1cm]
|
[page: width=5cm, height=5cm, margins=0.25cm]
|
||||||
|
|
||||||
[image: "res/tiger.jpg"]
|
[image: "res/tiger.jpg"]
|
||||||
|
|
||||||
[pagebreak]
|
[pagebreak]
|
||||||
|
|
||||||
# Tiger
|
# Tiger
|
||||||
[image: "res/tiger.jpg", width=3cm]
|
[image: "res/tiger.jpg", width=2cm]
|
||||||
[image: "res/tiger.jpg", height=3cm]
|
[image: "res/tiger-alpha.png", width=1cm]
|
||||||
|
[image: "res/tiger-alpha.png", height=2cm]
|
||||||
|
|
||||||
[pagebreak]
|
[pagebreak]
|
||||||
|
|
||||||
[align: center]
|
[align: center, bottom]
|
||||||
[image: "res/tiger.jpg", width=6cm, height=6cm]
|
[image: "res/tiger.jpg", width=2cm, height=3.5cm]
|
||||||
|
@ -248,7 +248,7 @@ fn draw_text(canvas: &mut Canvas, pos: Point, env: &Env, shaped: &Shaped) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, image: &ImageElement) {
|
fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, image: &ImageElement) {
|
||||||
let buf = env.resources.get_loaded::<DynamicImage>(image.resource);
|
let buf = env.resources.get_loaded::<DynamicImage>(image.res);
|
||||||
|
|
||||||
let mut pixmap = Pixmap::new(buf.width(), buf.height()).unwrap();
|
let mut pixmap = Pixmap::new(buf.width(), buf.height()).unwrap();
|
||||||
for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) {
|
for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user