///|
// Vector graphics in the page model: a `GraphicItem` is a small display list
// of paths, clips, transforms, text and images, drawn in a coordinate space
// of its own. It is what vector image formats (SVG) lower to, and what
// anything else that needs more than axis-aligned rectangles can use.
//
// A graphic's space starts out as the page's (points, y down) translated to
// the item's `(x_pt, y_pt)`; `Transform` ops change it for the ops after
// them, and `Save`/`Restore` bracket such changes together with clips and
// transparency, in the manner of PDF's `q`/`Q`.

///|
/// An affine transformation `[a b c d e f]`, mapping `(x, y)` to
/// `(a*x + c*y + e, b*x + d*y + f)` (PDF's and SVG's convention).
pub(all) struct Matrix {
  a : Double
  b : Double
  c : Double
  d : Double
  e : Double
  f : Double
} derive(Eq, Debug, ToJson)

///|
/// The identity transformation.
pub let identity : Matrix = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0, }

///|
/// `self` applied after `other`: the transformation that maps a point
/// through `other` first, then through `self`.
pub fn Matrix::after(self : Matrix, other : Matrix) -> Matrix {
  {
    a: self.a * other.a + self.c * other.b,
    b: self.b * other.a + self.d * other.b,
    c: self.a * other.c + self.c * other.d,
    d: self.b * other.c + self.d * other.d,
    e: self.a * other.e + self.c * other.f + self.e,
    f: self.b * other.e + self.d * other.f + self.f,
  }
}

///|
/// Map a point.
pub fn Matrix::apply(self : Matrix, x : Double, y : Double) -> (Double, Double) {
  (self.a * x + self.c * y + self.e, self.b * x + self.d * y + self.f)
}

///|
/// One segment of a path. A path is a sequence of subpaths, each starting
/// with `MoveTo` or `Rectangle`; `LineTo`, `CurveTo` and `Close` continue
/// from the current point, so they need one before them (checked by
/// `PageModel::validate`). `Close` joins a subpath's end to its start, which
/// becomes the current point.
pub(all) enum PathSegment {
  MoveTo(Double, Double)
  LineTo(Double, Double)
  /// A cubic Bézier curve: two control points, then the end point.
  CurveTo(Double, Double, Double, Double, Double, Double)
  /// An axis-aligned rectangle, a closed subpath of its own: x, y (its
  /// top-left corner), width, height. It starts at the bottom-left corner
  /// `(x, y + height)` and runs to the bottom-right, top-right and top-left
  /// corners before closing, counter-clockwise as seen on the page when
  /// the width and height are positive (as PDF's `re` does with positive
  /// extents in its y-up space). Its start is the current point after it.
  Rectangle(Double, Double, Double, Double)
  Close
} derive(Eq, Debug, ToJson)

///|
/// Which regions of a self-intersecting path are inside.
pub(all) enum FillRule {
  NonZero
  EvenOdd
} derive(Eq, Debug, ToJson)

///|
/// How the ends of an open stroked subpath are drawn.
pub(all) enum LineCap {
  ButtCap
  RoundCap
  SquareCap
} derive(Eq, Debug, ToJson)

///|
/// How the corners of a stroked path are drawn.
pub(all) enum LineJoin {
  MiterJoin
  RoundJoin
  BevelJoin
} derive(Eq, Debug, ToJson)

///|
/// How wide a stroke is. PDF and SVG disagree about a width of 0 (the
/// thinnest line a device can draw in PDF, nothing at all in SVG), so a
/// `Width` must be positive (checked by `PageModel::validate`) and the
/// thinnest line is asked for explicitly.
pub(all) enum StrokeWidth {
  /// This many units of the space the stroke is drawn in.
  Width(Double)
  /// The thinnest line the device can draw, whatever the transformation:
  /// PDF's line width 0; in SVG a non-scaling stroke one pixel wide. Its
  /// dashes, like any stroke's, are measured in the space it is drawn in
  /// (not the device's), so SVG draws a dashed hairline as its dashes,
  /// each a solid hairline; glyph outlines cannot be drawn so, and text is
  /// not outlined with a dashed hairline (checked by `PageModel::validate`).
  Hairline
} derive(Eq, Debug, ToJson)

///|
/// How a path is stroked. `dash` is the dash pattern (lengths of dashes and
/// gaps, alternately, at least one of them positive; empty for a solid
/// line) starting `dash_phase` into it. The miter limit is at least 1.
pub(all) struct StrokeStyle {
  width : StrokeWidth
  cap : LineCap
  join : LineJoin
  miter_limit : Double
  dash : Array[Double]
  dash_phase : Double
} derive(Eq, Debug, ToJson)

///|
/// A solid line one point wide with butt caps and miter joins (limit 10),
/// the initial stroke state of PDF.
pub fn StrokeStyle::default() -> StrokeStyle {
  {
    width: Width(1.0),
    cap: ButtCap,
    join: MiterJoin,
    miter_limit: 10.0,
    dash: [],
    dash_phase: 0.0,
  }
}

///|
/// A colour at a position (0 to 1) along a gradient.
pub(all) struct GradientStop {
  offset : Double
  color : Color
} derive(Eq, Debug, ToJson)

///|
/// Where a gradient's colours vary, in gradient space: offset 0 is at its
/// start and offset 1 at its end.
pub(all) enum GradientShape {
  /// Along the line from `(x1, y1)` to `(x2, y2)`, constant across it
  /// (PDF's axial shading, SVG's ``).
  Linear(x1~ : Double, y1~ : Double, x2~ : Double, y2~ : Double)
  /// From the circle of radius `r1` around `(x1, y1)` to the one of radius
  /// `r2` around `(x2, y2)` (PDF's radial shading; SVG's ``
  /// with focal circle `fx fy fr` = `x1 y1 r1` and end circle `cx cy r` =
  /// `x2 y2 r2`).
  Radial(
    x1~ : Double,
    y1~ : Double,
    r1~ : Double,
    x2~ : Double,
    y2~ : Double,
    r2~ : Double
  )
} derive(Eq, Debug, ToJson)

///|
/// A gradient: its colours vary over `shape`, in gradient space, which
/// `transform` maps to the painted path's space. Beyond its ends it keeps
/// its end colours (SVG's `pad`). Stops are in ascending order of offset,
/// the first at 0 and the last at 1. Stops at the same offset change the
/// colour sharply there, the later one taking over at the offset itself;
/// at offset 0 that leaves only the last of them to count, and at offset
/// 1 only the first.
///
/// A gradient whose shape has no extent paints its last stop's colour
/// everywhere (see `Gradient::degenerate_color`).
///
/// Any other gradient is from 0.001 to 1e12 points in size on the page
/// (checked by `PageModel::validate`): a linear one's length from its
/// start to its end; a radial one's largest radius or separation of its
/// centres along either axis of gradient space, measured on the page
/// along whichever axis is the longer there. Renderers draw gradients
/// outside that range wrongly or not at all (Cairo paints nothing for one
/// much below 1e-4 points, Poppler nothing for a linear one about 1e18
/// points long across a page), and the backends do not reshape them to
/// fit. Within it, both backends write a gradient's geometry and stop
/// offsets exactly. Renderers resolve a gradient's parameter to their own
/// precision: Poppler's Splash draws stops 1e-8 apart on a gradient 1e9
/// points long, or a sharp change on one 1e12 points long, where they
/// are; Cairo (and so librsvg) resolves the parameter to about 1/65536,
/// so it merges or moves colour changes nearer than that fraction of a
/// gradient's size.
pub(all) struct Gradient {
  shape : GradientShape
  stops : Array[GradientStop]
  transform : Matrix
} derive(Eq, Debug, ToJson)

///|
/// The one colour a gradient without extent paints, its last stop's: a
/// linear gradient whose ends coincide, a radial one whose end circle has
/// radius 0 (both as SVG specifies), or one whose circles coincide. `None`
/// for any other gradient (or one without stops). PDF's shadings leave
/// such shapes undefined, and renderers disagree about them, so both
/// backends paint the colour, not a shading.
pub fn Gradient::degenerate_color(self : Gradient) -> Color? {
  let degenerate = match self.shape {
    Linear(x1~, y1~, x2~, y2~) => x1 == x2 && y1 == y2
    Radial(x1~, y1~, r1~, x2~, y2~, r2~) =>
      r2 == 0.0 || (x1 == x2 && y1 == y2 && r1 == r2)
  }
  if degenerate && !self.stops.is_empty() {
    Some(self.stops[self.stops.length() - 1].color)
  } else {
    None
  }
}

///|
/// What fills or strokes a path.
pub(all) enum Paint {
  Solid(Color)
  GradientPaint(Gradient)
} derive(Eq, Debug, ToJson)

///|
/// A path painted with an optional fill (by `fill_rule`) and an optional
/// stroke (in `stroke_style`); the fill goes first. A path with neither
/// paints nothing.
pub(all) struct PathItem {
  segments : Array[PathSegment]
  fill : Paint?
  fill_rule : FillRule
  stroke : Paint?
  stroke_style : StrokeStyle
} derive(Eq, Debug, ToJson)

///|
/// How `PaintedText` paints its glyphs (PDF's text rendering modes): filled
/// in the run's colour or not, and outlined in `stroke` or not. An outline
/// is stroked in `stroke_style` as a path would be, wholly: nothing of the
/// stroke style of the paths before it carries over. A run neither filled
/// nor stroked is invisible but still text (it extracts and searches).
pub(all) struct TextPaint {
  fill : Bool
  stroke : Color?
  stroke_style : StrokeStyle
} derive(Eq, Debug, ToJson)

///|
/// One operation of a graphic, in order.
pub(all) enum GraphicOp {
  /// Remember the transformation, clip and alpha, for `Restore`.
  Save
  /// Return to the state of the matching `Save`.
  Restore
  /// Transform the space of the ops that follow: their coordinates are
  /// mapped through the matrix, then through the space before it.
  Transform(Matrix)
  /// Clip the ops that follow to the inside of the path.
  Clip(Array[PathSegment], FillRule)
  /// Constant opacity (0 to 1) of the fills and of the strokes that
  /// follow; text and images count as fills, text outlines as strokes.
  Alpha(Double, Double)
  Path(PathItem)
  /// A glyph run in the graphic's space (its baseline running along the
  /// space's x axis), filled in its colour.
  Text(GlyphRun)
  /// A glyph run painted otherwise: see `TextPaint`.
  PaintedText(GlyphRun, TextPaint)
  /// An image stretched to fill the rectangle at `(x_pt, y_pt)` (its
  /// top-left corner) `w_pt` by `h_pt` in the graphic's space, whatever
  /// its own aspect ratio.
  Image(ImageItem)
} derive(Eq, Debug, ToJson)

///|
/// A vector graphic whose space has its origin at `(x_pt, y_pt)` on the
/// page. Its ops draw in painter's order. Every `Save` has a matching
/// `Restore`, and every number is finite (checked, with the rest of the
/// ops' requirements, by `PageModel::validate`).
///
/// Every number is also within the range a PDF reader is sure to hold in
/// a real, ±3.403e38 (PDF 32000-1, Annex C), and so are the
/// transformations as they compose from the page and where each gradient
/// lands on it (also checked by `validate`, as are the sizes on the page
/// `Gradient` requires). Annex C has more limits a
/// graphic cannot be checked against: reals nearer 0 than about 1.175e-38
/// read as 0, older readers keep only about five significant digits, and
/// integers are sure only up to 2147483647 in magnitude. The PDF backend
/// writes a graphic's numbers exactly (an integral one beyond 2147483647
/// with a decimal point, so that it reads as a real), leaving precision
/// beyond five digits to the readers that keep it.
pub(all) struct GraphicItem {
  x_pt : Double
  y_pt : Double
  ops : Array[GraphicOp]
} derive(Eq, Debug, ToJson)