This commit is contained in:
John Whitington 2023-05-11 22:03:47 +01:00
parent 148cde595d
commit 4e3072803a
6 changed files with 2086 additions and 5 deletions

View File

@ -7,7 +7,8 @@ DOC = cpdfunicodedata cpdferror cpdfdebug cpdfjson cpdfstrftime cpdfcoord \
cpdfembed cpdfaddtext cpdffont cpdftype cpdfpad cpdfocg \
cpdfsqueeze cpdfdraft cpdfspot cpdfpagelabels cpdfcreate cpdfannot \
cpdfxobject cpdfimpose cpdftweak cpdftexttopdf cpdftoc cpdfjpeg \
cpdfpng cpdfimage cpdfdraw cpdfcomposition cpdfcommand
cpdfpng cpdfimage cpdfdraw cpdfcomposition cpdfgraphics cpdfshape \
cpdfcommand
MODS = $(NONDOC) $(DOC)

View File

@ -1832,14 +1832,14 @@ let addbezier s =
let addcircle s =
match readfloats s with
| [x; y; r] ->
let _, _, segs = hd (snd (Pdfshapes.circle x y r)) in
let _, _, segs = hd (snd (Cpdfshape.circle x y r)) in
(match segs with
| Pdfgraphics.Bezier ((a, b), _, _, _)::_ -> addop (Cpdfdraw.To (a, b))
| Cpdfgraphics.Bezier ((a, b), _, _, _)::_ -> addop (Cpdfdraw.To (a, b))
| _ -> assert false);
iter
(function
| Pdfgraphics.Bezier (_, (c, d), (e, f), (g, h)) -> addop (Cpdfdraw.Bezier (c, d, e, f, g, h))
| Pdfgraphics.Straight _ -> assert false)
| Cpdfgraphics.Bezier (_, (c, d), (e, f), (g, h)) -> addop (Cpdfdraw.Bezier (c, d, e, f, g, h))
| Cpdfgraphics.Straight _ -> assert false)
segs
| _ -> error "-circle requires three numbers"
| exception _ -> error "malformed -circle"

1745
cpdfgraphics.ml Normal file

File diff suppressed because it is too large Load Diff

172
cpdfgraphics.mli Normal file
View File

@ -0,0 +1,172 @@
(** Structured Graphics. This will (eventually) be a module allowing for the raising of a page's contents to a tree form, the manipulation of that tree and its writing back to the page, with no possible loss of fidelity.
It is only a little experiment at the moment... *)
open Pdfutil
open Pdfio
(** Point. *)
type fpoint = float * float
(** Winding rule. *)
type winding_rule = EvenOdd | NonZero
(** A segment (a straight line or bezier curve) *)
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
val string_of_path : path -> string
(** Colour values *)
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
type transparency_attributes =
{fill_transparency : float;
line_transparency : float}
(** Path attributes. *)
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 image_attributes =
{image_transform : Pdftransform.transform_matrix;
image_transparency : float;
image_softmask : softmask option} (* The /ca value *)
and softmask_subtype =
Alpha | Luminosity
and transparency_group =
{tr_group_colourspace : Pdf.pdfobject option;
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 fontname = string * Pdf.pdfobject
(** For now, just support for reading paths out. Eventually a tree-structure for
an op stream. *)
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
| 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 *)
(** Bounding box xmin, xmax, ymin, yman of a graphic *)
val bbox_of_graphic : t -> float * float * float * float
(** Make a graphic from operations. *)
val graphic_of_page : Pdf.t -> Pdfpage.t -> t
(** Make a graphic from a simple string. *)
val graphic_of_ops : Pdfops.t list -> t
(** Flatten a graphic to a list of operations and replace the operations in a
page by them, returning the new page. *)
val page_of_graphic : Pdf.t -> (float * float * float * float) -> t -> Pdfpage.t
(** Debug string of a [graphic] *)
val string_of_graphic : t -> string
(** Operations from a simple graphic (i.e no need for resources etc.) *)
val ops_of_simple_graphic : t -> Pdfops.t list
(** Pdfdoc.content entry from a simple graphic (i.e no need for resources etc.) *)
val streams_of_simple_graphic : t -> Pdf.pdfobject list
(** Transform a graphic by a matrix. *)
val transform_graphic : Pdftransform.transform_matrix -> t -> t

146
cpdfshape.ml Normal file
View File

@ -0,0 +1,146 @@
(* \chaptertitle{Shapes}{Stroking lines and making shapes} *)
(* This module provides for the stroking of lines, and production of shape
primitives (circles, regular polygons etc). *)
open Pdfutil
(* \section{Common geometric functions} *)
(* The factor by which we multiply the radius to find the length of the bezier
control lines when approximating quarter arcs to make semicircles and circles.
*)
let kappa = ((sqrt 2. -. 1.) /. 3.) *. 4.
(* Calculate rotation from [p] to [p'] about [c] with the shorter arc-length.
When arc-lengths are equal, the result may be either. *)
let rotation (cx, cy) (px, py) (px', py') =
let px = px -. cx and py = py -. cy
and px' = px' -. cx and py' = py' -. cy in
let a = px *. py' -. py *. px'
and b = px *. px' +. py *. py' in
atan2 a b
(* The absolute angle to a point [p] from a centre [c]. The angle is the
rotation clockwise (i.e the first quadrant encountered has positive [x] and [y]
values) from East. When the point is [(0, 0)], the result is [0].*)
let angle_to (cx, cy) (px, py) =
let r = atan2 (py -. cy) (px -. cx) in
if r < 0. then r +. 2. *. pi else r
(* Restrict an angle [a] to one of those at $s, 2s, 3s\ldots$. We find the two
candidate angles, and see which [a] is numerically closer to. The candidate
points are taken modulo $2\pi$ for this to work. *)
let restrict_angle s a =
let p = mod_float (floor (a /. s) *. s) (2. *. pi) in
let p' = mod_float (p +. s) (2. *. pi) in
if abs_float (p -. a) < abs_float (p' -. a) then p else p'
(* \section{Some Useful Shapes} *)
(* Make a quarter-circle from a single bezier curve from [s] to $(s + \pi / 2)
\bmod 2\pi$ with centre [c] and radius [r]. We cheat by making the standard
quarter from [(1, 0)] to [(0, 1)] and rotating using the [Transform] module.
*)
let quarter s (cx, cy) r =
let standard_quarter_points =
[(1., 0.); (1., kappa); (kappa, 1.); (0., 1.)]
and transform =
[Pdftransform.Translate(cx, cy);
Pdftransform.Scale((0., 0.), r, r);
Pdftransform.Rotate((0., 0.), s)]
in
match
map (Pdftransform.transform transform) standard_quarter_points
with
| [p; q; r; s] -> Cpdfgraphics.Bezier(p, q, r, s)
| _ -> raise (Pdf.PDFError ("Shapes.quarter: inconsistency"))
(* The anticlockwise variant. *)
let quarter_anticlockwise s c r =
match quarter s c r with
| Cpdfgraphics.Bezier(p, q, r, s) -> Cpdfgraphics.Bezier(s, r, q, p)
| _ -> raise (Pdf.PDFError "Shapes.quarter_anticlockwise: inconsistency")
(* Some of the following functions generate what is supposed to be a connected
list of segments. However, since they operate by calculating each segment
seperately, floating point inaccuracies can arise, making the end of one
segment misalign with the start of the next. This function corrects the defect
by copying the end of one segment to the beginning of the next. We only need to
deal with bezier segments for now. *)
let rec joinsegs segments =
match segments with
| [] -> []
| [x] -> [x]
| Cpdfgraphics.Bezier(_, _, _, d) as s::Cpdfgraphics.Bezier(_, b', c', d')::rest ->
s::joinsegs (Cpdfgraphics.Bezier(d, b', c', d')::rest)
| _ -> raise (Pdf.PDFError "PDFShapes.joinsegs: Segment not supported")
(* This version sets the start and end points to p1 and p2 respectively. Used
for ensuring round joins join correctly to the rails they connect *)
let joinsegs_ends p1 p2 segments =
match joinsegs segments with
| [] -> []
| [Cpdfgraphics.Bezier(a, b, c, d)] -> [Cpdfgraphics.Bezier(p1, b, c, p2)]
| segs ->
match extremes_and_middle segs with
| Cpdfgraphics.Bezier(_, b, c, d), m, Cpdfgraphics.Bezier(a', b', c', _) ->
Cpdfgraphics.Bezier(p1, b, c, d)::m @ [Cpdfgraphics.Bezier(a', b', c', p2)]
| _ -> raise (Pdf.PDFError "PDFShapes.joinsegs_ends: Segment not supported")
(* The shorter arc made from bezier curves from [p1] to [p2] with centre [c].
The arc is formed from zero or more quarter arcs rotated accordingly, and at
most one partial arc produced by truncating a quarter arc, again rotated. If
[p1=p2], no segments are produced. If the two curves defined by the arguments
are of equal length, the one chosen is undefined. *)
(*i let arc p1 p2 c =
let ninety = pi /. 2.
and angletogo = rotation c p1 p2 (*r signed angle to turn through *)
and abs_angle = angle_to c p1 (*r absolute angle to the first point *)
and r = distance_between p1 c in (*r radius of the resultant arc *)
let quarter, ninety_abs =
if angletogo > 0.
then quarter, ninety
else quarter_anticlockwise, ~-.ninety
in
let segments = ref []
and angletogo = ref (abs_float angletogo) (*r Have dealt with sign. *)
and abs_angle = ref abs_angle in
while !angletogo > 0. do
if !angletogo >= ninety then
begin
angletogo := !angletogo -. ninety;
segments := (quarter !abs_angle c r)::!segments;
abs_angle := mod_float (!abs_angle +. ninety_abs) (2. *. pi)
end
else
(* Calculate a partial arc to finish, if required. *)
if !angletogo > 0. then
begin
let q = quarter !abs_angle c r in
let portion_needed = !angletogo /. ninety in
let portion, _ = Polygon.bezier_split portion_needed q in
segments := portion::!segments;
angletogo := 0.
end;
done;
joinsegs_ends p1 p2 (rev !segments) i*)
(* Approximate a circle using four bezier curves.*)
let circle x y r =
Cpdfgraphics.NonZero,
[(Cpdfgraphics.Not_hole,
Cpdfgraphics.Closed,
joinsegs
[quarter 0. (x, y) r;
quarter (pi /. 2.) (x, y) r;
quarter pi (x, y) r;
quarter (3. *. pi /. 2.) (x, y) r ])]
let rectangle x y w h =
(Cpdfgraphics.EvenOdd,
([(Cpdfgraphics.Not_hole,
Cpdfgraphics.Closed,
[Cpdfgraphics.Straight ((x, y), (x +. w, y));
Cpdfgraphics.Straight ((x +. w, y), (x +. w, y +. h));
Cpdfgraphics.Straight ((x +. w, y +. h), (x, y +. h));
Cpdfgraphics.Straight ((x, y +. h), (x, y))])]))

17
cpdfshape.mli Normal file
View File

@ -0,0 +1,17 @@
(** Basic Shapes *)
(** The factor by which the radius of a circle is multiplied to find the length
of the bezier control lines when approximating quarter arcs to make circles. *)
val kappa : float
(** Calling [restrict_angle s a] restricts an angle [a] to one of those at [s,
2s, 3s...] returning the chosen one. *)
val restrict_angle : float -> float -> float
(** Calling [circle x y r] builds a path representing a circle at [(x, y)] with
radius [r]. *)
val circle : float -> float -> float -> Cpdfgraphics.path
(** Calling [rectangle x y w h] builds a path representing a rectangle with top
left [(x, y)], width [w] and height [h]. *)
val rectangle : float -> float -> float -> float -> Cpdfgraphics.path