/* * makegrid.c * * Version 1.2 -- October 8, 2005 -- Pius Fischer * * Version history: * 1.0 14 Aug 2005 First version * 1.1 16 Aug 2005 Added support for the PUZ format * 1.2 08 Oct 2005 Added the following command-line options: * -a show the grid filled out with the answers * -c check that the answer lengths are correct * -y never wrap the clue text after hyphens * (e.g. the SMH doesn't wrap after hyphens) */ #include #include #include #include #include #include #define GRID_SIZE 15 #define CLUE_NUMBER_ARRAY_SIZE 64 #define MAX_CLUE_BUFFER_LEN 511 #define MAX_LINE_WIDTH 12000 typedef unsigned int uint; typedef unsigned short ushort; typedef enum { false, true } bool; const uint Clue_Flag_Across = 0x0001; const uint Clue_Flag_Down = 0x0002; typedef struct { ushort x; ushort y; uint flags; ushort length_across; ushort length_down; } ClueNumber; typedef struct { char * clue_string; uint clue_number; int x; int y; } Clue; static char grid[GRID_SIZE][GRID_SIZE + 1]; static char * title; static char * author; static char * copyright; static Clue * clue_array; static uint clue_count_across; static uint clue_count_down; static uint max_clue_number; static ClueNumber clue_number_array[CLUE_NUMBER_ARRAY_SIZE]; static bool check_length = false; static bool show_answers = false; static bool wrap_hyphens = true; #include "helvetica.h" #include "helvetica-bold.h" #include "times-roman.h" void die(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); exit(1); } void number_clues(Clue **_clue_ptr, uint _clue_count, const uint _clue_flag) { uint i; for (i = 0; i < max_clue_number; ++i) if (clue_number_array[i].flags & _clue_flag) { (++*_clue_ptr)->clue_number = i + 1; --_clue_count; } if (_clue_count != 0) die("Internal Error (%d)\n", __LINE__); } void parse_grid(void) { int i, j, k; for (i = 0; i < GRID_SIZE; ++i) for (j = 0; j < GRID_SIZE; ++j) if (grid[i][j] != '.') { uint clue_flags = 0; ushort length_across = 0; ushort length_down = 0; if (j < (GRID_SIZE - 1) && grid[i][j + 1] != '.' && (j == 0 || grid[i][j - 1] == '.')) { clue_flags |= Clue_Flag_Across; ++clue_count_across; if (check_length) { k = j + 2; while (k < GRID_SIZE && grid[i][k] != '.') ++k; length_across = k - j; } } if (i < (GRID_SIZE - 1) && grid[i + 1][j] != '.' && (i == 0 || grid[i - 1][j] == '.')) { clue_flags |= Clue_Flag_Down; ++clue_count_down; if (check_length) { k = i + 2; while (k < GRID_SIZE && grid[k][j] != '.') ++k; length_down = k - i; } } if (clue_flags) { ClueNumber *clue_number = clue_number_array + max_clue_number; clue_number->x = j; clue_number->y = i; clue_number->flags = clue_flags; clue_number->length_across = length_across; clue_number->length_down = length_down; ++max_clue_number; } } clue_array = calloc(clue_count_across + clue_count_down + 3, sizeof(Clue)); if (clue_array == NULL) die("calloc() failed: %s\n", strerror(errno)); } char *trim_whitespace(char *_buffer, uint *_length) { char *start_ptr = _buffer; char *end_ptr = _buffer + strlen(_buffer); while ((start_ptr < end_ptr) && isspace(*start_ptr)) ++start_ptr; while ((end_ptr > start_ptr) && isspace(*(end_ptr - 1))) --end_ptr; *end_ptr = 0; *_length = end_ptr - start_ptr; return start_ptr; } void read_text_file(FILE *_file, const char *_file_name) { static char line_buffer[512]; char **copy_string = NULL; char *trim_buffer; uint line_length; uint line_number = 0; uint grid_line_count = 0; Clue *clue_ptr = NULL; Clue *clue_ptr_max = NULL; bool read_across_clues = false; bool read_down_clues = false; bool read_size = false; while (fgets(line_buffer, sizeof(line_buffer), _file) != NULL) { ++line_number; trim_buffer = trim_whitespace(line_buffer, &line_length); if (line_length == 0) continue; if (grid_line_count) { if (line_length != GRID_SIZE) die("Grid line %u must have length %u\n", GRID_SIZE - grid_line_count + 1, GRID_SIZE); strcpy(grid[GRID_SIZE - grid_line_count], trim_buffer); if (--grid_line_count == 0) parse_grid(); continue; } if (clue_ptr != NULL && (clue_ptr < clue_ptr_max)) { if ((clue_ptr->clue_string = strdup(trim_buffer)) == NULL) die("Line %u: strdup() failed: %s\n", line_number, strerror(errno)); ++clue_ptr; continue; } if (copy_string) { if ((*copy_string = strdup(trim_buffer)) == NULL) die("Line %u: strdup() failed: %s\n", line_number, strerror(errno)); copy_string = NULL; continue; } if (read_size) { if (strcmp(trim_buffer, "15x15") != 0) die("The grid size must be 15x15\n"); read_size = false; continue; } if (strcmp(trim_buffer, "") == 0) continue; else if (strcmp(trim_buffer, "") == 0) copy_string = &title; else if (strcmp(trim_buffer, "<AUTHOR>") == 0) copy_string = &author; else if (strcmp(trim_buffer, "<COPYRIGHT>") == 0) copy_string = ©right; else if (strcmp(trim_buffer, "<GRID>") == 0) grid_line_count = GRID_SIZE; else if (strcmp(trim_buffer, "<SIZE>") == 0) read_size = true; else if (strcmp(trim_buffer, "<ACROSS>") == 0) { if (clue_array == NULL) die("Error at line %u of %s -- " "The grid must be defined before the clues\n", line_number, _file_name); if (read_across_clues) die("Error at line %u of %s -- " "The across clues were already defined\n", line_number, _file_name); if (clue_ptr == NULL) clue_ptr = clue_array; clue_ptr->clue_string = "Across"; ++clue_ptr; clue_ptr_max = clue_ptr + clue_count_across; read_across_clues = true; } else if (strcmp(trim_buffer, "<DOWN>") == 0) { if (clue_array == NULL) die("Error at line %u of %s -- " "The grid must be defined before the clues\n", line_number, _file_name); if (read_down_clues) die("Error at line %u of %s -- " "The down clues were already defined\n", line_number, _file_name); if (clue_ptr == NULL) clue_ptr = clue_array; clue_ptr->clue_string = "Down"; ++clue_ptr; clue_ptr_max = clue_ptr + clue_count_down; read_down_clues = true; } else die("Cannot parse line %u of %s\n", line_number, _file_name); } if (clue_ptr != clue_array + clue_count_across + clue_count_down + 2) die("Not all clues were defined!\n"); for (clue_ptr = clue_array; clue_ptr->clue_string; ++clue_ptr) if (strcmp(clue_ptr->clue_string, "Across") == 0) number_clues(&clue_ptr, clue_count_across, Clue_Flag_Across); else if (strcmp(clue_ptr->clue_string, "Down") == 0) number_clues(&clue_ptr, clue_count_down, Clue_Flag_Down); else die("Internal Error (%d)\n", __LINE__); } char *read_puz_line(FILE *_file) { static char line_buffer[512]; char *str; uint len = 0; int ch; while ((ch = fgetc(_file)) != 0) { if (ch == EOF) die("Unexpected end of file\n"); if (len == sizeof(line_buffer) - 1) die("Exceeded line buffer length\n"); line_buffer[len++] = ch; } if (len == 0) return NULL; line_buffer[len] = 0; if ((str = strdup(line_buffer)) == NULL) die("strdup() failed (%d): %s\n", __LINE__, strerror(errno)); return str; } void read_puz_file(FILE *_file) { uint i; uint across_index; uint down_index; if (fseek(_file, 44, SEEK_SET) != 0) die("fseek() failed (%d): %s\n", __LINE__, strerror(errno)); if (fgetc(_file) != GRID_SIZE) // width die("The grid size must be 15x15\n"); if (fgetc(_file) != GRID_SIZE) // height die("The grid size must be 15x15\n"); if (fseek(_file, 6, SEEK_CUR) != 0) die("fseek() failed (%d): %s\n", __LINE__, strerror(errno)); for (i = 0; i < GRID_SIZE; ++i) { if (fread(grid[i], GRID_SIZE, 1, _file) != 1) die("fread() failed (%d)\n", __LINE__); grid[i][GRID_SIZE] = 0; } // Skip the solution grid if (fseek(_file, GRID_SIZE * GRID_SIZE, SEEK_CUR) != 0) die("fseek() failed (%d): %s\n", __LINE__, strerror(errno)); parse_grid(); title = read_puz_line(_file); author = read_puz_line(_file); copyright = read_puz_line(_file); // Skip any leading whitespace and copyright symbol while (*copyright != 0 && (*copyright == '\xA9' || isspace(*copyright))) ++copyright; across_index = 0; down_index = 1 + clue_count_across; clue_array[across_index++].clue_string = "Across"; clue_array[down_index++].clue_string = "Down"; for (i = 0; i < max_clue_number; ++i) { uint clue_flags = clue_number_array[i].flags; if (clue_flags & Clue_Flag_Across) { if ((clue_array[across_index].clue_string = read_puz_line(_file)) == NULL) die("Not all clues were defined!\n"); clue_array[across_index].clue_number = i + 1; ++across_index; } if (clue_flags & Clue_Flag_Down) { if ((clue_array[down_index].clue_string = read_puz_line(_file)) == NULL) die("Not all clues were defined!\n"); clue_array[down_index].clue_number = i + 1; ++down_index; } } } void read_input_file(const char *_file_name) { static char head_buffer[16]; FILE *file; if ((file = fopen(_file_name, "r")) == NULL) die("Cannot open %s: %s\n", _file_name, strerror(errno)); // Read the first 14 bytes to determine if it's a .puz file if (fread(head_buffer, 1, 14, file) != 14) die("Cannot parse the input file\n"); if (head_buffer[13] == 0) { if (strcmp(head_buffer + 2, "ACROSS&DOWN") != 0) die("Cannot parse the input file\n"); read_puz_file(file); } else if (head_buffer[0] == '<' || isspace(head_buffer[0])) { rewind(file); read_text_file(file, _file_name); } else die("Cannot parse the input file\n"); fclose(file); } ushort get_answer_length(const char *_clue_string) { ushort len = 0; ushort part_len = 0; ushort paren_count = 1; const char *ptr; const char *end_ptr; if ((end_ptr = strrchr(_clue_string, ')')) == NULL) return 0; for (ptr = end_ptr + 1; *ptr; ++ptr) if (!isspace(*ptr)) return 0; for (ptr = end_ptr - 1; ptr >= _clue_string; --ptr) if (*ptr == '(' && --paren_count == 0) break; else if (*ptr == ')') ++paren_count; if (*ptr != '(') return 0; while (++ptr < end_ptr) if (*ptr >= '0' && *ptr <= '9') part_len = 10 * part_len + (*ptr - '0'); else { len += part_len; part_len = 0; } return len + part_len; } void check_answer_lengths(void) { Clue *clue; ClueNumber *clue_number; const char *across_or_down = "Unknown"; uint clue_flag = 0; ushort len_clue; ushort len_grid; for (clue = clue_array; clue->clue_string; ++clue) { if (clue->clue_number == 0) // "Across" or "Down" { across_or_down = clue->clue_string; if (strcmp(across_or_down, "Across") == 0) clue_flag = Clue_Flag_Across; else if (strcmp(across_or_down, "Down") == 0) clue_flag = Clue_Flag_Down; else die("Internal Error (%d)\n", __LINE__); continue; } clue_number = clue_number_array + clue->clue_number - 1; if (clue_flag == Clue_Flag_Across) len_grid = clue_number->length_across; else if (clue_flag == Clue_Flag_Down) len_grid = clue_number->length_down; else die("Internal Error (%d)\n", __LINE__); len_clue = get_answer_length(clue->clue_string); if (len_clue != len_grid) fprintf(stderr, "The answer length given in the " "clue for %u %s doesn't match the " "grid (the clue says %hu, the grid " "says %hu).\n", clue->clue_number, across_or_down, len_clue, len_grid); } } void draw_grid(void) { int i, j; int x, y; printf("0 w\n"); for (i = 0, y = 749; i < 15; ++i, y -= 22) for (j = 0, x = 229; j < 15; ++j, x += 22) printf("%d %d 22 22 re %c\n", x, y, grid[i][j] == '.' ? 'B' : 'S'); if (show_answers) { int last_x = 0; int last_y = 0; printf("BT\n/F1 12 Tf\n"); for (i = 0, y = 754; i < 15; ++i, y -= 22) for (j = 0, x = 229; j < 15; ++j, x += 22) if (grid[i][j] != '.') { int cx = x + 11 - (HelveticaWidths[grid[i][j]] * 6 / 1000); printf("%d %d Td (%c) Tj\n", cx - last_x, y - last_y, grid[i][j]); last_x = cx; last_y = y; } printf("ET\n"); } } void draw_grid_numbers(void) { uint i; int last_x = 0; int last_y = 0; printf("BT\n/F1 7 Tf\n"); for (i = 0; i < max_clue_number; ++i) { int x = 229 + 22 * clue_number_array[i].x; int y = 765 - 22 * clue_number_array[i].y; printf("%d %d Td (%u) Tj\n", x - last_x, y - last_y, i + 1); last_x = x; last_y = y; } if (copyright) printf("%d %d Td (\xA9 %s) Tj\n", 229 - last_x, 431 - last_y, copyright); printf("ET\n"); } char *get_next_line(char **_clue_string_ptr) { static char clue_buffer[MAX_CLUE_BUFFER_LEN + 1]; char *read_ptr = *_clue_string_ptr; uint buffer_len = 0; uint line_width = 0; char *last_break_read_ptr = NULL; uint last_break_buffer_len = 0; if (!read_ptr) return NULL; // Skip leading whitespace while (*read_ptr && isspace(*read_ptr)) ++read_ptr; for (; *read_ptr; ++read_ptr) { if (isspace(*read_ptr)) { last_break_read_ptr = read_ptr; last_break_buffer_len = buffer_len; } if (*read_ptr == '(' || *read_ptr == ')' || *read_ptr == '\\') { if (buffer_len == MAX_CLUE_BUFFER_LEN) die("Exceeded clue buffer length\n"); clue_buffer[buffer_len++] = '\\'; } if (buffer_len == MAX_CLUE_BUFFER_LEN) die("Exceeded clue buffer length\n"); clue_buffer[buffer_len++] = *read_ptr; line_width += TimesRomanWidths[*read_ptr]; if (line_width > MAX_LINE_WIDTH && last_break_read_ptr) { // Roll back to the last possible line break clue_buffer[last_break_buffer_len] = 0; *_clue_string_ptr = last_break_read_ptr + 1; return clue_buffer; } if (*read_ptr == '-' && wrap_hyphens) { last_break_read_ptr = read_ptr; last_break_buffer_len = buffer_len; } } clue_buffer[buffer_len] = 0; *_clue_string_ptr = NULL; return clue_buffer; } void draw_clue_text(void) { int x = 56; int y = 786; char *next_ptr; char *curr_ptr; Clue *clue; printf("BT\n/F3 11 Tf 15 TL\n"); for (clue = clue_array; clue->clue_string; ++clue) { if (clue->clue_number == 0) // "Across" or "Down" { y -= 15; clue->x = x - 20; clue->y = y; if (clue == clue_array) printf("%d %d Td\n", x, y); else printf("T* T*\n"); y -= 15; continue; } clue->x = x; clue->y = y; next_ptr = clue->clue_string; while ((curr_ptr = get_next_line(&next_ptr)) != NULL) { printf("(%s) '\n", curr_ptr); y -= 15; if (y < 36) { int last_x = x; int last_y = y; x += 187; // 229 - 56 + 14 y = 396; printf("%d %d Td\n", x - last_x, y - last_y); } } } y += 15; printf("/F2 11 Tf\n"); for (clue = clue_array; clue->clue_string; ++clue) if (clue->clue_number == 0) { printf("%d %d Td (%s) Tj\n", clue->x - x, clue->y - y, clue->clue_string); x = clue->x; y = clue->y; } printf("ET\n"); } void draw_clue_numbers(void) { int x; int y; int last_x = 0; int last_y = 0; Clue *clue; printf("BT\n/F3 11 Tf\n"); for (clue = clue_array; clue->clue_string; ++clue) { if (clue->clue_number == 0) continue; x = clue->x - (clue->clue_number < 10 ? 9 : 14); y = clue->y; printf("%3d %3d Td (%d) Tj\n", x - last_x, y - last_y, clue->clue_number); last_x = x; last_y = y; } printf("ET\n"); } uint str_width(const char *_str, const uint *_widths) { uint line_width = 0; for (; *_str; ++_str) line_width += _widths[*_str]; return line_width; } void draw_title_and_author(void) { int x = 36; int y = 791; int last_x = 0; int last_y = 0; if (!title && !author) return; printf("BT\n"); if (title) { printf("/F2 10 Tf\n"); printf("%d %d Td (%s) Tj\n", x, y, title); last_x = x; last_y = y; // pixel_width = str_width() * point_size / 1000 x += str_width(title, HelveticaBoldWidths) / 100; x += 5; } if (author) { printf("/F1 10 Tf\n"); printf("%d %d Td (%s) Tj\n", x - last_x, y - last_y, author); } printf("ET\n"); } void usage(const char *_program_name) { die("Usage: %s [-a] [-c] [-y] <filename>\n", _program_name); } int main(int argc, char **argv) { int i; for (i = 1; i < argc; ++i) if (argv[i][0] != '-') break; else if (strcmp(argv[i], "-a") == 0) show_answers = true; else if (strcmp(argv[i], "-c") == 0) check_length = true; else if (strcmp(argv[i], "-y") == 0) wrap_hyphens = false; else usage(argv[0]); if (i != argc - 1) usage(argv[0]); read_input_file(argv[i]); if (check_length) check_answer_lengths(); draw_grid(); draw_grid_numbers(); draw_clue_text(); draw_clue_numbers(); draw_title_and_author(); return 0; }