/* * gEDA - GNU Electronic Design Automation * This is a part of gerbv * * Copyright (C) 2000-2003 Stefan Petersen (spe@stacken.kth.se) * * $Id$ * * 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 of the License, 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA */ /** \file gerber.c \brief RS274X parsing functions \ingroup libgerbv */ #include "gerbv.h" #include #include #include #include #include /* pow() */ #include #include #include "common.h" #include "gerb_image.h" #include "gerber.h" #include "gerb_stats.h" #include "amacro.h" #undef AMACRO_DEBUG #undef DPRINTF #define DPRINTF(...) do { if (DEBUG) printf(__VA_ARGS__); } while (0) #define A2I(a,b) (((a & 0xff) << 8) + (b & 0xff)) #define MAXL 200 /* Local function prototypes */ static void parse_G_code(gerb_file_t *fd, gerb_state_t *state, gerbv_image_t *image, long int *line_num_p); static void parse_D_code(gerb_file_t *fd, gerb_state_t *state, gerbv_image_t *image, long int *line_num_p); static int parse_M_code(gerb_file_t *fd, gerbv_image_t *image, long int *line_num_p); static void parse_rs274x(gint levelOfRecursion, gerb_file_t *fd, gerbv_image_t *image, gerb_state_t *state, gerbv_net_t *curr_net, gerbv_stats_t *stats, gchar *directoryPath, long int *line_num_p); static int parse_aperture_definition(gerb_file_t *fd, gerbv_aperture_t *aperture, gerbv_image_t *image, gdouble scale, long int *line_num_p); static void calc_cirseg_sq(struct gerbv_net *net, int cw, double delta_cp_x, double delta_cp_y); void calc_cirseg_mq(struct gerbv_net *net, int cw, double delta_cp_x, double delta_cp_y); static void calc_cirseg_bbox(const gerbv_cirseg_t *cirseg, double apert_size_x, double apert_size_y, gerbv_render_size_t *bbox); static void gerber_update_any_running_knockout_measurements( gerbv_image_t *image); static void gerber_calculate_final_justify_effects (gerbv_image_t *image); static gboolean add_trailing_zeros_if_omitted(int *coord, int omitted_num, gerbv_format_t *format); gboolean knockoutMeasure = FALSE; gdouble knockoutLimitXmin, knockoutLimitYmin, knockoutLimitXmax, knockoutLimitYmax; gerbv_layer_t *knockoutLayer = NULL; cairo_matrix_t currentMatrix; /* --------------------------------------------------------- */ gerbv_net_t * gerber_create_new_net (gerbv_net_t *currentNet, gerbv_layer_t *layer, gerbv_netstate_t *state){ gerbv_net_t *newNet = g_new0 (gerbv_net_t, 1); currentNet->next = newNet; if (layer) newNet->layer = layer; else newNet->layer = currentNet->layer; if (state) newNet->state = state; else newNet->state = currentNet->state; return newNet; } /* --------------------------------------------------------- */ gboolean gerber_create_new_aperture (gerbv_image_t *image, int *indexNumber, gerbv_aperture_type_t apertureType, gdouble parameter1, gdouble parameter2){ int i; /* search for an available aperture spot */ for (i = 0; i <= APERTURE_MAX; i++) { if (image->aperture[i] == NULL) { image->aperture[i] = g_new0 (gerbv_aperture_t, 1); image->aperture[i]->type = apertureType; image->aperture[i]->parameter[0] = parameter1; image->aperture[i]->parameter[1] = parameter2; *indexNumber = i; return TRUE; } } return FALSE; } /* --------------------------------------------------------- */ /*! This function reads the Gerber file char by char, looking * for various Gerber codes (e.g. G, D, etc). Once it reads * a code, it then dispatches control to one or another * bits of code which parse the individual code. * It also updates the state struct, which holds info about * the current state of the hypothetical photoplotter * (i.e. updates whether the aperture is on or off, updates * any other parameters, like units, offsets, apertures, etc.) */ gboolean gerber_parse_file_segment (gint levelOfRecursion, gerbv_image_t *image, gerb_state_t *state, gerbv_net_t *curr_net, gerbv_stats_t *stats, gerb_file_t *fd, gchar *directoryPath) { int read, coord, len, polygonPoints=0; double x_scale = 0.0, y_scale = 0.0; double delta_cp_x = 0.0, delta_cp_y = 0.0; double aperture_sizeX, aperture_sizeY; double scale; gboolean foundEOF = FALSE; gerbv_render_size_t boundingBoxNew = {HUGE_VAL,-HUGE_VAL,HUGE_VAL,-HUGE_VAL}, boundingBox = boundingBoxNew; gerbv_error_list_t *error_list = stats->error_list; long int line_num = 1; while ((read = gerb_fgetc(fd)) != EOF) { /* figure out the scale, since we need to normalize all dimensions to inches */ if (state->state->unit == GERBV_UNIT_MM) scale = 25.4; else scale = 1.0; switch ((char)(read & 0xff)) { case 'G': DPRINTF("... Found G code at line %ld\n", line_num); parse_G_code(fd, state, image, &line_num); break; case 'D': DPRINTF("... Found D code at line %ld\n", line_num); parse_D_code(fd, state, image, &line_num); break; case 'M': DPRINTF("... Found M code at line %ld\n", line_num); switch(parse_M_code(fd, image, &line_num)) { case 1 : case 2 : case 3 : foundEOF = TRUE; break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unknown M code found at line %ld in file \"%s\""), line_num, fd->filename); } /* switch(parse_M_code) */ break; case 'X': stats->X++; coord = gerb_fgetint(fd, &len); if (image->format) add_trailing_zeros_if_omitted(&coord, image->format->x_int + image->format->x_dec - len, image->format); DPRINTF("... Found X code %d at line %ld\n", coord, line_num); if (image->format && image->format->coordinate==GERBV_COORDINATE_INCREMENTAL) state->curr_x += coord; else state->curr_x = coord; state->changed = 1; break; case 'Y': stats->Y++; coord = gerb_fgetint(fd, &len); if (image->format) add_trailing_zeros_if_omitted(&coord, image->format->y_int + image->format->y_dec - len, image->format); DPRINTF("... Found Y code %d at line %ld\n", coord, line_num); if (image->format && image->format->coordinate==GERBV_COORDINATE_INCREMENTAL) state->curr_y += coord; else state->curr_y = coord; state->changed = 1; break; case 'I': stats->I++; coord = gerb_fgetint(fd, &len); if (image->format) add_trailing_zeros_if_omitted(&coord, image->format->x_int + image->format->x_dec - len, image->format); DPRINTF("... Found I code %d at line %ld\n", coord, line_num); state->delta_cp_x = coord; state->changed = 1; break; case 'J': stats->J++; coord = gerb_fgetint(fd, &len); if (image->format) add_trailing_zeros_if_omitted(&coord, image->format->y_int + image->format->y_dec - len, image->format); DPRINTF("... Found J code %d at line %ld\n", coord, line_num); state->delta_cp_y = coord; state->changed = 1; break; case '%': DPRINTF("... Found %% code at line %ld\n", line_num); while (1) { parse_rs274x(levelOfRecursion, fd, image, state, curr_net, stats, directoryPath, &line_num); /* Handle aperture block open/close transitions */ if ((state->in_block) && (state->saved_curr_net == NULL)) { /* Just entered a block — redirect curr_net */ state->saved_curr_net = curr_net; curr_net = state->block_netlist; } else if ((!state->in_block) && (state->saved_curr_net != NULL)) { /* Just closed a block — restore curr_net */ curr_net = state->saved_curr_net; state->saved_curr_net = NULL; } /* advance past any whitespace here */ int c; while (1) { c = gerb_fgetc(fd); switch (c) { case '\0': case '\t': case ' ': continue; case '\n': line_num++; /* Get char, if any, from pair */ read = gerb_fgetc(fd); if (read != '\r' && read != EOF) gerb_ungetc(fd); continue; case '\r': line_num++; /* Get char, if any, from pair */ read = gerb_fgetc(fd); if (read != '\n' && read != EOF) gerb_ungetc(fd); continue; } break; /* break while(1) */ }; if(c == EOF || c == '%') break; /* Loop again to catch multiple blocks on the same line * (separated by * char) */ gerb_ungetc(fd); } break; case '*': DPRINTF("... Found * code at line %ld\n", line_num); stats->star++; if (state->changed == 0) break; state->changed = 0; /* don't even bother saving the net if the aperture state is GERBV_APERTURE_STATE_OFF and we aren't starting a polygon fill (where we need it to get to the start point) */ if ((state->aperture_state == GERBV_APERTURE_STATE_OFF)&&(!state->in_parea_fill)&& (state->interpolation != GERBV_INTERPOLATION_PAREA_START)) { /* save the coordinate so the next net can use it for a start point */ state->prev_x = state->curr_x; state->prev_y = state->curr_y; break; } curr_net = gerber_create_new_net (curr_net, state->layer, state->state); /* * Scale to given coordinate format * XXX only "omit leading zeros". */ if (image && image->format ){ x_scale = pow(10.0, (double)image->format->x_dec); y_scale = pow(10.0, (double)image->format->y_dec); } x_scale *= scale; y_scale *= scale; curr_net->start_x = (double)state->prev_x / x_scale; curr_net->start_y = (double)state->prev_y / y_scale; curr_net->stop_x = (double)state->curr_x / x_scale; curr_net->stop_y = (double)state->curr_y / y_scale; delta_cp_x = (double)state->delta_cp_x / x_scale; delta_cp_y = (double)state->delta_cp_y / y_scale; switch (state->interpolation) { case GERBV_INTERPOLATION_CW_CIRCULAR : case GERBV_INTERPOLATION_CCW_CIRCULAR : { int cw = (state->interpolation == GERBV_INTERPOLATION_CW_CIRCULAR); curr_net->cirseg = g_new0 (gerbv_cirseg_t, 1); if (state->mq_on) { calc_cirseg_mq(curr_net, cw, delta_cp_x, delta_cp_y); } else { calc_cirseg_sq(curr_net, cw, delta_cp_x, delta_cp_y); /* * In single quadrant circular interpolation Ix and Jy * incremental distance must be unsigned. */ if (delta_cp_x < 0 || delta_cp_y < 0) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Signed incremental distance IxJy " "in single quadrant %s circular " "interpolation %s at line %ld " "in file \"%s\""), cw? _("CW"): _("CCW"), cw? "G02": "G03", line_num, fd->filename); } } break; } case GERBV_INTERPOLATION_PAREA_START : /* * To be able to get back and fill in number of polygon corners */ state->parea_start_node = curr_net; state->in_parea_fill = 1; polygonPoints = 0; boundingBox = boundingBoxNew; break; case GERBV_INTERPOLATION_PAREA_END : /* save the calculated bounding box to the master node */ if (state->parea_start_node != NULL) { state->parea_start_node->boundingBox = boundingBox; } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("End of polygon without start " "at line %ld in file \"%s\""), line_num, fd->filename); } /* close out the polygon */ state->parea_start_node = NULL; state->in_parea_fill = 0; polygonPoints = 0; break; default : break; } /* switch(state->interpolation) */ /* * Count number of points in Polygon Area */ if (state->in_parea_fill && state->parea_start_node) { /* * "...all lines drawn with D01 are considered edges of the * polygon. D02 closes and fills the polygon." * p.49 rs274xrevd_e.pdf * D02 -> state->aperture_state == GERBV_APERTURE_STATE_OFF */ /* UPDATE: only end the polygon during a D02 call if we've already drawn a polygon edge (with D01) */ if (state->aperture_state == GERBV_APERTURE_STATE_OFF && state->interpolation != GERBV_INTERPOLATION_PAREA_START && polygonPoints > 0) { curr_net->interpolation = GERBV_INTERPOLATION_PAREA_END; curr_net = gerber_create_new_net (curr_net, state->layer, state->state); curr_net->interpolation = GERBV_INTERPOLATION_PAREA_START; state->parea_start_node->boundingBox = boundingBox; state->parea_start_node = curr_net; polygonPoints = 0; curr_net = gerber_create_new_net (curr_net, state->layer, state->state); curr_net->start_x = (double)state->prev_x / x_scale; curr_net->start_y = (double)state->prev_y / y_scale; curr_net->stop_x = (double)state->curr_x / x_scale; curr_net->stop_y = (double)state->curr_y / y_scale; boundingBox = boundingBoxNew; } else if (state->interpolation != GERBV_INTERPOLATION_PAREA_START) polygonPoints++; } /* if (state->in_parea_fill && state->parea_start_node) */ curr_net->interpolation = state->interpolation; /* * Override circular interpolation if no center was given. * This should be a safe hack, since a good file should always * include I or J. And even if the radius is zero, the endpoint * should be the same as the start point, creating no line */ if (((state->interpolation == GERBV_INTERPOLATION_CW_CIRCULAR) || (state->interpolation == GERBV_INTERPOLATION_CCW_CIRCULAR)) && ((state->delta_cp_x == 0.0) && (state->delta_cp_y == 0.0))) curr_net->interpolation = GERBV_INTERPOLATION_LINEARx1; /* * If we detected the end of Polygon Area Fill we go back to * the interpolation we had before that. * Also if we detected any of the quadrant flags, since some * gerbers don't reset the interpolation (EagleCad again). */ if ((state->interpolation == GERBV_INTERPOLATION_PAREA_START || state->interpolation == GERBV_INTERPOLATION_PAREA_END) && state->prev_interpolation != GERBV_INTERPOLATION_PAREA_END) { state->interpolation = state->prev_interpolation; } /* * Save layer polarity and unit */ curr_net->layer = state->layer; state->delta_cp_x = 0.0; state->delta_cp_y = 0.0; curr_net->aperture = state->curr_aperture; curr_net->aperture_state = state->aperture_state; /* * For next round we save the current position as * the previous position */ state->prev_x = state->curr_x; state->prev_y = state->curr_y; /* * If we have an aperture defined at the moment we find * min and max of image with compensation for mm. */ if ((curr_net->aperture == 0) && !state->in_parea_fill) break; /* only update the min/max values and aperture stats if we are drawing */ if ((curr_net->aperture_state != GERBV_APERTURE_STATE_OFF)&& (curr_net->interpolation != GERBV_INTERPOLATION_PAREA_START)){ double repeat_off_X = 0.0, repeat_off_Y = 0.0; /* Update stats with current aperture number if not in polygon */ if (!state->in_parea_fill) { DPRINTF(" In %s(), adding 1 to D_list ...\n", __func__); int retcode = gerbv_stats_increment_D_list_count( stats->D_code_list, curr_net->aperture, 1, error_list); if (retcode == -1) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Found undefined D code D%02d " "at line %ld in file \"%s\""), curr_net->aperture, line_num, fd->filename); stats->D_unknown++; } } /* * If step_and_repeat (%SR%) is used, check min_x,max_y etc for * the ends of the step_and_repeat lattice. This goes wrong in * the case of negative dist_X or dist_Y, in which case we * should compare against the startpoints of the lines, not * the stoppoints, but that seems an uncommon case (and the * error isn't very big any way). */ repeat_off_X = (state->layer->stepAndRepeat.X - 1) * state->layer->stepAndRepeat.dist_X; repeat_off_Y = (state->layer->stepAndRepeat.Y - 1) * state->layer->stepAndRepeat.dist_Y; cairo_matrix_init (¤tMatrix, 1, 0, 0, 1, 0, 0); /* offset image */ cairo_matrix_translate (¤tMatrix, image->info->offsetA, image->info->offsetB); /* do image rotation */ cairo_matrix_rotate (¤tMatrix, image->info->imageRotation); /* it's a new layer, so recalculate the new transformation * matrix for it */ /* do any rotations */ cairo_matrix_rotate (¤tMatrix, state->layer->rotation); /* calculate current layer and state transformation matrices */ /* apply scale factor */ cairo_matrix_scale (¤tMatrix, state->state->scaleA, state->state->scaleB); /* apply offset */ cairo_matrix_translate (¤tMatrix, state->state->offsetA, state->state->offsetB); /* apply mirror */ switch (state->state->mirrorState) { case GERBV_MIRROR_STATE_FLIPA: cairo_matrix_scale (¤tMatrix, -1, 1); break; case GERBV_MIRROR_STATE_FLIPB: cairo_matrix_scale (¤tMatrix, 1, -1); break; case GERBV_MIRROR_STATE_FLIPAB: cairo_matrix_scale (¤tMatrix, -1, -1); break; default: break; } /* finally, apply axis select */ if (state->state->axisSelect == GERBV_AXIS_SELECT_SWAPAB) { /* we do this by rotating 270 (counterclockwise, then * mirroring the Y axis */ cairo_matrix_rotate (¤tMatrix, M_PI + M_PI_2); cairo_matrix_scale (¤tMatrix, 1, -1); } /* if it's a macro, step through all the primitive components and calculate the true bounding box */ if ((image->aperture[curr_net->aperture] != NULL) && (image->aperture[curr_net->aperture]->type == GERBV_APTYPE_MACRO)) { gerbv_simplified_amacro_t *ls = image->aperture[curr_net->aperture]->simplified; while (ls != NULL) { gdouble offsetx = 0, offsety = 0, widthx = 0, widthy = 0; gboolean calculatedAlready = FALSE; if (ls->type == GERBV_APTYPE_MACRO_CIRCLE) { offsetx=ls->parameter[CIRCLE_CENTER_X]; offsety=ls->parameter[CIRCLE_CENTER_Y]; gerbv_rotate_coord(&offsetx, &offsety, DEG2RAD(ls->parameter[CIRCLE_ROTATION])); widthx=widthy=ls->parameter[CIRCLE_DIAMETER]; } else if (ls->type == GERBV_APTYPE_MACRO_OUTLINE) { int pointCounter,numberOfPoints; gdouble rotation = DEG2RAD(ls->parameter[ OUTLINE_ROTATION_IDX(ls->parameter)]); numberOfPoints = ls->parameter[OUTLINE_NUMBER_OF_POINTS] + 1; for (pointCounter = 0; pointCounter < numberOfPoints; pointCounter++) { gdouble px = ls->parameter[OUTLINE_X_IDX_OF_POINT(pointCounter)]; gdouble py = ls->parameter[OUTLINE_Y_IDX_OF_POINT(pointCounter)]; gerbv_rotate_coord(&px, &py, rotation); gerber_update_min_and_max (&boundingBox, curr_net->stop_x + px, curr_net->stop_y + py, 0,0,0,0); } calculatedAlready = TRUE; } else if (ls->type == GERBV_APTYPE_MACRO_POLYGON) { offsetx = ls->parameter[POLYGON_CENTER_X]; offsety = ls->parameter[POLYGON_CENTER_Y]; gerbv_rotate_coord(&offsetx, &offsety, DEG2RAD(ls->parameter[POLYGON_ROTATION])); widthx = widthy = ls->parameter[POLYGON_DIAMETER]; } else if (ls->type == GERBV_APTYPE_MACRO_MOIRE) { offsetx = ls->parameter[MOIRE_CENTER_X]; offsety = ls->parameter[MOIRE_CENTER_Y]; gerbv_rotate_coord(&offsetx, &offsety, DEG2RAD(ls->parameter[MOIRE_ROTATION])); widthx = widthy = ls->parameter[MOIRE_OUTSIDE_DIAMETER]; } else if (ls->type == GERBV_APTYPE_MACRO_THERMAL) { offsetx = ls->parameter[THERMAL_CENTER_X]; offsety = ls->parameter[THERMAL_CENTER_Y]; gerbv_rotate_coord(&offsetx, &offsety, DEG2RAD(ls->parameter[THERMAL_ROTATION])); widthx = widthy = ls->parameter[THERMAL_OUTSIDE_DIAMETER]; } else if (ls->type == GERBV_APTYPE_MACRO_LINE20) { gdouble rotation = DEG2RAD(ls->parameter[LINE20_ROTATION]); gdouble sx = ls->parameter[LINE20_START_X]; gdouble sy = ls->parameter[LINE20_START_Y]; gdouble ex = ls->parameter[LINE20_END_X]; gdouble ey = ls->parameter[LINE20_END_Y]; gerbv_rotate_coord(&sx, &sy, rotation); gerbv_rotate_coord(&ex, &ey, rotation); widthx = widthy = ls->parameter[LINE20_LINE_WIDTH]; gerber_update_min_and_max (&boundingBox, curr_net->stop_x + sx, curr_net->stop_y + sy, widthx/2,widthx/2,widthy/2,widthy/2); gerber_update_min_and_max (&boundingBox, curr_net->stop_x + ex, curr_net->stop_y + ey, widthx/2,widthx/2,widthy/2,widthy/2); calculatedAlready = TRUE; } else if (ls->type == GERBV_APTYPE_MACRO_LINE21) { gdouble largestDimension = hypot(ls->parameter[LINE21_WIDTH], ls->parameter[LINE21_HEIGHT]); offsetx = ls->parameter[LINE21_CENTER_X]; offsety = ls->parameter[LINE21_CENTER_Y]; gerbv_rotate_coord(&offsetx, &offsety, DEG2RAD(ls->parameter[LINE21_ROTATION])); widthx = widthy = largestDimension; } else if (ls->type == GERBV_APTYPE_MACRO_LINE22) { gdouble largestDimension = hypot(ls->parameter[LINE22_WIDTH], ls->parameter[LINE22_HEIGHT]); offsetx = ls->parameter[LINE22_LOWER_LEFT_X] + ls->parameter[LINE22_WIDTH]/2; offsety = ls->parameter[LINE22_LOWER_LEFT_Y] + ls->parameter[LINE22_HEIGHT]/2; gerbv_rotate_coord(&offsetx, &offsety, DEG2RAD(ls->parameter[LINE22_ROTATION])); widthx = widthy=largestDimension; } if (!calculatedAlready) { gerber_update_min_and_max (&boundingBox, curr_net->stop_x + offsetx, curr_net->stop_y + offsety, widthx/2,widthx/2,widthy/2,widthy/2); } ls = ls->next; } } else { if (image->aperture[curr_net->aperture] != NULL) { aperture_sizeX = image->aperture[curr_net->aperture]->parameter[0]; if ((image->aperture[curr_net->aperture]->type == GERBV_APTYPE_RECTANGLE) || (image->aperture[curr_net->aperture]->type == GERBV_APTYPE_OVAL)) { aperture_sizeY = image->aperture[curr_net->aperture]->parameter[1]; } else aperture_sizeY = aperture_sizeX; } else { /* this is usually for polygon fills, where the aperture width is "zero" */ aperture_sizeX = aperture_sizeY = 0; } /* if it's an arc path, use a special calc */ if ((curr_net->interpolation == GERBV_INTERPOLATION_CW_CIRCULAR) || (curr_net->interpolation == GERBV_INTERPOLATION_CCW_CIRCULAR)) { calc_cirseg_bbox(curr_net->cirseg, aperture_sizeX, aperture_sizeY, &boundingBox); } else { /* check both the start and stop of the aperture points against a running min/max counter */ /* Note: only check start coordinate if this isn't a flash, since the start point may be bogus if it is a flash */ if (curr_net->aperture_state != GERBV_APERTURE_STATE_FLASH) { gerber_update_min_and_max (&boundingBox, curr_net->start_x, curr_net->start_y, aperture_sizeX/2,aperture_sizeX/2, aperture_sizeY/2,aperture_sizeY/2); } gerber_update_min_and_max (&boundingBox, curr_net->stop_x, curr_net->stop_y, aperture_sizeX/2,aperture_sizeX/2, aperture_sizeY/2,aperture_sizeY/2); } } /* update the info bounding box with this latest bounding box */ /* don't change the bounding box if the polarity is clear */ if (state->layer->polarity != GERBV_POLARITY_CLEAR){ gerber_update_image_min_max(&boundingBox, repeat_off_X, repeat_off_Y, image); } /* optionally update the knockout measurement box */ if (knockoutMeasure) { if (boundingBox.left < knockoutLimitXmin) knockoutLimitXmin = boundingBox.left; if (boundingBox.right+repeat_off_X > knockoutLimitXmax) knockoutLimitXmax = boundingBox.right+repeat_off_X; if (boundingBox.bottom < knockoutLimitYmin) knockoutLimitYmin = boundingBox.bottom; if (boundingBox.top+repeat_off_Y > knockoutLimitYmax) knockoutLimitYmax = boundingBox.top+repeat_off_Y; } /* if we're not in a polygon fill, then update the object bounding box */ if (!state->in_parea_fill) { curr_net->boundingBox = boundingBox; boundingBox = boundingBoxNew; } } break; case '\0': case '\t': case ' ': break; case '\n': line_num++; /* Get char, if any, from pair */ read = gerb_fgetc(fd); if (read != '\r' && read != EOF) gerb_ungetc(fd); break; case '\r': line_num++; /* Get char, if any, from pair */ read = gerb_fgetc(fd); if (read != '\n' && read != EOF) gerb_ungetc(fd); break; default: stats->unknown++; gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Found unknown character '%s' (0x%x) " "at line %ld in file \"%s\""), gerbv_escape_char(read), read, line_num, fd->filename); } /* switch((char) (read & 0xff)) */ } return foundEOF; } /* ------------------------------------------------------------------ */ /*! This is a wrapper which gets called from top level. It * does some initialization and pre-processing, and * then calls gerber_parse_file_segment * which processes the actual file. Then it does final * modifications to the image created. */ gerbv_image_t * parse_gerb(gerb_file_t *fd, gchar *directoryPath) { gerb_state_t *state = NULL; gerbv_image_t *image = NULL; gerbv_net_t *curr_net = NULL; gerbv_stats_t *stats; gboolean foundEOF = FALSE; /* added by t.motylewski@bfad.de * many locales redefine "." as "," and so on, * so sscanf and strtod has problems when * reading files using %f format */ setlocale(LC_NUMERIC, "C" ); /* * Create new state. This is used locally to keep track * of the photoplotter's state as the Gerber is read in. */ state = g_new0 (gerb_state_t, 1); /* * Create new image. This will be returned. */ image = gerbv_create_image(image, "RS274-X (Gerber) File"); if (image == NULL) GERB_FATAL_ERROR("malloc image failed in %s()", __FUNCTION__); curr_net = image->netlist; image->layertype = GERBV_LAYERTYPE_RS274X; image->gerbv_stats = gerbv_stats_new(); if (image->gerbv_stats == NULL) GERB_FATAL_ERROR("malloc gerbv_stats failed in %s()", __FUNCTION__); stats = image->gerbv_stats; /* set active layer and netstate to point to first default one created */ state->layer = image->layers; state->state = image->states; curr_net->layer = state->layer; curr_net->state = state->state; /* * Start parsing */ DPRINTF("In %s(), starting to parse file...\n", __func__); foundEOF = gerber_parse_file_segment (1, image, state, curr_net, stats, fd, directoryPath); if (!foundEOF) { gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1, _("Missing Gerber EOF code in file \"%s\""), fd->filename); } g_free(state); DPRINTF(" ... done parsing Gerber file\n"); gerber_update_any_running_knockout_measurements (image); gerber_calculate_final_justify_effects(image); return image; } /* parse_gerb */ /* ------------------------------------------------------------------- */ /*! Checks for signs that this is a RS-274X file * Returns TRUE if it is, FALSE if not. */ gboolean gerber_is_rs274x_p(gerb_file_t *fd, gboolean *returnFoundBinary) { char *buf; int len = 0; char *letter; int i; gboolean found_binary = FALSE; gboolean found_ADD = FALSE; gboolean found_percent_cmd = FALSE; gboolean found_D0 = FALSE; gboolean found_D2 = FALSE; gboolean found_M0 = FALSE; gboolean found_M2 = FALSE; gboolean found_star = FALSE; gboolean found_X = FALSE; gboolean found_Y = FALSE; DPRINTF("%s(%p, %p), fd->fd = %p\n", __func__, fd, returnFoundBinary, fd->fd); buf = (char *) g_malloc(MAXL); if (buf == NULL) GERB_FATAL_ERROR("malloc buf failed while checking for rs274x in %s()", __FUNCTION__); while (fgets(buf, MAXL, fd->fd) != NULL) { DPRINTF("buf = \"%s\"\n", buf); len = strlen(buf); /* First look through the file for indications of its type by * checking that file is not binary (non-printing chars and white * spaces) */ for (i = 0; i < len; i++) { if (!isprint((int) buf[i]) && (buf[i] != '\r') && (buf[i] != '\n') && (buf[i] != '\t')) { found_binary = TRUE; DPRINTF("found_binary (%d)\n", buf[i]); } } if (g_strstr_len(buf, len, "%ADD")) { found_ADD = TRUE; DPRINTF("found_ADD\n"); } if (g_strstr_len(buf, len, "%FS") || g_strstr_len(buf, len, "%MO") || g_strstr_len(buf, len, "%LP") || g_strstr_len(buf, len, "%AM")) { found_percent_cmd = TRUE; DPRINTF("found_percent_cmd\n"); } if (g_strstr_len(buf, len, "D00") || g_strstr_len(buf, len, "D0")) { found_D0 = TRUE; DPRINTF("found_D0\n"); } if (g_strstr_len(buf, len, "D02") || g_strstr_len(buf, len, "D2")) { found_D2 = TRUE; DPRINTF("found_D2\n"); } if (g_strstr_len(buf, len, "M00") || g_strstr_len(buf, len, "M0")) { found_M0 = TRUE; DPRINTF("found_M0\n"); } if (g_strstr_len(buf, len, "M02") || g_strstr_len(buf, len, "M2")) { found_M2 = TRUE; DPRINTF("found_M2\n"); } if (g_strstr_len(buf, len, "*")) { found_star = TRUE; DPRINTF("found_star\n"); } /* look for X or Y */ if ((letter = g_strstr_len(buf, len, "X")) != NULL) { if (isdigit((int) letter[1])) { /* grab char after X */ found_X = TRUE; DPRINTF("found_X\n"); } } if ((letter = g_strstr_len(buf, len, "Y")) != NULL) { if (isdigit((int) letter[1])) { /* grab char after Y */ found_Y = TRUE; DPRINTF("found_Y\n"); } } } rewind(fd->fd); free(buf); *returnFoundBinary = found_binary; /* Now form logical expression determining if the file is RS-274X */ if ((found_D0 || found_D2 || found_M0 || found_M2) && (found_ADD || found_percent_cmd) && found_star && (found_X || found_Y)) return TRUE; return FALSE; } /* gerber_is_rs274x */ /* ------------------------------------------------------------------- */ /*! Checks for signs that this is a RS-274D file * Returns TRUE if it is, FALSE if not. */ gboolean gerber_is_rs274d_p(gerb_file_t *fd) { char *buf; int len = 0; char *letter; int i; gboolean found_binary = FALSE; gboolean found_ADD = FALSE; gboolean found_D0 = FALSE; gboolean found_D2 = FALSE; gboolean found_M0 = FALSE; gboolean found_M2 = FALSE; gboolean found_star = FALSE; gboolean found_X = FALSE; gboolean found_Y = FALSE; buf = malloc(MAXL); if (buf == NULL) GERB_FATAL_ERROR("malloc buf failed while checking for rs274d in %s()", __FUNCTION__); while (fgets(buf, MAXL, fd->fd) != NULL) { len = strlen(buf); /* First look through the file for indications of its type */ /* check that file is not binary (non-printing chars */ for (i = 0; i < len; i++) { if (!isprint( (int) buf[i]) && (buf[i] != '\r') && (buf[i] != '\n') && (buf[i] != '\t')) { found_binary = TRUE; } } if (g_strstr_len(buf, len, "%ADD")) { found_ADD = TRUE; } if (g_strstr_len(buf, len, "D00") || g_strstr_len(buf, len, "D0")) { found_D0 = TRUE; } if (g_strstr_len(buf, len, "D02") || g_strstr_len(buf, len, "D2")) { found_D2 = TRUE; } if (g_strstr_len(buf, len, "M00") || g_strstr_len(buf, len, "M0")) { found_M0 = TRUE; } if (g_strstr_len(buf, len, "M02") || g_strstr_len(buf, len, "M2")) { found_M2 = TRUE; } if (g_strstr_len(buf, len, "*")) { found_star = TRUE; } /* look for X or Y */ if ((letter = g_strstr_len(buf, len, "X")) != NULL) { /* grab char after X */ if (isdigit( (int) letter[1])) { found_X = TRUE; } } if ((letter = g_strstr_len(buf, len, "Y")) != NULL) { /* grab char after Y */ if (isdigit( (int) letter[1])) { found_Y = TRUE; } } } rewind(fd->fd); free(buf); /* Now form logical expression determining if the file is RS-274D */ if ((found_D0 || found_D2 || found_M0 || found_M2) && !found_ADD && found_star && (found_X || found_Y) && !found_binary) return TRUE; return FALSE; } /* gerber_is_rs274d */ /* ------------------------------------------------------------------- */ /*! This function reads a G number and updates the current * state. It also updates the G stats counters */ static void parse_G_code(gerb_file_t *fd, gerb_state_t *state, gerbv_image_t *image, long int *line_num_p) { int op_int; gerbv_format_t *format = image->format; gerbv_stats_t *stats = image->gerbv_stats; gerbv_error_list_t *error_list = stats->error_list; int c; op_int=gerb_fgetint(fd, NULL); /* Emphasize text with new line '\n' in the beginning */ DPRINTF("\n Found G%02d at line %ld (%s)\n", op_int, *line_num_p, gerber_g_code_name(op_int)); switch(op_int) { case 0: /* Move */ /* Is this doing anything really? */ stats->G0++; break; case 1: /* Linear Interpolation (1X scale) */ state->interpolation = GERBV_INTERPOLATION_LINEARx1; stats->G1++; break; case 2: /* Clockwise Linear Interpolation */ state->interpolation = GERBV_INTERPOLATION_CW_CIRCULAR; stats->G2++; break; case 3: /* Counter Clockwise Linear Interpolation */ state->interpolation = GERBV_INTERPOLATION_CCW_CIRCULAR; stats->G3++; break; case 4: /* Ignore Data Block */ /* Don't do anything, just read 'til * */ do { c = gerb_fgetc(fd); if (c == '\r' || c == '\n') { gerbv_stats_printf(error_list, GERBV_MESSAGE_WARNING, -1, _("Found newline while parsing " "G04 code at line %ld in file \"%s\", " "maybe you forgot a \"*\"?"), *line_num_p, fd->filename); } } while (c != EOF && c != '*'); stats->G4++; break; case 10: /* Linear Interpolation (10X scale) */ state->interpolation = GERBV_INTERPOLATION_LINEARx10; stats->G10++; break; case 11: /* Linear Interpolation (0.1X scale) */ state->interpolation = GERBV_INTERPOLATION_LINEARx01; stats->G11++; break; case 12: /* Linear Interpolation (0.01X scale) */ state->interpolation = GERBV_INTERPOLATION_LINEARx001; stats->G12++; break; case 36: /* Turn on Polygon Area Fill */ state->prev_interpolation = state->interpolation; state->interpolation = GERBV_INTERPOLATION_PAREA_START; state->changed = 1; stats->G36++; break; case 37: /* Turn off Polygon Area Fill */ state->interpolation = GERBV_INTERPOLATION_PAREA_END; state->changed = 1; stats->G37++; break; case 54: /* Tool prepare */ /* XXX Maybe uneccesary??? */ if (gerb_fgetc(fd) == 'D') { int a = gerb_fgetint(fd, NULL); if ((a >= 0) && (a <= APERTURE_MAX)) { state->curr_aperture = a; } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Found aperture D%02d out of bounds while parsing " "G code at line %ld in file \"%s\""), a, *line_num_p, fd->filename); } } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Found unexpected code after G54 " "at line %ld in file \"%s\""), *line_num_p, fd->filename); /* TODO: insert error count here */ } stats->G54++; break; case 55: /* Prepare for flash */ stats->G55++; break; case 70: /* Specify inches */ state->state = gerbv_image_return_new_netstate (state->state); state->state->unit = GERBV_UNIT_INCH; stats->G70++; break; case 71: /* Specify millimeters */ state->state = gerbv_image_return_new_netstate (state->state); state->state->unit = GERBV_UNIT_MM; stats->G71++; break; case 74: /* Disable 360 circular interpolation */ state->mq_on = 0; stats->G74++; break; case 75: /* Enable 360 circular interpolation */ state->mq_on = 1; stats->G75++; break; case 90: /* Specify absolut format */ if (format) format->coordinate = GERBV_COORDINATE_ABSOLUTE; stats->G90++; break; case 91: /* Specify incremental format */ if (format) format->coordinate = GERBV_COORDINATE_INCREMENTAL; stats->G91++; break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Encountered unknown G code G%02d " "at line %ld in file \"%s\""), op_int, *line_num_p, fd->filename); gerbv_stats_printf(error_list, GERBV_MESSAGE_WARNING, -1, _("Ignoring unknown G code G%02d"), op_int); stats->G_unknown++; /* TODO: insert error count here */ break; } return; } /* parse_G_code */ /* ------------------------------------------------------------------ */ /*! This function reads the numeric value of a D code and updates the * state. It also updates the D stats counters */ static void parse_D_code(gerb_file_t *fd, gerb_state_t *state, gerbv_image_t *image, long int *line_num_p) { int a; gerbv_stats_t *stats = image->gerbv_stats; gerbv_error_list_t *error_list = stats->error_list; a = gerb_fgetint(fd, NULL); DPRINTF(" Found D%02d code at line %ld\n", a, *line_num_p); switch(a) { case 0 : /* Invalid code */ gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Found invalid D00 code at line %ld in file \"%s\""), *line_num_p, fd->filename); stats->D_error++; break; case 1 : /* Exposure on */ state->aperture_state = GERBV_APERTURE_STATE_ON; state->changed = 1; stats->D1++; break; case 2 : /* Exposure off */ state->aperture_state = GERBV_APERTURE_STATE_OFF; state->changed = 1; stats->D2++; break; case 3 : /* Flash aperture */ state->aperture_state = GERBV_APERTURE_STATE_FLASH; state->changed = 1; stats->D3++; break; default: /* Aperture in use */ if ((a >= 0) && (a <= APERTURE_MAX)) { state->curr_aperture = a; } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Found out of bounds aperture D%02d " "at line %ld in file \"%s\""), a, *line_num_p, fd->filename); stats->D_error++; } state->changed = 0; break; } return; } /* parse_D_code */ /* ------------------------------------------------------------------ */ static int parse_M_code(gerb_file_t *fd, gerbv_image_t *image, long int *line_num_p) { int op_int; gerbv_stats_t *stats = image->gerbv_stats; op_int=gerb_fgetint(fd, NULL); switch (op_int) { case 0: /* Program stop */ stats->M0++; return 1; case 1: /* Optional stop */ stats->M1++; return 2; case 2: /* End of program */ stats->M2++; return 3; default: gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1, _("Encountered unknown M%02d code at line %ld in file \"%s\""), op_int, *line_num_p, fd->filename); gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1, _("Ignoring unknown M%02d code"), op_int); stats->M_unknown++; } return 0; } /* parse_M_code */ /* ------------------------------------------------------------------ */ static void parse_rs274x(gint levelOfRecursion, gerb_file_t *fd, gerbv_image_t *image, gerb_state_t *state, gerbv_net_t *curr_net, gerbv_stats_t *stats, gchar *directoryPath, long int *line_num_p) { int op[2]; char str[3]; int tmp; gerbv_aperture_t *a = NULL; gerbv_amacro_t *tmp_amacro; int ano; gdouble scale = 1.0; gerbv_error_list_t *error_list = stats->error_list; if (state->state->unit == GERBV_UNIT_MM) scale = 25.4; op[0] = gerb_fgetc(fd); op[1] = gerb_fgetc(fd); if (op[0] == EOF || op[1] == EOF) gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unexpected EOF found in file \"%s\""), fd->filename); switch (A2I(op[0], op[1])){ case A2I('A','B'): /* Aperture Block */ op[0] = gerb_fgetc(fd); if (op[0] == 'D') { /* %ABD*% — open block definition */ int ap_num = 0; int c; while (((c = gerb_fgetc(fd)) != EOF) && (c != '*')) { if ((c >= '0') && (c <= '9')) { ap_num = ap_num * 10 + (c - '0'); } } if (state->in_block) { /* Reject nested %ABD*% — the parser keeps a single * (block_aperture_num, block_netlist, saved_curr_net) * triple, not a stack. Silently overwriting it would * leak the outer block's net pointer and route the * inner block's flashes into the outer's net list. The * Gerber X2 spec permits nesting, but real-world * generators don't emit it; if a file ever needs it * we'll grow this into a stack. The outer block is * preserved; the inner definition is dropped (`ap_num` * never gets allocated as an aperture). */ gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Nested %%AB%% aperture block (D%d inside D%d) " "is not supported at line %ld in file \"%s\" — " "inner block dropped"), ap_num, state->block_aperture_num, *line_num_p, fd->filename); } else if ((ap_num < APERTURE_MIN) || (ap_num >= APERTURE_MAX)) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Aperture block D-code %d out of range " "at line %ld in file \"%s\""), ap_num, *line_num_p, fd->filename); } else { state->in_block = TRUE; state->block_aperture_num = ap_num; /* Allocate the aperture and set its type */ image->aperture[ap_num] = g_new0(gerbv_aperture_t, 1); image->aperture[ap_num]->type = GERBV_APTYPE_BLOCK; /* Register in stats so D-code usage tracking works */ gerbv_stats_add_aperture(stats->aperture_list, -1, ap_num, GERBV_APTYPE_BLOCK, image->aperture[ap_num]->parameter); gerbv_stats_add_to_D_list(stats->D_code_list, ap_num); /* Create head node for the block net list */ state->block_netlist = g_new0(gerbv_net_t, 1); state->block_netlist->layer = state->layer; state->block_netlist->state = state->state; } } else { /* %AB*% — close block definition */ gerb_ungetc(fd); if (state->in_block) { image->aperture[state->block_aperture_num]->block_netlist = state->block_netlist; state->in_block = FALSE; state->block_netlist = NULL; } } break; /* * Directive parameters */ case A2I('A','S'): /* Axis Select */ op[0] = gerb_fgetc(fd); op[1] = gerb_fgetc(fd); state->state = gerbv_image_return_new_netstate (state->state); if (op[0] == EOF || op[1] == EOF) gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unexpected EOF found in file \"%s\""), fd->filename); if (((op[0] == 'A') && (op[1] == 'Y')) || ((op[0] == 'B') && (op[1] == 'X'))) { state->state->axisSelect = GERBV_AXIS_SELECT_SWAPAB; } else { state->state->axisSelect = GERBV_AXIS_SELECT_NOSELECT; } op[0] = gerb_fgetc(fd); op[1] = gerb_fgetc(fd); if (op[0] == EOF || op[1] == EOF) gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unexpected EOF found in file \"%s\""), fd->filename); if (((op[0] == 'A') && (op[1] == 'Y')) || ((op[0] == 'B') && (op[1] == 'X'))) { state->state->axisSelect = GERBV_AXIS_SELECT_SWAPAB; } else { state->state->axisSelect = GERBV_AXIS_SELECT_NOSELECT; } break; case A2I('F','S'): /* Format Statement */ image->format = g_new0 (gerbv_format_t,1); switch (gerb_fgetc(fd)) { case 'L': image->format->omit_zeros = GERBV_OMIT_ZEROS_LEADING; break; case 'T': image->format->omit_zeros = GERBV_OMIT_ZEROS_TRAILING; break; case 'D': image->format->omit_zeros = GERBV_OMIT_ZEROS_EXPLICIT; break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("EagleCad bug detected: Undefined handling of zeros " "in format code at line %ld in file \"%s\""), *line_num_p, fd->filename); gerbv_stats_printf(error_list, GERBV_MESSAGE_WARNING, -1, _("Defaulting to omitting leading zeros")); gerb_ungetc(fd); image->format->omit_zeros = GERBV_OMIT_ZEROS_LEADING; } switch (gerb_fgetc(fd)) { case 'A': image->format->coordinate = GERBV_COORDINATE_ABSOLUTE; break; case 'I': image->format->coordinate = GERBV_COORDINATE_INCREMENTAL; break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Invalid coordinate type defined in format code " "at line %ld in file \"%s\""), *line_num_p, fd->filename); gerbv_stats_printf(error_list, GERBV_MESSAGE_WARNING, -1, _("Defaulting to absolute coordinates")); image->format->coordinate = GERBV_COORDINATE_ABSOLUTE; } op[0] = gerb_fgetc(fd); while((op[0] != '*')&&(op[0] != EOF)) { switch (op[0]) { case 'N': op[0] = (char)gerb_fgetc(fd); image->format->lim_seqno = op[0] - '0'; break; case 'G': op[0] = (char)gerb_fgetc(fd); image->format->lim_gf = op[0] - '0'; break; case 'D': op[0] = (char)gerb_fgetc(fd); image->format->lim_pf = op[0] - '0'; break; case 'M': op[0] = (char)gerb_fgetc(fd); image->format->lim_mf = op[0] - '0'; break; case 'X' : op[0] = gerb_fgetc(fd); if ((op[0] < '0') || (op[0] > '6')) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Illegal format size '%s' " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } image->format->x_int = op[0] - '0'; op[0] = gerb_fgetc(fd); if ((op[0] < '0') || (op[0] > '6')) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Illegal format size '%s' " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } image->format->x_dec = op[0] - '0'; break; case 'Y': op[0] = gerb_fgetc(fd); if ((op[0] < '0') || (op[0] > '6')) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Illegal format size '%s' " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } image->format->y_int = op[0] - '0'; op[0] = gerb_fgetc(fd); if ((op[0] < '0') || (op[0] > '6')) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Illegal format size '%s' " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } image->format->y_dec = op[0] - '0'; break; default : gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Illegal format statement '%s' " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); gerbv_stats_printf(error_list, GERBV_MESSAGE_WARNING, -1, _("Ignoring invalid format statement")); } op[0] = gerb_fgetc(fd); } break; case A2I('M','I'): /* Mirror Image */ op[0] = gerb_fgetc(fd); state->state = gerbv_image_return_new_netstate (state->state); while ((op[0] != '*')&&(op[0] != EOF)) { gint readValue=0; switch (op[0]) { case 'A' : readValue = gerb_fgetint(fd, NULL); if (readValue == 1) { if (state->state->mirrorState == GERBV_MIRROR_STATE_FLIPB) state->state->mirrorState=GERBV_MIRROR_STATE_FLIPAB; else state->state->mirrorState=GERBV_MIRROR_STATE_FLIPA; } break; case 'B' : readValue = gerb_fgetint(fd, NULL); if (readValue == 1) { if (state->state->mirrorState == GERBV_MIRROR_STATE_FLIPA) state->state->mirrorState=GERBV_MIRROR_STATE_FLIPAB; else state->state->mirrorState=GERBV_MIRROR_STATE_FLIPB; } break; default : gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Wrong character '%s' in mirror " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } op[0] = gerb_fgetc(fd); } break; case A2I('M','O'): /* Mode of Units */ op[0] = gerb_fgetc(fd); op[1] = gerb_fgetc(fd); if (op[0] == EOF || op[1] == EOF) gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unexpected EOF found in file \"%s\""), fd->filename); switch (A2I(op[0],op[1])) { case A2I('I','N'): state->state = gerbv_image_return_new_netstate (state->state); state->state->unit = GERBV_UNIT_INCH; break; case A2I('M','M'): state->state = gerbv_image_return_new_netstate (state->state); state->state->unit = GERBV_UNIT_MM; break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Illegal unit '%s%s' at line %ld in file \"%s\""), gerbv_escape_char(op[0]), gerbv_escape_char(op[1]), *line_num_p, fd->filename); } break; case A2I('O','F'): /* Offset */ op[0] = gerb_fgetc(fd); while ((op[0] != '*')&&(op[0] != EOF)) { switch (op[0]) { case 'A' : state->state->offsetA = gerb_fgetdouble(fd) / scale; break; case 'B' : state->state->offsetB = gerb_fgetdouble(fd) / scale; break; default : gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Wrong character '%s' in offset " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } op[0] = gerb_fgetc(fd); } break; case A2I('I','F'): /* Include file */ { gchar *includeFilename = gerb_fgetstring(fd, '*'); if (includeFilename) { gchar *fullPath; if (!g_path_is_absolute(includeFilename)) { fullPath = g_build_filename (directoryPath, includeFilename, NULL); } else { fullPath = g_strdup (includeFilename); } if (levelOfRecursion < 10) { gerb_file_t *includefd = NULL; includefd = gerb_fopen(fullPath); if (includefd) { gerber_parse_file_segment (levelOfRecursion + 1, image, state, curr_net, stats, includefd, directoryPath); gerb_fclose(includefd); } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Included file \"%s\" cannot be found " "at line %ld in file \"%s\""), fullPath, *line_num_p, fd->filename); } g_free (fullPath); } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Parser encountered more than 10 levels of " "include file recursion which is not allowed " "by the RS-274X spec")); } g_free (includeFilename); } } break; case A2I('I','O'): /* Image offset */ op[0] = gerb_fgetc(fd); while ((op[0] != '*')&&(op[0] != EOF)) { switch (op[0]) { case 'A' : image->info->offsetA = gerb_fgetdouble(fd) / scale; break; case 'B' : image->info->offsetB = gerb_fgetdouble(fd) / scale; break; default : gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Wrong character '%s' in image offset " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } op[0] = gerb_fgetc(fd); } break; case A2I('S','F'): /* Scale Factor */ state->state = gerbv_image_return_new_netstate (state->state); if (gerb_fgetc(fd) == 'A') state->state->scaleA = gerb_fgetdouble(fd); else gerb_ungetc(fd); if (gerb_fgetc(fd) == 'B') state->state->scaleB = gerb_fgetdouble(fd); else gerb_ungetc(fd); break; case A2I('I','C'): /* Input Code */ /* Thanks to Stephen Adam for providing this information. As he writes: * btw, here's a logic puzzle for you. If you need to * read the gerber file to see how it's encoded, then * how can you read it? */ op[0] = gerb_fgetc(fd); op[1] = gerb_fgetc(fd); if (op[0] == EOF || op[1] == EOF) gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unexpected EOF found in file \"%s\""), fd->filename); switch (A2I(op[0],op[1])) { case A2I('A','S'): image->info->encoding = GERBV_ENCODING_ASCII; break; case A2I('E','B'): image->info->encoding = GERBV_ENCODING_EBCDIC; break; case A2I('B','C'): image->info->encoding = GERBV_ENCODING_BCD; break; case A2I('I','S'): image->info->encoding = GERBV_ENCODING_ISO_ASCII; break; case A2I('E','I'): image->info->encoding = GERBV_ENCODING_EIA; break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unknown input code (IC) '%s%s' " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), gerbv_escape_char(op[1]), *line_num_p, fd->filename); } break; /* Image parameters */ case A2I('I','J'): /* Image Justify */ op[0] = gerb_fgetc(fd); image->info->imageJustifyTypeA = GERBV_JUSTIFY_LOWERLEFT; image->info->imageJustifyTypeB = GERBV_JUSTIFY_LOWERLEFT; image->info->imageJustifyOffsetA = 0.0; image->info->imageJustifyOffsetB = 0.0; while ((op[0] != '*')&&(op[0] != EOF)) { switch (op[0]) { case 'A' : op[0] = gerb_fgetc(fd); if (op[0] == 'C') { image->info->imageJustifyTypeA = GERBV_JUSTIFY_CENTERJUSTIFY; } else if (op[0] == 'L') { image->info->imageJustifyTypeA = GERBV_JUSTIFY_LOWERLEFT; } else { gerb_ungetc (fd); image->info->imageJustifyOffsetA = gerb_fgetdouble(fd) / scale; } break; case 'B' : op[0] = gerb_fgetc(fd); if (op[0] == 'C') { image->info->imageJustifyTypeB = GERBV_JUSTIFY_CENTERJUSTIFY; } else if (op[0] == 'L') { image->info->imageJustifyTypeB = GERBV_JUSTIFY_LOWERLEFT; } else { gerb_ungetc (fd); image->info->imageJustifyOffsetB = gerb_fgetdouble(fd) / scale; } break; default : gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Wrong character '%s' in image justify " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } op[0] = gerb_fgetc(fd); } break; case A2I('I','N'): /* Image Name */ image->info->name = gerb_fgetstring(fd, '*'); break; case A2I('I','P'): /* Image Polarity */ for (ano = 0; ano < 3; ano++) { op[0] = gerb_fgetc(fd); if (op[0] == EOF) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unexpected EOF while reading image polarity (IP) " "in file \"%s\""), fd->filename); } str[ano] = (char)op[0]; } if (strncmp(str, "POS", 3) == 0) image->info->polarity = GERBV_POLARITY_POSITIVE; else if (strncmp(str, "NEG", 3) == 0) image->info->polarity = GERBV_POLARITY_NEGATIVE; else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unknown polarity '%s%s%s' " "at line %ld in file \"%s\""), gerbv_escape_char(str[0]), gerbv_escape_char(str[1]), gerbv_escape_char(str[2]), *line_num_p, fd->filename); } break; case A2I('I','R'): /* Image Rotation */ tmp = gerb_fgetint(fd, NULL) % 360; if (tmp == 0) image->info->imageRotation = 0.0; else if (tmp == 90) image->info->imageRotation = M_PI_2; else if (tmp == 180) image->info->imageRotation = M_PI; else if (tmp == 270) image->info->imageRotation = M_PI + M_PI_2; else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Image rotation must be 0, 90, 180 or 270 " "(is actually %d) at line %ld in file \"%s\""), tmp, *line_num_p, fd->filename); } break; case A2I('P','F'): /* Plotter Film */ image->info->plotterFilm = gerb_fgetstring(fd, '*'); break; /* Aperture parameters */ case A2I('A','D'): /* Aperture Description */ a = (gerbv_aperture_t *) g_new0 (gerbv_aperture_t,1); ano = parse_aperture_definition(fd, a, image, scale, line_num_p); if (ano == -1) { /* error with line parse, so just quietly ignore */ } else if ((ano >= 0) && (ano <= APERTURE_MAX)) { a->unit = state->state->unit; image->aperture[ano] = a; DPRINTF(" In %s(), adding new aperture to aperture list ...\n", __func__); gerbv_stats_add_aperture(stats->aperture_list, -1, ano, a->type, a->parameter); gerbv_stats_add_to_D_list(stats->D_code_list, ano); if (ano < APERTURE_MIN) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Aperture number out of bounds %d " "at line %ld in file \"%s\""), ano, *line_num_p, fd->filename); } } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Aperture number out of bounds %d " "at line %ld in file \"%s\""), ano, *line_num_p, fd->filename); } /* Add aperture info to stats->aperture_list here */ break; case A2I('A','M'): /* Aperture Macro */ tmp_amacro = image->amacro; image->amacro = parse_aperture_macro(fd); if (image->amacro) { image->amacro->next = tmp_amacro; #ifdef AMACRO_DEBUG print_program(image->amacro); #endif } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Failed to parse aperture macro " "at line %ld in file \"%s\""), *line_num_p, fd->filename); } // return, since we want to skip the later back-up loop return; /* Layer */ case A2I('L','N'): /* Layer Name */ state->layer = gerbv_image_return_new_layer (state->layer); state->layer->name = gerb_fgetstring(fd, '*'); break; case A2I('L','P'): /* Layer Polarity */ state->layer = gerbv_image_return_new_layer (state->layer); switch (gerb_fgetc(fd)) { case 'D': /* Dark Polarity (default) */ state->layer->polarity = GERBV_POLARITY_DARK; break; case 'C': /* Clear Polarity */ state->layer->polarity = GERBV_POLARITY_CLEAR; break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unknown layer polarity '%s' " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } break; case A2I('L','R'): /* Load Rotation */ state->state = gerbv_image_return_new_netstate(state->state); state->state->rotation = DEG2RAD(gerb_fgetdouble(fd)); break; case A2I('L','S'): /* Load Scaling */ state->state = gerbv_image_return_new_netstate(state->state); state->state->scaleA = gerb_fgetdouble(fd); state->state->scaleB = state->state->scaleA; break; case A2I('L','M'): /* Load Mirroring */ state->state = gerbv_image_return_new_netstate(state->state); op[0] = gerb_fgetc(fd); if (op[0] == 'N') { state->state->mirrorState = GERBV_MIRROR_STATE_NOMIRROR; } else if (op[0] == 'X') { op[1] = gerb_fgetc(fd); if (op[1] == 'Y') { state->state->mirrorState = GERBV_MIRROR_STATE_FLIPAB; } else { gerb_ungetc(fd); state->state->mirrorState = GERBV_MIRROR_STATE_FLIPA; } } else if (op[0] == 'Y') { state->state->mirrorState = GERBV_MIRROR_STATE_FLIPB; } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unknown load mirroring parameter '%s' " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), *line_num_p, fd->filename); } break; case A2I('K','O'): /* Knock Out */ state->layer = gerbv_image_return_new_layer (state->layer); gerber_update_any_running_knockout_measurements (image); /* reset any previous knockout measurements */ knockoutMeasure = FALSE; op[0] = gerb_fgetc(fd); if (op[0] == '*') { /* Disable previous SR parameters */ state->layer->knockout.type = GERBV_KNOCKOUT_TYPE_NOKNOCKOUT; break; } else if (op[0] == 'C') { state->layer->knockout.polarity = GERBV_POLARITY_CLEAR; } else if (op[0] == 'D') { state->layer->knockout.polarity = GERBV_POLARITY_DARK; } else { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Knockout must supply a polarity (C, D, or *) " "at line %ld in file \"%s\""), *line_num_p, fd->filename); } state->layer->knockout.lowerLeftX = 0.0; state->layer->knockout.lowerLeftY = 0.0; state->layer->knockout.width = 0.0; state->layer->knockout.height = 0.0; state->layer->knockout.border = 0.0; state->layer->knockout.firstInstance = TRUE; op[0] = gerb_fgetc(fd); while ((op[0] != '*')&&(op[0] != EOF)) { switch (op[0]) { case 'X': state->layer->knockout.type = GERBV_KNOCKOUT_TYPE_FIXEDKNOCK; state->layer->knockout.lowerLeftX = gerb_fgetdouble(fd) / scale; break; case 'Y': state->layer->knockout.type = GERBV_KNOCKOUT_TYPE_FIXEDKNOCK; state->layer->knockout.lowerLeftY = gerb_fgetdouble(fd) / scale; break; case 'I': state->layer->knockout.type = GERBV_KNOCKOUT_TYPE_FIXEDKNOCK; state->layer->knockout.width = gerb_fgetdouble(fd) / scale; break; case 'J': state->layer->knockout.type = GERBV_KNOCKOUT_TYPE_FIXEDKNOCK; state->layer->knockout.height = gerb_fgetdouble(fd) / scale; break; case 'K': state->layer->knockout.type = GERBV_KNOCKOUT_TYPE_BORDER; state->layer->knockout.border = gerb_fgetdouble(fd) / scale; /* this is a bordered knockout, so we need to start measuring the size of a square bordering all future components */ knockoutMeasure = TRUE; knockoutLimitXmin = HUGE_VAL; knockoutLimitYmin = HUGE_VAL; knockoutLimitXmax = -HUGE_VAL; knockoutLimitYmax = -HUGE_VAL; knockoutLayer = state->layer; break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unknown variable in knockout " "at line %ld in file \"%s\""), *line_num_p, fd->filename); } op[0] = gerb_fgetc(fd); } break; case A2I('S','R'): /* Step and Repeat */ /* start by generating a new layer (duplicating previous layer settings */ state->layer = gerbv_image_return_new_layer (state->layer); op[0] = gerb_fgetc(fd); if (op[0] == '*') { /* Disable previous SR parameters */ state->layer->stepAndRepeat.X = 1; state->layer->stepAndRepeat.Y = 1; state->layer->stepAndRepeat.dist_X = 0.0; state->layer->stepAndRepeat.dist_Y = 0.0; break; } while ((op[0] != '*')&&(op[0] != EOF)) { switch (op[0]) { case 'X': state->layer->stepAndRepeat.X = gerb_fgetint(fd, NULL); break; case 'Y': state->layer->stepAndRepeat.Y = gerb_fgetint(fd, NULL); break; case 'I': state->layer->stepAndRepeat.dist_X = gerb_fgetdouble(fd) / scale; break; case 'J': state->layer->stepAndRepeat.dist_Y = gerb_fgetdouble(fd) / scale; break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Step-and-repeat parameter error " "at line %ld in file \"%s\""), *line_num_p, fd->filename); } /* * Repeating 0 times in any direction would disable the whole plot, and * is probably not intended. At least one other tool (viewmate) seems * to interpret 0-time repeating as repeating just once too. */ if(state->layer->stepAndRepeat.X == 0) state->layer->stepAndRepeat.X = 1; if(state->layer->stepAndRepeat.Y == 0) state->layer->stepAndRepeat.Y = 1; op[0] = gerb_fgetc(fd); } break; /* is this an actual RS274X command?? It isn't explainined in the spec... */ case A2I('R','O'): state->layer = gerbv_image_return_new_layer (state->layer); state->layer->rotation = DEG2RAD(gerb_fgetdouble(fd)); op[0] = gerb_fgetc(fd); if (op[0] != '*') { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Error in layer rotation command " "at line %ld in file \"%s\""), *line_num_p, fd->filename); } break; /* Gerber X2 attribute commands (TF, TA, TO, TD) — metadata only, skip silently */ case A2I('T','F'): /* File Attribute */ case A2I('T','A'): /* Aperture Attribute */ case A2I('T','O'): /* Object Attribute */ case A2I('T','D'): /* Delete Attribute */ gerbv_stats_printf(error_list, GERBV_MESSAGE_NOTE, -1, _("Ignoring Gerber X2 attribute %%%s%s%% " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), gerbv_escape_char(op[1]), *line_num_p, fd->filename); break; default: gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Unknown RS-274X extension found %%%s%s%% " "at line %ld in file \"%s\""), gerbv_escape_char(op[0]), gerbv_escape_char(op[1]), *line_num_p, fd->filename); } // make sure we read until the trailing * character // first, backspace once in case we already read the trailing * gerb_ungetc(fd); do { tmp = gerb_fgetc(fd); } while (tmp != EOF && tmp != '*'); return; } /* parse_rs274x */ /* * Stack declarations and operations to be used by the simple engine that * executes the parsed aperture macros. */ typedef struct { double *stack; size_t sp; size_t capacity; } macro_stack_t; static macro_stack_t * new_stack(size_t stack_size) { macro_stack_t *s; s = (macro_stack_t *) g_new0 (macro_stack_t,1); s->stack = (double *) g_new0 (double, stack_size); s->sp = 0; s->capacity = stack_size; return s; } /* new_stack */ static void free_stack(macro_stack_t *s) { if (s && s->stack) free(s->stack); if (s) free(s); return; } /* free_stack */ static void push(macro_stack_t *s, double val) { if (s->sp >= s->capacity) { GERB_FATAL_ERROR(_("push will overflow stack capacity")); return; } s->stack[s->sp++] = val; return; } /* push */ static int pop(macro_stack_t *s, double *value) { /* Check if we try to pop an empty stack */ if (s->sp == 0) { return -1; } *value = s->stack[--s->sp]; return 0; } /* pop */ /* ------------------------------------------------------------------ */ static int simplify_aperture_macro(gerbv_aperture_t *aperture, gdouble scale) { const int extra_stack_size = 10; macro_stack_t *s; gerbv_instruction_t *ip; int handled = 1, nuf_parameters = 0, i, j, clearOperatorUsed = FALSE; double *lp; /* Local copy of parameters */ double tmp[2] = {0.0, 0.0}; gerbv_aperture_type_t type = GERBV_APTYPE_NONE; gerbv_simplified_amacro_t *sam; if (aperture == NULL) GERB_FATAL_ERROR(_("aperture NULL in simplify aperture macro")); if (aperture->amacro == NULL) GERB_FATAL_ERROR(_("aperture->amacro NULL in simplify aperture macro")); /* Allocate stack for VM */ s = new_stack(aperture->amacro->nuf_push + extra_stack_size); if (s == NULL) GERB_FATAL_ERROR("malloc stack failed in %s()", __FUNCTION__); /* Make a copy of the parameter list that we can rewrite if necessary */ lp = g_new (double,APERTURE_PARAMETERS_MAX); memcpy(lp, aperture->parameter, sizeof(double) * APERTURE_PARAMETERS_MAX); for(ip = aperture->amacro->program; ip != NULL; ip = ip->next) { switch(ip->opcode) { case GERBV_OPCODE_NOP: break; case GERBV_OPCODE_PUSH : push(s, ip->data.fval); break; case GERBV_OPCODE_PPUSH : { ssize_t const idx = ip->data.ival - 1; if ((idx < 0) || (idx >= APERTURE_PARAMETERS_MAX)) GERB_FATAL_ERROR(_("Tried to access oob aperture")); push(s, lp[idx]); break; } case GERBV_OPCODE_PPOP: { if (pop(s, &tmp[0]) < 0) GERB_FATAL_ERROR(_("Tried to pop an empty stack")); ssize_t const idx = ip->data.ival - 1; if ((idx < 0) || (idx >= APERTURE_PARAMETERS_MAX)) GERB_FATAL_ERROR(_("Tried to access oob aperture")); lp[idx] = tmp[0]; break; } case GERBV_OPCODE_ADD : if (pop(s, &tmp[0]) < 0) GERB_FATAL_ERROR(_("Tried to pop an empty stack")); if (pop(s, &tmp[1]) < 0) GERB_FATAL_ERROR(_("Tried to pop an empty stack")); push(s, tmp[1] + tmp[0]); break; case GERBV_OPCODE_SUB : if (pop(s, &tmp[0]) < 0) GERB_FATAL_ERROR(_("Tried to pop an empty stack")); if (pop(s, &tmp[1]) < 0) GERB_FATAL_ERROR(_("Tried to pop an empty stack")); push(s, tmp[1] - tmp[0]); break; case GERBV_OPCODE_MUL : if (pop(s, &tmp[0]) < 0) GERB_FATAL_ERROR(_("Tried to pop an empty stack")); if (pop(s, &tmp[1]) < 0) GERB_FATAL_ERROR(_("Tried to pop an empty stack")); push(s, tmp[1] * tmp[0]); break; case GERBV_OPCODE_DIV : if (pop(s, &tmp[0]) < 0) GERB_FATAL_ERROR(_("Tried to pop an empty stack")); if (pop(s, &tmp[1]) < 0) GERB_FATAL_ERROR(_("Tried to pop an empty stack")); push(s, tmp[1] / tmp[0]); break; case GERBV_OPCODE_PRIM : /* * This handles the exposure thing in the aperture macro * The exposure is always the first element on stack independent * of aperture macro. */ switch(ip->data.ival) { case 1: DPRINTF(" Aperture macro circle [1] ("); type = GERBV_APTYPE_MACRO_CIRCLE; nuf_parameters = 5; break; case 3: break; case 4 : DPRINTF(" Aperture macro outline [4] ("); type = GERBV_APTYPE_MACRO_OUTLINE; /* * Number of parameters are: * - number of points defined in entry 1 of the stack + * start point. Times two since it is both X and Y. * - Then three more; exposure, nuf points and rotation. * * @warning Calculation must be guarded against signed integer * overflow * * @see CVE-2021-40394 */ int const sstack = (int)s->stack[1]; if ((sstack < 0) || (sstack >= INT_MAX / 4)) { GERB_COMPILE_ERROR(_("Possible signed integer overflow " "in calculating number of parameters " "to aperture macro, will clamp to " "(%d)"), APERTURE_PARAMETERS_MAX); nuf_parameters = APERTURE_PARAMETERS_MAX; } else { nuf_parameters = (sstack + 1) * 2 + 3; } break; case 5 : DPRINTF(" Aperture macro polygon [5] ("); type = GERBV_APTYPE_MACRO_POLYGON; nuf_parameters = 6; break; case 6 : DPRINTF(" Aperture macro moire [6] ("); type = GERBV_APTYPE_MACRO_MOIRE; nuf_parameters = 9; break; case 7 : DPRINTF(" Aperture macro thermal [7] ("); type = GERBV_APTYPE_MACRO_THERMAL; nuf_parameters = 6; break; case 2 : case 20 : DPRINTF(" Aperture macro line 20/2 ("); type = GERBV_APTYPE_MACRO_LINE20; nuf_parameters = 7; break; case 21 : DPRINTF(" Aperture macro line 21 ("); type = GERBV_APTYPE_MACRO_LINE21; nuf_parameters = 6; break; case 22 : DPRINTF(" Aperture macro line 22 ("); type = GERBV_APTYPE_MACRO_LINE22; nuf_parameters = 6; break; default : handled = 0; } if (type != GERBV_APTYPE_NONE) { if (nuf_parameters > APERTURE_PARAMETERS_MAX) { GERB_COMPILE_ERROR(_("Number of parameters to aperture macro (%d) " "are more than gerbv is able to store (%d)"), nuf_parameters, APERTURE_PARAMETERS_MAX); nuf_parameters = APERTURE_PARAMETERS_MAX; } /* * Create struct for simplified aperture macro and * start filling in the blanks. */ sam = g_new (gerbv_simplified_amacro_t, 1); sam->type = type; sam->next = NULL; memset(sam->parameter, 0, sizeof(double) * APERTURE_PARAMETERS_MAX); /* CVE-2021-40400 */ if (nuf_parameters > s->capacity) { GERB_COMPILE_ERROR(_("Number of parameters to aperture macro (%d) " "capped to stack capacity (%zu)"), nuf_parameters, s->capacity); nuf_parameters = s->capacity; } /* Don't read beyond what was actually pushed for this * primitive. The Gerber spec allows trailing optional * parameters (e.g. circle rotation) to be omitted; * sam->parameter is already zeroed so omitted values * default to 0. */ if (nuf_parameters > s->sp) nuf_parameters = s->sp; memcpy(sam->parameter, s->stack, sizeof(double) * nuf_parameters); /* convert any mm values to inches */ switch (type) { case GERBV_APTYPE_MACRO_CIRCLE: if (fabs(sam->parameter[0]) < 0.001) clearOperatorUsed = TRUE; sam->parameter[1]/=scale; sam->parameter[2]/=scale; sam->parameter[3]/=scale; break; case GERBV_APTYPE_MACRO_OUTLINE: if (fabs(sam->parameter[0]) < 0.001) clearOperatorUsed = TRUE; for (j=2; jparameter[j]/=scale; } break; case GERBV_APTYPE_MACRO_POLYGON: if (fabs(sam->parameter[0]) < 0.001) clearOperatorUsed = TRUE; sam->parameter[2]/=scale; sam->parameter[3]/=scale; sam->parameter[4]/=scale; break; case GERBV_APTYPE_MACRO_MOIRE: sam->parameter[0]/=scale; sam->parameter[1]/=scale; sam->parameter[2]/=scale; sam->parameter[3]/=scale; sam->parameter[4]/=scale; sam->parameter[6]/=scale; sam->parameter[7]/=scale; break; case GERBV_APTYPE_MACRO_THERMAL: sam->parameter[0]/=scale; sam->parameter[1]/=scale; sam->parameter[2]/=scale; sam->parameter[3]/=scale; sam->parameter[4]/=scale; break; case GERBV_APTYPE_MACRO_LINE20: if (fabs(sam->parameter[0]) < 0.001) clearOperatorUsed = TRUE; sam->parameter[1]/=scale; sam->parameter[2]/=scale; sam->parameter[3]/=scale; sam->parameter[4]/=scale; sam->parameter[5]/=scale; break; case GERBV_APTYPE_MACRO_LINE21: case GERBV_APTYPE_MACRO_LINE22: if (fabs(sam->parameter[0]) < 0.001) clearOperatorUsed = TRUE; sam->parameter[1]/=scale; sam->parameter[2]/=scale; sam->parameter[3]/=scale; sam->parameter[4]/=scale; break; default: break; } /* * Add this simplified aperture macro to the end of the list * of simplified aperture macros. If first entry, put it * in the top. */ if (aperture->simplified == NULL) { aperture->simplified = sam; } else { gerbv_simplified_amacro_t *tmp_sam; tmp_sam = aperture->simplified; while (tmp_sam->next != NULL) { tmp_sam = tmp_sam->next; } tmp_sam->next = sam; } #ifdef DEBUG for (i = 0; i < nuf_parameters; i++) { DPRINTF("%f, ", s->stack[i]); } #endif /* DEBUG */ DPRINTF(")\n"); } /* * Here we reset the stack pointer. It's not general correct * correct to do this, but since I know how the compiler works * I can do this. The correct way to do this should be to * subtract number of used elements in each primitive operation. */ s->sp = 0; break; default : break; } } free_stack(s); g_free (lp); /* store a flag to let the renderer know if it should expect any "clear" primatives */ aperture->parameter[0]= (gdouble) clearOperatorUsed; return handled; } /* simplify_aperture_macro */ /* ------------------------------------------------------------------ */ static int parse_aperture_definition(gerb_file_t *fd, gerbv_aperture_t *aperture, gerbv_image_t *image, gdouble scale, long int *line_num_p) { int ano, i; char *ad; char *token; gerbv_amacro_t *curr_amacro; gerbv_amacro_t *amacro = image->amacro; gerbv_error_list_t *error_list = image->gerbv_stats->error_list; gdouble tempHolder; if (gerb_fgetc(fd) != 'D') { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Found AD code with no following 'D' " "at line %ld in file \"%s\""), *line_num_p, fd->filename); return -1; } /* * Get aperture no */ ano = gerb_fgetint(fd, NULL); /* * Read in the whole aperture defintion and tokenize it */ ad = gerb_fgetstring(fd, '*'); if (ad == NULL) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Invalid aperture definition at line %ld in file \"%s\", " "cannot find '*'"), *line_num_p, fd->filename); return -1; } token = strtok(ad, ","); if (token == NULL) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Invalid aperture definition at line %ld in file \"%s\""), *line_num_p, fd->filename); return -1; } if (strlen(token) == 1) { switch (token[0]) { case 'C': aperture->type = GERBV_APTYPE_CIRCLE; break; case 'R' : aperture->type = GERBV_APTYPE_RECTANGLE; break; case 'O' : aperture->type = GERBV_APTYPE_OVAL; break; case 'P' : aperture->type = GERBV_APTYPE_POLYGON; break; } /* Here a should a T be defined, but I don't know what it represents */ } else { aperture->type = GERBV_APTYPE_MACRO; /* * In aperture definition, point to the aperture macro * used in the defintion */ curr_amacro = amacro; while (curr_amacro) { if ((strlen(curr_amacro->name) == strlen(token)) && (strcmp(curr_amacro->name, token) == 0)) { aperture->amacro = curr_amacro; break; } curr_amacro = curr_amacro->next; } } /* * Parse all parameters */ for (token = strtok(NULL, "X"), i = 0; token != NULL; token = strtok(NULL, "X"), i++) { if (i == APERTURE_PARAMETERS_MAX) { gerbv_stats_printf(error_list, GERBV_MESSAGE_ERROR, -1, _("Maximum number of allowed parameters exceeded " "in aperture %d at line %ld in file \"%s\""), ano, *line_num_p, fd->filename); break; } errno = 0; tempHolder = strtod(token, NULL); /* convert any MM values to inches */ /* don't scale polygon angles or side numbers, or macro parmaeters */ if (!(((aperture->type == GERBV_APTYPE_POLYGON) && ((i==1) || (i==2)))|| (aperture->type == GERBV_APTYPE_MACRO))) { tempHolder /= scale; } aperture->parameter[i] = tempHolder; if (errno) { gerbv_stats_printf(error_list, GERBV_MESSAGE_WARNING, -1, _("Failed to read all parameters exceeded in " "aperture %d at line %ld in file \"%s\""), ano, *line_num_p, fd->filename); aperture->parameter[i] = 0.0; } } aperture->nuf_parameters = i; gerb_ungetc(fd); if (aperture->type == GERBV_APTYPE_MACRO) { DPRINTF("Simplifying aperture %d using aperture macro \"%s\"\n", ano, aperture->amacro->name); simplify_aperture_macro(aperture, scale); DPRINTF("Done simplifying\n"); } g_free(ad); return ano; } /* parse_aperture_definition */ /* ------------------------------------------------------------------ */ static void calc_cirseg_sq(struct gerbv_net *net, int cw, double delta_cp_x, double delta_cp_y) { double d1x, d1y, d2x, d2y; double alfa, beta; int quadrant = 0; /* * Quadrant detection (based on ccw, converted below if cw) * Y ^ * /!\ * ! * ---->X */ if (net->start_x > net->stop_x) /* 1st and 2nd quadrant */ if (net->start_y < net->stop_y) quadrant = 1; else quadrant = 2; else /* 3rd and 4th quadrant */ if (net->start_y > net->stop_y) quadrant = 3; else quadrant = 4; /* * If clockwise, rotate quadrant */ if (cw) { switch (quadrant) { case 1 : quadrant = 3; break; case 2 : quadrant = 4; break; case 3 : quadrant = 1; break; case 4 : quadrant = 2; break; default : GERB_COMPILE_ERROR(_("Unknow quadrant value while converting to cw")); } } /* * Calculate arc center point */ switch (quadrant) { case 1 : net->cirseg->cp_x = net->start_x - delta_cp_x; net->cirseg->cp_y = net->start_y - delta_cp_y; break; case 2 : net->cirseg->cp_x = net->start_x + delta_cp_x; net->cirseg->cp_y = net->start_y - delta_cp_y; break; case 3 : net->cirseg->cp_x = net->start_x + delta_cp_x; net->cirseg->cp_y = net->start_y + delta_cp_y; break; case 4 : net->cirseg->cp_x = net->start_x - delta_cp_x; net->cirseg->cp_y = net->start_y + delta_cp_y; break; default : GERB_COMPILE_ERROR(_("Strange quadrant: %d"), quadrant); } /* * Some good values */ d1x = fabs(net->start_x - net->cirseg->cp_x); d1y = fabs(net->start_y - net->cirseg->cp_y); d2x = fabs(net->stop_x - net->cirseg->cp_x); d2y = fabs(net->stop_y - net->cirseg->cp_y); alfa = atan2(d1y, d1x); beta = atan2(d2y, d2x); /* * Avoid divide by zero when sin(0) = 0 and cos(90) = 0 */ net->cirseg->width = alfa < beta ? 2 * (d1x / cos(alfa)) : 2 * (d2x / cos(beta)); net->cirseg->height = alfa > beta ? 2 * (d1y / sin(alfa)) : 2 * (d2y / sin(beta)); if (alfa < GERBV_PRECISION_ANGLE_RAD && beta < GERBV_PRECISION_ANGLE_RAD) { net->cirseg->height = 0; } switch (quadrant) { case 1 : net->cirseg->angle1 = RAD2DEG(alfa); net->cirseg->angle2 = RAD2DEG(beta); break; case 2 : net->cirseg->angle1 = 180.0 - RAD2DEG(alfa); net->cirseg->angle2 = 180.0 - RAD2DEG(beta); break; case 3 : net->cirseg->angle1 = 180.0 + RAD2DEG(alfa); net->cirseg->angle2 = 180.0 + RAD2DEG(beta); break; case 4 : net->cirseg->angle1 = 360.0 - RAD2DEG(alfa); net->cirseg->angle2 = 360.0 - RAD2DEG(beta); break; default : GERB_COMPILE_ERROR(_("Strange quadrant: %d"), quadrant); } if (net->cirseg->width < 0.0) GERB_COMPILE_WARNING(_("Negative width [%f] in quadrant %d [%f][%f]"), net->cirseg->width, quadrant, RAD2DEG(alfa), RAD2DEG(beta)); if (net->cirseg->height < 0.0) GERB_COMPILE_WARNING(_("Negative height [%f] in quadrant %d [%f][%f]"), net->cirseg->height, quadrant, RAD2DEG(alfa), RAD2DEG(beta)); return; } /* calc_cirseg_sq */ /* Multiquadrant circular interpolation */ void calc_cirseg_mq(struct gerbv_net *net, int cw, double delta_cp_x, double delta_cp_y) { double d1x, d1y, d2x, d2y; double alfa, beta; net->cirseg->cp_x = net->start_x + delta_cp_x; net->cirseg->cp_y = net->start_y + delta_cp_y; /* * Some good values */ d1x = -delta_cp_x; d1y = -delta_cp_y; d2x = net->stop_x - net->cirseg->cp_x; d2y = net->stop_y - net->cirseg->cp_y; /* * It's possible for values to be calculated that smaller than epsilon, * but containing opposite signs. These values cause essentially a * "signed zero" effect, which makes atan2 results unpredictable. */ if (fabs(d1x) < DBL_EPSILON) d1x = 0; if (fabs(d1y) < DBL_EPSILON) d1y = 0; if (fabs(d2x) < DBL_EPSILON) d2x = 0; if (fabs(d2y) < DBL_EPSILON) d2y = 0; net->cirseg->width = hypot(delta_cp_x, delta_cp_y); net->cirseg->width *= 2.0; net->cirseg->height = net->cirseg->width; /* * Keep values in radians, convert to degrees only results */ alfa = atan2(d1y, d1x); beta = atan2(d2y, d2x); /* * Make sure it's always positive angles */ if (alfa < 0.0) { alfa += M_PI + M_PI; beta += M_PI + M_PI; } if (beta < 0.0) beta += M_PI + M_PI; /* * This is a sanity check for angles after the nature of atan2. * If cw we must make sure angle1-angle2 are always positive, * If ccw we must make sure angle2-angle1 are always positive. * We should really return one angle and the difference as GTK * uses them. But what the heck, it works for me. */ if (cw) { if (alfa - beta < DBL_EPSILON) beta -= M_PI + M_PI; } else { if (beta - alfa < DBL_EPSILON) beta += M_PI + M_PI; } net->cirseg->angle1 = RAD2DEG(alfa); net->cirseg->angle2 = RAD2DEG(beta); } /* Calculate circular interpolation bounding box */ static void calc_cirseg_bbox(const gerbv_cirseg_t *cirseg, double apert_size_x, double apert_size_y, gerbv_render_size_t *bbox) { gdouble x, y, ang1, ang2, step_pi_2; /* For bounding box calculation only half of aperture size is used */ apert_size_x /= 2; apert_size_y /= 2; ang1 = DEG2RAD(MIN(cirseg->angle1, cirseg->angle2)); ang2 = DEG2RAD(MAX(cirseg->angle1, cirseg->angle2)); /* Start arc point */ x = cirseg->cp_x + cirseg->width*cos(ang1)/2; y = cirseg->cp_y + cirseg->width*sin(ang1)/2; gerber_update_min_and_max(bbox, x, y, apert_size_x, apert_size_x, apert_size_y, apert_size_y); /* Middle arc points at each 90-degree axis crossing */ for (step_pi_2 = (floor(ang1/M_PI_2) + 1)*M_PI_2; step_pi_2 < MIN(ang2, ang1 + 2*M_PI); step_pi_2 += M_PI_2) { x = cirseg->cp_x + cirseg->width*cos(step_pi_2)/2; y = cirseg->cp_y + cirseg->width*sin(step_pi_2)/2; gerber_update_min_and_max(bbox, x, y, apert_size_x, apert_size_x, apert_size_y, apert_size_y); } /* Stop arc point */ x = cirseg->cp_x + cirseg->width*cos(ang2)/2; y = cirseg->cp_y + cirseg->width*sin(ang2)/2; gerber_update_min_and_max(bbox, x, y, apert_size_x, apert_size_x, apert_size_y, apert_size_y); } static void gerber_update_any_running_knockout_measurements (gerbv_image_t *image) { if (knockoutMeasure) { knockoutLayer->knockout.lowerLeftX = knockoutLimitXmin; knockoutLayer->knockout.lowerLeftY = knockoutLimitYmin; knockoutLayer->knockout.width = knockoutLimitXmax - knockoutLimitXmin; knockoutLayer->knockout.height = knockoutLimitYmax - knockoutLimitYmin; knockoutMeasure = FALSE; } } static void gerber_calculate_final_justify_effects(gerbv_image_t *image) { gdouble translateA = 0.0, translateB = 0.0; if (image->info->imageJustifyTypeA != GERBV_JUSTIFY_NOJUSTIFY) { if (image->info->imageJustifyTypeA == GERBV_JUSTIFY_CENTERJUSTIFY) translateA = (image->info->max_x - image->info->min_x) / 2.0; else translateA = -image->info->min_x; } if (image->info->imageJustifyTypeB != GERBV_JUSTIFY_NOJUSTIFY) { if (image->info->imageJustifyTypeB == GERBV_JUSTIFY_CENTERJUSTIFY) translateB = (image->info->max_y - image->info->min_y) / 2.0; else translateB = -image->info->min_y; } /* update the min/max values so the autoscale function can correctly centered a justified image */ image->info->min_x += translateA+ image->info->imageJustifyOffsetA; image->info->max_x += translateA+ image->info->imageJustifyOffsetA; image->info->min_y += translateB+ image->info->imageJustifyOffsetB; image->info->max_y += translateB+ image->info->imageJustifyOffsetB; /* store the absolute offset for the justify so we can quickly offset the rendered picture during drawing */ image->info->imageJustifyOffsetActualA = translateA + image->info->imageJustifyOffsetA; image->info->imageJustifyOffsetActualB = translateB + image->info->imageJustifyOffsetB; } /* gerber_calculate_final_justify_effects */ void gerber_update_image_min_max (gerbv_render_size_t *boundingBox, double repeat_off_X, double repeat_off_Y, gerbv_image_t* image) { image->info->min_x = MIN(image->info->min_x, boundingBox->left); image->info->min_y = MIN(image->info->min_y, boundingBox->bottom); image->info->max_x = MAX(image->info->max_x, boundingBox->right + repeat_off_X); image->info->max_y = MAX(image->info->max_y, boundingBox->top + repeat_off_Y); } void gerber_update_min_and_max(gerbv_render_size_t *boundingBox, gdouble x, gdouble y, gdouble apertureSizeX1, gdouble apertureSizeX2,gdouble apertureSizeY1, gdouble apertureSizeY2) { gdouble ourX1 = x - apertureSizeX1, ourY1 = y - apertureSizeY1; gdouble ourX2 = x + apertureSizeX2, ourY2 = y + apertureSizeY2; /* transform the point to the final rendered position, accounting for any scaling, offsets, mirroring, etc */ /* NOTE: we need to already add/subtract in the aperture size since the final rendering may be scaled */ cairo_matrix_transform_point (¤tMatrix, &ourX1, &ourY1); cairo_matrix_transform_point (¤tMatrix, &ourX2, &ourY2); /* check both points against the min/max, since depending on the rotation, mirroring, etc, either point could possibly be a min or max */ boundingBox->left = MIN(boundingBox->left, ourX1); boundingBox->left = MIN(boundingBox->left, ourX2); boundingBox->right = MAX(boundingBox->right, ourX1); boundingBox->right = MAX(boundingBox->right, ourX2); boundingBox->bottom = MIN(boundingBox->bottom, ourY1); boundingBox->bottom = MIN(boundingBox->bottom, ourY2); boundingBox->top = MAX(boundingBox->top, ourY1); boundingBox->top = MAX(boundingBox->top, ourY2); } /* gerber_update_min_and_max */ static gboolean add_trailing_zeros_if_omitted(int *coord, int omitted_num, gerbv_format_t *format) { if (format && format->omit_zeros == GERBV_OMIT_ZEROS_TRAILING && omitted_num > 0) { for (int i = 0; i < omitted_num; i++) *coord *= 10; return TRUE; } return FALSE; } /* add_trailing_zeros_if_omitted() */ /** Return Gerber D-code name by code number. */ const char *gerber_d_code_name(int d_code) { switch (d_code) { case 1: return N_("exposure on"); case 2: return N_("exposure off"); case 3: return N_("flash aperture"); default: return N_("unknown D-code"); } } /* gerber_d_code_name() */ /** Return Gerber G-code name by code number. */ const char *gerber_g_code_name(int g_code) { switch (g_code) { case 0: return N_("move"); case 1: return N_("1X linear interpolation"); case 2: return N_("CW interpolation"); case 3: return N_("CCW interpolation"); case 4: return N_("comment/ignore block"); case 10: return N_("10X linear interpolation"); case 11: return N_("0.1X linear interpolation"); case 12: return N_("0.01X linear interpolation"); case 36: return N_("poly fill on"); case 37: return N_("poly fill off"); case 54: return N_("tool prepare"); case 55: return N_("flash prepare"); case 70: return N_("units = inches"); case 71: return N_("units = mm"); case 74: return N_("disable 360 circ. interpolation"); case 75: return N_("enable 360 circ. interpolation"); case 90: return N_("absolute units"); case 91: return N_("incremental units"); default: return N_("unknown G-code"); } } /* gerber_g_code_name() */ /** Return Gerber M-code name by code number. */ const char *gerber_m_code_name(int m_code) { switch (m_code) { case 0: return N_("program stop (obsolete)"); case 1: return N_("optional stop (obsolete)"); case 2: return N_("end of file"); default: return N_("unknown M-code"); } } /* gerber_m_code_name() */