aboutsummaryrefslogtreecommitdiff
path: root/latex_frcursive/texmf/mf/frcf14.mf
blob: da9cd81d9389181dac12459b188eead00341a494 (plain)
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
%%% This file is part of the French Cursive font definition.
%%% This is a parameter file for the fixed-thickness variant

font_identifier := "FRCF";
font_size 14.4 pt#;

mode_setup;

ex# := 223.20/36 pt#;
med# := .576 pt#;
thin# := .576 pt#;
thick# := .576 pt#;
dot_size# := 1.152 pt#;

base_width := .754;
slant := 0;
accent_wd := 0.7;
link_width := 0.13;
straight_ascend := 2;
loop_ascend := 2.5;
uc_ascend := 2.5;
straight_descend := 1.2;
loop_descend := 1.5;
dot_height := 1.4;
accent_bot := 1.2;
accent_top := 1.6;
cedilla_dp = 0.2;

boolean fixed; fixed := true;
def penstroke text t =
  begingroup
    save e; def e = enddef;
    draw t withpen pencircle scaled med;
  endgroup
enddef;

input frcursive
9'>239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
open StdLabels
open Brr

let backgroundColor = Blog.Nord.nord0

type mode =
  | Edit
  | Selection of int
  | Out

(** Events *)
type canva_events =
  [ `Click of float * float
  | `Out of float * float
  ]

type button_events =
  [ `Delete
  | `Export
  ]
type render_event =
  [
    `Rendering of Layer.Paths.printer
  ]

type worker_event =
  [ `Basic of Jv.t
  | `Complete of (int * (Path.Fixed.path array))
  ]

type events =
  [ canva_events
  | button_events
  | render_event
  | worker_event
  | `Point of float * (float * float)
  | `Width of float
  | `Angle of float
  ]

(*
 The state cant hold functionnal values, and thus cannot be used to store
 elements like timer
 *)
type state =
  { mode : mode
  ; paths : Path.Fixed.t list
  ; current : Path.Path_Builder.t
  ; width : float
  ; angle : float
  ; rendering : Layer.Paths.printer
  }

let insert_or_replace state ((x, y) as p) stamp path =
  let width = state.width
  and angle = state.angle in
  let point = Path.Point.create ~x ~y ~angle ~width ~stamp in
  match Path.Path_Builder.peek path with
  | None ->
    Path.Path_Builder.add_point
      point
      path
  | Some p1 ->
    let open Gg.V2 in

    let p1' = Path.Point.get_coord p1 in

    let dist = (norm (p1' - (of_tuple p))) in
    if dist < 5. then (
      path
    ) else (
      Path.Path_Builder.add_point
        point
        path
    )

let threshold = 20.

let check_selection
  : (float * float) -> Path.Fixed.t list -> (Gg.v2 * Path.Fixed.t) option
  = fun position paths ->
    let point = Gg.V2.of_tuple position in
    (* If the user click on a curve, select it *)
    let _, res = List.fold_left paths
        ~init:(threshold, None)
        ~f:(fun (dist, selection) path ->
            match Path.Fixed.distance point path with
            | Some (point', p) when p < dist ->
              dist, Some (point', path)
            | _ -> dist, selection
          ) in
    res

(** Update the path in the selection with the given function applied to
    every point *)
let update_selection id state f =

  let paths = List.map state.paths
      ~f:(fun path ->
          let id' = Path.Fixed.id path in
          match id = id' with
          | false -> path
          | true -> Path.Fixed.map_point path f
        ) in
  { state with paths }

let do_action
  : Brr_webworkers.Worker.t -> Elements.Timer.t -> events -> state -> state
  = fun worker timer event state ->
    match event, state.mode with
    | `Point (delay, point), Edit ->
      (* Add the point in the list *)
      let current = insert_or_replace
          state
          point
          delay
          state.current in
      { state with current }

    (* Click anywhere while in Out mode, we switch in edition *)
    | `Click ((x, y) as p), Out ->
      Elements.Timer.start timer 0.3;

      let width = state.width
      and angle = state.angle in

      let stamp = 0. in
      let point =
        match check_selection p state.paths with
        | None ->
          (* Start a new path with the point clicked *)
          Path.Point.create ~x ~y ~angle ~width ~stamp
        | Some (p, _) ->
          (* If the point is close to an existing path, we use the closest
             point in the path instead *)
          let x, y = Gg.V2.to_tuple p in
          Path.Point.create ~x ~y ~angle ~width ~stamp
      in

      let current = Path.Path_Builder.add_point
          point
          state.current in
      { state with current; mode = Edit }

    (* Click anywhere while in selection mode, we either select another path,
       or switch to Out mode*)
    | `Click position, (Selection _) ->
      begin match check_selection position state.paths with
        | None ->
          { state with
            mode = Out }
        | Some (_, selected) ->

          (* Start the timer in order to handle the mouse moves *)

          let id = Path.Fixed.id selected in
          Elements.Timer.start timer 0.3;
          { state with
            mode = (Selection id)}
      end

    | `Out point, Edit ->
      let stamp = Elements.Timer.delay timer in
      Elements.Timer.stop timer;
      begin match Path.Path_Builder.peek2 state.current with
        (* If there is at last two points selected, handle this as a curve
            creation. And we add the new point in the current path *)
        | Some _ ->

(*
          let point = match check_selection point state.paths with
            | None -> point
            | Some (p, _) -> Gg.V2.to_tuple p in
*)

          let current = insert_or_replace state point stamp state.current in
          let paths =
            let last = Path.Fixed.to_fixed
                (module Path.Path_Builder)
                current
            in

            let id = Path.Fixed.id last
            and path = Path.Fixed.path last in
            let () = Brr_webworkers.Worker.post worker (`Complete (id, path)) in
            last::state.paths
          and current = Path.Path_Builder.empty in


          { state with
            mode = Out
          ; paths; current }

        (* Else, check if there is a curve undre the cursor, and remove it *)
        | None ->
          let current = Path.Path_Builder.empty in
          begin match check_selection point state.paths with
            | None ->
              { state with
                mode = Out
              ; current
              }
            | Some (_, selected) ->
              let id = Path.Fixed.id selected in
              { state with
                mode = (Selection id)
              ; current }
          end
      end
    | `Delete, Selection id ->
      let paths = List.filter state.paths ~f:(fun p -> Path.Fixed.id p != id) in
      { state with paths ; mode = Out}


    | `Export, _ ->

      let my_host = Uri.host @@ Window.location @@ G.window in
      if (Hashtbl.hash my_host) = Blog.Hash_host.expected_host then (
        (* Convert the path into an sVG element *)
        let svg = Layer.Svg.svg
            ~at:Brr.At.[
                v (Jstr.v "xmlns") (Jstr.v "http://www.w3.org/2000/svg")
              ; v (Jstr.v "xmlns:xlink") (Jstr.v "http://www.w3.org/1999/xlink") ]
            (List.map state.paths
               ~f:(fun path ->

                   Layer.Paths.to_svg
                     ~color:backgroundColor
                     (module Path.Fixed)
                     path
                     state.rendering

                 )) in
        let content = El.prop Elements.Prop.outerHTML svg in

        let btoa = Jv.get Jv.global "btoa" in
        let base64data = Jv.apply btoa
            [| Jv.of_jstr content |] in

        (* Create the link to download the the element, and simulate a click on it *)
        let a = El.a
            ~at:At.[
                href Jstr.( (v "data:image/svg+xml;base64,") + (Jv.Id.of_jv base64data))
              ; v (Jstr.v "download") (Jstr.v "out.svg")
              ]
            [] in
        El.click a
      );
      state

    (* Change the select curve with the appropriate setting *)
    | `Angle angle, Selection s ->
      let state = { state with angle } in
      update_selection s state (fun p -> Path.Point.set_angle p angle)
    | `Width width, Selection s ->
      let state = { state with width } in
      update_selection s state (fun p -> Path.Point.set_width p width)

    | `Angle angle, _ ->
      { state with angle}
    | `Width width, _ ->
      { state with width}

    | `Delete, Out
      -> state

    | `Rendering rendering, _ ->
      { state with rendering}


    | `Basic t, _ ->
      Console.(log [t]);
      state

    | `Complete (id, paths), _ ->
      let paths = List.map state.paths
          ~f:(fun path ->
              let id' = Path.Fixed.id path in
              match id = id' with
              | false -> path
              | true ->
                Path.Fixed.update path paths
            ) in
      { state with paths }


    (* Some non possible cases *)
    | `Out _, Out
    | `Point _, Out
    | `Point _, Selection _
    | `Out _, Selection _
    | `Click _, Edit
    | `Delete, Edit
      -> state

let init =
  { paths = []
  ; current = Path.Path_Builder.empty
  ; mode = Out
  ; angle = 30.
  ; width = 10.
  ; rendering = `Fill
  }