mirror of
https://github.com/johnwhitington/cpdf-source.git
synced 2024-12-12 08:46:39 +01:00
1746 lines
64 KiB
OCaml
1746 lines
64 KiB
OCaml
(* Structured Graphics *)
|
|
open Pdfutil
|
|
open Pdfio
|
|
|
|
type fpoint = float * float
|
|
|
|
type winding_rule = EvenOdd | NonZero
|
|
|
|
type segment =
|
|
| Straight of fpoint * fpoint
|
|
| Bezier of fpoint * fpoint * fpoint * fpoint
|
|
|
|
(* Each segment list may be marked as a hole or not. *)
|
|
type hole = Hole | Not_hole
|
|
|
|
(* A [subpath] is either closed or open. *)
|
|
type closure = Closed | Open
|
|
|
|
(* A [subpath] is the pair of a hole and a list of segments. *)
|
|
type subpath = hole * closure * segment list
|
|
|
|
(* A path is made from a number of subpaths. *)
|
|
type path = winding_rule * subpath list
|
|
|
|
type tiling = Tiling
|
|
|
|
type function_shading =
|
|
{funshading_domain : float * float * float * float;
|
|
funshading_matrix : Pdftransform.transform_matrix;
|
|
funshading_function : Pdffun.t}
|
|
|
|
type radial_shading =
|
|
{radialshading_coords : float * float * float * float * float * float;
|
|
radialshading_domain : float * float;
|
|
radialshading_function : Pdffun.t list;
|
|
radialshading_extend : bool * bool}
|
|
|
|
type axial_shading =
|
|
{axialshading_coords : float * float * float * float;
|
|
axialshading_domain : float * float;
|
|
axialshading_function : Pdffun.t list;
|
|
axialshading_extend : bool * bool}
|
|
|
|
type shading_kind =
|
|
| FunctionShading of function_shading
|
|
| AxialShading of axial_shading
|
|
| RadialShading of radial_shading
|
|
| FreeFormGouraudShading
|
|
| LatticeFormGouraudShading
|
|
| CoonsPatchMesh
|
|
| TensorProductPatchMesh
|
|
|
|
type shading =
|
|
{shading_colourspace : Pdf.pdfobject;
|
|
shading_background : Pdf.pdfobject option;
|
|
shading_bbox : Pdf.pdfobject option;
|
|
shading_antialias : bool;
|
|
shading_matrix : Pdftransform.transform_matrix;
|
|
shading_extgstate : Pdf.pdfobject;
|
|
shading : shading_kind}
|
|
|
|
type pattern =
|
|
| ColouredTilingPattern of tiling
|
|
| UncolouredTilingPattern of tiling
|
|
| ShadingPattern of shading
|
|
|
|
type colvals =
|
|
| Floats of float list
|
|
| Named of (string * float list)
|
|
| Pattern of pattern
|
|
|
|
let rec string_of_colvals = function
|
|
| Floats fs ->
|
|
"Floats " ^ fold_left ( ^ ) "" (map (function x -> string_of_float x ^ " ") fs)
|
|
| Named (n, fs) ->
|
|
"Named " ^ n ^ " " ^ string_of_colvals (Floats fs)
|
|
| Pattern p ->
|
|
"Pattern"
|
|
|
|
type objectclass =
|
|
| PathObject
|
|
| TextObject
|
|
| ClippingPathObject
|
|
| PageDescriptionLevel
|
|
| ShadingObject
|
|
| InlineImageObject
|
|
| ExternalObject
|
|
|
|
let string_of_objectclass = function
|
|
| PathObject -> "PathObject"
|
|
| TextObject -> "TextObject"
|
|
| ClippingPathObject -> "ClippingPathObject"
|
|
| PageDescriptionLevel -> "PageDescriptionLevel"
|
|
| ShadingObject -> "ShadingObject"
|
|
| InlineImageObject -> "InlineImageObject"
|
|
| ExternalObject -> "ExternalObject"
|
|
|
|
type transparency_attributes =
|
|
{fill_transparency : float;
|
|
line_transparency : float}
|
|
|
|
type path_attributes =
|
|
{path_transform : Pdftransform.transform_matrix;
|
|
path_fill : (Pdfspace.t * colvals) option;
|
|
path_line : (Pdfspace.t * colvals) option;
|
|
path_linewidth : float;
|
|
path_joinstyle : int;
|
|
path_capstyle : int;
|
|
path_dash : float list * float;
|
|
path_mitrelimit : float;
|
|
path_transparency : transparency_attributes;
|
|
path_intent : string}
|
|
|
|
type text_attributes =
|
|
{textmode : int}
|
|
|
|
type textblock_attributes =
|
|
{textblock_transform : Pdftransform.transform_matrix}
|
|
|
|
type textblock =
|
|
text_attributes * Pdfops.t
|
|
|
|
type softmask_subtype =
|
|
Alpha | Luminosity
|
|
|
|
type transparency_group =
|
|
{tr_group_colourspace : Pdf.pdfobject option; (* FIXME: This should be colourspace *)
|
|
isolated : bool;
|
|
knockout : bool;
|
|
tr_graphic : t}
|
|
|
|
and softmask =
|
|
{softmask_subtype : softmask_subtype;
|
|
transparency_group : transparency_group;
|
|
softmask_bbox : float * float * float * float;
|
|
backdrop : float list option;
|
|
softmask_transfer : Pdffun.t option}
|
|
|
|
and image_attributes =
|
|
{image_transform : Pdftransform.transform_matrix;
|
|
image_transparency : float; (* The /ca value *)
|
|
image_softmask : softmask option}
|
|
|
|
and fontname = string * Pdf.pdfobject (*r Name, font *)
|
|
|
|
(* The main type for a graphic. It must be kept paired with the PDF it comes
|
|
from, since it will reference objects (fonts, images etc) in that PDF. *)
|
|
and graphic_elt =
|
|
| Path of (path * path_attributes)
|
|
| Text of textblock list * textblock_attributes
|
|
| MCPoint of string
|
|
| MCPointProperties of string * Pdf.pdfobject
|
|
| MCSection of string * graphic_elt list
|
|
| MCSectionProperties of string * Pdf.pdfobject * graphic_elt list
|
|
| Image of image_attributes * int (* object number *)
|
|
| GraphicInlineImage of Pdf.pdfobject * bytes * Pdftransform.transform_matrix
|
|
| Clip of path * graphic_elt list
|
|
| Shading of path option * shading * Pdftransform.transform_matrix
|
|
|
|
and t =
|
|
{elements : graphic_elt list; (* Page content *)
|
|
fonts : fontname list; (* Fonts *)
|
|
resources : Pdf.pdfobject} (* Anything else in /Resources *)
|
|
|
|
(* Calculate the bounding box (xmin, xmax, ymin, ymax) of a graphic. *)
|
|
let bbox_of_segment = function
|
|
| Straight ((x1, y1), (x2, y2)) ->
|
|
fmin x1 x2, fmax x1 x2, fmin y1 y2, fmax y1 y2
|
|
| Bezier ((x1, y1), (x2, y2), (x3, y3), (x4, y4)) ->
|
|
fmin (fmin x1 x2) (fmin x3 x4), fmax (fmax x1 x2) (fmax x3 x4),
|
|
fmin (fmin y1 y2) (fmin y3 y4), fmax (fmax y1 y2) (fmax y3 y4)
|
|
|
|
let bbox_of_path (_, subpaths) =
|
|
let segments =
|
|
flatten (map (function (_, _, l) -> l) subpaths)
|
|
in
|
|
fold_left
|
|
box_union_float
|
|
(max_float, min_float, max_float, min_float)
|
|
(map bbox_of_segment segments)
|
|
|
|
let rec bbox_of_graphic_inner (xmin, xmax, ymin, ymax) = function
|
|
| [] -> xmin, xmax, ymin, ymax
|
|
| (Path (p, _) | Clip (p, _))::t ->
|
|
bbox_of_graphic_inner
|
|
(box_union_float (xmin, xmax, ymin, ymax) (bbox_of_path p)) t
|
|
| h::t -> bbox_of_graphic_inner (xmin, xmax, ymin, ymax) t
|
|
|
|
let bbox_of_graphic graphic =
|
|
bbox_of_graphic_inner
|
|
(max_float, min_float, max_float, min_float)
|
|
graphic.elements
|
|
|
|
(* For debug purposes, build a string of a graphic. *)
|
|
let string_of_segment = function
|
|
| Straight ((ax, ay), (bx, by)) ->
|
|
Printf.sprintf "Straight line: (%f, %f) --> (%f, %f)\n" ax ay bx by
|
|
| Bezier ((ax, ay), (bx, by), (cx, cy), (dx, dy)) ->
|
|
Printf.sprintf
|
|
"Bezier curve: (%f, %f) --> (%f, %f) --> (%f, %f) --> (%f, %f)\n"
|
|
ax ay bx by cx cy dx dy
|
|
|
|
let string_of_subpath (h, o ,segments) =
|
|
Printf.sprintf "Hole: %b, Open: %b, segments:%s\n"
|
|
(h = Hole) (o = Open) (fold_left ( ^ ) "" (map string_of_segment segments))
|
|
|
|
let string_of_path (windingrule, subpaths) =
|
|
Printf.sprintf "%s %s"
|
|
(match windingrule with
|
|
| EvenOdd -> "Even-odd\n"
|
|
| NonZero -> "Non-zero\n")
|
|
(fold_left ( ^ ) "" (map string_of_subpath subpaths))
|
|
|
|
let string_of_textblock (st, op) =
|
|
"TEXTPIECE: " ^ Pdfops.string_of_op op ^ "\n"
|
|
|
|
let string_of_font (f, i) = f ^ " " ^ Pdfwrite.string_of_pdf i ^ "\n"
|
|
|
|
let string_of_colvals = function
|
|
| Floats l -> Printf.sprintf "Floats (%i)" (length l)
|
|
| Named (s, fl) -> Printf.sprintf "Named %s, Floats (%i)" s (length fl)
|
|
| Pattern _ -> Printf.sprintf "Pattern"
|
|
|
|
let string_of_attributes a =
|
|
let line =
|
|
match a.path_line with
|
|
| None -> "none"
|
|
| Some (cs, vals) ->
|
|
Printf.sprintf "line colourspace is %s, %s vals"
|
|
(Pdfspace.string_of_colourspace cs)
|
|
(string_of_colvals vals)
|
|
and fill =
|
|
match a.path_fill with
|
|
| None -> "none"
|
|
| Some (cs, vals) ->
|
|
Printf.sprintf "fill colourspace is %s, %s vals"
|
|
(Pdfspace.string_of_colourspace cs)
|
|
(string_of_colvals vals)
|
|
in
|
|
line ^ "\n" ^ fill ^ "\n"
|
|
|
|
let rec string_of_graphic_elt = function
|
|
| MCSection (n, g) ->
|
|
Printf.sprintf "Marked content section %s...\n" n ^ "BEGIN\n" ^
|
|
(fold_left ( ^ ) "" (map string_of_graphic_elt g))
|
|
^ "\nEND Marked content section\n"
|
|
| MCSectionProperties (n, d, g) ->
|
|
Printf.sprintf "Marked content section %s with properties %s...\n" n
|
|
(Pdfwrite.string_of_pdf d)
|
|
^ "BEGIN\n" ^
|
|
(fold_left ( ^ ) "" (map string_of_graphic_elt g))
|
|
^ "\nEND Marked content section\n"
|
|
| MCPoint n ->
|
|
Printf.sprintf "Marked content point %s...\n" n
|
|
| MCPointProperties (n, d) ->
|
|
Printf.sprintf "Marked content point %s with properties %s...\n" n (Pdfwrite.string_of_pdf d)
|
|
| Path (p, attributes) -> Printf.sprintf "Path: %s\nAttributes\n%s\n" (string_of_path p) (string_of_attributes attributes)
|
|
| Text (ts, attr) ->
|
|
"-----BEGIN TEXT - fonts:\n" ^
|
|
fold_left ( ^ ) "" (map string_of_textblock ts) ^
|
|
"-----END TEXT\n"
|
|
| Image (tr, x) ->
|
|
"Image " ^ string_of_int x ^ "\n"
|
|
| GraphicInlineImage _ ->
|
|
"Inline image\n"
|
|
| Clip (p, g) ->
|
|
"Clipview: path = " ^ string_of_path p ^ "\ngraphic is " ^
|
|
fold_left ( ^ ) "" (map string_of_graphic_elt g)
|
|
| Shading (clip, shading, tr) ->
|
|
"Shading\n"
|
|
|
|
and string_of_graphic g =
|
|
"Elements:\n" ^
|
|
fold_left ( ^ ) "" (map string_of_graphic_elt g.elements) ^
|
|
"Fonts:\n" ^
|
|
fold_left ( ^ ) ""
|
|
(map
|
|
(fun (name, obj) -> name ^ " " ^ Pdfwrite.string_of_pdf obj)
|
|
g.fonts) ^
|
|
"Resources:\n" ^
|
|
Pdfwrite.string_of_pdf g.resources
|
|
|
|
type state =
|
|
{mutable objectclass : objectclass; (*r Not strictly part of the state, but fits here. *)
|
|
mutable clip : path option; (*r Ditto - stores a clipping path which is to be invoked on the next path operation. *)
|
|
mutable intent : string;
|
|
mutable fill : colvals;
|
|
mutable linewidth : float;
|
|
mutable line : colvals;
|
|
mutable mitrelimit : float;
|
|
mutable joinstyle : int;
|
|
mutable capstyle : int;
|
|
mutable colourspace_stroke : Pdfspace.t;
|
|
mutable colourspace_nonstroke : Pdfspace.t;
|
|
mutable dash : float list * float;
|
|
mutable flatness : int;
|
|
mutable transform : Pdftransform.transform_matrix;
|
|
mutable extra_transform : Pdftransform.transform_matrix;
|
|
mutable text_transform : Pdftransform.transform_matrix;
|
|
mutable text_line_transform : Pdftransform.transform_matrix;
|
|
mutable opacity_stroke : float;
|
|
mutable opacity_nonstroke : float;
|
|
mutable character_spacing : float;
|
|
mutable word_spacing : float;
|
|
mutable scale : float;
|
|
mutable leading : float;
|
|
mutable font_and_size : (string * float) option;
|
|
mutable font_render : int;
|
|
mutable font_rise : float;
|
|
mutable blendmode : int;
|
|
mutable softmask : softmask option;
|
|
mutable in_xobject : int;
|
|
mutable opdo_matrix : Pdftransform.transform_matrix}
|
|
|
|
let default_state () =
|
|
{objectclass = PageDescriptionLevel;
|
|
clip = None;
|
|
intent = "/RelativeColorimetric";
|
|
fill = Floats [1.];
|
|
linewidth = 1.;
|
|
line = Floats [1.];
|
|
mitrelimit = 10.;
|
|
joinstyle = 0;
|
|
capstyle = 0;
|
|
colourspace_stroke = Pdfspace.DeviceGray;
|
|
colourspace_nonstroke = Pdfspace.DeviceGray;
|
|
dash = [], 0.;
|
|
flatness = 0;
|
|
transform = Pdftransform.i_matrix;
|
|
extra_transform = Pdftransform.i_matrix;
|
|
text_transform = Pdftransform.i_matrix;
|
|
text_line_transform = Pdftransform.i_matrix;
|
|
opacity_stroke = 1.;
|
|
opacity_nonstroke = 1.;
|
|
character_spacing = 0.;
|
|
word_spacing = 0.;
|
|
scale = 100.;
|
|
leading = 0.;
|
|
font_and_size = None; (*r No initial value. *)
|
|
font_render = 0;
|
|
font_rise = 0.;
|
|
blendmode = 1;
|
|
softmask = None;
|
|
in_xobject = 0;
|
|
opdo_matrix = Pdftransform.i_matrix}
|
|
|
|
let state = ref (default_state ())
|
|
|
|
let string_of_state s =
|
|
(*i "Object class: " ^ string_of_objectclass s.objectclass ^ "\n" ^ i*)
|
|
"Stroke Colourspace: " ^ Pdfspace.string_of_colourspace s.colourspace_stroke ^ "\n" ^
|
|
"Nonstroke Colourspace: " ^ Pdfspace.string_of_colourspace s.colourspace_nonstroke ^ "\n" ^
|
|
"Stroke colours: " ^ string_of_colvals s.line ^ "\n" ^
|
|
"NonStroke colours: " ^ string_of_colvals s.fill ^ "\n"
|
|
|
|
let path_attributes_fill_and_stroke () =
|
|
{path_transform = (!state).transform;
|
|
path_fill = Some ((!state).colourspace_nonstroke, (!state).fill);
|
|
path_line = Some ((!state).colourspace_stroke, (!state).line);
|
|
path_linewidth = (!state).linewidth;
|
|
path_joinstyle = (!state).joinstyle;
|
|
path_capstyle = (!state).capstyle;
|
|
path_dash = (!state).dash;
|
|
path_mitrelimit = (!state).mitrelimit;
|
|
path_transparency =
|
|
{fill_transparency = (!state).opacity_nonstroke;
|
|
line_transparency = (!state).opacity_stroke};
|
|
path_intent = (!state).intent}
|
|
|
|
let path_attributes_fill () =
|
|
{path_transform = (!state).transform;
|
|
path_fill = Some ((!state).colourspace_nonstroke, (!state).fill);
|
|
path_line = None;
|
|
path_linewidth = (!state).linewidth;
|
|
path_joinstyle = (!state).joinstyle;
|
|
path_capstyle = (!state).capstyle;
|
|
path_dash = (!state).dash;
|
|
path_mitrelimit = (!state).mitrelimit;
|
|
path_transparency =
|
|
{fill_transparency = (!state).opacity_nonstroke;
|
|
line_transparency = 1.};
|
|
path_intent = (!state).intent}
|
|
|
|
let path_attributes_stroke () =
|
|
{path_transform = (!state).transform;
|
|
path_fill = None;
|
|
path_line = Some ((!state).colourspace_stroke, (!state).line);
|
|
path_linewidth = (!state).linewidth;
|
|
path_joinstyle = (!state).joinstyle;
|
|
path_capstyle = (!state).capstyle;
|
|
path_dash = (!state).dash;
|
|
path_mitrelimit = (!state).mitrelimit;
|
|
path_transparency =
|
|
{fill_transparency = 1.;
|
|
line_transparency = (!state).opacity_stroke};
|
|
path_intent = (!state).intent}
|
|
|
|
let textstate () =
|
|
{textmode = 0}
|
|
|
|
let nonzero = EvenOdd
|
|
|
|
let rec initial_colour pdf resources = function
|
|
| Pdf.Name "/DeviceGray"
|
|
| Pdf.Array (Pdf.Name "/CalGray"::_) ->
|
|
Floats [0.]
|
|
| Pdf.Name "/DeviceRGB"
|
|
| Pdf.Array (Pdf.Name "/CalRGB"::_) ->
|
|
Floats [0.; 0.; 0.]
|
|
| Pdf.Name "/DeviceCMYK" ->
|
|
Floats [0.; 0.; 0.; 1.]
|
|
| Pdf.Name "/Pattern"
|
|
| Pdf.Array [Pdf.Name "/Pattern"] ->
|
|
Floats [0.]
|
|
| Pdf.Array elts as cs ->
|
|
begin match elts with
|
|
| [Pdf.Name "/ICCBased"; iccstream] ->
|
|
begin match Pdf.lookup_direct pdf "/Alternate" iccstream with
|
|
| Some space -> initial_colour pdf resources space
|
|
| None ->
|
|
begin match Pdf.lookup_direct pdf "/N" iccstream with
|
|
| Some (Pdf.Integer 1) -> Floats [0.]
|
|
| Some (Pdf.Integer 3) -> Floats [0.; 0.; 0.]
|
|
| Some (Pdf.Integer 4) -> Floats [0.; 0.; 0.; 0.]
|
|
| _ -> raise (Pdf.PDFError "Bad ICCBased Alternate")
|
|
end
|
|
end
|
|
| Pdf.Name "/DeviceN"::_::alternate::_
|
|
| [Pdf.Name "/Separation"; _; alternate; _] ->
|
|
initial_colour pdf resources alternate
|
|
| [Pdf.Name "/Pattern"; alternate] ->
|
|
initial_colour pdf resources alternate
|
|
| _ -> Pdfe.log (Printf.sprintf "%s\n" (Pdfwrite.string_of_pdf cs)); raise (Pdf.PDFError "Unknown colourspace A")
|
|
end
|
|
| Pdf.Indirect _ as indirect ->
|
|
initial_colour pdf resources (Pdf.direct pdf indirect)
|
|
| _ -> raise (Pdf.PDFError "Unknown colourspace B")
|
|
|
|
(* PartialPath (sp, cp, p, s) is starting point [sp], current point [cp] the
|
|
partial segment list [p], subpath [s] and graphic [g]. *)
|
|
type partial =
|
|
| NoPartial
|
|
| PartialText of textblock list
|
|
| PartialPath of fpoint * fpoint * segment list * subpath list
|
|
|
|
(* g is a [group_transparency] xobject *)
|
|
let rec read_transparency_group pdf g =
|
|
let group =
|
|
match Pdf.lookup_direct pdf "/Group" g with
|
|
| Some gr -> gr
|
|
| None -> raise (Pdf.PDFError "Pdfgraphics.read_transparency_group: no /Group found")
|
|
in
|
|
let colourspace =
|
|
Pdf.lookup_direct pdf "/CS" group
|
|
and isolated =
|
|
match Pdf.lookup_direct pdf "/I" group with
|
|
| Some (Pdf.Boolean b) -> b
|
|
| _ -> false
|
|
and knockout =
|
|
match Pdf.lookup_direct pdf "/K" group with
|
|
| Some (Pdf.Boolean b) -> b
|
|
| _ -> false
|
|
and graphic =
|
|
let fakepage =
|
|
let resources =
|
|
match Pdf.lookup_direct pdf "/Resources" g with
|
|
| Some (Pdf.Dictionary d) -> Pdf.Dictionary d
|
|
| _ -> Pdf.Dictionary []
|
|
and contents =
|
|
[g]
|
|
in
|
|
{Pdfpage.content = contents;
|
|
Pdfpage.mediabox = Pdf.Null;
|
|
Pdfpage.resources = resources;
|
|
Pdfpage.rotate = Pdfpage.Rotate0;
|
|
Pdfpage.rest = Pdf.Dictionary []}
|
|
in
|
|
graphic_of_page pdf fakepage
|
|
and a, b, c, d =
|
|
Pdf.parse_rectangle pdf (Pdf.lookup_fail "no bbox" pdf "/BBox" g)
|
|
in
|
|
{tr_group_colourspace = colourspace;
|
|
isolated = isolated;
|
|
knockout = knockout;
|
|
tr_graphic = graphic}, a, b, c, d
|
|
|
|
and read_soft_mask pdf mask =
|
|
match
|
|
match Pdf.lookup_direct pdf "/S" mask with
|
|
| Some (Pdf.Name "/Alpha") -> Some Alpha
|
|
| Some (Pdf.Name "/Luminosity") -> Some Luminosity
|
|
| _ -> None
|
|
with
|
|
| None -> None
|
|
| Some subtype ->
|
|
let transparency_group, a, b, c, d =
|
|
match Pdf.lookup_direct pdf "/G" mask with
|
|
| Some g -> read_transparency_group pdf g
|
|
| None -> raise (Pdf.PDFError "Pdfgraphics.transparency group not found in soft mask")
|
|
and backdrop =
|
|
match Pdf.lookup_direct pdf "/BC" mask with
|
|
| Some (Pdf.Array nums) -> Some (map (Pdf.getnum pdf) nums)
|
|
| _ -> None
|
|
and transfer =
|
|
match Pdf.lookup_direct pdf "/TR" mask with
|
|
| Some (Pdf.Dictionary d) ->
|
|
Some (Pdffun.parse_function pdf (Pdf.Dictionary d))
|
|
| _ -> None
|
|
in
|
|
Some
|
|
{softmask_subtype = subtype;
|
|
transparency_group = transparency_group;
|
|
backdrop = backdrop;
|
|
softmask_transfer = transfer;
|
|
softmask_bbox = (a, b, c, d)}
|
|
|
|
and update_graphics_state_from_dict pdf resources gdict =
|
|
begin match Pdf.lookup_direct pdf "/SMask" gdict with
|
|
| Some softmask -> (!state).softmask <- read_soft_mask pdf softmask
|
|
| None -> ()
|
|
end;
|
|
begin match Pdf.lookup_direct pdf "/CA" gdict with
|
|
| Some (Pdf.Real o) -> (!state).opacity_stroke <- o
|
|
| _ -> ()
|
|
end;
|
|
begin match Pdf.lookup_direct pdf "/ca" gdict with
|
|
| Some (Pdf.Real o) -> (!state).opacity_nonstroke <- o
|
|
| _ -> ()
|
|
end;
|
|
begin match Pdf.lookup_direct pdf "/BM" gdict with
|
|
| Some (Pdf.Name n)
|
|
| Some (Pdf.Array (Pdf.Name n::_)) ->
|
|
(!state).blendmode <- 0 (* FIXME: Do properly *)
|
|
| _ -> ()
|
|
end;
|
|
begin match Pdf.lookup_direct pdf "/LW" gdict with
|
|
| Some (Pdf.Integer width) ->
|
|
(!state).linewidth <- float width
|
|
| Some (Pdf.Real width) ->
|
|
(!state).linewidth <- width
|
|
| _ -> ()
|
|
end;
|
|
begin match Pdf.lookup_direct pdf "/LC" gdict with
|
|
| Some (Pdf.Integer style) ->
|
|
(!state).capstyle <- style
|
|
| _ -> ()
|
|
end;
|
|
begin match Pdf.lookup_direct pdf "/LC" gdict with
|
|
| Some (Pdf.Integer join) ->
|
|
(!state).joinstyle <- join
|
|
| _ -> ()
|
|
end;
|
|
begin match Pdf.lookup_direct pdf "/ML" gdict with
|
|
| Some (Pdf.Integer limit) ->
|
|
(!state).mitrelimit <- float limit
|
|
| Some (Pdf.Real limit) ->
|
|
(!state).mitrelimit <- limit
|
|
| _ -> ()
|
|
end;
|
|
begin match Pdf.lookup_direct pdf "/D" gdict with
|
|
| Some (Pdf.Array [Pdf.Array dashes; phase]) ->
|
|
let dashnums, phase =
|
|
map
|
|
(function
|
|
| (Pdf.Integer n) -> float n
|
|
| (Pdf.Real n) -> n
|
|
| _ -> raise (Pdf.PDFError "Malformed dash."))
|
|
dashes,
|
|
match phase with
|
|
| Pdf.Integer phase -> float phase
|
|
| Pdf.Real phase -> phase
|
|
| _ -> raise (Pdf.PDFError "Malformed dash phase.")
|
|
in
|
|
(!state).dash <- dashnums, phase
|
|
| _ -> ()
|
|
end
|
|
|
|
and statestack : state list ref = ref []
|
|
|
|
and copystate () =
|
|
{!state with fill = (!state).fill}
|
|
|
|
and push_statestack () =
|
|
(*i Printf.printf "push_statestack\n"; i*)
|
|
statestack =| copystate ()
|
|
|
|
and pop_statestack () =
|
|
(*i Printf.printf "pop_statestack: %i items in stack before pop\n" (length !statestack);
|
|
Printf.printf "Before pop_statestack, line and fill spaces are %s and %s\n"
|
|
(Pdfspace.string_of_colourspace (!state).colourspace_stroke) (Pdfspace.string_of_colourspace (!state).colourspace_nonstroke); I*)
|
|
begin match !statestack with
|
|
| [] -> raise (Pdf.PDFError "Unbalanced q/Q Ops")
|
|
| h::t -> statestack := t; state := h
|
|
end(*i ;
|
|
Printf.printf "After pop_statestack, line and fill spaces are %s and %s\n"
|
|
(Pdfspace.string_of_colourspace (!state).colourspace_stroke) (Pdfspace.string_of_colourspace (!state).colourspace_nonstroke) i*)
|
|
|
|
and read_tiling_pattern _ =
|
|
ColouredTilingPattern Tiling
|
|
|
|
and read_function_shading pdf shading =
|
|
let domain =
|
|
match Pdf.lookup_direct pdf "/Domain" shading with
|
|
| Some (Pdf.Array [a; b; c; d]) -> Pdf.getnum pdf a, Pdf.getnum pdf b, Pdf.getnum pdf c, Pdf.getnum pdf d
|
|
| _ -> 0., 1., 0., 1.
|
|
and matrix =
|
|
Pdf.parse_matrix pdf "/Matrix" shading
|
|
and func =
|
|
Pdf.lookup_fail "No function found" pdf "/Function" shading
|
|
in
|
|
FunctionShading
|
|
{funshading_domain = domain;
|
|
funshading_matrix = matrix;
|
|
funshading_function = Pdffun.parse_function pdf func}
|
|
|
|
and read_radial_shading pdf shading =
|
|
let coords =
|
|
match Pdf.lookup_direct pdf "/Coords" shading with
|
|
| Some (Pdf.Array [a; b; c; d; e; f]) ->
|
|
Pdf.getnum pdf a, Pdf.getnum pdf b, Pdf.getnum pdf c, Pdf.getnum pdf d, Pdf.getnum pdf e, Pdf.getnum pdf f
|
|
| _ -> raise (Pdf.PDFError "Pdfgraphics.read_radial_shading: no coords in radial shading")
|
|
and domain =
|
|
match Pdf.lookup_direct pdf "/Domain" shading with
|
|
| Some (Pdf.Array [a; b]) -> Pdf.getnum pdf a, Pdf.getnum pdf b
|
|
| _ -> 0., 1.
|
|
and func =
|
|
match Pdf.lookup_direct pdf "/Function" shading with
|
|
| Some (Pdf.Array fs) -> map (Pdffun.parse_function pdf) fs
|
|
| Some f -> [Pdffun.parse_function pdf f]
|
|
| _ -> raise (Pdf.PDFError "Pdfgraphics.read_radial_shading: no function in radial shading")
|
|
and extend =
|
|
match Pdf.lookup_direct pdf "/Extend" shading with
|
|
| Some (Pdf.Array [Pdf.Boolean a; Pdf.Boolean b]) -> a, b
|
|
| _ -> false, false
|
|
in
|
|
RadialShading
|
|
{radialshading_coords = coords;
|
|
radialshading_domain = domain;
|
|
radialshading_function = func;
|
|
radialshading_extend = extend}
|
|
|
|
and read_axial_shading pdf shading =
|
|
let coords =
|
|
match Pdf.lookup_direct pdf "/Coords" shading with
|
|
| Some (Pdf.Array [a; b; c; d]) ->
|
|
Pdf.getnum pdf a, Pdf.getnum pdf b, Pdf.getnum pdf c, Pdf.getnum pdf d
|
|
| _ -> raise (Pdf.PDFError "Pdfgraphics.read_axial_shading: no coords in radial shading")
|
|
and domain =
|
|
match Pdf.lookup_direct pdf "/Domain" shading with
|
|
| Some (Pdf.Array [a; b]) -> Pdf.getnum pdf a, Pdf.getnum pdf b
|
|
| _ -> 0., 1.
|
|
and func =
|
|
match Pdf.lookup_direct pdf "/Function" shading with
|
|
| Some (Pdf.Array fs) -> map (Pdffun.parse_function pdf) fs
|
|
| Some f -> [Pdffun.parse_function pdf f]
|
|
| _ -> raise (Pdf.PDFError "Pdfgraphics.read_axial_shading: no function in radial shading")
|
|
and extend =
|
|
match Pdf.lookup_direct pdf "/Extend" shading with
|
|
| Some (Pdf.Array [Pdf.Boolean a; Pdf.Boolean b]) -> a, b
|
|
| _ -> false, false
|
|
in
|
|
AxialShading
|
|
{axialshading_coords = coords;
|
|
axialshading_domain = domain;
|
|
axialshading_function = func;
|
|
axialshading_extend = extend}
|
|
|
|
(* Read a shading pattern *)
|
|
and read_shading pdf matrix extgstate shading =
|
|
let colourspace =
|
|
Pdf.lookup_fail "No colourspace in shading" pdf "/ColorSpace" shading
|
|
and background =
|
|
Pdf.lookup_direct pdf "/Background" shading
|
|
and bbox =
|
|
Pdf.lookup_direct pdf "/BBox" shading
|
|
and antialias =
|
|
match Pdf.lookup_direct pdf "/BBox" shading with
|
|
| Some (Pdf.Boolean true) -> true
|
|
| _ -> false
|
|
in
|
|
let shading =
|
|
match Pdf.lookup_fail "no /ShadingType" pdf "/ShadingType" shading with
|
|
| Pdf.Integer 1 -> read_function_shading pdf shading
|
|
| Pdf.Integer 3 -> read_radial_shading pdf shading
|
|
| Pdf.Integer 2 -> read_axial_shading pdf shading
|
|
| Pdf.Integer 4 -> FreeFormGouraudShading
|
|
| Pdf.Integer 5 -> LatticeFormGouraudShading
|
|
| Pdf.Integer 6 -> CoonsPatchMesh
|
|
| Pdf.Integer 7 -> TensorProductPatchMesh
|
|
| _ -> raise (Pdf.PDFError "Pdfgraphics.unknown shadingtype")
|
|
in
|
|
{shading_colourspace = colourspace;
|
|
shading_background = background;
|
|
shading_bbox = bbox;
|
|
shading_antialias = antialias;
|
|
shading_matrix = matrix;
|
|
shading_extgstate = extgstate;
|
|
shading = shading}
|
|
|
|
and read_shading_pattern pdf p =
|
|
let matrix = Pdf.parse_matrix pdf "/Matrix" p
|
|
and extgstate =
|
|
match Pdf.lookup_direct pdf "/ExtGState" p with
|
|
| Some (Pdf.Dictionary _ as d) -> d
|
|
| _ -> Pdf.Dictionary []
|
|
in
|
|
match Pdf.lookup_direct pdf "/Shading" p with
|
|
| Some shading ->
|
|
ShadingPattern (read_shading pdf matrix extgstate shading)
|
|
| _ ->
|
|
raise (Pdf.PDFError "No shading dictionary")
|
|
|
|
and read_pattern pdf page name =
|
|
match Pdf.lookup_direct pdf "/Pattern" page.Pdfpage.resources with
|
|
| None -> raise (Pdf.PDFError "No pattern dictionary")
|
|
| Some patterndict ->
|
|
match Pdf.lookup_direct pdf name patterndict with
|
|
| None -> raise (Pdf.PDFError "Pattern not found")
|
|
| Some pattern ->
|
|
match Pdf.lookup_direct pdf "/PatternType" pattern with
|
|
| Some (Pdf.Integer 1) ->
|
|
read_tiling_pattern pattern
|
|
| Some (Pdf.Integer 2) ->
|
|
read_shading_pattern pdf pattern
|
|
| _ -> raise (Pdf.PDFError "unknown pattern")
|
|
|
|
and process_op pdf page (partial, graphic) op =
|
|
let ret = (partial, graphic) in
|
|
(*i flprint (string_of_state !state); *)
|
|
(*i flprint (Pdfpages.string_of_op op ^ "\n"); i*)
|
|
match op with
|
|
| Pdfops.Op_W ->
|
|
(* Move the current partial path into Clip, and return *)
|
|
begin match partial with
|
|
| PartialPath (_, _, segments, subpaths) ->
|
|
if segments = [] && subpaths = [] then ret else
|
|
let path =
|
|
if segments <> []
|
|
then (Not_hole, Closed, rev segments)::subpaths
|
|
else subpaths
|
|
in
|
|
(!state).clip <- Some (NonZero, path); ret
|
|
| _ -> ret
|
|
end
|
|
(* FIXME: In NextClip needs to support possibly several clips, since we can do W n W n W n f, for instance? *)
|
|
| Pdfops.Op_W' ->
|
|
begin match partial with
|
|
| PartialPath (_, _, segments, subpaths) ->
|
|
if segments = [] && subpaths = [] then ret else
|
|
let path =
|
|
if segments <> []
|
|
then (Not_hole, Closed, rev segments)::subpaths
|
|
else subpaths
|
|
in
|
|
(!state).clip <- Some (EvenOdd, path); ret
|
|
| _ -> ret
|
|
end
|
|
| Pdfops.InlineImage (dict, data) ->
|
|
(NoPartial, GraphicInlineImage (dict, data, (!state).transform)::graphic)
|
|
| Pdfops.Op_MP name ->
|
|
begin match (!state).objectclass with
|
|
| PageDescriptionLevel -> (NoPartial, MCPoint name::graphic)
|
|
| TextObject -> ret (* FIXME -- Add it to the text partial. *)
|
|
| _ -> ret (* Invalid, silently drop *)
|
|
end
|
|
| Pdfops.Op_DP (name, properties) ->
|
|
begin match (!state).objectclass with
|
|
| PageDescriptionLevel ->
|
|
(NoPartial, MCPointProperties (name, properties)::graphic)
|
|
| TextObject -> ret (* FIXME -- Add it to the text partial. *)
|
|
| _ -> ret (* Invalid, silently drop *)
|
|
end
|
|
| Pdfops.Op_BX | Pdfops.Op_EX -> ret
|
|
| Pdfops.Op_ri n -> (!state).intent <- n; ret
|
|
| Pdfops.Op_j j -> (!state).joinstyle <- j; ret
|
|
| Pdfops.Op_J c -> (!state).capstyle <- c; ret
|
|
| Pdfops.Op_w w -> (!state).linewidth <- w; ret
|
|
| Pdfops.Op_M m -> (!state).mitrelimit <- m; ret
|
|
| Pdfops.Op_q ->
|
|
(*i flprint "Op_q\n"; i*)
|
|
push_statestack ();
|
|
ret
|
|
| Pdfops.Op_Q ->
|
|
(*i flprint "Op_Q\n"; i*)
|
|
pop_statestack ();
|
|
ret
|
|
| Pdfops.Op_SC vals | Pdfops.Op_SCN vals ->
|
|
(!state).line <- Floats vals;
|
|
ret
|
|
| Pdfops.Op_sc vals | Pdfops.Op_scn vals ->
|
|
(!state).fill <- Floats vals;
|
|
ret
|
|
| Pdfops.Op_scnName (name, vals) ->
|
|
begin match (!state).colourspace_nonstroke with
|
|
| Pdfspace.Pattern | Pdfspace.PatternWithBaseColourspace _ ->
|
|
begin try
|
|
(!state).fill <- Pattern (read_pattern pdf page name);
|
|
ret
|
|
with
|
|
_ -> ret
|
|
end
|
|
| _ ->
|
|
(!state).fill <- Named (name, vals);
|
|
ret
|
|
end
|
|
| Pdfops.Op_SCNName (name, vals) ->
|
|
begin match (!state).colourspace_stroke with
|
|
| Pdfspace.Pattern | Pdfspace.PatternWithBaseColourspace _ ->
|
|
begin try
|
|
(!state).line <- Pattern (read_pattern pdf page name);
|
|
ret
|
|
with
|
|
_ -> ret
|
|
end
|
|
| _ ->
|
|
(!state).line <- Named (name, vals);
|
|
ret
|
|
end
|
|
| Pdfops.Op_CS c ->
|
|
(*i Printf.printf "Op_CS: %s\n" c; *)
|
|
(!state).colourspace_nonstroke <- Pdfspace.read_colourspace pdf page.Pdfpage.resources (Pdf.Name c);
|
|
ret
|
|
| Pdfops.Op_cs c ->
|
|
(*i flprint "Op_cs\n";
|
|
Printf.printf "Pdfgraphics: Op_cs: %s\n" c; i*)
|
|
(!state).colourspace_nonstroke <- Pdfspace.read_colourspace pdf page.Pdfpage.resources (Pdf.Name c);
|
|
ret
|
|
| Pdfops.Op_G gv ->
|
|
(*i flprint "Op_G\n"; i*)
|
|
(!state).colourspace_stroke <- Pdfspace.DeviceGray;
|
|
(!state).line <- Floats [gv];
|
|
ret
|
|
| Pdfops.Op_g gv ->
|
|
(*i flprint "Op_g\n"; i*)
|
|
(!state).colourspace_nonstroke <- Pdfspace.DeviceGray;
|
|
(!state).fill <- Floats [gv];
|
|
ret
|
|
| Pdfops.Op_RG (rv, gv, bv) ->
|
|
(!state).colourspace_stroke <- Pdfspace.DeviceRGB;
|
|
(!state).line <- Floats [rv; gv; bv];
|
|
ret
|
|
| Pdfops.Op_rg (rv, gv, bv) ->
|
|
(!state).colourspace_nonstroke <- Pdfspace.DeviceRGB;
|
|
(!state).fill <- Floats [rv; gv; bv];
|
|
ret
|
|
| Pdfops.Op_K (c, m, y, k) ->
|
|
(!state).colourspace_stroke <- Pdfspace.DeviceCMYK;
|
|
(!state).line <- Floats [c; y; m; k];
|
|
ret
|
|
| Pdfops.Op_k (c, m, y, k) ->
|
|
(!state).colourspace_nonstroke <- Pdfspace.DeviceCMYK;
|
|
(!state).fill <- Floats [c; y; m; k];
|
|
ret
|
|
| Pdfops.Op_gs name ->
|
|
let ext_state_dict = Pdf.lookup_fail "Bad Op_gs" pdf "/ExtGState" page.Pdfpage.resources in
|
|
let gdict = Pdf.lookup_fail "Bad Op_gs" pdf name ext_state_dict in
|
|
update_graphics_state_from_dict pdf page.Pdfpage.resources gdict;
|
|
ret
|
|
| Pdfops.Op_m (x, y) ->
|
|
(* Begin a new subpath. Get into path mode if not already there. If the last op was an
|
|
[Op_m], it should have no effect. *)
|
|
(!state).objectclass <- PathObject;
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
if segs = []
|
|
then PartialPath ((x, y), (x, y), [], subpaths), graphic
|
|
else PartialPath ((x, y), (x, y), [], (Not_hole, Open, rev segs)::subpaths), graphic
|
|
| _ ->
|
|
PartialPath ((x, y), (x, y), [], []), graphic
|
|
end
|
|
| Pdfops.Op_l (x, y) ->
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_l");
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
PartialPath (sp, (x, y), Straight (cp, (x, y))::segs, subpaths), graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_l")
|
|
end
|
|
| Pdfops.Op_c (a, b, c, d, e, f) ->
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_c");
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
let ep = (e, f) in
|
|
let curve = Bezier (cp, (a, b), (c, d), ep) in
|
|
PartialPath (sp, ep, curve::segs, subpaths), graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_c")
|
|
end
|
|
| Pdfops.Op_v (a, b, c, d) ->
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_v");
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
let ep = (c, d) in
|
|
let curve = Bezier (cp, cp, (a, b), ep) in
|
|
PartialPath (sp, ep, curve::segs, subpaths), graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_v")
|
|
end
|
|
| Pdfops.Op_y (a, b, c, d) ->
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_y");
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
let ep = (c, d) in
|
|
let curve = Bezier (cp, (a, b), ep, ep) in
|
|
PartialPath (sp, ep, curve::segs, subpaths), graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_y")
|
|
end
|
|
| Pdfops.Op_h ->
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_h - not in PathObject");
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
PartialPath (sp, cp, [], (Not_hole, Closed, rev segs)::subpaths), graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_h - not a partial path")
|
|
end
|
|
| Pdfops.Op_s ->
|
|
(* Close and stroke. Equivalent to h S *)
|
|
process_ops pdf page ret [Pdfops.Op_h; Pdfops.Op_S]
|
|
| Pdfops.Op_b ->
|
|
(* Close, fill, stroke, nonzero. Equivalent to h B *)
|
|
process_ops pdf page ret [Pdfops.Op_h; Pdfops.Op_B]
|
|
| Pdfops.Op_b' ->
|
|
(* Close, fill, stroke, evenodd. Equivalent to h B* *)
|
|
process_ops pdf page ret [Pdfops.Op_h; Pdfops.Op_B']
|
|
| Pdfops.Op_f | Pdfops.Op_F ->
|
|
(* Close and Fill non-zero *)
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_f");
|
|
let partial, graphic = process_op pdf page (partial, graphic) Pdfops.Op_h in
|
|
(!state).objectclass <- PageDescriptionLevel;
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
(* segs is empty, due to [Op_h] *)
|
|
PartialPath (sp, cp, [], []),
|
|
Path ((NonZero, rev subpaths), path_attributes_fill ())::graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_f")
|
|
end
|
|
| Pdfops.Op_S ->
|
|
(* Stroke *)
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_S");
|
|
(!state).objectclass <- PageDescriptionLevel;
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
if segs = [] then
|
|
PartialPath (sp, cp, [], []),
|
|
Path ((EvenOdd, rev subpaths), path_attributes_stroke ())::graphic
|
|
else
|
|
PartialPath (sp, cp, [], []),
|
|
Path ((EvenOdd, rev ((Not_hole, Open, rev segs)::subpaths)), path_attributes_stroke ())::graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_S")
|
|
end
|
|
| Pdfops.Op_B ->
|
|
(* Fill and stroke, non-zero. *)
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_B");
|
|
(!state).objectclass <- PageDescriptionLevel;
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
if segs = [] then
|
|
PartialPath (sp, cp, [], []),
|
|
Path ((NonZero, rev subpaths), path_attributes_fill_and_stroke ())::graphic
|
|
else
|
|
PartialPath (sp, cp, [], []),
|
|
Path ((NonZero, rev ((Not_hole, Open, rev segs)::subpaths)), path_attributes_fill_and_stroke ())
|
|
::graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_B")
|
|
end
|
|
| Pdfops.Op_B' ->
|
|
(* Fill and stroke, even-odd. *)
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_B*");
|
|
let partial, graphic = process_op pdf page (partial, graphic) Pdfops.Op_h in
|
|
(!state).objectclass <- PageDescriptionLevel;
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
if segs = [] then
|
|
PartialPath (sp, cp, [], []),
|
|
Path ((EvenOdd, rev subpaths), path_attributes_fill_and_stroke ())::graphic
|
|
else
|
|
PartialPath (sp, cp, [], []),
|
|
Path ((EvenOdd, rev ((Not_hole, Open, rev segs)::subpaths)), path_attributes_fill_and_stroke ())
|
|
::graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_B*")
|
|
end
|
|
| Pdfops.Op_f' ->
|
|
(* Fill, even-odd *)
|
|
if (!state).objectclass <> PathObject then
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_f*");
|
|
(!state).objectclass <- PageDescriptionLevel;
|
|
begin match partial with
|
|
| PartialPath (sp, cp, segs, subpaths) ->
|
|
if segs = [] then
|
|
PartialPath (sp, cp, [], []),
|
|
Path ((EvenOdd, rev subpaths), path_attributes_fill ())::graphic
|
|
else
|
|
PartialPath (sp, cp, [], []),
|
|
Path ((EvenOdd, rev ((Not_hole, Open, rev segs)::subpaths)), path_attributes_fill ())
|
|
::graphic
|
|
| _ ->
|
|
raise (Pdf.PDFError "Pdfgraphics: Op_f*")
|
|
end
|
|
| Pdfops.Op_n ->
|
|
(* no-op *)
|
|
(!state).objectclass <- PageDescriptionLevel;
|
|
(* for now, until we support clipviews, clean up the polygon *)
|
|
(NoPartial, graphic)
|
|
| Pdfops.Op_re (x, y, w, h) ->
|
|
(* Rectangle. *)
|
|
let ops =
|
|
[Pdfops.Op_m (x, y);
|
|
Pdfops.Op_l (x +. w, y);
|
|
Pdfops.Op_l (x +. w, y +. h);
|
|
Pdfops.Op_l (x, y +. h);
|
|
Pdfops.Op_h]
|
|
in
|
|
process_ops pdf page (partial, graphic) ops
|
|
| Pdfops.Op_Do name ->
|
|
begin match Pdf.lookup_direct pdf "/XObject" page.Pdfpage.resources with
|
|
| Some d ->
|
|
begin match Pdf.lookup_direct pdf name d with
|
|
| Some xobj ->
|
|
begin match Pdf.lookup_direct pdf "/Subtype" xobj with
|
|
| Some (Pdf.Name "/Image") ->
|
|
let objnum =
|
|
match Pdf.find_indirect name d with
|
|
| None -> raise (Pdf.PDFError "image not found")
|
|
| Some i -> i
|
|
in
|
|
partial,
|
|
Image
|
|
({image_transform = (!state).transform;
|
|
image_transparency = (!state).opacity_nonstroke;
|
|
image_softmask = (!state).softmask}
|
|
, objnum)::graphic
|
|
| Some (Pdf.Name "/Form") ->
|
|
let elts = read_form_xobject pdf page xobj in
|
|
partial, rev elts @ graphic
|
|
| _ -> raise (Pdf.PDFError "Unknown kind of xobject")
|
|
end
|
|
| _ -> raise (Pdf.PDFError "Unknown xobject")
|
|
end
|
|
| None -> raise (Pdf.PDFError "xobject not found")
|
|
end
|
|
| Pdfops.Op_cm tr ->
|
|
(!state).transform <- Pdftransform.matrix_compose (!state).transform tr;
|
|
ret
|
|
| ( Pdfops.Op_Tc _
|
|
| Pdfops.Op_Tw _
|
|
| Pdfops.Op_Tz _
|
|
| Pdfops.Op_TL _
|
|
| Pdfops.Op_Tf _
|
|
| Pdfops.Op_Tr _
|
|
| Pdfops.Op_Ts _
|
|
| Pdfops.Op_Td _
|
|
| Pdfops.Op_TD _
|
|
| Pdfops.Op_Tm _
|
|
| Pdfops.Op_T'
|
|
| Pdfops.Op_Tj _
|
|
| Pdfops.Op_TJ _
|
|
| Pdfops.Op_' _
|
|
| Pdfops.Op_'' _
|
|
| Pdfops.Op_d0 _
|
|
| Pdfops.Op_d1 _) as op ->
|
|
begin match partial with
|
|
| PartialText t ->
|
|
let st = textstate () in
|
|
PartialText ((st, op)::t), graphic
|
|
| _ ->
|
|
(* If there's no partial text, this is an op affecting the text state but not in a text section. Such ops are allowed. FIXME: Deal with them properly - by ops altering the text state so this can be reflected in the initial state at the start of a text section *)
|
|
ret
|
|
end
|
|
| Pdfops.Op_sh n ->
|
|
let shading =
|
|
let shadingdict = Pdf.lookup_fail "no /Shading" pdf "/Shading" page.Pdfpage.resources in
|
|
let shading = Pdf.lookup_fail "named shading not found" pdf n shadingdict in
|
|
read_shading pdf Pdftransform.i_matrix Pdf.Null shading
|
|
and currentclip = (!state).clip in
|
|
partial, Shading (currentclip, shading, (!state).transform)::graphic
|
|
| Pdfops.Op_i flatness ->
|
|
if flatness >= 0 && flatness <= 100 then (!state).flatness <- flatness;
|
|
ret
|
|
| Pdfops.Op_d (spec, phase) ->
|
|
(!state).dash <- spec, phase;
|
|
ret
|
|
| Pdfops.Op_Unknown _ -> ret
|
|
| _ -> Pdfe.log "Operator shouldn't appear at this place"; ret
|
|
|
|
and getuntil_matching_emc level prev = function
|
|
| (Pdfops.Op_BMC _ | Pdfops.Op_BDC (_, _)) as h::t ->
|
|
getuntil_matching_emc (level + 1) (h::prev) t
|
|
| Pdfops.Op_EMC::t ->
|
|
if level < 0
|
|
then raise (Pdf.PDFError "Too many EMCs\n")
|
|
else if level = 0
|
|
then rev prev, t
|
|
else getuntil_matching_emc (level - 1) (Pdfops.Op_EMC::prev) t
|
|
| h::t -> getuntil_matching_emc level (h::prev) t
|
|
| [] -> raise (Pdf.PDFError "Missing EMC\n")
|
|
|
|
and getuntil_matching_Q level prev = function
|
|
| Pdfops.Op_q::t -> getuntil_matching_Q (level + 1) (Pdfops.Op_q::prev) t
|
|
| Pdfops.Op_Q::t ->
|
|
if level = 0
|
|
then rev prev, Pdfops.Op_Q::t
|
|
else getuntil_matching_Q (level - 1) (Pdfops.Op_Q::prev) t
|
|
| [] -> rev prev, []
|
|
| h::t -> getuntil_matching_Q level (h::prev) t
|
|
|
|
and process_ops pdf page (partial, graphic) ops =
|
|
match ops with
|
|
| [] -> partial, rev graphic
|
|
| Pdfops.Op_n::t ->
|
|
(* If there's a NextClip, select all operators within the scope of this
|
|
clip. That is, all operators until an [Op_Q] which puts the stack level
|
|
below the current level or the end of the stream, whichever comes first.*)
|
|
begin match (!state).clip with
|
|
| None ->
|
|
process_ops pdf page (partial, graphic) t
|
|
| Some path ->
|
|
(* We process the operators concerned, putting them inside a Clip,
|
|
and then proceed with the remaining operators (including any [Op_Q]).
|
|
However, to deal with the case of overlapping pairs of marked content
|
|
sections and q/Q pairs (which is allowed). Currently just chuck BDC we don't understand. *)
|
|
let toq, rest = getuntil_matching_Q 0 [] t in
|
|
let _, elts =
|
|
process_ops pdf page (NoPartial, []) toq
|
|
in
|
|
process_ops pdf page (NoPartial, Clip (path, elts)::graphic) rest
|
|
end
|
|
| Pdfops.Op_BMC n::t ->
|
|
(* FIXME: Marked content regions / q/Q pairs overlapping problem *)
|
|
begin try
|
|
let ops, rest = getuntil_matching_emc 0 [] t in
|
|
let partial, graphic' = process_ops pdf page (partial, []) ops in
|
|
process_ops pdf page (partial, MCSection (n, graphic')::graphic) rest
|
|
with
|
|
_ -> process_ops pdf page (partial, graphic) t
|
|
end
|
|
| Pdfops.Op_BDC (n, d)::t ->
|
|
(* FIXME: Marked content regions / q/Q pairs overlapping problem *)
|
|
begin try
|
|
let ops, rest = getuntil_matching_emc 0 [] t in
|
|
let partial, graphic' = process_ops pdf page (partial, []) ops in
|
|
process_ops pdf page (partial, MCSectionProperties (n, d, graphic')::graphic) rest
|
|
with
|
|
_ -> process_ops pdf page (partial, graphic) t
|
|
end
|
|
| Pdfops.Op_BT::t ->
|
|
(* Can't nest text sections, so just get to ET *)
|
|
let textops, rest = cleavewhile (neq Pdfops.Op_ET) t in
|
|
begin match rest with
|
|
| Pdfops.Op_ET::_ | [] ->
|
|
(* We allow blank in case of wrongly nested EMC / ET etc *)
|
|
let more = tail_no_fail rest in
|
|
(* We need to process the ops, and capture the text operations, but state changes inside
|
|
text sections (e.g colour changes) have global effect, so need to keep the state *)
|
|
(!state).objectclass <- TextObject;
|
|
let partial, _ =
|
|
process_ops pdf page (PartialText [], graphic) textops
|
|
in
|
|
begin match partial with
|
|
| PartialText t ->
|
|
let textblock =
|
|
Text (rev t, {textblock_transform = (!state).transform})
|
|
in
|
|
process_ops pdf page (partial, textblock::graphic) (Pdfops.Op_ET::more)
|
|
| _ -> raise (Pdf.PDFError "Bad operations in text block")
|
|
end
|
|
| _ ->
|
|
(*i Printf.printf "textops: %s\n\n" (Pdfops.string_of_ops textops);
|
|
Printf.printf "rest: %s\n\n" (Pdfops.string_of_ops rest); i*)
|
|
raise (Pdf.PDFError "No Matching Op_ET")
|
|
end
|
|
| Pdfops.Op_ET::t ->
|
|
(!state).objectclass <- PageDescriptionLevel;
|
|
process_ops pdf page (partial, graphic) t
|
|
| h::t -> process_ops pdf page (process_op pdf page (partial, graphic) h) t
|
|
|
|
(* Load the fonts as (name, pdfobject) pairs *)
|
|
and fonts_of_page pdf page =
|
|
match Pdf.lookup_direct pdf "/Font" page.Pdfpage.resources with
|
|
| Some (Pdf.Dictionary fs) -> fs
|
|
| _ -> []
|
|
|
|
(* Find the operations of a form xobject. *)
|
|
and read_form_xobject pdf page pdfobject =
|
|
let content = [Pdf.direct pdf pdfobject] in
|
|
let pagedict =
|
|
match Pdf.direct pdf page.Pdfpage.resources with
|
|
| Pdf.Dictionary rs -> rs
|
|
| _ -> []
|
|
and xobjdict =
|
|
match Pdf.direct pdf pdfobject with
|
|
| Pdf.Stream {contents = (dict, _)} ->
|
|
begin match Pdf.lookup_direct pdf "/Resources" dict with
|
|
| Some (Pdf.Dictionary rs) -> rs
|
|
| _ -> []
|
|
end
|
|
| _ -> raise (Pdf.PDFError "bad stream in read_form_xobject")
|
|
in
|
|
let total_resources =
|
|
Pdf.Dictionary (mergedict pagedict xobjdict)
|
|
in
|
|
let fake_page =
|
|
{Pdfpage.content = [];
|
|
Pdfpage.mediabox = Pdf.Null;
|
|
Pdfpage.resources = total_resources;
|
|
Pdfpage.rotate = Pdfpage.Rotate0;
|
|
Pdfpage.rest = Pdf.Dictionary []}
|
|
in
|
|
let _, graphic_elts =
|
|
(process_ops pdf fake_page (NoPartial, [])
|
|
(Pdfops.parse_operators pdf total_resources content))
|
|
in
|
|
graphic_elts
|
|
|
|
(* Main function - build a graphic from a page *)
|
|
and graphic_of_page pdf page =
|
|
statestack := [];
|
|
state := default_state ();
|
|
if Pdfcrypt.is_encrypted pdf then
|
|
raise (Pdf.PDFError "Pdfgraphics: File is encrypted")
|
|
else
|
|
begin
|
|
let _, elts =
|
|
let ops =
|
|
Pdfops.parse_operators pdf page.Pdfpage.resources page.Pdfpage.content
|
|
in
|
|
process_ops pdf page (NoPartial, []) ops
|
|
in
|
|
{elements = elts;
|
|
fonts = fonts_of_page pdf page;
|
|
resources = page.Pdfpage.resources}
|
|
end
|
|
|
|
let graphic_of_ops ops =
|
|
graphic_of_page
|
|
(Pdf.empty ())
|
|
{(Pdfpage.blankpage Pdfpaper.a4) with
|
|
Pdfpage.content =
|
|
[Pdf.Stream {contents =
|
|
(Pdf.Dictionary [], Pdf.Got (bytes_of_string (Pdfops.string_of_ops ops)))}]}
|
|
|
|
(* \section{Building a page from a graphic} *)
|
|
|
|
let int_of_shading_kind = function
|
|
| FunctionShading _ -> 1
|
|
| AxialShading _ -> 2
|
|
| RadialShading _ -> 3
|
|
| FreeFormGouraudShading -> 4
|
|
| LatticeFormGouraudShading -> 5
|
|
| CoonsPatchMesh -> 6
|
|
| TensorProductPatchMesh -> 7
|
|
|
|
let entries_of_shading pdf s =
|
|
match s.shading with
|
|
| RadialShading r ->
|
|
let coords =
|
|
let a, b, c, d, e, f = r.radialshading_coords in
|
|
Pdf.Array
|
|
[Pdf.Real a; Pdf.Real b; Pdf.Real c; Pdf.Real d; Pdf.Real e; Pdf.Real f]
|
|
and domain =
|
|
let a, b = r.radialshading_domain in
|
|
Pdf.Array
|
|
[Pdf.Real a; Pdf.Real b]
|
|
and funcnum =
|
|
match r.radialshading_function with
|
|
| [f] ->
|
|
Pdf.addobj pdf (Pdffun.pdfobject_of_function pdf f)
|
|
| funs ->
|
|
Pdf.addobj pdf (Pdf.Array (map (Pdffun.pdfobject_of_function pdf) funs))
|
|
and extend =
|
|
Pdf.Array
|
|
[Pdf.Boolean (fst r.radialshading_extend);
|
|
Pdf.Boolean (snd r.radialshading_extend)]
|
|
in
|
|
["/Coords", coords;
|
|
"/Domain", domain;
|
|
"/Function", Pdf.Indirect funcnum;
|
|
"/Extend", extend]
|
|
| AxialShading a ->
|
|
let coords =
|
|
let a, b, c, d = a.axialshading_coords in
|
|
Pdf.Array
|
|
[Pdf.Real a; Pdf.Real b; Pdf.Real c; Pdf.Real d]
|
|
and domain =
|
|
let a, b = a.axialshading_domain in
|
|
Pdf.Array
|
|
[Pdf.Real a; Pdf.Real b]
|
|
and funcnum =
|
|
match a.axialshading_function with
|
|
| [f] ->
|
|
Pdf.addobj pdf (Pdffun.pdfobject_of_function pdf f)
|
|
| funs ->
|
|
Pdf.addobj pdf (Pdf.Array (map (Pdffun.pdfobject_of_function pdf) funs))
|
|
and extend =
|
|
Pdf.Array
|
|
[Pdf.Boolean (fst a.axialshading_extend);
|
|
Pdf.Boolean (snd a.axialshading_extend)]
|
|
in
|
|
["/Coords", coords;
|
|
"/Domain", domain;
|
|
"/Function", Pdf.Indirect funcnum;
|
|
"/Extend", extend]
|
|
| _ -> []
|
|
|
|
let shading_object_of_shading pdf s =
|
|
let background =
|
|
match s.shading_background with
|
|
| None -> []
|
|
| Some b -> ["/Background", b]
|
|
and bbox =
|
|
match s.shading_bbox with
|
|
| None -> []
|
|
| Some b -> ["/BBox", b]
|
|
in
|
|
Pdf.Dictionary
|
|
(["/ShadingType", Pdf.Integer (int_of_shading_kind s.shading);
|
|
"/ColorSpace", s.shading_colourspace;
|
|
"/AntiAlias", Pdf.Boolean s.shading_antialias]
|
|
@ background @ bbox @ entries_of_shading pdf s)
|
|
|
|
let pattern_object_of_pattern xobject_level opdo_matrix pdf = function
|
|
| ShadingPattern s ->
|
|
begin try
|
|
let shading_matrix =
|
|
if xobject_level > 0 then
|
|
let inverted = Pdftransform.matrix_invert opdo_matrix in
|
|
Pdftransform.matrix_compose inverted s.shading_matrix
|
|
else
|
|
s.shading_matrix
|
|
in
|
|
Pdf.Dictionary
|
|
["/Type", Pdf.Name "/Pattern";
|
|
"/PatternType", Pdf.Integer 2;
|
|
"/Shading", shading_object_of_shading pdf s;
|
|
"/Matrix", Pdf.make_matrix shading_matrix]
|
|
with
|
|
Pdftransform.NonInvertable -> raise (Pdf.PDFError "Pdfgraphics.Bad pattern")
|
|
end
|
|
| _ ->
|
|
Pdfe.log "Unknown pattern\n";
|
|
Pdf.Dictionary []
|
|
|
|
(* Output a move and line/curve ops. *)
|
|
let ops_of_segs segs closure =
|
|
let raw_seg_ops =
|
|
map
|
|
(function
|
|
| Straight (_, (x, y)) -> Pdfops.Op_l (x, y)
|
|
| Bezier (_, (bx, by), (cx, cy), (dx, dy)) -> Pdfops.Op_c (bx, by, cx, cy, dx, dy))
|
|
segs
|
|
and get_move = function
|
|
| Straight ((x, y), _) | Bezier ((x, y), _, _, _) -> Pdfops.Op_m (x, y)
|
|
in
|
|
(* Predicate: Do we need to close this subpath? *)
|
|
match segs with
|
|
| [] -> []
|
|
| h::_ -> get_move h::raw_seg_ops @ (if closure = Closed then [Pdfops.Op_h] else [])
|
|
|
|
let protect ops =
|
|
[Pdfops.Op_q] @ ops @ [Pdfops.Op_Q]
|
|
|
|
let attribute_ops_of_path (_, a) =
|
|
[Pdfops.Op_w a.path_linewidth;
|
|
Pdfops.Op_J a.path_capstyle;
|
|
Pdfops.Op_j a.path_joinstyle;
|
|
begin match a.path_dash with (x, y) -> Pdfops.Op_d (x, y) end;
|
|
Pdfops.Op_M a.path_mitrelimit;
|
|
Pdfops.Op_ri a.path_intent]
|
|
|
|
let transform_ops_of_path (_, a) =
|
|
[Pdfops.Op_cm a.path_transform]
|
|
|
|
let stroke_ops_of_path ((winding, _), a) =
|
|
match winding, a.path_fill, a.path_line with
|
|
| _, None, None -> Pdfops.Op_n
|
|
| EvenOdd, Some _, Some _ -> Pdfops.Op_B'
|
|
| EvenOdd, Some _, None -> Pdfops.Op_f'
|
|
| NonZero, Some _, Some _ -> Pdfops.Op_B
|
|
| NonZero, Some _, None -> Pdfops.Op_f
|
|
| _, None, Some _ -> Pdfops.Op_S
|
|
|
|
let path_ops_of_path (_, subpaths) =
|
|
flatten (map (fun (_, closure, segs) -> ops_of_segs segs closure) subpaths)
|
|
|
|
let ops_of_path pdf page (((winding, subpaths), a) as p) =
|
|
(* Add a colourspace returning new resources and a new name, or return the name it's already held under. *)
|
|
let name_of_colourspace cs resources =
|
|
match cs with
|
|
(*i | Pdf.Name (("/DeviceGray" | "/DeviceRGB" | "/DeviceCMYK" | "/Pattern") as str) -> resources, str i*)
|
|
| Pdfspace.DeviceGray | Pdfspace.DeviceRGB | Pdfspace.DeviceCMYK | Pdfspace.Pattern -> resources, Pdfspace.string_of_colourspace cs
|
|
| _ ->
|
|
let existing_colourspacedict =
|
|
match Pdf.lookup_direct pdf "/ColorSpace" resources with
|
|
| Some ((Pdf.Dictionary _) as d) -> d
|
|
| _ -> Pdf.Dictionary []
|
|
in
|
|
(* FIXME: For now, we just always create a new one. Must search to see if it's already there for efficiency. *)
|
|
let name = Pdf.unique_key "cs" existing_colourspacedict in
|
|
let newcolourspacedict = Pdf.add_dict_entry existing_colourspacedict name (Pdfspace.write_colourspace pdf cs) in
|
|
Pdf.add_dict_entry resources "/ColorSpace" newcolourspacedict, name
|
|
in
|
|
let resources = page.Pdfpage.resources in
|
|
let attribute_ops = attribute_ops_of_path p
|
|
and transform = transform_ops_of_path p
|
|
and stroke_op = stroke_ops_of_path p in
|
|
let colours_stroke, resources =
|
|
match a.path_line with
|
|
| Some (cs, Floats vals) ->
|
|
let resources', name = name_of_colourspace cs resources in
|
|
[Pdfops.Op_CS name; Pdfops.Op_SCN vals], resources
|
|
| Some (cs, Named (n, vals)) ->
|
|
let resources', name = name_of_colourspace cs resources in
|
|
[Pdfops.Op_CS name; Pdfops.Op_SCNName (n, vals)], resources'
|
|
| _ -> [], resources
|
|
in
|
|
let colours_nonstroke, resources =
|
|
match a.path_fill with
|
|
| Some (cs, Floats vals) ->
|
|
let resources', name = name_of_colourspace cs resources in
|
|
[Pdfops.Op_cs name; Pdfops.Op_scn vals], resources'
|
|
| Some (cs, Named (n, vals)) ->
|
|
let resources', name = name_of_colourspace cs resources in
|
|
[Pdfops.Op_cs name; Pdfops.Op_scnName (n, vals)], resources'
|
|
| Some (_, Pattern p) ->
|
|
(* Build /Pattern cs and reference to pattern, having built the
|
|
pattern in the pattern dictionary *)
|
|
let pattern = pattern_object_of_pattern (!state).in_xobject (!state).opdo_matrix pdf p in
|
|
let resources, name =
|
|
let existing_patterndict =
|
|
match Pdf.lookup_direct pdf "/Pattern" resources with
|
|
| Some ((Pdf.Dictionary _) as d) -> d
|
|
| _ -> Pdf.Dictionary []
|
|
in
|
|
let name = Pdf.unique_key "pt" existing_patterndict in
|
|
let newpatterndict = Pdf.add_dict_entry existing_patterndict name pattern in
|
|
Pdf.add_dict_entry page.Pdfpage.resources "/Pattern" newpatterndict, name
|
|
in
|
|
[Pdfops.Op_cs "/Pattern"; Pdfops.Op_scnName (name, [])], resources
|
|
| _ -> [], resources
|
|
in
|
|
let gs, resources =
|
|
if a.path_transparency.fill_transparency < 1. || a.path_transparency.line_transparency < 1.
|
|
then
|
|
let resources, name =
|
|
let existing_extgstate =
|
|
match Pdf.lookup_direct pdf "/ExtGState" resources with
|
|
| Some ((Pdf.Dictionary _) as d) -> d
|
|
| _ -> Pdf.Dictionary []
|
|
in
|
|
let name = Pdf.unique_key "gs" existing_extgstate
|
|
and gsdict =
|
|
Pdf.Dictionary
|
|
[("/ca", Pdf.Real a.path_transparency.fill_transparency);
|
|
("/CA", Pdf.Real a.path_transparency.line_transparency)]
|
|
in
|
|
let new_extgstate = Pdf.add_dict_entry existing_extgstate name gsdict in
|
|
Pdf.add_dict_entry page.Pdfpage.resources "/ExtGState" new_extgstate, name
|
|
in
|
|
[Pdfops.Op_gs name], resources
|
|
else
|
|
[], resources
|
|
in
|
|
let path_ops = path_ops_of_path (winding, subpaths) in
|
|
protect (gs @ transform @ attribute_ops @ colours_stroke @ colours_nonstroke @ path_ops @ [stroke_op]),
|
|
resources
|
|
|
|
let ops_of_textstate st = []
|
|
|
|
let ops_of_textpiece (st, op) =
|
|
ops_of_textstate st @ [op]
|
|
|
|
(* Upon entry to this, the transformation matrix is identity *)
|
|
let ops_of_text tr ops =
|
|
protect ([Pdfops.Op_cm tr; Pdfops.Op_BT] @ (flatten <| map ops_of_textpiece ops) @ [Pdfops.Op_ET])
|
|
|
|
(* Transform a bounding box by a given matrix *)
|
|
let extreme_of_4 f a b c d =
|
|
hd <| sort f [a; b; c; d]
|
|
|
|
let min_of_4 = extreme_of_4 compare
|
|
|
|
let max_of_4 = extreme_of_4 (fun a b -> ~-(compare a b))
|
|
|
|
let transform_bbox tr l b r t =
|
|
let (x0, y0) = Pdftransform.transform_matrix tr (l, t)
|
|
and (x1, y1) = Pdftransform.transform_matrix tr (l, b)
|
|
and (x2, y2) = Pdftransform.transform_matrix tr (r, t)
|
|
and (x3, y3) = Pdftransform.transform_matrix tr (r, b) in
|
|
min_of_4 x0 x1 x2 x3,
|
|
min_of_4 y0 y1 y2 y3,
|
|
max_of_4 x0 x1 x2 x3,
|
|
max_of_4 y0 y1 y2 y3
|
|
|
|
(* Build a transparency group xobject, add it to the pdf and return its object number *)
|
|
let rec pdfobject_of_transparency_group (a, b, c, d) pdf t =
|
|
(!state).in_xobject <- (!state).in_xobject + 1;
|
|
let r =
|
|
let page = page_of_graphic pdf (0., 0., 0., 0.) t.tr_graphic
|
|
and group_attributes =
|
|
let cs =
|
|
match t.tr_group_colourspace with
|
|
| None -> []
|
|
| Some pdfobject -> ["/CS", pdfobject]
|
|
in
|
|
Pdf.Dictionary
|
|
(["/Type", Pdf.Name "/Group";
|
|
"/S", Pdf.Name "/Transparency";
|
|
"/I", Pdf.Boolean t.isolated;
|
|
"/K", Pdf.Boolean t.knockout] @ cs)
|
|
in
|
|
let extras =
|
|
["/Type", Pdf.Name "/XObject";
|
|
"/Subtype", Pdf.Name "/Form";
|
|
"/BBox", Pdf.Array [Pdf.Real a; Pdf.Real b; Pdf.Real c; Pdf.Real d];
|
|
"/Resources", page.Pdfpage.resources;
|
|
"/Group", group_attributes]
|
|
in
|
|
match page.Pdfpage.content with
|
|
| Pdf.Stream ({contents = Pdf.Dictionary dict, Pdf.Got data})::_ ->
|
|
Pdf.addobj pdf (Pdf.Stream ({contents = Pdf.Dictionary (extras @ dict), Pdf.Got data}))
|
|
| _ -> raise (Pdf.PDFError "Pdfgraphics: Bad page content")
|
|
in
|
|
(!state).in_xobject <- (!state).in_xobject - 1;
|
|
r
|
|
|
|
and pdfobject_of_softmask pdf m =
|
|
let bc =
|
|
match m.backdrop with
|
|
| None -> []
|
|
| Some fs -> ["/BC", Pdf.Array (map (function x -> Pdf.Real x) fs)]
|
|
and tr =
|
|
match m.softmask_transfer with
|
|
| None -> []
|
|
| Some f -> ["/TR", Pdffun.pdfobject_of_function pdf f]
|
|
in
|
|
Pdf.addobj pdf
|
|
(Pdf.Dictionary
|
|
(["/Type", Pdf.Name "/Mask";
|
|
"/S", Pdf.Name (match m.softmask_subtype with Alpha -> "/Alpha" | Luminosity -> "/Luminosity");
|
|
"/G", Pdf.Indirect (pdfobject_of_transparency_group m.softmask_bbox pdf m.transparency_group)]
|
|
@ bc @ tr))
|
|
|
|
and ops_of_image pdf page (a, i) =
|
|
(!state).opdo_matrix <- a.image_transform;
|
|
let resources = page.Pdfpage.resources in
|
|
let ops, resources =
|
|
let opgs, resources =
|
|
if a.image_transparency < 1. || a.image_softmask <> None
|
|
then
|
|
let resources, name =
|
|
let existing_extgstate =
|
|
match Pdf.lookup_direct pdf "/ExtGState" page.Pdfpage.resources with
|
|
| Some ((Pdf.Dictionary _) as d) -> d
|
|
| _ -> Pdf.Dictionary []
|
|
in
|
|
let name = Pdf.unique_key "gs" existing_extgstate
|
|
and gsdict =
|
|
let softmask =
|
|
match a.image_softmask with
|
|
| None -> []
|
|
| Some m -> ["/SMask", Pdf.Indirect (pdfobject_of_softmask pdf m)]
|
|
in
|
|
Pdf.Dictionary
|
|
([("/ca", Pdf.Real a.image_transparency)] @ softmask)
|
|
in
|
|
let new_extgstate = Pdf.add_dict_entry existing_extgstate name gsdict in
|
|
Pdf.add_dict_entry resources "/ExtGState" new_extgstate, name
|
|
in
|
|
[Pdfops.Op_gs name], resources
|
|
else
|
|
[], resources
|
|
in
|
|
[Pdfops.Op_cm a.image_transform] @ opgs @ [Pdfops.Op_Do ("/Im" ^ string_of_int i)], resources
|
|
in
|
|
protect ops, resources
|
|
|
|
and ops_of_shading pdf page path shading transform =
|
|
let resources', name =
|
|
(* Add new entry to shading dictionary, return its name, new resources *)
|
|
let existing_shadingdict =
|
|
match Pdf.lookup_direct pdf "/Shading" page.Pdfpage.resources with
|
|
| Some ((Pdf.Dictionary _) as d) -> d
|
|
| _ -> Pdf.Dictionary []
|
|
in
|
|
let name = Pdf.unique_key "sh" existing_shadingdict
|
|
and objnum = Pdf.addobj pdf (shading_object_of_shading pdf shading) in
|
|
let shadingref = Pdf.Indirect objnum in
|
|
let new_shadingdict = Pdf.add_dict_entry existing_shadingdict name shadingref in
|
|
let r =
|
|
Pdf.add_dict_entry page.Pdfpage.resources "/Shading" new_shadingdict
|
|
in
|
|
r, name
|
|
in
|
|
let ops =
|
|
let pathops, clipops =
|
|
match path with
|
|
| None -> [], []
|
|
| Some p ->
|
|
path_ops_of_path p, [Pdfops.Op_W; Pdfops.Op_n] (* FIXME: Even-odd vs Non-Zero *)
|
|
in
|
|
pathops @ clipops @ [Pdfops.Op_cm transform; Pdfops.Op_sh name]
|
|
in
|
|
protect ops, resources'
|
|
|
|
and ops_of_graphic_acc pdf page oplists = function
|
|
| [] ->
|
|
flatten (rev oplists), page
|
|
| Path p::t ->
|
|
let ops, resources' = ops_of_path pdf page p in
|
|
let page' = {page with Pdfpage.resources = resources'} in
|
|
ops_of_graphic_acc pdf page' (ops::oplists) t
|
|
| Image (attr, i)::t ->
|
|
let ops, resources' = ops_of_image pdf page (attr, i) in
|
|
let page' = {page with Pdfpage.resources = resources'} in
|
|
ops_of_graphic_acc pdf page' (ops::oplists) t
|
|
| Text (ts, {textblock_transform = tr})::t ->
|
|
let ops = ops_of_text tr ts in
|
|
ops_of_graphic_acc pdf page (ops::oplists) t
|
|
| MCSection (n, graphic)::t ->
|
|
let oplist, page' =
|
|
ops_of_graphic_acc pdf page [] graphic
|
|
in
|
|
ops_of_graphic_acc pdf page' (([Pdfops.Op_BMC n] @ oplist @ [Pdfops.Op_EMC])::oplists) t
|
|
| MCSectionProperties (n, d, graphic)::t ->
|
|
let oplist, page' =
|
|
ops_of_graphic_acc pdf page [] graphic
|
|
in
|
|
ops_of_graphic_acc pdf page' (([Pdfops.Op_BDC (n, d)] @ oplist @ [Pdfops.Op_EMC])::oplists) t
|
|
| MCPoint n::t ->
|
|
ops_of_graphic_acc pdf page ([Pdfops.Op_MP n]::oplists) t
|
|
| MCPointProperties (n, d)::t ->
|
|
ops_of_graphic_acc pdf page ([Pdfops.Op_DP (n, d)]::oplists) t
|
|
| GraphicInlineImage (dict, data, tr)::t ->
|
|
ops_of_graphic_acc pdf page (protect [Pdfops.Op_cm tr; Pdfops.InlineImage (dict, data)]::oplists) t
|
|
| Clip ((w, _) as p, elts)::t ->
|
|
let ops, page' =
|
|
let path_ops =
|
|
[Pdfops.Op_cm (!state).transform] @ path_ops_of_path p
|
|
and clipviewops =
|
|
[if w = NonZero then Pdfops.Op_W else Pdfops.Op_W'; Pdfops.Op_n]
|
|
and insideclipops, page' =
|
|
ops_of_graphic_acc pdf page [] elts
|
|
in
|
|
protect (path_ops @ clipviewops @ insideclipops), page'
|
|
in
|
|
ops_of_graphic_acc pdf page' (ops::oplists) t
|
|
| Shading (path, shading, transform)::t ->
|
|
let ops, resources' = ops_of_shading pdf page path shading transform in
|
|
let oplists'= protect ops::oplists
|
|
and page' = {page with Pdfpage.resources = resources'} in
|
|
ops_of_graphic_acc pdf page' oplists' t
|
|
|
|
(* Build a page from a graphic in the same PDF. *)
|
|
and image_numbers_of_elts prev = function
|
|
| Image (_, i)::t -> image_numbers_of_elts (i::prev) t
|
|
| MCSection (_, elts)::t
|
|
| MCSectionProperties (_, _, elts)::t
|
|
| Clip (_, elts)::t ->
|
|
let these = image_numbers_of_elts [] elts in
|
|
image_numbers_of_elts (these @ prev) t
|
|
| _::t -> image_numbers_of_elts prev t
|
|
| [] -> prev
|
|
|
|
and make_xobjects pdf elts =
|
|
let numbers = image_numbers_of_elts [] elts in
|
|
setify <| map (function n -> ("/Im" ^ string_of_int n), Pdf.Indirect n) numbers
|
|
|
|
and make_resources pdf g page' =
|
|
let resources =
|
|
match g.resources with
|
|
| Pdf.Dictionary rs -> rs
|
|
| _ -> []
|
|
and fontdict =
|
|
Pdf.Dictionary g.fonts
|
|
and xobjdict =
|
|
let objs = make_xobjects pdf g.elements in
|
|
Pdf.Dictionary objs
|
|
and resources_frompage =
|
|
match page'.Pdfpage.resources with
|
|
| Pdf.Dictionary d -> d
|
|
| _ -> raise (Pdf.PDFError "bad resources in page in make_resources")
|
|
in
|
|
let resources = remove "/Shading" resources in
|
|
let resources = remove "/Pattern" resources in
|
|
let resources = remove "/ExtGState" resources in
|
|
let resources = remove "/ColorSpace" resources in
|
|
(* [fold_right] so that entries overwrite *)
|
|
Pdf.Dictionary
|
|
(fold_right
|
|
(fun (k, v) d -> add k v d)
|
|
["/Font", fontdict; "/XObject", xobjdict]
|
|
(resources_frompage @ resources))
|
|
|
|
and page_of_graphic pdf (xmin, ymin, xmax, ymax) graphic =
|
|
let page =
|
|
Pdfpage.custompage (Pdf.Array [Pdf.Real xmin; Pdf.Real ymin; Pdf.Real xmax; Pdf.Real ymax])
|
|
in
|
|
let ops, page' = ops_of_graphic_acc pdf page [] graphic.elements in
|
|
(* We're not including the ExtGState because it's in the page', so need
|
|
to merge with resources *)
|
|
let resources = make_resources pdf graphic page' in
|
|
{page' with
|
|
Pdfpage.content = [Pdfops.stream_of_ops ops];
|
|
Pdfpage.resources = resources}
|
|
|
|
let ops_of_simple_graphic graphic =
|
|
fst (ops_of_graphic_acc (Pdf.empty ()) (Pdfpage.blankpage Pdfpaper.a4) [] graphic.elements)
|
|
|
|
(* FIXME Add in here a function to copy a page/graphic from one document to another *)
|
|
|
|
let streams_of_simple_graphic g =
|
|
(page_of_graphic (Pdf.empty ()) (0., 0., 600., 400.) g).Pdfpage.content
|
|
|
|
(* Transforming a graphic *)
|
|
let transform_segment tr s =
|
|
let f = Pdftransform.transform_matrix tr in
|
|
match s with
|
|
| Straight (x, y) -> Straight (f x, f y)
|
|
| Bezier (a, b, c, d) -> Bezier (f a, f b, f c, f d)
|
|
|
|
let transform_subpath tr (h, c, segments) =
|
|
(h, c, map (transform_segment tr) segments)
|
|
|
|
let transform_path tr (w, subpaths) =
|
|
(w, map (transform_subpath tr) subpaths)
|
|
|
|
let transform_element tr = function
|
|
| Path (pth, attr) -> Path (transform_path tr pth, attr)
|
|
| x -> x (* FIXME: Add rest of elements. *)
|
|
|
|
let transform_graphic tr g =
|
|
{g with elements = map (transform_element tr) g.elements}
|