/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Unifont.c                              */
/*                                                 */
/* Purpose: Routines to output ISO10646 characters */
/*          using UTF-8.                           */
/*                                                 */
/* Author : K.J.Bracey                             */
/*                                                 */
/* History: 20-Aug-97: Created.                    */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#include "swis.h"

#include "Unicode/iso10646.h"
#include "Unicode/utf8.h"

#include "event.h"

#include "toolbox.h"

#include "Global.h"
#include "FromROSLib.h"

#include "Utils.h"

#include "Unifont.h"

#define Service_International 0x43
#define Service_International_DefineUCS 7

#ifdef UNIFONT

static UCS4 map[0x40];
static char olddefs[0x40][9];

static char sprarea[300];
static char *sprptr;
static char *maskptr;
static char *bmpptr;
static int factors[4];
static int xoff, yoff;

/*************************************************/
/* unifont_start_redraw()                        */
/*                                               */
/* Sets up the system font ISO10646 renderer at  */
/* the start of a redraw loop. Must be called    */
/* before unifont_write0 is called.              */
/*************************************************/

_kernel_oserror *unifont_start_redraw()
{
  int i;

  for (i = 0xC0; i < 0x100; i++)
      map[i - 0xC0] = 0xFFFFFFFFu;

  return NULL;
}

/*************************************************/
/* unifont_widechar_init()                       */
/*                                               */
/* Prepare all the necessary sprite gubbins for  */
/* the bitmap CJK renderer, and load in the      */
/* font file.                                    */
/*                                               */
/* Assumes: Called once only before the first    */
/*          CJK character is plotted.            */
/*************************************************/

static _kernel_oserror *unifont_widechar_init()
{
  _kernel_oserror *e;

  /*
   * Prepare a small sprite area in which we prepare the 16x16
   * sprite mask to plot
   */
  ((int *)sprarea)[0] = 300;
  ((int *)sprarea)[2] = 16;
  e = _swix(OS_SpriteOp, _INR(0,1), 256+9, sprarea);
  if (e) return e;

  e = _swix(OS_SpriteOp, _INR(0,6), 256+15, sprarea, "sprite",
                                    0, 16, 16, 18);
  if (e) return e;

  e = _swix(OS_SpriteOp, _INR(0,2), 256+29, sprarea, "sprite");
  if (e) return e;

  e = _swix(OS_SpriteOp, _INR(0,2)|_OUT(2), 256+24, sprarea, "sprite", &sprptr);
  if (e) return e;

  maskptr = sprptr + ((int *)sprptr)[9];

  /*
   * Load the big bitmap file (yum).
   */
  bmpptr = malloc(1703996);
  if (!bmpptr) return make_no_memory_error(12);

  ((int *)bmpptr)[0] = 1703996;
  ((int *)bmpptr)[2] = 16;
  e = _swix(OS_SpriteOp, _INR(0,1), 256+9, bmpptr);
  if (e) return e;

  e = _swix(OS_SpriteOp, _INR(0,2), 256+10, bmpptr, "Browse:Widefont");
  if (e) return e;

  e = _swix(OS_SpriteOp, _INR(0,2)|_OUT(2), 256+24, bmpptr, "bmp", &bmpptr);
  if (e) return e;

  bmpptr = bmpptr + ((int *)bmpptr)[8];

  return NULL;
}

/*************************************************/
/* unifont_prepare_widechar()                    */
/*                                               */
/* Set up the transformation matrix etc before   */
/* plotting the first CJK character in each call */
/* to unifont_write0().                          */
/*                                               */
/* Parameters: Width of the system font in       */
/*             pixels, as set with VDU 23,17,7;  */
/*                                               */
/*             Height of the system font in      */
/*             pixels, as set with VDU 23,17,7.  */
/*************************************************/

static _kernel_oserror *unifont_prepare_widechar(int xs, int ys)
{
  static int widechar_initted;
  _kernel_oserror *e;

  if (!widechar_initted)
  {
      e = unifont_widechar_init();
      if (e) return e;
      widechar_initted = 1;
  }

  factors[0] = xs;
  factors[1] = ys;
  factors[2] = 8;
  factors[3] = 16;

  xoff = xs * wimpt_dx() * 2;
  yoff = (ys - 1) * wimpt_dy();

  return NULL;
}

/*************************************************/
/* unifont_plot_widechar()                       */
/*                                               */
/* Plot a CJK character.                         */
/*                                               */
/* Parameters: ISO10646 character code;          */
/*                                               */
/*             Pointer to current x-coordinate;  */
/*                                               */
/*             Pointer to current y-coordinate.  */
/*                                               */
/* Returns:    The coordinates are updated       */
/*             according to the size of the      */
/*             character.                        */
/*                                               */
/* Assumes:    unifont_widechar(c) == 1.         */
/*************************************************/

static _kernel_oserror *unifont_plot_widechar(UCS4 c, int *xp, int *yp)
{
  _kernel_oserror *e;
  int y;
  char *p = bmpptr + (c & 255) * 2 + ((c - 0x3000) >> 8) * 2 * 256 * 16;
  char *s = maskptr;

  for (y = 0; y < 16; y++)
  {
    s[0] = p[0];
    s[1] = p[1];
    s += 4;
    p += 0x200;
  }

  e = _swix(OS_SpriteOp, _INR(0,6), 512+50, sprarea, sprptr,
                  *xp, *yp - yoff, 0, factors);
  if (e) return e;

  *xp += xoff;

  return _swix(OS_Plot, _INR(0,2), 0, xoff, 0);
}

/*************************************************/
/* unifont_write0()                              */
/*                                               */
/* Print a UTF-8 string in the system font,      */
/* using 16x16 bitmaps for CJK characters, and   */
/* a new Service_International reason code to    */
/* find funny characters.                        */
/*                                               */
/* Parameters: Pointer to the 0-terminated       */
/*             string (UTF-8 encoded);           */
/*                                               */
/*             x-coordinate to plot at;          */
/*                                               */
/*             y-coordinate to plot at;          */
/*                                               */
/*             Width of the system font in       */
/*             pixels, as set with VDU 23,17,7;  */
/*                                               */
/*             Height of the system font in      */
/*             pixels, as set with VDU 23,17,7.  */
/*                                               */
/* Assumes:    VDU 23,17,7 already called to set */
/*             character size;                   */
/*                                               */
/*             Graphics background colour set to */
/*             desired text foreground colour(!) */
/*                                               */
/*             The current alphabet has          */
/*             characters 0x20 to 0x7E the same  */
/*             as ASCII/ISO10646;                */
/*                                               */
/*             unifont_start_redraw called at    */
/*             start of this redraw.             */
/*************************************************/

_kernel_oserror *unifont_write0(const char *s, int x, int y, int xs, int ys)
{
  _kernel_oserror *e;
  int n;
  UCS4 code;
  char c;
  int widecharready = 0;
  int xstep = xs * wimpt_dx();

  while (*s != 0 && *s != 10 && *s != 13)
  {
    /* Get the next code from the UTF-8 string */
    s += UTF8_to_UCS4(s, &code);

    /* If it's ASCII, just print that code */
    if (code < 0x7F && code >= 0x20)
    {
      e = _swix(OS_WriteI + code, 0);
      if (e) return e;
      x += xstep;
      continue;
    }

    if (unifont_widechar(code))
    {
        if (!widecharready)
        {
            e = unifont_prepare_widechar(xs, ys);
            if (e) return e;
            widecharready = 1;
        }

        e = unifont_plot_widechar(code, &x, &y);
        if (e) return e;
        continue;
    }

    /* Let c = the character we will print. */
    c = 0xC0 + (code & 0x3F);

    /* Is that character already defined correctly? */
    if (map[c - 0xC0] != code)
    {
      /* No, ask someone to redefine it. First note the previous
       * definition if we haven't touched it yet
       */
      if (map[c - 0xC0] == 0xFFFFFFFFu)
      {
        olddefs[c - 0xC0][0] = c;
        e = _swix(OS_Word, _INR(0,1), 10, olddefs[c - 0xC0]);
        if (e) return e;
      }

      /* Issue the service call asking someone to define the
       * character.
       */
      e = _swix(OS_ServiceCall, _INR(1,4)|_OUT(1),
                                Service_International,
                                Service_International_DefineUCS,
                                c,
                                code,
                                &n);
      if (e) return e;

      /* If no-one defined it, try to define the replacement
       * character here instead.
       */
      if (n)
      {
        e = _swix(OS_ServiceCall, _INR(1,4)|_OUT(1),
                                  Service_International,
                                  Service_International_DefineUCS,
                                  c,
                                  0xFFFD,
                                  &n);
        if (e) return e;

        /* If we can't even get the replacement character, print
         * ? instead. Else, mark this character as having been
         * found (okay, so it actually failed, but it will speed
         * up redraw of text containing a lot of instances of an
         * unknown character.
         */
        if (n)
          c = '?';
        else
          map[c - 0xC0] = code;
      }
      else
        /* Note that we've defined this one for next time */
        map[c - 0xC0] = code;
    }

    /* And print it! */
    e = _swix(OS_WriteI + c, 0);
    if (e) return e;
    x += xstep;
  }

  return NULL;
}

/*************************************************/
/* unifont_end_redraw()                          */
/*                                               */
/* Clear up at the end of a redraw, resetting    */
/* the system font back to what it was.          */
/*************************************************/

_kernel_oserror *unifont_end_redraw()
{
  int i;
  _kernel_oserror *e;

  /* Put back all the character definitions we've screwed around with */
  for (i = 0xC0; i < 0x100; i++)
    if (map[i - 0xC0] != 0xFFFFFFFFu)
    {
      e = _swix(OS_WriteI + 23, 0);
      if (e) return e;

      e = _swix(OS_WriteN, _INR(0,1), olddefs[i - 0xC0], 9);
      if (e) return e;
    }

  return NULL;
}

/*************************************************/
/* unifont_widechar()                            */
/*                                               */
/* Is a character a "wide" one - ie one plotted  */
/* from a special 16x16 bitmap rather than using */
/* the system font.                              */
/*                                               */
/* Parameters: The ISO 10646 character code.     */
/*                                               */
/* Returns:    1 if a wide character, 0 if not.  */
/*************************************************/

int unifont_widechar(UCS4 c)
{
    if (c < 0x3000) return 0;
    if (c < 0xD800) return 1;  /* 3000-D7FF: CJK + Hangul */
    if (c < 0xF900) return 0;
    if (c < 0xFB00) return 1;  /* F900-FAFF: CJK Compat */
    if (c < 0xFE30) return 0;
    if (c < 0xFE70) return 1;  /* FE30-FE70: CJK Compat + Small form variants */
    if (c < 0xFF00) return 0;
    if (c < 0xFF60) return 1;  /* FF00-FF5F: Fullwidth ASCII */
    if (c < 0xFFE0) return 0;
    if (c < 0xFFE8) return 1;  /* FFE0-FFE7: Fullwidth others */
    return 0;
}

#endif