/**************************************************************************\
*  display.c -- module to support the main MDI child windows.
*
*         Steve Firebaugh
*         Microsoft Developer Support
*         Copyright (c) 1992 Microsoft Corporation
*
* window class registered in uniput.c
* windows are created in main frame window procedure.
*
* design:  We have one window procedure here for potentially multiple
*  MDI child windows.  The nCharPerLine, SqrHeight, and SqrWidth are
*  stored on a per window basis.  Also, the window class is registered
*  with the style CS_OWNDC.  For this reason, it is possible to select
*  a logical font into the HDC, and it will be there each time you get
*  the DC new for the window.  In this way we have a log font for each
*  window without actually doing any work in this module.
*
*  Some of the data is global, and works for all of the windows.  The
*  startcount & endcount arrays are a good example.  These are computed
*  once when the font is created, but the values remain the same for each
*  window.  Thus we have the following assumption:
*
*   1.  No fonts will be installed or removed while program is running.
*       (CountUCSegments values remain valid for run time duration).
*
*
*  There are WM_USER+ messages to set values for this window, and to
*  notify it of global changes.  Looking at the create time, user message
*  stream is helpful.  On the WM_CREATE message, we send ourselves a
*  WMU_NEWFONT message to create a logical font and count the number of
*  character ranges (segments).  Before returning from this message, we
*  send ourselves a WMU_NEWRANGE message to set the title, and the
*  horizontal scroll bar correctly.
*
*
\**************************************************************************/
#define UNICODE

#include <windows.h>
#include "uniput.h"


/* global variables store the start and end codepoints for UC ranges. */
#define MAXSEGMENTS  100
USHORT endCount[MAXSEGMENTS];
USHORT startCount[MAXSEGMENTS];

int CountUCSegments (HDC);
/* error return value from CountUCSegments */
#define SEGMENTERROR -1


/* conversion between (x,y) pairs and rectangle index */
int transXYtoIndex (int, int, int, int, int);
int transIndextoRect (int, PRECT, int, int, int);


/* window extra bytes for use to store window specific data (see register class) */
#define GWLU_NCHAR        0
#define GWLU_SQRHEIGHT    4
#define GWLU_SQRWIDTH     8



/* structure for the character range 'name' lookup table.  */
typedef struct tagLookupEntry{
    USHORT   start;
    USHORT   end;
    TCHAR    String[32];
} LookupEntry;

/* The following data comes straight out of the Addison-Wesley Unicode book. */
#define NRANGE 24
LookupEntry  RangeName[NRANGE] =
    {{ 0x0000,0x007f, TEXT("ASCII")},
     { 0x0080,0x00ff, TEXT("Latin1 Characters")},
     { 0x0100,0x017f, TEXT("European Latin")},
     { 0x0180,0x01ff, TEXT("Extended Latin")},
     { 0x0200,0x024f, TEXT("<Bad unicode range.>")},
     { 0x0250,0x02af, TEXT("Standard Phonetic")},
     { 0x02b0,0x02ff, TEXT("Modifier Letters")},
     { 0x0300,0x036f, TEXT("Generic Diacritical Marks")},
     { 0x0370,0x03ff, TEXT("Greek")},
     { 0x0400,0x04ff, TEXT("Cyrillic")},
     { 0x0500,0x052f, TEXT("<Bad unicode range.>")},
     { 0x0530,0x058f, TEXT("Armenian")},
     { 0x0590,0x05ff, TEXT("Hebrew")},
     { 0x0600,0x06ff, TEXT("Arabic")},
     { 0x0900,0x1fff, TEXT("<not specified in table.>")},
     { 0x2000,0x206f, TEXT("General Punctutation")},
     { 0x2070,0x209f, TEXT("Superscipts & Subscripts")},
     { 0x20a0,0x20cf, TEXT("Currency Symbols")},
     { 0x20d0,0x20ff, TEXT("Diacritical Marks for Symbols")},
     { 0x2100,0x214f, TEXT("Letterlike Symbols")},
     { 0x2150,0x218f, TEXT("Number Forms")},
     { 0x2190,0x21ff, TEXT("Arrows")},
     { 0x2200,0x22ff, TEXT("Mathematical Operators")},
     { 0x2300,0xffff, TEXT("<not specified in table.>")}};



/* Global logfont.  Used for CreateFontIndirect().
 *  also regerenced in the uniput.c file.
 */
LOGFONT logfont = {
      UCFONTHEIGHT ,
      UCFONTWIDTH ,
       0 ,
       0 ,
     400 ,
       0 ,
       0 ,
       0 ,
       UNICODE_CHARSET ,
       0 ,
       0 ,
       2 ,
       2 ,
      TEXT("Lucida Sans Unicode")};





/**************************************************************************\
*
*  function:  DisplayWndProc()
*
*  input parameters:  normal window procedure parameters.
*
\**************************************************************************/
LRESULT CALLBACK DisplayWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HFONT  hfont, hfontOld;

static HDC hdc;
static RECT rect;
static int iSeg;
static TCHAR buffer[100];

/* rDown and index used as the user points to a square. */
static RECT rDown;
static int index;


  switch (message) {

    /**********************************************************************\
    *  WM_CREATE
    *
    * Create font.
    \**********************************************************************/
    case WM_CREATE:
      SendMessage (hwnd, WMU_SETNCHAR, 16, 0);
      SendMessage (hwnd, WMU_NEWFONT, (WPARAM) logfont.lfWidth, (LPARAM) logfont.lfHeight);
    break;


    /**********************************************************************\
    *  WM_DESTROY
    *
    * Get rid of the logical font we create for each display window.
    *  relying here on CS_ONWDC
    \**********************************************************************/
    case WM_DESTROY:
      hdc = GetDC (hwnd);
      hfont = SelectObject  (hdc, GetStockObject(SYSTEM_FONT));
      ReleaseDC (hwnd, hdc);
      DeleteObject (hfont);

    break;





    /**********************************************************************\
    *  WMU_SETNCHAR
    *
    * wParam - nCharPerLine
    *
    \**********************************************************************/
    case WMU_SETNCHAR:
      SetWindowLong (hwnd, GWLU_NCHAR, (int) wParam);
      InvalidateRect (hwnd, NULL, TRUE);
    break;

    /**********************************************************************\
    *  WMU_NEWFONT
    *
    * wParam - Width
    * lParam - Height
    *
    * Create font, select it into the HDC, and reset ranges.
    \**********************************************************************/
    case WMU_NEWFONT: {
      int nSegments;

      hdc = GetDC(hwnd);

      logfont.lfHeight = (int) lParam;
      logfont.lfWidth  = (int) wParam;

      hfont = CreateFontIndirect (&logfont);
      hfontOld = SelectObject (hdc, hfont);
      DeleteObject (hfontOld);

      nSegments = CountUCSegments (hdc); /* slow computation */
      ReleaseDC (hwnd, hdc);


      /* verify that we have SOME ranges to work with */
      if (nSegments == SEGMENTERROR) return FALSE;

      /* warn the user if we can't find the right font */
      if (nSegments < 30)
        MessageBox (hwnd,
            TEXT("Working w/ limited codepoint coverage."),
            TEXT("Lucida Sans Unicode font not found."), MB_OK);



      SetScrollRange (hwnd, SB_HORZ, 0, (nSegments-1), TRUE);
      SetScrollPos (hwnd, SB_HORZ, 0, TRUE);

      SendMessage (hwnd, WMU_NEWRANGE, 0,0);

    } return TRUE;


    /**********************************************************************\
    *  WMU_NEWRANGE
    *
    *
    * Range changes, or font size changes, or title status changes.
    \**********************************************************************/
    case WMU_NEWRANGE: {
      int sqrHeight, sqrWidth;
      TEXTMETRIC tm;
      int i;

      hdc = GetDC(hwnd);
      GetTextMetrics (hdc, &tm);
      ReleaseDC (hwnd, hdc);

      /* index into the start, end arrays stored in scroll bar */
      iSeg = GetScrollPos (hwnd, SB_HORZ);

      /* Set the window title text to show display range. */
      wsprintf (buffer, TEXT("[%04x, %04x]"),
                                  (int)startCount[iSeg],
                                  (int)endCount[iSeg]);

      /* if we are supposed to look up the character range name,
       *  then step through the ranges stored in the RangeName
       *  table.  Find the correct one, and reset the buffer string.
       */
      if (gShowNames) {
        for (i = 0; i< NRANGE; i++) {
          if ((RangeName[i].start <= startCount[iSeg]) &&
              ( startCount[iSeg] <= RangeName[i].end))

            wsprintf (buffer, TEXT("%s [%04x, %04x]"),
                                  RangeName[i].String,
                                  (int)startCount[iSeg],
                                  (int)endCount[iSeg]);

        }
      }
      SetWindowText (hwnd, buffer);

      sqrHeight =  tm.tmHeight + tm.tmExternalLeading;
      sqrWidth = tm.tmMaxCharWidth;

      SetWindowLong (hwnd, GWLU_SQRHEIGHT,sqrHeight);
      SetWindowLong (hwnd, GWLU_SQRWIDTH, sqrWidth );

    } return TRUE;









    /**********************************************************************\
    \**********************************************************************/
    case WM_LBUTTONDOWN: {
      int x,y;
      int nCharPerLine;
      int sqrHeight, sqrWidth;
      nCharPerLine = GetWindowLong (hwnd, GWLU_NCHAR);
      sqrHeight    = GetWindowLong (hwnd, GWLU_SQRHEIGHT);
      sqrWidth     = GetWindowLong (hwnd, GWLU_SQRWIDTH );



      x = (int)LOWORD (lParam);
      y = (int)HIWORD (lParam);

      index = transXYtoIndex (x,y, sqrWidth, sqrHeight, nCharPerLine);

      /* verify that the index is within the shown segment range */
      iSeg= GetScrollPos (hwnd, SB_HORZ);
      if (index > (endCount[iSeg]- startCount[iSeg])) return NULL;


      transIndextoRect (index, &rDown, sqrWidth, sqrHeight, nCharPerLine);

      hdc = GetDC (hwnd);
      InvertRect (hdc, &rDown);
      ReleaseDC (hwnd, hdc);

      GdiFlush ();
      SetCapture (hwnd);
    } break;

    /**********************************************************************\
    *
    * rDown & index set in WM_LBUTTONDOWN
    *
    \**********************************************************************/
    case WM_LBUTTONUP: {
      POINT p;


      if (GetCapture() == hwnd) {

        p.x = (int)LOWORD (lParam);
        p.y = (int)HIWORD (lParam);

        if (PtInRect (&rDown, p)) {
          iSeg= GetScrollPos (hwnd, SB_HORZ);
          index += startCount[iSeg];
          Beep (40,40);
          SendMessage (hwndMain, WMU_CHARACTER, 0, (LPARAM) index);
        }

        hdc = GetDC (hwnd);
        InvertRect (hdc, &rDown);
        ReleaseDC (hwnd, hdc);
        GdiFlush ();
        ReleaseCapture ();

      }
    } break;






    /**********************************************************************\
    *  WM_HSCROLL
    *
    * Step through the character ranges.
    * In every case, inform the window the range has changed,
    *  and invalidate it to force a repaint.
    \**********************************************************************/
    case WM_HSCROLL:

      switch (LOWORD(wParam)){
        int OldPos, NewPos;

        case SB_PAGEDOWN:
        case SB_LINEDOWN:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          SetScrollPos (hwnd, SB_HORZ, (OldPos+1), TRUE);
          SendMessage (hwnd,WMU_NEWRANGE, 0,0);
          InvalidateRect (hwnd, NULL, TRUE);
        break;

        case SB_PAGEUP:
        case SB_LINEUP:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          SetScrollPos (hwnd, SB_HORZ, (OldPos-1), TRUE);
          SendMessage (hwnd,WMU_NEWRANGE, 0,0);
          InvalidateRect (hwnd, NULL, TRUE);
        break;

        case SB_THUMBPOSITION:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          NewPos = HIWORD (wParam);
          SetScrollPos (hwnd, SB_HORZ, NewPos, TRUE);
          SendMessage (hwnd,WMU_NEWRANGE, 0,0);
          InvalidateRect (hwnd, NULL, TRUE);
        break;

      }

    break;







    /**********************************************************************\
    *  WM_SIZE
    *
    \**********************************************************************/
    case WM_SIZE:
      /* make sure that scroll metrics are ok */
      SendMessage (hwnd, WMU_NEWRANGE, 0,0);

    break;  /* fall through to MDIChildProc */


    /**********************************************************************\
    *  WM_PAINT
    *
    \**********************************************************************/
    case WM_PAINT: {
      HDC hdc;
      PAINTSTRUCT ps;
      RECT rect;
      POINT  point;
      int i;
      USHORT start, end;
      WCHAR outChar;
      USHORT codepoint;
      int nCharPerLine;
      int sqrHeight, sqrWidth;
      nCharPerLine = GetWindowLong (hwnd, GWLU_NCHAR);
      sqrHeight    = GetWindowLong (hwnd, GWLU_SQRHEIGHT);
      sqrWidth     = GetWindowLong (hwnd, GWLU_SQRWIDTH );


      hdc = BeginPaint(hwnd, &ps);
      SetBkMode (hdc, TRANSPARENT);

      iSeg= GetScrollPos (hwnd, SB_HORZ);
      start = startCount[iSeg];
      end = endCount[iSeg];

      /* ensure that we are in a valid range... some fonts have problems */
      if (start != 0xffff)

        for (codepoint = start, i=0; codepoint<=end; codepoint++,i++) {

          /* paint box and frame it */
          transIndextoRect (i, &rect, sqrWidth, sqrHeight, nCharPerLine);
          InflateRect (&rect, -1, -1);
          FillRect (hdc, &rect, GetStockObject (LTGRAY_BRUSH));
          InflateRect (&rect, 1, 1);
          FrameRect (hdc, &rect, GetStockObject (GRAY_BRUSH));
          InflateRect (&rect, -1, -1);
          SelectObject (hdc, GetStockObject (WHITE_PEN));
          MoveToEx (hdc, rect.right, rect.top, NULL);
          LineTo (hdc,rect.left, rect.top);
          LineTo (hdc,rect.left, rect.bottom);



          /* set point that we will draw glyph at */
          point.x = (rect.right + rect.left)/2;
          point.y = rect.top;
          SetTextAlign (hdc, TA_CENTER | TA_TOP);
          SetTextColor (hdc, PALETTEINDEX (0));


          /* special case the non-spacing diacritic marks. U+0300 -> U+036F
           *  Write a space first, for them to 'modify.'
           */
          if ( (0x0300 <= codepoint) && (codepoint <= 0x036F) ) {
            outChar = (WCHAR) 0x0020;
            TextOutW (hdc, 0,0, &outChar, 1);
          }

          outChar = (WCHAR) codepoint;
          ExtTextOut(hdc, point.x, point.y, ETO_CLIPPED, &rect, (LPCTSTR)&outChar, 1, NULL);



          /* fill in unicode code point in hex */
          if (gShowhex) {
            int nchar;
            HANDLE hfonttemp;

            nchar = wsprintf (buffer, TEXT("%04x"), (int) codepoint);
            hfonttemp = SelectObject (hdc,GetStockObject (SYSTEM_FIXED_FONT));

            point.y = rect.bottom;
            SetTextAlign (hdc, TA_CENTER | TA_BOTTOM);
            SetTextColor (hdc, PALETTEINDEX (5));

            TextOut( hdc, point.x, point.y,
                          buffer, nchar);
            SelectObject (hdc,hfonttemp);
          }


        } /* end for */

      EndPaint (hwnd, &ps);
    } return FALSE; /* end WM_PAINT */




  } /* end switch */

  return (DefMDIChildProc(hwnd, message, wParam, lParam));
}




/**************************************************************************\
*
* Need a mapping between the (x,y) pair, and the index of the square on
*  the window.  Two routines provide this... one for each direction.
*
\**************************************************************************/

/**********************************************************************\
*  transXYtoIndex
*
*  Given x & y values, return the index.
*
\**********************************************************************/
int transXYtoIndex (int x, int y,
     int sqrWidth, int sqrHeight, int nCharPerLine)
{
    x /= sqrWidth;
    if (x < 0) x = 0;
    if (x >= nCharPerLine) x = nCharPerLine-1;

    y /= sqrHeight;
    if (y < 0) y = 0;

    return ( (y * nCharPerLine) + x);
}


/**********************************************************************\
*  transIndextoRect
*
*  Given an index, i.e. the number of one of the squares on the display
*   window, fill in the rectangle structure.
*
\**********************************************************************/
int transIndextoRect (int index, PRECT pr,
     int sqrWidth, int sqrHeight, int nCharPerLine)
{
int x,y;

    x = index % nCharPerLine;
    y = index / nCharPerLine;

    pr->left = x*sqrWidth;
    pr->top = y*sqrHeight;
    pr->right = pr->left +sqrWidth;
    pr->bottom = pr->top +sqrHeight;

    return TRUE;
}






/**************************************************************************\
*
* All of the code below is used for parsing 'font data.'  It will only
*  make sense if you have a copy of the True Type font spec.  In short,
*  we grab the 'cmap' table, look through it for a subtable, and then
*  get two parallel arrays from the subtable.  Complications arise because
*  the true type data is 'BIG ENDIAN' and NT is being run 'little endian.'
*  For this reason, once we locate the short or long, we call Swap* to
*  change the byte ordering.
*
\**************************************************************************/


VOID SwapShort (PUSHORT);
VOID SwapULong  (PULONG);



typedef struct tagTABLE{
    USHORT platformID;
    USHORT encodingID;
    ULONG  offset;
} TABLE, *PTABLE;

typedef struct tagSUBTABLE{
    USHORT format;
    USHORT length;
    USHORT version;
    USHORT segCountX2;
    USHORT searchRange;
    USHORT entrySelector;
    USHORT rangeShift;
} SUBTABLE, *PSUBTABLE;


/* 'cmap' is passed in by value in a DWORD */
#define CMAPHEX  0x636d6170
#define NBYTES   256
#define OFFSETERROR  0



/**************************************************************************\
*
*  function:  CountUCSegments()
*
*  input parameters:
*   hdc - with the logical font set into it.
*   prect - pointer to client rectangle.
*
*  Global variables:
*   startCount
*   endCount
*
*  returns:
*   number of UC segments or
*   SEGMENTERROR if there is some kind of error.
*
*  essential side effect:
*   Fills in global startCount, endCount arrays.
\**************************************************************************/
int CountUCSegments (HDC hdc)
{
DWORD       cbData;
USHORT      aShort[2];
DWORD       nBytes;
USHORT      i, nTables;
PTABLE      pTable;
PSUBTABLE   pSubTable;
ULONG       offset,offsetFormat4;
USHORT      segCount;
BYTE        buffer[NBYTES];




    /* find number of 'subtables', second long in cmap */
    nBytes=GetFontData (hdc, CMAPHEX, 0, aShort, 4);
    if (nBytes == (DWORD)-1) {
      MessageBox (NULL, MBGETFONTDATAERR,MBERROR , MBERRORFLAGS);
      return SEGMENTERROR;
    }
    nTables = aShort[1];
    SwapShort (&nTables);


    cbData = nTables * sizeof(TABLE);
    if (cbData >NBYTES) {
      MessageBox (NULL, TEXT("cbData >NBYTES"),MBERROR , MBERRORFLAGS);
      return SEGMENTERROR;
    }

    /* get array of subtables information.  Check each one for 3,1*/
    nBytes=GetFontData (hdc, CMAPHEX, 4, buffer, cbData);
    pTable = (PTABLE)buffer;
    offsetFormat4 = OFFSETERROR;
    for (i = 0; i< nTables; i++) {

        SwapShort (&(pTable->encodingID));
        SwapShort (&(pTable->platformID));

        if ((pTable->platformID == 3)&&(pTable->encodingID == 1)) {
          offsetFormat4 = pTable->offset;
          SwapULong (&offsetFormat4);
          break;
        }
        pTable++;
    }

    /* verify that we got the correct offset to the FORMAT 4 subtable */
    if (offsetFormat4 == OFFSETERROR){
      MessageBox (NULL, TEXT("Can not find 3,1 subtable"),MBERROR , MBERRORFLAGS);
      return SEGMENTERROR;
    }

    /* Get the beginning of the subtable, especially the segment count */
    nBytes=GetFontData (hdc, CMAPHEX, offsetFormat4, buffer, sizeof(SUBTABLE));
    pSubTable = (PSUBTABLE) buffer;
    SwapShort (&(pSubTable->format));
    SwapShort (&(pSubTable->segCountX2));

    if (pSubTable->format != 4){
      MessageBox (NULL, TEXT("format !=4"), MBERROR, MBERRORFLAGS);
      return SEGMENTERROR;
    }

    segCount = pSubTable->segCountX2 / 2;

    if (segCount > MAXSEGMENTS){
      MessageBox (NULL, TEXT("segCount > MAXSEGMENTS"), MBERROR, MBERRORFLAGS);
      return SEGMENTERROR;
    }

    /* read in the array of endCount values */
    offset = offsetFormat4
           + (7 * sizeof (USHORT));  /* skip constant # bytes in subtable */
    cbData = segCount * sizeof (USHORT);
    nBytes=GetFontData (hdc, CMAPHEX, offset, endCount, cbData );
    for (i = 0; i<segCount; i++)
        SwapShort (& (endCount[i]));

    /* read in the array of startCount values */
    offset = offsetFormat4
           + (7 * sizeof (USHORT))   /* skip constant # bytes in subtable */
           + (segCount * sizeof (USHORT)) /* skip endCount array */
           + sizeof (USHORT);             /* skip reservedPad */
    cbData = segCount * sizeof (USHORT);
    nBytes=GetFontData (hdc, CMAPHEX, offset, startCount, cbData );
    for (i = 0; i<segCount; i++)
        SwapShort (& (startCount[i]));


    return segCount;
}










VOID SwapShort (PUSHORT p)
{
SHORT temp;

    temp =(SHORT)( HIBYTE (*p) + (LOBYTE(*p) << 8));
    *p = temp;
}



VOID SwapULong (PULONG p)
{
ULONG temp;

    temp = (LONG) ((BYTE) *p);
    temp <<= 8;
    *p >>=8;

    temp += (LONG) ((BYTE) *p);
    temp <<= 8;
    *p >>=8;

    temp += (LONG) ((BYTE) *p);
    temp <<= 8;
    *p >>=8;

    temp += (LONG) ((BYTE) *p);
    *p = temp;
}
