// // Copyright 2006-2015 Johannes Hofmann // // This software may be used and distributed according to the terms // of the GNU General Public License, incorporated herein by reference. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "PSEditor.H" #include "util.h" #include "../config.h" PSEditor *psed_p = NULL; Fl_Scroll *scroll = NULL; Fl_Hold_Browser *page_sel = NULL; Fl_Window *win; int xev_handler(int ev) { if (psed_p) return psed_p->handleX11(ev); else return 0; } void save_cb(); int check_save(void) { if (!psed_p->modified()) return 1; int r = fl_choice("The current file has not been saved.\n" "Would you like to save it now?", "Cancel", "Save", "Discard Changes"); if (r == 1) { save_cb(); return !psed_p->modified(); } return (r == 2) ? 1 : 0; } static int confirm_overwrite(const char *f) { struct stat sb; if (stat(f, &sb) == 0) return fl_choice("The file exists.\n", "Cancel", "Overwrite", NULL); else return 1; } char *filename = NULL; void scroll_to_begin() { assert(scroll); scroll->scroll_to(0, 0); scroll->redraw(); } void scroll_to_end() { assert(scroll && psed_p); int new_pos = psed_p->h() > scroll->h() ? psed_p->h() - scroll->h() : 0; scroll->scroll_to(0, new_pos); scroll->redraw(); } void page_sel_cb(Fl_Widget *w, void *) { int new_page, old_page; assert(page_sel && psed_p); new_page = page_sel->value(); old_page = psed_p->get_page(); if (new_page > 0 && new_page != old_page) { psed_p->load_page(page_sel->value()); if(old_page < new_page) scroll_to_begin(); else if(old_page > new_page) scroll_to_end(); } } void page_sel_fill() { char buf[128]; int p = psed_p->get_pages(); page_sel->clear(); if (!p) return; for (int i = 1; i <= p; i++) { snprintf(buf, sizeof(buf), "%d", i); page_sel->add(buf); } page_sel->select(psed_p->get_page()); } bool is_pdf(const char *file) { FILE *f = fopen(file, "r"); char buf[4] = {'\0'}; size_t read = 0; if (!f) return false; while (read < sizeof(buf) && !feof(f) && !ferror(f)) read += fread(buf + read, 1, sizeof(buf) - read, f); fclose(f); return (strncmp(buf, "%PDF", sizeof(buf)) == 0); } void open_file(char *file) { FILE *p; int status; const char *args[5]; pid_t pid; static const char *title_prefix = "flpsed - "; int title_len = strlen(file) + strlen(title_prefix) + 1; char *title = (char*) malloc(title_len); snprintf(title, title_len, "%s%s", title_prefix, file); if (is_pdf(file)) { // assume pdf and try to import it using pdftops args[0] = "pdftops"; args[1] = file; args[2] = "-"; args[3] = NULL; p = pexecvp("pdftops", const_cast(args), &pid, "r"); if (p) { psed_p->open_file(p); psed_p->load(); fclose(p); waitpid(pid, &status, 0); if (WEXITSTATUS(status) == 127 || WEXITSTATUS(status) == 126) { fl_message("PDF import depends on pdftops from xpdf.\n" "Make sure pdftops is available on your system.\n"); } else if (WEXITSTATUS(status) != 0) { fl_message("PDF import failed\n"); } win->copy_label(title); } else { perror("pexecvp"); } } else { // assume postscript psed_p->open_file(file); psed_p->load(); win->copy_label(title); } free(title); page_sel_fill(); } void open_cb() { char *file; if (!check_save()) return; file = fl_file_chooser("Open File?", "*.{ps,pdf}", filename); if (file) { open_file(file); if (filename) free(filename); filename = strdup(file); } } void export_pdf_cb() { char *file; FILE *p; int status; const char *args[5]; pid_t pid; file = fl_file_chooser("Open File?", "*.pdf", NULL); if(file != NULL && confirm_overwrite(file)) { args[0] = "ps2pdf"; args[1] = "-"; args[2] = file; args[3] = NULL; signal(SIGPIPE, SIG_IGN); p = pexecvp("ps2pdf", const_cast(args), &pid, "w"); if (p) { psed_p->save(p); fclose(p); waitpid(pid, &status, 0); if (WEXITSTATUS(status) == 127 || WEXITSTATUS(status) == 126) { fl_message("PDF export depends on ps2pdf from ghostscript.\n" "Make sure ps2pdf is available on your system.\n"); } else if (WEXITSTATUS(status) != 0) { fl_message("PDF export failed\n"); } } else { perror("pexecvp"); } signal(SIGPIPE, SIG_DFL); } } void import_cb() { char *file = fl_file_chooser("Import Overlay from File?", "*.ps", NULL); if (file) psed_p->import(file); } void first_cb() { psed_p->load(); page_sel->select(psed_p->get_page()); scroll_to_begin(); } void last_cb() { int pg = psed_p->get_pages(); if(psed_p->get_page() == pg) return; psed_p->load_page(pg); page_sel->select(psed_p->get_page()); scroll_to_begin(); } void next_cb() { int pg = psed_p->get_page(); psed_p->next(); if(psed_p->get_page() == pg) return; page_sel->select(psed_p->get_page()); scroll_to_begin(); } void prev_cb() { int pg = psed_p->get_page(); psed_p->prev(); if(psed_p->get_page() == pg) return; page_sel->select(psed_p->get_page()); scroll_to_end(); } void quit_cb() { if (!check_save()) return; delete psed_p; exit(0); } void save_cb() { char *file = fl_file_chooser("Open File?", "*.ps", filename); if (file && confirm_overwrite(file)) psed_p->save(file); } void print_cb() { char *pref_cmd; int r = 0; const char *print_cmd; Fl_Preferences prefs(Fl_Preferences::USER, "Johannes.HofmannATgmx.de", "flpsed"); prefs.get("printCommand", pref_cmd, "lpr"); print_cmd = fl_input("Print Command, e.g. \"lpr\" or \"psnup -2 | lpr\"", pref_cmd); if (!print_cmd || print_cmd[0] == '\0') return; if (strcmp(print_cmd, pref_cmd)) prefs.set("printCommand", print_cmd); FILE *fp = popen(print_cmd, "w"); if (fp) { signal(SIGPIPE, SIG_IGN); r = psed_p->save(fp); pclose(fp); signal(SIGPIPE, SIG_DFL); } if (!fp || r) fprintf(stderr, "Print Command %s failed\n", print_cmd); } void about_cb() { fl_message("flpsed -- a PostScript annotator\n" "Version %s\n\n" "(c) Johannes Hofmann 2004-2015\n\n" "PostScript is a registered trademark of Adobe Systems", VERSION); } Fl_Choice *size_c; Fl_Button *color_b; static struct { const char *label; int size; } text_sizes[] = { {"8", 8}, {"10", 10}, {"12", 12}, {"14", 14}, {"18", 18}, {"24", 24}, {NULL, 0} }; void property_changed_cb() { PSEditColor c; int size; psed_p->get_color(&c); color_b->color(fl_rgb_color(c.get_r(), c.get_g(), c.get_b())); color_b->redraw(); size = psed_p->get_size(); for (int i=0; text_sizes[i].label != NULL; i++) { if (size == text_sizes[i].size) { size_c->value(i); size_c->redraw(); } } } void size_cb(Fl_Widget *w, void *) { Fl_Menu_* mw = (Fl_Menu_*)w; const Fl_Menu_Item* m = mw->mvalue(); if (m) psed_p->set_size(atoi(m->label())); } void color_cb(Fl_Widget *w, void *v) { uchar r, g, b; PSEditColor pc; psed_p->get_color(&pc); r = pc.get_r(); g = pc.get_g(); b = pc.get_b(); if (!fl_color_chooser("Text Color", r, g, b)) return; Fl_Button* button = (Fl_Button*)v; pc.set(r, g, b); psed_p->set_color(&pc); button->color(fl_rgb_color(r, g, b)); button->parent()->redraw(); } void zoom_cb(Fl_Widget *w, void *) { Fl_Menu_* mw = (Fl_Menu_*)w; const Fl_Menu_Item* m = mw->mvalue(); if (m) { if (psed_p) { psed_p->zoom(atoi(m->label())); scroll_to_begin(); } } } void show_tags_cb(Fl_Widget* w, void*d) { psed_p->set_show_tags(d==NULL?0:1); } void edit_tag_cb() { char *tag = psed_p->get_tag(); const char *new_tag; new_tag = fl_input("Tag Name", tag?tag:""); if (new_tag) { if (strcmp(new_tag, "") != 0) { psed_p->set_tag(new_tag); } else { psed_p->set_tag(NULL); } } } Fl_Menu_Item menuitems[] = { { "&File", 0, 0, 0, FL_SUBMENU }, { "&Open File...", FL_CTRL + 'o', (Fl_Callback *)open_cb }, { "&Save File as...", FL_CTRL + 's', (Fl_Callback *)save_cb }, { "I&mport Tags from File...", FL_CTRL + 'm', (Fl_Callback *)import_cb }, { "&Import PDF...", FL_CTRL + 'i', (Fl_Callback *)open_cb }, { "E&xport PDF...", FL_CTRL + 'x', (Fl_Callback *)export_pdf_cb }, { "&Print...", FL_CTRL + 'p', (Fl_Callback *)print_cb, 0, FL_MENU_DIVIDER }, { "&Quit", FL_CTRL + 'q', (Fl_Callback *)quit_cb, 0 }, { 0 }, { "&Page", 0, 0, 0, FL_SUBMENU }, { "&First", FL_CTRL + 'f', (Fl_Callback *)first_cb }, { "&Last", FL_CTRL + 'l', (Fl_Callback *)last_cb }, { "&Next", FL_CTRL + 'n', (Fl_Callback *)next_cb }, { "P&revious", FL_CTRL + 'r', (Fl_Callback *)prev_cb }, { 0 }, { "&Zoom", 0, 0, 0, FL_SUBMENU }, { "50 %", 0, (Fl_Callback *)zoom_cb }, { "75 %", 0, (Fl_Callback *)zoom_cb }, { "100 %", 0, (Fl_Callback *)zoom_cb }, { "150 %", 0, (Fl_Callback *)zoom_cb }, { "200 %", 0, (Fl_Callback *)zoom_cb }, { "250 %", 0, (Fl_Callback *)zoom_cb }, { 0 }, { "&Tags", 0, 0, 0, FL_SUBMENU }, { "Show &Tags", FL_CTRL + 't', (Fl_Callback *)show_tags_cb, (void *)1, FL_MENU_RADIO|FL_MENU_VALUE}, { "&Hide Tags", FL_CTRL + 'h', (Fl_Callback *)show_tags_cb, (void *)0, FL_MENU_RADIO}, { "&Edit Tag", FL_CTRL + 'e', (Fl_Callback *)edit_tag_cb }, { 0 }, { "&Help", 0, 0, 0, FL_SUBMENU }, { "About", 0, (Fl_Callback *)about_cb }, { 0 }, { 0 } }; Fl_Menu_Item size_menu[] = { { text_sizes[0].label, 0, (Fl_Callback *)size_cb }, { text_sizes[1].label, 0, (Fl_Callback *)size_cb }, { text_sizes[2].label, 0, (Fl_Callback *)size_cb }, { text_sizes[3].label, 0, (Fl_Callback *)size_cb }, { text_sizes[4].label, 0, (Fl_Callback *)size_cb }, { text_sizes[5].label, 0, (Fl_Callback *)size_cb }, { 0 } }; void usage() { fprintf(stderr, "usage: flpsed [-hbdz] [-t =] [] []\n" " -h print this message\n" " -b batch mode (no gui)\n" " -d dump tags and values from a document\n" " to stdout (this implies -b)\n" " -z set the zoom percentage (e.g. 200)\n" " -t = set text to where tag is \n" " optional input file; in batch mode if no\n" " input file is given, input is read from stdin\n" " optional output file for batch mode; if no\n" " output file is given, output is written to stdout\n\n" "additionally flpsed supports the following FLTK standard options:\n" " -geometry WxH+X+Y\n" " -iconic\n" " -scheme string\n" ); } #define TV_LEN 256 int main(int argc, char** argv) { char *sep, *tmp, **my_argv; int c, err, bflag = 0, dflag = 0; int i, n; Fl_Menu_Bar *m; struct {char *tag; char *value;} tv[TV_LEN]; int tv_idx = 0, my_argc; FILE *out_fp = NULL; int zoom_val = 0; err = 0; while ((c = getopt(argc, argv, "hdbt:z:g:d:f:i:s:")) != -1) { switch (c) { case 'h': usage(); exit(0); break; case 'b': bflag = 1; break; case 'd': dflag = 1; break; case 't': tmp = strdup(optarg); sep = strchr(tmp, '='); if (!sep) { fprintf(stderr, "Cannot parse %s\n", optarg); free(tmp); continue; } *sep = '\0'; if (tv_idx >= TV_LEN) { fprintf(stderr, "More than %d tag/value pairs; ignoring %s->%s\n", TV_LEN, tmp, sep+1); } else { tv[tv_idx].tag = strdup(tmp); tv[tv_idx].value = strdup(sep+1); tv_idx++; } free(tmp); break; case 'z': zoom_val = atoi(optarg); break; default: i = optind -1; n = Fl::arg(argc, argv, i); if (n == 0) { err++; } else { optind = i; } } } if (err) { usage(); exit(1); } my_argc = argc - optind; my_argv = argv + optind; if (bflag || dflag) { // // Batch Mode // PSEditModel *m = new PSEditModel(); int tmp_fd; FILE *in_fp; if (my_argc >= 1) { in_fp = fopen(my_argv[0], "r"); if (!in_fp) { perror("fopen"); exit(1); } } else { in_fp = stdin; } tmp_fd = m->load(in_fp); if (tmp_fd == -1) { fprintf(stderr, "Could not load %s\n", argv[argc - 2]); exit(1); } if (in_fp != stdin) fclose(in_fp); for (int i = 0; i < tv_idx; i++) { m->replace_tag(tv[i].tag, tv[i].value); free(tv[i].tag); free(tv[i].value); } if (bflag) { if (my_argc >= 2) { out_fp = fopen(my_argv[1], "w"); if (!in_fp) { perror("fopen"); exit(1); } } else { out_fp = stdout; } m->save(out_fp, tmp_fd); if (out_fp != stdout) fclose(out_fp); } else { m->dump_tags(); } } else { // // Interactive Mode // win = new Fl_Window(600, 700); m = new Fl_Menu_Bar(0, 0, 600, 30); m->menu(menuitems); Fl_Box props_box(FL_UP_BOX, 0, 30, 600, 25, ""); Fl_Group props(0, 30, 600, 25); props.resizable(NULL); size_c = new Fl_Choice(30, 32, 50, 21, NULL); size_c->menu(size_menu); size_c->callback(size_cb); size_c->tooltip("Text Size"); color_b = new Fl_Button(100, 32, 21, 21); color_b->color(FL_BLACK); color_b->callback(color_cb, color_b); color_b->shortcut(FL_ALT + 'c'); color_b->tooltip("Text Color"); props.end(); page_sel = new Fl_Hold_Browser(0, 55, 40, win->h()-55); page_sel->callback(page_sel_cb); page_sel->end(); scroll = new Fl_Scroll(40, 55, win->w()-40, win->h()-55); psed_p = new PSEditor(40, 55, 700, 900); psed_p->property_changed_callback(property_changed_cb); scroll->end(); fl_open_display(); Fl::add_handler(xev_handler); win->resizable(scroll); win->end(); win->callback((Fl_Callback *)quit_cb); win->show(1, argv); property_changed_cb(); // sync properties with UI if (zoom_val) psed_p->zoom(zoom_val); if (my_argc >= 1) { filename = strdup(my_argv[0]); open_file(filename); } for (int i = 0; i < tv_idx; i++) { psed_p->replace_tag(tv[i].tag, tv[i].value); free(tv[i].tag); free(tv[i].value); } return Fl::run(); } }