diff --git a/Makefile b/Makefile index 8d0a700..37a842b 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ MODS = cpdfyojson cpdfxmlm \ cpdfattach cpdfpagespec cpdfposition cpdfpresent cpdfmetadata \ cpdfbookmarks cpdfpage cpdfaddtext cpdf cpdfimage cpdffont cpdftype \ cpdftexttopdf cpdftoc cpdfpad cpdfocg cpdfsqueeze cpdfdraft cpdfspot \ - cpdfpagelabels cpdfcreate cpdfannot cpdfcommand + cpdfpagelabels cpdfcreate cpdfannot cpdfxobject cpdfcommand SOURCES = $(foreach x,$(MODS),$(x).ml $(x).mli) cpdfcommandrun.ml diff --git a/cpdf.ml b/cpdf.ml index 696d6dc..151a54f 100644 --- a/cpdf.ml +++ b/cpdf.ml @@ -424,31 +424,6 @@ let do_stamp relative_to_cropbox fast position topline midline scale_to_fit isov Pdfpage.resources = combine_pdf_resources pdf u.Pdfpage.resources o.Pdfpage.resources} -(* Alter bookmark destinations given a hash table of (old page reference - * number, new page reference number) pairings *) -let change_destination t = function - Pdfdest.XYZ (Pdfdest.PageObject p, a, b, c) -> - Pdfdest.XYZ (Pdfdest.PageObject (Hashtbl.find t p), a, b, c) - | Pdfdest.Fit (Pdfdest.PageObject p) -> - Pdfdest.Fit (Pdfdest.PageObject (Hashtbl.find t p)) - | Pdfdest.FitH (Pdfdest.PageObject p, x) -> - Pdfdest.FitH (Pdfdest.PageObject (Hashtbl.find t p), x) - | Pdfdest.FitV (Pdfdest.PageObject p, x) -> - Pdfdest.FitV (Pdfdest.PageObject (Hashtbl.find t p), x) - | Pdfdest.FitR (Pdfdest.PageObject p, a, b, c, d) -> - Pdfdest.FitR (Pdfdest.PageObject (Hashtbl.find t p), a, b, c, d) - | Pdfdest.FitB (Pdfdest.PageObject p) -> - Pdfdest.Fit (Pdfdest.PageObject (Hashtbl.find t p)) - | Pdfdest.FitBH (Pdfdest.PageObject p, x) -> - Pdfdest.FitBH (Pdfdest.PageObject (Hashtbl.find t p), x) - | Pdfdest.FitBV (Pdfdest.PageObject p, x) -> - Pdfdest.FitBV (Pdfdest.PageObject (Hashtbl.find t p), x) - | x -> x - -let change_bookmark t m = - {m with Pdfmarks.target = - try change_destination t m.Pdfmarks.target with Not_found -> m.Pdfmarks.target} - let stamp relative_to_cropbox position topline midline fast scale_to_fit isover range over pdf = let prefix = Pdfpage.shortest_unused_prefix pdf in Pdfpage.add_prefix over prefix; @@ -492,93 +467,9 @@ let stamp relative_to_cropbox position topline midline fast scale_to_fit isover in let new_refnumbers = Pdf.page_reference_numbers changed in let changetable = hashtable_of_dictionary (combine marks_refnumbers new_refnumbers) in - let new_marks = map (change_bookmark changetable) marks in + let new_marks = map (Cpdfbookmarks.change_bookmark changetable) marks in Pdfmarks.add_bookmarks new_marks changed -let add_xobject_to_page xobjname xobjnum page pdf = - let resources' = - let xobjects = - match Pdf.lookup_direct pdf "/XObject" page.Pdfpage.resources with - | Some xobjects -> xobjects - | _ -> Pdf.Dictionary [] - in - let new_xobjects = - Pdf.add_dict_entry xobjects xobjname (Pdf.Indirect xobjnum) - in - Pdf.add_dict_entry page.Pdfpage.resources "/XObject" new_xobjects - in - {page with Pdfpage.resources = resources'} - -let add_page_as_xobject pdf range page name = - let xobject_data = - match Pdfops.stream_of_ops (Pdfops.parse_operators pdf page.Pdfpage.resources page.Pdfpage.content) with - Pdf.Stream {contents = (_, Got b)} -> b - | _ -> assert false - in - let xobject_dict = - ["/Type", Pdf.Name "/XObject"; - "/Subtype", Pdf.Name "/Form"; - "/BBox", page.Pdfpage.mediabox; - "/Resources", page.Pdfpage.resources; - "/Length", Pdf.Integer (bytes_size xobject_data)] - in - let xobject = - Pdf.Stream {contents = (Pdf.Dictionary xobject_dict, Pdf.Got xobject_data)} - in - let xobject_objnum = Pdf.addobj pdf xobject in - let pages = Pdfpage.pages_of_pagetree pdf in - let new_pages = - List.map2 - (fun page pnum -> - if mem pnum range - then add_xobject_to_page name xobject_objnum page pdf - else page) - pages - (indx pages) - in - Pdfpage.change_pages true pdf new_pages - -(* n.b the use of change_pages here ensures no inheritable resources in the - * stamp, therefore creation of xobject from page is as simple as expected. *) -let stamp_as_xobject pdf range over = - let prefix = Pdfpage.shortest_unused_prefix pdf in - Pdfpage.add_prefix over prefix; - let marks = Pdfmarks.read_bookmarks pdf in - let marks_refnumbers = Pdf.page_reference_numbers pdf in - let pdf = Pdfmarks.remove_bookmarks pdf in - let over = Pdfmarks.remove_bookmarks over in - let pageseqs = ilist 1 (Pdfpage.endpage pdf) in - let over_firstpage_pdf = - match Pdfpage.pages_of_pagetree over with - | [] -> error "empty PDF" - | h::_ -> Pdfpage.change_pages ~changes:[(1, 1)] true over [h] - in - let merged = - Pdfmerge.merge_pdfs - false false ["a"; "b"] [pdf; over_firstpage_pdf] [pageseqs; [1]] - in - let merged = - {merged with Pdf.saved_encryption = pdf.Pdf.saved_encryption} - in - let merged = Cpdfmetadata.copy_id true pdf merged in - let merged_pages = Pdfpage.pages_of_pagetree merged in - let under_pages, over_page = - all_but_last merged_pages, last merged_pages - in - let new_pages = under_pages in - let changed = - let changes = - map (fun x -> (x, x)) (ilist 1 (length new_pages)) - in - Pdfpage.change_pages ~changes true merged new_pages - in - let new_refnumbers = Pdf.page_reference_numbers changed in - let changetable = hashtable_of_dictionary (combine marks_refnumbers new_refnumbers) in - let new_marks = map (change_bookmark changetable) marks in - let pdf = Pdfmarks.add_bookmarks new_marks changed in - let name = "/" ^ Pdfpage.shortest_unused_prefix pdf ^ "CPDFXObj" in - (add_page_as_xobject pdf range over_page name, name) - (* Combine pages from two PDFs. For now, assume equal length. *) (* If [over] has more pages than [under], chop the excess. If the converse, pad diff --git a/cpdf.mli b/cpdf.mli index ef5af3a..4b852e4 100644 --- a/cpdf.mli +++ b/cpdf.mli @@ -121,8 +121,6 @@ val process_xobjects : Pdf.t -> Pdfpage.t -> (Pdf.t -> Pdf.pdfobject -> Pdf.pdfo val append_page_content : string -> bool -> bool -> int list -> Pdf.t -> Pdf.t -val stamp_as_xobject : Pdf.t -> int list -> Pdf.t -> Pdf.t * string - val remove_dict_entry : Pdf.t -> string -> Pdf.pdfobject option -> unit val replace_dict_entry : Pdf.t -> string -> Pdf.pdfobject -> Pdf.pdfobject option -> unit diff --git a/cpdfbookmarks.ml b/cpdfbookmarks.ml index 8be3464..1f552a4 100644 --- a/cpdfbookmarks.ml +++ b/cpdfbookmarks.ml @@ -393,3 +393,28 @@ let bookmarks_open_to_level n pdf = in Pdfmarks.add_bookmarks newmarks pdf +(* Alter bookmark destinations given a hash table of (old page reference + * number, new page reference number) pairings *) +let change_destination t = function + Pdfdest.XYZ (Pdfdest.PageObject p, a, b, c) -> + Pdfdest.XYZ (Pdfdest.PageObject (Hashtbl.find t p), a, b, c) + | Pdfdest.Fit (Pdfdest.PageObject p) -> + Pdfdest.Fit (Pdfdest.PageObject (Hashtbl.find t p)) + | Pdfdest.FitH (Pdfdest.PageObject p, x) -> + Pdfdest.FitH (Pdfdest.PageObject (Hashtbl.find t p), x) + | Pdfdest.FitV (Pdfdest.PageObject p, x) -> + Pdfdest.FitV (Pdfdest.PageObject (Hashtbl.find t p), x) + | Pdfdest.FitR (Pdfdest.PageObject p, a, b, c, d) -> + Pdfdest.FitR (Pdfdest.PageObject (Hashtbl.find t p), a, b, c, d) + | Pdfdest.FitB (Pdfdest.PageObject p) -> + Pdfdest.Fit (Pdfdest.PageObject (Hashtbl.find t p)) + | Pdfdest.FitBH (Pdfdest.PageObject p, x) -> + Pdfdest.FitBH (Pdfdest.PageObject (Hashtbl.find t p), x) + | Pdfdest.FitBV (Pdfdest.PageObject p, x) -> + Pdfdest.FitBV (Pdfdest.PageObject (Hashtbl.find t p), x) + | x -> x + +let change_bookmark t m = + {m with Pdfmarks.target = + try change_destination t m.Pdfmarks.target with Not_found -> m.Pdfmarks.target} + diff --git a/cpdfbookmarks.mli b/cpdfbookmarks.mli index ade5d69..7772c0c 100644 --- a/cpdfbookmarks.mli +++ b/cpdfbookmarks.mli @@ -18,4 +18,7 @@ val name_of_spec : Cpdfmetadata.encoding -> Pdf.t -> int -> string -> int -> string -> int -> int -> string val add_bookmark_title : string -> bool -> Pdf.t -> Pdf.t + val bookmarks_open_to_level : int -> Pdf.t -> Pdf.t + +val change_bookmark : (int, int) Hashtbl.t -> Pdfmarks.t -> Pdfmarks.t diff --git a/cpdfcommand.ml b/cpdfcommand.ml index cc919dc..d27b10e 100644 --- a/cpdfcommand.ml +++ b/cpdfcommand.ml @@ -3900,7 +3900,7 @@ let go () = let pdf = get_single_pdf args.op false in let range = parse_pagespec_allow_empty pdf (get_pagespec ()) in let pdf, xobj_name = - Cpdf.stamp_as_xobject pdf range stamp_pdf + Cpdfxobject.stamp_as_xobject pdf range stamp_pdf in Printf.printf "%s\n" xobj_name; flush stdout; diff --git a/cpdfxobject.ml b/cpdfxobject.ml new file mode 100644 index 0000000..3108caa --- /dev/null +++ b/cpdfxobject.ml @@ -0,0 +1,88 @@ +open Pdfutil +open Pdfio +open Cpdferror + +let add_xobject_to_page xobjname xobjnum page pdf = + let resources' = + let xobjects = + match Pdf.lookup_direct pdf "/XObject" page.Pdfpage.resources with + | Some xobjects -> xobjects + | _ -> Pdf.Dictionary [] + in + let new_xobjects = + Pdf.add_dict_entry xobjects xobjname (Pdf.Indirect xobjnum) + in + Pdf.add_dict_entry page.Pdfpage.resources "/XObject" new_xobjects + in + {page with Pdfpage.resources = resources'} + +let add_page_as_xobject pdf range page name = + let xobject_data = + match Pdfops.stream_of_ops (Pdfops.parse_operators pdf page.Pdfpage.resources page.Pdfpage.content) with + Pdf.Stream {contents = (_, Got b)} -> b + | _ -> assert false + in + let xobject_dict = + ["/Type", Pdf.Name "/XObject"; + "/Subtype", Pdf.Name "/Form"; + "/BBox", page.Pdfpage.mediabox; + "/Resources", page.Pdfpage.resources; + "/Length", Pdf.Integer (bytes_size xobject_data)] + in + let xobject = + Pdf.Stream {contents = (Pdf.Dictionary xobject_dict, Pdf.Got xobject_data)} + in + let xobject_objnum = Pdf.addobj pdf xobject in + let pages = Pdfpage.pages_of_pagetree pdf in + let new_pages = + List.map2 + (fun page pnum -> + if mem pnum range + then add_xobject_to_page name xobject_objnum page pdf + else page) + pages + (indx pages) + in + Pdfpage.change_pages true pdf new_pages + +(* n.b the use of change_pages here ensures no inheritable resources in the + * stamp, therefore creation of xobject from page is as simple as expected. *) +let stamp_as_xobject pdf range over = + let prefix = Pdfpage.shortest_unused_prefix pdf in + Pdfpage.add_prefix over prefix; + let marks = Pdfmarks.read_bookmarks pdf in + let marks_refnumbers = Pdf.page_reference_numbers pdf in + let pdf = Pdfmarks.remove_bookmarks pdf in + let over = Pdfmarks.remove_bookmarks over in + let pageseqs = ilist 1 (Pdfpage.endpage pdf) in + let over_firstpage_pdf = + match Pdfpage.pages_of_pagetree over with + | [] -> error "empty PDF" + | h::_ -> Pdfpage.change_pages ~changes:[(1, 1)] true over [h] + in + let merged = + Pdfmerge.merge_pdfs + false false ["a"; "b"] [pdf; over_firstpage_pdf] [pageseqs; [1]] + in + let merged = + {merged with Pdf.saved_encryption = pdf.Pdf.saved_encryption} + in + let merged = Cpdfmetadata.copy_id true pdf merged in + let merged_pages = Pdfpage.pages_of_pagetree merged in + let under_pages, over_page = + all_but_last merged_pages, last merged_pages + in + let new_pages = under_pages in + let changed = + let changes = + map (fun x -> (x, x)) (ilist 1 (length new_pages)) + in + Pdfpage.change_pages ~changes true merged new_pages + in + let new_refnumbers = Pdf.page_reference_numbers changed in + let changetable = hashtable_of_dictionary (combine marks_refnumbers new_refnumbers) in + let new_marks = map (Cpdfbookmarks.change_bookmark changetable) marks in + let pdf = Pdfmarks.add_bookmarks new_marks changed in + let name = "/" ^ Pdfpage.shortest_unused_prefix pdf ^ "CPDFXObj" in + (add_page_as_xobject pdf range over_page name, name) + diff --git a/cpdfxobject.mli b/cpdfxobject.mli new file mode 100644 index 0000000..f1b0f96 --- /dev/null +++ b/cpdfxobject.mli @@ -0,0 +1,2 @@ + +val stamp_as_xobject : Pdf.t -> int list -> Pdf.t -> Pdf.t * string