-
Notifications
You must be signed in to change notification settings - Fork 0
/
number.go
154 lines (135 loc) · 3.6 KB
/
number.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Copyright (c) 2017 Andrey Gayvoronsky <plandem@gmail.com>
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package css
import (
"encoding/xml"
"fmt"
"regexp"
"strconv"
)
// Number is helper type which allow to encode numbers with units. To simplify, integer as value in pixels and float as value in points. Eg. 10 => 10px, 10.5 => 10.5pt
type Number struct {
val interface{}
unit numberUnit
}
type numberUnit byte
const (
unitUnknown numberUnit = iota
UnitPx //used to encode 'px' numbers
UnitCm //used to encode 'cm' numbers
UnitMm //used to encode 'mm' numbers
UnitIn //used to encode 'in' numbers
UnitPt //used to encode 'pt' numbers
UnitPc //used to encode 'pc' numbers
UnitPercentage //used to encode '%' numbers
)
var (
regExpNumber = regexp.MustCompile("^([0-9.]+)(cm|mm|in|pt|pc|px|%)?$")
)
// NewNumber returns a Number type for provided value
func NewNumber(n interface{}, o ...numberUnit) Number {
if s, ok := n.(string); ok {
return fromString(s)
}
var u numberUnit
if len(o) > 0 {
u = o[0]
} else {
u = unitUnknown
}
//for numeric types we just need to resolve type of unit
switch n.(type) {
case float32, float64:
if u == unitUnknown || u == UnitPx {
u = UnitPt
}
case byte, uint, uint16, uint32, uint64, int, int8, int16, int32, int64:
if u == unitUnknown {
u = UnitPx
}
default:
n = 0
u = UnitPx
}
return Number{n, u}
}
// String returns string presentation of Number
func (t Number) String() string {
switch t.unit {
case UnitCm:
return fmt.Sprintf("%vcm", t.val)
case UnitMm:
return fmt.Sprintf("%vmm", t.val)
case UnitIn:
return fmt.Sprintf("%vin", t.val)
case UnitPt:
return fmt.Sprintf("%vpt", t.val)
case UnitPc:
return fmt.Sprintf("%vpc", t.val)
case UnitPx:
return fmt.Sprintf("%vpx", t.val)
case UnitPercentage:
return fmt.Sprintf("%v%%", t.val)
}
return ""
}
// MarshalXMLAttr marshal Number
func (t Number) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
if t.unit == unitUnknown {
return xml.Attr{}, nil
}
return xml.Attr{Name: name, Value: t.String()}, nil
}
// UnmarshalXMLAttr unmarshal Number
func (t *Number) UnmarshalXMLAttr(attr xml.Attr) error {
*t = NewNumber(attr.Value)
return nil
}
// convert string into Number
func fromString(n string) Number {
parsed := regExpNumber.FindStringSubmatch(n)
if parsed != nil {
switch parsed[2] {
case "cm":
if cm, err := strconv.ParseFloat(parsed[1], 64); err == nil {
return Number{cm, UnitCm}
}
case "mm":
if mm, err := strconv.ParseFloat(parsed[1], 64); err == nil {
return Number{mm, UnitMm}
}
case "in":
if in, err := strconv.ParseFloat(parsed[1], 64); err == nil {
return Number{in, UnitIn}
}
case "pt":
if pt, err := strconv.ParseFloat(parsed[1], 64); err == nil {
return Number{pt, UnitPt}
}
case "pc":
if pc, err := strconv.ParseFloat(parsed[1], 64); err == nil {
return Number{pc, UnitPc}
}
case "%":
if num, err := strconv.ParseInt(parsed[1], 10, 64); err == nil {
return Number{int(num), UnitPercentage}
} else {
if num, err := strconv.ParseFloat(parsed[1], 64); err == nil {
return Number{num, UnitPercentage}
}
}
case "px":
fallthrough
default:
if num, err := strconv.ParseInt(parsed[1], 10, 64); err == nil {
return Number{int(num), UnitPx}
} else {
if num, err := strconv.ParseFloat(parsed[1], 64); err == nil {
return Number{num, UnitPt}
}
}
}
}
return Number{0, UnitPx}
}