/* * Parse AFM files. * Copyright (c) 1995-1998 Markku Rossi. * * Author: Markku Rossi */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "afmint.h" #include "afm.h" /* * Definitions. */ #define ISSPACE(ch) \ ((ch) == ' ' || (ch) == '\n' || (ch) == '\r' || (ch) == '\t' || (ch) == ';') #define GET_VALUE(typenum) get_type (handle, ctx, (typenum), &node) struct parse_ctx_st { FILE *fp; char token[1024]; /* maximum line length is 255, this should be enought */ unsigned int tokenlen; /* length of the token */ }; typedef struct parse_ctx_st ParseCtx; /* * Static variables. */ /* * The AFM keys. This array must be kept sorted because keys are * searched by using binary search. */ static struct keyname_st { char *name; AFMKey key; } keynames[] = { {"Ascender", kAscender}, {"Axes", kAxes}, {"AxisLabel", kAxisLabel}, {"AxisType", kAxisType}, {"B", kB}, {"BlendAxisTypes", kBlendAxisTypes}, {"BlendDesignMap", kBlendDesignMap}, {"BlendDesignPositions", kBlendDesignPositions}, {"C", kC}, {"CC", kCC}, {"CH", kCH}, {"CapHeight", kCapHeight}, {"CharWidth", kCharWidth}, {"CharacterSet", kCharacterSet}, {"Characters", kCharacters}, {"Comment", kComment}, {"Descendents", kDescendents}, {"Descender", kDescender}, {"EncodingScheme", kEncodingScheme}, {"EndAxis", kEndAxis}, {"EndCharMetrics", kEndCharMetrics}, {"EndCompFontMetrics", kEndCompFontMetrics}, {"EndComposites", kEndComposites}, {"EndDescendent", kEndDescendent}, {"EndDirection", kEndDirection}, {"EndFontMetrics", kEndFontMetrics}, {"EndKernData", kEndKernData}, {"EndKernPairs", kEndKernPairs}, {"EndMaster", kEndMaster}, {"EndMasterFontMetrics", kEndMasterFontMetrics}, {"EndTrackKern", kEndTrackKern}, {"EscChar", kEscChar}, {"FamilyName", kFamilyName}, {"FontBBox", kFontBBox}, {"FontName", kFontName}, {"FullName", kFullName}, {"IsBaseFont", kIsBaseFont}, {"IsFixedPitch", kIsFixedPitch}, {"IsFixedV", kIsFixedV}, {"ItalicAngle", kItalicAngle}, {"KP", kKP}, {"KPH", kKPH}, {"KPX", kKPX}, {"KPY", kKPY}, {"L", kL}, {"MappingScheme", kMappingScheme}, {"Masters", kMasters}, {"MetricsSets", kMetricsSets}, {"N", kN}, {"Notice", kNotice}, {"PCC", kPCC}, {"StartAxis", kStartAxis}, {"StartCharMetrics", kStartCharMetrics}, {"StartCompFontMetrics", kStartCompFontMetrics}, {"StartComposites", kStartComposites}, {"StartDescendent", kStartDescendent}, {"StartDirection", kStartDirection}, {"StartFontMetrics", kStartFontMetrics}, {"StartKernData", kStartKernData}, {"StartKernPairs", kStartKernPairs}, {"StartMaster", kStartMaster}, {"StartMasterFontMetrics", kStartMasterFontMetrics}, {"StartTrackKern", kStartTrackKern}, {"TrackKern", kTrackKern}, {"UnderlinePosition", kUnderlinePosition}, {"UnderlineThickness", kUnderlineThickness}, {"VV", kVV}, {"VVector", kVVector}, {"Version", kVersion}, {"W", kW}, {"W0", kW0}, {"W0X", kW0X}, {"W0Y", kW0Y}, {"W1", kW1}, {"W1X", kW1X}, {"W1Y", kW1Y}, {"WX", kWX}, {"WY", kWY}, {"Weight", kWeight}, {"WeightVector", kWeightVector}, {"XHeight", kXHeight}, {NULL, 0}, }; #define NUM_KEYS (sizeof (keynames) / sizeof (struct keyname_st) - 1) /* * Prototypes for static functions. */ /* Throw parse error . Never returns. */ static void parse_error ___P ((AFMHandle handle, AFMError error)); static int get_token ___P ((AFMHandle handle, ParseCtx *ctx)); static int get_line_token ___P ((AFMHandle handle, ParseCtx *ctx)); static void get_key ___P ((AFMHandle handle, ParseCtx *ctx, AFMKey *key_return)); static void get_type ___P ((AFMHandle handle, ParseCtx *ctx, int type, AFMNode *type_return)); static void read_character_metrics ___P ((AFMHandle handle, ParseCtx *ctx, AFMFont font)); static void read_kern_pairs ___P ((AFMHandle handle, ParseCtx *ctx, AFMFont font)); static void read_track_kerns ___P ((AFMHandle handle, ParseCtx *ctx, AFMFont font)); static void read_composites ___P ((AFMHandle handle, ParseCtx *ctx, AFMFont font)); /* * Global functions. */ void afm_parse_file (AFMHandle handle, const char *filename, AFMFont font) { AFMKey key; AFMNode node; ParseCtx context; ParseCtx *ctx = &context; int wd = 0; /* Writing direction. */ int done = 0; ctx->fp = fopen (filename, "r"); if (ctx->fp == NULL) parse_error (handle, SYSERROR (AFM_ERROR_FILE_IO)); /* Check that file is really an AFM file. */ get_key (handle, ctx, &key); if (key != kStartFontMetrics) parse_error (handle, AFM_ERROR_NOT_AFM_FILE); GET_VALUE (AFM_TYPE_NUMBER); font->version = node.u.number; /* Parse it. */ while (!done) { get_key (handle, ctx, &key); switch (key) { case kComment: (void) get_line_token (handle, ctx); continue; break; /* File structure. */ case kStartFontMetrics: GET_VALUE (AFM_TYPE_NUMBER); font->version = node.u.number; break; case kEndFontMetrics: done = 1; break; case kStartCompFontMetrics: case kEndCompFontMetrics: case kStartMasterFontMetrics: case kEndMasterFontMetrics: parse_error (handle, AFM_ERROR_UNSUPPORTED_FORMAT); break; /* Global font information. */ case kFontName: GET_VALUE (AFM_TYPE_STRING); font->global_info.FontName = node.u.string; break; case kFullName: GET_VALUE (AFM_TYPE_STRING); font->global_info.FullName = node.u.string; break; case kFamilyName: GET_VALUE (AFM_TYPE_STRING); font->global_info.FamilyName = node.u.string; break; case kWeight: GET_VALUE (AFM_TYPE_STRING); font->global_info.Weight = node.u.string; break; case kFontBBox: GET_VALUE (AFM_TYPE_NUMBER); font->global_info.FontBBox_llx = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); font->global_info.FontBBox_lly = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); font->global_info.FontBBox_urx = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); font->global_info.FontBBox_ury = node.u.number; break; case kVersion: GET_VALUE (AFM_TYPE_STRING); font->global_info.Version = node.u.string; break; case kNotice: GET_VALUE (AFM_TYPE_STRING); font->global_info.Notice = node.u.string; break; case kEncodingScheme: GET_VALUE (AFM_TYPE_STRING); font->global_info.EncodingScheme = node.u.string; break; case kMappingScheme: GET_VALUE (AFM_TYPE_INTEGER); font->global_info.MappingScheme = node.u.integer; break; case kEscChar: GET_VALUE (AFM_TYPE_INTEGER); font->global_info.EscChar = node.u.integer; break; case kCharacterSet: GET_VALUE (AFM_TYPE_STRING); font->global_info.CharacterSet = node.u.string; break; case kCharacters: GET_VALUE (AFM_TYPE_INTEGER); font->global_info.Characters = node.u.integer; break; case kIsBaseFont: GET_VALUE (AFM_TYPE_BOOLEAN); font->global_info.IsBaseFont = node.u.boolean; break; case kVVector: GET_VALUE (AFM_TYPE_NUMBER); font->global_info.VVector_0 = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); font->global_info.VVector_1 = node.u.number; break; case kIsFixedV: GET_VALUE (AFM_TYPE_BOOLEAN); font->global_info.IsFixedV = node.u.boolean; break; case kCapHeight: GET_VALUE (AFM_TYPE_NUMBER); font->global_info.CapHeight = node.u.number; break; case kXHeight: GET_VALUE (AFM_TYPE_NUMBER); font->global_info.XHeight = node.u.number; break; case kAscender: GET_VALUE (AFM_TYPE_NUMBER); font->global_info.Ascender = node.u.number; break; case kDescender: GET_VALUE (AFM_TYPE_NUMBER); font->global_info.Descender = node.u.number; break; /* Writing directions. */ case kStartDirection: GET_VALUE (AFM_TYPE_INTEGER); wd = node.u.integer; font->writing_direction_metrics[wd].is_valid = AFMTrue; break; case kUnderlinePosition: GET_VALUE (AFM_TYPE_NUMBER); font->writing_direction_metrics[wd].UnderlinePosition = node.u.number; break; case kUnderlineThickness: GET_VALUE (AFM_TYPE_NUMBER); font->writing_direction_metrics[wd].UnderlineThickness = node.u.number; break; case kItalicAngle: GET_VALUE (AFM_TYPE_NUMBER); font->writing_direction_metrics[wd].ItalicAngle = node.u.number; break; case kCharWidth: GET_VALUE (AFM_TYPE_NUMBER); font->writing_direction_metrics[wd].CharWidth_x = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); font->writing_direction_metrics[wd].CharWidth_y = node.u.number; break; case kIsFixedPitch: GET_VALUE (AFM_TYPE_BOOLEAN); font->writing_direction_metrics[wd].IsFixedPitch = node.u.boolean; break; case kEndDirection: break; /* Individual Character Metrics. */ case kStartCharMetrics: GET_VALUE (AFM_TYPE_INTEGER); font->num_character_metrics = node.u.integer; font->character_metrics = ((AFMIndividualCharacterMetrics *) calloc (font->num_character_metrics + 1, sizeof (AFMIndividualCharacterMetrics))); if (font->character_metrics == NULL) parse_error (handle, AFM_ERROR_MEMORY); read_character_metrics (handle, ctx, font); break; /* Kerning Data. */ case kStartKernData: break; case kStartKernPairs: if (font->info_level & AFM_I_KERN_PAIRS) { GET_VALUE (AFM_TYPE_INTEGER); font->num_kern_pairs = node.u.integer; font->kern_pairs = (AFMPairWiseKerning *) calloc (font->num_kern_pairs + 1, sizeof (AFMPairWiseKerning)); if (font->kern_pairs == NULL) parse_error (handle, AFM_ERROR_MEMORY); read_kern_pairs (handle, ctx, font); } else { do { (void) get_line_token (handle, ctx); get_key (handle, ctx, &key); } while (key != kEndKernPairs); } break; case kStartTrackKern: if (font->info_level & AFM_I_TRACK_KERNS) { GET_VALUE (AFM_TYPE_INTEGER); font->num_track_kerns = node.u.integer; font->track_kerns = (AFMTrackKern *) calloc (font->num_track_kerns + 1, sizeof (AFMTrackKern)); if (font->track_kerns == NULL) parse_error (handle, AFM_ERROR_MEMORY); read_track_kerns (handle, ctx, font); } else { do { (void) get_line_token (handle, ctx); get_key (handle, ctx, &key); } while (key != kEndTrackKern); } break; case kEndKernData: break; /* Composite Character Data. */ case kStartComposites: if (font->info_level & AFM_I_COMPOSITES) { GET_VALUE (AFM_TYPE_INTEGER); font->num_composites = node.u.integer; font->composites = (AFMComposite *) calloc (font->num_composites + 1, sizeof (AFMComposite)); if (font->composites == NULL) parse_error (handle, AFM_ERROR_MEMORY); read_composites (handle, ctx, font); } else { do { (void) get_line_token (handle, ctx); get_key (handle, ctx, &key); } while (key != kEndComposites); } break; default: /* Ignore. */ break; } } fclose (ctx->fp); /* Check post conditions. */ if (!font->writing_direction_metrics[0].is_valid && !font->writing_direction_metrics[1].is_valid) /* No direction specified, 0 implied. */ font->writing_direction_metrics[0].is_valid = AFMTrue; /* Undef character. */ if (!strhash_get (font->private->fontnames, "space", 5, (void *) font->private->undef)) { /* Character "space" is not defined. Select the first one. */ assert (font->num_character_metrics > 0); font->private->undef = &font->character_metrics[0]; } /* Fixed pitch. */ if (font->writing_direction_metrics[0].is_valid && font->writing_direction_metrics[0].IsFixedPitch) { /* Take one, it doesn't matter which one. */ font->writing_direction_metrics[0].CharWidth_x = font->character_metrics[0].w0x; font->writing_direction_metrics[0].CharWidth_y = font->character_metrics[0].w0y; } if (font->writing_direction_metrics[1].is_valid && font->writing_direction_metrics[1].IsFixedPitch) { font->writing_direction_metrics[1].CharWidth_x = font->character_metrics[1].w1x; font->writing_direction_metrics[1].CharWidth_y = font->character_metrics[1].w1y; } } /* * Static functions. */ static void parse_error (AFMHandle handle, AFMError error) { handle->parse_error = error; longjmp (handle->jmpbuf, 1); /* If this is reached, then all is broken. */ fprintf (stderr, "AFM: fatal internal longjmp() error.\n"); abort (); } static int get_token (AFMHandle handle, ParseCtx *ctx) { int ch; int i; /* Skip the leading whitespace. */ while ((ch = getc (ctx->fp)) != EOF) if (!ISSPACE (ch)) break; if (ch == EOF) return 0; ungetc (ch, ctx->fp); /* Get name. */ for (i = 0, ch = getc (ctx->fp); i < sizeof (ctx->token) && ch != EOF && !ISSPACE (ch); i++, ch = getc (ctx->fp)) ctx->token[i] = ch; if (i >= sizeof (ctx->token)) /* Line is too long, this is against AFM specification. */ parse_error (handle, AFM_ERROR_SYNTAX); ctx->token[i] = '\0'; ctx->tokenlen = i; return 1; } static int get_line_token (AFMHandle handle, ParseCtx *ctx) { int i, ch; /* Skip the leading whitespace. */ while ((ch = getc (ctx->fp)) != EOF) if (!ISSPACE (ch)) break; if (ch == EOF) return 0; ungetc (ch, ctx->fp); /* Read to the end of the line. */ for (i = 0, ch = getc (ctx->fp); i < sizeof (ctx->token) && ch != EOF && ch != '\n'; i++, ch = getc (ctx->fp)) ctx->token[i] = ch; if (i >= sizeof (ctx->token)) parse_error (handle, AFM_ERROR_SYNTAX); /* Skip all trailing whitespace. */ for (i--; i >= 0 && ISSPACE (ctx->token[i]); i--) ; i++; ctx->token[i] = '\0'; ctx->tokenlen = i; return 1; } static int match_key (char *key) { int lower = 0; int upper = NUM_KEYS; int midpoint, cmpvalue; AFMBoolean found = AFMFalse; while ((upper >= lower) && !found) { midpoint = (lower + upper) / 2; if (keynames[midpoint].name == NULL) break; cmpvalue = strcmp (key, keynames[midpoint].name); if (cmpvalue == 0) found = AFMTrue; else if (cmpvalue < 0) upper = midpoint - 1; else lower = midpoint + 1; } if (found) return keynames[midpoint].key; return -1; } static void get_key (AFMHandle handle, ParseCtx *ctx, AFMKey *key_return) { int key; char msg[256]; while (1) { if (!get_token (handle, ctx)) /* Unexpected EOF. */ parse_error (handle, AFM_ERROR_SYNTAX); key = match_key (ctx->token); if (key >= 0) { *key_return = key; return; } /* No match found. According to standard, we must skip this key. */ sprintf (msg, "skipping key \"%s\"", ctx->token); afm_error (handle, msg); get_line_token (handle, ctx); } /* NOTREACHED */ } /* Reader for AFM types. */ static void get_type (AFMHandle handle, ParseCtx *ctx, int type, AFMNode *type_return) { char buf[256]; switch (type) { case AFM_TYPE_STRING: if (!get_line_token (handle, ctx)) parse_error (handle, AFM_ERROR_SYNTAX); type_return->u.string = (AFMString) calloc (1, ctx->tokenlen + 1); if (type_return->u.string == NULL) parse_error (handle, AFM_ERROR_MEMORY); memcpy (type_return->u.string, ctx->token, ctx->tokenlen); break; case AFM_TYPE_NAME: if (!get_token (handle, ctx)) parse_error (handle, AFM_ERROR_SYNTAX); type_return->u.name = (AFMName) calloc (1, ctx->tokenlen + 1); if (type_return->u.string == NULL) parse_error (handle, AFM_ERROR_MEMORY); memcpy (type_return->u.name, ctx->token, ctx->tokenlen); break; case AFM_TYPE_NUMBER: if (!get_token (handle, ctx)) parse_error (handle, AFM_ERROR_SYNTAX); memcpy (buf, ctx->token, ctx->tokenlen); buf[ctx->tokenlen] = '\0'; type_return->u.number = atof (buf); break; case AFM_TYPE_INTEGER: if (!get_token (handle, ctx)) parse_error (handle, AFM_ERROR_SYNTAX); memcpy (buf, ctx->token, ctx->tokenlen); buf[ctx->tokenlen] = '\0'; type_return->u.integer = atoi (buf); break; case AFM_TYPE_ARRAY: fprintf (stderr, "Array types not implemented yet.\n"); abort (); break; case AFM_TYPE_BOOLEAN: if (!get_token (handle, ctx)) parse_error (handle, AFM_ERROR_SYNTAX); memcpy (buf, ctx->token, ctx->tokenlen); buf[ctx->tokenlen] = '\0'; if (strcmp (buf, "true") == 0) type_return->u.boolean = AFMTrue; else if (strcmp (buf, "false") == 0) type_return->u.boolean = AFMFalse; else parse_error (handle, AFM_ERROR_SYNTAX); break; default: fprintf (stderr, "get_type(): illegal type %d\n", type_return->type); abort (); break; } } static void read_character_metrics (AFMHandle handle, ParseCtx *ctx, AFMFont font) { int i = 0; AFMNode node; AFMIndividualCharacterMetrics *cm = NULL; AFMKey key; int done = 0; int first = 1; while (!done) { get_key (handle, ctx, &key); switch (key) { case kC: if (first) first = 0; else i++; if (i >= font->num_character_metrics) parse_error (handle, AFM_ERROR_SYNTAX); cm = &font->character_metrics[i]; GET_VALUE (AFM_TYPE_INTEGER); cm->character_code = node.u.integer; if (cm->character_code >= 0 && cm->character_code <= 255) font->encoding[cm->character_code] = cm; break; case kCH: printf ("* CH\n"); break; case kWX: case kW0X: GET_VALUE (AFM_TYPE_NUMBER); cm->w0x = node.u.number; cm->w0y = 0.0; break; case kW1X: GET_VALUE (AFM_TYPE_NUMBER); cm->w1x = node.u.number; cm->w1y = 0.0; break; case kWY: case kW0Y: GET_VALUE (AFM_TYPE_NUMBER); cm->w0y = node.u.number; cm->w0x = 0.0; break; case kW1Y: GET_VALUE (AFM_TYPE_NUMBER); cm->w1y = node.u.number; cm->w1x = 0.0; break; case kW: case kW0: GET_VALUE (AFM_TYPE_NUMBER); cm->w0x = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); cm->w0y = node.u.number; break; case kW1: GET_VALUE (AFM_TYPE_NUMBER); cm->w1x = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); cm->w1y = node.u.number; break; case kVV: GET_VALUE (AFM_TYPE_NUMBER); cm->vv_x = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); cm->vv_y = node.u.number; break; case kN: GET_VALUE (AFM_TYPE_NAME); cm->name = node.u.name; if (!strhash_put (font->private->fontnames, cm->name, strlen (cm->name), cm, NULL)) parse_error (handle, AFM_ERROR_MEMORY); break; case kB: GET_VALUE (AFM_TYPE_NUMBER); cm->llx = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); cm->lly = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); cm->urx = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); cm->ury = node.u.number; break; case kL: /* XXX Skip ligatures. */ get_line_token (handle, ctx); break; case kEndCharMetrics: if (i != font->num_character_metrics - 1) { /* * My opinion is that this is a syntax error; the * creator of this AFM file should have been smart * enought to count these character metrics. Well, * maybe that is too much asked... */ font->num_character_metrics = i + 1; } done = 1; break; default: parse_error (handle, AFM_ERROR_SYNTAX); break; } } } static void read_kern_pairs (AFMHandle handle, ParseCtx *ctx, AFMFont font) { int i; AFMNode node; AFMPairWiseKerning *kp; AFMKey key; for (i = 0; i < font->num_kern_pairs; i++) { kp = &font->kern_pairs[i]; get_key (handle, ctx, &key); switch (key) { case kKP: case kKPX: case kKPY: GET_VALUE (AFM_TYPE_NAME); kp->name1 = node.u.name; GET_VALUE (AFM_TYPE_NAME); kp->name2 = node.u.name; GET_VALUE (AFM_TYPE_NUMBER); switch (key) { case kKP: kp->kx = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); kp->ky = node.u.number; break; case kKPX: kp->kx = node.u.number; kp->ky = 0.0; break; case kKPY: kp->ky = node.u.number; kp->kx = 0.0; break; default: fprintf (stderr, "AFM: fatal corruption\n"); abort (); break; } break; case kKPH: /* XXX ignore. */ break; default: parse_error (handle, AFM_ERROR_SYNTAX); break; } } /* Get end token. */ get_key (handle, ctx, &key); if (key != kEndKernPairs) parse_error (handle, AFM_ERROR_SYNTAX); } static void read_track_kerns (AFMHandle handle, ParseCtx *ctx, AFMFont font) { int i; AFMNode node; AFMTrackKern *tk; AFMKey key; for (i = 0; i < font->num_kern_pairs; i++) { tk = &font->track_kerns[i]; get_key (handle, ctx, &key); /* TrackKern degree min-ptsize min-kern max-ptrsize max-kern */ if (key != kTrackKern) parse_error (handle, AFM_ERROR_SYNTAX); GET_VALUE (AFM_TYPE_INTEGER); tk->degree = node.u.integer; GET_VALUE (AFM_TYPE_NUMBER); tk->min_ptsize = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); tk->min_kern = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); tk->max_ptsize = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); tk->max_kern = node.u.number; } /* Get end token. */ get_key (handle, ctx, &key); if (key != kEndTrackKern) parse_error (handle, AFM_ERROR_SYNTAX); } static void read_composites (AFMHandle handle, ParseCtx *ctx, AFMFont font) { int i, j; AFMNode node; AFMComposite *cm; AFMKey key; for (i = 0; i < font->num_composites; i++) { cm = &font->composites[i]; get_key (handle, ctx, &key); if (key != kCC) parse_error (handle, AFM_ERROR_SYNTAX); GET_VALUE (AFM_TYPE_NAME); cm->name = node.u.name; /* Create name -> AFMComposite mapping. */ if (!strhash_put (font->private->compositenames, cm->name, strlen (cm->name), cm, NULL)) parse_error (handle, AFM_ERROR_MEMORY); GET_VALUE (AFM_TYPE_INTEGER); cm->num_components = node.u.integer; cm->components = (AFMCompositeComponent *) calloc (cm->num_components + 1, sizeof (AFMCompositeComponent)); /* Read composite components. */ for (j = 0; j < cm->num_components; j++) { /* Read "PCC". */ get_key (handle, ctx, &key); if (key != kPCC) parse_error (handle, AFM_ERROR_SYNTAX); /* Read values. */ GET_VALUE (AFM_TYPE_NAME); cm->components[j].name = node.u.name; GET_VALUE (AFM_TYPE_NUMBER); cm->components[j].deltax = node.u.number; GET_VALUE (AFM_TYPE_NUMBER); cm->components[j].deltay = node.u.number; } } /* Get end token. */ get_key (handle, ctx, &key); if (key != kEndComposites) parse_error (handle, AFM_ERROR_SYNTAX); }