## ## SDD VALUE OBJECTS ## #include("lib.sdd.table"); include("lib.sdd.table_cell"); include("lib.pdf"); include("lib.sdd.dependencies"); subroutine(dump_sdd(node sdd), ()); # # sddvalue_as_string() # # Purpose: This returns a string version of an SDD value. # # Parameters: val: the value # returns the human-readable string # subroutine(sddvalue_as_string(node val), ( "["; "(" . (val + 0) . ") "; if (@val{"type"} eq "constant") then @val; else if (@val{"type"} eq "sddleft") then "left of " . node_name(@val{"sdd"}) . " {" . (@val{"sdd"} + 0) . "}"; else if (@val{"type"} eq "sddright") then "right of " . node_name(@val{"sdd"}) . " {" . (@val{"sdd"} + 0) . "}"; else if (@val{"type"} eq "sddtop") then "top of " . node_name(@val{"sdd"}) . " {" . (@val{"sdd"} + 0) . "}"; else if (@val{"type"} eq "sddwidth") then "width of " . node_name(@val{"sdd"}) . " {" . (@val{"sdd"} + 0) . "}"; else if (@val{"type"} eq "sddwidth_autolayout") then "width of " . node_name(@val{"sdd"}) . " [on autolayout, group width of " . node_name(@val{"sdd_autolayout"}) . "] {" . (@val{"sdd"} + 0) . "}"; else if (@val{"type"} eq "sddgroupwidth") then "group width of " . node_name(@val{"sdd"}) . " {" . (@val{"sdd"} + 0) . "}"; else if (@val{"type"} eq "sddgroupheight") then "group height of " . node_name(@val{"sdd"}) . " {" . (@val{"sdd"} + 0) . "}"; else if (@val{"type"} eq "sddtablecolumnwidth") then "width of column " . @val{"column"} . " of table " . node_name(@val{"tbl"}) . " {" . (@val{"tbl"} + 0) . "}"; else if (@val{"type"} eq "sddtablerowheight") then "height of row " . @val{"row"} . " of table " . node_name(@val{"tbl"}) . " {" . (@val{"tbl"} + 0) . "}"; else if (@val{"type"} eq "sddheight") then "height of " . node_name(@val{"sdd"}) . " {" . (@val{"sdd"} + 0) . "}"; else if (@val{"type"} eq "sddtextwidth") then "text width of " . node_name(@val{"sdd"}) . " {" . (@val{"sdd"} + 0) . "}, constrained to wrap width specified in SDD " . @val{"wrap_width_sdd"} . " (" . (@val{"wrap_width_sdd"} + 0) . "): " . @val{"wrap_width_sdd"}{"wrap_width"} . ""; else if (@val{"type"} eq "sddtextheight") then "text height of " . node_name(@val{"sdd"}) . " {" . (@val{"sdd"} + 0) . "}"; else if (@val{"type"} eq "sddtablecolumnmaxwidth") then "max width of column " . @val{"column"} . " of " . node_name(@val{"tbl"}) . " {" . (@val{"tbl"} + 0) . "}"; else if (@val{"type"} eq "sddtablerowmaxheight") then "max height of row " . @val{"row"} . " of " . node_name(@val{"tbl"}) . " {" . (@val{"tbl"} + 0) . "}"; else if (@val{"type"} eq "subtract") then ( (sddvalue_as_string(@val{"value1"})) . " - " . (sddvalue_as_string(@val{"value2"})); ); else if (@val{"type"} eq "add") then ( (sddvalue_as_string(@val{"value1"})) . " + " . (sddvalue_as_string(@val{"value2"})); ); else "type=" . @val{"type"}; "]"; )); # sddvalue_as_string() subroutine(compute_table_column_width_outer(node tbl, int column), ( )); subroutine(compute_table_row_height_outer(node tbl, int row), ( )); subroutine(get_sdd_width(node val), ()); subroutine(compute_group_width_and_height(node sdd), ()); # # get_sdd_value() # # Purpose: This gets one SDD value. # # Parameters: v: the SDD value node. # subroutine(get_sdd_value(node val), ( if (val == 0) then ( 0; return; ); # echo("get_sdd_value; val=" . sddvalue_as_string(val) . "; type=" . @val{"type"}); if (!?"v.get_sdd_value_indent") then v.get_sdd_value_indent = ""; else v.get_sdd_value_indent .= " "; # If it's a constant, get the value directly from the node if (@val{"type"} eq "constant") then ( @val; v.get_sdd_value_indent = substr(v.get_sdd_value_indent, 2); return; ); # If it's cached, use the cached value else if (subnode_exists(val, "cached_value")) then ( #echo("Got cached sddvalue for " . sddvalue_as_string(val) . ":" . @val{"cached_value"}); @val{"cached_value"}; val.get_sdd_value_indent = substr(val.get_sdd_value_indent, 2); return; ); # If it's not cached, compute the value else ( # Set the value, once we compute it from the long "if" clause below bool cache = true; string cached_value = ( # Compute sddleft, the left side of an SDD object if (@val{"type"} eq "sddleft") then ( get_sdd_value(@(@val{"sdd"}){"xmin"}); ); # Compute sddright, the right side of an SDD object else if (@val{"type"} eq "sddright") then get_sdd_value(@(@val{"sdd"}){"xmin"}) + get_sdd_value(@(@val{"sdd"}){"width"}); # Compute sddbottom, the bottom side of an SDD object else if (@val{"type"} eq "sddbottom") then get_sdd_value(@(@val{"sdd"}){"ymax"}) - get_sdd_value(@(@val{"sdd"}){"height"}); # Compute sddtop, the top side of an SDD object else if (@val{"type"} eq "sddtop") then ( node sdd = @val{"sdd"}; node ymax = @sdd{"ymax"}; get_sdd_value(ymax); ); else if (@val{"type"} eq "sddwidth_autolayout") then ( #echo("Getting sdd width AUTOLAYOUT"); #echo("val=" . node_as_string(val)); #echo('val?{"performing_autolayout"}: ' . val?{"performing_autolayout"}); #echo("val?{performed_autolayout}: " . val?{"performed_autolayout"}); # if (!val?{"performed_autolayout"} and !((val?{"performing_autolayout"} and @val{"performing_autolayout"}))) then ( #echo("Performing autolayout"); # @val{"performed_autolayout"} = true; # During autolayout, use 0 as width of every SDD; otherwise we create a cycle if there's a in a [ThreadID:1017398] if ((('v'?{'autolayout'}) and v.autolayout) or (val?{"performing_autolayout"} and @val{"performing_autolayout"})) then ( #echo("sddwidth_autolayout with group width"); # Remember that we're doing autolayout on this value now; if we're asked for the width again, just return sddwidth so we don't have a cycle. @val{"performing_autolayout"} = true; ## DEBUG: plugged sddgroupwidth code in here node sdd = @val{"sdd_autolayout"}; #echo("Using SDDGROUPWIDTH on sdd:" . sdd); node saved_xmin = @sdd{"xmin"}; @sdd{"xmin"} = 0; node subsddp; node items = sdd{"items"}; float groupwidth = 0; if (num_subnodes(items) == 0) then 0; else ( float xmin = 99999999; float xmax = -99999999; foreach subsddp items ( node subsdd = @subsddp; #echo("subsdd: " . subsdd); float sub_xmin = get_sdd_value(@subsdd{"xmin"}); float sub_xmax = sub_xmin + get_sdd_value(@subsdd{"width"}); if (sub_xmin < xmin) then xmin = sub_xmin; if (sub_xmax > xmax) then xmax = sub_xmax; ); groupwidth = xmax - xmin; ); #echo("groupwidth: " . groupwidth); #echo("announcing xmin change for " . sdd); @sdd{"xmin"} = saved_xmin; announce_sdd_parameter_change(sdd, "xmin"); @val{"performing_autolayout"} = false; # @val{"performed_autolayout"} = true; # 0; groupwidth; ); # if autolayout else ( #echo("sddwidth_autolayout with get_sdd_width()"); get_sdd_width(val); ); # If not autolayout ); # Compute sddwidth, the width of an SDD object else if (@val{"type"} eq "sddwidth") then ( #echo("Calling get_sdd_width()"); get_sdd_width(val); ); # if sddwidth # Compute sddparentwidth, the width of an SDD object's parent else if (@val{"type"} eq "sddparentwidth") then ( node sdd = @val{"sdd"}; node parent = node_parent(node_parent(sdd)); get_sdd_value(@parent{"width"}); ); # Compute sddheight, the height of an SDD object else if (@val{"type"} eq "sddheight") then ( node sdd = @val{"sdd"}; float height = get_sdd_value(@(@val{"sdd"}){"height"}); if (sdd?{"scaling_factor"}) then ( height = height * @sdd{"scaling_factor"}; ); height; ); # Compute "add", the sum of two SDD values else if (@val{"type"} eq "add") then ( node value1 = @val{"value1"}; node value2 = @val{"value2"}; get_sdd_value(value1) + get_sdd_value(value2); ); # Compute "subtract", the difference of two SDD values else if (@val{"type"} eq "subtract") then ( node value1 = @val{"value1"}; node value2 = @val{"value2"}; get_sdd_value(value1) - get_sdd_value(value2); ); # Compute "divide", the quotient of two SDD values else if (@val{"type"} eq "divide") then ( node value1 = @val{"value1"}; node value2 = @val{"value2"}; get_sdd_value(value1) / get_sdd_value(value2); ); # Compute sddbelowwrap, which puts an SDD below another, or wraps it to another page if it doesn't fit. else if (@val{"type"} eq "sddbelowwrap") then ( node sdd = @val{"sdd"}; node below_sdd = @val{"below_sdd"}; float below_sddymax = get_sdd_value(@below_sdd{"ymax"}); float below_sddymin = below_sddymax - get_sdd_value(@below_sdd{"height"}); float content_box_ymax = get_sdd_value(@(v.content_box){"ymax"}); node saved_ymax = @sdd{"ymax"}; @sdd{"ymax"} = 0; float sdd_height = get_sdd_value(@sdd{"height"}); float sdd_width = get_sdd_value(@sdd{"width"}); float content_box_height = get_sdd_value(@(v.content_box){"height"}); float content_box_width = get_sdd_value(@(v.content_box){"width"}); float content_box_ymin = get_sdd_value(@(v.content_box){"ymax"}) - content_box_height; float sdd_ymin = below_sddymin - sdd_height; # Compute the amount of space available below below_sdd_ymin; float remaining_content_box_height = below_sddymin - content_box_ymin; # If we require scaling to fit this horizontally, compute how much. float scaling_factor_for_horizontal_fit = 1.0; if (sdd_width > content_box_width) then ( scaling_factor_for_horizontal_fit = content_box_width / sdd_width; ); # Compute how heigh it will be if we scale it horizontally float hscaled_sdd_height = sdd_height * scaling_factor_for_horizontal_fit; # If sdd fits in the content box, put it below below_sdd; # 2007-08-01 - GMF - Added v.autolayout check. Without this, it will try to wrap to a new page while # doing table layout. During table layout, just let it get as big as it wants. # 2007-08-01 - GMF - Nope, that doesn't quite work, because this gets called *after* autolayout too. # Switching to look for sdd_ymin < 0, a sign that we are doing 0-based relative layout. # It might be best to switch to a separate pagination step.... if ((sdd_ymin < 0) or (hscaled_sdd_height < remaining_content_box_height)) then ( # Scale horizontally if necessary if (scaling_factor_for_horizontal_fit != 1.0) then ( #echo("horizontal scale: " . scaling_factor_for_horizontal_fit); @sdd{"scaling_factor"} = scaling_factor_for_horizontal_fit; ); below_sddymin; ); # if fits # If it doesn't fit in the content box after hscaling.... else ( # Create a new page, unless we're already at the top of the page. if (below_sddymin < content_box_ymax) then ( v.page = new_page(v.pdf, 0, 0, v.page_width, v.page_height); v.drew_something_on_this_page = false; ); # Put this at the top of the new page get_sdd_value(@(v.content_box){"ymax"}); # If it still doesn't fit, even alone on the page, scale it so it does float scaling_factor_for_vertical_fit = 1.0; if (hscaled_sdd_height > content_box_height) then ( scaling_factor_for_vertical_fit = content_box_height / sdd_height; ); # We now have a scaling factor required for it to fit horizontally, and one required for # it to fit vertically; choose the greater of the two. if (scaling_factor_for_vertical_fit < scaling_factor_for_horizontal_fit) then ( @sdd{"scaling_factor"} = scaling_factor_for_vertical_fit; ); else ( @sdd{"scaling_factor"} = scaling_factor_for_horizontal_fit; ); ); # if doesn't fit vertically @sdd{"ymax"} = saved_ymax; announce_sdd_parameter_change(sdd, "ymax"); ); # sddbelowwrap # Compute sddtextwidth, the natural width of a "text" SDD object. else if (@val{"type"} eq "sddtextwidth") then ( node sdd = @val{"sdd"}; string text = @sdd{"text"}; node font = @sdd{"font"}; float font_size = @sdd{"font_size"}; node wrap_width_sdd = @val{"wrap_width_sdd"}; # If the wrap_width is "sdd_width", it means to wrap to the wrap_width SDD (typically, the surrounding SDD). Otherwise, it is exactly the width to wrap to. float wrap_width; if (@wrap_width_sdd{"wrap_width"} eq "sdd_width") then ( #echo("(('v'?{'autolayout'}) and v.autolayout): " . (('v'?{'autolayout'}) and v.autolayout)); # If we're doing table layout, disable wrapping, because otherwise this creates a cycle, as the table layout enging looks to the text width to determine # the column width, and the text looks to the column width to determine wrapping. if (('v'?{'autolayout'}) and v.autolayout) then wrap_width = 999999; else ( # Re-enabling wrap_width, because otherwise a page containing a simple

section which should wrap, doesn't wrap. wrap_width = get_sdd_value(@(@val{"wrap_width_sdd"}){"width"}); ); # if not autolayout ); # if wrap_width eq sdd_width else wrap_width = @wrap_width_sdd{"wrap_width"}; bool wrap = true; float width = draw_wrapped_text(0, font, font_size, 0, 0, wrap_width, text, "left", false, wrap, true, false); #echo("width: " . width); width; ); # Compute "sddtextheight", the natural height of a (possibly wrapped) "text" SDD object else if (@val{"type"} eq "sddtextheight") then ( node sdd = @val{"sdd"}; node font = @sdd{"font"}; float font_size = @sdd{"font_size"}; bool wrap = @sdd{"wrap"}; # Run draw_wrapped_text() without drawing, to compute how high it should be float width = get_sdd_value(@(@val{"sdd"}){"width"}); string text = @sdd{"text"}; float height = draw_wrapped_text(0, font, font_size, 0, 0, width, text, "left", false, wrap, false, false); height; ); # sddtextheight # Compute "groupheight", the height of the entire group. else if (@val{"type"} eq "sddgroupheight") then ( node sdd = @val{"sdd"}; node saved_ymax = @sdd{"ymax"}; @sdd{"ymax"} = 0; node subsddp; node items = sdd{"items"}; float groupheight = 0; if (num_subnodes(items) == 0) then 0; else ( float ymin = 99999999; float ymax = -99999999; foreach subsddp items ( node subsdd = @subsddp; float sub_ymax = get_sdd_value(@subsdd{"ymax"}); float sub_ymin = sub_ymax - get_sdd_value(@subsdd{"height"}); if (sub_ymin < ymin) then ymin = sub_ymin; if (sub_ymax > ymax) then ymax = sub_ymax; ); groupheight = ymax - ymin; ); @sdd{"ymax"} = saved_ymax; announce_sdd_parameter_change(sdd, "ymax"); groupheight; ); # sddgroupheight # Compute "sddtablecolumnwidth", the width of a column of a table (as computed by the table layout engine) else if (@val{"type"} eq "sddtablecolumnwidth") then ( compute_table_column_width_outer(@val{"tbl"}, @val{"column"}); ); # sddtablecolumnwidth # Compute "sddtablerowheight", the height of a row of a table (as computed by the table layout engine) else if (@val{"type"} eq "sddtablerowheight") then ( node sdd = @val{"tbl"}; float tablerowheight = compute_table_row_height_outer(@val{"tbl"}, @val{"row"}); tablerowheight; ); # sddtablerowheight # Compute "sddgroupwidth", the width of the entire group. else if (@val{"type"} eq "sddgroupwidth") then ( node sdd = @val{"sdd"}; node saved_xmin = @sdd{"xmin"}; @sdd{"xmin"} = 0; node subsddp; node items = sdd{"items"}; float groupwidth = 0; if (num_subnodes(items) == 0) then 0; else ( float xmin = 99999999; float xmax = -99999999; foreach subsddp items ( node subsdd = @subsddp; float sub_xmin = get_sdd_value(@subsdd{"xmin"}); float sub_xmax = sub_xmin + get_sdd_value(@subsdd{"width"}); if (sub_xmin < xmin) then xmin = sub_xmin; if (sub_xmax > xmax) then xmax = sub_xmax; ); groupwidth = xmax - xmin; ); @sdd{"xmin"} = saved_xmin; announce_sdd_parameter_change(sdd, "xmin"); groupwidth; ); # sddgroupwidth # Compute "sddtablerowmaxheight", the maximum height of a table row else if (@val{"type"} eq "sddtablerowmaxheight") then ( cache = false; compute_table_row_height_outer(@val{"tbl"}, @val{"row"}); ); # sddtablerowmaxheight # Compute "sddtablecolumnmaxwidth", the maximum width of a table column else if (@val{"type"} eq "sddtablecolumnmaxwidth") then ( cache = false; compute_table_column_width_outer(@val{"tbl"}, @val{"column"}); ); # sddtablecolumnmaxwidth else ( error("Unknown SDD value type: \"" . @val{"type"} . "\""); ); ); # end of "if" computing cached_value #echo("Done computing sddvalue for " . sddvalue_as_string(val) . "; cached_value=" . cached_value); if (cache) then @val{"cached_value"} = cached_value; cached_value; ); # if not cached v.get_sdd_value_indent = substr(v.get_sdd_value_indent, 2); )); # get_sdd_value() subroutine(get_sdd_width(node val), ( #echo("get_sdd_width(); val=" . val); node sdd = @val{"sdd"}; if ((node_name(sdd) eq "cell_contents") and (sdd?{"fixed_width"})) then ( @sdd{"fixed_width"}; ); else ( get_sdd_value(@sdd{"width"}); ); )); # get_sdd_width() # # compute_table_cell_width_outer() # # Purpose: this computes the outer width of a table cell. # # Parameter: tbl: the table # column: the column number # returns the outer width of the column; sets v.cell_height to the height of the column # subroutine(compute_table_cell_width_outer(node tbl, int row, int column), ( float cell_width_outer; node innercell = sdd_get_table_innercell(tbl, row, column); node outercell = sdd_get_table_outercell(tbl, row, column); ## ## GET THE CELL WIDTH ## if (@(@outercell{"width"}){"type"} eq "constant") then ( cell_width_outer = @(@outercell{"width"}); node xmin_saved = @outercell{"xmin"}; node ymax_saved = @outercell{"ymax"}; @outercell{"xmin"} = 0; @outercell{"ymax"} = 0; v.cell_height = get_sdd_value(@innercell{"height"}) + @outercell{"top_padding"} + @outercell{"bottom_padding"}; @outercell{"xmin"} = xmin_saved; @outercell{"ymax"} = ymax_saved; announce_sdd_parameter_change(outercell, "xmin"); announce_sdd_parameter_change(outercell, "ymax"); ); else if (v.layout_width_mode eq "computed_by_auto_layout") then ( cell_width_outer = @tbl{"auto_layout_column_widths_outer"}{column}; v.cell_height_outer = @tbl{"auto_layout_row_heights_outer"}{row}; ); # If not constant else ( node cell_contents = sdd_get_table_cell_contents(tbl, row, column); cell_width_outer = get_sdd_value(@cell_contents{"width"}) + @outercell{"left_padding"} + @outercell{"right_padding"}; v.cell_height = get_sdd_value(@cell_contents{"height"}) + @outercell{"top_padding"} + @outercell{"bottom_padding"}; ); # if not constant cell_width_outer; )); # compute_table_cell_width() subroutine(set_sdd_width(node sdd, node newwidth), ()); subroutine(set_sdd_height(node sdd, node newheight), ()); subroutine(sddfloat(float f), ()); # # compute_table_column_minimum_width_outer() # # Purpose: This computes the minimun outer width of a table column # # Parameters: tbl: the table # column: the column number # returns the minimum outer width for that column # subroutine(compute_table_column_minimum_width_outer(node tbl, int column), ( # Loop through all rows in this column, finding the largest minimum. int rows = @tbl{"rows"}; int row; int column_minimum_width_outer = 0; int padding = 0; int margin = 0; for (row = 0; row < rows; row++) ( # Compute the padding on the cell (for use after the loop exits) node outercell = sdd_get_table_outercell(tbl, row, column); padding = @outercell{"left_padding"} + @outercell{"right_padding"}; margin = @outercell{"left_margin"} + @outercell{"right_margin"}; node cell_contents = sdd_get_table_cell_contents(tbl, row, column); # Sum the minimum width of each item, to get the minimum width of the cell node items = cell_contents{"items"}; node subsddptr; node subsdd; float subsdd_width_inner; float subsdd_width_outer; float cell_minimum_width_outer = 0; foreach subsddptr items ( subsdd = @subsddptr; # If it's text, get the minimum width the text can wrap to if (@subsdd{"type"} eq "text") then ( string text = @subsdd{"text"}; node font = @subsdd{"font"}; float font_size = @subsdd{"font_size"}; subsdd_width_inner = draw_wrapped_text(0, font, font_size, 0, 0, 1, text, "left", false, true, true, true); subsdd_width_outer = subsdd_width_inner + padding + margin; ); else ( subsdd_width_outer = get_sdd_value(@subsdd{"width"}); ); cell_minimum_width_outer += subsdd_width_outer; ); # foreach subsddptr if (cell_minimum_width_outer > column_minimum_width_outer) then column_minimum_width_outer = cell_minimum_width_outer; ); # for row column_minimum_width_outer; )); # compute_table_column_minimum_width_outer() # # compute_table_cells_ideal_sizes() # # Purpose: This computes the ideal sizes of all table cells in a table # # Parameters: tbl: the table # subroutine(compute_table_cells_ideal_sizes(node tbl), ( #echo("compute_table_cells_ideal_sizes()"); # If we haven't computed ideal sizes yet for this table, do it now if (!@tbl{"computed_ideal_sizes"}) then ( # Iterate through all rows int rows = @tbl{"rows"}; int columns = @tbl{"columns"}; int row; int column; for (row = 0; row < rows; row++) ( for (int column = 0; column < columns; column++) ( node cell_contents = sdd_get_table_cell_contents(tbl, row, column); #echo("row=" . row . ", column=" . column); # Compute the total width and height of this group float cell_width_inner = compute_group_width_and_height(cell_contents); float cell_height_inner = v.compute_group_width_height; # Adjust the total width for spacing and padding node outercell = sdd_get_table_outercell(tbl, row, column); float cell_width_outer = cell_width_inner + @outercell{"left_margin"} + @outercell{"right_margin"} + @outercell{"left_padding"} + @outercell{"right_padding"}; float cell_height_outer = cell_height_inner + @outercell{"top_margin"} + @outercell{"bottom_margin"} + @outercell{"top_padding"} + @outercell{"bottom_padding"}; # Save the computed width and height @tbl{"computed_ideal_sizes_inner"}{row}{column}{"width"} = cell_width_inner; @tbl{"computed_ideal_sizes_inner"}{row}{column}{"height"} = cell_height_inner; @tbl{"computed_ideal_sizes_outer"}{row}{column}{"width"} = cell_width_outer; @tbl{"computed_ideal_sizes_outer"}{row}{column}{"height"} = cell_height_outer; ); # for columns ); # for row # Remember that we have computed the ideal sizes. @tbl{"computed_ideal_sizes"} = true; ); # if not computed ideal sizes #echo("Computed ideal sizes"); )); # compute_table_cells_ideal_sizes() # # compute_table_cells_final_sizes() # # Purpose: This computes the final sizes of all table cells in a table, based on the auto_layout_column_widths # # Parameters: tbl: the table # subroutine(compute_table_cells_final_sizes(node tbl), ( # If we haven't computed final sizes yet for this table, do it now if (!@tbl{"computed_final_sizes"}) then ( # Iterate through all rows int rows = @tbl{"rows"}; int columns = @tbl{"columns"}; int row; int column; for (row = 0; row < rows; row++) ( for (int column = 0; column < columns; column++) ( node cell_contents = sdd_get_table_cell_contents(tbl, row, column); node outercell = sdd_get_table_outercell(tbl, row, column); node innercell = sdd_get_table_innercell(tbl, row, column); float horizontal_padding_and_margin = @outercell{"left_margin"} + @outercell{"right_margin"} + @outercell{"left_padding"} + @outercell{"right_padding"}; float vertical_padding_and_margin = @outercell{"top_margin"} + @outercell{"bottom_margin"} + @outercell{"top_padding"} + @outercell{"bottom_padding"}; # Temporary force the width to the specified value node saved_width = @cell_contents{"width"}; float cell_width_outer = @tbl{"auto_layout_column_widths_outer"}{column}; float cell_width_inner = cell_width_outer - horizontal_padding_and_margin; set_sdd_width(cell_contents, sddfloat(cell_width_inner)); @cell_contents{"wrap_width"} = cell_width_inner; # We didn't really change "width", but we changed the wrap_width parameter, and anything that uses sddtextwidth will need to be updated. announce_sdd_parameter_change(cell_contents, "width"); # Compute the inner dimensions of this cell, given that width float cell_width_inner = compute_group_width_and_height(cell_contents); float cell_height_inner = v.compute_group_width_height; # Save the computed content (innercell) width and height @tbl{"computed_final_sizes_inner"}{row}{column}{"width"} = cell_width_inner; @tbl{"computed_final_sizes_inner"}{row}{column}{"height"} = cell_height_inner; # Adjust the total width for spacing and padding -- the sizes we computed above are the content sizes, but we're saving the outercell sizes. float cell_width_outer = cell_width_inner + horizontal_padding_and_margin; float cell_height_outer = cell_height_inner + vertical_padding_and_margin; # Save the computed outercell width and height @tbl{"computed_final_sizes_outer"}{row}{column}{"width"} = cell_width_outer; @tbl{"computed_final_sizes_outer"}{row}{column}{"height"} = cell_height_outer; ); # for columns ); # for row # Remember that we have computed the final sizes. @tbl{"computed_final_sizes"} = true; ); # if not computed final sizes )); # compute_table_cells_final_sizes() # # compute_table_column_ideal_width_outer() # # Purpose: This computes the ideal width of a table column # # Parameters: tbl: the table # column: the column number # returns the ideal outer width for that column # subroutine(compute_table_column_ideal_width_outer(node tbl, int column), ( # Make sure we have all the ideal cell sizes compute_table_cells_ideal_sizes(tbl); int rows = @tbl{"rows"}; int row; float column_width_outer = 0; for (row = 0; row < rows; row++) ( float cell_width_outer = @tbl{"computed_ideal_sizes_outer"}{row}{column}{"width"}; if (cell_width_outer > column_width_outer) then column_width_outer = cell_width_outer; ); # for row column_width_outer; )); # compute_table_column_ideal_width_outer() # # compute_table_column_ideal_width_inner() # # Purpose: This computes the ideal width of a table column # # Parameters: tbl: the table # column: the column number # returns the ideal inner width for that column # subroutine(compute_table_column_ideal_width_inner(node tbl, int column), ( # Make sure we have all the ideal cell sizes compute_table_cells_ideal_sizes(tbl); int rows = @tbl{"rows"}; int row; float column_width_inner = 0; for (row = 0; row < rows; row++) ( float cell_width_inner = @tbl{"computed_ideal_sizes_inner"}{row}{column}{"width"}; if (cell_width_inner > column_width_inner) then column_width_inner = cell_width_inner; ); # for row column_width_inner; )); # compute_table_column_ideal_width_inner() subroutine(force_table_cell_width_outer(node tbl, int row, int column, float outerwidth), ()); subroutine(compute_table_cell_height_from_width(node tbl, int row, int column, float column_width), ()); subroutine(auto_layout(node sdd), ( #echo("auto_layout(); sdd=" . sdd . "\n"); # We are now doing autolayout. v.autolayout = true; int width = get_sdd_value(@sdd{"width"}); # echo("width: " . width); # We are done with autolayout v.autolayout = false; )); # auto_layout # Automatically layout a table. subroutine(auto_layout_table(node tbl), ( #echo("auto_layout_table()"); # if (('v'?{'autolayout'}) and v.autolayout) then # error("Internal: PDF generation loop; auto_layout_table() recursively called itself"); if (@tbl{"running_autolayout"}) then ( error("Internal: PDF generation loop; auto_layout_table() recursively called itself"); ); @tbl{"running_autolayout"} = true; # Remember whether we were doing autolayout before we started this (e.g., for a surrounding table). bool saved_autolayout = false; if (node_exists('v.autolayout')) then saved_autolayout = v.autolayout; # Get the width of the page, and use that as the maximum width of the table float page_width = v.page_width - (v.page_left_margin + v.page_right_margin); float maximum_table_width = page_width; # If this float minimum_table_width = 0; if (@tbl{"width_layout_type"} eq "100%") then ( minimum_table_width = maximum_table_width; ); node marginbox = @tbl{"marginbox"}; # If this is a 100% width table, use the width of the container, as the width of the table if (maximum_table_width == minimum_table_width) then ( maximum_table_width = get_sdd_value(@marginbox{"width"}); ); # We are now doing autolayout. v.autolayout = true; ## ## TRY IDEAL WIDTH ## # Try to layout using ideal widths. int columns = @tbl{"columns"}; int rows = @tbl{"rows"}; float ideal_table_width = 0; v.layout_width_mode = "ideal"; for (int column = 0; column < columns; column++) ( #echo("getting ideal outer width"); float this_column_ideal_width_outer = compute_table_column_ideal_width_outer(tbl, column); #echo("getting ideal inner width"); float this_column_ideal_width_inner = compute_table_column_ideal_width_inner(tbl, column); ideal_table_width += this_column_ideal_width_outer; @tbl{"auto_layout_column_widths_inner"}{column} = this_column_ideal_width_inner; @tbl{"auto_layout_column_widths_outer"}{column} = this_column_ideal_width_outer; ); ## ## IF IDEAL DOESN'T FIT, TRY MINIMUM WIDTH, AND THEN EXPAND IT TO FULL WIDTH ## # If ideal widths don't work, shink them proportionally. # Lots of assumptions for now; default page size; assuming left-aligned table. if ((ideal_table_width > maximum_table_width) or (ideal_table_width < minimum_table_width)) then ( # Compute the minimum width of each cell v.layout_width_mode = "minimum"; delete_node(tbl{"ideal_width_cache"}); float total_characters_seen = 0; float minimum_column_width_outer_sum = 0; for (int column = 0; column < columns; column++) ( v.characters_seen = 0; float this_column_minimum_width_outer = compute_table_column_minimum_width_outer(tbl, column); total_characters_seen += v.characters_seen; minimum_column_width_outer_sum += this_column_minimum_width_outer; @tbl{"auto_layout_minimum_column_widths_outer"}{column} = this_column_minimum_width_outer; @tbl{"auto_layout_characters"}{column} = v.characters_seen; ); # for column # Choose all minimum values for now. float remaining_width = maximum_table_width - minimum_column_width_outer_sum; if (remaining_width < 0) then remaining_width = 0; # Compute the column widths v.layout_width_mode = "computed_by_auto_layout"; for (int column = 0; column < columns; column++) ( float extra_width = ((remaining_width) * (@tbl{"auto_layout_characters"}{column} / total_characters_seen)); if (extra_width < 0) then extra_width = 0; float column_width_outer = @tbl{"auto_layout_minimum_column_widths_outer"}{column} + extra_width; @tbl{"auto_layout_column_widths_outer"}{column} = column_width_outer; ); # for column for (int row = 0; row < rows; row++) ( for (int column = 0; column < columns; column++) ( # If this cell is a colspan cell, add the widths of all its additional columns to compute the full width. node outercell = sdd_get_table_outercell(tbl, row, column); # Get the colspan int colspan; if (outercell?{"colspan"}) then colspan = @outercell{"colspan"}; else colspan = 1; # Compute the column width of the colspan column_width_outer = 0; for (int i = column; i < column + colspan; i++) ( column_width_outer += @tbl{"auto_layout_minimum_column_widths_outer"}{i}; ); # for i # Skip over this colpan to the next column column += colspan; ); # for column ); # for row ## ## Now we've set the table to minimum size. Expand expandable columns to fill all available space ## float column_widths_outer_sum = 0; int number_of_expandable_columns = 0; float sum_of_expandable_column_widths_outer = 0; float sum_of_constant_column_widths_outer = 0; for (int column = 0; column < columns; column++) ( int column_width_outer = @tbl{"auto_layout_column_widths_outer"}{column}; column_widths_outer_sum += column_width_outer; # Check if this column is expandable (non-constant); count the number of columns that are. node outercell = sdd_get_table_outercell(tbl, 0, column); if (@(@outercell{"width"}){"type"} ne "constant") then ( number_of_expandable_columns++; sum_of_expandable_column_widths_outer += column_width_outer; ); else ( sum_of_constant_column_widths_outer += column_width_outer; ); ); # for column # Make the table as wide as it can be float table_width = maximum_table_width; # If the auto-layout width of the entire table is less than the specified table width, expand all # columns proportionally to make the width right. if (table_width > column_widths_outer_sum) then ( float table_width_without_constant_columns = column_widths_outer_sum; float required_table_width_without_constant_columns = table_width - sum_of_constant_column_widths_outer; float ratio = required_table_width_without_constant_columns / sum_of_expandable_column_widths_outer; for (int column = 0; column < columns; column++) ( # If this is an expandable (non-constant) column, expand it by the ratio node outercell = sdd_get_table_outercell(tbl, 0, column); if (@(@outercell{"width"}){"type"} ne "constant") then ( int column_width_outer = @tbl{"auto_layout_column_widths_outer"}{column}; column_width_outer = column_width_outer * ratio; @tbl{"auto_layout_column_widths_outer"}{column} = column_width_outer; # Fix the table cell widths at the values specified for (int row = 0; row < rows; row++) force_table_cell_width_outer(tbl, row, column, column_width_outer); ); # if expandable ); # for columns ); # if table_width > column_widths_outer_sum # Finally, force the table itself to be the width we rendered it to set_sdd_width(tbl, sddfloat(table_width)); ); # if ideal width < table width # If ideal widths do work, use that as the table width and height else ( set_sdd_width(tbl, sddfloat(ideal_table_width)); set_sdd_width(marginbox, sddfloat(ideal_table_width)); ); # if ideal width works ## ## COMPUTE ROWS HEIGHTS ## # Compute the final sizes of all cells compute_table_cells_final_sizes(tbl); # Now that we've decided the column widths, compute the row heights from them. for (int row = 0; row < rows; row++) ( float maximum_row_height_outer = 0; for (int column = 0; column < columns; column++) ( # Get the row height from the computed_final_sizes float this_row_height_outer = @tbl{"computed_final_sizes_outer"}{row}{column}{"height"}; if (this_row_height_outer > maximum_row_height_outer) then maximum_row_height_outer = this_row_height_outer; ); # column @tbl{"auto_layout_row_heights_outer"}{row} = maximum_row_height_outer; ); # for row # We are now back to the previous autolayout state v.autolayout = saved_autolayout; # We have now completed autolayout for this table @tbl{"auto_layout_complete"} = true; # Get the left side of the table float table_xmin = get_sdd_value(@tbl{"xmin"}); # Subtract the table xmin from the maximum width. Because of problems with fancy cycles that I # don't want to try to resolve now (but briefly: the table container's width is based on the # table width, so we can't compute the container width right now, so we can't scale ourselves # to fit the container), we can't use the container width; so fake it by making sure the table # doesn't hang off the right of the page. This is good enough for Sawmill's purposes for now. maximum_table_width -= table_xmin; # If the table width is greater than the contents can hold, shrink it to fit float table_width = get_sdd_value(@tbl{"width"}); node contents = tbl{"items"}{"contents"}; float contents_width = get_sdd_value(@(@contents){"width"}); if (contents_width > maximum_table_width) then ( float scaling_factor = (maximum_table_width) / contents_width; @tbl{"scaling_factor"} = scaling_factor; #echo("table scaling factor: " . scaling_factor); ); )); # auto_layout_table() # # compute_table_row_height_outer() # # Purpose: This returns the outer height of a table row # # Parameters: tbl: the table # row: row row number # returns the outer height of the row # subroutine(compute_table_row_height_outer(node tbl, int row), ( # If this is auto width layout, compute the minimum width, for now. float row_height_outer = 0; if (!(tbl?{"auto_layout_complete"})) then ( auto_layout_table(tbl); ); row_height_outer = @tbl{"auto_layout_row_heights_outer"}{row}; row_height_outer; )); # compute_table_row_height_outer() # # compute_table_column_width_outer() # # Purpose: This returns the outer width of a table column # # Parameters: tbl: the table # column: the column number # returns the the width of the column # subroutine(compute_table_column_width_outer(node tbl, int column), ( # If this is auto width layout, compute the outer width float column_width_outer = 0; if (!(tbl?{"auto_layout_complete"})) then ( auto_layout_table(tbl); ); column_width_outer = @tbl{"auto_layout_column_widths_outer"}{column}; column_width_outer; )); # compute_table_column_width_outer() ## ## SDD VALUE CONSTRUCTORS ## # This creates a constant SDD string value object subroutine(new_sdd_constant_value(string value), ( node val = new_node(); @val{"type"} = "constant"; @val = value; val; )); # new_sdd_constant_value() # This creates a constant SDD float value object subroutine(sddfloat(float f), ( new_sdd_constant_value(f); )); # sddfloat # This creates an SDD value object which computes the left (xmin) of an SDD object. subroutine(sddleft(node sdd), ( node val = new_node(); @val{"type"} = "sddleft"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddfloat # This creates an SDD value object which computes the right (xmax) of an SDD object. subroutine(sddright(node sdd), ( node val = new_node(); @val{"type"} = "sddright"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddfloat # This creates an SDD value object which computes the bottom (ymin) of an SDD object. subroutine(sddbottom(node sdd), ( node val = new_node(); @val{"type"} = "sddbottom"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddbottom() # This creates an SDD value object which computes the top (ymax) of an SDD object. subroutine(sddtop(node sdd), ( node val = new_node(); @val{"type"} = "sddtop"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddtop() # This creates an SDD value object which computes the width of an SDD object. subroutine(sddwidth(node sdd), ( node val = new_node(); @val{"type"} = "sddwidth"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddwidth() # This creates an SDD value object which normally computes the width of an SDD object, # but during autolayout computes the width as the group width of itself. subroutine(sddwidth_autolayout(node sdd, node sdd_autolayout), ( node val = new_node(); @val{"type"} = "sddwidth_autolayout"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; set_node_type(val{"sdd_autolayout"}, "node"); @val{"sdd_autolayout"} = sdd_autolayout; val; )); # sddwidth_autolayout() # This creates an SDD value object which computes the width of an SDD object's parent. subroutine(sddparentwidth(node sdd), ( node val = new_node(); @val{"type"} = "sddparentwidth"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddparentwidth() # This creates an SDD value object which computes the xmin needed to right-align sdd within outersdd subroutine(sddrightalign(node sdd, node outersdd), ( node val = new_node(); @val{"type"} = "subtract"; set_node_type(val{"value1"}, "node"); @val{"value1"} = sddright(outersdd); set_node_type(val{"value2"}, "node"); @val{"value2"} = sddwidth(sdd); val; )); # sddrightalign # This creates an SDD value object which computes the xmin needed to right-align sdd within outersdd, with a fixed padding at the right subroutine(sddrightalignpadding(node sdd, node outersdd, float padding), ( node val = new_node(); @val{"type"} = "subtract"; set_node_type(val{"value1"}, "node"); @val{"value1"} = sddrightalign(sdd, outersdd); set_node_type(val{"value2"}, "node"); @val{"value2"} = sddfloat(padding); val; )); # sddrightalign # This creates an SDD value object which computes the xmin needed to left-align sdd within outersdd, with a fixed padding at the right subroutine(sddleftalignpadding(node outersdd, float padding), ( node val = new_node(); @val{"type"} = "add"; set_node_type(val{"value1"}, "node"); @val{"value1"} = sddleft(outersdd); set_node_type(val{"value2"}, "node"); @val{"value2"} = sddfloat(padding); val; )); # sddleftalignpadding # This returns an sdd value which adds two other sdd values. subroutine(sddadd(node sddvalue1, node sddvalue2), ( node val = new_node(); @val{"type"} = "add"; set_node_type(val{"value1"}, "node"); @val{"value1"} = sddvalue1; set_node_type(val{"value2"}, "node"); @val{"value2"} = sddvalue2; val; )); # sddadd() # This returns an sdd value which subtracts two other sdd values. subroutine(sddsubtract(node sddvalue1, node sddvalue2), ( node val = new_node(); @val{"type"} = "subtract"; set_node_type(val{"value1"}, "node"); @val{"value1"} = sddvalue1; set_node_type(val{"value2"}, "node"); @val{"value2"} = sddvalue2; val; )); # sddsubtract() # This returns an sdd value which divides two other sdd values. subroutine(sdddivide(node sddvalue1, node sddvalue2), ( node val = new_node(); @val{"type"} = "divide"; set_node_type(val{"value1"}, "node"); @val{"value1"} = sddvalue1; set_node_type(val{"value2"}, "node"); @val{"value2"} = sddvalue2; val; )); # sdddivide() # This creates an SDD value object which computes the xmin needed to center sdd within outersdd, with a fixed padding on both sides subroutine(sdd_horiz_center_align(node sdd, node outersdd), ( node outerleft = sddleft(outersdd); node outerwidth = sddwidth(outersdd); node innerwidth = sddwidth(sdd); node width_difference = sddsubtract(outerwidth, innerwidth); node half_width_difference = sdddivide(width_difference, sddfloat(2)); node xmin = sddadd(outerleft, half_width_difference); xmin; )); # sdd_horiz_center_align_padding() # This creates an SDD value object which computes the height of an SDD object. subroutine(sddheight(node sdd), ( node val = new_node(); @val{"type"} = "sddheight"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddheight() # This creates an SDD value object which computes the xmin needed to center sdd within outersdd, with a fixed padding on both sides subroutine(sdd_vert_center_align(node sdd, node outersdd), ( node outertop = sddtop(outersdd); node outerheight = sddheight(outersdd); node innerheight = sddheight(sdd); node height_difference = sddsubtract(outerheight, innerheight); node half_height_difference = sdddivide(height_difference, sddfloat(2)); node ymax = sddsubtract(outertop, half_height_difference); ymax; )); # sdd_vert_center_align() # This creates an SDD value object which computes the y position required to make thissdd be directly below sdd. subroutine(sddbelow(node sdd, node thissdd), ( sddbottom(sdd); )); # sddbelow # This creates an SDD value object which puts an sdd directly below below_sdd, or on the next page if there's no room for it there. subroutine(sddbelowwrap(node sdd, node below_sdd), ( node val = new_node(); @val{"type"} = "sddbelowwrap"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; set_node_type(val{"below_sdd"}, "node"); @val{"below_sdd"} = below_sdd; val; )); # sddbelowwrap # This creates an SDD value object which computes the y position requires to make thissdd be at the top of sdd. subroutine(sddbelowtop(node sdd, node thissdd), ( sddtop(sdd); )); # ssdbelowtop() # This creates an SDD value object which computes the natural text width of a "text" SDD object. # wrap_width_sdd is an SDD containing a "wrap_width" subnode, which is a constant integer used as the wrap width. subroutine(sddtextwidth(node sdd, node wrap_width_sdd), ( node val = new_node(); @val{"type"} = "sddtextwidth"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; set_node_type(val{"wrap_width_sdd"}, "node"); @val{"wrap_width_sdd"} = wrap_width_sdd; val; )); # sddtextwidth() # This creates an SDD value object which computes the natural text height of a "text" SDD object. subroutine(sddtextheight(node sdd), ( node val = new_node(); @val{"type"} = "sddtextheight"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddtextheight() # This creates an SDD value object which computes the height of a group. subroutine(sddgroupheight(node sdd), ( node val = new_node(); @val{"type"} = "sddgroupheight"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddgroupheight() # This creates an SDD value object which computes the width of a group. subroutine(sddgroupwidth(node sdd), ( node val = new_node(); @val{"type"} = "sddgroupwidth"; set_node_type(val{"sdd"}, "node"); @val{"sdd"} = sdd; val; )); # sddgroupwidth() # This creates an SDD value object which computes the maximum height of all cells in a particular table row subroutine(sddtablerowmaxheight(node tbl, int row), ( node val = new_node(); @val{"type"} = "sddtablerowmaxheight"; set_node_type(val{"tbl"}, "node"); @val{"tbl"} = tbl; @val{"row"} = row; val; )); # sddtablerowmaxheight() # This creates an SDD value object which computes the maximum height of all cells in a particular table row subroutine(sddtablecolumnmaxwidth(node tbl, int column), ( node val = new_node(); @val{"type"} = "sddtablecolumnmaxwidth"; set_node_type(val{"tbl"}, "node"); @val{"tbl"} = tbl; @val{"column"} = column; val; )); # sddtablecolumnmaxwidth() # This creates an SDD value object which computes the width of a particular table column subroutine(sddtablecolumnwidth(node tbl, int column), ( node val = new_node(); @val{"type"} = "sddtablecolumnwidth"; set_node_type(val{"tbl"}, "node"); @val{"tbl"} = tbl; @val{"column"} = column; val; )); # sddtablecolumnwidth() # This creates an SDD value object which computes the height of a particular table row subroutine(sddtablerowheight(node tbl, int row), ( node val = new_node(); @val{"type"} = "sddtablerowheight"; set_node_type(val{"tbl"}, "node"); @val{"tbl"} = tbl; @val{"row"} = row; val; )); # sddtablerowheight() # # compute_group_width_and_height() # # Purpose: This computes the width and height of a group of nodes (and the height) # # Parameters: sdd: the node whose subnodes we should compute the width/height of # returns the total width of the group; also sets v.compute_group_width_height to the total height # subroutine(compute_group_width_and_height(node sdd), ( #echo("compute_group_width_and_height(); sdd=" . sdd); node saved_xmin = @sdd{"xmin"}; node saved_ymax = @sdd{"ymax"}; @sdd{"xmin"} = 0; @sdd{"ymax"} = 0; node subsddp; node items = sdd{"items"}; float groupwidth = 0; if (num_subnodes(items) == 0) then ( v.compute_group_width_height = 0; 0; ); else ( float xmin = 99999999; float xmax = -99999999; float ymin = 99999999; float ymax = -99999999; foreach subsddp items ( node subsdd = @subsddp; float sub_xmin = get_sdd_value(@subsdd{"xmin"}); #echo("getting width of subsdd: " . @subsddp); #dump_sdd(subsdd); float sub_xmax = sub_xmin + get_sdd_value(@subsdd{"width"}); if (sub_xmin < xmin) then xmin = sub_xmin; if (sub_xmax > xmax) then xmax = sub_xmax; float sub_ymax = get_sdd_value(@subsdd{"ymax"}); float sub_ymin = sub_ymax - get_sdd_value(@subsdd{"height"}); if (sub_ymax > ymax) then ymax = sub_ymax; if (sub_ymin < ymin) then ymin = sub_ymin; ); groupwidth = xmax - xmin; v.compute_group_width_height = ymax - ymin; ); # else @sdd{"xmin"} = saved_xmin; @sdd{"ymax"} = saved_ymax; announce_sdd_parameter_change(sdd, "xmin"); announce_sdd_parameter_change(sdd, "ymax"); #echo("computed groupwidth=" . groupwidth); groupwidth; )); # compute_group_width()