Subversion Repositories Scribus

Rev

Rev 14952 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright © 2004 Keith Packard
 * Copyright © 2008 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it either under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation
 * (the "LGPL") or, at your option, under the terms of the Mozilla
 * Public License Version 1.1 (the "MPL"). If you do not alter this
 * notice, a recipient may use your version of this file under either
 * the MPL or the LGPL.
 *
 * You should have received a copy of the LGPL along with this library
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 * You should have received a copy of the MPL along with this library
 * in the file COPYING-MPL-1.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (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.mozilla.org/MPL/
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
 * the specific language governing rights and limitations.
 *
 * The Original Code is the cairo graphics library.
 *
 * The Initial Developer of the Original Code is Keith Packard
 *
 * Contributor(s):
 *      Keith Packard <keithp@keithp.com>
 *      Behdad Esfahbod <behdad@behdad.org>
 */


#include "cairoint.h"

#include <math.h>

/*
 * This file implements a user-font rendering the descendant of the Hershey
 * font coded by Keith Packard for use in the Twin window system.
 * The actual font data is in cairo-font-face-twin-data.c
 *
 * Ported to cairo user font and extended by Behdad Esfahbod.
 */




static cairo_user_data_key_t twin_properties_key;


/*
 * Face properties
 */


/* We synthesize multiple faces from the twin data.  Here is the parameters. */

/* The following tables and matching code are copied from Pango */

/* CSS weight */
typedef enum {
  TWIN_WEIGHT_THIN = 100,
  TWIN_WEIGHT_ULTRALIGHT = 200,
  TWIN_WEIGHT_LIGHT = 300,
  TWIN_WEIGHT_BOOK = 380,
  TWIN_WEIGHT_NORMAL = 400,
  TWIN_WEIGHT_MEDIUM = 500,
  TWIN_WEIGHT_SEMIBOLD = 600,
  TWIN_WEIGHT_BOLD = 700,
  TWIN_WEIGHT_ULTRABOLD = 800,
  TWIN_WEIGHT_HEAVY = 900,
  TWIN_WEIGHT_ULTRAHEAVY = 1000
} twin_face_weight_t;

/* CSS stretch */
typedef enum {
  TWIN_STRETCH_ULTRA_CONDENSED,
  TWIN_STRETCH_EXTRA_CONDENSED,
  TWIN_STRETCH_CONDENSED,
  TWIN_STRETCH_SEMI_CONDENSED,
  TWIN_STRETCH_NORMAL,
  TWIN_STRETCH_SEMI_EXPANDED,
  TWIN_STRETCH_EXPANDED,
  TWIN_STRETCH_EXTRA_EXPANDED,
  TWIN_STRETCH_ULTRA_EXPANDED
} twin_face_stretch_t;

typedef struct
{
  int value;
  const char str[16];
} FieldMap;

static const FieldMap slant_map[] = {
  { CAIRO_FONT_SLANT_NORMAL, "" },
  { CAIRO_FONT_SLANT_NORMAL, "Roman" },
  { CAIRO_FONT_SLANT_OBLIQUE, "Oblique" },
  { CAIRO_FONT_SLANT_ITALIC, "Italic" }
};

static const FieldMap smallcaps_map[] = {
  { FALSE, "" },
  { TRUE, "Small-Caps" }
};

static const FieldMap weight_map[] = {
  { TWIN_WEIGHT_THIN, "Thin" },
  { TWIN_WEIGHT_ULTRALIGHT, "Ultra-Light" },
  { TWIN_WEIGHT_ULTRALIGHT, "Extra-Light" },
  { TWIN_WEIGHT_LIGHT, "Light" },
  { TWIN_WEIGHT_BOOK, "Book" },
  { TWIN_WEIGHT_NORMAL, "" },
  { TWIN_WEIGHT_NORMAL, "Regular" },
  { TWIN_WEIGHT_MEDIUM, "Medium" },
  { TWIN_WEIGHT_SEMIBOLD, "Semi-Bold" },
  { TWIN_WEIGHT_SEMIBOLD, "Demi-Bold" },
  { TWIN_WEIGHT_BOLD, "Bold" },
  { TWIN_WEIGHT_ULTRABOLD, "Ultra-Bold" },
  { TWIN_WEIGHT_ULTRABOLD, "Extra-Bold" },
  { TWIN_WEIGHT_HEAVY, "Heavy" },
  { TWIN_WEIGHT_HEAVY, "Black" },
  { TWIN_WEIGHT_ULTRAHEAVY, "Ultra-Heavy" },
  { TWIN_WEIGHT_ULTRAHEAVY, "Extra-Heavy" },
  { TWIN_WEIGHT_ULTRAHEAVY, "Ultra-Black" },
  { TWIN_WEIGHT_ULTRAHEAVY, "Extra-Black" }
};

static const FieldMap stretch_map[] = {
  { TWIN_STRETCH_ULTRA_CONDENSED, "Ultra-Condensed" },
  { TWIN_STRETCH_EXTRA_CONDENSED, "Extra-Condensed" },
  { TWIN_STRETCH_CONDENSED,       "Condensed" },
  { TWIN_STRETCH_SEMI_CONDENSED,  "Semi-Condensed" },
  { TWIN_STRETCH_NORMAL,          "" },
  { TWIN_STRETCH_SEMI_EXPANDED,   "Semi-Expanded" },
  { TWIN_STRETCH_EXPANDED,        "Expanded" },
  { TWIN_STRETCH_EXTRA_EXPANDED,  "Extra-Expanded" },
  { TWIN_STRETCH_ULTRA_EXPANDED,  "Ultra-Expanded" }
};

static const FieldMap monospace_map[] = {
  { FALSE, "" },
  { TRUE, "Mono" },
  { TRUE, "Monospace" }
};


typedef struct _twin_face_properties {
    cairo_font_slant_t  slant;
    twin_face_weight_t  weight;
    twin_face_stretch_t stretch;

    /* lets have some fun */
    cairo_bool_t monospace;
    cairo_bool_t smallcaps;
} twin_face_properties_t;

static cairo_bool_t
field_matches (const char *s1,
               const char *s2,
               int len)
{
  int c1, c2;

  while (len && *s1 && *s2)
    {
#define TOLOWER(c) \
   (((c) >= 'A' && (c) <= 'Z') ? (c) - 'A' + 'a' : (c))


      c1 = TOLOWER (*s1);
      c2 = TOLOWER (*s2);
      if (c1 != c2) {
        if (c1 == '-') {
          s1++;
          continue;
        }
        return FALSE;
      }
      s1++; s2++;
      len--;
    }

  return len == 0 && *s1 == '\0';
}

static cairo_bool_t
parse_int (const char *word,
           size_t      wordlen,
           int        *out)
{
  char *end;
  long val = strtol (word, &end, 10);
  int i = val;

  if (end != word && (end == word + wordlen) && val >= 0 && val == i)
    {
      if (out)
        *out = i;

      return TRUE;
    }

  return FALSE;
}

static cairo_bool_t
find_field (const char *what,
            const FieldMap *map,
            int n_elements,
            const char *str,
            int len,
            int *val)
{
  int i;
  cairo_bool_t had_prefix = FALSE;

  if (what)
    {
      i = strlen (what);
      if (len > i && 0 == strncmp (what, str, i) && str[i] == '=')
        {
          str += i + 1;
          len -= i + 1;
          had_prefix = TRUE;
        }
    }

  for (i=0; i<n_elements; i++)
    {
      if (map[i].str[0] && field_matches (map[i].str, str, len))
        {
          if (val)
            *val = map[i].value;
          return TRUE;
        }
    }

  if (!what || had_prefix)
    return parse_int (str, len, val);

  return FALSE;
}

static void
parse_field (twin_face_properties_t *props,
             const char *str,
             int len)
{
  if (field_matches ("Normal", str, len))
    return;

#define FIELD(NAME) \
  if (find_field (STRINGIFY (NAME), NAME##_map, ARRAY_LENGTH (NAME##_map), str, len, \
                  (int *)(void *)&props->NAME)) \
      return; \

  FIELD (weight);
  FIELD (slant);
  FIELD (stretch);
  FIELD (smallcaps);
  FIELD (monospace);

#undef FIELD
}

static void
face_props_parse (twin_face_properties_t *props,
             const char *s)
{
    const char *start, *end;

    for (start = end = s; *end; end++) {
        if (*end != ' ' && *end != ':')
            continue;

        if (start < end)
                parse_field (props, start, end - start);
        start = end + 1;
    }
    if (start < end)
            parse_field (props, start, end - start);
}

static cairo_status_t
twin_font_face_create_properties (cairo_font_face_t *twin_face,
                                  twin_face_properties_t **props_out)
{
    twin_face_properties_t *props;
    cairo_status_t status;

    props = malloc (sizeof (twin_face_properties_t));
    if (unlikely (props == NULL))
        return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    props->stretch  = TWIN_STRETCH_NORMAL;
    props->monospace = FALSE;
    props->smallcaps = FALSE;

    status = cairo_font_face_set_user_data (twin_face,
                                            &twin_properties_key,
                                            props, free);
    if (unlikely (status)) {
        free (props);
        return status;
    }

    if (props_out)
        *props_out = props;

    return CAIRO_STATUS_SUCCESS;
}

static cairo_status_t
twin_font_face_set_properties_from_toy (cairo_font_face_t *twin_face,
                                        cairo_toy_font_face_t *toy_face)
{
    cairo_status_t status;
    twin_face_properties_t *props;

    status = twin_font_face_create_properties (twin_face, &props);
    if (unlikely (status))
        return status;

    props->slant = toy_face->slant;
    props->weight = toy_face->weight == CAIRO_FONT_WEIGHT_NORMAL ?
                    TWIN_WEIGHT_NORMAL : TWIN_WEIGHT_BOLD;
    face_props_parse (props, toy_face->family);

    return CAIRO_STATUS_SUCCESS;
}


/*
 * Scaled properties
 */


typedef struct _twin_scaled_properties {
        twin_face_properties_t *face_props;

        cairo_bool_t snap; /* hint outlines */

        double weight; /* unhinted pen width */
        double penx, peny; /* hinted pen width */
        double marginl, marginr; /* hinted side margins */

        double stretch; /* stretch factor */
} twin_scaled_properties_t;

static void
compute_hinting_scale (cairo_t *cr,
                       double x, double y,
                       double *scale, double *inv)
{
    cairo_user_to_device_distance (cr, &x, &y);
    *scale = x == 0 ? y : y == 0 ? x :sqrt (x*x + y*y);
    *inv = 1 / *scale;
}

static void
compute_hinting_scales (cairo_t *cr,
                        double *x_scale, double *x_scale_inv,
                        double *y_scale, double *y_scale_inv)
{
    double x, y;

    x = 1; y = 0;
    compute_hinting_scale (cr, x, y, x_scale, x_scale_inv);

    x = 0; y = 1;
    compute_hinting_scale (cr, x, y, y_scale, y_scale_inv);
}

#define SNAPXI(p)       (_cairo_round ((p) * x_scale) * x_scale_inv)
#define SNAPYI(p)       (_cairo_round ((p) * y_scale) * y_scale_inv)

/* This controls the global font size */
#define F(g)            ((g) / 72.)

static void
twin_hint_pen_and_margins(cairo_t *cr,
                          double *penx, double *peny,
                          double *marginl, double *marginr)
{
    double x_scale, x_scale_inv;
    double y_scale, y_scale_inv;
    double margin;

    compute_hinting_scales (cr,
                            &x_scale, &x_scale_inv,
                            &y_scale, &y_scale_inv);

    *penx = SNAPXI (*penx);
    if (*penx < x_scale_inv)
        *penx = x_scale_inv;

    *peny = SNAPYI (*peny);
    if (*peny < y_scale_inv)
        *peny = y_scale_inv;

    margin = *marginl + *marginr;
    *marginl = SNAPXI (*marginl);
    if (*marginl < x_scale_inv)
        *marginl = x_scale_inv;

    *marginr = margin - *marginl;
    if (*marginr < 0)
        *marginr = 0;
    *marginr = SNAPXI (*marginr);
}

static cairo_status_t
twin_scaled_font_compute_properties (cairo_scaled_font_t *scaled_font,
                                     cairo_t           *cr)
{
    cairo_status_t status;
    twin_scaled_properties_t *props;

    props = malloc (sizeof (twin_scaled_properties_t));
    if (unlikely (props == NULL))
        return _cairo_error (CAIRO_STATUS_NO_MEMORY);


    props->face_props = cairo_font_face_get_user_data (cairo_scaled_font_get_font_face (scaled_font),
                                                       &twin_properties_key);

    props->snap = scaled_font->options.hint_style > CAIRO_HINT_STYLE_NONE;

    /* weight */
    props->weight = props->face_props->weight * (F (4) / TWIN_WEIGHT_NORMAL);

    /* pen & margins */
    props->penx = props->peny = props->weight;
    props->marginl = props->marginr = F (4);
    if (scaled_font->options.hint_style > CAIRO_HINT_STYLE_SLIGHT)
        twin_hint_pen_and_margins(cr,
                                  &props->penx, &props->peny,
                                  &props->marginl, &props->marginr);

    /* stretch */
    props->stretch = 1 + .1 * ((int) props->face_props->stretch - (int) TWIN_STRETCH_NORMAL);


    /* Save it */
    status = cairo_scaled_font_set_user_data (scaled_font,
                                              &twin_properties_key,
                                              props, free);
    if (unlikely (status))
        goto FREE_PROPS;

    return CAIRO_STATUS_SUCCESS;

FREE_PROPS:
    free (props);
    return status;
}


/*
 * User-font implementation
 */


static cairo_status_t
twin_scaled_font_init (cairo_scaled_font_t  *scaled_font,
                       cairo_t              *cr,
                       cairo_font_extents_t *metrics)
{
  metrics->ascent  = F (54);
  metrics->descent = 1 - metrics->ascent;

  return twin_scaled_font_compute_properties (scaled_font, cr);
}

#define TWIN_GLYPH_MAX_SNAP_X 4
#define TWIN_GLYPH_MAX_SNAP_Y 7

typedef struct {
    int n_snap_x;
    int8_t snap_x[TWIN_GLYPH_MAX_SNAP_X];
    double snapped_x[TWIN_GLYPH_MAX_SNAP_X];
    int n_snap_y;
    int8_t snap_y[TWIN_GLYPH_MAX_SNAP_Y];
    double snapped_y[TWIN_GLYPH_MAX_SNAP_Y];
} twin_snap_info_t;

#define twin_glyph_left(g)      ((g)[0])
#define twin_glyph_right(g)     ((g)[1])
#define twin_glyph_ascent(g)    ((g)[2])
#define twin_glyph_descent(g)   ((g)[3])

#define twin_glyph_n_snap_x(g)  ((g)[4])
#define twin_glyph_n_snap_y(g)  ((g)[5])
#define twin_glyph_snap_x(g)    (&g[6])
#define twin_glyph_snap_y(g)    (twin_glyph_snap_x(g) + twin_glyph_n_snap_x(g))
#define twin_glyph_draw(g)      (twin_glyph_snap_y(g) + twin_glyph_n_snap_y(g))

static void
twin_compute_snap (cairo_t             *cr,
                   twin_snap_info_t    *info,
                   const signed char   *b)
{
    int                 s, n;
    const signed char   *snap;
    double x_scale, x_scale_inv;
    double y_scale, y_scale_inv;

    compute_hinting_scales (cr,
                            &x_scale, &x_scale_inv,
                            &y_scale, &y_scale_inv);

    snap = twin_glyph_snap_x (b);
    n = twin_glyph_n_snap_x (b);
    info->n_snap_x = n;
    assert (n <= TWIN_GLYPH_MAX_SNAP_X);
    for (s = 0; s < n; s++) {
        info->snap_x[s] = snap[s];
        info->snapped_x[s] = SNAPXI (F (snap[s]));
    }

    snap = twin_glyph_snap_y (b);
    n = twin_glyph_n_snap_y (b);
    info->n_snap_y = n;
    assert (n <= TWIN_GLYPH_MAX_SNAP_Y);
    for (s = 0; s < n; s++) {
        info->snap_y[s] = snap[s];
        info->snapped_y[s] = SNAPYI (F (snap[s]));
    }
}

static double
twin_snap (int8_t v, int n, int8_t *snap, double *snapped)
{
    int s;

    if (!n)
        return F(v);

    if (snap[0] == v)
        return snapped[0];

    for (s = 0; s < n - 1; s++)
    {
        if (snap[s+1] == v)
            return snapped[s+1];

        if (snap[s] <= v && v <= snap[s+1])
        {
            int before = snap[s];
            int after = snap[s+1];
            int dist = after - before;
            double snap_before = snapped[s];
            double snap_after = snapped[s+1];
            double dist_before = v - before;
            return snap_before + (snap_after - snap_before) * dist_before / dist;
        }
    }
    return F(v);
}

#define SNAPX(p)        twin_snap (p, info.n_snap_x, info.snap_x, info.snapped_x)
#define SNAPY(p)        twin_snap (p, info.n_snap_y, info.snap_y, info.snapped_y)

static cairo_status_t
twin_scaled_font_render_glyph (cairo_scaled_font_t  *scaled_font,
                               unsigned long         glyph,
                               cairo_t              *cr,
                               cairo_text_extents_t *metrics)
{
    double x1, y1, x2, y2, x3, y3;
    double marginl;
    twin_scaled_properties_t *props;
    twin_snap_info_t info;
    const int8_t *b;
    const int8_t *g;
    int8_t w;
    double gw;

    props = cairo_scaled_font_get_user_data (scaled_font, &twin_properties_key);

    /* Save glyph space, we need it when stroking */
    cairo_save (cr);

    /* center the pen */
    cairo_translate (cr, props->penx * .5, -props->peny * .5);

    /* small-caps */
    if (props->face_props->smallcaps && glyph >= 'a' && glyph <= 'z') {
        glyph += 'A' - 'a';
        /* 28 and 42 are small and capital letter heights of the glyph data */
        cairo_scale (cr, 1, 28. / 42);
    }

    /* slant */
    if (props->face_props->slant != CAIRO_FONT_SLANT_NORMAL) {
        cairo_matrix_t shear = { 1, 0, -.2, 1, 0, 0};
        cairo_transform (cr, &shear);
    }

    b = _cairo_twin_outlines +
        _cairo_twin_charmap[unlikely (glyph >= ARRAY_LENGTH (_cairo_twin_charmap)) ? 0 : glyph];
    g = twin_glyph_draw(b);
    w = twin_glyph_right(b);
    gw = F(w);

    marginl = props->marginl;

    /* monospace */
    if (props->face_props->monospace) {
        double monow = F(24);
        double extra =  props->penx + props->marginl + props->marginr;
        cairo_scale (cr, (monow + extra) / (gw + extra), 1);
        gw = monow;

        /* resnap margin for new transform */
        {
            double x, y, x_scale, x_scale_inv;
            x = 1; y = 0;
            compute_hinting_scale (cr, x, y, &x_scale, &x_scale_inv);
            marginl = SNAPXI (marginl);
        }
    }

    cairo_translate (cr, marginl, 0);

    /* stretch */
    cairo_scale (cr, props->stretch, 1);

    if (props->snap)
        twin_compute_snap (cr, &info, b);
    else
        info.n_snap_x = info.n_snap_y = 0;

    /* advance width */
    metrics->x_advance = gw * props->stretch + props->penx + props->marginl + props->marginr;

    /* glyph shape */
    for (;;) {
        switch (*g++) {
        case 'M':
            cairo_close_path (cr);
            /* fall through */
        case 'm':
            x1 = SNAPX(*g++);
            y1 = SNAPY(*g++);
            cairo_move_to (cr, x1, y1);
            continue;
        case 'L':
            cairo_close_path (cr);
            /* fall through */
        case 'l':
            x1 = SNAPX(*g++);
            y1 = SNAPY(*g++);
            cairo_line_to (cr, x1, y1);
            continue;
        case 'C':
            cairo_close_path (cr);
            /* fall through */
        case 'c':
            x1 = SNAPX(*g++);
            y1 = SNAPY(*g++);
            x2 = SNAPX(*g++);
            y2 = SNAPY(*g++);
            x3 = SNAPX(*g++);
            y3 = SNAPY(*g++);
            cairo_curve_to (cr, x1, y1, x2, y2, x3, y3);
            continue;
        case 'E':
            cairo_close_path (cr);
            /* fall through */
        case 'e':
            cairo_restore (cr); /* restore glyph space */
            cairo_set_tolerance (cr, 0.01);
            cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
            cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
            cairo_set_line_width (cr, 1);
            cairo_scale (cr, props->penx, props->peny);
            cairo_stroke (cr);
            break;
        case 'X':
            /* filler */
            continue;
        }
        break;
    }

    return CAIRO_STATUS_SUCCESS;
}

static cairo_status_t
twin_scaled_font_unicode_to_glyph (cairo_scaled_font_t *scaled_font,
                                   unsigned long        unicode,
                                   unsigned long       *glyph)
{
    /* We use an identity charmap.  Which means we could live
     * with no unicode_to_glyph method too.  But we define this
     * to map all unknown chars to a single unknown glyph to
     * reduce pressure on cache. */


    if (likely (unicode < ARRAY_LENGTH (_cairo_twin_charmap)))
        *glyph = unicode;
    else
        *glyph = 0;

    return CAIRO_STATUS_SUCCESS;
}


/*
 * Face constructor
 */


static cairo_font_face_t *
_cairo_font_face_twin_create_internal (void)
{
    cairo_font_face_t *twin_font_face;

    twin_font_face = cairo_user_font_face_create ();
    cairo_user_font_face_set_init_func             (twin_font_face, twin_scaled_font_init);
    cairo_user_font_face_set_render_glyph_func     (twin_font_face, twin_scaled_font_render_glyph);
    cairo_user_font_face_set_unicode_to_glyph_func (twin_font_face, twin_scaled_font_unicode_to_glyph);

    return twin_font_face;
}

cairo_font_face_t *
_cairo_font_face_twin_create_fallback (void)
{
    cairo_font_face_t *twin_font_face;
    cairo_status_t status;

    twin_font_face = _cairo_font_face_twin_create_internal ();
    status = twin_font_face_create_properties (twin_font_face, NULL);
    if (status) {
        cairo_font_face_destroy (twin_font_face);
        return (cairo_font_face_t *) &_cairo_font_face_nil;
    }

    return twin_font_face;
}

cairo_status_t
_cairo_font_face_twin_create_for_toy (cairo_toy_font_face_t   *toy_face,
                                      cairo_font_face_t      **font_face)
{
    cairo_status_t status;
    cairo_font_face_t *twin_font_face;

    twin_font_face = _cairo_font_face_twin_create_internal ();
    status = twin_font_face_set_properties_from_toy (twin_font_face, toy_face);
    if (status) {
        cairo_font_face_destroy (twin_font_face);
        return status;
    }

    *font_face = twin_font_face;

    return CAIRO_STATUS_SUCCESS;
}