The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "image.h"
#define STRICT
#include <windows.h>

/*
=head1 NAME

win32.c - implements some win32 specific code, specifically Win32 font support.

=head1 SYNOPSIS

   int bbox[6];
   if (i_wf_bbox(facename, size, text, text_len, bbox)) {
     // we have the bbox
   }
   i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa);
   i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa)

=head1 DESCRIPTION

An Imager interface to font output using the Win32 GDI.

=over

=cut
*/

#define fixed(x) ((x).value + ((x).fract) / 65536.0)

static void set_logfont(char *face, int size, LOGFONT *lf);

static LPVOID render_text(char *face, int size, char *text, int length, int aa,
                          HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm);

/*
=item i_wf_bbox(face, size, text, length, bbox)

Calculate a bounding box for the text.

=cut
*/

int i_wf_bbox(char *face, int size, char *text, int length, int *bbox) {
  LOGFONT lf;
  HFONT font, oldFont;
  HDC dc;
  SIZE sz;
  TEXTMETRIC tm;
  ABC first, last;
  GLYPHMETRICS gm;
  int i;
  MAT2 mat;

  set_logfont(face, size, &lf);
  font = CreateFontIndirect(&lf);
  if (!font) 
    return 0;
  dc = GetDC(NULL);
  oldFont = (HFONT)SelectObject(dc, font);

#if 1
  if (!GetTextExtentPoint32(dc, text, length, &sz)
      || !GetTextMetrics(dc, &tm)) {
    SelectObject(dc, oldFont);
    ReleaseDC(NULL, dc);
    DeleteObject(font);
    return 0;
  }
  /* if there's a way to get a characters ascent/descent reliably, I can't
     see it.  GetGlyphOutline() seems to return the same size for
     all characters.
  */
  bbox[1] = bbox[4] = tm.tmDescent;
  bbox[2] = sz.cx;
  bbox[3] = bbox[5] = tm.tmAscent;
  
  if (GetCharABCWidths(dc, text[0], text[0], &first)
      && GetCharABCWidths(dc, text[length-1], text[length-1], &last)) {
    bbox[0] = first.abcA;
    if (last.abcC < 0)
      bbox[2] -= last.abcC;
  }
  else {
    bbox[0] = 0;
  }
#else
  for (i = 0; i < length; ++i) {
    memset(&gm, 0, sizeof(gm));
    memset(&mat, 0, sizeof(mat));
    mat.eM11.value = 1;
    mat.eM22.value = 1;
    if (GetGlyphOutline(dc, GGO_METRICS, text[i], &gm, 0, NULL, &mat) != GDI_ERROR) {
      printf("%02X: black (%d, %d) origin (%d, %d) cell(%d, %d)\n",
	     text[i], gm.gmBlackBoxX, gm.gmBlackBoxY, gm.gmptGlyphOrigin.x, 
	     gm.gmptGlyphOrigin.y, gm.gmCellIncX, gm.gmCellIncY);
      printf("  : mat [ %-8f  %-8f ]\n", fixed(mat.eM11), fixed(mat.eM12));
      printf("        [ %-8f  %-8f ]\n", fixed(mat.eM21), fixed(mat.eM22));
    }
    else {
      printf("Could not get metrics for '\\x%02X'\n", text[i]);
    }
    if (GetCharABCWidths(dc, text[i], text[i], &first)) {
      printf("%02X: %d %d %d\n", text[i], first.abcA, first.abcB, first.abcC);
    }
  }
#endif

  SelectObject(dc, oldFont);
  ReleaseDC(NULL, dc);
  DeleteObject(font);

  return 1;
}

/*
=item i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa)

Draws the text in the given color.

=cut
*/

int
i_wf_text(char *face, i_img *im, int tx, int ty, i_color *cl, int size, 
	  char *text, int len, int align, int aa) {
  unsigned char *bits;
  HBITMAP bm;
  SIZE sz;
  int line_width;
  int x, y;
  int ch;
  TEXTMETRIC tm;
  int top;

  bits = render_text(face, size, text, len, aa, &bm, &sz, &tm);
  if (!bits)
    return 0;
  
  line_width = sz.cx * 3;
  line_width = (line_width + 3) / 4 * 4;
  top = ty;
  if (align)
    top -= tm.tmAscent;

  for (y = 0; y < sz.cy; ++y) {
    for (x = 0; x < sz.cx; ++x) {
      i_color pel;
      int scale = bits[3 * x];
      i_gpix(im, tx+x, top+sz.cy-y-1, &pel);
      for (ch = 0; ch < im->channels; ++ch) {
	pel.channel[ch] = 
	  ((255-scale) * pel.channel[ch] + scale*cl->channel[ch]) / 255.0;
      }
      i_ppix(im, tx+x, top+sz.cy-y-1, &pel);
    }
    bits += line_width;
  }
  DeleteObject(bm);

  return 1;
}

/*
=item i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa)

Draws the text in the given channel.

=cut
*/

int
i_wf_cp(char *face, i_img *im, int tx, int ty, int channel, int size, 
	  char *text, int len, int align, int aa) {
  unsigned char *bits;
  HBITMAP bm;
  SIZE sz;
  int line_width;
  int x, y;
  int ch;
  TEXTMETRIC tm;
  int top;

  bits = render_text(face, size, text, len, aa, &bm, &sz, &tm);
  if (!bits)
    return 0;
  
  line_width = sz.cx * 3;
  line_width = (line_width + 3) / 4 * 4;
  top = ty;
  if (align)
    top -= tm.tmAscent;

  for (y = 0; y < sz.cy; ++y) {
    for (x = 0; x < sz.cx; ++x) {
      i_color pel;
      int scale = bits[3 * x];
      i_gpix(im, tx+x, top+sz.cy-y-1, &pel);
      pel.channel[channel] = scale;
      i_ppix(im, tx+x, top+sz.cy-y-1, &pel);
    }
    bits += line_width;
  }
  DeleteObject(bm);

  return 1;
}

/*
=back

=head1 INTERNAL FUNCTIONS

=over

=item set_logfont(face, size, lf)

Fills in a LOGFONT structure with reasonable defaults.

=cut
*/

static void set_logfont(char *face, int size, LOGFONT *lf) {
  memset(lf, 0, sizeof(LOGFONT));

  lf->lfHeight = -size; /* character height rather than cell height */
  lf->lfCharSet = ANSI_CHARSET;
  lf->lfOutPrecision = OUT_TT_PRECIS;
  lf->lfClipPrecision = CLIP_DEFAULT_PRECIS;
  lf->lfQuality = PROOF_QUALITY;
  strncpy(lf->lfFaceName, face, sizeof(lf->lfFaceName)-1);
  /* NUL terminated by the memset at the top */
}

/*
=item render_text(face, size, text, length, aa, pbm, psz, tm)

renders the text to an in-memory RGB bitmap 

It would be nice to render to greyscale, but Windows doesn't have
native greyscale bitmaps.

=cut
*/
static LPVOID render_text(char *face, int size, char *text, int length, int aa,
		   HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm) {
  BITMAPINFO bmi;
  BITMAPINFOHEADER *bmih = &bmi.bmiHeader;
  HDC dc, bmpDc;
  LOGFONT lf;
  HFONT font, oldFont;
  SIZE sz;
  HBITMAP bm, oldBm;
  LPVOID bits;
  
  dc = GetDC(NULL);
  set_logfont(face, size, &lf);

#ifdef ANTIALIASED_QUALITY
  /* See KB article Q197076
     "INFO: Controlling Anti-aliased Text via the LOGFONT Structure"
  */
  lf.lfQuality = aa ? ANTIALIASED_QUALITY : NONANTIALIASED_QUALITY;
#endif

  bmpDc = CreateCompatibleDC(dc);
  if (bmpDc) {
    font = CreateFontIndirect(&lf);
    if (font) {
      oldFont = SelectObject(bmpDc, font);
      GetTextExtentPoint32(bmpDc, text, length, &sz);
      GetTextMetrics(bmpDc, tm);
      
      memset(&bmi, 0, sizeof(bmi));
      bmih->biSize = sizeof(*bmih);
      bmih->biWidth = sz.cx;
      bmih->biHeight = sz.cy;
      bmih->biPlanes = 1;
      bmih->biBitCount = 24;
      bmih->biCompression = BI_RGB;
      bmih->biSizeImage = 0;
      bmih->biXPelsPerMeter = 72 / 2.54 * 100;
      bmih->biYPelsPerMeter = bmih->biXPelsPerMeter;
      bmih->biClrUsed = 0;
      bmih->biClrImportant = 0;
      
      bm = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);

      if (bm) {
	oldBm = SelectObject(bmpDc, bm);
	SetTextColor(bmpDc, RGB(255, 255, 255));
	SetBkColor(bmpDc, RGB(0, 0, 0));
	TextOut(bmpDc, 0, 0, text, length);
	SelectObject(bmpDc, oldBm);
      }
      else {
	i_push_errorf(0, "Could not create DIB section for render: %ld",
		      GetLastError());
	SelectObject(bmpDc, oldFont);
	DeleteObject(font);
	DeleteDC(bmpDc);
	ReleaseDC(NULL, dc);
	return NULL;
      }
      SelectObject(bmpDc, oldFont);
      DeleteObject(font);
    }
    else {
      i_push_errorf(0, "Could not create logical font: %ld",
		    GetLastError());
      DeleteDC(bmpDc);
      ReleaseDC(NULL, dc);
      return NULL;
    }
    DeleteDC(bmpDc);
  }
  else {
    i_push_errorf(0, "Could not create rendering DC: %ld", GetLastError());
    ReleaseDC(NULL, dc);
    return NULL;
  }

  ReleaseDC(NULL, dc);

  *pbm = bm;
  *psz = sz;

  return bits;
}

/*
=head1 BUGS

Should really use a structure so we can set more attributes.

Should support UTF8

=head1 AUTHOR

Tony Cook <tony@develop-help.com>

=head1 SEE ALSO

Imager(3)

=cut
*/