# # Sawmill Document Description Library # # Graphical Object routines # include("lib.pdf"); include("lib.sdd.values"); ## ## SDD GRAPHICAL OBJECT CREATION ## # # new_sdd() # # Purpose: This creates a new SDD object # # Parameter: returns the new SDD. # subroutine(new_sdd, ( node sdd = new_node(); set_node_type(sdd{"xmin"}, "node"); set_node_type(sdd{"ymax"}, "node"); set_node_type(sdd{"width"}, "node"); set_node_type(sdd{"height"}, "node"); @sdd{"xmin"} = 0; @sdd{"ymax"} = 0; @sdd{"width"} = 0; @sdd{"height"} = 0; sdd; )); # new_sdd() # # set_sdd_width() # # Purpose: This sets the width of an SDD object. # # Parameters: sdd: the SDD object # newwidth: the new width # subroutine(set_sdd_width(node sdd, node newwidth), ( # Free memory if we already have a width if (@sdd{"width"} != 0) then ( delete_node(@sdd{"width"}); set_node_type(sdd{"width"}, "node"); ); # Set the new width @sdd{"width"} = newwidth; )); # set_sdd_width # # set_sdd_draw_width() # # Purpose: This sets the drawing width of an SDD object. # # Parameters: sdd: the SDD object # new_draw_width: the new drawing width # subroutine(set_sdd_draw_width(node sdd, node new_draw_width), ( # Free memory if we already have a draw_width if (@sdd{"draw_width"} != 0) then ( delete_node(@sdd{"draw_width"}); set_node_type(sdd{"draw_width"}, "node"); ); # Set the new draw_width @sdd{"draw_width"} = new_draw_width; )); # set_sdd_draw_width # # set_sdd_height() # # Purpose: This sets the height of an SDD object. # # Parameters: sdd: the SDD object # newheight: the new height # subroutine(set_sdd_height(node sdd, node newheight), ( # Free memory if we already have a height if (@sdd{"height"} != 0) then ( delete_node(@sdd{"height"}); set_node_type(sdd{"height"}, "node"); ); # Set the new height @sdd{"height"} = newheight; )); # set_sdd_height # # set_sdd_xmin() # # Purpose: This sets the xmin of an SDD object. # # Parameters: sdd: the SDD object # newxmin: the new xmin # subroutine(set_sdd_xmin(node sdd, node newxmin), ( # Free memory if we already have a xmin if (@sdd{"xmin"} != 0) then ( delete_node(@sdd{"xmin"}); set_node_type(sdd{"xmin"}, "node"); ); # Set the new xmin @sdd{"xmin"} = newxmin; )); # set_sdd_xmin # # set_sdd_ymax() # # Purpose: This sets the ymax of an SDD object. # # Parameters: sdd: the SDD object # newymax: the new ymax # subroutine(set_sdd_ymax(node sdd, node newymax), ( # Free memory if we already have a ymax if (@sdd{"ymax"} != 0) then ( delete_node(@sdd{"ymax"}); set_node_type(sdd{"ymax"}, "node"); ); # Set the new ymax set_node_type(sdd{"ymax"}, 'node'); @sdd{"ymax"} = newymax; )); # set_sdd_ymax # # set_sdd_bounds() # # Purpose: This sets the boundary of an SDD graphics object, using four SDD value objects. # # Parameters: sdd: the SDD to set # xmin, ymax, width, height: the boundary to set # subroutine(set_sdd_bounds(node sdd, node xmin, node ymax, node width, node height), ( set_sdd_xmin(sdd, xmin); set_sdd_ymax(sdd, ymax); set_sdd_width(sdd, width); set_sdd_height(sdd, height); )); # set_sdd_bounds() # # add_subsdd() # # Purpose: This adds one SDD graphical object as a subsdd of another. # # Parameters: sdd: the parent/containing SDD # subsdd: the SDD to add # subroutine(add_subsdd(node sdd, node subsdd), ( int itemnum = num_subnodes(sdd{"items"}) + 1; set_node_type(sdd{"items"}{itemnum}, 'node'); @sdd{"items"}{itemnum} = subsdd; )); # add_subsdd() # # add_subsdd_named() # # Purpose: This adds one SDD graphical object as a subsdd of another, and sets its name. # # Parameters: sdd: the parent/containing SDD. # subsdd: the SDD to add # nodename: the name of the new SDD, in the items list of "sdd" # subroutine(add_subsdd_named(node sdd, node subsdd, string nodename), ( set_node_type(sdd{"items"}{nodename}, 'node'); @sdd{"items"}{nodename} = subsdd; )); # add_subsdd() # # set_sdd_dimensions_full_width_text() # # Purpose: This sets the bounds of a text SDD to make it as wide as its enclosure, and as high as its text height. # # Parameters: sdd: the SDD to set the bounds of # parentsdd: the parent SDD # subroutine(set_sdd_dimensions_full_width_text(node sdd, node parentsdd), ( set_sdd_width(sdd, sddwidth(parentsdd)); register_sdd_dependency(sdd, "width", parentsdd, "width"); set_sdd_height(sdd, sddtextheight(sdd)); register_sdd_dependency(sdd, "height", parentsdd, "width"); )); # set_sdd_dimensions_full_width_text() # # new_text_sdd() # # Purpose: This creates a new "text" SDD object. # # Parameters: parentsdd: the parent SDD to add the text SDD to; 0 to leave detached # text: the text displayed by the new object # font: the font object of the font to display the text with # fontSize: the size of the font to display # wrap: true if text should be wrapped; false if it should all be on one line. # returns the new test SDD # subroutine(new_text_sdd(node parentsdd, string text, node font, float fontSize, bool wrap), ( node sdd = new_sdd(); @sdd{"type"} = "text"; @sdd{"text"} = text; set_node_type(sdd{"font"}, "node"); @sdd{"font"} = font; @sdd{"font_size"} = fontSize; @sdd{"wrap"} = wrap; if (parentsdd != 0) then add_subsdd(parentsdd, sdd); # If there is no parent, use the text's own width as the width if (parentsdd == 0) then ( set_sdd_width(sdd, sddtextwidth(sdd, sdd)); ); # If there is a parent, use the parent's width as the width else ( set_sdd_dimensions_full_width_text(sdd, parentsdd); ); set_sdd_height(sdd, sddtextheight(sdd)); register_sdd_dependency(sdd, "height", parentsdd, "width"); sdd; )); # new_text_sdd() subroutine(new_text_sdd_minimum_dimensions(node parentsdd, string text, node font, float fontSize, bool wrap), ( node textsdd = new_text_sdd(parentsdd, text, font, fontSize, wrap); # Use sddtextwidth as the width, which computes the width of a block of text, but constrains (wraps) it to the a specific value (in this case, the width of parentsdd). @parentsdd{"wrap_width"} = "sdd_width"; set_sdd_width(textsdd, sddtextwidth(textsdd, parentsdd)); register_sdd_dependency(textsdd, "width", parentsdd, "width"); set_sdd_height(textsdd, sddtextheight(textsdd)); register_sdd_dependency(textsdd, "height", textsdd, "width"); textsdd; )); # new_text_sdd_minimum_dimensions() # # new_box_sdd() # # Purpose: This creates a new box SDD object. # # Parameters: parentsdd: the parent SDD of the new box, or 0 to leave it detached. # xmin, ymax, width, height: the dimensions and location of the box # returns the new box SDD # subroutine(new_box_sdd(node parentsdd, node xmin, node ymax, node width, node height), ( node sdd = new_sdd(); @sdd{"type"} = "box"; # @sdd{"filled"} = false; set_sdd_bounds(sdd, xmin, ymax, width, height); if (parentsdd != 0) then add_subsdd(parentsdd, sdd); # Default to unfilled, black-stroked @sdd{"stroke_color"}{"red"} = 0; @sdd{"stroke_color"}{"green"} = 0; @sdd{"stroke_color"}{"blue"} = 0; sdd; )); # new_box_sdd() # # new_line_sdd() # # Purpose: This creates a new line SDD object. # # Parameters: parentsdd: the parent SDD of the new line, or 0 to leave it detached. # xmin, ymax, width, height: the dimensions and location of the box surrounding the line. # returns the new line SDD # subroutine(new_line_sdd(node parentsdd, node xmin, node ymax, node width, node height), ( node sdd = new_sdd(); @sdd{"type"} = "line"; set_sdd_bounds(sdd, xmin, ymax, width, height); if (parentsdd != 0) then add_subsdd(parentsdd, sdd); # Default to black-stroked @sdd{"stroke_color"}{"red"} = 0; @sdd{"stroke_color"}{"green"} = 0; @sdd{"stroke_color"}{"blue"} = 0; sdd; )); # new_line_sdd() subroutine(new_group_box_sdd(node parentsdd, node xmin, node ymax), ( node box = new_box_sdd(parentsdd, xmin, ymax, 0, 0); set_sdd_width(box, sddgroupwidth(box)); set_sdd_height(box, sddgroupheight(box)); box; )); # new_group_box_sdd() subroutine(set_sdd_stroke_color(node box, float red, float green, float blue), ( @box{"stroke_color"}{"red"} = red; @box{"stroke_color"}{"green"} = green; @box{"stroke_color"}{"blue"} = blue; )); # set_sdd_stroke_color() subroutine(remove_sdd_stroke_color(node box), ( delete_node(box{"stroke_color"}); )); # remove_sdd_stroke_color() subroutine(set_sdd_fill_color(node box, float red, float green, float blue), ( @box{"fill_color"}{"red"} = red; @box{"fill_color"}{"green"} = green; @box{"fill_color"}{"blue"} = blue; )); # set_sdd_fill_color() subroutine(new_spacer_sdd(node parentsdd, float width, float height), ( node box = new_box_sdd(parentsdd, 0, 0, sddfloat(width), sddfloat(height)); remove_sdd_stroke_color(box); box; )); # new_spacer_sdd() # # new_image_sdd() # # Purpose: This creates a new image SDD object. # # Parameters: parentsdd: the parent SDD of the new image, or 0 to leave it detached. # pathname: the pathname of the image file # subroutine(new_image_sdd(node parentsdd, string pathname), ( node img = new_sdd(); @img{"type"} = "image"; string image_handle = read_image_from_disk(pathname); @img{"image_handle"} = image_handle; int imageWidth = get_image_width(image_handle); int imageHeight = get_image_height(image_handle); @img{"image_width"} = imageWidth; @img{"image_height"} = imageHeight; set_sdd_width(img, sddfloat(imageWidth)); set_sdd_height(img, sddfloat(imageHeight)); if (parentsdd != 0) then add_subsdd(parentsdd, img); img; )); # new_image_sdd() # # set_sdd_bounds_match() # # Purpose: This sets the bounds of an SDD graphics object to match another SDD. # # Parameters: sdd: the SDD to set the bounds of # basesdd: the existing SDD to base the bound of sdd on # subroutine(set_sdd_bounds_match(node sdd, node basesdd), ( set_sdd_xmin(sdd, sddleft(basesdd)); register_sdd_dependency(sdd, "xmin", basesdd, "xmin"); set_sdd_ymax(sdd, sddtop(basesdd)); register_sdd_dependency(sdd, "ymax", basesdd, "ymax"); set_sdd_width(sdd, sddwidth(basesdd)); set_sdd_height(sdd, sddheight(basesdd)); )); # set_sdd_bounds_match() # 2010-09-21 - GMF - Table gridlines are no longer rendered, and *might* not be needed at all, with the better # box border code. Or maybe they are? Commenting it out for now, in case we need it again later. # #subroutine(new_table_gridlines_sdd(node tbl), ( # # node sdd = new_sdd(); # @sdd{"type"} = "table_gridlines"; # # add_subsdd(tbl, sdd); # # set_sdd_bounds_match(sdd, tbl); # # set_node_type(sdd{"tbl"}, "node"); # @sdd{"tbl"} = tbl; # sdd; # #)); # new_table_gridlines_sdd() # # set_sdd_bounds_offset() # # Purpose: This sets the bounds of an SDD graphics object to be offset, by a constant amount, from the bounds of another one. # # Parameters: sdd: the SDD to set the bounds of # basesdd: the existing SDD to base the bound of sdd on # xminoffset: the offset of the xmin of sdd from basesdd # yminoffset: the offset of the ymin of sdd from basesdd # xmaxoffset: the offset of the xmax of sdd from basesdd # ymaxoffset: the offset of the ymax of sdd from basesdd # subroutine(set_sdd_bounds_offset(node sdd, node basesdd, float xminoffset, float yminoffset, float xmaxoffset, float ymaxoffset), ( node xmin = new_node(); @xmin{"type"} = "add"; set_node_type(xmin{"value1"}, "node"); @xmin{"value1"} = @basesdd{"xmin"}; set_node_type(xmin{"value2"}, "node"); @xmin{"value2"} = sddfloat(xminoffset); set_sdd_xmin(sdd, xmin); node ymax = new_node(); @ymax{"type"} = "add"; set_node_type(ymax{"value1"}, "node"); @ymax{"value1"} = @basesdd{"ymax"}; set_node_type(ymax{"value2"}, "node"); @ymax{"value2"} = sddfloat(ymaxoffset); set_sdd_ymax(sdd, ymax); node width = new_node(); @width{"type"} = "subtract"; set_node_type(width{"value1"}, "node"); @width{"value1"} = @basesdd{"width"}; set_node_type(width{"value2"}, "node"); @width{"value2"} = sddfloat(xminoffset - xmaxoffset); set_sdd_width(sdd, width); node height = new_node(); @height{"type"} = "subtract"; set_node_type(height{"value1"}, "node"); @height{"value1"} = @basesdd{"height"}; set_node_type(height{"value2"}, "node"); @height{"value2"} = sddfloat(yminoffset - ymaxoffset); set_sdd_height(sdd, height); )); # set_sdd_bounds_offset() subroutine(set_sdd_position_offset(node sdd, node basesdd, float xminoffset, float ymaxoffset), ( node xmin = new_node(); @xmin{"type"} = "add"; set_node_type(xmin{"value1"}, "node"); @xmin{"value1"} = @basesdd{"xmin"}; set_node_type(xmin{"value2"}, "node"); @xmin{"value2"} = sddfloat(xminoffset); set_sdd_xmin(sdd, xmin); node ymax = new_node(); @ymax{"type"} = "add"; set_node_type(ymax{"value1"}, "node"); @ymax{"value1"} = @basesdd{"ymax"}; set_node_type(ymax{"value2"}, "node"); @ymax{"value2"} = sddfloat(ymaxoffset); set_sdd_ymax(sdd, ymax); )); # set_sdd_position_offset() # # set_sdd_position_upper_left() # # Purpose: Set the position of sdd so its upper left matches the upper left of sdd2. # # Parameter: sdd: the SDD to set the position of # sdd2: the SDD to get the position from # subroutine(set_sdd_position_upper_left(node sdd, node sdd2), ( set_sdd_xmin(sdd, sddleft(sdd2)); register_sdd_dependency(sdd, "xmin", sdd2, "xmin"); set_sdd_ymax(sdd, sddtop(sdd2)); register_sdd_dependency(sdd, "ymax", sdd2, "ymax"); )); # set_sdd_position_upper_left() # # set_sdd_position_upper_right() # # Purpose: Set the position of sdd so its upper left matches the upper right of sdd2. # # Parameter: sdd: the SDD to set the position of # sdd2: the SDD to get the position from # subroutine(set_sdd_position_upper_right(node sdd, node sdd2), ( set_sdd_xmin(sdd, sddright(sdd2)); register_sdd_dependency(sdd, "xmin", sdd2, "xmin"); register_sdd_dependency(sdd, "xmin", sdd2, "width"); set_sdd_ymax(sdd, sddtop(sdd2)); register_sdd_dependency(sdd, "ymax", sdd2, "ymax"); )); # set_sdd_position_upper_right() # # set_sdd_position_below() # # Purpose: This sets the bounds of sdd to put it below the bottom of another sdd (i.e., so top of sdd is bottom of sdd2). # # Parameters: sdd: the SDD to change the position of # sdd2: the SDD to get the position from # subroutine(set_sdd_position_below(node sdd, node sdd2), ( set_sdd_xmin(sdd, sddleft(sdd2)); register_sdd_dependency(sdd, "xmin", sdd2, "xmin"); set_sdd_ymax(sdd, sddbelow(sdd2, sdd)); )); # set_sdd_position_below() # # set_sdd_position_next() # # Purpose: This sets the bounds of sdd to put it below the last subsdd of another sdd (i.e., so top of the last subsdd is bottom of sdd2). # sdd is presumed to be newly-added to parentsdd, but not yet positioned; this positions it below the last other item. # # Parameters: sdd: the SDD to set the position of # parentsdd: the parent of sdd # subroutine(set_sdd_position_next(node sdd, node parentsdd), ( # Get item numitems-2 node items = parentsdd{"items"}; int numitems = num_subnodes(items); # If there's just one item (i.e., no items except sdd), there's nothing to put it below; put it at the upper left. # This means that you can just call set_sdd_bounds_next() for every item, and it will stack them down the page. if (numitems == 1) then ( set_sdd_position_upper_left(sdd, parentsdd); ); # If there's more than one item, put this below the last. else ( # Get item numitems-2. This is the second-to-last item, because we assume sdd is the last. node sdd2 = node_value(subnode_by_number(items, numitems - 2)); # If the next position is "down", put this one below the last one, at the left if ((@sdd2{"next_position"} eq "down") or (@sdd2{"next_position"} eq "")) then ( set_sdd_xmin(sdd, sddleft(parentsdd)); register_sdd_dependency(sdd, "xmin", parentsdd, "xmin"); set_sdd_ymax(sdd, sddbelowwrap(sdd, sdd2)); # 2010-09-16 - GMF - I'm not sure we need *all* of these, but I don't think it hurts, either. register_sdd_dependency(sdd, "ymax", sdd2, "ymax"); register_sdd_dependency(sdd, "ymax", sdd2, "height"); register_sdd_dependency(sdd, "ymax", sdd2, "xmin"); register_sdd_dependency(sdd, "ymax", sdd2, "width"); ); # if next position down # If the next position is "right", put this to the right of the last one, at the top else if (@sdd2{"next_position"} eq "right") then ( set_sdd_xmin(sdd, sddright(sdd2)); register_sdd_dependency(sdd, "xmin", sdd2, "xmin"); register_sdd_dependency(sdd, "xmin", sdd2, "width"); set_sdd_ymax(sdd, sddtop(sdd2)); register_sdd_dependency(sdd, "ymax", sdd2, "ymax"); ); # if next position down else error("Internal: unknown next_position '" . @sdd2{"next_position"} . "'"); ); )); # set_sdd_position_next() subroutine(set_sdd_bounds_next_text(node sdd, node parentsdd), ( set_sdd_position_next(sdd, parentsdd); set_sdd_dimensions_full_width_text(sdd, parentsdd); )); # set_sdd_bounds_next_text() # This creates a new page in an SDD document. subroutine(sdd_new_page(node sdd), ( # float actual_page_height = get_sdd_value(@sdd{"height"}); #echo("2actual_page_height: " . actual_page_height); # Create a page box, which surrounds the entire page. node page_box = new_box_sdd(sdd, sddfloat(0), sddfloat(v.page_height), sddfloat(v.page_width), sddfloat(v.page_height)); rename_node(page_box, "page_box"); remove_sdd_stroke_color(page_box); # Create a content box, which is within the page box, but with a 20-unit margin node content_box = new_box_sdd(page_box, 0, 0, 0, 0); rename_node(content_box, "content_box"); remove_sdd_stroke_color(content_box); set_sdd_bounds_offset(content_box, page_box, 20, 20, -20, -20); v.content_box = ""; set_node_type('v.content_box', 'node'); v.content_box = content_box; # set_sdd_height(content_box, sddgroupheight(content_box)); set_sdd_height(page_box, sddgroupheight(page_box)); set_sdd_height(sdd, sddgroupheight(sdd)); #echo("DUMP1"); #dump_sdd(content_box); )); # sdd_new_page() # # new_outline_marker_sdd() # # Purpose: This creates a new outline marker SDD object. This object inserts an item in the PDF outline when rendered. # # Parameters: parentsdd: the parent SDD of the new box, or 0 to leave it detached. # returns the new outline marker SDD # subroutine(new_outline_marker_sdd(node parentsdd, string label), ( node sdd = new_sdd(); @sdd{"type"} = "outline_marker"; @sdd{"label"} = label; add_subsdd(parentsdd, sdd); # Make this a 0-width, 0-height item. set_sdd_width(sdd, sddfloat(0)); set_sdd_height(sdd, sddfloat(0)); set_sdd_position_next(sdd, parentsdd); sdd; )); # new_outline_marker_sdd() # # new_page_break_sdd() # # Purpose: This creates a new page break SDD object. This object starts a new page when rendered. # # Parameters: parentsdd: the parent SDD of the new page break, or 0 to leave it detached. # returns the new page break SDD # subroutine(new_page_break_sdd(node parentsdd), ( node sdd = new_sdd(); @sdd{"type"} = "page_break"; add_subsdd(parentsdd, sdd); # Make this a 0-width, 0-height item. set_sdd_width(sdd, sddfloat(0)); set_sdd_height(sdd, sddfloat(0)); set_sdd_position_upper_left(sdd, v.content_box); sdd; )); # new_page_break_sdd()