The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* Pango
 * OpenGL Freetype2 handling
 *
 * Copyright (C) 1999 Red Hat Software
 * Copyright (C) 2000 Tor Lillqvist
 * Copyright (C) 2006 Marc Lehmann <pcg@goof.com>
 *
 * This file is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#define PANGO_ENABLE_BACKEND

#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <glib.h>
#include <glib/gprintf.h>

#include "pangoopengl.h"

#include "pango/pangofc-font.h"
#include "pango/pangofc-fontmap.h"

#define PANGO_OPENGL_FONT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO_TYPE_OPENGL_FONT, PangoOpenGLFontClass))
#define PANGO_OPENGL_IS_FONT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO_TYPE_OPENGL_FONT))
#define PANGO_OPENGL_FONT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO_TYPE_OPENGL_FONT, PangoOpenGLFontClass))

typedef struct _PangoOpenGLFontClass   PangoOpenGLFontClass;

struct _PangoOpenGLFontClass
{
  PangoFcFontClass parent_class;
};

static void pango_opengl_font_finalize (GObject *object);

static void pango_opengl_font_get_glyph_extents (PangoFont *font,
		           	                 PangoGlyph glyph,
		           	                 PangoRectangle *ink_rect,
		           			 PangoRectangle *logical_rect);

static FT_Face pango_opengl_font_real_lock_face   (PangoFcFont      *font);
static void    pango_opengl_font_real_unlock_face (PangoFcFont      *font);

PangoOpenGLFont *
_pango_opengl_font_new (PangoOpenGLFontMap *fontmap_, FcPattern *pattern)
{
  PangoFontMap *fontmap = PANGO_FONT_MAP (fontmap_);
  PangoOpenGLFont *font;
  double d;

  g_return_val_if_fail (fontmap != NULL, NULL);
  g_return_val_if_fail (pattern != NULL, NULL);

  font = (PangoOpenGLFont *)g_object_new (PANGO_TYPE_OPENGL_FONT,
					  "pattern", pattern,
					  NULL);

  if (FcPatternGetDouble (pattern, FC_PIXEL_SIZE, 0, &d) == FcResultMatch)
    font->size = d * PANGO_SCALE;

#if PANGO_VERSION_CHECK (1, 16, 0)
  /* pango 1.24 is ABI-incompatible with 1.22, but distros */
  /* usually package it under the same name, so a runtime check */
  /* is required */
  /* unfortunately, pango fdoesn't sipport runtime checks in < 1.16 */
  /* so there is no real way to ensure binary compatibility portably */
  /* greta move, pango */
  if (pango_version () > PANGO_VERSION_ENCODE (1, 23, 0))
    PANGO_FC_FONT (font)->fontmap = fontmap;
#endif

  return font;
}

static void
load_fallback_face (PangoOpenGLFont *font, const char *original_file)
{
  PangoFcFont *fcfont = PANGO_FC_FONT (font);
  FcPattern *sans;
  FcPattern *matched;
  FcResult result;
  FT_Error error;
  FcChar8 *filename2 = NULL;
  gchar *name;
  int id;
  
  sans = FcPatternBuild (NULL,
			 FC_FAMILY,     FcTypeString, "sans",
			 FC_PIXEL_SIZE, FcTypeDouble, (double)font->size / PANGO_SCALE,
			 NULL);
  
  matched = FcFontMatch (NULL, sans, &result);
  
  if (FcPatternGetString (matched, FC_FILE, 0, &filename2) != FcResultMatch)
    goto bail1;
  
  if (FcPatternGetInteger (matched, FC_INDEX, 0, &id) != FcResultMatch)
    goto bail1;
  
  error = FT_New_Face (_pango_opengl_font_map_get_library (fcfont->fontmap),
		       (char *) filename2, id, &font->face);
  
  if (error)
    {
    bail1:
      name = pango_font_description_to_string (fcfont->description);
      g_warning ("Unable to open font file %s for font %s, exiting\n", filename2, name);
      exit (1);
    }
  else
    {
      name = pango_font_description_to_string (fcfont->description);
      g_warning ("Unable to open font file %s for font %s, falling back to %s\n", original_file, name, filename2);
      g_free (name);
    }

  FcPatternDestroy (sans);
  FcPatternDestroy (matched);
}

static void
set_transform (PangoOpenGLFont *font)
{
  PangoFcFont *fcfont = (PangoFcFont *)font;
  FcMatrix *fc_matrix;

  if (FcPatternGetMatrix (fcfont->font_pattern, FC_MATRIX, 0, &fc_matrix) == FcResultMatch)
    {
      FT_Matrix ft_matrix;
      
      ft_matrix.xx = 0x10000L * fc_matrix->xx;
      ft_matrix.yy = 0x10000L * fc_matrix->yy;
      ft_matrix.xy = 0x10000L * fc_matrix->xy;
      ft_matrix.yx = 0x10000L * fc_matrix->yx;

      FT_Set_Transform (font->face, &ft_matrix, NULL);
    }
}

FT_Face
pango_opengl_font_get_face (PangoFont *font)
{
  PangoOpenGLFont *glfont = (PangoOpenGLFont *)font;
  PangoFcFont *fcfont = (PangoFcFont *)font;
  FT_Error error;
  FcPattern *pattern;
  FcChar8 *filename;
  FcBool antialias, hinting, autohint;
  int id;

  pattern = fcfont->font_pattern;

  if (!glfont->face)
    {
      glfont->load_flags = 0;

      /* disable antialiasing if requested */
      if (FcPatternGetBool (pattern, FC_ANTIALIAS, 0, &antialias) != FcResultMatch)
	antialias = FcTrue;

      glfont->load_flags |= FT_LOAD_NO_BITMAP;

      /* disable hinting if requested */
      if (FcPatternGetBool (pattern, FC_HINTING, 0, &hinting) != FcResultMatch)
	hinting = FcTrue;

      if (!hinting)
        glfont->load_flags |= FT_LOAD_NO_HINTING;

      /* force autohinting if requested */
      if (FcPatternGetBool (pattern, FC_AUTOHINT, 0, &autohint) != FcResultMatch)
	autohint = FcFalse;

      if (autohint)
        glfont->load_flags |= FT_LOAD_FORCE_AUTOHINT;

      if (FcPatternGetString (pattern, FC_FILE, 0, &filename) != FcResultMatch)
	goto bail0;
      
      if (FcPatternGetInteger (pattern, FC_INDEX, 0, &id) != FcResultMatch)
	goto bail0;

      error = FT_New_Face (_pango_opengl_font_map_get_library (fcfont->fontmap),
			   (char *)filename, id, &glfont->face);

      if (error != FT_Err_Ok)
	{
	bail0:
	  load_fallback_face (glfont, (char *)filename);
	}

      g_assert (glfont->face);

      set_transform (glfont);

      error = FT_Set_Char_Size (glfont->face,
				PANGO_PIXELS_26_6 (glfont->size),
				PANGO_PIXELS_26_6 (glfont->size),
				0, 0);
      if (error)
	g_warning ("Error in FT_Set_Char_Size: %d", error);
    }
  
  return glfont->face;
}

G_DEFINE_TYPE (PangoOpenGLFont, pango_opengl_font, PANGO_TYPE_FC_FONT)

static void 
pango_opengl_font_init (PangoOpenGLFont *font)
{
  font->face = NULL;
  font->size = 0;
  font->glyph_info = g_hash_table_new (NULL, NULL);
}

static void
pango_opengl_font_class_init (PangoOpenGLFontClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  PangoFontClass *font_class = PANGO_FONT_CLASS (class);
  PangoFcFontClass *fc_font_class = PANGO_FC_FONT_CLASS (class);
  
  object_class->finalize = pango_opengl_font_finalize;
  
  font_class->get_glyph_extents = pango_opengl_font_get_glyph_extents;
  
  fc_font_class->lock_face = pango_opengl_font_real_lock_face;
  fc_font_class->unlock_face = pango_opengl_font_real_unlock_face;
}

static PangoOpenGLGlyphInfo *
pango_opengl_font_get_glyph_info (PangoFont *font_, PangoGlyph glyph, gboolean create)
{
  PangoOpenGLFont *font = (PangoOpenGLFont *)font_;
  PangoFcFont *fcfont = (PangoFcFont *)font;
  PangoOpenGLGlyphInfo *info;

  info = g_hash_table_lookup (font->glyph_info, GUINT_TO_POINTER (glyph));

  if ((info == NULL) && create)
    {
      info = g_slice_new0 (PangoOpenGLGlyphInfo);

      pango_fc_font_get_raw_extents (fcfont, font->load_flags,
				     glyph,
				     &info->ink_rect,
				     &info->logical_rect);

      g_hash_table_insert (font->glyph_info, GUINT_TO_POINTER(glyph), info);
    }

  return info;
}

PangoGlyph
pango_opengl_get_unknown_glyph (PangoFont *font)
{
  FT_Face face = pango_opengl_font_get_face (font);

  if (face && FT_IS_SFNT (face))
    /* TrueType fonts have an 'unknown glyph' box on glyph index 0 */
    return 0;
  else
    return PANGO_GLYPH_EMPTY;
}

static void
pango_opengl_font_get_glyph_extents (PangoFont      *font,
				     PangoGlyph      glyph,
				     PangoRectangle *ink_rect,
				     PangoRectangle *logical_rect)
{
  PangoOpenGLGlyphInfo *info;

  if (glyph == PANGO_GLYPH_EMPTY)
    {
      if (ink_rect)
	ink_rect->x = ink_rect->y = ink_rect->height = ink_rect->width = 0;
      if (logical_rect)
	logical_rect->x = logical_rect->y = logical_rect->height = logical_rect->width = 0;
      return;
    }

  if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
    {
      glyph = pango_opengl_get_unknown_glyph (font);
      if (glyph == PANGO_GLYPH_EMPTY)
        {
	  /* No unknown glyph found for the font, draw a box */
	  PangoFontMetrics *metrics = pango_font_get_metrics (font, NULL);

	  if (metrics)
	    {
	      if (ink_rect)
		{
		  ink_rect->x = PANGO_SCALE;
		  ink_rect->width = metrics->approximate_char_width - 2 * PANGO_SCALE;
		  ink_rect->y = - (metrics->ascent - PANGO_SCALE);
		  ink_rect->height = metrics->ascent + metrics->descent - 2 * PANGO_SCALE;
		}
	      if (logical_rect)
		{
		  logical_rect->x = 0;
		  logical_rect->width = metrics->approximate_char_width;
		  logical_rect->y = -metrics->ascent;
		  logical_rect->height = metrics->ascent + metrics->descent;
		}

	      pango_font_metrics_unref (metrics);
	    }
	  else
	    {
	      if (ink_rect)
		ink_rect->x = ink_rect->y = ink_rect->height = ink_rect->width = 0;
	      if (logical_rect)
		logical_rect->x = logical_rect->y = logical_rect->height = logical_rect->width = 0;
	    }

	  return;
	}
    }

  info = pango_opengl_font_get_glyph_info (font, glyph, TRUE);

  if (ink_rect)
    *ink_rect = info->ink_rect;
  if (logical_rect)
    *logical_rect = info->logical_rect;
}

int
pango_opengl_font_get_kerning (PangoFont *font,
			    PangoGlyph left,
			    PangoGlyph right)
{
  PangoFcFont *fc_font = PANGO_FC_FONT (font);
  
  FT_Face face;
  FT_Error error;
  FT_Vector kerning;

  face = pango_fc_font_lock_face (fc_font);
  if (!face)
    return 0;

  if (!FT_HAS_KERNING (face))
    {
      pango_fc_font_unlock_face (fc_font);
      return 0;
    }

  error = FT_Get_Kerning (face, left, right, ft_kerning_default, &kerning);
  if (error != FT_Err_Ok)
    {
      pango_fc_font_unlock_face (fc_font);
      return 0;
    }

  pango_fc_font_unlock_face (fc_font);
  return PANGO_UNITS_26_6 (kerning.x);
}

static FT_Face
pango_opengl_font_real_lock_face (PangoFcFont *font)
{
  return pango_opengl_font_get_face ((PangoFont *)font);
}

static void
pango_opengl_font_real_unlock_face (PangoFcFont *font)
{
}

static gboolean
pango_opengl_free_glyph_info_callback (gpointer key, gpointer value, gpointer data)
{
  PangoOpenGLFont *font = PANGO_OPENGL_FONT (data);
  PangoOpenGLGlyphInfo *info = value;
  
  if (font->glyph_cache_destroy && info->cached_glyph)
    (*font->glyph_cache_destroy) (info->cached_glyph);

  g_slice_free (PangoOpenGLGlyphInfo, info);
  return TRUE;
}

static void
pango_opengl_font_finalize (GObject *object)
{
  PangoOpenGLFont *font = (PangoOpenGLFont *)object;

  if (font->face)
    {
      FT_Done_Face (font->face);
      font->face = NULL;
    }

  g_hash_table_foreach_remove (font->glyph_info, pango_opengl_free_glyph_info_callback, object);
  g_hash_table_destroy (font->glyph_info);
  
  G_OBJECT_CLASS (pango_opengl_font_parent_class)->finalize (object);
}

PangoCoverage *
pango_opengl_font_get_coverage (PangoFont *font, PangoLanguage *language)
{
  return pango_font_get_coverage (font, language);
}

void *
_pango_opengl_font_get_cache_glyph_data (PangoFont *font, int glyph_index)
{
  PangoOpenGLGlyphInfo *info = pango_opengl_font_get_glyph_info (font, glyph_index, FALSE);

  return info ? info->cached_glyph : 0;
}

void
_pango_opengl_font_set_cache_glyph_data (PangoFont *font, int glyph_index, void *cached_glyph)
{
  PangoOpenGLGlyphInfo *info = pango_opengl_font_get_glyph_info (font, glyph_index, TRUE);
  info->cached_glyph = cached_glyph;
}

void
_pango_opengl_font_set_glyph_cache_destroy (PangoFont *font, GDestroyNotify destroy_notify)
{
  PANGO_OPENGL_FONT (font)->glyph_cache_destroy = destroy_notify;
}