-- $Date: 2004/02/23 08:35:20 $
-- $Revision: 1.33 $
-- $Author: jcrocholl $

with Messages; use Messages;
with Integer_Strings; use Integer_Strings;
with Real_Strings; use Real_Strings;
with Real_Vectors; use Real_Vectors;
with Gray_Images; use Gray_Images;
with Lines; use Lines;
with Glyphs; use Glyphs;
with Outlines; use Outlines;
with Intersections; use Intersections;
with Intersection_Heaps; use Intersection_Heaps;

package body Render_Glyphs is

   -- One heap of intersection points for each row in the output image.
   type Heap_Array is array(Positive range <>) of Heap;

   -- One pixel row with a higher range to collect anti-alias pixels.
   type Natural_Array is array(Positive range <>) of Natural;

   function To_Pixel
     (Y        : in Real;
      Zoom_Y   : in Real;
      Center_Y : in Positive;
      Offset   : in Real)
     return Real is
   begin
      return Real(Center_Y) + Y * Zoom_Y + Offset;
   end To_Pixel;

   function To_Glyph
     (Y        : in Positive;
      Zoom_Y   : in Real;
      Center_Y : in Positive;
      Offset   : in Real)
     return Real is
   begin
      return (Real(Y - Center_Y) - Offset) / Zoom_Y;
   end To_Glyph;

   procedure Intersect
     (This     : access Glyph-- The glyph to be rendered.
      Zoom_X   : in Real;      -- Zoom_Y * Aspect_Ratio
      Zoom_Y   : in Real;      -- Staff_Height / 4.0
      Center_X : in Positive;  -- 1 + Integer(Zoom_X * Center.X)
      Center_Y : in Positive;  -- 1 + Integer(Zoom_Y * Center.Y)
      Offset   : in Real;      -- Vertical offset for anti-alias.
      Heaps    : in out Heap_Array)
   is
      -- List iteration.
      use Glyphs.Outline_Lists;
      use Outlines.Line_Lists;
      Outlines : Outline_List_Access-- All outlines in the glyph.
      Outline  : Outline_Access;      -- Current outline during iteration.
      Line     : Line_Access;         -- Current line during iteration.
      -- Positions and calculation variables.
      P1, P2  : Vector;  -- Start and end point of the line.
      Y1, Y2  : Integer-- Start and end pixel of line.
      Slope   : Real;    -- Slope of the line.
      Pixel_X : Real;    -- Pixel coordinate of intersection.
      Real_X  : Real;    -- Horizontal coordinate of intersection.
      Real_Y  : Real;    -- Vertical coordinate of intersection.
      -- Direction of the next intersection.
      White_To_Black : Boolean-- Inside or outside of the glyph.
   begin
      -- Debug(Real'Image(Offset));
      Outlines := Get_Outlines(This);
      Reset(Outlines);
      while Next(Outlines) loop
         Outline := Current(Outlines);
         P2 := Last(Outline).To;
         Reset(Outline);
         while Next(Outline) loop
            Line := Current(Outline);
            P1 := P2;
            P2 := Line.To;
            if P1.Y /= P2.Y then
               White_To_Black := P1.Y < P2.Y;
               if White_To_Black then
                  Y1 := Greater(To_Pixel(P1.Y, Zoom_Y, Center_Y, Offset));
                  Y2 := Smaller_Or_Equal(To_Pixel(P2.Y, Zoom_Y, Center_Y, Offset));
               else
                  Y1 := Greater(To_Pixel(P2.Y, Zoom_Y, Center_Y, Offset));
                  Y2 := Smaller_Or_Equal(To_Pixel(P1.Y, Zoom_Y, Center_Y, Offset));
               end if;
               if (P1.X = P2.X) then
                  Pixel_X := To_Pixel(P1.X, Zoom_X, Center_X, 0.0);
                  if Y1 < Heaps'First then Y1 := Heaps'First; end if;
                  if Y2 > Heaps'Last then Y2 := Heaps'Last; end if;
                  for Y in Y1 .. Y2 loop
                     Insert(Heaps(Y), (Pixel_X, White_To_Black));
                  end loop;
               else
                  -- Debug("Y1=" & To_String(Y1) & " .. Y2=" & To_String(Y2));
                  Slope := (P1.Y - P2.Y) / (P1.X - P2.X);
                  if Y1 < 1 then Y1 := 1; end if;
                  for Y in Y1 .. Y2 loop
                     Real_Y := To_Glyph(Y, Zoom_Y, Center_Y, Offset);
                     Real_X := Real(P1.X) + (Real_Y - P1.Y) / Slope;
                     Pixel_X := To_Pixel(Real_X, Zoom_X, Center_X, 0.0);
                     -- Debug("insert Y=" & To_String(Y) &
                     --   " X=" & To_String(Pixel_X) &
                     --   " w2b=" & Boolean'Image(White_To_Black));
                     Insert(Heaps(Y), (Pixel_X, White_To_Black));
                  end loop;
               end if;
            end if;
         end loop;
      end loop;
   end Intersect;

   -- Create a rastered gray image from an array of heaps of
   -- intersections.
   procedure Collect
     (Anti_Alias : in Positive;            -- Factor for vertical anti-alias.
      Heaps      : in out Heap_Array;      -- The glyph's intersections.
      Pixels     : out Gray_Pixel_Array_2-- The resulting gray image.
   is
      Black_Count : Integer-- Number of layers of black.
      Black_Start : Real;    -- Pixel X coordinate where black starts.
      Pixel_Black : Real;    -- Cumulative blackness from 0 to 1.

      use type Gray_Pixel;
      Width     : Positive := Pixels'Length(2);
      Collected : Natural_Array(1 .. Width) := (others => 0);

      -- Fill pixels in this row up to limit L with white.
      procedure Fill_White
        (L : in Real;            -- Limit: black starts here.
         X : in out Positive) is -- Horizontal pixel coordinate.
      begin
         -- Debug("Fill_White");
         if X <= Smaller_Or_Equal(L - 0.5) and X <= Pixels'Last(2) then
            -- Debug(Real'Image(Pixel_Black));
            Collected(X) := Collected(X) +
              Natural(255.0 * (1.0 - Pixel_Black));
            Pixel_Black := 0.0;
            X := X + 1;
         end if;
         while X <= Smaller_Or_Equal(L - 0.5) and X <= Pixels'Last(2) loop
            Collected(X) := Collected(X) + 255;
            X := X + 1;
         end loop;
         Black_Start := L;
      end Fill_White;

      -- Fill pixels in this row up to limit L with black.
      procedure Fill_Black
        (L : in Real;         -- Limit: black stops here.
         X : in out Positive-- Horizontal pixel coordinate.
      is
         Black_Stop : Real-- Pixel X coordinate where black ends.
      begin
         -- Debug("Fill_Black");
         if L <= Real(X) + 0.5 then
            Black_Stop := L;
            -- Debug("0" & Real'Image(Black_Start) & Real'Image(Black_Stop));
            Pixel_Black := Pixel_Black + Black_Stop - Black_Start;
         else
            Black_Stop := Real(X) + 0.5;
            -- Debug("A" & Real'Image(Black_Start) & Real'Image(Black_Stop));
            Pixel_Black := Pixel_Black + Black_Stop - Black_Start;
            if X <= Smaller_Or_Equal(L - 0.5) and X <= Pixels'Last(2) then
               -- Debug(Real'Image(Pixel_Black));
               if Pixel_Black < 1.0 then
                  Collected(X) := Collected(X) +
                    Natural(255.0 * (1.0 - Pixel_Black));
               end if;
               Pixel_Black := 0.0;
               X := X + 1;
            end if;
            while X <= Smaller_Or_Equal(L - 0.5) and X <= Pixels'Last(2) loop
               -- Collected(X) := Collected(X) + 0;
               X := X + 1;
            end loop;
            Black_Start := Real(X) - 0.5;
            Black_Stop := L;
            -- Debug("B" & Real'Image(Black_Start) & Real'Image(Black_Stop));
            Pixel_Black := Pixel_Black + Black_Stop - Black_Start;
         end if;
      end Fill_Black;

      X : Positive;     -- Horizontal pixel coordinate.
      Y : Positive;     -- Vertical pixel coordinate.
      I : Intersection-- Extracted from heap.
   begin
      for Heaps_Y in Heaps'Range loop
         -- Debug("collecting line " & To_String(Heaps_Y));
         -- Debug(Heaps(Heaps_Y));
         Black_Count := 0;
         Pixel_Black := 0.0;
         X := Collected'First;
         while Count(Heaps(Heaps_Y)) /= 0 loop
            I := Extract(Heaps(Heaps_Y));
            if not I.White_To_Black then
               -- Debug2(">" & To_String(Black_Count));
               Black_Count := Black_Count + 1;
               -- Debug2(To_String(Black_Count));
               if Black_Count = 1 then Fill_White(I.X, X); end if;
            else
               if Black_Count = 1 then Fill_Black(I.X, X); end if;
               -- Debug2("<" & To_String(Black_Count));
               Black_Count := Black_Count - 1;
               -- Debug2(To_String(Black_Count));
            end if;
            exit when X > Pixels'Last(2);
         end loop;
         if X <= Pixels'Last(2) then
            Fill_White(Real(Pixels'Last(2) + 2), X);
         end if;
         if Heaps_Y mod Anti_Alias = 0 then
            Y := Heaps_Y / Anti_Alias;
            for X in Collected'Range loop
               Pixels(Y, X) := Gray_Pixel(Collected(X) / Anti_Alias);
               Collected(X) := 0;
            end loop;
         end if;
      end loop;
   end Collect;

   -- Create a raster image of this glyph with the given resolution.
   function Render
     (This         : access Glyph;     -- The glyph to be rendered.
      Staff_Height : in Real := 1.0;   -- Zoom factor.
      Aspect_Ratio : in Real := 1.0;   -- Extra horizontal zoom factor.
      Anti_Alias   : in Positive := 1) -- Anti-alias factor.
     return Glyph_Image_Access         -- The resulting gray image.
   is
      Result : Glyph_Image_Access :=
        Bounds_To_Image(Get_Bounds(This), Staff_Height, Aspect_Ratio);
      Heaps : Heap_Array(1 .. Result.Height * Anti_Alias);
   begin
      -- Debug("h" & To_String(Staff_Height) & " a" & To_String(Anti_Alias));
      -- Debug(To_String(Result.Width) & "x" & To_String(Result.Height));
      -- Debug(To_String(Result.Center.X) & "+" & To_String(Result.Center.Y));
      Intersect(This,
        Staff_Height / 400.0 * Aspect_Ratio,
        Staff_Height / 400.0 * Real(Anti_Alias),
        Integer(Result.Center.X), Integer(Result.Center.Y) * Anti_Alias,
        -Real(Anti_Alias - 1) / 2.0,
        Heaps);
      Collect(Anti_Alias, Heaps, Result.Pixels);
      return Result;
   end Render;

end Render_Glyphs;