package main /* TODO: * zonder -o : uitvoer naar stdout * horizontale positie van labels niet helemaal correct */ import ( "github.com/pebbe/util" "github.com/ungerik/go-cairo" "bufio" "flag" "fmt" "math" "os" "path/filepath" "strconv" "strings" ) const ( denVERSION = "1.30.pdf" ) type nodeType int type linkType int type metricsType struct { x, y float64 w, h float64 size float64 } type clusterType struct { index int value float64 text string textsize metricsType group [2]int node [2]nodeType cluster [2]int label [2]string labelsize [2]metricsType } const ( PDFVersion = cairo.PDF_VERSION_1_4 CLS = nodeType(iota) LBL RECT = linkType(iota) TRI ARC ARC2 maxCOLORS = 19 defFONTSIZE = 8 ) var ( colors = [][3]float64{ {0.0, 0.0, 1.0}, {0.0, 1.0, 0.0}, {0.0, 1.0, 1.0}, {1.0, 0.0, 0.0}, {1.0, 0.0, 1.0}, {1.0, 1.0, 0.0}, {0.0, 0.0, 0.5}, {0.0, 0.5, 0.0}, {0.0, 0.5, 0.5}, {0.5, 0.0, 0.0}, {0.5, 0.0, 0.5}, {0.5, 0.5, 0.0}, {0.3, 0.3, 0.7}, {0.3, 0.7, 0.3}, {0.3, 0.7, 0.7}, {0.7, 0.3, 0.3}, {0.7, 0.3, 0.7}, {0.7, 0.7, 0.3}, {0.3, 0.3, 0.3}, } cl = make([]clusterType, 0) colorlink = false colorlabel = false evenodd = false oe = false ruler = true first = true mindefined = false use_rainbow = false use_bright = false use_usercolours = false use_renum = false linktype = RECT opt_1 = flag.Bool("1", false, "iso-8859-1") opt_a = flag.Bool("a", false, "curved links") opt_A = flag.Bool("A", false, "curved links, new version") opt_b = flag.Float64("b", math.NaN(), "base offset") opt_c = flag.Bool("c", false, "color links") opt_C = flag.Bool("C", false, "color labels") opt_e = flag.Float64("e", 1.0, "exponent") opt_E = flag.Bool("E", false, "example cluster file") opt_f = flag.Int("f", defFONTSIZE, "fontsize") opt_g = flag.Bool("g", false, "output PNG") opt_G = flag.Int("G", 72, "PNG resolution") opt_h = flag.Bool("h", false, "rainbow colours, light and dark") opt_H = flag.Bool("H", false, "rainbow colours, light") opt_I = flag.Bool("I", false, "cluster numbers") opt_L = flag.Bool("L", false, "no labels") opt_n = flag.Int("n", 1, "number of groups") opt_o = flag.String("o", "", "output file") opt_O = flag.Bool("O", false, "reorder colours") opt_p = flag.Bool("p", false, "placement of labels in two columns") opt_P = flag.Bool("P", false, "patterns") opt_Q = flag.Bool("Q", false, "symbols") opt_r = flag.Float64("r", -1, "line skip for ruler") opt_R = flag.Bool("R", false, "no ruler") opt_s = flag.Float64("s", -1, "line skip within groups") opt_S = flag.Float64("S", -1, "line skip between groups") opt_t = flag.Bool("t", false, "triangular links") opt_T = flag.Bool("T", false, "serif font") opt_u = flag.String("u", "", "user-defined colours") font = "sans-serif" fonti = cairo.FONT_SLANT_NORMAL fontw = cairo.FONT_WEIGHT_NORMAL currentgroup = -1 currentrenum = 1 currentcol = 0 currentlw = 0.0 labelcount = 0 groups = make([]int, 0) ngroup = 1 max_colors = 0 n_colors = int(maxCOLORS) fontsize = int(defFONTSIZE) top int inputline = 0 max = 0 used = 0 urx = 459.0 width = 300.0 topmargin = 100.0 leftmargin = 150.0 leftmargin2 float64 png = false dpi int labels = true patterns = false symbols = false numbers = false LineSkip = -1.0 LineSkip2 = -1.0 RulerSkip = -1.0 exponent = 1.0 maxlabelwidth = 0.0 maxlabelwidth1 = 0.0 maxlabelwidth2 = 0.0 maxvalue = float64(-math.MaxFloat32) minvalue = float64(math.MaxFloat32) shift float64 up float64 down float64 cury float64 groupnums []int outfile string colorfile string Ag metricsType surface *cairo.Surface x = util.CheckErr ) func main() { flag.Usage = syntax flag.Parse() if *opt_E { fmt.Print(`# Example cluster file 1 .12 L Norwegian L Swedish 2 .15 C 1 L Danish 3 .3 L Dutch L German 4 .35 Nordic group L Icelandic C 2 5 .7 C 4 C 3 `) return } switch flag.NArg() { case 0: if util.IsTerminal(os.Stdin) { syntax() return } case 1: // ok default: syntax() return } if *opt_1 { TODO("1") } if *opt_a { linktype = ARC } if *opt_A { linktype = ARC2 } if !math.IsNaN(*opt_b) { minvalue = *opt_b mindefined = true } if *opt_c { colorlink = true } if *opt_C { colorlabel = true } exponent = *opt_e fontsize = *opt_f if *opt_g { png = true } dpi = *opt_G if dpi < 1 { dpi = 72 } else if dpi < 36 { dpi = 36 } else if dpi > 1200 { dpi = 1200 } if *opt_h { use_rainbow = true use_bright = false } if *opt_H { use_rainbow = true use_bright = true } if *opt_I { TODO("I") } if *opt_L { labels = false } ngroup = *opt_n outfile = *opt_o if *opt_O { use_renum = true } if *opt_p { evenodd = true leftmargin = 200 width = 250 } if *opt_P { TODO("P") } if *opt_Q { TODO("Q") } RulerSkip = *opt_r if *opt_R { ruler = false } LineSkip = *opt_s LineSkip2 = *opt_S if *opt_t { linktype = TRI } if *opt_T { font = "serif" } if *opt_u != "" { colorfile = *opt_u use_usercolours = true } // TODO: weg if outfile == "" { x(fmt.Errorf("Missing name for output file")) } if ngroup < 1 { ngroup = 1 } if patterns || symbols || numbers { var s string if patterns { s = "patterns" } else if symbols { s = "symbols" } else { s = "numbers" } if colorlabel || colorlink { x(fmt.Errorf("No colours with %s", s)) } if (ngroup < 2 || ngroup > n_colors) && !numbers { x(fmt.Errorf("Illegal number of %s: %i", s, ngroup)) } labels = false } // TODO: met optie -u kan n_colors nog groter worden if (colorlink || colorlabel) && (ngroup < 2 || ngroup > n_colors) && !use_rainbow { x(fmt.Errorf("Invalid number of groups with coloured labels or links. Try rainbow colours.")) } if use_rainbow && !(colorlink || colorlabel) { x(fmt.Errorf("Missing option -c and/or -C with rainbow colours")) } if colorlabel && !labels { x(fmt.Errorf("Colour for no labels\n")) } if fontsize < 4 { x(fmt.Errorf("fontsize too small")) } if fontsize > 20 { x(fmt.Errorf("fontsize too large")) } if evenodd && !labels { x(fmt.Errorf("Placement of labels in two colums without labels")) } if use_usercolours && (colorlabel || colorlink) { colors = colors[0:0] n_colors = 0 int2float := false fp, err := os.Open(colorfile) x(err) inputline = 0 scanner := bufio.NewScanner(fp) for scanner.Scan() { inputline++ aa := strings.Fields(scanner.Text()) if len(aa) != 3 { x(fmt.Errorf("Wrong number of value(s) for in file \"%s\", line %d", colorfile, inputline)) } r, err := strconv.ParseFloat(aa[0], 64) x(err) g, err := strconv.ParseFloat(aa[1], 64) x(err) b, err := strconv.ParseFloat(aa[2], 64) x(err) if r < 0 || r > 255 { x(fmt.Errorf("Red component out of range in file \"%s\", line %d", colorfile, inputline)) } if g < 0 || g > 255 { x(fmt.Errorf("Green component out of range in file \"%s\", line %d", colorfile, inputline)) } if b < 0 || b > 255 { x(fmt.Errorf("Blue component out of range in file \"%s\", line %d", colorfile, inputline)) } if r > 1 || g > 1 || b > 1 { int2float = true } colors = append(colors, [3]float64{r, g, b}) n_colors++ } x(scanner.Err()) fp.Close() if int2float { for i := 0; i < n_colors; i++ { for j := 0; j < 3; j++ { colors[i][j] /= 255 } } } } if use_rainbow { colors = make([][3]float64, ngroup) var b float64 for i := 0; i < ngroup; i++ { if use_bright || i%2 == 0 { b = 1 } else { b = .6 } colors[i] = hsb2rgb(float64(i)/float64(ngroup), 1, b) } } var fp *os.File var err error if flag.NArg() == 0 { fp = os.Stdin } else { fp, err = os.Open(flag.Arg(0)) x(err) defer fp.Close() } lineno := 0 scanner := bufio.NewScanner(fp) for scanner.Scan() { lineno++ aa := strings.Fields(scanner.Text()) if len(aa) == 0 || aa[0][0] == '#' { continue } if len(aa) < 2 { x(fmt.Errorf("Syntax error at line %d: \"%s\"", inputline, strings.Join(aa, " "))) } index, err := strconv.Atoi(aa[0]) x(err) value, err := strconv.ParseFloat(aa[1], 64) x(err) if value > maxvalue { maxvalue = value } text := strings.Join(aa[2:], " ") if len(text) > 0 && text[0] == '#' { text = "" } item := clusterType{ index: index, value: value, text: text, } for n := 0; n < 2; n++ { lineno++ if !scanner.Scan() { x(fmt.Errorf("Missing line")) } aa := strings.Fields(scanner.Text()) switch aa[0] { case "l", "L": item.node[n] = LBL item.label[n] = strings.Join(aa[1:], " ") case "c", "C": item.node[n] = CLS item.cluster[n], err = strconv.Atoi(aa[1]) x(err) default: } } cl = append(cl, item) } x(scanner.Err()) used = len(cl) if used == 0 { x(fmt.Errorf("No data")) } getTextSizes() // replace indexes for i := 0; i < used; i++ { for j := 0; j < 2; j++ { if cl[i].node[j] == CLS { for k := 0; k < used; k++ { if cl[i].cluster[j] == cl[k].index { cl[i].cluster[j] = k break } } } } } // locate top node top := 0 for { found := false for i := 1; i < used; i++ { if cl[i].node[0] == CLS && cl[i].cluster[0] == top || cl[i].node[1] == CLS && cl[i].cluster[1] == top { top = i found = true break } } if !found { break } } if !mindefined { for i := 0; i < used; i++ { if cl[i].value < minvalue { minvalue = cl[i].value } } if minvalue > 0 { minvalue = 0 } } for i := 0; i < used; i++ { if cl[i].text != "" { f := cl[i].textsize.size + leftmargin + width/math.Pow(maxvalue-minvalue, exponent)*math.Pow(cl[i].value-minvalue, exponent) + 5.0 if f > urx { urx = f + .5 } } } if ngroup > 1 { // divide into color groups j := 0 for i := 0; i < used; i++ { cl[i].group[0] = 1 cl[i].group[1] = 1 for k := 0; k < 2; k++ { if cl[i].node[k] == LBL { j++ } } } if ngroup > j { x(fmt.Errorf("Too many groups")) } groups = make([]int, ngroup) groups[0] = top for n := 1; n < ngroup; n++ { f := float64(-math.MaxFloat32) for i := 0; i < n; i++ { if groups[i] < used && cl[groups[i]].value > f { j = i f = cl[groups[i]].value } } cl[groups[j]].group[0] = n + 1 cl[groups[j]].group[1] = j + 1 if cl[groups[j]].node[0] == CLS { groups[n] = cl[groups[j]].cluster[0] } else { groups[n] = math.MaxInt32 } if cl[groups[j]].node[1] == CLS { groups[j] = cl[groups[j]].cluster[1] } else { groups[j] = math.MaxInt32 } setclgroups(groups[n], n+1) // setclgroups (groups [j], j + 1) } if use_renum { // renumber groupnums = make([]int, ngroup+1) currentgroup = -1 currentrenum = 1 renumber(top) for i := 0; i < used; i++ { for j := 0; j < 2; j++ { cl[i].group[j] = groupnums[cl[i].group[j]] } } } } if LineSkip < 0 { if labels { LineSkip = 1.2 * float64(fontsize) if evenodd { LineSkip /= 2 } } else { LineSkip = 2 } } if LineSkip2 < 0 { if labels { LineSkip2 = 1.5 * LineSkip if evenodd { LineSkip2 *= 2 } } else { LineSkip2 = 4 } } if RulerSkip < 0 { RulerSkip = 1.5*LineSkip + 4 } x1 := leftmargin - 6 - maxlabelwidth if evenodd { process_width(top) leftmargin2 = leftmargin - 4 - maxlabelwidth1 x1 = leftmargin2 - 6 - maxlabelwidth2 } x2 := urx y2 := 101 + float64(used-ngroup+1)*LineSkip + float64(ngroup-1)*LineSkip2 if ruler { y2 += RulerSkip + float64(fontsize) + 1 } else { y2 += float64(fontsize) / 2 } y1 := 99 - float64(fontsize)/2 if png { surface = cairo.NewSurface(cairo.FORMAT_ARGB32, int((x2-x1)/72*float64(dpi)), int((y2-y1)/72*float64(dpi))) surface.Scale(float64(dpi)/72, float64(dpi)/72) defer func() { surface.WriteToPNG(outfile) defer surface.Finish() }() } else { surface = cairo.NewPDFSurface(outfile, x2-x1, y2-y1, PDFVersion) //surface = cairo.NewPDFSurface(outfile, 595, 842, PDFVersion) defer surface.Finish() } surface.SelectFontFace(font, fonti, fontw) surface.SetFontSize(float64(fontsize)) surface.Translate(-x1, -y1) /* surface.Rectangle(x1, y1, x2-x1, y2-y1) surface.SetSourceRGB(.9, .9, .9) surface.Fill() surface.SetSourceRGB(0, 0, 0) */ step := math.Pow(10, math.Ceil(math.Log10(maxvalue-minvalue))-1) if (maxvalue-minvalue)/step > 6.0 { step *= 2.0 } else if (maxvalue-minvalue)/step < 3.0 { step *= 0.5 } format := "%.0f" if step < 1 { a := fmt.Sprintf("%.1g", step) n := 0 for _, c := range a { if c == '0' { n++ } } format = fmt.Sprintf("%%.%df", n) } shift = -Ag.y - Ag.h/2 up = Ag.y // ?? down = Ag.h + Ag.y cury = topmargin oe = false currentgroup = -1 process(top) doColor(0) doLinewidth(0) if ruler { cury = cury - LineSkip + RulerSkip for m := float64(0); m <= maxvalue; m += step / 5 { if m >= minvalue { surface.MoveTo(math.Pow(m-minvalue, exponent)*width/math.Pow(maxvalue-minvalue, exponent)+leftmargin, cury) surface.RelLineTo(0, -2) surface.Stroke() } } for m := float64(0) - step/5; m >= minvalue; m -= step / 5 { if m <= maxvalue { surface.MoveTo(math.Pow(m-minvalue, exponent)*width/math.Pow(maxvalue-minvalue, exponent)+leftmargin, cury) surface.RelLineTo(0, -2) surface.Stroke() } } for m := float64(0); m <= maxvalue; m += step { if m >= minvalue { x := math.Pow(m-minvalue, exponent)*width/math.Pow(maxvalue-minvalue, exponent) + leftmargin surface.MoveTo(x, cury) surface.RelLineTo(0, -4) surface.Stroke() s := fmt.Sprintf(format, m) ww := surface.TextExtents(s) surface.MoveTo(x-ww.Xbearing-(ww.Width-ww.Xbearing)/2, cury+float64(fontsize)) surface.ShowText(s) } } for m := float64(0) - step; m >= minvalue; m -= step { if m <= maxvalue { x := math.Pow(m-minvalue, exponent)*width/math.Pow(maxvalue-minvalue, exponent) + leftmargin surface.MoveTo(x, cury) surface.RelLineTo(0, -4) surface.Stroke() s := fmt.Sprintf(format, m) ww := surface.TextExtents(s + "-") surface.MoveTo(x-ww.Xbearing-(ww.Width-ww.Xbearing)/2, cury+float64(fontsize)) surface.ShowText(s) } } surface.MoveTo(leftmargin, cury) surface.RelLineTo(width, 0) surface.Stroke() if minvalue < 0 && maxvalue > 0 { x := math.Pow(-minvalue, exponent)*width/math.Pow(maxvalue-minvalue, exponent) + leftmargin surface.MoveTo(x, cury) surface.LineTo(x, topmargin-float64(fontsize)/2) surface.SetDash([]float64{3}, 1, 0) surface.Stroke() } } /* currentgroup = -1; process (top); fputs ( "pop pop\n" "\n", fp_out ); if (ruler) { fputs ("% This draws the ruler\n", fp_out); if (numbers) fputs ("Font1 setfont\n", fp_out); fputs ( "/setmark1 {\n" " Min sub EXP Width mul Max Min sub EXP div LeftMargin add\n" " y moveto\n" " 0 2 rlineto\n" " stroke\n" "} bind def\n" "\n" "/setmark {\n" " dup\n" " Min sub EXP Width mul Max Min sub EXP div LeftMargin add\n" " y moveto\n" " gsave\n" " 0 4 rlineto\n" " stroke\n" " grestore\n" " 0 FontSize neg rmoveto\n" " 20 string cvs\n" " dup stringwidth pop 2 div neg 0 rmoveto\n" " show\n" "} bind def\n" "\n" "/y y LineSkip add RulerSkip sub def\n" "0 RulerStep 5 div Max {\n" " dup Min ge { setmark1 } { pop } ifelse\n" "} for\n" "0 RulerStep Max {\n" " dup Min ge { setmark } { pop } ifelse\n" "} for\n" "RulerStep neg 5 div dup Min {\n" " dup Max le { setmark1 } { pop } ifelse\n" "} for\n" "RulerStep neg dup Min {\n" " dup Max le { setmark } { pop } ifelse\n" "} for\n" "LeftMargin y moveto\n" "Width 0 rlineto stroke\n" "\n" "% This draws the vertical line for X equals 0\n" "Min 0 lt Max 0 gt and {\n" " Min neg EXP Width mul Max Min sub EXP div LeftMargin add\n" " dup\n" " y\n" " moveto\n" " TopMargin FontSize 2 div add\n" " lineto\n" " [ 3 ] 0 setdash\n" " stroke\n" "} if\n" "\n", fp_out ); } fputs ( "end\n" "showpage\n" "%%EOF\n", fp_out ); if (outfile) fclose (fp_out); return 0; */ } func renumber(cluster int) int { n := -1 for i := 0; i < 2; i++ { if cl[cluster].node[i] == LBL { if cl[cluster].group[i] != currentgroup { currentgroup = cl[cluster].group[i] groupnums[currentgroup] = currentrenum currentrenum++ } n = currentgroup } else { n = renumber(cl[cluster].cluster[i]) } } return n } func setclgroups(cluster int, group int) { if cluster < used { for i := 0; i < 2; i++ { cl[cluster].group[i] = group if cl[cluster].node[i] == CLS { setclgroups(cl[cluster].cluster[i], group) } } } } func setlinewidth(w float64) { if w != currentlw { surface.SetLineWidth(w) currentlw = w } } func doLinewidth(i int) { if i < 1 { setlinewidth(.5) } else { setlinewidth(1) } } func doColor(i int) { if i != currentcol { currentcol = i if i < 0 { surface.SetSourceRGB(1, 1, 1) } else if i == 0 { surface.SetSourceRGB(0, 0, 0) } else { surface.SetSourceRGB(colors[i-1][0], colors[i-1][1], colors[i-1][2]) } } } func doLabel(s string, size metricsType, y float64) (x float64) { if !labels { return leftmargin } m := leftmargin ls := LineSkip if evenodd { ls *= 2 if oe { m = leftmargin2 } } if colorlabel && currentgroup > 0 { doColor(currentgroup) surface.Rectangle(m-4-size.w-1, y-ls/2, size.w+2, ls) surface.Fill() rgb := colors[currentgroup-1] // https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale if rgb[0]*.2126+rgb[1]*.7152+rgb[2]*.0722 > .5 { doColor(0) } else { doColor(-1) } } else { doColor(0) } if evenodd { if !oe { oe = true } else { oe = false cc := currentcol doColor(0) setlinewidth(.2) surface.MoveTo(leftmargin-4, y) surface.LineTo(leftmargin2, y) surface.Stroke() doColor(cc) } } surface.MoveTo(m-4-size.w, y+shift) surface.ShowText(s) x = leftmargin return x } func doLink(v float64, x1, y1, x2, y2 float64) (x float64, y float64) { if colorlink { doColor(currentgroup) doLinewidth(currentgroup) } else { doColor(0) doLinewidth(0) } x = math.Pow(v-minvalue, exponent)*width/ math.Pow(maxvalue-minvalue, exponent) + leftmargin y = (y1 + y2) / 2 surface.MoveTo(x1, y1) switch linktype { case RECT: surface.LineTo(x, y1) surface.LineTo(x, y2) surface.LineTo(x2, y2) case TRI: surface.LineTo(x, y) surface.LineTo(x2, y2) case ARC: x1b := (x-x1)*.552284 + x1 x2b := (x-x2)*.552284 + x2 y1b := y - (y-y1)*.552284 y2b := y + (y2-y)*.552284 surface.CurveTo(x1b, y1, x, y1b, x, y) surface.CurveTo(x, y2b, x2b, y2, x2, y2) case ARC2: surface.CurveTo(x, y1, x, y1, x, y) surface.CurveTo(x, y2, x, y2, x2, y2) } surface.Stroke() return x, y } func doText(s string, size metricsType, x, y float64) { doColor(0) surface.MoveTo(x+3, y-2-down) surface.ShowText(s) } func process(i int) (x float64, y float64) { var xx [2]float64 var yy [2]float64 for j := 0; j < 2; j++ { if cl[i].node[j] == CLS { xx[j], yy[j] = process(cl[i].cluster[j]) } else { if ngroup > 1 && currentgroup != cl[i].group[j] { currentgroup = cl[i].group[j] if first { first = false } else { cury = cury - LineSkip + LineSkip2 } labelcount = 0 } xx[j] = doLabel(cl[i].label[j], cl[i].labelsize[j], cury) yy[j] = cury cury += LineSkip labelcount++ } } if labelcount == 1 { currentgroup = 0 } x, y = doLink(cl[i].value, xx[0], yy[0], xx[1], yy[1]) if cl[i].text != "" { doText(cl[i].text, cl[i].textsize, x, y) } labelcount-- return x, y } func process_width(i int) { for j := 0; j < 2; j++ { if cl[i].node[j] == CLS { process_width(cl[i].cluster[j]) } else { f := cl[i].labelsize[j].size if oe { if f > maxlabelwidth2 { maxlabelwidth2 = f } oe = false } else { if f > maxlabelwidth1 { maxlabelwidth1 = f } oe = true } } } } func syntax() { programname := filepath.Base(os.Args[0]) fmt.Printf(` Grouped Colour Dendrogram Generator, Version `+denVERSION+` (c) P. Kleiweg 1997 - 2020 Usage: %s [-1] [-a|-A|-t] [-b float] [-c] [-C] [-e float] [-f int] [-g [-G int]] [-h] [-H] [-L] [-n int] [-o filename] [-p] [-r float] [-R] [-s float] [-S float] [-T] [-u colour file] [cluster file] [ > file.pdf ] Usage: %s -I|-P|-Q -n int [-a|-A|-t] [-b float] [-e float] [-f int] [-g [-G int]] [-o filename] [-r float] [-R] [-s float] [-S float] [-T] [cluster file] [ > file.pdf ] Usage: %s -E [-o filename] -1 : input ISO-8859-1 (default: UTF-8) -a : curved links (default: rectangular) -A : curved links, new version (default: rectangular) -b : base offset, may be negative (default: minimum of 0.0 and smallest value in cluster file) -c : colour links (with number of groups from 2 through %d) -C : colour labels (with number of groups from 2 through %d) -e : exponent (default: 1.0) -E : example cluster file -f : fontsize (default: %d) -g : output PNG (default: PDF) -G : resolution for PNG output (default: 72) -h : use rainbow colours, light and dark, instead of standard colours (no limit to number of groups) -H : use rainbow colours, light, instead of standard colours (no limit to number of groups) -I : cluster numbers (implies: -L) -L : no labels -n : number of groups (default: 1) -o : output file -O : reorder colours -p : placement of labels in two columns -P : patterns (implies: -L, with number of groups from 2 through %d) -Q : symbols (implies: -L, with number of groups from 2 through %d) -r : line skip for ruler -R : no ruler -s : line skip within groups -S : line skip between groups -t : triangular links (default: rectangular) -T : font serif (default: sans-serif) -u : user-defined colours `, programname, programname, programname, n_colors, n_colors, int(defFONTSIZE), n_colors, n_colors) } func getTextSizes() { if png { surface = cairo.NewSurface(cairo.FORMAT_ARGB32, 1, 1) } else { surface = cairo.NewPDFSurface(outfile, 1, 1, PDFVersion) } surface.SelectFontFace(font, fonti, fontw) surface.SetFontSize(float64(fontsize)) Ag = getTextSize(surface, "Ag") if labels { for i, item := range cl { if item.text != "" { cl[i].textsize = getTextSize(surface, item.text) } for j := 0; j < 2; j++ { if item.node[j] == LBL { s := getTextSize(surface, item.label[j]) cl[i].labelsize[j] = s if s.size > maxlabelwidth { maxlabelwidth = s.size } } } } } // TODO // berekenen hoever tekst onder x-as naar links uitsteekt, en zonodig maxlabelwidth vergroten // item voor rechterkant surface.Finish() } func getTextSize(surface *cairo.Surface, s string) metricsType { te := surface.TextExtents(s) return metricsType{ x: te.Xbearing, y: te.Ybearing, w: te.Width, h: te.Height, size: te.Width - te.Xbearing, } } func hsb2rgb(hue, saturation, brightness float64) [3]float64 { var red, green, blue, domainOffset float64 if brightness < .0001 { red = 0 green = 0 blue = 0 } else if saturation < .0001 { red = brightness green = brightness blue = brightness } else if hue < 1.0/6 { domainOffset = hue red = brightness blue = brightness * (1 - saturation) green = blue + (brightness-blue)*domainOffset*6 } else if hue < 2.0/6 { domainOffset = hue - 1.0/6 green = brightness blue = brightness * (1 - saturation) red = green - (brightness-blue)*domainOffset*6 } else if hue < 3.0/6 { domainOffset = hue - 2.0/6 green = brightness red = brightness * (1 - saturation) blue = red + (brightness-red)*domainOffset*6 } else if hue < 4.0/6 { domainOffset = hue - 3.0/6 blue = brightness red = brightness * (1 - saturation) green = blue - (brightness-red)*domainOffset*6 } else if hue < 5.0/6 { domainOffset = hue - 4.0/6 blue = brightness green = brightness * (1 - saturation) red = green + (brightness-green)*domainOffset*6 } else { domainOffset = hue - 5.0/6 red = brightness green = brightness * (1 - saturation) blue = red - (brightness-green)*domainOffset*6 } return [3]float64{limit(red), limit(green), limit(blue)} } func limit(f float64) float64 { if f < 0 { return 0 } if f > 1 { return 1 } return f } func TODO(s string) { fmt.Fprintln(os.Stderr, "WARNING: option not yet implemented: -"+s) }