package scorecard import ( "image" "image/color" "image/draw" "strconv" "golang.org/x/image/font" "golang.org/x/image/font/basicfont" "golang.org/x/image/math/fixed" ) type DrawContext struct { Img *image.RGBA Scale int } func NewDrawContext(img *image.RGBA, scale int) *DrawContext { return &DrawContext{Img: img, Scale: scale} } func (dc *DrawContext) S(v int) int { return v * dc.Scale } func (dc *DrawContext) FillRect(x, y, w, h int, c color.RGBA) { for dy := 0; dy < h; dy++ { for dx := 0; dx < w; dx++ { px, py := x+dx, y+dy if px >= 0 && px < dc.Img.Bounds().Dx() && py >= 0 && py < dc.Img.Bounds().Dy() { dc.Img.Set(px, py, c) } } } } func (dc *DrawContext) DrawRect(x1, y1, x2, y2 int, c color.RGBA, width int) { for i := 0; i < width; i++ { dc.DrawHLine(x1, y1+i, x2, c) dc.DrawHLine(x1, y2-i, x2, c) dc.DrawVLine(x1+i, y1, y2, c) dc.DrawVLine(x2-i, y1, y2, c) } } func (dc *DrawContext) DrawHLine(x1, y, x2 int, c color.RGBA) { if y < 0 || y >= dc.Img.Bounds().Dy() { return } if x1 > x2 { x1, x2 = x2, x1 } for x := x1; x <= x2; x++ { if x >= 0 && x < dc.Img.Bounds().Dx() { dc.Img.Set(x, y, c) } } } func (dc *DrawContext) DrawVLine(x, y1, y2 int, c color.RGBA) { if x < 0 || x >= dc.Img.Bounds().Dx() { return } if y1 > y2 { y1, y2 = y2, y1 } for y := y1; y <= y2; y++ { if y >= 0 && y < dc.Img.Bounds().Dy() { dc.Img.Set(x, y, c) } } } func (dc *DrawContext) DrawRoundedRect(x, y, w, h, r int, c color.RGBA) { dc.FillRect(x+r, y, w-2*r, h, c) dc.FillRect(x, y+r, w, h-2*r, c) for dy := -r; dy <= 0; dy++ { for dx := -r; dx <= 0; dx++ { if dx*dx+dy*dy >= r*r { continue } dc.Img.Set(x+r+dx, y+r+dy, c) dc.Img.Set(x+w-r-1-dx, y+r+dy, c) dc.Img.Set(x+r+dx, y+h-r-1-dy, c) dc.Img.Set(x+w-r-1-dx, y+h-r-1-dy, c) } } } func (dc *DrawContext) DrawRoundedRectWithOutline(x, y, w, h, r int, fill, outline color.RGBA, outlineWidth int) { dc.DrawRoundedRect(x, y, w, h, r, fill) rr := r - outlineWidth if rr < 0 { rr = 0 } for i := 0; i < outlineWidth; i++ { ri := r - i if ri < 0 { ri = 0 } dc.DrawHLine(x+ri, y+i, x+w-ri-1, outline) dc.DrawHLine(x+ri, y+h-i-1, x+w-ri-1, outline) dc.DrawVLine(x+i, y+ri, y+h-ri-1, outline) dc.DrawVLine(x+w-i-1, y+ri, y+h-ri-1, outline) } } func (dc *DrawContext) DrawDiamond(cx, cy, size int, c color.RGBA) { for dy := -size; dy <= size; dy++ { for dx := -size; dx <= size; dx++ { if abs(dx)+abs(dy) <= size { px, py := cx+dx, cy+dy if px >= 0 && px < dc.Img.Bounds().Dx() && py >= 0 && py < dc.Img.Bounds().Dy() { dc.Img.Set(px, py, c) } } } } } func (dc *DrawContext) DrawRuleWithOrnament(y, x1, x2, cx int, lineColor, ornamentColor color.RGBA) { gap := dc.S(8) dc.DrawHLine(x1, y, cx-gap, lineColor) dc.DrawHLine(cx+gap, y, x2, lineColor) dc.DrawDiamond(cx, y, dc.S(3), ornamentColor) } func (dc *DrawContext) DrawVertRuleWithOrnament(x, y1, y2, cy int, lineColor, ornamentColor color.RGBA) { gap := dc.S(8) dc.DrawVLine(x, y1, cy-gap, lineColor) dc.DrawVLine(x, cy+gap, y2, lineColor) dc.DrawDiamond(x, cy, dc.S(3), ornamentColor) } func (dc *DrawContext) DrawText(text string, x, y int, face font.Face, c color.RGBA) { d := font.Drawer{ Dst: dc.Img, Src: &image.Uniform{c}, Face: face, Dot: fixed.Point26_6{X: fixed.I(x), Y: fixed.I(y)}, } d.DrawString(text) } func (dc *DrawContext) DrawCenteredText(text string, cx, y int, face font.Face, c color.RGBA) { advance := font.MeasureString(face, text) x := cx - (advance.Ceil() / 2) dc.DrawText(text, x, y, face, c) } func (dc *DrawContext) DrawRightAlignedText(text string, rx, y int, face font.Face, c color.RGBA) { advance := font.MeasureString(face, text) x := rx - advance.Ceil() dc.DrawText(text, x, y, face, c) } func (dc *DrawContext) FillBackground(c color.RGBA) { draw.Draw(dc.Img, dc.Img.Bounds(), &image.Uniform{c}, image.Point{}, draw.Src) } func (dc *DrawContext) DrawDoubleFrame(x1, y1, x2, y2 int, outerColor, innerColor color.RGBA, outerWidth, innerWidth int) { dc.DrawRect(x1, y1, x2, y2, outerColor, outerWidth) innerX1 := x1 + outerWidth + 2 innerY1 := y1 + outerWidth + 2 innerX2 := x2 - outerWidth - 2 innerY2 := y2 - outerWidth - 2 dc.DrawRect(innerX1, innerY1, innerX2, innerY2, innerColor, innerWidth) } func (dc *DrawContext) TextWidth(text string, face font.Face) int { return font.MeasureString(face, text).Ceil() } func (dc *DrawContext) TextBounds(text string, face font.Face) (width, height, offsetY int) { advance := font.MeasureString(face, text) width = advance.Ceil() metrics := face.Metrics() height = (metrics.Ascent + metrics.Descent).Ceil() offsetY = -metrics.Ascent.Ceil() return } func (dc *DrawContext) TruncateText(text string, maxWidth int, face font.Face) string { if dc.TextWidth(text, face) <= maxWidth { return text } ellipsis := "…" ellipsisWidth := dc.TextWidth(ellipsis, face) for len(text) > 0 { text = text[:len(text)-1] if dc.TextWidth(text, face)+ellipsisWidth <= maxWidth { return text + ellipsis } } return ellipsis } func GetFont() font.Face { return basicfont.Face7x13 } func FmtScore(score float64) string { if score == float64(int(score)) { return strconv.Itoa(int(score)) } return strconv.FormatFloat(score, 'f', 1, 64) } func abs(x int) int { if x < 0 { return -x } return x } func max(a, b int) int { if a > b { return a } return b } func min(a, b int) int { if a < b { return a } return b }