package tiny_appkits

  1. Overview
  2. Docs
Application engines from scratch: a spreadsheet, rich text, paint, draw, CAD, editors and more

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0

doc/src/tiny_appkits.appkit_indexed/Indexed.ml.html

Source file Indexed.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
(* Claude Code
 *
 * Copyright (C) 2026 Yoann Padioleau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * (LGPL) as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *)

(* See Indexed.mli *)

type t = { width : int; height : int; pixels : Bytes.t }

let create w h colour = { width = w; height = h; pixels = Bytes.make (w * h) (Char.chr colour) }
let get (t : t) x y = if x < 0 || y < 0 || x >= t.width || y >= t.height then 0 else Char.code (Bytes.get t.pixels ((y * t.width) + x))

let change (t : t) (f : t -> unit) : t =
  let c = { t with pixels = Bytes.copy t.pixels } in
  f c;
  c

let dot (t : t) x y colour = if x >= 0 && y >= 0 && x < t.width && y < t.height then Bytes.set t.pixels ((y * t.width) + x) (Char.chr colour)

type brush = Dots of (int * int) list | Piece of t * int

let round r = Dots (List.concat (List.init ((2 * r) + 1) (fun j -> List.filter_map (fun i -> let dx = i - r and dy = j - r in if (dx * dx) + (dy * dy) <= (r * r) + r then Some (dx, dy) else None) (List.init ((2 * r) + 1) Fun.id))))
let square n = Dots (List.concat (List.init n (fun j -> List.init n (fun i -> (i - (n / 2), j - (n / 2))))))

let stamp (t : t) (b : brush) colour ((x, y) : int * int) =
  match b with
  | Dots ds -> List.iter (fun (dx, dy) -> dot t (x + dx) (y + dy) colour) ds
  | Piece (p, transparent) ->
      (* its own colours, but the transparent one *)
      for j = 0 to p.height - 1 do
        for i = 0 to p.width - 1 do
          let c = get p i j in
          if c <> transparent then dot t (x + i - (p.width / 2)) (y + j - (p.height / 2)) c
        done
      done

(* Bresenham's: the error of the ideal line kept, a step along the
   longer axis each time, a step along the other when the error says *)
let line_dots ((x0, y0) : int * int) ((x1, y1) : int * int) : (int * int) list =
  let dx = abs (x1 - x0) and dy = -abs (y1 - y0) in
  let sx = if x0 < x1 then 1 else -1 and sy = if y0 < y1 then 1 else -1 in
  let rec go x y err acc =
    let acc = (x, y) :: acc in
    if x = x1 && y = y1 then List.rev acc
    else
      let e2 = 2 * err in
      let x, err = if e2 >= dy then (x + sx, err + dy) else (x, err) in
      let y, err = if e2 <= dx then (y + sy, err + dx) else (y, err) in
      go x y err acc
  in
  go x0 y0 (dx + dy) []

let line t b colour a z = List.iter (stamp t b colour) (line_dots a z)

let order (x0, y0) (x1, y1) = (min x0 x1, min y0 y1, max x0 x1, max y0 y1)

let fill_rect t colour a z =
  let l, top, r, b = order a z in
  for y = top to b do
    for x = l to r do
      dot t x y colour
    done
  done

let frame_rect t colour a z =
  let l, top, r, b = order a z in
  line t (Dots [ (0, 0) ]) colour (l, top) (r, top);
  line t (Dots [ (0, 0) ]) colour (l, b) (r, b);
  line t (Dots [ (0, 0) ]) colour (l, top) (l, b);
  line t (Dots [ (0, 0) ]) colour (r, top) (r, b)

(* the dots of the ellipse inscribed in the rectangle: a row at a time,
   from its half-width at that height *)
let ellipse_rows a z : (int * int * int) list =
  let l, top, r, b = order a z in
  let cx = float_of_int (l + r) /. 2. and cy = float_of_int (top + b) /. 2. in
  let rx = Float.max 0.5 (float_of_int (r - l) /. 2.) and ry = Float.max 0.5 (float_of_int (b - top) /. 2.) in
  List.init (b - top + 1) (fun j ->
      let y = top + j in
      let dy = (float_of_int y -. cy) /. ry in
      let half = rx *. sqrt (Float.max 0. (1. -. (dy *. dy))) in
      (y, int_of_float (Float.round (cx -. half)), int_of_float (Float.round (cx +. half))))

let fill_ellipse t colour a z = List.iter (fun (y, x0, x1) -> for x = x0 to x1 do dot t x y colour done) (ellipse_rows a z)

let frame_ellipse t colour a z =
  (* each row's ends, joined to the row before's so the outline has no gaps *)
  let rows = ellipse_rows a z in
  List.iteri
    (fun i (y, x0, x1) ->
      match List.nth_opt rows (i - 1) with
      | Some (py, px0, px1) ->
          line t (Dots [ (0, 0) ]) colour (px0, py) (x0, y);
          line t (Dots [ (0, 0) ]) colour (px1, py) (x1, y)
      | None -> for x = x0 to x1 do dot t x y colour done)
    rows;
  match List.rev rows with (y, x0, x1) :: _ -> for x = x0 to x1 do dot t x y colour done | [] -> ()

let fill (t : t) colour ((x, y) : int * int) =
  let target = get t x y in
  if target <> colour && x >= 0 && y >= 0 && x < t.width && y < t.height then begin
    let stack = ref [ (x, y) ] in
    while !stack <> [] do
      match !stack with
      | (x, y) :: rest ->
          stack := rest;
          if x >= 0 && y >= 0 && x < t.width && y < t.height && get t x y = target then begin
            dot t x y colour;
            stack := (x + 1, y) :: (x - 1, y) :: (x, y + 1) :: (x, y - 1) :: !stack
          end
      | [] -> ()
    done
  end

let cut (t : t) a z =
  let l, top, r, b = order a z in
  let w = r - l + 1 and h = b - top + 1 in
  let p = create w h 0 in
  for j = 0 to h - 1 do
    for i = 0 to w - 1 do
      dot p i j (get t (l + i) (top + j))
    done
  done;
  p

let symmetric ~(order : int) ~(centre : int * int) ((x, y) : int * int) : (int * int) list =
  let cx, cy = centre in
  List.init order (fun k ->
      let a = 2. *. Float.pi *. float_of_int k /. float_of_int order in
      let dx = float_of_int (x - cx) and dy = float_of_int (y - cy) in
      (cx + int_of_float (Float.round ((dx *. cos a) -. (dy *. sin a))), cy + int_of_float (Float.round ((dx *. sin a) +. (dy *. cos a)))))