To render the spatial map of rooms as an EPS (Encapsulated PostScript) file.


§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;

§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);

§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);
        }

§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);

§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);
    }

§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;
        }
    }

§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;

§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");

§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");

§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);
    }

§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;

§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);

§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;

§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);

§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);

§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;

§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");

§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);
    }

§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);

§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
    }

§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);
    }

§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);
    }

§3.

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);
    }

§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);
}

§7. Straight lines.

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");
}

§8. Dashed arrows.

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);
}