-
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: tingerrr <[email protected]>
- Loading branch information
1 parent
d817e80
commit dd53e58
Showing
8 changed files
with
539 additions
and
5 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
//! This example demonstrates how to link/embed videos. | ||
use image::ColorType; | ||
use pdf_writer::types::{ | ||
ActionType, AnnotationType, MediaClipType, RenditionOperation, RenditionType, | ||
TempFileType, | ||
}; | ||
use pdf_writer::{Content, Filter, Finish, Name, Pdf, Rect, Ref, Str, TextStr}; | ||
|
||
fn get_bbox(page: &Rect, mut w: f32, mut h: f32) -> Rect { | ||
// Limit the width and height of the object to the page size, retaining the | ||
// aspect ratio. | ||
if w > (page.x2 - page.x1) { | ||
let f = (page.x2 - page.x1) / w; | ||
w *= f; | ||
h *= f; | ||
} | ||
if h > (page.y2 - page.y1) { | ||
let f = (page.y2 - page.y1) / h; | ||
w *= f; | ||
h *= f; | ||
} | ||
|
||
// Return a bounding box for the object centered on the page. | ||
Rect::new( | ||
(page.x2 - w) / 2.0, | ||
(page.y2 - h) / 2.0, | ||
(page.x2 + w) / 2.0, | ||
(page.y2 + h) / 2.0, | ||
) | ||
} | ||
|
||
fn main() -> std::io::Result<()> { | ||
let embedded = true; | ||
|
||
// Start writing. | ||
let mut pdf = Pdf::new(); | ||
|
||
// Define some indirect reference ids we'll use. | ||
let catalog_id = Ref::new(1); | ||
let page_tree_id = Ref::new(2); | ||
let page_id = Ref::new(3); | ||
let annotation_id = Ref::new(4); | ||
let video_file_id = Ref::new(5); | ||
let form_xobject_id = Ref::new(6); | ||
let image_id = Ref::new(7); | ||
let image_name = Name(b"Im1"); | ||
|
||
// Set up the page tree. For more details see `hello.rs`. | ||
pdf.catalog(catalog_id).pages(page_tree_id); | ||
pdf.pages(page_tree_id).kids([page_id]).count(1); | ||
|
||
// Specify one A4 landscape page. | ||
let mut page = pdf.page(page_id); | ||
let a4_landscape = Rect::new(0.0, 0.0, 842.0, 595.0); | ||
page.media_box(a4_landscape); | ||
page.parent(page_tree_id); | ||
page.annotations([annotation_id]); | ||
page.finish(); | ||
|
||
// Decode the image. | ||
// Image extracte from video file using ffmpeg: | ||
// ffmpeg -i bear-1280x720.mp4 -vf "select=eq(n\,0)" -q:v 3 bear-1280x720.jpg | ||
let data = std::fs::read("examples/bear-1280x720.jpg").unwrap(); | ||
let dynamic = image::load_from_memory(&data).unwrap(); | ||
assert!(dynamic.color() == ColorType::Rgb8); | ||
|
||
// Write the stream for the image we want to embed. | ||
let mut image = pdf.image_xobject(image_id, &data); | ||
image.filter(Filter::DctDecode); | ||
image.width(dynamic.width() as i32); | ||
image.height(dynamic.height() as i32); | ||
image.color_space().device_rgb(); | ||
image.bits_per_component(8); | ||
image.finish(); | ||
|
||
// Get a centered and fitted bounding box for the screen annotation and image. | ||
let bbox = get_bbox(&a4_landscape, dynamic.width() as f32, dynamic.height() as f32); | ||
|
||
// Place and size the image in a content stream. | ||
// | ||
// By default, PDF XObjects always have a size of 1x1 user units (and 1 user | ||
// unit is one 1pt if you don't change that). To position and size them, you | ||
// have to change the current transformation matrix, which is structured as | ||
// [scale_x, skew_x, skew_y, scale_y, translate_x, translate_y]. Also, | ||
// remember that the PDF coordinate system starts at the bottom left! When | ||
// you have other elements after the image, it's also important to save & | ||
// restore the state so that they are not affected by the transformation. | ||
let mut content = Content::new(); | ||
content.save_state(); | ||
content.transform([ | ||
(bbox.x2 - bbox.x1), | ||
0.0, | ||
0.0, | ||
(bbox.y2 - bbox.y1), | ||
bbox.x1, | ||
bbox.y1, | ||
]); | ||
content.x_object(image_name); | ||
content.restore_state(); | ||
let content_data = content.finish(); | ||
|
||
// Create a form XObject with the image for the appearance stream in the | ||
// screen annotation. | ||
let mut form_xobject = pdf.form_xobject(form_xobject_id, &content_data); | ||
form_xobject.bbox(bbox); | ||
form_xobject.resources().x_objects().pair(image_name, image_id); | ||
form_xobject.finish(); | ||
|
||
// Video file | ||
// Downloaded from the Chromium sources at: | ||
// https://github.com/chromium/chromium/blob/main/media/test/data/bear-1280x720.mp4 | ||
// Get the absolute path and file name. | ||
let file_path = std::fs::canonicalize("examples/bear-1280x720.mp4").unwrap(); | ||
let file_name = file_path.file_name().unwrap(); | ||
|
||
if embedded { | ||
// Read video file and add to pdf as embedded file. | ||
let data = std::fs::read(&file_path).unwrap(); | ||
pdf.embedded_file(video_file_id, &data); | ||
} | ||
|
||
// Create a screen annotation and set the appearance stream. | ||
let mut annotation = pdf.annotation(annotation_id); | ||
annotation.subtype(AnnotationType::Screen); | ||
annotation.rect(bbox); | ||
annotation.page(page_id); | ||
annotation.appearance().normal().stream(form_xobject_id); | ||
|
||
// Write a rendition action for the screen annotation. | ||
let mut action = annotation.action(); | ||
action.action_type(ActionType::Rendition); | ||
action.operation(RenditionOperation::Play); | ||
action.annotation(annotation_id); | ||
|
||
// Write a media rendition for the action. | ||
let mut rendition = action.rendition(); | ||
rendition.subtype(RenditionType::Media); | ||
|
||
// Write the media clip data for the media rendition. | ||
let mut media_clip = rendition.media_clip(); | ||
media_clip.subtype(MediaClipType::Data); | ||
if embedded { | ||
media_clip | ||
.data() | ||
.path(Str(file_name.as_encoded_bytes())) | ||
.embedded_file(video_file_id); | ||
} else { | ||
// FIXME: Is there a more elegant way to assemble the URL? | ||
let file_url = &[b"file://", file_path.as_os_str().as_encoded_bytes()].concat(); | ||
media_clip.data().file_system(Name(b"URL")).path(Str(file_url)); | ||
} | ||
media_clip.data_type(Str(b"video/mp4")); | ||
media_clip.permissions().temp_file(TempFileType::Access); | ||
media_clip.alt_texts([TextStr(""), TextStr("default text")]); | ||
media_clip.finish(); | ||
|
||
// Add controls for the media player. | ||
rendition.media_play_params().controls(true); | ||
|
||
// Finish off a few things. | ||
rendition.finish(); | ||
action.finish(); | ||
annotation.finish(); | ||
|
||
// Write the thing to a file. | ||
std::fs::write("target/video.pdf", pdf.finish()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.