From 1588b49f0d3d121334470c0a7ed56fb39ebd966a Mon Sep 17 00:00:00 2001 From: Drahoslav Date: Wed, 10 Jan 2018 20:21:07 +0100 Subject: [PATCH] Optimize svg output sligtly --- draw2dsvg/converters.go | 77 +++++++++++++++++++++++++++++++++-------- draw2dsvg/gc.go | 2 +- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/draw2dsvg/converters.go b/draw2dsvg/converters.go index 70c6ace..2b6d462 100644 --- a/draw2dsvg/converters.go +++ b/draw2dsvg/converters.go @@ -12,6 +12,7 @@ import ( "image/color" "image/png" "math" + "strconv" "strings" ) @@ -19,19 +20,19 @@ func toSvgRGBA(c color.Color) string { r, g, b, a := c.RGBA() r, g, b, a = r>>8, g>>8, b>>8, a>>8 if a == 255 { - return fmt.Sprintf("#%02X%02X%02X", r, g, b) + return optiSprintf("#%02X%02X%02X", r, g, b) } - return fmt.Sprintf("rgba(%v,%v,%v,%.3f)", r, g, b, float64(a)/255) + return optiSprintf("rgba(%v,%v,%v,%f)", r, g, b, float64(a)/255) } func toSvgLength(l float64) string { - return fmt.Sprintf("%.4f", l) + return optiSprintf("%f", l) } func toSvgArray(nums []float64) string { arr := make([]string, len(nums)) for i, num := range nums { - arr[i] = fmt.Sprintf("%.4f", num) + arr[i] = optiSprintf("%f", num) } return strings.Join(arr, ",") } @@ -49,16 +50,16 @@ func toSvgPathDesc(p *draw2d.Path) string { for i, cmp := range p.Components { switch cmp { case draw2d.MoveToCmp: - parts[i] = fmt.Sprintf("M %.4f,%.4f", ps[0], ps[1]) + parts[i] = optiSprintf("M %f,%f", ps[0], ps[1]) ps = ps[2:] case draw2d.LineToCmp: - parts[i] = fmt.Sprintf("L %.4f,%.4f", ps[0], ps[1]) + parts[i] = optiSprintf("L %f,%f", ps[0], ps[1]) ps = ps[2:] case draw2d.QuadCurveToCmp: - parts[i] = fmt.Sprintf("Q %.4f,%.4f %.4f,%.4f", ps[0], ps[1], ps[2], ps[3]) + parts[i] = optiSprintf("Q %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3]) ps = ps[4:] case draw2d.CubicCurveToCmp: - parts[i] = fmt.Sprintf("C %.4f,%.4f %.4f,%.4f %.4f,%.4f", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5]) + parts[i] = optiSprintf("C %f,%f %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5]) ps = ps[6:] case draw2d.ArcToCmp: cx, cy := ps[0], ps[1] // center @@ -83,15 +84,15 @@ func toSvgPathDesc(p *draw2d.Path) string { // dirty hack to ensure whole arc is drawn // if start point equals end point if sweep == 1 { - x += 0.001 * sinfi - y += 0.001 * -cosfi + x += 0.0001 * sinfi + y += 0.0001 * -cosfi } else { - x += 0.001 * sinfi - y += 0.001 * cosfi + x += 0.0001 * sinfi + y += 0.0001 * cosfi } // rx ry x-axis-rotation large-arc-flag sweep-flag x y - parts[i] = fmt.Sprintf("A %.4f %.4f %v %v %v %.4f %.4f", + parts[i] = optiSprintf("A %f %f %v %v %v %F %F", rx, ry, 0, large, sweep, x, y, ) ps = ps[6:] @@ -108,9 +109,9 @@ func toSvgTransform(mat draw2d.Matrix) string { } if mat.IsTranslation() { x, y := mat.GetTranslation() - return fmt.Sprintf("translate(%f,%f)", x, y) + return optiSprintf("translate(%f,%f)", x, y) } - return fmt.Sprintf("matrix(%f,%f,%f,%f,%f,%f)", + return optiSprintf("matrix(%f,%f,%f,%f,%f,%f)", mat[0], mat[1], mat[2], mat[3], mat[4], mat[5], ) } @@ -122,3 +123,49 @@ func imageToSvgHref(image image.Image) string { out += base64.RawStdEncoding.EncodeToString(pngBuf.Bytes()) return out } + +// Do the same thing as fmt.Sprintf +// except it uses the optimal precition for floats: (0-3) for f and (0-6) for F +// eg.: +// optiSprintf("%f", 3.0) => fmt.Sprintf("%.0f", 3.0) +// optiSprintf("%f", 3.33) => fmt.Sprintf("%.2f", 3.33) +// optiSprintf("%f", 3.3001) => fmt.Sprintf("%.1f", 3.3001) +// optiSprintf("%f", 3.333333333333333) => fmt.Sprintf("%.3f", 3.333333333333333) +// optiSprintf("%F", 3.333333333333333) => fmt.Sprintf("%.6f", 3.333333333333333) +func optiSprintf(format string, a ...interface{}) string { + chunks := strings.Split(format, "%") + newChunks := make([]string, len(chunks)) + for i, chunk := range chunks { + if i != 0 { + verb := chunk[0] + if verb == 'f' || verb == 'F' { + num := a[i-1].(float64) + p := strconv.Itoa(getPrec(num, verb == 'F')) + chunk = strings.Replace(chunk, string(verb), "."+p+"f", 1) + } + } + newChunks[i] = chunk + } + format = strings.Join(newChunks, "%") + return fmt.Sprintf(format, a...) +} + +func getPrec(num float64, better bool) int { + max := 3 + eps := 0.0005 + if better { + max = 6 + eps = 0.0000005 + } + prec := 0 + for math.Mod(num, 1) > eps { + num *= 10 + eps *= 10 + prec++ + } + + if max < prec { + return max + } + return prec +} diff --git a/draw2dsvg/gc.go b/draw2dsvg/gc.go index 50cc7bf..2af5ad7 100644 --- a/draw2dsvg/gc.go +++ b/draw2dsvg/gc.go @@ -369,7 +369,7 @@ func (gc *GraphicContext) DrawImage(image image.Image) { /////////////////////////////////////// // TODO implement following methods (or remove if not neccesary) -// GetFontName gets the current FontData as a string +// GetFontName gets the current FontData with fontSize as a string func (gc *GraphicContext) GetFontName() string { fontData := gc.Current.FontData return fmt.Sprintf("%s:%d:%d:%d", fontData.Name, fontData.Family, fontData.Style, gc.Current.FontSize)