diff --git a/cpdf.ml b/cpdf.ml index d1bd6cd..2a0f7ab 100644 --- a/cpdf.ml +++ b/cpdf.ml @@ -2082,17 +2082,18 @@ let change_pattern_matrices pdf tr resources = with Pdftransform.NonInvertable -> resources -let shift_page ?(fast=false) dx dy pdf _ page = - let transform_op = - Pdfops.Op_cm (Pdftransform.matrix_of_op (Pdftransform.Translate (dx, dy))) - in - let resources' = - change_pattern_matrices pdf (Pdftransform.mktranslate ~-.dx ~-.dy) page.Pdfpage.resources +let shift_page ?(fast=false) dxdylist pdf pnum page = + let dx, dy = List.nth dxdylist (pnum - 1) in + let transform_op = + Pdfops.Op_cm (Pdftransform.matrix_of_op (Pdftransform.Translate (dx, dy))) in - Pdfpage.prepend_operators pdf [transform_op] ~fast {page with Pdfpage.resources = resources'} + let resources' = + change_pattern_matrices pdf (Pdftransform.mktranslate ~-.dx ~-.dy) page.Pdfpage.resources + in + Pdfpage.prepend_operators pdf [transform_op] ~fast {page with Pdfpage.resources = resources'} -let shift_pdf ?(fast=false) dx dy pdf range = - process_pages (shift_page ~fast dx dy pdf) pdf range +let shift_pdf ?(fast=false) dxdylist pdf range = + process_pages (shift_page ~fast dxdylist pdf) pdf range (* Change a page's media box so its minimum x and y are 0, making other operations simpler to think about. Any shift that is done is reflected in @@ -2106,7 +2107,7 @@ let rectify_boxes ?(fast=false) pdf page = in let page = change_boxes f pdf page in if minx <> 0. || miny <> 0. - then shift_page ~fast (-.minx) (-.miny) pdf 0 page + then shift_page ~fast [(-.minx),(-.miny)] pdf 1 page else page (* \section{Flip pages} *) @@ -2398,8 +2399,9 @@ let nobble_page pdf _ page = do_stamp false false (BottomLeft 0.) false false false true pdf page' page (Pdf.empty ()) (* \section{Set media box} *) -let set_mediabox x y w h pdf range = - let crop_page _ page = +let set_mediabox xywhlist pdf range = + let crop_page pnum page = + let x, y, w, h = List.nth xywhlist (pnum - 1) in {page with Pdfpage.mediabox = (Pdf.Array @@ -2419,16 +2421,17 @@ let setBox box minx maxx miny maxy pdf range = process_pages set_box_page pdf range (* \section{Cropping} *) -let crop_pdf x y w h pdf range = - let crop_page _ page = +let crop_pdf xywhlist pdf range = + let crop_page pagenum page = {page with Pdfpage.rest = (Pdf.add_dict_entry page.Pdfpage.rest "/CropBox" - (Pdf.Array - [Pdf.Real x; Pdf.Real y; - Pdf.Real (x +. w); Pdf.Real (y +. h)]))} + (let x, y, w, h = List.nth xywhlist (pagenum - 1) in + (Pdf.Array + [Pdf.Real x; Pdf.Real y; + Pdf.Real (x +. w); Pdf.Real (y +. h)])))} in process_pages crop_page pdf range @@ -2552,26 +2555,28 @@ let upright ?(fast=false) range pdf = process_pages (upright_page pdf) pdf range (* \section{Scale page data} *) -let scale_pdf ?(fast=false) sx sy pdf range = - let scale_page _ page = - let f (xmin, ymin, xmax, ymax) = - xmin *. sx, ymin *. sy, xmax *. sx, ymax *. sy - in - let page = change_boxes f pdf page - and matrix = Pdftransform.matrix_of_op (Pdftransform.Scale ((0., 0.), sx, sy)) in - let transform_op = - Pdfops.Op_cm matrix - and resources' = - change_pattern_matrices pdf (Pdftransform.matrix_invert matrix) page.Pdfpage.resources - in - Pdfpage.prepend_operators pdf ~fast [transform_op] {page with Pdfpage.resources = resources'} - in - process_pages scale_page pdf range +let scale_pdf ?(fast=false) sxsylist pdf range = + let scale_page pnum page = + let sx, sy = List.nth sxsylist (pnum - 1) in + let f (xmin, ymin, xmax, ymax) = + xmin *. sx, ymin *. sy, xmax *. sx, ymax *. sy + in + let page = change_boxes f pdf page + and matrix = Pdftransform.matrix_of_op (Pdftransform.Scale ((0., 0.), sx, sy)) in + let transform_op = + Pdfops.Op_cm matrix + and resources' = + change_pattern_matrices pdf (Pdftransform.matrix_invert matrix) page.Pdfpage.resources + in + Pdfpage.prepend_operators pdf ~fast [transform_op] {page with Pdfpage.resources = resources'} + in + process_pages scale_page pdf range (* Scale to fit page of size x * y *) (* FIXME: Can we do this in terms of scale_contents - and then just fix up the boxes? For 1.8 *) -let scale_to_fit_pdf ?(fast=false) input_scale x y op pdf range = - let scale_page_to_fit _ page = +let scale_to_fit_pdf ?(fast=false) input_scale xylist op pdf range = + let scale_page_to_fit pnum page = + let x, y = List.nth xylist (pnum - 1) in let matrix = let (minx, miny, maxx, maxy) = (* Use cropbox if available *) diff --git a/cpdf.mli b/cpdf.mli index 3a4c6e9..f35af41 100644 --- a/cpdf.mli +++ b/cpdf.mli @@ -346,11 +346,11 @@ val output_page_info : Pdf.t -> int list -> unit (** True if a given page in a PDF has a given box *) val hasbox : Pdf.t -> int -> string -> bool -(** [crop_pdf x y w h pdf range] sets the cropbox on the given pages. *) -val crop_pdf : float -> float -> float -> float -> Pdf.t -> int list -> Pdf.t +(** [crop_pdf xywhlist pdf range] sets the cropbox on the given pages. *) +val crop_pdf : (float * float * float * float) list -> Pdf.t -> int list -> Pdf.t -(** [set_mediabox x y w h pdf range] sets the cropbox on the given pages. *) -val set_mediabox : float -> float -> float -> float -> Pdf.t -> int list -> Pdf.t +(** [set_mediabox xywhlist pdf range] sets the media box on the given pages. *) +val set_mediabox : (float * float * float * float) list -> Pdf.t -> int list -> Pdf.t (** [setBox boxname x y w h pdf range] sets the given box on the given pages. *) val setBox : string -> float -> float -> float -> float -> Pdf.t -> int list -> Pdf.t @@ -385,16 +385,18 @@ val vflip_pdf : ?fast:bool -> Pdf.t -> int list -> Pdf.t (** Flip the given pages horizontally *) val hflip_pdf : ?fast:bool -> Pdf.t -> int list -> Pdf.t -(** Shift a PDF in x and y (in pts) in the given pages. *) -val shift_pdf : ?fast:bool -> float -> float -> Pdf.t -> int list -> Pdf.t +(** Shift a PDF in x and y (in pts) in the given pages. List of (x, y) pairs is +for all pages in pdf. *) +val shift_pdf : ?fast:bool -> (float * float) list -> Pdf.t -> int list -> Pdf.t -(** Scale a PDF in sx, sy in the given pages. *) -val scale_pdf : ?fast:bool -> float -> float -> Pdf.t -> int list -> Pdf.t +(** Scale a PDF in sx, sy in the given pages. List of (sx, sy) pairs is +for all pages in pdf. *) +val scale_pdf : ?fast:bool -> (float * float) list -> Pdf.t -> int list -> Pdf.t (** [scale_to_fit_pdf input_scale x y op pdf range] scales a page to fit the page size given by (x, y) and by the [input_scale] (e.g 1.0 = scale to fit, 0.9 = scale to fit leaving a border etc.). [op] is unused. *) -val scale_to_fit_pdf : ?fast:bool -> float -> float -> float -> 'a -> Pdf.t -> int list -> Pdf.t +val scale_to_fit_pdf : ?fast:bool -> float -> (float * float) list -> 'a -> Pdf.t -> int list -> Pdf.t (** Scale the contents of a page by a given factor centred around a given point in a given range. *) val scale_contents : ?fast:bool -> position -> float -> Pdf.t -> int list -> Pdf.t diff --git a/cpdfcommand.ml b/cpdfcommand.ml index 7b2c5ca..c68b4ef 100644 --- a/cpdfcommand.ml +++ b/cpdfcommand.ml @@ -11,6 +11,9 @@ open Pdfio let initial_file_size = ref 0 +let empty = Pdf.empty () +let emptypage = Pdfpage.blankpage Pdfpaper.a4 + (* Wrap up the file reading functions to exit with code 1 when an encryption problem occurs. This happens when object streams are in an encrypted document and so it can't be read without the right password... The existing error @@ -738,8 +741,8 @@ let points_of_papersize p = let c = Pdfunits.convert 0. unit Pdfunits.PdfPoint in c w, c h -let firstpage pdf = - List.hd (Pdfpage.pages_of_pagetree pdf) +(*let firstpage pdf = + List.hd (Pdfpage.pages_of_pagetree pdf)*) let cropbox pdf page = match Pdf.lookup_direct pdf "/CropBox" page.Pdfpage.rest with @@ -824,100 +827,100 @@ let update_last_number pdf page unt op num = function in h'::t - -let rec parse_units_again pdf numbers papersize more = +let rec parse_units_again pdf page numbers papersize more = let w, h = points_of_papersize papersize in - parse_units pdf (h::w::numbers) more + parse_units pdf page (h::w::numbers) more -and parse_units pdf numbers = function +and parse_units pdf page numbers = function | Pdfgenlex.LexName "a10portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a10 more + parse_units_again pdf page numbers Pdfpaper.a10 more | Pdfgenlex.LexName "a9portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a9 more + parse_units_again pdf page numbers Pdfpaper.a9 more | Pdfgenlex.LexName "a8portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a8 more + parse_units_again pdf page numbers Pdfpaper.a8 more | Pdfgenlex.LexName "a7portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a7 more + parse_units_again pdf page numbers Pdfpaper.a7 more | Pdfgenlex.LexName "a6portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a6 more + parse_units_again pdf page numbers Pdfpaper.a6 more | Pdfgenlex.LexName "a5portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a5 more + parse_units_again pdf page numbers Pdfpaper.a5 more | Pdfgenlex.LexName "a4portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a4 more + parse_units_again pdf page numbers Pdfpaper.a4 more | Pdfgenlex.LexName "a3portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a3 more + parse_units_again pdf page numbers Pdfpaper.a3 more | Pdfgenlex.LexName "a2portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a2 more + parse_units_again pdf page numbers Pdfpaper.a2 more | Pdfgenlex.LexName "a1portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a1 more + parse_units_again pdf page numbers Pdfpaper.a1 more | Pdfgenlex.LexName "a0portrait"::more -> - parse_units_again pdf numbers Pdfpaper.a0 more + parse_units_again pdf page numbers Pdfpaper.a0 more | Pdfgenlex.LexName "a10landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a10) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a10) more | Pdfgenlex.LexName "a9landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a9) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a9) more | Pdfgenlex.LexName "a8landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a8) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a8) more | Pdfgenlex.LexName "a7landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a7) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a7) more | Pdfgenlex.LexName "a6landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a6) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a6) more | Pdfgenlex.LexName "a5landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a5) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a5) more | Pdfgenlex.LexName "a4landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a4) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a4) more | Pdfgenlex.LexName "a3landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a3) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a3) more | Pdfgenlex.LexName "a2landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a2) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a2) more | Pdfgenlex.LexName "a1landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a1) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a1) more | Pdfgenlex.LexName "a0landscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.a0) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.a0) more | Pdfgenlex.LexName "uslegalportrait"::more -> - parse_units_again pdf numbers Pdfpaper.uslegal more + parse_units_again pdf page numbers Pdfpaper.uslegal more | Pdfgenlex.LexName "usletterportrait"::more -> - parse_units_again pdf numbers Pdfpaper.usletter more + parse_units_again pdf page numbers Pdfpaper.usletter more | Pdfgenlex.LexName "uslegallandscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.uslegal) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.uslegal) more | Pdfgenlex.LexName "usletterlandscape"::more -> - parse_units_again pdf numbers (Pdfpaper.landscape Pdfpaper.usletter) more + parse_units_again pdf page numbers (Pdfpaper.landscape Pdfpaper.usletter) more | Pdfgenlex.LexInt x::Pdfgenlex.LexName "mm"::more -> - parse_units pdf ((mm <| float_of_int x)::numbers) more + parse_units pdf page ((mm <| float_of_int x)::numbers) more | Pdfgenlex.LexReal x::Pdfgenlex.LexName "mm"::more -> - parse_units pdf (mm x::numbers) more + parse_units pdf page (mm x::numbers) more | Pdfgenlex.LexInt x::Pdfgenlex.LexName "cm"::more -> - parse_units pdf ((cm <| float_of_int x)::numbers) more + parse_units pdf page ((cm <| float_of_int x)::numbers) more | Pdfgenlex.LexReal x::Pdfgenlex.LexName "cm"::more -> - parse_units pdf (cm x::numbers) more + parse_units pdf page (cm x::numbers) more | Pdfgenlex.LexInt x::Pdfgenlex.LexName "in"::more -> - parse_units pdf ((inch <| float_of_int x)::numbers) more + parse_units pdf page ((inch <| float_of_int x)::numbers) more | Pdfgenlex.LexReal x::Pdfgenlex.LexName "in"::more -> - parse_units pdf (inch x::numbers) more + parse_units pdf page (inch x::numbers) more | Pdfgenlex.LexInt x::more -> - parse_units pdf (float_of_int x::numbers) more + parse_units pdf page (float_of_int x::numbers) more | Pdfgenlex.LexReal x::more -> - parse_units pdf (x::numbers) more + parse_units pdf page (x::numbers) more | Pdfgenlex.LexName "pt"::more -> - parse_units pdf numbers more + parse_units pdf page numbers more | Pdfgenlex.LexName ( "PW" | "PH" | "CW" | "CH" | "PMINX" | "PMINY" | "PMAXX" | "PMAXY" | "CMINX" | "CMINY" | "CMAXX" | "CMAXY") as page_characteristic::more -> parse_units pdf - ((find_page_characteristic pdf (firstpage pdf) page_characteristic)::numbers) + page + ((find_page_characteristic pdf page page_characteristic)::numbers) more | Pdfgenlex.LexName ("add" | "sub" | "mul" | "div") as op:: ((Pdfgenlex.LexInt _ | Pdfgenlex.LexReal _ | Pdfgenlex.LexName ( "PW" | "PH" | "CW" | "CH" | "PMINX" | "PMINY" | "PMAXX" | "PMAXY" | "CMINX" | "CMINY" | "CMAXX" | "CMAXY")) as num):: (Pdfgenlex.LexName ("pt" | "mm" | "cm" | "in") as unt)::more -> - parse_units pdf (update_last_number pdf (firstpage pdf) unt op num numbers) more + parse_units pdf page (update_last_number pdf page unt op num numbers) more | Pdfgenlex.LexName ("add" | "sub" | "mul" | "div") as op:: ((Pdfgenlex.LexInt _ | Pdfgenlex.LexReal _ | Pdfgenlex.LexName ( "PW" | "PH" | "CW" | "CH" | "PMINX" | "PMINY" | "PMAXX" | "PMAXY" | "CMINX" | "CMINY" | "CMAXX" | "CMAXY")) as num)::more -> - parse_units pdf (update_last_number pdf (firstpage pdf) (Pdfgenlex.LexName "pt") op num numbers) more + parse_units pdf page (update_last_number pdf page (Pdfgenlex.LexName "pt") op num numbers) more | _ -> rev numbers let rec space_units_inner = function @@ -931,33 +934,56 @@ let rec space_units_inner = function let space_units s = implode (space_units_inner (explode s)) -let parse_units_string pdf s = - (*Printf.printf "Parsing string [%s]\n" s;*) - let fs = parse_units pdf [] (Pdfgenlex.lex_string <| space_units s) in - (*Printf.printf "Got numbers: %s\n" - (List.fold_left (fun x y -> x ^ " " ^ y) "" (List.map string_of_float - fs));*) +let parse_units_string pdf page s = + Printf.printf "Parsing string [%s]\n" s; + let fs = parse_units pdf page [] (Pdfgenlex.lex_string <| space_units s) in + Printf.printf "Got numbers: %s\n" + (List.fold_left (fun x y -> x ^ " " ^ y) "" (List.map string_of_float fs)); fs let parse_rectangle pdf s = try - match parse_units_string pdf s with + match parse_units_string pdf emptypage s with | [x; y; w; h] -> x, y, w, h | _ -> error "Bad rectangle specification" with _ -> error "Bad rectangle specification" +let parse_rectangles pdf s = + try + let pages = Pdfpage.pages_of_pagetree pdf in + let groups = List.map (fun page -> parse_units_string pdf page s) pages in + List.map + (function + | [x; y; w; h] -> (x, y, w, h) + | _ -> error "Bad rectangle specification") + groups + with + _ -> error "Bad rectangle specification" + let parse_coordinate pdf s = try - match parse_units_string pdf s with + match parse_units_string pdf emptypage s with | [dx; dy] -> dx, dy | _ -> error "Bad coordinate specification" with _ -> error "Bad coordinate specification" +let parse_coordinates pdf s = + try + let pages = Pdfpage.pages_of_pagetree pdf in + let groups = List.map (fun page -> parse_units_string pdf page s) pages in + List.map + (function + | [dx; dy] -> (dx, dy) + | _ -> error "Bad coordinate specification") + groups + with + _ -> error "Bad coordinate specification" + let parse_single_number pdf s = try - match parse_units_string pdf s with + match parse_units_string pdf emptypage s with | [x] -> x | _ -> error "Bad number Argument" with @@ -1026,7 +1052,7 @@ let displaydoctitle b = try setop (DisplayDocTitle (bool_of_string b)) () with _ -> failwith "DisplayDocTitle: must use true or false" -let empty = Pdf.empty () + let setsplitbookmarks i = setop (SplitOnBookmarks i) () let setstdout () = args.out <- Stdout @@ -3252,20 +3278,19 @@ let go () = begin match args.inputs, args.out with | (_, pagespec, _, _, _, _)::_, _ -> let pdf = get_single_pdf (Some Crop) false in - let x, y, w, h = parse_rectangle pdf args.rectangle in + let xywhlist = parse_rectangles pdf args.rectangle in let range = parse_pagespec pdf pagespec in - let pdf = Cpdf.crop_pdf x y w h pdf range in + let pdf = Cpdf.crop_pdf xywhlist pdf range in write_pdf false pdf | _ -> error "crop: bad command line" end - | Some MediaBox -> begin match args.inputs, args.out with | (_, pagespec, _, _, _, _)::_, _ -> let pdf = get_single_pdf (Some MediaBox) false in - let x, y, w, h = parse_rectangle pdf args.rectangle in + let xywhlist = parse_rectangles pdf args.rectangle in let range = parse_pagespec pdf pagespec in - let pdf = Cpdf.set_mediabox x y w h pdf range in + let pdf = Cpdf.set_mediabox xywhlist pdf range in write_pdf false pdf | _ -> error "set media box: bad command line" end @@ -3554,21 +3579,19 @@ let go () = | Some Shift -> let pdf = get_single_pdf args.op false in let range = parse_pagespec pdf (get_pagespec ()) in - begin match parse_coordinate pdf args.coord with (dx, dy) -> - write_pdf false (Cpdf.shift_pdf ~fast:args.fast dx dy pdf range) - end + let dxdylist = parse_coordinates pdf args.coord in + write_pdf false (Cpdf.shift_pdf ~fast:args.fast dxdylist pdf range) | Some Scale -> let pdf = get_single_pdf args.op false in let range = parse_pagespec pdf (get_pagespec ()) in - begin match parse_coordinate pdf args.coord with (sx, sy) -> - write_pdf false (Cpdf.scale_pdf ~fast:args.fast sx sy pdf range) - end + let sxsylist = parse_coordinates pdf args.coord in + write_pdf false (Cpdf.scale_pdf ~fast:args.fast sxsylist pdf range) | Some ScaleToFit -> let pdf = get_single_pdf args.op false in let range = parse_pagespec pdf (get_pagespec ()) in - let x, y = parse_coordinate pdf args.coord + let xylist = parse_coordinates pdf args.coord and scale = args.scale in - write_pdf false (Cpdf.scale_to_fit_pdf ~fast:args.fast scale x y args.op pdf range) + write_pdf false (Cpdf.scale_to_fit_pdf ~fast:args.fast scale xylist args.op pdf range) | Some (ScaleContents scale) -> let pdf = get_single_pdf args.op false in let range = parse_pagespec pdf (get_pagespec ()) in