(* Drawing operations. *) open Pdfutil open Cpdferror let embed_font = ref (fun () -> Cpdfembed.ExistingNamedFont) let getfontname = ref (fun () -> "") let getfontsize = ref (fun () -> 0.) let setfontname = ref (fun _ -> ()) let setfontsize = ref (fun _ -> ()) let ttfs = null_hash () let fontpack_initialised = ref false let drawops = ref [("_MAIN", [])] let startxobj n = drawops := (n, [])::!drawops let xobj_bbox = ref (0., 0., 1000., 1000.) let xobjbbox s = xobj_bbox := Cpdfcoord.parse_rectangle (Pdf.empty ()) s let addop o = match !drawops with | (n, ops)::t -> drawops := (n, (o::ops))::t | [] -> error "no drawops" let endxobj () = match !drawops with | (n, ops)::t -> drawops := t; let a, b, c, d = !xobj_bbox in addop (Cpdfdraw.FormXObject (a, b, c, d, n, rev ops)) | [] -> error "too many -end-xobj or -et" let addbt () = drawops := ("_TEXT", [])::!drawops let addet () = match !drawops with | ("_TEXT", ops)::t -> drawops := t; addop (Cpdfdraw.TextSection (rev ops)) | _ -> error "not in a text section at -et" let push () = drawops := ("_PUSH", [])::!drawops let pop () = match !drawops with | ("_PUSH", ops)::t -> drawops := t; addop (Cpdfdraw.Qq (rev ops)) | _ -> error "not in a pushed section at -pop" let readfloats s = map float_of_string (String.split_on_char ' ' s) let parse_color s = match lookup (String.lowercase_ascii s) Cpdfcolours.colours with | Some c -> let r = float_of_int ((c land 0xFF0000) lsr 16) /. 255. in let g = float_of_int ((c land 0x00FF00) lsr 8) /. 255. in let b = float_of_int (c land 0x0000FF) /. 255. in Cpdfaddtext.RGB (r, g, b) | None -> let getnum = function | Pdfgenlex.LexInt i -> float i | Pdfgenlex.LexReal f -> f | _ -> error "Bad color" in match Pdfgenlex.lex_string s with | [g] -> Cpdfaddtext.Grey (getnum g) | [r; g; b] -> Cpdfaddtext.RGB (getnum r, getnum g, getnum b) | [c; y; m; k] -> Cpdfaddtext.CYMK (getnum c, getnum y, getnum m, getnum k) | _ -> error "Bad color" let col_of_string s = match parse_color s with | Cpdfaddtext.RGB (r, g, b) -> Cpdfdraw.RGB (r, g, b) | Cpdfaddtext.Grey g -> Cpdfdraw.Grey g | Cpdfaddtext.CYMK (c, y, m, k) -> Cpdfdraw.CYMK (c, y, m, k) | exception _ -> Cpdfdraw.NoCol let setstroke s = addop (Cpdfdraw.SetStroke (col_of_string s)) let setfill s = addop (Cpdfdraw.SetFill (col_of_string s)) let addrect s = let x, y, w, h = Cpdfcoord.parse_rectangle (Pdf.empty ()) s in addop (Cpdfdraw.Rect (x, y, w, h)) let addto s = let x, y = Cpdfcoord.parse_coordinate (Pdf.empty ()) s in addop (Cpdfdraw.To (x, y)) let addline s = let x, y = Cpdfcoord.parse_coordinate (Pdf.empty ()) s in addop (Cpdfdraw.Line (x, y)) let addbezier s = match readfloats s with | [a; b; c; d; e; f] -> addop (Cpdfdraw.Bezier (a, b, c, d, e, f)) | _ -> error "-bez requires six numbers" | exception _ -> error "malformed -bez" let addbezier23 s = match readfloats s with | [a; b; c; d] -> addop (Cpdfdraw.Bezier23 (a, b, c, d)) | _ -> error "-bez23 requires four numbers" | exception _ -> error "malformed -bez23" let addbezier13 s = match readfloats s with | [a; b; c; d] -> addop (Cpdfdraw.Bezier13 (a, b, c, d)) | _ -> error "-bez13 requires four numbers" | exception _ -> error "malformed -bez13" let addcircle s = match readfloats s with | [x; y; r] -> let _, _, segs = hd (snd (Cpdfshape.circle x y r)) in (match segs with | Cpdfshape.Bezier ((a, b), _, _, _)::_ -> addop (Cpdfdraw.To (a, b)) | _ -> assert false); iter (function | Cpdfshape.Bezier (_, (c, d), (e, f), (g, h)) -> addop (Cpdfdraw.Bezier (c, d, e, f, g, h)) | Cpdfshape.Straight _ -> assert false) segs | _ -> error "-circle requires three numbers" | exception _ -> error "malformed -circle" let stroke () = addop Cpdfdraw.Stroke let fill () = addop Cpdfdraw.Fill let fillevenodd () = addop Cpdfdraw.FillEvenOdd let strokefill () = addop Cpdfdraw.FillStroke let strokefillevenodd () = addop Cpdfdraw.FillStrokeEvenOdd let clip () = addop Cpdfdraw.Clip let clipevenodd () = addop Cpdfdraw.ClipEvenOdd let closepath () = addop Cpdfdraw.ClosePath let setthickness s = try addop (Cpdfdraw.SetLineThickness (float_of_string s)) with _ -> error "Thickness must be a number" let setcap s = let num = match s with | "butt" -> 0 | "round" -> 1 | "square" -> 2 | _ -> error "Unknown cap type" in addop (Cpdfdraw.SetLineCap num) let setjoin s = let num = match s with | "miter" -> 0 | "round" -> 1 | "bevel" -> 2 | _ -> error "Unknown join type" in addop (Cpdfdraw.SetLineJoin num) let setmiter s = try addop (Cpdfdraw.SetMiterLimit (float_of_string s)) with _ -> error "Miter limit must be a number" let setdash s = try let x, y = let nums = readfloats s in all_but_last nums, last nums in addop (Cpdfdraw.SetDashPattern (x, y)) with _ -> error "Dash pattern elements must one or more numbers" let setmatrix s = match readfloats s with | [a; b; c; d; e; f] -> addop (Cpdfdraw.Matrix {Pdftransform.a = a; Pdftransform.b = b; Pdftransform.c = c; Pdftransform.d = d; Pdftransform.e = e; Pdftransform.f = f}) | _ -> error "Matrix must have six numbers" | exception _ -> error "Matrix elements must be numbers" let setmtranslate s = match readfloats s with | [a; b] -> addop (Cpdfdraw.Matrix (Pdftransform.matrix_of_transform [Pdftransform.Translate (a, b)])) | _ | exception _ -> error "-mtrans takes two numbers" let setmrotate s = match readfloats s with | [a; b; c] -> addop (Cpdfdraw.Matrix (Pdftransform.matrix_of_transform [Pdftransform.Rotate ((a, b), c)])) | _ | exception _ -> error "-mrot takes three numbers" let setmscale s = match readfloats s with | [a; b; c; d] -> addop (Cpdfdraw.Matrix (Pdftransform.matrix_of_transform [Pdftransform.Scale ((a, b), c, d)])) | _ | exception _ -> error "-mscale takes four numbers" let setmshearx s = match readfloats s with | [a; b; c] -> addop (Cpdfdraw.Matrix (Pdftransform.matrix_of_transform [Pdftransform.ShearX ((a, b), c)])) | _ | exception _ -> error "-mshearx takes three numbers" let setmsheary s = match readfloats s with | [a; b; c] -> addop (Cpdfdraw.Matrix (Pdftransform.matrix_of_transform [Pdftransform.ShearY ((a, b), c)])) | _ | exception _ -> error "-msheary takes three numbers" let usexobj s = addop (Cpdfdraw.Use s) let addjpeg n = let name, filename = match String.split_on_char '=' n with | [name; filename] -> name, filename | _ -> error "addjpeg: bad file specification" in try let data = Pdfio.bytes_of_string (contents_of_file filename) in addop (Cpdfdraw.ImageXObject (name, Cpdfimage.obj_of_jpeg_data data)) with _ -> error "addjpeg: could not load JPEG" let addpng n = let name, filename = match String.split_on_char '=' n with | [name; filename] -> name, filename | _ -> error "addpng: bad file specification" in let data = Pdfio.bytes_of_string (contents_of_file filename) in addop (Cpdfdraw.ImageXObject (name, Cpdfimage.obj_of_png_data data)) let addimage s = addop (Cpdfdraw.Image s) let addnewpage s = addop Cpdfdraw.NewPage let addopacity f = addop (Cpdfdraw.Opacity f) let addsopacity f = addop (Cpdfdraw.SOpacity f) let addleading f = addop (Cpdfdraw.Leading f) let addcharspace f = addop (Cpdfdraw.CharSpace f) let addwordspace f = addop (Cpdfdraw.WordSpace f) let addtextscale f = addop (Cpdfdraw.TextScale f) let addrendermode i = addop (Cpdfdraw.RenderMode i) let addrise f = addop (Cpdfdraw.Rise f) let addnewline () = addop Cpdfdraw.Newline let add_default_fontpack fontname = if not !fontpack_initialised then begin addop (Cpdfdraw.FontPack (fontname, !embed_font (), null_hash ())); set fontpack_initialised end let addtext s = begin match !drawops with _::_::_ -> () | _ -> error "-text must be in a -bt / -et section" end; add_default_fontpack (!getfontname ()); addop (Cpdfdraw.Font (!getfontname (), !getfontsize ())); addop (Cpdfdraw.Text s) let addspecialtext s = begin match !drawops with _::_::_ -> () | _ -> error "-stext must be in a -bt / -et section" end; add_default_fontpack (!getfontname ()); addop (Cpdfdraw.Font (!getfontname (), !getfontsize ())); addop (Cpdfdraw.SpecialText s)