/* Class : statictext SuperClass : text Author : Cathy Waite Turing Institute Date : Wed Jan 23 20:27:24 GMT 1991 Funtionality : Can display and format several lines of text. */ #include #include "extensions.h" /* CLASS VARIABLES */ /* How far the text is inset from the border of the object. */ int margin = 10; int margin2 = margin*2; /* The spacing between each line of text */ int line_gap = 3; /* INSTANCE VARIABLES */ /* The string displayed in the statictext object */ char *value = ""; persist(value) /* Whether or not to display the bounding box */ boolean has_bounding_box = true; persist(has_bounding_box) /* The line wrapping format. Can be one of three : /none : the lines are clipped. /char : the lines are wrapped at the nearest character. /word : the lines are wrapped at the neared word. */ name line_wrap = /word; persist(line_wrap) /* The "white space" used to delimit words */ char *word_spaces = "(){}[].,!?; \n\t"; /* The length (in pixels) of the tabs. If set to zero, tabs are ignored */ int tab_width = 36; /* Initially ~ 0.5 inches */ /* Used to control tabs. Initially tabs are on. */ /* text_show_proc and width_show_proc are called from the operators */ /* text_show and text_width defined in statictext.h */ name text_show_proc = /show_with_tabs; name text_width_proc = /width_with_tabs; /* The mapping from value to the display of the text. The array conceptually consist of pairs. Each pair corresponds to a display line of text. The first element in the pair (all even items) is the the index of the value array which is the start of a display line (i.e. the index of the first character in the line. The second element in the pair is the length of the line (in characters). */ /* The macros "MAPLINE(x) and "MAPLENGTH(x) (see statictext.h) should be used */ /* to access the array.*/ int *display_map = [0, 0]; /* The amount of space allocated to the display_map at a time */ #define DISPLAY_MAP_CHUNK 20 /* The macro to allocate more memory to the display map if required */ #define GROW_DISPLAY_MAP(x) if ( (x) << 1 == length(display_map)) display_map = (int *)append(display_map,(int *) array(DISPLAY_MAP_CHUNK)) /* The number of display lines for the whole value */ int total_display_lines = 0; /* The number of display lines the text object can display */ int max_display_lines = 0; /* The index of the last line that is displayed */ int display_bottom_line = 0; /* The index of first line that is displayed */ int display_top_line = 0; /* INTERNAL CONVERISON ROUTINES */ /* Returns the y-position (in pixels) of the display line, relative to */ /* the bottom left-hand corener of the text object */ float ypos_from_line(int line) { return((h-margin) - ((line+1-display_top_line)*(fontheight(textfont)+line_gap))); } boolean is_word_space(char ch) { return(arraycontains((any *)word_spaces,(any)ch)); } /* METHODS TO CALCULATE DISPLAY TEXT DIMENSIONS */ /* The width in which text can be displayed */ int textarea_width() { return w - margin2; } void clip_textarea() { rectpath(margin,margin,textarea_width,h-margin2); clip(); newpath(); } /* The width of the text object (seems obvious now, but is changed is sub-classes */ int boxarea_width() { return w; } void clip_boxarea() { rectpath(0,0,boxarea_width,h); clip(); newpath(); } /* Calculates the number of lines the text object can display */ void compute_no_display_lines() { max_display_lines = max(1, idiv(cvi(h-margin2), cvi(fontheight(textfont)+ line_gap))); } /* Finds the start of the word at "poistion" on display line "line". Returns the index of the start of the word. */ int wrap_word(char *line, int position) { int pos = position; boolean not_word = true; while(pos >= 0 && not_word) { if (is_word_space(line[pos])) not_word = false; else pos--; } if (pos < 0) /* Word goes to start of line, so just clip it */ return(position); else /* Return index of end of word */ return(pos+1); } /* Returns the index fo the character the line should be wrapped at. NOTE : Doesn't setfont as this is done in compute_display_lines. If called from anywhere else, do need to setfont, so that stringwidthonly returns values for correct font */ int wrap_line(char *line) { int char_count; /* The average width of a character */ int char_width = stringwidthonly("n"); /* The maximum width (in pixels of a line) */ int max_width = textarea_width(); /* The length of the line to be wrapped */ int line_width = text_width(line); if (line_width < max_width) /* It all fits on one line */ return(length(line)); /* Find character to break at */ char_count = min(length(line),max(1,idiv(max_width,char_width))); /* This should be correct for a fixed length font, but will be a good starting point for the seacrh for variable width font. Search forward and backwards from this char until the nearest one is found; */ if (text_width(headinterval(line,char_count)) < max_width) { /* Search forwards */ while(text_width(headinterval(line,char_count+1)) < max_width) char_count++; } else { /* Search backwards */ while(text_width(headinterval(line,char_count-1)) >= max_width) char_count--; } if (line_wrap == /word) char_count = wrap_word(line,char_count); return(char_count); } /* Calculates the display map from the given line. Does not change any of the previous lines. line_no : lineto start recomputing from. char_count : the number of chars before the start of the line. rest_of_text : the value, from this line onwards */ void compute_display_map_from(int line_no, int char_count, char *rest_of_text) { boolean finished = false; char **search_result, *value_line, *rest_of_line; int wrap_result; setfont(textfont); while(!finished) { /* Find the next real line in the value */ search_result = search(rest_of_text,"\n"); if (search_result == null) { finished = true; value_line = rest_of_text; } else { rest_of_text = search_result[0]; value_line = search_result[2]; } /* line wrap the "real" line */ if (value_line == "") { /* empty line */ MAPLINE(line_no) = char_count; MAPLENGTH(line_no) = 0; line_no++; /* check array is big enough */ GROW_DISPLAY_MAP(line_no); } else if (line_wrap == /none) { /* Allocate whole value line to display line, and deypend on clipping to stop damage */ MAPLINE(line_no) = char_count; MAPLENGTH(line_no) = length(value_line); char_count += length(value_line); line_no++; /* check array is big enough */ GROW_DISPLAY_MAP(line_no); } else { /* Wrap the value line into one or more display_lines */ rest_of_line = value_line; while(rest_of_line != "") { wrap_result = wrap_line(rest_of_line); MAPLINE(line_no) = char_count; MAPLENGTH(line_no) = wrap_result; rest_of_line = tailinterval(rest_of_line,wrap_result); char_count = char_count+wrap_result; line_no++; /* check array is big enough */ GROW_DISPLAY_MAP(line_no); } } /* Jump passed the end of line symbol */ char_count++; } total_display_lines = line_no-1; display_bottom_line = min(total_display_lines, max(0,display_top_line + max_display_lines-1)); } /* Updates the display map from line "line" */ void compute_updated_display_map(int line) { char *rest_of_text; int char_count; rest_of_text = getinterval(value,MAPLINE(line),length(value) - MAPLINE(line)); char_count = MAPLINE(line); compute_display_map_from(line,char_count,rest_of_text); } /* Calculates the start and length of each display line, putting the values into the display_map array. This is wher ethe line-wrapping occurs */ void compute_display_map() { if (length(value) == 0) { display_top_line = 0; display_bottom_line = 0; total_display_lines = 0; display_map = [0,0]; return; } compute_display_map_from(0,0,value); } /* ROUTINES TO HANDLE TABS */ /* Returns the required width of the tab to align with the next tab column Takes as input the xposition of the last character before the tab. Returns the width of the tab, rather than the actual xposition after the tab so that the result can be used with "rmoveto" */ int nexttab(int current_width) { return(mul(idiv(current_width,tab_width)+1,tab_width) - current_width); } /* Takes as input as string (with or without tabs) It works out where the tabs should go, depending on the starting point of the string (currentpoint) and displays the string, tabs 'n all */ void show_with_tabs(char *str) { int tab_space; char **results; char *rest = str; int width = (currentpoint())[0] - margin; results = search(rest,"\t"); while(results != null) { rest = results[0]; show(results[2]); width += stringwidthonly(results[2]); tab_space = nexttab(width); rmoveto(tab_space,0); width += tab_space; results = search(rest,"\t"); } show(rest); } /* Takes as input a string (with or without tabs) It works out the width of the string, including tabs. WARNING : This assumes that the string is the first part of a text line, i.e. it will be displayed at position (0,y). If this is not the case, then the starting position should be passed in and used as the initial value of width rather than 0; */ int width_with_tabs(char *str) { int width = 0; char **results; char *rest = str; results = search(rest,"\t"); while(results != null) { rest = results[0]; width += stringwidthonly(results[2]); width += nexttab(width); results = search(rest,"\t"); } width += stringwidthonly(rest); return(width); } /* Routines to treat tabs as spaces for right and centered justification */ int width_with_spaces(char *str) { int no = 0; char **results; char *rest = str; results = search(rest,"\t"); while(results != null) { rest = results[0]; no++; results = search(rest,"\t"); } return(stringwidthonly(str)+(no*(stringwidthonly(" ")))); } void show_with_spaces(char *str) { widthshow(stringwidthonly(" "),0,9 ,str); } /* Takes an integer, which is used to determine the width of the tab */ /* olumns. If the width is 0, the routines to show and get the */ /* width of strings is changed to ones which ignore tabs */ void set_tabs(int tabs) { /* If text is currently justified, do not change the procedures, just update the tab size, do when justification set back to /Left, tabs isze will be as required */ softpromote(/tab_width,tabs); if (justification == /Left) { if( (tabs == 0)) { text_show_proc = /show_with_spaces; text_width_proc = /width_with_spaces; } else { /* By upromoting will go back to class version with tabs on */ unpromote(/text_show_proc); unpromote(/text_width_proc); } compute_display_map(); paint(); } } /* ROUTINES TO HANDLE JUSTIFICATION */ /* Returns the "indentation" for the given line of text. Only owrks if the string is a full display line */ int justification_indent(char *str) { switch(justification) { default : /* Left */ return(0); case /Centered : return( (textarea_width - text_width(str)) << -1); case /Right : return( textarea_width - text_width(str)); } } /* Justification can be either /Left, /Right, /Centered */ void set_justification(name justify) { switch(justify) { default: /* By upromoting will go back to class version with tabs on */ unpromote(/text_show_proc); unpromote(/text_width_proc); break; case /Centered : text_show_proc = /show_with_spaces; text_width_proc = /width_with_spaces; break; case /Right : text_show_proc = /show_with_spaces; text_width_proc = /width_with_spaces; break; } softpromote(/justification,justify); compute_display_map(); paint(); } /* PAINT METHODS */ /* /* Paints lines of text, from line "line". Will only try to display those lines visible */ void paint_text_from_line_to(int line,int to_line) { float line_height, line_pos; int i; char *str_line; int anchor_y = (to_line == display_bottom_line) ? 3 : ypos_from_line(to_line); /* Check that from line is within range of text_object. Note this */ /* is not a comparison against display_botton_line, as even if */ /* "line" is beyond the last line of text, the area from "line" to */ /* the end of the text object souhld be filled (undrawn) */ if (line <= display_top_line+max_display_lines) { gsave(); clip_textarea(); /* If the line is before the top_display_line, redraw only */ /* from the top display_line */ line = max(line,display_top_line); /* Un-draw the area that is going to be re-painted */ /* Draw background */ setcolor(BG); rectpath(3,anchor_y,boxarea_width-6,ypos_from_line(line-1)-line_gap-anchor_y); fill(); /* Draw the required text */ setcolor(FG); setfont(textfont); line_height = fontheight(textfont)+line_gap; line_pos = ypos_from_line(line); for(i=line;i <= to_line; i++) { str_line = getinterval(value,MAPLINE(i),MAPLENGTH(i)); moveto(margin+justification_indent(str_line), line_pos+fontdescent(textfont)); text_show(str_line); line_pos -= line_height; } grestore(); } } void paint_text_from_line(int line) { paint_text_from_line_to(line,display_bottom_line); } /* Repaint the whole text object */ void Paint() { setcolor(BG); clippath(); fill(); paint_text_from_line(0); if (has_bounding_box) OLBox(false,0,0,boxarea_width,h); } /* METHODS TO SET INSTANCE VARIABLES */ /* Selectively paints or unpaints the bounding box depending on the */ /* boolean argument */ void set_bounding_box(boolean flag) { softpromote(/has_bounding_box,flag); if (flag) gexeca(/OLBox,[false,0,0,boxarea_width,h]); else gexeca(/undrawOLBox,[0,0,boxarea_width,h]); } /* changes the vlaue of the text objecxt and re-paints */ void set_value(char *new_string) { value = copy(new_string,string(length(new_string))); display_top_line = 0; compute_display_map(); paint(); } void clearvalue() { set_value(""); } /* Change the line wrapping alogrithm */ /* "wrap" should be one of /none, /char, or /word */ void set_line_wrap(name wrap) { softpromote(/line_wrap,wrap); compute_display_map(); paint(); } /* Change the font for all of the text */ void set_font(name font, int font_size) { textfont = printermatchfont(scalefont(findfont(font),font_size),false); compute_no_display_lines(); compute_display_map(); paint(); } void Validate() { super.Validate(); compute_no_display_lines(); compute_display_map(); } float minwidth() { return(margin2+10); } float minheight() { return( margin2 + 10); } operator char *loadfile(char *filename); void setfile(char * filename) { set_value(loadfile(filename)); } #postscript /loadfile { % string (PATH) (~) ?getenv filepathparse (r) filepathopen { /contents () def exch pop % file { dup 256 string readline { /contents contents 3 -1 roll append (\n) append store }{ /contents contents 3 -1 roll append store closefile contents exit } ifelse }loop }{ (Cannot open file : ) exch append } ifelse } def #end