package world import ( "hash" "hash/fnv" "image/color" "math" ) // hashU64 writes v to the hash in little-endian form. // We keep it manual to avoid extra allocations and dependencies. func hashU64(h hash.Hash64, v uint64) { var b [8]byte b[0] = byte(v) b[1] = byte(v >> 8) b[2] = byte(v >> 16) b[3] = byte(v >> 24) b[4] = byte(v >> 32) b[5] = byte(v >> 40) b[6] = byte(v >> 48) b[7] = byte(v >> 56) _, _ = h.Write(b[:]) } func hashBool(h hash.Hash64, v bool) { if v { hashU64(h, 1) } else { hashU64(h, 0) } } func hashColor(h hash.Hash64, c color.Color) { if c == nil { hashU64(h, 0) return } r, g, b, a := c.RGBA() hashU64(h, uint64(r)) hashU64(h, uint64(g)) hashU64(h, uint64(b)) hashU64(h, uint64(a)) } // fingerprint returns a stable hash of the override content. // // Notes on semantics: // - FillColor / StrokeColor: nil means "unset" (do not override). Transparent override is represented // by a non-nil color with alpha=0. // - Pointer fields (*float64, *[]float64) encode presence via nil/non-nil. // - StrokeDashes: nil pointer means "unset"; non-nil pointer to nil slice means "set to nil". func (o StyleOverride) fingerprint() uint64 { h := fnv.New64a() // returns hash.Hash64 // FillColor / StrokeColor hashBool(h, o.FillColor != nil) hashColor(h, o.FillColor) hashBool(h, o.StrokeColor != nil) hashColor(h, o.StrokeColor) // StrokeWidthPx hashBool(h, o.StrokeWidthPx != nil) if o.StrokeWidthPx != nil { hashU64(h, math.Float64bits(*o.StrokeWidthPx)) } // StrokeDashes hashBool(h, o.StrokeDashes != nil) if o.StrokeDashes != nil { ds := *o.StrokeDashes if ds == nil { // Explicitly set to nil slice hashU64(h, 0xffffffffffffffff) } else { hashU64(h, uint64(len(ds))) for _, v := range ds { hashU64(h, math.Float64bits(v)) } } } // StrokeDashOffset hashBool(h, o.StrokeDashOffset != nil) if o.StrokeDashOffset != nil { hashU64(h, math.Float64bits(*o.StrokeDashOffset)) } // PointRadiusPx hashBool(h, o.PointRadiusPx != nil) if o.PointRadiusPx != nil { hashU64(h, math.Float64bits(*o.PointRadiusPx)) } return h.Sum64() }