1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
(* Read and Write Destinations *)
open Pdfutil
type targetpage =
| PageObject of int
| OtherDocPageNumber of int
type t =
| Action of Pdf.pdfobject
| NullDestination
| NamedDestination of string
| StringDestination of string
| XYZ of targetpage * float option * float option * float option
| Fit of targetpage
| FitH of targetpage * float option
| FitV of targetpage * float option
| FitR of targetpage * float * float * float * float
| FitB of targetpage
| FitBH of targetpage * float option
| FitBV of targetpage * float option
(* Read the destination - it's either direct, or in /Dests in the document
catalog, or in /Dests in the document name tree. *)
let read_targetpage = function
| Pdf.Indirect i -> PageObject i
| Pdf.Integer i -> OtherDocPageNumber i
| _ -> assert false (* ruled out in read_destination *)
let rec read_destination ?(shallow=false) pdf pdfobject =
let option_getnum = function
| Pdf.Null -> None
| x -> Some (Pdf.getnum pdf x)
in
match Pdf.direct pdf pdfobject with
| Pdf.Dictionary d ->
begin
(* We're discarding any other dictionary entries here... *)
match Pdf.lookup_direct pdf "/D" (Pdf.Dictionary d) with
| Some dest -> read_destination ~shallow pdf dest
| None -> NullDestination
end
| Pdf.Array
[(Pdf.Indirect _ | Pdf.Integer _) as p;
Pdf.Name "/XYZ"; l; t; z] ->
XYZ
(read_targetpage p, option_getnum l, option_getnum t, option_getnum z)
| Pdf.Array (* Read common malformed one. *)
[(Pdf.Indirect _ | Pdf.Integer _) as p;
Pdf.Name "/XYZ"; l; t] ->
XYZ
(read_targetpage p, option_getnum l, option_getnum t, None)
| Pdf.Array (* Read common malformed one. *)
[(Pdf.Indirect _ | Pdf.Integer _) as p;
Pdf.Name "/XYZ"; l] ->
XYZ
(read_targetpage p, option_getnum l, None, None)
| Pdf.Array [(Pdf.Indirect _ | Pdf.Integer _) as p; Pdf.Name "/Fit"] ->
Fit (read_targetpage p)
| Pdf.Array [(Pdf.Indirect _ | Pdf.Integer _) as p; Pdf.Name "/FitH"; t] ->
FitH (read_targetpage p, option_getnum t)
| Pdf.Array [(Pdf.Indirect _ | Pdf.Integer _) as p; Pdf.Name "/FitV"; l] ->
FitV (read_targetpage p, option_getnum l)
| Pdf.Array [(Pdf.Indirect _ | Pdf.Integer _) as p;
Pdf.Name "/FitR"; l; b; r; t] ->
FitR
(read_targetpage p, Pdf.getnum pdf l, Pdf.getnum pdf b,
Pdf.getnum pdf r, Pdf.getnum pdf t)
| Pdf.Array [(Pdf.Indirect _ | Pdf.Integer _) as p; Pdf.Name "/FitB"] ->
FitB (read_targetpage p)
| Pdf.Array [(Pdf.Indirect _ | Pdf.Integer _) as p; Pdf.Name "/FitBH"; t] ->
FitBH (read_targetpage p, option_getnum t)
| Pdf.Array [(Pdf.Indirect _ | Pdf.Integer _) as p; Pdf.Name "/FitBV"; l] ->
FitBV (read_targetpage p, option_getnum l)
| Pdf.Name n ->
if shallow then NamedDestination n else
begin match Pdf.lookup_direct pdf "/Root" pdf.Pdf.trailerdict with
| Some catalog ->
begin match Pdf.lookup_direct pdf "/Dests" catalog with
| Some dests ->
begin match Pdf.lookup_direct pdf n dests with
| Some dest' -> read_destination ~shallow pdf dest'
| None -> NamedDestination n
end
| None -> NamedDestination n
end
| None -> raise (Pdf.PDFError "read_destination: no catalog")
end
| Pdf.String s ->
if shallow then StringDestination s else
let rootdict = Pdf.lookup_obj pdf pdf.Pdf.root in
begin match Pdf.lookup_direct pdf "/Names" rootdict with
| Some namedict ->
begin match Pdf.lookup_direct pdf "/Dests" namedict with
| Some destsdict ->
begin match
Pdf.nametree_lookup pdf (Pdf.String s) destsdict
with
| None -> StringDestination s
| Some dest -> read_destination ~shallow pdf (Pdf.direct pdf dest)
end
| _ -> StringDestination s
end
| _ -> StringDestination s
end
| p ->
Pdfe.log (Printf.sprintf "Warning: Could not read destination %s\n" (Pdfwrite.string_of_pdf p));
NullDestination
let pdf_of_targetpage = function
| PageObject i -> Pdf.Indirect i
| OtherDocPageNumber i -> Pdf.Integer i
let pos_null = function
None -> Pdf.Null
| Some x -> Pdf.Real x
let pdfobject_of_destination = function
| Action a -> a
| NullDestination -> Pdf.Null
| NamedDestination s -> Pdf.Name s
| StringDestination s -> Pdf.String s
| XYZ (p, left, top, zoom) ->
Pdf.Array [pdf_of_targetpage p; Pdf.Name "/XYZ"; pos_null left; pos_null top; pos_null zoom]
| Fit p ->
Pdf.Array [pdf_of_targetpage p; Pdf.Name "/Fit"]
| FitH (p, top) ->
Pdf.Array [pdf_of_targetpage p; Pdf.Name "/FitH"; pos_null top]
| FitV (p, left) ->
Pdf.Array [pdf_of_targetpage p; Pdf.Name "/FitV"; pos_null left]
| FitR (p, left, bottom, right, top) ->
Pdf.Array
[pdf_of_targetpage p; Pdf.Name "/FitR"; Pdf.Real left;
Pdf.Real bottom; Pdf.Real right; Pdf.Real top]
| FitB p ->
Pdf.Array [pdf_of_targetpage p; Pdf.Name "/FitB"]
| FitBH (p, top) ->
Pdf.Array [pdf_of_targetpage p; Pdf.Name "/FitBH"; pos_null top]
| FitBV (p, left) ->
Pdf.Array [pdf_of_targetpage p; Pdf.Name "/FitBV"; pos_null left]
(* Transform destinations by a given matrix. Where we have a proper pair making
a point, it is easy. Where we do not, we improvise. It is likely only to be
sensible for scaling / shifting / uprighting anyway. For example, a vertical
flip of a page is hardly likely to make a paragraph come up in the right
position. Careful to preserve nulls, and handle all combinations. *)
(* Acrobat doesn't like /OpenActions (at least) which contain numbers outside
the float range -32768...32768. This is the old (pre-ISO) float range. *)
let clip_pair (a, b) =
let clip f =
if f < -32768.0 then -32768.0 else
if f > 32768.0 then 32768.0 else f
in
(clip a, clip b)
let rec transform_destination pdf t = function
| FitH (PageObject _ as p, Some top) ->
let (_, top) = clip_pair (Pdftransform.transform_matrix t (0., top)) in
FitH (p, Some top)
| FitV (PageObject _ as p, Some left) ->
let (left, _) = clip_pair (Pdftransform.transform_matrix t (left, 0.)) in
FitV (p, Some left)
| FitBH (PageObject _ as p, Some top) ->
let (_, top) = clip_pair (Pdftransform.transform_matrix t (0., top)) in
FitBH (p, Some top)
| FitBV (PageObject _ as p, Some left) ->
let (left, _) = clip_pair (Pdftransform.transform_matrix t (left, 0.)) in
FitBV (p, Some left)
| XYZ (PageObject _ as p, Some left, Some top, zoom) ->
let left, top = clip_pair (Pdftransform.transform_matrix t (left, top)) in
XYZ (p, Some left, Some top, zoom)
| XYZ (PageObject _ as p, None, Some top, zoom) ->
let _, top = clip_pair (Pdftransform.transform_matrix t (0., top)) in
XYZ (p, None, Some top, zoom)
| XYZ (PageObject _ as p, Some left, None, zoom) ->
let left, _ = clip_pair (Pdftransform.transform_matrix t (left, 0.)) in
XYZ (p, Some left, None, zoom)
| FitR (PageObject _ as p, left, bottom, right, top) ->
let left, top = clip_pair (Pdftransform.transform_matrix t (left, top)) in
let right, bottom = clip_pair (Pdftransform.transform_matrix t (right, bottom)) in
FitR (p, left, bottom, right, top)
| Action a ->
begin match Pdf.lookup_direct pdf "/S" a, Pdf.lookup_direct pdf "/D" a with
| Some (Pdf.Name "/GoTo"), Some d ->
Action (Pdf.add_dict_entry a "/D" (pdfobject_of_destination (transform_destination pdf t (read_destination ~shallow:true pdf d))))
| _ -> Action a
end
| x -> x
|