/**************************************************************************** * 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 * * CalculateStringLength * * INPUT * * OUTPUT * * RETURNS * * AUTHOR * * Alexander Ennzmann * * DESCRIPTION * * Takes a FontFileInfo struct, a text string, and an offset between * characters, and calculates the length of the string in units * * CHANGES * * - Jamis Buck: took the ProcessNewTTF Function and gutted it to make * this one. :) * ******************************************************************************/ DBL CalculateStringLength(ffile, text_string, offset) FontFileInfo *ffile; char *text_string; VECTOR offset; { int slen; VECTOR local_offset, total_offset; TTF *ttf; 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 info about each character in the string */ slen = strlen(text_string); Make_Vector(total_offset, 0.0, 0.0, 0.0); 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(); /* * 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; /* 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; /* 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); last_index = glyph_index; } #ifdef TTF_DEBUG Debug_Info("TTF parsing of \"%s\" from %s complete\n", text_string, filename); #endif return total_offset[X]; } /***************************************************************************** * * 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; DBL total_str_len; 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; total_str_len = CalculateStringLength( ffile, text_string, offset ); 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 - total_str_len / 2; 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 - total_str_len / 2; 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); }