Renderer port (crates/graphviz-pure-render)
Copied at build time from docs/porting/render.md.
Module map
Section titled “Module map”| Rust | C source |
|---|---|
emit/model.rs |
the post-layout Agraphinfo_t/Agnodeinfo_t/Agedgeinfo_t state: GD_clust tree, ND_coord/lw/rw/ht, ND_shape_info, ED_spl, textlabel_t/textspan_t, place_root_label/place_graph_label fallbacks, agfstout order (head id, then sequence) |
emit/job.rs |
lib/gvc/gvrender.c (gvrender_*, gvrender_resolve_color, gvrender_set_style), obj_state_t stack (push_obj_state/pop_obj_state), job geometry from gvRenderJobs (init_job_pad/margin/dpi/viewport/pagination, setup_page, emit_graph scale) |
emit/emit.rs |
lib/common/emit.c: emit_graph, emit_page, emit_background, emit_view (outputorder), emit_clusters, emit_node, emit_edge, emit_begin_edge/emit_end_edge, emit_edge_graphics (multicolor, parallel colors, tapered, ortho rounded corners), emit_edge_label, emit_attachment, initObjMapData/getObjId/preprocessTooltip, layers (init_layering, parse_layers, parse_layerselect, selectedLayer, *_in_layer), parseSegs, findStopColor, stripedBox; emit_label from labels.c |
emit/shapes.rs |
shapes.c gencode: poly_gencode, point_gencode, record_gencode/gen_fields, checkStyle, stylenode, penColor, findFill, Mcircle_hack, round_corners (rounded, diagonals, cylinder), wedgedEllipse |
emit/corners.rs |
round_corners shape cases: note, tab, folder, box3d, component and all SBOL shapes |
emit/arrows.rs |
arrows.c: arrow_flags, arrow_match_name, arrow_gen, every arrow type with its miter/penwidth corrections, Bezier |
emit/html.rs |
htmltable.c emit half: emit_html_label, emit_html_tbl, emit_html_cell, emit_html_rules, doBorder, setFill, emit_htextspans, initAnchor/endAnchor |
emit/geom.rs |
dotneato_closest, splitBSpline, computeoffset_p/qr, get_gradient_points (utils.c), taper (taper.c), ellipticWedge (ellipse.c) |
emit/output.rs |
output.c: attach_attrs_and_arrows, set_record_rects, rec_attach_bb, write_plain |
emit/num.rs |
gvprintdouble, gvprintnum, agxbuf_trim_zeros, %.Ng |
emit/attrs.rs |
late_*, mapbool, parse_style, strtod/strtol |
svg_engine.rs |
plugin/core/gvrender_core_svg.c (incl. gradients, layers, anchors, gv_xml_escape) |
xdot_engine.rs |
xdot half of plugin/core/gvrender_core_dot.c (per-state buffers, penwidth/textflags state, gradients) |
json.rs |
plugin/core/gvrender_core_json.c and the xdot parser of lib/xdot/xdot.c |
agwrite.rs |
lib/cgraph/write.c (-Tdot, -Txdot, -Tcanon) over the cgraph dictionaries recorded by the parser (Graph::dicts: bindattrs, attrstmt/setattr, unviewsubgraphsattr, sym->print, ProtoGraph label) |
lib.rs |
format dispatch, safe_dcl/agxset bookkeeping (sync_dicts), dot’s mark_lowclusters edge membership |
graphviz-pure-layout/src/nop.rs ports neato’s Nop path (init_nop, user_pos,
user_spline, nop_init_graphs, nop_init_edges, chkBB, compute_bb,
neato_translate, neato_set_aspect, place_graph_label) for -n, -n1,
-n2, -Knop/nop1/nop2. Edges without a usable pos go through the
neatogen spline_edges_ machinery (ports, equivalence classes, straight or
splines= routing, clipping).
Fidelity
Section titled “Fidelity”The render corpus isolates the renderers: C lays every tests/wild,
tests/fixtures and tests/render graph out (dot -Tdot), then both C and
the port render that file in no-layout mode (neato -n2). See
scripts/gen-render-golden.sh and gv-diff render
(crates/graphviz-pure-diff/src/render.rs, ratchet tests/golden/render/baseline.json,
test crates/graphviz-pure-diff/tests/render_fidelity.rs). The SVG generator comment is
the only line ignored. tests/render/*.gv are 21 feature graphs (all arrow
types, styles, shapes, gradients, HTML, records, URLs, labels, rotation,
size/ratio, layers, output order).
Byte-identical files, original 508-file corpus (before = legacy renderers with
-n2 support, after = this port; plain formats exclude 4 nondeterministic C
references):
| format | before | after |
|---|---|---|
| svg | 36 / 508 | 507 / 508 |
| json | 0 / 472 | 472 / 472 |
| json0 | 0 / 508 | 507 / 508 |
| xdot | 2 / 508 | 507 / 508 |
| dot | 207 / 508 | 507 / 508 |
| plain | 276 / 504 | 501 / 504 |
| plain-ext | 260 / 504 | 501 / 504 |
All 21 feature graphs are identical in every format. -Tcanon (agwrite on the
parsed input) matches C on 493 of 508 inputs (454 before).
Known differences
Section titled “Known differences”-Tplainof labels changed byhtmlEntityUTF8/latin1ToUTF8that need no quoting:canoninwrite_plain(lib/common/output.c) interns the text,agstrcanonreturns it unquoted andagstrfreefrees it before it is printed, so C prints whatever the freed memory holds (gv16_graphs_Latin1,b60; thecharset_*Latin-1 cases,gallery__index_7,Symbol,b34,b56change from run to run and are skipped,UNSTABLEingv-diff). The port prints the converted label; the garbage is not emulated.- HTML cell
STYLE="dashed"borders: C’sxdot_stylewalks past the end of a one-element style list and prints adjacent string literals (undefined behaviour); not reproduced (tests/fuzz-open/034-html-cell-dashed-style-overread). - Images (
image=,shapefile=,<IMG>) are never loaded; C draws them only when the file exists. - The SVG generator comment names graphviz-pure.
- Gradient and HTML anchor ids are numbered per
render_dot_allcall, like C’s process-wide statics for a single invocation.
Charset
Section titled “Charset”C keeps the input’s bytes and converts only label text. graphviz_pure_core::charset
gives the port the same model with Strings: bytes that are not part of valid
UTF-8 become the code points U+10FF00 + byte (decode), and encode turns
them back into bytes. The CLI decodes its input and encodes its output, and
gv-diff reads inputs and C references the same way, so attribute values,
names, -Tdot/-Tcanon/-Txdot strings, SVG titles and ids, tooltips and
URLs carry C’s bytes (SVG always declares encoding="UTF-8"; gv_xml_escape
leaves bytes >= 0x80 alone, and so does the port).
make_label, HTML fallbacks and tooltips (preprocessTooltip) convert withlatin1ToUTF8(charset=latin1) orhtmlEntityUTF8(else) on the encoded bytes, once, where C does.htmlEntityUTF8reproduces C’s recovery for invalid UTF-8 (the offending byte is taken as Latin-1, possibly leaving invalid UTF-8, kept as raw bytes) and its once-per-graph warning.- Text width is measured per encoded byte, with
textspan_lut.c’s once-per-process “no value for width” warnings. - HTML labels: expat gets the graph’s encoding. Latin-1 bytes are decoded to
UTF-8, invalid UTF-8 is a fatal error, and
BIG-5is an unknown encoding, so every Big5 HTML label fails. A failed HTML label is plain text with its unconverted source afterwards (lp->htmlcleared):-Tplainquotes it. charset=big5: labels convert like UTF-8;make_simple_labelkeeps the byte after a0xA1..=0xFEbyte with it (so a\there is not an escape). Unknown charsets warnUnsupported charsetand act as UTF-8.-Txdotstring lengths count bytes;-Tjsonrunslatin1ToUTF8over every string of a Latin-1 graph (so label text, already UTF-8, is converted again, as in C) and escapes only",\,/,\b,\f,\n,\r,\t; other control characters and bytes are written as is.- The charset of the graph being processed is kept per thread
(
charset::begin_layout/begin_render, called bylayout_graph_with_optionsandrender_graph_named);NodeSizeEnv::newand-n2labels use it. - Not reproduced: HTML lexer error messages (
Error: not well-formed ...,unknown encoding); input that really containsU+10FF00..=U+10FFFFis written out as raw bytes.
Debugging aid
Section titled “Debugging aid”scripts/c-probe.c prints C’s exact (%.17g) node coordinates, sizes and shape
vertices after -n2, for chasing last-bit floating-point differences.