2024-11-21 13:08:07

New pages featuring SVG drawing in kc3_httpd

→ Computer Generated Images

The images are generated on the fly by a custom route and controller in kc3_httpd. The xml body is then rendered with content type image/svg+xml.

This is an excerpt of our production code in app/controllers/svg_controller.kc3 :

defmodule SvgController do

  def margin = 10

  def points_on_circle = fn {
    (n, size) { points_on_circle(n, size, n, []) }
    (n, size, 0, acc) { List.reverse(acc) }
    (n, size, i, acc) {
      i = i - 1
      a = 2 * F128.pi * i / n + F128.pi / 2
      p = {(F128.cos(a) + 1) / 2 * size + margin,
           (F128.sin(a) + 1) / 2 * size + margin}
      points_on_circle(n, size, i, [p | acc])
    }
  }
    
  def polygon = fn (request) {
    name = File.name(request.url)
    #puts("SvgController.polygon: name = #{name}")
    if Str.starts_with?(name, "polygon_") do
      name = Str.slice(name, 8, -1)
      if [n, size, "svg"] = Str.split(name, ".") do
        n = (U64) n
        size = (U64) size
        if (n > 1 && size > margin * 2) do
          points = points_on_circle(n, size - margin * 2)
          svg = SvgView.render_polygon(points, size)
          %HTTP.Response{body: svg,
                         headers: [{"Content-Type", "image/svg+xml"}]}
        end
      end
    end
  }

  def points_to_star = fn {
    (points, n, k) { points_to_star(points, n, n - 1 - k, 0, []) }
    (points, n, i, j, acc) {
      acc = [points[i] | acc]
      if (j == n) do
        acc
      else
        i = if i < 2 do n + i - 2 else i - 2 end
        points_to_star(points, n, i, j + 1, acc)
      end
    }
  }

  def star = fn (request) {
    name = File.name(request.url)
    puts("SvgController.star: name = #{name}")
    if Str.starts_with?(name, "star_") do
      name = Str.slice(name, 5, -1)
      if [n, size, "svg"] = Str.split(name, ".") do
        n = (U64) n
        size = (U64) size
        if (n > 1 && size > margin * 2) do
          points = points_on_circle(n, size - margin * 2)
          star_0 = points_to_star(points, n, 0)
          star_1 = if (! (n mod 2)) do
            points_to_star(points, n, 1)
          else
            []
          end
          svg = SvgView.render_star([star_0, star_1], size)
          %HTTP.Response{body: svg,
                         headers: [{"Content-Type", "image/svg+xml"}]}
        end
      end
    end
  }
    
  def complete_graph = fn (request) {
    name = File.name(request.url)
    if Str.starts_with?(name, "complete_graph_") do
      name = Str.slice(name, 15, -1)
      if [n, size, "svg"] = Str.split(name, ".") do
        n = (U64) n
        size = (U64) size
        if (n > 1 && size > margin * 2) do
          points = points_on_circle(n, size - margin * 2)
          svg = SvgView.render_complete_graph(points, size)
          %HTTP.Response{body: svg,
                         headers: [{"Content-Type", "image/svg+xml"}]}
        end
      end
    end
  }

end

To understand better you should learn more Elixir because all the data model of KC3 is taken from Elixir. The main advantages of KC3 is that everything is introspectable through the graph database (triple store), and there is a much more simple and efficient KC3 / C interoperability with shared libraries through dlopen and dlsym.

We return a struct : %HTTP.Response{} which is a KC3 struct compatible with C code. Unfortunately the KC3 <-> C interoperability is not portable and does not work on ARM64 (Apple Sillicon) because the padding and size of structs is different on each C compiler infrastructure. So it will always be a lot of work to follow each ABI on each system.