-
Notifications
You must be signed in to change notification settings - Fork 0
/
dimension.go
170 lines (140 loc) · 3.84 KB
/
dimension.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright 2016 Manlio Perillo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Support for CSS dimensions, as specified in
//
// CSS Values and Units Module Level 3
//
// Only the length type is supported, with only absolute units (excluding
// pixels and quarter-millimiters). Only real numbers are supported, as
// float64. For zero lengths the unit identifier is optional.
package css
import (
"fmt"
"strconv"
"strings"
"unicode"
)
// Number represents a CSS number. Only real numbers are supported.
type Number float64
// String implements the Stringer interface.
func (n Number) String() string {
// 'g' and 5 are used to print a number with minimal decimal digits,
// avoiding scientific notation.
return strconv.FormatFloat(float64(n), 'g', 5, 64)
}
// Set implements the Value interface.
func (n *Number) Set(s string) error {
if strings.TrimSpace(s) == "" {
return fmt.Errorf("expected number: %q", s)
}
v, err := strconv.ParseFloat(s, 64)
if err != nil {
// TODO(mperillo): Improve error message in Number.Set; it can be
// syntax error or range error (see strconv.NumErr type).
return fmt.Errorf("invalid number: %q: %v", s, err)
}
if v < 0 {
return fmt.Errorf("invalid number: %q", s)
}
*n = Number(v)
return nil
}
// Unit represents the unit of a CSS quantity. Only absolute length units are
// supported, excluding pixels and quarter-millimeters.
type Unit string
// Supported units.
const (
NoUnit Unit = ""
Point Unit = "pt"
Pica Unit = "pc"
Inch Unit = "inch"
Millimeter Unit = "mm"
Centimeter Unit = "cm"
)
// String implements the Stringer interface.
func (u Unit) String() string {
return string(u)
}
var units = map[string]bool{
"": true,
"pt": true,
"pc": true,
"inch": true,
"mm": true,
"cm": true,
}
// Set implements the Value interface.
func (u *Unit) Set(s string) error {
if s == "" {
return nil
}
if strings.TrimSpace(s) == "" {
return fmt.Errorf("expected unit: %q", s)
}
if ok := units[s]; !ok {
return fmt.Errorf("invalid unit: %q", s)
}
*u = Unit(s)
return nil
}
// Dimension represents a CSS number with unit.
type Dimension struct {
Value Number
Unit Unit
}
// String implements the Stringer interface.
func (d Dimension) String() string {
return fmt.Sprintf("%v%s", d.Value, d.Unit)
}
func numberToken(ch rune) bool {
// No scientific notation.
return strings.ContainsRune("-0123456789.", ch)
}
func unitToken(ch rune) bool {
return unicode.IsLetter(ch)
}
func dimensionToken(ch rune) bool {
return numberToken(ch) || unitToken(ch)
}
// Scan implements the Scanner interface.
func (d *Dimension) Scan(state fmt.ScanState, verb rune) error {
if verb != 'v' {
return fmt.Errorf("Dimension.Scan: invalid verb %c", verb)
}
// Scan the entire string <num><unit> and delegate to the Set method.
// Ensure leading white space are not ignored.
//
// Dimension implements the Scanner interface only to make it more easy for
// Font and PageMargin to implement the Value interface.
ws := readspace(state)
tok, err := state.Token(false, dimensionToken)
if err != nil {
return fmt.Errorf("Dimension.Scan: %v", err)
}
return d.Set(ws + string(tok))
}
// Set implements the Value interface.
func (d *Dimension) Set(s string) error {
var v Dimension
// Split number and unit.
i := strings.IndexFunc(s, unitToken)
if i < 0 {
if err := v.Value.Set(s); err != nil {
return fmt.Errorf("invalid dimension: %v", err)
}
if v.Value == 0 {
// 0 is a valid dimension.
return nil
}
return fmt.Errorf("invalid dimension: expected unit: %q", s)
}
if err := v.Value.Set(s[:i]); err != nil {
return fmt.Errorf("invalid dimension: %q: %v", s, err)
}
if err := v.Unit.Set(s[i:]); err != nil {
return fmt.Errorf("invalid dimension: %q: %v", s, err)
}
*d = v
return nil
}