/****************************************************************************
* truetype.c
*
* This module implements rendering of TrueType fonts.
* This file was written by Alexander Enzmann. He wrote the code for
* rendering glyphs and generously provided us these enhancements.
*
* from Persistence of Vision(tm) Ray Tracer
* Copyright 1996 Persistence of Vision Team
*---------------------------------------------------------------------------
* NOTICE: This source code file is provided so that users may experiment
* with enhancements to POV-Ray and to port the software to platforms other
* than those supported by the POV-Ray Team. There are strict rules under
* which you are permitted to use this file. The rules are in the file
* named POVLEGAL.DOC which should be distributed with this file. If
* POVLEGAL.DOC is not available or for more info please contact the POV-Ray
* Team Coordinator by leaving a message in CompuServe's Graphics Developer's
* Forum. The latest version of POV-Ray may be found there as well.
*
* This program is based on the popular DKB raytracer version 2.12.
* DKBTrace was originally written by David K. Buck.
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
*
* Alexander Ennzmann - original code...
* Eduard Schwan - Mac compatibility...
* Steve Demlow - Various bug fixes, data size independence...
* Andreas Dilger - Kerning, multiple component glyphs...
*
* Changed I/O macros to functions for machine independence, Jan 1996 [AED]
* Changed I/O functions for data size independence, Jan 1996 [AED], [SCD]
* Major re-organization to make code more understandable, Jan 1996 [AED]
* Added proper letter spacing, full kerning support, Jan 1996 [AED]
* Changed to find own x/y min/max for each glyph (fonts bad?), Jan 1996 [AED]
* Added special case for space character, Jan 1996 [AED]
* Added support for multiple glyph characters, Feb 1996 [AED]
* Changed functions to only use promoted types (no USHORTs), Mar 1996 [AED]
* Cache Format 4 glyph table index to speed up parsing, Mar 1996 [AED]
* Fixed TTF rendering bug that leaves horizontal streaks, Nov. 1996 Kochin Chang (KochinC@aol.com)
* Allowed Macintosh-charmap TTF files to be processed, Nov. 1996 Bob Spence/Eduard Schwan
*****************************************************************************/
#include "frame.h"
#include "povray.h"
#include "vector.h"
#include "povproto.h"
#include "bbox.h"
#include "matrices.h"
#include "objects.h"
#include "truetype.h"
#include "csg.h" /* [ARE 11/94] */
#include "povray.h"
/*****************************************************************************
* Local preprocessor defines
******************************************************************************/
/* uncomment this to debug ttf. DEBUG1 gives less output than DEBUG2
#define TTF_DEBUG2 1
#define TTF_DEBUG 1
*/
#undef EPSILON
#define EPSILON 1.0e-10
#ifndef TTF_Tolerance
#define TTF_Tolerance 1.0e-6 /* -4 worked, -8 failed */
#endif
#ifndef SEEK_SET
#define SEEK_SET 0 /* seek from start of file */
#define SEEK_CUR 1 /* relative to current position */
#define SEEK_END 2 /* relative to end of file */
#endif
#define MAX_ITERATIONS 50
#define COEFF_LIMIT 1.0e-20
/* For decoding glyph coordinate bit flags */
#define ONCURVE 0x01
#define XSHORT 0x02
#define YSHORT 0x04
#define REPEAT_FLAGS 0x08 /* repeat flag n times */
#define SHORT_X_IS_POS 0x10 /* the short vector is positive */
#define NEXT_X_IS_ZERO 0x10 /* the relative x coordinate is zero */
#define SHORT_Y_IS_POS 0x20 /* the short vector is positive */
#define NEXT_Y_IS_ZERO 0x20 /* the relative y coordinate is zero */
/* For decoding multi-component glyph bit flags */
#define ARG_1_AND_2_ARE_WORDS 0x0001
#define ARGS_ARE_XY_VALUES 0x0002
#define ROUND_XY_TO_GRID 0x0004
#define WE_HAVE_A_SCALE 0x0008
/* RESERVED 0x0010 */
#define MORE_COMPONENTS 0x0020
#define WE_HAVE_AN_X_AND_Y_SCALE 0x0040
#define WE_HAVE_A_TWO_BY_TWO 0x0080
#define WE_HAVE_INSTRUCTIONS 0x0100
#define USE_MY_METRICS 0x0200
/* For decoding kern coverage bit flags */
#define KERN_HORIZONTAL 0x01
#define KERN_MINIMUM 0x02
#define KERN_CROSS_STREAM 0x04
#define KERN_OVERRIDE 0x08
/* Some marcos to make error detection easier, as well as clarify code */
#define READSHORT(fp) readSHORT(fp, __LINE__, __FILE__)
#define READLONG(fp) readLONG(fp, __LINE__, __FILE__)
#define READUSHORT(fp) readUSHORT(fp, __LINE__, __FILE__)
#define READULONG(fp) readULONG(fp, __LINE__, __FILE__)
#define READFIXED(fp) readLONG(fp, __LINE__, __FILE__)
#define READFWORD(fp) readSHORT(fp, __LINE__, __FILE__)
#define READUFWORD(fp) readUSHORT(fp, __LINE__, __FILE__)
/*****************************************************************************
* Local typedefs
******************************************************************************/
/* Type definitions to match the TTF spec, makes code clearer */
typedef char CHAR;
typedef unsigned char BYTE;
typedef short SHORT;
typedef unsigned short USHORT;
typedef long LONG;
typedef unsigned long ULONG;
typedef short FWord;
typedef unsigned short uFWord;
#if !defined(MACOS)
typedef long Fixed;
#endif
typedef struct
{
Fixed version; /* 0x10000 (1.0) */
USHORT numTables; /* number of tables */
USHORT searchRange; /* (max2 <= numTables)*16 */
USHORT entrySelector; /* log2 (max2 <= numTables) */
USHORT rangeShift; /* numTables*16-searchRange */
} sfnt_OffsetTable;
typedef struct
{
BYTE tag[4];
ULONG checkSum;
ULONG offset;
ULONG length;
} sfnt_TableDirectory;
typedef sfnt_TableDirectory *sfnt_TableDirectoryPtr;
typedef struct
{
ULONG bc;
ULONG ad;
} longDateTime;
typedef struct
{
Fixed version; /* for this table, set to 1.0 */
Fixed fontRevision; /* For Font Manufacturer */
ULONG checkSumAdjustment;
ULONG magicNumber; /* signature, must be 0x5F0F3CF5 == MAGIC */
USHORT flags;
USHORT unitsPerEm; /* How many in Font Units per EM */
longDateTime created;
longDateTime modified;
FWord xMin; /* Font wide bounding box in ideal space */
FWord yMin; /* Baselines and metrics are NOT worked */
FWord xMax; /* into these numbers) */
FWord yMax;
USHORT macStyle; /* macintosh style word */
USHORT lowestRecPPEM; /* lowest recommended pixels per Em */
SHORT fontDirectionHint;
SHORT indexToLocFormat; /* 0 - short offsets, 1 - long offsets */
SHORT glyphDataFormat;
} sfnt_FontHeader;
typedef struct
{
USHORT platformID;
USHORT specificID;
ULONG offset;
} sfnt_platformEntry;
typedef sfnt_platformEntry *sfnt_platformEntryPtr;
typedef struct
{
USHORT format;
USHORT length;
USHORT version;
} sfnt_mappingTable;
typedef struct
{
Fixed version;
FWord Ascender;
FWord Descender;
FWord LineGap;
uFWord advanceWidthMax;
FWord minLeftSideBearing;
FWord minRightSideBearing;
FWord xMaxExtent;
SHORT caretSlopeRise;
SHORT caretSlopeRun;
SHORT reserved1;
SHORT reserved2;
SHORT reserved3;
SHORT reserved4;
SHORT reserved5;
SHORT metricDataFormat;
USHORT numberOfHMetrics; /* number of hMetrics in the hmtx table */
} sfnt_HorizHeader;
typedef struct
{
SHORT numContours;
SHORT xMin;
SHORT yMin;
SHORT xMax;
SHORT yMax;
} GlyphHeader;
typedef struct
{
GlyphHeader header;
USHORT numPoints;
USHORT *endPoints;
BYTE *flags;
DBL *x, *y;
USHORT myMetrics;
} GlyphOutline;
typedef struct
{
BYTE inside_flag; /* 1 if this an inside contour, 0 if outside */
USHORT count; /* Number of points in the contour */
BYTE *flags; /* On/off curve flags */
DBL *x, *y; /* Coordinates of control vertices */
} Contour;
typedef struct GlyphStruct *GlyphPtr;
/* Contour information for a single glyph */
typedef struct GlyphStruct
{
GlyphHeader header; /* Count and sizing information about this
* glyph */
USHORT glyph_index; /* Internal glyph index for this character */
Contour *contours; /* Array of outline contours */
USHORT unitsPerEm; /* Max units character */
GlyphPtr next; /* Next cached glyph */
USHORT c; /* Character code */
USHORT myMetrics; /* Which glyph index this is for metrics */
} Glyph;
typedef struct KernData_struct
{
USHORT left, right; /* Glyph index of left/right to kern */
FWord value; /* Delta in FUnits to apply in between */
} KernData;
/*
* [esp] There's already a "KernTable" on the Mac... renamed to TTKernTable for
* now in memorium to its author.
*/
typedef struct KernStruct
{
USHORT coverage; /* Coverage bit field of this subtable */
USHORT nPairs; /* # of kerning pairs in this table */
KernData *kern_pairs; /* Array of kerning values */
} TTKernTable;
typedef struct KernTableStruct
{
USHORT nTables; /* # of subtables in the kerning table */
TTKernTable *tables;
} KernTables;
typedef struct longHorMertric
{
uFWord advanceWidth; /* Total width of a glyph in FUnits */
FWord lsb; /* FUnits to the left of the glyph */
} longHorMetric;
/* Useful general data about this font */
typedef struct FontFileInfoStruct
{
char *filename;
FILE *fp;
USHORT platformID; /* Which character encoding to use */
USHORT specificID;
ULONG cmap_table_offset; /* File locations for these tables */
ULONG glyf_table_offset;
USHORT numGlyphs; /* How many symbols in this file */
USHORT unitsPerEm; /* The "resoultion" of this font */
SHORT indexToLocFormat; /* 0 - short format, 1 - long format */
ULONG *loca_table; /* Mapping from characters to glyphs */
GlyphPtr glyphs; /* Cached info for this font */
KernTables kerning_tables; /* Kerning info for this font */
USHORT numberOfHMetrics; /* The number of explicit spacings */
longHorMetric *hmtx_table; /* Horizontal spacing info */
ULONG glyphIDoffset; /* Offset for Type 4 encoding tables */
USHORT segCount, searchRange, /* Counts for Type 4 encoding tables */
entrySelector, rangeShift;
USHORT *startCount, *endCount, /* Type 4 (MS) encoding tables */
*idDelta, *idRangeOffset;
struct FontFileInfoStruct *next; /* Next font */
} FontFileInfo;
/*****************************************************************************
* Local variables
******************************************************************************/
static BYTE tag_CharToIndexMap[] = "cmap"; /* 0x636d6170; */
static BYTE tag_FontHeader[] = "head"; /* 0x68656164; */
static BYTE tag_GlyphData[] = "glyf"; /* 0x676c7966; */
static BYTE tag_IndexToLoc[] = "loca"; /* 0x6c6f6361; */
static BYTE tag_Kerning[] = "kern"; /* 0x6b65726e; */
static BYTE tag_MaxProfile[] = "maxp"; /* 0x6d617870; */
static BYTE tag_HorizHeader[] = "hhea"; /* 0x68686561; */
static BYTE tag_HorizMetric[] = "hmtx"; /* 0x686d7478; */
static FontFileInfo *TTFonts = NULL;
/*****************************************************************************
* Static functions
******************************************************************************/
/* Byte order independent I/O routines (probably already in other routines) */
static SHORT readSHORT PARAMS((FILE *infile, int line, char *file));
static USHORT readUSHORT PARAMS((FILE *infile, int line, char *file));
static LONG readLONG PARAMS((FILE *infile, int line, char *file));
static ULONG readULONG PARAMS((FILE *infile, int line, char *file));
static int compare_tag4 PARAMS((BYTE *ttf_tag, BYTE *known_tag));
/* Internal TTF input routines */
static FontFileInfo *ProcessFontFile PARAMS((char *fontfilename));
static FontFileInfo *OpenFontFile PARAMS((char *filename));
static void ProcessHeadTable PARAMS((FontFileInfo *ffile, long head_table_offset));
static void ProcessLocaTable PARAMS((FontFileInfo *ffile, long loca_table_offset));
static void ProcessMaxpTable PARAMS((FontFileInfo *ffile, long maxp_table_offset));
static void ProcessKernTable PARAMS((FontFileInfo *ffile, long kern_table_offset));
static void ProcessHheaTable PARAMS((FontFileInfo *ffile, long hhea_table_offset));
static void ProcessHmtxTable PARAMS((FontFileInfo *ffile, long hmtx_table_offset));
static GlyphPtr ProcessCharacter PARAMS((FontFileInfo *ffile, unsigned int search_char, unsigned int *glyph_index));
static USHORT ProcessCharMap PARAMS((FontFileInfo *ffile, unsigned int search_char));
static USHORT ProcessFormat0Glyph PARAMS((FontFileInfo *ffile, unsigned int search_char));
static USHORT ProcessFormat4Glyph PARAMS((FontFileInfo *ffile, unsigned int search_char));
static USHORT ProcessFormat6Glyph PARAMS((FontFileInfo *ffile, unsigned int search_char));
static GlyphPtr ExtractGlyphInfo PARAMS((FontFileInfo *ffile, unsigned int *glyph_index, unsigned int c));
static GlyphOutline *ExtractGlyphOutline PARAMS((FontFileInfo *ffile, unsigned int *glyph_index, unsigned int c));
static GlyphPtr ConvertOutlineToGlyph PARAMS((FontFileInfo *ffile, GlyphOutline *ttglyph));
/* Glyph surface intersection routines */
static int Inside_Glyph PARAMS((double x, double y, GlyphPtr glyph));
static int solve_quad PARAMS((double *x, double *y, double mindist, DBL maxdist));
static void GetZeroOneHits PARAMS((GlyphPtr glyph, VECTOR P, VECTOR D, DBL glyph_depth, double *t0, double *t1));
static int GlyphIntersect PARAMS((OBJECT *Object, VECTOR P, VECTOR D, DBL len, GlyphPtr glyph, DBL glyph_depth, RAY *Ray, ISTACK *Depth_Stack));
/* POV-Ray object intersection/creation routines */
static TTF *Create_TTF PARAMS((void));
static int All_TTF_Intersections PARAMS((OBJECT *Object, RAY *Ray, ISTACK *Depth_Stack));
static int Inside_TTF PARAMS((VECTOR IPoint, OBJECT *Object));
static void TTF_Normal PARAMS((VECTOR Result, OBJECT *Object, INTERSECTION *Inter));
static void *Copy_TTF PARAMS((OBJECT *Object));
static void Translate_TTF PARAMS((OBJECT *Object, VECTOR Vector, TRANSFORM *Trans));
static void Rotate_TTF PARAMS((OBJECT *Object, VECTOR Vector, TRANSFORM *Trans));
static void Scale_TTF PARAMS((OBJECT *Object, VECTOR Vector, TRANSFORM *Trans));
static void Transform_TTF PARAMS((OBJECT *Object, TRANSFORM *Trans));
static void Invert_TTF PARAMS((OBJECT *Object));
static void Destroy_TTF PARAMS((OBJECT *Object));
METHODS TTF_Methods =
{All_TTF_Intersections,
Inside_TTF, TTF_Normal,
Copy_TTF, Translate_TTF, Rotate_TTF, Scale_TTF, Transform_TTF,
Invert_TTF, Destroy_TTF};
/*
* The following work as macros if sizeof(short) == 16 bits and
* sizeof(long) == 32 bits, but tend to break otherwise. Making these
* into error functions also allows file error checking. Do not attempt to
* "optimize" these functions - some architectures require them the way
* that they are written.
*/
static SHORT readSHORT(infile, line, file)
FILE *infile;
int line;
char *file;
{
int i0, i1 = 0; /* To quiet warnings */
if ((i0 = fgetc(infile)) == EOF || (i1 = fgetc(infile)) == EOF)
{
Error("Error reading TrueType font file at line %d, %s\n", line, file);
}
if (i0 & 0x80) /* Subtract 1 after value is negated to avoid overflow [AED] */
return -(((255 - i0) << 8) | (255 - i1)) - 1;
else
return (i0 << 8) | i1;
}
static USHORT readUSHORT(infile, line, file)
FILE *infile;
int line;
char *file;
{
int i0, i1 = 0; /* To quiet warnings */
if ((i0 = fgetc(infile)) == EOF || (i1 = fgetc(infile)) == EOF)
{
Error("Error reading TrueType font file at line %d, %s\n", line, file);
}
return (USHORT)((((USHORT)i0) << 8) | ((USHORT)i1));
}
static LONG readLONG(infile, line, file)
FILE *infile;
int line;
char *file;
{
LONG i0, i1 = 0, i2 = 0, i3 = 0; /* To quiet warnings */
if ((i0 = fgetc(infile)) == EOF || (i1 = fgetc(infile)) == EOF ||
(i2 = fgetc(infile)) == EOF || (i3 = fgetc(infile)) == EOF)
{
Error("Error reading TrueType font file at line %d, %s\n", line, file);
}
if (i0 & 0x80) /* Subtract 1 after value is negated to avoid overflow [AED] */
return -(((255 - i0) << 24) | ((255 - i1) << 16) |
((255 - i2) << 8) | (255 - i3)) - 1;
else
return (i0 << 24) | (i1 << 16) | (i2 << 8) | i3;
}
static ULONG readULONG(infile, line, file)
FILE *infile;
int line;
char *file;
{
int i0, i1 = 0, i2 = 0, i3 = 0; /* To quiet warnings */
if ((i0 = fgetc(infile)) == EOF || (i1 = fgetc(infile)) == EOF ||
(i2 = fgetc(infile)) == EOF || (i3 = fgetc(infile)) == EOF)
{
Error("Error reading TrueType font file at line %d, %s\n", line, file);
}
return (ULONG) ((((ULONG) i0) << 24) | (((ULONG) i1) << 16) |
(((ULONG) i2) << 8) | ((ULONG) i3));
}
static int compare_tag4(ttf_tag, known_tag)
BYTE *ttf_tag;
BYTE *known_tag;
{
return (ttf_tag[0] == known_tag[0] && ttf_tag[1] == known_tag[1] &&
ttf_tag[2] == known_tag[2] && ttf_tag[3] == known_tag[3]);
}
/*****************************************************************************
*
* FUNCTION
*
* ProcessNewTTF
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* Alexander Ennzmann
*
* DESCRIPTION
*
* Takes an input string and a font filename, and creates a POV-Ray CSG
* object for each letter in the string.
*
* CHANGES
*
* -
*
******************************************************************************/
void ProcessNewTTF(object, filename, text_string, depth, offset)
OBJECT *object;
char *filename, *text_string;
DBL depth;
VECTOR offset;
{
FontFileInfo *ffile;
int slen;
VECTOR local_offset, total_offset;
CSG *Object = (CSG *) object;
TTF *ttf;
OBJECT *Local;
GlyphPtr glyph;
DBL funit_size;
TTKernTable *table;
USHORT coverage;
unsigned int search_char;
unsigned int glyph_index, last_index = 0;
FWord kern_value_x, kern_value_min_x;
FWord kern_value_y, kern_value_min_y;
int i, j, k;
TRANSFORM Trans;
/* Get general font info */
ffile = ProcessFontFile(filename);
/* Get info about each character in the string */
slen = strlen(text_string);
Make_Vector(total_offset, 0.0, 0.0, 0.0);
Object->Children = NULL;
for (i = 0; i < slen; i++)
{
/*
* We need to make sure (for now) that this is only the lower 8 bits,
* so we don't have all the high bits set if converted from a signed
* char to an unsigned short.
*/
search_char = ((unsigned int)text_string[i]) & 0xFF;
#ifdef TTF_DEBUG
Debug_Info("\nChar: '%c' (0x%X), Offset[%d]: <%g,%g,%g>\n", text_string[i],
search_char, i, total_offset[X], total_offset[Y], total_offset[Z]);
#endif
/* Make a new child for each character */
ttf = Create_TTF();
Local = (OBJECT *) ttf;
/* Set the depth information for the character */
ttf->depth = depth;
/*
* Get pointers to the contour information for each character
* in the text string.
*/
glyph = ProcessCharacter(ffile, search_char, &glyph_index);
ttf->glyph = (void *)glyph;
funit_size = 1.0 / (DBL)(ffile->unitsPerEm);
/*
* Spacing based on the horizontal metric table, the kerning table,
* and (possibly) the previous glyph.
*/
if (i == 0) /* Ignore spacing on the left for the first character only */
{
/* Shift the glyph to start at the origin */
total_offset[X] = -glyph->header.xMin * funit_size;
Compute_Translation_Transform(&Trans, total_offset);
Translate_TTF(Local, total_offset, &Trans);
/* Shift next glyph by the width of this one excluding the left offset*/
total_offset[X] = (ffile->hmtx_table[glyph->myMetrics].advanceWidth -
ffile->hmtx_table[glyph->myMetrics].lsb) * funit_size;
#ifdef TTF_DEBUG
Debug_Info("aw(%d): %g\n", i,
(ffile->hmtx_table[glyph->myMetrics].advanceWidth -
ffile->hmtx_table[glyph->myMetrics].lsb)*funit_size);
#endif
}
else /* Kern all of the other characters */
{
kern_value_x = kern_value_y = 0;
kern_value_min_x = kern_value_min_y = -ffile->unitsPerEm;
Make_Vector(local_offset, 0.0, 0.0, 0.0);
for (j = 0; j < ffile->kerning_tables.nTables; j++)
{
table = ffile->kerning_tables.tables;
coverage = table->coverage;
/*
* Don't use vertical kerning until such a time when we support
* characters moving in the vertical direction...
*/
if (!(coverage & KERN_HORIZONTAL))
continue;
/*
* If we were keen, we could do a binary search for this
* character combination, since the pairs are sorted in
* order as if the left and right index values were a 32 bit
* unsigned int (mostly - at least they are sorted on the
* left glyph). Something to do when everything else works...
*/
for (k = 0; k < table[j].nPairs; k++)
{
if (table[j].kern_pairs[k].left == last_index &&
table[j].kern_pairs[k].right == glyph->myMetrics)
{
#ifdef TTF_DEBUG2
Debug_Info("Found a kerning for <%d, %d> = %d",
last_index, glyph_index, table[j].kern_pairs[k].value);
#endif
/*
* By default, Windows & OS/2 assume at most a single table with
* !KERN_MINIMUM, !KERN_CROSS_STREAM, KERN_OVERRIDE.
*/
if (coverage & KERN_MINIMUM)
{
#ifdef TTF_DEBUG2
Debug_Info(" KERN_MINIMUM\n");
#endif
if (coverage & KERN_CROSS_STREAM)
kern_value_min_y = table[j].kern_pairs[k].value;
else
kern_value_min_x = table[j].kern_pairs[k].value;
}
else
{
if (coverage & KERN_CROSS_STREAM)
{
#ifdef TTF_DEBUG2
Debug_Info(" KERN_CROSS_STREAM\n");
#endif
if (table[j].kern_pairs[k].value == (FWord)0x8000)
{
kern_value_y = 0;
}
else
{
if (coverage & KERN_OVERRIDE)
kern_value_y = table[j].kern_pairs[k].value;
else
kern_value_y += table[j].kern_pairs[k].value;
}
}
else
{
#ifdef TTF_DEBUG2
Debug_Info(" KERN_VALUE\n");
#endif
if (coverage & KERN_OVERRIDE)
kern_value_x = table[j].kern_pairs[k].value;
else
kern_value_x += table[j].kern_pairs[k].value;
}
}
break;
}
/* Abort now if we have passed all potential matches */
else if (table[j].kern_pairs[k].left > last_index)
{
break;
}
}
}
kern_value_x = (kern_value_x > kern_value_min_x ?
kern_value_x : kern_value_min_x);
kern_value_y = (kern_value_y > kern_value_min_y ?
kern_value_y : kern_value_min_y);
/*
* Offset this character so that the left edge of the glyph is at
* the previous offset + the lsb + any kerning amount.
*/
local_offset[X] = total_offset[X] +
(DBL)(ffile->hmtx_table[glyph->myMetrics].lsb -
glyph->header.xMin + kern_value_x) * funit_size;
local_offset[Y] = total_offset[Y] + (DBL)kern_value_y * funit_size;
/* Translate this glyph to its final position in the string */
Compute_Translation_Transform(&Trans, local_offset);
Translate_TTF(Local, local_offset, &Trans);
/* Shift next glyph by the width of this one */
total_offset[X] += ffile->hmtx_table[glyph->myMetrics].advanceWidth *
funit_size;
#ifdef TTF_DEBUG
Debug_Info("kern(%d): <%d, %d> (%g,%g)\n", i, last_index, glyph_index,
(DBL)kern_value_x*funit_size, (DBL)kern_value_y * funit_size);
Debug_Info("lsb(%d): %g\n", i,
(DBL)ffile->hmtx_table[glyph->myMetrics].lsb * funit_size);
Debug_Info("aw(%d): %g\n", i,
(DBL)ffile->hmtx_table[glyph->myMetrics].advanceWidth *
funit_size);
#endif
}
/*
* Add to the offset of the next character the minimum spacing specified.
*/
VAddEq(total_offset, offset);
/* Link this glyph with the others in the union */
Object->Type |= (Local->Type & CHILDREN_FLAGS);
Local->Type |= IS_CHILD_OBJECT;
Local->Sibling = Object->Children;
Object->Children = Local;
last_index = glyph_index;
}
#ifdef TTF_DEBUG
Debug_Info("TTF parsing of \"%s\" from %s complete\n", text_string, filename);
#endif
/* Close the font file descriptor */
fclose(ffile->fp);
ffile->fp = NULL;
}
/*****************************************************************************
*
* FUNCTION
*
* ProcessFontFile
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* Alexander Ennzmann
*
* DESCRIPTION
*
* Read the header information about the specific font. Parse the tables
* as we come across them.
*
* CHANGES
*
* Added tests for reading manditory tables/validity checks - Jan 1996 [AED]
* Reordered table parsing to avoid lots of file seeking - Jan 1996 [AED]
*
******************************************************************************/
static FontFileInfo *ProcessFontFile(fontfilename)
char *fontfilename;
{
unsigned i;
long head_table_offset = 0;
long loca_table_offset = 0;
long maxp_table_offset = 0;
long kern_table_offset = 0;
long hhea_table_offset = 0;
long hmtx_table_offset = 0;
sfnt_OffsetTable OffsetTable;
sfnt_TableDirectory Table;
FontFileInfo *ffile;
/* Open the font file */
ffile = OpenFontFile(fontfilename);
/* We have already read all the header info, no need to do it again */
if (ffile->cmap_table_offset != 0)
{
return (ffile);
}
/*
* Read the initial directory header on the TTF. The numTables variable
* tells us how many tables are present in this file.
*/
OffsetTable.version = READFIXED(ffile->fp);
OffsetTable.numTables = READUSHORT(ffile->fp);
OffsetTable.searchRange = READUSHORT(ffile->fp);
OffsetTable.entrySelector = READUSHORT(ffile->fp);
OffsetTable.rangeShift = READUSHORT(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("OffsetTable:\n");
Debug_Info("version=%d\n", OffsetTable.version);
Debug_Info("numTables=%u\n", OffsetTable.numTables);
Debug_Info("searchRange=%u\n", OffsetTable.searchRange);
Debug_Info("entrySelector=%u\n", OffsetTable.entrySelector);
Debug_Info("rangeShift=%u\n", OffsetTable.rangeShift);
#endif
/*
* I don't know why we limit this to 40 tables, since the spec says there
* can be any number, but that's how it was when I got it. Added a warning
* just in case it ever happens in real life. [AED]
*/
if (OffsetTable.numTables > 40)
{
Warning(0.0, "More than 40 (%d) TTF Tables in %s - some info may be lost!",
OffsetTable.numTables, ffile->filename);
}
/* Process general font information and save it. */
for (i = 0; i < OffsetTable.numTables && i < 40; i++)
{
if (fread(&Table.tag, sizeof(BYTE), 4, ffile->fp) != 4)
{
Error("Error reading TrueType font file table tag\n");
}
Table.checkSum = READULONG(ffile->fp);
Table.offset = READULONG(ffile->fp);
Table.length = READULONG(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("\nTable %d:\n",i);
Debug_Info("tag=%c%c%c%c\n", Table.tag[0], Table.tag[1],
Table.tag[2], Table.tag[3]);
Debug_Info("checkSum=%u\n", Table.checkSum);
Debug_Info("offset=%u\n", Table.offset);
Debug_Info("length=%u\n", Table.length);
#endif
if (compare_tag4(Table.tag, tag_CharToIndexMap))
ffile->cmap_table_offset = Table.offset;
else if (compare_tag4(Table.tag, tag_GlyphData))
ffile->glyf_table_offset = Table.offset;
else if (compare_tag4(Table.tag, tag_FontHeader))
head_table_offset = Table.offset;
else if (compare_tag4(Table.tag, tag_IndexToLoc))
loca_table_offset = Table.offset;
else if (compare_tag4(Table.tag, tag_MaxProfile))
maxp_table_offset = Table.offset;
else if (compare_tag4(Table.tag, tag_Kerning))
kern_table_offset = Table.offset;
else if (compare_tag4(Table.tag, tag_HorizHeader))
hhea_table_offset = Table.offset;
else if (compare_tag4(Table.tag, tag_HorizMetric))
hmtx_table_offset = Table.offset;
}
if (ffile->cmap_table_offset == 0 || ffile->glyf_table_offset == 0 ||
head_table_offset == 0 || loca_table_offset == 0 ||
hhea_table_offset == 0 || hmtx_table_offset == 0 ||
maxp_table_offset == 0)
{
Error("Invalid TrueType font headers in %s\n", ffile->filename);
}
ProcessHeadTable(ffile, head_table_offset); /* Need indexToLocFormat */
if ((ffile->indexToLocFormat != 0 && ffile->indexToLocFormat != 1) ||
(ffile->unitsPerEm < 16 || ffile->unitsPerEm > 16384))
Error("Invalid TrueType font data in %s\n", ffile->filename);
ProcessMaxpTable(ffile, maxp_table_offset); /* Need numGlyphs */
if (ffile->numGlyphs <= 0)
Error("Invalid TrueType font data in %s\n", ffile->filename);
ProcessLocaTable(ffile, loca_table_offset); /* Now we can do loca_table */
ProcessHheaTable(ffile, hhea_table_offset); /* Need numberOfHMetrics */
if (ffile->numberOfHMetrics <= 0)
Error("Invalid TrueType font data in %s\n", ffile->filename);
ProcessHmtxTable(ffile, hmtx_table_offset); /* Now we can read HMetrics */
if (kern_table_offset != 0)
ProcessKernTable(ffile, kern_table_offset);
/* Return the information about this font */
return ffile;
}
/*****************************************************************************
*
* FUNCTION
*
* OpenFontFile
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* Alexander Ennzmann
*
* DESCRIPTION
*
* -
*
* CHANGES
*
* -
*
******************************************************************************/
static FontFileInfo *OpenFontFile(filename)
char *filename;
{
int i;
FontFileInfo *fontlist;
/* First look to see if we have already opened this font */
for (fontlist = TTFonts; fontlist != NULL; fontlist = fontlist->next)
if (!strcmp(filename, fontlist->filename))
break;
if (fontlist != NULL)
{
/* We have a match, use the previous information */
if ((fontlist->fp == NULL) &&
(fontlist->fp = Locate_File(fontlist->filename, READ_FILE_STRING,
".ttf", ".TTF",TRUE)) == NULL)
Error("Can't open font file.\n");
#ifdef TTF_DEBUG
else
Debug_Info("Using cached font info for %s\n", fontlist->filename);
#endif
}
else
{
/*
* We haven't looked at this font before, let's allocate a holder for the
* information and set some defaults
*/
fontlist = POV_CALLOC(1, sizeof(FontFileInfo), "FontFileInfo");
i = strlen(filename) + 1;
fontlist->filename = POV_MALLOC(i * sizeof(unsigned char), "FontFile Name");
strcpy(fontlist->filename, filename);
if ((fontlist->fp = Locate_File(fontlist->filename, READ_FILE_STRING,
".ttf", ".TTF",TRUE)) == NULL)
{
Error("Can't open font file.\n");
}
/*
* We try to choose ISO 8859-1 (Latin-1) as default for now.
* For Microsoft encodings 3, 1 is for Unicode/Latin-1,
* 3, 0 is for Non-unicode (ie symbols),
* For Macintosh encodings 1, 0 is for Roman character set.
*/
fontlist->platformID = 3;
fontlist->specificID = 1;
fontlist->next = TTFonts;
TTFonts = fontlist;
}
return fontlist;
}
void FreeFontInfo()
{
int i;
FontFileInfo *oldfont, *tempfont;
GlyphPtr glyphs, tempglyph;
for (oldfont = TTFonts; oldfont != NULL;)
{
if (oldfont->fp != NULL)
fclose(oldfont->fp);
if (oldfont->filename != NULL)
POV_FREE(oldfont->filename);
if (oldfont->loca_table != NULL)
POV_FREE(oldfont->loca_table);
if (oldfont->hmtx_table != NULL)
POV_FREE(oldfont->hmtx_table);
if (oldfont->kerning_tables.nTables != 0)
{
for (i = 0; i < oldfont->kerning_tables.nTables; i++)
{
if (oldfont->kerning_tables.tables[i].kern_pairs)
POV_FREE(oldfont->kerning_tables.tables[i].kern_pairs);
}
POV_FREE(oldfont->kerning_tables.tables);
}
for (glyphs = oldfont->glyphs; glyphs != NULL;)
{
for (i = 0; i < glyphs->header.numContours; i++)
{
POV_FREE(glyphs->contours[i].flags);
POV_FREE(glyphs->contours[i].x);
POV_FREE(glyphs->contours[i].y);
}
if (glyphs->contours != NULL)
POV_FREE(glyphs->contours);
tempglyph = glyphs;
glyphs = glyphs->next;
POV_FREE(tempglyph);
}
if (oldfont->segCount != 0)
{
POV_FREE(oldfont->endCount);
POV_FREE(oldfont->startCount);
POV_FREE(oldfont->idDelta);
POV_FREE(oldfont->idRangeOffset);
}
tempfont = oldfont;
oldfont = oldfont->next;
POV_FREE(tempfont);
}
TTFonts = NULL;
}
/* Process the font header table */
static void ProcessHeadTable(ffile, head_table_offset)
FontFileInfo *ffile;
long head_table_offset;
{
sfnt_FontHeader fontHeader;
/* Read head table */
fseek(ffile->fp, head_table_offset, SEEK_SET);
fontHeader.version = READFIXED(ffile->fp);
fontHeader.fontRevision = READFIXED(ffile->fp);
fontHeader.checkSumAdjustment = READULONG(ffile->fp);
fontHeader.magicNumber = READULONG(ffile->fp); /* should be 0x5F0F3CF5 */
fontHeader.flags = READUSHORT(ffile->fp);
fontHeader.unitsPerEm = READUSHORT(ffile->fp);
fontHeader.created.bc = READULONG(ffile->fp);
fontHeader.created.ad = READULONG(ffile->fp);
fontHeader.modified.bc = READULONG(ffile->fp);
fontHeader.modified.ad = READULONG(ffile->fp);
fontHeader.xMin = READFWORD(ffile->fp);
fontHeader.yMin = READFWORD(ffile->fp);
fontHeader.xMax = READFWORD(ffile->fp);
fontHeader.yMax = READFWORD(ffile->fp);
fontHeader.macStyle = READUSHORT(ffile->fp);
fontHeader.lowestRecPPEM = READUSHORT(ffile->fp);
fontHeader.fontDirectionHint = READSHORT(ffile->fp);
fontHeader.indexToLocFormat = READSHORT(ffile->fp);
fontHeader.glyphDataFormat = READSHORT(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("\nfontHeader:\n");
Debug_Info("version: %d\n",fontHeader.version);
Debug_Info("fontRevision: %d\n",fontHeader.fontRevision);
Debug_Info("checkSumAdjustment: %u\n",fontHeader.checkSumAdjustment);
Debug_Info("magicNumber: 0x%8X\n",fontHeader.magicNumber);
Debug_Info("flags: %u\n",fontHeader.flags);
Debug_Info("unitsPerEm: %u\n",fontHeader.unitsPerEm);
Debug_Info("created.bc: %u\n",fontHeader.created.bc);
Debug_Info("created.ad: %u\n",fontHeader.created.ad);
Debug_Info("modified.bc: %u\n",fontHeader.modified.bc);
Debug_Info("modified.ad: %u\n",fontHeader.modified.ad);
Debug_Info("xMin: %d\n",fontHeader.xMin);
Debug_Info("yMin: %d\n",fontHeader.yMin);
Debug_Info("xMax: %d\n",fontHeader.xMax);
Debug_Info("yMax: %d\n",fontHeader.yMax);
Debug_Info("macStyle: %u\n",fontHeader.macStyle);
Debug_Info("lowestRecPPEM: %u\n",fontHeader.lowestRecPPEM);
Debug_Info("fontDirectionHint: %d\n",fontHeader.fontDirectionHint);
Debug_Info("indexToLocFormat: %d\n",fontHeader.indexToLocFormat);
Debug_Info("glyphDataFormat: %d\n",fontHeader.glyphDataFormat);
#endif
if (fontHeader.magicNumber != 0x5F0F3CF5)
{
Error("Error reading TrueType font %s. Bad magic number (0x%8X)\n",
ffile->filename, fontHeader.magicNumber);
}
ffile->indexToLocFormat = fontHeader.indexToLocFormat;
ffile->unitsPerEm = fontHeader.unitsPerEm;
}
/* Determine the relative offsets of glyphs */
static void ProcessLocaTable(ffile, loca_table_offset)
FontFileInfo *ffile;
long loca_table_offset;
{
int i;
/* Move to location of table in file */
fseek(ffile->fp, loca_table_offset, SEEK_SET);
ffile->loca_table = POV_MALLOC((ffile->numGlyphs+1) * sizeof(ULONG), "ttf");
#ifdef TTF_DEBUG
Debug_Info("\nlocation table:\n");
Debug_Info("version: %s\n",(ffile->indexToLocFormat?"long":"short"));
#endif
/* Now read and save the location table */
if (ffile->indexToLocFormat == 0) /* short version */
{
for (i = 0; i < ffile->numGlyphs; i++)
{
ffile->loca_table[i] = ((ULONG)READUSHORT(ffile->fp)) << 1;
#ifdef TTF_DEBUG2
Debug_Info("loca_table[%d] @ %u\n", i, ffile->loca_table[i]);
#endif
}
}
else /* long version */
{
for (i = 0; i < ffile->numGlyphs; i++)
{
ffile->loca_table[i] = READULONG(ffile->fp);
#ifdef TTF_DEBUG2
Debug_Info("loca_table[%d] @ %u\n", i, ffile->loca_table[i]);
#endif
}
}
}
/*
* This routine determines the total number of glyphs in a TrueType file.
* Necessary so that we can allocate the proper amount of storage for the glyph
* location table.
*/
static void ProcessMaxpTable(ffile, maxp_table_offset)
FontFileInfo *ffile;
long maxp_table_offset;
{
/* Seek to the maxp table, skipping the 4 byte version number */
fseek(ffile->fp, maxp_table_offset + 4, SEEK_SET);
ffile->numGlyphs = READUSHORT(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("\nmaximum profile table:\n");
Debug_Info("numGlyphs: %u\n", ffile->numGlyphs);
#endif
}
/* Read the kerning information for a glyph */
static void ProcessKernTable(ffile, kern_table_offset)
FontFileInfo *ffile;
long kern_table_offset;
{
int i, j;
USHORT temp16;
USHORT length;
KernTables *kern_table;
kern_table = &ffile->kerning_tables;
/* Move to the beginning of the kerning table, skipping the 2 byte version */
fseek(ffile->fp, kern_table_offset + 2, SEEK_SET);
/* Read in the number of kerning tables */
kern_table->nTables = READUSHORT(ffile->fp);
kern_table->tables = NULL; /*<==[esp] added (in case nTables is zero)*/
#ifdef TTF_DEBUG
Debug_Info("\nKerning table:\n", kern_table_offset);
Debug_Info("Offset: %ld\n", kern_table_offset);
Debug_Info("Number of tables: %u\n",kern_table->nTables);
#endif
/* Don't do any more work if there isn't kerning info */
if (kern_table->nTables == 0)
return;
kern_table->tables = POV_MALLOC(kern_table->nTables * sizeof(TTKernTable),
"ProcessKernTable");
for (i = 0; i < kern_table->nTables; i++)
{
/* Read in a subtable */
temp16 = READUSHORT(ffile->fp); /* Subtable version */
length = READUSHORT(ffile->fp); /* Subtable length */
kern_table->tables[i].coverage = READUSHORT(ffile->fp); /* Coverage bits */
#ifdef TTF_DEBUG
Debug_Info("Coverage table[%d] (0x%X):", i, kern_table->tables[i].coverage);
Debug_Info(" type %u", (kern_table->tables[i].coverage >> 8));
Debug_Info(" %s", (kern_table->tables[i].coverage & KERN_HORIZONTAL ?
"Horizontal" : "Vertical" ));
Debug_Info(" %s values", (kern_table->tables[i].coverage & KERN_MINIMUM ?
"Minimum" : "Kerning" ));
Debug_Info("%s", (kern_table->tables[i].coverage & KERN_CROSS_STREAM ?
" Cross-stream" : "" ));
Debug_Info("%s\n", (kern_table->tables[i].coverage & KERN_OVERRIDE ?
" Override" : "" ));
#endif
kern_table->tables[i].kern_pairs = NULL; /*<==[esp] added*/
kern_table->tables[i].nPairs = 0; /*<==[esp] added*/
if ((kern_table->tables[i].coverage >> 8) == 0)
{
/* Can only handle format 0 kerning subtables */
kern_table->tables[i].nPairs = READUSHORT(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("entries in table[%d]: %d\n", i, kern_table->tables[i].nPairs);
#endif
temp16 = READUSHORT(ffile->fp); /* searchRange */
temp16 = READUSHORT(ffile->fp); /* entrySelector */
temp16 = READUSHORT(ffile->fp); /* rangeShift */
kern_table->tables[i].kern_pairs =
POV_MALLOC(kern_table->tables[i].nPairs * sizeof(KernData), "Kern Pairs");
for (j = 0; j < kern_table->tables[i].nPairs; j++)
{
/* Read in a kerning pair */
kern_table->tables[i].kern_pairs[j].left = READUSHORT(ffile->fp);
kern_table->tables[i].kern_pairs[j].right = READUSHORT(ffile->fp);
kern_table->tables[i].kern_pairs[j].value = READFWORD(ffile->fp);
#ifdef TTF_DEBUG2
Debug_Info("Kern pair: <%d,%d> = %d\n",
(int)kern_table->tables[i].kern_pairs[j].left,
(int)kern_table->tables[i].kern_pairs[j].right,
(int)kern_table->tables[i].kern_pairs[j].value);
#endif
}
}
else
{
#ifdef TTF_DEBUG2
Warning(0.0, "Cannot handle format %u kerning data\n",
(kern_table->tables[i].coverage >> 8));
#endif
/*
* Seek to the end of this table, excluding the length of the version,
* length, and coverage USHORTs, which we have already read.
*/
fseek(ffile->fp, (long)(length - 6), SEEK_CUR);
kern_table->tables[i].nPairs = 0;
}
}
}
/*
* This routine determines the total number of horizontal metrics.
*/
static void ProcessHheaTable(ffile, hhea_table_offset)
FontFileInfo *ffile;
long hhea_table_offset;
{
#ifdef TTF_DEBUG
sfnt_HorizHeader horizHeader;
/* Seek to the hhea table */
fseek(ffile->fp, hhea_table_offset, SEEK_SET);
horizHeader.version = READFIXED(ffile->fp);
horizHeader.Ascender = READFWORD(ffile->fp);
horizHeader.Descender = READFWORD(ffile->fp);
horizHeader.LineGap = READFWORD(ffile->fp);
horizHeader.advanceWidthMax = READUFWORD(ffile->fp);
horizHeader.minLeftSideBearing = READFWORD(ffile->fp);
horizHeader.minRightSideBearing = READFWORD(ffile->fp);
horizHeader.xMaxExtent = READFWORD(ffile->fp);
horizHeader.caretSlopeRise = READSHORT(ffile->fp);
horizHeader.caretSlopeRun = READSHORT(ffile->fp);
horizHeader.reserved1 = READSHORT(ffile->fp);
horizHeader.reserved2 = READSHORT(ffile->fp);
horizHeader.reserved3 = READSHORT(ffile->fp);
horizHeader.reserved4 = READSHORT(ffile->fp);
horizHeader.reserved5 = READSHORT(ffile->fp);
horizHeader.metricDataFormat = READSHORT(ffile->fp);
#else
/* Seek to the hhea table, skipping all that stuff we don't need */
fseek(ffile->fp, hhea_table_offset + 34, SEEK_SET);
#endif
ffile->numberOfHMetrics = READUSHORT(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("\nhorizontal header table:\n");
Debug_Info("Ascender: %d\n",horizHeader.Ascender);
Debug_Info("Descender: %d\n",horizHeader.Descender);
Debug_Info("LineGap: %d\n",horizHeader.LineGap);
Debug_Info("advanceWidthMax: %d\n",horizHeader.advanceWidthMax);
Debug_Info("minLeftSideBearing: %d\n",horizHeader.minLeftSideBearing);
Debug_Info("minRightSideBearing: %d\n",horizHeader.minRightSideBearing);
Debug_Info("xMaxExtent: %d\n",horizHeader.xMaxExtent);
Debug_Info("caretSlopeRise: %d\n",horizHeader.caretSlopeRise);
Debug_Info("caretSlopeRun: %d\n",horizHeader.caretSlopeRun);
Debug_Info("metricDataFormat: %d\n",horizHeader.metricDataFormat);
Debug_Info("numberOfHMetrics: %d\n",ffile->numberOfHMetrics);
#endif
}
static void ProcessHmtxTable (ffile, hmtx_table_offset)
FontFileInfo *ffile;
long hmtx_table_offset;
{
int i;
longHorMetric *metric;
uFWord lastAW = 0; /* Just to quiet warnings. */
fseek(ffile->fp, hmtx_table_offset, SEEK_SET);
ffile->hmtx_table = POV_MALLOC(ffile->numGlyphs*sizeof(longHorMetric), "ttf");
/*
* Read in the total glyph width, and the left side offset. There is
* guaranteed to be at least one longHorMetric entry in this table to
* set the advanceWidth for the subsequent lsb entries.
*/
for (i=0, metric=ffile->hmtx_table; i < ffile->numberOfHMetrics; i++,metric++)
{
lastAW = metric->advanceWidth = READUFWORD(ffile->fp);
metric->lsb = READFWORD(ffile->fp);
}
/* Read in the remaining left offsets */
for (; i < ffile->numGlyphs; i++, metric++)
{
metric->advanceWidth = lastAW;
metric->lsb = READFWORD(ffile->fp);
}
}
/*****************************************************************************
*
* FUNCTION
*
* ProcessCharacter
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* POV-Ray Team
*
* DESCRIPTION
*
* Finds the glyph description for the current character.
*
* CHANGES
*
* -
*
******************************************************************************/
static GlyphPtr ProcessCharacter(ffile, search_char, glyph_index)
FontFileInfo *ffile;
unsigned int search_char;
unsigned int *glyph_index;
{
GlyphPtr glyph;
/* See if we have already processed this glyph */
for (glyph = ffile->glyphs; glyph != NULL; glyph = glyph->next)
{
if (glyph->c == search_char)
{
/* Found it, no need to do any more work */
#ifdef TTF_DEBUG
Debug_Info("Cached glyph: %c/%u\n",(char)search_char,glyph->glyph_index);
#endif
*glyph_index = glyph->glyph_index;
return glyph;
}
}
*glyph_index = ProcessCharMap(ffile, search_char);
if (*glyph_index == 0)
Warning(0.0, "Character %d (0x%X) not found in %s\n", (BYTE)search_char,
search_char, ffile->filename);
/* See if we have already processed this glyph (using the glyph index) */
for (glyph = ffile->glyphs; glyph != NULL; glyph = glyph->next)
{
if (glyph->glyph_index == *glyph_index)
{
/* Found it, no need to do any more work */
#ifdef TTF_DEBUG
Debug_Info("Cached glyph: %c/%u\n",(char)search_char,glyph->glyph_index);
#endif
*glyph_index = glyph->glyph_index;
return glyph;
}
}
glyph = ExtractGlyphInfo(ffile, glyph_index, search_char);
/* Add this glyph to the ones we already know about */
glyph->next = ffile->glyphs;
ffile->glyphs = glyph;
/* Glyph is all built */
return glyph;
}
/*****************************************************************************
*
* FUNCTION
*
* ProcessCharMap
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* POV-Ray Team
*
* DESCRIPTION
*
* Find the character mapping for 'search_char'. We should really know
* which character set we are using (ie ISO 8859-1, Mac, Unicode, etc).
* Search char should really be a USHORT to handle double byte systems.
*
* CHANGES
*
* 961120 esp Added check to allow Macintosh encodings to pass
*
******************************************************************************/
static USHORT ProcessCharMap(ffile, search_char)
FontFileInfo *ffile;
unsigned int search_char;
{
long old_table_offset;
long entry_offset;
sfnt_platformEntry cmapEntry;
sfnt_mappingTable encodingTable;
int i, table_count;
/* Move to the start of the character map, skipping the 2 byte version */
fseek(ffile->fp, ffile->cmap_table_offset + 2, SEEK_SET);
table_count = READUSHORT(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("table_count=%d\n", table_count);
#endif
/*
* Search the tables until we find the glyph index for the search character.
* Just return the first one we find...
*/
for (i = 0; i < table_count; i++)
{
cmapEntry.platformID = READUSHORT(ffile->fp);
cmapEntry.specificID = READUSHORT(ffile->fp);
cmapEntry.offset = READULONG(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("cmapEntry: platformID=%d\n", cmapEntry.platformID);
Debug_Info("cmapEntry: specificID=%d\n", cmapEntry.specificID);
Debug_Info("cmapEntry: offset=%d\n", cmapEntry.offset);
#endif
/*
* Check if this is the encoding table we want to use. We can't
* be very picky, since we don't know what the user really wants.
* It would be nice to know what we are really looking for.
* Allow Type 1 platform as well (Macintosh) [esp]
*/
if ((ffile->platformID != cmapEntry.platformID) && (1 != cmapEntry.platformID))
{
continue;
}
entry_offset = cmapEntry.offset;
old_table_offset = ftell(ffile->fp); /* Save the current position */
fseek(ffile->fp, ffile->cmap_table_offset + entry_offset, SEEK_SET);
encodingTable.format = READUSHORT(ffile->fp);
encodingTable.length = READUSHORT(ffile->fp);
encodingTable.version = READUSHORT(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("Encoding table, format: %u, length: %u, version: %u\n",
encodingTable.format, encodingTable.length, encodingTable.version);
#endif
if (encodingTable.format == 0)
{
/*
* Translation is simple - add 'entry_char' to the start of the
* table and grab what's there.
*/
#ifdef TTF_DEBUG
Debug_Info("Apple standard index mapping\n");
#endif
return(ProcessFormat0Glyph(ffile, search_char));
}
#if 0 /* Want to get the rest of these working first */
else if (encodingTable.format == 2)
{
/* Used for multi-byte character encoding (Chinese, Japanese, etc) */
#ifdef TTF_DEBUG
Debug_Info("High-byte index mapping\n");
#endif
return(ProcessFormat2Glyph(ffile, search_char));
}
#endif
else if (encodingTable.format == 4)
{
/* Microsoft UGL encoding */
#ifdef TTF_DEBUG
Debug_Info("Microsoft standard index mapping\n");
#endif
return(ProcessFormat4Glyph(ffile, search_char));
}
else if (encodingTable.format == 6)
{
#ifdef TTF_DEBUG
Debug_Info("Trimmed table mapping\n");
#endif
return(ProcessFormat6Glyph(ffile, search_char));
}
#ifdef TTF_DEBUG
else
Debug_Info("Unsupported index mapping format: %u\n",
encodingTable.format);
#endif
/* Go to the next table entry if we didn't find a match */
fseek(ffile->fp, old_table_offset, SEEK_SET);
}
/*
* No character mapping was found - very odd, we should really have had the
* character in at least one table. Perhaps getting here means we didn't
* have any character mapping tables. '0' means no mapping.
*/
return 0;
}
/*****************************************************************************
*
* FUNCTION
*
* ProcessFormat0Glyph
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* POV-Ray Team
*
* DESCRIPTION
*
* This handles the Apple standard index mapping for glyphs.
* The file pointer must be pointing immediately after the version entry in the
* encoding table for the next two functions to work.
*
* CHANGES
*
* -
*
******************************************************************************/
static USHORT ProcessFormat0Glyph(ffile, search_char)
FontFileInfo *ffile;
unsigned int search_char;
{
BYTE temp_index;
fseek(ffile->fp, (long)search_char, SEEK_CUR);
if (fread(&temp_index, 1, 1, ffile->fp) != 1) /* Each index is 1 byte */
{
Error("Error reading TrueType font file at line %d, %s\n",
__LINE__, __FILE__);
}
return (USHORT)(temp_index);
}
/*****************************************************************************
*
* FUNCTION
*
* ProcessFormat4Glyph
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* POV-Ray Team
*
* DESCRIPTION
*
* This handles the Microsoft standard index mapping for glyph tables
*
* CHANGES
*
* Mar 26, 1996: Cache segment info rather than read each time. [AED]
*
******************************************************************************/
static USHORT ProcessFormat4Glyph(ffile, search_char)
FontFileInfo *ffile;
unsigned int search_char;
{
int i;
unsigned int glyph_index = 0; /* Set the glyph index to "not present" */
/*
* If this is the first time we are here, read all of the segment headers,
* and save them for later calls to this function, rather than seeking and
* mallocing for each character
*/
if (ffile->segCount == 0)
{
USHORT temp16;
ffile->segCount = READUSHORT(ffile->fp) >> 1;
ffile->searchRange = READUSHORT(ffile->fp);
ffile->entrySelector = READUSHORT(ffile->fp);
ffile->rangeShift = READUSHORT(ffile->fp);
/* Now allocate and read in the segment arrays */
ffile->endCount = POV_MALLOC(ffile->segCount * sizeof(USHORT), "ttf");
ffile->startCount = POV_MALLOC(ffile->segCount * sizeof(USHORT), "ttf");
ffile->idDelta = POV_MALLOC(ffile->segCount * sizeof(USHORT), "ttf");
ffile->idRangeOffset = POV_MALLOC(ffile->segCount * sizeof(USHORT), "ttf");
for (i = 0; i < ffile->segCount; i++)
{
ffile->endCount[i] = READUSHORT(ffile->fp);
}
temp16 = READUSHORT(ffile->fp); /* Skip over 'reservedPad' */
for (i = 0; i < ffile->segCount; i++)
{
ffile->startCount[i] = READUSHORT(ffile->fp);
}
for (i = 0; i < ffile->segCount; i++)
{
ffile->idDelta[i] = READUSHORT(ffile->fp);
}
/* location of start of idRangeOffset */
ffile->glyphIDoffset = ftell(ffile->fp);
for (i = 0; i < ffile->segCount; i++)
{
ffile->idRangeOffset[i] = READUSHORT(ffile->fp);
}
}
/* Search the segments for our character */
glyph_search:
for (i = 0; i < ffile->segCount; i++)
{
if (search_char <= ffile->endCount[i])
{
if (search_char >= ffile->startCount[i])
{
/* Found correct range for this character */
if (ffile->idRangeOffset[i] == 0)
{
glyph_index = search_char + ffile->idDelta[i];
}
else
{
/*
* Alternate encoding of glyph indices, relies on a quite unusual way
* of storing the offsets. We need the *2s because we are talking
* about addresses of shorts and not bytes.
*
* (glyphIDoffset + i*2 + idRangeOffset[i]) == &idRangeOffset[i]
*/
fseek(ffile->fp, ffile->glyphIDoffset + 2*i + ffile->idRangeOffset[i]+
2*(search_char - ffile->startCount[i]), SEEK_SET);
glyph_index = READUSHORT(ffile->fp);
if (glyph_index != 0)
glyph_index = glyph_index + ffile->idDelta[i];
}
}
break;
}
}
/*
* If we haven't found the character yet, and this is the first time to
* search the tables, try looking in the Unicode user space, since this
* is the location Microsoft recommends for symbol characters like those
* in wingdings and dingbats.
*/
if (glyph_index == 0 && search_char < 0x100)
{
search_char += 0xF000;
#ifdef TTF_DEBUG
Debug_Info("Looking for glyph in Unicode user space (0x%X)\n", search_char);
#endif
goto glyph_search;
}
/* Deallocate the memory we used for the segment arrays */
return glyph_index;
}
/*****************************************************************************
*
* FUNCTION
*
* ProcessFormat6Glyph
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* POV-Ray Team
*
* DESCRIPTION
*
* This handles the trimmed table mapping for glyphs.
*
* CHANGES
*
* -
*
******************************************************************************/
static USHORT ProcessFormat6Glyph(ffile, search_char)
FontFileInfo *ffile;
unsigned int search_char;
{
USHORT firstCode, entryCount;
BYTE glyph_index;
firstCode = READUSHORT(ffile->fp);
entryCount = READUSHORT(ffile->fp);
if (search_char >= firstCode && search_char < firstCode + entryCount)
{
fseek(ffile->fp, (long)(search_char - firstCode), SEEK_CUR);
glyph_index = READUSHORT(ffile->fp);
}
else
glyph_index = 0;
return glyph_index;
}
/*****************************************************************************
*
* FUNCTION
*
* ExtractGlyphInfo
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* POV-Ray Team
*
* DESCRIPTION
*
* Change TTF outline information for the glyph(s) into a useful format
*
* CHANGES
*
* -
*
******************************************************************************/
static GlyphPtr ExtractGlyphInfo(ffile, glyph_index, c)
FontFileInfo *ffile;
unsigned int *glyph_index;
unsigned int c;
{
GlyphOutline *ttglyph;
GlyphPtr glyph;
ttglyph = ExtractGlyphOutline(ffile, glyph_index, c);
/*
* Convert the glyph outline information from TrueType layout into a more
* easily processed format
*/
glyph = ConvertOutlineToGlyph(ffile, ttglyph);
glyph->c = c;
glyph->glyph_index = *glyph_index;
glyph->myMetrics = ttglyph->myMetrics;
/* Free up outline information */
if (ttglyph)
{
if (ttglyph->y) POV_FREE(ttglyph->y);
if (ttglyph->x) POV_FREE(ttglyph->x);
if (ttglyph->endPoints) POV_FREE(ttglyph->endPoints);
if (ttglyph->flags) POV_FREE(ttglyph->flags);
POV_FREE(ttglyph);
}
return glyph;
}
/*****************************************************************************
*
* FUNCTION
*
* ExtractGlyphOutline
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* POV-Ray Team
*
* DESCRIPTION
*
* Read the contour information for a specific glyph. This has to be a
* separate routine from ExtractGlyphInfo because we call it recurisvely
* for multiple component glyphs.
*
* CHANGES
*
* -
*
******************************************************************************/
static GlyphOutline *ExtractGlyphOutline(ffile, glyph_index, c)
FontFileInfo *ffile;
unsigned int *glyph_index;
unsigned int c;
{
int i;
USHORT n;
SHORT nc;
GlyphOutline *ttglyph;
ttglyph = POV_CALLOC(1, sizeof(GlyphOutline), "ttf");
ttglyph->myMetrics = *glyph_index;
/* Have to treat space characters differently */
if (c != ' ')
{
fseek(ffile->fp, ffile->glyf_table_offset+ffile->loca_table[*glyph_index],
SEEK_SET);
ttglyph->header.numContours = READSHORT(ffile->fp);
ttglyph->header.xMin = READFWORD(ffile->fp); /* These may be */
ttglyph->header.yMin = READFWORD(ffile->fp); /* unreliable in */
ttglyph->header.xMax = READFWORD(ffile->fp); /* some fonts. */
ttglyph->header.yMax = READFWORD(ffile->fp);
}
#ifdef TTF_DEBUG
Debug_Info("ttglyph->header:\n");
Debug_Info("glyph_index=%d\n", *glyph_index);
Debug_Info("loca_table[%d]=%d\n",*glyph_index,ffile->loca_table[*glyph_index]);
Debug_Info("numContours=%d\n", ttglyph->header.numContours);
#endif
nc = ttglyph->header.numContours;
/*
* A positive number of contours means a regular glyph, with possibly
* several separate line segments making up the outline.
*/
if (nc > 0)
{
FWord coord;
BYTE flag, repeat_count;
USHORT temp16;
/* Grab the contour endpoints */
ttglyph->endPoints = POV_MALLOC(nc * sizeof(USHORT), "ttf");
for (i = 0; i < nc; i++)
{
ttglyph->endPoints[i] = READUSHORT(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("endPoints[%d]=%d\n", i, ttglyph->endPoints[i]);
#endif
}
/* Skip over the instructions */
temp16 = READUSHORT(ffile->fp);
fseek(ffile->fp, temp16, SEEK_CUR);
/* Determine the number of points making up this glyph */
n = ttglyph->numPoints = ttglyph->endPoints[nc - 1] + 1;
#ifdef TTF_DEBUG
Debug_Info("numPoints=%d\n", ttglyph->numPoints);
#endif
/* Read the flags */
ttglyph->flags = POV_MALLOC(n * sizeof(BYTE), "ttf");
for (i = 0; i < ttglyph->numPoints; i++)
{
if (fread(&ttglyph->flags[i], sizeof(BYTE), 1, ffile->fp) != 1)
{
Error("Error reading TrueType font file at line %d, %s\n",
__LINE__, __FILE__);
}
if (ttglyph->flags[i] & REPEAT_FLAGS)
{
if (fread(&repeat_count, sizeof(BYTE), 1, ffile->fp) != 1)
{
Error("Error reading TrueType font file at line %d, %s\n",
__LINE__, __FILE__);
}
for (; repeat_count > 0; repeat_count--, i++)
{
ttglyph->flags[i + 1] = ttglyph->flags[i];
}
}
}
/* Read the coordinate vectors */
ttglyph->x = POV_MALLOC(n * sizeof(DBL), "ttf");
ttglyph->y = POV_MALLOC(n * sizeof(DBL), "ttf");
coord = 0;
for (i = 0; i < ttglyph->numPoints; i++)
{
/* Read each x coordinate */
flag = ttglyph->flags[i];
if (flag & XSHORT)
{
BYTE temp8;
if (fread(&temp8, 1, 1, ffile->fp) != 1)
{
Error("Error reading TrueType font file at line %d, %s\n",
__LINE__, __FILE__);
}
if (flag & SHORT_X_IS_POS)
coord += temp8;
else
coord -= temp8;
}
else if (!(flag & NEXT_X_IS_ZERO))
{
coord += READSHORT(ffile->fp);
}
/* Find our own maximum and minimum x coordinates */
if (coord > ttglyph->header.xMax)
ttglyph->header.xMax = coord;
if (coord < ttglyph->header.xMin)
ttglyph->header.xMin = coord;
ttglyph->x[i] = (DBL)coord / (DBL)ffile->unitsPerEm;
}
coord = 0;
for (i = 0; i < ttglyph->numPoints; i++)
{
/* Read each y coordinate */
flag = ttglyph->flags[i];
if (flag & YSHORT)
{
BYTE temp8;
if (fread(&temp8, 1, 1, ffile->fp) != 1)
{
Error("Error reading TrueType font file at line %d, %s\n",
__LINE__, __FILE__);
}
if (flag & SHORT_Y_IS_POS)
coord += temp8;
else
coord -= temp8;
}
else if (!(flag & NEXT_Y_IS_ZERO))
{
coord += READSHORT(ffile->fp);
}
/* Find out our own maximum and minimum y coordinates */
if (coord > ttglyph->header.yMax)
ttglyph->header.yMax = coord;
if (coord < ttglyph->header.yMin)
ttglyph->header.yMin = coord;
ttglyph->y[i] = (DBL)coord / (DBL)ffile->unitsPerEm;
}
}
/*
* A negative number for numContours means that this glyph is
* made up of several separate glyphs.
*/
else if (nc < 0)
{
USHORT flags;
ttglyph->header.numContours = 0;
ttglyph->numPoints = 0;
do
{
GlyphOutline *sub_ttglyph;
unsigned int sub_glyph_index;
long current_pos;
SHORT arg1, arg2;
DBL xoff = 0, yoff = 0;
DBL xscale = 1, yscale = 1;
DBL scale01 = 0, scale10 = 0;
USHORT n2;
SHORT nc2;
flags = READUSHORT(ffile->fp);
sub_glyph_index = READUSHORT(ffile->fp);
#ifdef TTF_DEBUG
Debug_Info("sub_glyph %d: ", sub_glyph_index);
#endif
if (flags & ARG_1_AND_2_ARE_WORDS)
{
#ifdef TTF_DEBUG
Debug_Info("ARG_1_AND_2_ARE_WORDS ");
#endif
arg1 = READSHORT(ffile->fp);
arg2 = READSHORT(ffile->fp);
}
else
{
arg1 = READUSHORT(ffile->fp);
arg2 = arg1 & 0xFF;
arg1 = (arg1 >> 8) & 0xFF;
}
#ifdef TTF_DEBUG
if (flags & ROUND_XY_TO_GRID)
{
Debug_Info("ROUND_XY_TO_GRID ");
}
if (flags & MORE_COMPONENTS)
{
Debug_Info("MORE_COMPONENTS ");
}
#endif
if (flags & WE_HAVE_A_SCALE)
{
xscale = yscale = (DBL)READSHORT(ffile->fp)/0x4000;
#ifdef TTF_DEBUG
Debug_Info("WE_HAVE_A_SCALE ");
Debug_Info("xscale = %lf\t", xscale);
Debug_Info("scale01 = %lf\n", scale01);
Debug_Info("scale10 = %lf\t", scale10);
Debug_Info("yscale = %lf\n", yscale);
#endif
}
else if (flags & WE_HAVE_AN_X_AND_Y_SCALE)
{
xscale = (DBL)READSHORT(ffile->fp)/0x4000;
yscale = (DBL)READSHORT(ffile->fp)/0x4000;
#ifdef TTF_DEBUG
Debug_Info("WE_HAVE_AN_X_AND_Y_SCALE ");
Debug_Info("xscale = %lf\t", xscale);
Debug_Info("scale01 = %lf\n", scale01);
Debug_Info("scale10 = %lf\t", scale10);
Debug_Info("yscale = %lf\n", yscale);
#endif
}
else if (flags & WE_HAVE_A_TWO_BY_TWO)
{
xscale = (DBL)READSHORT(ffile->fp)/0x4000;
scale01 = (DBL)READSHORT(ffile->fp)/0x4000;
scale10 = (DBL)READSHORT(ffile->fp)/0x4000;
yscale = (DBL)READSHORT(ffile->fp)/0x4000;
#ifdef TTF_DEBUG
Debug_Info("WE_HAVE_A_TWO_BY_TWO ");
Debug_Info("xscale = %lf\t", xscale);
Debug_Info("scale01 = %lf\n", scale01);
Debug_Info("scale10 = %lf\t", scale10);
Debug_Info("yscale = %lf\n", yscale);
#endif
}
if (flags & ARGS_ARE_XY_VALUES)
{
xoff = (DBL)arg1 / ffile->unitsPerEm;
yoff = (DBL)arg2 / ffile->unitsPerEm;
#ifdef TTF_DEBUG
Debug_Info("ARGS_ARE_XY_VALUES ");
Debug_Info("\narg1 = %d xoff = %lf\t", arg1, xoff);
Debug_Info("arg2 = %d yoff = %lf\n", arg2, yoff);
#endif
}
else /* until I understand how this method works... */
{
Warning(0.0, "Can't handle part of glyph %d (0x%X).\n", c, c);
continue;
}
if (flags & USE_MY_METRICS)
{
#ifdef TTF_DEBUG
Debug_Info("USE_MY_METRICS ");
#endif
ttglyph->myMetrics = sub_glyph_index;
}
current_pos = ftell(ffile->fp);
sub_ttglyph = ExtractGlyphOutline(ffile, &sub_glyph_index, c);
fseek(ffile->fp, current_pos, SEEK_SET);
if ((nc2 = sub_ttglyph->header.numContours) == 0)
continue;
nc = ttglyph->header.numContours;
n = ttglyph->numPoints;
n2 = sub_ttglyph->numPoints;
ttglyph->endPoints = POV_REALLOC(ttglyph->endPoints,
(nc + nc2) * sizeof(USHORT), "ttf");
ttglyph->flags = POV_REALLOC(ttglyph->flags, (n+n2)*sizeof(BYTE), "ttf");
ttglyph->x = POV_REALLOC(ttglyph->x, (n + n2) * sizeof(DBL), "ttf");
ttglyph->y = POV_REALLOC(ttglyph->y, (n + n2) * sizeof(DBL), "ttf");
/* Add the sub glyph info to the end of the current glyph */
ttglyph->header.numContours += nc2;
ttglyph->numPoints += n2;
for (i = 0; i < nc2; i++)
{
ttglyph->endPoints[i + nc] = sub_ttglyph->endPoints[i] + n;
#ifdef TTF_DEBUG
Debug_Info("endPoints[%d]=%d\n", i + nc, ttglyph->endPoints[i + nc]);
#endif
}
for (i = 0; i < n2; i++)
{
#ifdef TTF_DEBUG
Debug_Info("x[%d]=%lf\t", i, sub_ttglyph->x[i]);
Debug_Info("y[%d]=%lf\n", i, sub_ttglyph->y[i]);
#endif
ttglyph->flags[i + n] = sub_ttglyph->flags[i];
ttglyph->x[i + n] = xscale * sub_ttglyph->x[i] +
scale01 * sub_ttglyph->y[i] + xoff;
ttglyph->y[i + n] = scale10 * sub_ttglyph->x[i] +
yscale * sub_ttglyph->y[i] + yoff;
#ifdef TTF_DEBUG
Debug_Info("x[%d]=%lf\t", i+n, ttglyph->x[i+n]);
Debug_Info("y[%d]=%lf\n", i+n, ttglyph->y[i+n]);
#endif
if (ttglyph->x[i + n] < ttglyph->header.xMin)
ttglyph->header.xMin = ttglyph->x[i + n];
if (ttglyph->x[i + n] > ttglyph->header.xMax)
ttglyph->header.xMax = ttglyph->x[i + n];
if (ttglyph->y[i + n] < ttglyph->header.yMin)
ttglyph->header.yMin = ttglyph->y[i + n];
if (ttglyph->y[i + n] > ttglyph->header.yMax)
ttglyph->header.yMax = ttglyph->y[i + n];
}
/* Free up the sub glyph outline information */
if (sub_ttglyph->y) POV_FREE(sub_ttglyph->y);
if (sub_ttglyph->x) POV_FREE(sub_ttglyph->x);
if (sub_ttglyph->endPoints) POV_FREE(sub_ttglyph->endPoints);
if (sub_ttglyph->flags) POV_FREE(sub_ttglyph->flags);
POV_FREE(sub_ttglyph);
} while (flags & MORE_COMPONENTS);
}
#ifdef TTF_DEBUG
Debug_Info("xMin=%d\n",ttglyph->header.xMin);
Debug_Info("yMin=%d\n",ttglyph->header.yMin);
Debug_Info("xMax=%d\n",ttglyph->header.xMax);
Debug_Info("yMax=%d\n",ttglyph->header.yMax);
#endif
return ttglyph;
}
/*****************************************************************************
*
* FUNCTION
*
* ConvertOutlineToGlyph
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* POV-Ray Team
*
* DESCRIPTION
*
* Transform a glyph from TrueType storage format to something a little easier
* to manage.
*
* CHANGES
*
* -
*
******************************************************************************/
static GlyphPtr ConvertOutlineToGlyph(ffile, ttglyph)
FontFileInfo *ffile;
GlyphOutline *ttglyph;
{
GlyphPtr glyph;
DBL *temp_x, *temp_y;
BYTE *temp_f;
USHORT i, j, last_j;
/* Create storage for this glyph */
glyph = POV_MALLOC(sizeof(Glyph), "ttf");
if (ttglyph->header.numContours > 0)
{
glyph->contours = POV_MALLOC(ttglyph->header.numContours * sizeof(Contour), "ttf");
}
else
{
glyph->contours = NULL;
}
/* Copy sizing information about this glyph */
memcpy(&glyph->header, &ttglyph->header, sizeof(GlyphHeader));
/* Keep track of the size for this glyph */
glyph->unitsPerEm = ffile->unitsPerEm;
/* Now copy the vertex information into the contours */
for (i = 0, last_j = 0; i < (USHORT) ttglyph->header.numContours; i++)
{
/* Figure out number of points in contour */
j = ttglyph->endPoints[i] - last_j + 1;
/* Copy the coordinate information into the glyph */
temp_x = POV_MALLOC((j + 1) * sizeof(DBL), "ttf");
temp_y = POV_MALLOC((j + 1) * sizeof(DBL), "ttf");
temp_f = POV_MALLOC((j + 1) * sizeof(BYTE), "ttf");
memcpy(temp_x, &ttglyph->x[last_j], j * sizeof(DBL));
memcpy(temp_y, &ttglyph->y[last_j], j * sizeof(DBL));
memcpy(temp_f, &ttglyph->flags[last_j], j * sizeof(BYTE));
temp_x[j] = ttglyph->x[last_j];
temp_y[j] = ttglyph->y[last_j];
temp_f[j] = ttglyph->flags[last_j];
/* Figure out if this is an inside or outside contour */
glyph->contours[i].inside_flag = 0;
/* Plug in the reset of the contour components into the glyph */
glyph->contours[i].count = j;
glyph->contours[i].x = temp_x;
glyph->contours[i].y = temp_y;
glyph->contours[i].flags = temp_f;
/*
* Set last_j to point to the beginning of the next contour's coordinate
* information
*/
last_j = ttglyph->endPoints[i] + 1;
}
/* Show statistics about this glyph */
#ifdef TTF_DEBUG
Debug_Info("Number of contours: %u\n", glyph->header.numContours);
Debug_Info("X extent: [%f, %f]\n",
(DBL)glyph->header.xMin / (DBL)ffile->unitsPerEm,
(DBL)glyph->header.xMax / (DBL)ffile->unitsPerEm);
Debug_Info("Y extent: [%f, %f]\n",
(DBL)glyph->header.yMin / (DBL)ffile->unitsPerEm,
(DBL)glyph->header.yMax / (DBL)ffile->unitsPerEm);
Debug_Info("Converted coord list(%d):\n", (int)glyph->header.numContours);
for (i=0;i<(USHORT)glyph->header.numContours;i++)
{
for (j=0;j<=glyph->contours[i].count;j++)
Debug_Info(" %c[%f, %f]\n",
(glyph->contours[i].flags[j] & ONCURVE ? '*' : ' '),
glyph->contours[i].x[j], glyph->contours[i].y[j]);
Debug_Info("\n");
}
#endif
return glyph;
}
/* Test to see if "point" is inside the splined polygon "points". */
static int Inside_Glyph(x, y, glyph)
double x;
double y;
GlyphPtr glyph;
{
int i, j, k, n, n1, crossings;
int qi, ri, qj, rj;
Contour *contour;
double xt[3], yt[3], roots[2];
DBL *xv, *yv;
double x0, x1, x2, t;
double y0, y1, y2;
double m, b, xc;
BYTE *fv;
crossings = 0;
n = glyph->header.numContours;
contour = glyph->contours;
for (i = 0; i < n; i++)
{
xv = contour[i].x;
yv = contour[i].y;
fv = contour[i].flags;
x0 = xv[0];
y0 = yv[0];
n1 = contour[i].count;
for (j = 1; j <= n1; j++)
{
x1 = xv[j];
y1 = yv[j];
if (fv[j] & ONCURVE)
{
/* Straight line - first set up for the next */
/* Now do the crossing test */
qi = ri = qj = rj = 0;
if (y0 == y1)
goto end_line_test;
/* if (fabs((y - y0) / (y1 - y0)) < EPSILON) goto end_line_test; */
if (y0 < y)
qi = 1;
if (y1 < y)
qj = 1;
if (qi == qj)
goto end_line_test;
if (x0 > x)
ri = 1;
if (x1 > x)
rj = 1;
if (ri & rj)
{
crossings++;
goto end_line_test;
}
if ((ri | rj) == 0)
goto end_line_test;
m = (y1 - y0) / (x1 - x0);
b = (y1 - y) - m * (x1 - x);
if ((b / m) < EPSILON)
{
crossings++;
}
end_line_test:
x0 = x1;
y0 = y1;
}
else
{
if (j == n1)
{
x2 = xv[0];
y2 = yv[0];
}
else
{
x2 = xv[j + 1];
y2 = yv[j + 1];
if (!(fv[j + 1] & ONCURVE))
{
/*
* Parabola with far end floating - readjust the far end so that it
* is on the curve.
*/
x2 = 0.5 * (x1 + x2);
y2 = 0.5 * (y1 + y2);
}
}
/* only test crossing when y is in the range */
/* this should also help saving some computations */
if (((y0 < y) && (y1 < y) && (y2 < y)) ||
((y0 > y) && (y1 > y) && (y2 > y)))
goto end_curve_test;
yt[0] = y0 - 2.0 * y1 + y2;
yt[1] = 2.0 * (y1 - y0);
yt[2] = y0 - y;
k = solve_quad(yt, roots, 0.0, 1.0);
for (ri = 0; ri < k;) {
if (roots[ri] <= EPSILON) {
/* if y actually is not in range, discard the root */
if (((y <= y0) && (y < y1)) || ((y >= y0) && (y > y1))) {
k--;
if (k > ri)
roots[ri] = roots[ri+1];
continue;
}
}
else if (roots[ri] >= (1.0 - EPSILON)) {
/* if y actually is not in range, discard the root */
if (((y < y2) && (y < y1)) || ((y > y2) && (y > y1))) {
k--;
if (k > ri)
roots[ri] = roots[ri+1];
continue;
}
}
ri++;
}
if (k > 0)
{
xt[0] = x0 - 2.0 * x1 + x2;
xt[1] = 2.0 * (x1 - x0);
xt[2] = x0;
t = roots[0];
xc = (xt[0] * t + xt[1]) * t + xt[2];
if (xc > x)
crossings++;
if (k > 1)
{
t = roots[1];
xc = (xt[0] * t + xt[1]) * t + xt[2];
if (xc > x)
crossings++;
}
}
end_curve_test:
x0 = x2;
y0 = y2;
}
}
}
return (crossings & 1);
}
static int solve_quad(x, y, mindist, maxdist)
double *x;
double *y;
double mindist;
DBL maxdist;
{
double d, t, a, b, c, q;
a = x[0];
b = -x[1];
c = x[2];
if (fabs(a) < COEFF_LIMIT)
{
if (fabs(b) < COEFF_LIMIT)
return 0;
q = c / b;
if (q >= mindist && q <= maxdist)
{
y[0] = q;
return 1;
}
else
return 0;
}
d = b * b - 4.0 * a * c;
if (d < EPSILON)
return 0;
d = sqrt(d);
t = 2.0 * a;
q = (b + d) / t;
if (q >= mindist && q <= maxdist)
{
y[0] = q;
q = (b - d) / t;
if (q >= mindist && q <= maxdist)
{
y[1] = q;
return 2;
}
return 1;
}
q = (b - d) / t;
if (q >= mindist && q <= maxdist)
{
y[0] = q;
return 1;
}
return 0;
}
/*
* Returns the distance to z = 0 in t0, and the distance to z = 1 in t1.
* These distances are to the the bottom and top surfaces of the glyph.
* The distances are set to -1 if there is no hit.
*/
static void GetZeroOneHits(glyph, P, D, glyph_depth, t0, t1)
GlyphPtr glyph;
VECTOR P;
VECTOR D;
DBL glyph_depth;
double *t0;
double *t1;
{
double x0, y0, t;
*t0 = -1.0;
*t1 = -1.0;
/* Are we parallel to the x-y plane? */
if (fabs(D[Z]) < EPSILON)
return;
/* Solve: P[Y] + t * D[Y] = 0 */
t = -P[Z] / D[Z];
x0 = P[X] + t * D[X];
y0 = P[Y] + t * D[Y];
if (Inside_Glyph(x0, y0, glyph))
*t0 = t;
/* Solve: P[Y] + t * D[Y] = glyph_depth */
t += (glyph_depth / D[Z]);
x0 = P[X] + t * D[X];
y0 = P[Y] + t * D[Y];
if (Inside_Glyph(x0, y0, glyph))
*t1 = t;
}
/*
* Solving for a linear sweep of a non-linear curve can be performed by
* projecting the ray onto the x-y plane, giving a parametric equation for the
* ray as:
*
* x = x0 + x1 t, y = y0 + y1 t
*
* Eliminating t from the above gives the implicit equation:
*
* y1 x - x1 y - (x0 y1 - y0 x1) = 0.
*
* Substituting a parametric equation for x and y gives:
*
* y1 x(s) - x1 y(s) - (x0 y1 - y0 x1) = 0.
*
* which can be written as
*
* a x(s) + b y(s) + c = 0,
*
* where a = y1, b = -x1, c = (y0 x1 - x0 y1).
*
* For piecewise quadratics, the parametric equations will have the forms:
*
* x(s) = (1-s)^2 P0(x) + 2 s (1 - s) P1(x) + s^2 P2(x) y(s) = (1-s)^2 P0(y) + 2 s
* (1 - s) P1(y) + s^2 P2(y)
*
* where P0 is the first defining vertex of the spline, P1 is the second, P2 is
* the third. Using the substitutions:
*
* xt2 = x0 - 2 x1 + x2, xt1 = 2 * (x1 - x0), xt0 = x0; yt2 = y0 - 2 y1 + y2, yt1
* = 2 * (y1 - y0), yt0 = y0;
*
* the equations can be written as:
*
* x(s) = xt2 s^2 + xt1 s + xt0, y(s) = yt2 s^2 + yt1 s + yt0.
*
* Substituting and multiplying out gives the following equation in s:
*
* s^2 * (a*xt2 + b*yt2) + s * (a*xt1 + b*yt1) + c + a*xt0 + b*yt0
*
* This is then solved using the quadratic formula. Any solutions of s that are
* between 0 and 1 (inclusive) are valid solutions.
*/
static int GlyphIntersect(Object, P, D, len, glyph, glyph_depth,
Ray, Depth_Stack)
OBJECT *Object;
VECTOR P;
VECTOR D;
DBL len;
GlyphPtr glyph;
DBL glyph_depth;
RAY *Ray;
ISTACK *Depth_Stack;
{
Contour *contour;
int i, j, k, l, n, m, Flag = 0;
VECTOR N, IPoint;
DBL Depth;
double x0, x1, y0, y1, x2, y2, t, t0, t1, z;
double xt0, xt1, xt2, yt0, yt1, yt2;
double a, b, c, d0, d1, C[3], S[2];
DBL *xv, *yv;
BYTE *fv;
TTF *ttf = (TTF *) Object;
int dirflag = 0;
/*
* First thing to do is to get any hits at z = 0 and z = 1 (which are the
* bottom and top surfaces of the glyph.
*/
GetZeroOneHits(glyph, P, D, glyph_depth, &t0, &t1);
if (t0 > 0.0)
{
Depth = t0 / len;
VScale(IPoint, Ray->Direction, Depth);
VAddEq(IPoint, Ray->Initial);
if (Depth > TTF_Tolerance &&
Point_In_Clip(IPoint, Object->Clip))
{
Make_Vector(N, 0.0, 0.0, -1.0);
MTransNormal(N, N, ttf->Trans);
VNormalize(N, N);
push_normal_entry(Depth, IPoint, N, Object, Depth_Stack);
Flag = TRUE;
}
}
if (t1 > 0.0)
{
Depth = t1 / len;
VScale(IPoint, Ray->Direction, Depth);
VAddEq(IPoint, Ray->Initial);
if (Depth > TTF_Tolerance &&
Point_In_Clip(IPoint, Object->Clip))
{
Make_Vector(N, 0.0, 0.0, 1.0);
MTransNormal(N, N, ttf->Trans);
VNormalize(N, N);
push_normal_entry(Depth, IPoint, N, Object, Depth_Stack);
Flag = TRUE;
}
}
/* Simple test to see if we can just toss this ray */
if (fabs(D[X]) < EPSILON)
{
if (fabs(D[Y]) < EPSILON)
{
/*
* This means the ray is moving parallel to the walls of the sweep
* surface
*/
return Flag;
}
else
{
dirflag = 0;
}
}
else
{
dirflag = 1;
}
/*
* Now walk through the glyph, looking for places where the ray hits the
* walls
*/
a = D[Y];
b = -D[X];
c = (P[Y] * D[X] - P[X] * D[Y]);
n = glyph->header.numContours;
for (i = 0, contour = glyph->contours; i < n; i++, contour++)
{
xv = contour->x;
yv = contour->y;
fv = contour->flags;
x0 = xv[0];
y0 = yv[0];
m = contour->count;
for (j = 1; j <= m; j++)
{
x1 = xv[j];
y1 = yv[j];
if (fv[j] & ONCURVE)
{
/* Straight line */
d0 = (x1 - x0);
d1 = (y1 - y0);
t0 = d1 * D[X] - d0 * D[Y];
if (fabs(t0) < EPSILON)
/* No possible intersection */
goto end_line_test;
t = (D[X] * (P[Y] - y0) - D[Y] * (P[X] - x0)) / t0;
if (t < 0.0 || t > 1.0)
goto end_line_test;
if (dirflag)
t = ((x0 + t * d0) - P[X]) / D[X];
else
t = ((y0 + t * d1) - P[Y]) / D[Y];
z = P[Z] + t * D[Z];
Depth = t / len;
if (z >= 0 && z <= glyph_depth && Depth > TTF_Tolerance)
{
VScale(IPoint, Ray->Direction, Depth);
VAddEq(IPoint, Ray->Initial);
if (Point_In_Clip(IPoint, Object->Clip))
{
Make_Vector(N, d1, -d0, 0.0);
MTransNormal(N, N, ttf->Trans);
VNormalize(N, N);
push_normal_entry(Depth, IPoint, N, Object, Depth_Stack);
Flag = TRUE;
}
}
end_line_test:
x0 = x1;
y0 = y1;
}
else
{
if (j == m)
{
x2 = xv[0];
y2 = yv[0];
}
else
{
x2 = xv[j + 1];
y2 = yv[j + 1];
if (!(fv[j + 1] & ONCURVE))
{
/*
* Parabola with far end DBLing - readjust the far end so that it
* is on the curve. (In the correct place too.)
*/
x2 = 0.5 * (x1 + x2);
y2 = 0.5 * (y1 + y2);
}
}
/* Make the interpolating quadrics */
xt2 = x0 - 2.0 * x1 + x2;
xt1 = 2.0 * (x1 - x0);
xt0 = x0;
yt2 = y0 - 2.0 * y1 + y2;
yt1 = 2.0 * (y1 - y0);
yt0 = y0;
C[0] = a * xt2 + b * yt2;
C[1] = a * xt1 + b * yt1;
C[2] = a * xt0 + b * yt0 + c;
k = solve_quad(C, S, 0.0, 1.0);
for (l = 0; l < k; l++)
{
if (dirflag)
t = ((S[l] * S[l] * xt2 + S[l] * xt1 + xt0) - P[X]) / D[X];
else
t = ((S[l] * S[l] * yt2 + S[l] * yt1 + yt0) - P[Y]) / D[Y];
/*
* If the intersection with this wall is between 0 and glyph_depth
* along the z-axis, then it is a valid hit.
*/
z = P[Z] + t * D[Z];
Depth = t / len;
if (z >= 0 && z <= glyph_depth && Depth > TTF_Tolerance)
{
VScale(IPoint, Ray->Direction, Depth);
VAddEq(IPoint, Ray->Initial);
if (Point_In_Clip(IPoint, Object->Clip))
{
Make_Vector(N, 2.0 * yt2 * S[l] + yt1, -2.0 * xt2 * S[l] - xt1, 0.0);
MTransNormal(N, N, ttf->Trans);
VNormalize(N, N);
push_normal_entry(Depth, IPoint, N, Object, Depth_Stack);
Flag = TRUE;
}
}
}
x0 = x2;
y0 = y2;
}
}
}
return Flag;
}
static int All_TTF_Intersections(Object, Ray, Depth_Stack)
OBJECT *Object;
RAY *Ray;
ISTACK *Depth_Stack;
{
TTF *ttf = (TTF *) Object;
DBL len;
VECTOR P, D;
GlyphPtr glyph;
Increase_Counter(stats[Ray_TTF_Tests]);
/* Transform the point into the glyph's space */
MInvTransPoint(P, Ray->Initial, ttf->Trans);
MInvTransDirection(D, Ray->Direction, ttf->Trans);
/* Tweak the ray to try to avoid pathalogical intersections */
D[0] *= 1.0000013147;
D[1] *= 1.0000022741;
D[2] *= 1.0000017011;
VLength(len, D);
VInverseScaleEq(D, len);
glyph = (GlyphPtr)ttf->glyph;
if (GlyphIntersect(Object, P, D, len, glyph,ttf->depth,Ray,Depth_Stack))
{
Increase_Counter(stats[Ray_TTF_Tests_Succeeded]);
return TRUE;
}
return FALSE;
}
static int Inside_TTF(IPoint, Object)
VECTOR IPoint;
OBJECT *Object;
{
VECTOR New_Point;
TTF *ttf = (TTF *) Object;
GlyphPtr glyph;
/* Transform the point into font space */
MInvTransPoint(New_Point, IPoint, ttf->Trans);
glyph = (GlyphPtr)ttf->glyph;
if (New_Point[Z] >= 0.0 && New_Point[Z] <= ttf->depth &&
Inside_Glyph(New_Point[X], New_Point[Y], glyph))
return (!Test_Flag(ttf, INVERTED_FLAG));
else
return (Test_Flag(ttf, INVERTED_FLAG));
}
static void TTF_Normal(Result, Object, Inter)
OBJECT *Object;
VECTOR Result;
INTERSECTION *Inter;
{
/* Use precomputed normal. [ARE 11/94] */
Assign_Vector(Result, Inter->INormal);
}
static void *Copy_TTF(Object)
OBJECT *Object;
{
TTF *New, *Old = (TTF *) Object;
New = Create_TTF();
Destroy_Transform(New->Trans);
*New = *Old;
New->Trans = Copy_Transform(((TTF *) Object)->Trans);
New->glyph = Old->glyph;
return (New);
}
static void Translate_TTF(Object, Vector, Trans)
OBJECT *Object;
VECTOR Vector;
TRANSFORM *Trans;
{
Transform_TTF(Object, Trans);
}
static void Rotate_TTF(Object, Vector, Trans)
OBJECT *Object;
VECTOR Vector;
TRANSFORM *Trans;
{
Transform_TTF(Object, Trans);
}
static void Scale_TTF(Object, Vector, Trans)
OBJECT *Object;
VECTOR Vector;
TRANSFORM *Trans;
{
Transform_TTF(Object, Trans);
}
static void Invert_TTF(Object)
OBJECT *Object;
{
Invert_Flag(Object, INVERTED_FLAG);
}
static void Transform_TTF(Object, Trans)
OBJECT *Object;
TRANSFORM *Trans;
{
TTF *ttf = (TTF *) Object;
Compose_Transforms(ttf->Trans, Trans);
/* Calculate the bounds */
Compute_TTF_BBox(ttf);
}
static TTF *Create_TTF()
{
TTF *New;
New = (TTF *) POV_MALLOC(sizeof(TTF), "ttf");
INIT_OBJECT_FIELDS(New, TTF_OBJECT, &TTF_Methods)
/* Initialize TTF specific information */
New->Trans = Create_Transform();
New->glyph = NULL;
New->depth = 1.0;
/* Default bounds */
Make_BBox(New->BBox, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
return New;
}
static void Destroy_TTF(Object)
OBJECT *Object;
{
TTF *ttf = (TTF *) Object;
Destroy_Transform(ttf->Trans);
POV_FREE(Object);
}
/*****************************************************************************
*
* FUNCTION
*
* Compute_TTF_BBox
*
* INPUT
*
* ttf - ttf
*
* OUTPUT
*
* ttf
*
* RETURNS
*
* AUTHOR
*
* Dieter Bayer, August 1994
*
* DESCRIPTION
*
* Calculate the bounding box of a true type font.
*
* CHANGES
*
* -
*
******************************************************************************/
void Compute_TTF_BBox(ttf)
TTF *ttf;
{
DBL funit_size, xMin, yMin, zMin, xMax, yMax, zMax;
GlyphPtr glyph;
glyph = (GlyphPtr)ttf->glyph;
funit_size = 1.0 / (DBL)(glyph->unitsPerEm);
xMin = (DBL)glyph->header.xMin * funit_size;
yMin = (DBL)glyph->header.yMin * funit_size;
zMin = -TTF_Tolerance;
xMax = (DBL)glyph->header.xMax * funit_size;
yMax = (DBL)glyph->header.yMax * funit_size;
zMax = ttf->depth + TTF_Tolerance;
Make_BBox(ttf->BBox, xMin, yMin, zMin, xMax - xMin, yMax - yMin, zMax - zMin);
#ifdef TTF_DEBUG
Debug_Info("Bounds: <%g,%g,%g> -> <%g,%g,%g>\n",
ttf->BBox.Lower_Left[0],
ttf->BBox.Lower_Left[1],
ttf->BBox.Lower_Left[2],
ttf->BBox.Lengths[0],
ttf->BBox.Lengths[1],
ttf->BBox.Lengths[2]);
#endif
/* Apply the transformation to the bounding box */
Recompute_BBox(&ttf->BBox, ttf->Trans);
}