Commit 47faab2dd2fbc2ef0a1d0ace054ab364acb4e09b

Thomas de Grivel 2022-02-12T10:55:20

wip glyphs

diff --git a/lib/exterm/font.ex b/lib/exterm/font.ex
index bd72ce9..5ac2e4e 100644
--- a/lib/exterm/font.ex
+++ b/lib/exterm/font.ex
@@ -30,7 +30,7 @@ defmodule Exterm.Font do
     if :gl.isList(id) do
       :ok
     else
-      :ok = :gl.newList(id, :gl_const.gl_compile)
+      :ok = :gl.newList(id, :gl_const.gl_compile_and_execute)
       :ok = render_glyph(font, codepoint)
       :ok = :gl.endList()
       :ok
@@ -55,10 +55,13 @@ defmodule Exterm.Font do
     ttf = ttf(font.family)
     if glyph = TTF.glyph(ttf, codepoint) do
       Enum.each(glyph.contours, fn contour ->
-        :gl.begin(:gl_const.gl_line_strip)
-        Enum.each(contour, fn {x1, y1, _, _, _, _, _, _} ->
-          :gl.vertex2f(x1, y1)
+        :gl.begin(:gl_const.gl_line_loop)
+        :gl.color3f(0.0, 0.0, 0.0)
+        Enum.each(TTF.Glyph.contour_segments(contour), fn control_point ->
+          IO.inspect(control_point)
+          :gl.vertex2f(control_point.a.x * 0.01, control_point.a.y * 0.01)
         end)
+        :gl.end()
       end)
     end
   end
diff --git a/lib/ttf.ex b/lib/ttf.ex
index fb80d38..bd43db5 100644
--- a/lib/ttf.ex
+++ b/lib/ttf.ex
@@ -3,6 +3,8 @@ defmodule TTF do
 
   defstruct bounding_box: nil, cmap: [], cmap_subtable_count: nil, data: nil, glyph_count: nil, glyph_locations: nil, header: nil, loca_offset_format: nil, magic: nil, path: nil, table_count: nil, units_per_em: nil
 
+  alias TTF.Glyph
+
   @cmap_platform_id_unicode   0
   @cmap_unicode_2_bmp_only      3
   @cmap_platform_id_apple     1
@@ -75,7 +77,8 @@ defmodule TTF do
 
   def parse_cmap(ttf) do
     cmap = table(ttf, "cmap")
-    <<version::unsigned-big-16, subtable_count::unsigned-big-16, rest::binary>> = cmap
+    <<_version::unsigned-big-16, subtable_count::unsigned-big-16, rest::binary>> = cmap
+    
     %__MODULE__{ttf | cmap_subtable_count: subtable_count, cmap: []}
     |> parse_cmap_subtable(cmap, rest, 0)
   end
@@ -87,15 +90,15 @@ defmodule TTF do
     ttf
     |> parse_cmap_subtable(cmap, rest, i, platform_id, platform_specific_id, offset)
   end
-  def parse_cmap_subtable(ttf, cmap, binary, i, @cmap_platform_id_unicode, @cmap_unicode_2_bmp_only, offset) do
+  def parse_cmap_subtable(ttf, cmap, binary, i, @cmap_platform_id_unicode, @cmap_unicode_2_bmp_only, _offset) do
     ttf
     |> parse_cmap_subtable(cmap, binary, i + 1)
   end
-  def parse_cmap_subtable(ttf, cmap, binary, i, @cmap_platform_id_apple, _, offset) do
+  def parse_cmap_subtable(ttf, cmap, binary, i, @cmap_platform_id_apple, _, _offset) do
     ttf
     |> parse_cmap_subtable(cmap, binary, i + 1)
   end
-  def parse_cmap_subtable(ttf, cmap, binary, i, @cmap_platform_id_microsoft, @cmap_microsoft_unicode_bmp, offset) do
+  def parse_cmap_subtable(ttf, cmap, _binary, i, @cmap_platform_id_microsoft, @cmap_microsoft_unicode_bmp, offset) do
     <<_::binary-size(offset), format::unsigned-big-16, rest::binary>> = cmap
     if format != 4 do
       raise ArgumentError, "invalid cmap subtable format #{format}"
@@ -103,32 +106,32 @@ defmodule TTF do
     table_start_rest_byte_size = byte_size(rest) + 2
     <<subtable_length::unsigned-big-16, _language_code::unsigned-big-16, segment_count::unsigned-big-16, _search_range::unsigned-big-16, _entry_selector::unsigned-big-16, _range_shift::unsigned-big-16, rest::binary>> = rest
     segment_count = trunc(segment_count / 2)
-    {end_codes, rest} = parse_cmap_subtable_array(rest, segment_count)
+    {end_codes, rest} = parse_ub16_array(rest, segment_count)
     <<_::unsigned-16, rest::binary>> = rest
-    {start_codes, rest} = parse_cmap_subtable_array(rest, segment_count)
-    {id_deltas, rest} = parse_cmap_subtable_array(rest, segment_count)
-    {id_range_offsets, rest} = parse_cmap_subtable_array(rest, segment_count)
-    {start_codes, rest} = parse_cmap_subtable_array(rest, segment_count)
+    {start_codes, rest} = parse_ub16_array(rest, segment_count)
+    {id_deltas, rest} = parse_ub16_array(rest, segment_count)
+    id_deltas = id_deltas
+    |> Tuple.to_list()
+    |> Enum.map(&ub16_to_signed/1)
+    |> List.to_tuple()
+    {id_range_offsets, rest} = parse_ub16_array(rest, segment_count)
     glyph_index_array_size = trunc((subtable_length - (table_start_rest_byte_size - byte_size(rest))) / 2)
-    id_deltas = id_deltas |> Tuple.to_list() |> Enum.map(&ub16_to_signed/1) |> List.to_tuple()
-    glyph_indexes = parse_cmap_subtable_array(rest, glyph_index_array_size)
+    glyph_indexes = parse_ub16_array(rest, glyph_index_array_size)
     subtable = %{platform_id: @cmap_platform_id_microsoft, platform_specific_id: @cmap_microsoft_unicode_bmp, segment_count: segment_count, end_codes: end_codes, start_codes: start_codes, id_deltas: id_deltas, id_range_offsets: id_range_offsets, glyph_indexes: glyph_indexes}
     %__MODULE__{ttf | cmap: [subtable | ttf.cmap]}
     |> parse_cmap_subtable(cmap, rest, i + 1)
   end
 
-  def parse_cmap_subtable_array(subtable, size) do
-    parse_cmap_subtable_array(subtable, size, [])
-  end
-  def parse_cmap_subtable_array(rest, 0, acc) do
+  def parse_ub16_array(binary, size), do: parse_ub16_array(binary, size, 0, [])
+  def parse_ub16_array(rest, size, i, acc) when i >= size do
     {acc |> Enum.reverse() |> List.to_tuple(), rest}
   end
-  def parse_cmap_subtable_array(<<ub16::unsigned-big-16, rest::binary>>, size, acc) do
-    parse_cmap_subtable_array(rest, size - 1, [ub16 | acc])
+  def parse_ub16_array(<<first::unsigned-big-16, rest::binary>>, size, i, acc) do
+    parse_ub16_array(rest, size, i + 1, [first | acc])
   end
 
   def ub16_to_signed(ub16) do
-    if (band(0b1000000000000000, ub16) != 0) do
+    if (band(bsl(1, 15), ub16) != 0) do
       - band(0xFFFF, bnot(ub16)) - 1
     else
       ub16
@@ -173,14 +176,10 @@ defmodule TTF do
     ttf
   end
 
-  def parse_glyph(ttf = %{glyphs: glyphs}, index) do
-    ttf
-  end
-
   def codepoint_to_font_index(font, codepoint) do
     codepoint_to_font_index(font, codepoint, 0, hd(font.cmap).segment_count)
   end
-  def codepoint_to_font_index(font, codepoint, i, segment_count) when i >= segment_count do
+  def codepoint_to_font_index(_font, _codepoint, i, segment_count) when i >= segment_count do
     nil
   end
   def codepoint_to_font_index(font, codepoint, i, _segment_count) do
@@ -200,6 +199,11 @@ defmodule TTF do
       nil
     end
   end
+
+  def glyph_location(ttf, index) do
+    elem(ttf.glyph_locations, index)
+  end
+
   def has_glyph?(ttf, codepoint) do
     if codepoint_to_font_index(ttf, codepoint) do
       true
@@ -211,7 +215,14 @@ defmodule TTF do
   def glyph(ttf, codepoint) do
     id = codepoint_to_font_index(ttf, codepoint) || 0
     glyf = table(ttf, "glyf")
-    
-    nil
+    location = glyph_location(ttf, id)
+    <<_::binary-size(location), contour_count::signed-big-16, xmin::unsigned-big-16, ymin::unsigned-big-16, xmax::unsigned-big-16, ymax::unsigned-big-16, rest::binary>> = glyf
+    IO.inspect([contour_count: contour_count, id: id, location: location])
+    glyph = %Glyph{bounding_box: {xmin, ymin, xmax, ymax}, codepoint: codepoint, contour_count: contour_count, ttf: ttf}
+    if (contour_count == -1) do
+      glyph |> Glyph.CompoundContours.parse(rest)
+    else
+      glyph |> Glyph.SimpleContours.parse(rest)
+    end
   end
 end
diff --git a/lib/ttf/control_point.ex b/lib/ttf/control_point.ex
new file mode 100644
index 0000000..d7b8e50
--- /dev/null
+++ b/lib/ttf/control_point.ex
@@ -0,0 +1,5 @@
+defmodule TTF.ControlPoint do
+
+  defstruct x: nil, y: nil, on_curve?: nil
+
+end
diff --git a/lib/ttf/glyph.ex b/lib/ttf/glyph.ex
new file mode 100644
index 0000000..99967ba
--- /dev/null
+++ b/lib/ttf/glyph.ex
@@ -0,0 +1,71 @@
+defmodule TTF.Glyph do
+
+  defstruct bounding_box: nil, codepoint: nil, contour_count: nil, contour_endpoint_indexes: nil, contours: nil, control_points: nil, flags: nil, n_points: nil, name: nil, ttf: nil
+
+  def contour_segments([]) do
+    []
+  end
+  def contour_segments(contour) do
+    length = Enum.count(contour)
+    i = 1
+    stack = nil
+    next = nil
+    start = Enum.at(contour, 0)
+    mid = nil
+    end_ = nil
+    segments = []
+    contour_segments(contour, length, i, stack, next, start, mid, end_, segments)
+  end
+
+  def contour_segments(contour, length, i, stack, next, start, mid, end_, segments) do
+    mid = nil
+    {next, i} = contour_next_point(contour, length, i)
+    if next == nil do
+      mid = stack
+      end_ = Enum.at(contour, 0)
+      contour_segments_body(contour, length, i, stack, next, start, mid, end_, segments)
+    else
+      if next.on_curve? do
+        end_ = next
+        mid = stack
+        stack = nil
+        contour_segments_body(contour, length, i, stack, next, start, mid, end_, segments)
+      else
+        if stack != nil do
+          mid = stack
+          end_ = midpoint(stack, next)
+          stack = next
+          contour_segments_body(contour, length, i, stack, next, start, mid, end_, segments)
+        else
+          stack = next
+          contour_segments(contour, length, i, stack, next, start, mid, end_, segments)
+        end
+      end
+    end
+  end
+
+  defp contour_segments_body(contour, length, i, stack, next, start, mid, end_, segments) do
+    segment = %{a: start, b: mid, c: end_}
+    if next != nil do
+      start = end_
+      contour_segments(contour, length, i, stack, next, start, mid, end_, [segment | segments])
+    else
+      segments |> Enum.reverse()
+    end
+  end
+
+  defp contour_next_point(contour, length, i) do
+    if i < length do
+      {Enum.at(contour, i), i + 1}
+    else
+      {nil, i}
+    end
+  end
+
+  defp midpoint(p0, p1) do
+    %TTF.ControlPoint{x: (p0.x + p1.x) / 2.0,
+                      y: (p0.y + p1.y) / 2.0,
+                      on_curve?: true}
+  end
+
+end
diff --git a/lib/ttf/glyph/compound_contours.ex b/lib/ttf/glyph/compound_contours.ex
new file mode 100644
index 0000000..92da60f
--- /dev/null
+++ b/lib/ttf/glyph/compound_contours.ex
@@ -0,0 +1,6 @@
+defmodule TTF.Glyph.CompoundContours do
+
+  def parse(_glyph, _rest) do
+    raise ArgumentError, "not implemented"
+  end
+end
diff --git a/lib/ttf/glyph/simple_contours.ex b/lib/ttf/glyph/simple_contours.ex
new file mode 100644
index 0000000..36c8101
--- /dev/null
+++ b/lib/ttf/glyph/simple_contours.ex
@@ -0,0 +1,109 @@
+defmodule TTF.Glyph.SimpleContours do
+  use Bitwise
+
+  alias TTF.Glyph
+  alias TTF.ControlPoint
+
+  @on_curve    0x01
+  @x_short     0x02
+  @y_short     0x04
+  @repeat_flag 0x08
+  @x_same      0x10
+  @y_same      0x20
+
+  def parse(glyph, rest) do
+    {contour_endpoint_indexes, rest} = TTF.parse_ub16_array(rest, glyph.contour_count)
+    n_points = 1 + elem(contour_endpoint_indexes, glyph.contour_count - 1)
+    <<instruction_length::unsigned-big-16, _::binary-size(instruction_length), rest::binary>> = rest
+    glyph = %Glyph{glyph | contour_endpoint_indexes: contour_endpoint_indexes, n_points: n_points}
+    {glyph, rest} = flags(glyph, rest)
+    glyph
+    |> points(rest)
+    |> contours()
+  end
+
+  def flags(glyph, rest) do
+    flags(glyph, rest, 0, [])
+  end
+  def flags(glyph = %Glyph{n_points: n_points}, rest, contour, acc) when contour >= n_points do
+    flags = acc |> Enum.reverse() |> List.to_tuple()
+    {%Glyph{glyph | flags: flags}, rest}
+  end
+  def flags(glyph, <<byte, rest::binary>>, contour, acc) do
+    if band(@repeat_flag, byte) do
+      <<repeat_n::unsigned-8, rest::binary>> = rest
+      repeat = 0..(repeat_n - 1)
+      |> Enum.map(fn _ -> byte end)
+      flags(glyph, rest, contour + 1, repeat ++ acc)
+    else
+      flags(glyph, rest, contour + 1, [byte | acc])
+    end
+  end
+
+  def points(glyph, rest) do
+    {x_points, rest} = parse_points(glyph, rest, :x)
+    {y_points, _rest} = parse_points(glyph, rest, :y)
+    glyph
+    |> points(0, x_points, y_points, 0, 0, [])
+  end
+
+  def points(glyph = %Glyph{n_points: n_points}, i, _x_points, _y_points, _x, _y, acc) when i >= n_points do
+    control_points = acc |> Enum.reverse()
+    %Glyph{glyph | control_points: control_points}
+  end
+  def points(glyph = %Glyph{flags: flags}, i, x_points, y_points, x, y, acc) do
+      x_point = elem(x_points, i)
+      y_point = elem(y_points, i)
+      flag = elem(flags, i)
+      x = x + x_point
+      y = y + y_point
+      on_curve? = band(1, flag) != 0
+      acc = [%ControlPoint{x: x, y: y, on_curve?: on_curve?} | acc]
+      points(glyph, i + 1, x_points, y_points, x, y, acc)
+  end
+
+  def contours(glyph) do
+    contours(glyph, 0, [], 0)
+  end
+  def contours(glyph = %Glyph{contour_count: contour_count}, i, acc, _start) when i >= contour_count do
+    contours = acc |> Enum.reverse()
+    %Glyph{glyph | contours: contours}
+  end
+  def contours(glyph, i, acc, start) do
+    end_ = elem(glyph.contour_endpoint_indexes, i)
+    acc = [Enum.slice(glyph.control_points, start..end_) | acc]
+    contours(glyph, i + 1, acc, end_ + 1)
+  end
+
+  def parse_points(glyph, rest, axis) do
+    points = []
+    short_mask = if axis == :x, do: @x_short, else: @y_short
+    same_mask = if axis == :x, do: @x_same, else: @y_same
+    parse_points(glyph, rest, short_mask, same_mask, points, 0)
+  end
+
+  def parse_points(%Glyph{n_points: n_points}, rest, _short_mask, _same_mask, points, i) when i >= n_points do
+    points = points |> Enum.reverse() |> List.to_tuple()
+    {points, rest}
+  end
+  def parse_points(glyph = %Glyph{flags: flags}, rest, short_mask, same_mask, points, i) do
+    flag = elem(flags, i)
+    short? = band(short_mask, flag) != 0
+    same? = band(same_mask, flag) != 0
+    if short? do
+      <<new_point::unsigned-8, rest::binary>> = rest
+      p = if same?, do: new_point, else: -new_point
+      points = [p | points]
+      parse_points(glyph, rest, short_mask, same_mask, points, i + 1)
+    else
+      if same? do
+        points = [0 | points]
+        parse_points(glyph, rest, short_mask, same_mask, points, i + 1)
+      else
+        <<new_point::signed-big-16, rest::binary>> = rest
+        points = [new_point | points]
+        parse_points(glyph, rest, short_mask, same_mask, points, i + 1)
+      end
+    end
+  end
+end
diff --git a/src/gl_const.erl b/src/gl_const.erl
index 82c655a..4415535 100644
--- a/src/gl_const.erl
+++ b/src/gl_const.erl
@@ -5,9 +5,11 @@
 
 gl_color_buffer_bit() -> ?GL_COLOR_BUFFER_BIT.
 gl_compile() -> ?GL_COMPILE.
+gl_compile_and_execute() -> ?GL_COMPILE_AND_EXECUTE.
 gl_depth_buffer_bit() -> ?GL_DEPTH_BUFFER_BIT.
 gl_depth_test() -> ?GL_DEPTH_TEST.
 gl_lequal() -> ?GL_LEQUAL.
+gl_line_loop() -> ?GL_LINE_LOOP.
 gl_line_strip() -> ?GL_LINE_STRIP.
 gl_modelview() -> ?GL_MODELVIEW.
 gl_nicest() -> ?GL_NICEST.