To render the spatial map of rooms as an EPS (Encapsulated PostScript) file.
- §2. Writing text in EPS
- §4. EPS header
- §5. Circles and rectangles
- §7. Straight lines
- §8. Dashed arrows
- §9. Bezier curves
- §10. Line thickness
- §11. Text
- §12. RGB colours
§1. The test case Index-MapEPS-ROTA may be useful here: its output is a plain text file, but simply removing the top line (which contains the test case number) and then renaming it to have file extension .eps should make it a valid EPS file, which can be opened in Ghostscript, Photoshop or similar.
void RenderEPSMap::render_map_as_EPS(filename *F, text_stream *F_alt, index_session *session) { localisation_dictionary *LD = Indexing::get_localisation(session); SpatialMap::establish_spatial_coordinates(session); HTMLMap::compute_room_colours(session); Prepare the EPS levels1.1; Open a stream and write the EPS map to it1.2; }
§1.1. Prepare the EPS levels1.1 =
faux_instance_set *faux_set = Indexing::get_set_of_instances(session); Create the main EPS map super-level1.1.1; for (int z=session->calc.Universe.corner1.z; z>=session->calc.Universe.corner0.z; z--) Create an EPS map level for this z-slice1.1.2; FauxInstances::decode_hints(session, 2); if (session->changed_global_room_colour == FALSE) Inherit EPS room colours from those used in the World Index1.1.3;
- This code is used in §1.
§1.1.1. Create the main EPS map super-level1.1.1 =
EPS_map_level *main_eml = CREATE(EPS_map_level); Indexing::add_EPS_map_levels(session, main_eml); main_eml->width = ConfigureIndexMap::get_int_mp(I"minimum-map-width", NULL, session); main_eml->actual_height = 0; main_eml->titling_point_size = ConfigureIndexMap::get_int_mp(I"title-size", NULL, session); main_eml->titling = Str::new(); Localisation::roman(main_eml->titling, LD, I"Index.EPSMap.DefaultTitle"); main_eml->contains_titling = TRUE; main_eml->contains_rooms = FALSE; ConfigureIndexMap::prepare_map_parameter_scope(&(main_eml->map_parameters), session); ConfigureIndexMap::put_text_mp(I"title", &(main_eml->map_parameters), main_eml->titling, session);
- This code is used in §1.1.
§1.1.2. Create an EPS map level for this z-slice1.1.2 =
EPS_map_level *eml = CREATE(EPS_map_level); Indexing::add_EPS_map_levels(session, eml); eml->contains_rooms = TRUE; eml->map_level = z; eml->y_max = -100000, eml->y_min = 100000; faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) if (Room_position(R).z == z) { if (Room_position(R).y < eml->y_min) eml->y_min = Room_position(R).y; if (Room_position(R).y > eml->y_max) eml->y_max = Room_position(R).y; } Str::clear(eml->titling); HTMLMap::devise_level_rubric(z, eml->titling, session); if (Str::len(eml->titling) == 0) eml->contains_titling = FALSE; else eml->contains_titling = TRUE; ConfigureIndexMap::prepare_map_parameter_scope(&(eml->map_parameters), session); ConfigureIndexMap::put_text_mp(I"subtitle", &(eml->map_parameters), eml->titling, session); LOOP_OVER_FAUX_ROOMS(faux_set, R) if (Room_position(R).z == z) { FauxInstances::get_parameters(R)->wider_scope = &(eml->map_parameters); }
- This code is used in §1.1.
§1.1.3. Inherit EPS room colours from those used in the World Index1.1.3 =
faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) ConfigureIndexMap::put_text_mp(I"room-colour", FauxInstances::get_parameters(R), R->fimd.colour, session);
- This code is used in §1.1.
§1.2. Open a stream and write the EPS map to it1.2 =
text_stream *OUT = F_alt; text_stream EPS_struct; if (F_alt == NULL) { OUT = &EPS_struct; if (STREAM_OPEN_TO_FILE(OUT, F, ISO_ENC) == FALSE) { #ifdef CORE_MODULE Problems::fatal_on_file("Can't open index file", F); #endif #ifndef CORE_MODULE Errors::fatal_with_file("can't open index file", F); #endif } } faux_instance_set *faux_set = Indexing::get_set_of_instances(session); Plot the map itself1.2.1; Plot all of the rubrics onto the EPS map1.2.2; if (F_alt == NULL) { STREAM_CLOSE(OUT); }
- This code is used in §1.
§1.2.1. Plot the map itself1.2.1 =
int blh, total height of the EPS map area (not counting border) blw, total width of the EPS map area (not counting border) border = ConfigureIndexMap::get_int_mp(I"border-size", NULL, session), vskip = ConfigureIndexMap::get_int_mp(I"vertical-spacing", NULL, session); Compute the dimensions of the EPS map1.2.1.1; int bounding_box_width = blw+2*border, bounding_box_height = blh+2*border; RenderEPSMap::EPS_compile_header(OUT, bounding_box_width, bounding_box_height, ConfigureIndexMap::get_text_mp(I"title-font", NULL, session), ConfigureIndexMap::get_int_mp(I"title-size", NULL, session)); if (ConfigureIndexMap::get_int_mp(I"map-outline", NULL, session)) Draw a big rectangular outline around the entire EPS map1.2.1.2; linked_list *L = Indexing::get_list_of_EPS_map_levels(session); EPS_map_level *eml; LOOP_OVER_LINKED_LIST(eml, EPS_map_level, L) { map_parameter_scope *level_scope = &(eml->map_parameters); int mapunit = ConfigureIndexMap::get_int_mp(I"grid-size", level_scope, session); if (eml->contains_rooms == FALSE) if (ConfigureIndexMap::get_int_mp(I"map-outline", NULL, session)) Draw an intermediate strut in the big rectangular outline1.2.1.3; if (eml->contains_titling) Draw the title for this EPS map level1.2.1.4; if (eml->contains_rooms) { faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) if (Room_position(R).z == eml->map_level) Establish EPS coordinates for this room1.2.1.5; LOOP_OVER_FAUX_ROOMS(faux_set, R) if (Room_position(R).z == eml->map_level) Draw the map connections from this room as EPS paths1.2.1.6; LOOP_OVER_FAUX_ROOMS(faux_set, R) if (Room_position(R).z == eml->map_level) Draw the boxes for the rooms themselves1.2.1.7; } }
- This code is used in §1.2.
§1.2.1.1. Compute the dimensions of the EPS map1.2.1.1 =
int total_chunk_height = 0, max_chunk_width = 0; EPS_map_level *eml; LOOP_BACKWARDS_OVER(eml, EPS_map_level) { map_parameter_scope *level_scope = &(eml->map_parameters); int mapunit = ConfigureIndexMap::get_int_mp(I"grid-size", level_scope, session); int p = ConfigureIndexMap::get_int_mp(I"title-size", level_scope, session); if (eml->contains_rooms) p = ConfigureIndexMap::get_int_mp(I"subtitle-size", level_scope, session); eml->titling_point_size = p; eml->width = (session->calc.Universe.corner1.x-session->calc.Universe.corner0.x+2)*mapunit; if (eml->allocation_id == 0) eml->actual_height = 0; else eml->actual_height = (eml->y_max-eml->y_min+1)*mapunit; eml->eps_origin = total_chunk_height + border; eml->height = eml->actual_height + vskip; if (eml->contains_rooms) eml->height += vskip; if (eml->contains_titling) eml->height += eml->titling_point_size+vskip; total_chunk_height += eml->height; if (max_chunk_width < eml->width) max_chunk_width = eml->width; } blh = total_chunk_height; blw = max_chunk_width;
- This code is used in §1.2.1.
§1.2.1.2. The outline is a little like drawing the shape of a bookcase: there's a big rectangle around the whole thing...
Draw a big rectangular outline around the entire EPS map1.2.1.2 =
WRITE("newpath %% Ruled outline outer box of map\n"); RenderEPSMap::EPS_compile_rectangular_path(OUT, border, border, border+blw, border+blh); WRITE("stroke\n");
- This code is used in §1.2.1.
§1.2.1.3. ...and then there are horizontal shelves dividing it into compartments. (Each map level will be drawn inside one of these compartments.)
Draw an intermediate strut in the big rectangular outline1.2.1.3 =
WRITE("newpath %% Ruled horizontal line\n"); RenderEPSMap::EPS_compile_horizontal_line_path(OUT, border, blw+border, eml->eps_origin); WRITE("stroke\n");
- This code is used in §1.2.1.
§1.2.1.4. Draw the title for this EPS map level1.2.1.4 =
int y = eml->eps_origin + vskip + eml->actual_height; if (eml->contains_rooms) { if (ConfigureIndexMap::get_int_mp(I"monochrome", level_scope, session)) RenderEPSMap::EPS_compile_set_greyscale(OUT, 0); else RenderEPSMap::EPS_compile_set_colour(OUT, ConfigureIndexMap::get_text_mp(I"subtitle-colour", level_scope, session)); RenderEPSMap::plot_stream_at(OUT, ConfigureIndexMap::get_text_mp(I"subtitle", level_scope, session), NULL, 128, ConfigureIndexMap::get_text_mp(I"subtitle-font", level_scope, session), border*2, y+vskip, ConfigureIndexMap::get_int_mp(I"subtitle-size", level_scope, session), FALSE, FALSE); } else { if (ConfigureIndexMap::get_int_mp(I"monochrome", level_scope, session)) RenderEPSMap::EPS_compile_set_greyscale(OUT, 0); else RenderEPSMap::EPS_compile_set_colour(OUT, ConfigureIndexMap::get_text_mp(I"title-colour", level_scope, session)); RenderEPSMap::plot_stream_at(OUT, ConfigureIndexMap::get_text_mp(I"title", NULL, session), NULL, 128, ConfigureIndexMap::get_text_mp(I"title-font", level_scope, session), border*2, y+2*vskip, ConfigureIndexMap::get_int_mp(I"title-size", level_scope, session), FALSE, TRUE); }
- This code is used in §1.2.1.
§1.2.1.5. Establish EPS coordinates for this room1.2.1.5 =
map_parameter_scope *room_scope = FauxInstances::get_parameters(R); int bx = Room_position(R).x-session->calc.Universe.corner0.x; int by = Room_position(R).y-eml->y_min; int offs = ConfigureIndexMap::get_int_mp(I"room-offset", room_scope, session); int xpart = offs%10000, ypart = offs/10000; while (xpart > 5000) xpart-=10000; while (xpart < -5000) xpart+=10000; bx = (bx)*mapunit + border + mapunit/2; by = (by)*mapunit + eml->eps_origin + vskip + mapunit/2; bx += xpart*mapunit/100; by += ypart*mapunit/100; R->fimd.eps_x = bx; R->fimd.eps_y = by;
- This code is used in §1.2.1.
§1.2.1.6. Draw the map connections from this room as EPS paths1.2.1.6 =
map_parameter_scope *room_scope = FauxInstances::get_parameters(R); RenderEPSMap::EPS_compile_line_width_setting(OUT, ConfigureIndexMap::get_int_mp(I"route-thickness", room_scope, session)); int bx = R->fimd.eps_x; int by = R->fimd.eps_y; int boxsize = ConfigureIndexMap::get_int_mp(I"room-size", room_scope, session)/2; int R_stiffness = ConfigureIndexMap::get_int_mp(I"route-stiffness", room_scope, session); int dir; LOOP_OVER_STORY_DIRECTIONS(dir) { faux_instance *T = SpatialMap::room_exit(R, dir, NULL); int exit = session->story_dir_to_page_dir[dir]; if (FauxInstances::is_a_room(T)) Draw a single map connection as an EPS arrow1.2.1.6.1; } RenderEPSMap::EPS_compile_line_width_unsetting(OUT);
- This code is used in §1.2.1.
§1.2.1.6.1. Draw a single map connection as an EPS arrow1.2.1.6.1 =
int T_stiffness = ConfigureIndexMap::get_int_mp(I"route-stiffness", FauxInstances::get_parameters(T), session); if (ConfigureIndexMap::get_int_mp(I"monochrome", level_scope, session)) RenderEPSMap::EPS_compile_set_greyscale(OUT, 0); else RenderEPSMap::EPS_compile_set_colour(OUT, ConfigureIndexMap::get_text_mp(I"route-colour", level_scope, session)); if ((Room_position(T).z == Room_position(R).z) && (SpatialMap::room_exit(T, SpatialMap::opposite(dir, session), FALSE) == R)) Draw a two-ended arrow for a two-way horizontal connection1.2.1.6.1.1 else Draw a one-way arrow for a distant or off-level connection1.2.1.6.1.2;
- This code is used in §1.2.1.6.
§1.2.1.6.1.1. We don't want to draw this twice (once for R, once for T), so we draw it just for the earlier-defined room.
Draw a two-ended arrow for a two-way horizontal connection1.2.1.6.1.1 =
if (R->allocation_id <= T->allocation_id) RenderEPSMap::EPS_compile_Bezier_curve(OUT, R_stiffness*mapunit, T_stiffness*mapunit, bx, by, exit, T->fimd.eps_x, T->fimd.eps_y, SpatialMap::opposite(exit, session), session);
- This code is used in §1.2.1.6.1.
§1.2.1.6.1.2. A one-way arrow has the destination marked on it textually, since it doesn't actually go there in any visual way.
Draw a one-way arrow for a distant or off-level connection1.2.1.6.1.2 =
int scaled = 1; vector E = SpatialMap::direction_as_vector(exit, session); switch(exit) { case 8: E = U_vector_EPS; scaled = 2; break; case 9: E = D_vector_EPS; scaled = 2; break; case 10: E = IN_vector_EPS; scaled = 2; break; case 11: E = OUT_vector_EPS; scaled = 2; break; } RenderEPSMap::EPS_compile_dashed_arrow(OUT, boxsize/scaled, E, bx, by); RenderEPSMap::plot_text_at(OUT, NULL, T, ConfigureIndexMap::get_int_mp(I"annotation-length", NULL, session), ConfigureIndexMap::get_text_mp(I"annotation-font", NULL, session), bx+E.x*boxsize*6/scaled/5, by+E.y*boxsize*6/scaled/5, ConfigureIndexMap::get_int_mp(I"annotation-size", NULL, session), TRUE, TRUE);
- This code is used in §1.2.1.6.1.
§1.2.1.7. Draw the boxes for the rooms themselves1.2.1.7 =
map_parameter_scope *room_scope = FauxInstances::get_parameters(R); int bx = R->fimd.eps_x; int by = R->fimd.eps_y; int boxsize = ConfigureIndexMap::get_int_mp(I"room-size", room_scope, session)/2; Draw the filled box for the room1.2.1.7.1; Draw the outline of the box for the room1.2.1.7.2; Write in the name of the room1.2.1.7.3;
- This code is used in §1.2.1.
§1.2.1.7.1. Draw the filled box for the room1.2.1.7.1 =
WRITE("newpath %% Room interior\n"); if (ConfigureIndexMap::get_int_mp(I"monochrome", room_scope, session)) RenderEPSMap::EPS_compile_set_greyscale(OUT, 75); else RenderEPSMap::EPS_compile_set_colour(OUT, ConfigureIndexMap::get_text_mp(I"room-colour", room_scope, session)); RenderEPSMap::EPS_compile_room_boundary_path(OUT, bx, by, boxsize, ConfigureIndexMap::get_text_mp(I"room-shape", room_scope, session)); WRITE("fill\n\n");
- This code is used in §1.2.1.7.
§1.2.1.7.2. Draw the outline of the box for the room1.2.1.7.2 =
if (ConfigureIndexMap::get_int_mp(I"room-outline", room_scope, session)) { RenderEPSMap::EPS_compile_line_width_setting(OUT, ConfigureIndexMap::get_int_mp(I"room-outline-thickness", room_scope, session)); WRITE("newpath %% Room outline\n"); if (ConfigureIndexMap::get_int_mp(I"monochrome", level_scope, session)) RenderEPSMap::EPS_compile_set_greyscale(OUT, 0); else RenderEPSMap::EPS_compile_set_colour(OUT, ConfigureIndexMap::get_text_mp(I"room-outline-colour", room_scope, session)); RenderEPSMap::EPS_compile_room_boundary_path(OUT, bx, by, boxsize, ConfigureIndexMap::get_text_mp(I"room-shape", room_scope, session)); WRITE("stroke\n"); RenderEPSMap::EPS_compile_line_width_unsetting(OUT); }
- This code is used in §1.2.1.7.
§1.2.1.7.3. Write in the name of the room1.2.1.7.3 =
int offs = ConfigureIndexMap::get_int_mp(I"room-name-offset", room_scope, session); int xpart = offs%10000, ypart = offs/10000; while (xpart > 5000) xpart-=10000; while (xpart < -5000) xpart+=10000; bx += xpart*mapunit/100; by += ypart*mapunit/100; if (ConfigureIndexMap::get_int_mp(I"monochrome", level_scope, session)) RenderEPSMap::EPS_compile_set_greyscale(OUT, 0); else RenderEPSMap::EPS_compile_set_colour(OUT, ConfigureIndexMap::get_text_mp(I"room-name-colour", room_scope, session)); text_stream *legend = ConfigureIndexMap::get_text_mp(I"room-name", room_scope, session); faux_instance *room_to_name = NULL; if (Str::len(legend) == 0) { room_to_name = R; legend = NULL; } RenderEPSMap::plot_text_at(OUT, legend, room_to_name, ConfigureIndexMap::get_int_mp(I"room-name-length", room_scope, session), ConfigureIndexMap::get_text_mp(I"room-name-font", room_scope, session), bx, by, ConfigureIndexMap::get_int_mp(I"room-name-size", room_scope, session), TRUE, TRUE);
- This code is used in §1.2.1.7.
§1.2.2. Plot all of the rubrics onto the EPS map1.2.2 =
rubric_holder *rh; LOOP_OVER_LINKED_LIST(rh, rubric_holder, faux_set->rubrics) { int bx = 0, by = 0; int xpart = rh->at_offset%10000, ypart = rh->at_offset/10000; int mapunit = ConfigureIndexMap::get_int_mp(I"grid-size", NULL, session); while (xpart > 5000) xpart-=10000; while (xpart < -5000) xpart+=10000; if (ConfigureIndexMap::get_int_mp(I"monochrome", NULL, session)) RenderEPSMap::EPS_compile_set_greyscale(OUT, 0); else RenderEPSMap::EPS_compile_set_colour(OUT, rh->colour); faux_instance *O = rh->offset_from; if (O) { bx = O->fimd.eps_x; by = O->fimd.eps_y; } bx += xpart*mapunit/100; by += ypart*mapunit/100; RenderEPSMap::plot_text_at(OUT, rh->annotation, NULL, 128, rh->font, bx, by, rh->point_size, TRUE, TRUE); centred both horizontally and vertically }
- This code is used in §1.2.
§2. Writing text in EPS. All of words written on the map — titles, labels for arrows, rubrics, and so on — come from here.
define MAX_EPS_TEXT_LENGTH 1000 define MAX_EPS_ABBREVIATED_LENGTH MAX_EPS_TEXT_LENGTH
void RenderEPSMap::plot_text_at(OUTPUT_STREAM, text_stream *text_to_plot, faux_instance *I, int abbrev_to, text_stream *font, int x, int y, int pointsize, int centre_h, int centre_v) { TEMPORARY_TEXT(txt) if (text_to_plot) { WRITE_TO(txt, "%S", text_to_plot); } else if (I) { Try taking the name from the printed name property of the room2.1; If that fails, try taking the name from its source text name2.2; } else return; RenderEPSMap::plot_stream_at(OUT, txt, I, abbrev_to, font, x, y, pointsize, centre_h, centre_v); DISCARD_TEXT(txt) }
§2.1. Try taking the name from the printed name property of the room2.1 =
if (Str::len(I->printed_name) > 0) { WRITE_TO(txt, "%S", I->printed_name); }
- This code is used in §2.
§2.2. If that fails, try taking the name from its source text name2.2 =
if (Str::len(txt) == 0) { text_stream *N = FauxInstances::get_name(I); if (Str::len(N)) return; WRITE_TO(txt, "%S", N); }
- This code is used in §2.
void RenderEPSMap::plot_stream_at(OUTPUT_STREAM, text_stream *text_to_plot, faux_instance *I, int abbrev_to, text_stream *font, int x, int y, int pointsize, int centre_h, int centre_v) { TEMPORARY_TEXT(txt) Str::copy(txt, text_to_plot); Abbreviate the text to be printed by stripping dispensable letters3.1; RenderEPSMap::EPS_compile_text(OUT, txt, x, y, font, pointsize, centre_h, centre_v); DISCARD_TEXT(txt) }
§3.1. The following cuts the text down to the abbreviation length by knocking out, in sequence: (a) lower-case vowels; (b) spaces; (c) lower-case consonants; (d) punctuation marks. If that doesn't do it, the text is simply truncated. For example, "Peisey-Nancroix" abbreviated to 10 is "Pesy-Nncrx" and to 5 is "PsyNn".
Abbreviate the text to be printed by stripping dispensable letters3.1 =
if (abbrev_to > MAX_EPS_ABBREVIATED_LENGTH) abbrev_to = MAX_EPS_ABBREVIATED_LENGTH; while (Str::len(txt) > abbrev_to) { int j; for (j=Str::len(txt)-1; j>=0; j--) if (Characters::vowel(Str::get_at(txt, j))) goto RemoveOne; for (j=Str::len(txt)-1; j>=0; j--) if (Str::get_at(txt, j) == ' ') goto RemoveOne; for (j=Str::len(txt)-1; j>=0; j--) if (Characters::islower(Str::get_at(txt, j))) goto RemoveOne; for (j=Str::len(txt)-1; j>=0; j--) if (Characters::isupper(Str::get_at(txt, j)) == FALSE) goto RemoveOne; Str::truncate(txt, abbrev_to); break; RemoveOne: Str::delete_nth_character(txt, j); }
- This code is used in §3.
§4. EPS header. EPS files are identified and version-numbered by a header, as follows.
void RenderEPSMap::EPS_compile_header(OUTPUT_STREAM, int bounding_box_width, int bounding_box_height, text_stream *default_font, int default_point_size) { WRITE("%%!PS-Adobe EPSF-3.0\n"); WRITE("%%%%BoundingBox: 0 0 %d %d\n", bounding_box_width, bounding_box_height); WRITE("%%%%IncludeFont: %S\n", default_font); WRITE("/%S findfont %d scalefont setfont\n", default_font, default_point_size); }
§5. Circles and rectangles. In EPS files, there's an imaginary pen which traces out "paths". These begin whenever the pen moves to a new location, and then continue until they are closed (joined up back to the start position) with a closepath command.
void RenderEPSMap::EPS_compile_circular_path(OUTPUT_STREAM, int x0, int y0, int radius) { WRITE("%d %d moveto %% rightmost point\n", x0+radius, y0); WRITE("%d %d %d %d %d arc %% full circle traced anticlockwise\n", x0, y0, radius, 0, 360); WRITE("closepath\n"); } void RenderEPSMap::EPS_compile_rectangular_path(OUTPUT_STREAM, int x0, int y0, int x1, int y1) { WRITE("%d %d moveto %% bottom left corner\n", x0, y0); WRITE("%d %d lineto %% bottom side\n", x1, y0); WRITE("%d %d lineto %% right side\n", x1, y1); WRITE("%d %d lineto %% top side\n", x0, y1); WRITE("closepath\n"); }
§6. The boundary of a room is always one of these:
void RenderEPSMap::EPS_compile_room_boundary_path(OUTPUT_STREAM, int bx, int by, int boxsize, text_stream *shape) { if (Str::cmp(shape, I"square") == 0) RenderEPSMap::EPS_compile_rectangular_path(OUT, bx-boxsize, by-boxsize, bx+boxsize, by+boxsize); else if (Str::cmp(shape, I"rectangle") == 0) RenderEPSMap::EPS_compile_rectangular_path(OUT, bx-2*boxsize, by-boxsize, bx+2*boxsize, by+boxsize); else if (Str::cmp(shape, I"circle") == 0) RenderEPSMap::EPS_compile_circular_path(OUT, bx, by, boxsize); else RenderEPSMap::EPS_compile_rectangular_path(OUT, bx-boxsize, by-boxsize, bx+boxsize, by+boxsize); }
void RenderEPSMap::EPS_compile_horizontal_line_path(OUTPUT_STREAM, int x0, int x1, int y) { WRITE("%d %d moveto %% LHS\n", x0, y); WRITE("%d %d lineto %% RHS\n", x1, y); WRITE("closepath\n"); }
void RenderEPSMap::EPS_compile_dashed_arrow(OUTPUT_STREAM, int length, vector Dir, int x0, int y0) { WRITE("[2 1] 0 setdash %% dashed line for arrow\n"); WRITE("%d %d moveto %% room centre\n", x0, y0); WRITE("%d %d rlineto %% arrow out\n", Dir.x*length, Dir.y*length); WRITE("stroke\n"); WRITE("[] 0 setdash %% back to normal solid lines\n"); }
§9. Bezier curves. The other sort of path we'll need is a Bézier curve, a quadratic curve which interpolates between vectors. EPS has support for these built-in; see any reference book on PostScript.
void RenderEPSMap::EPS_compile_Bezier_curve(OUTPUT_STREAM, int stiffness0, int stiffness1, int x0, int y0, int exit0, int x1, int y1, int exit1, index_session *session) { int cx1, cy1, cx2, cy2; vector E = SpatialMap::direction_as_vector(exit0, session); cx1 = x0+E.x*stiffness0/100; cy1 = y0+E.y*stiffness0/100; E = SpatialMap::direction_as_vector(exit1, session); cx2 = x1+E.x*stiffness1/100; cy2 = y1+E.y*stiffness1/100; WRITE("%d %d moveto %% start of Bezier curve\n", x0, y0); WRITE("%d %d %d %d %d %d curveto %% control points 1, 2 and end\n", cx1, cy1, cx2, cy2, x1, y1); WRITE("stroke\n"); }
§10. Line thickness. The following routines should be used in nested pairs, so that the PostScript stack is kept in order.
void RenderEPSMap::EPS_compile_line_width_setting(OUTPUT_STREAM, int new) { WRITE("currentlinewidth %% Push old line width onto stack\n"); WRITE("%d setlinewidth\n", new); } void RenderEPSMap::EPS_compile_line_width_unsetting(OUTPUT_STREAM) { WRITE("setlinewidth %% Pull old line width from stack\n"); }
§11. Text. In EPS world, text is just another sort of path.
void RenderEPSMap::EPS_compile_text(OUTPUT_STREAM, text_stream *text, int x, int y, text_stream *font, int pointsize, int centre_h, int centre_v) { WRITE("/%S findfont %d scalefont setfont\n", font, pointsize); WRITE("newpath (%S)\n", text); if (centre_h) WRITE("dup stringwidth add 2 div %d exch sub %% = X centre-offset\n", x); else WRITE("%d %% = X\n", x); if (centre_v) WRITE("%d %d 2 div sub %% = Y centre-offset\n", y, pointsize); else WRITE("%d %% = Y\n", y); WRITE("moveto show\n"); }
§12. RGB colours. Inform internally stores colours as six hexadecimal digits, in traditional HTML way: RRGGBB, with each colour from 0 to 255. In EPS files, colours are written as triples of floating point numbers \(0 \leq b \leq 1\).
EPS uses reverse Polish notation, so the command here is: R G B setrgbcolor.
void RenderEPSMap::EPS_compile_set_colour(OUTPUT_STREAM, text_stream *htmlcolour) { if (Str::len(htmlcolour) != 6) { WRITE_TO(STDERR, "Colour '%S'\n", htmlcolour); internal_error("Improper HTML colour"); } RenderEPSMap::choose_colour_beam(OUT, Str::get_at(htmlcolour, 0), Str::get_at(htmlcolour, 1)); RenderEPSMap::choose_colour_beam(OUT, Str::get_at(htmlcolour, 2), Str::get_at(htmlcolour, 3)); RenderEPSMap::choose_colour_beam(OUT, Str::get_at(htmlcolour, 4), Str::get_at(htmlcolour, 5)); WRITE("setrgbcolor %% From HTML colour %S\n", htmlcolour); } void RenderEPSMap::choose_colour_beam(OUTPUT_STREAM, inchar32_t hex1, inchar32_t hex2) { int k = RenderEPSMap::hex_to_int(hex1)*16 + RenderEPSMap::hex_to_int(hex2); WRITE("%.6g ", (double) (((float) k)/255.0)); } int RenderEPSMap::hex_to_int(inchar32_t hex) { switch(hex) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: internal_error("Improper character in HTML colour"); } return 0; }
§13. EPS also supports greyscale, where there's only one beam:
void RenderEPSMap::EPS_compile_set_greyscale(OUTPUT_STREAM, int N) { WRITE("%0.02f setgray %% greyscale %d/100ths of white\n", (float) N/100, N); }