Instantly share code, notes, and snippets.
Created
March 15, 2023 20:56
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save steveroush/4287562c90855fd0d6a991e6159003ce to your computer and use it in GitHub Desktop.
word wrap for Graphviz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## set -x | |
## This is a Linux (and maybe MacOS?) shell script. | |
## It will need to be modified to run on Windows. | |
## It performs word-wrap (https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap) on the labels of certain Graphviz nodes. | |
## - not html-like labels | |
## - not record or Mrecord shaped nodes | |
## - not edge labels | |
## - not graph or cluster labels | |
## - not xlabels | |
## - it DOES NOT recognize any meaning of TAB character (ASCII 09) or | |
## of HTML non-breaking space: ( ) | |
## - it DOES try to honor \n \r \l embedded within labels | |
## | |
## to use: | |
## - set (new) maxTextWidth attribute for each node where word-wrap is desired | |
## maxTextWidth units are points (1/72 of inch) | |
## - invoke this script with one argument: the name of the Graphviz input file | |
## - output will be a modified version of the input file, with appropriate | |
## linebreaks (\n, \r, or \l) inserted in the node labels | |
## | |
## | |
## personal note: this is NOT the "right" way to do word-wrap | |
## it is a true kludge | |
## but it seems to work reasonably well | |
## steve roush | |
## | |
E=dot | |
E=twopi # we want a fast engine | |
gvpr -c ' | |
BEGIN{ | |
node_t thisNode, newNode; | |
int i, cnt; | |
string nl, rl, ll, Bell, DC1, DC2, DC3, aChar; | |
string p1, p2, justChar; | |
nl="\\" + "n"; | |
rl="\\" + "r"; | |
ll="\\" + "l"; | |
// Bell, DC1, DC2, and DC3 are unprintable ASCII characters | |
// this program assumes that they will not be part of any label | |
// so we use them to convey formatting purposes | |
Bell=sprintf("%c", 7); // ascii Bell (decimal 7) | |
DC1=sprintf("%c", 17); // ascii DC1 (decimal 17) | |
DC2=sprintf("%c", 18); // ascii DC1 (decimal 18) | |
DC3=sprintf("%c", 19); // ascii DC1 (decimal 18) | |
} | |
N{ | |
if (hasAttr($, "maxTextWidth") && $.maxTextWidth!="" && $.maxTextWidth!="0"){ | |
}else{ | |
//printf(2, "Note:: no word-wrap for node: %s. It does not have maxTextWidth attribute.\n", $.name); | |
break; | |
} | |
//HASATTR($, height) | |
if (hasAttr($, "height") && $.height!="") | |
$.__saveHeight=$.height; | |
else | |
$.__saveHeight=""; | |
if (hasAttr($, "width") && $.width!="") | |
$.__saveWidth=$.width; | |
else | |
$.__saveWidth=""; | |
if (hasAttr($, "label") && $.label!="" && (ishtml($.label)!=1)){ | |
//// need to count \ chars before [nrl] | |
//// if the count is odd, replace with \[nrl] Bell nl | |
//// else leave alone | |
cnt=0; | |
i=0; | |
while (i<length($.label)){ | |
aChar=substr($.label,i,1); | |
if (strcmp(aChar,"\\")==0) { | |
cnt++; | |
}else{ | |
if (((cnt%2)==1) && aChar=="[rnl]"){ | |
p1=substr($.label,0,i+1); | |
p2=substr($.label,i+1); | |
$.label=p1 + Bell + aChar + nl + p2; | |
i+=length(Bell + aChar + nl); | |
} | |
cnt=0; | |
} | |
i++; | |
} | |
// ugh, we need to find any justification chars (\[rln]) | |
// and then propagate that info backwards (toward the front of the text) | |
justChar="n"; | |
for (i=length($.label)-2;i>0;i--){ | |
aChar=substr($.label,i,1); | |
if (strcmp(aChar,Bell)==0) { | |
justChar=substr($.label,i+1,1); | |
}else if (aChar==" "){ | |
p1=substr($.label,0,i); | |
p2=substr($.label,i+1); | |
if (justChar=="n") $.label=p1 + DC1 + p2; | |
if (justChar=="l") $.label=p1 + DC2 + p2; | |
if (justChar=="r") $.label=p1 + DC3 + p2; | |
} | |
} | |
$.label=gsub($.label,DC1,nl+" "+nl); // wrap \? around each space | |
$.label=gsub($.label,DC2,ll+" "+ll); // wrap \? around each space | |
$.label=gsub($.label,DC3,rl+" "+rl); // wrap \? around each space | |
} | |
} | |
/*********************************************************** | |
**** for time being, do not worry about edge labels | |
**** or headlabels, taillabels, xlabels, or graph/cluster labels | |
*************************************************************/ | |
' $1 | | |
$E -G"linelength=999999" -Txdot | | |
/bin/gvpr -c ' | |
BEGIN{ | |
int cnt, i, tmpCnt, just, oldJust, len; | |
int maxLineWidth, lineWidth, wordWidth; // units = points | |
string Xstr, tok, thisWord, nl, rl, ll, Bell; | |
float Xpos, Ypos; | |
obj_t thisObj; | |
nl="\\" + "n"; | |
rl="\\" + "r"; | |
ll="\\" + "l"; | |
Bell=sprintf("%c", 7); // ascii Bell (decimal 7) | |
///////////////////////////////////////////////// | |
// index is (temporarily) broken, so ... | |
///////////////////////////////////////////////// | |
int myIndex(string Main, string Pattern){ | |
int _I, _L, _P, _RC ; | |
string _SUB; | |
_RC=-1; | |
_L=length(Main); | |
_P=length(Pattern); | |
for (_I=0; _I<(_L-_P+1);_I++){ | |
_SUB=substr(Main,_I,_P); | |
if (_SUB==Pattern){ | |
_RC=_I; | |
break; | |
} | |
} | |
return(_RC); | |
} | |
///////////////////////////////////////////////// | |
int adjustCnt(string rawTxt, int cnt){ | |
string tmpStr; | |
int j; | |
tmpStr=rawTxt+" "; // pad for substr | |
for (j=0;j<cnt;j++){ | |
if (strcmp(substr(tmpStr,j,2),"\\\\")==0){ | |
cnt++; | |
} | |
} | |
return cnt; | |
} | |
/////////////////////////////////////////////////////////////////// | |
string getTok(){ | |
string tstr; | |
Xstr=sub(Xstr,"^+( )",""); // strip leading spaces - ksh pattern | |
i=myIndex(Xstr, " "); | |
tstr=substr(Xstr,0,i); | |
Xstr=substr(Xstr,i); | |
Xstr=sub(Xstr,"^+( )",""); // strip leading spaces - ksh pattern | |
return(tstr); | |
} | |
///////////////////////////////////////////////////////////////// | |
void ldrawParse(){ | |
tok=getTok(); | |
just=0; // | |
while (tok!=""){ | |
switch(tok){ | |
case "F": // font | |
tok=getTok(); // font size, discard | |
//cnt=1+(int)getTok(); // font name char count (plus leading -) | |
cnt=(int)getTok(); // font name char count (plus leading -) | |
cnt++; | |
tok=substr(Xstr,0,cnt); // font name, discard | |
Xstr=substr(Xstr,cnt); // strip fontname from Xstr | |
break; | |
case "c": // pen color | |
case "C": // fill color | |
cnt=1+(int)getTok(); // color char count (plus leading -) | |
tok=substr(Xstr,0,cnt); // font name, discard | |
Xstr=substr(Xstr,cnt); // strip fontname from Xstr | |
break; | |
case "T": // text | |
// by now, we have put each "word" on a different line | |
// time to stitch the words into lines | |
Xpos=(float)getTok(); // X pos | |
Ypos=(float)getTok(); // Y pos | |
oldJust=just; | |
just=(int)getTok(); // justify | |
wordWidth=(int)getTok(); // word width | |
cnt=(int)getTok(); // word char count | |
/////////////////////////////////////////////////////////// | |
// if string contains // | |
// count will be short - length of "//" is 1 !!!!!! | |
// we have already taken care of \G \N \E ... | |
/////////////////////////////////////////////////////////// | |
Xstr=substr(Xstr,1); // chop off leading "-" | |
cnt=adjustCnt(Xstr,cnt); | |
// now we have adjusted character/byte count | |
thisWord=substr(Xstr,0,cnt); // word | |
//////////// time to wrap //////////////////// | |
tmpCnt=wordWidth; | |
if (substr(thisWord,0,1)==Bell){ | |
lineWidth=0; | |
thisObj.label+=("\\"+substr(thisWord,1,1)); | |
}else if ((lineWidth+tmpCnt)<=maxLineWidth){ | |
lineWidth+=tmpCnt; | |
thisObj.label+=thisWord; | |
}else{ | |
// first, strip trailing space at end of previous line | |
// ksh pattern matching does not support ^ or $, | |
// so grind it out | |
len=length(thisObj.label); | |
if(substr(thisObj.label, len-1)==" ") | |
thisObj.label=substr(thisObj.label,0,len-1); | |
// do not start line with space | |
if (thisWord!=" "){ | |
lineWidth=wordWidth; | |
// add justification | |
if (oldJust==0) thisObj.label+=(nl+thisWord); | |
if (oldJust==-1) thisObj.label+=(ll+thisWord); | |
if (oldJust==1) thisObj.label+=(rl+thisWord); | |
} | |
} | |
Xstr=substr(Xstr,cnt); // strip word from Xstr | |
Xstr=sub(Xstr,"^+( )",""); // strip leading spaces - ksh pattern | |
break; | |
case "S": // style | |
cnt=1+(int)getTok(); // style char count (plus leading -) | |
tok=substr(Xstr,0,cnt); // style, discard | |
Xstr=substr(Xstr,cnt); // strip style from Xstr | |
break; | |
case "t": // font characteristics | |
cnt=1+(int)getTok(); // style char count (plus leading -) | |
tok=substr(Xstr,0,cnt); // style, discard | |
Xstr=substr(Xstr,cnt); // strip style from Xstr | |
break; | |
case "E": // filled ellipse | |
case "e": // unfilled ellipse | |
tok=substr(Xstr,0,cnt); // X, discard | |
tok=substr(Xstr,0,cnt); // Y, discard | |
tok=substr(Xstr,0,cnt); // width, discard | |
tok=substr(Xstr,0,cnt); // height, discard | |
break; | |
case "P": // filled polygon | |
case "p": // unfilled polygon | |
case "L": // polyline | |
case "B": // unfilled B-spline | |
case "b": // filled polygon | |
cnt=1+(int)getTok(); // point (X,Y) count | |
for (i=0;i<cnt;i++){ // delete multiple point (pos) values | |
tok=substr(Xstr,0,cnt); // X, discard | |
tok=substr(Xstr,0,cnt); // Y, discard | |
} | |
Xstr=substr(Xstr,cnt); // strip style from Xstr | |
break; | |
case "I": // external image | |
break; | |
default: | |
break; | |
} | |
if (length(Xstr)>0){ | |
tok=getTok(); | |
}else{ | |
break; | |
} | |
} | |
} | |
} | |
N{ | |
thisObj=$; | |
lineWidth=0; | |
maxLineWidth=0; // ?? | |
if (hasAttr($, "maxTextWidth") && $.maxTextWidth!="" && $.maxTextWidth!="0"){ | |
maxLineWidth=(int)(72. * maxTextWidth); | |
}else{ | |
printf(2, "Note:: no word-wrap for node: %s. It does not have maxTextWidth attribute.\n", $.name); | |
break; | |
} | |
if (hasAttr($, "__saveHeight") && $.__saveHeight!="") | |
$.height=$.__saveHeight; | |
else | |
$.height=""; | |
if (hasAttr($, "__saveWidth") && $.__saveWidth!="") | |
$.width=$.__saveWidth; | |
else | |
$.width=""; | |
if (hasAttr($, "_ldraw_") && $._ldraw_!=""){ | |
$.label=""; // we will rebuild | |
Xstr=$._ldraw_; | |
ldrawParse(); // do heavy lifting | |
$._ldraw_=""; | |
} | |
}' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment