mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Bump pdf-writer
This commit is contained in:
parent
7f48e8fe66
commit
d2618acd8d
@ -24,7 +24,7 @@ fxhash = "0.2.1"
|
|||||||
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
miniz_oxide = "0.4"
|
miniz_oxide = "0.4"
|
||||||
pdf-writer = "0.3.2"
|
pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "a750b66" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rustybuzz = "0.4"
|
rustybuzz = "0.4"
|
||||||
serde = { version = "1", features = ["derive", "rc"] }
|
serde = { version = "1", features = ["derive", "rc"] }
|
||||||
|
@ -6,10 +6,10 @@ use std::hash::Hash;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba};
|
use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba};
|
||||||
use pdf_writer::{
|
use pdf_writer::types::{
|
||||||
ActionType, AnnotationType, CidFontType, ColorSpace, Content, Filter, FontFlags,
|
ActionType, AnnotationType, CidFontType, ColorSpace, FontFlags, SystemInfo,
|
||||||
Name, PdfWriter, Rect, Ref, Str, SystemInfo, UnicodeCmap,
|
|
||||||
};
|
};
|
||||||
|
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Str, UnicodeCmap};
|
||||||
use ttf_parser::{name_id, GlyphId, Tag};
|
use ttf_parser::{name_id, GlyphId, Tag};
|
||||||
|
|
||||||
use super::subset;
|
use super::subset;
|
||||||
@ -70,11 +70,8 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut writer = PdfWriter::new(1, 7);
|
|
||||||
writer.set_indent(2);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
writer,
|
writer: PdfWriter::new(),
|
||||||
refs: Refs::new(frames.len(), font_map.len(), image_map.len(), alpha_masks),
|
refs: Refs::new(frames.len(), font_map.len(), image_map.len(), alpha_masks),
|
||||||
frames,
|
frames,
|
||||||
fonts: &ctx.fonts,
|
fonts: &ctx.fonts,
|
||||||
@ -108,7 +105,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
fonts.pair(Name(name.as_bytes()), refs.type0_font);
|
fonts.pair(Name(name.as_bytes()), refs.type0_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(fonts);
|
fonts.finish();
|
||||||
|
|
||||||
let mut images = resources.x_objects();
|
let mut images = resources.x_objects();
|
||||||
for (id, im) in self.refs.images().zip(self.image_map.pdf_indices()) {
|
for (id, im) in self.refs.images().zip(self.image_map.pdf_indices()) {
|
||||||
@ -116,9 +113,9 @@ impl<'a> PdfExporter<'a> {
|
|||||||
images.pair(Name(name.as_bytes()), id);
|
images.pair(Name(name.as_bytes()), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(images);
|
images.finish();
|
||||||
drop(resources);
|
resources.finish();
|
||||||
drop(pages);
|
pages.finish();
|
||||||
|
|
||||||
// The page objects (non-root nodes in the page tree).
|
// The page objects (non-root nodes in the page tree).
|
||||||
for ((page_id, content_id), page) in
|
for ((page_id, content_id), page) in
|
||||||
@ -150,7 +147,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(annotations);
|
annotations.finish();
|
||||||
page_writer.contents(content_id);
|
page_writer.contents(content_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,8 +166,24 @@ impl<'a> PdfExporter<'a> {
|
|||||||
let mut face_id = None;
|
let mut face_id = None;
|
||||||
let mut size = Length::zero();
|
let mut size = Length::zero();
|
||||||
let mut fill: Option<Paint> = None;
|
let mut fill: Option<Paint> = None;
|
||||||
|
let mut in_text_state = false;
|
||||||
|
|
||||||
for (pos, element) in page.elements() {
|
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 x = pos.x.to_pt() as f32;
|
||||||
let y = (page.size.h - pos.y).to_pt() as f32;
|
let y = (page.size.h - pos.y).to_pt() as f32;
|
||||||
|
|
||||||
@ -181,8 +194,6 @@ impl<'a> PdfExporter<'a> {
|
|||||||
fill = Some(text.fill);
|
fill = Some(text.fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut text_writer = content.text();
|
|
||||||
|
|
||||||
// Then, also check if we need to issue a font switching
|
// Then, also check if we need to issue a font switching
|
||||||
// action.
|
// action.
|
||||||
if face_id != Some(text.face_id) || text.size != size {
|
if face_id != Some(text.face_id) || text.size != size {
|
||||||
@ -190,15 +201,16 @@ impl<'a> PdfExporter<'a> {
|
|||||||
size = text.size;
|
size = text.size;
|
||||||
|
|
||||||
let name = format!("F{}", self.font_map.map(text.face_id));
|
let name = format!("F{}", self.font_map.map(text.face_id));
|
||||||
text_writer.font(Name(name.as_bytes()), size.to_pt() as f32);
|
content.set_font(Name(name.as_bytes()), size.to_pt() as f32);
|
||||||
}
|
}
|
||||||
|
|
||||||
let face = self.fonts.get(text.face_id);
|
let face = self.fonts.get(text.face_id);
|
||||||
|
|
||||||
// Position the text.
|
// Position the text.
|
||||||
text_writer.matrix(1.0, 0.0, 0.0, 1.0, x, y);
|
content.set_text_matrix([1.0, 0.0, 0.0, 1.0, x, y]);
|
||||||
|
|
||||||
let mut positioned = text_writer.show_positioned();
|
let mut positioned = content.show_positioned();
|
||||||
|
let mut items = positioned.items();
|
||||||
let mut adjustment = Em::zero();
|
let mut adjustment = Em::zero();
|
||||||
let mut encoded = vec![];
|
let mut encoded = vec![];
|
||||||
|
|
||||||
@ -208,11 +220,11 @@ impl<'a> PdfExporter<'a> {
|
|||||||
|
|
||||||
if !adjustment.is_zero() {
|
if !adjustment.is_zero() {
|
||||||
if !encoded.is_empty() {
|
if !encoded.is_empty() {
|
||||||
positioned.show(Str(&encoded));
|
items.show(Str(&encoded));
|
||||||
encoded.clear();
|
encoded.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
positioned.adjust(-adjustment.to_pdf());
|
items.adjust(-adjustment.to_pdf());
|
||||||
adjustment = Em::zero();
|
adjustment = Em::zero();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +239,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !encoded.is_empty() {
|
if !encoded.is_empty() {
|
||||||
positioned.show(Str(&encoded));
|
items.show(Str(&encoded));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,24 +252,27 @@ impl<'a> PdfExporter<'a> {
|
|||||||
let h = h.to_pt() as f32;
|
let h = h.to_pt() as f32;
|
||||||
if w > 0.0 && h > 0.0 {
|
if w > 0.0 && h > 0.0 {
|
||||||
write_fill(&mut content, paint);
|
write_fill(&mut content, paint);
|
||||||
content.rect(x, y - h, w, h, false, true);
|
content.rect(x, y - h, w, h);
|
||||||
|
content.fill_nonzero();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Geometry::Ellipse(size) => {
|
Geometry::Ellipse(size) => {
|
||||||
let path = geom::Path::ellipse(size);
|
let path = geom::Path::ellipse(size);
|
||||||
write_fill(&mut content, paint);
|
write_fill(&mut content, paint);
|
||||||
write_path(&mut content, x, y, &path, false, true);
|
write_path(&mut content, x, y, &path);
|
||||||
}
|
}
|
||||||
Geometry::Line(target, thickness) => {
|
Geometry::Line(target, thickness) => {
|
||||||
write_stroke(&mut content, paint, thickness.to_pt() as f32);
|
write_stroke(&mut content, paint, thickness.to_pt() as f32);
|
||||||
content.path(true, false).move_to(x, y).line_to(
|
content.move_to(x, y);
|
||||||
|
content.line_to(
|
||||||
x + target.x.to_pt() as f32,
|
x + target.x.to_pt() as f32,
|
||||||
y - target.y.to_pt() as f32,
|
y - target.y.to_pt() as f32,
|
||||||
);
|
);
|
||||||
|
content.stroke();
|
||||||
}
|
}
|
||||||
Geometry::Path(ref path) => {
|
Geometry::Path(ref path) => {
|
||||||
write_fill(&mut content, paint);
|
write_fill(&mut content, paint);
|
||||||
write_path(&mut content, x, y, path, false, true)
|
write_path(&mut content, x, y, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +285,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
let h = h.to_pt() as f32;
|
let h = h.to_pt() as f32;
|
||||||
|
|
||||||
content.save_state();
|
content.save_state();
|
||||||
content.matrix(w, 0.0, 0.0, h, x, y - h);
|
content.concat_matrix([w, 0.0, 0.0, h, x, y - h]);
|
||||||
content.x_object(Name(name.as_bytes()));
|
content.x_object(Name(name.as_bytes()));
|
||||||
content.restore_state();
|
content.restore_state();
|
||||||
}
|
}
|
||||||
@ -279,6 +294,10 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if in_text_state {
|
||||||
|
content.end_text();
|
||||||
|
}
|
||||||
|
|
||||||
self.writer
|
self.writer
|
||||||
.stream(id, &deflate(&content.finish()))
|
.stream(id, &deflate(&content.finish()))
|
||||||
.filter(Filter::FlateDecode);
|
.filter(Filter::FlateDecode);
|
||||||
@ -432,7 +451,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
let (alpha_data, alpha_filter) = encode_alpha(img);
|
let (alpha_data, alpha_filter) = encode_alpha(img);
|
||||||
let mask_id = self.refs.alpha_mask(masks_seen);
|
let mask_id = self.refs.alpha_mask(masks_seen);
|
||||||
image.s_mask(mask_id);
|
image.s_mask(mask_id);
|
||||||
drop(image);
|
image.finish();
|
||||||
|
|
||||||
let mut mask = self.writer.image(mask_id, &alpha_data);
|
let mut mask = self.writer.image(mask_id, &alpha_data);
|
||||||
mask.filter(alpha_filter);
|
mask.filter(alpha_filter);
|
||||||
@ -459,39 +478,31 @@ impl<'a> PdfExporter<'a> {
|
|||||||
/// Write a fill change into a content stream.
|
/// Write a fill change into a content stream.
|
||||||
fn write_fill(content: &mut Content, fill: Paint) {
|
fn write_fill(content: &mut Content, fill: Paint) {
|
||||||
let Paint::Color(Color::Rgba(c)) = fill;
|
let Paint::Color(Color::Rgba(c)) = fill;
|
||||||
content.fill_rgb(c.r as f32 / 255.0, c.g as f32 / 255.0, c.b as f32 / 255.0);
|
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.
|
/// Write a stroke change into a content stream.
|
||||||
fn write_stroke(content: &mut Content, stroke: Paint, thickness: f32) {
|
fn write_stroke(content: &mut Content, stroke: Paint, thickness: f32) {
|
||||||
match stroke {
|
match stroke {
|
||||||
Paint::Color(Color::Rgba(c)) => {
|
Paint::Color(Color::Rgba(c)) => {
|
||||||
content.stroke_rgb(
|
content.set_stroke_rgb(
|
||||||
c.r as f32 / 255.0,
|
c.r as f32 / 255.0,
|
||||||
c.g as f32 / 255.0,
|
c.g as f32 / 255.0,
|
||||||
c.b as f32 / 255.0,
|
c.b as f32 / 255.0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content.line_width(thickness);
|
content.set_line_width(thickness);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a path into a content stream.
|
/// Write a path into a content stream.
|
||||||
fn write_path(
|
fn write_path(content: &mut Content, x: f32, y: f32, path: &geom::Path) {
|
||||||
content: &mut Content,
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
path: &geom::Path,
|
|
||||||
stroke: bool,
|
|
||||||
fill: bool,
|
|
||||||
) {
|
|
||||||
let f = |length: Length| length.to_pt() as f32;
|
let f = |length: Length| length.to_pt() as f32;
|
||||||
let mut builder = content.path(stroke, fill);
|
|
||||||
for elem in &path.0 {
|
for elem in &path.0 {
|
||||||
match elem {
|
match elem {
|
||||||
geom::PathElement::MoveTo(p) => builder.move_to(x + f(p.x), y + f(p.y)),
|
geom::PathElement::MoveTo(p) => content.move_to(x + f(p.x), y + f(p.y)),
|
||||||
geom::PathElement::LineTo(p) => builder.line_to(x + f(p.x), y + f(p.y)),
|
geom::PathElement::LineTo(p) => content.line_to(x + f(p.x), y + f(p.y)),
|
||||||
geom::PathElement::CubicTo(p1, p2, p3) => builder.cubic_to(
|
geom::PathElement::CubicTo(p1, p2, p3) => content.cubic_to(
|
||||||
x + f(p1.x),
|
x + f(p1.x),
|
||||||
y + f(p1.y),
|
y + f(p1.y),
|
||||||
x + f(p2.x),
|
x + f(p2.x),
|
||||||
@ -499,9 +510,10 @@ fn write_path(
|
|||||||
x + f(p3.x),
|
x + f(p3.x),
|
||||||
y + f(p3.y),
|
y + f(p3.y),
|
||||||
),
|
),
|
||||||
geom::PathElement::ClosePath => builder.close_path(),
|
geom::PathElement::ClosePath => content.close_path(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
content.fill_nonzero();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The compression level for the deflating.
|
/// The compression level for the deflating.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user